@livedesk/client 0.1.63 → 0.1.64
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 +470 -19
- 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';
|
|
@@ -1035,16 +1035,148 @@ function getDashboardAuthLabel(choice, loggedOut = false) {
|
|
|
1035
1035
|
return email ? `Google - ${email}` : 'Google signed in';
|
|
1036
1036
|
}
|
|
1037
1037
|
|
|
1038
|
+
function formatDashboardBytes(value) {
|
|
1039
|
+
const bytes = Number(value || 0);
|
|
1040
|
+
if (!Number.isFinite(bytes) || bytes <= 0) {
|
|
1041
|
+
return '-';
|
|
1042
|
+
}
|
|
1043
|
+
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
1044
|
+
let next = bytes;
|
|
1045
|
+
let unitIndex = 0;
|
|
1046
|
+
while (next >= 1024 && unitIndex < units.length - 1) {
|
|
1047
|
+
next /= 1024;
|
|
1048
|
+
unitIndex += 1;
|
|
1049
|
+
}
|
|
1050
|
+
const precision = next >= 100 || unitIndex === 0 ? 0 : next >= 10 ? 1 : 2;
|
|
1051
|
+
return `${next.toFixed(precision)} ${units[unitIndex]}`;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
function formatDashboardDuration(seconds) {
|
|
1055
|
+
const totalSeconds = Math.max(0, Math.floor(Number(seconds || 0)));
|
|
1056
|
+
const days = Math.floor(totalSeconds / 86400);
|
|
1057
|
+
const hours = Math.floor((totalSeconds % 86400) / 3600);
|
|
1058
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
1059
|
+
if (days > 0) {
|
|
1060
|
+
return `${days}d ${hours}h`;
|
|
1061
|
+
}
|
|
1062
|
+
if (hours > 0) {
|
|
1063
|
+
return `${hours}h ${minutes}m`;
|
|
1064
|
+
}
|
|
1065
|
+
return `${minutes}m`;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
function getDashboardNetworkInfo() {
|
|
1069
|
+
const rows = [];
|
|
1070
|
+
const interfaces = os.networkInterfaces();
|
|
1071
|
+
for (const [name, addresses] of Object.entries(interfaces)) {
|
|
1072
|
+
for (const address of addresses || []) {
|
|
1073
|
+
if (!address || address.internal) {
|
|
1074
|
+
continue;
|
|
1075
|
+
}
|
|
1076
|
+
rows.push({
|
|
1077
|
+
name,
|
|
1078
|
+
family: address.family,
|
|
1079
|
+
address: address.address,
|
|
1080
|
+
cidr: address.cidr || ''
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
return rows.slice(0, 8);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
function getDashboardDiskInfo() {
|
|
1088
|
+
try {
|
|
1089
|
+
const stats = statfsSync(os.homedir());
|
|
1090
|
+
const totalBytes = Number(stats.blocks || 0) * Number(stats.bsize || 0);
|
|
1091
|
+
const freeBytes = Number(stats.bavail || stats.bfree || 0) * Number(stats.bsize || 0);
|
|
1092
|
+
const usedBytes = Math.max(0, totalBytes - freeBytes);
|
|
1093
|
+
return {
|
|
1094
|
+
path: os.homedir(),
|
|
1095
|
+
total: formatDashboardBytes(totalBytes),
|
|
1096
|
+
free: formatDashboardBytes(freeBytes),
|
|
1097
|
+
used: formatDashboardBytes(usedBytes),
|
|
1098
|
+
usedPercent: totalBytes > 0 ? Math.round(usedBytes * 100 / totalBytes) : 0
|
|
1099
|
+
};
|
|
1100
|
+
} catch {
|
|
1101
|
+
return {
|
|
1102
|
+
path: os.homedir(),
|
|
1103
|
+
total: '-',
|
|
1104
|
+
free: '-',
|
|
1105
|
+
used: '-',
|
|
1106
|
+
usedPercent: 0
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
function getDashboardUsername() {
|
|
1112
|
+
try {
|
|
1113
|
+
return os.userInfo().username || process.env.USERNAME || process.env.USER || '';
|
|
1114
|
+
} catch {
|
|
1115
|
+
return process.env.USERNAME || process.env.USER || '';
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
function getDashboardSystemInfo(state = {}) {
|
|
1120
|
+
const totalMem = os.totalmem();
|
|
1121
|
+
const freeMem = os.freemem();
|
|
1122
|
+
const usedMem = Math.max(0, totalMem - freeMem);
|
|
1123
|
+
const cpus = os.cpus();
|
|
1124
|
+
const load = os.loadavg().map(value => Number.isFinite(value) ? Number(value.toFixed(2)) : 0);
|
|
1125
|
+
const fastRuntime = getFastRuntime();
|
|
1126
|
+
const fastPackaged = fastRuntime
|
|
1127
|
+
? (hasFastExecutable(fastRuntime) || hasFastDll(fastRuntime))
|
|
1128
|
+
: false;
|
|
1129
|
+
const agent = state.agent || {};
|
|
1130
|
+
return {
|
|
1131
|
+
hostname: os.hostname(),
|
|
1132
|
+
platform: os.platform(),
|
|
1133
|
+
release: os.release(),
|
|
1134
|
+
arch: os.arch(),
|
|
1135
|
+
type: os.type(),
|
|
1136
|
+
user: getDashboardUsername(),
|
|
1137
|
+
home: os.homedir(),
|
|
1138
|
+
cwd: process.cwd(),
|
|
1139
|
+
node: process.version,
|
|
1140
|
+
packageVersion: readPackageVersion(),
|
|
1141
|
+
pid: process.pid,
|
|
1142
|
+
processUptime: formatDashboardDuration(process.uptime()),
|
|
1143
|
+
systemUptime: formatDashboardDuration(os.uptime()),
|
|
1144
|
+
cpuModel: cpus[0]?.model || '-',
|
|
1145
|
+
cpuCores: cpus.length,
|
|
1146
|
+
loadAverage: load.join(' / '),
|
|
1147
|
+
memory: {
|
|
1148
|
+
total: formatDashboardBytes(totalMem),
|
|
1149
|
+
free: formatDashboardBytes(freeMem),
|
|
1150
|
+
used: formatDashboardBytes(usedMem),
|
|
1151
|
+
usedPercent: totalMem > 0 ? Math.round(usedMem * 100 / totalMem) : 0
|
|
1152
|
+
},
|
|
1153
|
+
disk: getDashboardDiskInfo(),
|
|
1154
|
+
network: getDashboardNetworkInfo(),
|
|
1155
|
+
runtime: {
|
|
1156
|
+
requestedEngine: agent.requestedEngine || '',
|
|
1157
|
+
activeEngine: agent.engine || 'pending',
|
|
1158
|
+
state: agent.state || 'waiting',
|
|
1159
|
+
pid: agent.pid || '',
|
|
1160
|
+
command: agent.command || '',
|
|
1161
|
+
args: Array.isArray(agent.args) ? agent.args.join(' ') : '',
|
|
1162
|
+
runtimeId: agent.runtimeId || fastRuntime?.rid || '',
|
|
1163
|
+
fastPackaged,
|
|
1164
|
+
startedAt: agent.startedAt || ''
|
|
1165
|
+
}
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1038
1169
|
function renderConnectionDashboardPage(state = {}) {
|
|
1039
1170
|
const choice = state.choice || null;
|
|
1040
1171
|
const connectedAt = state.connectedAt || new Date().toISOString();
|
|
1041
1172
|
const authLabel = getDashboardAuthLabel(choice, state.loggedOut);
|
|
1042
1173
|
const manager = state.manager || (choice?.manager || '');
|
|
1174
|
+
const agentState = state.agent?.state || '';
|
|
1043
1175
|
const slot = normalizeSlotNumber(state.slot) ? `Slot ${String(state.slot).padStart(3, '0')}` : 'First available';
|
|
1044
1176
|
const statusLabel = state.loggedOut
|
|
1045
1177
|
? 'Signed out for next restart'
|
|
1046
1178
|
: manager
|
|
1047
|
-
? 'Client starting'
|
|
1179
|
+
? agentState === 'running' ? 'Agent running' : 'Client starting'
|
|
1048
1180
|
: 'Finding Hub';
|
|
1049
1181
|
const message = state.message || (manager
|
|
1050
1182
|
? 'The LiveDesk client is running from the terminal. Keep this tab open as the local client dashboard.'
|
|
@@ -1057,7 +1189,8 @@ function renderConnectionDashboardPage(state = {}) {
|
|
|
1057
1189
|
slot,
|
|
1058
1190
|
startup: Boolean(state.startup),
|
|
1059
1191
|
completed: true,
|
|
1060
|
-
statusLabel
|
|
1192
|
+
statusLabel,
|
|
1193
|
+
system: getDashboardSystemInfo(state)
|
|
1061
1194
|
}).replaceAll('<', '\\u003c');
|
|
1062
1195
|
|
|
1063
1196
|
return `<!doctype html>
|
|
@@ -1073,22 +1206,24 @@ function renderConnectionDashboardPage(state = {}) {
|
|
|
1073
1206
|
body {
|
|
1074
1207
|
min-height: 100vh;
|
|
1075
1208
|
display: grid;
|
|
1076
|
-
|
|
1077
|
-
padding:
|
|
1209
|
+
align-items: stretch;
|
|
1210
|
+
padding: 18px;
|
|
1078
1211
|
background:
|
|
1079
|
-
radial-gradient(circle at
|
|
1212
|
+
radial-gradient(circle at 84% 0%, rgba(203, 213, 225, 0.36), transparent 26%),
|
|
1080
1213
|
linear-gradient(180deg, #f8fafc, #eef2f7);
|
|
1081
1214
|
color: #111827;
|
|
1082
1215
|
}
|
|
1083
1216
|
main {
|
|
1084
|
-
width: min(
|
|
1217
|
+
width: min(1280px, 100%);
|
|
1218
|
+
min-height: calc(100vh - 36px);
|
|
1219
|
+
margin: 0 auto;
|
|
1085
1220
|
display: grid;
|
|
1086
|
-
grid-template-columns: minmax(
|
|
1087
|
-
overflow: hidden;
|
|
1221
|
+
grid-template-columns: minmax(286px, 0.74fr) minmax(640px, 1.26fr);
|
|
1088
1222
|
border: 1px solid rgba(148, 163, 184, 0.28);
|
|
1089
|
-
border-radius:
|
|
1223
|
+
border-radius: 20px;
|
|
1090
1224
|
background: rgba(255, 255, 255, 0.92);
|
|
1091
1225
|
box-shadow: 0 36px 110px rgba(15, 23, 42, 0.14);
|
|
1226
|
+
overflow: hidden;
|
|
1092
1227
|
}
|
|
1093
1228
|
.hero {
|
|
1094
1229
|
display: grid;
|
|
@@ -1144,10 +1279,14 @@ function renderConnectionDashboardPage(state = {}) {
|
|
|
1144
1279
|
}
|
|
1145
1280
|
.panel {
|
|
1146
1281
|
display: grid;
|
|
1147
|
-
|
|
1148
|
-
|
|
1282
|
+
align-content: start;
|
|
1283
|
+
gap: 14px;
|
|
1284
|
+
padding: 24px;
|
|
1285
|
+
background:
|
|
1286
|
+
linear-gradient(180deg, rgba(248, 250, 252, 0.78), rgba(241, 245, 249, 0.72)),
|
|
1287
|
+
#f8fafc;
|
|
1149
1288
|
}
|
|
1150
|
-
.status-card {
|
|
1289
|
+
.status-card, .section-card {
|
|
1151
1290
|
display: grid;
|
|
1152
1291
|
gap: 14px;
|
|
1153
1292
|
padding: 18px;
|
|
@@ -1158,6 +1297,7 @@ function renderConnectionDashboardPage(state = {}) {
|
|
|
1158
1297
|
0 20px 50px rgba(15, 23, 42, 0.09),
|
|
1159
1298
|
0 1px 0 rgba(255, 255, 255, 0.9) inset;
|
|
1160
1299
|
}
|
|
1300
|
+
.section-card { gap: 12px; }
|
|
1161
1301
|
.status-head {
|
|
1162
1302
|
display: flex;
|
|
1163
1303
|
align-items: center;
|
|
@@ -1191,6 +1331,17 @@ function renderConnectionDashboardPage(state = {}) {
|
|
|
1191
1331
|
font-size: 20px;
|
|
1192
1332
|
line-height: 1.18;
|
|
1193
1333
|
}
|
|
1334
|
+
.section-title {
|
|
1335
|
+
display: flex;
|
|
1336
|
+
align-items: center;
|
|
1337
|
+
justify-content: space-between;
|
|
1338
|
+
gap: 12px;
|
|
1339
|
+
color: #0f172a;
|
|
1340
|
+
font-size: 12px;
|
|
1341
|
+
font-weight: 850;
|
|
1342
|
+
letter-spacing: 0.04em;
|
|
1343
|
+
text-transform: uppercase;
|
|
1344
|
+
}
|
|
1194
1345
|
dl {
|
|
1195
1346
|
display: grid;
|
|
1196
1347
|
grid-template-columns: 118px minmax(0, 1fr);
|
|
@@ -1213,6 +1364,91 @@ function renderConnectionDashboardPage(state = {}) {
|
|
|
1213
1364
|
text-overflow: ellipsis;
|
|
1214
1365
|
white-space: nowrap;
|
|
1215
1366
|
}
|
|
1367
|
+
.hero-metrics, .metric-grid {
|
|
1368
|
+
display: grid;
|
|
1369
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
1370
|
+
gap: 10px;
|
|
1371
|
+
}
|
|
1372
|
+
.hero-metric, .metric-card {
|
|
1373
|
+
display: grid;
|
|
1374
|
+
gap: 6px;
|
|
1375
|
+
min-height: 68px;
|
|
1376
|
+
padding: 12px;
|
|
1377
|
+
border: 1px solid rgba(148, 163, 184, 0.24);
|
|
1378
|
+
border-radius: 12px;
|
|
1379
|
+
background: rgba(255, 255, 255, 0.78);
|
|
1380
|
+
box-shadow: 0 12px 30px rgba(15, 23, 42, 0.06);
|
|
1381
|
+
}
|
|
1382
|
+
.hero-metric span, .metric-card span {
|
|
1383
|
+
color: #64748b;
|
|
1384
|
+
font-size: 11px;
|
|
1385
|
+
font-weight: 780;
|
|
1386
|
+
letter-spacing: 0.04em;
|
|
1387
|
+
text-transform: uppercase;
|
|
1388
|
+
}
|
|
1389
|
+
.hero-metric strong, .metric-card strong {
|
|
1390
|
+
min-width: 0;
|
|
1391
|
+
overflow: hidden;
|
|
1392
|
+
color: #0f172a;
|
|
1393
|
+
font-size: 18px;
|
|
1394
|
+
font-weight: 780;
|
|
1395
|
+
text-overflow: ellipsis;
|
|
1396
|
+
white-space: nowrap;
|
|
1397
|
+
}
|
|
1398
|
+
.progress {
|
|
1399
|
+
width: 100%;
|
|
1400
|
+
height: 4px;
|
|
1401
|
+
overflow: hidden;
|
|
1402
|
+
border-radius: 999px;
|
|
1403
|
+
background: #e2e8f0;
|
|
1404
|
+
}
|
|
1405
|
+
.progress i {
|
|
1406
|
+
display: block;
|
|
1407
|
+
width: 0%;
|
|
1408
|
+
height: 100%;
|
|
1409
|
+
border-radius: inherit;
|
|
1410
|
+
background: #111827;
|
|
1411
|
+
transition: width 180ms ease;
|
|
1412
|
+
}
|
|
1413
|
+
.info-grid {
|
|
1414
|
+
display: grid;
|
|
1415
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
1416
|
+
gap: 9px 14px;
|
|
1417
|
+
}
|
|
1418
|
+
.info-item {
|
|
1419
|
+
min-width: 0;
|
|
1420
|
+
display: grid;
|
|
1421
|
+
gap: 3px;
|
|
1422
|
+
}
|
|
1423
|
+
.info-item span, .network-row span {
|
|
1424
|
+
color: #64748b;
|
|
1425
|
+
font-size: 11px;
|
|
1426
|
+
font-weight: 760;
|
|
1427
|
+
}
|
|
1428
|
+
.info-item strong, .network-row strong {
|
|
1429
|
+
min-width: 0;
|
|
1430
|
+
overflow: hidden;
|
|
1431
|
+
color: #111827;
|
|
1432
|
+
font-size: 13px;
|
|
1433
|
+
font-weight: 720;
|
|
1434
|
+
text-overflow: ellipsis;
|
|
1435
|
+
white-space: nowrap;
|
|
1436
|
+
}
|
|
1437
|
+
.network-list {
|
|
1438
|
+
display: grid;
|
|
1439
|
+
gap: 7px;
|
|
1440
|
+
}
|
|
1441
|
+
.network-row {
|
|
1442
|
+
display: grid;
|
|
1443
|
+
grid-template-columns: minmax(88px, 0.45fr) minmax(0, 1fr);
|
|
1444
|
+
gap: 12px;
|
|
1445
|
+
min-height: 32px;
|
|
1446
|
+
align-items: center;
|
|
1447
|
+
padding: 8px 10px;
|
|
1448
|
+
border: 1px solid rgba(148, 163, 184, 0.24);
|
|
1449
|
+
border-radius: 9px;
|
|
1450
|
+
background: #f8fafc;
|
|
1451
|
+
}
|
|
1216
1452
|
.actions {
|
|
1217
1453
|
display: grid;
|
|
1218
1454
|
grid-template-columns: 1fr 1fr;
|
|
@@ -1256,6 +1492,7 @@ function renderConnectionDashboardPage(state = {}) {
|
|
|
1256
1492
|
.hero, .panel { padding: 24px; }
|
|
1257
1493
|
h1 { font-size: 28px; }
|
|
1258
1494
|
.actions { grid-template-columns: 1fr; }
|
|
1495
|
+
.hero-metrics, .metric-grid, .info-grid { grid-template-columns: 1fr; }
|
|
1259
1496
|
}
|
|
1260
1497
|
</style>
|
|
1261
1498
|
</head>
|
|
@@ -1274,6 +1511,26 @@ function renderConnectionDashboardPage(state = {}) {
|
|
|
1274
1511
|
<h1>Client is ready</h1>
|
|
1275
1512
|
<p id="message">${escapeHtml(message)}</p>
|
|
1276
1513
|
</div>
|
|
1514
|
+
<div class="hero-metrics">
|
|
1515
|
+
<div class="hero-metric">
|
|
1516
|
+
<span>Host</span>
|
|
1517
|
+
<strong id="hero-host">-</strong>
|
|
1518
|
+
</div>
|
|
1519
|
+
<div class="hero-metric">
|
|
1520
|
+
<span>Engine</span>
|
|
1521
|
+
<strong id="hero-engine">pending</strong>
|
|
1522
|
+
</div>
|
|
1523
|
+
<div class="hero-metric">
|
|
1524
|
+
<span>Memory</span>
|
|
1525
|
+
<strong id="hero-memory">-</strong>
|
|
1526
|
+
<div class="progress"><i id="memory-bar"></i></div>
|
|
1527
|
+
</div>
|
|
1528
|
+
<div class="hero-metric">
|
|
1529
|
+
<span>Disk</span>
|
|
1530
|
+
<strong id="hero-disk">-</strong>
|
|
1531
|
+
<div class="progress"><i id="disk-bar"></i></div>
|
|
1532
|
+
</div>
|
|
1533
|
+
</div>
|
|
1277
1534
|
<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
1535
|
</section>
|
|
1279
1536
|
<section class="panel">
|
|
@@ -1290,6 +1547,47 @@ function renderConnectionDashboardPage(state = {}) {
|
|
|
1290
1547
|
<dt>Started</dt><dd id="connected-at">${escapeHtml(connectedAt)}</dd>
|
|
1291
1548
|
</dl>
|
|
1292
1549
|
</article>
|
|
1550
|
+
<article class="section-card">
|
|
1551
|
+
<div class="section-title">System</div>
|
|
1552
|
+
<div class="metric-grid">
|
|
1553
|
+
<div class="metric-card"><span>CPU</span><strong id="cpu-model">-</strong></div>
|
|
1554
|
+
<div class="metric-card"><span>Cores</span><strong id="cpu-cores">-</strong></div>
|
|
1555
|
+
<div class="metric-card"><span>OS</span><strong id="os-line">-</strong></div>
|
|
1556
|
+
<div class="metric-card"><span>Uptime</span><strong id="system-uptime">-</strong></div>
|
|
1557
|
+
<div class="metric-card"><span>Load avg</span><strong id="load-average">-</strong></div>
|
|
1558
|
+
<div class="metric-card"><span>Architecture</span><strong id="arch-line">-</strong></div>
|
|
1559
|
+
</div>
|
|
1560
|
+
</article>
|
|
1561
|
+
<article class="section-card">
|
|
1562
|
+
<div class="section-title">Runtime</div>
|
|
1563
|
+
<div class="info-grid">
|
|
1564
|
+
<div class="info-item"><span>Agent state</span><strong id="agent-state">waiting</strong></div>
|
|
1565
|
+
<div class="info-item"><span>Agent PID</span><strong id="agent-pid">-</strong></div>
|
|
1566
|
+
<div class="info-item"><span>Runtime</span><strong id="runtime-id">-</strong></div>
|
|
1567
|
+
<div class="info-item"><span>Fast package</span><strong id="fast-packaged">-</strong></div>
|
|
1568
|
+
<div class="info-item"><span>Client PID</span><strong id="client-pid">-</strong></div>
|
|
1569
|
+
<div class="info-item"><span>Node</span><strong id="node-version">-</strong></div>
|
|
1570
|
+
<div class="info-item"><span>Package</span><strong id="package-version">-</strong></div>
|
|
1571
|
+
<div class="info-item"><span>Process uptime</span><strong id="process-uptime">-</strong></div>
|
|
1572
|
+
<div class="info-item"><span>Agent started</span><strong id="agent-started">-</strong></div>
|
|
1573
|
+
</div>
|
|
1574
|
+
</article>
|
|
1575
|
+
<article class="section-card">
|
|
1576
|
+
<div class="section-title">Storage & Paths</div>
|
|
1577
|
+
<div class="info-grid">
|
|
1578
|
+
<div class="info-item"><span>Disk used</span><strong id="disk-used">-</strong></div>
|
|
1579
|
+
<div class="info-item"><span>Disk free</span><strong id="disk-free">-</strong></div>
|
|
1580
|
+
<div class="info-item"><span>User</span><strong id="user-name">-</strong></div>
|
|
1581
|
+
<div class="info-item"><span>Home</span><strong id="home-path">-</strong></div>
|
|
1582
|
+
<div class="info-item"><span>Working dir</span><strong id="cwd-path">-</strong></div>
|
|
1583
|
+
<div class="info-item"><span>Agent command</span><strong id="agent-command">-</strong></div>
|
|
1584
|
+
<div class="info-item"><span>Agent args</span><strong id="agent-args">-</strong></div>
|
|
1585
|
+
</div>
|
|
1586
|
+
</article>
|
|
1587
|
+
<article class="section-card">
|
|
1588
|
+
<div class="section-title">Network</div>
|
|
1589
|
+
<div id="network-list" class="network-list"></div>
|
|
1590
|
+
</article>
|
|
1293
1591
|
<div class="actions">
|
|
1294
1592
|
<a class="button secondary" href="/">Refresh dashboard</a>
|
|
1295
1593
|
<form method="post" action="/logout">
|
|
@@ -1305,7 +1603,33 @@ function renderConnectionDashboardPage(state = {}) {
|
|
|
1305
1603
|
const node = document.getElementById(id);
|
|
1306
1604
|
if (node) node.textContent = value || '';
|
|
1307
1605
|
}
|
|
1606
|
+
function setBar(id, value) {
|
|
1607
|
+
const node = document.getElementById(id);
|
|
1608
|
+
if (!node) return;
|
|
1609
|
+
const percent = Math.max(0, Math.min(100, Number(value) || 0));
|
|
1610
|
+
node.style.width = percent + '%';
|
|
1611
|
+
}
|
|
1612
|
+
function renderNetworkRows(items) {
|
|
1613
|
+
const list = document.getElementById('network-list');
|
|
1614
|
+
if (!list) return;
|
|
1615
|
+
list.textContent = '';
|
|
1616
|
+
const rows = Array.isArray(items) && items.length > 0 ? items : [{ name: 'Network', address: 'not reported', family: '' }];
|
|
1617
|
+
for (const item of rows) {
|
|
1618
|
+
const row = document.createElement('div');
|
|
1619
|
+
row.className = 'network-row';
|
|
1620
|
+
const name = document.createElement('span');
|
|
1621
|
+
name.textContent = [item.name, item.family].filter(Boolean).join(' / ');
|
|
1622
|
+
const address = document.createElement('strong');
|
|
1623
|
+
address.textContent = item.cidr || item.address || 'not reported';
|
|
1624
|
+
row.append(name, address);
|
|
1625
|
+
list.append(row);
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1308
1628
|
function applyState(state) {
|
|
1629
|
+
const system = state.system || {};
|
|
1630
|
+
const memory = system.memory || {};
|
|
1631
|
+
const disk = system.disk || {};
|
|
1632
|
+
const runtime = system.runtime || {};
|
|
1309
1633
|
setText('status', state.statusLabel || initialState.statusLabel);
|
|
1310
1634
|
setText('auth', state.authLabel || initialState.authLabel);
|
|
1311
1635
|
setText('manager', state.manager || 'waiting');
|
|
@@ -1313,6 +1637,35 @@ function renderConnectionDashboardPage(state = {}) {
|
|
|
1313
1637
|
setText('startup', state.startup ? 'enabled' : 'manual');
|
|
1314
1638
|
setText('connected-at', state.connectedAt || initialState.connectedAt);
|
|
1315
1639
|
setText('message', state.message || initialState.message);
|
|
1640
|
+
setText('hero-host', system.hostname || '-');
|
|
1641
|
+
setText('hero-engine', runtime.activeEngine || runtime.state || 'pending');
|
|
1642
|
+
setText('hero-memory', memory.used && memory.total ? memory.used + ' / ' + memory.total : '-');
|
|
1643
|
+
setText('hero-disk', disk.usedPercent ? disk.usedPercent + '% used' : disk.free ? disk.free + ' free' : '-');
|
|
1644
|
+
setBar('memory-bar', memory.usedPercent);
|
|
1645
|
+
setBar('disk-bar', disk.usedPercent);
|
|
1646
|
+
setText('cpu-model', system.cpuModel || '-');
|
|
1647
|
+
setText('cpu-cores', system.cpuCores ? String(system.cpuCores) : '-');
|
|
1648
|
+
setText('os-line', [system.type || system.platform, system.release, system.arch].filter(Boolean).join(' '));
|
|
1649
|
+
setText('system-uptime', system.systemUptime || '-');
|
|
1650
|
+
setText('load-average', system.loadAverage || '-');
|
|
1651
|
+
setText('arch-line', [system.platform, system.arch].filter(Boolean).join(' / '));
|
|
1652
|
+
setText('agent-state', runtime.state || 'waiting');
|
|
1653
|
+
setText('agent-pid', runtime.pid ? String(runtime.pid) : '-');
|
|
1654
|
+
setText('runtime-id', runtime.runtimeId || runtime.requestedEngine || '-');
|
|
1655
|
+
setText('fast-packaged', runtime.fastPackaged ? 'available' : 'missing');
|
|
1656
|
+
setText('client-pid', system.pid ? String(system.pid) : '-');
|
|
1657
|
+
setText('node-version', system.node || '-');
|
|
1658
|
+
setText('package-version', system.packageVersion || '-');
|
|
1659
|
+
setText('process-uptime', system.processUptime || '-');
|
|
1660
|
+
setText('agent-started', runtime.startedAt || '-');
|
|
1661
|
+
setText('disk-used', disk.used && disk.total ? disk.used + ' / ' + disk.total : '-');
|
|
1662
|
+
setText('disk-free', disk.free || '-');
|
|
1663
|
+
setText('user-name', system.user || '-');
|
|
1664
|
+
setText('home-path', system.home || '-');
|
|
1665
|
+
setText('cwd-path', system.cwd || '-');
|
|
1666
|
+
setText('agent-command', runtime.command || '-');
|
|
1667
|
+
setText('agent-args', runtime.args || '-');
|
|
1668
|
+
renderNetworkRows(system.network);
|
|
1316
1669
|
}
|
|
1317
1670
|
applyState(initialState);
|
|
1318
1671
|
if (location.pathname === '/callback') {
|
|
@@ -1458,6 +1811,10 @@ async function startConnectionChoiceServer(supabase, options = {}) {
|
|
|
1458
1811
|
let completedTitle = 'LiveDesk connection complete';
|
|
1459
1812
|
let completedMessage = 'The LiveDesk client is starting automatically.';
|
|
1460
1813
|
const dashboardState = {
|
|
1814
|
+
agent: {
|
|
1815
|
+
requestedEngine: options.engine || '',
|
|
1816
|
+
state: 'waiting'
|
|
1817
|
+
},
|
|
1461
1818
|
choice: null,
|
|
1462
1819
|
connectedAt: '',
|
|
1463
1820
|
endpointCandidates: [],
|
|
@@ -1506,6 +1863,7 @@ async function startConnectionChoiceServer(supabase, options = {}) {
|
|
|
1506
1863
|
});
|
|
1507
1864
|
const getDashboardApiState = () => {
|
|
1508
1865
|
const manager = dashboardState.manager || dashboardState.choice?.manager || '';
|
|
1866
|
+
const agentState = dashboardState.agent?.state || '';
|
|
1509
1867
|
return {
|
|
1510
1868
|
authLabel: getDashboardAuthLabel(dashboardState.choice, dashboardState.loggedOut),
|
|
1511
1869
|
connectedAt: dashboardState.connectedAt || '',
|
|
@@ -1517,10 +1875,11 @@ async function startConnectionChoiceServer(supabase, options = {}) {
|
|
|
1517
1875
|
statusLabel: dashboardState.loggedOut
|
|
1518
1876
|
? 'Signed out for next restart'
|
|
1519
1877
|
: manager
|
|
1520
|
-
? 'Client starting'
|
|
1878
|
+
? agentState === 'running' ? 'Agent running' : 'Client starting'
|
|
1521
1879
|
: completed
|
|
1522
1880
|
? 'Finding Hub'
|
|
1523
|
-
: 'Waiting for auth'
|
|
1881
|
+
: 'Waiting for auth',
|
|
1882
|
+
system: getDashboardSystemInfo(dashboardState)
|
|
1524
1883
|
};
|
|
1525
1884
|
};
|
|
1526
1885
|
|
|
@@ -1789,6 +2148,7 @@ async function chooseClientConnection(supabase, options = {}) {
|
|
|
1789
2148
|
const connectionPage = await startConnectionChoiceServer(supabase, {
|
|
1790
2149
|
authPort: options.authPort,
|
|
1791
2150
|
autoGoogle: options.autoGoogle,
|
|
2151
|
+
engine: options.engine,
|
|
1792
2152
|
slot: options.slot,
|
|
1793
2153
|
startupArgs: options.startupArgs,
|
|
1794
2154
|
savedSession: options.savedSession,
|
|
@@ -1944,6 +2304,7 @@ async function prepareLoginConnection(parsed) {
|
|
|
1944
2304
|
choice = await chooseClientConnection(supabase, {
|
|
1945
2305
|
authPort: parsed.authPort,
|
|
1946
2306
|
autoGoogle: !savedSession?.access_token && !savedPin,
|
|
2307
|
+
engine: parsed.engine,
|
|
1947
2308
|
slot: parsed.slot,
|
|
1948
2309
|
startupArgs,
|
|
1949
2310
|
savedSession,
|
|
@@ -2169,6 +2530,27 @@ function buildFastArgs(args, fakeThumbnail) {
|
|
|
2169
2530
|
return forwarded;
|
|
2170
2531
|
}
|
|
2171
2532
|
|
|
2533
|
+
function redactAgentArgs(args = []) {
|
|
2534
|
+
const redacted = [];
|
|
2535
|
+
const secretFlags = new Set(['--pair', '--token', '--key']);
|
|
2536
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
2537
|
+
const arg = String(args[index] || '');
|
|
2538
|
+
const equalsIndex = arg.indexOf('=');
|
|
2539
|
+
if (equalsIndex > 0 && secretFlags.has(arg.slice(0, equalsIndex))) {
|
|
2540
|
+
redacted.push(`${arg.slice(0, equalsIndex)}=<hidden>`);
|
|
2541
|
+
continue;
|
|
2542
|
+
}
|
|
2543
|
+
redacted.push(arg);
|
|
2544
|
+
if (secretFlags.has(arg)) {
|
|
2545
|
+
if (index + 1 < args.length) {
|
|
2546
|
+
redacted.push('<hidden>');
|
|
2547
|
+
index += 1;
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
return redacted;
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2172
2554
|
function resolveFastLaunch(runtime) {
|
|
2173
2555
|
if (!runtime) {
|
|
2174
2556
|
return {
|
|
@@ -2217,12 +2599,15 @@ function resolveFastLaunch(runtime) {
|
|
|
2217
2599
|
};
|
|
2218
2600
|
}
|
|
2219
2601
|
|
|
2220
|
-
function spawnAgent(command, args, env = process.env) {
|
|
2602
|
+
function spawnAgent(command, args, env = process.env, onStart = null) {
|
|
2221
2603
|
const child = spawn(command, args, {
|
|
2222
2604
|
env,
|
|
2223
2605
|
stdio: 'inherit',
|
|
2224
2606
|
windowsHide: true
|
|
2225
2607
|
});
|
|
2608
|
+
if (typeof onStart === 'function') {
|
|
2609
|
+
onStart(child);
|
|
2610
|
+
}
|
|
2226
2611
|
|
|
2227
2612
|
return new Promise(resolve => {
|
|
2228
2613
|
child.once('error', error => {
|
|
@@ -2267,22 +2652,88 @@ async function main() {
|
|
|
2267
2652
|
const prepared = await prepareLoginConnection(parsed);
|
|
2268
2653
|
const fastRuntime = getFastRuntime();
|
|
2269
2654
|
const useFast = shouldTryFast(prepared, fastRuntime);
|
|
2655
|
+
const updateAgentDashboard = (patch = {}) => {
|
|
2656
|
+
prepared.connectionPage?.update({
|
|
2657
|
+
agent: {
|
|
2658
|
+
requestedEngine: prepared.engine,
|
|
2659
|
+
state: 'launching',
|
|
2660
|
+
...patch
|
|
2661
|
+
},
|
|
2662
|
+
message: patch.state === 'running'
|
|
2663
|
+
? `LiveDesk client agent is running with ${patch.engine || 'agent'} mode.`
|
|
2664
|
+
: 'LiveDesk client agent is launching.'
|
|
2665
|
+
});
|
|
2666
|
+
};
|
|
2270
2667
|
let result;
|
|
2271
2668
|
if (useFast) {
|
|
2272
2669
|
const fastArgs = buildFastArgs(prepared.forwarded, prepared.fakeThumbnail);
|
|
2273
2670
|
const fastLaunch = resolveFastLaunch(fastRuntime);
|
|
2274
2671
|
if (fastLaunch.ok) {
|
|
2275
|
-
|
|
2672
|
+
const launchArgs = [...fastLaunch.argsPrefix, ...fastArgs];
|
|
2673
|
+
updateAgentDashboard({
|
|
2674
|
+
engine: 'fast',
|
|
2675
|
+
command: fastLaunch.command,
|
|
2676
|
+
args: redactAgentArgs(launchArgs),
|
|
2677
|
+
runtimeId: fastRuntime?.rid || '',
|
|
2678
|
+
state: 'launching'
|
|
2679
|
+
});
|
|
2680
|
+
result = await spawnAgent(fastLaunch.command, launchArgs, buildFastEnvironment(), child => {
|
|
2681
|
+
updateAgentDashboard({
|
|
2682
|
+
engine: 'fast',
|
|
2683
|
+
command: fastLaunch.command,
|
|
2684
|
+
args: redactAgentArgs(launchArgs),
|
|
2685
|
+
runtimeId: fastRuntime?.rid || '',
|
|
2686
|
+
pid: child.pid,
|
|
2687
|
+
startedAt: new Date().toISOString(),
|
|
2688
|
+
state: 'running'
|
|
2689
|
+
});
|
|
2690
|
+
});
|
|
2276
2691
|
} else if (prepared.engine === 'fast') {
|
|
2277
2692
|
console.error(`C# RemoteFast is unavailable: ${fastLaunch.reason}. Use --engine node to run the legacy Node agent.`);
|
|
2278
2693
|
prepared.connectionPage?.close?.();
|
|
2279
2694
|
process.exit(2);
|
|
2280
2695
|
} else {
|
|
2281
2696
|
console.warn(`C# RemoteFast is unavailable (${fastLaunch.reason}). Falling back to the Node remote agent.`);
|
|
2282
|
-
|
|
2697
|
+
const launchArgs = [nodeAgentPath, ...prepared.forwarded];
|
|
2698
|
+
updateAgentDashboard({
|
|
2699
|
+
engine: 'node',
|
|
2700
|
+
command: process.execPath,
|
|
2701
|
+
args: redactAgentArgs(launchArgs),
|
|
2702
|
+
runtimeId: 'node-fallback',
|
|
2703
|
+
state: 'launching'
|
|
2704
|
+
});
|
|
2705
|
+
result = await spawnAgent(process.execPath, launchArgs, process.env, child => {
|
|
2706
|
+
updateAgentDashboard({
|
|
2707
|
+
engine: 'node',
|
|
2708
|
+
command: process.execPath,
|
|
2709
|
+
args: redactAgentArgs(launchArgs),
|
|
2710
|
+
runtimeId: 'node-fallback',
|
|
2711
|
+
pid: child.pid,
|
|
2712
|
+
startedAt: new Date().toISOString(),
|
|
2713
|
+
state: 'running'
|
|
2714
|
+
});
|
|
2715
|
+
});
|
|
2283
2716
|
}
|
|
2284
2717
|
} else {
|
|
2285
|
-
|
|
2718
|
+
const launchArgs = [nodeAgentPath, ...prepared.forwarded];
|
|
2719
|
+
updateAgentDashboard({
|
|
2720
|
+
engine: 'node',
|
|
2721
|
+
command: process.execPath,
|
|
2722
|
+
args: redactAgentArgs(launchArgs),
|
|
2723
|
+
runtimeId: 'node',
|
|
2724
|
+
state: 'launching'
|
|
2725
|
+
});
|
|
2726
|
+
result = await spawnAgent(process.execPath, launchArgs, process.env, child => {
|
|
2727
|
+
updateAgentDashboard({
|
|
2728
|
+
engine: 'node',
|
|
2729
|
+
command: process.execPath,
|
|
2730
|
+
args: redactAgentArgs(launchArgs),
|
|
2731
|
+
runtimeId: 'node',
|
|
2732
|
+
pid: child.pid,
|
|
2733
|
+
startedAt: new Date().toISOString(),
|
|
2734
|
+
state: 'running'
|
|
2735
|
+
});
|
|
2736
|
+
});
|
|
2286
2737
|
}
|
|
2287
2738
|
prepared.connectionPage?.close?.();
|
|
2288
2739
|
if (result?.signal) {
|