@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,231 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useRef, useMemo, useCallback } from 'react'
|
|
2
|
-
import { useQuery } from '@tanstack/react-query'
|
|
3
|
-
import { ArrowDownToLine, Filter } from 'lucide-react'
|
|
4
|
-
import { useBackendClient } from '../../hooks/useBackendClient'
|
|
5
|
-
|
|
6
|
-
// ---------------------------------------------------------------------------
|
|
7
|
-
// Types
|
|
8
|
-
// ---------------------------------------------------------------------------
|
|
9
|
-
|
|
10
|
-
type LogLevel = 'all' | 'debug' | 'info' | 'warn' | 'error'
|
|
11
|
-
|
|
12
|
-
interface AgentLogsProps {
|
|
13
|
-
/** List of agent names/IDs for the filter dropdown */
|
|
14
|
-
agentNames: readonly string[]
|
|
15
|
-
/** List of addon names for the filter dropdown */
|
|
16
|
-
addonNames: readonly string[]
|
|
17
|
-
/** Pre-selected agent name from clicking an agent in the tree */
|
|
18
|
-
preselectedAgent?: string
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// ---------------------------------------------------------------------------
|
|
22
|
-
// Styles
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
|
|
25
|
-
const LEVEL_STYLES: Record<string, { badge: string; text: string }> = {
|
|
26
|
-
debug: { badge: 'bg-foreground-subtle/10 text-foreground-subtle', text: 'text-foreground-subtle' },
|
|
27
|
-
info: { badge: 'bg-info/10 text-info', text: 'text-info' },
|
|
28
|
-
warn: { badge: 'bg-warning/10 text-warning', text: 'text-warning' },
|
|
29
|
-
error: { badge: 'bg-danger/10 text-danger', text: 'text-danger' },
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// ---------------------------------------------------------------------------
|
|
33
|
-
// Component
|
|
34
|
-
// ---------------------------------------------------------------------------
|
|
35
|
-
|
|
36
|
-
export function AgentLogs({ agentNames, addonNames, preselectedAgent }: AgentLogsProps) {
|
|
37
|
-
const client = useBackendClient()
|
|
38
|
-
const [levelFilter, setLevelFilter] = useState<LogLevel>('all')
|
|
39
|
-
const [agentFilter, setAgentFilter] = useState<string>(preselectedAgent ?? 'all')
|
|
40
|
-
const [addonFilter, setAddonFilter] = useState<string>('all')
|
|
41
|
-
|
|
42
|
-
// Sync agentFilter when preselectedAgent changes externally
|
|
43
|
-
const prevPreselected = useRef(preselectedAgent)
|
|
44
|
-
useEffect(() => {
|
|
45
|
-
if (preselectedAgent !== prevPreselected.current) {
|
|
46
|
-
setAgentFilter(preselectedAgent ?? 'all')
|
|
47
|
-
prevPreselected.current = preselectedAgent
|
|
48
|
-
}
|
|
49
|
-
}, [preselectedAgent])
|
|
50
|
-
const [autoScroll, setAutoScroll] = useState(true)
|
|
51
|
-
const scrollContainerRef = useRef<HTMLDivElement>(null)
|
|
52
|
-
// bottomRef removed — auto-scroll goes to top (newest first)
|
|
53
|
-
|
|
54
|
-
// Build scope filters from agent + addon selections
|
|
55
|
-
const scopeFilter = useMemo(() => {
|
|
56
|
-
const scopes: string[] = []
|
|
57
|
-
if (agentFilter !== 'all') scopes.push(agentFilter)
|
|
58
|
-
if (addonFilter !== 'all') scopes.push(addonFilter)
|
|
59
|
-
return scopes.length > 0 ? scopes : undefined
|
|
60
|
-
}, [agentFilter, addonFilter])
|
|
61
|
-
|
|
62
|
-
// Always fetch live -- no pause concept
|
|
63
|
-
const { data: logsData, isLoading, isError } = useQuery({
|
|
64
|
-
queryKey: ['agent-logs', levelFilter, agentFilter, addonFilter],
|
|
65
|
-
queryFn: () =>
|
|
66
|
-
client.getLogs({
|
|
67
|
-
...(levelFilter !== 'all' ? { level: levelFilter as 'debug' | 'info' | 'warn' | 'error' } : {}),
|
|
68
|
-
limit: 300,
|
|
69
|
-
...(scopeFilter ? { scope: scopeFilter } : {}),
|
|
70
|
-
} as Parameters<typeof client.getLogs>[0]),
|
|
71
|
-
refetchInterval: 3000,
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
const rawLogs = (logsData ?? []) as unknown as Array<Record<string, unknown>>
|
|
75
|
-
|
|
76
|
-
// Newest first — most recent logs at the top
|
|
77
|
-
const logs = useMemo(() => [...rawLogs].reverse(), [rawLogs])
|
|
78
|
-
|
|
79
|
-
// Auto-scroll to top when new logs arrive (newest is at top)
|
|
80
|
-
useEffect(() => {
|
|
81
|
-
if (autoScroll) {
|
|
82
|
-
scrollContainerRef.current?.scrollTo({ top: 0, behavior: 'smooth' })
|
|
83
|
-
}
|
|
84
|
-
}, [logs, autoScroll])
|
|
85
|
-
|
|
86
|
-
// Detect user scroll to auto-disable auto-scroll when scrolling down
|
|
87
|
-
const handleScroll = useCallback(() => {
|
|
88
|
-
const container = scrollContainerRef.current
|
|
89
|
-
if (!container) return
|
|
90
|
-
const isAtTop = container.scrollTop < 40
|
|
91
|
-
setAutoScroll(isAtTop)
|
|
92
|
-
}, [])
|
|
93
|
-
|
|
94
|
-
function formatTs(ts: unknown): string {
|
|
95
|
-
if (!ts) return '--'
|
|
96
|
-
const d = new Date(typeof ts === 'number' ? ts : String(ts))
|
|
97
|
-
return d.toLocaleTimeString('en-GB', {
|
|
98
|
-
hour12: false,
|
|
99
|
-
hour: '2-digit',
|
|
100
|
-
minute: '2-digit',
|
|
101
|
-
second: '2-digit',
|
|
102
|
-
fractionalSecondDigits: 3,
|
|
103
|
-
})
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return (
|
|
107
|
-
<div className="space-y-3">
|
|
108
|
-
{/* Filters bar */}
|
|
109
|
-
<div className="flex items-center gap-2 flex-wrap">
|
|
110
|
-
<Filter className="h-3.5 w-3.5 text-foreground-subtle shrink-0" />
|
|
111
|
-
|
|
112
|
-
<select
|
|
113
|
-
value={agentFilter}
|
|
114
|
-
onChange={(e) => setAgentFilter(e.target.value)}
|
|
115
|
-
className="rounded-lg border border-border bg-surface text-xs text-foreground px-2 py-1.5 focus:outline-none focus:ring-1 focus:ring-primary"
|
|
116
|
-
>
|
|
117
|
-
<option value="all">All agents</option>
|
|
118
|
-
<option value="Hub">Hub</option>
|
|
119
|
-
{agentNames.map((name) => (
|
|
120
|
-
<option key={name} value={name}>{name}</option>
|
|
121
|
-
))}
|
|
122
|
-
</select>
|
|
123
|
-
|
|
124
|
-
<select
|
|
125
|
-
value={addonFilter}
|
|
126
|
-
onChange={(e) => setAddonFilter(e.target.value)}
|
|
127
|
-
className="rounded-lg border border-border bg-surface text-xs text-foreground px-2 py-1.5 focus:outline-none focus:ring-1 focus:ring-primary"
|
|
128
|
-
>
|
|
129
|
-
<option value="all">All addons</option>
|
|
130
|
-
{addonNames.map((name) => (
|
|
131
|
-
<option key={name} value={name}>{name}</option>
|
|
132
|
-
))}
|
|
133
|
-
</select>
|
|
134
|
-
|
|
135
|
-
<select
|
|
136
|
-
value={levelFilter}
|
|
137
|
-
onChange={(e) => setLevelFilter(e.target.value as LogLevel)}
|
|
138
|
-
className="rounded-lg border border-border bg-surface text-xs text-foreground px-2 py-1.5 focus:outline-none focus:ring-1 focus:ring-primary"
|
|
139
|
-
>
|
|
140
|
-
<option value="all">All levels</option>
|
|
141
|
-
<option value="debug">Debug</option>
|
|
142
|
-
<option value="info">Info</option>
|
|
143
|
-
<option value="warn">Warn</option>
|
|
144
|
-
<option value="error">Error</option>
|
|
145
|
-
</select>
|
|
146
|
-
|
|
147
|
-
<button
|
|
148
|
-
type="button"
|
|
149
|
-
onClick={() => {
|
|
150
|
-
const next = !autoScroll
|
|
151
|
-
setAutoScroll(next)
|
|
152
|
-
if (next) {
|
|
153
|
-
scrollContainerRef.current?.scrollTo({ top: 0, behavior: 'smooth' })
|
|
154
|
-
}
|
|
155
|
-
}}
|
|
156
|
-
className={`inline-flex items-center gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs font-medium transition-colors ${
|
|
157
|
-
autoScroll
|
|
158
|
-
? 'border-primary/30 bg-primary/10 text-primary'
|
|
159
|
-
: 'border-border bg-surface text-foreground-subtle hover:text-foreground'
|
|
160
|
-
}`}
|
|
161
|
-
>
|
|
162
|
-
<ArrowDownToLine className="h-3.5 w-3.5" />
|
|
163
|
-
Auto-scroll: {autoScroll ? 'ON' : 'OFF'}
|
|
164
|
-
</button>
|
|
165
|
-
</div>
|
|
166
|
-
|
|
167
|
-
{/* Log display */}
|
|
168
|
-
{isLoading && (
|
|
169
|
-
<div className="text-xs text-foreground-subtle animate-pulse">Loading logs...</div>
|
|
170
|
-
)}
|
|
171
|
-
|
|
172
|
-
{isError && (
|
|
173
|
-
<div className="text-xs text-danger">Failed to load logs</div>
|
|
174
|
-
)}
|
|
175
|
-
|
|
176
|
-
{!isLoading && !isError && logs.length === 0 && (
|
|
177
|
-
<div className="text-xs text-foreground-subtle italic">No logs match the current filters</div>
|
|
178
|
-
)}
|
|
179
|
-
|
|
180
|
-
{logs.length > 0 && (
|
|
181
|
-
<div className="rounded-lg border border-border bg-surface overflow-hidden">
|
|
182
|
-
<div
|
|
183
|
-
ref={scrollContainerRef}
|
|
184
|
-
onScroll={handleScroll}
|
|
185
|
-
className="max-h-[400px] overflow-y-auto"
|
|
186
|
-
>
|
|
187
|
-
<table className="w-full text-[10px]">
|
|
188
|
-
<thead className="sticky top-0">
|
|
189
|
-
<tr>
|
|
190
|
-
<th className="text-left px-2.5 py-1.5 text-foreground-subtle font-medium bg-surface border-b border-border w-24">Time</th>
|
|
191
|
-
<th className="text-left px-2.5 py-1.5 text-foreground-subtle font-medium bg-surface border-b border-border w-14">Level</th>
|
|
192
|
-
<th className="text-left px-2.5 py-1.5 text-foreground-subtle font-medium bg-surface border-b border-border w-28">Scope</th>
|
|
193
|
-
<th className="text-left px-2.5 py-1.5 text-foreground-subtle font-medium bg-surface border-b border-border">Message</th>
|
|
194
|
-
</tr>
|
|
195
|
-
</thead>
|
|
196
|
-
<tbody>
|
|
197
|
-
{logs.map((entry, i) => {
|
|
198
|
-
const level = String(entry.level ?? 'info').toLowerCase()
|
|
199
|
-
const style = LEVEL_STYLES[level] ?? LEVEL_STYLES['info']!
|
|
200
|
-
const message = String(entry.message ?? entry.msg ?? entry.text ?? '')
|
|
201
|
-
const scope = Array.isArray(entry.scope)
|
|
202
|
-
? (entry.scope as string[]).join(' > ')
|
|
203
|
-
: String(entry.scope ?? '')
|
|
204
|
-
return (
|
|
205
|
-
<tr key={i} className="hover:bg-primary/5">
|
|
206
|
-
<td className="px-2.5 py-1 text-foreground-subtle border-b border-border font-mono whitespace-nowrap">
|
|
207
|
-
{formatTs(entry.timestamp ?? entry.ts ?? entry.time)}
|
|
208
|
-
</td>
|
|
209
|
-
<td className="px-2.5 py-1 border-b border-border">
|
|
210
|
-
<span className={`inline-flex rounded-full px-1.5 py-0.5 text-[9px] font-medium uppercase ${style.badge}`}>
|
|
211
|
-
{level}
|
|
212
|
-
</span>
|
|
213
|
-
</td>
|
|
214
|
-
<td className="px-2.5 py-1 border-b border-border text-foreground-subtle font-mono truncate max-w-[180px]" title={scope}>
|
|
215
|
-
{scope || '--'}
|
|
216
|
-
</td>
|
|
217
|
-
<td className={`px-2.5 py-1 border-b border-border font-mono ${style.text} break-all`}>
|
|
218
|
-
{message}
|
|
219
|
-
</td>
|
|
220
|
-
</tr>
|
|
221
|
-
)
|
|
222
|
-
})}
|
|
223
|
-
</tbody>
|
|
224
|
-
</table>
|
|
225
|
-
{/* newest-first: no bottom anchor needed */}
|
|
226
|
-
</div>
|
|
227
|
-
</div>
|
|
228
|
-
)}
|
|
229
|
-
</div>
|
|
230
|
-
)
|
|
231
|
-
}
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react'
|
|
2
|
-
import { RotateCcw } from 'lucide-react'
|
|
3
|
-
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
|
4
|
-
import { useBackendClient } from '../../hooks/useBackendClient'
|
|
5
|
-
|
|
6
|
-
export interface ProcessEntry {
|
|
7
|
-
id: string
|
|
8
|
-
name: string
|
|
9
|
-
pid?: number
|
|
10
|
-
status: string
|
|
11
|
-
cpu?: number
|
|
12
|
-
memoryMb?: number
|
|
13
|
-
uptime?: number
|
|
14
|
-
restarts?: number
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function StatusDot({ status }: { status: string }) {
|
|
18
|
-
const color =
|
|
19
|
-
status === 'running'
|
|
20
|
-
? 'bg-green-400'
|
|
21
|
-
: status === 'error'
|
|
22
|
-
? 'bg-red-400'
|
|
23
|
-
: 'bg-foreground-subtle'
|
|
24
|
-
return <span className={`inline-block h-2 w-2 rounded-full shrink-0 ${color}`} title={status} />
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function formatUptime(seconds: number): string {
|
|
28
|
-
if (seconds < 60) return `${seconds}s`
|
|
29
|
-
if (seconds < 3600) return `${Math.floor(seconds / 60)}m ${seconds % 60}s`
|
|
30
|
-
const h = Math.floor(seconds / 3600)
|
|
31
|
-
const m = Math.floor((seconds % 3600) / 60)
|
|
32
|
-
return `${h}h ${m}m`
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
interface ProcessListProps {
|
|
36
|
-
processes: ProcessEntry[]
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function ProcessList({ processes }: ProcessListProps) {
|
|
40
|
-
const client = useBackendClient()
|
|
41
|
-
const queryClient = useQueryClient()
|
|
42
|
-
const [confirmingId, setConfirmingId] = useState<string | null>(null)
|
|
43
|
-
|
|
44
|
-
const restartMutation = useMutation({
|
|
45
|
-
mutationFn: (id: string) => client.trpc.processes.restartProcess.mutate({ id }),
|
|
46
|
-
onSuccess: () => {
|
|
47
|
-
void queryClient.invalidateQueries({ queryKey: ['processes'] })
|
|
48
|
-
setConfirmingId(null)
|
|
49
|
-
},
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
if (processes.length === 0) {
|
|
53
|
-
return <div className="text-[10px] text-foreground-subtle italic">No processes</div>
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return (
|
|
57
|
-
<div className="rounded-md border border-border overflow-hidden">
|
|
58
|
-
<table className="w-full text-[10px]">
|
|
59
|
-
<thead>
|
|
60
|
-
<tr>
|
|
61
|
-
<th className="text-left px-2.5 py-1.5 text-foreground-subtle font-medium bg-background border-b border-border w-4" />
|
|
62
|
-
<th className="text-left px-2.5 py-1.5 text-foreground-subtle font-medium bg-background border-b border-border">Name</th>
|
|
63
|
-
<th className="text-left px-2.5 py-1.5 text-foreground-subtle font-medium bg-background border-b border-border">PID</th>
|
|
64
|
-
<th className="text-left px-2.5 py-1.5 text-foreground-subtle font-medium bg-background border-b border-border">CPU%</th>
|
|
65
|
-
<th className="text-left px-2.5 py-1.5 text-foreground-subtle font-medium bg-background border-b border-border">RSS</th>
|
|
66
|
-
<th className="text-left px-2.5 py-1.5 text-foreground-subtle font-medium bg-background border-b border-border">Uptime</th>
|
|
67
|
-
<th className="px-2.5 py-1.5 bg-background border-b border-border" />
|
|
68
|
-
</tr>
|
|
69
|
-
</thead>
|
|
70
|
-
<tbody>
|
|
71
|
-
{processes.map((proc) => {
|
|
72
|
-
const isConfirming = confirmingId === proc.id
|
|
73
|
-
return (
|
|
74
|
-
<tr key={proc.id} className="hover:bg-primary/5">
|
|
75
|
-
<td className="px-2.5 py-1.5 border-b border-border">
|
|
76
|
-
<StatusDot status={proc.status} />
|
|
77
|
-
</td>
|
|
78
|
-
<td className="px-2.5 py-1.5 border-b border-border font-mono text-foreground">
|
|
79
|
-
{proc.name}
|
|
80
|
-
</td>
|
|
81
|
-
<td className="px-2.5 py-1.5 border-b border-border text-foreground-subtle">
|
|
82
|
-
{proc.pid ?? '—'}
|
|
83
|
-
</td>
|
|
84
|
-
<td className="px-2.5 py-1.5 border-b border-border text-foreground-subtle">
|
|
85
|
-
{proc.cpu != null ? `${proc.cpu.toFixed(1)}%` : '—'}
|
|
86
|
-
</td>
|
|
87
|
-
<td className="px-2.5 py-1.5 border-b border-border text-foreground-subtle">
|
|
88
|
-
{proc.memoryMb != null ? `${proc.memoryMb.toFixed(0)} MB` : '—'}
|
|
89
|
-
</td>
|
|
90
|
-
<td className="px-2.5 py-1.5 border-b border-border text-foreground-subtle">
|
|
91
|
-
{proc.uptime != null ? formatUptime(proc.uptime) : '—'}
|
|
92
|
-
</td>
|
|
93
|
-
<td className="px-2.5 py-1.5 border-b border-border">
|
|
94
|
-
{isConfirming ? (
|
|
95
|
-
<div className="flex items-center gap-1">
|
|
96
|
-
<button
|
|
97
|
-
onClick={() => restartMutation.mutate(proc.id)}
|
|
98
|
-
disabled={restartMutation.isPending}
|
|
99
|
-
className="rounded px-1.5 py-0.5 bg-danger/10 text-danger hover:bg-danger/20"
|
|
100
|
-
>
|
|
101
|
-
Confirm
|
|
102
|
-
</button>
|
|
103
|
-
<button
|
|
104
|
-
onClick={() => setConfirmingId(null)}
|
|
105
|
-
className="rounded px-1.5 py-0.5 bg-surface text-foreground-subtle hover:text-foreground border border-border"
|
|
106
|
-
>
|
|
107
|
-
Cancel
|
|
108
|
-
</button>
|
|
109
|
-
</div>
|
|
110
|
-
) : (
|
|
111
|
-
<button
|
|
112
|
-
onClick={() => setConfirmingId(proc.id)}
|
|
113
|
-
className="inline-flex items-center gap-1 rounded px-1.5 py-0.5 bg-primary/10 text-primary hover:bg-primary/20"
|
|
114
|
-
>
|
|
115
|
-
<RotateCcw className="h-2.5 w-2.5" />
|
|
116
|
-
Restart
|
|
117
|
-
</button>
|
|
118
|
-
)}
|
|
119
|
-
</td>
|
|
120
|
-
</tr>
|
|
121
|
-
)
|
|
122
|
-
})}
|
|
123
|
-
</tbody>
|
|
124
|
-
</table>
|
|
125
|
-
</div>
|
|
126
|
-
)
|
|
127
|
-
}
|