@camstack/addon-admin-ui 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/dist/assets/index-BoVZEQ1j.js +598 -0
  2. package/dist/assets/index-DwSc8ann.css +1 -0
  3. package/{index.html → dist/index.html} +3 -1
  4. package/dist/server/addon.d.ts +11 -0
  5. package/dist/server/addon.js +50 -0
  6. package/dist/server/addon.js.map +1 -0
  7. package/package.json +4 -1
  8. package/src/App.tsx +0 -71
  9. package/src/components/addons/AddonCard.tsx +0 -355
  10. package/src/components/addons/AddonUploadZone.tsx +0 -69
  11. package/src/components/addons/CapabilityBadge.tsx +0 -55
  12. package/src/components/addons/CapabilityMap.tsx +0 -133
  13. package/src/components/addons/UpdatesList.tsx +0 -108
  14. package/src/components/agents/AgentCard.tsx +0 -281
  15. package/src/components/agents/AgentLogs.tsx +0 -231
  16. package/src/components/agents/ProcessList.tsx +0 -127
  17. package/src/components/agents/ProcessTree.tsx +0 -369
  18. package/src/components/agents/TaskList.tsx +0 -68
  19. package/src/components/cameras/CameraCard.tsx +0 -60
  20. package/src/components/cameras/LiveEventsPanel.tsx +0 -91
  21. package/src/components/cameras/ProviderSection.tsx +0 -50
  22. package/src/components/cameras/StreamArea.tsx +0 -107
  23. package/src/components/cameras/tabs/AddonsTab.tsx +0 -113
  24. package/src/components/cameras/tabs/CameraEventsTab.tsx +0 -129
  25. package/src/components/cameras/tabs/PipelineTab.tsx +0 -118
  26. package/src/components/cameras/tabs/StreamsTab.tsx +0 -114
  27. package/src/components/dashboard/BlockPicker.tsx +0 -54
  28. package/src/components/dashboard/BlockWrapper.tsx +0 -97
  29. package/src/components/dashboard/DashboardGrid.tsx +0 -160
  30. package/src/components/dashboard/block-registry.ts +0 -15
  31. package/src/components/dashboard/blocks/PipelineStagesBlock.tsx +0 -39
  32. package/src/components/dashboard/blocks/StorageBlock.tsx +0 -66
  33. package/src/components/dashboard/blocks/SystemStatusBlock.tsx +0 -67
  34. package/src/components/dashboard/blocks/index.ts +0 -32
  35. package/src/components/device/DeviceHeader.tsx +0 -116
  36. package/src/components/device/FloatingPanel.tsx +0 -132
  37. package/src/components/device/FloatingPanelManager.tsx +0 -167
  38. package/src/components/device/PanelContent.tsx +0 -196
  39. package/src/components/device/QuickConfigWizard.tsx +0 -507
  40. package/src/components/device/tabs/DetectionConfigTab.tsx +0 -96
  41. package/src/components/device/tabs/EventsTab.tsx +0 -19
  42. package/src/components/device/tabs/LogsTab.tsx +0 -22
  43. package/src/components/device/tabs/OverviewTab.tsx +0 -104
  44. package/src/components/device/tabs/ProviderSettingsTab.tsx +0 -34
  45. package/src/components/device/tabs/RecordingTab.tsx +0 -47
  46. package/src/components/device/tabs/ReplTab.tsx +0 -153
  47. package/src/components/device/tabs/TrackTrailTab.tsx +0 -49
  48. package/src/components/device/tabs/ZonesTab.tsx +0 -98
  49. package/src/components/device/zone-editor/ZoneCanvas.tsx +0 -354
  50. package/src/components/device/zone-editor/ZoneForm.tsx +0 -128
  51. package/src/components/device/zone-editor/ZoneList.tsx +0 -150
  52. package/src/components/form-builder/FormBuilder.tsx +0 -135
  53. package/src/components/form-builder/FormField.tsx +0 -732
  54. package/src/components/form-builder/ModelSelector.tsx +0 -239
  55. package/src/components/integrations/AddDeviceDialog.tsx +0 -205
  56. package/src/components/integrations/CompactDeviceCard.tsx +0 -35
  57. package/src/components/integrations/DeviceCard.tsx +0 -29
  58. package/src/components/integrations/DeviceDiscoveryStep.tsx +0 -105
  59. package/src/components/integrations/DeviceGrid.tsx +0 -79
  60. package/src/components/integrations/DeviceGroupHeader.tsx +0 -17
  61. package/src/components/integrations/DiscoveredDeviceCard.tsx +0 -26
  62. package/src/components/integrations/IntegrationCard.tsx +0 -40
  63. package/src/components/integrations/IntegrationWizard.tsx +0 -172
  64. package/src/components/integrations/ProviderConfigForm.tsx +0 -89
  65. package/src/components/integrations/ProviderPicker.tsx +0 -91
  66. package/src/components/integrations/SnapshotPopover.tsx +0 -68
  67. package/src/components/metrics/AgentLoad.tsx +0 -105
  68. package/src/components/metrics/IntegrationUsage.tsx +0 -73
  69. package/src/components/metrics/PipelineStatus.tsx +0 -74
  70. package/src/components/metrics/ProcessResources.tsx +0 -123
  71. package/src/components/pipeline/PhaseSettings.tsx +0 -131
  72. package/src/components/shared/CapabilityBadges.tsx +0 -30
  73. package/src/components/shared/ProviderIcon.tsx +0 -42
  74. package/src/components/shared/StatusBadge.tsx +0 -23
  75. package/src/components/shared/WebRtcPlayer.tsx +0 -211
  76. package/src/components/timeline/EventMarker.tsx +0 -32
  77. package/src/components/timeline/TimelineBar.tsx +0 -131
  78. package/src/components/ui/ConfirmDialog.tsx +0 -115
  79. package/src/components/ui/ToastContainer.tsx +0 -92
  80. package/src/contexts/auth-context.tsx +0 -91
  81. package/src/hooks/useBackendClient.ts +0 -6
  82. package/src/hooks/useTheme.ts +0 -1
  83. package/src/i18n/en.json +0 -164
  84. package/src/i18n/index.ts +0 -29
  85. package/src/i18n/it.json +0 -164
  86. package/src/index.css +0 -63
  87. package/src/layouts/AddonPageLoader.tsx +0 -120
  88. package/src/layouts/AppLayout.tsx +0 -254
  89. package/src/layouts/ProtectedRoute.tsx +0 -25
  90. package/src/lib/addon-page-context.ts +0 -29
  91. package/src/lib/backend.ts +0 -16
  92. package/src/main.tsx +0 -21
  93. package/src/pages/AccessDenied.tsx +0 -22
  94. package/src/pages/Cameras.tsx +0 -127
  95. package/src/pages/Dashboard.tsx +0 -6
  96. package/src/pages/DeviceDetail.tsx +0 -175
  97. package/src/pages/IntegrationDetail.tsx +0 -222
  98. package/src/pages/Integrations.tsx +0 -333
  99. package/src/pages/Login.tsx +0 -106
  100. package/src/pages/Metrics.tsx +0 -18
  101. package/src/pages/PipelineConfig.tsx +0 -282
  102. package/src/pages/Showroom.tsx +0 -351
  103. package/src/pages/Timeline.tsx +0 -269
  104. package/src/pages/system/Addons.tsx +0 -396
  105. package/src/pages/system/Agents.tsx +0 -362
  106. package/src/pages/system/Logs.tsx +0 -131
  107. package/src/pages/system/Models.tsx +0 -102
  108. package/src/pages/system/Processes.tsx +0 -129
  109. package/src/pages/system/Repl.tsx +0 -148
  110. package/src/pages/system/Settings.tsx +0 -168
  111. package/src/pages/system/Users.tsx +0 -174
  112. package/src/server/addon.ts +0 -54
  113. package/src/types/config-ui.ts +0 -28
  114. package/src/types/dashboard.ts +0 -39
  115. package/tsconfig.json +0 -29
  116. package/tsconfig.server.json +0 -16
  117. package/tsup.config.ts +0 -20
  118. package/vite.config.ts +0 -68
  119. /package/{public → dist}/brand/logo-dark.svg +0 -0
  120. /package/{public → dist}/brand/logo-horizontal-dark.svg +0 -0
  121. /package/{public → dist}/brand/logo-horizontal-light.svg +0 -0
  122. /package/{public → dist}/brand/logo-light.svg +0 -0
  123. /package/{public → dist}/brand/logo-wide-dark.svg +0 -0
  124. /package/{public → dist}/brand/logo-wide-light.svg +0 -0
  125. /package/{public → dist}/favicon.svg +0 -0
  126. /package/{public → dist}/vendor/react-jsx-runtime.mjs +0 -0
  127. /package/{public → dist}/vendor/react.mjs +0 -0
@@ -1,354 +0,0 @@
1
- import { useRef, useState, useEffect, useCallback } from 'react'
2
- import { Stage, Layer, Line, Circle, Text, Group, Arrow } from 'react-konva'
3
- import type Konva from 'konva'
4
-
5
- // ---------------------------------------------------------------------------
6
- // Types
7
- // ---------------------------------------------------------------------------
8
-
9
- export interface Zone {
10
- id: string
11
- name: string
12
- type: 'intrusion' | 'package-watch' | 'tripwire'
13
- color: string
14
- points: { x: number; y: number }[] // normalized 0-1
15
- }
16
-
17
- interface ZoneCanvasProps {
18
- zones: Zone[]
19
- selectedZoneId: string | null
20
- onSelectZone: (id: string | null) => void
21
- onZonePointsChange: (id: string, points: { x: number; y: number }[]) => void
22
- onZoneComplete: (zone: Zone) => void
23
- drawingType: 'intrusion' | 'package-watch' | 'tripwire' | null
24
- }
25
-
26
- // ---------------------------------------------------------------------------
27
- // Helpers
28
- // ---------------------------------------------------------------------------
29
-
30
- function toCanvas(p: { x: number; y: number }, w: number, h: number) {
31
- return { x: p.x * w, y: p.y * h }
32
- }
33
-
34
- function toNorm(p: { x: number; y: number }, w: number, h: number) {
35
- return { x: p.x / w, y: p.y / h }
36
- }
37
-
38
- function flatPoints(points: { x: number; y: number }[], w: number, h: number): number[] {
39
- return points.flatMap((p) => [p.x * w, p.y * h])
40
- }
41
-
42
- function zoneColor(zone: Zone, selected: boolean): string {
43
- return selected ? '#6366f1' : zone.color
44
- }
45
-
46
- const GRID_COLS = 16
47
- const GRID_ROWS = 9
48
-
49
- // ---------------------------------------------------------------------------
50
- // Component
51
- // ---------------------------------------------------------------------------
52
-
53
- export function ZoneCanvas({
54
- zones,
55
- selectedZoneId,
56
- onSelectZone,
57
- onZonePointsChange,
58
- onZoneComplete,
59
- drawingType,
60
- }: ZoneCanvasProps) {
61
- const containerRef = useRef<HTMLDivElement>(null)
62
- const [size, setSize] = useState({ w: 800, h: 450 })
63
- const [drawPoints, setDrawPoints] = useState<{ x: number; y: number }[]>([])
64
- const [cursorPos, setCursorPos] = useState<{ x: number; y: number } | null>(null)
65
-
66
- // Resize observer
67
- useEffect(() => {
68
- const el = containerRef.current
69
- if (!el) return
70
- const ro = new ResizeObserver((entries) => {
71
- const entry = entries[0]
72
- if (entry) {
73
- const { width } = entry.contentRect
74
- setSize({ w: width, h: (width * 9) / 16 })
75
- }
76
- })
77
- ro.observe(el)
78
- return () => ro.disconnect()
79
- }, [])
80
-
81
- const isDrawing = drawingType !== null
82
-
83
- const handleStageClick = useCallback(
84
- (e: Konva.KonvaEventObject<MouseEvent>) => {
85
- if (!isDrawing) {
86
- onSelectZone(null)
87
- return
88
- }
89
- const stage = e.target.getStage()
90
- if (!stage) return
91
- const pos = stage.getPointerPosition()
92
- if (!pos) return
93
-
94
- const norm = toNorm(pos, size.w, size.h)
95
- const isTripwire = drawingType === 'tripwire'
96
-
97
- if (drawPoints.length === 0) {
98
- setDrawPoints([norm])
99
- return
100
- }
101
-
102
- // For tripwire: complete after 2 points
103
- if (isTripwire && drawPoints.length === 1) {
104
- const newPoints = [...drawPoints, norm]
105
- const zone: Zone = {
106
- id: crypto.randomUUID(),
107
- name: 'Tripwire',
108
- type: 'tripwire',
109
- color: '#f59e0b',
110
- points: newPoints,
111
- }
112
- onZoneComplete(zone)
113
- setDrawPoints([])
114
- return
115
- }
116
-
117
- // Check if clicking near first point to close polygon
118
- if (drawPoints.length >= 3 && !isTripwire) {
119
- const first = toCanvas(drawPoints[0]!, size.w, size.h)
120
- const dist = Math.hypot(pos.x - first.x, pos.y - first.y)
121
- if (dist < 12) {
122
- const zone: Zone = {
123
- id: crypto.randomUUID(),
124
- name: drawingType === 'package-watch' ? 'Package Watch' : 'Zone',
125
- type: drawingType,
126
- color: drawingType === 'package-watch' ? '#10b981' : '#3b82f6',
127
- points: drawPoints,
128
- }
129
- onZoneComplete(zone)
130
- setDrawPoints([])
131
- return
132
- }
133
- }
134
-
135
- setDrawPoints((prev) => [...prev, norm])
136
- },
137
- [isDrawing, drawPoints, size, drawingType, onZoneComplete, onSelectZone],
138
- )
139
-
140
- const handleDblClick = useCallback(
141
- (e: Konva.KonvaEventObject<MouseEvent>) => {
142
- if (!isDrawing || drawingType === 'tripwire') return
143
- if (drawPoints.length < 3) return
144
- e.cancelBubble = true
145
- const zone: Zone = {
146
- id: crypto.randomUUID(),
147
- name: drawingType === 'package-watch' ? 'Package Watch' : 'Zone',
148
- type: drawingType,
149
- color: drawingType === 'package-watch' ? '#10b981' : '#3b82f6',
150
- points: drawPoints,
151
- }
152
- onZoneComplete(zone)
153
- setDrawPoints([])
154
- },
155
- [isDrawing, drawingType, drawPoints, onZoneComplete],
156
- )
157
-
158
- const handleMouseMove = useCallback(
159
- (e: Konva.KonvaEventObject<MouseEvent>) => {
160
- const stage = e.target.getStage()
161
- if (!stage) return
162
- const pos = stage.getPointerPosition()
163
- setCursorPos(pos ?? null)
164
- },
165
- [],
166
- )
167
-
168
- // Grid lines
169
- const gridLines: React.ReactNode[] = []
170
- for (let i = 1; i < GRID_COLS; i++) {
171
- const x = (size.w / GRID_COLS) * i
172
- gridLines.push(
173
- <Line key={`gv-${i}`} points={[x, 0, x, size.h]} stroke="#ffffff" strokeWidth={0.5} opacity={0.08} />,
174
- )
175
- }
176
- for (let i = 1; i < GRID_ROWS; i++) {
177
- const y = (size.h / GRID_ROWS) * i
178
- gridLines.push(
179
- <Line key={`gh-${i}`} points={[0, y, size.w, y]} stroke="#ffffff" strokeWidth={0.5} opacity={0.08} />,
180
- )
181
- }
182
-
183
- return (
184
- <div
185
- ref={containerRef}
186
- className="w-full bg-zinc-900 rounded-lg overflow-hidden"
187
- style={{ cursor: isDrawing ? 'crosshair' : 'default' }}
188
- >
189
- <Stage
190
- width={size.w}
191
- height={size.h}
192
- onClick={handleStageClick}
193
- onDblClick={handleDblClick}
194
- onMouseMove={handleMouseMove}
195
- >
196
- {/* Background + grid */}
197
- <Layer>
198
- <Line points={[0, 0, size.w, 0, size.w, size.h, 0, size.h, 0, 0]} stroke="transparent" strokeWidth={0} />
199
- {gridLines}
200
- </Layer>
201
-
202
- {/* Existing zones */}
203
- <Layer>
204
- {zones.map((zone) => {
205
- const selected = zone.id === selectedZoneId
206
- const color = zoneColor(zone, selected)
207
- const pts = flatPoints(zone.points, size.w, size.h)
208
- const isTripwire = zone.type === 'tripwire'
209
-
210
- return (
211
- <Group
212
- key={zone.id}
213
- onClick={(e) => {
214
- e.cancelBubble = true
215
- if (!isDrawing) onSelectZone(zone.id)
216
- }}
217
- >
218
- {isTripwire ? (
219
- <Arrow
220
- points={pts}
221
- stroke={color}
222
- strokeWidth={selected ? 3 : 2}
223
- fill={color}
224
- pointerLength={10}
225
- pointerWidth={8}
226
- opacity={selected ? 1 : 0.8}
227
- />
228
- ) : (
229
- <Line
230
- points={pts}
231
- closed
232
- fill={color + '33'}
233
- stroke={color}
234
- strokeWidth={selected ? 2.5 : 1.5}
235
- opacity={selected ? 1 : 0.85}
236
- />
237
- )}
238
-
239
- {/* Zone label */}
240
- {zone.points.length > 0 && (
241
- <Text
242
- x={toCanvas(zone.points[0]!, size.w, size.h).x + 4}
243
- y={toCanvas(zone.points[0]!, size.w, size.h).y + 4}
244
- text={zone.name}
245
- fontSize={11}
246
- fill={color}
247
- fontStyle="bold"
248
- />
249
- )}
250
-
251
- {/* Drag handles for selected zone */}
252
- {selected &&
253
- zone.points.map((p, idx) => {
254
- const cp = toCanvas(p, size.w, size.h)
255
- return (
256
- <Circle
257
- key={idx}
258
- x={cp.x}
259
- y={cp.y}
260
- radius={5}
261
- fill={color}
262
- stroke="#ffffff"
263
- strokeWidth={1.5}
264
- draggable
265
- onDragMove={(e) => {
266
- const newPt = toNorm({ x: e.target.x(), y: e.target.y() }, size.w, size.h)
267
- const updated = zone.points.map((pp, i) => (i === idx ? newPt : pp))
268
- onZonePointsChange(zone.id, updated)
269
- }}
270
- />
271
- )
272
- })}
273
- </Group>
274
- )
275
- })}
276
- </Layer>
277
-
278
- {/* Drawing preview */}
279
- <Layer>
280
- {isDrawing && drawPoints.length > 0 && (
281
- <Group>
282
- {drawPoints.length >= 2 && drawingType !== 'tripwire' && (
283
- <Line
284
- points={flatPoints(drawPoints, size.w, size.h)}
285
- stroke="#6366f1"
286
- strokeWidth={1.5}
287
- dash={[6, 3]}
288
- opacity={0.8}
289
- />
290
- )}
291
- {drawPoints.length === 1 && drawingType === 'tripwire' && cursorPos && (
292
- <Line
293
- points={[
294
- drawPoints[0]!.x * size.w,
295
- drawPoints[0]!.y * size.h,
296
- cursorPos.x,
297
- cursorPos.y,
298
- ]}
299
- stroke="#f59e0b"
300
- strokeWidth={1.5}
301
- dash={[6, 3]}
302
- opacity={0.7}
303
- />
304
- )}
305
- {/* Ghost line to cursor */}
306
- {cursorPos && drawPoints.length >= 1 && drawingType !== 'tripwire' && (
307
- <Line
308
- points={[
309
- drawPoints[drawPoints.length - 1]!.x * size.w,
310
- drawPoints[drawPoints.length - 1]!.y * size.h,
311
- cursorPos.x,
312
- cursorPos.y,
313
- ]}
314
- stroke="#6366f1"
315
- strokeWidth={1}
316
- dash={[4, 4]}
317
- opacity={0.5}
318
- />
319
- )}
320
- {/* Points */}
321
- {drawPoints.map((p, idx) => {
322
- const cp = toCanvas(p, size.w, size.h)
323
- const isFirst = idx === 0
324
- return (
325
- <Circle
326
- key={idx}
327
- x={cp.x}
328
- y={cp.y}
329
- radius={isFirst && drawPoints.length >= 3 ? 7 : 4}
330
- fill={isFirst && drawPoints.length >= 3 ? '#6366f1' : '#ffffff'}
331
- stroke="#6366f1"
332
- strokeWidth={1.5}
333
- opacity={0.9}
334
- />
335
- )
336
- })}
337
- </Group>
338
- )}
339
- </Layer>
340
- </Stage>
341
-
342
- {/* Hint overlay */}
343
- {isDrawing && (
344
- <div className="px-3 py-1.5 bg-zinc-800/80 text-[11px] text-zinc-400 border-t border-zinc-700">
345
- {drawingType === 'tripwire'
346
- ? 'Click to set start point, click again to complete the tripwire'
347
- : drawPoints.length < 3
348
- ? 'Click to add points (min 3). Double-click or click first point to close.'
349
- : 'Continue adding points. Double-click or click first point to close.'}
350
- </div>
351
- )}
352
- </div>
353
- )
354
- }
@@ -1,128 +0,0 @@
1
- import { Trash2 } from 'lucide-react'
2
- import type { Zone } from './ZoneCanvas'
3
-
4
- // ---------------------------------------------------------------------------
5
- // Constants
6
- // ---------------------------------------------------------------------------
7
-
8
- const PRESET_COLORS = [
9
- '#3b82f6', // blue
10
- '#10b981', // green
11
- '#f59e0b', // amber
12
- '#ef4444', // red
13
- '#8b5cf6', // purple
14
- '#06b6d4', // cyan
15
- '#f97316', // orange
16
- '#ec4899', // pink
17
- ]
18
-
19
- const TYPE_OPTIONS: { value: Zone['type']; label: string }[] = [
20
- { value: 'intrusion', label: 'Intrusion' },
21
- { value: 'package-watch', label: 'Package Watch' },
22
- { value: 'tripwire', label: 'Tripwire' },
23
- ]
24
-
25
- const INPUT_CLASS =
26
- 'w-full rounded-md border border-border bg-background px-3 py-1.5 text-sm text-foreground focus:border-primary focus:ring-1 focus:ring-primary/30 outline-none'
27
-
28
- // ---------------------------------------------------------------------------
29
- // Props
30
- // ---------------------------------------------------------------------------
31
-
32
- interface ZoneFormProps {
33
- zone: Zone
34
- onChange: (updated: Zone) => void
35
- onDelete: () => void
36
- }
37
-
38
- // ---------------------------------------------------------------------------
39
- // Component
40
- // ---------------------------------------------------------------------------
41
-
42
- export function ZoneForm({ zone, onChange, onDelete }: ZoneFormProps) {
43
- return (
44
- <div className="flex flex-col gap-3">
45
- <h3 className="text-xs font-semibold text-foreground-subtle uppercase tracking-wider">Edit Zone</h3>
46
-
47
- {/* Name */}
48
- <div>
49
- <label className="block text-xs font-medium text-foreground mb-1">Name</label>
50
- <input
51
- type="text"
52
- className={INPUT_CLASS}
53
- value={zone.name}
54
- onChange={(e) => onChange({ ...zone, name: e.target.value })}
55
- placeholder="Zone name"
56
- />
57
- </div>
58
-
59
- {/* Type */}
60
- <div>
61
- <label className="block text-xs font-medium text-foreground mb-1">Type</label>
62
- <select
63
- className={INPUT_CLASS}
64
- value={zone.type}
65
- onChange={(e) => onChange({ ...zone, type: e.target.value as Zone['type'] })}
66
- >
67
- {TYPE_OPTIONS.map((opt) => (
68
- <option key={opt.value} value={opt.value}>
69
- {opt.label}
70
- </option>
71
- ))}
72
- </select>
73
- </div>
74
-
75
- {/* Color */}
76
- <div>
77
- <label className="block text-xs font-medium text-foreground mb-1.5">Color</label>
78
- <div className="flex flex-wrap gap-1.5 mb-2">
79
- {PRESET_COLORS.map((color) => (
80
- <button
81
- key={color}
82
- type="button"
83
- title={color}
84
- className={[
85
- 'h-5 w-5 rounded-md border-2 transition-transform hover:scale-110',
86
- zone.color === color ? 'border-white scale-110' : 'border-transparent',
87
- ].join(' ')}
88
- style={{ backgroundColor: color }}
89
- onClick={() => onChange({ ...zone, color })}
90
- />
91
- ))}
92
- </div>
93
- {/* Custom color input */}
94
- <div className="flex items-center gap-2">
95
- <input
96
- type="color"
97
- className="h-7 w-7 rounded border border-border bg-background cursor-pointer p-0.5"
98
- value={zone.color}
99
- onChange={(e) => onChange({ ...zone, color: e.target.value })}
100
- />
101
- <input
102
- type="text"
103
- className={INPUT_CLASS + ' font-mono flex-1'}
104
- value={zone.color}
105
- placeholder="#000000"
106
- onChange={(e) => onChange({ ...zone, color: e.target.value })}
107
- />
108
- </div>
109
- </div>
110
-
111
- {/* Point count info */}
112
- <p className="text-[10px] text-foreground-subtle">
113
- {zone.type === 'tripwire' ? 'Tripwire: 2 points' : `Polygon: ${zone.points.length} points`}
114
- {zone.points.length > 0 && ' — drag handles on canvas to reshape'}
115
- </p>
116
-
117
- {/* Delete */}
118
- <button
119
- type="button"
120
- onClick={onDelete}
121
- className="mt-1 flex items-center justify-center gap-1.5 w-full rounded-md border border-danger/40 bg-danger/10 px-3 py-1.5 text-xs font-medium text-danger hover:bg-danger/20 transition-colors"
122
- >
123
- <Trash2 className="h-3 w-3" />
124
- Delete zone
125
- </button>
126
- </div>
127
- )
128
- }
@@ -1,150 +0,0 @@
1
- import { Plus, Pencil, Trash2, Minus } from 'lucide-react'
2
- import type { Zone } from './ZoneCanvas'
3
-
4
- // ---------------------------------------------------------------------------
5
- // Type badge helper
6
- // ---------------------------------------------------------------------------
7
-
8
- const TYPE_LABELS: Record<Zone['type'], string> = {
9
- intrusion: 'Intrusion',
10
- 'package-watch': 'Package',
11
- tripwire: 'Tripwire',
12
- }
13
-
14
- const TYPE_COLORS: Record<Zone['type'], string> = {
15
- intrusion: 'bg-danger/10 text-danger',
16
- 'package-watch': 'bg-success/10 text-success',
17
- tripwire: 'bg-warning/10 text-warning',
18
- }
19
-
20
- // ---------------------------------------------------------------------------
21
- // Props
22
- // ---------------------------------------------------------------------------
23
-
24
- interface ZoneListProps {
25
- zones: Zone[]
26
- selectedZoneId: string | null
27
- drawingType: 'intrusion' | 'package-watch' | 'tripwire' | null
28
- onSelectZone: (id: string | null) => void
29
- onDeleteZone: (id: string) => void
30
- onStartDraw: (type: 'intrusion' | 'package-watch' | 'tripwire') => void
31
- onCancelDraw: () => void
32
- }
33
-
34
- // ---------------------------------------------------------------------------
35
- // Component
36
- // ---------------------------------------------------------------------------
37
-
38
- export function ZoneList({
39
- zones,
40
- selectedZoneId,
41
- drawingType,
42
- onSelectZone,
43
- onDeleteZone,
44
- onStartDraw,
45
- onCancelDraw,
46
- }: ZoneListProps) {
47
- return (
48
- <div className="flex flex-col gap-3">
49
- {/* Add buttons */}
50
- <div className="flex flex-col gap-1.5">
51
- {drawingType ? (
52
- <button
53
- type="button"
54
- onClick={onCancelDraw}
55
- className="w-full flex items-center justify-center gap-1.5 rounded-md border border-warning/40 bg-warning/10 px-3 py-1.5 text-xs font-medium text-warning hover:bg-warning/20 transition-colors"
56
- >
57
- <Minus className="h-3 w-3" />
58
- Cancel drawing
59
- </button>
60
- ) : (
61
- <>
62
- <button
63
- type="button"
64
- onClick={() => onStartDraw('intrusion')}
65
- className="w-full flex items-center justify-center gap-1.5 rounded-md border border-border bg-surface px-3 py-1.5 text-xs font-medium text-foreground hover:bg-surface-hover transition-colors"
66
- >
67
- <Plus className="h-3 w-3" />
68
- Add Zone
69
- </button>
70
- <button
71
- type="button"
72
- onClick={() => onStartDraw('tripwire')}
73
- className="w-full flex items-center justify-center gap-1.5 rounded-md border border-border bg-surface px-3 py-1.5 text-xs font-medium text-foreground hover:bg-surface-hover transition-colors"
74
- >
75
- <Plus className="h-3 w-3" />
76
- Add Tripwire
77
- </button>
78
- </>
79
- )}
80
- </div>
81
-
82
- {/* Zone list */}
83
- {zones.length === 0 ? (
84
- <p className="text-center text-[11px] text-foreground-subtle py-4">
85
- No zones yet. Click "Add Zone" or "Add Tripwire" to start.
86
- </p>
87
- ) : (
88
- <div className="flex flex-col gap-1">
89
- {zones.map((zone) => {
90
- const isSelected = zone.id === selectedZoneId
91
- return (
92
- <div
93
- key={zone.id}
94
- className={[
95
- 'flex items-center gap-2 rounded-md border px-2 py-1.5 cursor-pointer transition-colors',
96
- isSelected
97
- ? 'border-primary/50 bg-primary/10'
98
- : 'border-border bg-surface hover:bg-surface-hover',
99
- ].join(' ')}
100
- onClick={() => onSelectZone(isSelected ? null : zone.id)}
101
- >
102
- {/* Color swatch */}
103
- <div
104
- className="h-3 w-3 rounded-sm flex-shrink-0"
105
- style={{ backgroundColor: zone.color }}
106
- />
107
-
108
- {/* Name */}
109
- <span className="flex-1 truncate text-xs font-medium text-foreground">{zone.name}</span>
110
-
111
- {/* Type badge */}
112
- <span
113
- className={`inline-flex items-center rounded-full px-1.5 py-0.5 text-[9px] font-medium flex-shrink-0 ${TYPE_COLORS[zone.type]}`}
114
- >
115
- {TYPE_LABELS[zone.type]}
116
- </span>
117
-
118
- {/* Actions */}
119
- <div className="flex items-center gap-0.5 flex-shrink-0">
120
- <button
121
- type="button"
122
- title="Select"
123
- className="p-0.5 text-foreground-subtle hover:text-primary rounded transition-colors"
124
- onClick={(e) => {
125
- e.stopPropagation()
126
- onSelectZone(zone.id)
127
- }}
128
- >
129
- <Pencil className="h-3 w-3" />
130
- </button>
131
- <button
132
- type="button"
133
- title="Delete"
134
- className="p-0.5 text-foreground-subtle hover:text-danger rounded transition-colors"
135
- onClick={(e) => {
136
- e.stopPropagation()
137
- onDeleteZone(zone.id)
138
- }}
139
- >
140
- <Trash2 className="h-3 w-3" />
141
- </button>
142
- </div>
143
- </div>
144
- )
145
- })}
146
- </div>
147
- )}
148
- </div>
149
- )
150
- }