From 821f342476427259906067800a660bb5d02e2c48 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Wed, 12 Aug 2026 11:32:44 +0700 Subject: [PATCH] feat(modules): enhance ModuleKpiCards and add update functionality for modules Updated the ModuleKpiCards component to improve the display of KPI metrics by introducing a new entries count prop and simplifying the logic for displaying community and DoH information. Added a new mutation hook for updating modules, which includes success and error handling with toast notifications. Enhanced the ModuleDetailComponent to support editing modules with a new dialog and integrated the entries count into the KPI display. Additionally, introduced a new helper function for generating short labels for DoH profiles, improving the overall user experience in module management. --- .../components/modules/module-edit-dialog.tsx | 260 ++++++++++++++++++ .../components/modules/module-kpi-cards.tsx | 85 +++--- apps/web/src/lib/modules/helpers.ts | 13 + apps/web/src/queries/modules.ts | 29 +- .../src/routes/_auth/modules/$moduleId.tsx | 33 ++- apps/web/tsconfig.tsbuildinfo | 2 +- 6 files changed, 383 insertions(+), 39 deletions(-) create mode 100644 apps/web/src/components/modules/module-edit-dialog.tsx diff --git a/apps/web/src/components/modules/module-edit-dialog.tsx b/apps/web/src/components/modules/module-edit-dialog.tsx new file mode 100644 index 0000000..f5ab3c5 --- /dev/null +++ b/apps/web/src/components/modules/module-edit-dialog.tsx @@ -0,0 +1,260 @@ +import { useEffect, useState } from 'react' +import { toast } from 'sonner' + +import { Button } from '@evobgp/ui/components/button' +import { Checkbox } from '@evobgp/ui/components/checkbox' +import { Input } from '@evobgp/ui/components/input' +import { Label } from '@evobgp/ui/components/label' + +import { FormDrawer } from '@/components/form-drawer' +import { LoadingButton } from '@/components/loading-button' +import { CommunitySelect } from '@/components/modules/community-select' +import { SelectField } from '@/components/select-field' +import { + dohProfileShortLabel, + moduleDohProfileIds, +} from '@/lib/modules/helpers' +import { dohPolicyRu, moduleTypeRu } from '@/lib/ui-labels' +import { useUpdateModuleMutation } from '@/queries/modules' +import type { + BgpCommunity, + DohProfile, + DohResolverPolicy, + ModulePatch, + ModuleRow, +} from '@/types/api' + +interface ModuleEditDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + mod: ModuleRow + communities: BgpCommunity[] + dohProfiles: DohProfile[] +} + +const DOH_POLICY_ITEMS: { value: DohResolverPolicy; label: string }[] = [ + { value: 'primary_only', label: dohPolicyRu('primary_only') }, + { value: 'failover', label: dohPolicyRu('failover') }, + { value: 'union', label: dohPolicyRu('union') }, +] + +export function ModuleEditDialog({ + open, + onOpenChange, + mod, + communities, + dohProfiles, +}: ModuleEditDialogProps) { + const updateMutation = useUpdateModuleMutation() + const isDomains = mod.type === 'DOMAINS' + + const [name, setName] = useState('') + const [enabled, setEnabled] = useState(true) + const [priority, setPriority] = useState('0') + const [refreshIntervalSec, setRefreshIntervalSec] = useState('') + const [cronExpr, setCronExpr] = useState('') + const [defaultCommunityId, setDefaultCommunityId] = useState(null) + const [dohResolverPolicy, setDohResolverPolicy] = useState('primary_only') + const [dohProfileIds, setDohProfileIds] = useState([]) + + useEffect(() => { + if (!open) return + setName(mod.name ?? '') + setEnabled(mod.enabled !== false) + setPriority(String(mod.priority ?? 0)) + setRefreshIntervalSec( + mod.refresh_interval_sec === null || mod.refresh_interval_sec === undefined + ? '' + : String(mod.refresh_interval_sec), + ) + setCronExpr(mod.cron_expr ?? '') + setDefaultCommunityId(mod.default_community_id ?? null) + setDohResolverPolicy(mod.doh_resolver_policy ?? 'primary_only') + setDohProfileIds(moduleDohProfileIds(mod)) + }, [mod, open]) + + function toggleDohProfile(id: string, checked: boolean) { + setDohProfileIds((prev) => { + if (checked) { + if (prev.includes(id)) return prev + return [...prev, id] + } + return prev.filter((x) => x !== id) + }) + } + + async function save() { + const trimmedName = name.trim() + if (!trimmedName) { + toast.error('Укажите название модуля') + return + } + + const priorityNum = Number(priority) + if (!Number.isFinite(priorityNum) || !Number.isInteger(priorityNum)) { + toast.error('Приоритет должен быть целым числом') + return + } + + let refresh: number | null = null + if (refreshIntervalSec.trim() !== '') { + const n = Number(refreshIntervalSec) + if (!Number.isFinite(n) || !Number.isInteger(n) || n < 0) { + toast.error('Интервал обновления должен быть целым числом ≥ 0') + return + } + refresh = n + } + + const body: ModulePatch = { + name: trimmedName, + enabled, + priority: priorityNum, + refresh_interval_sec: refresh, + cron_expr: cronExpr.trim() || null, + default_community_id: defaultCommunityId, + } + + if (isDomains) { + body.doh_resolver_policy = dohResolverPolicy + body.doh_profile_ids = dohProfileIds + } + + try { + await updateMutation.mutateAsync({ id: mod.id, body }) + onOpenChange(false) + } catch { + // toast in mutation + } + } + + return ( + + + void save()}> + Сохранить + + + } + > +
+ + setName(e.target.value)} + placeholder="Имя модуля" + /> +
+ +
+
+ +

+ Выключенный модуль не участвует в refresh и apply. +

+
+ setEnabled(v === true)} + /> +
+ +
+ + setPriority(e.target.value)} + /> +
+ +
+ + setRefreshIntervalSec(e.target.value)} + /> +
+ +
+ + setCronExpr(e.target.value)} + /> +
+ + + + {isDomains ? ( + <> + setDohResolverPolicy(v as DohResolverPolicy)} + /> + +
+ + {dohProfiles.length === 0 ? ( +

Нет профилей в справочнике

+ ) : ( +
+ {dohProfiles.map((p) => { + const checked = dohProfileIds.includes(p.id) + return ( + + ) + })} +
+ )} +
+ + ) : null} +
+ ) +} diff --git a/apps/web/src/components/modules/module-kpi-cards.tsx b/apps/web/src/components/modules/module-kpi-cards.tsx index 7a42662..501457c 100644 --- a/apps/web/src/components/modules/module-kpi-cards.tsx +++ b/apps/web/src/components/modules/module-kpi-cards.tsx @@ -2,29 +2,25 @@ import { Clock, Gauge, Globe, Tags } from 'lucide-react' import { KpiStatGrid, type KpiStatItem } from '@/components/kpi-stat-grid' import { Badge } from '@/components/reui/badge' -import { formatDateTime, moduleIntervalLabel } from '@/lib/modules/display' -import { - communityLabel, - dohProfileLabel, - moduleDohProfileIds, -} from '@/lib/modules/helpers' -import { dohPolicyRu } from '@/lib/ui-labels' import { KpiStatGridSkeleton } from '@/components/skeletons' -import type { AsEntry, BgpCommunity, DohProfile, ModuleRow } from '@/types/api' +import { formatDateTime, moduleIntervalLabel } from '@/lib/modules/display' +import { communityLabel, moduleDohProfileIds } from '@/lib/modules/helpers' +import { dohPolicyRu } from '@/lib/ui-labels' +import type { AsEntry, BgpCommunity, ModuleRow } from '@/types/api' interface ModuleKpiCardsProps { mod: ModuleRow | null communities: BgpCommunity[] - dohProfiles: DohProfile[] asEntries: AsEntry[] + entriesCount?: number loading?: boolean } export function ModuleKpiCards({ mod, communities, - dohProfiles, asEntries, + entriesCount = 0, loading = false, }: ModuleKpiCardsProps) { if (loading || !mod) { @@ -33,6 +29,49 @@ export function ModuleKpiCards({ const asPrefixTotal = asEntries.reduce((acc, entry) => acc + (entry.prefix_count ?? 0), 0) const dohIds = moduleDohProfileIds(mod) + const isDomains = mod.type === 'DOMAINS' + const isAsPrefixes = mod.type === 'AS_PREFIXES' + const community = communityLabel(mod.default_community_id, communities) + + const entriesFooter = isAsPrefixes ? ( + + {asEntries.length} AS · в модуле + + ) : isDomains ? ( + + {mod.default_community_id ? community : 'без community'} + + ) : ( + + {entriesCount} · в модуле + + ) + + const policyItem: KpiStatItem = isDomains + ? { + id: 'doh', + icon: , + iconClassName: 'text-primary', + value: dohIds.length > 0 ? String(dohIds.length) : '—', + label: 'DoH', + footer: ( + 0 ? 'info-light' : 'outline'} size="sm"> + {dohIds.length > 0 ? dohPolicyRu(mod.doh_resolver_policy) : 'без DoH'} + + ), + } + : { + id: 'community', + icon: , + iconClassName: 'text-primary', + value: community, + label: 'Community', + footer: ( + + по умолчанию + + ), + } const items: KpiStatItem[] = [ { @@ -63,29 +102,11 @@ export function ModuleKpiCards({ id: 'prefixes', icon: , iconClassName: 'text-success', - value: mod.type === 'AS_PREFIXES' ? String(asPrefixTotal) : String(asEntries.length), - label: mod.type === 'AS_PREFIXES' ? 'Префиксы AS' : 'Записи модуля', - footer: ( - - {asEntries.length} AS · в модуле - - ), - }, - { - id: 'policy', - icon: , - iconClassName: 'text-primary', - value: dohIds.length > 0 ? String(dohIds.length) : '—', - label: communityLabel(mod.default_community_id, communities), - footer: ( - - {dohPolicyRu(mod.doh_resolver_policy)} - {dohIds.length > 0 - ? ` · ${dohIds.map((id) => dohProfileLabel(id, dohProfiles)).join(', ')}` - : ' · без DoH'} - - ), + value: isAsPrefixes ? String(asPrefixTotal) : String(entriesCount), + label: isAsPrefixes ? 'Префиксы AS' : 'Записи модуля', + footer: entriesFooter, }, + policyItem, ] return diff --git a/apps/web/src/lib/modules/helpers.ts b/apps/web/src/lib/modules/helpers.ts index b2c2f4b..353e1cb 100644 --- a/apps/web/src/lib/modules/helpers.ts +++ b/apps/web/src/lib/modules/helpers.ts @@ -36,6 +36,19 @@ export function dohProfileLabel(id: string, dohProfiles: DohProfile[]): string { return p ? (p.name?.trim() ? `${p.name} (${p.url})` : p.url) : `${id.slice(0, 8)}…` } +/** Short label for forms / chips — name preferred, else hostname from URL. */ +export function dohProfileShortLabel(id: string, dohProfiles: DohProfile[]): string { + const p = dohProfiles.find((d) => d.id === id) + if (!p) return `${id.slice(0, 8)}…` + const name = p.name?.trim() + if (name) return name + try { + return new URL(p.url).hostname + } catch { + return p.url + } +} + export function normalizeCdnSourceKind(k: string): 'plaintext' | 'json' { return k.trim().toLowerCase() === 'json' ? 'json' : 'plaintext' } diff --git a/apps/web/src/queries/modules.ts b/apps/web/src/queries/modules.ts index 828edf4..d0da4d2 100644 --- a/apps/web/src/queries/modules.ts +++ b/apps/web/src/queries/modules.ts @@ -1,6 +1,10 @@ -import { queryOptions } from '@tanstack/react-query' -import { apiJSON } from '@/lib/api-client' +import { queryOptions, useMutation, useQueryClient } from '@tanstack/react-query' +import { toast } from 'sonner' + +import { apiJSON, apiMutate } from '@/lib/api-client' +import { overviewKeys } from '@/queries/overview' import type { + ModulePatch, ModuleRow, ModulesResponse, Page, @@ -16,6 +20,14 @@ export const modulesKeys = { ipRangeEntries: (id: string) => [...modulesKeys.all, 'ip-range-entries', id] as const, } +function invalidateModules(qc: ReturnType, id?: string) { + void qc.invalidateQueries({ queryKey: modulesKeys.all }) + void qc.invalidateQueries({ queryKey: overviewKeys.modules() }) + if (id) { + void qc.invalidateQueries({ queryKey: modulesKeys.detail(id) }) + } +} + export function modulesListQueryOptions() { return queryOptions({ queryKey: modulesKeys.list(), @@ -51,3 +63,16 @@ export function moduleEntriesQueryOptions(id: string, type: ModuleRow['type']) { queryFn: () => apiJSON(path), }) } + +export function useUpdateModuleMutation() { + const qc = useQueryClient() + return useMutation({ + mutationFn: ({ id, body }: { id: string; body: ModulePatch }) => + apiMutate(`/v1/modules/${id}`, 'PATCH', body, { idempotent: false }), + onSuccess: (_data, vars) => { + toast.success('Модуль обновлён') + invalidateModules(qc, vars.id) + }, + onError: (e) => toast.error(e instanceof Error ? e.message : 'Не удалось обновить модуль'), + }) +} diff --git a/apps/web/src/routes/_auth/modules/$moduleId.tsx b/apps/web/src/routes/_auth/modules/$moduleId.tsx index 8750d4f..d1b20c6 100644 --- a/apps/web/src/routes/_auth/modules/$moduleId.tsx +++ b/apps/web/src/routes/_auth/modules/$moduleId.tsx @@ -1,16 +1,20 @@ import { createFileRoute, Link } from '@tanstack/react-router' import { useQuery, useQueryClient } from '@tanstack/react-query' -import { ArrowLeft, RefreshCw } from 'lucide-react' +import { ArrowLeft, Pencil, RefreshCw } from 'lucide-react' +import { useState } from 'react' import { Button } from '@evobgp/ui/components/button' +import { ModuleEditDialog } from '@/components/modules/module-edit-dialog' +import { ModuleEntriesSection } from '@/components/modules/module-entries-section' +import { ModuleKpiCards } from '@/components/modules/module-kpi-cards' import { PageHeader } from '@/components/page-header' import { QueryState } from '@/components/query-state' import { TableSkeleton } from '@/components/skeletons' import { StatusBadge } from '@/components/status-badge' -import { ModuleEntriesSection } from '@/components/modules/module-entries-section' -import { ModuleKpiCards } from '@/components/modules/module-kpi-cards' +import { sessionCanWriteModules } from '@/lib/auth' import { moduleTypeRu } from '@/lib/ui-labels' +import { authSessionQueryOptions } from '@/queries/auth' import { directoriesCommunitiesQueryOptions, directoriesDohQueryOptions, @@ -25,8 +29,12 @@ export const Route = createFileRoute('/_auth/modules/$moduleId')({ function ModuleDetailComponent() { const { moduleId } = Route.useParams() const queryClient = useQueryClient() + const [editOpen, setEditOpen] = useState(false) + const detail = useQuery(moduleDetailQueryOptions(moduleId)) const mod = detail.data + const sessionQ = useQuery(authSessionQueryOptions()) + const canWrite = sessionCanWriteModules(sessionQ.data) const communitiesQ = useQuery(directoriesCommunitiesQueryOptions()) const dohQ = useQuery(directoriesDohQueryOptions()) @@ -57,6 +65,7 @@ function ModuleDetailComponent() { const communities = communitiesQ.data?.items ?? [] const dohProfiles = dohQ.data?.items ?? [] const asEntries = mod?.type === 'AS_PREFIXES' ? (asEntriesQ.data ?? []) : [] + const entriesCount = entriesQuery.data?.items.length ?? 0 return (
@@ -69,6 +78,12 @@ function ModuleDetailComponent() { К списку + {canWrite && mod ? ( + + ) : null}
)} diff --git a/apps/web/tsconfig.tsbuildinfo b/apps/web/tsconfig.tsbuildinfo index 0d8d3eb..66fddfb 100644 --- a/apps/web/tsconfig.tsbuildinfo +++ b/apps/web/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/main.tsx","./src/routetree.gen.ts","./src/vite-env.d.ts","./src/components/app-switcher.tsx","./src/components/badge-tabs.tsx","./src/components/category-badge.tsx","./src/components/confirm-dialog.tsx","./src/components/counted-line-tabs.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/kpi-stat-grid.tsx","./src/components/loading-button.tsx","./src/components/page-header.tsx","./src/components/page-shell.tsx","./src/components/panel-card.tsx","./src/components/query-state.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/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-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-2.tsx","./src/components/examples/c-tabs-6.tsx","./src/components/examples/c-tabs-7.tsx","./src/components/layout/app-shell.tsx","./src/components/layout/apps-menu.tsx","./src/components/layout/command-palette.tsx","./src/components/layout/nav-user.tsx","./src/components/layout/system-monitor-popover.tsx","./src/components/lookup/lookup-add-step.tsx","./src/components/lookup/lookup-matches-grid.tsx","./src/components/lookup/lookup-search-form.tsx","./src/components/lookup/lookup-status-banner.tsx","./src/components/lookup/lookup-wizard.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-discovered-peers-card.tsx","./src/components/network/network-kpi.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/alert.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/stepper.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/reui-kit/detail-panel.tsx","./src/components/reui-kit/filter-utils.ts","./src/components/reui-kit/frame-data-grid.tsx","./src/components/reui-kit/index.ts","./src/components/reui-kit/kpi-cols.ts","./src/components/reui-kit/kpi-stat-grid.tsx","./src/components/reui-kit/ops-dashboard.tsx","./src/components/reui-kit/quick-action-grid.tsx","./src/components/reui-kit/resource-page.tsx","./src/components/reui-kit/settings-shell.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/appearance-settings-tab.tsx","./src/components/settings/connection-settings-tab.tsx","./src/components/settings/sections-settings-tab.tsx","./src/components/settings/session-settings-tab.tsx","./src/components/settings/settings-kv-grid.tsx","./src/components/settings/settings-page-shell.tsx","./src/components/settings/settings-setting-field.tsx","./src/components/settings/settings-tabs-data.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-app-switcher.ts","./src/hooks/use-client-data-grid.ts","./src/hooks/use-copy-to-clipboard.ts","./src/hooks/use-file-upload.ts","./src/hooks/use-mobile.ts","./src/lib/api-client.test.ts","./src/lib/api-client.ts","./src/lib/app-switcher-config.ts","./src/lib/auth.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/app-switcher.ts","./src/queries/auth.ts","./src/queries/directories.ts","./src/queries/lookup.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/auth.callback.tsx","./src/routes/index.tsx","./src/routes/_auth/access.tsx","./src/routes/_auth/dashboard.tsx","./src/routes/_auth/directories.tsx","./src/routes/_auth/lookup.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.gen.ts","./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/vite-env.d.ts","./src/components/app-switcher.tsx","./src/components/badge-tabs.tsx","./src/components/category-badge.tsx","./src/components/confirm-dialog.tsx","./src/components/counted-line-tabs.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/kpi-stat-grid.tsx","./src/components/loading-button.tsx","./src/components/page-header.tsx","./src/components/page-shell.tsx","./src/components/panel-card.tsx","./src/components/query-state.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/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-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-2.tsx","./src/components/examples/c-tabs-6.tsx","./src/components/examples/c-tabs-7.tsx","./src/components/layout/app-shell.tsx","./src/components/layout/apps-menu.tsx","./src/components/layout/command-palette.tsx","./src/components/layout/nav-user.tsx","./src/components/layout/system-monitor-popover.tsx","./src/components/lookup/lookup-add-step.tsx","./src/components/lookup/lookup-matches-grid.tsx","./src/components/lookup/lookup-search-form.tsx","./src/components/lookup/lookup-status-banner.tsx","./src/components/lookup/lookup-wizard.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-edit-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-discovered-peers-card.tsx","./src/components/network/network-kpi.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/alert.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/stepper.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/reui-kit/detail-panel.tsx","./src/components/reui-kit/filter-utils.ts","./src/components/reui-kit/frame-data-grid.tsx","./src/components/reui-kit/index.ts","./src/components/reui-kit/kpi-cols.ts","./src/components/reui-kit/kpi-stat-grid.tsx","./src/components/reui-kit/ops-dashboard.tsx","./src/components/reui-kit/quick-action-grid.tsx","./src/components/reui-kit/resource-page.tsx","./src/components/reui-kit/settings-shell.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/appearance-settings-tab.tsx","./src/components/settings/connection-settings-tab.tsx","./src/components/settings/sections-settings-tab.tsx","./src/components/settings/session-settings-tab.tsx","./src/components/settings/settings-kv-grid.tsx","./src/components/settings/settings-page-shell.tsx","./src/components/settings/settings-setting-field.tsx","./src/components/settings/settings-tabs-data.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-app-switcher.ts","./src/hooks/use-client-data-grid.ts","./src/hooks/use-copy-to-clipboard.ts","./src/hooks/use-file-upload.ts","./src/hooks/use-mobile.ts","./src/lib/api-client.test.ts","./src/lib/api-client.ts","./src/lib/app-switcher-config.ts","./src/lib/auth.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/app-switcher.ts","./src/queries/auth.ts","./src/queries/directories.ts","./src/queries/lookup.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/auth.callback.tsx","./src/routes/index.tsx","./src/routes/_auth/access.tsx","./src/routes/_auth/dashboard.tsx","./src/routes/_auth/directories.tsx","./src/routes/_auth/lookup.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.gen.ts","./src/types/api.ts","./vite.config.ts"],"version":"5.9.3"} \ No newline at end of file