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 (