add disconnect reasons to dashboard and fix icons in sideview

This commit is contained in:
KingKDot
2026-04-05 22:55:03 -05:00
parent 3c0f22ceab
commit ff0f29f920
9 changed files with 101 additions and 12 deletions
+1
View File
@@ -2184,6 +2184,7 @@ body.sb-collapsed .sb-collapse-btn i {
body.sb-collapsed .sb-link {
justify-content: center;
padding: 12px 0;
gap: 0;
}
.sb-icon {
flex-shrink: 0;
+8
View File
@@ -266,6 +266,14 @@ export function createRenderer({
<i class="fa-solid fa-circle"></i>
${client.online ? "Online" : "Offline"}
</span>
${!client.online && client.disconnectReason && client.disconnectReason !== "normal" ? (() => {
const iconMap = { panic: "fa-skull-crossbones", crash: "fa-skull", timeout: "fa-clock", network: "fa-plug-circle-xmark" };
const colorMap = { panic: "text-red-400", crash: "text-red-400", timeout: "text-amber-400", network: "text-slate-400" };
const icon = iconMap[client.disconnectReason] || "fa-circle-exclamation";
const color = colorMap[client.disconnectReason] || "text-slate-400";
const detail = client.disconnectDetail ? escapeHtml(client.disconnectDetail) : "";
return `<span class="pill pill-ghost text-xs ${color}" ${detail ? `title="${detail}"` : ""}><i class="fa-solid ${icon}"></i> ${escapeHtml(client.disconnectReason)}</span>`;
})() : ""}
</div>
${hasTagNote ? `<div class="client-tag-note rounded-lg border border-amber-900/60 bg-amber-950/20 px-3 py-2 text-sm text-amber-100 whitespace-pre-wrap break-words max-h-48 overflow-auto ${isTagNoteExpanded ? "" : "hidden"}">${escapeHtml(customTagNote)}</div>` : ""}
<div class="flex items-center gap-2 flex-wrap text-sm text-slate-300">
+25 -8
View File
@@ -97,6 +97,12 @@ db.run(
db.run(
`CREATE INDEX IF NOT EXISTS idx_clients_ping_ms ON clients(ping_ms);`,
);
try {
db.run(`ALTER TABLE clients ADD COLUMN disconnect_reason TEXT`);
} catch {}
try {
db.run(`ALTER TABLE clients ADD COLUMN disconnect_detail TEXT`);
} catch {}
db.run(`
CREATE TABLE IF NOT EXISTS banned_ips (
@@ -304,13 +310,22 @@ export function upsertClientRow(
}
}
export function setOnlineState(id: string, online: boolean) {
db.run(
`UPDATE clients SET online=?, last_seen=? WHERE id=?`,
online ? 1 : 0,
Date.now(),
id,
);
export function setOnlineState(id: string, online: boolean, disconnectReason?: string, disconnectDetail?: string) {
if (online) {
db.run(
`UPDATE clients SET online=1, last_seen=?, disconnect_reason=NULL, disconnect_detail=NULL WHERE id=?`,
Date.now(),
id,
);
} else {
db.run(
`UPDATE clients SET online=0, last_seen=?, disconnect_reason=?, disconnect_detail=? WHERE id=?`,
Date.now(),
disconnectReason ?? null,
disconnectDetail ?? null,
id,
);
}
}
export function deleteClientRow(id: string) {
@@ -541,7 +556,7 @@ export function listClients(filters: ListFilters): ListResult {
const rows = db
.query<any>(
`SELECT id, hwid, role, host, os, arch, version, user, nickname, custom_tag as customTag, custom_tag_note as customTagNote, monitors, country, last_seen as lastSeen, online, ping_ms as pingMs, bookmarked, enrollment_status as enrollmentStatus, public_key as publicKey, key_fingerprint as keyFingerprint, cpu, gpu, ram
`SELECT id, hwid, role, host, os, arch, version, user, nickname, custom_tag as customTag, custom_tag_note as customTagNote, monitors, country, last_seen as lastSeen, online, ping_ms as pingMs, bookmarked, enrollment_status as enrollmentStatus, public_key as publicKey, key_fingerprint as keyFingerprint, cpu, gpu, ram, disconnect_reason as disconnectReason, disconnect_detail as disconnectDetail
FROM clients
${whereSql}
${orderBy}
@@ -573,6 +588,8 @@ export function listClients(filters: ListFilters): ListResult {
cpu: c.cpu || null,
gpu: c.gpu || null,
ram: c.ram || null,
disconnectReason: c.disconnectReason || null,
disconnectDetail: c.disconnectDetail || null,
thumbnail: getThumbnail(c.id),
}));
@@ -555,6 +555,16 @@ export async function handleWebSocketMessage(
}
break;
}
case "disconnect_info": {
const reason = typeof (payload as any).reason === "string" ? (payload as any).reason : "";
const detail = typeof (payload as any).detail === "string" ? (payload as any).detail : "";
if (reason) {
ws.data.disconnectReason = reason;
ws.data.disconnectDetail = detail || undefined;
logger.debug(`[disconnect_info] ${client.id} reason=${reason}`);
}
break;
}
default:
break;
}
@@ -709,15 +719,18 @@ export function handleWebSocketClose(
});
}
const storedDisconnectReason = ws.data.disconnectReason;
const storedDisconnectDetail = ws.data.disconnectDetail;
clientManager.deleteClient(clientId);
stopAllProxiesForClient(clientId);
clearClientSyncState(clientId);
deps.notifyConsoleClosed(clientId, "Client disconnected");
setOnlineState(clientId, false);
setOnlineState(clientId, false, storedDisconnectReason, storedDisconnectDetail);
deps.clearPendingNotificationScreenshots(clientId);
deps.clearClientPluginState(clientId);
deps.notifyDashboard();
logger.info(`[close] ${clientId} code=${code} reason=${reason}`);
logger.info(`[close] ${clientId} code=${code} reason=${reason} disconnect_reason=${storedDisconnectReason || "unknown"}`);
if (role === "client") {
deps.notifyRemoteDesktopStatus(clientId, "offline", "Client disconnected");
@@ -729,7 +742,7 @@ export function handleWebSocketClose(
action: AuditAction.CLIENT_DISCONNECT,
targetClientId: clientId,
success: true,
details: JSON.stringify({ code, reason: String(reason || "") }),
details: JSON.stringify({ code, reason: String(reason || ""), disconnectReason: storedDisconnectReason || null }),
});
}
}
+2
View File
@@ -24,6 +24,8 @@ export type SocketData = {
autoTasksRan?: boolean;
autoDeploysRan?: boolean;
enrollmentNonce?: string;
disconnectReason?: string;
disconnectDetail?: string;
};
export type ConsoleSession = {
+2
View File
@@ -31,6 +31,8 @@ export type ClientInfo = {
enrollmentStatus?: EnrollmentStatus;
publicKey?: string;
keyFingerprint?: string;
disconnectReason?: string;
disconnectDetail?: string;
};
export type ListFilters = {
+1
View File
@@ -32,6 +32,7 @@ export const ALLOWED_CLIENT_MESSAGE_TYPES = new Set([
"clipboard_content",
"proxy_data",
"proxy_close",
"disconnect_info",
]);
export function isAllowedClientMessageType(type: string): boolean {