feat(web): enhance debugging capabilities across components
Build, Test, and Push CFDM Docker Image / test (push) Failing after 46s
Build, Test, and Push CFDM Docker Image / build-and-push (push) Has been skipped
Build, Test, and Push CFDM Docker Image / update-wiki (push) Has been skipped
Build, Test, and Push CFDM Docker Image / create-release (push) Has been skipped
Build, Test, and Push CFDM Docker Image / test (push) Failing after 46s
Build, Test, and Push CFDM Docker Image / build-and-push (push) Has been skipped
Build, Test, and Push CFDM Docker Image / update-wiki (push) Has been skipped
Build, Test, and Push CFDM Docker Image / create-release (push) Has been skipped
- Integrated debug logging in main application file to track boot process. - Added layout width logging in PageShell and various ReUI components to monitor rendering dimensions. - Implemented chart size logging in dashboard analytics components for better performance insights. - Enhanced OpsDashboard and ResourcePage with width tracking to improve layout responsiveness. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
import type { ReactNode } from 'react'
|
import { useEffect, useRef, type ReactNode } from 'react'
|
||||||
|
import { useRouterState } from '@tanstack/react-router'
|
||||||
import { cn } from '@cfdm/ui/lib/utils'
|
import { cn } from '@cfdm/ui/lib/utils'
|
||||||
|
import { DEBUG_BUILD_STAMP, debugAgentLog } from '@/lib/debug-agent-log'
|
||||||
|
|
||||||
interface PageShellProps {
|
interface PageShellProps {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
@@ -7,5 +9,38 @@ interface PageShellProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function PageShell({ children, className }: PageShellProps) {
|
export function PageShell({ children, className }: PageShellProps) {
|
||||||
return <div className={cn('flex flex-col gap-4 md:gap-6', className)}>{children}</div>
|
const ref = useRef<HTMLDivElement>(null)
|
||||||
|
const pathname = useRouterState({ select: (s) => s.location.pathname })
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const el = ref.current
|
||||||
|
if (!el) return
|
||||||
|
const main = el.closest('main')
|
||||||
|
const mainWidth = main?.clientWidth ?? 0
|
||||||
|
const shellWidth = el.clientWidth
|
||||||
|
const child = el.firstElementChild as HTMLElement | null
|
||||||
|
const childWidth = child?.clientWidth ?? 0
|
||||||
|
const childMaxWidth = child ? getComputedStyle(child).maxWidth : 'none'
|
||||||
|
debugAgentLog(
|
||||||
|
'page-shell.tsx:mount',
|
||||||
|
'page layout widths',
|
||||||
|
{
|
||||||
|
buildStamp: DEBUG_BUILD_STAMP,
|
||||||
|
pathname,
|
||||||
|
mainWidth,
|
||||||
|
shellWidth,
|
||||||
|
childWidth,
|
||||||
|
childMaxWidth,
|
||||||
|
shellClass: el.className,
|
||||||
|
childClass: child?.className ?? null,
|
||||||
|
},
|
||||||
|
'A',
|
||||||
|
)
|
||||||
|
}, [pathname])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={ref} className={cn('flex flex-col gap-4 md:gap-6', className)}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useMemo } from 'react'
|
import { useMemo, useEffect, useRef } from 'react'
|
||||||
import { Bar, BarChart, CartesianGrid, Cell, Pie, PieChart, XAxis } from 'recharts'
|
import { Bar, BarChart, CartesianGrid, Cell, Pie, PieChart, XAxis } from 'recharts'
|
||||||
|
|
||||||
import { StatusBadge } from '@/components/status-badge'
|
import { StatusBadge } from '@/components/status-badge'
|
||||||
@@ -17,6 +17,34 @@ import {
|
|||||||
} from '@cfdm/ui/components/chart'
|
} from '@cfdm/ui/components/chart'
|
||||||
import { Separator } from '@cfdm/ui/components/separator'
|
import { Separator } from '@cfdm/ui/components/separator'
|
||||||
import { cn } from '@cfdm/ui/lib/utils'
|
import { cn } from '@cfdm/ui/lib/utils'
|
||||||
|
import { debugAgentLog } from '@/lib/debug-agent-log'
|
||||||
|
|
||||||
|
function useChartSizeLog(chartId: string, dataLen: number) {
|
||||||
|
const ref = useRef<HTMLDivElement>(null)
|
||||||
|
useEffect(() => {
|
||||||
|
const el = ref.current
|
||||||
|
if (!el) return
|
||||||
|
const svg = el.querySelector('svg.recharts-surface')
|
||||||
|
const chartSlot = el.querySelector('[data-slot=chart]') as HTMLElement | null
|
||||||
|
debugAgentLog(
|
||||||
|
'dashboard-analytics.tsx:chart-mount',
|
||||||
|
'chart container dimensions',
|
||||||
|
{
|
||||||
|
chartId,
|
||||||
|
dataLen,
|
||||||
|
containerW: el.clientWidth,
|
||||||
|
containerH: el.clientHeight,
|
||||||
|
chartSlotW: chartSlot?.clientWidth ?? 0,
|
||||||
|
chartSlotH: chartSlot?.clientHeight ?? 0,
|
||||||
|
svgW: svg?.getAttribute('width') ?? null,
|
||||||
|
svgH: svg?.getAttribute('height') ?? null,
|
||||||
|
hasSvg: Boolean(svg),
|
||||||
|
},
|
||||||
|
'D',
|
||||||
|
)
|
||||||
|
}, [chartId, dataLen])
|
||||||
|
return ref
|
||||||
|
}
|
||||||
|
|
||||||
const statusChartConfig = {
|
const statusChartConfig = {
|
||||||
count: { label: 'Сертификаты' },
|
count: { label: 'Сертификаты' },
|
||||||
@@ -45,6 +73,7 @@ interface CertStatusChartProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function CertStatusChart({ data }: CertStatusChartProps) {
|
export function CertStatusChart({ data }: CertStatusChartProps) {
|
||||||
|
const chartRef = useChartSizeLog('cert-status', data.length)
|
||||||
const total = useMemo(
|
const total = useMemo(
|
||||||
() => data.reduce((sum, entry) => sum + entry.count, 0),
|
() => data.reduce((sum, entry) => sum + entry.count, 0),
|
||||||
[data],
|
[data],
|
||||||
@@ -60,7 +89,10 @@ export function CertStatusChart({ data }: CertStatusChartProps) {
|
|||||||
{data.length === 0 ? (
|
{data.length === 0 ? (
|
||||||
<p className="text-muted-foreground text-sm">Нет данных о сертификатах</p>
|
<p className="text-muted-foreground text-sm">Нет данных о сертификатах</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid w-full gap-6 @md:grid-cols-[9rem_minmax(0,1fr)] @md:items-center">
|
<div
|
||||||
|
ref={chartRef}
|
||||||
|
className="grid w-full gap-6 @md:grid-cols-[9rem_minmax(0,1fr)] @md:items-center"
|
||||||
|
>
|
||||||
<div className="relative mx-auto size-36 shrink-0">
|
<div className="relative mx-auto size-36 shrink-0">
|
||||||
<ChartContainer
|
<ChartContainer
|
||||||
config={statusChartConfig}
|
config={statusChartConfig}
|
||||||
@@ -127,6 +159,8 @@ interface GroupDomainsChartProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function GroupDomainsChart({ data }: GroupDomainsChartProps) {
|
export function GroupDomainsChart({ data }: GroupDomainsChartProps) {
|
||||||
|
const chartRef = useChartSizeLog('group-domains', data.length)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Frame dense spacing="sm" className="h-full w-full">
|
<Frame dense spacing="sm" className="h-full w-full">
|
||||||
<FrameHeader>
|
<FrameHeader>
|
||||||
@@ -137,7 +171,7 @@ export function GroupDomainsChart({ data }: GroupDomainsChartProps) {
|
|||||||
{data.length === 0 ? (
|
{data.length === 0 ? (
|
||||||
<p className="text-muted-foreground text-sm">Нет групп с доменами</p>
|
<p className="text-muted-foreground text-sm">Нет групп с доменами</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex w-full flex-col gap-4">
|
<div ref={chartRef} className="flex w-full flex-col gap-4">
|
||||||
<ChartContainer
|
<ChartContainer
|
||||||
config={groupChartConfig}
|
config={groupChartConfig}
|
||||||
className="aspect-auto h-52 w-full min-h-52"
|
className="aspect-auto h-52 w-full min-h-52"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { ReactNode } from 'react'
|
import { useEffect, useRef, type ReactNode } from 'react'
|
||||||
import { Link } from '@tanstack/react-router'
|
import { Link } from '@tanstack/react-router'
|
||||||
import {
|
import {
|
||||||
Frame,
|
Frame,
|
||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
FrameTitle,
|
FrameTitle,
|
||||||
} from '@/components/reui/frame'
|
} from '@/components/reui/frame'
|
||||||
import { cn } from '@cfdm/ui/lib/utils'
|
import { cn } from '@cfdm/ui/lib/utils'
|
||||||
|
import { debugAgentLog } from '@/lib/debug-agent-log'
|
||||||
import { Item, ItemMedia } from '@cfdm/ui/components/item'
|
import { Item, ItemMedia } from '@cfdm/ui/components/item'
|
||||||
|
|
||||||
export interface OpsKpiCard {
|
export interface OpsKpiCard {
|
||||||
@@ -77,8 +78,28 @@ export function OpsDashboard({
|
|||||||
charts,
|
charts,
|
||||||
queue,
|
queue,
|
||||||
}: OpsDashboardProps) {
|
}: OpsDashboardProps) {
|
||||||
|
const rootRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const el = rootRef.current
|
||||||
|
if (!el) return
|
||||||
|
debugAgentLog(
|
||||||
|
'ops-dashboard.tsx:mount',
|
||||||
|
'ops dashboard width',
|
||||||
|
{
|
||||||
|
clientWidth: el.clientWidth,
|
||||||
|
maxWidth: getComputedStyle(el).maxWidth,
|
||||||
|
className: el.className,
|
||||||
|
},
|
||||||
|
'A',
|
||||||
|
)
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="text-foreground @container mx-auto flex w-full max-w-7xl flex-col gap-4 md:gap-6">
|
<div
|
||||||
|
ref={rootRef}
|
||||||
|
className="text-foreground @container mx-auto flex w-full max-w-7xl flex-col gap-4 md:gap-6"
|
||||||
|
>
|
||||||
<header className="px-1">
|
<header className="px-1">
|
||||||
<h1 className="text-xl font-semibold tracking-tight">{title}</h1>
|
<h1 className="text-xl font-semibold tracking-tight">{title}</h1>
|
||||||
<p className="text-muted-foreground max-w-2xl text-sm leading-relaxed">
|
<p className="text-muted-foreground max-w-2xl text-sm leading-relaxed">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useMemo, useState, type ReactNode } from 'react'
|
import { useCallback, useMemo, useState, useEffect, useRef, type ReactNode } from 'react'
|
||||||
import {
|
import {
|
||||||
getCoreRowModel,
|
getCoreRowModel,
|
||||||
getPaginationRowModel,
|
getPaginationRowModel,
|
||||||
@@ -36,6 +36,7 @@ import { Tabs, TabsList, TabsTrigger } from '@cfdm/ui/components/tabs'
|
|||||||
import { Alert, AlertDescription, AlertTitle } from '@cfdm/ui/components/alert'
|
import { Alert, AlertDescription, AlertTitle } from '@cfdm/ui/components/alert'
|
||||||
import { EmptyState } from '@/components/empty-state'
|
import { EmptyState } from '@/components/empty-state'
|
||||||
import { applyFiltersToData } from './filter-utils'
|
import { applyFiltersToData } from './filter-utils'
|
||||||
|
import { debugAgentLog } from '@/lib/debug-agent-log'
|
||||||
|
|
||||||
export interface ResourcePageTab {
|
export interface ResourcePageTab {
|
||||||
id: string
|
id: string
|
||||||
@@ -115,6 +116,7 @@ export function ResourcePage<T extends object>({
|
|||||||
toolbarExtra,
|
toolbarExtra,
|
||||||
hideHeader = false,
|
hideHeader = false,
|
||||||
}: ResourcePageProps<T>) {
|
}: ResourcePageProps<T>) {
|
||||||
|
const frameRef = useRef<HTMLDivElement>(null)
|
||||||
const [internalTab, setInternalTab] = useState(tabs?.[0]?.id ?? 'all')
|
const [internalTab, setInternalTab] = useState(tabs?.[0]?.id ?? 'all')
|
||||||
const activeTab = controlledTab ?? internalTab
|
const activeTab = controlledTab ?? internalTab
|
||||||
|
|
||||||
@@ -225,7 +227,23 @@ export function ResourcePage<T extends object>({
|
|||||||
|
|
||||||
const emptyMessage = 'Нет записей по выбранным фильтрам.'
|
const emptyMessage = 'Нет записей по выбранным фильтрам.'
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const el = frameRef.current
|
||||||
|
if (!el) return
|
||||||
|
debugAgentLog(
|
||||||
|
'resource-page.tsx:mount',
|
||||||
|
'resource page width',
|
||||||
|
{
|
||||||
|
title,
|
||||||
|
clientWidth: el.clientWidth,
|
||||||
|
maxWidth: getComputedStyle(el).maxWidth,
|
||||||
|
},
|
||||||
|
'A',
|
||||||
|
)
|
||||||
|
}, [title])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div ref={frameRef} className="w-full">
|
||||||
<DataGrid
|
<DataGrid
|
||||||
table={table}
|
table={table}
|
||||||
recordCount={filteredData.length}
|
recordCount={filteredData.length}
|
||||||
@@ -339,5 +357,6 @@ export function ResourcePage<T extends object>({
|
|||||||
</FramePanel>
|
</FramePanel>
|
||||||
</Frame>
|
</Frame>
|
||||||
</DataGrid>
|
</DataGrid>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import type { ReactNode } from 'react'
|
import { useEffect, useRef, type ReactNode } from 'react'
|
||||||
import { Link, Outlet, useRouterState } from '@tanstack/react-router'
|
import { Link, Outlet, useRouterState } from '@tanstack/react-router'
|
||||||
import { SettingsIcon } from 'lucide-react'
|
import { SettingsIcon } from 'lucide-react'
|
||||||
|
|
||||||
import { useIsMobile } from '@cfdm/ui/hooks/use-mobile'
|
import { useIsMobile } from '@cfdm/ui/hooks/use-mobile'
|
||||||
import { cn } from '@cfdm/ui/lib/utils'
|
import { cn } from '@cfdm/ui/lib/utils'
|
||||||
import { PageShell } from '@/components/page-shell'
|
import { PageShell } from '@/components/page-shell'
|
||||||
|
import { debugAgentLog } from '@/lib/debug-agent-log'
|
||||||
|
|
||||||
export interface SettingsTabConfig {
|
export interface SettingsTabConfig {
|
||||||
id: string
|
id: string
|
||||||
@@ -34,11 +35,28 @@ export function SettingsShell({
|
|||||||
tabs = DEFAULT_TABS,
|
tabs = DEFAULT_TABS,
|
||||||
}: SettingsShellProps) {
|
}: SettingsShellProps) {
|
||||||
const isMobile = useIsMobile()
|
const isMobile = useIsMobile()
|
||||||
|
const rootRef = useRef<HTMLDivElement>(null)
|
||||||
const pathname = useRouterState({ select: (s) => s.location.pathname })
|
const pathname = useRouterState({ select: (s) => s.location.pathname })
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const el = rootRef.current
|
||||||
|
if (!el) return
|
||||||
|
debugAgentLog(
|
||||||
|
'settings-shell.tsx:mount',
|
||||||
|
'settings shell width',
|
||||||
|
{
|
||||||
|
pathname,
|
||||||
|
clientWidth: el.clientWidth,
|
||||||
|
maxWidth: getComputedStyle(el).maxWidth,
|
||||||
|
className: el.className,
|
||||||
|
},
|
||||||
|
'A',
|
||||||
|
)
|
||||||
|
}, [pathname])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageShell>
|
<PageShell>
|
||||||
<div className="mx-auto flex w-full max-w-4xl flex-col gap-6">
|
<div ref={rootRef} className="mx-auto flex w-full max-w-4xl flex-col gap-6">
|
||||||
<header className="px-1">
|
<header className="px-1">
|
||||||
<h1 className="text-xl font-semibold tracking-tight">{title}</h1>
|
<h1 className="text-xl font-semibold tracking-tight">{title}</h1>
|
||||||
<p className="text-muted-foreground max-w-2xl text-sm leading-relaxed">
|
<p className="text-muted-foreground max-w-2xl text-sm leading-relaxed">
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/** Debug session 943716 — remove after layout/chart investigation */
|
||||||
|
export const DEBUG_BUILD_STAMP = 'layout-charts-v1'
|
||||||
|
|
||||||
|
export function debugAgentLog(
|
||||||
|
location: string,
|
||||||
|
message: string,
|
||||||
|
data: Record<string, unknown>,
|
||||||
|
hypothesisId: string,
|
||||||
|
) {
|
||||||
|
// #region agent log
|
||||||
|
fetch('http://127.0.0.1:7580/ingest/5c1b60ca-3f59-41ce-8435-d25bcc12c3cf', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Debug-Session-Id': '943716',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
sessionId: '943716',
|
||||||
|
location,
|
||||||
|
message,
|
||||||
|
data,
|
||||||
|
hypothesisId,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
runId: 'pre-fix',
|
||||||
|
}),
|
||||||
|
}).catch(() => {})
|
||||||
|
// #endregion
|
||||||
|
}
|
||||||
@@ -8,6 +8,12 @@ import { Toaster } from '@cfdm/ui/components/sonner'
|
|||||||
import { routeTree } from './routeTree.gen'
|
import { routeTree } from './routeTree.gen'
|
||||||
import { queryClient } from './lib/queryClient'
|
import { queryClient } from './lib/queryClient'
|
||||||
import '@cfdm/ui/globals.css'
|
import '@cfdm/ui/globals.css'
|
||||||
|
import { DEBUG_BUILD_STAMP, debugAgentLog } from '@/lib/debug-agent-log'
|
||||||
|
|
||||||
|
debugAgentLog('main.tsx:boot', 'app boot', {
|
||||||
|
buildStamp: DEBUG_BUILD_STAMP,
|
||||||
|
href: typeof window !== 'undefined' ? window.location.href : '',
|
||||||
|
}, 'B')
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
routeTree,
|
routeTree,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createFileRoute, Link } from '@tanstack/react-router'
|
import { createFileRoute, Link } from '@tanstack/react-router'
|
||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { useMemo, useState } from 'react'
|
import { useMemo, useState, useEffect } from 'react'
|
||||||
import {
|
import {
|
||||||
AlertTriangleIcon,
|
AlertTriangleIcon,
|
||||||
FolderTreeIcon,
|
FolderTreeIcon,
|
||||||
@@ -30,6 +30,7 @@ import {
|
|||||||
ItemTitle,
|
ItemTitle,
|
||||||
} from '@cfdm/ui/components/item'
|
} from '@cfdm/ui/components/item'
|
||||||
import { formatRelative } from '@/lib/format'
|
import { formatRelative } from '@/lib/format'
|
||||||
|
import { DEBUG_BUILD_STAMP, debugAgentLog } from '@/lib/debug-agent-log'
|
||||||
|
|
||||||
export const Route = createFileRoute('/_auth/')({
|
export const Route = createFileRoute('/_auth/')({
|
||||||
loader: ({ context: { queryClient } }) =>
|
loader: ({ context: { queryClient } }) =>
|
||||||
@@ -109,6 +110,25 @@ function DashboardPage() {
|
|||||||
const certWarnings = countByStatus(summary, ['warning', 'expired', 'error'])
|
const certWarnings = countByStatus(summary, ['warning', 'expired', 'error'])
|
||||||
const certOk = countByStatus(summary, ['active', 'ok'])
|
const certOk = countByStatus(summary, ['active', 'ok'])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isLoading) return
|
||||||
|
debugAgentLog(
|
||||||
|
'index.tsx:dashboard-data',
|
||||||
|
'dashboard chart inputs',
|
||||||
|
{
|
||||||
|
buildStamp: DEBUG_BUILD_STAMP,
|
||||||
|
summaryRaw: summary ?? null,
|
||||||
|
statusChartLen: statusChartData.length,
|
||||||
|
statusChartData,
|
||||||
|
groupChartLen: groupChartData.length,
|
||||||
|
groupChartData,
|
||||||
|
domainsLen: domains?.length ?? 0,
|
||||||
|
groupsLen: groups?.length ?? 0,
|
||||||
|
},
|
||||||
|
'C',
|
||||||
|
)
|
||||||
|
}, [isLoading, summary, statusChartData, groupChartData, domains, groups])
|
||||||
|
|
||||||
const kpiCards = [
|
const kpiCards = [
|
||||||
{
|
{
|
||||||
id: 'domains',
|
id: 'domains',
|
||||||
|
|||||||
Reference in New Issue
Block a user