fix: Distributed API UI & metrics polish + DEP0060 suppression

Add Node modal — type selector & state reset:
- Restored a Local/Remote <Select> dropdown in renderFormFields so users can
  explicitly choose the node type instead of it defaulting silently to 'remote'.
- Switching type clears api_url and api_token so no stale remote credentials
  carry over if a user switches from Remote to Local mid-form.
- Replaced the static "Add Remote Node" title with a dynamic one that reflects
  the currently selected type ("Add Local Node" / "Add Remote Node").
- onOpenChange now resets formData to defaultFormData whenever the dialog
  opens, preventing stale values from a previous session leaking in.

Remote connection details — real metrics:
- testRemoteConnection previously returned hard-coded '-' for containers,
  images, and cpus after a successful auth/check ping.
- Now fires three parallel requests (Promise.allSettled) after auth passes:
    /api/stats          → containers total + running count
    /api/system/stats   → cpu.cores
    /api/system/images  → image list length
- Each field falls back to '-' gracefully if an endpoint is unavailable,
  so a slow or older remote instance never breaks the connection test.

DEP0060 util._extend suppression:
- http-proxy@1.18.1 calls util._extend when createProxyServer() is first
  invoked at runtime (NOT at import time). A process.emitWarning override
  placed before the proxy instantiations intercepts only DEP0060 without
  suppressing any other warnings. No package version changes needed.

Also includes linter/formatter normalisation across multiple files.
This commit is contained in:
SaelixCode
2026-03-19 16:06:47 -04:00
parent 9e6f72111d
commit eb0c0263c7
12 changed files with 135 additions and 77 deletions
+4 -4
View File
@@ -75,7 +75,7 @@ export default function BashExecModal({ isOpen, onClose, containerId, containerN
return;
}
// Node exists and has layout safe to initialize xterm!
// Node exists and has layout - safe to initialize xterm!
initTerminal(container);
};
@@ -160,7 +160,7 @@ export default function BashExecModal({ isOpen, onClose, containerId, containerN
setIsConnected(false);
};
// Handle user input JSON up
// Handle user input - JSON up
term.onData((data) => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({
@@ -224,9 +224,9 @@ export default function BashExecModal({ isOpen, onClose, containerId, containerN
Interactive bash terminal session for {containerName}
</DialogDescription>
</DialogHeader>
{/* Styling wrapper padding and rounded corners go here */}
{/* Styling wrapper - padding and rounded corners go here */}
<div className="flex-1 rounded-lg bg-[#1e1e1e] p-1 min-h-0" style={{ overflow: 'hidden' }}>
{/* Clean xterm container NO padding, NO overflow-hidden, explicit dimensions */}
{/* Clean xterm container - NO padding, NO overflow-hidden, explicit dimensions */}
<div
ref={terminalRef}
style={{ width: '100%', height: '100%' }}
+8 -9
View File
@@ -147,7 +147,7 @@ export default function EditorLayout() {
}
};
// Notification polling independent of active node, runs once on mount
// Notification polling - independent of active node, runs once on mount
useEffect(() => {
fetchNotifications();
const notificationInterval = setInterval(fetchNotifications, 5000);
@@ -208,10 +208,10 @@ export default function EditorLayout() {
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const ws = new WebSocket(`${wsProtocol}//${window.location.host}`);
wsMap[container.Id] = ws;
ws.onopen = () => ws.send(JSON.stringify({
action: 'streamStats',
containerId: container.Id,
nodeId: localStorage.getItem('sencho-active-node') || undefined
ws.onopen = () => ws.send(JSON.stringify({
action: 'streamStats',
containerId: container.Id,
nodeId: localStorage.getItem('sencho-active-node') || undefined
}));
ws.onmessage = (event) => {
try {
@@ -688,10 +688,9 @@ export default function EditorLayout() {
{nodes.map(node => (
<SelectItem key={node.id} value={node.id.toString()}>
<div className="flex items-center gap-2">
<div className={`w-2 h-2 rounded-full shrink-0 ${
node.status === 'online' ? 'bg-green-500' :
node.status === 'offline' ? 'bg-red-500' : 'bg-gray-400'
}`} />
<div className={`w-2 h-2 rounded-full shrink-0 ${node.status === 'online' ? 'bg-green-500' :
node.status === 'offline' ? 'bg-red-500' : 'bg-gray-400'
}`} />
{node.name}
</div>
</SelectItem>
+42 -8
View File
@@ -12,6 +12,7 @@ import { Badge } from './ui/badge';
import { Separator } from './ui/separator';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from './ui/table';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
import { Plus, Trash2, Wifi, WifiOff, Star, Pencil, Server, Monitor, Globe, Copy, KeyRound, Check } from 'lucide-react';
interface NodeFormData {
@@ -199,7 +200,7 @@ export function NodeManager() {
toast.success('Token copied to clipboard');
setTimeout(() => setTokenCopied(false), 2000);
} catch {
toast.error('Could not copy automatically please select and copy the token manually.');
toast.error('Could not copy automatically - please select and copy the token manually.');
}
}
};
@@ -233,6 +234,32 @@ export function NodeManager() {
/>
</div>
<div className="space-y-2">
<Label htmlFor="node-type">Type</Label>
<Select
value={formData.type}
onValueChange={(val) => setFormData({ ...formData, type: val as 'local' | 'remote', api_url: '', api_token: '' })}
>
<SelectTrigger id="node-type">
<SelectValue placeholder="Select type" />
</SelectTrigger>
<SelectContent>
<SelectItem value="local">
<div className="flex items-center gap-2">
<Monitor className="w-4 h-4" />
Local Docker socket on this machine
</div>
</SelectItem>
<SelectItem value="remote">
<div className="flex items-center gap-2">
<Globe className="w-4 h-4" />
Remote another Sencho instance
</div>
</SelectItem>
</SelectContent>
</Select>
</div>
{formData.type === 'remote' && (
<>
<div className="space-y-2">
@@ -292,7 +319,14 @@ export function NodeManager() {
Manage connections to local and remote Sencho instances
</p>
</div>
<Dialog open={createOpen} onOpenChange={setCreateOpen}>
<Dialog
open={createOpen}
onOpenChange={(open) => {
setCreateOpen(open);
// Reset form to defaults every time the dialog opens
if (open) setFormData(defaultFormData);
}}
>
<DialogTrigger asChild>
<Button size="sm" className="gap-1 shrink-0">
<Plus className="w-4 h-4" />
@@ -301,7 +335,7 @@ export function NodeManager() {
</DialogTrigger>
<DialogContent className="max-w-lg">
<DialogHeader className="pr-8">
<DialogTitle>Add Remote Node</DialogTitle>
<DialogTitle>Add {formData.type === 'local' ? 'Local' : 'Remote'} Node</DialogTitle>
</DialogHeader>
{renderFormFields()}
<DialogFooter>
@@ -319,7 +353,7 @@ export function NodeManager() {
<Separator />
{/* Generate Node Token for use on THIS instance as a remote target */}
{/* Generate Node Token - for use on THIS instance as a remote target */}
<div className="rounded-md border p-4 space-y-3">
<div className="flex items-start justify-between gap-4">
<div>
@@ -328,7 +362,7 @@ export function NodeManager() {
Generate Node Token
</p>
<p className="text-xs text-muted-foreground mt-1">
Create a long-lived token that allows another Sencho instance to use <strong>this</strong> instance as a remote node. Copy it and paste it into the main dashboard's "Add Node" form.
Create a long-lived token that allows another Sencho instance to use <strong>this</strong> instance as a remote node. Copy it and paste it into the other Sencho instance's "Add Node" form.
</p>
</div>
<Button
@@ -390,7 +424,7 @@ export function NodeManager() {
<Badge variant="outline">{node.type === 'local' ? 'Local' : 'Remote'}</Badge>
</TableCell>
<TableCell className="text-muted-foreground text-sm font-mono">
{node.type === 'local' ? 'docker.sock' : (node.api_url || '')}
{node.type === 'local' ? 'docker.sock' : (node.api_url || '-')}
</TableCell>
<TableCell>{getStatusBadge(node.status)}</TableCell>
<TableCell className="text-right">
@@ -458,7 +492,7 @@ export function NodeManager() {
<div className="rounded-md border p-4 bg-muted/30 space-y-2">
<h3 className="text-sm font-semibold flex items-center gap-2">
<Wifi className="w-4 h-4 text-green-500" />
Connection Details {nodes.find(n => n.id === testResult.nodeId)?.name}
Connection Details - {nodes.find(n => n.id === testResult.nodeId)?.name}
</h3>
<div className="grid grid-cols-2 md:grid-cols-3 gap-3 text-sm">
<div><span className="text-muted-foreground">Instance:</span> {testResult.info.serverVersion}</div>
@@ -496,7 +530,7 @@ export function NodeManager() {
<AlertDialogHeader>
<AlertDialogTitle>Delete Node</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to remove <strong>{deletingNode?.name}</strong>? This will only remove the node from Sencho it will not affect the remote instance or any running containers.
Are you sure you want to remove <strong>{deletingNode?.name}</strong>? This will only remove the node from Sencho - it will not affect the remote instance or any running containers.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
+1 -1
View File
@@ -476,7 +476,7 @@ export function SettingsModal({ isOpen, onClose, isDarkMode, setIsDarkMode }: Se
</SelectContent>
</Select>
{settings.developer_mode === '1' && (
<p className="text-xs text-amber-500">SSE streaming is active polling rate is overridden by real-time streaming.</p>
<p className="text-xs text-amber-500">SSE streaming is active - polling rate is overridden by real-time streaming.</p>
)}
</div>
</div>
+1 -1
View File
@@ -67,7 +67,7 @@ export function NodeProvider({ children }: { children: React.ReactNode }) {
} finally {
setIsLoading(false);
}
}, []); // stable reads activeNode via ref, not closure capture
}, []); // stable - reads activeNode via ref, not closure capture
const setActiveNode = useCallback((node: Node) => {
setActiveNodeState(node);