diff --git a/VERSION b/VERSION index 3f7543c..1152f77 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.2.77 +1.2.78 diff --git a/management-ui/src/i18n/locales/de/common.json b/management-ui/src/i18n/locales/de/common.json index 1576cee..54275b7 100644 --- a/management-ui/src/i18n/locales/de/common.json +++ b/management-ui/src/i18n/locales/de/common.json @@ -1823,7 +1823,11 @@ "ruleId": "Regel-ID", "severity": "Schwere", "msg": "Meldung" - } + }, + "addException": "Als Ausnahme", + "exceptionAdded": "Regel als Ausnahme für diese Domain hinzugefügt.", + "exceptionFailed": "Ausnahme konnte nicht gespeichert werden.", + "noDomain": "Domain nicht gefunden — Ausnahme manuell konfigurieren." } } } \ 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 533e44f..ad80897 100644 --- a/management-ui/src/i18n/locales/en/common.json +++ b/management-ui/src/i18n/locales/en/common.json @@ -1823,7 +1823,11 @@ "ruleId": "Rule ID", "severity": "Severity", "msg": "Message" - } + }, + "addException": "Add exception", + "exceptionAdded": "Rule added as exception for this domain.", + "exceptionFailed": "Failed to add exception.", + "noDomain": "Domain not found — configure exception manually." } } } \ 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 90f0c78..8820ae6 100644 --- a/management-ui/src/pages/WAF/index.tsx +++ b/management-ui/src/pages/WAF/index.tsx @@ -272,6 +272,25 @@ function AlertsTab({ domainId }: { domainId?: number }) { }, }) + const addException = useMutation({ + mutationFn: async ({ domainId, ruleId }: { domainId: number; ruleId: number }) => { + // Fetch current config (returns default if none exists yet) + const r = await apiClient.get(`/waf/configs/${domainId}`) + const cfg: WafConfig = isEnvelope(r.data) + ? (r.data.data as { config: WafConfig }).config + : defaultConfig(domainId) + const exclusions = [...(cfg.rule_exclusions ?? [])] + const ruleStr = String(ruleId) + if (!exclusions.includes(ruleStr)) exclusions.push(ruleStr) + return apiClient.put(`/waf/configs/${domainId}`, { ...cfg, rule_exclusions: exclusions }) + }, + onSuccess: () => { + message.success(t('waf.alerts.exceptionAdded')) + void qc.invalidateQueries({ queryKey: ['waf'] }) + }, + onError: () => message.error(t('waf.alerts.exceptionFailed')), + }) + const severityColor = (s: string) => { switch (s?.toLowerCase()) { case 'critical': return 'red' @@ -316,6 +335,32 @@ function AlertsTab({ domainId }: { domainId?: number }) { render: (v: string) => {v || '—'} }, { title: t('waf.alerts.col.msg'), dataIndex: 'rule_msg', key: 'rule_msg', ellipsis: true, render: (v: string) => {v || '—'} }, + { + title: '', + key: 'exception', + width: 130, + render: (_: unknown, row: WafAlert) => { + const canExclude = !!row.domain_id && row.rule_id > 0 && !isViewer + const isPending = addException.isPending && + addException.variables?.domainId === row.domain_id && + addException.variables?.ruleId === row.rule_id + return ( + + + + ) + }, + }, ] return (