@camstack/addon-admin-ui 0.1.2 → 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 +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,105 +0,0 @@
1
- import { useQuery } from '@tanstack/react-query'
2
- import { useBackendClient } from '../../hooks/useBackendClient'
3
- import { CapabilityBadges } from '../shared/CapabilityBadges'
4
-
5
- // agents.listAgents returns Record<string, unknown>[] — typed view for rendering
6
- interface AgentView {
7
- id: string
8
- name: string
9
- state: 'online' | 'offline' | 'degraded'
10
- capabilities: string[]
11
- activeTaskCount: number
12
- }
13
-
14
- function stateStyle(state: string): { dot: string; label: string } {
15
- switch (state) {
16
- case 'online': return { dot: 'bg-success', label: 'text-success' }
17
- case 'degraded': return { dot: 'bg-warning', label: 'text-warning' }
18
- case 'offline':
19
- default: return { dot: 'bg-foreground-subtle/40', label: 'text-foreground-subtle' }
20
- }
21
- }
22
-
23
- export function AgentLoad() {
24
- const client = useBackendClient()
25
-
26
- const { data, isLoading, isError } = useQuery({
27
- queryKey: ['agents'],
28
- queryFn: () => client.listAgents(),
29
- refetchInterval: 5_000,
30
- })
31
-
32
- // agents.listAgents returns Record<string, unknown>[] — cast needed until tRPC router is strongly typed
33
- const agents = (data ?? []) as unknown as readonly AgentView[]
34
- const onlineCount = agents.filter((a) => a.state === 'online').length
35
- const totalActive = agents.reduce((sum, a) => sum + (a.activeTaskCount ?? 0), 0)
36
-
37
- return (
38
- <div className="rounded-lg border border-border bg-surface p-4">
39
- <h2 className="text-xs font-semibold text-foreground-subtle uppercase tracking-wider mb-3">
40
- Agent Load
41
- </h2>
42
-
43
- {isLoading && (
44
- <div className="text-xs text-foreground-subtle animate-pulse">Loading...</div>
45
- )}
46
-
47
- {isError && (
48
- <div className="text-xs text-danger">Failed to load data</div>
49
- )}
50
-
51
- {!isLoading && !isError && agents.length === 0 && (
52
- <div className="text-xs text-foreground-subtle">No data available</div>
53
- )}
54
-
55
- {!isLoading && !isError && agents.length > 0 && (
56
- <>
57
- {/* Aggregate */}
58
- <div className="flex items-center gap-3 mb-3 pb-3 border-b border-border">
59
- <span className="text-xs text-foreground-subtle">
60
- <span className="text-foreground font-semibold">{onlineCount}</span>/{agents.length} online
61
- </span>
62
- <span className="text-xs text-foreground-subtle">
63
- <span className="text-foreground font-semibold">{totalActive}</span> active tasks
64
- </span>
65
- </div>
66
-
67
- {/* Agent list */}
68
- <div className="space-y-3">
69
- {agents.map((a) => {
70
- const { dot, label: labelClass } = stateStyle(a.state)
71
- const isIdle = (a.activeTaskCount ?? 0) === 0
72
- return (
73
- <div key={a.id} className="space-y-1">
74
- <div className="flex items-center justify-between gap-2">
75
- <div className="flex items-center gap-2 min-w-0">
76
- <span className={`h-2 w-2 flex-shrink-0 rounded-full ${dot}`} />
77
- <span className="text-xs text-foreground font-medium truncate">
78
- {a.name ?? a.id}
79
- </span>
80
- </div>
81
- <div className="flex items-center gap-2 flex-shrink-0">
82
- <span className={`text-[10px] font-medium ${labelClass}`}>
83
- {a.state}
84
- </span>
85
- <span className="text-[10px] text-foreground-subtle tabular-nums bg-surface-hover rounded px-1.5 py-0.5">
86
- {isIdle
87
- ? 'idle'
88
- : `${a.activeTaskCount} task${a.activeTaskCount !== 1 ? 's' : ''}`}
89
- </span>
90
- </div>
91
- </div>
92
- {a.capabilities && a.capabilities.length > 0 && (
93
- <div className="pl-4">
94
- <CapabilityBadges capabilities={a.capabilities} />
95
- </div>
96
- )}
97
- </div>
98
- )
99
- })}
100
- </div>
101
- </>
102
- )}
103
- </div>
104
- )
105
- }
@@ -1,73 +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
- import type { ProviderStatus } from '@camstack/types'
7
-
8
- function resolveStatus(status: ProviderStatus): string {
9
- return status.connected ? 'running' : 'stopped'
10
- }
11
-
12
- export function IntegrationUsage() {
13
- const client = useBackendClient()
14
-
15
- const { data, isLoading, isError } = useQuery({
16
- queryKey: ['providers'],
17
- queryFn: () => client.listProviders(),
18
- refetchInterval: 10_000,
19
- })
20
-
21
- const providers = data ?? []
22
-
23
- return (
24
- <div className="rounded-lg border border-border bg-surface p-4">
25
- <h2 className="text-xs font-semibold text-foreground-subtle uppercase tracking-wider mb-3">
26
- Integration Usage
27
- </h2>
28
-
29
- {isLoading && (
30
- <div className="text-xs text-foreground-subtle animate-pulse">Loading...</div>
31
- )}
32
-
33
- {isError && (
34
- <div className="text-xs text-danger">Failed to load data</div>
35
- )}
36
-
37
- {!isLoading && !isError && providers.length === 0 && (
38
- <div className="text-xs text-foreground-subtle">No data available</div>
39
- )}
40
-
41
- {!isLoading && !isError && providers.length > 0 && (
42
- <>
43
- {/* Aggregate */}
44
- <div className="flex items-center gap-3 mb-3 pb-3 border-b border-border">
45
- <span className="text-xs text-foreground-subtle">
46
- <span className="text-foreground font-semibold">{providers.length}</span> providers
47
- </span>
48
- </div>
49
-
50
- {/* Provider list */}
51
- <div className="space-y-2">
52
- {providers.map((p) => (
53
- <div
54
- key={p.id}
55
- className="flex items-center justify-between gap-2"
56
- >
57
- <div className="flex items-center gap-2 min-w-0">
58
- <ProviderIcon type={p.type} size="sm" />
59
- <span className="text-xs text-foreground font-medium truncate">
60
- {p.name ?? p.id}
61
- </span>
62
- </div>
63
- <div className="flex items-center gap-2 flex-shrink-0">
64
- <StatusBadge status={resolveStatus(p.status)} />
65
- </div>
66
- </div>
67
- ))}
68
- </div>
69
- </>
70
- )}
71
- </div>
72
- )
73
- }
@@ -1,74 +0,0 @@
1
- import { useQuery } from '@tanstack/react-query'
2
- import { useBackendClient } from '../../hooks/useBackendClient'
3
-
4
- export function PipelineStatus() {
5
- const client = useBackendClient()
6
-
7
- const { data, isLoading, isError } = useQuery({
8
- queryKey: ['pipelines'],
9
- queryFn: () => client.listPipelines(),
10
- refetchInterval: 5_000,
11
- })
12
-
13
- const pipelines = data ?? []
14
- const withSlot = pipelines.filter((p) => p.slot != null)
15
-
16
- return (
17
- <div className="rounded-lg border border-border bg-surface p-4">
18
- <h2 className="text-xs font-semibold text-foreground-subtle uppercase tracking-wider mb-3">
19
- Pipeline Status
20
- </h2>
21
-
22
- {isLoading && (
23
- <div className="text-xs text-foreground-subtle animate-pulse">Loading...</div>
24
- )}
25
-
26
- {isError && (
27
- <div className="text-xs text-danger">Failed to load data</div>
28
- )}
29
-
30
- {!isLoading && !isError && pipelines.length === 0 && (
31
- <div className="text-xs text-foreground-subtle">No data available</div>
32
- )}
33
-
34
- {!isLoading && !isError && pipelines.length > 0 && (
35
- <>
36
- {/* Aggregate */}
37
- <div className="flex items-center gap-3 mb-3 pb-3 border-b border-border">
38
- <span className="text-xs text-foreground-subtle">
39
- <span className="text-foreground font-semibold">{withSlot.length}</span> active
40
- {' '}/ <span className="text-foreground font-semibold">{pipelines.length}</span> total
41
- </span>
42
- </div>
43
-
44
- {/* Pipeline list */}
45
- <div className="space-y-1.5">
46
- {pipelines.map((p) => {
47
- const isActive = p.slot != null
48
- return (
49
- <div
50
- key={p.id}
51
- className="flex items-center justify-between gap-2 text-xs"
52
- >
53
- <div className="flex items-center gap-2 min-w-0">
54
- <span
55
- className={`h-2 w-2 flex-shrink-0 rounded-full ${
56
- isActive ? 'bg-success' : 'bg-foreground-subtle/40'
57
- }`}
58
- />
59
- <span className="text-foreground truncate font-medium">{p.id}</span>
60
- </div>
61
- <div className="flex items-center gap-3 flex-shrink-0">
62
- <span className="text-foreground-subtle">
63
- {isActive ? p.slot! : 'idle'}
64
- </span>
65
- </div>
66
- </div>
67
- )
68
- })}
69
- </div>
70
- </>
71
- )}
72
- </div>
73
- )
74
- }
@@ -1,123 +0,0 @@
1
- import { useQuery } from '@tanstack/react-query'
2
- import { useBackendClient } from '../../hooks/useBackendClient'
3
- import type { BackendClient } from '@camstack/sdk'
4
-
5
- type ProcessEntry = Awaited<ReturnType<BackendClient['listProcesses']>>[number]
6
-
7
- function formatMemoryMB(bytes?: number): string {
8
- if (bytes == null) return '—'
9
- return `${(bytes / 1024 / 1024).toFixed(0)} MB`
10
- }
11
-
12
- function formatUptime(ms?: number): string {
13
- if (ms == null || ms <= 0) return '—'
14
- const totalSeconds = Math.floor(ms / 1000)
15
- const h = Math.floor(totalSeconds / 3600)
16
- const m = Math.floor((totalSeconds % 3600) / 60)
17
- const s = totalSeconds % 60
18
- if (h > 0) return `${h}h ${m}m`
19
- if (m > 0) return `${m}m ${s}s`
20
- return `${s}s`
21
- }
22
-
23
- function stateColor(state: string): string {
24
- switch (state) {
25
- case 'running': return 'text-success'
26
- case 'stopped': return 'text-foreground-subtle'
27
- case 'crashed':
28
- case 'error': return 'text-danger'
29
- case 'starting': return 'text-warning'
30
- default: return 'text-foreground-subtle'
31
- }
32
- }
33
-
34
- export function ProcessResources() {
35
- const client = useBackendClient()
36
-
37
- const { data, isLoading, isError } = useQuery({
38
- queryKey: ['processes'],
39
- queryFn: () => client.listProcesses(),
40
- refetchInterval: 5_000,
41
- })
42
-
43
- const processes = data ?? []
44
- const runningCount = processes.filter((p) => p.state === 'running').length
45
-
46
- const totalCpu = processes.reduce((sum, p) => sum + (p.stats?.cpu ?? 0), 0)
47
- const totalMemory = processes.reduce((sum, p) => sum + (p.stats?.memory ?? 0), 0)
48
-
49
- return (
50
- <div className="rounded-lg border border-border bg-surface p-4">
51
- <h2 className="text-xs font-semibold text-foreground-subtle uppercase tracking-wider mb-3">
52
- Process Resources
53
- </h2>
54
-
55
- {isLoading && (
56
- <div className="text-xs text-foreground-subtle animate-pulse">Loading...</div>
57
- )}
58
-
59
- {isError && (
60
- <div className="text-xs text-danger">Failed to load data</div>
61
- )}
62
-
63
- {!isLoading && !isError && processes.length === 0 && (
64
- <div className="text-xs text-foreground-subtle">No data available</div>
65
- )}
66
-
67
- {!isLoading && !isError && processes.length > 0 && (
68
- <>
69
- {/* System totals */}
70
- <div className="flex items-center gap-4 mb-3 pb-3 border-b border-border">
71
- <span className="text-xs text-foreground-subtle">
72
- <span className="text-foreground font-semibold">{runningCount}</span>/{processes.length} running
73
- </span>
74
- <span className="text-xs text-foreground-subtle">
75
- CPU: <span className="text-foreground font-semibold tabular-nums">{totalCpu.toFixed(1)}%</span>
76
- </span>
77
- <span className="text-xs text-foreground-subtle">
78
- Mem: <span className="text-foreground font-semibold tabular-nums">{formatMemoryMB(totalMemory)}</span>
79
- </span>
80
- </div>
81
-
82
- {/* Process table */}
83
- <div className="space-y-1.5">
84
- {/* Header */}
85
- <div className="grid grid-cols-[1fr_4rem_5rem_4rem_3rem] gap-2 text-[10px] font-medium text-foreground-subtle uppercase tracking-wider pb-1">
86
- <span>Process</span>
87
- <span className="text-right">CPU</span>
88
- <span className="text-right">Memory</span>
89
- <span className="text-right">Uptime</span>
90
- <span className="text-right">Rst</span>
91
- </div>
92
-
93
- {processes.map((p) => (
94
- <div
95
- key={p.id}
96
- className="grid grid-cols-[1fr_4rem_5rem_4rem_3rem] gap-2 text-xs items-center"
97
- >
98
- <div className="flex items-center gap-1.5 min-w-0">
99
- <span className={`text-[10px] font-semibold ${stateColor(p.state)}`}>
100
-
101
- </span>
102
- <span className="text-foreground truncate">{p.label ?? p.id}</span>
103
- </div>
104
- <span className="tabular-nums text-foreground-subtle text-right">
105
- {p.stats?.cpu != null ? `${p.stats.cpu.toFixed(1)}%` : '—'}
106
- </span>
107
- <span className="tabular-nums text-foreground-subtle text-right">
108
- {formatMemoryMB(p.stats?.memory)}
109
- </span>
110
- <span className="tabular-nums text-foreground-subtle text-right">
111
- {formatUptime(p.stats?.uptime)}
112
- </span>
113
- <span className="tabular-nums text-foreground-subtle text-right">
114
- {p.restartCount ?? 0}
115
- </span>
116
- </div>
117
- ))}
118
- </div>
119
- </>
120
- )}
121
- </div>
122
- )
123
- }
@@ -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
- }