diff --git a/apps/web/src/components/reui-kit/detail-panel.tsx b/apps/web/src/components/reui-kit/detail-panel.tsx
index 5079a9f..fb23a6d 100644
--- a/apps/web/src/components/reui-kit/detail-panel.tsx
+++ b/apps/web/src/components/reui-kit/detail-panel.tsx
@@ -6,13 +6,23 @@ import {
FramePanel,
FrameTitle,
} from '@/components/reui/frame'
+import {
+ KpiStatGrid,
+ type KpiStatCard,
+ type KpiStatVariant,
+} from '@/components/reui-kit/kpi-stat-grid'
import { cn } from '@evofw/ui/lib/utils'
export interface DetailMetricCard {
id: string
icon: ReactNode
label: string
+ /** Primary numeric / text value (shown large). */
description: string
+ /** Outline badge on the right (hybrid KPI). */
+ hint?: string
+ iconClassName?: string
+ variant?: KpiStatVariant
footer?: ReactNode
}
@@ -22,7 +32,11 @@ interface DetailPanelProps {
}
function DetailPanelRoot({ children, className }: DetailPanelProps) {
- return
{children}
+ return (
+
+ {children}
+
+ )
}
interface DetailPanelHeaderProps {
@@ -53,34 +67,29 @@ function DetailPanelHeader({
) : null}
- {children ? {children} : null}
+ {children ? (
+ {children}
+ ) : null}
)
}
/**
- * Compact KPI strip — stats-7 pattern (single Frame, divided cells).
- * Preview: https://reui.io/preview/base/stats-7
+ * Hybrid KPI cards — KpiStatGrid / stats-12.
+ * Preview: https://reui.io/preview/base/stats-12
*/
function DetailPanelMetrics({ cards }: { cards: DetailMetricCard[] }) {
- return (
-
-
- {cards.map((card) => (
-
-
- {card.icon}
- {card.label}
-
-
- {card.description}
-
- {card.footer}
-
- ))}
-
-
- )
+ const kpiCards: KpiStatCard[] = cards.map((card) => ({
+ id: card.id,
+ label: card.label,
+ value: card.description,
+ icon: card.icon,
+ iconClassName: card.iconClassName,
+ hint: card.hint,
+ variant: card.variant,
+ footer: card.footer,
+ }))
+ return
}
function DetailPanelSection({
diff --git a/apps/web/src/routes/_auth/agents/$id.tsx b/apps/web/src/routes/_auth/agents/$id.tsx
index cdea734..af6a57b 100644
--- a/apps/web/src/routes/_auth/agents/$id.tsx
+++ b/apps/web/src/routes/_auth/agents/$id.tsx
@@ -226,24 +226,31 @@ function AgentDetailPage() {
{
id: 'dropped',
icon: ,
+ iconClassName: 'text-warning',
label: 'Dropped',
description: String(a.last_apply_packets_dropped ?? 0),
+ hint: 'сумма counters',
+ variant: 'warning',
},
{
id: 'accepted',
icon: ,
+ iconClassName: 'text-success',
label: 'Accepted',
description: String(a.last_apply_packets_accepted ?? 0),
+ hint: 'сумма counters',
},
{
id: 'kernel',
icon: ,
+ iconClassName: 'text-info',
label: 'Kernel',
description: a.last_apply_kernel_method ?? '—',
},
{
id: 'apply',
icon: ,
+ iconClassName: 'text-primary',
label: 'Last apply',
description: a.last_apply_at ?? '—',
},
diff --git a/apps/web/src/routes/_auth/lists/index.tsx b/apps/web/src/routes/_auth/lists/index.tsx
index e1f336b..f07c892 100644
--- a/apps/web/src/routes/_auth/lists/index.tsx
+++ b/apps/web/src/routes/_auth/lists/index.tsx
@@ -82,7 +82,7 @@ type ListItem = {
* Lists — master-detail (ReUI PRO composition).
* Catalog: https://reui.io/preview/base/list-9
* Entries: https://reui.io/preview/base/data-grid-filtering-2
- * KPI: https://reui.io/preview/base/stats-7
+ * KPI: https://reui.io/preview/base/stats-12 (KpiStatGrid hybrid)
* Sheet: https://reui.io/preview/base/sheet-1 · sheet-8
* Empty: https://reui.io/preview/base/empty-state-12
*/
@@ -461,27 +461,38 @@ function ListsPage() {
{
id: 'count',
icon: ,
+ iconClassName: 'text-info',
label: 'Записей',
description: String(entryItems.length),
+ hint: manual ? 'ручной список' : 'внешний источник',
},
{
id: 'type',
icon: ,
+ iconClassName: 'text-primary',
label: 'Источник',
description:
+ detail.type === 'json_url'
+ ? 'JSON'
+ : detail.type === 'evobgp_community'
+ ? 'EvoBGP'
+ : 'Ручной',
+ hint:
detail.type === 'json_url'
? 'JSON по URL'
: detail.type === 'evobgp_community'
- ? 'EvoBGP community'
- : 'Ручной',
+ ? 'community prefixes'
+ : 'IP / CIDR / домен / список',
},
{
id: 'cidrs',
icon: ,
+ iconClassName: 'text-success',
label: 'CIDR в политике',
description: String(
detail.entry_count ?? detail.entries.length,
),
+ hint: 'materialized',
},
]}
/>
diff --git a/apps/web/src/routes/_auth/rules/$setId.tsx b/apps/web/src/routes/_auth/rules/$setId.tsx
index ba7dfed..c4d00b8 100644
--- a/apps/web/src/routes/_auth/rules/$setId.tsx
+++ b/apps/web/src/routes/_auth/rules/$setId.tsx
@@ -410,20 +410,25 @@ function PolicySetDetailPage() {
{
id: 'rules',
icon: ,
+ iconClassName: 'text-info',
label: 'Правила',
description: String(set.rules_count ?? rules.length),
},
{
id: 'agents',
icon: ,
+ iconClassName: 'text-primary',
label: 'Агенты',
description: String(set.agents_count ?? assignedIds.length),
+ hint: 'назначено',
},
{
id: 'status',
icon: ,
+ iconClassName: set.enabled ? 'text-success' : 'text-muted-foreground',
label: 'Статус',
description: set.enabled ? 'Включён' : 'Выключен',
+ hint: set.enabled ? 'active' : 'disabled',
},
]}
/>