@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
package/src/pages/Timeline.tsx
DELETED
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
import { useState, useMemo } from 'react'
|
|
2
|
-
import { useQuery } from '@tanstack/react-query'
|
|
3
|
-
import { Film, Calendar, ChevronLeft, ChevronRight } from 'lucide-react'
|
|
4
|
-
import { useTranslation } from 'react-i18next'
|
|
5
|
-
import { useBackendClient } from '../hooks/useBackendClient'
|
|
6
|
-
import { TimelineBar, type RecordingSegment, type TimelineEvent } from '../components/timeline/TimelineBar'
|
|
7
|
-
|
|
8
|
-
// ---------------------------------------------------------------------------
|
|
9
|
-
// Class → colour mapping
|
|
10
|
-
// ---------------------------------------------------------------------------
|
|
11
|
-
const CLASS_COLORS: Record<string, string> = {
|
|
12
|
-
person: '#f59e0b',
|
|
13
|
-
vehicle: '#3b82f6',
|
|
14
|
-
car: '#3b82f6',
|
|
15
|
-
face: '#a855f7',
|
|
16
|
-
plate: '#ec4899',
|
|
17
|
-
animal: '#10b981',
|
|
18
|
-
motion: '#ef4444',
|
|
19
|
-
unknown: '#6b7280',
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function colorForClass(cls: string): string {
|
|
23
|
-
return CLASS_COLORS[cls.toLowerCase()] ?? CLASS_COLORS['unknown']!
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
|
-
// Date helpers
|
|
28
|
-
// ---------------------------------------------------------------------------
|
|
29
|
-
function startOfDay(date: Date): number {
|
|
30
|
-
const d = new Date(date)
|
|
31
|
-
d.setHours(0, 0, 0, 0)
|
|
32
|
-
return d.getTime()
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function formatDateInput(ms: number): string {
|
|
36
|
-
return new Date(ms).toISOString().slice(0, 10)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function parseDateInput(value: string): number {
|
|
40
|
-
return new Date(value + 'T00:00:00').getTime()
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// ---------------------------------------------------------------------------
|
|
44
|
-
// Placeholder segments (shown when no real API data exists)
|
|
45
|
-
// ---------------------------------------------------------------------------
|
|
46
|
-
function buildPlaceholderSegments(dayStartMs: number): readonly RecordingSegment[] {
|
|
47
|
-
return [
|
|
48
|
-
{ startMs: dayStartMs + 0 * 3_600_000, endMs: dayStartMs + 4 * 3_600_000 },
|
|
49
|
-
{ startMs: dayStartMs + 6 * 3_600_000, endMs: dayStartMs + 10 * 3_600_000 },
|
|
50
|
-
{ startMs: dayStartMs + 12 * 3_600_000, endMs: dayStartMs + 16 * 3_600_000 },
|
|
51
|
-
{ startMs: dayStartMs + 18 * 3_600_000, endMs: dayStartMs + 23 * 3_600_000 },
|
|
52
|
-
]
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function buildPlaceholderEvents(dayStartMs: number): readonly TimelineEvent[] {
|
|
56
|
-
const classes = ['person', 'vehicle', 'face', 'motion']
|
|
57
|
-
return Array.from({ length: 24 }, (_, i) => ({
|
|
58
|
-
id: `placeholder-${i}`,
|
|
59
|
-
timestampMs: dayStartMs + Math.floor(Math.random() * 24 * 3_600_000),
|
|
60
|
-
label: classes[i % classes.length]!,
|
|
61
|
-
color: colorForClass(classes[i % classes.length]!),
|
|
62
|
-
})).sort((a, b) => a.timestampMs - b.timestampMs)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// ---------------------------------------------------------------------------
|
|
66
|
-
// Component
|
|
67
|
-
// ---------------------------------------------------------------------------
|
|
68
|
-
export function TimelinePage() {
|
|
69
|
-
const { t } = useTranslation()
|
|
70
|
-
const client = useBackendClient()
|
|
71
|
-
|
|
72
|
-
const today = startOfDay(new Date())
|
|
73
|
-
const [selectedDayMs, setSelectedDayMs] = useState(today)
|
|
74
|
-
const [selectedCameraId, setSelectedCameraId] = useState<string | null>(null)
|
|
75
|
-
const [playheadMs, setPlayheadMs] = useState(today + 12 * 3_600_000)
|
|
76
|
-
|
|
77
|
-
const dayStartMs = selectedDayMs
|
|
78
|
-
const dayEndMs = selectedDayMs + 24 * 3_600_000
|
|
79
|
-
|
|
80
|
-
// Fetch devices to populate camera tabs
|
|
81
|
-
const { data: devices, isLoading: devicesLoading } = useQuery({
|
|
82
|
-
queryKey: ['devices'],
|
|
83
|
-
queryFn: () => client.listDevices(),
|
|
84
|
-
staleTime: 30_000,
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
const deviceList = (devices ?? []) as unknown as Array<Record<string, unknown>>
|
|
88
|
-
|
|
89
|
-
const cameras = useMemo(() => {
|
|
90
|
-
const list = deviceList.filter((d) => {
|
|
91
|
-
const type = String(d.type ?? '').toLowerCase()
|
|
92
|
-
const caps = (d.capabilities ?? []) as string[]
|
|
93
|
-
return (
|
|
94
|
-
type === 'camera' ||
|
|
95
|
-
type.includes('camera') ||
|
|
96
|
-
caps.some((c) => c.toLowerCase().includes('video') || c.toLowerCase().includes('stream'))
|
|
97
|
-
)
|
|
98
|
-
})
|
|
99
|
-
return list.map((d) => ({ id: String(d.id), name: String(d.name ?? d.id) }))
|
|
100
|
-
}, [deviceList])
|
|
101
|
-
|
|
102
|
-
// Auto-select first camera
|
|
103
|
-
const activeCameraId = selectedCameraId ?? cameras[0]?.id ?? null
|
|
104
|
-
|
|
105
|
-
// Fetch real events for the selected camera on the selected day
|
|
106
|
-
const { data: rawEvents } = useQuery({
|
|
107
|
-
queryKey: ['events', activeCameraId, dayStartMs],
|
|
108
|
-
queryFn: (): Promise<unknown> =>
|
|
109
|
-
activeCameraId ? client.getEvents(activeCameraId, { limit: 200 }) : Promise.resolve(null),
|
|
110
|
-
enabled: !!activeCameraId,
|
|
111
|
-
staleTime: 60_000,
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
const timelineEvents: readonly TimelineEvent[] = useMemo(() => {
|
|
115
|
-
const raw = rawEvents as unknown
|
|
116
|
-
// getEvents returns { events: [...], total: n } or an array depending on router version
|
|
117
|
-
const evtsArr: unknown[] =
|
|
118
|
-
Array.isArray(raw)
|
|
119
|
-
? (raw as unknown[])
|
|
120
|
-
: raw != null && typeof raw === 'object' && Array.isArray((raw as Record<string, unknown>)['events'])
|
|
121
|
-
? ((raw as Record<string, unknown>)['events'] as unknown[])
|
|
122
|
-
: []
|
|
123
|
-
const evts = evtsArr as Array<Record<string, unknown>>
|
|
124
|
-
if (evts.length === 0) {
|
|
125
|
-
return activeCameraId ? buildPlaceholderEvents(dayStartMs) : []
|
|
126
|
-
}
|
|
127
|
-
return evts.map((ev, idx) => {
|
|
128
|
-
const cls = String((ev.class ?? ev.type ?? ev.label ?? 'unknown')).toLowerCase()
|
|
129
|
-
const ts = typeof ev.timestamp === 'number' ? ev.timestamp : dayStartMs
|
|
130
|
-
return {
|
|
131
|
-
id: String(ev.id ?? idx),
|
|
132
|
-
timestampMs: ts,
|
|
133
|
-
label: cls,
|
|
134
|
-
color: colorForClass(cls),
|
|
135
|
-
}
|
|
136
|
-
})
|
|
137
|
-
}, [rawEvents, activeCameraId, dayStartMs])
|
|
138
|
-
|
|
139
|
-
// Use placeholder segments (no real segment API exposed yet)
|
|
140
|
-
const segments: readonly RecordingSegment[] = useMemo(
|
|
141
|
-
() => (activeCameraId ? buildPlaceholderSegments(dayStartMs) : []),
|
|
142
|
-
[activeCameraId, dayStartMs],
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
function navigateDay(delta: number) {
|
|
146
|
-
setSelectedDayMs((prev) => prev + delta * 24 * 3_600_000)
|
|
147
|
-
setPlayheadMs((prev) => prev + delta * 24 * 3_600_000)
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return (
|
|
151
|
-
<div className="flex flex-col h-full p-6 gap-5 overflow-hidden">
|
|
152
|
-
{/* ── Header ── */}
|
|
153
|
-
<div className="flex items-center justify-between gap-4 shrink-0">
|
|
154
|
-
<h1 className="text-lg font-semibold text-foreground">{t('nav.timeline', 'Timeline')}</h1>
|
|
155
|
-
|
|
156
|
-
{/* Date navigation */}
|
|
157
|
-
<div className="flex items-center gap-2">
|
|
158
|
-
<button
|
|
159
|
-
onClick={() => navigateDay(-1)}
|
|
160
|
-
className="p-1 rounded hover:bg-surface-hover text-foreground-subtle hover:text-foreground transition-colors"
|
|
161
|
-
>
|
|
162
|
-
<ChevronLeft className="h-4 w-4" />
|
|
163
|
-
</button>
|
|
164
|
-
<div className="relative flex items-center">
|
|
165
|
-
<Calendar className="absolute left-2 h-3.5 w-3.5 text-foreground-subtle pointer-events-none" />
|
|
166
|
-
<input
|
|
167
|
-
type="date"
|
|
168
|
-
value={formatDateInput(selectedDayMs)}
|
|
169
|
-
onChange={(e) => {
|
|
170
|
-
if (e.target.value) {
|
|
171
|
-
const ms = parseDateInput(e.target.value)
|
|
172
|
-
setSelectedDayMs(ms)
|
|
173
|
-
setPlayheadMs(ms + 12 * 3_600_000)
|
|
174
|
-
}
|
|
175
|
-
}}
|
|
176
|
-
className="rounded-lg border border-border bg-surface pl-7 pr-3 py-1.5 text-xs text-foreground focus:outline-none focus:border-primary/50"
|
|
177
|
-
/>
|
|
178
|
-
</div>
|
|
179
|
-
<button
|
|
180
|
-
onClick={() => navigateDay(1)}
|
|
181
|
-
className="p-1 rounded hover:bg-surface-hover text-foreground-subtle hover:text-foreground transition-colors"
|
|
182
|
-
disabled={selectedDayMs >= today}
|
|
183
|
-
>
|
|
184
|
-
<ChevronRight className="h-4 w-4" />
|
|
185
|
-
</button>
|
|
186
|
-
</div>
|
|
187
|
-
</div>
|
|
188
|
-
|
|
189
|
-
{/* ── Camera selector tabs ── */}
|
|
190
|
-
<div className="flex gap-1.5 shrink-0 overflow-x-auto pb-1">
|
|
191
|
-
{devicesLoading && (
|
|
192
|
-
<div className="h-8 w-48 rounded-lg bg-surface animate-pulse" />
|
|
193
|
-
)}
|
|
194
|
-
{!devicesLoading && cameras.length === 0 && (
|
|
195
|
-
<span className="text-xs text-foreground-subtle">No cameras found</span>
|
|
196
|
-
)}
|
|
197
|
-
{cameras.map((cam) => {
|
|
198
|
-
const active = cam.id === activeCameraId
|
|
199
|
-
return (
|
|
200
|
-
<button
|
|
201
|
-
key={cam.id}
|
|
202
|
-
onClick={() => setSelectedCameraId(cam.id)}
|
|
203
|
-
className={`shrink-0 rounded-lg px-3 py-1.5 text-[12px] font-medium transition-all border ${
|
|
204
|
-
active
|
|
205
|
-
? 'bg-primary/12 text-primary border-primary/30'
|
|
206
|
-
: 'bg-surface text-foreground-subtle border-border hover:bg-surface-hover hover:text-foreground'
|
|
207
|
-
}`}
|
|
208
|
-
>
|
|
209
|
-
{cam.name}
|
|
210
|
-
</button>
|
|
211
|
-
)
|
|
212
|
-
})}
|
|
213
|
-
</div>
|
|
214
|
-
|
|
215
|
-
{/* ── Video player placeholder ── */}
|
|
216
|
-
<div className="relative rounded-xl border border-border bg-black shrink-0 overflow-hidden" style={{ aspectRatio: '16/9', maxHeight: '45vh' }}>
|
|
217
|
-
<div className="absolute inset-0 flex flex-col items-center justify-center gap-3 text-foreground-subtle">
|
|
218
|
-
<Film className="h-12 w-12 opacity-20" />
|
|
219
|
-
<p className="text-sm opacity-50">
|
|
220
|
-
{activeCameraId
|
|
221
|
-
? `${cameras.find((c) => c.id === activeCameraId)?.name ?? activeCameraId} — seek to play`
|
|
222
|
-
: 'Select a camera'}
|
|
223
|
-
</p>
|
|
224
|
-
</div>
|
|
225
|
-
|
|
226
|
-
{/* Playhead timestamp overlay */}
|
|
227
|
-
<div className="absolute top-3 left-3 rounded bg-black/60 px-2 py-1 text-[11px] text-white font-mono">
|
|
228
|
-
{new Date(playheadMs).toLocaleTimeString()}
|
|
229
|
-
</div>
|
|
230
|
-
</div>
|
|
231
|
-
|
|
232
|
-
{/* ── Timeline scrub bar ── */}
|
|
233
|
-
<div className="rounded-xl border border-border bg-surface px-4 pt-3 pb-4 shrink-0">
|
|
234
|
-
{/* Legend */}
|
|
235
|
-
<div className="flex items-center gap-4 mb-3">
|
|
236
|
-
<span className="text-[10px] font-semibold text-foreground-subtle uppercase tracking-wider">Timeline</span>
|
|
237
|
-
<div className="flex items-center gap-1.5">
|
|
238
|
-
<span className="inline-block h-2 w-4 rounded-sm bg-success/60" />
|
|
239
|
-
<span className="text-[10px] text-foreground-subtle">Recording</span>
|
|
240
|
-
</div>
|
|
241
|
-
{Array.from(new Set(timelineEvents.map((e) => e.label))).slice(0, 5).map((cls) => (
|
|
242
|
-
<div key={cls} className="flex items-center gap-1.5">
|
|
243
|
-
<span
|
|
244
|
-
className="inline-block h-2.5 w-2.5 rounded-full"
|
|
245
|
-
style={{ backgroundColor: colorForClass(cls) }}
|
|
246
|
-
/>
|
|
247
|
-
<span className="text-[10px] text-foreground-subtle capitalize">{cls}</span>
|
|
248
|
-
</div>
|
|
249
|
-
))}
|
|
250
|
-
</div>
|
|
251
|
-
|
|
252
|
-
{activeCameraId ? (
|
|
253
|
-
<TimelineBar
|
|
254
|
-
startMs={dayStartMs}
|
|
255
|
-
endMs={dayEndMs}
|
|
256
|
-
segments={segments}
|
|
257
|
-
events={timelineEvents}
|
|
258
|
-
playheadMs={playheadMs}
|
|
259
|
-
onSeek={setPlayheadMs}
|
|
260
|
-
/>
|
|
261
|
-
) : (
|
|
262
|
-
<div className="h-8 rounded bg-surface-hover flex items-center justify-center text-xs text-foreground-subtle">
|
|
263
|
-
Select a camera to view timeline
|
|
264
|
-
</div>
|
|
265
|
-
)}
|
|
266
|
-
</div>
|
|
267
|
-
</div>
|
|
268
|
-
)
|
|
269
|
-
}
|