feat(waf): ausgeschlossene Regeln grün + Bereits-Ausnahme-Tag — v1.2.84

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Debian
2026-06-03 13:52:03 +02:00
parent 134466293d
commit 025854150d
5 changed files with 32 additions and 8 deletions

View File

@@ -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"
}
}
}

View File

@@ -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"
}
}
}

View File

@@ -292,7 +292,7 @@ async function fetchAlerts(domainId?: number): Promise<WafAlert[]> {
return (r.data.data as { alerts?: WafAlert[] }).alerts ?? []
}
function AlertsTab({ domainId }: { domainId?: number }) {
function AlertsTab({ domainId, configMap }: { domainId?: number; configMap: Map<number, WafConfig> }) {
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 (
<Tag color="success" icon={<CheckCircleOutlined />}>
{t('waf.alerts.alreadyExcluded')}
</Tag>
)
}
const canExclude = !!row.domain_id && row.rule_id > 0 && !isViewer
return (
<Tooltip title={!row.domain_id ? t('waf.alerts.noDomain') : undefined}>
@@ -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: <AlertsTab />,
children: <AlertsTab configMap={configMap} />,
},
]}
/>

View File

@@ -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;