@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.
- 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 +4 -1
- package/src/App.tsx +0 -71
- package/src/components/addons/AddonCard.tsx +0 -355
- package/src/components/addons/AddonUploadZone.tsx +0 -69
- package/src/components/addons/CapabilityBadge.tsx +0 -55
- package/src/components/addons/CapabilityMap.tsx +0 -133
- package/src/components/addons/UpdatesList.tsx +0 -108
- 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 -172
- 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 -105
- package/src/components/metrics/IntegrationUsage.tsx +0 -73
- package/src/components/metrics/PipelineStatus.tsx +0 -74
- package/src/components/metrics/ProcessResources.tsx +0 -123
- 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 -254
- 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 -222
- package/src/pages/Integrations.tsx +0 -333
- 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 -396
- 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 -28
- 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,133 +0,0 @@
|
|
|
1
|
-
import { CheckCircle } from 'lucide-react'
|
|
2
|
-
|
|
3
|
-
// ---------------------------------------------------------------------------
|
|
4
|
-
// Types
|
|
5
|
-
// ---------------------------------------------------------------------------
|
|
6
|
-
|
|
7
|
-
export type CapabilityMode = 'singleton' | 'collection'
|
|
8
|
-
|
|
9
|
-
export interface CapabilityProvider {
|
|
10
|
-
addonId: string
|
|
11
|
-
addonName: string
|
|
12
|
-
active: boolean
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface CapabilityEntry {
|
|
16
|
-
name: string
|
|
17
|
-
mode: CapabilityMode
|
|
18
|
-
providers: CapabilityProvider[]
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// ---------------------------------------------------------------------------
|
|
22
|
-
// Color helpers
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
|
|
25
|
-
const CAPABILITY_BORDER_COLORS: Record<string, string> = {
|
|
26
|
-
detection: 'border-green-500',
|
|
27
|
-
detector: 'border-green-500',
|
|
28
|
-
streaming: 'border-blue-500',
|
|
29
|
-
decode: 'border-blue-500',
|
|
30
|
-
decoder: 'border-blue-500',
|
|
31
|
-
recording: 'border-orange-500',
|
|
32
|
-
recorder: 'border-orange-500',
|
|
33
|
-
transcoding: 'border-purple-500',
|
|
34
|
-
transcoder: 'border-purple-500',
|
|
35
|
-
restream: 'border-cyan-500',
|
|
36
|
-
restreamer: 'border-cyan-500',
|
|
37
|
-
storage: 'border-yellow-500',
|
|
38
|
-
notification: 'border-pink-500',
|
|
39
|
-
notifier: 'border-pink-500',
|
|
40
|
-
faces: 'border-indigo-500',
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const CAPABILITY_BG_COLORS: Record<string, string> = {
|
|
44
|
-
detection: 'bg-green-500/5',
|
|
45
|
-
detector: 'bg-green-500/5',
|
|
46
|
-
streaming: 'bg-blue-500/5',
|
|
47
|
-
decode: 'bg-blue-500/5',
|
|
48
|
-
decoder: 'bg-blue-500/5',
|
|
49
|
-
recording: 'bg-orange-500/5',
|
|
50
|
-
recorder: 'bg-orange-500/5',
|
|
51
|
-
transcoding: 'bg-purple-500/5',
|
|
52
|
-
transcoder: 'bg-purple-500/5',
|
|
53
|
-
restream: 'bg-cyan-500/5',
|
|
54
|
-
restreamer: 'bg-cyan-500/5',
|
|
55
|
-
storage: 'bg-yellow-500/5',
|
|
56
|
-
notification: 'bg-pink-500/5',
|
|
57
|
-
notifier: 'bg-pink-500/5',
|
|
58
|
-
faces: 'bg-indigo-500/5',
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// ---------------------------------------------------------------------------
|
|
62
|
-
// CapabilityCard
|
|
63
|
-
// ---------------------------------------------------------------------------
|
|
64
|
-
|
|
65
|
-
function CapabilityCard({ entry }: { entry: CapabilityEntry }) {
|
|
66
|
-
const lower = entry.name.toLowerCase()
|
|
67
|
-
const borderColor = CAPABILITY_BORDER_COLORS[lower] ?? 'border-primary'
|
|
68
|
-
const bgColor = CAPABILITY_BG_COLORS[lower] ?? 'bg-primary/5'
|
|
69
|
-
const activeProvider = entry.providers.find((p) => p.active)
|
|
70
|
-
|
|
71
|
-
return (
|
|
72
|
-
<div className={`rounded-lg border-t-2 border border-border ${borderColor} ${bgColor} p-3 space-y-2`}>
|
|
73
|
-
<div className="flex items-center justify-between gap-2">
|
|
74
|
-
<span className="text-xs font-semibold text-foreground capitalize">{entry.name}</span>
|
|
75
|
-
<span className={`rounded-full px-2 py-0.5 text-[10px] font-medium ${entry.mode === 'singleton' ? 'bg-primary/10 text-primary' : 'bg-info/10 text-info'}`}>
|
|
76
|
-
{entry.mode}
|
|
77
|
-
</span>
|
|
78
|
-
</div>
|
|
79
|
-
|
|
80
|
-
{entry.mode === 'singleton' ? (
|
|
81
|
-
<div>
|
|
82
|
-
{activeProvider ? (
|
|
83
|
-
<div className="flex items-center gap-1.5 text-[10px] text-foreground">
|
|
84
|
-
<CheckCircle className="h-3 w-3 text-green-400 shrink-0" />
|
|
85
|
-
<span className="font-medium">{activeProvider.addonName}</span>
|
|
86
|
-
<span className="rounded-full bg-green-500/10 text-green-400 px-1.5 py-0.5 font-medium ml-auto">ACTIVE</span>
|
|
87
|
-
</div>
|
|
88
|
-
) : (
|
|
89
|
-
<div className="text-[10px] text-foreground-subtle italic">No active provider</div>
|
|
90
|
-
)}
|
|
91
|
-
</div>
|
|
92
|
-
) : (
|
|
93
|
-
<div className="space-y-1">
|
|
94
|
-
{entry.providers.length === 0 && (
|
|
95
|
-
<div className="text-[10px] text-foreground-subtle italic">No providers</div>
|
|
96
|
-
)}
|
|
97
|
-
{entry.providers.map((p) => (
|
|
98
|
-
<div key={p.addonId} className="flex items-center justify-between gap-2 text-[10px]">
|
|
99
|
-
<span className="text-foreground-subtle">{p.addonName}</span>
|
|
100
|
-
{p.active && (
|
|
101
|
-
<span className="rounded-full bg-green-500/10 text-green-400 px-1.5 py-0.5 font-medium">ACTIVE</span>
|
|
102
|
-
)}
|
|
103
|
-
</div>
|
|
104
|
-
))}
|
|
105
|
-
</div>
|
|
106
|
-
)}
|
|
107
|
-
</div>
|
|
108
|
-
)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// ---------------------------------------------------------------------------
|
|
112
|
-
// CapabilityMap
|
|
113
|
-
// ---------------------------------------------------------------------------
|
|
114
|
-
|
|
115
|
-
interface CapabilityMapProps {
|
|
116
|
-
capabilities: CapabilityEntry[]
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export function CapabilityMap({ capabilities }: CapabilityMapProps) {
|
|
120
|
-
if (capabilities.length === 0) {
|
|
121
|
-
return (
|
|
122
|
-
<div className="text-xs text-foreground-subtle text-center py-8">No capabilities registered</div>
|
|
123
|
-
)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return (
|
|
127
|
-
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3">
|
|
128
|
-
{capabilities.map((cap) => (
|
|
129
|
-
<CapabilityCard key={cap.name} entry={cap} />
|
|
130
|
-
))}
|
|
131
|
-
</div>
|
|
132
|
-
)
|
|
133
|
-
}
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { ArrowRight, RotateCcw, Zap } from 'lucide-react'
|
|
2
|
-
|
|
3
|
-
// ---------------------------------------------------------------------------
|
|
4
|
-
// Types — inferred from tRPC update.listUpdates return type
|
|
5
|
-
// ---------------------------------------------------------------------------
|
|
6
|
-
|
|
7
|
-
import type { BackendClient } from '@camstack/sdk'
|
|
8
|
-
|
|
9
|
-
export type UpdateEntry = Awaited<ReturnType<BackendClient['trpc']['update']['listUpdates']['query']>>[number]
|
|
10
|
-
|
|
11
|
-
// ---------------------------------------------------------------------------
|
|
12
|
-
// UpdatesList
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
|
-
|
|
15
|
-
interface UpdatesListProps {
|
|
16
|
-
updates: readonly UpdateEntry[]
|
|
17
|
-
onUpdate: (name: string) => void
|
|
18
|
-
onUpdateAll: () => void
|
|
19
|
-
onCheckUpdates: () => void
|
|
20
|
-
isChecking: boolean
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function UpdatesList({ updates, onUpdate, onUpdateAll, onCheckUpdates, isChecking }: UpdatesListProps) {
|
|
24
|
-
return (
|
|
25
|
-
<div className="space-y-4">
|
|
26
|
-
{/* Toolbar */}
|
|
27
|
-
<div className="flex items-center justify-between gap-3">
|
|
28
|
-
<button
|
|
29
|
-
type="button"
|
|
30
|
-
onClick={onCheckUpdates}
|
|
31
|
-
disabled={isChecking}
|
|
32
|
-
className="flex items-center gap-1.5 rounded-md border border-border bg-background px-3 py-1.5 text-xs text-foreground hover:bg-surface transition-colors disabled:opacity-50"
|
|
33
|
-
>
|
|
34
|
-
<RotateCcw className={`h-3.5 w-3.5 ${isChecking ? 'animate-spin' : ''}`} />
|
|
35
|
-
{isChecking ? 'Checking…' : 'Check for updates'}
|
|
36
|
-
</button>
|
|
37
|
-
|
|
38
|
-
{updates.length > 0 && (
|
|
39
|
-
<button
|
|
40
|
-
type="button"
|
|
41
|
-
onClick={onUpdateAll}
|
|
42
|
-
className="flex items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground hover:bg-primary/90 transition-colors"
|
|
43
|
-
>
|
|
44
|
-
Update all ({updates.length})
|
|
45
|
-
</button>
|
|
46
|
-
)}
|
|
47
|
-
</div>
|
|
48
|
-
|
|
49
|
-
{updates.length === 0 && !isChecking && (
|
|
50
|
-
<div className="rounded-lg border border-border bg-surface py-10 text-center text-xs text-foreground-subtle">
|
|
51
|
-
All packages are up to date
|
|
52
|
-
</div>
|
|
53
|
-
)}
|
|
54
|
-
|
|
55
|
-
{updates.length > 0 && (
|
|
56
|
-
<div className="space-y-2">
|
|
57
|
-
{updates.map((update) => (
|
|
58
|
-
<div
|
|
59
|
-
key={update.name}
|
|
60
|
-
className="rounded-lg border border-border bg-surface px-4 py-3 flex items-start justify-between gap-4"
|
|
61
|
-
>
|
|
62
|
-
<div className="flex items-start gap-3 min-w-0">
|
|
63
|
-
{/* Icon letter */}
|
|
64
|
-
<div className="flex-shrink-0 h-8 w-8 rounded-lg bg-primary/10 flex items-center justify-center">
|
|
65
|
-
<span className="text-xs font-bold text-primary uppercase">
|
|
66
|
-
{update.name.charAt(0)}
|
|
67
|
-
</span>
|
|
68
|
-
</div>
|
|
69
|
-
|
|
70
|
-
<div className="min-w-0">
|
|
71
|
-
<div className="flex items-center gap-2 flex-wrap">
|
|
72
|
-
<span className="text-sm font-semibold text-foreground">{update.name}</span>
|
|
73
|
-
{update.requiresRestart ? (
|
|
74
|
-
<span className="inline-flex items-center gap-1 rounded-full bg-warning/10 text-warning px-2 py-0.5 text-[10px] font-medium">
|
|
75
|
-
<RotateCcw className="h-2.5 w-2.5" />
|
|
76
|
-
Restart
|
|
77
|
-
</span>
|
|
78
|
-
) : (
|
|
79
|
-
<span className="inline-flex items-center gap-1 rounded-full bg-green-500/10 text-green-400 px-2 py-0.5 text-[10px] font-medium">
|
|
80
|
-
<Zap className="h-2.5 w-2.5" />
|
|
81
|
-
Hot-swap
|
|
82
|
-
</span>
|
|
83
|
-
)}
|
|
84
|
-
</div>
|
|
85
|
-
|
|
86
|
-
{/* Version diff */}
|
|
87
|
-
<div className="flex items-center gap-1.5 mt-1 text-[10px]">
|
|
88
|
-
<span className="text-foreground-subtle font-mono">v{update.currentVersion}</span>
|
|
89
|
-
<ArrowRight className="h-3 w-3 text-foreground-subtle" />
|
|
90
|
-
<span className="text-green-400 font-mono font-medium">v{update.latestVersion}</span>
|
|
91
|
-
</div>
|
|
92
|
-
</div>
|
|
93
|
-
</div>
|
|
94
|
-
|
|
95
|
-
<button
|
|
96
|
-
type="button"
|
|
97
|
-
onClick={() => onUpdate(update.name)}
|
|
98
|
-
className="flex-shrink-0 rounded-md bg-primary/10 text-primary hover:bg-primary/20 px-3 py-1.5 text-xs font-medium transition-colors"
|
|
99
|
-
>
|
|
100
|
-
Update
|
|
101
|
-
</button>
|
|
102
|
-
</div>
|
|
103
|
-
))}
|
|
104
|
-
</div>
|
|
105
|
-
)}
|
|
106
|
-
</div>
|
|
107
|
-
)
|
|
108
|
-
}
|
|
@@ -1,281 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react'
|
|
2
|
-
import {
|
|
3
|
-
ChevronDown,
|
|
4
|
-
ChevronUp,
|
|
5
|
-
Cpu,
|
|
6
|
-
HardDrive,
|
|
7
|
-
Monitor,
|
|
8
|
-
Layers,
|
|
9
|
-
Activity,
|
|
10
|
-
Server,
|
|
11
|
-
} from 'lucide-react'
|
|
12
|
-
import { TaskList } from './TaskList'
|
|
13
|
-
import { ProcessList } from './ProcessList'
|
|
14
|
-
import type { AgentTask } from './TaskList'
|
|
15
|
-
import type { ProcessEntry } from './ProcessList'
|
|
16
|
-
|
|
17
|
-
// ---------------------------------------------------------------------------
|
|
18
|
-
// Types
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
|
|
21
|
-
export interface AgentRegistrationInfo {
|
|
22
|
-
id: string
|
|
23
|
-
name: string
|
|
24
|
-
capabilities: string[]
|
|
25
|
-
host: string
|
|
26
|
-
port: number
|
|
27
|
-
platform: string
|
|
28
|
-
arch: string
|
|
29
|
-
cpuModel?: string
|
|
30
|
-
cpuCores: number
|
|
31
|
-
memoryMB: number
|
|
32
|
-
gpuModel?: string
|
|
33
|
-
pythonRuntimes: string[]
|
|
34
|
-
httpPort: number
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface AgentRuntimeStatus {
|
|
38
|
-
activeCameras: number
|
|
39
|
-
cpuPercent: number
|
|
40
|
-
memoryPercent: number
|
|
41
|
-
fps: Record<string, number>
|
|
42
|
-
errors: string[]
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface ConnectedAgent {
|
|
46
|
-
info: AgentRegistrationInfo
|
|
47
|
-
status?: AgentRuntimeStatus
|
|
48
|
-
connectedSince: number
|
|
49
|
-
isHub?: boolean
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// ---------------------------------------------------------------------------
|
|
53
|
-
// Sub-components
|
|
54
|
-
// ---------------------------------------------------------------------------
|
|
55
|
-
|
|
56
|
-
function StatusDot({ online }: { online: boolean }) {
|
|
57
|
-
return (
|
|
58
|
-
<span
|
|
59
|
-
className={`inline-block h-2.5 w-2.5 rounded-full shrink-0 ${online ? 'bg-green-400' : 'bg-red-400'}`}
|
|
60
|
-
title={online ? 'Online' : 'Offline'}
|
|
61
|
-
/>
|
|
62
|
-
)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function Bar({ percent, color = 'bg-primary' }: { percent: number; color?: string }) {
|
|
66
|
-
return (
|
|
67
|
-
<div className="h-1.5 w-full rounded-full bg-border overflow-hidden">
|
|
68
|
-
<div
|
|
69
|
-
className={`h-full rounded-full transition-all ${color}`}
|
|
70
|
-
style={{ width: `${Math.min(100, percent)}%` }}
|
|
71
|
-
/>
|
|
72
|
-
</div>
|
|
73
|
-
)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const CAPABILITY_COLORS: Record<string, string> = {
|
|
77
|
-
decode: 'bg-blue-500/10 text-blue-400',
|
|
78
|
-
decoder: 'bg-blue-500/10 text-blue-400',
|
|
79
|
-
detect: 'bg-green-500/10 text-green-400',
|
|
80
|
-
detector: 'bg-green-500/10 text-green-400',
|
|
81
|
-
record: 'bg-orange-500/10 text-orange-400',
|
|
82
|
-
recorder: 'bg-orange-500/10 text-orange-400',
|
|
83
|
-
transcode: 'bg-purple-500/10 text-purple-400',
|
|
84
|
-
transcoder: 'bg-purple-500/10 text-purple-400',
|
|
85
|
-
restream: 'bg-cyan-500/10 text-cyan-400',
|
|
86
|
-
restreamer: 'bg-cyan-500/10 text-cyan-400',
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const RUNTIME_COLORS: Record<string, string> = {
|
|
90
|
-
onnx: 'bg-yellow-500/10 text-yellow-400',
|
|
91
|
-
coreml: 'bg-pink-500/10 text-pink-400',
|
|
92
|
-
pytorch: 'bg-orange-500/10 text-orange-400',
|
|
93
|
-
openvino:'bg-blue-500/10 text-blue-400',
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function CapBadge({ cap }: { cap: string }) {
|
|
97
|
-
const lower = cap.toLowerCase()
|
|
98
|
-
const color = CAPABILITY_COLORS[lower] ?? 'bg-primary/10 text-primary'
|
|
99
|
-
return (
|
|
100
|
-
<span className={`inline-flex rounded-md px-1.5 py-0.5 text-[10px] font-medium ${color}`}>
|
|
101
|
-
{cap}
|
|
102
|
-
</span>
|
|
103
|
-
)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function RuntimeBadge({ runtime }: { runtime: string }) {
|
|
107
|
-
const lower = runtime.toLowerCase()
|
|
108
|
-
const color = RUNTIME_COLORS[lower] ?? 'bg-foreground-subtle/10 text-foreground-subtle'
|
|
109
|
-
return (
|
|
110
|
-
<span className={`inline-flex rounded-md px-1.5 py-0.5 text-[10px] font-medium ${color}`}>
|
|
111
|
-
{runtime}
|
|
112
|
-
</span>
|
|
113
|
-
)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function formatUptime(connectedSince: number): string {
|
|
117
|
-
const seconds = Math.floor((Date.now() - connectedSince) / 1000)
|
|
118
|
-
if (seconds < 60) return `${seconds}s`
|
|
119
|
-
if (seconds < 3600) return `${Math.floor(seconds / 60)}m`
|
|
120
|
-
return `${Math.floor(seconds / 3600)}h`
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// ---------------------------------------------------------------------------
|
|
124
|
-
// AgentCard
|
|
125
|
-
// ---------------------------------------------------------------------------
|
|
126
|
-
|
|
127
|
-
interface AgentCardProps {
|
|
128
|
-
agent: ConnectedAgent
|
|
129
|
-
processes: ProcessEntry[]
|
|
130
|
-
tasks: AgentTask[]
|
|
131
|
-
defaultExpanded?: boolean
|
|
132
|
-
online?: boolean
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export function AgentCard({ agent, processes, tasks, defaultExpanded = false, online = true }: AgentCardProps) {
|
|
136
|
-
const [expanded, setExpanded] = useState(defaultExpanded)
|
|
137
|
-
const { info, status } = agent
|
|
138
|
-
|
|
139
|
-
return (
|
|
140
|
-
<div className={`rounded-xl border border-border bg-surface overflow-hidden transition-opacity ${!online ? 'opacity-50' : ''}`}>
|
|
141
|
-
{/* Header */}
|
|
142
|
-
<button
|
|
143
|
-
type="button"
|
|
144
|
-
className="w-full flex items-start justify-between gap-3 px-4 py-3 hover:bg-primary/5 transition-colors text-left"
|
|
145
|
-
onClick={() => setExpanded((e) => !e)}
|
|
146
|
-
>
|
|
147
|
-
<div className="flex items-center gap-2.5 min-w-0">
|
|
148
|
-
<StatusDot online={online} />
|
|
149
|
-
<Server className="h-4 w-4 shrink-0 text-foreground-subtle" />
|
|
150
|
-
<div className="min-w-0">
|
|
151
|
-
<div className="flex items-center gap-2 flex-wrap">
|
|
152
|
-
<span className="text-sm font-semibold text-foreground truncate">{info.name}</span>
|
|
153
|
-
{agent.isHub && (
|
|
154
|
-
<span className="rounded-full bg-primary/10 text-primary px-2 py-0.5 text-[10px] font-medium shrink-0">HUB</span>
|
|
155
|
-
)}
|
|
156
|
-
{!online && (
|
|
157
|
-
<span className="rounded-full bg-foreground-subtle/10 text-foreground-subtle px-2 py-0.5 text-[10px] shrink-0">Offline</span>
|
|
158
|
-
)}
|
|
159
|
-
</div>
|
|
160
|
-
<div className="flex items-center gap-2 mt-0.5 text-[10px] text-foreground-subtle flex-wrap">
|
|
161
|
-
<span className="font-mono">{info.host}:{info.port}</span>
|
|
162
|
-
<span>{info.platform}/{info.arch}</span>
|
|
163
|
-
{status && (
|
|
164
|
-
<>
|
|
165
|
-
<span>CPU {status.cpuPercent.toFixed(0)}%</span>
|
|
166
|
-
<span>MEM {status.memoryPercent.toFixed(0)}%</span>
|
|
167
|
-
</>
|
|
168
|
-
)}
|
|
169
|
-
</div>
|
|
170
|
-
</div>
|
|
171
|
-
</div>
|
|
172
|
-
|
|
173
|
-
<div className="flex items-center gap-2 shrink-0">
|
|
174
|
-
{/* task count badge */}
|
|
175
|
-
{tasks.length > 0 && (
|
|
176
|
-
<span className="rounded-full bg-info/10 text-info px-2 py-0.5 text-[10px] font-medium">
|
|
177
|
-
{tasks.length} task{tasks.length !== 1 ? 's' : ''}
|
|
178
|
-
</span>
|
|
179
|
-
)}
|
|
180
|
-
{/* runtime badges */}
|
|
181
|
-
{info.pythonRuntimes.slice(0, 2).map((rt) => (
|
|
182
|
-
<RuntimeBadge key={rt} runtime={rt} />
|
|
183
|
-
))}
|
|
184
|
-
<span className="text-[10px] text-foreground-subtle">up {formatUptime(agent.connectedSince)}</span>
|
|
185
|
-
{expanded ? <ChevronUp className="h-4 w-4 text-foreground-subtle" /> : <ChevronDown className="h-4 w-4 text-foreground-subtle" />}
|
|
186
|
-
</div>
|
|
187
|
-
</button>
|
|
188
|
-
|
|
189
|
-
{/* Load bars (always visible when online) */}
|
|
190
|
-
{status && !expanded && (
|
|
191
|
-
<div className="px-4 pb-3 space-y-1">
|
|
192
|
-
<Bar percent={status.cpuPercent} color="bg-blue-400" />
|
|
193
|
-
<Bar percent={status.memoryPercent} color="bg-purple-400" />
|
|
194
|
-
</div>
|
|
195
|
-
)}
|
|
196
|
-
|
|
197
|
-
{/* Expanded content */}
|
|
198
|
-
{expanded && (
|
|
199
|
-
<div className="border-t border-border divide-y divide-border">
|
|
200
|
-
{/* Hardware details */}
|
|
201
|
-
<div className="px-4 py-3 grid grid-cols-2 gap-x-4 gap-y-1.5 text-[10px] text-foreground-subtle">
|
|
202
|
-
<div className="flex items-center gap-1.5">
|
|
203
|
-
<Cpu className="h-3 w-3 shrink-0" />
|
|
204
|
-
<span className="truncate">{info.cpuModel ?? `${info.cpuCores} cores`}</span>
|
|
205
|
-
</div>
|
|
206
|
-
<div className="flex items-center gap-1.5">
|
|
207
|
-
<HardDrive className="h-3 w-3 shrink-0" />
|
|
208
|
-
<span>{Math.round(info.memoryMB / 1024)} GB RAM</span>
|
|
209
|
-
</div>
|
|
210
|
-
<div className="flex items-center gap-1.5">
|
|
211
|
-
<Monitor className="h-3 w-3 shrink-0" />
|
|
212
|
-
<span>{info.platform}/{info.arch}</span>
|
|
213
|
-
</div>
|
|
214
|
-
{info.gpuModel && (
|
|
215
|
-
<div className="flex items-center gap-1.5">
|
|
216
|
-
<Layers className="h-3 w-3 shrink-0" />
|
|
217
|
-
<span className="truncate">{info.gpuModel}</span>
|
|
218
|
-
</div>
|
|
219
|
-
)}
|
|
220
|
-
</div>
|
|
221
|
-
|
|
222
|
-
{/* Load bars */}
|
|
223
|
-
{status && (
|
|
224
|
-
<div className="px-4 py-3 space-y-2">
|
|
225
|
-
<div className="flex items-center justify-between text-[10px] text-foreground-subtle mb-1">
|
|
226
|
-
<span className="flex items-center gap-1">
|
|
227
|
-
<Activity className="h-3 w-3" />
|
|
228
|
-
{status.activeCameras} active camera{status.activeCameras !== 1 ? 's' : ''}
|
|
229
|
-
</span>
|
|
230
|
-
<span>CPU {status.cpuPercent.toFixed(0)}% / MEM {status.memoryPercent.toFixed(0)}%</span>
|
|
231
|
-
</div>
|
|
232
|
-
<Bar percent={status.cpuPercent} color="bg-blue-400" />
|
|
233
|
-
<Bar percent={status.memoryPercent} color="bg-purple-400" />
|
|
234
|
-
</div>
|
|
235
|
-
)}
|
|
236
|
-
|
|
237
|
-
{/* Capabilities */}
|
|
238
|
-
<div className="px-4 py-3 space-y-2">
|
|
239
|
-
<div className="text-[10px] font-medium text-foreground-subtle uppercase tracking-wide">Capabilities</div>
|
|
240
|
-
<div className="flex flex-wrap gap-1.5">
|
|
241
|
-
{info.capabilities.map((cap) => (
|
|
242
|
-
<CapBadge key={cap} cap={cap} />
|
|
243
|
-
))}
|
|
244
|
-
{info.capabilities.length === 0 && (
|
|
245
|
-
<span className="text-[10px] text-foreground-subtle italic">None registered</span>
|
|
246
|
-
)}
|
|
247
|
-
</div>
|
|
248
|
-
</div>
|
|
249
|
-
|
|
250
|
-
{/* Runtimes */}
|
|
251
|
-
{info.pythonRuntimes.length > 0 && (
|
|
252
|
-
<div className="px-4 py-3 space-y-2">
|
|
253
|
-
<div className="text-[10px] font-medium text-foreground-subtle uppercase tracking-wide">Runtimes</div>
|
|
254
|
-
<div className="flex flex-wrap gap-1.5">
|
|
255
|
-
{info.pythonRuntimes.map((rt) => (
|
|
256
|
-
<RuntimeBadge key={rt} runtime={rt} />
|
|
257
|
-
))}
|
|
258
|
-
</div>
|
|
259
|
-
</div>
|
|
260
|
-
)}
|
|
261
|
-
|
|
262
|
-
{/* Active Tasks */}
|
|
263
|
-
<div className="px-4 py-3 space-y-2">
|
|
264
|
-
<div className="text-[10px] font-medium text-foreground-subtle uppercase tracking-wide">
|
|
265
|
-
Active Tasks ({tasks.length})
|
|
266
|
-
</div>
|
|
267
|
-
<TaskList tasks={tasks} />
|
|
268
|
-
</div>
|
|
269
|
-
|
|
270
|
-
{/* Processes */}
|
|
271
|
-
<div className="px-4 py-3 space-y-2">
|
|
272
|
-
<div className="text-[10px] font-medium text-foreground-subtle uppercase tracking-wide">
|
|
273
|
-
Processes ({processes.length})
|
|
274
|
-
</div>
|
|
275
|
-
<ProcessList processes={processes} />
|
|
276
|
-
</div>
|
|
277
|
-
</div>
|
|
278
|
-
)}
|
|
279
|
-
</div>
|
|
280
|
-
)
|
|
281
|
-
}
|