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 rows, List 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 "현재 요약 기준으로 즉시 조치할 항목은 없습니다."; } }