diff --git a/apps/api/src/routes/control.ts b/apps/api/src/routes/control.ts index b67a4e3..a8905c7 100644 --- a/apps/api/src/routes/control.ts +++ b/apps/api/src/routes/control.ts @@ -968,6 +968,28 @@ export const controlRoutes: FastifyPluginAsync<{ config: AppConfig }> = async ( })), })) + 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, + }) + repos.deleteStatsSamplesForAgent(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, diff --git a/apps/web/src/components/agents/agent-card.tsx b/apps/web/src/components/agents/agent-card.tsx index 9d202d8..949d030 100644 --- a/apps/web/src/components/agents/agent-card.tsx +++ b/apps/web/src/components/agents/agent-card.tsx @@ -55,6 +55,10 @@ export function AgentCard({ agent, selected, onSelect }: AgentCardProps) { const hasApply = Boolean(agent.last_apply_at || agent.last_apply_status) const dropped = formatPackets(agent.last_apply_packets_dropped, hasApply) const accepted = formatPackets(agent.last_apply_packets_accepted, hasApply) + const traffic = + dropped === '—' && accepted === '—' + ? '—' + : `↓${dropped} · ↑${accepted}` const seen = formatShort(agent.last_seen_at ?? agent.last_apply_at) const defaultAction = agent.default_action === 'drop' ? 'Drop' : 'Accept' @@ -68,19 +72,28 @@ export function AgentCard({ agent, selected, onSelect }: AgentCardProps) { const stats = [ { - label: 'Dropped', - value: dropped, - valueClass: dropped === '—' ? undefined : 'text-warning', - }, - { - label: 'Accepted', - value: accepted, - valueClass: accepted === '—' ? undefined : 'text-success', + label: 'Traffic', + value: traffic, + valueClass: + traffic === '—' + ? undefined + : '[&]:text-foreground [&>span]:tabular-nums', + valueNode: + traffic === '—' ? ( + '—' + ) : ( + <> + ↓{dropped} + · + ↑{accepted} + > + ), }, { label: 'Seen', value: seen, valueClass: 'text-muted-foreground', + valueNode: seen, }, ] as const @@ -129,7 +142,7 @@ export function AgentCard({ agent, selected, onSelect }: AgentCardProps) { -