feat(web): live-данные — polling, онлайн-индикаторы, относительное время, actionable-очередь ошибок apply, часовая агрегация статистики

This commit is contained in:
Denozordec
2026-09-25 01:17:33 +07:00
parent 98e4dcdefb
commit 0b426fa7ae
11 changed files with 272 additions and 69 deletions
@@ -21,10 +21,8 @@ import {
} from '@/components/reui/alert'
import { Badge } from '@/components/reui/badge'
import { StatusBadge } from '@/components/status-badge'
import {
AgentPlatformIcon,
platformLabel,
} from '@/components/agents/agent-platform-icon'
import { AgentPlatformIcon, platformLabel } from '@/components/agents/agent-platform-icon'
import { AgentOnlineDot } from '@/components/agents/agent-online-dot'
import {
agentTrafficAccepted,
agentTrafficDropped,
@@ -48,6 +46,7 @@ import {
agentQueryOptions,
} from '@/queries'
import { apiFetch } from '@/lib/api'
import { formatDateTime, formatRelativeTime } from '@/lib/format'
import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard'
import { Button } from '@evofw/ui/components/button'
import { Skeleton } from '@evofw/ui/components/skeleton'
@@ -138,8 +137,19 @@ export function AgentDetailView({ agentId, onDelete }: AgentDetailViewProps) {
<Alert variant="destructive">
<CircleAlertIcon />
<AlertTitle>Ошибка загрузки</AlertTitle>
<AlertDescription>
{agentQ.error?.message ?? 'Не удалось загрузить агента'}
<AlertDescription className="flex flex-col gap-2">
<span>
{agentQ.error?.message ?? 'Не удалось загрузить агента'}
</span>
<Button
type="button"
variant="outline"
size="sm"
className="w-fit"
onClick={() => void agentQ.refetch()}
>
Повторить
</Button>
</AlertDescription>
</Alert>
</div>
@@ -149,8 +159,8 @@ export function AgentDetailView({ agentId, onDelete }: AgentDetailViewProps) {
const headerDesc = [
a.hostname,
platformLabel(a.platform),
`gen ${a.policy_generation}`,
a.default_action === 'drop' ? 'default Drop' : 'default Accept',
`поколение ${a.policy_generation}`,
a.default_action === 'drop' ? 'по умолчанию: блокировать' : 'по умолчанию: пропускать',
]
.filter(Boolean)
.join(' · ')
@@ -165,7 +175,10 @@ export function AgentDetailView({ agentId, onDelete }: AgentDetailViewProps) {
actions={
<>
<AgentPlatformIcon platform={a.platform} />
<StatusBadge status={a.status} />
<span className="flex items-center gap-1.5">
<AgentOnlineDot agent={a} />
<StatusBadge status={a.status} />
</span>
<Badge
variant={
a.default_action === 'drop'
@@ -304,7 +317,10 @@ export function AgentDetailView({ agentId, onDelete }: AgentDetailViewProps) {
icon: <ClockIcon aria-hidden />,
iconClassName: 'text-primary',
label: 'Last apply',
description: a.last_apply_at ?? '—',
description: formatDateTime(a.last_apply_at),
hint: a.last_apply_at
? formatRelativeTime(a.last_apply_at)
: undefined,
},
]}
/>