@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.
- 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 +5 -1
- package/src/App.tsx +0 -71
- package/src/components/addons/AddonCard.tsx +0 -339
- package/src/components/addons/AddonUploadZone.tsx +0 -307
- package/src/components/addons/CapabilityBadge.tsx +0 -55
- package/src/components/addons/CapabilityMap.tsx +0 -133
- package/src/components/addons/UpdatesList.tsx +0 -119
- 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 -171
- 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 -113
- package/src/components/metrics/IntegrationUsage.tsx +0 -90
- package/src/components/metrics/PipelineStatus.tsx +0 -105
- package/src/components/metrics/ProcessResources.tsx +0 -139
- 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 -238
- 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 -224
- package/src/pages/Integrations.tsx +0 -330
- 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 -525
- 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 -210
- 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,282 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useCallback } from 'react'
|
|
2
|
-
import { GitBranch, Save, ChevronDown } from 'lucide-react'
|
|
3
|
-
import { useTranslation } from 'react-i18next'
|
|
4
|
-
import { useBackendClient } from '../hooks/useBackendClient'
|
|
5
|
-
import { PipelineBuilder } from '@camstack/ui-library'
|
|
6
|
-
import type { PipelineStepDisplayConfig } from '@camstack/ui-library'
|
|
7
|
-
import type {
|
|
8
|
-
PipelineSchema,
|
|
9
|
-
InferenceCapabilities,
|
|
10
|
-
PipelineTemplate,
|
|
11
|
-
} from '@camstack/types'
|
|
12
|
-
import { PhaseSettings } from '../components/pipeline/PhaseSettings'
|
|
13
|
-
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
// Global orchestrator settings
|
|
16
|
-
// ---------------------------------------------------------------------------
|
|
17
|
-
interface OrchestratorSettings {
|
|
18
|
-
motionFps: number
|
|
19
|
-
detectionFps: number
|
|
20
|
-
cooldownMs: number
|
|
21
|
-
maxConcurrentInferences: number | null // null = auto-detect
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const DEFAULT_ORCHESTRATOR: OrchestratorSettings = {
|
|
25
|
-
motionFps: 2,
|
|
26
|
-
detectionFps: 5,
|
|
27
|
-
cooldownMs: 10000,
|
|
28
|
-
maxConcurrentInferences: null,
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// No client-side tree building — steps come from pipeline.getDefaultSteps() server-side
|
|
32
|
-
|
|
33
|
-
// ---------------------------------------------------------------------------
|
|
34
|
-
// Component
|
|
35
|
-
// ---------------------------------------------------------------------------
|
|
36
|
-
|
|
37
|
-
export function PipelineConfigPage() {
|
|
38
|
-
const { t } = useTranslation()
|
|
39
|
-
const client = useBackendClient()
|
|
40
|
-
|
|
41
|
-
const [loading, setLoading] = useState(true)
|
|
42
|
-
const [schema, setSchema] = useState<PipelineSchema | null>(null)
|
|
43
|
-
const [capabilities, setCapabilities] = useState<InferenceCapabilities | null>(null)
|
|
44
|
-
const [steps, setSteps] = useState<readonly PipelineStepDisplayConfig[]>([])
|
|
45
|
-
const [templates, setTemplates] = useState<PipelineTemplate[]>([])
|
|
46
|
-
const [selectedTemplateId, setSelectedTemplateId] = useState<string | null>(null)
|
|
47
|
-
const [orchestrator, setOrchestrator] = useState<OrchestratorSettings>(DEFAULT_ORCHESTRATOR)
|
|
48
|
-
const [isDirty, setIsDirty] = useState(false)
|
|
49
|
-
const [isSaving, setIsSaving] = useState(false)
|
|
50
|
-
const [settingsOpen, setSettingsOpen] = useState(true)
|
|
51
|
-
|
|
52
|
-
// Load schema + capabilities + templates on mount
|
|
53
|
-
useEffect(() => {
|
|
54
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
55
|
-
const trpc = (client as any).trpc
|
|
56
|
-
if (!trpc) {
|
|
57
|
-
setLoading(false)
|
|
58
|
-
return
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
Promise.all([
|
|
62
|
-
trpc.pipeline?.getSchema?.query?.().catch(() => null) as Promise<PipelineSchema | null>,
|
|
63
|
-
trpc.inference?.getCapabilities?.query?.().catch(() => null) as Promise<InferenceCapabilities | null>,
|
|
64
|
-
trpc.pipeline?.getGlobalSteps?.query?.().catch(() => null) as Promise<PipelineStepDisplayConfig[] | null>,
|
|
65
|
-
trpc.pipeline?.getOrchestratorSettings?.query?.().catch(() => null) as Promise<OrchestratorSettings | null>,
|
|
66
|
-
trpc.pipeline?.listTemplates?.query?.().catch(() => []) as Promise<PipelineTemplate[]>,
|
|
67
|
-
])
|
|
68
|
-
.then(([sch, caps, globalSteps, orchSettings, tpls]) => {
|
|
69
|
-
if (sch) setSchema(sch)
|
|
70
|
-
if (caps) setCapabilities(caps)
|
|
71
|
-
if (globalSteps) setSteps(globalSteps)
|
|
72
|
-
if (orchSettings) setOrchestrator(orchSettings)
|
|
73
|
-
setTemplates(tpls ?? [])
|
|
74
|
-
setLoading(false)
|
|
75
|
-
})
|
|
76
|
-
.catch(() => setLoading(false))
|
|
77
|
-
}, [client])
|
|
78
|
-
|
|
79
|
-
const handleStepsChange = useCallback((newSteps: readonly PipelineStepDisplayConfig[]) => {
|
|
80
|
-
setSteps(newSteps)
|
|
81
|
-
setIsDirty(true)
|
|
82
|
-
}, [])
|
|
83
|
-
|
|
84
|
-
// Template handlers
|
|
85
|
-
const handleSelectTemplate = useCallback(
|
|
86
|
-
(id: string | null) => {
|
|
87
|
-
setSelectedTemplateId(id)
|
|
88
|
-
if (id && schema) {
|
|
89
|
-
const tpl = templates.find((t) => t.id === id)
|
|
90
|
-
if (tpl?.steps) {
|
|
91
|
-
const schemaMap = new Map(
|
|
92
|
-
schema.slots.flatMap((s) => s.addons.map((a) => [a.id, a] as const)),
|
|
93
|
-
)
|
|
94
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
95
|
-
const convertStep = (s: any): PipelineStepDisplayConfig => {
|
|
96
|
-
const addonSchema = schemaMap.get(s.addonId)
|
|
97
|
-
return {
|
|
98
|
-
addonId: s.addonId,
|
|
99
|
-
addonName: addonSchema?.name ?? s.addonId,
|
|
100
|
-
slot: addonSchema?.slot ?? 'detector',
|
|
101
|
-
inputClasses: addonSchema ? [...addonSchema.inputClasses] : [],
|
|
102
|
-
outputClasses: addonSchema ? [...addonSchema.outputClasses] : [],
|
|
103
|
-
enabled: s.enabled ?? true,
|
|
104
|
-
agentId: s.agentId ?? 'hub',
|
|
105
|
-
runtime: s.runtime ?? 'node',
|
|
106
|
-
backend: s.backend ?? 'cpu',
|
|
107
|
-
modelId: s.modelId ?? addonSchema?.defaultModelId ?? '',
|
|
108
|
-
confidence: s.confidence ?? addonSchema?.defaultConfidence ?? 0.3,
|
|
109
|
-
classFilters: s.classFilters ? [...s.classFilters] : [],
|
|
110
|
-
children: (s.children ?? []).map(convertStep),
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
setSteps(tpl.steps.map(convertStep))
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
-
[schema, templates],
|
|
118
|
-
)
|
|
119
|
-
|
|
120
|
-
const handleSaveTemplate = useCallback(
|
|
121
|
-
async (name: string, s: readonly PipelineStepDisplayConfig[]) => {
|
|
122
|
-
try {
|
|
123
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
124
|
-
const trpc = (client as any).trpc
|
|
125
|
-
const tpl = (await trpc.pipeline.saveTemplate.mutate({ name, steps: s })) as PipelineTemplate
|
|
126
|
-
setTemplates((prev) => [tpl, ...prev])
|
|
127
|
-
setSelectedTemplateId(tpl.id)
|
|
128
|
-
} catch {
|
|
129
|
-
// ignore — endpoint may not be registered yet
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
|
-
[client],
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
const handleUpdateTemplate = useCallback(
|
|
136
|
-
async (id: string, s: readonly PipelineStepDisplayConfig[]) => {
|
|
137
|
-
try {
|
|
138
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
139
|
-
const trpc = (client as any).trpc
|
|
140
|
-
const tpl = (await trpc.pipeline.updateTemplate.mutate({ id, steps: s })) as PipelineTemplate
|
|
141
|
-
setTemplates((prev) => prev.map((t) => (t.id === id ? tpl : t)))
|
|
142
|
-
} catch {
|
|
143
|
-
// ignore
|
|
144
|
-
}
|
|
145
|
-
},
|
|
146
|
-
[client],
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
const handleDeleteTemplate = useCallback(
|
|
150
|
-
async (id: string) => {
|
|
151
|
-
try {
|
|
152
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
153
|
-
const trpc = (client as any).trpc
|
|
154
|
-
await trpc.pipeline.deleteTemplate.mutate({ id })
|
|
155
|
-
setTemplates((prev) => prev.filter((t) => t.id !== id))
|
|
156
|
-
if (selectedTemplateId === id) setSelectedTemplateId(null)
|
|
157
|
-
} catch {
|
|
158
|
-
// ignore
|
|
159
|
-
}
|
|
160
|
-
},
|
|
161
|
-
[client, selectedTemplateId],
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
async function handleSave() {
|
|
165
|
-
setIsSaving(true)
|
|
166
|
-
try {
|
|
167
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
168
|
-
const trpc = (client as any).trpc
|
|
169
|
-
await Promise.all([
|
|
170
|
-
trpc.pipeline.setGlobalSteps.mutate({ steps }),
|
|
171
|
-
trpc.pipeline.setOrchestratorSettings.mutate(orchestrator),
|
|
172
|
-
])
|
|
173
|
-
setIsDirty(false)
|
|
174
|
-
} catch {
|
|
175
|
-
// ignore
|
|
176
|
-
} finally {
|
|
177
|
-
setIsSaving(false)
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function updateOrchestrator<K extends keyof OrchestratorSettings>(key: K, value: OrchestratorSettings[K]) {
|
|
182
|
-
setOrchestrator((prev) => ({ ...prev, [key]: value }))
|
|
183
|
-
setIsDirty(true)
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (loading) {
|
|
187
|
-
return (
|
|
188
|
-
<div className="flex items-center justify-center h-full">
|
|
189
|
-
<div className="text-sm text-foreground-subtle animate-pulse">
|
|
190
|
-
Loading pipeline schema…
|
|
191
|
-
</div>
|
|
192
|
-
</div>
|
|
193
|
-
)
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (!schema || !capabilities) {
|
|
197
|
-
return (
|
|
198
|
-
<div className="flex items-center justify-center h-full">
|
|
199
|
-
<div className="text-sm text-foreground-subtle">
|
|
200
|
-
Pipeline schema not available. Make sure detection addons are loaded.
|
|
201
|
-
</div>
|
|
202
|
-
</div>
|
|
203
|
-
)
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return (
|
|
207
|
-
<div className="flex h-full overflow-hidden">
|
|
208
|
-
{/* Main pipeline area */}
|
|
209
|
-
<div className="flex-1 flex flex-col overflow-hidden">
|
|
210
|
-
{/* Header */}
|
|
211
|
-
<div className="flex items-center justify-between gap-4 px-6 py-4 border-b border-border shrink-0">
|
|
212
|
-
<div className="flex items-center gap-2">
|
|
213
|
-
<GitBranch className="h-4 w-4 text-foreground-subtle" />
|
|
214
|
-
<h1 className="text-lg font-semibold text-foreground">
|
|
215
|
-
{t('nav.pipeline', 'Pipeline Config')}
|
|
216
|
-
</h1>
|
|
217
|
-
</div>
|
|
218
|
-
<div className="flex items-center gap-3">
|
|
219
|
-
{isDirty && (
|
|
220
|
-
<span className="text-[10px] text-amber-400 font-medium">Unsaved changes</span>
|
|
221
|
-
)}
|
|
222
|
-
<button
|
|
223
|
-
onClick={handleSave}
|
|
224
|
-
disabled={!isDirty || isSaving}
|
|
225
|
-
className={`flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-medium transition-all ${
|
|
226
|
-
isDirty && !isSaving
|
|
227
|
-
? 'bg-primary text-white hover:bg-primary/90'
|
|
228
|
-
: 'bg-surface text-foreground-subtle border border-border cursor-not-allowed'
|
|
229
|
-
}`}
|
|
230
|
-
>
|
|
231
|
-
<Save className="h-3.5 w-3.5" />
|
|
232
|
-
{isSaving ? 'Saving…' : 'Save Pipeline'}
|
|
233
|
-
</button>
|
|
234
|
-
</div>
|
|
235
|
-
</div>
|
|
236
|
-
|
|
237
|
-
{/* PipelineBuilder from ui-library */}
|
|
238
|
-
<div className="flex-1 overflow-y-auto p-6">
|
|
239
|
-
<PipelineBuilder
|
|
240
|
-
schema={schema}
|
|
241
|
-
capabilities={capabilities}
|
|
242
|
-
steps={steps}
|
|
243
|
-
onChange={handleStepsChange}
|
|
244
|
-
templates={templates}
|
|
245
|
-
selectedTemplateId={selectedTemplateId}
|
|
246
|
-
onSelectTemplate={handleSelectTemplate}
|
|
247
|
-
onSaveTemplate={handleSaveTemplate}
|
|
248
|
-
onUpdateTemplate={handleUpdateTemplate}
|
|
249
|
-
onDeleteTemplate={handleDeleteTemplate}
|
|
250
|
-
/>
|
|
251
|
-
</div>
|
|
252
|
-
</div>
|
|
253
|
-
|
|
254
|
-
{/* Settings sidebar */}
|
|
255
|
-
<aside className="w-72 shrink-0 border-l border-border flex flex-col overflow-hidden bg-surface/40">
|
|
256
|
-
<button
|
|
257
|
-
onClick={() => setSettingsOpen(!settingsOpen)}
|
|
258
|
-
className="flex items-center justify-between gap-2 px-4 py-3 border-b border-border text-xs font-semibold text-foreground uppercase tracking-wider hover:bg-surface-hover transition-colors"
|
|
259
|
-
>
|
|
260
|
-
<span>Settings</span>
|
|
261
|
-
<ChevronDown
|
|
262
|
-
className={`h-3.5 w-3.5 text-foreground-subtle transition-transform ${settingsOpen ? '' : '-rotate-90'}`}
|
|
263
|
-
/>
|
|
264
|
-
</button>
|
|
265
|
-
{settingsOpen && (
|
|
266
|
-
<div className="flex-1 overflow-y-auto p-4">
|
|
267
|
-
<PhaseSettings
|
|
268
|
-
motionFps={orchestrator.motionFps}
|
|
269
|
-
detectionFps={orchestrator.detectionFps}
|
|
270
|
-
cooldownMs={orchestrator.cooldownMs}
|
|
271
|
-
maxConcurrentInferences={orchestrator.maxConcurrentInferences}
|
|
272
|
-
onMotionFpsChange={(v) => updateOrchestrator('motionFps', v)}
|
|
273
|
-
onDetectionFpsChange={(v) => updateOrchestrator('detectionFps', v)}
|
|
274
|
-
onCooldownMsChange={(v) => updateOrchestrator('cooldownMs', v)}
|
|
275
|
-
onMaxConcurrentInferencesChange={(v) => updateOrchestrator('maxConcurrentInferences', v)}
|
|
276
|
-
/>
|
|
277
|
-
</div>
|
|
278
|
-
)}
|
|
279
|
-
</aside>
|
|
280
|
-
</div>
|
|
281
|
-
)
|
|
282
|
-
}
|
package/src/pages/Showroom.tsx
DELETED
|
@@ -1,351 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react'
|
|
2
|
-
import { StatusBadge } from '../components/shared/StatusBadge'
|
|
3
|
-
import { CapabilityBadges } from '../components/shared/CapabilityBadges'
|
|
4
|
-
import { ProviderIcon } from '../components/shared/ProviderIcon'
|
|
5
|
-
import { FormBuilder } from '../components/form-builder/FormBuilder'
|
|
6
|
-
import type { ConfigUISchema } from '../types/config-ui'
|
|
7
|
-
|
|
8
|
-
// ---------------------------------------------------------------------------
|
|
9
|
-
// Helper components
|
|
10
|
-
// ---------------------------------------------------------------------------
|
|
11
|
-
|
|
12
|
-
function Section({ title, children }: { title: string; children: React.ReactNode }) {
|
|
13
|
-
return (
|
|
14
|
-
<div className="rounded-lg border border-border bg-surface overflow-hidden">
|
|
15
|
-
<div className="border-b border-border px-4 py-2.5">
|
|
16
|
-
<h2 className="text-xs font-semibold text-foreground uppercase tracking-wider">{title}</h2>
|
|
17
|
-
</div>
|
|
18
|
-
<div className="p-4">{children}</div>
|
|
19
|
-
</div>
|
|
20
|
-
)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function ColorSwatch({ name, cssVar, hex }: { name: string; cssVar?: string; hex?: string }) {
|
|
24
|
-
const bg = cssVar ? `var(${cssVar})` : hex
|
|
25
|
-
return (
|
|
26
|
-
<div className="flex flex-col items-center gap-1.5">
|
|
27
|
-
<div
|
|
28
|
-
className="h-10 w-10 rounded-lg border border-border shadow-inner"
|
|
29
|
-
style={{ backgroundColor: bg }}
|
|
30
|
-
/>
|
|
31
|
-
<span className="text-[10px] text-foreground-subtle text-center leading-tight">{name}</span>
|
|
32
|
-
</div>
|
|
33
|
-
)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// ---------------------------------------------------------------------------
|
|
37
|
-
// Demo FormBuilder schema
|
|
38
|
-
// ---------------------------------------------------------------------------
|
|
39
|
-
|
|
40
|
-
const DEMO_SCHEMA: ConfigUISchema = {
|
|
41
|
-
sections: [
|
|
42
|
-
{
|
|
43
|
-
id: 'demo',
|
|
44
|
-
title: 'Demo Fields',
|
|
45
|
-
columns: 2,
|
|
46
|
-
fields: [
|
|
47
|
-
{ key: 'name', type: 'text', label: 'Name', placeholder: 'Enter name' },
|
|
48
|
-
{ key: 'count', type: 'number', label: 'Count', min: 0, max: 100, unit: 'items' },
|
|
49
|
-
{ key: 'enabled', type: 'boolean', label: 'Enabled' },
|
|
50
|
-
{ key: 'mode', type: 'select', label: 'Mode', options: [{ value: 'a', label: 'Alpha' }, { value: 'b', label: 'Beta' }] },
|
|
51
|
-
{ key: 'tags', type: 'tags', label: 'Tags', span: 2, suggestions: ['camera', 'zone', 'alert'] },
|
|
52
|
-
{ key: 'confidence', type: 'slider', label: 'Confidence', min: 0, max: 100, step: 5, unit: '%', span: 2 },
|
|
53
|
-
{ key: 'info', type: 'info', label: 'Note', content: 'This is an informational message for the user.', variant: 'info' },
|
|
54
|
-
],
|
|
55
|
-
},
|
|
56
|
-
],
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// ---------------------------------------------------------------------------
|
|
60
|
-
// Page
|
|
61
|
-
// ---------------------------------------------------------------------------
|
|
62
|
-
|
|
63
|
-
export function ShowroomPage() {
|
|
64
|
-
const [formValues, setFormValues] = useState<Record<string, unknown>>({
|
|
65
|
-
name: 'Example',
|
|
66
|
-
count: 10,
|
|
67
|
-
enabled: true,
|
|
68
|
-
mode: 'a',
|
|
69
|
-
tags: ['camera'],
|
|
70
|
-
confidence: 60,
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
return (
|
|
74
|
-
<div className="p-6 space-y-6 max-w-5xl mx-auto">
|
|
75
|
-
<div className="mb-6">
|
|
76
|
-
<h1 className="text-xl font-bold text-foreground">Component Showroom</h1>
|
|
77
|
-
<p className="text-sm text-foreground-subtle mt-1">All UI components at a glance</p>
|
|
78
|
-
</div>
|
|
79
|
-
|
|
80
|
-
{/* ------------------------------------------------------------------ */}
|
|
81
|
-
{/* 1. Typography */}
|
|
82
|
-
{/* ------------------------------------------------------------------ */}
|
|
83
|
-
<Section title="Typography">
|
|
84
|
-
<div className="space-y-3">
|
|
85
|
-
<h1 className="text-2xl font-bold text-foreground">Heading 1 — Display title</h1>
|
|
86
|
-
<h2 className="text-xl font-semibold text-foreground">Heading 2 — Section title</h2>
|
|
87
|
-
<h3 className="text-base font-semibold text-foreground">Heading 3 — Card title</h3>
|
|
88
|
-
<h4 className="text-sm font-semibold text-foreground">Heading 4 — Sub-section</h4>
|
|
89
|
-
<p className="text-sm text-foreground">Body text — Regular paragraph content goes here. It uses the foreground color.</p>
|
|
90
|
-
<p className="text-sm text-foreground-subtle">Subtle text — Secondary/descriptive text with reduced emphasis.</p>
|
|
91
|
-
<p className="text-xs text-foreground-subtle uppercase tracking-wider font-semibold">Label / Caption</p>
|
|
92
|
-
<p className="text-xs text-foreground-disabled">Disabled / placeholder text</p>
|
|
93
|
-
</div>
|
|
94
|
-
</Section>
|
|
95
|
-
|
|
96
|
-
{/* ------------------------------------------------------------------ */}
|
|
97
|
-
{/* 2. Colors */}
|
|
98
|
-
{/* ------------------------------------------------------------------ */}
|
|
99
|
-
<Section title="Colors">
|
|
100
|
-
<div className="flex flex-wrap gap-4">
|
|
101
|
-
<ColorSwatch name="primary" cssVar="--color-primary" />
|
|
102
|
-
<ColorSwatch name="background" cssVar="--color-background" />
|
|
103
|
-
<ColorSwatch name="surface" cssVar="--color-surface" />
|
|
104
|
-
<ColorSwatch name="foreground" cssVar="--color-foreground" />
|
|
105
|
-
<ColorSwatch name="success" cssVar="--color-success" />
|
|
106
|
-
<ColorSwatch name="warning" cssVar="--color-warning" />
|
|
107
|
-
<ColorSwatch name="danger" cssVar="--color-danger" />
|
|
108
|
-
<ColorSwatch name="info" cssVar="--color-info" />
|
|
109
|
-
<ColorSwatch name="border" cssVar="--color-border" />
|
|
110
|
-
<div className="w-px" />
|
|
111
|
-
<ColorSwatch name="frigate" hex="#3b82f6" />
|
|
112
|
-
<ColorSwatch name="scrypted" hex="#a855f7" />
|
|
113
|
-
<ColorSwatch name="reolink" hex="#06b6d4" />
|
|
114
|
-
<ColorSwatch name="ha" hex="#22d3ee" />
|
|
115
|
-
<ColorSwatch name="rtsp" hex="#78716c" />
|
|
116
|
-
</div>
|
|
117
|
-
</Section>
|
|
118
|
-
|
|
119
|
-
{/* ------------------------------------------------------------------ */}
|
|
120
|
-
{/* 3. Buttons */}
|
|
121
|
-
{/* ------------------------------------------------------------------ */}
|
|
122
|
-
<Section title="Buttons">
|
|
123
|
-
<div className="flex flex-wrap gap-3">
|
|
124
|
-
<button className="inline-flex items-center gap-1.5 rounded-md bg-primary px-4 py-2 text-sm font-medium text-white hover:opacity-90 transition-opacity">
|
|
125
|
-
Primary
|
|
126
|
-
</button>
|
|
127
|
-
<button className="inline-flex items-center gap-1.5 rounded-md border border-border bg-surface px-4 py-2 text-sm font-medium text-foreground hover:bg-surface-hover transition-colors">
|
|
128
|
-
Secondary
|
|
129
|
-
</button>
|
|
130
|
-
<button className="inline-flex items-center gap-1.5 rounded-md border border-danger/40 bg-danger/10 px-4 py-2 text-sm font-medium text-danger hover:bg-danger/20 transition-colors">
|
|
131
|
-
Danger
|
|
132
|
-
</button>
|
|
133
|
-
<button disabled className="inline-flex items-center gap-1.5 rounded-md bg-primary px-4 py-2 text-sm font-medium text-white opacity-40 cursor-not-allowed">
|
|
134
|
-
Disabled
|
|
135
|
-
</button>
|
|
136
|
-
<button className="inline-flex items-center gap-1.5 rounded-md border border-warning/40 bg-warning/10 px-4 py-2 text-sm font-medium text-warning hover:bg-warning/20 transition-colors">
|
|
137
|
-
Warning
|
|
138
|
-
</button>
|
|
139
|
-
<button className="inline-flex items-center gap-1.5 rounded-md border border-success/40 bg-success/10 px-4 py-2 text-sm font-medium text-success hover:bg-success/20 transition-colors">
|
|
140
|
-
Success
|
|
141
|
-
</button>
|
|
142
|
-
</div>
|
|
143
|
-
</Section>
|
|
144
|
-
|
|
145
|
-
{/* ------------------------------------------------------------------ */}
|
|
146
|
-
{/* 4. Inputs */}
|
|
147
|
-
{/* ------------------------------------------------------------------ */}
|
|
148
|
-
<Section title="Inputs">
|
|
149
|
-
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
150
|
-
<div>
|
|
151
|
-
<label className="block text-xs font-medium text-foreground mb-1">Text input</label>
|
|
152
|
-
<input
|
|
153
|
-
type="text"
|
|
154
|
-
className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground outline-none focus:border-primary focus:ring-1 focus:ring-primary/30"
|
|
155
|
-
placeholder="Type something..."
|
|
156
|
-
/>
|
|
157
|
-
</div>
|
|
158
|
-
<div>
|
|
159
|
-
<label className="block text-xs font-medium text-foreground mb-1">Number input</label>
|
|
160
|
-
<input
|
|
161
|
-
type="number"
|
|
162
|
-
className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground outline-none focus:border-primary focus:ring-1 focus:ring-primary/30"
|
|
163
|
-
placeholder="0"
|
|
164
|
-
/>
|
|
165
|
-
</div>
|
|
166
|
-
<div>
|
|
167
|
-
<label className="block text-xs font-medium text-foreground mb-1">Password input</label>
|
|
168
|
-
<input
|
|
169
|
-
type="password"
|
|
170
|
-
className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground outline-none focus:border-primary focus:ring-1 focus:ring-primary/30"
|
|
171
|
-
placeholder="••••••••"
|
|
172
|
-
/>
|
|
173
|
-
</div>
|
|
174
|
-
<div>
|
|
175
|
-
<label className="block text-xs font-medium text-foreground mb-1">Select</label>
|
|
176
|
-
<select className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground outline-none focus:border-primary focus:ring-1 focus:ring-primary/30">
|
|
177
|
-
<option>Option A</option>
|
|
178
|
-
<option>Option B</option>
|
|
179
|
-
<option>Option C</option>
|
|
180
|
-
</select>
|
|
181
|
-
</div>
|
|
182
|
-
<div className="md:col-span-2">
|
|
183
|
-
<label className="block text-xs font-medium text-foreground mb-1">Textarea</label>
|
|
184
|
-
<textarea
|
|
185
|
-
className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground outline-none focus:border-primary focus:ring-1 focus:ring-primary/30 resize-y min-h-[60px]"
|
|
186
|
-
placeholder="Enter multi-line text..."
|
|
187
|
-
rows={3}
|
|
188
|
-
/>
|
|
189
|
-
</div>
|
|
190
|
-
<div>
|
|
191
|
-
<label className="flex items-center gap-2 cursor-pointer select-none">
|
|
192
|
-
<input type="checkbox" className="h-3.5 w-3.5 rounded border-border accent-primary" defaultChecked />
|
|
193
|
-
<span className="text-xs font-medium text-foreground">Checkbox (checked)</span>
|
|
194
|
-
</label>
|
|
195
|
-
</div>
|
|
196
|
-
<div>
|
|
197
|
-
<div className="flex items-center justify-between">
|
|
198
|
-
<span className="text-xs font-medium text-foreground">Toggle switch</span>
|
|
199
|
-
<ToggleDemo />
|
|
200
|
-
</div>
|
|
201
|
-
</div>
|
|
202
|
-
<div>
|
|
203
|
-
<label className="block text-xs font-medium text-foreground mb-1">Slider</label>
|
|
204
|
-
<input type="range" className="w-full h-1 accent-primary cursor-pointer" min={0} max={100} defaultValue={60} />
|
|
205
|
-
</div>
|
|
206
|
-
<div>
|
|
207
|
-
<label className="block text-xs font-medium text-foreground mb-1">Tags example</label>
|
|
208
|
-
<div className="flex flex-wrap gap-1.5 rounded-md border border-border bg-background px-2 py-1.5 min-h-[38px]">
|
|
209
|
-
{['camera', 'zone', 'alert'].map((tag) => (
|
|
210
|
-
<span key={tag} className="inline-flex items-center gap-1 rounded-md bg-primary/10 px-1.5 py-0.5 text-xs text-primary">
|
|
211
|
-
{tag}
|
|
212
|
-
</span>
|
|
213
|
-
))}
|
|
214
|
-
</div>
|
|
215
|
-
</div>
|
|
216
|
-
</div>
|
|
217
|
-
</Section>
|
|
218
|
-
|
|
219
|
-
{/* ------------------------------------------------------------------ */}
|
|
220
|
-
{/* 5. Badges */}
|
|
221
|
-
{/* ------------------------------------------------------------------ */}
|
|
222
|
-
<Section title="Badges">
|
|
223
|
-
<div className="space-y-3">
|
|
224
|
-
<div>
|
|
225
|
-
<p className="text-xs text-foreground-subtle mb-2">StatusBadge</p>
|
|
226
|
-
<div className="flex flex-wrap gap-2">
|
|
227
|
-
{(['running', 'stopped', 'error', 'online', 'offline', 'idle'] as const).map((s) => (
|
|
228
|
-
<StatusBadge key={s} status={s} />
|
|
229
|
-
))}
|
|
230
|
-
</div>
|
|
231
|
-
</div>
|
|
232
|
-
<div>
|
|
233
|
-
<p className="text-xs text-foreground-subtle mb-2">CapabilityBadges</p>
|
|
234
|
-
<CapabilityBadges capabilities={['streaming', 'detection', 'recording', 'audio']} />
|
|
235
|
-
</div>
|
|
236
|
-
</div>
|
|
237
|
-
</Section>
|
|
238
|
-
|
|
239
|
-
{/* ------------------------------------------------------------------ */}
|
|
240
|
-
{/* 6. Cards */}
|
|
241
|
-
{/* ------------------------------------------------------------------ */}
|
|
242
|
-
<Section title="Cards">
|
|
243
|
-
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
244
|
-
<div className="rounded-lg bg-surface p-4">
|
|
245
|
-
<h3 className="text-sm font-semibold text-foreground mb-1">Surface card</h3>
|
|
246
|
-
<p className="text-xs text-foreground-subtle">Uses `bg-surface`. Default card background.</p>
|
|
247
|
-
</div>
|
|
248
|
-
<div className="rounded-lg border border-border bg-surface p-4">
|
|
249
|
-
<h3 className="text-sm font-semibold text-foreground mb-1">Bordered card</h3>
|
|
250
|
-
<p className="text-xs text-foreground-subtle">Uses `border border-border bg-surface`.</p>
|
|
251
|
-
</div>
|
|
252
|
-
<div className="rounded-lg border border-primary/30 bg-primary/5 p-4">
|
|
253
|
-
<h3 className="text-sm font-semibold text-primary mb-1">Accent card</h3>
|
|
254
|
-
<p className="text-xs text-foreground-subtle">Highlighted with primary color.</p>
|
|
255
|
-
</div>
|
|
256
|
-
<div className="rounded-lg bg-background p-4">
|
|
257
|
-
<h3 className="text-sm font-semibold text-foreground mb-1">Background card</h3>
|
|
258
|
-
<p className="text-xs text-foreground-subtle">Uses `bg-background`. Inset card.</p>
|
|
259
|
-
</div>
|
|
260
|
-
</div>
|
|
261
|
-
</Section>
|
|
262
|
-
|
|
263
|
-
{/* ------------------------------------------------------------------ */}
|
|
264
|
-
{/* 7. Provider Icons */}
|
|
265
|
-
{/* ------------------------------------------------------------------ */}
|
|
266
|
-
<Section title="Provider Icons">
|
|
267
|
-
<div className="flex flex-wrap gap-4">
|
|
268
|
-
{(['frigate', 'scrypted', 'reolink', 'homeassistant', 'rtsp', 'onvif'] as const).map((p) => (
|
|
269
|
-
<ProviderIcon key={p} type={p} size="lg" showLabel />
|
|
270
|
-
))}
|
|
271
|
-
</div>
|
|
272
|
-
</Section>
|
|
273
|
-
|
|
274
|
-
{/* ------------------------------------------------------------------ */}
|
|
275
|
-
{/* 8. Form Builder Demo */}
|
|
276
|
-
{/* ------------------------------------------------------------------ */}
|
|
277
|
-
<Section title="Form Builder Demo">
|
|
278
|
-
<FormBuilder
|
|
279
|
-
schema={DEMO_SCHEMA}
|
|
280
|
-
values={formValues}
|
|
281
|
-
onChange={setFormValues}
|
|
282
|
-
/>
|
|
283
|
-
<details className="mt-3">
|
|
284
|
-
<summary className="text-[10px] text-foreground-subtle cursor-pointer hover:text-foreground">
|
|
285
|
-
View current values
|
|
286
|
-
</summary>
|
|
287
|
-
<pre className="mt-2 rounded-md bg-background p-3 text-[10px] text-foreground-subtle overflow-auto">
|
|
288
|
-
{JSON.stringify(formValues, null, 2)}
|
|
289
|
-
</pre>
|
|
290
|
-
</details>
|
|
291
|
-
</Section>
|
|
292
|
-
|
|
293
|
-
{/* ------------------------------------------------------------------ */}
|
|
294
|
-
{/* 9. Info Boxes */}
|
|
295
|
-
{/* ------------------------------------------------------------------ */}
|
|
296
|
-
<Section title="Info Boxes">
|
|
297
|
-
<div className="space-y-2">
|
|
298
|
-
{(
|
|
299
|
-
[
|
|
300
|
-
{ variant: 'info', label: 'Info', content: 'This is an informational message. Use it for tips and hints.' },
|
|
301
|
-
{ variant: 'warning', label: 'Warning', content: 'This action may have side effects. Proceed with caution.' },
|
|
302
|
-
{ variant: 'success', label: 'Success', content: 'Operation completed successfully. All systems are green.' },
|
|
303
|
-
{ variant: 'danger', label: 'Danger', content: 'This operation is irreversible. Data will be permanently deleted.' },
|
|
304
|
-
] as const
|
|
305
|
-
).map(({ variant, label, content }) => {
|
|
306
|
-
const STYLES = {
|
|
307
|
-
info: { border: 'border-info', bg: 'bg-info/5', text: 'text-info' },
|
|
308
|
-
warning: { border: 'border-warning', bg: 'bg-warning/5', text: 'text-warning' },
|
|
309
|
-
success: { border: 'border-success', bg: 'bg-success/5', text: 'text-success' },
|
|
310
|
-
danger: { border: 'border-danger', bg: 'bg-danger/5', text: 'text-danger' },
|
|
311
|
-
}
|
|
312
|
-
const s = STYLES[variant]
|
|
313
|
-
return (
|
|
314
|
-
<div key={variant} className={`rounded-md border-l-4 px-3 py-2.5 ${s.border} ${s.bg}`}>
|
|
315
|
-
<p className={`text-xs font-semibold mb-0.5 ${s.text}`}>{label}</p>
|
|
316
|
-
<p className="text-xs text-foreground-subtle leading-relaxed">{content}</p>
|
|
317
|
-
</div>
|
|
318
|
-
)
|
|
319
|
-
})}
|
|
320
|
-
</div>
|
|
321
|
-
</Section>
|
|
322
|
-
</div>
|
|
323
|
-
)
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// ---------------------------------------------------------------------------
|
|
327
|
-
// Local ToggleDemo to avoid hook extraction issues
|
|
328
|
-
// ---------------------------------------------------------------------------
|
|
329
|
-
|
|
330
|
-
function ToggleDemo() {
|
|
331
|
-
const [on, setOn] = useState(true)
|
|
332
|
-
return (
|
|
333
|
-
<button
|
|
334
|
-
type="button"
|
|
335
|
-
role="switch"
|
|
336
|
-
aria-checked={on}
|
|
337
|
-
onClick={() => setOn((v) => !v)}
|
|
338
|
-
className={[
|
|
339
|
-
'relative inline-flex h-4 w-8 shrink-0 items-center rounded-full transition-colors duration-150 cursor-pointer',
|
|
340
|
-
on ? 'bg-primary' : 'bg-foreground-subtle/30',
|
|
341
|
-
].join(' ')}
|
|
342
|
-
>
|
|
343
|
-
<span
|
|
344
|
-
className={[
|
|
345
|
-
'inline-block h-3 w-3 rounded-full bg-white shadow transition-transform duration-150',
|
|
346
|
-
on ? 'translate-x-4' : 'translate-x-0.5',
|
|
347
|
-
].join(' ')}
|
|
348
|
-
/>
|
|
349
|
-
</button>
|
|
350
|
-
)
|
|
351
|
-
}
|