Fix rule parsing for single reverse proxies

This commit is contained in:
fuomag9
2026-03-04 21:16:11 +01:00
parent 77d3e35c63
commit 7341070c0d
14 changed files with 685 additions and 143 deletions

View File

@@ -77,6 +77,9 @@ interface BlockedEvent {
}
interface BlockedPage { events: BlockedEvent[]; total: number; page: number; pages: number; }
interface TopWafRule { ruleId: number; count: number; message: string | null; }
interface WafStats { total: number; topRules: TopWafRule[]; }
// ── Helpers ───────────────────────────────────────────────────────────────────
function countryFlag(code: string): string {
@@ -158,6 +161,7 @@ export default function AnalyticsClient() {
const [protocols, setProtocols] = useState<ProtoStats[]>([]);
const [userAgents, setUserAgents] = useState<UAStats[]>([]);
const [blocked, setBlocked] = useState<BlockedPage | null>(null);
const [wafStats, setWafStats] = useState<WafStats | null>(null);
const [loading, setLoading] = useState(true);
const [selectedCountry, setSelectedCountry] = useState<string | null>(null);
@@ -201,13 +205,15 @@ export default function AnalyticsClient() {
fetch(`/api/analytics/protocols${params}`).then(r => r.json()),
fetch(`/api/analytics/user-agents${params}`).then(r => r.json()),
fetch(`/api/analytics/blocked${params}&page=1`).then(r => r.json()),
]).then(([s, t, c, p, u, b]) => {
fetch(`/api/analytics/waf-stats${params}`).then(r => r.json()),
]).then(([s, t, c, p, u, b, w]) => {
setSummary(s);
setTimeline(t);
setCountries(c);
setProtocols(p);
setUserAgents(u);
setBlocked(b);
setWafStats(w);
}).catch(() => {}).finally(() => setLoading(false));
}, [buildParams, interval, customFrom, customTo]);
@@ -427,20 +433,20 @@ export default function AnalyticsClient() {
<>
{/* Stats row */}
<Grid container spacing={2}>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<Grid size={{ xs: 12, sm: 6, md: 2.4 }}>
<StatCard label="Total Requests" value={summary.totalRequests.toLocaleString()} />
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<Grid size={{ xs: 12, sm: 6, md: 2.4 }}>
<StatCard label="Unique IPs" value={summary.uniqueIps.toLocaleString()} />
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<Grid size={{ xs: 12, sm: 6, md: 2.4 }}>
<StatCard
label="Blocked Requests"
value={summary.blockedRequests.toLocaleString()}
color={summary.blockedRequests > 0 ? '#ef4444' : undefined}
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<Grid size={{ xs: 12, sm: 6, md: 2.4 }}>
<StatCard
label="Block Rate"
value={`${summary.blockedPercent}%`}
@@ -448,6 +454,14 @@ export default function AnalyticsClient() {
color={summary.blockedPercent > 10 ? '#f59e0b' : undefined}
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 2.4 }}>
<StatCard
label="WAF Events"
value={(wafStats?.total ?? 0).toLocaleString()}
sub={wafStats && wafStats.topRules.length > 0 ? `${wafStats.topRules.length} rules triggered` : 'No WAF events'}
color={(wafStats?.total ?? 0) > 0 ? '#f59e0b' : undefined}
/>
</Grid>
</Grid>
{/* Timeline */}
@@ -644,6 +658,40 @@ export default function AnalyticsClient() {
)}
</CardContent>
</Card>
{/* WAF Top Rules */}
{wafStats && wafStats.total > 0 && (
<Card elevation={0} sx={{ border: '1px solid rgba(148,163,184,0.12)' }}>
<CardContent>
<Typography variant="subtitle1" fontWeight={600} sx={{ mb: 2 }}>
Top WAF Rules Triggered
</Typography>
<Table size="small">
<TableHead>
<TableRow>
{['Rule ID', 'Message', 'Hits'].map(h => (
<TableCell key={h} sx={{ color: 'text.secondary', borderColor: 'rgba(255,255,255,0.06)', whiteSpace: 'nowrap' }}>{h}</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{wafStats.topRules.map(rule => (
<TableRow key={rule.ruleId} sx={{ '& td': { borderColor: 'rgba(255,255,255,0.04)' } }}>
<TableCell>
<Typography variant="body2" fontFamily="monospace" color="warning.light">{rule.ruleId}</Typography>
</TableCell>
<TableCell sx={{ maxWidth: 400, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
<Typography variant="body2" color="text.secondary" title={rule.message ?? undefined}>{rule.message ?? '—'}</Typography>
</TableCell>
<TableCell>
<Typography variant="body2" fontWeight={600}>{rule.count.toLocaleString()}</Typography>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
</Card>
)}
</>
)}
</Stack>