Files
EvoFirewall/apps/api/src/routes/stats.ts
T
DenozordecandCursor c5069fbdaf
Build and Push EvoFirewall Docker Image / build-and-push (push) Successful in 1m59s
Build and Push EvoFirewall Docker Image / create-release (push) Skipped
feat(api, web): add Linux nft destination port hits for blocked IPs
Track tcp/udp dports via deny_port_hits, expose aggregate and per-IP ports in UI; install-link re-run refreshes nft rules.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 00:33:03 +07:00

108 lines
3.3 KiB
TypeScript

import type { FastifyPluginAsync } from 'fastify'
import { repos } from '@evofw/db'
import { AppError } from '../plugins/error-handler.js'
import type { AppConfig } from '../config.js'
import { auditMutation } from '../services/audit.js'
import { mapAgent } from '../services/row-mappers.js'
export const statsRoutes: FastifyPluginAsync<{ config: AppConfig }> = async (
app,
opts,
) => {
const { config } = opts
app.get<{ Params: { id: string } }>('/agents/:id/stats', async (req) => ({
items: repos.listStatsSamples(app.db, req.params.id).map((s) => ({
id: s.id,
agent_id: s.agentId,
packets_dropped: s.packetsDropped,
packets_accepted: s.packetsAccepted,
prefix_count: s.prefixCount,
kernel_method: s.kernelMethod,
recorded_at: s.recordedAt,
})),
}))
app.get<{ Params: { id: string } }>(
'/agents/:id/blocked-ips',
async (req) => {
const agent = repos.getAgent(app.db, req.params.id)
if (!agent) throw new AppError('NOT_FOUND', 'Agent not found', 404)
const items = repos.listIpBlockStats(app.db, agent.id)
const portsByIp = repos.mapTopPortsByIp(
app.db,
agent.id,
items.map((s) => s.ip),
5,
)
return {
items: items.map((s) => ({
ip: s.ip,
packets: s.packets,
first_seen_at: s.firstSeenAt,
last_seen_at: s.lastSeenAt,
ports: (portsByIp.get(s.ip) ?? []).map((p) => ({
port: p.port,
protocol: p.protocol,
packets: p.packets,
})),
})),
}
},
)
app.get<{ Params: { id: string } }>(
'/agents/:id/blocked-ports',
async (req) => {
const agent = repos.getAgent(app.db, req.params.id)
if (!agent) throw new AppError('NOT_FOUND', 'Agent not found', 404)
return {
items: repos.listPortBlockStatsAggregate(app.db, agent.id, 50).map((s) => ({
port: s.port,
protocol: s.protocol,
packets: s.packets,
last_seen_at: s.lastSeenAt,
})),
}
},
)
app.post<{ Params: { id: string } }>(
'/agents/:id/stats/reset',
async (req) => {
const agent = repos.getAgent(app.db, req.params.id)
if (!agent) throw new AppError('NOT_FOUND', 'Agent not found', 404)
const updated = repos.updateAgent(app.db, agent.id, {
lastApplyPacketsDropped: 0,
lastApplyPacketsAccepted: 0,
totalPacketsDropped: 0,
totalPacketsAccepted: 0,
})
repos.deleteStatsSamplesForAgent(app.db, agent.id)
repos.deleteIpBlockStatsForAgent(app.db, agent.id)
repos.deletePortBlockStatsForAgent(app.db, agent.id)
auditMutation(app, config, req, {
action: 'agent.stats_reset',
severity: 'info',
targetType: 'app_resource',
targetId: agent.id,
summary: `Сброшена статистика counters агента ${agent.name}`,
details: { agent_id: agent.id },
})
return mapAgent(updated!)
},
)
app.get('/stats/recent', async () => ({
items: repos.listRecentStats(app.db).map((s) => ({
id: s.id,
agent_id: s.agentId,
packets_dropped: s.packetsDropped,
packets_accepted: s.packetsAccepted,
prefix_count: s.prefixCount,
kernel_method: s.kernelMethod,
recorded_at: s.recordedAt,
})),
}))
}