@camstack/addon-admin-ui 0.1.1 → 0.1.3

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-DjELGD4R.css +1 -0
  2. package/dist/assets/index-w55PwKyu.js +598 -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 +5 -1
  8. package/src/App.tsx +0 -71
  9. package/src/components/addons/AddonCard.tsx +0 -339
  10. package/src/components/addons/AddonUploadZone.tsx +0 -307
  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 -119
  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 -171
  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 -113
  68. package/src/components/metrics/IntegrationUsage.tsx +0 -90
  69. package/src/components/metrics/PipelineStatus.tsx +0 -105
  70. package/src/components/metrics/ProcessResources.tsx +0 -139
  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 -238
  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 -224
  98. package/src/pages/Integrations.tsx +0 -330
  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 -525
  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 -210
  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,113 +0,0 @@
1
- import { useQuery } from '@tanstack/react-query'
2
- import { useBackendClient } from '../../hooks/useBackendClient'
3
- import { CapabilityBadges } from '../shared/CapabilityBadges'
4
-
5
- interface AgentStatus {
6
- id: string
7
- name: string
8
- state: 'online' | 'offline' | 'degraded'
9
- capabilities: string[]
10
- lastHeartbeat: number
11
- connectedSince?: number
12
- activeTaskCount: number
13
- completedTaskCount: number
14
- failedTaskCount: number
15
- resources?: {
16
- cpuCores?: number
17
- memoryMB?: number
18
- gpuAvailable?: boolean
19
- gpuModel?: string
20
- }
21
- }
22
-
23
- function stateStyle(state: string): { dot: string; label: string } {
24
- switch (state) {
25
- case 'online': return { dot: 'bg-success', label: 'text-success' }
26
- case 'degraded': return { dot: 'bg-warning', label: 'text-warning' }
27
- case 'offline':
28
- default: return { dot: 'bg-foreground-subtle/40', label: 'text-foreground-subtle' }
29
- }
30
- }
31
-
32
- export function AgentLoad() {
33
- const client = useBackendClient()
34
-
35
- const { data, isLoading, isError } = useQuery({
36
- queryKey: ['agents'],
37
- queryFn: () => client.listAgents(),
38
- refetchInterval: 5_000,
39
- })
40
-
41
- const agents = (data ?? []) as unknown as AgentStatus[]
42
- const onlineCount = agents.filter((a) => a.state === 'online').length
43
- const totalActive = agents.reduce((sum, a) => sum + (a.activeTaskCount ?? 0), 0)
44
-
45
- return (
46
- <div className="rounded-lg border border-border bg-surface p-4">
47
- <h2 className="text-xs font-semibold text-foreground-subtle uppercase tracking-wider mb-3">
48
- Agent Load
49
- </h2>
50
-
51
- {isLoading && (
52
- <div className="text-xs text-foreground-subtle animate-pulse">Loading...</div>
53
- )}
54
-
55
- {isError && (
56
- <div className="text-xs text-danger">Failed to load data</div>
57
- )}
58
-
59
- {!isLoading && !isError && agents.length === 0 && (
60
- <div className="text-xs text-foreground-subtle">No data available</div>
61
- )}
62
-
63
- {!isLoading && !isError && agents.length > 0 && (
64
- <>
65
- {/* Aggregate */}
66
- <div className="flex items-center gap-3 mb-3 pb-3 border-b border-border">
67
- <span className="text-xs text-foreground-subtle">
68
- <span className="text-foreground font-semibold">{onlineCount}</span>/{agents.length} online
69
- </span>
70
- <span className="text-xs text-foreground-subtle">
71
- <span className="text-foreground font-semibold">{totalActive}</span> active tasks
72
- </span>
73
- </div>
74
-
75
- {/* Agent list */}
76
- <div className="space-y-3">
77
- {agents.map((a) => {
78
- const { dot, label: labelClass } = stateStyle(a.state)
79
- const isIdle = (a.activeTaskCount ?? 0) === 0
80
- return (
81
- <div key={a.id} className="space-y-1">
82
- <div className="flex items-center justify-between gap-2">
83
- <div className="flex items-center gap-2 min-w-0">
84
- <span className={`h-2 w-2 flex-shrink-0 rounded-full ${dot}`} />
85
- <span className="text-xs text-foreground font-medium truncate">
86
- {a.name ?? a.id}
87
- </span>
88
- </div>
89
- <div className="flex items-center gap-2 flex-shrink-0">
90
- <span className={`text-[10px] font-medium ${labelClass}`}>
91
- {a.state}
92
- </span>
93
- <span className="text-[10px] text-foreground-subtle tabular-nums bg-surface-hover rounded px-1.5 py-0.5">
94
- {isIdle
95
- ? 'idle'
96
- : `${a.activeTaskCount} task${a.activeTaskCount !== 1 ? 's' : ''}`}
97
- </span>
98
- </div>
99
- </div>
100
- {a.capabilities && a.capabilities.length > 0 && (
101
- <div className="pl-4">
102
- <CapabilityBadges capabilities={a.capabilities} />
103
- </div>
104
- )}
105
- </div>
106
- )
107
- })}
108
- </div>
109
- </>
110
- )}
111
- </div>
112
- )
113
- }
@@ -1,90 +0,0 @@
1
- import { useQuery } from '@tanstack/react-query'
2
- import { useBackendClient } from '../../hooks/useBackendClient'
3
- import { ProviderIcon } from '../shared/ProviderIcon'
4
- import { StatusBadge } from '../shared/StatusBadge'
5
-
6
- interface ProviderEntry {
7
- id: string
8
- name: string
9
- type: string
10
- status: string | { state?: string }
11
- deviceCount: number
12
- }
13
-
14
- function resolveStatus(status: ProviderEntry['status']): string {
15
- if (typeof status === 'string') return status
16
- if (typeof status === 'object' && status !== null) {
17
- return String((status as Record<string, unknown>).state ?? 'stopped')
18
- }
19
- return 'stopped'
20
- }
21
-
22
- export function IntegrationUsage() {
23
- const client = useBackendClient()
24
-
25
- const { data, isLoading, isError } = useQuery({
26
- queryKey: ['providers'],
27
- queryFn: () => client.listProviders(),
28
- refetchInterval: 10_000,
29
- })
30
-
31
- const providers = (data ?? []) as unknown as ProviderEntry[]
32
- const totalDevices = providers.reduce((sum, p) => sum + (p.deviceCount ?? 0), 0)
33
-
34
- return (
35
- <div className="rounded-lg border border-border bg-surface p-4">
36
- <h2 className="text-xs font-semibold text-foreground-subtle uppercase tracking-wider mb-3">
37
- Integration Usage
38
- </h2>
39
-
40
- {isLoading && (
41
- <div className="text-xs text-foreground-subtle animate-pulse">Loading...</div>
42
- )}
43
-
44
- {isError && (
45
- <div className="text-xs text-danger">Failed to load data</div>
46
- )}
47
-
48
- {!isLoading && !isError && providers.length === 0 && (
49
- <div className="text-xs text-foreground-subtle">No data available</div>
50
- )}
51
-
52
- {!isLoading && !isError && providers.length > 0 && (
53
- <>
54
- {/* Aggregate */}
55
- <div className="flex items-center gap-3 mb-3 pb-3 border-b border-border">
56
- <span className="text-xs text-foreground-subtle">
57
- <span className="text-foreground font-semibold">{providers.length}</span> providers
58
- </span>
59
- <span className="text-xs text-foreground-subtle">
60
- <span className="text-foreground font-semibold">{totalDevices}</span> devices total
61
- </span>
62
- </div>
63
-
64
- {/* Provider list */}
65
- <div className="space-y-2">
66
- {providers.map((p) => (
67
- <div
68
- key={p.id}
69
- className="flex items-center justify-between gap-2"
70
- >
71
- <div className="flex items-center gap-2 min-w-0">
72
- <ProviderIcon type={p.type} size="sm" />
73
- <span className="text-xs text-foreground font-medium truncate">
74
- {p.name ?? p.id}
75
- </span>
76
- </div>
77
- <div className="flex items-center gap-2 flex-shrink-0">
78
- <span className="text-xs text-foreground-subtle tabular-nums">
79
- {p.deviceCount ?? 0}d
80
- </span>
81
- <StatusBadge status={resolveStatus(p.status)} />
82
- </div>
83
- </div>
84
- ))}
85
- </div>
86
- </>
87
- )}
88
- </div>
89
- )
90
- }
@@ -1,105 +0,0 @@
1
- import { useQuery } from '@tanstack/react-query'
2
- import { useBackendClient } from '../../hooks/useBackendClient'
3
-
4
- interface PipelineEntry {
5
- deviceId: string
6
- status: {
7
- active: boolean
8
- decode: {
9
- fps: number
10
- droppedFrames: number
11
- totalFrames: number
12
- }
13
- videoConsumers: number
14
- audioConsumers: number
15
- uptime: number
16
- source: {
17
- protocol: string
18
- connected: boolean
19
- } | null
20
- outputs: Array<{
21
- format: string
22
- consumers: number
23
- active: boolean
24
- }>
25
- }
26
- }
27
-
28
- export function PipelineStatus() {
29
- const client = useBackendClient()
30
-
31
- const { data, isLoading, isError } = useQuery({
32
- queryKey: ['pipelines'],
33
- queryFn: () => client.listPipelines(),
34
- refetchInterval: 5_000,
35
- })
36
-
37
- const pipelines = (data ?? []) as unknown as PipelineEntry[]
38
- const activePipelines = pipelines.filter((p) => p.status.active)
39
- const totalFps = activePipelines.reduce((sum, p) => sum + (p.status.decode?.fps ?? 0), 0)
40
-
41
- return (
42
- <div className="rounded-lg border border-border bg-surface p-4">
43
- <h2 className="text-xs font-semibold text-foreground-subtle uppercase tracking-wider mb-3">
44
- Pipeline Status
45
- </h2>
46
-
47
- {isLoading && (
48
- <div className="text-xs text-foreground-subtle animate-pulse">Loading...</div>
49
- )}
50
-
51
- {isError && (
52
- <div className="text-xs text-danger">Failed to load data</div>
53
- )}
54
-
55
- {!isLoading && !isError && pipelines.length === 0 && (
56
- <div className="text-xs text-foreground-subtle">No data available</div>
57
- )}
58
-
59
- {!isLoading && !isError && pipelines.length > 0 && (
60
- <>
61
- {/* Aggregate */}
62
- <div className="flex items-center gap-3 mb-3 pb-3 border-b border-border">
63
- <span className="text-xs text-foreground-subtle">
64
- <span className="text-foreground font-semibold">{activePipelines.length}</span> active
65
- {' '}/ <span className="text-foreground font-semibold">{pipelines.length}</span> total
66
- </span>
67
- <span className="text-xs text-foreground-subtle">
68
- <span className="text-foreground font-semibold tabular-nums">
69
- {totalFps.toFixed(1)}
70
- </span>{' '}
71
- total FPS
72
- </span>
73
- </div>
74
-
75
- {/* Pipeline list */}
76
- <div className="space-y-1.5">
77
- {pipelines.map((p) => (
78
- <div
79
- key={p.deviceId}
80
- className="flex items-center justify-between gap-2 text-xs"
81
- >
82
- <div className="flex items-center gap-2 min-w-0">
83
- <span
84
- className={`h-2 w-2 flex-shrink-0 rounded-full ${
85
- p.status.active ? 'bg-success' : 'bg-foreground-subtle/40'
86
- }`}
87
- />
88
- <span className="text-foreground truncate font-medium">{p.deviceId}</span>
89
- </div>
90
- <div className="flex items-center gap-3 flex-shrink-0">
91
- <span className="text-foreground-subtle">
92
- {p.status.active ? 'running' : 'idle'}
93
- </span>
94
- <span className="tabular-nums text-foreground-subtle w-14 text-right">
95
- {p.status.active ? `${(p.status.decode?.fps ?? 0).toFixed(1)} fps` : '—'}
96
- </span>
97
- </div>
98
- </div>
99
- ))}
100
- </div>
101
- </>
102
- )}
103
- </div>
104
- )
105
- }
@@ -1,139 +0,0 @@
1
- import { useQuery } from '@tanstack/react-query'
2
- import { useBackendClient } from '../../hooks/useBackendClient'
3
-
4
- interface ProcessStats {
5
- pid: number
6
- cpu: number
7
- memory: number
8
- uptime: number
9
- restartCount: number
10
- }
11
-
12
- interface ProcessEntry {
13
- id: string
14
- label: string
15
- state: string
16
- pid?: number
17
- stats?: ProcessStats
18
- restartCount: number
19
- lastCrashAt?: number
20
- lastCrashError?: string
21
- }
22
-
23
- function formatMemoryMB(bytes?: number): string {
24
- if (bytes == null) return '—'
25
- return `${(bytes / 1024 / 1024).toFixed(0)} MB`
26
- }
27
-
28
- function formatUptime(ms?: number): string {
29
- if (ms == null || ms <= 0) return '—'
30
- const totalSeconds = Math.floor(ms / 1000)
31
- const h = Math.floor(totalSeconds / 3600)
32
- const m = Math.floor((totalSeconds % 3600) / 60)
33
- const s = totalSeconds % 60
34
- if (h > 0) return `${h}h ${m}m`
35
- if (m > 0) return `${m}m ${s}s`
36
- return `${s}s`
37
- }
38
-
39
- function stateColor(state: string): string {
40
- switch (state) {
41
- case 'running': return 'text-success'
42
- case 'stopped': return 'text-foreground-subtle'
43
- case 'crashed':
44
- case 'error': return 'text-danger'
45
- case 'starting': return 'text-warning'
46
- default: return 'text-foreground-subtle'
47
- }
48
- }
49
-
50
- export function ProcessResources() {
51
- const client = useBackendClient()
52
-
53
- const { data, isLoading, isError } = useQuery({
54
- queryKey: ['processes'],
55
- queryFn: () => client.listProcesses(),
56
- refetchInterval: 5_000,
57
- })
58
-
59
- const processes = (data ?? []) as ProcessEntry[]
60
- const runningCount = processes.filter((p) => p.state === 'running').length
61
-
62
- const totalCpu = processes.reduce((sum, p) => sum + (p.stats?.cpu ?? 0), 0)
63
- const totalMemory = processes.reduce((sum, p) => sum + (p.stats?.memory ?? 0), 0)
64
-
65
- return (
66
- <div className="rounded-lg border border-border bg-surface p-4">
67
- <h2 className="text-xs font-semibold text-foreground-subtle uppercase tracking-wider mb-3">
68
- Process Resources
69
- </h2>
70
-
71
- {isLoading && (
72
- <div className="text-xs text-foreground-subtle animate-pulse">Loading...</div>
73
- )}
74
-
75
- {isError && (
76
- <div className="text-xs text-danger">Failed to load data</div>
77
- )}
78
-
79
- {!isLoading && !isError && processes.length === 0 && (
80
- <div className="text-xs text-foreground-subtle">No data available</div>
81
- )}
82
-
83
- {!isLoading && !isError && processes.length > 0 && (
84
- <>
85
- {/* System totals */}
86
- <div className="flex items-center gap-4 mb-3 pb-3 border-b border-border">
87
- <span className="text-xs text-foreground-subtle">
88
- <span className="text-foreground font-semibold">{runningCount}</span>/{processes.length} running
89
- </span>
90
- <span className="text-xs text-foreground-subtle">
91
- CPU: <span className="text-foreground font-semibold tabular-nums">{totalCpu.toFixed(1)}%</span>
92
- </span>
93
- <span className="text-xs text-foreground-subtle">
94
- Mem: <span className="text-foreground font-semibold tabular-nums">{formatMemoryMB(totalMemory)}</span>
95
- </span>
96
- </div>
97
-
98
- {/* Process table */}
99
- <div className="space-y-1.5">
100
- {/* Header */}
101
- <div className="grid grid-cols-[1fr_4rem_5rem_4rem_3rem] gap-2 text-[10px] font-medium text-foreground-subtle uppercase tracking-wider pb-1">
102
- <span>Process</span>
103
- <span className="text-right">CPU</span>
104
- <span className="text-right">Memory</span>
105
- <span className="text-right">Uptime</span>
106
- <span className="text-right">Rst</span>
107
- </div>
108
-
109
- {processes.map((p) => (
110
- <div
111
- key={p.id}
112
- className="grid grid-cols-[1fr_4rem_5rem_4rem_3rem] gap-2 text-xs items-center"
113
- >
114
- <div className="flex items-center gap-1.5 min-w-0">
115
- <span className={`text-[10px] font-semibold ${stateColor(p.state)}`}>
116
-
117
- </span>
118
- <span className="text-foreground truncate">{p.label ?? p.id}</span>
119
- </div>
120
- <span className="tabular-nums text-foreground-subtle text-right">
121
- {p.stats?.cpu != null ? `${p.stats.cpu.toFixed(1)}%` : '—'}
122
- </span>
123
- <span className="tabular-nums text-foreground-subtle text-right">
124
- {formatMemoryMB(p.stats?.memory)}
125
- </span>
126
- <span className="tabular-nums text-foreground-subtle text-right">
127
- {formatUptime(p.stats?.uptime)}
128
- </span>
129
- <span className="tabular-nums text-foreground-subtle text-right">
130
- {p.restartCount ?? 0}
131
- </span>
132
- </div>
133
- ))}
134
- </div>
135
- </>
136
- )}
137
- </div>
138
- )
139
- }
@@ -1,131 +0,0 @@
1
- interface PhaseSettingsProps {
2
- readonly motionFps: number
3
- readonly detectionFps: number
4
- readonly cooldownMs: number
5
- readonly maxConcurrentInferences: number | null
6
- readonly onMotionFpsChange: (v: number) => void
7
- readonly onDetectionFpsChange: (v: number) => void
8
- readonly onCooldownMsChange: (v: number) => void
9
- readonly onMaxConcurrentInferencesChange: (v: number | null) => void
10
- }
11
-
12
- interface SliderFieldProps {
13
- readonly label: string
14
- readonly value: number
15
- readonly min: number
16
- readonly max: number
17
- readonly step?: number
18
- readonly unit?: string
19
- readonly hint?: string
20
- readonly onChange: (v: number) => void
21
- }
22
-
23
- function SliderField({ label, value, min, max, step = 1, unit, hint, onChange }: SliderFieldProps) {
24
- return (
25
- <div>
26
- <div className="flex items-center justify-between mb-1">
27
- <label className="text-[10px] font-medium text-foreground-subtle">{label}</label>
28
- {hint && <span className="text-[9px] text-foreground-subtle/50">{hint}</span>}
29
- </div>
30
- <div className="flex items-center gap-3">
31
- <input
32
- type="range"
33
- className="flex-1 h-1 accent-primary cursor-pointer"
34
- min={min}
35
- max={max}
36
- step={step}
37
- value={value}
38
- onChange={(e) => onChange(Number(e.target.value))}
39
- />
40
- <span className="text-xs text-foreground tabular-nums min-w-[3rem] text-right">
41
- {value}{unit ? ` ${unit}` : ''}
42
- </span>
43
- </div>
44
- </div>
45
- )
46
- }
47
-
48
- export function PhaseSettings({
49
- motionFps,
50
- detectionFps,
51
- cooldownMs,
52
- maxConcurrentInferences,
53
- onMotionFpsChange,
54
- onDetectionFpsChange,
55
- onCooldownMsChange,
56
- onMaxConcurrentInferencesChange,
57
- }: PhaseSettingsProps) {
58
- const isAuto = maxConcurrentInferences === null
59
-
60
- return (
61
- <div className="space-y-4">
62
- <h3 className="text-[10px] font-semibold text-foreground uppercase tracking-wider">
63
- Orchestrator
64
- </h3>
65
- <SliderField
66
- label="Motion FPS"
67
- value={motionFps}
68
- min={1}
69
- max={15}
70
- onChange={onMotionFpsChange}
71
- unit="fps"
72
- hint="Frame-diff rate"
73
- />
74
- <SliderField
75
- label="Detection FPS"
76
- value={detectionFps}
77
- min={1}
78
- max={30}
79
- onChange={onDetectionFpsChange}
80
- unit="fps"
81
- hint="Inference rate"
82
- />
83
- <SliderField
84
- label="Motion Cooldown"
85
- value={cooldownMs / 1000}
86
- min={1}
87
- max={60}
88
- onChange={(v) => onCooldownMsChange(v * 1000)}
89
- unit="s"
90
- hint="No motion → idle"
91
- />
92
-
93
- {/* Max concurrent inferences with Auto toggle */}
94
- <div>
95
- <div className="flex items-center justify-between mb-1">
96
- <label className="text-[10px] font-medium text-foreground-subtle">Max Parallel</label>
97
- <button
98
- type="button"
99
- onClick={() => onMaxConcurrentInferencesChange(isAuto ? 2 : null)}
100
- className={`text-[9px] font-medium px-1.5 py-0.5 rounded transition-colors ${
101
- isAuto
102
- ? 'bg-primary/15 text-primary'
103
- : 'bg-surface text-foreground-subtle hover:text-foreground'
104
- }`}
105
- >
106
- {isAuto ? 'Auto' : 'Manual'}
107
- </button>
108
- </div>
109
- {isAuto ? (
110
- <p className="text-[10px] text-foreground-subtle/60">
111
- Auto-detected from hardware (CPU cores, GPU)
112
- </p>
113
- ) : (
114
- <div className="flex items-center gap-3">
115
- <input
116
- type="range"
117
- className="flex-1 h-1 accent-primary cursor-pointer"
118
- min={1}
119
- max={8}
120
- value={maxConcurrentInferences}
121
- onChange={(e) => onMaxConcurrentInferencesChange(Number(e.target.value))}
122
- />
123
- <span className="text-xs text-foreground tabular-nums min-w-[3rem] text-right">
124
- {maxConcurrentInferences}
125
- </span>
126
- </div>
127
- )}
128
- </div>
129
- </div>
130
- )
131
- }
@@ -1,30 +0,0 @@
1
- import { Video, Eye, CircleDot, Mic } from 'lucide-react'
2
- import type { LucideIcon } from 'lucide-react'
3
-
4
- const CAPABILITIES: Record<string, { icon: LucideIcon; label: string }> = {
5
- streaming: { icon: Video, label: 'Stream' },
6
- detection: { icon: Eye, label: 'Detection' },
7
- recording: { icon: CircleDot, label: 'Recording' },
8
- audio: { icon: Mic, label: 'Audio' },
9
- }
10
-
11
- interface CapabilityBadgesProps {
12
- capabilities: string[]
13
- }
14
-
15
- export function CapabilityBadges({ capabilities }: CapabilityBadgesProps) {
16
- return (
17
- <div className="flex items-center gap-1">
18
- {capabilities.map((cap) => {
19
- const info = CAPABILITIES[cap]
20
- if (!info) return null
21
- const Icon = info.icon
22
- return (
23
- <span key={cap} className="inline-flex items-center gap-1 rounded-md bg-surface-hover px-1.5 py-0.5 text-[10px] text-foreground-subtle" title={info.label}>
24
- <Icon className="h-3 w-3" />
25
- </span>
26
- )
27
- })}
28
- </div>
29
- )
30
- }
@@ -1,42 +0,0 @@
1
- import { Cctv, Server, Radio, Home, Wifi } from 'lucide-react'
2
- import type { LucideIcon } from 'lucide-react'
3
-
4
- const PROVIDERS: Record<string, { icon: LucideIcon; color: string; label: string }> = {
5
- frigate: { icon: Cctv, color: '#3b82f6', label: 'Frigate' },
6
- scrypted: { icon: Server, color: '#a855f7', label: 'Scrypted' },
7
- reolink: { icon: Radio, color: '#06b6d4', label: 'Reolink' },
8
- homeassistant: { icon: Home, color: '#22d3ee', label: 'Home Assistant' },
9
- rtsp: { icon: Wifi, color: '#78716c', label: 'RTSP' },
10
- onvif: { icon: Wifi, color: '#78716c', label: 'ONVIF' },
11
- }
12
-
13
- interface ProviderIconProps {
14
- type: string
15
- size?: 'sm' | 'md' | 'lg'
16
- showLabel?: boolean
17
- }
18
-
19
- const SIZES = { sm: 'h-4 w-4', md: 'h-5 w-5', lg: 'h-6 w-6' }
20
- const CONTAINER_SIZES = { sm: 'h-7 w-7', md: 'h-8 w-8', lg: 'h-10 w-10' }
21
-
22
- export function ProviderIcon({ type, size = 'md', showLabel }: ProviderIconProps) {
23
- const provider = PROVIDERS[type] ?? PROVIDERS['rtsp']!
24
- const Icon = provider.icon
25
-
26
- return (
27
- <div className="flex items-center gap-2">
28
- <div className={`flex items-center justify-center rounded-lg ${CONTAINER_SIZES[size]}`} style={{ backgroundColor: `${provider.color}15` }}>
29
- <Icon className={SIZES[size]} style={{ color: provider.color }} />
30
- </div>
31
- {showLabel && <span className="text-xs font-medium text-foreground-subtle">{provider.label}</span>}
32
- </div>
33
- )
34
- }
35
-
36
- export function getProviderColor(type: string): string {
37
- return PROVIDERS[type]?.color ?? '#78716c'
38
- }
39
-
40
- export function getProviderLabel(type: string): string {
41
- return PROVIDERS[type]?.label ?? type
42
- }
@@ -1,23 +0,0 @@
1
- const STATUS_STYLES: Record<string, { bg: string; text: string; dot: string; label: string }> = {
2
- running: { bg: 'bg-success/10', text: 'text-success', dot: 'bg-success', label: 'Running' },
3
- stopped: { bg: 'bg-foreground-subtle/10', text: 'text-foreground-subtle', dot: 'bg-foreground-subtle', label: 'Stopped' },
4
- error: { bg: 'bg-danger/10', text: 'text-danger', dot: 'bg-danger', label: 'Error' },
5
- online: { bg: 'bg-success/10', text: 'text-success', dot: 'bg-success', label: 'Online' },
6
- offline: { bg: 'bg-foreground-subtle/10', text: 'text-foreground-subtle', dot: 'bg-foreground-subtle', label: 'Offline' },
7
- idle: { bg: 'bg-info/10', text: 'text-info', dot: 'bg-info', label: 'Idle' },
8
- }
9
-
10
- interface StatusBadgeProps {
11
- status: string
12
- }
13
-
14
- export function StatusBadge({ status }: StatusBadgeProps) {
15
- const style = STATUS_STYLES[status] ?? STATUS_STYLES['stopped']!
16
-
17
- return (
18
- <span className={`inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-[10px] font-medium ${style.bg} ${style.text}`}>
19
- <span className={`h-1.5 w-1.5 rounded-full ${style.dot}`} />
20
- {style.label}
21
- </span>
22
- )
23
- }