/* global React, Card, StatusGlyph, Ic, Button, VS, LoadingState, ErrorState */
function ComputersScreen() {
const [list, setList] = React.useState(null);
const [err, setErr] = React.useState('');
const [busyHost, setBusyHost] = React.useState('');
const [confirmDelete, setConfirmDelete] = React.useState(null);
const [, force] = React.useState(0);
async function load() {
try {
const d = await VS.computers();
setList(Array.isArray(d) ? d : (d.computers || []));
setErr('');
} catch (e) { setErr(e.message); }
}
React.useEffect(() => {
load();
const t = setInterval(load, 3000);
const tick = setInterval(() => force(x => x + 1), 1000);
return () => { clearInterval(t); clearInterval(tick); };
}, []);
async function restart(host) {
setBusyHost(host);
try { await VS.restartComputer(host); }
catch (e) { setErr(e.message); }
finally { setBusyHost(''); load(); }
}
async function remove(host) {
setBusyHost(host);
try { await VS.deleteComputer(host); setConfirmDelete(null); }
catch (e) { setErr(e.message); }
finally { setBusyHost(''); load(); }
}
if (err && !list) return ;
if (!list) return ;
return (
Infrastruktura
Komputery klienckie
Stanowiska z agentem synchronizacji. Heartbeat co 10 s, autoreset na zdalne polecenie.
} onClick={load}>Odśwież
{err && (
{err}
)}
{list.length === 0 && (
Brak komputerów
Żaden agent nie zarejestrował jeszcze heartbeatu.
)}
{list.length > 0 && (
Hostname
Ostatni heartbeat
Pomiarów
Wersja
Akcje
{list.map((c, i) => {
const secAgo = effectiveSeconds(c);
const online = secAgo !== null && secAgo < 30;
const st = online ? 'ok' : (secAgo !== null && secAgo < 120 ? 'warn' : 'alarm');
const pendingRestart = !!c.restart_requested;
return (
{c.hostname}
pid {c.pid || '—'}
{c.uptime_seconds ? <> · uptime {formatUptime(c.uptime_seconds)}> : null}
{c.last_measid ? <> · measid {c.last_measid}> : null}
{formatAgoLabel(secAgo)}
{c.last_error && (
{c.last_error}
)}
{c.measurements_sent_total || 0}
{c.version || '—'}
{pendingRestart ? (
restart pending
) : (
}
disabled={busyHost === c.hostname}
onClick={() => restart(c.hostname)}>Restart
)}
);
})}
)}
{confirmDelete && (
setConfirmDelete(null)}>
e.stopPropagation()} style={{
background: 'var(--surface)', border: '1px solid var(--border-strong)',
borderRadius: 'var(--r-md)', padding: 20, width: 360,
}}>
Usunąć wpis?
Usuniesz rekord {confirmDelete} z bazy. Jeśli agent nadal działa na tej maszynie, pojawi się ponownie po kolejnym heartbeacie.
)}
);
}
function effectiveSeconds(c) {
if (typeof c.seconds_ago === 'number') {
const d = c.last_seen ? new Date(c.last_seen) : null;
if (d && !isNaN(d.getTime())) {
return Math.max(c.seconds_ago, Math.floor((Date.now() - d.getTime()) / 1000));
}
return c.seconds_ago;
}
if (c.last_seen) {
const d = new Date(c.last_seen);
if (!isNaN(d.getTime())) return Math.max(0, Math.floor((Date.now() - d.getTime()) / 1000));
}
return null;
}
function formatAgoLabel(secAgo) {
if (secAgo === null || secAgo === undefined) return 'nigdy';
if (secAgo < 60) return `${secAgo} s temu`;
if (secAgo < 3600) return `${Math.floor(secAgo / 60)} min temu`;
if (secAgo < 86400) return `${Math.floor(secAgo / 3600)} h temu`;
return `${Math.floor(secAgo / 86400)} d temu`;
}
function formatUptime(sec) {
if (!sec) return '0 s';
if (sec < 60) return `${sec} s`;
if (sec < 3600) return `${Math.floor(sec / 60)} min`;
if (sec < 86400) return `${Math.floor(sec / 3600)} h`;
return `${Math.floor(sec / 86400)} d`;
}
Object.assign(window, { ComputersScreen });