From bbcf9d76d951b98626979fe4f86e2ce3ac3bd9dd Mon Sep 17 00:00:00 2001 From: Denozordec Date: Thu, 9 Jul 2026 23:10:20 +0700 Subject: [PATCH] refactor: enhance schedule components with improved filtering and layout Updated the ScheduleAgendaPanel to include a job filter feature, allowing users to filter jobs by type (all, refresh, failed). Refactored the layout to integrate a new ScheduleCalendarView for better organization. Enhanced the ScheduleJobsGrid to conditionally display pagination based on the number of items. Additionally, modified the Schedule component to utilize the new ScheduleJobsCard for improved job display and loading states, ensuring a more cohesive user experience. --- .../schedule/schedule-agenda-panel.tsx | 186 +++++++++++++----- .../schedule/schedule-calendar-view.tsx | 181 +++++++++++++++++ .../schedule/schedule-jobs-card.tsx | 77 ++++++++ .../schedule/schedule-jobs-grid.tsx | 1 + apps/web/src/routes/_auth/schedule.tsx | 74 ++++--- apps/web/tsconfig.tsbuildinfo | 2 +- 6 files changed, 427 insertions(+), 94 deletions(-) create mode 100644 apps/web/src/components/schedule/schedule-calendar-view.tsx create mode 100644 apps/web/src/components/schedule/schedule-jobs-card.tsx diff --git a/apps/web/src/components/schedule/schedule-agenda-panel.tsx b/apps/web/src/components/schedule/schedule-agenda-panel.tsx index 09532bf..3f83b5b 100644 --- a/apps/web/src/components/schedule/schedule-agenda-panel.tsx +++ b/apps/web/src/components/schedule/schedule-agenda-panel.tsx @@ -1,15 +1,69 @@ import { useMemo, useState } from 'react' import { format, isSameDay, parseISO } from 'date-fns' import { ru } from 'date-fns/locale' +import { CalendarDays, Clock } from 'lucide-react' -import { PanelCard } from '@/components/panel-card' +import { IllustratedEmptyState } from '@/components/patterns/illustrated-empty-state' +import { PanelCard, panelCardContentFlushClassName } from '@/components/panel-card' import { StatusBadge } from '@/components/status-badge' -import { Calendar } from '@evobgp/ui/components/calendar' +import { Item } from '@evobgp/ui/components/item' import { ScrollArea } from '@evobgp/ui/components/scroll-area' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@evobgp/ui/components/select' +import { cn } from '@evobgp/ui/lib/utils' import { jobKindRu } from '@/lib/ui-labels' import type { JobRow } from '@/types/api' -/** schedule-1 inspired agenda: calendar + day job list. */ +import { ScheduleCalendarView } from './schedule-calendar-view' + +type JobFilter = 'all' | 'refresh' | 'failed' + +const FILTER_ITEMS: { value: JobFilter; label: string }[] = [ + { value: 'all', label: 'Все задачи' }, + { value: 'refresh', label: 'Обновление' }, + { value: 'failed', label: 'С ошибкой' }, +] + +function jobTimestamp(job: JobRow): string | undefined { + return job.created_at ?? job.started_at ?? job.finished_at ?? undefined +} + +function matchesFilter(job: JobRow, filter: JobFilter): boolean { + if (filter === 'refresh') return job.kind === 'module_refresh' + if (filter === 'failed') + return ['failed', 'error', 'cancelled'].includes(job.status.toLowerCase()) + return true +} + +function ScheduleJobCard({ job }: { job: JobRow }) { + const ts = jobTimestamp(job) + const timeLabel = ts + ? format(parseISO(ts), 'd MMM · HH:mm', { locale: ru }) + : '—' + + return ( + +
+

{jobKindRu(job.kind)}

+
+ + + + {timeLabel} + +
+

{job.job_id}

+
+
+ ) +} + +/** schedule-1 layout: calendar column + day job list. */ export function ScheduleAgendaPanel({ jobs, isLoading, @@ -18,25 +72,12 @@ export function ScheduleAgendaPanel({ isLoading?: boolean }) { const [date, setDate] = useState(new Date()) - - const dayJobs = useMemo( - () => - jobs.filter((job) => { - const raw = job.created_at ?? job.started_at ?? job.finished_at - if (!raw) return false - try { - return isSameDay(parseISO(raw), date) - } catch { - return false - } - }), - [jobs, date], - ) + const [filter, setFilter] = useState('all') const markedDays = useMemo(() => { const days = new Set() for (const job of jobs) { - const raw = job.created_at ?? job.started_at ?? job.finished_at + const raw = jobTimestamp(job) if (!raw) continue try { days.add(format(parseISO(raw), 'yyyy-MM-dd')) @@ -47,44 +88,85 @@ export function ScheduleAgendaPanel({ return days }, [jobs]) + const dayJobs = useMemo( + () => + jobs.filter((job) => { + const raw = jobTimestamp(job) + if (!raw) return false + try { + return isSameDay(parseISO(raw), date) && matchesFilter(job, filter) + } catch { + return false + } + }), + [jobs, date, filter], + ) + + const headingLabel = format(date, 'EEEE, d MMMM', { locale: ru }) + return ( -
- d && setDate(d)} - locale={ru} - modifiers={{ - hasJob: (d) => markedDays.has(format(d, 'yyyy-MM-dd')), - }} - modifiersClassNames={{ hasJob: 'font-bold underline' }} - /> - - {isLoading ? ( -

Загрузка…

- ) : dayJobs.length === 0 ? ( -

- Нет задач за {format(date, 'd MMMM yyyy', { locale: ru })} -

- ) : ( -
    - {dayJobs.map((job) => ( -
  • -
    - {jobKindRu(job.kind)} - -
    -

    {job.job_id}

    -
  • - ))} -
- )} -
+
+
+ d && setDate(d)} + datesWithEvents={markedDays} + /> +
+ +
+
+
+

{headingLabel}

+

+ {isLoading + ? 'Загрузка…' + : dayJobs.length > 0 + ? `${dayJobs.length} ${dayJobs.length === 1 ? 'задача' : dayJobs.length < 5 ? 'задачи' : 'задач'}` + : 'Нет задач за выбранный день'} +

+
+ +
+ +
+ {isLoading ? ( +

Загрузка задач…

+ ) : dayJobs.length === 0 ? ( + + ) : ( + +
    + {dayJobs.map((job) => ( +
  • + +
  • + ))} +
+
+ )} +
+
) diff --git a/apps/web/src/components/schedule/schedule-calendar-view.tsx b/apps/web/src/components/schedule/schedule-calendar-view.tsx new file mode 100644 index 0000000..2e32267 --- /dev/null +++ b/apps/web/src/components/schedule/schedule-calendar-view.tsx @@ -0,0 +1,181 @@ +import { useState, type ComponentPropsWithoutRef } from 'react' +import { DayButton } from 'react-day-picker' +import { format } from 'date-fns' +import { ru } from 'date-fns/locale' +import { ChevronLeft, ChevronRight } from 'lucide-react' + +import { cn } from '@evobgp/ui/lib/utils' +import { Button } from '@evobgp/ui/components/button' +import { Calendar, CalendarDayButton } from '@evobgp/ui/components/calendar' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@evobgp/ui/components/select' + +const MONTHS_RU = Array.from({ length: 12 }, (_, i) => + format(new Date(2024, i, 1), 'LLLL', { locale: ru }), +) + +const CURRENT_YEAR = new Date().getFullYear() +const YEARS = Array.from({ length: 11 }, (_, i) => CURRENT_YEAR - 5 + i) + +const TODAY_WEEKDAY = format(new Date(), 'EEEEEE', { locale: ru }).toUpperCase() + +export function ScheduleCalendarView({ + selected, + onSelect, + datesWithEvents = new Set(), +}: { + selected: Date | undefined + onSelect: (date: Date | undefined) => void + datesWithEvents?: Set +}) { + const [month, setMonth] = useState(selected ?? new Date()) + + const stepMonth = (delta: number) => + setMonth((prev) => new Date(prev.getFullYear(), prev.getMonth() + delta, 1)) + + const handleMonthSelect = (value: string) => { + const i = MONTHS_RU.indexOf(value) + if (i >= 0) setMonth(new Date(month.getFullYear(), i, 1)) + } + + const handleYearSelect = (value: string) => { + const y = parseInt(value, 10) + if (!Number.isNaN(y)) setMonth(new Date(y, month.getMonth(), 1)) + } + + return ( +
+
+ + + + + + + +
+ + + date.toLocaleString('ru-RU', { weekday: 'short' }).replace('.', '').toUpperCase(), + }} + classNames={{ + month_caption: 'hidden', + nav: 'hidden', + weekdays: 'flex gap-1', + weekday: + 'flex-1 flex items-center justify-center h-6 text-[0.65rem] font-medium text-muted-foreground', + week: 'flex gap-1 mt-1', + day: 'flex-1 aspect-square p-0', + day_button: cn( + 'bg-muted/50 hover:bg-muted rounded-md', + 'data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[selected-single=true]:hover:bg-primary data-[selected-single=true]:hover:text-primary-foreground!', + ), + outside: 'opacity-60', + disabled: 'opacity-60', + today: cn('bg-accent text-foreground rounded-md'), + }} + components={{ + Weekday: ({ children, className: cls, ...props }: ComponentPropsWithoutRef<'th'>) => { + const isToday = children === TODAY_WEEKDAY + return ( + + {children} + + ) + }, + DayButton: ({ + children, + modifiers, + day, + ...props + }: React.ComponentProps) => { + const dateKey = format(day.date, 'yyyy-MM-dd') + const hasEvents = !modifiers.outside && datesWithEvents.has(dateKey) + + return ( + + {hasEvents ? ( + + ) : ( + + )} + {children} + + ) + }, + }} + /> +
+ ) +} diff --git a/apps/web/src/components/schedule/schedule-jobs-card.tsx b/apps/web/src/components/schedule/schedule-jobs-card.tsx new file mode 100644 index 0000000..ea6f664 --- /dev/null +++ b/apps/web/src/components/schedule/schedule-jobs-card.tsx @@ -0,0 +1,77 @@ +import { useMemo, useState } from 'react' + +import { DataGridCard } from '@/components/data-grid-shell' +import { QueryState } from '@/components/query-state' +import { TableSkeleton } from '@/components/skeletons' +import { Tabs, TabsList, TabsTrigger } from '@evobgp/ui/components/tabs' +import type { JobRow } from '@/types/api' + +import { ScheduleJobsGrid } from './schedule-jobs-grid' + +type JobTab = 'all' | 'refresh' | 'failed' + +function filterJobs(items: JobRow[], tab: JobTab): JobRow[] { + if (tab === 'refresh') return items.filter((j) => j.kind === 'module_refresh') + if (tab === 'failed') + return items.filter((j) => ['failed', 'error', 'cancelled'].includes(j.status.toLowerCase())) + return items +} + +function tabCounts(items: JobRow[]) { + return { + all: items.length, + refresh: items.filter((j) => j.kind === 'module_refresh').length, + failed: items.filter((j) => ['failed', 'error', 'cancelled'].includes(j.status.toLowerCase())) + .length, + } +} + +/** Jobs data-grid with status tabs (data-grid-filtering pattern). */ +export function ScheduleJobsCard({ + jobs, + isLoading, + isError, + error, + onRetry, +}: { + jobs: JobRow[] + isLoading: boolean + isError: boolean + error: unknown + onRetry: () => void +}) { + const [tab, setTab] = useState('all') + const counts = useMemo(() => tabCounts(jobs), [jobs]) + const filtered = useMemo(() => filterJobs(jobs, tab), [jobs, tab]) + + return ( + +
+ setTab(v as JobTab)}> + + Все ({counts.all}) + Обновление ({counts.refresh}) + С ошибкой ({counts.failed}) + + +
+ } + onRetry={onRetry} + > + {(items) => ( + 0} + /> + )} + +
+ ) +} diff --git a/apps/web/src/components/schedule/schedule-jobs-grid.tsx b/apps/web/src/components/schedule/schedule-jobs-grid.tsx index e90072f..a55c1c2 100644 --- a/apps/web/src/components/schedule/schedule-jobs-grid.tsx +++ b/apps/web/src/components/schedule/schedule-jobs-grid.tsx @@ -90,6 +90,7 @@ export function ScheduleJobsGrid({ recordCount={filteredCount} isLoading={isLoading} emptyMessage="Нет задач" + showPagination={items.length > 10} searchValue={globalFilter} onSearchChange={setGlobalFilter} searchPlaceholder="Поиск задач…" diff --git a/apps/web/src/routes/_auth/schedule.tsx b/apps/web/src/routes/_auth/schedule.tsx index a51b2a2..1ba95dd 100644 --- a/apps/web/src/routes/_auth/schedule.tsx +++ b/apps/web/src/routes/_auth/schedule.tsx @@ -2,13 +2,12 @@ import { createFileRoute } from '@tanstack/react-router' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { AlertTriangle, Clock, ListTodo, RefreshCw } from 'lucide-react' import { toast } from 'sonner' -import { useState } from 'react' +import { useEffect, useState } from 'react' import { Button } from '@evobgp/ui/components/button' -import { BadgeTabs, TabsContent } from '@/components/badge-tabs' import { DataGridCard } from '@/components/data-grid-shell' import { ScheduleAgendaPanel } from '@/components/schedule/schedule-agenda-panel' -import { ScheduleJobsGrid } from '@/components/schedule/schedule-jobs-grid' +import { ScheduleJobsCard } from '@/components/schedule/schedule-jobs-card' import { ScheduleModulesGrid } from '@/components/schedule/schedule-modules-grid' import { PageHeader } from '@/components/page-header' import { QueryState } from '@/components/query-state' @@ -18,7 +17,6 @@ import { SectionCardsSkeleton } from '@/components/skeletons' import { operationsJobsQueryOptions } from '@/queries/operations' import { modulesListQueryOptions } from '@/queries/modules' import { apiMutate } from '@/lib/api-client' -import type { JobRow } from '@/types/api' export const Route = createFileRoute('/_auth/schedule')({ component: ScheduleComponent, @@ -38,6 +36,30 @@ function ScheduleComponent() { ['failed', 'error', 'cancelled'].includes(j.status.toLowerCase()), ).length + // #region agent log + useEffect(() => { + fetch('http://127.0.0.1:7311/ingest/6b35c3ae-1bcd-4c9c-81eb-f157c9347393', { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'X-Debug-Session-Id': 'ce85d7' }, + body: JSON.stringify({ + sessionId: 'ce85d7', + runId: 'pre-fix', + hypothesisId: 'C', + location: 'schedule.tsx:mount', + message: 'schedule page data loaded', + data: { + modules: modules.length, + jobs: jobs.length, + loading, + modulesError: modulesQ.isError, + jobsError: jobsQ.isError, + }, + timestamp: Date.now(), + }), + }).catch(() => {}) + }, [modules.length, jobs.length, loading, modulesQ.isError, jobsQ.isError]) + // #endregion + const items: SectionCardItem[] = [ { label: 'Всего задач', value: jobs.length, icon: , hint: 'в выборке' }, { label: 'В работе', value: running, icon: , hint: 'в очереди и выполняются' }, @@ -115,43 +137,13 @@ function ScheduleComponent() { - - - + jobsQ.refetch()} + />
) } - -function JobsTabs({ jobs, loading }: { jobs: JobRow[]; loading: boolean }) { - const refresh = jobs.filter((j) => j.kind === 'module_refresh') - const failed = jobs.filter((j) => - ['failed', 'error', 'cancelled'].includes(j.status.toLowerCase()), - ) - - return ( - 0 ? 'destructive-light' : 'primary-light', - }, - ]} - > - - - - - - - - - - - ) -} diff --git a/apps/web/tsconfig.tsbuildinfo b/apps/web/tsconfig.tsbuildinfo index 1bc9647..c2bf8d6 100644 --- a/apps/web/tsconfig.tsbuildinfo +++ b/apps/web/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/main.tsx","./src/routetree.gen.ts","./src/components/badge-tabs.tsx","./src/components/category-badge.tsx","./src/components/confirm-dialog.tsx","./src/components/data-grid-cell.tsx","./src/components/data-grid-shell.tsx","./src/components/data-grid-toolbar.tsx","./src/components/drawer-layout.tsx","./src/components/empty-state.tsx","./src/components/form-drawer.tsx","./src/components/loading-button.tsx","./src/components/mode-toggle.tsx","./src/components/page-header.tsx","./src/components/page-shell.tsx","./src/components/panel-card.tsx","./src/components/query-state.tsx","./src/components/section-cards.tsx","./src/components/select-field.tsx","./src/components/skeletons.tsx","./src/components/status-badge.tsx","./src/components/theme-provider.tsx","./src/components/truncated-text.tsx","./src/components/access/access-api-keys-card.tsx","./src/components/access/access-api-keys-grid.tsx","./src/components/access/api-key-create-dialog.tsx","./src/components/access/api-key-token-dialog.tsx","./src/components/analytics/analytics-activity-list.tsx","./src/components/analytics/analytics-card-shell.tsx","./src/components/analytics/analytics-kpi-row.tsx","./src/components/analytics/analytics-progress.tsx","./src/components/analytics/analytics-segment-control.tsx","./src/components/analytics/chart-bar-strip.tsx","./src/components/analytics/chart-donut-metric.tsx","./src/components/analytics/dashboard-network-capacity-card.tsx","./src/components/analytics/dashboard-operations-flow-card.tsx","./src/components/analytics/dashboard-platform-card.tsx","./src/components/analytics/index.ts","./src/components/analytics/monitoring-health-card.tsx","./src/components/analytics/network-overview-analytics-card.tsx","./src/components/analytics/operations-analytics-card.tsx","./src/components/dashboard/card-dot-field.tsx","./src/components/dashboard/dashboard-activity-timeline.tsx","./src/components/dashboard/dashboard-frame-panel.tsx","./src/components/dashboard/dashboard-kpi-grid.tsx","./src/components/dashboard/dashboard-kpi-sparkline-row.tsx","./src/components/dashboard/dashboard-modules-grid.tsx","./src/components/dashboard/dashboard-network-health.tsx","./src/components/dashboard/dashboard-network-panel.tsx","./src/components/dashboard/dashboard-operations-breakdown.tsx","./src/components/dashboard/dashboard-quick-link-card.tsx","./src/components/dashboard/dashboard-quick-links.tsx","./src/components/dashboard/dashboard-recent-jobs-grid.tsx","./src/components/dashboard/dashboard-recent-revisions-grid.tsx","./src/components/directories/directories-communities-grid.tsx","./src/components/directories/directories-doh-grid.tsx","./src/components/examples/c-input-group-37.tsx","./src/components/examples/c-select-4.tsx","./src/components/examples/c-tabs-6.tsx","./src/components/examples/c-tabs-7.tsx","./src/components/firewall/firewall-clients-grid.tsx","./src/components/firewall/firewall-rule-create-dialog.tsx","./src/components/firewall/firewall-rules-grid.tsx","./src/components/layout/app-shell.tsx","./src/components/layout/command-palette.tsx","./src/components/layout/system-monitor-popover.tsx","./src/components/modules/community-select.tsx","./src/components/modules/module-as-entry-dialog.tsx","./src/components/modules/module-cdn-source-dialog.tsx","./src/components/modules/module-domain-entry-dialog.tsx","./src/components/modules/module-entries-grid.tsx","./src/components/modules/module-entries-section.tsx","./src/components/modules/module-ip-range-entry-dialog.tsx","./src/components/modules/module-kpi-cards.tsx","./src/components/modules/modules-list-grid.tsx","./src/components/monitoring/monitoring-ready-grid.tsx","./src/components/network/network-peers-card.tsx","./src/components/network/network-peers-grid.tsx","./src/components/network/network-speakers-card.tsx","./src/components/network/network-speakers-grid.tsx","./src/components/network/peer-form-dialog.tsx","./src/components/network/speaker-form-dialog.tsx","./src/components/operations/operations-jobs-card.tsx","./src/components/operations/operations-jobs-grid.tsx","./src/components/operations/operations-revisions-grid.tsx","./src/components/patterns/donut-breakdown-card.tsx","./src/components/patterns/illustrated-empty-state.tsx","./src/components/patterns/index.ts","./src/components/patterns/kpi-sparkline-card.tsx","./src/components/patterns/metric-tone-styles.ts","./src/components/patterns/panel-corners.tsx","./src/components/patterns/projects-empty-state.tsx","./src/components/patterns/segmented-progress-card.tsx","./src/components/reui/autocomplete.tsx","./src/components/reui/badge.tsx","./src/components/reui/date-selector.tsx","./src/components/reui/filters.tsx","./src/components/reui/frame.tsx","./src/components/reui/icon-stack.tsx","./src/components/reui/number-field.tsx","./src/components/reui/rating.tsx","./src/components/reui/timeline.tsx","./src/components/reui/data-grid/data-grid-column-filter.tsx","./src/components/reui/data-grid/data-grid-column-header.tsx","./src/components/reui/data-grid/data-grid-column-visibility.tsx","./src/components/reui/data-grid/data-grid-pagination.tsx","./src/components/reui/data-grid/data-grid-scroll-area.tsx","./src/components/reui/data-grid/data-grid-table-dnd-rows.tsx","./src/components/reui/data-grid/data-grid-table-dnd.tsx","./src/components/reui/data-grid/data-grid-table-virtual.tsx","./src/components/reui/data-grid/data-grid-table.tsx","./src/components/reui/data-grid/data-grid.tsx","./src/components/schedule/schedule-agenda-panel.tsx","./src/components/schedule/schedule-jobs-grid.tsx","./src/components/schedule/schedule-modules-grid.tsx","./src/components/settings/settings-kv-grid.tsx","./src/components/settings/settings-setting-field.tsx","./src/components/ui/svgs/anthropicblack.tsx","./src/components/ui/svgs/anthropicwhite.tsx","./src/components/ui/svgs/convex.tsx","./src/components/ui/svgs/discord.tsx","./src/components/ui/svgs/gemini.tsx","./src/components/ui/svgs/googlecloud.tsx","./src/components/ui/svgs/hono.tsx","./src/components/ui/svgs/loom.tsx","./src/components/ui/svgs/mintlify.tsx","./src/components/ui/svgs/n8n.tsx","./src/components/ui/svgs/neon.tsx","./src/components/ui/svgs/openai.tsx","./src/components/ui/svgs/openaidark.tsx","./src/components/ui/svgs/paper.tsx","./src/components/ui/svgs/planetscale.tsx","./src/components/ui/svgs/planetscaledark.tsx","./src/components/ui/svgs/prisma.tsx","./src/components/ui/svgs/prismadark.tsx","./src/components/ui/svgs/remixdark.tsx","./src/components/ui/svgs/remixlight.tsx","./src/components/ui/svgs/resendiconblack.tsx","./src/components/ui/svgs/resendiconwhite.tsx","./src/components/ui/svgs/slack.tsx","./src/components/ui/svgs/stripe.tsx","./src/components/ui/svgs/supabase.tsx","./src/components/ui/svgs/zoom.tsx","./src/hooks/use-client-data-grid.ts","./src/hooks/use-copy-to-clipboard.ts","./src/hooks/use-mobile.ts","./src/lib/api-client.ts","./src/lib/data-grid-defaults.ts","./src/lib/queryclient.ts","./src/lib/router.ts","./src/lib/ui-labels.ts","./src/lib/ui-surface.ts","./src/lib/access/api-key-labels.ts","./src/lib/metrics/deployment-progress.ts","./src/lib/metrics/index.ts","./src/lib/metrics/job-status-breakdown.ts","./src/lib/metrics/module-type-breakdown.ts","./src/lib/metrics/peer-capacity-bars.ts","./src/lib/metrics/peer-session-breakdown.ts","./src/lib/metrics/readiness-breakdown.ts","./src/lib/metrics/recent-platform-activity.ts","./src/lib/metrics/types.ts","./src/lib/modules/display.ts","./src/lib/modules/helpers.ts","./src/queries/api-keys.ts","./src/queries/auth.ts","./src/queries/directories.ts","./src/queries/firewall.ts","./src/queries/modules.ts","./src/queries/monitoring.ts","./src/queries/network.ts","./src/queries/operations.ts","./src/queries/overview.ts","./src/queries/settings.ts","./src/routes/__root.tsx","./src/routes/_auth.tsx","./src/routes/index.tsx","./src/routes/_auth/access.tsx","./src/routes/_auth/dashboard.tsx","./src/routes/_auth/directories.tsx","./src/routes/_auth/firewall.tsx","./src/routes/_auth/monitoring.tsx","./src/routes/_auth/network.tsx","./src/routes/_auth/operations.tsx","./src/routes/_auth/schedule.tsx","./src/routes/_auth/settings.tsx","./src/routes/_auth/tenant-settings.tsx","./src/routes/_auth/modules/$moduleid.tsx","./src/routes/_auth/modules/index.tsx","./src/routes/_auth/modules/new.tsx","./src/types/api.ts","./vite.config.ts"],"version":"5.9.3"} \ No newline at end of file +{"root":["./src/main.tsx","./src/routetree.gen.ts","./src/components/badge-tabs.tsx","./src/components/category-badge.tsx","./src/components/confirm-dialog.tsx","./src/components/data-grid-cell.tsx","./src/components/data-grid-shell.tsx","./src/components/data-grid-toolbar.tsx","./src/components/drawer-layout.tsx","./src/components/empty-state.tsx","./src/components/form-drawer.tsx","./src/components/loading-button.tsx","./src/components/mode-toggle.tsx","./src/components/page-header.tsx","./src/components/page-shell.tsx","./src/components/panel-card.tsx","./src/components/query-state.tsx","./src/components/section-cards.tsx","./src/components/select-field.tsx","./src/components/skeletons.tsx","./src/components/status-badge.tsx","./src/components/theme-provider.tsx","./src/components/truncated-text.tsx","./src/components/access/access-api-keys-card.tsx","./src/components/access/access-api-keys-grid.tsx","./src/components/access/api-key-create-dialog.tsx","./src/components/access/api-key-token-dialog.tsx","./src/components/analytics/analytics-activity-list.tsx","./src/components/analytics/analytics-card-shell.tsx","./src/components/analytics/analytics-kpi-row.tsx","./src/components/analytics/analytics-progress.tsx","./src/components/analytics/analytics-segment-control.tsx","./src/components/analytics/chart-bar-strip.tsx","./src/components/analytics/chart-donut-metric.tsx","./src/components/analytics/dashboard-network-capacity-card.tsx","./src/components/analytics/dashboard-operations-flow-card.tsx","./src/components/analytics/dashboard-platform-card.tsx","./src/components/analytics/index.ts","./src/components/analytics/monitoring-health-card.tsx","./src/components/analytics/network-overview-analytics-card.tsx","./src/components/analytics/operations-analytics-card.tsx","./src/components/dashboard/card-dot-field.tsx","./src/components/dashboard/dashboard-activity-timeline.tsx","./src/components/dashboard/dashboard-frame-panel.tsx","./src/components/dashboard/dashboard-kpi-grid.tsx","./src/components/dashboard/dashboard-kpi-sparkline-row.tsx","./src/components/dashboard/dashboard-modules-grid.tsx","./src/components/dashboard/dashboard-network-health.tsx","./src/components/dashboard/dashboard-network-panel.tsx","./src/components/dashboard/dashboard-operations-breakdown.tsx","./src/components/dashboard/dashboard-quick-link-card.tsx","./src/components/dashboard/dashboard-quick-links.tsx","./src/components/dashboard/dashboard-recent-jobs-grid.tsx","./src/components/dashboard/dashboard-recent-revisions-grid.tsx","./src/components/directories/directories-communities-grid.tsx","./src/components/directories/directories-doh-grid.tsx","./src/components/examples/c-input-group-37.tsx","./src/components/examples/c-select-4.tsx","./src/components/examples/c-tabs-6.tsx","./src/components/examples/c-tabs-7.tsx","./src/components/firewall/firewall-clients-grid.tsx","./src/components/firewall/firewall-rule-create-dialog.tsx","./src/components/firewall/firewall-rules-grid.tsx","./src/components/layout/app-shell.tsx","./src/components/layout/command-palette.tsx","./src/components/layout/system-monitor-popover.tsx","./src/components/modules/community-select.tsx","./src/components/modules/module-as-entry-dialog.tsx","./src/components/modules/module-cdn-source-dialog.tsx","./src/components/modules/module-domain-entry-dialog.tsx","./src/components/modules/module-entries-grid.tsx","./src/components/modules/module-entries-section.tsx","./src/components/modules/module-ip-range-entry-dialog.tsx","./src/components/modules/module-kpi-cards.tsx","./src/components/modules/modules-list-grid.tsx","./src/components/monitoring/monitoring-ready-grid.tsx","./src/components/network/network-peers-card.tsx","./src/components/network/network-peers-grid.tsx","./src/components/network/network-speakers-card.tsx","./src/components/network/network-speakers-grid.tsx","./src/components/network/peer-form-dialog.tsx","./src/components/network/speaker-form-dialog.tsx","./src/components/operations/operations-jobs-card.tsx","./src/components/operations/operations-jobs-grid.tsx","./src/components/operations/operations-revisions-grid.tsx","./src/components/patterns/donut-breakdown-card.tsx","./src/components/patterns/illustrated-empty-state.tsx","./src/components/patterns/index.ts","./src/components/patterns/kpi-sparkline-card.tsx","./src/components/patterns/metric-tone-styles.ts","./src/components/patterns/panel-corners.tsx","./src/components/patterns/projects-empty-state.tsx","./src/components/patterns/segmented-progress-card.tsx","./src/components/reui/autocomplete.tsx","./src/components/reui/badge.tsx","./src/components/reui/date-selector.tsx","./src/components/reui/filters.tsx","./src/components/reui/frame.tsx","./src/components/reui/icon-stack.tsx","./src/components/reui/number-field.tsx","./src/components/reui/rating.tsx","./src/components/reui/timeline.tsx","./src/components/reui/data-grid/data-grid-column-filter.tsx","./src/components/reui/data-grid/data-grid-column-header.tsx","./src/components/reui/data-grid/data-grid-column-visibility.tsx","./src/components/reui/data-grid/data-grid-pagination.tsx","./src/components/reui/data-grid/data-grid-scroll-area.tsx","./src/components/reui/data-grid/data-grid-table-dnd-rows.tsx","./src/components/reui/data-grid/data-grid-table-dnd.tsx","./src/components/reui/data-grid/data-grid-table-virtual.tsx","./src/components/reui/data-grid/data-grid-table.tsx","./src/components/reui/data-grid/data-grid.tsx","./src/components/schedule/schedule-agenda-panel.tsx","./src/components/schedule/schedule-calendar-view.tsx","./src/components/schedule/schedule-jobs-card.tsx","./src/components/schedule/schedule-jobs-grid.tsx","./src/components/schedule/schedule-modules-grid.tsx","./src/components/settings/settings-kv-grid.tsx","./src/components/settings/settings-setting-field.tsx","./src/components/ui/svgs/anthropicblack.tsx","./src/components/ui/svgs/anthropicwhite.tsx","./src/components/ui/svgs/convex.tsx","./src/components/ui/svgs/discord.tsx","./src/components/ui/svgs/gemini.tsx","./src/components/ui/svgs/googlecloud.tsx","./src/components/ui/svgs/hono.tsx","./src/components/ui/svgs/loom.tsx","./src/components/ui/svgs/mintlify.tsx","./src/components/ui/svgs/n8n.tsx","./src/components/ui/svgs/neon.tsx","./src/components/ui/svgs/openai.tsx","./src/components/ui/svgs/openaidark.tsx","./src/components/ui/svgs/paper.tsx","./src/components/ui/svgs/planetscale.tsx","./src/components/ui/svgs/planetscaledark.tsx","./src/components/ui/svgs/prisma.tsx","./src/components/ui/svgs/prismadark.tsx","./src/components/ui/svgs/remixdark.tsx","./src/components/ui/svgs/remixlight.tsx","./src/components/ui/svgs/resendiconblack.tsx","./src/components/ui/svgs/resendiconwhite.tsx","./src/components/ui/svgs/slack.tsx","./src/components/ui/svgs/stripe.tsx","./src/components/ui/svgs/supabase.tsx","./src/components/ui/svgs/zoom.tsx","./src/hooks/use-client-data-grid.ts","./src/hooks/use-copy-to-clipboard.ts","./src/hooks/use-mobile.ts","./src/lib/api-client.ts","./src/lib/data-grid-defaults.ts","./src/lib/queryclient.ts","./src/lib/router.ts","./src/lib/ui-labels.ts","./src/lib/ui-surface.ts","./src/lib/access/api-key-labels.ts","./src/lib/metrics/deployment-progress.ts","./src/lib/metrics/index.ts","./src/lib/metrics/job-status-breakdown.ts","./src/lib/metrics/module-type-breakdown.ts","./src/lib/metrics/peer-capacity-bars.ts","./src/lib/metrics/peer-session-breakdown.ts","./src/lib/metrics/readiness-breakdown.ts","./src/lib/metrics/recent-platform-activity.ts","./src/lib/metrics/types.ts","./src/lib/modules/display.ts","./src/lib/modules/helpers.ts","./src/queries/api-keys.ts","./src/queries/auth.ts","./src/queries/directories.ts","./src/queries/firewall.ts","./src/queries/modules.ts","./src/queries/monitoring.ts","./src/queries/network.ts","./src/queries/operations.ts","./src/queries/overview.ts","./src/queries/settings.ts","./src/routes/__root.tsx","./src/routes/_auth.tsx","./src/routes/index.tsx","./src/routes/_auth/access.tsx","./src/routes/_auth/dashboard.tsx","./src/routes/_auth/directories.tsx","./src/routes/_auth/firewall.tsx","./src/routes/_auth/monitoring.tsx","./src/routes/_auth/network.tsx","./src/routes/_auth/operations.tsx","./src/routes/_auth/schedule.tsx","./src/routes/_auth/settings.tsx","./src/routes/_auth/tenant-settings.tsx","./src/routes/_auth/modules/$moduleid.tsx","./src/routes/_auth/modules/index.tsx","./src/routes/_auth/modules/new.tsx","./src/types/api.ts","./vite.config.ts"],"version":"5.9.3"} \ No newline at end of file