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.",
|
||||
"noExclusions": "Noch keine Regelausnahmen.",
|
||||
"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": {
|
||||
"domains": "Domains",
|
||||
|
||||
@@ -1947,7 +1947,11 @@
|
||||
"saveFailed": "Failed to save WAF configuration.",
|
||||
"noExclusions": "No rule exclusions yet.",
|
||||
"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": {
|
||||
"domains": "Domains",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useState } from 'react'
|
||||
import { useMemo, useState } from 'react'
|
||||
import {
|
||||
Alert, Button, Card, Col, Drawer, Form, Input, Modal, Popconfirm, Row,
|
||||
Select, Space, Switch, Tabs, Tag, Tooltip, Typography, message,
|
||||
} from 'antd'
|
||||
import {
|
||||
CheckCircleOutlined, CloseCircleOutlined, DeleteOutlined,
|
||||
CheckCircleOutlined, CloseCircleOutlined, DeleteOutlined, PlusOutlined,
|
||||
SafetyCertificateOutlined, SettingOutlined, WarningOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@@ -14,7 +14,7 @@ import apiClient, { isEnvelope } from '../../api/client'
|
||||
import PageHeader from '../../components/PageHeader'
|
||||
import DataTable from '../../components/DataTable'
|
||||
import { useAuthStore } from '../../stores/auth'
|
||||
import { getRuleDescription } from './crsRules'
|
||||
import { CRS_RULES, getRuleDescription } from './crsRules'
|
||||
|
||||
const { Text } = Typography
|
||||
|
||||
@@ -102,6 +102,14 @@ function ConfigDrawer({ domainName, domainId, onClose }: ConfigDrawerProps) {
|
||||
const qc = useQueryClient()
|
||||
const isViewer = useAuthStore((s) => s.user?.role) === 'viewer'
|
||||
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({
|
||||
queryKey: ['waf', 'config', domainId],
|
||||
@@ -261,9 +269,58 @@ function ConfigDrawer({ domainName, domainId, onClose }: ConfigDrawerProps) {
|
||||
))}
|
||||
</Space>
|
||||
)}
|
||||
<div style={{ marginTop: 8 }}>
|
||||
<Text type="secondary" style={{ fontSize: 11 }}>{t('waf.config.exclusionsAddHint')}</Text>
|
||||
</div>
|
||||
{!isViewer && (
|
||||
<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>
|
||||
<Text type="secondary" style={{ fontSize: 11, display: 'block', marginTop: 6 }}>
|
||||
{t('waf.config.exclusionsAddHint')}
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
|
||||
Reference in New Issue
Block a user