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>
108 lines
3.3 KiB
TypeScript
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,
|
|
})),
|
|
}))
|
|
}
|