diff --git a/VERSION b/VERSION index 7000921..101bb3a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.2.83 +1.2.84 diff --git a/management-ui/src/i18n/locales/de/common.json b/management-ui/src/i18n/locales/de/common.json index 17cfa07..185e823 100644 --- a/management-ui/src/i18n/locales/de/common.json +++ b/management-ui/src/i18n/locales/de/common.json @@ -1833,7 +1833,8 @@ "noDomain": "Domain nicht gefunden — Ausnahme manuell konfigurieren.", "exceptionModalTitle": "Ausnahme für Regel {{rule}} hinzufügen", "exceptionModalHint": "Optional: Begründung warum diese Regel ein False Positive für diese Domain ist.", - "exceptionNotePlaceholder": "z.B. Unsere API verwendet nicht-standardisierte Header die diese Regel auslösen." + "exceptionNotePlaceholder": "z.B. Unsere API verwendet nicht-standardisierte Header die diese Regel auslösen.", + "alreadyExcluded": "Bereits Ausnahme" } } } \ No newline at end of file diff --git a/management-ui/src/i18n/locales/en/common.json b/management-ui/src/i18n/locales/en/common.json index 1d0ca36..d541918 100644 --- a/management-ui/src/i18n/locales/en/common.json +++ b/management-ui/src/i18n/locales/en/common.json @@ -1833,7 +1833,8 @@ "noDomain": "Domain not found — configure exception manually.", "exceptionModalTitle": "Add exception for rule {{rule}}", "exceptionModalHint": "Optional: describe why this rule is a false positive for this domain.", - "exceptionNotePlaceholder": "e.g. Our custom API uses non-standard headers that trigger this rule." + "exceptionNotePlaceholder": "e.g. Our custom API uses non-standard headers that trigger this rule.", + "alreadyExcluded": "Already excluded" } } } \ No newline at end of file diff --git a/management-ui/src/pages/WAF/index.tsx b/management-ui/src/pages/WAF/index.tsx index 4f83338..4e091ca 100644 --- a/management-ui/src/pages/WAF/index.tsx +++ b/management-ui/src/pages/WAF/index.tsx @@ -292,7 +292,7 @@ async function fetchAlerts(domainId?: number): Promise { return (r.data.data as { alerts?: WafAlert[] }).alerts ?? [] } -function AlertsTab({ domainId }: { domainId?: number }) { +function AlertsTab({ domainId, configMap }: { domainId?: number; configMap: Map }) { const { t } = useTranslation() const qc = useQueryClient() const isViewer = useAuthStore((s) => s.user?.role) === 'viewer' @@ -388,6 +388,15 @@ function AlertsTab({ domainId }: { domainId?: number }) { key: 'exception', width: 130, render: (_: unknown, row: WafAlert) => { + const isExcluded = !!row.domain_id && + (configMap.get(row.domain_id)?.rule_exclusions ?? []).includes(String(row.rule_id)) + if (isExcluded) { + return ( + }> + {t('waf.alerts.alreadyExcluded')} + + ) + } const canExclude = !!row.domain_id && row.rule_id > 0 && !isViewer return ( @@ -432,9 +441,13 @@ function AlertsTab({ domainId }: { domainId?: number }) { loading={isLoading} dataSource={alerts ?? []} columns={columns} - rowClassName={(row: WafAlert) => - row.action === 'blocked' ? 'fw-rule-row--zero-hit' : '' - } + rowClassName={(row: WafAlert) => { + const isExcluded = !!row.domain_id && + (configMap.get(row.domain_id)?.rule_exclusions ?? []).includes(String(row.rule_id)) + if (isExcluded) return 'waf-alert-row--excluded' + if (row.action === 'blocked') return 'fw-rule-row--zero-hit' + return '' + }} /> )} @@ -642,7 +655,7 @@ export default function WAFPage() { { key: 'alerts', label: t('waf.tabs.alerts'), - children: , + children: , }, ]} /> diff --git a/management-ui/src/styles/enterprise.css b/management-ui/src/styles/enterprise.css index e980831..66041a3 100644 --- a/management-ui/src/styles/enterprise.css +++ b/management-ui/src/styles/enterprise.css @@ -3057,6 +3057,15 @@ h1, h2, h3, h4, h5, h6 { border-radius: 0 !important; } +/* ── WAF Alert rows ──────────────────────────────────────────────────── */ +.waf-alert-row--excluded > td { + background: #F0FDF4 !important; + color: #15803D !important; +} +.waf-alert-row--excluded:hover > td { + background: #DCFCE7 !important; +} + /* ── Firewall Filter-Bar ─────────────────────────────────────────────── */ .fw-filter-bar { display: flex;