@camstack/addon-admin-ui 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/dist/assets/index-DjELGD4R.css +1 -0
  2. package/dist/assets/index-w55PwKyu.js +598 -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,97 +0,0 @@
1
- import { useState } from 'react'
2
- import { Settings, X, GripVertical } from 'lucide-react'
3
- import type { DashboardBlock } from '../../types/dashboard'
4
-
5
- interface BlockWrapperProps {
6
- block: DashboardBlock
7
- config: Record<string, unknown>
8
- size: { w: number; h: number }
9
- onRemove: () => void
10
- onConfigChange: (config: Record<string, unknown>) => void
11
- }
12
-
13
- export function BlockWrapper({ block, config, size, onRemove, onConfigChange }: BlockWrapperProps) {
14
- const [showConfig, setShowConfig] = useState(false)
15
- const Component = block.component
16
- const Icon = block.icon
17
-
18
- return (
19
- <div className="flex flex-col h-full rounded-lg border border-border bg-surface shadow-sm shadow-black/5 overflow-hidden hover:shadow-md hover:shadow-black/8 transition-shadow">
20
- {/* Title bar */}
21
- <div className="drag-handle flex items-center h-8 px-2 border-b border-border bg-surface/80 shrink-0 cursor-move group">
22
- <GripVertical className="h-3 w-3 text-foreground-subtle/30 group-hover:text-foreground-subtle transition-colors mr-1" />
23
- <Icon className="h-3 w-3 text-primary/70 mr-1.5 shrink-0" />
24
- <span className="text-[11px] font-semibold text-foreground/80 truncate flex-1">
25
- {block.name}
26
- </span>
27
- <div className="flex items-center gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity">
28
- {block.configSchema && block.configSchema.length > 0 && (
29
- <button
30
- onClick={(e) => { e.stopPropagation(); setShowConfig(!showConfig) }}
31
- className="p-1 rounded-md hover:bg-surface-hover text-foreground-subtle hover:text-foreground transition-colors"
32
- >
33
- <Settings className="h-3 w-3" />
34
- </button>
35
- )}
36
- <button
37
- onClick={(e) => { e.stopPropagation(); onRemove() }}
38
- className="p-1 rounded-md hover:bg-danger/10 text-foreground-subtle hover:text-danger transition-colors"
39
- >
40
- <X className="h-3 w-3" />
41
- </button>
42
- </div>
43
- </div>
44
-
45
- {/* Config panel */}
46
- {showConfig && block.configSchema && (
47
- <div className="p-2.5 border-b border-border bg-background/50 space-y-2">
48
- {block.configSchema.map((field) => (
49
- <label key={field.key} className="flex items-center gap-2 text-[11px]">
50
- <span className="text-foreground-subtle w-20 shrink-0">{field.label}</span>
51
- {field.type === 'number' && (
52
- <input
53
- type="number"
54
- value={(config[field.key] as number) ?? field.defaultValue ?? ''}
55
- onChange={(e) => onConfigChange({ ...config, [field.key]: Number(e.target.value) })}
56
- className="flex-1 rounded-md border border-border bg-surface px-2 py-1 text-foreground text-[11px] focus:border-primary outline-none"
57
- />
58
- )}
59
- {field.type === 'text' && (
60
- <input
61
- type="text"
62
- value={(config[field.key] as string) ?? field.defaultValue ?? ''}
63
- onChange={(e) => onConfigChange({ ...config, [field.key]: e.target.value })}
64
- className="flex-1 rounded-md border border-border bg-surface px-2 py-1 text-foreground text-[11px] focus:border-primary outline-none"
65
- />
66
- )}
67
- {field.type === 'boolean' && (
68
- <input
69
- type="checkbox"
70
- checked={(config[field.key] as boolean) ?? field.defaultValue ?? false}
71
- onChange={(e) => onConfigChange({ ...config, [field.key]: e.target.checked })}
72
- className="accent-primary"
73
- />
74
- )}
75
- {field.type === 'select' && (
76
- <select
77
- value={(config[field.key] as string) ?? field.defaultValue ?? ''}
78
- onChange={(e) => onConfigChange({ ...config, [field.key]: e.target.value })}
79
- className="flex-1 rounded-md border border-border bg-surface px-2 py-1 text-foreground text-[11px] focus:border-primary outline-none"
80
- >
81
- {field.options?.map((opt) => (
82
- <option key={opt.value} value={opt.value}>{opt.label}</option>
83
- ))}
84
- </select>
85
- )}
86
- </label>
87
- ))}
88
- </div>
89
- )}
90
-
91
- {/* Block content */}
92
- <div className="flex-1 overflow-auto p-3">
93
- <Component config={config} size={size} />
94
- </div>
95
- </div>
96
- )
97
- }
@@ -1,160 +0,0 @@
1
- import { useCallback, useState } from 'react'
2
- import { ResponsiveGridLayout } from 'react-grid-layout'
3
-
4
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
5
- const GridLayout = ResponsiveGridLayout as any
6
- import { Plus, LayoutDashboard } from 'lucide-react'
7
- import { BlockWrapper } from './BlockWrapper'
8
- import { BlockPicker } from './BlockPicker'
9
- import { getBlock } from './block-registry'
10
- import type { DashboardBlock, DashboardLayoutItem, DashboardState } from '../../types/dashboard'
11
- import 'react-grid-layout/css/styles.css'
12
-
13
- const STORAGE_KEY = 'camstack-dashboard-state'
14
- const BREAKPOINTS = { lg: 1200, md: 996, sm: 768, xs: 0 }
15
- const COLS = { lg: 12, md: 8, sm: 4, xs: 2 }
16
-
17
- function loadState(): DashboardState {
18
- try {
19
- const raw = localStorage.getItem(STORAGE_KEY)
20
- if (raw) return JSON.parse(raw)
21
- } catch { /* ignore */ }
22
- return { items: [] }
23
- }
24
-
25
- function saveState(state: DashboardState): void {
26
- localStorage.setItem(STORAGE_KEY, JSON.stringify(state))
27
- }
28
-
29
- let instanceCounter = 0
30
-
31
- export function DashboardGrid() {
32
- const [state, setState] = useState<DashboardState>(loadState)
33
- const [pickerOpen, setPickerOpen] = useState(false)
34
-
35
- const updateState = useCallback((next: DashboardState) => {
36
- setState(next)
37
- saveState(next)
38
- }, [])
39
-
40
- const handleAddBlock = useCallback((block: DashboardBlock) => {
41
- instanceCounter++
42
- const instanceId = `${block.id}-${instanceCounter}`
43
- const defaultConfig: Record<string, unknown> = {}
44
- block.configSchema?.forEach((f) => {
45
- if (f.defaultValue !== undefined) defaultConfig[f.key] = f.defaultValue
46
- })
47
-
48
- const newItem: DashboardLayoutItem = {
49
- i: instanceId,
50
- blockId: block.id,
51
- x: 0,
52
- y: Infinity, // place at bottom
53
- w: block.defaultSize.w,
54
- h: block.defaultSize.h,
55
- config: defaultConfig,
56
- }
57
-
58
- updateState({ items: [...state.items, newItem] })
59
- }, [state, updateState])
60
-
61
- const handleRemoveBlock = useCallback((instanceId: string) => {
62
- updateState({ items: state.items.filter((item) => item.i !== instanceId) })
63
- }, [state, updateState])
64
-
65
- const handleConfigChange = useCallback((instanceId: string, config: Record<string, unknown>) => {
66
- updateState({
67
- items: state.items.map((item) =>
68
- item.i === instanceId ? { ...item, config } : item,
69
- ),
70
- })
71
- }, [state, updateState])
72
-
73
- const handleLayoutChange = useCallback((layout: any) => {
74
- updateState({
75
- items: state.items.map((item) => {
76
- const layoutItem = layout.find((l: any) => l.i === item.i)
77
- if (!layoutItem) return item
78
- return { ...item, x: layoutItem.x, y: layoutItem.y, w: layoutItem.w, h: layoutItem.h }
79
- }),
80
- })
81
- }, [state, updateState])
82
-
83
- const gridLayout = state.items.map((item) => {
84
- const block = getBlock(item.blockId)
85
- return {
86
- i: item.i,
87
- x: item.x,
88
- y: item.y,
89
- w: item.w,
90
- h: item.h,
91
- minW: block?.minSize?.w ?? 2,
92
- minH: block?.minSize?.h ?? 2,
93
- }
94
- })
95
-
96
- return (
97
- <div className="p-4">
98
- <div className="flex items-center justify-between mb-4">
99
- <h1 className="text-lg font-semibold text-foreground">Dashboard</h1>
100
- <button
101
- onClick={() => setPickerOpen(true)}
102
- className="flex items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground"
103
- >
104
- <Plus className="h-3.5 w-3.5" />
105
- Add Block
106
- </button>
107
- </div>
108
-
109
- {state.items.length === 0 ? (
110
- <div className="flex flex-col items-center justify-center py-24 text-foreground-subtle">
111
- <div className="flex h-16 w-16 items-center justify-center rounded-2xl bg-surface border border-border mb-4">
112
- <LayoutDashboard className="h-8 w-8 text-foreground-subtle/40" />
113
- </div>
114
- <p className="text-sm font-medium text-foreground/70">Your dashboard is empty</p>
115
- <p className="text-xs text-foreground-subtle mt-1 mb-4">Add blocks to monitor your cameras and system</p>
116
- <button
117
- onClick={() => setPickerOpen(true)}
118
- className="flex items-center gap-1.5 rounded-lg bg-primary px-4 py-2 text-xs font-semibold text-primary-foreground shadow-md shadow-primary/20 hover:shadow-lg transition-all"
119
- >
120
- <Plus className="h-3.5 w-3.5" />
121
- Add your first block
122
- </button>
123
- </div>
124
- ) : (
125
- <GridLayout
126
- layouts={{ lg: gridLayout }}
127
- breakpoints={BREAKPOINTS}
128
- cols={COLS}
129
- rowHeight={60}
130
- draggableHandle=".drag-handle"
131
- onLayoutChange={handleLayoutChange}
132
- isResizable
133
- isDraggable
134
- >
135
- {state.items.map((item) => {
136
- const block = getBlock(item.blockId)
137
- if (!block) return <div key={item.i} />
138
- return (
139
- <div key={item.i}>
140
- <BlockWrapper
141
- block={block}
142
- config={item.config}
143
- size={{ w: item.w, h: item.h }}
144
- onRemove={() => handleRemoveBlock(item.i)}
145
- onConfigChange={(config) => handleConfigChange(item.i, config)}
146
- />
147
- </div>
148
- )
149
- })}
150
- </GridLayout>
151
- )}
152
-
153
- <BlockPicker
154
- open={pickerOpen}
155
- onClose={() => setPickerOpen(false)}
156
- onSelect={handleAddBlock}
157
- />
158
- </div>
159
- )
160
- }
@@ -1,15 +0,0 @@
1
- import type { DashboardBlock } from '../../types/dashboard'
2
-
3
- const registry = new Map<string, DashboardBlock>()
4
-
5
- export function registerBlock(block: DashboardBlock): void {
6
- registry.set(block.id, block)
7
- }
8
-
9
- export function getBlock(id: string): DashboardBlock | undefined {
10
- return registry.get(id)
11
- }
12
-
13
- export function getAllBlocks(): DashboardBlock[] {
14
- return Array.from(registry.values())
15
- }
@@ -1,39 +0,0 @@
1
- import { useQuery } from '@tanstack/react-query'
2
- import { useBackendClient } from '../../../hooks/useBackendClient'
3
- import type { BlockProps } from '../../../types/dashboard'
4
-
5
- const STAGE_ORDER = [
6
- 'class-filter', 'tracker', 'sub-detection', 'recognition',
7
- 'zone-analysis', 'event-generation', 'object-snapshot',
8
- ]
9
-
10
- export function PipelineStagesBlock({ config, size }: BlockProps) {
11
- const client = useBackendClient()
12
- const { data, isLoading, error } = useQuery({
13
- queryKey: ['pipelines'],
14
- queryFn: () => client.listPipelines(),
15
- refetchInterval: 5_000,
16
- })
17
-
18
- if (isLoading) return <div className="text-xs text-foreground-subtle animate-pulse">Loading...</div>
19
- if (error) return <div className="text-xs text-danger">Failed to load pipelines</div>
20
-
21
- const pipelines = (data ?? []) as Array<Record<string, unknown>>
22
-
23
- return (
24
- <div className="space-y-2 text-xs">
25
- <div className="flex justify-between text-foreground-subtle">
26
- <span>Active pipelines</span>
27
- <span className="font-medium text-foreground">{pipelines.length}</span>
28
- </div>
29
- <div className="space-y-0.5">
30
- {STAGE_ORDER.map((stage) => (
31
- <div key={stage} className="flex items-center gap-1.5">
32
- <div className="h-1.5 w-1.5 rounded-full bg-success shrink-0" />
33
- <span className="text-foreground-subtle">{stage}</span>
34
- </div>
35
- ))}
36
- </div>
37
- </div>
38
- )
39
- }
@@ -1,66 +0,0 @@
1
- import { useQuery } from '@tanstack/react-query'
2
- import { useBackendClient } from '../../../hooks/useBackendClient'
3
- import type { BlockProps } from '../../../types/dashboard'
4
-
5
- interface SystemInfo {
6
- version?: string
7
- uptime?: number
8
- nodeVersion?: string
9
- platform?: string
10
- [key: string]: unknown
11
- }
12
-
13
- function formatUptime(seconds: number): string {
14
- if (seconds < 60) return `${Math.floor(seconds)}s`
15
- if (seconds < 3600) return `${Math.floor(seconds / 60)}m`
16
- const h = Math.floor(seconds / 3600)
17
- const m = Math.floor((seconds % 3600) / 60)
18
- return `${h}h ${m}m`
19
- }
20
-
21
- export function StorageBlock({ config: _config, size: _size }: BlockProps) {
22
- const client = useBackendClient()
23
-
24
- const { data: systemInfo, isLoading, error } = useQuery<SystemInfo>({
25
- queryKey: ['system-info'],
26
- queryFn: async () => {
27
- const result = await client.getSystemInfo()
28
- return result as SystemInfo
29
- },
30
- refetchInterval: 30_000,
31
- })
32
-
33
- const { data: addons } = useQuery({
34
- queryKey: ['storage-addons'],
35
- queryFn: () => client.listAddons(),
36
- refetchInterval: 60_000,
37
- })
38
-
39
- if (isLoading) return <div className="text-xs text-foreground-subtle animate-pulse">Loading...</div>
40
- if (error) return <div className="text-xs text-danger">Failed to load system info</div>
41
-
42
- const addonList = (addons ?? []) as Array<{ manifest: { id: string } }>
43
-
44
- return (
45
- <div className="space-y-1.5 text-xs">
46
- <div className="flex justify-between">
47
- <span className="text-foreground-subtle">Platform</span>
48
- <span className="text-foreground font-medium">{systemInfo?.platform ?? 'N/A'}</span>
49
- </div>
50
- <div className="flex justify-between">
51
- <span className="text-foreground-subtle">Uptime</span>
52
- <span className="text-foreground font-medium">
53
- {systemInfo?.uptime != null ? formatUptime(systemInfo.uptime) : 'N/A'}
54
- </span>
55
- </div>
56
- <div className="flex justify-between">
57
- <span className="text-foreground-subtle">Node</span>
58
- <span className="text-foreground font-medium font-mono">{systemInfo?.nodeVersion ?? 'N/A'}</span>
59
- </div>
60
- <div className="flex justify-between">
61
- <span className="text-foreground-subtle">Addons</span>
62
- <span className="text-foreground font-medium">{addonList.length} installed</span>
63
- </div>
64
- </div>
65
- )
66
- }
@@ -1,67 +0,0 @@
1
- import { useQuery } from '@tanstack/react-query'
2
- import { CheckCircle, AlertTriangle, Loader2 } from 'lucide-react'
3
- import { useBackendClient } from '../../../hooks/useBackendClient'
4
- import type { BlockProps } from '../../../types/dashboard'
5
-
6
- export function SystemStatusBlock({ config, size }: BlockProps) {
7
- const client = useBackendClient()
8
- const { data, isLoading, error } = useQuery({
9
- queryKey: ['system-info'],
10
- queryFn: () => client.getSystemInfo(),
11
- refetchInterval: 10_000,
12
- })
13
-
14
- if (isLoading) {
15
- return (
16
- <div className="flex items-center gap-2 text-xs text-foreground-subtle">
17
- <Loader2 className="h-3.5 w-3.5 animate-spin" />
18
- <span>Connecting to server...</span>
19
- </div>
20
- )
21
- }
22
-
23
- if (error) {
24
- return (
25
- <div className="flex items-center gap-2 text-xs text-danger">
26
- <AlertTriangle className="h-3.5 w-3.5" />
27
- <span>Unable to reach server</span>
28
- </div>
29
- )
30
- }
31
-
32
- const info = data as Record<string, unknown> | undefined
33
-
34
- return (
35
- <div className="space-y-2 text-xs">
36
- <div className="flex items-center gap-1.5 mb-2">
37
- <CheckCircle className="h-3.5 w-3.5 text-success" />
38
- <span className="text-success font-medium">Online</span>
39
- </div>
40
- <Row label="Version" value={String(info?.version ?? 'N/A')} />
41
- <Row label="Uptime" value={formatUptime(info?.uptime as number)} />
42
- <Row label="Memory" value={`${formatMB(info?.memoryUsage as number)} MB`} />
43
- <Row label="Platform" value={String(info?.platform ?? 'N/A')} />
44
- </div>
45
- )
46
- }
47
-
48
- function Row({ label, value }: { label: string; value: string }) {
49
- return (
50
- <div className="flex justify-between items-center">
51
- <span className="text-foreground-subtle">{label}</span>
52
- <span className="text-foreground font-medium tabular-nums">{value}</span>
53
- </div>
54
- )
55
- }
56
-
57
- function formatUptime(seconds?: number): string {
58
- if (!seconds) return 'N/A'
59
- const h = Math.floor(seconds / 3600)
60
- const m = Math.floor((seconds % 3600) / 60)
61
- return h > 0 ? `${h}h ${m}m` : `${m}m`
62
- }
63
-
64
- function formatMB(bytes?: number): string {
65
- if (!bytes) return 'N/A'
66
- return (bytes / 1024 / 1024).toFixed(0)
67
- }
@@ -1,32 +0,0 @@
1
- import { Monitor, Workflow, HardDrive } from 'lucide-react'
2
- import { registerBlock } from '../block-registry'
3
- import { SystemStatusBlock } from './SystemStatusBlock'
4
- import { PipelineStagesBlock } from './PipelineStagesBlock'
5
- import { StorageBlock } from './StorageBlock'
6
-
7
- registerBlock({
8
- id: 'system-status',
9
- name: 'System Status',
10
- icon: Monitor,
11
- defaultSize: { w: 4, h: 2 },
12
- minSize: { w: 2, h: 2 },
13
- component: SystemStatusBlock,
14
- })
15
-
16
- registerBlock({
17
- id: 'pipeline-stages',
18
- name: 'Pipeline Stages',
19
- icon: Workflow,
20
- defaultSize: { w: 4, h: 3 },
21
- minSize: { w: 3, h: 2 },
22
- component: PipelineStagesBlock,
23
- })
24
-
25
- registerBlock({
26
- id: 'storage',
27
- name: 'Storage',
28
- icon: HardDrive,
29
- defaultSize: { w: 4, h: 2 },
30
- minSize: { w: 2, h: 2 },
31
- component: StorageBlock,
32
- })
@@ -1,116 +0,0 @@
1
- import { useState } from 'react'
2
- import { Link } from 'react-router-dom'
3
- import { ChevronRight, Video, ScrollText, Terminal, Bell, Zap } from 'lucide-react'
4
- import { StatusBadge } from '../shared/StatusBadge'
5
- import { ProviderIcon, getProviderLabel } from '../shared/ProviderIcon'
6
- import { CapabilityBadges } from '../shared/CapabilityBadges'
7
- import { QuickConfigWizard } from './QuickConfigWizard'
8
- import type { PanelId } from './FloatingPanelManager'
9
-
10
- interface DeviceHeaderProps {
11
- deviceId: string
12
- name: string
13
- status: string
14
- provider: string
15
- capabilities: string[]
16
- activePanel: PanelId | null
17
- onTogglePanel: (panel: PanelId) => void
18
- }
19
-
20
- const PANEL_BUTTONS: Array<{
21
- id: PanelId
22
- icon: React.ComponentType<{ className?: string }>
23
- label: string
24
- }> = [
25
- { id: 'stream', icon: Video, label: 'Stream' },
26
- { id: 'logs', icon: ScrollText, label: 'Logs' },
27
- { id: 'repl', icon: Terminal, label: 'REPL' },
28
- { id: 'events', icon: Bell, label: 'Events' },
29
- ]
30
-
31
- export function DeviceHeader({
32
- deviceId,
33
- name,
34
- status,
35
- provider,
36
- capabilities,
37
- activePanel,
38
- onTogglePanel,
39
- }: DeviceHeaderProps) {
40
- const [wizardOpen, setWizardOpen] = useState(false)
41
-
42
- return (
43
- <>
44
- <div className="border-b border-border bg-surface px-6 py-4">
45
- {/* Breadcrumb */}
46
- <nav className="flex items-center gap-1 text-[11px] text-foreground-subtle mb-3">
47
- <Link to="/integrations" className="hover:text-foreground transition-colors">
48
- Integrations
49
- </Link>
50
- <ChevronRight className="h-3 w-3" />
51
- <span className="text-foreground-subtle">{getProviderLabel(provider)}</span>
52
- <ChevronRight className="h-3 w-3" />
53
- <span className="text-foreground">{name}</span>
54
- </nav>
55
-
56
- {/* Header row */}
57
- <div className="flex items-center justify-between gap-4">
58
- <div className="flex items-center gap-3 min-w-0">
59
- <ProviderIcon type={provider} size="lg" />
60
- <div className="min-w-0">
61
- <div className="flex items-center gap-2">
62
- <h1 className="text-base font-semibold text-foreground truncate">{name}</h1>
63
- <StatusBadge status={status} />
64
- </div>
65
- <div className="mt-1">
66
- <CapabilityBadges capabilities={capabilities} />
67
- </div>
68
- </div>
69
- </div>
70
-
71
- {/* Action buttons */}
72
- <div className="flex items-center gap-1 flex-shrink-0">
73
- {/* Quick Config wizard button */}
74
- <button
75
- onClick={() => setWizardOpen(true)}
76
- title="Quick Config"
77
- className="inline-flex items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-[11px] font-medium border border-border text-foreground-subtle hover:text-foreground hover:bg-surface-hover transition-colors"
78
- >
79
- <Zap className="h-3.5 w-3.5" />
80
- Quick Config
81
- </button>
82
-
83
- {/* Divider */}
84
- <div className="h-5 w-px bg-border mx-1" />
85
-
86
- {/* Floating panel toggle buttons */}
87
- {PANEL_BUTTONS.map(({ id, icon: Icon, label }) => {
88
- const isActive = activePanel === id
89
- return (
90
- <button
91
- key={id}
92
- onClick={() => onTogglePanel(id)}
93
- title={label}
94
- className={`inline-flex items-center justify-center rounded-lg p-2 transition-colors ${
95
- isActive
96
- ? 'bg-primary/15 text-primary'
97
- : 'text-foreground-subtle hover:text-foreground hover:bg-surface-hover'
98
- }`}
99
- >
100
- <Icon className="h-4 w-4" />
101
- </button>
102
- )
103
- })}
104
- </div>
105
- </div>
106
- </div>
107
-
108
- {/* Quick Config Wizard (modal) */}
109
- <QuickConfigWizard
110
- open={wizardOpen}
111
- onClose={() => setWizardOpen(false)}
112
- deviceId={deviceId}
113
- />
114
- </>
115
- )
116
- }