feat(ui/waf): Regel-Ausnahmen direkt im Config-Dialog + durchsuchbarer Select — v1.3.14
Feedback: in der Regel-Ausnahmen-Sektion konnte man Ausnahmen nur SEHEN, nicht hinzufügen (nur Hinweis auf den Alarme-Tab), und die Rule-ID war Freitext. Jetzt: durchsuchbarer Select (aus CRS_RULES, filtert nach ID UND Beschreibung — z.B. "941" oder "XSS") + optionale Notiz + Hinzufügen-Button direkt im Dialog. Speichert sofort (wie der Entfernen-Button). Der Dropdown deckt alle 331 message-behafteten CRS-Regeln ab = genau die, die je in einem Alarm auftauchen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1947,7 +1947,11 @@
|
|||||||
"saveFailed": "WAF-Konfiguration konnte nicht gespeichert werden.",
|
"saveFailed": "WAF-Konfiguration konnte nicht gespeichert werden.",
|
||||||
"noExclusions": "Noch keine Regelausnahmen.",
|
"noExclusions": "Noch keine Regelausnahmen.",
|
||||||
"noNote": "Keine Notiz",
|
"noNote": "Keine Notiz",
|
||||||
"exclusionsAddHint": "Ausnahmen über den Alarme-Tab hinzufügen — \"Als Ausnahme\" auf einem Alarm klicken."
|
"exclusionsAddHint": "Regel oben suchen und hinzufügen — oder im Alarme-Tab per \"Als Ausnahme\" auf einem Alarm.",
|
||||||
|
"exclusionAddPlaceholder": "Regel suchen (ID oder Beschreibung)…",
|
||||||
|
"exclusionAddNote": "Notiz (optional)",
|
||||||
|
"exclusionAddNotFound": "Keine Regel gefunden",
|
||||||
|
"exclusionAddBtn": "Hinzufügen"
|
||||||
},
|
},
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"domains": "Domains",
|
"domains": "Domains",
|
||||||
|
|||||||
@@ -1947,7 +1947,11 @@
|
|||||||
"saveFailed": "Failed to save WAF configuration.",
|
"saveFailed": "Failed to save WAF configuration.",
|
||||||
"noExclusions": "No rule exclusions yet.",
|
"noExclusions": "No rule exclusions yet.",
|
||||||
"noNote": "No note",
|
"noNote": "No note",
|
||||||
"exclusionsAddHint": "Add exceptions via the Alerts tab — click \"Add exception\" on an alert."
|
"exclusionsAddHint": "Search for a rule above and add it — or via the Alerts tab with \"Add exception\" on an alert.",
|
||||||
|
"exclusionAddPlaceholder": "Search rule (ID or description)…",
|
||||||
|
"exclusionAddNote": "Note (optional)",
|
||||||
|
"exclusionAddNotFound": "No rule found",
|
||||||
|
"exclusionAddBtn": "Add"
|
||||||
},
|
},
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"domains": "Domains",
|
"domains": "Domains",
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
Alert, Button, Card, Col, Drawer, Form, Input, Modal, Popconfirm, Row,
|
Alert, Button, Card, Col, Drawer, Form, Input, Modal, Popconfirm, Row,
|
||||||
Select, Space, Switch, Tabs, Tag, Tooltip, Typography, message,
|
Select, Space, Switch, Tabs, Tag, Tooltip, Typography, message,
|
||||||
} from 'antd'
|
} from 'antd'
|
||||||
import {
|
import {
|
||||||
CheckCircleOutlined, CloseCircleOutlined, DeleteOutlined,
|
CheckCircleOutlined, CloseCircleOutlined, DeleteOutlined, PlusOutlined,
|
||||||
SafetyCertificateOutlined, SettingOutlined, WarningOutlined,
|
SafetyCertificateOutlined, SettingOutlined, WarningOutlined,
|
||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@@ -14,7 +14,7 @@ import apiClient, { isEnvelope } from '../../api/client'
|
|||||||
import PageHeader from '../../components/PageHeader'
|
import PageHeader from '../../components/PageHeader'
|
||||||
import DataTable from '../../components/DataTable'
|
import DataTable from '../../components/DataTable'
|
||||||
import { useAuthStore } from '../../stores/auth'
|
import { useAuthStore } from '../../stores/auth'
|
||||||
import { getRuleDescription } from './crsRules'
|
import { CRS_RULES, getRuleDescription } from './crsRules'
|
||||||
|
|
||||||
const { Text } = Typography
|
const { Text } = Typography
|
||||||
|
|
||||||
@@ -102,6 +102,14 @@ function ConfigDrawer({ domainName, domainId, onClose }: ConfigDrawerProps) {
|
|||||||
const qc = useQueryClient()
|
const qc = useQueryClient()
|
||||||
const isViewer = useAuthStore((s) => s.user?.role) === 'viewer'
|
const isViewer = useAuthStore((s) => s.user?.role) === 'viewer'
|
||||||
const [form] = Form.useForm<WafFormValues>()
|
const [form] = Form.useForm<WafFormValues>()
|
||||||
|
const [addRuleId, setAddRuleId] = useState<string>('')
|
||||||
|
const [addNote, setAddNote] = useState<string>('')
|
||||||
|
// Durchsuchbare Optionen aus der CRS-Regel-Liste (ID — Beschreibung).
|
||||||
|
// filterOption unten matcht sowohl ID als auch Beschreibung.
|
||||||
|
const ruleOptions = useMemo(
|
||||||
|
() => Object.entries(CRS_RULES).map(([id, desc]) => ({ value: id, label: `${id} — ${desc}` })),
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
const { data: cfg, isLoading } = useQuery({
|
const { data: cfg, isLoading } = useQuery({
|
||||||
queryKey: ['waf', 'config', domainId],
|
queryKey: ['waf', 'config', domainId],
|
||||||
@@ -261,9 +269,58 @@ function ConfigDrawer({ domainName, domainId, onClose }: ConfigDrawerProps) {
|
|||||||
))}
|
))}
|
||||||
</Space>
|
</Space>
|
||||||
)}
|
)}
|
||||||
<div style={{ marginTop: 8 }}>
|
{!isViewer && (
|
||||||
<Text type="secondary" style={{ fontSize: 11 }}>{t('waf.config.exclusionsAddHint')}</Text>
|
<div style={{ marginTop: 10 }}>
|
||||||
|
<div style={{ display: 'flex', gap: 8 }}>
|
||||||
|
<Select
|
||||||
|
showSearch
|
||||||
|
value={addRuleId || undefined}
|
||||||
|
placeholder={t('waf.config.exclusionAddPlaceholder')}
|
||||||
|
style={{ flex: 1, minWidth: 0 }}
|
||||||
|
options={ruleOptions}
|
||||||
|
optionFilterProp="label"
|
||||||
|
notFoundContent={t('waf.config.exclusionAddNotFound')}
|
||||||
|
onChange={(v) => setAddRuleId(v)}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder={t('waf.config.exclusionAddNote')}
|
||||||
|
value={addNote}
|
||||||
|
onChange={(e) => setAddNote(e.target.value)}
|
||||||
|
style={{ width: 150 }}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
loading={save.isPending}
|
||||||
|
disabled={!addRuleId}
|
||||||
|
onClick={() => {
|
||||||
|
if (!addRuleId) return
|
||||||
|
const existing = cfg?.rule_exclusions ?? []
|
||||||
|
if (existing.includes(addRuleId)) { setAddRuleId(''); setAddNote(''); return }
|
||||||
|
const newNotes = { ...(cfg?.exclusion_notes ?? {}) }
|
||||||
|
if (addNote.trim()) newNotes[addRuleId] = addNote.trim()
|
||||||
|
save.mutate({
|
||||||
|
domain_id: domainId!,
|
||||||
|
enabled: cfg?.enabled ?? false,
|
||||||
|
mode: cfg?.mode ?? 'detection',
|
||||||
|
paranoia_level: cfg?.paranoia_level ?? 1,
|
||||||
|
rule_exclusions: [...existing, addRuleId],
|
||||||
|
crs_plugins: cfg?.crs_plugins ?? [],
|
||||||
|
exclusion_notes: newNotes,
|
||||||
|
trusted_proxies: cfg?.trusted_proxies ?? [],
|
||||||
|
custom_rules: cfg?.custom_rules ?? '',
|
||||||
|
})
|
||||||
|
setAddRuleId(''); setAddNote('')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('waf.config.exclusionAddBtn')}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
<Text type="secondary" style={{ fontSize: 11, display: 'block', marginTop: 6 }}>
|
||||||
|
{t('waf.config.exclusionsAddHint')}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
|
|||||||
Reference in New Issue
Block a user