@livedesk/client 0.1.63 → 0.1.65
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/bin/livedesk-client.js +514 -26
- package/fast/linux-x64/livedesk-client-fast.dll +0 -0
- package/fast/linux-x64/livedesk-client-fast.pdb +0 -0
- package/fast/osx-arm64/livedesk-client-fast.dll +0 -0
- package/fast/osx-arm64/livedesk-client-fast.pdb +0 -0
- package/fast/osx-x64/livedesk-client-fast.dll +0 -0
- package/fast/osx-x64/livedesk-client-fast.pdb +0 -0
- package/fast/win-x64/livedesk-client-fast.dll +0 -0
- package/fast/win-x64/livedesk-client-fast.pdb +0 -0
- package/package.json +1 -1
package/bin/livedesk-client.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, statfsSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { dirname, join, resolve } from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { spawn, spawnSync } from 'node:child_process';
|
|
@@ -429,6 +429,10 @@ function writeSavedSessionToFile(session) {
|
|
|
429
429
|
return true;
|
|
430
430
|
}
|
|
431
431
|
|
|
432
|
+
function clearSavedSession() {
|
|
433
|
+
rmSync(CLIENT_AUTH_PATH, { force: true });
|
|
434
|
+
}
|
|
435
|
+
|
|
432
436
|
async function createSupabaseClient() {
|
|
433
437
|
const { createClient } = await import('@supabase/supabase-js');
|
|
434
438
|
return createClient(SUPABASE_URL, SUPABASE_PUBLISHABLE_KEY, {
|
|
@@ -466,6 +470,14 @@ function getNestedErrorMessage(error) {
|
|
|
466
470
|
return messages.join(' ');
|
|
467
471
|
}
|
|
468
472
|
|
|
473
|
+
function isInvalidRefreshTokenError(error) {
|
|
474
|
+
const code = getNestedErrorCode(error).toLowerCase();
|
|
475
|
+
const message = getNestedErrorMessage(error);
|
|
476
|
+
return code === 'refresh_token_not_found'
|
|
477
|
+
|| code === 'invalid_grant'
|
|
478
|
+
|| /invalid refresh token|refresh token not found/i.test(message);
|
|
479
|
+
}
|
|
480
|
+
|
|
469
481
|
function isTransientNetworkError(error) {
|
|
470
482
|
const code = getNestedErrorCode(error).toUpperCase();
|
|
471
483
|
if (['ENOTFOUND', 'EAI_AGAIN', 'ECONNRESET', 'ECONNREFUSED', 'ETIMEDOUT', 'ENETUNREACH', 'EHOSTUNREACH'].includes(code)) {
|
|
@@ -483,8 +495,18 @@ function formatDiscoveryError(error) {
|
|
|
483
495
|
}
|
|
484
496
|
|
|
485
497
|
async function refreshSessionIfNeeded(supabase) {
|
|
486
|
-
|
|
487
|
-
|
|
498
|
+
let existingSession = null;
|
|
499
|
+
try {
|
|
500
|
+
const { data: existing } = await supabase.auth.getSession();
|
|
501
|
+
existingSession = existing?.session || null;
|
|
502
|
+
} catch (error) {
|
|
503
|
+
if (isInvalidRefreshTokenError(error)) {
|
|
504
|
+
clearSavedSession();
|
|
505
|
+
return null;
|
|
506
|
+
}
|
|
507
|
+
throw error;
|
|
508
|
+
}
|
|
509
|
+
const session = existingSession || readSavedSessionFromFile();
|
|
488
510
|
if (!session?.access_token) {
|
|
489
511
|
return null;
|
|
490
512
|
}
|
|
@@ -495,6 +517,10 @@ async function refreshSessionIfNeeded(supabase) {
|
|
|
495
517
|
}
|
|
496
518
|
const { data, error } = await supabase.auth.refreshSession(session);
|
|
497
519
|
if (error) {
|
|
520
|
+
if (isInvalidRefreshTokenError(error)) {
|
|
521
|
+
clearSavedSession();
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
498
524
|
throw error;
|
|
499
525
|
}
|
|
500
526
|
const refreshed = data?.session || session;
|
|
@@ -1035,16 +1061,148 @@ function getDashboardAuthLabel(choice, loggedOut = false) {
|
|
|
1035
1061
|
return email ? `Google - ${email}` : 'Google signed in';
|
|
1036
1062
|
}
|
|
1037
1063
|
|
|
1064
|
+
function formatDashboardBytes(value) {
|
|
1065
|
+
const bytes = Number(value || 0);
|
|
1066
|
+
if (!Number.isFinite(bytes) || bytes <= 0) {
|
|
1067
|
+
return '-';
|
|
1068
|
+
}
|
|
1069
|
+
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
1070
|
+
let next = bytes;
|
|
1071
|
+
let unitIndex = 0;
|
|
1072
|
+
while (next >= 1024 && unitIndex < units.length - 1) {
|
|
1073
|
+
next /= 1024;
|
|
1074
|
+
unitIndex += 1;
|
|
1075
|
+
}
|
|
1076
|
+
const precision = next >= 100 || unitIndex === 0 ? 0 : next >= 10 ? 1 : 2;
|
|
1077
|
+
return `${next.toFixed(precision)} ${units[unitIndex]}`;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
function formatDashboardDuration(seconds) {
|
|
1081
|
+
const totalSeconds = Math.max(0, Math.floor(Number(seconds || 0)));
|
|
1082
|
+
const days = Math.floor(totalSeconds / 86400);
|
|
1083
|
+
const hours = Math.floor((totalSeconds % 86400) / 3600);
|
|
1084
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
1085
|
+
if (days > 0) {
|
|
1086
|
+
return `${days}d ${hours}h`;
|
|
1087
|
+
}
|
|
1088
|
+
if (hours > 0) {
|
|
1089
|
+
return `${hours}h ${minutes}m`;
|
|
1090
|
+
}
|
|
1091
|
+
return `${minutes}m`;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
function getDashboardNetworkInfo() {
|
|
1095
|
+
const rows = [];
|
|
1096
|
+
const interfaces = os.networkInterfaces();
|
|
1097
|
+
for (const [name, addresses] of Object.entries(interfaces)) {
|
|
1098
|
+
for (const address of addresses || []) {
|
|
1099
|
+
if (!address || address.internal) {
|
|
1100
|
+
continue;
|
|
1101
|
+
}
|
|
1102
|
+
rows.push({
|
|
1103
|
+
name,
|
|
1104
|
+
family: address.family,
|
|
1105
|
+
address: address.address,
|
|
1106
|
+
cidr: address.cidr || ''
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
return rows.slice(0, 8);
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
function getDashboardDiskInfo() {
|
|
1114
|
+
try {
|
|
1115
|
+
const stats = statfsSync(os.homedir());
|
|
1116
|
+
const totalBytes = Number(stats.blocks || 0) * Number(stats.bsize || 0);
|
|
1117
|
+
const freeBytes = Number(stats.bavail || stats.bfree || 0) * Number(stats.bsize || 0);
|
|
1118
|
+
const usedBytes = Math.max(0, totalBytes - freeBytes);
|
|
1119
|
+
return {
|
|
1120
|
+
path: os.homedir(),
|
|
1121
|
+
total: formatDashboardBytes(totalBytes),
|
|
1122
|
+
free: formatDashboardBytes(freeBytes),
|
|
1123
|
+
used: formatDashboardBytes(usedBytes),
|
|
1124
|
+
usedPercent: totalBytes > 0 ? Math.round(usedBytes * 100 / totalBytes) : 0
|
|
1125
|
+
};
|
|
1126
|
+
} catch {
|
|
1127
|
+
return {
|
|
1128
|
+
path: os.homedir(),
|
|
1129
|
+
total: '-',
|
|
1130
|
+
free: '-',
|
|
1131
|
+
used: '-',
|
|
1132
|
+
usedPercent: 0
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
function getDashboardUsername() {
|
|
1138
|
+
try {
|
|
1139
|
+
return os.userInfo().username || process.env.USERNAME || process.env.USER || '';
|
|
1140
|
+
} catch {
|
|
1141
|
+
return process.env.USERNAME || process.env.USER || '';
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
function getDashboardSystemInfo(state = {}) {
|
|
1146
|
+
const totalMem = os.totalmem();
|
|
1147
|
+
const freeMem = os.freemem();
|
|
1148
|
+
const usedMem = Math.max(0, totalMem - freeMem);
|
|
1149
|
+
const cpus = os.cpus();
|
|
1150
|
+
const load = os.loadavg().map(value => Number.isFinite(value) ? Number(value.toFixed(2)) : 0);
|
|
1151
|
+
const fastRuntime = getFastRuntime();
|
|
1152
|
+
const fastPackaged = fastRuntime
|
|
1153
|
+
? (hasFastExecutable(fastRuntime) || hasFastDll(fastRuntime))
|
|
1154
|
+
: false;
|
|
1155
|
+
const agent = state.agent || {};
|
|
1156
|
+
return {
|
|
1157
|
+
hostname: os.hostname(),
|
|
1158
|
+
platform: os.platform(),
|
|
1159
|
+
release: os.release(),
|
|
1160
|
+
arch: os.arch(),
|
|
1161
|
+
type: os.type(),
|
|
1162
|
+
user: getDashboardUsername(),
|
|
1163
|
+
home: os.homedir(),
|
|
1164
|
+
cwd: process.cwd(),
|
|
1165
|
+
node: process.version,
|
|
1166
|
+
packageVersion: readPackageVersion(),
|
|
1167
|
+
pid: process.pid,
|
|
1168
|
+
processUptime: formatDashboardDuration(process.uptime()),
|
|
1169
|
+
systemUptime: formatDashboardDuration(os.uptime()),
|
|
1170
|
+
cpuModel: cpus[0]?.model || '-',
|
|
1171
|
+
cpuCores: cpus.length,
|
|
1172
|
+
loadAverage: load.join(' / '),
|
|
1173
|
+
memory: {
|
|
1174
|
+
total: formatDashboardBytes(totalMem),
|
|
1175
|
+
free: formatDashboardBytes(freeMem),
|
|
1176
|
+
used: formatDashboardBytes(usedMem),
|
|
1177
|
+
usedPercent: totalMem > 0 ? Math.round(usedMem * 100 / totalMem) : 0
|
|
1178
|
+
},
|
|
1179
|
+
disk: getDashboardDiskInfo(),
|
|
1180
|
+
network: getDashboardNetworkInfo(),
|
|
1181
|
+
runtime: {
|
|
1182
|
+
requestedEngine: agent.requestedEngine || '',
|
|
1183
|
+
activeEngine: agent.engine || 'pending',
|
|
1184
|
+
state: agent.state || 'waiting',
|
|
1185
|
+
pid: agent.pid || '',
|
|
1186
|
+
command: agent.command || '',
|
|
1187
|
+
args: Array.isArray(agent.args) ? agent.args.join(' ') : '',
|
|
1188
|
+
runtimeId: agent.runtimeId || fastRuntime?.rid || '',
|
|
1189
|
+
fastPackaged,
|
|
1190
|
+
startedAt: agent.startedAt || ''
|
|
1191
|
+
}
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1038
1195
|
function renderConnectionDashboardPage(state = {}) {
|
|
1039
1196
|
const choice = state.choice || null;
|
|
1040
1197
|
const connectedAt = state.connectedAt || new Date().toISOString();
|
|
1041
1198
|
const authLabel = getDashboardAuthLabel(choice, state.loggedOut);
|
|
1042
1199
|
const manager = state.manager || (choice?.manager || '');
|
|
1200
|
+
const agentState = state.agent?.state || '';
|
|
1043
1201
|
const slot = normalizeSlotNumber(state.slot) ? `Slot ${String(state.slot).padStart(3, '0')}` : 'First available';
|
|
1044
1202
|
const statusLabel = state.loggedOut
|
|
1045
1203
|
? 'Signed out for next restart'
|
|
1046
1204
|
: manager
|
|
1047
|
-
? 'Client starting'
|
|
1205
|
+
? agentState === 'running' ? 'Agent running' : 'Client starting'
|
|
1048
1206
|
: 'Finding Hub';
|
|
1049
1207
|
const message = state.message || (manager
|
|
1050
1208
|
? 'The LiveDesk client is running from the terminal. Keep this tab open as the local client dashboard.'
|
|
@@ -1057,7 +1215,8 @@ function renderConnectionDashboardPage(state = {}) {
|
|
|
1057
1215
|
slot,
|
|
1058
1216
|
startup: Boolean(state.startup),
|
|
1059
1217
|
completed: true,
|
|
1060
|
-
statusLabel
|
|
1218
|
+
statusLabel,
|
|
1219
|
+
system: getDashboardSystemInfo(state)
|
|
1061
1220
|
}).replaceAll('<', '\\u003c');
|
|
1062
1221
|
|
|
1063
1222
|
return `<!doctype html>
|
|
@@ -1073,22 +1232,24 @@ function renderConnectionDashboardPage(state = {}) {
|
|
|
1073
1232
|
body {
|
|
1074
1233
|
min-height: 100vh;
|
|
1075
1234
|
display: grid;
|
|
1076
|
-
|
|
1077
|
-
padding:
|
|
1235
|
+
align-items: stretch;
|
|
1236
|
+
padding: 18px;
|
|
1078
1237
|
background:
|
|
1079
|
-
radial-gradient(circle at
|
|
1238
|
+
radial-gradient(circle at 84% 0%, rgba(203, 213, 225, 0.36), transparent 26%),
|
|
1080
1239
|
linear-gradient(180deg, #f8fafc, #eef2f7);
|
|
1081
1240
|
color: #111827;
|
|
1082
1241
|
}
|
|
1083
1242
|
main {
|
|
1084
|
-
width: min(
|
|
1243
|
+
width: min(1280px, 100%);
|
|
1244
|
+
min-height: calc(100vh - 36px);
|
|
1245
|
+
margin: 0 auto;
|
|
1085
1246
|
display: grid;
|
|
1086
|
-
grid-template-columns: minmax(
|
|
1087
|
-
overflow: hidden;
|
|
1247
|
+
grid-template-columns: minmax(286px, 0.74fr) minmax(640px, 1.26fr);
|
|
1088
1248
|
border: 1px solid rgba(148, 163, 184, 0.28);
|
|
1089
|
-
border-radius:
|
|
1249
|
+
border-radius: 20px;
|
|
1090
1250
|
background: rgba(255, 255, 255, 0.92);
|
|
1091
1251
|
box-shadow: 0 36px 110px rgba(15, 23, 42, 0.14);
|
|
1252
|
+
overflow: hidden;
|
|
1092
1253
|
}
|
|
1093
1254
|
.hero {
|
|
1094
1255
|
display: grid;
|
|
@@ -1144,10 +1305,14 @@ function renderConnectionDashboardPage(state = {}) {
|
|
|
1144
1305
|
}
|
|
1145
1306
|
.panel {
|
|
1146
1307
|
display: grid;
|
|
1147
|
-
|
|
1148
|
-
|
|
1308
|
+
align-content: start;
|
|
1309
|
+
gap: 14px;
|
|
1310
|
+
padding: 24px;
|
|
1311
|
+
background:
|
|
1312
|
+
linear-gradient(180deg, rgba(248, 250, 252, 0.78), rgba(241, 245, 249, 0.72)),
|
|
1313
|
+
#f8fafc;
|
|
1149
1314
|
}
|
|
1150
|
-
.status-card {
|
|
1315
|
+
.status-card, .section-card {
|
|
1151
1316
|
display: grid;
|
|
1152
1317
|
gap: 14px;
|
|
1153
1318
|
padding: 18px;
|
|
@@ -1158,6 +1323,7 @@ function renderConnectionDashboardPage(state = {}) {
|
|
|
1158
1323
|
0 20px 50px rgba(15, 23, 42, 0.09),
|
|
1159
1324
|
0 1px 0 rgba(255, 255, 255, 0.9) inset;
|
|
1160
1325
|
}
|
|
1326
|
+
.section-card { gap: 12px; }
|
|
1161
1327
|
.status-head {
|
|
1162
1328
|
display: flex;
|
|
1163
1329
|
align-items: center;
|
|
@@ -1191,6 +1357,17 @@ function renderConnectionDashboardPage(state = {}) {
|
|
|
1191
1357
|
font-size: 20px;
|
|
1192
1358
|
line-height: 1.18;
|
|
1193
1359
|
}
|
|
1360
|
+
.section-title {
|
|
1361
|
+
display: flex;
|
|
1362
|
+
align-items: center;
|
|
1363
|
+
justify-content: space-between;
|
|
1364
|
+
gap: 12px;
|
|
1365
|
+
color: #0f172a;
|
|
1366
|
+
font-size: 12px;
|
|
1367
|
+
font-weight: 850;
|
|
1368
|
+
letter-spacing: 0.04em;
|
|
1369
|
+
text-transform: uppercase;
|
|
1370
|
+
}
|
|
1194
1371
|
dl {
|
|
1195
1372
|
display: grid;
|
|
1196
1373
|
grid-template-columns: 118px minmax(0, 1fr);
|
|
@@ -1213,6 +1390,91 @@ function renderConnectionDashboardPage(state = {}) {
|
|
|
1213
1390
|
text-overflow: ellipsis;
|
|
1214
1391
|
white-space: nowrap;
|
|
1215
1392
|
}
|
|
1393
|
+
.hero-metrics, .metric-grid {
|
|
1394
|
+
display: grid;
|
|
1395
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
1396
|
+
gap: 10px;
|
|
1397
|
+
}
|
|
1398
|
+
.hero-metric, .metric-card {
|
|
1399
|
+
display: grid;
|
|
1400
|
+
gap: 6px;
|
|
1401
|
+
min-height: 68px;
|
|
1402
|
+
padding: 12px;
|
|
1403
|
+
border: 1px solid rgba(148, 163, 184, 0.24);
|
|
1404
|
+
border-radius: 12px;
|
|
1405
|
+
background: rgba(255, 255, 255, 0.78);
|
|
1406
|
+
box-shadow: 0 12px 30px rgba(15, 23, 42, 0.06);
|
|
1407
|
+
}
|
|
1408
|
+
.hero-metric span, .metric-card span {
|
|
1409
|
+
color: #64748b;
|
|
1410
|
+
font-size: 11px;
|
|
1411
|
+
font-weight: 780;
|
|
1412
|
+
letter-spacing: 0.04em;
|
|
1413
|
+
text-transform: uppercase;
|
|
1414
|
+
}
|
|
1415
|
+
.hero-metric strong, .metric-card strong {
|
|
1416
|
+
min-width: 0;
|
|
1417
|
+
overflow: hidden;
|
|
1418
|
+
color: #0f172a;
|
|
1419
|
+
font-size: 18px;
|
|
1420
|
+
font-weight: 780;
|
|
1421
|
+
text-overflow: ellipsis;
|
|
1422
|
+
white-space: nowrap;
|
|
1423
|
+
}
|
|
1424
|
+
.progress {
|
|
1425
|
+
width: 100%;
|
|
1426
|
+
height: 4px;
|
|
1427
|
+
overflow: hidden;
|
|
1428
|
+
border-radius: 999px;
|
|
1429
|
+
background: #e2e8f0;
|
|
1430
|
+
}
|
|
1431
|
+
.progress i {
|
|
1432
|
+
display: block;
|
|
1433
|
+
width: 0%;
|
|
1434
|
+
height: 100%;
|
|
1435
|
+
border-radius: inherit;
|
|
1436
|
+
background: #111827;
|
|
1437
|
+
transition: width 180ms ease;
|
|
1438
|
+
}
|
|
1439
|
+
.info-grid {
|
|
1440
|
+
display: grid;
|
|
1441
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
1442
|
+
gap: 9px 14px;
|
|
1443
|
+
}
|
|
1444
|
+
.info-item {
|
|
1445
|
+
min-width: 0;
|
|
1446
|
+
display: grid;
|
|
1447
|
+
gap: 3px;
|
|
1448
|
+
}
|
|
1449
|
+
.info-item span, .network-row span {
|
|
1450
|
+
color: #64748b;
|
|
1451
|
+
font-size: 11px;
|
|
1452
|
+
font-weight: 760;
|
|
1453
|
+
}
|
|
1454
|
+
.info-item strong, .network-row strong {
|
|
1455
|
+
min-width: 0;
|
|
1456
|
+
overflow: hidden;
|
|
1457
|
+
color: #111827;
|
|
1458
|
+
font-size: 13px;
|
|
1459
|
+
font-weight: 720;
|
|
1460
|
+
text-overflow: ellipsis;
|
|
1461
|
+
white-space: nowrap;
|
|
1462
|
+
}
|
|
1463
|
+
.network-list {
|
|
1464
|
+
display: grid;
|
|
1465
|
+
gap: 7px;
|
|
1466
|
+
}
|
|
1467
|
+
.network-row {
|
|
1468
|
+
display: grid;
|
|
1469
|
+
grid-template-columns: minmax(88px, 0.45fr) minmax(0, 1fr);
|
|
1470
|
+
gap: 12px;
|
|
1471
|
+
min-height: 32px;
|
|
1472
|
+
align-items: center;
|
|
1473
|
+
padding: 8px 10px;
|
|
1474
|
+
border: 1px solid rgba(148, 163, 184, 0.24);
|
|
1475
|
+
border-radius: 9px;
|
|
1476
|
+
background: #f8fafc;
|
|
1477
|
+
}
|
|
1216
1478
|
.actions {
|
|
1217
1479
|
display: grid;
|
|
1218
1480
|
grid-template-columns: 1fr 1fr;
|
|
@@ -1256,6 +1518,7 @@ function renderConnectionDashboardPage(state = {}) {
|
|
|
1256
1518
|
.hero, .panel { padding: 24px; }
|
|
1257
1519
|
h1 { font-size: 28px; }
|
|
1258
1520
|
.actions { grid-template-columns: 1fr; }
|
|
1521
|
+
.hero-metrics, .metric-grid, .info-grid { grid-template-columns: 1fr; }
|
|
1259
1522
|
}
|
|
1260
1523
|
</style>
|
|
1261
1524
|
</head>
|
|
@@ -1274,6 +1537,26 @@ function renderConnectionDashboardPage(state = {}) {
|
|
|
1274
1537
|
<h1>Client is ready</h1>
|
|
1275
1538
|
<p id="message">${escapeHtml(message)}</p>
|
|
1276
1539
|
</div>
|
|
1540
|
+
<div class="hero-metrics">
|
|
1541
|
+
<div class="hero-metric">
|
|
1542
|
+
<span>Host</span>
|
|
1543
|
+
<strong id="hero-host">-</strong>
|
|
1544
|
+
</div>
|
|
1545
|
+
<div class="hero-metric">
|
|
1546
|
+
<span>Engine</span>
|
|
1547
|
+
<strong id="hero-engine">pending</strong>
|
|
1548
|
+
</div>
|
|
1549
|
+
<div class="hero-metric">
|
|
1550
|
+
<span>Memory</span>
|
|
1551
|
+
<strong id="hero-memory">-</strong>
|
|
1552
|
+
<div class="progress"><i id="memory-bar"></i></div>
|
|
1553
|
+
</div>
|
|
1554
|
+
<div class="hero-metric">
|
|
1555
|
+
<span>Disk</span>
|
|
1556
|
+
<strong id="hero-disk">-</strong>
|
|
1557
|
+
<div class="progress"><i id="disk-bar"></i></div>
|
|
1558
|
+
</div>
|
|
1559
|
+
</div>
|
|
1277
1560
|
<p>Keep this page open if you want a local control surface for this workstation. Reboots can reconnect automatically when startup is enabled.</p>
|
|
1278
1561
|
</section>
|
|
1279
1562
|
<section class="panel">
|
|
@@ -1290,6 +1573,47 @@ function renderConnectionDashboardPage(state = {}) {
|
|
|
1290
1573
|
<dt>Started</dt><dd id="connected-at">${escapeHtml(connectedAt)}</dd>
|
|
1291
1574
|
</dl>
|
|
1292
1575
|
</article>
|
|
1576
|
+
<article class="section-card">
|
|
1577
|
+
<div class="section-title">System</div>
|
|
1578
|
+
<div class="metric-grid">
|
|
1579
|
+
<div class="metric-card"><span>CPU</span><strong id="cpu-model">-</strong></div>
|
|
1580
|
+
<div class="metric-card"><span>Cores</span><strong id="cpu-cores">-</strong></div>
|
|
1581
|
+
<div class="metric-card"><span>OS</span><strong id="os-line">-</strong></div>
|
|
1582
|
+
<div class="metric-card"><span>Uptime</span><strong id="system-uptime">-</strong></div>
|
|
1583
|
+
<div class="metric-card"><span>Load avg</span><strong id="load-average">-</strong></div>
|
|
1584
|
+
<div class="metric-card"><span>Architecture</span><strong id="arch-line">-</strong></div>
|
|
1585
|
+
</div>
|
|
1586
|
+
</article>
|
|
1587
|
+
<article class="section-card">
|
|
1588
|
+
<div class="section-title">Runtime</div>
|
|
1589
|
+
<div class="info-grid">
|
|
1590
|
+
<div class="info-item"><span>Agent state</span><strong id="agent-state">waiting</strong></div>
|
|
1591
|
+
<div class="info-item"><span>Agent PID</span><strong id="agent-pid">-</strong></div>
|
|
1592
|
+
<div class="info-item"><span>Runtime</span><strong id="runtime-id">-</strong></div>
|
|
1593
|
+
<div class="info-item"><span>Fast package</span><strong id="fast-packaged">-</strong></div>
|
|
1594
|
+
<div class="info-item"><span>Client PID</span><strong id="client-pid">-</strong></div>
|
|
1595
|
+
<div class="info-item"><span>Node</span><strong id="node-version">-</strong></div>
|
|
1596
|
+
<div class="info-item"><span>Package</span><strong id="package-version">-</strong></div>
|
|
1597
|
+
<div class="info-item"><span>Process uptime</span><strong id="process-uptime">-</strong></div>
|
|
1598
|
+
<div class="info-item"><span>Agent started</span><strong id="agent-started">-</strong></div>
|
|
1599
|
+
</div>
|
|
1600
|
+
</article>
|
|
1601
|
+
<article class="section-card">
|
|
1602
|
+
<div class="section-title">Storage & Paths</div>
|
|
1603
|
+
<div class="info-grid">
|
|
1604
|
+
<div class="info-item"><span>Disk used</span><strong id="disk-used">-</strong></div>
|
|
1605
|
+
<div class="info-item"><span>Disk free</span><strong id="disk-free">-</strong></div>
|
|
1606
|
+
<div class="info-item"><span>User</span><strong id="user-name">-</strong></div>
|
|
1607
|
+
<div class="info-item"><span>Home</span><strong id="home-path">-</strong></div>
|
|
1608
|
+
<div class="info-item"><span>Working dir</span><strong id="cwd-path">-</strong></div>
|
|
1609
|
+
<div class="info-item"><span>Agent command</span><strong id="agent-command">-</strong></div>
|
|
1610
|
+
<div class="info-item"><span>Agent args</span><strong id="agent-args">-</strong></div>
|
|
1611
|
+
</div>
|
|
1612
|
+
</article>
|
|
1613
|
+
<article class="section-card">
|
|
1614
|
+
<div class="section-title">Network</div>
|
|
1615
|
+
<div id="network-list" class="network-list"></div>
|
|
1616
|
+
</article>
|
|
1293
1617
|
<div class="actions">
|
|
1294
1618
|
<a class="button secondary" href="/">Refresh dashboard</a>
|
|
1295
1619
|
<form method="post" action="/logout">
|
|
@@ -1305,7 +1629,33 @@ function renderConnectionDashboardPage(state = {}) {
|
|
|
1305
1629
|
const node = document.getElementById(id);
|
|
1306
1630
|
if (node) node.textContent = value || '';
|
|
1307
1631
|
}
|
|
1632
|
+
function setBar(id, value) {
|
|
1633
|
+
const node = document.getElementById(id);
|
|
1634
|
+
if (!node) return;
|
|
1635
|
+
const percent = Math.max(0, Math.min(100, Number(value) || 0));
|
|
1636
|
+
node.style.width = percent + '%';
|
|
1637
|
+
}
|
|
1638
|
+
function renderNetworkRows(items) {
|
|
1639
|
+
const list = document.getElementById('network-list');
|
|
1640
|
+
if (!list) return;
|
|
1641
|
+
list.textContent = '';
|
|
1642
|
+
const rows = Array.isArray(items) && items.length > 0 ? items : [{ name: 'Network', address: 'not reported', family: '' }];
|
|
1643
|
+
for (const item of rows) {
|
|
1644
|
+
const row = document.createElement('div');
|
|
1645
|
+
row.className = 'network-row';
|
|
1646
|
+
const name = document.createElement('span');
|
|
1647
|
+
name.textContent = [item.name, item.family].filter(Boolean).join(' / ');
|
|
1648
|
+
const address = document.createElement('strong');
|
|
1649
|
+
address.textContent = item.cidr || item.address || 'not reported';
|
|
1650
|
+
row.append(name, address);
|
|
1651
|
+
list.append(row);
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1308
1654
|
function applyState(state) {
|
|
1655
|
+
const system = state.system || {};
|
|
1656
|
+
const memory = system.memory || {};
|
|
1657
|
+
const disk = system.disk || {};
|
|
1658
|
+
const runtime = system.runtime || {};
|
|
1309
1659
|
setText('status', state.statusLabel || initialState.statusLabel);
|
|
1310
1660
|
setText('auth', state.authLabel || initialState.authLabel);
|
|
1311
1661
|
setText('manager', state.manager || 'waiting');
|
|
@@ -1313,6 +1663,35 @@ function renderConnectionDashboardPage(state = {}) {
|
|
|
1313
1663
|
setText('startup', state.startup ? 'enabled' : 'manual');
|
|
1314
1664
|
setText('connected-at', state.connectedAt || initialState.connectedAt);
|
|
1315
1665
|
setText('message', state.message || initialState.message);
|
|
1666
|
+
setText('hero-host', system.hostname || '-');
|
|
1667
|
+
setText('hero-engine', runtime.activeEngine || runtime.state || 'pending');
|
|
1668
|
+
setText('hero-memory', memory.used && memory.total ? memory.used + ' / ' + memory.total : '-');
|
|
1669
|
+
setText('hero-disk', disk.usedPercent ? disk.usedPercent + '% used' : disk.free ? disk.free + ' free' : '-');
|
|
1670
|
+
setBar('memory-bar', memory.usedPercent);
|
|
1671
|
+
setBar('disk-bar', disk.usedPercent);
|
|
1672
|
+
setText('cpu-model', system.cpuModel || '-');
|
|
1673
|
+
setText('cpu-cores', system.cpuCores ? String(system.cpuCores) : '-');
|
|
1674
|
+
setText('os-line', [system.type || system.platform, system.release, system.arch].filter(Boolean).join(' '));
|
|
1675
|
+
setText('system-uptime', system.systemUptime || '-');
|
|
1676
|
+
setText('load-average', system.loadAverage || '-');
|
|
1677
|
+
setText('arch-line', [system.platform, system.arch].filter(Boolean).join(' / '));
|
|
1678
|
+
setText('agent-state', runtime.state || 'waiting');
|
|
1679
|
+
setText('agent-pid', runtime.pid ? String(runtime.pid) : '-');
|
|
1680
|
+
setText('runtime-id', runtime.runtimeId || runtime.requestedEngine || '-');
|
|
1681
|
+
setText('fast-packaged', runtime.fastPackaged ? 'available' : 'missing');
|
|
1682
|
+
setText('client-pid', system.pid ? String(system.pid) : '-');
|
|
1683
|
+
setText('node-version', system.node || '-');
|
|
1684
|
+
setText('package-version', system.packageVersion || '-');
|
|
1685
|
+
setText('process-uptime', system.processUptime || '-');
|
|
1686
|
+
setText('agent-started', runtime.startedAt || '-');
|
|
1687
|
+
setText('disk-used', disk.used && disk.total ? disk.used + ' / ' + disk.total : '-');
|
|
1688
|
+
setText('disk-free', disk.free || '-');
|
|
1689
|
+
setText('user-name', system.user || '-');
|
|
1690
|
+
setText('home-path', system.home || '-');
|
|
1691
|
+
setText('cwd-path', system.cwd || '-');
|
|
1692
|
+
setText('agent-command', runtime.command || '-');
|
|
1693
|
+
setText('agent-args', runtime.args || '-');
|
|
1694
|
+
renderNetworkRows(system.network);
|
|
1316
1695
|
}
|
|
1317
1696
|
applyState(initialState);
|
|
1318
1697
|
if (location.pathname === '/callback') {
|
|
@@ -1458,6 +1837,10 @@ async function startConnectionChoiceServer(supabase, options = {}) {
|
|
|
1458
1837
|
let completedTitle = 'LiveDesk connection complete';
|
|
1459
1838
|
let completedMessage = 'The LiveDesk client is starting automatically.';
|
|
1460
1839
|
const dashboardState = {
|
|
1840
|
+
agent: {
|
|
1841
|
+
requestedEngine: options.engine || '',
|
|
1842
|
+
state: 'waiting'
|
|
1843
|
+
},
|
|
1461
1844
|
choice: null,
|
|
1462
1845
|
connectedAt: '',
|
|
1463
1846
|
endpointCandidates: [],
|
|
@@ -1506,6 +1889,7 @@ async function startConnectionChoiceServer(supabase, options = {}) {
|
|
|
1506
1889
|
});
|
|
1507
1890
|
const getDashboardApiState = () => {
|
|
1508
1891
|
const manager = dashboardState.manager || dashboardState.choice?.manager || '';
|
|
1892
|
+
const agentState = dashboardState.agent?.state || '';
|
|
1509
1893
|
return {
|
|
1510
1894
|
authLabel: getDashboardAuthLabel(dashboardState.choice, dashboardState.loggedOut),
|
|
1511
1895
|
connectedAt: dashboardState.connectedAt || '',
|
|
@@ -1517,10 +1901,11 @@ async function startConnectionChoiceServer(supabase, options = {}) {
|
|
|
1517
1901
|
statusLabel: dashboardState.loggedOut
|
|
1518
1902
|
? 'Signed out for next restart'
|
|
1519
1903
|
: manager
|
|
1520
|
-
? 'Client starting'
|
|
1904
|
+
? agentState === 'running' ? 'Agent running' : 'Client starting'
|
|
1521
1905
|
: completed
|
|
1522
1906
|
? 'Finding Hub'
|
|
1523
|
-
: 'Waiting for auth'
|
|
1907
|
+
: 'Waiting for auth',
|
|
1908
|
+
system: getDashboardSystemInfo(dashboardState)
|
|
1524
1909
|
};
|
|
1525
1910
|
};
|
|
1526
1911
|
|
|
@@ -1588,7 +1973,7 @@ async function startConnectionChoiceServer(supabase, options = {}) {
|
|
|
1588
1973
|
await supabase.auth.signOut();
|
|
1589
1974
|
} catch {
|
|
1590
1975
|
}
|
|
1591
|
-
|
|
1976
|
+
clearSavedSession();
|
|
1592
1977
|
clearSavedPin();
|
|
1593
1978
|
dashboardState.choice = null;
|
|
1594
1979
|
dashboardState.loggedOut = true;
|
|
@@ -1621,11 +2006,22 @@ async function startConnectionChoiceServer(supabase, options = {}) {
|
|
|
1621
2006
|
|
|
1622
2007
|
if (requestUrl.pathname === '/google') {
|
|
1623
2008
|
pendingStartup = normalizeStartupChoice(requestUrl.searchParams.get('startup'), pendingStartup);
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
2009
|
+
let existingSession = null;
|
|
2010
|
+
try {
|
|
2011
|
+
const { data: existing } = await supabase.auth.getSession();
|
|
2012
|
+
existingSession = existing?.session || null;
|
|
2013
|
+
} catch (err) {
|
|
2014
|
+
if (isInvalidRefreshTokenError(err)) {
|
|
2015
|
+
clearSavedSession();
|
|
2016
|
+
} else {
|
|
2017
|
+
handleError(res, err);
|
|
2018
|
+
return;
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
if (existingSession?.access_token) {
|
|
2022
|
+
writeSavedSessionToFile(existingSession);
|
|
1627
2023
|
applyStartupPreference(pendingStartup, startupArgs);
|
|
1628
|
-
const choice = { type: 'google', session:
|
|
2024
|
+
const choice = { type: 'google', session: existingSession };
|
|
1629
2025
|
complete(choice, 'LiveDesk client dashboard', 'Signed in. The client is starting automatically.');
|
|
1630
2026
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
1631
2027
|
res.end(renderDashboard());
|
|
@@ -1789,6 +2185,7 @@ async function chooseClientConnection(supabase, options = {}) {
|
|
|
1789
2185
|
const connectionPage = await startConnectionChoiceServer(supabase, {
|
|
1790
2186
|
authPort: options.authPort,
|
|
1791
2187
|
autoGoogle: options.autoGoogle,
|
|
2188
|
+
engine: options.engine,
|
|
1792
2189
|
slot: options.slot,
|
|
1793
2190
|
startupArgs: options.startupArgs,
|
|
1794
2191
|
savedSession: options.savedSession,
|
|
@@ -1944,6 +2341,7 @@ async function prepareLoginConnection(parsed) {
|
|
|
1944
2341
|
choice = await chooseClientConnection(supabase, {
|
|
1945
2342
|
authPort: parsed.authPort,
|
|
1946
2343
|
autoGoogle: !savedSession?.access_token && !savedPin,
|
|
2344
|
+
engine: parsed.engine,
|
|
1947
2345
|
slot: parsed.slot,
|
|
1948
2346
|
startupArgs,
|
|
1949
2347
|
savedSession,
|
|
@@ -2169,6 +2567,27 @@ function buildFastArgs(args, fakeThumbnail) {
|
|
|
2169
2567
|
return forwarded;
|
|
2170
2568
|
}
|
|
2171
2569
|
|
|
2570
|
+
function redactAgentArgs(args = []) {
|
|
2571
|
+
const redacted = [];
|
|
2572
|
+
const secretFlags = new Set(['--pair', '--token', '--key']);
|
|
2573
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
2574
|
+
const arg = String(args[index] || '');
|
|
2575
|
+
const equalsIndex = arg.indexOf('=');
|
|
2576
|
+
if (equalsIndex > 0 && secretFlags.has(arg.slice(0, equalsIndex))) {
|
|
2577
|
+
redacted.push(`${arg.slice(0, equalsIndex)}=<hidden>`);
|
|
2578
|
+
continue;
|
|
2579
|
+
}
|
|
2580
|
+
redacted.push(arg);
|
|
2581
|
+
if (secretFlags.has(arg)) {
|
|
2582
|
+
if (index + 1 < args.length) {
|
|
2583
|
+
redacted.push('<hidden>');
|
|
2584
|
+
index += 1;
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
return redacted;
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2172
2591
|
function resolveFastLaunch(runtime) {
|
|
2173
2592
|
if (!runtime) {
|
|
2174
2593
|
return {
|
|
@@ -2217,12 +2636,15 @@ function resolveFastLaunch(runtime) {
|
|
|
2217
2636
|
};
|
|
2218
2637
|
}
|
|
2219
2638
|
|
|
2220
|
-
function spawnAgent(command, args, env = process.env) {
|
|
2639
|
+
function spawnAgent(command, args, env = process.env, onStart = null) {
|
|
2221
2640
|
const child = spawn(command, args, {
|
|
2222
2641
|
env,
|
|
2223
2642
|
stdio: 'inherit',
|
|
2224
2643
|
windowsHide: true
|
|
2225
2644
|
});
|
|
2645
|
+
if (typeof onStart === 'function') {
|
|
2646
|
+
onStart(child);
|
|
2647
|
+
}
|
|
2226
2648
|
|
|
2227
2649
|
return new Promise(resolve => {
|
|
2228
2650
|
child.once('error', error => {
|
|
@@ -2267,22 +2689,88 @@ async function main() {
|
|
|
2267
2689
|
const prepared = await prepareLoginConnection(parsed);
|
|
2268
2690
|
const fastRuntime = getFastRuntime();
|
|
2269
2691
|
const useFast = shouldTryFast(prepared, fastRuntime);
|
|
2692
|
+
const updateAgentDashboard = (patch = {}) => {
|
|
2693
|
+
prepared.connectionPage?.update({
|
|
2694
|
+
agent: {
|
|
2695
|
+
requestedEngine: prepared.engine,
|
|
2696
|
+
state: 'launching',
|
|
2697
|
+
...patch
|
|
2698
|
+
},
|
|
2699
|
+
message: patch.state === 'running'
|
|
2700
|
+
? `LiveDesk client agent is running with ${patch.engine || 'agent'} mode.`
|
|
2701
|
+
: 'LiveDesk client agent is launching.'
|
|
2702
|
+
});
|
|
2703
|
+
};
|
|
2270
2704
|
let result;
|
|
2271
2705
|
if (useFast) {
|
|
2272
2706
|
const fastArgs = buildFastArgs(prepared.forwarded, prepared.fakeThumbnail);
|
|
2273
2707
|
const fastLaunch = resolveFastLaunch(fastRuntime);
|
|
2274
2708
|
if (fastLaunch.ok) {
|
|
2275
|
-
|
|
2709
|
+
const launchArgs = [...fastLaunch.argsPrefix, ...fastArgs];
|
|
2710
|
+
updateAgentDashboard({
|
|
2711
|
+
engine: 'fast',
|
|
2712
|
+
command: fastLaunch.command,
|
|
2713
|
+
args: redactAgentArgs(launchArgs),
|
|
2714
|
+
runtimeId: fastRuntime?.rid || '',
|
|
2715
|
+
state: 'launching'
|
|
2716
|
+
});
|
|
2717
|
+
result = await spawnAgent(fastLaunch.command, launchArgs, buildFastEnvironment(), child => {
|
|
2718
|
+
updateAgentDashboard({
|
|
2719
|
+
engine: 'fast',
|
|
2720
|
+
command: fastLaunch.command,
|
|
2721
|
+
args: redactAgentArgs(launchArgs),
|
|
2722
|
+
runtimeId: fastRuntime?.rid || '',
|
|
2723
|
+
pid: child.pid,
|
|
2724
|
+
startedAt: new Date().toISOString(),
|
|
2725
|
+
state: 'running'
|
|
2726
|
+
});
|
|
2727
|
+
});
|
|
2276
2728
|
} else if (prepared.engine === 'fast') {
|
|
2277
2729
|
console.error(`C# RemoteFast is unavailable: ${fastLaunch.reason}. Use --engine node to run the legacy Node agent.`);
|
|
2278
2730
|
prepared.connectionPage?.close?.();
|
|
2279
2731
|
process.exit(2);
|
|
2280
2732
|
} else {
|
|
2281
2733
|
console.warn(`C# RemoteFast is unavailable (${fastLaunch.reason}). Falling back to the Node remote agent.`);
|
|
2282
|
-
|
|
2734
|
+
const launchArgs = [nodeAgentPath, ...prepared.forwarded];
|
|
2735
|
+
updateAgentDashboard({
|
|
2736
|
+
engine: 'node',
|
|
2737
|
+
command: process.execPath,
|
|
2738
|
+
args: redactAgentArgs(launchArgs),
|
|
2739
|
+
runtimeId: 'node-fallback',
|
|
2740
|
+
state: 'launching'
|
|
2741
|
+
});
|
|
2742
|
+
result = await spawnAgent(process.execPath, launchArgs, process.env, child => {
|
|
2743
|
+
updateAgentDashboard({
|
|
2744
|
+
engine: 'node',
|
|
2745
|
+
command: process.execPath,
|
|
2746
|
+
args: redactAgentArgs(launchArgs),
|
|
2747
|
+
runtimeId: 'node-fallback',
|
|
2748
|
+
pid: child.pid,
|
|
2749
|
+
startedAt: new Date().toISOString(),
|
|
2750
|
+
state: 'running'
|
|
2751
|
+
});
|
|
2752
|
+
});
|
|
2283
2753
|
}
|
|
2284
2754
|
} else {
|
|
2285
|
-
|
|
2755
|
+
const launchArgs = [nodeAgentPath, ...prepared.forwarded];
|
|
2756
|
+
updateAgentDashboard({
|
|
2757
|
+
engine: 'node',
|
|
2758
|
+
command: process.execPath,
|
|
2759
|
+
args: redactAgentArgs(launchArgs),
|
|
2760
|
+
runtimeId: 'node',
|
|
2761
|
+
state: 'launching'
|
|
2762
|
+
});
|
|
2763
|
+
result = await spawnAgent(process.execPath, launchArgs, process.env, child => {
|
|
2764
|
+
updateAgentDashboard({
|
|
2765
|
+
engine: 'node',
|
|
2766
|
+
command: process.execPath,
|
|
2767
|
+
args: redactAgentArgs(launchArgs),
|
|
2768
|
+
runtimeId: 'node',
|
|
2769
|
+
pid: child.pid,
|
|
2770
|
+
startedAt: new Date().toISOString(),
|
|
2771
|
+
state: 'running'
|
|
2772
|
+
});
|
|
2773
|
+
});
|
|
2286
2774
|
}
|
|
2287
2775
|
prepared.connectionPage?.close?.();
|
|
2288
2776
|
if (result?.signal) {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|