@camstack/addon-admin-ui 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/dist/assets/index-BoVZEQ1j.js +598 -0
  2. package/dist/assets/index-DwSc8ann.css +1 -0
  3. package/{index.html → dist/index.html} +3 -1
  4. package/dist/server/addon.d.ts +11 -0
  5. package/dist/server/addon.js +50 -0
  6. package/dist/server/addon.js.map +1 -0
  7. package/package.json +4 -1
  8. package/src/App.tsx +0 -71
  9. package/src/components/addons/AddonCard.tsx +0 -355
  10. package/src/components/addons/AddonUploadZone.tsx +0 -69
  11. package/src/components/addons/CapabilityBadge.tsx +0 -55
  12. package/src/components/addons/CapabilityMap.tsx +0 -133
  13. package/src/components/addons/UpdatesList.tsx +0 -108
  14. package/src/components/agents/AgentCard.tsx +0 -281
  15. package/src/components/agents/AgentLogs.tsx +0 -231
  16. package/src/components/agents/ProcessList.tsx +0 -127
  17. package/src/components/agents/ProcessTree.tsx +0 -369
  18. package/src/components/agents/TaskList.tsx +0 -68
  19. package/src/components/cameras/CameraCard.tsx +0 -60
  20. package/src/components/cameras/LiveEventsPanel.tsx +0 -91
  21. package/src/components/cameras/ProviderSection.tsx +0 -50
  22. package/src/components/cameras/StreamArea.tsx +0 -107
  23. package/src/components/cameras/tabs/AddonsTab.tsx +0 -113
  24. package/src/components/cameras/tabs/CameraEventsTab.tsx +0 -129
  25. package/src/components/cameras/tabs/PipelineTab.tsx +0 -118
  26. package/src/components/cameras/tabs/StreamsTab.tsx +0 -114
  27. package/src/components/dashboard/BlockPicker.tsx +0 -54
  28. package/src/components/dashboard/BlockWrapper.tsx +0 -97
  29. package/src/components/dashboard/DashboardGrid.tsx +0 -160
  30. package/src/components/dashboard/block-registry.ts +0 -15
  31. package/src/components/dashboard/blocks/PipelineStagesBlock.tsx +0 -39
  32. package/src/components/dashboard/blocks/StorageBlock.tsx +0 -66
  33. package/src/components/dashboard/blocks/SystemStatusBlock.tsx +0 -67
  34. package/src/components/dashboard/blocks/index.ts +0 -32
  35. package/src/components/device/DeviceHeader.tsx +0 -116
  36. package/src/components/device/FloatingPanel.tsx +0 -132
  37. package/src/components/device/FloatingPanelManager.tsx +0 -167
  38. package/src/components/device/PanelContent.tsx +0 -196
  39. package/src/components/device/QuickConfigWizard.tsx +0 -507
  40. package/src/components/device/tabs/DetectionConfigTab.tsx +0 -96
  41. package/src/components/device/tabs/EventsTab.tsx +0 -19
  42. package/src/components/device/tabs/LogsTab.tsx +0 -22
  43. package/src/components/device/tabs/OverviewTab.tsx +0 -104
  44. package/src/components/device/tabs/ProviderSettingsTab.tsx +0 -34
  45. package/src/components/device/tabs/RecordingTab.tsx +0 -47
  46. package/src/components/device/tabs/ReplTab.tsx +0 -153
  47. package/src/components/device/tabs/TrackTrailTab.tsx +0 -49
  48. package/src/components/device/tabs/ZonesTab.tsx +0 -98
  49. package/src/components/device/zone-editor/ZoneCanvas.tsx +0 -354
  50. package/src/components/device/zone-editor/ZoneForm.tsx +0 -128
  51. package/src/components/device/zone-editor/ZoneList.tsx +0 -150
  52. package/src/components/form-builder/FormBuilder.tsx +0 -135
  53. package/src/components/form-builder/FormField.tsx +0 -732
  54. package/src/components/form-builder/ModelSelector.tsx +0 -239
  55. package/src/components/integrations/AddDeviceDialog.tsx +0 -205
  56. package/src/components/integrations/CompactDeviceCard.tsx +0 -35
  57. package/src/components/integrations/DeviceCard.tsx +0 -29
  58. package/src/components/integrations/DeviceDiscoveryStep.tsx +0 -105
  59. package/src/components/integrations/DeviceGrid.tsx +0 -79
  60. package/src/components/integrations/DeviceGroupHeader.tsx +0 -17
  61. package/src/components/integrations/DiscoveredDeviceCard.tsx +0 -26
  62. package/src/components/integrations/IntegrationCard.tsx +0 -40
  63. package/src/components/integrations/IntegrationWizard.tsx +0 -172
  64. package/src/components/integrations/ProviderConfigForm.tsx +0 -89
  65. package/src/components/integrations/ProviderPicker.tsx +0 -91
  66. package/src/components/integrations/SnapshotPopover.tsx +0 -68
  67. package/src/components/metrics/AgentLoad.tsx +0 -105
  68. package/src/components/metrics/IntegrationUsage.tsx +0 -73
  69. package/src/components/metrics/PipelineStatus.tsx +0 -74
  70. package/src/components/metrics/ProcessResources.tsx +0 -123
  71. package/src/components/pipeline/PhaseSettings.tsx +0 -131
  72. package/src/components/shared/CapabilityBadges.tsx +0 -30
  73. package/src/components/shared/ProviderIcon.tsx +0 -42
  74. package/src/components/shared/StatusBadge.tsx +0 -23
  75. package/src/components/shared/WebRtcPlayer.tsx +0 -211
  76. package/src/components/timeline/EventMarker.tsx +0 -32
  77. package/src/components/timeline/TimelineBar.tsx +0 -131
  78. package/src/components/ui/ConfirmDialog.tsx +0 -115
  79. package/src/components/ui/ToastContainer.tsx +0 -92
  80. package/src/contexts/auth-context.tsx +0 -91
  81. package/src/hooks/useBackendClient.ts +0 -6
  82. package/src/hooks/useTheme.ts +0 -1
  83. package/src/i18n/en.json +0 -164
  84. package/src/i18n/index.ts +0 -29
  85. package/src/i18n/it.json +0 -164
  86. package/src/index.css +0 -63
  87. package/src/layouts/AddonPageLoader.tsx +0 -120
  88. package/src/layouts/AppLayout.tsx +0 -254
  89. package/src/layouts/ProtectedRoute.tsx +0 -25
  90. package/src/lib/addon-page-context.ts +0 -29
  91. package/src/lib/backend.ts +0 -16
  92. package/src/main.tsx +0 -21
  93. package/src/pages/AccessDenied.tsx +0 -22
  94. package/src/pages/Cameras.tsx +0 -127
  95. package/src/pages/Dashboard.tsx +0 -6
  96. package/src/pages/DeviceDetail.tsx +0 -175
  97. package/src/pages/IntegrationDetail.tsx +0 -222
  98. package/src/pages/Integrations.tsx +0 -333
  99. package/src/pages/Login.tsx +0 -106
  100. package/src/pages/Metrics.tsx +0 -18
  101. package/src/pages/PipelineConfig.tsx +0 -282
  102. package/src/pages/Showroom.tsx +0 -351
  103. package/src/pages/Timeline.tsx +0 -269
  104. package/src/pages/system/Addons.tsx +0 -396
  105. package/src/pages/system/Agents.tsx +0 -362
  106. package/src/pages/system/Logs.tsx +0 -131
  107. package/src/pages/system/Models.tsx +0 -102
  108. package/src/pages/system/Processes.tsx +0 -129
  109. package/src/pages/system/Repl.tsx +0 -148
  110. package/src/pages/system/Settings.tsx +0 -168
  111. package/src/pages/system/Users.tsx +0 -174
  112. package/src/server/addon.ts +0 -54
  113. package/src/types/config-ui.ts +0 -28
  114. package/src/types/dashboard.ts +0 -39
  115. package/tsconfig.json +0 -29
  116. package/tsconfig.server.json +0 -16
  117. package/tsup.config.ts +0 -20
  118. package/vite.config.ts +0 -68
  119. /package/{public → dist}/brand/logo-dark.svg +0 -0
  120. /package/{public → dist}/brand/logo-horizontal-dark.svg +0 -0
  121. /package/{public → dist}/brand/logo-horizontal-light.svg +0 -0
  122. /package/{public → dist}/brand/logo-light.svg +0 -0
  123. /package/{public → dist}/brand/logo-wide-dark.svg +0 -0
  124. /package/{public → dist}/brand/logo-wide-light.svg +0 -0
  125. /package/{public → dist}/favicon.svg +0 -0
  126. /package/{public → dist}/vendor/react-jsx-runtime.mjs +0 -0
  127. /package/{public → dist}/vendor/react.mjs +0 -0
@@ -1,362 +0,0 @@
1
- import { useState, useMemo } from "react";
2
- import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
3
- import {
4
- Bot,
5
- Cpu,
6
- ListTodo,
7
- WifiOff,
8
- TreePine,
9
- ScrollText,
10
- X,
11
- FlaskConical,
12
- Square,
13
- } from "lucide-react";
14
- import { useBackendClient } from "../../hooks/useBackendClient";
15
- import { ProcessTree } from "../../components/agents/ProcessTree";
16
- import { AgentLogs } from "../../components/agents/AgentLogs";
17
- import type {
18
- ProcessTreeEntry,
19
- SubProcess,
20
- } from "../../components/agents/ProcessTree";
21
-
22
- // ---------------------------------------------------------------------------
23
- // Helpers
24
- // ---------------------------------------------------------------------------
25
-
26
- function normaliseProcessTreeEntry(
27
- raw: Record<string, unknown>,
28
- ): ProcessTreeEntry {
29
- const info = (raw.info ?? raw) as Record<string, unknown>;
30
- const status = (raw.status ?? {}) as Record<string, unknown>;
31
- return {
32
- id: String(info.id ?? raw.id ?? ""),
33
- name: String(info.name ?? raw.name ?? ""),
34
- pid: Number(raw.pid ?? 0),
35
- state: String(raw.state ?? "running"),
36
- cpuPercent: Number(status.cpuPercent ?? raw.cpuPercent ?? 0),
37
- memoryPercent: Number(status.memoryPercent ?? raw.memoryPercent ?? 0),
38
- memoryMB: Number(info.memoryMB ?? raw.memoryMB ?? 0),
39
- isHub: Boolean(raw.isHub ?? false),
40
- platform: String(info.platform ?? raw.platform ?? ""),
41
- arch: String(info.arch ?? raw.arch ?? ""),
42
- host: String(info.host ?? raw.host ?? ""),
43
- capabilities:
44
- (info.capabilities as string[]) ?? (raw.capabilities as string[]) ?? [],
45
- installedAddons:
46
- (info.installedAddons as string[]) ??
47
- (raw.installedAddons as string[]) ??
48
- [],
49
- pythonRuntimes:
50
- (info.pythonRuntimes as string[]) ??
51
- (raw.pythonRuntimes as string[]) ??
52
- [],
53
- connectedSince: Number(raw.connectedSince ?? Date.now()),
54
- subProcesses: normaliseSubProcesses(raw.subProcesses),
55
- };
56
- }
57
-
58
- function normaliseSubProcesses(raw: unknown): SubProcess[] {
59
- if (!Array.isArray(raw)) return [];
60
- return raw.map((p: Record<string, unknown>) => ({
61
- pid: Number(p.pid ?? 0),
62
- name: String(p.name ?? ""),
63
- command: String(p.command ?? ""),
64
- state: (p.state as SubProcess["state"]) ?? "running",
65
- cpuPercent: Number(p.cpuPercent ?? 0),
66
- memoryRss: Number(p.memoryRss ?? 0),
67
- uptimeSeconds: Number(p.uptimeSeconds ?? 0),
68
- }));
69
- }
70
-
71
- // ---------------------------------------------------------------------------
72
- // Stat Card
73
- // ---------------------------------------------------------------------------
74
-
75
- function StatCard({
76
- icon: Icon,
77
- label,
78
- value,
79
- color,
80
- }: {
81
- icon: React.ElementType;
82
- label: string;
83
- value: number | string;
84
- color: string;
85
- }) {
86
- return (
87
- <div className="rounded-xl border border-border bg-surface px-4 py-3 flex items-center gap-3">
88
- <div
89
- className={`flex-shrink-0 h-9 w-9 rounded-lg flex items-center justify-center ${color}`}
90
- >
91
- <Icon className="h-4 w-4" />
92
- </div>
93
- <div>
94
- <div className="text-xl font-bold text-foreground leading-none">
95
- {value}
96
- </div>
97
- <div className="text-[10px] text-foreground-subtle mt-0.5">{label}</div>
98
- </div>
99
- </div>
100
- );
101
- }
102
-
103
- // ---------------------------------------------------------------------------
104
- // Tab selector
105
- // ---------------------------------------------------------------------------
106
-
107
- type Tab = "tree" | "logs";
108
-
109
- function TabButton({
110
- tab,
111
- active,
112
- icon: Icon,
113
- label,
114
- onClick,
115
- }: {
116
- tab: Tab;
117
- active: boolean;
118
- icon: React.ElementType;
119
- label: string;
120
- onClick: () => void;
121
- }) {
122
- return (
123
- <button
124
- type="button"
125
- onClick={onClick}
126
- className={`inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-medium transition-colors ${
127
- active
128
- ? "bg-primary/10 text-primary border border-primary/30"
129
- : "bg-surface text-foreground-subtle border border-border hover:text-foreground hover:bg-primary/5"
130
- }`}
131
- >
132
- <Icon className="h-3.5 w-3.5" />
133
- {label}
134
- </button>
135
- );
136
- }
137
-
138
- // ---------------------------------------------------------------------------
139
- // Page
140
- // ---------------------------------------------------------------------------
141
-
142
- export function AgentsPage() {
143
- const client = useBackendClient();
144
- const queryClient = useQueryClient();
145
- const [activeTab, setActiveTab] = useState<Tab>("tree");
146
- const [selectedAgentName, setSelectedAgentName] = useState<string | null>(
147
- null,
148
- );
149
-
150
- // Test worker state
151
- const { data: testWorkerStatus } = useQuery({
152
- queryKey: ["agents", "testWorker", "status"],
153
- queryFn: () => client.trpc.agents.isTestWorkerRunning.query(),
154
- refetchInterval: 5000,
155
- });
156
- const testWorkerRunning = testWorkerStatus?.running ?? false;
157
-
158
- const startTestWorker = useMutation({
159
- mutationFn: () => client.trpc.agents.startTestWorker.mutate(),
160
- onSuccess: () => {
161
- queryClient.invalidateQueries({ queryKey: ["agents"] });
162
- queryClient.invalidateQueries({ queryKey: ["agent"] });
163
- },
164
- });
165
-
166
- const stopTestWorker = useMutation({
167
- mutationFn: () => client.trpc.agents.stopTestWorker.mutate(),
168
- onSuccess: () => {
169
- queryClient.invalidateQueries({ queryKey: ["agents"] });
170
- queryClient.invalidateQueries({ queryKey: ["agent"] });
171
- },
172
- });
173
-
174
- // Fetch process tree via the agent.processTree endpoint
175
- const { data: rawProcessTree = [], isLoading: loadingTree } = useQuery({
176
- queryKey: ["agent", "processTree"],
177
- queryFn: () => client.trpc.agent.processTree.query(),
178
- refetchInterval: 5000,
179
- });
180
-
181
- // Also fetch the classic agents list for stats
182
- const { data: rawAgents = [] } = useQuery({
183
- queryKey: ["agents", "list"],
184
- queryFn: () => client.listAgents(),
185
- refetchInterval: 5000,
186
- });
187
-
188
- const treeEntries: ProcessTreeEntry[] = useMemo(
189
- () =>
190
- (rawProcessTree as unknown as Record<string, unknown>[]).map(
191
- normaliseProcessTreeEntry,
192
- ),
193
- [rawProcessTree],
194
- );
195
-
196
- // Compute stats
197
- const agentCount = treeEntries.length;
198
- const totalSubProcesses = treeEntries.reduce(
199
- (sum, e) => sum + e.subProcesses.length,
200
- 0,
201
- );
202
- const totalAddons = treeEntries.reduce(
203
- (sum, e) => sum + e.installedAddons.length,
204
- 0,
205
- );
206
-
207
- // Build filter options for logs
208
- const agentNames = useMemo(
209
- () => treeEntries.filter((e) => !e.isHub).map((e) => e.name),
210
- [treeEntries],
211
- );
212
- const addonNames = useMemo(() => {
213
- const set = new Set<string>();
214
- for (const entry of treeEntries) {
215
- for (const addon of entry.installedAddons) {
216
- set.add(addon);
217
- }
218
- }
219
- return [...set].sort();
220
- }, [treeEntries]);
221
-
222
- return (
223
- <div className="p-6 space-y-6">
224
- {/* Header */}
225
- <div className="flex items-start justify-between gap-4">
226
- <div>
227
- <h1 className="text-lg font-semibold text-foreground">
228
- Agents &amp; Process Tree
229
- </h1>
230
- <p className="text-xs text-foreground-subtle mt-0.5">
231
- Distributed processing agents, sub-processes, and system logs.
232
- </p>
233
- </div>
234
- <div className="shrink-0">
235
- {testWorkerRunning ? (
236
- <button
237
- type="button"
238
- onClick={() => stopTestWorker.mutate()}
239
- disabled={stopTestWorker.isPending}
240
- className="inline-flex items-center gap-1.5 rounded-lg border border-danger/30 bg-danger/10 text-danger px-3 py-1.5 text-xs font-medium hover:bg-danger/20 transition-colors disabled:opacity-50"
241
- >
242
- <Square className="h-3.5 w-3.5" />
243
- {stopTestWorker.isPending ? "Stopping..." : "Stop Test Agent"}
244
- </button>
245
- ) : (
246
- <button
247
- type="button"
248
- onClick={() => startTestWorker.mutate()}
249
- disabled={startTestWorker.isPending}
250
- className="inline-flex items-center gap-1.5 rounded-lg border border-border bg-surface text-foreground-subtle px-3 py-1.5 text-xs font-medium hover:text-foreground hover:bg-primary/5 transition-colors disabled:opacity-50"
251
- >
252
- <FlaskConical className="h-3.5 w-3.5" />
253
- {startTestWorker.isPending ? "Starting..." : "Start Test Agent"}
254
- </button>
255
- )}
256
- </div>
257
- </div>
258
-
259
- {/* Overview bar */}
260
- <div className="grid grid-cols-1 sm:grid-cols-4 gap-3">
261
- <StatCard
262
- icon={Bot}
263
- label="Agents online"
264
- value={agentCount}
265
- color="bg-green-500/10 text-green-400"
266
- />
267
- <StatCard
268
- icon={Cpu}
269
- label="Sub-processes"
270
- value={totalSubProcesses}
271
- color="bg-blue-500/10 text-blue-400"
272
- />
273
- <StatCard
274
- icon={ListTodo}
275
- label="Installed addons"
276
- value={totalAddons}
277
- color="bg-purple-500/10 text-purple-400"
278
- />
279
- <StatCard
280
- icon={Bot}
281
- label="Capabilities"
282
- value={
283
- [...new Set(treeEntries.flatMap((e) => e.capabilities))].length
284
- }
285
- color="bg-cyan-500/10 text-cyan-400"
286
- />
287
- </div>
288
-
289
- {/* Tabs */}
290
- <div className="flex items-center gap-2">
291
- <TabButton
292
- tab="tree"
293
- active={activeTab === "tree"}
294
- icon={TreePine}
295
- label="Process Tree"
296
- onClick={() => setActiveTab("tree")}
297
- />
298
- <TabButton
299
- tab="logs"
300
- active={activeTab === "logs"}
301
- icon={ScrollText}
302
- label="Logs"
303
- onClick={() => setActiveTab("logs")}
304
- />
305
- </div>
306
-
307
- {/* Tab content */}
308
- {activeTab === "tree" && (
309
- <>
310
- {agentCount === 0 && !loadingTree && (
311
- <div className="flex flex-col items-center justify-center rounded-xl border border-border bg-surface py-12 text-foreground-subtle">
312
- <WifiOff className="h-8 w-8 mb-2 opacity-40" />
313
- <div className="text-sm">No agents connected</div>
314
- <div className="text-xs mt-1 opacity-70">
315
- Run{" "}
316
- <code className="font-mono bg-border px-1 rounded">
317
- camstack --agent --hub wss://&lt;host&gt;:4443
318
- </code>{" "}
319
- to connect one.
320
- </div>
321
- </div>
322
- )}
323
- <ProcessTree
324
- entries={treeEntries}
325
- isLoading={loadingTree}
326
- selectedAgentName={selectedAgentName}
327
- onSelectAgent={(name) => {
328
- setSelectedAgentName(name);
329
- setActiveTab("logs");
330
- }}
331
- />
332
- </>
333
- )}
334
-
335
- {activeTab === "logs" && (
336
- <>
337
- {selectedAgentName && (
338
- <div className="flex items-center gap-2 text-xs">
339
- <span className="text-foreground-subtle">Filtered to agent:</span>
340
- <span className="font-semibold text-foreground">
341
- {selectedAgentName}
342
- </span>
343
- <button
344
- type="button"
345
- onClick={() => setSelectedAgentName(null)}
346
- className="inline-flex items-center gap-1 rounded-lg border border-border bg-surface px-2 py-1 text-xs text-foreground-subtle hover:text-foreground hover:bg-primary/5 transition-colors"
347
- >
348
- <X className="h-3 w-3" />
349
- Clear selection
350
- </button>
351
- </div>
352
- )}
353
- <AgentLogs
354
- agentNames={agentNames}
355
- addonNames={addonNames}
356
- preselectedAgent={selectedAgentName ?? undefined}
357
- />
358
- </>
359
- )}
360
- </div>
361
- );
362
- }
@@ -1,131 +0,0 @@
1
- import { useState, useEffect, useRef, useMemo } from 'react'
2
- import { useQuery } from '@tanstack/react-query'
3
- import { Pause, Play } from 'lucide-react'
4
- import { useBackendClient } from '../../hooks/useBackendClient'
5
-
6
- type LogLevel = 'all' | 'debug' | 'info' | 'warn' | 'error'
7
-
8
- const LEVEL_STYLES: Record<string, { badge: string; text: string }> = {
9
- debug: { badge: 'bg-foreground-subtle/10 text-foreground-subtle', text: 'text-foreground-subtle' },
10
- info: { badge: 'bg-info/10 text-info', text: 'text-info' },
11
- warn: { badge: 'bg-warning/10 text-warning', text: 'text-warning' },
12
- error: { badge: 'bg-danger/10 text-danger', text: 'text-danger' },
13
- }
14
-
15
- export function LogsPage() {
16
- const client = useBackendClient()
17
- const [levelFilter, setLevelFilter] = useState<LogLevel>('all')
18
- const [paused, setPaused] = useState(false)
19
- const topRef = useRef<HTMLDivElement>(null)
20
-
21
- const { data: logsData, isLoading, isError } = useQuery({
22
- queryKey: ['logs', levelFilter],
23
- queryFn: () =>
24
- client.getLogs(
25
- levelFilter !== 'all'
26
- ? { level: levelFilter as 'debug' | 'info' | 'warn' | 'error', limit: 500 }
27
- : { limit: 500 },
28
- ),
29
- refetchInterval: paused ? false : 3000,
30
- })
31
-
32
- const rawLogs = (logsData ?? []) as unknown as Array<Record<string, unknown>>
33
-
34
- // Reverse so newest logs appear at the top
35
- const logs = useMemo(() => [...rawLogs].reverse(), [rawLogs])
36
-
37
- useEffect(() => {
38
- if (!paused) {
39
- topRef.current?.scrollIntoView({ behavior: 'smooth' })
40
- }
41
- }, [logs, paused])
42
-
43
- function formatTs(ts: unknown): string {
44
- if (!ts) return '—'
45
- const d = new Date(typeof ts === 'number' ? ts : String(ts))
46
- return d.toLocaleTimeString('en-GB', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit', fractionalSecondDigits: 3 })
47
- }
48
-
49
- return (
50
- <div className="p-6 space-y-4">
51
- <div className="flex items-center justify-between">
52
- <h1 className="text-lg font-semibold text-foreground">Logs</h1>
53
- <div className="flex items-center gap-2">
54
- <select
55
- value={levelFilter}
56
- onChange={(e) => setLevelFilter(e.target.value as LogLevel)}
57
- className="rounded-lg border border-border bg-surface text-xs text-foreground px-2 py-1.5 focus:outline-none focus:ring-1 focus:ring-primary"
58
- >
59
- <option value="all">All levels</option>
60
- <option value="debug">Debug</option>
61
- <option value="info">Info</option>
62
- <option value="warn">Warn</option>
63
- <option value="error">Error</option>
64
- </select>
65
- <button
66
- onClick={() => setPaused((p) => !p)}
67
- className={`inline-flex items-center gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs font-medium transition-colors ${
68
- paused
69
- ? 'border-primary/30 bg-primary/10 text-primary hover:bg-primary/20'
70
- : 'border-border bg-surface text-foreground-subtle hover:text-foreground'
71
- }`}
72
- >
73
- {paused ? <Play className="h-3.5 w-3.5" /> : <Pause className="h-3.5 w-3.5" />}
74
- {paused ? 'Resume' : 'Pause'}
75
- </button>
76
- </div>
77
- </div>
78
-
79
- {isLoading && (
80
- <div className="text-xs text-foreground-subtle animate-pulse">Loading...</div>
81
- )}
82
-
83
- {isError && (
84
- <div className="text-xs text-danger">Failed to load</div>
85
- )}
86
-
87
- {!isLoading && !isError && logs.length === 0 && (
88
- <div className="text-xs text-foreground-subtle">No data</div>
89
- )}
90
-
91
- {logs.length > 0 && (
92
- <div className="rounded-lg border border-border bg-surface overflow-hidden">
93
- <div className="max-h-[calc(100vh-220px)] overflow-y-auto">
94
- <div ref={topRef} />
95
- <table className="w-full text-xs">
96
- <thead className="sticky top-0">
97
- <tr>
98
- <th className="text-left px-3 py-2 text-foreground-subtle font-medium bg-surface border-b border-border w-36">Time</th>
99
- <th className="text-left px-3 py-2 text-foreground-subtle font-medium bg-surface border-b border-border w-16">Level</th>
100
- <th className="text-left px-3 py-2 text-foreground-subtle font-medium bg-surface border-b border-border">Message</th>
101
- </tr>
102
- </thead>
103
- <tbody>
104
- {logs.map((entry, i) => {
105
- const level = String(entry.level ?? 'info').toLowerCase()
106
- const style = LEVEL_STYLES[level] ?? LEVEL_STYLES['info']!
107
- const message = String(entry.message ?? entry.msg ?? entry.text ?? '')
108
- return (
109
- <tr key={i} className="hover:bg-primary/5">
110
- <td className="px-3 py-1.5 text-foreground-subtle border-b border-border font-mono whitespace-nowrap">
111
- {formatTs(entry.timestamp ?? entry.ts ?? entry.time)}
112
- </td>
113
- <td className="px-3 py-1.5 border-b border-border">
114
- <span className={`inline-flex rounded-full px-2 py-0.5 text-[10px] font-medium uppercase ${style.badge}`}>
115
- {level}
116
- </span>
117
- </td>
118
- <td className={`px-3 py-1.5 border-b border-border font-mono ${style.text} break-all`}>
119
- {message}
120
- </td>
121
- </tr>
122
- )
123
- })}
124
- </tbody>
125
- </table>
126
- </div>
127
- </div>
128
- )}
129
- </div>
130
- )
131
- }
@@ -1,102 +0,0 @@
1
- import { useQuery } from '@tanstack/react-query'
2
- import { Info, Package } from 'lucide-react'
3
- import { useBackendClient } from '../../hooks/useBackendClient'
4
-
5
- // ---------------------------------------------------------------------------
6
- // Types
7
- // ---------------------------------------------------------------------------
8
-
9
- interface AddonInfo {
10
- id: string
11
- name?: string
12
- description?: string
13
- enabled?: boolean
14
- [key: string]: unknown
15
- }
16
-
17
- // ---------------------------------------------------------------------------
18
- // Main page
19
- // ---------------------------------------------------------------------------
20
-
21
- export function ModelsPage() {
22
- const client = useBackendClient()
23
-
24
- const { data: addons = [], isLoading } = useQuery<AddonInfo[]>({
25
- queryKey: ['models', 'bridge-addons'],
26
- queryFn: async () => {
27
- const result = await client.bridgeListAddons()
28
- return (result as AddonInfo[]) ?? []
29
- },
30
- })
31
-
32
- const visionAddons = addons.filter(
33
- (a) => a.id?.includes('vision') || a.id?.includes('detection') || a.id?.includes('model'),
34
- )
35
-
36
- return (
37
- <div className="p-6 space-y-6 max-w-5xl mx-auto">
38
- {/* Header */}
39
- <div className="flex items-start justify-between">
40
- <div>
41
- <h1 className="text-lg font-bold text-foreground">Models</h1>
42
- <p className="text-xs text-foreground-subtle mt-0.5">
43
- ML model management for detection and recognition.
44
- </p>
45
- </div>
46
- </div>
47
-
48
- {/* Info banner */}
49
- <div className="rounded-lg border border-info/30 bg-info/5 px-4 py-3 flex items-start gap-3">
50
- <Info className="h-4 w-4 text-info shrink-0 mt-0.5" />
51
- <div className="text-xs text-foreground leading-relaxed">
52
- <p className="font-medium mb-1">Model management has moved to addon settings</p>
53
- <p className="text-foreground-subtle">
54
- ML models are now managed through individual vision addons. Go to{' '}
55
- <span className="font-medium text-foreground">System &rarr; Addons</span> and configure the
56
- vision/detection addon to download and manage models.
57
- </p>
58
- </div>
59
- </div>
60
-
61
- {/* Related addons list */}
62
- <div className="rounded-lg border border-border overflow-hidden">
63
- <div className="px-4 py-2.5 border-b border-border bg-surface/40 flex items-center">
64
- <h2 className="text-xs font-semibold text-foreground uppercase tracking-wide">Related Addons</h2>
65
- </div>
66
-
67
- {isLoading ? (
68
- <div className="px-4 py-8 text-center text-xs text-foreground-subtle">Loading addons...</div>
69
- ) : visionAddons.length === 0 ? (
70
- <div className="px-4 py-8 text-center text-xs text-foreground-subtle">
71
- {addons.length === 0
72
- ? 'No addons registered. Install a vision addon to enable model management.'
73
- : 'No vision/detection addons found. Install one to manage ML models.'}
74
- </div>
75
- ) : (
76
- <div className="divide-y divide-border/50">
77
- {visionAddons.map((addon) => (
78
- <div key={addon.id} className="px-4 py-3 flex items-center gap-3 hover:bg-surface/30 transition-colors">
79
- <Package className="h-4 w-4 text-foreground-subtle shrink-0" />
80
- <div className="flex-1 min-w-0">
81
- <div className="text-xs font-medium text-foreground">{addon.name ?? addon.id}</div>
82
- {addon.description && (
83
- <div className="text-[10px] text-foreground-subtle mt-0.5">{String(addon.description)}</div>
84
- )}
85
- </div>
86
- <span
87
- className={`text-[10px] rounded px-1.5 py-0.5 font-medium ${
88
- addon.enabled
89
- ? 'bg-success/10 text-success border border-success/30'
90
- : 'bg-border/40 text-foreground-subtle'
91
- }`}
92
- >
93
- {addon.enabled ? 'Enabled' : 'Disabled'}
94
- </span>
95
- </div>
96
- ))}
97
- </div>
98
- )}
99
- </div>
100
- </div>
101
- )
102
- }