@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.
- package/dist/assets/index-DjELGD4R.css +1 -0
- package/dist/assets/index-w55PwKyu.js +598 -0
- package/{index.html → dist/index.html} +3 -1
- package/dist/server/addon.d.ts +11 -0
- package/dist/server/addon.js +50 -0
- package/dist/server/addon.js.map +1 -0
- package/package.json +4 -1
- package/src/App.tsx +0 -71
- package/src/components/addons/AddonCard.tsx +0 -355
- package/src/components/addons/AddonUploadZone.tsx +0 -69
- package/src/components/addons/CapabilityBadge.tsx +0 -55
- package/src/components/addons/CapabilityMap.tsx +0 -133
- package/src/components/addons/UpdatesList.tsx +0 -108
- package/src/components/agents/AgentCard.tsx +0 -281
- package/src/components/agents/AgentLogs.tsx +0 -231
- package/src/components/agents/ProcessList.tsx +0 -127
- package/src/components/agents/ProcessTree.tsx +0 -369
- package/src/components/agents/TaskList.tsx +0 -68
- package/src/components/cameras/CameraCard.tsx +0 -60
- package/src/components/cameras/LiveEventsPanel.tsx +0 -91
- package/src/components/cameras/ProviderSection.tsx +0 -50
- package/src/components/cameras/StreamArea.tsx +0 -107
- package/src/components/cameras/tabs/AddonsTab.tsx +0 -113
- package/src/components/cameras/tabs/CameraEventsTab.tsx +0 -129
- package/src/components/cameras/tabs/PipelineTab.tsx +0 -118
- package/src/components/cameras/tabs/StreamsTab.tsx +0 -114
- package/src/components/dashboard/BlockPicker.tsx +0 -54
- package/src/components/dashboard/BlockWrapper.tsx +0 -97
- package/src/components/dashboard/DashboardGrid.tsx +0 -160
- package/src/components/dashboard/block-registry.ts +0 -15
- package/src/components/dashboard/blocks/PipelineStagesBlock.tsx +0 -39
- package/src/components/dashboard/blocks/StorageBlock.tsx +0 -66
- package/src/components/dashboard/blocks/SystemStatusBlock.tsx +0 -67
- package/src/components/dashboard/blocks/index.ts +0 -32
- package/src/components/device/DeviceHeader.tsx +0 -116
- package/src/components/device/FloatingPanel.tsx +0 -132
- package/src/components/device/FloatingPanelManager.tsx +0 -167
- package/src/components/device/PanelContent.tsx +0 -196
- package/src/components/device/QuickConfigWizard.tsx +0 -507
- package/src/components/device/tabs/DetectionConfigTab.tsx +0 -96
- package/src/components/device/tabs/EventsTab.tsx +0 -19
- package/src/components/device/tabs/LogsTab.tsx +0 -22
- package/src/components/device/tabs/OverviewTab.tsx +0 -104
- package/src/components/device/tabs/ProviderSettingsTab.tsx +0 -34
- package/src/components/device/tabs/RecordingTab.tsx +0 -47
- package/src/components/device/tabs/ReplTab.tsx +0 -153
- package/src/components/device/tabs/TrackTrailTab.tsx +0 -49
- package/src/components/device/tabs/ZonesTab.tsx +0 -98
- package/src/components/device/zone-editor/ZoneCanvas.tsx +0 -354
- package/src/components/device/zone-editor/ZoneForm.tsx +0 -128
- package/src/components/device/zone-editor/ZoneList.tsx +0 -150
- package/src/components/form-builder/FormBuilder.tsx +0 -135
- package/src/components/form-builder/FormField.tsx +0 -732
- package/src/components/form-builder/ModelSelector.tsx +0 -239
- package/src/components/integrations/AddDeviceDialog.tsx +0 -205
- package/src/components/integrations/CompactDeviceCard.tsx +0 -35
- package/src/components/integrations/DeviceCard.tsx +0 -29
- package/src/components/integrations/DeviceDiscoveryStep.tsx +0 -105
- package/src/components/integrations/DeviceGrid.tsx +0 -79
- package/src/components/integrations/DeviceGroupHeader.tsx +0 -17
- package/src/components/integrations/DiscoveredDeviceCard.tsx +0 -26
- package/src/components/integrations/IntegrationCard.tsx +0 -40
- package/src/components/integrations/IntegrationWizard.tsx +0 -172
- package/src/components/integrations/ProviderConfigForm.tsx +0 -89
- package/src/components/integrations/ProviderPicker.tsx +0 -91
- package/src/components/integrations/SnapshotPopover.tsx +0 -68
- package/src/components/metrics/AgentLoad.tsx +0 -105
- package/src/components/metrics/IntegrationUsage.tsx +0 -73
- package/src/components/metrics/PipelineStatus.tsx +0 -74
- package/src/components/metrics/ProcessResources.tsx +0 -123
- package/src/components/pipeline/PhaseSettings.tsx +0 -131
- package/src/components/shared/CapabilityBadges.tsx +0 -30
- package/src/components/shared/ProviderIcon.tsx +0 -42
- package/src/components/shared/StatusBadge.tsx +0 -23
- package/src/components/shared/WebRtcPlayer.tsx +0 -211
- package/src/components/timeline/EventMarker.tsx +0 -32
- package/src/components/timeline/TimelineBar.tsx +0 -131
- package/src/components/ui/ConfirmDialog.tsx +0 -115
- package/src/components/ui/ToastContainer.tsx +0 -92
- package/src/contexts/auth-context.tsx +0 -91
- package/src/hooks/useBackendClient.ts +0 -6
- package/src/hooks/useTheme.ts +0 -1
- package/src/i18n/en.json +0 -164
- package/src/i18n/index.ts +0 -29
- package/src/i18n/it.json +0 -164
- package/src/index.css +0 -63
- package/src/layouts/AddonPageLoader.tsx +0 -120
- package/src/layouts/AppLayout.tsx +0 -254
- package/src/layouts/ProtectedRoute.tsx +0 -25
- package/src/lib/addon-page-context.ts +0 -29
- package/src/lib/backend.ts +0 -16
- package/src/main.tsx +0 -21
- package/src/pages/AccessDenied.tsx +0 -22
- package/src/pages/Cameras.tsx +0 -127
- package/src/pages/Dashboard.tsx +0 -6
- package/src/pages/DeviceDetail.tsx +0 -175
- package/src/pages/IntegrationDetail.tsx +0 -222
- package/src/pages/Integrations.tsx +0 -333
- package/src/pages/Login.tsx +0 -106
- package/src/pages/Metrics.tsx +0 -18
- package/src/pages/PipelineConfig.tsx +0 -282
- package/src/pages/Showroom.tsx +0 -351
- package/src/pages/Timeline.tsx +0 -269
- package/src/pages/system/Addons.tsx +0 -396
- package/src/pages/system/Agents.tsx +0 -362
- package/src/pages/system/Logs.tsx +0 -131
- package/src/pages/system/Models.tsx +0 -102
- package/src/pages/system/Processes.tsx +0 -129
- package/src/pages/system/Repl.tsx +0 -148
- package/src/pages/system/Settings.tsx +0 -168
- package/src/pages/system/Users.tsx +0 -174
- package/src/server/addon.ts +0 -54
- package/src/types/config-ui.ts +0 -28
- package/src/types/dashboard.ts +0 -39
- package/tsconfig.json +0 -29
- package/tsconfig.server.json +0 -16
- package/tsup.config.ts +0 -20
- package/vite.config.ts +0 -68
- /package/{public → dist}/brand/logo-dark.svg +0 -0
- /package/{public → dist}/brand/logo-horizontal-dark.svg +0 -0
- /package/{public → dist}/brand/logo-horizontal-light.svg +0 -0
- /package/{public → dist}/brand/logo-light.svg +0 -0
- /package/{public → dist}/brand/logo-wide-dark.svg +0 -0
- /package/{public → dist}/brand/logo-wide-light.svg +0 -0
- /package/{public → dist}/favicon.svg +0 -0
- /package/{public → dist}/vendor/react-jsx-runtime.mjs +0 -0
- /package/{public → dist}/vendor/react.mjs +0 -0
|
@@ -1,507 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react'
|
|
2
|
-
import { X, ChevronLeft, ChevronRight, Check, Zap } from 'lucide-react'
|
|
3
|
-
|
|
4
|
-
interface QuickConfigWizardProps {
|
|
5
|
-
open: boolean
|
|
6
|
-
onClose: () => void
|
|
7
|
-
deviceId: string
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
// ----- Step 1: Detection Classes -----
|
|
11
|
-
interface DetectionClass {
|
|
12
|
-
id: string
|
|
13
|
-
label: string
|
|
14
|
-
enabled: boolean
|
|
15
|
-
score: number
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const DEFAULT_DETECTION_CLASSES: DetectionClass[] = [
|
|
19
|
-
{ id: 'person', label: 'Person', enabled: true, score: 50 },
|
|
20
|
-
{ id: 'vehicle', label: 'Vehicle', enabled: true, score: 50 },
|
|
21
|
-
{ id: 'animal', label: 'Animal', enabled: false, score: 50 },
|
|
22
|
-
{ id: 'package', label: 'Package', enabled: false, score: 50 },
|
|
23
|
-
]
|
|
24
|
-
|
|
25
|
-
// ----- Step 2: Pipeline Stages -----
|
|
26
|
-
interface PipelineStage {
|
|
27
|
-
id: string
|
|
28
|
-
label: string
|
|
29
|
-
enabled: boolean
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const DEFAULT_PIPELINE_STAGES: PipelineStage[] = [
|
|
33
|
-
{ id: 'class-filter', label: 'Class Filter', enabled: true },
|
|
34
|
-
{ id: 'tracker', label: 'Tracker', enabled: true },
|
|
35
|
-
{ id: 'sub-detection', label: 'Sub-Detection', enabled: false },
|
|
36
|
-
{ id: 'recognition', label: 'Recognition', enabled: false },
|
|
37
|
-
{ id: 'zone-analysis', label: 'Zone Analysis', enabled: false },
|
|
38
|
-
{ id: 'event-generation', label: 'Event Generation', enabled: true },
|
|
39
|
-
{ id: 'object-snapshot', label: 'Object Snapshot', enabled: true },
|
|
40
|
-
]
|
|
41
|
-
|
|
42
|
-
// ----- Step 3: Recording -----
|
|
43
|
-
interface RecordingConfig {
|
|
44
|
-
enabled: boolean
|
|
45
|
-
retentionDays: number
|
|
46
|
-
format: 'mp4' | 'ts'
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// ----- Step 4: Extensions -----
|
|
50
|
-
interface Extension {
|
|
51
|
-
id: string
|
|
52
|
-
label: string
|
|
53
|
-
enabled: boolean
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const DEFAULT_EXTENSIONS: Extension[] = [
|
|
57
|
-
{ id: 'face-recognition', label: 'Face Recognition', enabled: false },
|
|
58
|
-
{ id: 'plate-ocr', label: 'Plate OCR', enabled: false },
|
|
59
|
-
{ id: 'audio-classifier', label: 'Audio Classifier', enabled: false },
|
|
60
|
-
{ id: 'trail-capture', label: 'Trail Capture', enabled: false },
|
|
61
|
-
]
|
|
62
|
-
|
|
63
|
-
// ----- Wizard config state -----
|
|
64
|
-
interface WizardConfig {
|
|
65
|
-
detectionClasses: DetectionClass[]
|
|
66
|
-
pipelineStages: PipelineStage[]
|
|
67
|
-
recording: RecordingConfig
|
|
68
|
-
extensions: Extension[]
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const STEPS = [
|
|
72
|
-
'Detection Classes',
|
|
73
|
-
'Pipeline Stages',
|
|
74
|
-
'Recording',
|
|
75
|
-
'Extensions',
|
|
76
|
-
'Summary',
|
|
77
|
-
] as const
|
|
78
|
-
|
|
79
|
-
type Step = 0 | 1 | 2 | 3 | 4
|
|
80
|
-
|
|
81
|
-
// ----- Sub-components -----
|
|
82
|
-
|
|
83
|
-
function StepIndicator({ current, total }: { current: Step; total: number }) {
|
|
84
|
-
return (
|
|
85
|
-
<div className="flex items-center justify-center gap-2 mb-6">
|
|
86
|
-
{Array.from({ length: total }).map((_, i) => (
|
|
87
|
-
<div
|
|
88
|
-
key={i}
|
|
89
|
-
className={`rounded-full transition-all ${
|
|
90
|
-
i === current
|
|
91
|
-
? 'h-2 w-6 bg-primary'
|
|
92
|
-
: i < current
|
|
93
|
-
? 'h-2 w-2 bg-primary/50'
|
|
94
|
-
: 'h-2 w-2 bg-border'
|
|
95
|
-
}`}
|
|
96
|
-
/>
|
|
97
|
-
))}
|
|
98
|
-
</div>
|
|
99
|
-
)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function ToggleSwitch({
|
|
103
|
-
checked,
|
|
104
|
-
onChange,
|
|
105
|
-
}: {
|
|
106
|
-
checked: boolean
|
|
107
|
-
onChange: (v: boolean) => void
|
|
108
|
-
}) {
|
|
109
|
-
return (
|
|
110
|
-
<button
|
|
111
|
-
type="button"
|
|
112
|
-
role="switch"
|
|
113
|
-
aria-checked={checked}
|
|
114
|
-
onClick={() => onChange(!checked)}
|
|
115
|
-
className={`relative inline-flex h-5 w-9 flex-shrink-0 rounded-full border-2 border-transparent transition-colors focus:outline-none ${
|
|
116
|
-
checked ? 'bg-primary' : 'bg-border'
|
|
117
|
-
}`}
|
|
118
|
-
>
|
|
119
|
-
<span
|
|
120
|
-
className={`pointer-events-none inline-block h-4 w-4 rounded-full bg-white shadow transition-transform ${
|
|
121
|
-
checked ? 'translate-x-4' : 'translate-x-0'
|
|
122
|
-
}`}
|
|
123
|
-
/>
|
|
124
|
-
</button>
|
|
125
|
-
)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Step 1
|
|
129
|
-
function Step1DetectionClasses({
|
|
130
|
-
classes,
|
|
131
|
-
onChange,
|
|
132
|
-
}: {
|
|
133
|
-
classes: DetectionClass[]
|
|
134
|
-
onChange: (updated: DetectionClass[]) => void
|
|
135
|
-
}) {
|
|
136
|
-
function toggleClass(id: string) {
|
|
137
|
-
onChange(classes.map((c) => (c.id === id ? { ...c, enabled: !c.enabled } : c)))
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function setScore(id: string, score: number) {
|
|
141
|
-
onChange(classes.map((c) => (c.id === id ? { ...c, score } : c)))
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return (
|
|
145
|
-
<div className="space-y-4">
|
|
146
|
-
<p className="text-xs text-foreground-subtle">
|
|
147
|
-
Enable detection classes and set minimum confidence scores.
|
|
148
|
-
</p>
|
|
149
|
-
{classes.map((cls) => (
|
|
150
|
-
<div key={cls.id} className="rounded-lg border border-border bg-background p-3 space-y-2">
|
|
151
|
-
<div className="flex items-center justify-between">
|
|
152
|
-
<span className="text-sm font-medium text-foreground">{cls.label}</span>
|
|
153
|
-
<ToggleSwitch checked={cls.enabled} onChange={() => toggleClass(cls.id)} />
|
|
154
|
-
</div>
|
|
155
|
-
{cls.enabled && (
|
|
156
|
-
<div className="flex items-center gap-3">
|
|
157
|
-
<span className="text-[11px] text-foreground-subtle w-16 flex-shrink-0">
|
|
158
|
-
Min score
|
|
159
|
-
</span>
|
|
160
|
-
<input
|
|
161
|
-
type="range"
|
|
162
|
-
min={0}
|
|
163
|
-
max={100}
|
|
164
|
-
value={cls.score}
|
|
165
|
-
onChange={(e) => setScore(cls.id, Number(e.target.value))}
|
|
166
|
-
className="flex-1 accent-primary"
|
|
167
|
-
/>
|
|
168
|
-
<span className="text-[11px] text-foreground w-8 text-right">{cls.score}%</span>
|
|
169
|
-
</div>
|
|
170
|
-
)}
|
|
171
|
-
</div>
|
|
172
|
-
))}
|
|
173
|
-
</div>
|
|
174
|
-
)
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Step 2
|
|
178
|
-
function Step2PipelineStages({
|
|
179
|
-
stages,
|
|
180
|
-
onChange,
|
|
181
|
-
}: {
|
|
182
|
-
stages: PipelineStage[]
|
|
183
|
-
onChange: (updated: PipelineStage[]) => void
|
|
184
|
-
}) {
|
|
185
|
-
function toggleStage(id: string) {
|
|
186
|
-
onChange(stages.map((s) => (s.id === id ? { ...s, enabled: !s.enabled } : s)))
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return (
|
|
190
|
-
<div className="space-y-3">
|
|
191
|
-
<p className="text-xs text-foreground-subtle">
|
|
192
|
-
Toggle processing stages for the detection pipeline.
|
|
193
|
-
</p>
|
|
194
|
-
{stages.map((stage) => (
|
|
195
|
-
<div
|
|
196
|
-
key={stage.id}
|
|
197
|
-
className="flex items-center justify-between rounded-lg border border-border bg-background px-3 py-2.5"
|
|
198
|
-
>
|
|
199
|
-
<span className="text-sm text-foreground">{stage.label}</span>
|
|
200
|
-
<ToggleSwitch checked={stage.enabled} onChange={() => toggleStage(stage.id)} />
|
|
201
|
-
</div>
|
|
202
|
-
))}
|
|
203
|
-
</div>
|
|
204
|
-
)
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Step 3
|
|
208
|
-
function Step3Recording({
|
|
209
|
-
config,
|
|
210
|
-
onChange,
|
|
211
|
-
}: {
|
|
212
|
-
config: RecordingConfig
|
|
213
|
-
onChange: (updated: RecordingConfig) => void
|
|
214
|
-
}) {
|
|
215
|
-
return (
|
|
216
|
-
<div className="space-y-4">
|
|
217
|
-
<p className="text-xs text-foreground-subtle">Configure recording behavior for this device.</p>
|
|
218
|
-
|
|
219
|
-
<div className="flex items-center justify-between rounded-lg border border-border bg-background px-3 py-2.5">
|
|
220
|
-
<span className="text-sm text-foreground">Enable Recording</span>
|
|
221
|
-
<ToggleSwitch
|
|
222
|
-
checked={config.enabled}
|
|
223
|
-
onChange={(v) => onChange({ ...config, enabled: v })}
|
|
224
|
-
/>
|
|
225
|
-
</div>
|
|
226
|
-
|
|
227
|
-
{config.enabled && (
|
|
228
|
-
<>
|
|
229
|
-
<div className="rounded-lg border border-border bg-background p-3 space-y-2">
|
|
230
|
-
<div className="flex items-center justify-between">
|
|
231
|
-
<span className="text-sm font-medium text-foreground">Retention</span>
|
|
232
|
-
<span className="text-sm text-primary font-medium">{config.retentionDays} days</span>
|
|
233
|
-
</div>
|
|
234
|
-
<input
|
|
235
|
-
type="range"
|
|
236
|
-
min={1}
|
|
237
|
-
max={90}
|
|
238
|
-
value={config.retentionDays}
|
|
239
|
-
onChange={(e) => onChange({ ...config, retentionDays: Number(e.target.value) })}
|
|
240
|
-
className="w-full accent-primary"
|
|
241
|
-
/>
|
|
242
|
-
<div className="flex justify-between text-[10px] text-foreground-subtle">
|
|
243
|
-
<span>1 day</span>
|
|
244
|
-
<span>90 days</span>
|
|
245
|
-
</div>
|
|
246
|
-
</div>
|
|
247
|
-
|
|
248
|
-
<div className="rounded-lg border border-border bg-background p-3 space-y-2">
|
|
249
|
-
<span className="text-sm font-medium text-foreground block">Format</span>
|
|
250
|
-
<div className="flex gap-2">
|
|
251
|
-
{(['mp4', 'ts'] as const).map((fmt) => (
|
|
252
|
-
<button
|
|
253
|
-
key={fmt}
|
|
254
|
-
type="button"
|
|
255
|
-
onClick={() => onChange({ ...config, format: fmt })}
|
|
256
|
-
className={`flex-1 rounded-lg border py-2 text-xs font-medium transition-colors ${
|
|
257
|
-
config.format === fmt
|
|
258
|
-
? 'border-primary bg-primary/10 text-primary'
|
|
259
|
-
: 'border-border text-foreground-subtle hover:text-foreground'
|
|
260
|
-
}`}
|
|
261
|
-
>
|
|
262
|
-
.{fmt}
|
|
263
|
-
</button>
|
|
264
|
-
))}
|
|
265
|
-
</div>
|
|
266
|
-
</div>
|
|
267
|
-
</>
|
|
268
|
-
)}
|
|
269
|
-
</div>
|
|
270
|
-
)
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Step 4
|
|
274
|
-
function Step4Extensions({
|
|
275
|
-
extensions,
|
|
276
|
-
onChange,
|
|
277
|
-
}: {
|
|
278
|
-
extensions: Extension[]
|
|
279
|
-
onChange: (updated: Extension[]) => void
|
|
280
|
-
}) {
|
|
281
|
-
function toggleExtension(id: string) {
|
|
282
|
-
onChange(extensions.map((e) => (e.id === id ? { ...e, enabled: !e.enabled } : e)))
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
return (
|
|
286
|
-
<div className="space-y-3">
|
|
287
|
-
<p className="text-xs text-foreground-subtle">Enable optional processing add-ons.</p>
|
|
288
|
-
{extensions.map((ext) => (
|
|
289
|
-
<div
|
|
290
|
-
key={ext.id}
|
|
291
|
-
className="flex items-center justify-between rounded-lg border border-border bg-background px-3 py-2.5"
|
|
292
|
-
>
|
|
293
|
-
<span className="text-sm text-foreground">{ext.label}</span>
|
|
294
|
-
<ToggleSwitch checked={ext.enabled} onChange={() => toggleExtension(ext.id)} />
|
|
295
|
-
</div>
|
|
296
|
-
))}
|
|
297
|
-
</div>
|
|
298
|
-
)
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// Step 5 Summary
|
|
302
|
-
function Step5Summary({ config }: { config: WizardConfig }) {
|
|
303
|
-
const enabledClasses = config.detectionClasses.filter((c) => c.enabled)
|
|
304
|
-
const enabledStages = config.pipelineStages.filter((s) => s.enabled)
|
|
305
|
-
const enabledExtensions = config.extensions.filter((e) => e.enabled)
|
|
306
|
-
|
|
307
|
-
return (
|
|
308
|
-
<div className="space-y-4">
|
|
309
|
-
<p className="text-xs text-foreground-subtle">Review your configuration before applying.</p>
|
|
310
|
-
|
|
311
|
-
{/* Detection classes */}
|
|
312
|
-
<div className="rounded-lg border border-border bg-background p-3 space-y-1">
|
|
313
|
-
<span className="text-[11px] font-semibold text-foreground uppercase tracking-wider">
|
|
314
|
-
Detection Classes
|
|
315
|
-
</span>
|
|
316
|
-
{enabledClasses.length === 0 ? (
|
|
317
|
-
<p className="text-xs text-foreground-subtle">None enabled</p>
|
|
318
|
-
) : (
|
|
319
|
-
enabledClasses.map((c) => (
|
|
320
|
-
<div key={c.id} className="flex justify-between text-xs">
|
|
321
|
-
<span className="text-foreground">{c.label}</span>
|
|
322
|
-
<span className="text-foreground-subtle">{c.score}% min score</span>
|
|
323
|
-
</div>
|
|
324
|
-
))
|
|
325
|
-
)}
|
|
326
|
-
</div>
|
|
327
|
-
|
|
328
|
-
{/* Pipeline stages */}
|
|
329
|
-
<div className="rounded-lg border border-border bg-background p-3 space-y-1">
|
|
330
|
-
<span className="text-[11px] font-semibold text-foreground uppercase tracking-wider">
|
|
331
|
-
Pipeline Stages
|
|
332
|
-
</span>
|
|
333
|
-
{enabledStages.length === 0 ? (
|
|
334
|
-
<p className="text-xs text-foreground-subtle">None enabled</p>
|
|
335
|
-
) : (
|
|
336
|
-
<div className="flex flex-wrap gap-1 mt-1">
|
|
337
|
-
{enabledStages.map((s) => (
|
|
338
|
-
<span
|
|
339
|
-
key={s.id}
|
|
340
|
-
className="inline-flex items-center rounded-full bg-primary/10 px-2 py-0.5 text-[10px] font-medium text-primary"
|
|
341
|
-
>
|
|
342
|
-
{s.label}
|
|
343
|
-
</span>
|
|
344
|
-
))}
|
|
345
|
-
</div>
|
|
346
|
-
)}
|
|
347
|
-
</div>
|
|
348
|
-
|
|
349
|
-
{/* Recording */}
|
|
350
|
-
<div className="rounded-lg border border-border bg-background p-3 space-y-1">
|
|
351
|
-
<span className="text-[11px] font-semibold text-foreground uppercase tracking-wider">
|
|
352
|
-
Recording
|
|
353
|
-
</span>
|
|
354
|
-
{config.recording.enabled ? (
|
|
355
|
-
<div className="text-xs text-foreground space-y-0.5">
|
|
356
|
-
<div className="flex justify-between">
|
|
357
|
-
<span>Status</span>
|
|
358
|
-
<span className="text-success">Enabled</span>
|
|
359
|
-
</div>
|
|
360
|
-
<div className="flex justify-between">
|
|
361
|
-
<span>Retention</span>
|
|
362
|
-
<span>{config.recording.retentionDays} days</span>
|
|
363
|
-
</div>
|
|
364
|
-
<div className="flex justify-between">
|
|
365
|
-
<span>Format</span>
|
|
366
|
-
<span>.{config.recording.format}</span>
|
|
367
|
-
</div>
|
|
368
|
-
</div>
|
|
369
|
-
) : (
|
|
370
|
-
<p className="text-xs text-foreground-subtle">Disabled</p>
|
|
371
|
-
)}
|
|
372
|
-
</div>
|
|
373
|
-
|
|
374
|
-
{/* Extensions */}
|
|
375
|
-
<div className="rounded-lg border border-border bg-background p-3 space-y-1">
|
|
376
|
-
<span className="text-[11px] font-semibold text-foreground uppercase tracking-wider">
|
|
377
|
-
Extensions
|
|
378
|
-
</span>
|
|
379
|
-
{enabledExtensions.length === 0 ? (
|
|
380
|
-
<p className="text-xs text-foreground-subtle">None enabled</p>
|
|
381
|
-
) : (
|
|
382
|
-
<div className="flex flex-wrap gap-1 mt-1">
|
|
383
|
-
{enabledExtensions.map((e) => (
|
|
384
|
-
<span
|
|
385
|
-
key={e.id}
|
|
386
|
-
className="inline-flex items-center rounded-full bg-success/10 px-2 py-0.5 text-[10px] font-medium text-success"
|
|
387
|
-
>
|
|
388
|
-
{e.label}
|
|
389
|
-
</span>
|
|
390
|
-
))}
|
|
391
|
-
</div>
|
|
392
|
-
)}
|
|
393
|
-
</div>
|
|
394
|
-
</div>
|
|
395
|
-
)
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// ----- Main Wizard -----
|
|
399
|
-
|
|
400
|
-
export function QuickConfigWizard({ open, onClose, deviceId }: QuickConfigWizardProps) {
|
|
401
|
-
const [step, setStep] = useState<Step>(0)
|
|
402
|
-
const [config, setConfig] = useState<WizardConfig>({
|
|
403
|
-
detectionClasses: DEFAULT_DETECTION_CLASSES,
|
|
404
|
-
pipelineStages: DEFAULT_PIPELINE_STAGES,
|
|
405
|
-
recording: { enabled: false, retentionDays: 14, format: 'mp4' },
|
|
406
|
-
extensions: DEFAULT_EXTENSIONS,
|
|
407
|
-
})
|
|
408
|
-
|
|
409
|
-
if (!open) return null
|
|
410
|
-
|
|
411
|
-
function handleApply() {
|
|
412
|
-
console.log('[QuickConfigWizard] Applying config for device', deviceId, config)
|
|
413
|
-
onClose()
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
function handleClose() {
|
|
417
|
-
setStep(0)
|
|
418
|
-
onClose()
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
return (
|
|
422
|
-
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">
|
|
423
|
-
<div className="w-full max-w-lg rounded-xl border border-border bg-surface p-6 shadow-2xl">
|
|
424
|
-
{/* Header */}
|
|
425
|
-
<div className="flex items-center justify-between mb-2">
|
|
426
|
-
<div className="flex items-center gap-2">
|
|
427
|
-
<Zap className="h-4 w-4 text-primary" />
|
|
428
|
-
<h2 className="text-sm font-semibold text-foreground">Quick Config</h2>
|
|
429
|
-
</div>
|
|
430
|
-
<button
|
|
431
|
-
onClick={handleClose}
|
|
432
|
-
className="text-foreground-subtle hover:text-foreground transition-colors p-1 rounded"
|
|
433
|
-
>
|
|
434
|
-
<X className="h-4 w-4" />
|
|
435
|
-
</button>
|
|
436
|
-
</div>
|
|
437
|
-
|
|
438
|
-
{/* Step title */}
|
|
439
|
-
<p className="text-xs text-foreground-subtle mb-4">
|
|
440
|
-
Step {step + 1} of {STEPS.length} — {STEPS[step]}
|
|
441
|
-
</p>
|
|
442
|
-
|
|
443
|
-
{/* Step indicator dots */}
|
|
444
|
-
<StepIndicator current={step} total={STEPS.length} />
|
|
445
|
-
|
|
446
|
-
{/* Step content */}
|
|
447
|
-
<div className="min-h-[300px] max-h-[420px] overflow-y-auto">
|
|
448
|
-
{step === 0 && (
|
|
449
|
-
<Step1DetectionClasses
|
|
450
|
-
classes={config.detectionClasses}
|
|
451
|
-
onChange={(detectionClasses) => setConfig((prev) => ({ ...prev, detectionClasses }))}
|
|
452
|
-
/>
|
|
453
|
-
)}
|
|
454
|
-
{step === 1 && (
|
|
455
|
-
<Step2PipelineStages
|
|
456
|
-
stages={config.pipelineStages}
|
|
457
|
-
onChange={(pipelineStages) => setConfig((prev) => ({ ...prev, pipelineStages }))}
|
|
458
|
-
/>
|
|
459
|
-
)}
|
|
460
|
-
{step === 2 && (
|
|
461
|
-
<Step3Recording
|
|
462
|
-
config={config.recording}
|
|
463
|
-
onChange={(recording) => setConfig((prev) => ({ ...prev, recording }))}
|
|
464
|
-
/>
|
|
465
|
-
)}
|
|
466
|
-
{step === 3 && (
|
|
467
|
-
<Step4Extensions
|
|
468
|
-
extensions={config.extensions}
|
|
469
|
-
onChange={(extensions) => setConfig((prev) => ({ ...prev, extensions }))}
|
|
470
|
-
/>
|
|
471
|
-
)}
|
|
472
|
-
{step === 4 && <Step5Summary config={config} />}
|
|
473
|
-
</div>
|
|
474
|
-
|
|
475
|
-
{/* Navigation */}
|
|
476
|
-
<div className="flex items-center justify-between mt-6 pt-4 border-t border-border">
|
|
477
|
-
<button
|
|
478
|
-
onClick={() => setStep((s) => (s - 1) as Step)}
|
|
479
|
-
disabled={step === 0}
|
|
480
|
-
className="inline-flex items-center gap-1.5 rounded-lg border border-border px-3 py-1.5 text-xs font-medium text-foreground-subtle hover:text-foreground hover:bg-surface-hover disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
|
|
481
|
-
>
|
|
482
|
-
<ChevronLeft className="h-3.5 w-3.5" />
|
|
483
|
-
Back
|
|
484
|
-
</button>
|
|
485
|
-
|
|
486
|
-
{step < 4 ? (
|
|
487
|
-
<button
|
|
488
|
-
onClick={() => setStep((s) => (s + 1) as Step)}
|
|
489
|
-
className="inline-flex items-center gap-1.5 rounded-lg bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 transition-colors"
|
|
490
|
-
>
|
|
491
|
-
Next
|
|
492
|
-
<ChevronRight className="h-3.5 w-3.5" />
|
|
493
|
-
</button>
|
|
494
|
-
) : (
|
|
495
|
-
<button
|
|
496
|
-
onClick={handleApply}
|
|
497
|
-
className="inline-flex items-center gap-1.5 rounded-lg bg-success px-4 py-1.5 text-xs font-medium text-white hover:bg-success/90 transition-colors"
|
|
498
|
-
>
|
|
499
|
-
<Check className="h-3.5 w-3.5" />
|
|
500
|
-
Apply
|
|
501
|
-
</button>
|
|
502
|
-
)}
|
|
503
|
-
</div>
|
|
504
|
-
</div>
|
|
505
|
-
</div>
|
|
506
|
-
)
|
|
507
|
-
}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react'
|
|
2
|
-
|
|
3
|
-
interface ClassConfig {
|
|
4
|
-
id: string
|
|
5
|
-
label: string
|
|
6
|
-
enabled: boolean
|
|
7
|
-
score: number
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const DEFAULT_CLASSES: ClassConfig[] = [
|
|
11
|
-
{ id: 'person', label: 'Person', enabled: true, score: 0.5 },
|
|
12
|
-
{ id: 'vehicle', label: 'Vehicle', enabled: true, score: 0.5 },
|
|
13
|
-
{ id: 'animal', label: 'Animal', enabled: false, score: 0.5 },
|
|
14
|
-
{ id: 'package', label: 'Package', enabled: false, score: 0.5 },
|
|
15
|
-
]
|
|
16
|
-
|
|
17
|
-
const DEFAULT_STAGES = [
|
|
18
|
-
{ id: 'motion_filter', label: 'Motion Filter' },
|
|
19
|
-
{ id: 'object_detection', label: 'Object Detection' },
|
|
20
|
-
{ id: 'zone_filter', label: 'Zone Filter' },
|
|
21
|
-
{ id: 'tracking', label: 'Tracking' },
|
|
22
|
-
{ id: 'event_aggregation', label: 'Event Aggregation' },
|
|
23
|
-
]
|
|
24
|
-
|
|
25
|
-
export function DetectionConfigTab() {
|
|
26
|
-
const [classes] = useState<ClassConfig[]>(DEFAULT_CLASSES)
|
|
27
|
-
const [stages] = useState(
|
|
28
|
-
DEFAULT_STAGES.map((s) => ({ ...s, enabled: true }))
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<div className="space-y-6">
|
|
33
|
-
{/* Primary Detection Classes */}
|
|
34
|
-
<div className="rounded-lg border border-border bg-surface overflow-hidden">
|
|
35
|
-
<div className="border-b border-border px-4 py-2.5">
|
|
36
|
-
<h2 className="text-xs font-semibold text-foreground uppercase tracking-wider">Detection Classes</h2>
|
|
37
|
-
<p className="text-[11px] text-foreground-subtle mt-0.5">Read-only — configuration editing coming soon</p>
|
|
38
|
-
</div>
|
|
39
|
-
<div className="divide-y divide-border">
|
|
40
|
-
{classes.map((cls) => (
|
|
41
|
-
<div key={cls.id} className="flex items-center gap-4 px-4 py-3">
|
|
42
|
-
<input
|
|
43
|
-
type="checkbox"
|
|
44
|
-
checked={cls.enabled}
|
|
45
|
-
readOnly
|
|
46
|
-
className="h-3.5 w-3.5 rounded border-border text-primary accent-primary cursor-not-allowed opacity-70"
|
|
47
|
-
/>
|
|
48
|
-
<span className="w-20 text-xs font-medium text-foreground">{cls.label}</span>
|
|
49
|
-
<div className="flex flex-1 items-center gap-3">
|
|
50
|
-
<input
|
|
51
|
-
type="range"
|
|
52
|
-
min={0}
|
|
53
|
-
max={1}
|
|
54
|
-
step={0.01}
|
|
55
|
-
value={cls.score}
|
|
56
|
-
readOnly
|
|
57
|
-
className="flex-1 accent-primary cursor-not-allowed opacity-70"
|
|
58
|
-
/>
|
|
59
|
-
<span className="w-10 text-right text-[11px] font-mono text-foreground-subtle">
|
|
60
|
-
{cls.score.toFixed(2)}
|
|
61
|
-
</span>
|
|
62
|
-
</div>
|
|
63
|
-
<span className="text-[10px] text-foreground-subtle w-12">min score</span>
|
|
64
|
-
</div>
|
|
65
|
-
))}
|
|
66
|
-
</div>
|
|
67
|
-
</div>
|
|
68
|
-
|
|
69
|
-
{/* Pipeline Stages */}
|
|
70
|
-
<div className="rounded-lg border border-border bg-surface overflow-hidden">
|
|
71
|
-
<div className="border-b border-border px-4 py-2.5">
|
|
72
|
-
<h2 className="text-xs font-semibold text-foreground uppercase tracking-wider">Pipeline Stages</h2>
|
|
73
|
-
<p className="text-[11px] text-foreground-subtle mt-0.5">Read-only — stage control coming soon</p>
|
|
74
|
-
</div>
|
|
75
|
-
<div className="divide-y divide-border">
|
|
76
|
-
{stages.map((stage) => (
|
|
77
|
-
<div key={stage.id} className="flex items-center justify-between px-4 py-3">
|
|
78
|
-
<span className="text-xs text-foreground">{stage.label}</span>
|
|
79
|
-
<div
|
|
80
|
-
className={`relative inline-flex h-5 w-9 flex-shrink-0 cursor-not-allowed rounded-full border-2 border-transparent transition-colors opacity-70 ${
|
|
81
|
-
stage.enabled ? 'bg-primary' : 'bg-foreground-subtle/30'
|
|
82
|
-
}`}
|
|
83
|
-
>
|
|
84
|
-
<span
|
|
85
|
-
className={`inline-block h-4 w-4 transform rounded-full bg-white shadow ring-0 transition-transform ${
|
|
86
|
-
stage.enabled ? 'translate-x-4' : 'translate-x-0'
|
|
87
|
-
}`}
|
|
88
|
-
/>
|
|
89
|
-
</div>
|
|
90
|
-
</div>
|
|
91
|
-
))}
|
|
92
|
-
</div>
|
|
93
|
-
</div>
|
|
94
|
-
</div>
|
|
95
|
-
)
|
|
96
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { Bell } from 'lucide-react'
|
|
2
|
-
|
|
3
|
-
export function EventsTab() {
|
|
4
|
-
return (
|
|
5
|
-
<div className="rounded-lg border border-border bg-surface overflow-hidden">
|
|
6
|
-
<div className="border-b border-border px-4 py-2.5 flex items-center justify-between">
|
|
7
|
-
<h2 className="text-xs font-semibold text-foreground uppercase tracking-wider">Event Timeline</h2>
|
|
8
|
-
<span className="inline-flex items-center gap-1 rounded-full bg-foreground-subtle/10 px-2 py-0.5 text-[10px] text-foreground-subtle">
|
|
9
|
-
0 events
|
|
10
|
-
</span>
|
|
11
|
-
</div>
|
|
12
|
-
<div className="flex flex-col items-center justify-center py-16 text-foreground-subtle">
|
|
13
|
-
<Bell className="h-8 w-8 mb-3 opacity-30" />
|
|
14
|
-
<p className="text-sm">No events</p>
|
|
15
|
-
<p className="text-xs mt-1 opacity-70">Device events will appear here as they occur</p>
|
|
16
|
-
</div>
|
|
17
|
-
</div>
|
|
18
|
-
)
|
|
19
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { ScrollText } from 'lucide-react'
|
|
2
|
-
|
|
3
|
-
export function LogsTab() {
|
|
4
|
-
return (
|
|
5
|
-
<div className="rounded-lg border border-border bg-surface overflow-hidden">
|
|
6
|
-
<div className="border-b border-border px-4 py-2.5 flex items-center justify-between">
|
|
7
|
-
<div>
|
|
8
|
-
<h2 className="text-xs font-semibold text-foreground uppercase tracking-wider">Device Logs</h2>
|
|
9
|
-
<p className="text-[11px] text-foreground-subtle mt-0.5">Scoped log stream for this device</p>
|
|
10
|
-
</div>
|
|
11
|
-
<span className="inline-flex items-center gap-1.5 rounded-full bg-info/10 px-2.5 py-0.5 text-[10px] font-medium text-info">
|
|
12
|
-
Coming with backend integration
|
|
13
|
-
</span>
|
|
14
|
-
</div>
|
|
15
|
-
<div className="flex flex-col items-center justify-center py-16 text-foreground-subtle">
|
|
16
|
-
<ScrollText className="h-8 w-8 mb-3 opacity-30" />
|
|
17
|
-
<p className="text-sm font-medium text-foreground">Device logs</p>
|
|
18
|
-
<p className="text-xs mt-1 opacity-70">Device logs — coming with backend integration</p>
|
|
19
|
-
</div>
|
|
20
|
-
</div>
|
|
21
|
-
)
|
|
22
|
-
}
|