@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,104 +0,0 @@
|
|
|
1
|
-
import { StatusBadge } from '../../shared/StatusBadge'
|
|
2
|
-
import { CapabilityBadges } from '../../shared/CapabilityBadges'
|
|
3
|
-
import { ProviderIcon } from '../../shared/ProviderIcon'
|
|
4
|
-
import { WebRtcPlayer } from '../../shared/WebRtcPlayer'
|
|
5
|
-
import { getBackendClient } from '../../../lib/backend'
|
|
6
|
-
|
|
7
|
-
interface OverviewTabProps {
|
|
8
|
-
device: Record<string, unknown>
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function OverviewTab({ device }: OverviewTabProps) {
|
|
12
|
-
const id = String(device.id ?? '—')
|
|
13
|
-
const name = String(device.name ?? '—')
|
|
14
|
-
const type = String(device.type ?? '—')
|
|
15
|
-
const provider = String(device.providerId ?? device.provider ?? '—')
|
|
16
|
-
const status = String(device.status ?? 'offline')
|
|
17
|
-
const capabilities = (device.capabilities ?? []) as string[]
|
|
18
|
-
|
|
19
|
-
const client = getBackendClient()
|
|
20
|
-
const serverUrl = (client as unknown as { serverUrl?: string }).serverUrl ?? window.location.origin
|
|
21
|
-
|
|
22
|
-
return (
|
|
23
|
-
<div className="space-y-6">
|
|
24
|
-
{/* Live Stream Preview */}
|
|
25
|
-
<div className="rounded-lg border border-border bg-surface overflow-hidden">
|
|
26
|
-
<div className="border-b border-border px-4 py-2.5">
|
|
27
|
-
<h2 className="text-xs font-semibold text-foreground uppercase tracking-wider">Live Preview</h2>
|
|
28
|
-
</div>
|
|
29
|
-
<div className="p-3">
|
|
30
|
-
<WebRtcPlayer
|
|
31
|
-
serverUrl={serverUrl}
|
|
32
|
-
streamId={id}
|
|
33
|
-
className="w-full aspect-video rounded"
|
|
34
|
-
muted
|
|
35
|
-
/>
|
|
36
|
-
</div>
|
|
37
|
-
</div>
|
|
38
|
-
{/* Device Info */}
|
|
39
|
-
<div className="rounded-lg border border-border bg-surface overflow-hidden">
|
|
40
|
-
<div className="border-b border-border px-4 py-2.5">
|
|
41
|
-
<h2 className="text-xs font-semibold text-foreground uppercase tracking-wider">Device Info</h2>
|
|
42
|
-
</div>
|
|
43
|
-
<div className="divide-y divide-border">
|
|
44
|
-
<InfoRow label="ID" value={<span className="font-mono text-xs text-foreground">{id}</span>} />
|
|
45
|
-
<InfoRow label="Name" value={name} />
|
|
46
|
-
<InfoRow label="Type" value={type} />
|
|
47
|
-
<InfoRow
|
|
48
|
-
label="Provider"
|
|
49
|
-
value={
|
|
50
|
-
<div className="flex items-center gap-2">
|
|
51
|
-
<ProviderIcon type={provider} size="sm" showLabel />
|
|
52
|
-
</div>
|
|
53
|
-
}
|
|
54
|
-
/>
|
|
55
|
-
<InfoRow
|
|
56
|
-
label="Status"
|
|
57
|
-
value={<StatusBadge status={status} />}
|
|
58
|
-
/>
|
|
59
|
-
<InfoRow
|
|
60
|
-
label="Capabilities"
|
|
61
|
-
value={
|
|
62
|
-
capabilities.length > 0 ? (
|
|
63
|
-
<CapabilityBadges capabilities={capabilities} />
|
|
64
|
-
) : (
|
|
65
|
-
<span className="text-xs text-foreground-subtle">None</span>
|
|
66
|
-
)
|
|
67
|
-
}
|
|
68
|
-
/>
|
|
69
|
-
</div>
|
|
70
|
-
</div>
|
|
71
|
-
|
|
72
|
-
{/* Pipeline Status */}
|
|
73
|
-
<div className="rounded-lg border border-border bg-surface overflow-hidden">
|
|
74
|
-
<div className="border-b border-border px-4 py-2.5">
|
|
75
|
-
<h2 className="text-xs font-semibold text-foreground uppercase tracking-wider">Pipeline Status</h2>
|
|
76
|
-
</div>
|
|
77
|
-
<div className="px-4 py-8 flex flex-col items-center text-center text-foreground-subtle">
|
|
78
|
-
<p className="text-sm">Pipeline status</p>
|
|
79
|
-
<p className="text-xs mt-1">Pipeline metrics will appear here once the device is active</p>
|
|
80
|
-
</div>
|
|
81
|
-
</div>
|
|
82
|
-
|
|
83
|
-
{/* Last 5 Events */}
|
|
84
|
-
<div className="rounded-lg border border-border bg-surface overflow-hidden">
|
|
85
|
-
<div className="border-b border-border px-4 py-2.5">
|
|
86
|
-
<h2 className="text-xs font-semibold text-foreground uppercase tracking-wider">Recent Events</h2>
|
|
87
|
-
</div>
|
|
88
|
-
<div className="px-4 py-8 flex flex-col items-center text-center text-foreground-subtle">
|
|
89
|
-
<p className="text-sm">No recent events</p>
|
|
90
|
-
<p className="text-xs mt-1">Events will appear here as the device generates them</p>
|
|
91
|
-
</div>
|
|
92
|
-
</div>
|
|
93
|
-
</div>
|
|
94
|
-
)
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function InfoRow({ label, value }: { label: string; value: React.ReactNode }) {
|
|
98
|
-
return (
|
|
99
|
-
<div className="flex items-center gap-4 px-4 py-2.5">
|
|
100
|
-
<span className="w-28 flex-shrink-0 text-xs text-foreground-subtle">{label}</span>
|
|
101
|
-
<div className="text-xs text-foreground">{value}</div>
|
|
102
|
-
</div>
|
|
103
|
-
)
|
|
104
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { Settings } from 'lucide-react'
|
|
2
|
-
|
|
3
|
-
export function ProviderSettingsTab() {
|
|
4
|
-
return (
|
|
5
|
-
<div className="p-4 space-y-4">
|
|
6
|
-
{/* Explanation */}
|
|
7
|
-
<div className="rounded-lg border border-border bg-surface p-4">
|
|
8
|
-
<div className="flex items-start gap-3">
|
|
9
|
-
<div className="flex items-center justify-center rounded-full bg-primary/10 p-2 shrink-0">
|
|
10
|
-
<Settings className="h-4 w-4 text-primary" />
|
|
11
|
-
</div>
|
|
12
|
-
<div>
|
|
13
|
-
<p className="text-sm font-medium text-foreground">Provider Settings</p>
|
|
14
|
-
<p className="mt-1 text-xs text-foreground-subtle leading-relaxed">
|
|
15
|
-
Provider settings are loaded from the addon configuration schema. Each provider
|
|
16
|
-
addon declares its own settings using a declarative schema that is rendered
|
|
17
|
-
automatically as a form.
|
|
18
|
-
</p>
|
|
19
|
-
</div>
|
|
20
|
-
</div>
|
|
21
|
-
</div>
|
|
22
|
-
|
|
23
|
-
{/* Placeholder */}
|
|
24
|
-
<div className="flex flex-col items-center justify-center py-12 text-center rounded-lg border border-dashed border-border">
|
|
25
|
-
<p className="text-xs text-foreground-subtle">
|
|
26
|
-
Select an addon to configure provider-level settings.
|
|
27
|
-
</p>
|
|
28
|
-
<p className="text-[10px] text-foreground-disabled mt-1">
|
|
29
|
-
Settings will be rendered here using the FormBuilder once a provider addon is selected.
|
|
30
|
-
</p>
|
|
31
|
-
</div>
|
|
32
|
-
</div>
|
|
33
|
-
)
|
|
34
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { CircleDot, Film } from 'lucide-react'
|
|
2
|
-
|
|
3
|
-
export function RecordingTab() {
|
|
4
|
-
const isRecording = false
|
|
5
|
-
|
|
6
|
-
return (
|
|
7
|
-
<div className="space-y-4">
|
|
8
|
-
{/* Recording status */}
|
|
9
|
-
<div className="rounded-lg border border-border bg-surface overflow-hidden">
|
|
10
|
-
<div className="border-b border-border px-4 py-2.5">
|
|
11
|
-
<h2 className="text-xs font-semibold text-foreground uppercase tracking-wider">Recording Status</h2>
|
|
12
|
-
</div>
|
|
13
|
-
<div className="px-4 py-4 flex items-center gap-3">
|
|
14
|
-
<div
|
|
15
|
-
className={`flex items-center justify-center rounded-full p-2 ${
|
|
16
|
-
isRecording ? 'bg-danger/10' : 'bg-foreground-subtle/10'
|
|
17
|
-
}`}
|
|
18
|
-
>
|
|
19
|
-
<CircleDot
|
|
20
|
-
className={`h-5 w-5 ${isRecording ? 'text-danger' : 'text-foreground-subtle'}`}
|
|
21
|
-
/>
|
|
22
|
-
</div>
|
|
23
|
-
<div>
|
|
24
|
-
<p className={`text-sm font-medium ${isRecording ? 'text-danger' : 'text-foreground-subtle'}`}>
|
|
25
|
-
{isRecording ? 'Recording' : 'Not Recording'}
|
|
26
|
-
</p>
|
|
27
|
-
<p className="text-[11px] text-foreground-subtle mt-0.5">
|
|
28
|
-
{isRecording ? 'Recording in progress' : 'No active recording session'}
|
|
29
|
-
</p>
|
|
30
|
-
</div>
|
|
31
|
-
</div>
|
|
32
|
-
</div>
|
|
33
|
-
|
|
34
|
-
{/* Segments */}
|
|
35
|
-
<div className="rounded-lg border border-border bg-surface overflow-hidden">
|
|
36
|
-
<div className="border-b border-border px-4 py-2.5">
|
|
37
|
-
<h2 className="text-xs font-semibold text-foreground uppercase tracking-wider">Recorded Segments</h2>
|
|
38
|
-
</div>
|
|
39
|
-
<div className="flex flex-col items-center justify-center py-12 text-foreground-subtle">
|
|
40
|
-
<Film className="h-8 w-8 mb-3 opacity-30" />
|
|
41
|
-
<p className="text-sm">No recorded segments</p>
|
|
42
|
-
<p className="text-xs mt-1 opacity-70">Recorded clips will appear here</p>
|
|
43
|
-
</div>
|
|
44
|
-
</div>
|
|
45
|
-
</div>
|
|
46
|
-
)
|
|
47
|
-
}
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import { useState, useRef } from 'react'
|
|
2
|
-
import { useMutation } from '@tanstack/react-query'
|
|
3
|
-
import { Play, ChevronUp, Trash2 } from 'lucide-react'
|
|
4
|
-
import { useBackendClient } from '../../../hooks/useBackendClient'
|
|
5
|
-
|
|
6
|
-
interface ReplTabProps {
|
|
7
|
-
deviceId: string
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
interface HistoryEntry {
|
|
11
|
-
id: number
|
|
12
|
-
code: string
|
|
13
|
-
result: string
|
|
14
|
-
error: boolean
|
|
15
|
-
ts: string
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
let historyCounter = 0
|
|
19
|
-
|
|
20
|
-
export function ReplTab({ deviceId }: ReplTabProps) {
|
|
21
|
-
const client = useBackendClient()
|
|
22
|
-
const [code, setCode] = useState('')
|
|
23
|
-
const [history, setHistory] = useState<HistoryEntry[]>([])
|
|
24
|
-
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
|
25
|
-
|
|
26
|
-
const evalMutation = useMutation({
|
|
27
|
-
mutationFn: (src: string) =>
|
|
28
|
-
client.replEval(src, { type: 'device' as const, deviceId }),
|
|
29
|
-
onSuccess: (data, src) => {
|
|
30
|
-
const result = typeof data === 'string' ? data : JSON.stringify(data, null, 2)
|
|
31
|
-
setHistory((prev) => [
|
|
32
|
-
...prev,
|
|
33
|
-
{
|
|
34
|
-
id: ++historyCounter,
|
|
35
|
-
code: src,
|
|
36
|
-
result,
|
|
37
|
-
error: false,
|
|
38
|
-
ts: new Date().toLocaleTimeString('en-GB', { hour12: false }),
|
|
39
|
-
},
|
|
40
|
-
])
|
|
41
|
-
setCode('')
|
|
42
|
-
},
|
|
43
|
-
onError: (err: unknown, src) => {
|
|
44
|
-
const result = err instanceof Error ? err.message : String(err)
|
|
45
|
-
setHistory((prev) => [
|
|
46
|
-
...prev,
|
|
47
|
-
{
|
|
48
|
-
id: ++historyCounter,
|
|
49
|
-
code: src,
|
|
50
|
-
result,
|
|
51
|
-
error: true,
|
|
52
|
-
ts: new Date().toLocaleTimeString('en-GB', { hour12: false }),
|
|
53
|
-
},
|
|
54
|
-
])
|
|
55
|
-
},
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
function handleExecute() {
|
|
59
|
-
const trimmed = code.trim()
|
|
60
|
-
if (!trimmed) return
|
|
61
|
-
evalMutation.mutate(trimmed)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function handleKeyDown(e: React.KeyboardEvent<HTMLTextAreaElement>) {
|
|
65
|
-
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
|
|
66
|
-
e.preventDefault()
|
|
67
|
-
handleExecute()
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function reuseCode(src: string) {
|
|
72
|
-
setCode(src)
|
|
73
|
-
textareaRef.current?.focus()
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return (
|
|
77
|
-
<div className="space-y-4">
|
|
78
|
-
<div className="flex items-center justify-between">
|
|
79
|
-
<p className="text-xs text-foreground-subtle">
|
|
80
|
-
Scoped to device: <span className="font-mono text-foreground">{deviceId}</span>
|
|
81
|
-
</p>
|
|
82
|
-
{history.length > 0 && (
|
|
83
|
-
<button
|
|
84
|
-
onClick={() => setHistory([])}
|
|
85
|
-
className="inline-flex items-center gap-1 text-[10px] text-foreground-subtle hover:text-foreground"
|
|
86
|
-
>
|
|
87
|
-
<Trash2 className="h-3 w-3" />
|
|
88
|
-
Clear history
|
|
89
|
-
</button>
|
|
90
|
-
)}
|
|
91
|
-
</div>
|
|
92
|
-
|
|
93
|
-
{/* Input */}
|
|
94
|
-
<div className="rounded-lg border border-border bg-surface overflow-hidden">
|
|
95
|
-
<div className="flex items-center justify-between border-b border-border px-3 py-1.5">
|
|
96
|
-
<span className="text-[10px] text-foreground-subtle font-mono">JavaScript — Ctrl+Enter to run</span>
|
|
97
|
-
<button
|
|
98
|
-
onClick={handleExecute}
|
|
99
|
-
disabled={evalMutation.isPending || !code.trim()}
|
|
100
|
-
className="inline-flex items-center gap-1.5 rounded px-2.5 py-1 text-[10px] font-medium bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
|
|
101
|
-
>
|
|
102
|
-
<Play className="h-3 w-3" />
|
|
103
|
-
{evalMutation.isPending ? 'Running...' : 'Execute'}
|
|
104
|
-
</button>
|
|
105
|
-
</div>
|
|
106
|
-
<textarea
|
|
107
|
-
ref={textareaRef}
|
|
108
|
-
value={code}
|
|
109
|
-
onChange={(e) => setCode(e.target.value)}
|
|
110
|
-
onKeyDown={handleKeyDown}
|
|
111
|
-
rows={5}
|
|
112
|
-
placeholder="// Enter JavaScript code scoped to this device..."
|
|
113
|
-
className="w-full resize-y bg-background p-3 font-mono text-xs text-foreground placeholder:text-foreground-subtle focus:outline-none"
|
|
114
|
-
spellCheck={false}
|
|
115
|
-
/>
|
|
116
|
-
</div>
|
|
117
|
-
|
|
118
|
-
{/* History */}
|
|
119
|
-
{history.length === 0 && (
|
|
120
|
-
<div className="text-xs text-foreground-subtle">No evaluations yet</div>
|
|
121
|
-
)}
|
|
122
|
-
|
|
123
|
-
{history.length > 0 && (
|
|
124
|
-
<div className="space-y-3">
|
|
125
|
-
{[...history].reverse().map((entry) => (
|
|
126
|
-
<div key={entry.id} className="rounded-lg border border-border bg-surface overflow-hidden">
|
|
127
|
-
<div className="flex items-start justify-between gap-2 border-b border-border bg-background px-3 py-2">
|
|
128
|
-
<pre className="font-mono text-xs text-foreground whitespace-pre-wrap break-all flex-1">{entry.code}</pre>
|
|
129
|
-
<div className="flex items-center gap-1.5 flex-shrink-0">
|
|
130
|
-
<span className="text-[10px] text-foreground-subtle">{entry.ts}</span>
|
|
131
|
-
<button
|
|
132
|
-
onClick={() => reuseCode(entry.code)}
|
|
133
|
-
title="Reuse"
|
|
134
|
-
className="text-foreground-subtle hover:text-foreground"
|
|
135
|
-
>
|
|
136
|
-
<ChevronUp className="h-3.5 w-3.5" />
|
|
137
|
-
</button>
|
|
138
|
-
</div>
|
|
139
|
-
</div>
|
|
140
|
-
<pre
|
|
141
|
-
className={`px-3 py-2 font-mono text-xs whitespace-pre-wrap break-all ${
|
|
142
|
-
entry.error ? 'text-danger' : 'text-success'
|
|
143
|
-
}`}
|
|
144
|
-
>
|
|
145
|
-
{entry.result}
|
|
146
|
-
</pre>
|
|
147
|
-
</div>
|
|
148
|
-
))}
|
|
149
|
-
</div>
|
|
150
|
-
)}
|
|
151
|
-
</div>
|
|
152
|
-
)
|
|
153
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react'
|
|
2
|
-
import { Footprints } from 'lucide-react'
|
|
3
|
-
|
|
4
|
-
export function TrackTrailTab() {
|
|
5
|
-
const [trailEnabled, setTrailEnabled] = useState(false)
|
|
6
|
-
|
|
7
|
-
return (
|
|
8
|
-
<div className="space-y-4">
|
|
9
|
-
<div className="rounded-lg border border-border bg-surface overflow-hidden">
|
|
10
|
-
<div className="border-b border-border px-4 py-2.5">
|
|
11
|
-
<h2 className="text-xs font-semibold text-foreground uppercase tracking-wider">Track Trail Config</h2>
|
|
12
|
-
<p className="text-[11px] text-foreground-subtle mt-0.5">Visualize movement trails for tracked objects</p>
|
|
13
|
-
</div>
|
|
14
|
-
<div className="px-4 py-3">
|
|
15
|
-
<div className="flex items-center justify-between">
|
|
16
|
-
<div>
|
|
17
|
-
<p className="text-xs font-medium text-foreground">Enable Trail Visualization</p>
|
|
18
|
-
<p className="text-[11px] text-foreground-subtle mt-0.5">Show movement history for tracked objects</p>
|
|
19
|
-
</div>
|
|
20
|
-
<button
|
|
21
|
-
onClick={() => setTrailEnabled((v) => !v)}
|
|
22
|
-
className={`relative inline-flex h-5 w-9 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors ${
|
|
23
|
-
trailEnabled ? 'bg-primary' : 'bg-foreground-subtle/30'
|
|
24
|
-
}`}
|
|
25
|
-
>
|
|
26
|
-
<span
|
|
27
|
-
className={`inline-block h-4 w-4 transform rounded-full bg-white shadow ring-0 transition-transform ${
|
|
28
|
-
trailEnabled ? 'translate-x-4' : 'translate-x-0'
|
|
29
|
-
}`}
|
|
30
|
-
/>
|
|
31
|
-
</button>
|
|
32
|
-
</div>
|
|
33
|
-
</div>
|
|
34
|
-
</div>
|
|
35
|
-
|
|
36
|
-
{/* Active trails */}
|
|
37
|
-
<div className="rounded-lg border border-border bg-surface overflow-hidden">
|
|
38
|
-
<div className="border-b border-border px-4 py-2.5">
|
|
39
|
-
<h2 className="text-xs font-semibold text-foreground uppercase tracking-wider">Active Trails</h2>
|
|
40
|
-
</div>
|
|
41
|
-
<div className="flex flex-col items-center justify-center py-12 text-foreground-subtle">
|
|
42
|
-
<Footprints className="h-8 w-8 mb-3 opacity-30" />
|
|
43
|
-
<p className="text-sm">No active trails</p>
|
|
44
|
-
<p className="text-xs mt-1 opacity-70">Trails appear when objects are being tracked</p>
|
|
45
|
-
</div>
|
|
46
|
-
</div>
|
|
47
|
-
</div>
|
|
48
|
-
)
|
|
49
|
-
}
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react'
|
|
2
|
-
import { ZoneCanvas } from '../zone-editor/ZoneCanvas'
|
|
3
|
-
import { ZoneList } from '../zone-editor/ZoneList'
|
|
4
|
-
import { ZoneForm } from '../zone-editor/ZoneForm'
|
|
5
|
-
import type { Zone } from '../zone-editor/ZoneCanvas'
|
|
6
|
-
|
|
7
|
-
// ---------------------------------------------------------------------------
|
|
8
|
-
// ZonesTab
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
|
-
|
|
11
|
-
export function ZonesTab() {
|
|
12
|
-
const [zones, setZones] = useState<Zone[]>([])
|
|
13
|
-
const [selectedZoneId, setSelectedZoneId] = useState<string | null>(null)
|
|
14
|
-
const [drawingType, setDrawingType] = useState<'intrusion' | 'package-watch' | 'tripwire' | null>(null)
|
|
15
|
-
|
|
16
|
-
const selectedZone = zones.find((z) => z.id === selectedZoneId) ?? null
|
|
17
|
-
|
|
18
|
-
const handleZoneComplete = (zone: Zone) => {
|
|
19
|
-
setZones((prev) => [...prev, zone])
|
|
20
|
-
setSelectedZoneId(zone.id)
|
|
21
|
-
setDrawingType(null)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const handleZonePointsChange = (id: string, points: { x: number; y: number }[]) => {
|
|
25
|
-
setZones((prev) => prev.map((z) => (z.id === id ? { ...z, points } : z)))
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const handleZoneChange = (updated: Zone) => {
|
|
29
|
-
setZones((prev) => prev.map((z) => (z.id === updated.id ? updated : z)))
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const handleDeleteZone = (id: string) => {
|
|
33
|
-
setZones((prev) => prev.filter((z) => z.id !== id))
|
|
34
|
-
if (selectedZoneId === id) setSelectedZoneId(null)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const handleStartDraw = (type: 'intrusion' | 'package-watch' | 'tripwire') => {
|
|
38
|
-
setSelectedZoneId(null)
|
|
39
|
-
setDrawingType(type)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return (
|
|
43
|
-
<div className="space-y-4">
|
|
44
|
-
<div className="rounded-lg border border-border bg-surface overflow-hidden">
|
|
45
|
-
<div className="border-b border-border px-4 py-2.5 flex items-center justify-between">
|
|
46
|
-
<div>
|
|
47
|
-
<h2 className="text-xs font-semibold text-foreground uppercase tracking-wider">Zone Editor</h2>
|
|
48
|
-
<p className="text-[11px] text-foreground-subtle mt-0.5">
|
|
49
|
-
Define detection zones and tripwire lines
|
|
50
|
-
</p>
|
|
51
|
-
</div>
|
|
52
|
-
<span className="text-[10px] text-foreground-subtle">
|
|
53
|
-
{zones.length} zone{zones.length !== 1 ? 's' : ''}
|
|
54
|
-
</span>
|
|
55
|
-
</div>
|
|
56
|
-
|
|
57
|
-
{/* Main layout: canvas + sidebar */}
|
|
58
|
-
<div className="flex flex-col lg:flex-row">
|
|
59
|
-
{/* Canvas area */}
|
|
60
|
-
<div className="flex-1 p-4">
|
|
61
|
-
<ZoneCanvas
|
|
62
|
-
zones={zones}
|
|
63
|
-
selectedZoneId={selectedZoneId}
|
|
64
|
-
onSelectZone={setSelectedZoneId}
|
|
65
|
-
onZonePointsChange={handleZonePointsChange}
|
|
66
|
-
onZoneComplete={handleZoneComplete}
|
|
67
|
-
drawingType={drawingType}
|
|
68
|
-
/>
|
|
69
|
-
</div>
|
|
70
|
-
|
|
71
|
-
{/* Sidebar */}
|
|
72
|
-
<div className="w-full lg:w-64 border-t lg:border-t-0 lg:border-l border-border p-4 flex flex-col gap-4">
|
|
73
|
-
<ZoneList
|
|
74
|
-
zones={zones}
|
|
75
|
-
selectedZoneId={selectedZoneId}
|
|
76
|
-
drawingType={drawingType}
|
|
77
|
-
onSelectZone={setSelectedZoneId}
|
|
78
|
-
onDeleteZone={handleDeleteZone}
|
|
79
|
-
onStartDraw={handleStartDraw}
|
|
80
|
-
onCancelDraw={() => setDrawingType(null)}
|
|
81
|
-
/>
|
|
82
|
-
|
|
83
|
-
{selectedZone && !drawingType && (
|
|
84
|
-
<>
|
|
85
|
-
<div className="border-t border-border" />
|
|
86
|
-
<ZoneForm
|
|
87
|
-
zone={selectedZone}
|
|
88
|
-
onChange={handleZoneChange}
|
|
89
|
-
onDelete={() => handleDeleteZone(selectedZone.id)}
|
|
90
|
-
/>
|
|
91
|
-
</>
|
|
92
|
-
)}
|
|
93
|
-
</div>
|
|
94
|
-
</div>
|
|
95
|
-
</div>
|
|
96
|
-
</div>
|
|
97
|
-
)
|
|
98
|
-
}
|