diff --git a/apps/web/src/components/reui-kit/dashboard-analytics.tsx b/apps/web/src/components/reui-kit/dashboard-analytics.tsx
index 4fb0d12..1181c58 100644
--- a/apps/web/src/components/reui-kit/dashboard-analytics.tsx
+++ b/apps/web/src/components/reui-kit/dashboard-analytics.tsx
@@ -80,7 +80,7 @@ export function CertStatusChart({ data }: CertStatusChartProps) {
)
return (
-
+
Статусы сертификатов
Распределение по последней проверке
@@ -162,7 +162,7 @@ export function GroupDomainsChart({ data }: GroupDomainsChartProps) {
const chartRef = useChartSizeLog('group-domains', data.length)
return (
-
+
Домены по группам
Топ-6 групп по количеству зон
diff --git a/apps/web/src/components/reui-kit/kpi-stat-grid.tsx b/apps/web/src/components/reui-kit/kpi-stat-grid.tsx
index 48be41b..b91aa27 100644
--- a/apps/web/src/components/reui-kit/kpi-stat-grid.tsx
+++ b/apps/web/src/components/reui-kit/kpi-stat-grid.tsx
@@ -42,36 +42,40 @@ function KpiStatCardBody({ card }: { card: KpiStatCard }) {
}
function KpiStatCardItem({ card }: { card: KpiStatCard }) {
+ const interactive = Boolean(card.to || card.onSelect)
const panelClass = cn(
- 'relative isolate flex h-full flex-col gap-3',
+ 'relative isolate flex flex-col gap-3',
card.selected && 'ring-primary/30 bg-muted/30 ring-1',
+ interactive &&
+ 'hover:bg-muted/40 focus-within:ring-ring transition-colors focus-within:ring-2',
)
+ // card-35: FramePanel — прямой child грида; ссылка/кнопка внутри панели
if (card.to) {
return (
-
-
+
+
-
-
+
+
)
}
if (card.onSelect) {
return (
-
+
+
)
}
diff --git a/apps/web/src/components/reui-kit/ops-dashboard.tsx b/apps/web/src/components/reui-kit/ops-dashboard.tsx
index 5e798ad..5789baa 100644
--- a/apps/web/src/components/reui-kit/ops-dashboard.tsx
+++ b/apps/web/src/components/reui-kit/ops-dashboard.tsx
@@ -62,6 +62,57 @@ export function OpsDashboard({
},
'A',
)
+ // #region agent log
+ const kpi = el.querySelector('[aria-label="Ключевые метрики"]')
+ const analytics = el.querySelector('[aria-label="Аналитика"]')
+ const kpiFrame = kpi?.querySelector('[data-slot=frame]')
+ const kpiGrid = kpiFrame?.firstElementChild
+ const charts = analytics
+ ? [...analytics.querySelectorAll('[data-slot=frame]')].map((f) => {
+ const panel = f.querySelector('[data-slot=frame-panel]')
+ const chart = f.querySelector('[data-slot=chart]')
+ const inner = chart?.querySelector(
+ '.recharts-responsive-container > div',
+ ) as HTMLElement | null
+ return {
+ frameH: (f as HTMLElement).clientHeight,
+ panelH: (panel as HTMLElement | null)?.clientHeight ?? 0,
+ chartW: (chart as HTMLElement | null)?.clientWidth ?? 0,
+ chartH: (chart as HTMLElement | null)?.clientHeight ?? 0,
+ innerW: inner?.clientWidth ?? 0,
+ innerH: inner?.clientHeight ?? 0,
+ hasSvg: Boolean(f.querySelector('.recharts-surface')),
+ }
+ })
+ : []
+ fetch('http://127.0.0.1:7580/ingest/5c1b60ca-3f59-41ce-8435-d25bcc12c3cf', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-Debug-Session-Id': '0f5088',
+ },
+ body: JSON.stringify({
+ sessionId: '0f5088',
+ runId: 'post-fix',
+ location: 'ops-dashboard.tsx:layout',
+ message: 'dashboard layout metrics',
+ hypothesisId: 'A-C',
+ data: {
+ kpiPanelDirect:
+ kpiGrid != null &&
+ [...kpiGrid.children].every(
+ (c) => c.getAttribute('data-slot') === 'frame-panel',
+ ),
+ kpiFirstChildTag: kpiGrid?.firstElementChild?.tagName ?? null,
+ analyticsAutoRows: analytics
+ ? getComputedStyle(analytics).gridAutoRows
+ : null,
+ charts,
+ },
+ timestamp: Date.now(),
+ }),
+ }).catch(() => {})
+ // #endregion
}, [isLoading])
if (isLoading) {
@@ -88,7 +139,7 @@ export function OpsDashboard({
diff --git a/packages/ui/src/components/chart.tsx b/packages/ui/src/components/chart.tsx
index 3c50b1e..850bafa 100644
--- a/packages/ui/src/components/chart.tsx
+++ b/packages/ui/src/components/chart.tsx
@@ -65,13 +65,17 @@ function ChartContainer({
data-slot="chart"
data-chart={chartId}
className={cn(
- "flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",
+ // relative + block: flex на контейнере даёт Recharts ResponsiveContainer 0×0
+ "relative block w-full text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",
className
)}
{...props}
>
{children}