@mindexec/cli 0.2.5 → 0.2.7
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/README.md +12 -0
- package/package.json +6 -6
- package/remote-hub.js +167 -0
- package/scripts/remote-hub-smoke.mjs +54 -0
- package/server.js +28 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +722 -78
- package/wwwroot/_framework/MindExecution.Core.pnw79cgqjx.dll +0 -0
- package/wwwroot/_framework/{MindExecution.Kernel.9wfplilp5l.dll → MindExecution.Kernel.dt3w864bqn.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Admin.sb1vkmct0w.dll → MindExecution.Plugins.Admin.z93cu32xru.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Business.zr7rkofx44.dll → MindExecution.Plugins.Business.b6da8sg85t.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Concept.g6wd36v92i.dll → MindExecution.Plugins.Concept.mjooiqft9j.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Directory.bb5flwt0u7.dll → MindExecution.Plugins.Directory.rjod6rdmly.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.me8v9fpgwc.dll → MindExecution.Plugins.PlanMaster.1dcrzhsegj.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.YouTube.l811fqx9e0.dll → MindExecution.Plugins.YouTube.k75qxhbpp8.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Shared.oseamdg577.dll → MindExecution.Shared.y3eqxd3mvo.dll} +0 -0
- package/wwwroot/_framework/MindExecution.Web.wou9x6mn2f.dll +0 -0
- package/wwwroot/_framework/blazor.boot.json +21 -21
- package/wwwroot/index.html +1 -1
- package/wwwroot/service-worker-assets.js +24 -24
- package/wwwroot/service-worker.js +1 -1
- package/wwwroot/_framework/MindExecution.Core.27f2blpou6.dll +0 -0
- package/wwwroot/_framework/MindExecution.Web.96r3nnp9is.dll +0 -0
|
@@ -12005,8 +12005,11 @@
|
|
|
12005
12005
|
return `${Math.floor(seconds / 86400)}d ago`;
|
|
12006
12006
|
}
|
|
12007
12007
|
|
|
12008
|
-
function createRemoteFleetStat(label, value, tone = 'default') {
|
|
12008
|
+
function createRemoteFleetStat(label, value, tone = 'default', key = '') {
|
|
12009
12009
|
const item = document.createElement('div');
|
|
12010
|
+
if (key) {
|
|
12011
|
+
item.dataset.remoteFleetStat = key;
|
|
12012
|
+
}
|
|
12010
12013
|
item.style.cssText = `
|
|
12011
12014
|
display: flex;
|
|
12012
12015
|
flex-direction: column;
|
|
@@ -12029,6 +12032,7 @@
|
|
|
12029
12032
|
`;
|
|
12030
12033
|
|
|
12031
12034
|
const valueEl = document.createElement('strong');
|
|
12035
|
+
valueEl.dataset.remoteFleetStatValue = 'true';
|
|
12032
12036
|
valueEl.textContent = value;
|
|
12033
12037
|
valueEl.style.cssText = `
|
|
12034
12038
|
color: ${tone === 'online' ? '#047857' : '#0f172a'};
|
|
@@ -12073,6 +12077,141 @@
|
|
|
12073
12077
|
return button;
|
|
12074
12078
|
}
|
|
12075
12079
|
|
|
12080
|
+
function createRemoteFleetSelect(options, value, title) {
|
|
12081
|
+
const select = document.createElement('select');
|
|
12082
|
+
select.title = title || '';
|
|
12083
|
+
select.setAttribute('aria-label', title || 'Remote fleet option');
|
|
12084
|
+
select.style.cssText = `
|
|
12085
|
+
min-width: 0;
|
|
12086
|
+
height: 34px;
|
|
12087
|
+
border-radius: 7px;
|
|
12088
|
+
border: 1px solid rgba(148, 163, 184, 0.34);
|
|
12089
|
+
background: rgba(255, 255, 255, 0.94);
|
|
12090
|
+
color: #0f172a;
|
|
12091
|
+
padding: 0 8px;
|
|
12092
|
+
font-size: 11px;
|
|
12093
|
+
font-weight: 850;
|
|
12094
|
+
letter-spacing: 0;
|
|
12095
|
+
outline: none;
|
|
12096
|
+
pointer-events: auto;
|
|
12097
|
+
`;
|
|
12098
|
+
options.forEach(option => {
|
|
12099
|
+
const optionEl = document.createElement('option');
|
|
12100
|
+
optionEl.value = option.value;
|
|
12101
|
+
optionEl.textContent = option.label;
|
|
12102
|
+
select.appendChild(optionEl);
|
|
12103
|
+
});
|
|
12104
|
+
select.value = value;
|
|
12105
|
+
return select;
|
|
12106
|
+
}
|
|
12107
|
+
|
|
12108
|
+
function getRemoteFleetDeviceField(device, camelKey, pascalKey, fallback = '') {
|
|
12109
|
+
const value = device?.[camelKey] ?? device?.[pascalKey] ?? fallback;
|
|
12110
|
+
return value === undefined || value === null ? fallback : value;
|
|
12111
|
+
}
|
|
12112
|
+
|
|
12113
|
+
function getRemoteFleetDeviceId(device) {
|
|
12114
|
+
return String(getRemoteFleetDeviceField(device, 'deviceId', 'DeviceId', '')).trim();
|
|
12115
|
+
}
|
|
12116
|
+
|
|
12117
|
+
function getRemoteFleetDeviceName(device) {
|
|
12118
|
+
return String(
|
|
12119
|
+
getRemoteFleetDeviceField(device, 'name', 'Name')
|
|
12120
|
+
|| getRemoteFleetDeviceField(device, 'hostname', 'Hostname')
|
|
12121
|
+
|| getRemoteFleetDeviceId(device)
|
|
12122
|
+
|| 'device');
|
|
12123
|
+
}
|
|
12124
|
+
|
|
12125
|
+
function isRemoteFleetDeviceConnected(device) {
|
|
12126
|
+
return getRemoteFleetDeviceField(device, 'connected', 'Connected', false) === true;
|
|
12127
|
+
}
|
|
12128
|
+
|
|
12129
|
+
function isRemoteFleetDeviceTaskCapable(device) {
|
|
12130
|
+
return getRemoteFleetDeviceField(device, 'computerAgentEnabled', 'ComputerAgentEnabled', false) === true;
|
|
12131
|
+
}
|
|
12132
|
+
|
|
12133
|
+
function isRemoteFleetDeviceAiCapable(device) {
|
|
12134
|
+
return getRemoteFleetDeviceField(device, 'aiAssistEnabled', 'AiAssistEnabled', false) === true;
|
|
12135
|
+
}
|
|
12136
|
+
|
|
12137
|
+
function hasRemoteFleetThumbnail(device) {
|
|
12138
|
+
const dataUrl = String(getRemoteFleetDeviceField(device, 'thumbnailDataUrl', 'ThumbnailDataUrl', ''));
|
|
12139
|
+
return /^data:image\/(png|jpe?g|webp|svg\+xml);base64,/i.test(dataUrl);
|
|
12140
|
+
}
|
|
12141
|
+
|
|
12142
|
+
function hasRemoteFleetLiveFrame(device) {
|
|
12143
|
+
const dataUrl = String(getRemoteFleetDeviceField(device, 'liveFrameDataUrl', 'LiveFrameDataUrl', ''));
|
|
12144
|
+
return /^data:image\/(png|jpe?g|webp|svg\+xml);base64,/i.test(dataUrl);
|
|
12145
|
+
}
|
|
12146
|
+
|
|
12147
|
+
function isRemoteFleetLiveActive(device) {
|
|
12148
|
+
return getRemoteFleetDeviceField(device, 'liveStreamActive', 'LiveStreamActive', false) === true;
|
|
12149
|
+
}
|
|
12150
|
+
|
|
12151
|
+
function getRemoteFleetTimestampMs(value) {
|
|
12152
|
+
const timestamp = Date.parse(String(value || ''));
|
|
12153
|
+
return Number.isFinite(timestamp) ? timestamp : 0;
|
|
12154
|
+
}
|
|
12155
|
+
|
|
12156
|
+
function getRemoteFleetDeviceLastSeenMs(device) {
|
|
12157
|
+
return Math.max(
|
|
12158
|
+
getRemoteFleetTimestampMs(getRemoteFleetDeviceField(device, 'lastSeenAt', 'LastSeenAt', '')),
|
|
12159
|
+
getRemoteFleetTimestampMs(getRemoteFleetDeviceField(device, 'lastStatusAt', 'LastStatusAt', '')),
|
|
12160
|
+
getRemoteFleetTimestampMs(getRemoteFleetDeviceField(device, 'thumbnailReceivedAt', 'ThumbnailReceivedAt', '')));
|
|
12161
|
+
}
|
|
12162
|
+
|
|
12163
|
+
function getRemoteFleetDeviceNumber(device, camelKey, pascalKey) {
|
|
12164
|
+
const number = Number(getRemoteFleetDeviceField(device, camelKey, pascalKey, Number.NaN));
|
|
12165
|
+
return Number.isFinite(number) ? number : Number.NaN;
|
|
12166
|
+
}
|
|
12167
|
+
|
|
12168
|
+
function buildRemoteFleetSearchText(device) {
|
|
12169
|
+
return [
|
|
12170
|
+
getRemoteFleetDeviceId(device),
|
|
12171
|
+
getRemoteFleetDeviceName(device),
|
|
12172
|
+
getRemoteFleetDeviceField(device, 'hostname', 'Hostname', ''),
|
|
12173
|
+
getRemoteFleetDeviceField(device, 'platform', 'Platform', ''),
|
|
12174
|
+
getRemoteFleetDeviceField(device, 'release', 'Release', ''),
|
|
12175
|
+
getRemoteFleetDeviceField(device, 'arch', 'Arch', ''),
|
|
12176
|
+
getRemoteFleetDeviceField(device, 'agentVersion', 'AgentVersion', ''),
|
|
12177
|
+
getRemoteFleetDeviceField(device, 'liveStreamMode', 'LiveStreamMode', ''),
|
|
12178
|
+
isRemoteFleetLiveActive(device) ? 'live active remote-fast' : '',
|
|
12179
|
+
getRemoteFleetDeviceField(device, 'latestTaskTitle', 'LatestTaskTitle', ''),
|
|
12180
|
+
getRemoteFleetDeviceField(device, 'latestTaskStatus', 'LatestTaskStatus', ''),
|
|
12181
|
+
getRemoteFleetDeviceField(device, 'latestTaskResultSummary', 'LatestTaskResultSummary', ''),
|
|
12182
|
+
getRemoteFleetDeviceField(device, 'latestTaskError', 'LatestTaskError', '')
|
|
12183
|
+
].join(' ').toLowerCase();
|
|
12184
|
+
}
|
|
12185
|
+
|
|
12186
|
+
function compareRemoteFleetDevices(left, right, sortMode) {
|
|
12187
|
+
const connectedDelta = Number(isRemoteFleetDeviceConnected(right)) - Number(isRemoteFleetDeviceConnected(left));
|
|
12188
|
+
const byName = () => getRemoteFleetDeviceName(left).localeCompare(getRemoteFleetDeviceName(right))
|
|
12189
|
+
|| getRemoteFleetDeviceId(left).localeCompare(getRemoteFleetDeviceId(right));
|
|
12190
|
+
|
|
12191
|
+
switch (sortMode) {
|
|
12192
|
+
case 'recent':
|
|
12193
|
+
return getRemoteFleetDeviceLastSeenMs(right) - getRemoteFleetDeviceLastSeenMs(left) || byName();
|
|
12194
|
+
case 'memory': {
|
|
12195
|
+
const leftMem = getRemoteFleetDeviceNumber(left, 'usedMemRatio', 'UsedMemRatio');
|
|
12196
|
+
const rightMem = getRemoteFleetDeviceNumber(right, 'usedMemRatio', 'UsedMemRatio');
|
|
12197
|
+
return (Number.isFinite(rightMem) ? rightMem : -1) - (Number.isFinite(leftMem) ? leftMem : -1) || byName();
|
|
12198
|
+
}
|
|
12199
|
+
case 'load': {
|
|
12200
|
+
const leftLoad = getRemoteFleetDeviceNumber(left, 'load1', 'Load1');
|
|
12201
|
+
const rightLoad = getRemoteFleetDeviceNumber(right, 'load1', 'Load1');
|
|
12202
|
+
return (Number.isFinite(rightLoad) ? rightLoad : -1) - (Number.isFinite(leftLoad) ? leftLoad : -1) || byName();
|
|
12203
|
+
}
|
|
12204
|
+
case 'task':
|
|
12205
|
+
return String(getRemoteFleetDeviceField(right, 'latestTaskUpdatedAt', 'LatestTaskUpdatedAt', ''))
|
|
12206
|
+
.localeCompare(String(getRemoteFleetDeviceField(left, 'latestTaskUpdatedAt', 'LatestTaskUpdatedAt', ''))) || byName();
|
|
12207
|
+
case 'status':
|
|
12208
|
+
return connectedDelta || byName();
|
|
12209
|
+
case 'name':
|
|
12210
|
+
default:
|
|
12211
|
+
return byName();
|
|
12212
|
+
}
|
|
12213
|
+
}
|
|
12214
|
+
|
|
12076
12215
|
async function syncRemoteFleetNodeStateFromResult(result) {
|
|
12077
12216
|
const nodeState = result?.nodeState || result?.refresh?.nodeState;
|
|
12078
12217
|
if (nodeState && window.mindMap?.syncNodeStates) {
|
|
@@ -12082,14 +12221,34 @@
|
|
|
12082
12221
|
|
|
12083
12222
|
function renderRemoteFleetMonitor(bodyView, nodeModel) {
|
|
12084
12223
|
if (!bodyView) return;
|
|
12224
|
+
if (bodyView._remoteFleetLiveRefreshTimer) {
|
|
12225
|
+
clearInterval(bodyView._remoteFleetLiveRefreshTimer);
|
|
12226
|
+
bodyView._remoteFleetLiveRefreshTimer = null;
|
|
12227
|
+
}
|
|
12085
12228
|
|
|
12086
12229
|
const nodeId = String(nodeModel?.id ?? nodeModel?.Id ?? '');
|
|
12087
|
-
const
|
|
12230
|
+
const searchState = bodyView.dataset.remoteFleetSearch || '';
|
|
12231
|
+
const filterState = bodyView.dataset.remoteFleetFilter || 'all';
|
|
12232
|
+
const sortState = bodyView.dataset.remoteFleetSort || 'status';
|
|
12233
|
+
const densityState = bodyView.dataset.remoteFleetDensity || 'cards';
|
|
12234
|
+
const aiAssistState = bodyView.dataset.remoteFleetAiAssist === 'true';
|
|
12235
|
+
const focusState = bodyView.dataset.remoteFleetFocusDeviceId || '';
|
|
12236
|
+
const devices = [...parseRemoteFleetDevices(nodeModel)]
|
|
12237
|
+
.sort((left, right) => compareRemoteFleetDevices(left, right, sortState));
|
|
12238
|
+
const hasActiveLiveStream = devices.some(isRemoteFleetLiveActive);
|
|
12239
|
+
const focusedDevice = devices.find(device => getRemoteFleetDeviceId(device) === focusState)
|
|
12240
|
+
|| devices.find(device => isRemoteFleetLiveActive(device))
|
|
12241
|
+
|| null;
|
|
12242
|
+
if (focusedDevice) {
|
|
12243
|
+
bodyView.dataset.remoteFleetFocusDeviceId = getRemoteFleetDeviceId(focusedDevice);
|
|
12244
|
+
}
|
|
12088
12245
|
const total = Number(getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetDeviceCount', devices.length));
|
|
12089
12246
|
const connected = Number(getRemoteFleetMetadataValue(
|
|
12090
12247
|
nodeModel,
|
|
12091
12248
|
'RemoteFleetConnectedDeviceCount',
|
|
12092
|
-
devices.filter(
|
|
12249
|
+
devices.filter(isRemoteFleetDeviceConnected).length));
|
|
12250
|
+
const taskCapableCount = devices.filter(device => isRemoteFleetDeviceConnected(device) && isRemoteFleetDeviceTaskCapable(device)).length;
|
|
12251
|
+
const aiCapableCount = devices.filter(device => isRemoteFleetDeviceConnected(device) && isRemoteFleetDeviceAiCapable(device)).length;
|
|
12093
12252
|
const endpoint = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetHubEndpoint', '127.0.0.1:5197');
|
|
12094
12253
|
const command = getRemoteFleetMetadataValue(
|
|
12095
12254
|
nodeModel,
|
|
@@ -12123,8 +12282,8 @@
|
|
|
12123
12282
|
`;
|
|
12124
12283
|
top.appendChild(createRemoteFleetStat('Hub', hubStatus === 'online' ? 'Online' : 'Offline', hubStatus === 'online' ? 'online' : 'default'));
|
|
12125
12284
|
top.appendChild(createRemoteFleetStat('Connected', `${Number.isFinite(connected) ? connected : 0}/${Number.isFinite(total) ? total : devices.length}`, connected > 0 ? 'online' : 'default'));
|
|
12126
|
-
top.appendChild(createRemoteFleetStat('
|
|
12127
|
-
top.appendChild(createRemoteFleetStat('
|
|
12285
|
+
top.appendChild(createRemoteFleetStat('Task', String(taskCapableCount), taskCapableCount > 0 ? 'online' : 'default'));
|
|
12286
|
+
top.appendChild(createRemoteFleetStat('AI', String(aiCapableCount), aiCapableCount > 0 ? 'online' : 'default'));
|
|
12128
12287
|
bodyView.appendChild(top);
|
|
12129
12288
|
|
|
12130
12289
|
const commandRow = document.createElement('div');
|
|
@@ -12160,10 +12319,87 @@
|
|
|
12160
12319
|
commandRow.appendChild(refreshButton);
|
|
12161
12320
|
bodyView.appendChild(commandRow);
|
|
12162
12321
|
|
|
12322
|
+
const filterRow = document.createElement('div');
|
|
12323
|
+
filterRow.style.cssText = `
|
|
12324
|
+
display: grid;
|
|
12325
|
+
grid-template-columns: minmax(150px, 1.2fr) minmax(96px, 0.7fr) minmax(96px, 0.7fr) minmax(84px, 0.55fr) auto;
|
|
12326
|
+
gap: 8px;
|
|
12327
|
+
align-items: center;
|
|
12328
|
+
flex: 0 0 auto;
|
|
12329
|
+
`;
|
|
12330
|
+
|
|
12331
|
+
const searchInput = document.createElement('input');
|
|
12332
|
+
searchInput.type = 'search';
|
|
12333
|
+
searchInput.value = searchState;
|
|
12334
|
+
searchInput.placeholder = 'Search devices';
|
|
12335
|
+
searchInput.dataset.remoteFleetSearch = 'true';
|
|
12336
|
+
searchInput.autocomplete = 'off';
|
|
12337
|
+
searchInput.spellcheck = false;
|
|
12338
|
+
searchInput.style.cssText = `
|
|
12339
|
+
min-width: 0;
|
|
12340
|
+
height: 34px;
|
|
12341
|
+
border-radius: 7px;
|
|
12342
|
+
border: 1px solid rgba(148, 163, 184, 0.34);
|
|
12343
|
+
background: rgba(255, 255, 255, 0.94);
|
|
12344
|
+
color: #0f172a;
|
|
12345
|
+
padding: 0 10px;
|
|
12346
|
+
font-size: 12px;
|
|
12347
|
+
font-weight: 750;
|
|
12348
|
+
letter-spacing: 0;
|
|
12349
|
+
outline: none;
|
|
12350
|
+
pointer-events: auto;
|
|
12351
|
+
`;
|
|
12352
|
+
|
|
12353
|
+
const filterSelect = createRemoteFleetSelect([
|
|
12354
|
+
{ value: 'all', label: 'All' },
|
|
12355
|
+
{ value: 'connected', label: 'Online' },
|
|
12356
|
+
{ value: 'offline', label: 'Offline' },
|
|
12357
|
+
{ value: 'task', label: 'Task' },
|
|
12358
|
+
{ value: 'ai', label: 'AI' },
|
|
12359
|
+
{ value: 'live', label: 'Live' },
|
|
12360
|
+
{ value: 'frames', label: 'Frames' },
|
|
12361
|
+
{ value: 'issues', label: 'Issues' }
|
|
12362
|
+
], filterState, 'Device filter');
|
|
12363
|
+
|
|
12364
|
+
const sortSelect = createRemoteFleetSelect([
|
|
12365
|
+
{ value: 'status', label: 'Status' },
|
|
12366
|
+
{ value: 'name', label: 'Name' },
|
|
12367
|
+
{ value: 'recent', label: 'Recent' },
|
|
12368
|
+
{ value: 'memory', label: 'Memory' },
|
|
12369
|
+
{ value: 'load', label: 'Load' },
|
|
12370
|
+
{ value: 'task', label: 'Task' }
|
|
12371
|
+
], sortState, 'Device sort');
|
|
12372
|
+
|
|
12373
|
+
const densitySelect = createRemoteFleetSelect([
|
|
12374
|
+
{ value: 'cards', label: 'Cards' },
|
|
12375
|
+
{ value: 'dense', label: 'Dense' }
|
|
12376
|
+
], densityState, 'Device density');
|
|
12377
|
+
|
|
12378
|
+
const matchCount = document.createElement('div');
|
|
12379
|
+
matchCount.dataset.remoteFleetMatchCount = 'true';
|
|
12380
|
+
matchCount.style.cssText = `
|
|
12381
|
+
min-width: 72px;
|
|
12382
|
+
color: #334155;
|
|
12383
|
+
font-size: 11px;
|
|
12384
|
+
font-weight: 900;
|
|
12385
|
+
letter-spacing: 0;
|
|
12386
|
+
text-align: right;
|
|
12387
|
+
white-space: nowrap;
|
|
12388
|
+
overflow: hidden;
|
|
12389
|
+
text-overflow: ellipsis;
|
|
12390
|
+
`;
|
|
12391
|
+
|
|
12392
|
+
filterRow.appendChild(searchInput);
|
|
12393
|
+
filterRow.appendChild(filterSelect);
|
|
12394
|
+
filterRow.appendChild(sortSelect);
|
|
12395
|
+
filterRow.appendChild(densitySelect);
|
|
12396
|
+
filterRow.appendChild(matchCount);
|
|
12397
|
+
bodyView.appendChild(filterRow);
|
|
12398
|
+
|
|
12163
12399
|
const taskRow = document.createElement('div');
|
|
12164
12400
|
taskRow.style.cssText = `
|
|
12165
12401
|
display: grid;
|
|
12166
|
-
grid-template-columns: minmax(0, 1fr) auto auto;
|
|
12402
|
+
grid-template-columns: minmax(0, 1fr) auto auto auto;
|
|
12167
12403
|
gap: 8px;
|
|
12168
12404
|
align-items: stretch;
|
|
12169
12405
|
flex: 0 0 auto;
|
|
@@ -12212,6 +12448,7 @@
|
|
|
12212
12448
|
`;
|
|
12213
12449
|
const aiToggle = document.createElement('input');
|
|
12214
12450
|
aiToggle.type = 'checkbox';
|
|
12451
|
+
aiToggle.checked = aiAssistState;
|
|
12215
12452
|
aiToggle.dataset.remoteFleetAiToggle = 'true';
|
|
12216
12453
|
aiToggle.style.cssText = `
|
|
12217
12454
|
width: 14px;
|
|
@@ -12223,10 +12460,13 @@
|
|
|
12223
12460
|
aiToggleText.textContent = 'AI';
|
|
12224
12461
|
aiToggleLabel.appendChild(aiToggle);
|
|
12225
12462
|
aiToggleLabel.appendChild(aiToggleText);
|
|
12226
|
-
const
|
|
12463
|
+
const sendVisibleButton = createRemoteFleetButton('Send visible', 'Dispatch task to visible connected task-capable devices', 'task-visible');
|
|
12464
|
+
sendVisibleButton.style.height = '54px';
|
|
12465
|
+
const sendConnectedButton = createRemoteFleetButton('Send all', 'Dispatch task to all connected task-capable devices', 'task-connected');
|
|
12227
12466
|
sendConnectedButton.style.height = '54px';
|
|
12228
12467
|
taskRow.appendChild(taskInput);
|
|
12229
12468
|
taskRow.appendChild(aiToggleLabel);
|
|
12469
|
+
taskRow.appendChild(sendVisibleButton);
|
|
12230
12470
|
taskRow.appendChild(sendConnectedButton);
|
|
12231
12471
|
bodyView.appendChild(taskRow);
|
|
12232
12472
|
|
|
@@ -12249,6 +12489,141 @@
|
|
|
12249
12489
|
`;
|
|
12250
12490
|
bodyView.appendChild(taskFeedback);
|
|
12251
12491
|
|
|
12492
|
+
if (focusedDevice) {
|
|
12493
|
+
const focusedId = getRemoteFleetDeviceId(focusedDevice);
|
|
12494
|
+
const focusedName = getRemoteFleetDeviceName(focusedDevice);
|
|
12495
|
+
const liveActive = isRemoteFleetLiveActive(focusedDevice);
|
|
12496
|
+
const liveFrameDataUrl = String(getRemoteFleetDeviceField(focusedDevice, 'liveFrameDataUrl', 'LiveFrameDataUrl', ''));
|
|
12497
|
+
const liveFrameAt = String(getRemoteFleetDeviceField(focusedDevice, 'liveFrameReceivedAt', 'LiveFrameReceivedAt', ''));
|
|
12498
|
+
const liveStreamId = String(getRemoteFleetDeviceField(focusedDevice, 'liveStreamId', 'LiveStreamId', ''));
|
|
12499
|
+
const hasLiveFrame = hasRemoteFleetLiveFrame(focusedDevice);
|
|
12500
|
+
const livePanel = document.createElement('section');
|
|
12501
|
+
livePanel.dataset.remoteFleetLivePanel = 'true';
|
|
12502
|
+
livePanel.dataset.deviceId = focusedId;
|
|
12503
|
+
livePanel.style.cssText = `
|
|
12504
|
+
flex: 0 0 auto;
|
|
12505
|
+
display: grid;
|
|
12506
|
+
grid-template-columns: minmax(220px, 0.95fr) minmax(0, 1.25fr);
|
|
12507
|
+
gap: 10px;
|
|
12508
|
+
min-height: 170px;
|
|
12509
|
+
padding: 10px;
|
|
12510
|
+
border-radius: 8px;
|
|
12511
|
+
border: 1px solid rgba(14, 165, 233, 0.24);
|
|
12512
|
+
background: rgba(255, 255, 255, 0.82);
|
|
12513
|
+
`;
|
|
12514
|
+
|
|
12515
|
+
const livePreview = document.createElement('div');
|
|
12516
|
+
livePreview.style.cssText = `
|
|
12517
|
+
position: relative;
|
|
12518
|
+
min-width: 0;
|
|
12519
|
+
aspect-ratio: 16 / 9;
|
|
12520
|
+
overflow: hidden;
|
|
12521
|
+
border-radius: 7px;
|
|
12522
|
+
background: linear-gradient(135deg, #111827 0%, #1f2937 100%);
|
|
12523
|
+
border: 1px solid rgba(15, 23, 42, 0.16);
|
|
12524
|
+
`;
|
|
12525
|
+
if (hasLiveFrame) {
|
|
12526
|
+
const image = document.createElement('img');
|
|
12527
|
+
image.src = liveFrameDataUrl;
|
|
12528
|
+
image.alt = `${focusedName} live screen`;
|
|
12529
|
+
image.decoding = 'async';
|
|
12530
|
+
image.style.cssText = `
|
|
12531
|
+
width: 100%;
|
|
12532
|
+
height: 100%;
|
|
12533
|
+
object-fit: cover;
|
|
12534
|
+
display: block;
|
|
12535
|
+
`;
|
|
12536
|
+
livePreview.appendChild(image);
|
|
12537
|
+
} else {
|
|
12538
|
+
const placeholder = document.createElement('div');
|
|
12539
|
+
placeholder.textContent = liveActive ? 'Waiting for live frame' : 'Live view idle';
|
|
12540
|
+
placeholder.style.cssText = `
|
|
12541
|
+
position: absolute;
|
|
12542
|
+
inset: 0;
|
|
12543
|
+
display: flex;
|
|
12544
|
+
align-items: center;
|
|
12545
|
+
justify-content: center;
|
|
12546
|
+
color: rgba(226, 232, 240, 0.82);
|
|
12547
|
+
font-size: 12px;
|
|
12548
|
+
font-weight: 900;
|
|
12549
|
+
letter-spacing: 0;
|
|
12550
|
+
`;
|
|
12551
|
+
livePreview.appendChild(placeholder);
|
|
12552
|
+
}
|
|
12553
|
+
const liveBadge = document.createElement('span');
|
|
12554
|
+
liveBadge.textContent = liveActive
|
|
12555
|
+
? `LIVE ${liveFrameAt ? formatRemoteFleetAge(liveFrameAt) : ''}`.trim()
|
|
12556
|
+
: 'FOCUS';
|
|
12557
|
+
liveBadge.style.cssText = `
|
|
12558
|
+
position: absolute;
|
|
12559
|
+
left: 7px;
|
|
12560
|
+
top: 7px;
|
|
12561
|
+
max-width: calc(100% - 14px);
|
|
12562
|
+
padding: 4px 7px;
|
|
12563
|
+
border-radius: 999px;
|
|
12564
|
+
background: ${liveActive ? 'rgba(220, 38, 38, 0.86)' : 'rgba(15, 23, 42, 0.72)'};
|
|
12565
|
+
color: #ffffff;
|
|
12566
|
+
font-size: 9px;
|
|
12567
|
+
font-weight: 950;
|
|
12568
|
+
line-height: 1;
|
|
12569
|
+
overflow: hidden;
|
|
12570
|
+
text-overflow: ellipsis;
|
|
12571
|
+
white-space: nowrap;
|
|
12572
|
+
letter-spacing: 0;
|
|
12573
|
+
`;
|
|
12574
|
+
livePreview.appendChild(liveBadge);
|
|
12575
|
+
|
|
12576
|
+
const liveInfo = document.createElement('div');
|
|
12577
|
+
liveInfo.style.cssText = `
|
|
12578
|
+
min-width: 0;
|
|
12579
|
+
display: flex;
|
|
12580
|
+
flex-direction: column;
|
|
12581
|
+
justify-content: space-between;
|
|
12582
|
+
gap: 8px;
|
|
12583
|
+
`;
|
|
12584
|
+
const liveTitle = document.createElement('div');
|
|
12585
|
+
liveTitle.style.cssText = 'min-width:0;display:flex;flex-direction:column;gap:3px;';
|
|
12586
|
+
const liveName = document.createElement('strong');
|
|
12587
|
+
liveName.textContent = focusedName;
|
|
12588
|
+
liveName.title = focusedName;
|
|
12589
|
+
liveName.style.cssText = 'color:#0f172a;font-size:15px;font-weight:950;line-height:1.15;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:0;';
|
|
12590
|
+
const liveMeta = document.createElement('span');
|
|
12591
|
+
liveMeta.textContent = liveActive
|
|
12592
|
+
? `RemoteFast ${getRemoteFleetDeviceField(focusedDevice, 'liveStreamFps', 'LiveStreamFps', 0) || 12} fps`
|
|
12593
|
+
: 'View-only focused screen stream';
|
|
12594
|
+
liveMeta.style.cssText = 'color:#475569;font-size:11px;font-weight:800;line-height:1.25;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:0;';
|
|
12595
|
+
liveTitle.appendChild(liveName);
|
|
12596
|
+
liveTitle.appendChild(liveMeta);
|
|
12597
|
+
|
|
12598
|
+
const liveActions = document.createElement('div');
|
|
12599
|
+
liveActions.style.cssText = 'display:flex;align-items:center;gap:8px;flex-wrap:wrap;';
|
|
12600
|
+
if (isRemoteFleetDeviceConnected(focusedDevice) && getRemoteFleetDeviceField(focusedDevice, 'liveStreamEnabled', 'LiveStreamEnabled', false) === true) {
|
|
12601
|
+
const startLiveButton = createRemoteFleetButton(liveActive ? 'Restart live' : 'Start live', 'Start focused view-only live stream', 'live-start');
|
|
12602
|
+
startLiveButton.dataset.deviceId = focusedId;
|
|
12603
|
+
startLiveButton.style.height = '30px';
|
|
12604
|
+
liveActions.appendChild(startLiveButton);
|
|
12605
|
+
if (liveActive) {
|
|
12606
|
+
const stopLiveButton = createRemoteFleetButton('Stop live', 'Stop focused live stream', 'live-stop');
|
|
12607
|
+
stopLiveButton.dataset.deviceId = focusedId;
|
|
12608
|
+
stopLiveButton.dataset.streamId = liveStreamId;
|
|
12609
|
+
stopLiveButton.style.height = '30px';
|
|
12610
|
+
stopLiveButton.style.borderColor = 'rgba(220, 38, 38, 0.32)';
|
|
12611
|
+
stopLiveButton.style.color = '#b91c1c';
|
|
12612
|
+
liveActions.appendChild(stopLiveButton);
|
|
12613
|
+
}
|
|
12614
|
+
} else {
|
|
12615
|
+
const disabled = document.createElement('span');
|
|
12616
|
+
disabled.textContent = 'Live unavailable';
|
|
12617
|
+
disabled.style.cssText = 'color:#64748b;font-size:11px;font-weight:900;';
|
|
12618
|
+
liveActions.appendChild(disabled);
|
|
12619
|
+
}
|
|
12620
|
+
liveInfo.appendChild(liveTitle);
|
|
12621
|
+
liveInfo.appendChild(liveActions);
|
|
12622
|
+
livePanel.appendChild(livePreview);
|
|
12623
|
+
livePanel.appendChild(liveInfo);
|
|
12624
|
+
bodyView.appendChild(livePanel);
|
|
12625
|
+
}
|
|
12626
|
+
|
|
12252
12627
|
if (lastError.trim()) {
|
|
12253
12628
|
const errorEl = document.createElement('div');
|
|
12254
12629
|
errorEl.textContent = lastError;
|
|
@@ -12273,7 +12648,7 @@
|
|
|
12273
12648
|
overflow-y: auto;
|
|
12274
12649
|
overflow-x: hidden;
|
|
12275
12650
|
display: grid;
|
|
12276
|
-
grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
|
|
12651
|
+
grid-template-columns: ${densityState === 'dense' ? 'repeat(auto-fill, minmax(220px, 1fr))' : 'repeat(auto-fill, minmax(168px, 1fr))'};
|
|
12277
12652
|
align-content: start;
|
|
12278
12653
|
gap: 8px;
|
|
12279
12654
|
padding-right: 4px;
|
|
@@ -12298,19 +12673,25 @@
|
|
|
12298
12673
|
grid.appendChild(empty);
|
|
12299
12674
|
} else {
|
|
12300
12675
|
devices.forEach(device => {
|
|
12301
|
-
const connectedDevice = device
|
|
12302
|
-
const name =
|
|
12676
|
+
const connectedDevice = isRemoteFleetDeviceConnected(device);
|
|
12677
|
+
const name = getRemoteFleetDeviceName(device);
|
|
12303
12678
|
const platform = [device?.platform || device?.Platform, device?.arch || device?.Arch]
|
|
12304
12679
|
.filter(Boolean)
|
|
12305
12680
|
.join(' / ') || 'unknown';
|
|
12306
12681
|
const release = String(device?.release || device?.Release || '');
|
|
12307
|
-
const deviceId =
|
|
12682
|
+
const deviceId = getRemoteFleetDeviceId(device);
|
|
12308
12683
|
const thumbnailEnabled = device?.thumbnailEnabled === true || device?.ThumbnailEnabled === true;
|
|
12309
12684
|
const thumbnailDataUrl = String(device?.thumbnailDataUrl || device?.ThumbnailDataUrl || '');
|
|
12310
12685
|
const thumbnailCapturedAt = String(device?.thumbnailCapturedAt || device?.ThumbnailCapturedAt || '');
|
|
12311
|
-
const
|
|
12312
|
-
const
|
|
12313
|
-
const
|
|
12686
|
+
const liveStreamEnabled = device?.liveStreamEnabled === true || device?.LiveStreamEnabled === true;
|
|
12687
|
+
const liveStreamActive = isRemoteFleetLiveActive(device);
|
|
12688
|
+
const liveStreamId = String(device?.liveStreamId || device?.LiveStreamId || '');
|
|
12689
|
+
const liveFrameDataUrl = String(device?.liveFrameDataUrl || device?.LiveFrameDataUrl || '');
|
|
12690
|
+
const liveFrameReceivedAt = String(device?.liveFrameReceivedAt || device?.LiveFrameReceivedAt || '');
|
|
12691
|
+
const hasThumbnail = hasRemoteFleetThumbnail(device);
|
|
12692
|
+
const hasLiveFrame = hasRemoteFleetLiveFrame(device);
|
|
12693
|
+
const taskEnabled = isRemoteFleetDeviceTaskCapable(device);
|
|
12694
|
+
const aiAssistEnabled = isRemoteFleetDeviceAiCapable(device);
|
|
12314
12695
|
const aiModel = String(device?.aiModel || device?.AiModel || '');
|
|
12315
12696
|
const latestTaskStatus = String(device?.latestTaskStatus || device?.LatestTaskStatus || '');
|
|
12316
12697
|
const latestTaskTitle = String(device?.latestTaskTitle || device?.LatestTaskTitle || '');
|
|
@@ -12320,80 +12701,94 @@
|
|
|
12320
12701
|
const latestTaskResult = String(device?.latestTaskResultSummary || device?.LatestTaskResultSummary || '');
|
|
12321
12702
|
const card = document.createElement('article');
|
|
12322
12703
|
card.dataset.deviceId = deviceId;
|
|
12704
|
+
card.dataset.remoteFleetSearchText = buildRemoteFleetSearchText(device);
|
|
12705
|
+
card.dataset.remoteFleetConnected = connectedDevice ? 'true' : 'false';
|
|
12706
|
+
card.dataset.remoteFleetTaskCapable = taskEnabled ? 'true' : 'false';
|
|
12707
|
+
card.dataset.remoteFleetAiCapable = aiAssistEnabled ? 'true' : 'false';
|
|
12708
|
+
card.dataset.remoteFleetHasFrame = (hasThumbnail || hasLiveFrame) ? 'true' : 'false';
|
|
12709
|
+
card.dataset.remoteFleetLiveCapable = liveStreamEnabled ? 'true' : 'false';
|
|
12710
|
+
card.dataset.remoteFleetLiveActive = liveStreamActive ? 'true' : 'false';
|
|
12711
|
+
card.dataset.remoteFleetIssue = (!connectedDevice || latestTaskStatus === 'failed' || !!latestTaskError) ? 'true' : 'false';
|
|
12323
12712
|
card.style.cssText = `
|
|
12324
12713
|
display: flex;
|
|
12325
12714
|
flex-direction: column;
|
|
12326
|
-
gap: 8px;
|
|
12715
|
+
gap: ${densityState === 'dense' ? '6px' : '8px'};
|
|
12327
12716
|
min-width: 0;
|
|
12328
|
-
min-height: 134px;
|
|
12329
|
-
padding: 10px;
|
|
12717
|
+
min-height: ${densityState === 'dense' ? '92px' : '134px'};
|
|
12718
|
+
padding: ${densityState === 'dense' ? '8px' : '10px'};
|
|
12330
12719
|
border-radius: 8px;
|
|
12331
12720
|
background: #ffffff;
|
|
12332
12721
|
border: 1px solid ${connectedDevice ? 'rgba(16, 185, 129, 0.34)' : 'rgba(148, 163, 184, 0.28)'};
|
|
12333
12722
|
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.06);
|
|
12334
12723
|
`;
|
|
12335
12724
|
|
|
12336
|
-
|
|
12337
|
-
|
|
12338
|
-
|
|
12339
|
-
|
|
12340
|
-
|
|
12341
|
-
|
|
12342
|
-
border-radius: 7px;
|
|
12343
|
-
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
|
|
12344
|
-
border: 1px solid rgba(15, 23, 42, 0.12);
|
|
12345
|
-
`;
|
|
12346
|
-
if (hasThumbnail) {
|
|
12347
|
-
const image = document.createElement('img');
|
|
12348
|
-
image.src = thumbnailDataUrl;
|
|
12349
|
-
image.alt = `${name} thumbnail`;
|
|
12350
|
-
image.loading = 'lazy';
|
|
12351
|
-
image.decoding = 'async';
|
|
12352
|
-
image.style.cssText = `
|
|
12725
|
+
if (densityState !== 'dense') {
|
|
12726
|
+
const previewDataUrl = hasLiveFrame ? liveFrameDataUrl : thumbnailDataUrl;
|
|
12727
|
+
const previewAt = hasLiveFrame ? liveFrameReceivedAt : thumbnailCapturedAt;
|
|
12728
|
+
const preview = document.createElement('div');
|
|
12729
|
+
preview.style.cssText = `
|
|
12730
|
+
position: relative;
|
|
12353
12731
|
width: 100%;
|
|
12354
|
-
|
|
12355
|
-
object-fit: cover;
|
|
12356
|
-
display: block;
|
|
12357
|
-
`;
|
|
12358
|
-
preview.appendChild(image);
|
|
12359
|
-
} else {
|
|
12360
|
-
const placeholder = document.createElement('div');
|
|
12361
|
-
placeholder.textContent = thumbnailEnabled ? 'No frame yet' : 'Status only';
|
|
12362
|
-
placeholder.style.cssText = `
|
|
12363
|
-
position: absolute;
|
|
12364
|
-
inset: 0;
|
|
12365
|
-
display: flex;
|
|
12366
|
-
align-items: center;
|
|
12367
|
-
justify-content: center;
|
|
12368
|
-
color: rgba(226, 232, 240, 0.78);
|
|
12369
|
-
font-size: 11px;
|
|
12370
|
-
font-weight: 900;
|
|
12371
|
-
letter-spacing: 0;
|
|
12372
|
-
`;
|
|
12373
|
-
preview.appendChild(placeholder);
|
|
12374
|
-
}
|
|
12375
|
-
if (thumbnailCapturedAt) {
|
|
12376
|
-
const badge = document.createElement('span');
|
|
12377
|
-
badge.textContent = formatRemoteFleetAge(thumbnailCapturedAt);
|
|
12378
|
-
badge.style.cssText = `
|
|
12379
|
-
position: absolute;
|
|
12380
|
-
right: 6px;
|
|
12381
|
-
bottom: 6px;
|
|
12382
|
-
max-width: calc(100% - 12px);
|
|
12383
|
-
padding: 3px 6px;
|
|
12384
|
-
border-radius: 999px;
|
|
12385
|
-
background: rgba(15, 23, 42, 0.74);
|
|
12386
|
-
color: #e2e8f0;
|
|
12387
|
-
font-size: 9px;
|
|
12388
|
-
font-weight: 900;
|
|
12389
|
-
line-height: 1;
|
|
12732
|
+
aspect-ratio: 16 / 9;
|
|
12390
12733
|
overflow: hidden;
|
|
12391
|
-
|
|
12392
|
-
|
|
12734
|
+
border-radius: 7px;
|
|
12735
|
+
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
|
|
12736
|
+
border: 1px solid rgba(15, 23, 42, 0.12);
|
|
12393
12737
|
`;
|
|
12394
|
-
|
|
12738
|
+
if (hasLiveFrame || hasThumbnail) {
|
|
12739
|
+
const image = document.createElement('img');
|
|
12740
|
+
image.src = previewDataUrl;
|
|
12741
|
+
image.alt = hasLiveFrame ? `${name} live frame` : `${name} thumbnail`;
|
|
12742
|
+
image.loading = 'lazy';
|
|
12743
|
+
image.decoding = 'async';
|
|
12744
|
+
image.style.cssText = `
|
|
12745
|
+
width: 100%;
|
|
12746
|
+
height: 100%;
|
|
12747
|
+
object-fit: cover;
|
|
12748
|
+
display: block;
|
|
12749
|
+
`;
|
|
12750
|
+
preview.appendChild(image);
|
|
12751
|
+
} else {
|
|
12752
|
+
const placeholder = document.createElement('div');
|
|
12753
|
+
placeholder.textContent = thumbnailEnabled ? 'No frame yet' : 'Status only';
|
|
12754
|
+
placeholder.style.cssText = `
|
|
12755
|
+
position: absolute;
|
|
12756
|
+
inset: 0;
|
|
12757
|
+
display: flex;
|
|
12758
|
+
align-items: center;
|
|
12759
|
+
justify-content: center;
|
|
12760
|
+
color: rgba(226, 232, 240, 0.78);
|
|
12761
|
+
font-size: 11px;
|
|
12762
|
+
font-weight: 900;
|
|
12763
|
+
letter-spacing: 0;
|
|
12764
|
+
`;
|
|
12765
|
+
preview.appendChild(placeholder);
|
|
12766
|
+
}
|
|
12767
|
+
if (previewAt) {
|
|
12768
|
+
const badge = document.createElement('span');
|
|
12769
|
+
badge.textContent = hasLiveFrame
|
|
12770
|
+
? `LIVE ${formatRemoteFleetAge(previewAt)}`
|
|
12771
|
+
: formatRemoteFleetAge(previewAt);
|
|
12772
|
+
badge.style.cssText = `
|
|
12773
|
+
position: absolute;
|
|
12774
|
+
right: 6px;
|
|
12775
|
+
bottom: 6px;
|
|
12776
|
+
max-width: calc(100% - 12px);
|
|
12777
|
+
padding: 3px 6px;
|
|
12778
|
+
border-radius: 999px;
|
|
12779
|
+
background: ${hasLiveFrame ? 'rgba(220, 38, 38, 0.82)' : 'rgba(15, 23, 42, 0.74)'};
|
|
12780
|
+
color: #e2e8f0;
|
|
12781
|
+
font-size: 9px;
|
|
12782
|
+
font-weight: 900;
|
|
12783
|
+
line-height: 1;
|
|
12784
|
+
overflow: hidden;
|
|
12785
|
+
text-overflow: ellipsis;
|
|
12786
|
+
white-space: nowrap;
|
|
12787
|
+
`;
|
|
12788
|
+
preview.appendChild(badge);
|
|
12789
|
+
}
|
|
12790
|
+
card.appendChild(preview);
|
|
12395
12791
|
}
|
|
12396
|
-
card.appendChild(preview);
|
|
12397
12792
|
|
|
12398
12793
|
const cardHeader = document.createElement('div');
|
|
12399
12794
|
cardHeader.style.cssText = 'display:flex;align-items:flex-start;gap:8px;min-width:0;';
|
|
@@ -12470,7 +12865,23 @@
|
|
|
12470
12865
|
addMetric('Uptime', formatRemoteFleetDuration(device?.uptimeSec ?? device?.UptimeSec));
|
|
12471
12866
|
addMetric('Mem', formatRemoteFleetPercent(device?.usedMemRatio ?? device?.UsedMemRatio));
|
|
12472
12867
|
addMetric('Load', formatRemoteFleetNumber(device?.load1 ?? device?.Load1, 2));
|
|
12473
|
-
|
|
12868
|
+
if (densityState === 'dense') {
|
|
12869
|
+
const denseMeta = document.createElement('div');
|
|
12870
|
+
denseMeta.textContent = `Seen ${formatRemoteFleetAge(device?.lastSeenAt || device?.LastSeenAt)} - Mem ${formatRemoteFleetPercent(device?.usedMemRatio ?? device?.UsedMemRatio)} - Load ${formatRemoteFleetNumber(device?.load1 ?? device?.Load1, 2)}`;
|
|
12871
|
+
denseMeta.style.cssText = `
|
|
12872
|
+
color: #475569;
|
|
12873
|
+
font-size: 10px;
|
|
12874
|
+
font-weight: 750;
|
|
12875
|
+
line-height: 1.2;
|
|
12876
|
+
overflow: hidden;
|
|
12877
|
+
text-overflow: ellipsis;
|
|
12878
|
+
white-space: nowrap;
|
|
12879
|
+
letter-spacing: 0;
|
|
12880
|
+
`;
|
|
12881
|
+
card.appendChild(denseMeta);
|
|
12882
|
+
} else {
|
|
12883
|
+
card.appendChild(metrics);
|
|
12884
|
+
}
|
|
12474
12885
|
|
|
12475
12886
|
if (latestTaskStatus || latestTaskTitle || latestTaskResult || latestTaskError) {
|
|
12476
12887
|
const taskBox = document.createElement('div');
|
|
@@ -12523,11 +12934,11 @@
|
|
|
12523
12934
|
actions.style.cssText = 'display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:auto;';
|
|
12524
12935
|
const status = document.createElement('span');
|
|
12525
12936
|
status.textContent = connectedDevice
|
|
12526
|
-
? (aiAssistEnabled ? `AI ${aiModel || 'ready'}` : 'Connected')
|
|
12937
|
+
? (liveStreamActive ? 'Live' : (aiAssistEnabled ? `AI ${aiModel || 'ready'}` : 'Connected'))
|
|
12527
12938
|
: 'Offline';
|
|
12528
12939
|
status.style.cssText = `
|
|
12529
12940
|
min-width: 0;
|
|
12530
|
-
color: ${connectedDevice ? '#047857' : '#64748b'};
|
|
12941
|
+
color: ${liveStreamActive ? '#b91c1c' : (connectedDevice ? '#047857' : '#64748b')};
|
|
12531
12942
|
font-size: 11px;
|
|
12532
12943
|
font-weight: 900;
|
|
12533
12944
|
overflow: hidden;
|
|
@@ -12536,6 +12947,23 @@
|
|
|
12536
12947
|
`;
|
|
12537
12948
|
actions.appendChild(status);
|
|
12538
12949
|
if (connectedDevice && deviceId) {
|
|
12950
|
+
const focusButton = createRemoteFleetButton('Focus', 'Show this device in focused live panel', 'live-focus');
|
|
12951
|
+
focusButton.dataset.deviceId = deviceId;
|
|
12952
|
+
focusButton.style.height = '24px';
|
|
12953
|
+
focusButton.style.fontSize = '10px';
|
|
12954
|
+
actions.appendChild(focusButton);
|
|
12955
|
+
if (liveStreamEnabled) {
|
|
12956
|
+
const liveButton = createRemoteFleetButton(liveStreamActive ? 'Stop' : 'Live', liveStreamActive ? 'Stop live stream' : 'Start focused live stream', liveStreamActive ? 'live-stop' : 'live-start');
|
|
12957
|
+
liveButton.dataset.deviceId = deviceId;
|
|
12958
|
+
liveButton.dataset.streamId = liveStreamId;
|
|
12959
|
+
liveButton.style.height = '24px';
|
|
12960
|
+
liveButton.style.fontSize = '10px';
|
|
12961
|
+
if (liveStreamActive) {
|
|
12962
|
+
liveButton.style.borderColor = 'rgba(220, 38, 38, 0.32)';
|
|
12963
|
+
liveButton.style.color = '#b91c1c';
|
|
12964
|
+
}
|
|
12965
|
+
actions.appendChild(liveButton);
|
|
12966
|
+
}
|
|
12539
12967
|
if (taskEnabled) {
|
|
12540
12968
|
const taskButton = createRemoteFleetButton('Task', 'Dispatch task to this device', 'task-device');
|
|
12541
12969
|
taskButton.dataset.deviceId = deviceId;
|
|
@@ -12559,12 +12987,31 @@
|
|
|
12559
12987
|
card.appendChild(actions);
|
|
12560
12988
|
grid.appendChild(card);
|
|
12561
12989
|
});
|
|
12990
|
+
|
|
12991
|
+
const noMatch = document.createElement('div');
|
|
12992
|
+
noMatch.dataset.remoteFleetNoMatch = 'true';
|
|
12993
|
+
noMatch.textContent = 'No matching devices.';
|
|
12994
|
+
noMatch.style.cssText = `
|
|
12995
|
+
grid-column: 1 / -1;
|
|
12996
|
+
display: none;
|
|
12997
|
+
align-items: center;
|
|
12998
|
+
min-height: 74px;
|
|
12999
|
+
padding: 14px;
|
|
13000
|
+
border-radius: 8px;
|
|
13001
|
+
border: 1px dashed rgba(100, 116, 139, 0.36);
|
|
13002
|
+
color: #475569;
|
|
13003
|
+
font-size: 13px;
|
|
13004
|
+
font-weight: 850;
|
|
13005
|
+
background: rgba(255, 255, 255, 0.74);
|
|
13006
|
+
`;
|
|
13007
|
+
grid.appendChild(noMatch);
|
|
12562
13008
|
}
|
|
12563
13009
|
|
|
12564
13010
|
bodyView.appendChild(grid);
|
|
12565
13011
|
|
|
12566
13012
|
const footer = document.createElement('div');
|
|
12567
13013
|
footer.textContent = `Endpoint ${endpoint} · refreshed ${formatRemoteFleetAge(refreshedAt)}`;
|
|
13014
|
+
footer.textContent = `Endpoint ${endpoint} - all devices, no paging - refreshed ${formatRemoteFleetAge(refreshedAt)}`;
|
|
12568
13015
|
footer.style.cssText = `
|
|
12569
13016
|
flex: 0 0 auto;
|
|
12570
13017
|
color: #64748b;
|
|
@@ -12600,6 +13047,128 @@
|
|
|
12600
13047
|
|
|
12601
13048
|
const readTaskInstruction = () => String(taskInput.value || '').trim();
|
|
12602
13049
|
const useAiAssist = () => aiToggle.checked === true;
|
|
13050
|
+
const getDeviceCards = () => Array.from(grid.querySelectorAll('article[data-device-id]'));
|
|
13051
|
+
const getVisibleEligibleDeviceIds = () => {
|
|
13052
|
+
const wantsAi = useAiAssist();
|
|
13053
|
+
return getDeviceCards()
|
|
13054
|
+
.filter(card => card.style.display !== 'none'
|
|
13055
|
+
&& card.dataset.remoteFleetConnected === 'true'
|
|
13056
|
+
&& card.dataset.remoteFleetTaskCapable === 'true'
|
|
13057
|
+
&& (!wantsAi || card.dataset.remoteFleetAiCapable === 'true'))
|
|
13058
|
+
.map(card => String(card.dataset.deviceId || '').trim())
|
|
13059
|
+
.filter(Boolean);
|
|
13060
|
+
};
|
|
13061
|
+
|
|
13062
|
+
const applyRemoteFleetFilters = () => {
|
|
13063
|
+
const searchText = String(searchInput.value || '').trim().toLowerCase();
|
|
13064
|
+
const terms = searchText.split(/\s+/).filter(Boolean);
|
|
13065
|
+
const filterMode = String(filterSelect.value || 'all');
|
|
13066
|
+
let visible = 0;
|
|
13067
|
+
let eligible = 0;
|
|
13068
|
+
const wantsAi = useAiAssist();
|
|
13069
|
+
|
|
13070
|
+
getDeviceCards().forEach(card => {
|
|
13071
|
+
const matchesSearch = terms.length === 0
|
|
13072
|
+
|| terms.every(term => String(card.dataset.remoteFleetSearchText || '').includes(term));
|
|
13073
|
+
const matchesMode = filterMode === 'all'
|
|
13074
|
+
|| (filterMode === 'connected' && card.dataset.remoteFleetConnected === 'true')
|
|
13075
|
+
|| (filterMode === 'offline' && card.dataset.remoteFleetConnected !== 'true')
|
|
13076
|
+
|| (filterMode === 'task' && card.dataset.remoteFleetConnected === 'true' && card.dataset.remoteFleetTaskCapable === 'true')
|
|
13077
|
+
|| (filterMode === 'ai' && card.dataset.remoteFleetConnected === 'true' && card.dataset.remoteFleetAiCapable === 'true')
|
|
13078
|
+
|| (filterMode === 'live' && card.dataset.remoteFleetLiveActive === 'true')
|
|
13079
|
+
|| (filterMode === 'frames' && card.dataset.remoteFleetHasFrame === 'true')
|
|
13080
|
+
|| (filterMode === 'issues' && card.dataset.remoteFleetIssue === 'true');
|
|
13081
|
+
const show = matchesSearch && matchesMode;
|
|
13082
|
+
card.style.display = show ? 'flex' : 'none';
|
|
13083
|
+
if (show) {
|
|
13084
|
+
visible += 1;
|
|
13085
|
+
if (card.dataset.remoteFleetConnected === 'true'
|
|
13086
|
+
&& card.dataset.remoteFleetTaskCapable === 'true'
|
|
13087
|
+
&& (!wantsAi || card.dataset.remoteFleetAiCapable === 'true')) {
|
|
13088
|
+
eligible += 1;
|
|
13089
|
+
}
|
|
13090
|
+
}
|
|
13091
|
+
});
|
|
13092
|
+
|
|
13093
|
+
const noMatch = grid.querySelector('[data-remote-fleet-no-match="true"]');
|
|
13094
|
+
if (noMatch) {
|
|
13095
|
+
noMatch.style.display = devices.length > 0 && visible === 0 ? 'flex' : 'none';
|
|
13096
|
+
}
|
|
13097
|
+
|
|
13098
|
+
matchCount.textContent = `${visible}/${devices.length}`;
|
|
13099
|
+
matchCount.title = `${visible} visible, ${eligible} targetable`;
|
|
13100
|
+
sendVisibleButton.disabled = eligible <= 0;
|
|
13101
|
+
sendVisibleButton.title = eligible > 0
|
|
13102
|
+
? `Dispatch to ${eligible} visible target(s)`
|
|
13103
|
+
: 'No visible targetable devices';
|
|
13104
|
+
const allTargetCount = wantsAi ? aiCapableCount : taskCapableCount;
|
|
13105
|
+
sendConnectedButton.disabled = allTargetCount <= 0;
|
|
13106
|
+
sendConnectedButton.title = allTargetCount > 0
|
|
13107
|
+
? `Dispatch to ${allTargetCount} connected target(s)`
|
|
13108
|
+
: 'No connected targetable devices';
|
|
13109
|
+
grid.querySelectorAll('[data-remote-fleet-action="task-device"]').forEach(button => {
|
|
13110
|
+
const card = button.closest('article[data-device-id]');
|
|
13111
|
+
button.disabled = wantsAi && card?.dataset.remoteFleetAiCapable !== 'true';
|
|
13112
|
+
button.title = button.disabled ? 'AI assist is not enabled on this device' : 'Dispatch task to this device';
|
|
13113
|
+
});
|
|
13114
|
+
|
|
13115
|
+
bodyView.dataset.remoteFleetSearch = searchText;
|
|
13116
|
+
bodyView.dataset.remoteFleetFilter = filterMode;
|
|
13117
|
+
bodyView.dataset.remoteFleetSort = String(sortSelect.value || 'status');
|
|
13118
|
+
bodyView.dataset.remoteFleetDensity = String(densitySelect.value || 'cards');
|
|
13119
|
+
bodyView.dataset.remoteFleetAiAssist = wantsAi ? 'true' : 'false';
|
|
13120
|
+
};
|
|
13121
|
+
|
|
13122
|
+
[searchInput, filterSelect, sortSelect, densitySelect, aiToggle].forEach(control => {
|
|
13123
|
+
['mousedown', 'mouseup', 'click', 'dblclick', 'keydown'].forEach(eventName => {
|
|
13124
|
+
control.addEventListener(eventName, event => event.stopPropagation());
|
|
13125
|
+
});
|
|
13126
|
+
});
|
|
13127
|
+
|
|
13128
|
+
searchInput.addEventListener('input', applyRemoteFleetFilters);
|
|
13129
|
+
filterSelect.addEventListener('change', applyRemoteFleetFilters);
|
|
13130
|
+
aiToggle.addEventListener('change', applyRemoteFleetFilters);
|
|
13131
|
+
sortSelect.addEventListener('change', () => {
|
|
13132
|
+
bodyView.dataset.remoteFleetSort = String(sortSelect.value || 'status');
|
|
13133
|
+
renderRemoteFleetMonitor(bodyView, nodeModel);
|
|
13134
|
+
});
|
|
13135
|
+
densitySelect.addEventListener('change', () => {
|
|
13136
|
+
bodyView.dataset.remoteFleetDensity = String(densitySelect.value || 'cards');
|
|
13137
|
+
renderRemoteFleetMonitor(bodyView, nodeModel);
|
|
13138
|
+
});
|
|
13139
|
+
applyRemoteFleetFilters();
|
|
13140
|
+
|
|
13141
|
+
sendVisibleButton.addEventListener('click', async event => {
|
|
13142
|
+
event.preventDefault();
|
|
13143
|
+
event.stopPropagation();
|
|
13144
|
+
const instruction = readTaskInstruction();
|
|
13145
|
+
if (!instruction) {
|
|
13146
|
+
setTaskFeedback('Write a task first.', 'error');
|
|
13147
|
+
taskInput.focus();
|
|
13148
|
+
return;
|
|
13149
|
+
}
|
|
13150
|
+
|
|
13151
|
+
const targetIds = getVisibleEligibleDeviceIds();
|
|
13152
|
+
if (targetIds.length === 0) {
|
|
13153
|
+
setTaskFeedback('No visible targetable devices.', 'error');
|
|
13154
|
+
return;
|
|
13155
|
+
}
|
|
13156
|
+
|
|
13157
|
+
sendVisibleButton.disabled = true;
|
|
13158
|
+
setTaskFeedback('Dispatching task to visible devices...');
|
|
13159
|
+
try {
|
|
13160
|
+
const result = await invokeDotNetAsync('DispatchRemoteFleetTaskBatchFromJs', nodeId, targetIds, instruction, useAiAssist());
|
|
13161
|
+
await syncRemoteFleetNodeStateFromResult(result);
|
|
13162
|
+
if (result?.success) {
|
|
13163
|
+
const mode = useAiAssist() ? 'AI task' : 'remote task';
|
|
13164
|
+
setTaskFeedback(`Queued ${result.queued || targetIds.length} visible ${mode}(s).`, 'success');
|
|
13165
|
+
} else {
|
|
13166
|
+
setTaskFeedback(result?.error || 'Task dispatch failed.', 'error');
|
|
13167
|
+
}
|
|
13168
|
+
} finally {
|
|
13169
|
+
applyRemoteFleetFilters();
|
|
13170
|
+
}
|
|
13171
|
+
});
|
|
12603
13172
|
|
|
12604
13173
|
sendConnectedButton.addEventListener('click', async event => {
|
|
12605
13174
|
event.preventDefault();
|
|
@@ -12623,7 +13192,7 @@
|
|
|
12623
13192
|
setTaskFeedback(result?.error || 'Task dispatch failed.', 'error');
|
|
12624
13193
|
}
|
|
12625
13194
|
} finally {
|
|
12626
|
-
|
|
13195
|
+
applyRemoteFleetFilters();
|
|
12627
13196
|
}
|
|
12628
13197
|
});
|
|
12629
13198
|
|
|
@@ -12636,6 +13205,62 @@
|
|
|
12636
13205
|
refreshButton.disabled = false;
|
|
12637
13206
|
});
|
|
12638
13207
|
|
|
13208
|
+
bodyView.querySelectorAll('[data-remote-fleet-action="live-focus"]').forEach(button => {
|
|
13209
|
+
button.addEventListener('click', event => {
|
|
13210
|
+
event.preventDefault();
|
|
13211
|
+
event.stopPropagation();
|
|
13212
|
+
const deviceId = String(button.dataset.deviceId || '').trim();
|
|
13213
|
+
if (!deviceId) return;
|
|
13214
|
+
bodyView.dataset.remoteFleetFocusDeviceId = deviceId;
|
|
13215
|
+
renderRemoteFleetMonitor(bodyView, nodeModel);
|
|
13216
|
+
});
|
|
13217
|
+
});
|
|
13218
|
+
|
|
13219
|
+
bodyView.querySelectorAll('[data-remote-fleet-action="live-start"]').forEach(button => {
|
|
13220
|
+
button.addEventListener('click', async event => {
|
|
13221
|
+
event.preventDefault();
|
|
13222
|
+
event.stopPropagation();
|
|
13223
|
+
const deviceId = String(button.dataset.deviceId || '').trim();
|
|
13224
|
+
if (!deviceId) return;
|
|
13225
|
+
bodyView.dataset.remoteFleetFocusDeviceId = deviceId;
|
|
13226
|
+
button.disabled = true;
|
|
13227
|
+
setTaskFeedback('Starting focused live stream...');
|
|
13228
|
+
try {
|
|
13229
|
+
const result = await invokeDotNetAsync('StartRemoteFleetLiveStreamFromJs', nodeId, deviceId);
|
|
13230
|
+
await syncRemoteFleetNodeStateFromResult(result);
|
|
13231
|
+
if (result?.success) {
|
|
13232
|
+
setTaskFeedback('Focused live stream started.', 'success');
|
|
13233
|
+
} else {
|
|
13234
|
+
setTaskFeedback(result?.error || 'Live stream start failed.', 'error');
|
|
13235
|
+
}
|
|
13236
|
+
} finally {
|
|
13237
|
+
button.disabled = false;
|
|
13238
|
+
}
|
|
13239
|
+
});
|
|
13240
|
+
});
|
|
13241
|
+
|
|
13242
|
+
bodyView.querySelectorAll('[data-remote-fleet-action="live-stop"]').forEach(button => {
|
|
13243
|
+
button.addEventListener('click', async event => {
|
|
13244
|
+
event.preventDefault();
|
|
13245
|
+
event.stopPropagation();
|
|
13246
|
+
const deviceId = String(button.dataset.deviceId || '').trim();
|
|
13247
|
+
if (!deviceId) return;
|
|
13248
|
+
button.disabled = true;
|
|
13249
|
+
setTaskFeedback('Stopping focused live stream...');
|
|
13250
|
+
try {
|
|
13251
|
+
const result = await invokeDotNetAsync('StopRemoteFleetLiveStreamFromJs', nodeId, deviceId, button.dataset.streamId || '');
|
|
13252
|
+
await syncRemoteFleetNodeStateFromResult(result);
|
|
13253
|
+
if (result?.success) {
|
|
13254
|
+
setTaskFeedback('Focused live stream stopped.', 'success');
|
|
13255
|
+
} else {
|
|
13256
|
+
setTaskFeedback(result?.error || 'Live stream stop failed.', 'error');
|
|
13257
|
+
}
|
|
13258
|
+
} finally {
|
|
13259
|
+
button.disabled = false;
|
|
13260
|
+
}
|
|
13261
|
+
});
|
|
13262
|
+
});
|
|
13263
|
+
|
|
12639
13264
|
grid.querySelectorAll('[data-remote-fleet-action="ping-device"]').forEach(button => {
|
|
12640
13265
|
button.addEventListener('click', async event => {
|
|
12641
13266
|
event.preventDefault();
|
|
@@ -12670,6 +13295,7 @@
|
|
|
12670
13295
|
}
|
|
12671
13296
|
} finally {
|
|
12672
13297
|
button.disabled = false;
|
|
13298
|
+
applyRemoteFleetFilters();
|
|
12673
13299
|
}
|
|
12674
13300
|
});
|
|
12675
13301
|
});
|
|
@@ -12684,6 +13310,24 @@
|
|
|
12684
13310
|
button.disabled = false;
|
|
12685
13311
|
});
|
|
12686
13312
|
});
|
|
13313
|
+
|
|
13314
|
+
if (hasActiveLiveStream) {
|
|
13315
|
+
bodyView._remoteFleetLiveRefreshTimer = setInterval(async () => {
|
|
13316
|
+
if (!document.body.contains(bodyView)) {
|
|
13317
|
+
clearInterval(bodyView._remoteFleetLiveRefreshTimer);
|
|
13318
|
+
bodyView._remoteFleetLiveRefreshTimer = null;
|
|
13319
|
+
return;
|
|
13320
|
+
}
|
|
13321
|
+
|
|
13322
|
+
try {
|
|
13323
|
+
const result = await invokeDotNetAsync('RefreshRemoteFleetMonitorNodeFromJs', nodeId);
|
|
13324
|
+
await syncRemoteFleetNodeStateFromResult(result);
|
|
13325
|
+
} catch {
|
|
13326
|
+
clearInterval(bodyView._remoteFleetLiveRefreshTimer);
|
|
13327
|
+
bodyView._remoteFleetLiveRefreshTimer = null;
|
|
13328
|
+
}
|
|
13329
|
+
}, 1000);
|
|
13330
|
+
}
|
|
12687
13331
|
}
|
|
12688
13332
|
|
|
12689
13333
|
function bindMemoNodeEvents(container, nodeModel) {
|