Improve data access control UX guidance
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
package com.cloudhandson.vpdbackoffice.domain.operation;
|
||||
|
||||
import com.cloudhandson.vpdbackoffice.domain.masking.MaskingPolicyStatus;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Page-level health summary computed from the same DB-backed rows shown in
|
||||
* operation-status.html. It intentionally does not hide the row-level evidence:
|
||||
* the summary is only a triage layer for operators.
|
||||
*/
|
||||
public record OperationHealthSummary(
|
||||
int protectedObjectCount,
|
||||
int rowPolicyOkCount,
|
||||
int rowPolicyWarnCount,
|
||||
int rowPolicyErrorCount,
|
||||
int missingHandlerCount,
|
||||
int missingPolicyCount,
|
||||
int invalidFunctionCount,
|
||||
int unverifiedCount,
|
||||
int maskingPolicyCount,
|
||||
int maskingAppliedCount,
|
||||
int maskingInactiveExpectedCount,
|
||||
int maskingMismatchCount
|
||||
) {
|
||||
|
||||
public static OperationHealthSummary from(
|
||||
List<OperationStatusRow> rows,
|
||||
List<MaskingPolicyStatus> maskingStatuses
|
||||
) {
|
||||
int rowPolicyOk = 0;
|
||||
int rowPolicyWarn = 0;
|
||||
int rowPolicyError = 0;
|
||||
int missingHandler = 0;
|
||||
int missingPolicy = 0;
|
||||
int invalidFunction = 0;
|
||||
int unverified = 0;
|
||||
|
||||
for (OperationStatusRow row : rows) {
|
||||
switch (row.healthLevel()) {
|
||||
case "OK" -> rowPolicyOk++;
|
||||
case "ERROR" -> rowPolicyError++;
|
||||
default -> rowPolicyWarn++;
|
||||
}
|
||||
if (row.handlerId() == null) {
|
||||
missingHandler++;
|
||||
}
|
||||
if (row.policyNames() == null || row.policyNames().isBlank()) {
|
||||
missingPolicy++;
|
||||
}
|
||||
if (row.functionStatus() != null && !"VALID".equalsIgnoreCase(row.functionStatus())) {
|
||||
invalidFunction++;
|
||||
}
|
||||
if (row.lastProbeStatus() == null || row.lastProbeStatus().isBlank()) {
|
||||
unverified++;
|
||||
}
|
||||
}
|
||||
|
||||
int maskingApplied = 0;
|
||||
int maskingInactive = 0;
|
||||
int maskingMismatch = 0;
|
||||
for (MaskingPolicyStatus status : maskingStatuses) {
|
||||
if (status.applied()) {
|
||||
maskingApplied++;
|
||||
} else if (status.inactiveAsExpected()) {
|
||||
maskingInactive++;
|
||||
} else {
|
||||
maskingMismatch++;
|
||||
}
|
||||
}
|
||||
|
||||
return new OperationHealthSummary(
|
||||
rows.size(),
|
||||
rowPolicyOk,
|
||||
rowPolicyWarn,
|
||||
rowPolicyError,
|
||||
missingHandler,
|
||||
missingPolicy,
|
||||
invalidFunction,
|
||||
unverified,
|
||||
maskingStatuses.size(),
|
||||
maskingApplied,
|
||||
maskingInactive,
|
||||
maskingMismatch
|
||||
);
|
||||
}
|
||||
|
||||
public String overallLevel() {
|
||||
if (rowPolicyErrorCount > 0 || invalidFunctionCount > 0 || maskingMismatchCount > 0) {
|
||||
return "ERROR";
|
||||
}
|
||||
if (rowPolicyWarnCount > 0 || missingHandlerCount > 0 || missingPolicyCount > 0
|
||||
|| unverifiedCount > 0) {
|
||||
return "WARN";
|
||||
}
|
||||
return "OK";
|
||||
}
|
||||
|
||||
public String overallLabel() {
|
||||
return switch (overallLevel()) {
|
||||
case "ERROR" -> "조치 필요";
|
||||
case "WARN" -> "확인 필요";
|
||||
default -> "정상";
|
||||
};
|
||||
}
|
||||
|
||||
public String overallBadgeClass() {
|
||||
return switch (overallLevel()) {
|
||||
case "ERROR" -> "text-bg-danger";
|
||||
case "WARN" -> "text-bg-warning";
|
||||
default -> "text-bg-success";
|
||||
};
|
||||
}
|
||||
|
||||
public String rowPolicySummary() {
|
||||
return "정상 " + rowPolicyOkCount + " · 확인 " + rowPolicyWarnCount + " · 오류 " + rowPolicyErrorCount;
|
||||
}
|
||||
|
||||
public String maskingSummary() {
|
||||
return "적용 " + maskingAppliedCount + " · 미적용 정상 " + maskingInactiveExpectedCount
|
||||
+ " · 불일치 " + maskingMismatchCount;
|
||||
}
|
||||
|
||||
public String primaryAction() {
|
||||
if (invalidFunctionCount > 0) {
|
||||
return "컴파일 오류가 있는 VPD Filter를 먼저 확인하세요.";
|
||||
}
|
||||
if (maskingMismatchCount > 0) {
|
||||
return "컬럼 마스킹 화면에서 DB ASO 정책 동기화를 확인하세요.";
|
||||
}
|
||||
if (missingPolicyCount > 0) {
|
||||
return "보호 상태에서 행 접근 정책(VPD)을 적용하세요.";
|
||||
}
|
||||
if (missingHandlerCount > 0) {
|
||||
return "조회 대상 또는 조회 연동에서 ORDS handler를 확인하세요.";
|
||||
}
|
||||
if (unverifiedCount > 0) {
|
||||
return "접근 검증을 실행해 실제 결과를 확인하세요.";
|
||||
}
|
||||
return "현재 요약 기준으로 즉시 조치할 항목은 없습니다.";
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.cloudhandson.vpdbackoffice.web;
|
||||
|
||||
import com.cloudhandson.vpdbackoffice.domain.operation.OperationHealthSummary;
|
||||
import com.cloudhandson.vpdbackoffice.service.MaskingRuleService;
|
||||
import com.cloudhandson.vpdbackoffice.service.OperationStatusService;
|
||||
import org.springframework.stereotype.Controller;
|
||||
@@ -19,8 +20,11 @@ public class OperationStatusController {
|
||||
|
||||
@GetMapping("/operation-status")
|
||||
public String status(Model model) {
|
||||
model.addAttribute("rows", service.findRows());
|
||||
model.addAttribute("maskingPolicyStatuses", maskingRuleService.findPolicyStatuses());
|
||||
var rows = service.findRows();
|
||||
var maskingPolicyStatuses = maskingRuleService.findPolicyStatuses();
|
||||
model.addAttribute("rows", rows);
|
||||
model.addAttribute("maskingPolicyStatuses", maskingPolicyStatuses);
|
||||
model.addAttribute("summary", OperationHealthSummary.from(rows, maskingPolicyStatuses));
|
||||
return "operation-status";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user