@mindexec/cli 0.2.23 → 0.2.25

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.
Files changed (95) hide show
  1. package/package.json +1 -1
  2. package/remote-hub.js +134 -1
  3. package/scripts/remote-fleet-render-smoke.mjs +87 -53
  4. package/scripts/remote-http-smoke.mjs +219 -39
  5. package/scripts/remote-hub-smoke.mjs +139 -0
  6. package/server.js +20 -1
  7. package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js +18 -1
  8. package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +670 -395
  9. package/wwwroot/_framework/{Microsoft.CSharp.qrvp77qmhs.dll → Microsoft.CSharp.8bsm8vx6su.dll} +0 -0
  10. package/wwwroot/_framework/MindExecution.Core.6rfnfdndxq.dll +0 -0
  11. package/wwwroot/_framework/{MindExecution.Kernel.badrt1tkvv.dll → MindExecution.Kernel.z56elxihok.dll} +0 -0
  12. package/wwwroot/_framework/{MindExecution.Plugins.Admin.73w1bvz4r1.dll → MindExecution.Plugins.Admin.p5cs4ap87v.dll} +0 -0
  13. package/wwwroot/_framework/{MindExecution.Plugins.Business.dvd82y422m.dll → MindExecution.Plugins.Business.s35og5uz44.dll} +0 -0
  14. package/wwwroot/_framework/{MindExecution.Plugins.Concept.m3ukc0xvom.dll → MindExecution.Plugins.Concept.zczca3fsxz.dll} +0 -0
  15. package/wwwroot/_framework/{MindExecution.Plugins.Directory.23tm2uvfvu.dll → MindExecution.Plugins.Directory.y74f55e8x3.dll} +0 -0
  16. package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.8nrc7ge4ob.dll → MindExecution.Plugins.PlanMaster.jpdwbefrh1.dll} +0 -0
  17. package/wwwroot/_framework/{MindExecution.Plugins.YouTube.3ox59073d8.dll → MindExecution.Plugins.YouTube.8nz4wv2nsj.dll} +0 -0
  18. package/wwwroot/_framework/{MindExecution.Shared.va1gxp0crd.dll → MindExecution.Shared.v6ani8nfp8.dll} +0 -0
  19. package/wwwroot/_framework/MindExecution.Web.0cs29v57jl.dll +0 -0
  20. package/wwwroot/_framework/{System.Collections.x53e19vfsj.dll → System.Collections.23yxgetbju.dll} +0 -0
  21. package/wwwroot/_framework/{System.Collections.Concurrent.y1zmvuyipi.dll → System.Collections.Concurrent.vow3rm1dku.dll} +0 -0
  22. package/wwwroot/_framework/{System.Collections.Immutable.ug3j698qms.dll → System.Collections.Immutable.ap9596utv5.dll} +0 -0
  23. package/wwwroot/_framework/{System.Collections.NonGeneric.h66hj3863h.dll → System.Collections.NonGeneric.npjkaz40oc.dll} +0 -0
  24. package/wwwroot/_framework/{System.Collections.Specialized.umr3y27ntj.dll → System.Collections.Specialized.ugkjbs6p02.dll} +0 -0
  25. package/wwwroot/_framework/{System.ComponentModel.Annotations.tz6gnt4ebt.dll → System.ComponentModel.Annotations.clwo0z2oyu.dll} +0 -0
  26. package/wwwroot/_framework/{System.ComponentModel.Primitives.j7tiphu4rg.dll → System.ComponentModel.Primitives.end4v0xe2c.dll} +0 -0
  27. package/wwwroot/_framework/{System.ComponentModel.TypeConverter.ujlztox1gx.dll → System.ComponentModel.TypeConverter.tgtp5sm4iv.dll} +0 -0
  28. package/wwwroot/_framework/{System.ComponentModel.x9xz0ojfb6.dll → System.ComponentModel.wupoltkk1t.dll} +0 -0
  29. package/wwwroot/_framework/{System.Console.ijzpqmj7ne.dll → System.Console.9dik0wogo2.dll} +0 -0
  30. package/wwwroot/_framework/{System.Data.Common.1r0sqffq1p.dll → System.Data.Common.4v8jejsiu0.dll} +0 -0
  31. package/wwwroot/_framework/{System.Diagnostics.DiagnosticSource.9upoqwq09o.dll → System.Diagnostics.DiagnosticSource.e2q75ondtq.dll} +0 -0
  32. package/wwwroot/_framework/{System.Diagnostics.Process.m99azzntjm.dll → System.Diagnostics.Process.9736yjnxs8.dll} +0 -0
  33. package/wwwroot/_framework/{System.Diagnostics.TraceSource.pl7wv26myr.dll → System.Diagnostics.TraceSource.h9al53gbbw.dll} +0 -0
  34. package/wwwroot/_framework/{System.Diagnostics.Tracing.crlhfx6tut.dll → System.Diagnostics.Tracing.h4bcp2fo98.dll} +0 -0
  35. package/wwwroot/_framework/{System.Drawing.mi7d8hwowb.dll → System.Drawing.64oovy8qts.dll} +0 -0
  36. package/wwwroot/_framework/{System.Drawing.Primitives.22e4y9ikq9.dll → System.Drawing.Primitives.o6jiqpgbgl.dll} +0 -0
  37. package/wwwroot/_framework/{System.Formats.Asn1.jx23sjiqnn.dll → System.Formats.Asn1.rylx5ipd40.dll} +0 -0
  38. package/wwwroot/_framework/{System.IO.Compression.6fyoii3uej.dll → System.IO.Compression.iceabaupns.dll} +0 -0
  39. package/wwwroot/_framework/{System.IO.Pipelines.vg77t4cd4d.dll → System.IO.Pipelines.uw8csd3mlz.dll} +0 -0
  40. package/wwwroot/_framework/{System.Linq.Expressions.24xqiypwdt.dll → System.Linq.Expressions.ty95ava37f.dll} +0 -0
  41. package/wwwroot/_framework/{System.Linq.Queryable.hvd01d6rsa.dll → System.Linq.Queryable.hs2195jrwy.dll} +0 -0
  42. package/wwwroot/_framework/{System.Linq.1bkoxlqgmq.dll → System.Linq.hssodjwmlf.dll} +0 -0
  43. package/wwwroot/_framework/{System.Memory.8dx3lwgym4.dll → System.Memory.1k78n7wdxb.dll} +0 -0
  44. package/wwwroot/_framework/{System.Net.Http.eitrz660my.dll → System.Net.Http.3br8rfql4c.dll} +0 -0
  45. package/wwwroot/_framework/{System.Net.Http.Json.3mhdm9l1rf.dll → System.Net.Http.Json.860wbh17d8.dll} +0 -0
  46. package/wwwroot/_framework/{System.Net.NetworkInformation.3pkuofcv9r.dll → System.Net.NetworkInformation.e3wr00853o.dll} +0 -0
  47. package/wwwroot/_framework/{System.Net.Ping.8clj5pklrp.dll → System.Net.Ping.r5cw4mf1a4.dll} +0 -0
  48. package/wwwroot/_framework/{System.Net.Primitives.qrp4wcjz1p.dll → System.Net.Primitives.ksxwiwlvhu.dll} +0 -0
  49. package/wwwroot/_framework/{System.Net.WebSockets.qp6u31zvm5.dll → System.Net.WebSockets.6rt3n3gl2q.dll} +0 -0
  50. package/wwwroot/_framework/{System.Net.WebSockets.Client.2u6pv01g69.dll → System.Net.WebSockets.Client.z3usrzo7rz.dll} +0 -0
  51. package/wwwroot/_framework/{System.Numerics.Vectors.kc7ufp2j4l.dll → System.Numerics.Vectors.lbdzx8reja.dll} +0 -0
  52. package/wwwroot/_framework/{System.ObjectModel.qv82fot1ib.dll → System.ObjectModel.yct1gdirzf.dll} +0 -0
  53. package/wwwroot/_framework/{System.Private.CoreLib.rkafq04oma.dll → System.Private.CoreLib.ns29bor93l.dll} +0 -0
  54. package/wwwroot/_framework/{System.Private.Uri.t9542hmr6j.dll → System.Private.Uri.zp9kmg0z93.dll} +0 -0
  55. package/wwwroot/_framework/{System.Private.Xml.Linq.n8n3ptrbwu.dll → System.Private.Xml.Linq.lgv2n0akl4.dll} +0 -0
  56. package/wwwroot/_framework/{System.Private.Xml.rxd3tytisn.dll → System.Private.Xml.dpsk8g304y.dll} +0 -0
  57. package/wwwroot/_framework/{System.Reflection.Emit.ILGeneration.stxyk8zoo1.dll → System.Reflection.Emit.ILGeneration.1tcuz2cmbk.dll} +0 -0
  58. package/wwwroot/_framework/{System.Reflection.Emit.Lightweight.6xrd5v8vg0.dll → System.Reflection.Emit.Lightweight.ddt2wylovg.dll} +0 -0
  59. package/wwwroot/_framework/{System.Reflection.Emit.9tjhp6y0j3.dll → System.Reflection.Emit.d8vkadiwhg.dll} +0 -0
  60. package/wwwroot/_framework/{System.Reflection.Primitives.wgn8fpwwvv.dll → System.Reflection.Primitives.cpsl71xd1z.dll} +0 -0
  61. package/wwwroot/_framework/{System.Runtime.InteropServices.te07xr2we9.dll → System.Runtime.InteropServices.31vjgsfk1o.dll} +0 -0
  62. package/wwwroot/_framework/{System.Runtime.InteropServices.JavaScript.sliym526xh.dll → System.Runtime.InteropServices.JavaScript.pn2wvizzet.dll} +0 -0
  63. package/wwwroot/_framework/{System.Runtime.InteropServices.RuntimeInformation.oji7zut14z.dll → System.Runtime.InteropServices.RuntimeInformation.l5rk496q70.dll} +0 -0
  64. package/wwwroot/_framework/{System.Runtime.Intrinsics.507y4h8nzq.dll → System.Runtime.Intrinsics.0zee9qcqfy.dll} +0 -0
  65. package/wwwroot/_framework/{System.Runtime.Loader.v7gk4bse0k.dll → System.Runtime.Loader.xw2jr9wl92.dll} +0 -0
  66. package/wwwroot/_framework/{System.Runtime.Numerics.eqy5xjv3nd.dll → System.Runtime.Numerics.ezj1dfyvbj.dll} +0 -0
  67. package/wwwroot/_framework/{System.Runtime.Serialization.Formatters.zpkrub8lab.dll → System.Runtime.Serialization.Formatters.0y019e9zkr.dll} +0 -0
  68. package/wwwroot/_framework/{System.Runtime.Serialization.Primitives.vhkpnbxjip.dll → System.Runtime.Serialization.Primitives.bia5wb62c8.dll} +0 -0
  69. package/wwwroot/_framework/{System.Runtime.jn319d5nyg.dll → System.Runtime.h9cduidfkh.dll} +0 -0
  70. package/wwwroot/_framework/{System.Security.Claims.0ztig1q9vo.dll → System.Security.Claims.2r18wim2rl.dll} +0 -0
  71. package/wwwroot/_framework/{System.Security.Cryptography.vttizqc9ho.dll → System.Security.Cryptography.qqgybpoucx.dll} +0 -0
  72. package/wwwroot/_framework/{System.Text.Encoding.Extensions.utdd47ny8f.dll → System.Text.Encoding.Extensions.9t3hs6kyll.dll} +0 -0
  73. package/wwwroot/_framework/{System.Text.Encodings.Web.wah8r1zoe0.dll → System.Text.Encodings.Web.wftjfh2crk.dll} +0 -0
  74. package/wwwroot/_framework/{System.Text.Json.kxlfxj0wrs.dll → System.Text.Json.4s25e3op6i.dll} +0 -0
  75. package/wwwroot/_framework/{System.Text.RegularExpressions.dbqn58klox.dll → System.Text.RegularExpressions.cvkox11l6l.dll} +0 -0
  76. package/wwwroot/_framework/{System.Threading.Channels.hfa7j0uv2w.dll → System.Threading.Channels.419493szqu.dll} +0 -0
  77. package/wwwroot/_framework/{System.Threading.Thread.caul0pdqul.dll → System.Threading.Thread.xhmys87xh5.dll} +0 -0
  78. package/wwwroot/_framework/{System.Threading.42ao9vi047.dll → System.Threading.b66vzsz9g1.dll} +0 -0
  79. package/wwwroot/_framework/{System.Transactions.Local.fimi2hamzo.dll → System.Transactions.Local.7zfffdvnwf.dll} +0 -0
  80. package/wwwroot/_framework/{System.Web.HttpUtility.gq8yz50p2e.dll → System.Web.HttpUtility.9up6xfbtuq.dll} +0 -0
  81. package/wwwroot/_framework/{System.Xml.Linq.kitin4zjoj.dll → System.Xml.Linq.n5rzv9nbf7.dll} +0 -0
  82. package/wwwroot/_framework/{System.Xml.ReaderWriter.kzvw3qgxb0.dll → System.Xml.ReaderWriter.ag8pilllob.dll} +0 -0
  83. package/wwwroot/_framework/{System.Xml.XDocument.c539ki6cuq.dll → System.Xml.XDocument.sn51jas17n.dll} +0 -0
  84. package/wwwroot/_framework/{System.m05i39uvk9.dll → System.brmz7yk5qh.dll} +0 -0
  85. package/wwwroot/_framework/blazor.boot.json +161 -161
  86. package/wwwroot/_framework/dotnet.js +1 -1
  87. package/wwwroot/_framework/{dotnet.native.vz0adxojrz.wasm → dotnet.native.boem75ye5i.wasm} +0 -0
  88. package/wwwroot/_framework/{dotnet.native.xsn1d6x2kd.js → dotnet.native.qc8g39g30v.js} +1 -1
  89. package/wwwroot/_framework/{dotnet.runtime.dstopyvqzi.js → dotnet.runtime.opaiwunc3t.js} +1 -1
  90. package/wwwroot/_framework/{netstandard.0xet7jg7ky.dll → netstandard.yvr3prsx0x.dll} +0 -0
  91. package/wwwroot/index.html +1 -1
  92. package/wwwroot/service-worker-assets.js +166 -166
  93. package/wwwroot/service-worker.js +1 -1
  94. package/wwwroot/_framework/MindExecution.Core.c9fyqe953v.dll +0 -0
  95. package/wwwroot/_framework/MindExecution.Web.jmawk7z8d3.dll +0 -0
@@ -12139,6 +12139,25 @@
12139
12139
  return `${Math.floor(seconds / 86400)}d ago`;
12140
12140
  }
12141
12141
 
12142
+ function formatRemoteFleetTimeUntil(value) {
12143
+ const timestamp = Date.parse(String(value || ''));
12144
+ if (!Number.isFinite(timestamp)) {
12145
+ return '-';
12146
+ }
12147
+
12148
+ const seconds = Math.round((timestamp - Date.now()) / 1000);
12149
+ if (seconds <= 0) {
12150
+ return 'expired';
12151
+ }
12152
+ if (seconds < 60) {
12153
+ return `${seconds}s left`;
12154
+ }
12155
+ if (seconds < 3600) {
12156
+ return `${Math.floor(seconds / 60)}m left`;
12157
+ }
12158
+ return `${Math.floor(seconds / 3600)}h left`;
12159
+ }
12160
+
12142
12161
  function createRemoteFleetStat(label, value, tone = 'default', key = '') {
12143
12162
  const item = document.createElement('div');
12144
12163
  if (key) {
@@ -12244,6 +12263,142 @@
12244
12263
  const REMOTE_FLEET_TASK_FOLLOW_INITIAL_MS = 250;
12245
12264
  const REMOTE_FLEET_TASK_FOLLOW_REFRESH_MS = 2000;
12246
12265
  const REMOTE_FLEET_TASK_FOLLOW_MAX_TICKS = 60;
12266
+ const REMOTE_FLEET_HOST_LEASE_REFRESH_MS = 10000;
12267
+ const remoteFleetHostLeaseTimers = new Map();
12268
+ const remoteFleetLocalHostTargets = new Map();
12269
+
12270
+ function findRemoteFleetBodyByNodeId(nodeId) {
12271
+ const id = String(nodeId || '').trim();
12272
+ if (!id) return null;
12273
+ return Array.from(document.querySelectorAll('.map-node-remote-fleet__body[data-node-id]'))
12274
+ .find(body => String(body.dataset.nodeId || '').trim() === id) || null;
12275
+ }
12276
+
12277
+ function stopRemoteFleetHostLeaseTimer(nodeId) {
12278
+ const id = String(nodeId || '').trim();
12279
+ if (!id) return;
12280
+ const entry = remoteFleetHostLeaseTimers.get(id);
12281
+ if (entry?.timer) {
12282
+ clearInterval(entry.timer);
12283
+ }
12284
+ remoteFleetHostLeaseTimers.delete(id);
12285
+ }
12286
+
12287
+ function startRemoteFleetHostLeaseTimer(nodeId, renewCallback) {
12288
+ const id = String(nodeId || '').trim();
12289
+ if (!id || typeof renewCallback !== 'function') return;
12290
+ const existing = remoteFleetHostLeaseTimers.get(id);
12291
+ if (existing) {
12292
+ existing.renewCallback = renewCallback;
12293
+ return;
12294
+ }
12295
+
12296
+ const entry = { renewCallback, timer: null };
12297
+ const timer = setInterval(async () => {
12298
+ const currentBody = findRemoteFleetBodyByNodeId(id);
12299
+ if (!currentBody || currentBody.dataset.remoteFleetHostState !== 'hosting') {
12300
+ stopRemoteFleetHostLeaseTimer(id);
12301
+ return;
12302
+ }
12303
+
12304
+ try {
12305
+ await entry.renewCallback();
12306
+ } catch {
12307
+ stopRemoteFleetHostLeaseTimer(id);
12308
+ }
12309
+ }, REMOTE_FLEET_HOST_LEASE_REFRESH_MS);
12310
+
12311
+ timer?.unref?.();
12312
+ entry.timer = timer;
12313
+ remoteFleetHostLeaseTimers.set(id, entry);
12314
+ }
12315
+
12316
+ function getRemoteFleetNodeStateId(nodeState) {
12317
+ return String(nodeState?.nodeId ?? nodeState?.NodeId ?? nodeState?.id ?? nodeState?.Id ?? '').trim();
12318
+ }
12319
+
12320
+ function ensureRemoteFleetNodeStateMetadata(nodeState) {
12321
+ if (!nodeState || typeof nodeState !== 'object') return null;
12322
+ const metadata = nodeState.metadata && typeof nodeState.metadata === 'object'
12323
+ ? nodeState.metadata
12324
+ : nodeState.Metadata && typeof nodeState.Metadata === 'object'
12325
+ ? nodeState.Metadata
12326
+ : {};
12327
+ nodeState.metadata = metadata;
12328
+ nodeState.Metadata = metadata;
12329
+ return metadata;
12330
+ }
12331
+
12332
+ function readRemoteFleetObjectField(source, camelKey, pascalKey, fallback = '') {
12333
+ const value = source?.[camelKey] ?? source?.[pascalKey] ?? fallback;
12334
+ return value === undefined || value === null ? fallback : value;
12335
+ }
12336
+
12337
+ function isRemoteFleetResultSuccess(result) {
12338
+ return result?.success === true || result?.Success === true || result?.ok === true || result?.Ok === true;
12339
+ }
12340
+
12341
+ function isRemoteFleetResultActive(result) {
12342
+ return result?.active === true || result?.Active === true;
12343
+ }
12344
+
12345
+ function isRemoteFleetResultExplicitlyInactive(result) {
12346
+ return result?.active === false || result?.Active === false;
12347
+ }
12348
+
12349
+ function rememberRemoteFleetLocalHostTarget(nodeId, enabled, result) {
12350
+ const id = String(nodeId || '').trim();
12351
+ if (!id) return;
12352
+ if (enabled !== true) {
12353
+ if (isRemoteFleetResultSuccess(result)) {
12354
+ remoteFleetLocalHostTargets.delete(id);
12355
+ stopRemoteFleetHostLeaseTimer(id);
12356
+ }
12357
+ return;
12358
+ }
12359
+
12360
+ if (!isRemoteFleetResultSuccess(result) || isRemoteFleetResultExplicitlyInactive(result)) {
12361
+ return;
12362
+ }
12363
+
12364
+ const hostTarget = result?.hostTarget || result?.HostTarget || {};
12365
+ const now = new Date();
12366
+ const expiresAt = String(readRemoteFleetObjectField(hostTarget, 'expiresAt', 'ExpiresAt', '') || '').trim()
12367
+ || new Date(now.getTime() + 60000).toISOString();
12368
+ remoteFleetLocalHostTargets.set(id, {
12369
+ nodeId: id,
12370
+ leaseId: String(readRemoteFleetObjectField(hostTarget, 'leaseId', 'LeaseId', '') || '').trim(),
12371
+ endpoint: String(readRemoteFleetObjectField(hostTarget, 'endpoint', 'Endpoint', '') || '').trim(),
12372
+ activatedAt: String(readRemoteFleetObjectField(hostTarget, 'activatedAt', 'ActivatedAt', '') || '').trim() || now.toISOString(),
12373
+ updatedAt: String(readRemoteFleetObjectField(hostTarget, 'updatedAt', 'UpdatedAt', '') || '').trim() || now.toISOString(),
12374
+ expiresAt
12375
+ });
12376
+ }
12377
+
12378
+ function applyRemoteFleetLocalHostTarget(nodeState) {
12379
+ const nodeId = getRemoteFleetNodeStateId(nodeState);
12380
+ if (!nodeId) return nodeState;
12381
+ const localHost = remoteFleetLocalHostTargets.get(nodeId);
12382
+ if (!localHost) return nodeState;
12383
+
12384
+ const expiresTime = Date.parse(localHost.expiresAt);
12385
+ if (!Number.isFinite(expiresTime) || expiresTime <= Date.now()) {
12386
+ remoteFleetLocalHostTargets.delete(nodeId);
12387
+ stopRemoteFleetHostLeaseTimer(nodeId);
12388
+ return nodeState;
12389
+ }
12390
+
12391
+ const metadata = ensureRemoteFleetNodeStateMetadata(nodeState);
12392
+ if (!metadata) return nodeState;
12393
+ metadata.RemoteFleetHostTargetState = 'hosting';
12394
+ metadata.RemoteFleetHostTargetNodeId = nodeId;
12395
+ metadata.RemoteFleetHostTargetLeaseId = localHost.leaseId;
12396
+ metadata.RemoteFleetHostTargetEndpoint = localHost.endpoint || metadata.RemoteFleetHubEndpoint || metadata.remoteFleetHubEndpoint || '';
12397
+ metadata.RemoteFleetHostTargetActivatedAtUtc = localHost.activatedAt;
12398
+ metadata.RemoteFleetHostTargetUpdatedAtUtc = localHost.updatedAt;
12399
+ metadata.RemoteFleetHostTargetExpiresAtUtc = localHost.expiresAt;
12400
+ return nodeState;
12401
+ }
12247
12402
 
12248
12403
  function clearRemoteFleetTimers(bodyView) {
12249
12404
  if (!bodyView) return;
@@ -12259,6 +12414,10 @@
12259
12414
  clearTimeout(bodyView._remoteFleetTaskFollowTimer);
12260
12415
  bodyView._remoteFleetTaskFollowTimer = null;
12261
12416
  }
12417
+ if (bodyView._remoteFleetHostLeaseTimer) {
12418
+ clearInterval(bodyView._remoteFleetHostLeaseTimer);
12419
+ bodyView._remoteFleetHostLeaseTimer = null;
12420
+ }
12262
12421
  }
12263
12422
 
12264
12423
  function getRemoteFleetDeviceField(device, camelKey, pascalKey, fallback = '') {
@@ -12455,7 +12614,7 @@
12455
12614
  async function syncRemoteFleetNodeStateFromResult(result) {
12456
12615
  const nodeState = result?.nodeState || result?.refresh?.nodeState;
12457
12616
  if (nodeState && window.mindMap?.syncNodeStates) {
12458
- await window.mindMap.syncNodeStates([nodeState]);
12617
+ await window.mindMap.syncNodeStates([applyRemoteFleetLocalHostTarget(nodeState)]);
12459
12618
  }
12460
12619
  }
12461
12620
 
@@ -12997,6 +13156,16 @@
12997
13156
  if (focusedDevice) {
12998
13157
  bodyView.dataset.remoteFleetFocusDeviceId = getRemoteFleetDeviceId(focusedDevice);
12999
13158
  }
13159
+ const selectedState = bodyView.dataset.remoteFleetSelectedDeviceId || focusState || '';
13160
+ const selectedDevice = devices.find(device => getRemoteFleetDeviceId(device) === selectedState)
13161
+ || focusedDevice
13162
+ || devices[0]
13163
+ || null;
13164
+ if (selectedDevice) {
13165
+ bodyView.dataset.remoteFleetSelectedDeviceId = getRemoteFleetDeviceId(selectedDevice);
13166
+ } else {
13167
+ delete bodyView.dataset.remoteFleetSelectedDeviceId;
13168
+ }
13000
13169
  const total = Number(getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetDeviceCount', devices.length));
13001
13170
  const connected = Number(getRemoteFleetMetadataValue(
13002
13171
  nodeModel,
@@ -13005,15 +13174,24 @@
13005
13174
  const taskCapableCount = devices.filter(device => isRemoteFleetDeviceConnected(device) && isRemoteFleetDeviceTaskCapable(device)).length;
13006
13175
  const aiCapableCount = devices.filter(device => isRemoteFleetDeviceConnected(device) && isRemoteFleetDeviceAiCapable(device)).length;
13007
13176
  const endpoint = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetHubEndpoint', '127.0.0.1:5197');
13008
- const command = getRemoteFleetMetadataValue(
13009
- nodeModel,
13010
- 'RemoteFleetConnectCommand',
13011
- `npx @mindexec/remote connect --manager ${endpoint} --pair <pair-token>`);
13177
+ const managerPackage = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetManagerPackage', '@mindexec/cli');
13178
+ const managerVersion = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetManagerVersion', '');
13012
13179
  const hubStatus = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetHubStatus', 'offline');
13013
13180
  const refreshedAt = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetLastRefreshAtUtc', '');
13014
13181
  const lastError = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetLastError', '');
13182
+ const hostTargetState = String(getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetHostTargetState', 'inactive')).trim().toLowerCase();
13183
+ const hostTargetNodeId = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetHostTargetNodeId', '');
13184
+ const hostTargetEndpoint = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetHostTargetEndpoint', endpoint);
13185
+ const hostTargetExpiresAt = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetHostTargetExpiresAtUtc', '');
13186
+ const isHostingTarget = hostTargetState === 'hosting';
13187
+ const otherMonitorHosting = hostTargetState === 'other-monitor';
13188
+
13189
+ if (!isHostingTarget) {
13190
+ stopRemoteFleetHostLeaseTimer(nodeId);
13191
+ }
13015
13192
 
13016
13193
  bodyView.dataset.src = `remote-fleet:${refreshedAt}:${devices.length}:${connected}`;
13194
+ bodyView.dataset.remoteFleetHostState = hostTargetState;
13017
13195
  bodyView.classList.add('map-node-remote-fleet__body');
13018
13196
  bodyView.innerHTML = '';
13019
13197
  bodyView.style.cssText = `
@@ -13028,86 +13206,116 @@
13028
13206
  background: linear-gradient(180deg, rgba(248, 250, 252, 0.96), rgba(241, 245, 249, 0.92));
13029
13207
  `;
13030
13208
 
13031
- const top = document.createElement('div');
13032
- top.style.cssText = `
13033
- display: grid;
13034
- grid-template-columns: repeat(4, minmax(0, 1fr));
13035
- gap: 8px;
13209
+ const headerRow = document.createElement('div');
13210
+ headerRow.style.cssText = `
13211
+ display: flex;
13212
+ align-items: flex-start;
13213
+ justify-content: space-between;
13214
+ gap: 10px;
13036
13215
  flex: 0 0 auto;
13216
+ min-width: 0;
13037
13217
  `;
13038
- top.appendChild(createRemoteFleetStat('Hub', hubStatus === 'online' ? 'Online' : 'Offline', hubStatus === 'online' ? 'online' : 'default'));
13039
- top.appendChild(createRemoteFleetStat('Connected', `${Number.isFinite(connected) ? connected : 0}/${Number.isFinite(total) ? total : devices.length}`, connected > 0 ? 'online' : 'default'));
13040
- top.appendChild(createRemoteFleetStat('Task', String(taskCapableCount), taskCapableCount > 0 ? 'online' : 'default'));
13041
- top.appendChild(createRemoteFleetStat('AI', String(aiCapableCount), aiCapableCount > 0 ? 'online' : 'default'));
13042
- bodyView.appendChild(top);
13043
-
13044
- const commandRow = document.createElement('div');
13045
- commandRow.style.cssText = `
13046
- display: grid;
13047
- grid-template-columns: minmax(0, 1fr) auto auto auto;
13048
- gap: 8px;
13049
- align-items: center;
13218
+ const headerMeta = document.createElement('div');
13219
+ headerMeta.style.cssText = `
13220
+ min-width: 0;
13221
+ display: flex;
13222
+ flex-direction: column;
13223
+ gap: 4px;
13224
+ `;
13225
+ const managerRow = document.createElement('div');
13226
+ managerRow.dataset.remoteFleetManagerVersion = 'true';
13227
+ managerRow.textContent = `${managerPackage}${managerVersion ? ` ${managerVersion}` : ''} - ${endpoint}`;
13228
+ managerRow.title = managerRow.textContent;
13229
+ managerRow.style.cssText = `
13050
13230
  flex: 0 0 auto;
13231
+ min-width: 0;
13232
+ overflow: hidden;
13233
+ text-overflow: ellipsis;
13234
+ white-space: nowrap;
13235
+ color: #475569;
13236
+ font-size: 10px;
13237
+ font-weight: 850;
13238
+ line-height: 1.2;
13239
+ letter-spacing: 0;
13051
13240
  `;
13052
13241
 
13053
- const commandText = document.createElement('code');
13054
- commandText.textContent = command;
13055
- commandText.style.cssText = `
13242
+ const hostStatusRow = document.createElement('div');
13243
+ hostStatusRow.dataset.remoteFleetHostState = 'true';
13244
+ const hostStatusText = isHostingTarget
13245
+ ? 'Host: Active'
13246
+ : otherMonitorHosting
13247
+ ? 'Host: Other monitor'
13248
+ : 'Host: Inactive';
13249
+ const hostLeaseText = isHostingTarget && hostTargetExpiresAt
13250
+ ? ` - lease ${formatRemoteFleetTimeUntil(hostTargetExpiresAt)}`
13251
+ : '';
13252
+ hostStatusRow.textContent = `${hostStatusText}${hostLeaseText}`;
13253
+ hostStatusRow.title = otherMonitorHosting
13254
+ ? `Active host monitor: ${hostTargetNodeId || 'unknown'}`
13255
+ : `Endpoint ${hostTargetEndpoint || endpoint}`;
13256
+ hostStatusRow.style.cssText = `
13257
+ flex: 0 0 auto;
13056
13258
  min-width: 0;
13057
13259
  overflow: hidden;
13058
13260
  text-overflow: ellipsis;
13059
13261
  white-space: nowrap;
13060
- padding: 8px 10px;
13061
- border-radius: 7px;
13062
- background: rgba(15, 23, 42, 0.92);
13063
- color: #e2e8f0;
13064
- font-family: ui-monospace, SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;
13065
- font-size: 11px;
13262
+ color: ${isHostingTarget ? '#047857' : otherMonitorHosting ? '#7c2d12' : '#64748b'};
13263
+ font-size: 10px;
13264
+ font-weight: 900;
13066
13265
  line-height: 1.2;
13266
+ letter-spacing: 0;
13067
13267
  `;
13068
- commandText.title = command;
13268
+ headerMeta.appendChild(managerRow);
13269
+ headerMeta.appendChild(hostStatusRow);
13069
13270
 
13070
- const copyButton = createRemoteFleetButton('Copy', 'Copy agent command', 'copy-command');
13071
- const refreshButton = createRemoteFleetButton('Refresh', 'Refresh remote devices', 'refresh');
13072
- const autoMonitorLabel = document.createElement('label');
13073
- autoMonitorLabel.title = 'Refresh stale thumbnails on a bounded timer';
13074
- autoMonitorLabel.style.cssText = `
13271
+ const hostActions = document.createElement('div');
13272
+ hostActions.style.cssText = `
13273
+ flex: 0 0 auto;
13075
13274
  display: inline-flex;
13076
13275
  align-items: center;
13077
- justify-content: center;
13276
+ justify-content: flex-end;
13078
13277
  gap: 6px;
13079
- height: 34px;
13080
- padding: 0 9px;
13081
- border-radius: 7px;
13082
- border: 1px solid rgba(14, 165, 233, 0.28);
13083
- background: ${autoMonitorState ? 'rgba(240, 249, 255, 0.92)' : 'rgba(248, 250, 252, 0.92)'};
13084
- color: ${autoMonitorState ? '#0369a1' : '#475569'};
13085
- font-size: 11px;
13086
- font-weight: 900;
13087
- letter-spacing: 0;
13088
- cursor: pointer;
13089
- pointer-events: auto;
13090
- user-select: none;
13091
13278
  `;
13092
- const autoMonitorToggle = document.createElement('input');
13093
- autoMonitorToggle.type = 'checkbox';
13094
- autoMonitorToggle.checked = autoMonitorState;
13095
- autoMonitorToggle.dataset.remoteFleetAutoToggle = 'true';
13096
- autoMonitorToggle.style.cssText = `
13097
- width: 14px;
13098
- height: 14px;
13099
- margin: 0;
13100
- accent-color: #0284c7;
13279
+ const hostButton = createRemoteFleetButton(
13280
+ isHostingTarget ? 'Hosting' : 'Set Host',
13281
+ isHostingTarget
13282
+ ? 'Renew this monitor as the active host target for remote agents.'
13283
+ : 'Set this monitor as the active host target for remote agents.',
13284
+ 'set-host');
13285
+ hostButton.dataset.remoteFleetHostActive = isHostingTarget ? 'true' : 'false';
13286
+ hostButton.style.height = '30px';
13287
+ hostButton.style.minWidth = '78px';
13288
+ hostButton.style.borderColor = isHostingTarget ? 'rgba(16, 185, 129, 0.38)' : 'rgba(37, 99, 235, 0.32)';
13289
+ hostButton.style.background = isHostingTarget ? 'rgba(236, 253, 245, 0.96)' : '#ffffff';
13290
+ hostButton.style.color = isHostingTarget ? '#047857' : '#1d4ed8';
13291
+ let stopHostButton = null;
13292
+ if (isHostingTarget) {
13293
+ stopHostButton = createRemoteFleetButton('Stop', 'Stop using this monitor as the host target.', 'stop-host');
13294
+ stopHostButton.style.height = '30px';
13295
+ stopHostButton.style.borderColor = 'rgba(239, 68, 68, 0.30)';
13296
+ stopHostButton.style.color = '#b91c1c';
13297
+ stopHostButton.style.background = 'rgba(254, 242, 242, 0.94)';
13298
+ }
13299
+ hostActions.appendChild(hostButton);
13300
+ if (stopHostButton) {
13301
+ hostActions.appendChild(stopHostButton);
13302
+ }
13303
+ headerRow.appendChild(headerMeta);
13304
+ headerRow.appendChild(hostActions);
13305
+ bodyView.appendChild(headerRow);
13306
+
13307
+ const top = document.createElement('div');
13308
+ top.style.cssText = `
13309
+ display: grid;
13310
+ grid-template-columns: repeat(4, minmax(0, 1fr));
13311
+ gap: 8px;
13312
+ flex: 0 0 auto;
13101
13313
  `;
13102
- const autoMonitorText = document.createElement('span');
13103
- autoMonitorText.textContent = 'Auto';
13104
- autoMonitorLabel.appendChild(autoMonitorToggle);
13105
- autoMonitorLabel.appendChild(autoMonitorText);
13106
- commandRow.appendChild(commandText);
13107
- commandRow.appendChild(autoMonitorLabel);
13108
- commandRow.appendChild(copyButton);
13109
- commandRow.appendChild(refreshButton);
13110
- bodyView.appendChild(commandRow);
13314
+ top.appendChild(createRemoteFleetStat('Hub', hubStatus === 'online' ? 'Online' : 'Offline', hubStatus === 'online' ? 'online' : 'default'));
13315
+ top.appendChild(createRemoteFleetStat('Connected', `${Number.isFinite(connected) ? connected : 0}/${Number.isFinite(total) ? total : devices.length}`, connected > 0 ? 'online' : 'default'));
13316
+ top.appendChild(createRemoteFleetStat('Task', String(taskCapableCount), taskCapableCount > 0 ? 'online' : 'default'));
13317
+ top.appendChild(createRemoteFleetStat('AI', String(aiCapableCount), aiCapableCount > 0 ? 'online' : 'default'));
13318
+ bodyView.appendChild(top);
13111
13319
 
13112
13320
  const filterRow = document.createElement('div');
13113
13321
  filterRow.style.cssText = `
@@ -13197,7 +13405,7 @@
13197
13405
 
13198
13406
  const taskRow = document.createElement('div');
13199
13407
  taskRow.style.cssText = `
13200
- display: grid;
13408
+ display: none;
13201
13409
  grid-template-columns: minmax(0, 1fr) auto auto auto;
13202
13410
  gap: 8px;
13203
13411
  align-items: stretch;
@@ -13205,7 +13413,7 @@
13205
13413
  `;
13206
13414
  const taskInput = document.createElement('textarea');
13207
13415
  taskInput.dataset.remoteFleetTaskInput = 'true';
13208
- taskInput.placeholder = 'Task for remote agents';
13416
+ taskInput.placeholder = '';
13209
13417
  taskInput.rows = 2;
13210
13418
  taskInput.style.cssText = `
13211
13419
  width: 100%;
@@ -13267,7 +13475,7 @@
13267
13475
  taskRow.appendChild(aiToggleLabel);
13268
13476
  taskRow.appendChild(sendVisibleButton);
13269
13477
  taskRow.appendChild(sendConnectedButton);
13270
- bodyView.appendChild(taskRow);
13478
+ taskRow.dataset.remoteFleetTaskComposer = 'hidden';
13271
13479
 
13272
13480
  const taskFeedback = document.createElement('div');
13273
13481
  taskFeedback.dataset.remoteFleetTaskFeedback = 'true';
@@ -13606,14 +13814,299 @@
13606
13814
  bodyView.appendChild(errorEl);
13607
13815
  }
13608
13816
 
13817
+ const createDevicePreview = (device, mode = 'tile') => {
13818
+ const name = getRemoteFleetDeviceName(device);
13819
+ const connectedDevice = isRemoteFleetDeviceConnected(device);
13820
+ const thumbnailEnabled = device?.thumbnailEnabled === true || device?.ThumbnailEnabled === true;
13821
+ const liveStreamEnabled = device?.liveStreamEnabled === true || device?.LiveStreamEnabled === true;
13822
+ const thumbnailDataUrl = String(device?.thumbnailDataUrl || device?.ThumbnailDataUrl || '');
13823
+ const thumbnailCapturedAt = String(device?.thumbnailCapturedAt || device?.ThumbnailCapturedAt || '');
13824
+ const liveFrameDataUrl = String(device?.liveFrameDataUrl || device?.LiveFrameDataUrl || '');
13825
+ const liveFrameReceivedAt = String(device?.liveFrameReceivedAt || device?.LiveFrameReceivedAt || '');
13826
+ const hasThumbnail = hasRemoteFleetThumbnail(device);
13827
+ const hasLiveFrame = hasRemoteFleetLiveFrame(device);
13828
+ const previewDataUrl = hasLiveFrame ? liveFrameDataUrl : thumbnailDataUrl;
13829
+ const previewAt = hasLiveFrame ? liveFrameReceivedAt : thumbnailCapturedAt;
13830
+ const isDetail = mode === 'detail';
13831
+
13832
+ const preview = document.createElement('div');
13833
+ preview.dataset.remoteFleetDevicePreview = mode;
13834
+ preview.style.cssText = `
13835
+ position: relative;
13836
+ width: 100%;
13837
+ aspect-ratio: 16 / 9;
13838
+ overflow: hidden;
13839
+ border-radius: ${isDetail ? '8px' : '6px'};
13840
+ background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
13841
+ border: 1px solid rgba(15, 23, 42, ${isDetail ? '0.16' : '0.10'});
13842
+ `;
13843
+
13844
+ if (hasLiveFrame || hasThumbnail) {
13845
+ const image = document.createElement('img');
13846
+ image.src = previewDataUrl;
13847
+ image.alt = hasLiveFrame ? `${name} live frame` : `${name} thumbnail`;
13848
+ image.loading = 'lazy';
13849
+ image.decoding = 'async';
13850
+ image.style.cssText = `
13851
+ width: 100%;
13852
+ height: 100%;
13853
+ object-fit: cover;
13854
+ display: block;
13855
+ `;
13856
+ preview.appendChild(image);
13857
+ } else {
13858
+ const placeholder = document.createElement('div');
13859
+ placeholder.textContent = thumbnailEnabled || liveStreamEnabled ? 'No screen' : 'No screen';
13860
+ placeholder.style.cssText = `
13861
+ position: absolute;
13862
+ inset: 0;
13863
+ display: flex;
13864
+ align-items: center;
13865
+ justify-content: center;
13866
+ color: rgba(226, 232, 240, 0.80);
13867
+ font-size: ${isDetail ? '12px' : '10px'};
13868
+ font-weight: 900;
13869
+ letter-spacing: 0;
13870
+ `;
13871
+ preview.appendChild(placeholder);
13872
+ }
13873
+
13874
+ const dot = document.createElement('span');
13875
+ dot.style.cssText = `
13876
+ position: absolute;
13877
+ left: ${isDetail ? '9px' : '6px'};
13878
+ top: ${isDetail ? '9px' : '6px'};
13879
+ width: ${isDetail ? '10px' : '8px'};
13880
+ height: ${isDetail ? '10px' : '8px'};
13881
+ border-radius: 999px;
13882
+ background: ${connectedDevice ? '#10b981' : '#94a3b8'};
13883
+ box-shadow: 0 0 0 3px ${connectedDevice ? 'rgba(16,185,129,0.20)' : 'rgba(148,163,184,0.18)'};
13884
+ `;
13885
+ preview.appendChild(dot);
13886
+
13887
+ if (hasLiveFrame || (isDetail && previewAt)) {
13888
+ const badge = document.createElement('span');
13889
+ badge.textContent = hasLiveFrame
13890
+ ? (isDetail && previewAt ? `LIVE ${formatRemoteFleetAge(previewAt)}` : 'LIVE')
13891
+ : formatRemoteFleetAge(previewAt);
13892
+ badge.style.cssText = `
13893
+ position: absolute;
13894
+ right: ${isDetail ? '9px' : '6px'};
13895
+ bottom: ${isDetail ? '9px' : '6px'};
13896
+ max-width: calc(100% - ${isDetail ? '18px' : '12px'});
13897
+ padding: ${isDetail ? '4px 7px' : '3px 6px'};
13898
+ border-radius: 999px;
13899
+ background: ${hasLiveFrame ? 'rgba(220, 38, 38, 0.84)' : 'rgba(15, 23, 42, 0.72)'};
13900
+ color: #e2e8f0;
13901
+ font-size: ${isDetail ? '9px' : '8px'};
13902
+ font-weight: 950;
13903
+ line-height: 1;
13904
+ overflow: hidden;
13905
+ text-overflow: ellipsis;
13906
+ white-space: nowrap;
13907
+ letter-spacing: 0;
13908
+ `;
13909
+ preview.appendChild(badge);
13910
+ }
13911
+
13912
+ return preview;
13913
+ };
13914
+
13915
+ const createSelectedDevicePanel = device => {
13916
+ const panel = document.createElement('aside');
13917
+ panel.dataset.remoteFleetDetailPanel = 'true';
13918
+ panel.style.cssText = `
13919
+ min-width: 0;
13920
+ min-height: 0;
13921
+ overflow-y: auto;
13922
+ display: flex;
13923
+ flex-direction: column;
13924
+ gap: 9px;
13925
+ padding: 10px;
13926
+ border-radius: 8px;
13927
+ border: 1px solid rgba(148, 163, 184, 0.28);
13928
+ background: rgba(255, 255, 255, 0.86);
13929
+ `;
13930
+
13931
+ if (!device) {
13932
+ const empty = document.createElement('div');
13933
+ empty.textContent = 'Select a screen';
13934
+ empty.style.cssText = 'display:flex;align-items:center;justify-content:center;min-height:120px;color:#64748b;font-size:12px;font-weight:900;';
13935
+ panel.appendChild(empty);
13936
+ return panel;
13937
+ }
13938
+
13939
+ const deviceId = getRemoteFleetDeviceId(device);
13940
+ const name = getRemoteFleetDeviceName(device);
13941
+ const connectedDevice = isRemoteFleetDeviceConnected(device);
13942
+ const platform = [device?.platform || device?.Platform, device?.arch || device?.Arch]
13943
+ .filter(Boolean)
13944
+ .join(' / ') || 'unknown';
13945
+ const release = String(device?.release || device?.Release || '');
13946
+ const thumbnailEnabled = device?.thumbnailEnabled === true || device?.ThumbnailEnabled === true;
13947
+ const liveStreamEnabled = device?.liveStreamEnabled === true || device?.LiveStreamEnabled === true;
13948
+ const liveStreamActive = isRemoteFleetLiveActive(device);
13949
+ const liveStreamId = String(device?.liveStreamId || device?.LiveStreamId || '');
13950
+ const taskEnabled = isRemoteFleetDeviceTaskCapable(device);
13951
+ const aiAssistEnabled = isRemoteFleetDeviceAiCapable(device);
13952
+ const aiModel = String(device?.aiModel || device?.AiModel || '');
13953
+ const latestTaskStatus = String(device?.latestTaskStatus || device?.LatestTaskStatus || '');
13954
+ const latestTaskTitle = String(device?.latestTaskTitle || device?.LatestTaskTitle || '');
13955
+ const latestTaskApproval = String(device?.latestTaskApprovalLevel || device?.LatestTaskApprovalLevel || '');
13956
+ const latestTaskUpdatedAt = String(device?.latestTaskUpdatedAt || device?.LatestTaskUpdatedAt || '');
13957
+ const latestTaskError = String(device?.latestTaskError || device?.LatestTaskError || '');
13958
+ const latestTaskResultModel = String(device?.latestTaskResultModel || device?.LatestTaskResultModel || '');
13959
+ const latestTaskResultResponseId = String(device?.latestTaskResultResponseId || device?.LatestTaskResultResponseId || '');
13960
+ const latestTaskResult = String(device?.latestTaskResultSummary || device?.LatestTaskResultSummary || '');
13961
+ const statusText = connectedDevice
13962
+ ? (liveStreamActive ? 'Live' : (aiAssistEnabled ? `AI ${aiModel || 'ready'}` : 'Connected'))
13963
+ : 'Offline';
13964
+
13965
+ panel.dataset.deviceId = deviceId;
13966
+ panel.appendChild(createDevicePreview(device, 'detail'));
13967
+
13968
+ const header = document.createElement('div');
13969
+ header.style.cssText = 'display:flex;align-items:flex-start;justify-content:space-between;gap:8px;min-width:0;';
13970
+ const titleBox = document.createElement('div');
13971
+ titleBox.style.cssText = 'min-width:0;display:flex;flex-direction:column;gap:3px;';
13972
+ const title = document.createElement('strong');
13973
+ title.textContent = name;
13974
+ title.title = name;
13975
+ title.style.cssText = 'color:#0f172a;font-size:14px;font-weight:950;line-height:1.15;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:0;';
13976
+ const subtitle = document.createElement('span');
13977
+ subtitle.textContent = release ? `${platform} ${release}` : platform;
13978
+ subtitle.title = subtitle.textContent;
13979
+ subtitle.style.cssText = 'color:#64748b;font-size:11px;font-weight:750;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:0;';
13980
+ titleBox.appendChild(title);
13981
+ titleBox.appendChild(subtitle);
13982
+ const status = document.createElement('span');
13983
+ status.textContent = statusText;
13984
+ status.style.cssText = `
13985
+ flex: 0 0 auto;
13986
+ max-width: 82px;
13987
+ padding: 4px 7px;
13988
+ border-radius: 999px;
13989
+ background: ${liveStreamActive ? 'rgba(254, 226, 226, 0.92)' : (connectedDevice ? 'rgba(209, 250, 229, 0.92)' : 'rgba(226, 232, 240, 0.92)')};
13990
+ color: ${liveStreamActive ? '#b91c1c' : (connectedDevice ? '#047857' : '#475569')};
13991
+ font-size: 9px;
13992
+ font-weight: 950;
13993
+ line-height: 1;
13994
+ overflow: hidden;
13995
+ text-overflow: ellipsis;
13996
+ white-space: nowrap;
13997
+ letter-spacing: 0;
13998
+ `;
13999
+ header.appendChild(titleBox);
14000
+ header.appendChild(status);
14001
+ panel.appendChild(header);
14002
+
14003
+ const metrics = document.createElement('div');
14004
+ metrics.style.cssText = 'display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px;';
14005
+ const addDetailMetric = (label, value) => {
14006
+ const metric = document.createElement('div');
14007
+ metric.style.cssText = 'min-width:0;padding:7px 8px;border-radius:7px;background:rgba(241,245,249,0.86);border:1px solid rgba(148,163,184,0.16);';
14008
+ const labelEl = document.createElement('div');
14009
+ labelEl.textContent = label;
14010
+ labelEl.style.cssText = 'color:#64748b;font-size:9px;font-weight:850;letter-spacing:0;text-transform:uppercase;';
14011
+ const valueEl = document.createElement('div');
14012
+ valueEl.textContent = value;
14013
+ valueEl.title = value;
14014
+ valueEl.style.cssText = 'color:#0f172a;font-size:12px;font-weight:950;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:0;';
14015
+ metric.appendChild(labelEl);
14016
+ metric.appendChild(valueEl);
14017
+ metrics.appendChild(metric);
14018
+ };
14019
+ addDetailMetric('Seen', formatRemoteFleetAge(device?.lastSeenAt || device?.LastSeenAt));
14020
+ addDetailMetric('Uptime', formatRemoteFleetDuration(device?.uptimeSec ?? device?.UptimeSec));
14021
+ addDetailMetric('Mem', formatRemoteFleetPercent(device?.usedMemRatio ?? device?.UsedMemRatio));
14022
+ addDetailMetric('Load', formatRemoteFleetNumber(device?.load1 ?? device?.Load1, 2));
14023
+ panel.appendChild(metrics);
14024
+
14025
+ if (latestTaskStatus || latestTaskTitle || latestTaskResult || latestTaskError) {
14026
+ const taskBox = document.createElement('div');
14027
+ const taskTone = latestTaskError || latestTaskStatus === 'failed'
14028
+ ? 'error'
14029
+ : (latestTaskStatus === 'completed' ? 'done' : 'pending');
14030
+ taskBox.style.cssText = `
14031
+ display: flex;
14032
+ flex-direction: column;
14033
+ gap: 4px;
14034
+ min-width: 0;
14035
+ padding: 8px 9px;
14036
+ border-radius: 7px;
14037
+ background: ${taskTone === 'error' ? 'rgba(248, 113, 113, 0.12)' : taskTone === 'done' ? 'rgba(16, 185, 129, 0.10)' : 'rgba(37, 99, 235, 0.08)'};
14038
+ border: 1px solid ${taskTone === 'error' ? 'rgba(248, 113, 113, 0.24)' : taskTone === 'done' ? 'rgba(16, 185, 129, 0.20)' : 'rgba(37, 99, 235, 0.16)'};
14039
+ `;
14040
+ const taskLine = document.createElement('div');
14041
+ const taskModeLabel = latestTaskApproval === 'ai-assist' ? 'AI' : 'Task';
14042
+ taskLine.textContent = `${taskModeLabel} ${latestTaskStatus || 'task'}${latestTaskUpdatedAt ? ` - ${formatRemoteFleetAge(latestTaskUpdatedAt)}` : ''}${latestTaskResultModel ? ` - ${latestTaskResultModel}` : ''}`;
14043
+ taskLine.title = latestTaskResultResponseId
14044
+ ? `${taskLine.textContent} (${latestTaskResultResponseId})`
14045
+ : taskLine.textContent;
14046
+ taskLine.style.cssText = `color:${taskTone === 'error' ? '#991b1b' : taskTone === 'done' ? '#047857' : '#1d4ed8'};font-size:10px;font-weight:950;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:0;`;
14047
+ const taskSummary = document.createElement('div');
14048
+ taskSummary.textContent = formatRemoteFleetTaskError(latestTaskError) || latestTaskResult || latestTaskTitle || 'Task queued';
14049
+ taskSummary.title = taskSummary.textContent;
14050
+ taskSummary.style.cssText = 'color:#334155;font-size:10px;font-weight:750;line-height:1.25;overflow:hidden;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;letter-spacing:0;';
14051
+ taskBox.appendChild(taskLine);
14052
+ taskBox.appendChild(taskSummary);
14053
+ panel.appendChild(taskBox);
14054
+ }
14055
+
14056
+ const actions = document.createElement('div');
14057
+ actions.style.cssText = 'display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-top:auto;';
14058
+ if (deviceId) {
14059
+ const pinButton = createRemoteFleetButton('Pin', 'Pin this device as a canvas node', 'pin-device');
14060
+ pinButton.dataset.deviceId = deviceId;
14061
+ actions.appendChild(pinButton);
14062
+ }
14063
+ if (connectedDevice && deviceId) {
14064
+ const focusButton = createRemoteFleetButton('Focus', 'Show this device in focused live panel', 'live-focus');
14065
+ focusButton.dataset.deviceId = deviceId;
14066
+ actions.appendChild(focusButton);
14067
+ if (liveStreamEnabled) {
14068
+ const liveButton = createRemoteFleetButton(liveStreamActive ? 'Stop' : 'Live', liveStreamActive ? 'Stop live stream' : 'Start focused live stream', liveStreamActive ? 'live-stop' : 'live-start');
14069
+ liveButton.dataset.deviceId = deviceId;
14070
+ liveButton.dataset.streamId = liveStreamId;
14071
+ if (liveStreamActive) {
14072
+ liveButton.style.borderColor = 'rgba(220, 38, 38, 0.32)';
14073
+ liveButton.style.color = '#b91c1c';
14074
+ }
14075
+ actions.appendChild(liveButton);
14076
+ }
14077
+ if (thumbnailEnabled) {
14078
+ const thumbnailButton = createRemoteFleetButton('Shot', 'Request thumbnail', 'thumbnail-device');
14079
+ thumbnailButton.dataset.deviceId = deviceId;
14080
+ actions.appendChild(thumbnailButton);
14081
+ }
14082
+ const pingButton = createRemoteFleetButton('Ping', 'Ping device', 'ping-device');
14083
+ pingButton.dataset.deviceId = deviceId;
14084
+ actions.appendChild(pingButton);
14085
+ }
14086
+ panel.appendChild(actions);
14087
+ return panel;
14088
+ };
14089
+
14090
+ const monitorWorkspace = document.createElement('div');
14091
+ monitorWorkspace.dataset.remoteFleetMonitorWorkspace = 'true';
14092
+ monitorWorkspace.style.cssText = `
14093
+ flex: 1 1 auto;
14094
+ min-height: 0;
14095
+ overflow: hidden;
14096
+ display: grid;
14097
+ grid-template-columns: minmax(0, 1fr) minmax(230px, 270px);
14098
+ gap: 10px;
14099
+ align-items: stretch;
14100
+ `;
14101
+
13609
14102
  const grid = document.createElement('div');
14103
+ grid.dataset.remoteFleetDeviceGrid = 'true';
13610
14104
  grid.style.cssText = `
13611
- flex: 1 1 auto;
13612
14105
  min-height: 0;
13613
14106
  overflow-y: auto;
13614
14107
  overflow-x: hidden;
13615
14108
  display: grid;
13616
- grid-template-columns: ${densityState === 'dense' ? 'repeat(auto-fill, minmax(220px, 1fr))' : 'repeat(auto-fill, minmax(168px, 1fr))'};
14109
+ grid-template-columns: ${densityState === 'dense' ? 'repeat(auto-fill, minmax(118px, 1fr))' : 'repeat(auto-fill, minmax(148px, 1fr))'};
13617
14110
  align-content: start;
13618
14111
  gap: 8px;
13619
14112
  padding-right: 4px;
@@ -13659,32 +14152,15 @@
13659
14152
  devices.forEach(device => {
13660
14153
  const connectedDevice = isRemoteFleetDeviceConnected(device);
13661
14154
  const name = getRemoteFleetDeviceName(device);
13662
- const platform = [device?.platform || device?.Platform, device?.arch || device?.Arch]
13663
- .filter(Boolean)
13664
- .join(' / ') || 'unknown';
13665
- const release = String(device?.release || device?.Release || '');
13666
14155
  const deviceId = getRemoteFleetDeviceId(device);
13667
- const thumbnailEnabled = device?.thumbnailEnabled === true || device?.ThumbnailEnabled === true;
13668
- const thumbnailDataUrl = String(device?.thumbnailDataUrl || device?.ThumbnailDataUrl || '');
13669
- const thumbnailCapturedAt = String(device?.thumbnailCapturedAt || device?.ThumbnailCapturedAt || '');
13670
14156
  const liveStreamEnabled = device?.liveStreamEnabled === true || device?.LiveStreamEnabled === true;
13671
14157
  const liveStreamActive = isRemoteFleetLiveActive(device);
13672
- const liveStreamId = String(device?.liveStreamId || device?.LiveStreamId || '');
13673
- const liveFrameDataUrl = String(device?.liveFrameDataUrl || device?.LiveFrameDataUrl || '');
13674
- const liveFrameReceivedAt = String(device?.liveFrameReceivedAt || device?.LiveFrameReceivedAt || '');
13675
14158
  const hasThumbnail = hasRemoteFleetThumbnail(device);
13676
14159
  const hasLiveFrame = hasRemoteFleetLiveFrame(device);
13677
14160
  const taskEnabled = isRemoteFleetDeviceTaskCapable(device);
13678
14161
  const aiAssistEnabled = isRemoteFleetDeviceAiCapable(device);
13679
- const aiModel = String(device?.aiModel || device?.AiModel || '');
13680
14162
  const latestTaskStatus = String(device?.latestTaskStatus || device?.LatestTaskStatus || '');
13681
- const latestTaskTitle = String(device?.latestTaskTitle || device?.LatestTaskTitle || '');
13682
- const latestTaskApproval = String(device?.latestTaskApprovalLevel || device?.LatestTaskApprovalLevel || '');
13683
- const latestTaskUpdatedAt = String(device?.latestTaskUpdatedAt || device?.LatestTaskUpdatedAt || '');
13684
14163
  const latestTaskError = String(device?.latestTaskError || device?.LatestTaskError || '');
13685
- const latestTaskResultModel = String(device?.latestTaskResultModel || device?.LatestTaskResultModel || '');
13686
- const latestTaskResultResponseId = String(device?.latestTaskResultResponseId || device?.LatestTaskResultResponseId || '');
13687
- const latestTaskResult = String(device?.latestTaskResultSummary || device?.LatestTaskResultSummary || '');
13688
14164
  const groupInfo = getRemoteFleetGroupInfo(device, groupState);
13689
14165
  if (groupState !== 'none' && groupInfo.key && groupInfo.key !== lastGroupKey) {
13690
14166
  const stat = groupStats.get(groupInfo.key) || { label: groupInfo.label, total: 0, connected: 0 };
@@ -13719,6 +14195,8 @@
13719
14195
  grid.appendChild(groupHeader);
13720
14196
  lastGroupKey = groupInfo.key;
13721
14197
  }
14198
+ const selectedDeviceId = selectedDevice ? getRemoteFleetDeviceId(selectedDevice) : '';
14199
+ const isSelected = deviceId && deviceId === selectedDeviceId;
13722
14200
  const card = document.createElement('article');
13723
14201
  card.dataset.deviceId = deviceId;
13724
14202
  card.dataset.remoteFleetSearchText = buildRemoteFleetSearchText(device);
@@ -13730,292 +14208,28 @@
13730
14208
  card.dataset.remoteFleetLiveCapable = liveStreamEnabled ? 'true' : 'false';
13731
14209
  card.dataset.remoteFleetLiveActive = liveStreamActive ? 'true' : 'false';
13732
14210
  card.dataset.remoteFleetIssue = (!connectedDevice || latestTaskStatus === 'failed' || !!latestTaskError) ? 'true' : 'false';
14211
+ card.dataset.remoteFleetSelected = isSelected ? 'true' : 'false';
14212
+ card.dataset.remoteFleetAction = 'select-device';
14213
+ card.setAttribute('role', 'button');
14214
+ card.setAttribute('aria-label', `Show details for ${name}`);
14215
+ card.tabIndex = 0;
14216
+ card.title = name;
13733
14217
  card.style.cssText = `
14218
+ position: relative;
13734
14219
  display: flex;
13735
- flex-direction: column;
13736
- gap: ${densityState === 'dense' ? '6px' : '8px'};
14220
+ align-items: stretch;
13737
14221
  min-width: 0;
13738
- min-height: ${densityState === 'dense' ? '92px' : '134px'};
13739
- padding: ${densityState === 'dense' ? '8px' : '10px'};
14222
+ min-height: ${densityState === 'dense' ? '66px' : '84px'};
14223
+ padding: 2px;
13740
14224
  border-radius: 8px;
13741
14225
  background: #ffffff;
13742
- border: 1px solid ${connectedDevice ? 'rgba(16, 185, 129, 0.34)' : 'rgba(148, 163, 184, 0.28)'};
13743
- box-shadow: 0 8px 20px rgba(15, 23, 42, 0.06);
13744
- `;
13745
-
13746
- if (densityState !== 'dense') {
13747
- const previewDataUrl = hasLiveFrame ? liveFrameDataUrl : thumbnailDataUrl;
13748
- const previewAt = hasLiveFrame ? liveFrameReceivedAt : thumbnailCapturedAt;
13749
- const preview = document.createElement('div');
13750
- preview.style.cssText = `
13751
- position: relative;
13752
- width: 100%;
13753
- aspect-ratio: 16 / 9;
13754
- overflow: hidden;
13755
- border-radius: 7px;
13756
- background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
13757
- border: 1px solid rgba(15, 23, 42, 0.12);
13758
- `;
13759
- if (hasLiveFrame || hasThumbnail) {
13760
- const image = document.createElement('img');
13761
- image.src = previewDataUrl;
13762
- image.alt = hasLiveFrame ? `${name} live frame` : `${name} thumbnail`;
13763
- image.loading = 'lazy';
13764
- image.decoding = 'async';
13765
- image.style.cssText = `
13766
- width: 100%;
13767
- height: 100%;
13768
- object-fit: cover;
13769
- display: block;
13770
- `;
13771
- preview.appendChild(image);
13772
- } else {
13773
- const placeholder = document.createElement('div');
13774
- placeholder.textContent = thumbnailEnabled ? 'No frame yet' : 'Status only';
13775
- placeholder.style.cssText = `
13776
- position: absolute;
13777
- inset: 0;
13778
- display: flex;
13779
- align-items: center;
13780
- justify-content: center;
13781
- color: rgba(226, 232, 240, 0.78);
13782
- font-size: 11px;
13783
- font-weight: 900;
13784
- letter-spacing: 0;
13785
- `;
13786
- preview.appendChild(placeholder);
13787
- }
13788
- if (previewAt) {
13789
- const badge = document.createElement('span');
13790
- badge.textContent = hasLiveFrame
13791
- ? `LIVE ${formatRemoteFleetAge(previewAt)}`
13792
- : formatRemoteFleetAge(previewAt);
13793
- badge.style.cssText = `
13794
- position: absolute;
13795
- right: 6px;
13796
- bottom: 6px;
13797
- max-width: calc(100% - 12px);
13798
- padding: 3px 6px;
13799
- border-radius: 999px;
13800
- background: ${hasLiveFrame ? 'rgba(220, 38, 38, 0.82)' : 'rgba(15, 23, 42, 0.74)'};
13801
- color: #e2e8f0;
13802
- font-size: 9px;
13803
- font-weight: 900;
13804
- line-height: 1;
13805
- overflow: hidden;
13806
- text-overflow: ellipsis;
13807
- white-space: nowrap;
13808
- `;
13809
- preview.appendChild(badge);
13810
- }
13811
- card.appendChild(preview);
13812
- }
13813
-
13814
- const cardHeader = document.createElement('div');
13815
- cardHeader.style.cssText = 'display:flex;align-items:flex-start;gap:8px;min-width:0;';
13816
- const dot = document.createElement('span');
13817
- dot.style.cssText = `
13818
- flex: 0 0 auto;
13819
- width: 9px;
13820
- height: 9px;
13821
- margin-top: 4px;
13822
- border-radius: 999px;
13823
- background: ${connectedDevice ? '#10b981' : '#94a3b8'};
13824
- box-shadow: 0 0 0 4px ${connectedDevice ? 'rgba(16,185,129,0.14)' : 'rgba(148,163,184,0.14)'};
13825
- `;
13826
-
13827
- const titleBox = document.createElement('div');
13828
- titleBox.style.cssText = 'min-width:0;display:flex;flex-direction:column;gap:2px;';
13829
- const title = document.createElement('strong');
13830
- title.textContent = name;
13831
- title.title = name;
13832
- title.style.cssText = `
13833
- color: #0f172a;
13834
- font-size: 13px;
13835
- font-weight: 900;
13836
- line-height: 1.2;
13837
- overflow: hidden;
13838
- text-overflow: ellipsis;
13839
- white-space: nowrap;
13840
- letter-spacing: 0;
13841
- `;
13842
- const subtitle = document.createElement('span');
13843
- subtitle.textContent = release ? `${platform} ${release}` : platform;
13844
- subtitle.title = subtitle.textContent;
13845
- subtitle.style.cssText = `
13846
- color: #64748b;
13847
- font-size: 11px;
13848
- line-height: 1.2;
13849
- overflow: hidden;
13850
- text-overflow: ellipsis;
13851
- white-space: nowrap;
13852
- letter-spacing: 0;
13853
- `;
13854
- titleBox.appendChild(title);
13855
- titleBox.appendChild(subtitle);
13856
- cardHeader.appendChild(dot);
13857
- cardHeader.appendChild(titleBox);
13858
- card.appendChild(cardHeader);
13859
-
13860
- const metrics = document.createElement('div');
13861
- metrics.style.cssText = `
13862
- display: grid;
13863
- grid-template-columns: repeat(2, minmax(0, 1fr));
13864
- gap: 6px;
13865
- `;
13866
- const addMetric = (label, value) => {
13867
- const metric = document.createElement('div');
13868
- metric.style.cssText = `
13869
- min-width: 0;
13870
- padding: 6px 7px;
13871
- border-radius: 7px;
13872
- background: rgba(241, 245, 249, 0.84);
13873
- `;
13874
- const labelEl = document.createElement('div');
13875
- labelEl.textContent = label;
13876
- labelEl.style.cssText = 'color:#64748b;font-size:9px;font-weight:800;letter-spacing:0;text-transform:uppercase;';
13877
- const valueEl = document.createElement('div');
13878
- valueEl.textContent = value;
13879
- valueEl.title = value;
13880
- valueEl.style.cssText = 'color:#0f172a;font-size:12px;font-weight:900;letter-spacing:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;';
13881
- metric.appendChild(labelEl);
13882
- metric.appendChild(valueEl);
13883
- metrics.appendChild(metric);
13884
- };
13885
- addMetric('Seen', formatRemoteFleetAge(device?.lastSeenAt || device?.LastSeenAt));
13886
- addMetric('Uptime', formatRemoteFleetDuration(device?.uptimeSec ?? device?.UptimeSec));
13887
- addMetric('Mem', formatRemoteFleetPercent(device?.usedMemRatio ?? device?.UsedMemRatio));
13888
- addMetric('Load', formatRemoteFleetNumber(device?.load1 ?? device?.Load1, 2));
13889
- if (densityState === 'dense') {
13890
- const denseMeta = document.createElement('div');
13891
- denseMeta.textContent = `Seen ${formatRemoteFleetAge(device?.lastSeenAt || device?.LastSeenAt)} - Mem ${formatRemoteFleetPercent(device?.usedMemRatio ?? device?.UsedMemRatio)} - Load ${formatRemoteFleetNumber(device?.load1 ?? device?.Load1, 2)}`;
13892
- denseMeta.style.cssText = `
13893
- color: #475569;
13894
- font-size: 10px;
13895
- font-weight: 750;
13896
- line-height: 1.2;
13897
- overflow: hidden;
13898
- text-overflow: ellipsis;
13899
- white-space: nowrap;
13900
- letter-spacing: 0;
13901
- `;
13902
- card.appendChild(denseMeta);
13903
- } else {
13904
- card.appendChild(metrics);
13905
- }
13906
-
13907
- if (latestTaskStatus || latestTaskTitle || latestTaskResult || latestTaskError) {
13908
- const taskBox = document.createElement('div');
13909
- const taskTone = latestTaskError || latestTaskStatus === 'failed'
13910
- ? 'error'
13911
- : (latestTaskStatus === 'completed' ? 'done' : 'pending');
13912
- taskBox.style.cssText = `
13913
- display: flex;
13914
- flex-direction: column;
13915
- gap: 3px;
13916
- min-width: 0;
13917
- padding: 7px 8px;
13918
- border-radius: 7px;
13919
- background: ${taskTone === 'error' ? 'rgba(248, 113, 113, 0.12)' : taskTone === 'done' ? 'rgba(16, 185, 129, 0.10)' : 'rgba(37, 99, 235, 0.08)'};
13920
- border: 1px solid ${taskTone === 'error' ? 'rgba(248, 113, 113, 0.24)' : taskTone === 'done' ? 'rgba(16, 185, 129, 0.20)' : 'rgba(37, 99, 235, 0.16)'};
13921
- `;
13922
- const taskLine = document.createElement('div');
13923
- const taskModeLabel = latestTaskApproval === 'ai-assist' ? 'AI' : 'Task';
13924
- taskLine.textContent = `${taskModeLabel} ${latestTaskStatus || 'task'}${latestTaskUpdatedAt ? ` - ${formatRemoteFleetAge(latestTaskUpdatedAt)}` : ''}${latestTaskResultModel ? ` - ${latestTaskResultModel}` : ''}`;
13925
- taskLine.title = latestTaskResultResponseId
13926
- ? `${taskLine.textContent} (${latestTaskResultResponseId})`
13927
- : taskLine.textContent;
13928
- taskLine.style.cssText = `
13929
- color: ${taskTone === 'error' ? '#991b1b' : taskTone === 'done' ? '#047857' : '#1d4ed8'};
13930
- font-size: 10px;
13931
- font-weight: 900;
13932
- line-height: 1.2;
13933
- overflow: hidden;
13934
- text-overflow: ellipsis;
13935
- white-space: nowrap;
13936
- letter-spacing: 0;
13937
- `;
13938
- const taskSummary = document.createElement('div');
13939
- taskSummary.textContent = formatRemoteFleetTaskError(latestTaskError) || latestTaskResult || latestTaskTitle || 'Task queued';
13940
- taskSummary.title = taskSummary.textContent;
13941
- taskSummary.style.cssText = `
13942
- color: #334155;
13943
- font-size: 10px;
13944
- font-weight: 700;
13945
- line-height: 1.25;
13946
- overflow: hidden;
13947
- display: -webkit-box;
13948
- -webkit-line-clamp: 2;
13949
- -webkit-box-orient: vertical;
13950
- letter-spacing: 0;
13951
- `;
13952
- taskBox.appendChild(taskLine);
13953
- taskBox.appendChild(taskSummary);
13954
- card.appendChild(taskBox);
13955
- }
13956
-
13957
- const actions = document.createElement('div');
13958
- actions.style.cssText = 'display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-top:auto;';
13959
- const status = document.createElement('span');
13960
- status.textContent = connectedDevice
13961
- ? (liveStreamActive ? 'Live' : (aiAssistEnabled ? `AI ${aiModel || 'ready'}` : 'Connected'))
13962
- : 'Offline';
13963
- status.style.cssText = `
13964
- min-width: 0;
13965
- color: ${liveStreamActive ? '#b91c1c' : (connectedDevice ? '#047857' : '#64748b')};
13966
- font-size: 11px;
13967
- font-weight: 900;
13968
- overflow: hidden;
13969
- text-overflow: ellipsis;
13970
- white-space: nowrap;
14226
+ border: 2px solid ${isSelected ? 'rgba(37, 99, 235, 0.76)' : (connectedDevice ? 'rgba(16, 185, 129, 0.30)' : 'rgba(148, 163, 184, 0.24)')};
14227
+ box-shadow: ${isSelected ? '0 0 0 3px rgba(37, 99, 235, 0.15), 0 10px 22px rgba(15, 23, 42, 0.08)' : '0 8px 18px rgba(15, 23, 42, 0.05)'};
14228
+ cursor: pointer;
14229
+ pointer-events: auto;
14230
+ user-select: none;
13971
14231
  `;
13972
- actions.appendChild(status);
13973
- if (deviceId) {
13974
- const pinButton = createRemoteFleetButton('Pin', 'Pin this device as a canvas node', 'pin-device');
13975
- pinButton.dataset.deviceId = deviceId;
13976
- pinButton.style.height = '24px';
13977
- pinButton.style.fontSize = '10px';
13978
- actions.appendChild(pinButton);
13979
- }
13980
- if (connectedDevice && deviceId) {
13981
- const focusButton = createRemoteFleetButton('Focus', 'Show this device in focused live panel', 'live-focus');
13982
- focusButton.dataset.deviceId = deviceId;
13983
- focusButton.style.height = '24px';
13984
- focusButton.style.fontSize = '10px';
13985
- actions.appendChild(focusButton);
13986
- if (liveStreamEnabled) {
13987
- const liveButton = createRemoteFleetButton(liveStreamActive ? 'Stop' : 'Live', liveStreamActive ? 'Stop live stream' : 'Start focused live stream', liveStreamActive ? 'live-stop' : 'live-start');
13988
- liveButton.dataset.deviceId = deviceId;
13989
- liveButton.dataset.streamId = liveStreamId;
13990
- liveButton.style.height = '24px';
13991
- liveButton.style.fontSize = '10px';
13992
- if (liveStreamActive) {
13993
- liveButton.style.borderColor = 'rgba(220, 38, 38, 0.32)';
13994
- liveButton.style.color = '#b91c1c';
13995
- }
13996
- actions.appendChild(liveButton);
13997
- }
13998
- if (taskEnabled) {
13999
- const taskButton = createRemoteFleetButton('Task', 'Dispatch task to this device', 'task-device');
14000
- taskButton.dataset.deviceId = deviceId;
14001
- taskButton.style.height = '24px';
14002
- taskButton.style.fontSize = '10px';
14003
- actions.appendChild(taskButton);
14004
- }
14005
- if (thumbnailEnabled) {
14006
- const thumbnailButton = createRemoteFleetButton('Shot', 'Request thumbnail', 'thumbnail-device');
14007
- thumbnailButton.dataset.deviceId = deviceId;
14008
- thumbnailButton.style.height = '24px';
14009
- thumbnailButton.style.fontSize = '10px';
14010
- actions.appendChild(thumbnailButton);
14011
- }
14012
- const pingButton = createRemoteFleetButton('Ping', 'Ping device', 'ping-device');
14013
- pingButton.dataset.deviceId = deviceId;
14014
- pingButton.style.height = '24px';
14015
- pingButton.style.fontSize = '10px';
14016
- actions.appendChild(pingButton);
14017
- }
14018
- card.appendChild(actions);
14232
+ card.appendChild(createDevicePreview(device, 'tile'));
14019
14233
  grid.appendChild(card);
14020
14234
  });
14021
14235
 
@@ -14038,7 +14252,9 @@
14038
14252
  grid.appendChild(noMatch);
14039
14253
  }
14040
14254
 
14041
- bodyView.appendChild(grid);
14255
+ monitorWorkspace.appendChild(grid);
14256
+ monitorWorkspace.appendChild(createSelectedDevicePanel(selectedDevice));
14257
+ bodyView.appendChild(monitorWorkspace);
14042
14258
 
14043
14259
  const footer = document.createElement('div');
14044
14260
  footer.textContent = `Endpoint ${endpoint} - all devices, no paging - group ${groupState} - ${autoMonitorState ? 'auto monitor' : 'manual'} - refreshed ${formatRemoteFleetAge(refreshedAt)}`;
@@ -14054,12 +14270,6 @@
14054
14270
  `;
14055
14271
  bodyView.appendChild(footer);
14056
14272
 
14057
- copyButton.addEventListener('click', event => {
14058
- event.preventDefault();
14059
- event.stopPropagation();
14060
- navigator.clipboard?.writeText?.(command).catch(() => { });
14061
- });
14062
-
14063
14273
  const setTaskFeedback = (message, tone = 'info') => {
14064
14274
  taskFeedback.textContent = message || '';
14065
14275
  taskFeedback.style.display = message ? 'block' : 'none';
@@ -14264,9 +14474,12 @@
14264
14474
  sendConnectedButton.title = allTargetCount > 0
14265
14475
  ? `Dispatch to ${allTargetCount} connected target(s)`
14266
14476
  : 'No connected targetable devices';
14267
- grid.querySelectorAll('[data-remote-fleet-action="task-device"]').forEach(button => {
14268
- const card = button.closest('article[data-device-id]');
14269
- button.disabled = wantsAi && card?.dataset.remoteFleetAiCapable !== 'true';
14477
+ bodyView.querySelectorAll('[data-remote-fleet-action="task-device"]').forEach(button => {
14478
+ const deviceId = String(button.dataset.deviceId || '').trim();
14479
+ const card = getDeviceCards().find(item => String(item.dataset.deviceId || '').trim() === deviceId);
14480
+ const aiCapable = button.dataset.remoteFleetAiCapable === 'true'
14481
+ || card?.dataset.remoteFleetAiCapable === 'true';
14482
+ button.disabled = wantsAi && !aiCapable;
14270
14483
  button.title = button.disabled ? 'AI assist is not enabled on this device' : 'Dispatch task to this device';
14271
14484
  });
14272
14485
 
@@ -14283,19 +14496,34 @@
14283
14496
  control.addEventListener(eventName, event => event.stopPropagation());
14284
14497
  });
14285
14498
  });
14286
- [autoMonitorLabel, autoMonitorToggle].forEach(control => {
14499
+ [hostButton, stopHostButton].forEach(control => {
14500
+ if (!control) return;
14287
14501
  ['mousedown', 'mouseup', 'click', 'dblclick', 'keydown'].forEach(eventName => {
14288
14502
  control.addEventListener(eventName, event => event.stopPropagation());
14289
14503
  });
14290
14504
  });
14505
+ getDeviceCards().forEach(card => {
14506
+ ['mousedown', 'mouseup', 'dblclick'].forEach(eventName => {
14507
+ card.addEventListener(eventName, event => event.stopPropagation());
14508
+ });
14509
+ const selectCard = event => {
14510
+ event.preventDefault();
14511
+ event.stopPropagation();
14512
+ const deviceId = String(card.dataset.deviceId || '').trim();
14513
+ if (!deviceId) return;
14514
+ bodyView.dataset.remoteFleetSelectedDeviceId = deviceId;
14515
+ renderRemoteFleetMonitor(bodyView, nodeModel);
14516
+ };
14517
+ card.addEventListener('click', selectCard);
14518
+ card.addEventListener('keydown', event => {
14519
+ if (event.key !== 'Enter' && event.key !== ' ') return;
14520
+ selectCard(event);
14521
+ });
14522
+ });
14291
14523
 
14292
14524
  searchInput.addEventListener('input', applyRemoteFleetFilters);
14293
14525
  filterSelect.addEventListener('change', applyRemoteFleetFilters);
14294
14526
  aiToggle.addEventListener('change', applyRemoteFleetFilters);
14295
- autoMonitorToggle.addEventListener('change', () => {
14296
- bodyView.dataset.remoteFleetAutoMonitor = autoMonitorToggle.checked ? 'true' : 'false';
14297
- renderRemoteFleetMonitor(bodyView, nodeModel);
14298
- });
14299
14527
  sortSelect.addEventListener('change', () => {
14300
14528
  bodyView.dataset.remoteFleetSort = String(sortSelect.value || 'status');
14301
14529
  renderRemoteFleetMonitor(bodyView, nodeModel);
@@ -14367,18 +14595,61 @@
14367
14595
  }
14368
14596
  });
14369
14597
 
14370
- refreshButton.addEventListener('click', async event => {
14371
- event.preventDefault();
14372
- event.stopPropagation();
14373
- refreshButton.disabled = true;
14598
+ const setRemoteFleetHostTarget = async (enabled, options = {}) => {
14599
+ const quiet = options?.quiet === true;
14600
+ const activeButton = enabled ? hostButton : stopHostButton;
14601
+ if (!quiet && activeButton) {
14602
+ activeButton.disabled = true;
14603
+ }
14604
+
14605
+ if (!quiet) {
14606
+ setTaskFeedback(enabled ? 'Setting host target...' : 'Stopping host target...');
14607
+ }
14374
14608
  try {
14375
- await refreshRemoteFleetNode();
14609
+ const result = await invokeDotNetAsync('SetRemoteFleetHostFromJs', nodeId, enabled === true);
14610
+ window.RuntimeTrace?.emit?.('remote.hostTarget.result', {
14611
+ nodeId,
14612
+ enabled: enabled === true,
14613
+ success: isRemoteFleetResultSuccess(result),
14614
+ active: isRemoteFleetResultActive(result),
14615
+ error: result?.error || result?.Error || ''
14616
+ });
14617
+ rememberRemoteFleetLocalHostTarget(nodeId, enabled === true, result);
14618
+ await syncRemoteFleetNodeStateFromResult(result);
14619
+ if (quiet) {
14620
+ return result;
14621
+ }
14622
+ if (isRemoteFleetResultSuccess(result) && enabled !== true) {
14623
+ stopRemoteFleetHostLeaseTimer(nodeId);
14624
+ }
14625
+ if (isRemoteFleetResultSuccess(result)) {
14626
+ setTaskFeedback(enabled ? 'Host target set.' : 'Host target stopped.', 'success');
14627
+ } else {
14628
+ setTaskFeedback(result?.error || result?.Error || 'Host target update failed.', 'error');
14629
+ }
14630
+ return result;
14376
14631
  } finally {
14377
- refreshButton.disabled = false;
14632
+ if (!quiet && activeButton) {
14633
+ activeButton.disabled = false;
14634
+ }
14378
14635
  }
14636
+ };
14637
+
14638
+ hostButton.addEventListener('click', async event => {
14639
+ event.preventDefault();
14640
+ event.stopPropagation();
14641
+ await setRemoteFleetHostTarget(true);
14379
14642
  });
14380
14643
 
14381
- grid.querySelectorAll('[data-remote-fleet-action="pin-device"]').forEach(button => {
14644
+ if (stopHostButton) {
14645
+ stopHostButton.addEventListener('click', async event => {
14646
+ event.preventDefault();
14647
+ event.stopPropagation();
14648
+ await setRemoteFleetHostTarget(false);
14649
+ });
14650
+ }
14651
+
14652
+ bodyView.querySelectorAll('[data-remote-fleet-action="pin-device"]').forEach(button => {
14382
14653
  button.addEventListener('click', async event => {
14383
14654
  event.preventDefault();
14384
14655
  event.stopPropagation();
@@ -14456,7 +14727,7 @@
14456
14727
  });
14457
14728
  });
14458
14729
 
14459
- grid.querySelectorAll('[data-remote-fleet-action="ping-device"]').forEach(button => {
14730
+ bodyView.querySelectorAll('[data-remote-fleet-action="ping-device"]').forEach(button => {
14460
14731
  button.addEventListener('click', async event => {
14461
14732
  event.preventDefault();
14462
14733
  event.stopPropagation();
@@ -14467,7 +14738,7 @@
14467
14738
  });
14468
14739
  });
14469
14740
 
14470
- grid.querySelectorAll('[data-remote-fleet-action="task-device"]').forEach(button => {
14741
+ bodyView.querySelectorAll('[data-remote-fleet-action="task-device"]').forEach(button => {
14471
14742
  button.addEventListener('click', async event => {
14472
14743
  event.preventDefault();
14473
14744
  event.stopPropagation();
@@ -14496,7 +14767,7 @@
14496
14767
  });
14497
14768
  });
14498
14769
 
14499
- grid.querySelectorAll('[data-remote-fleet-action="thumbnail-device"]').forEach(button => {
14770
+ bodyView.querySelectorAll('[data-remote-fleet-action="thumbnail-device"]').forEach(button => {
14500
14771
  button.addEventListener('click', async event => {
14501
14772
  event.preventDefault();
14502
14773
  event.stopPropagation();
@@ -14507,6 +14778,10 @@
14507
14778
  });
14508
14779
  });
14509
14780
 
14781
+ if (isHostingTarget) {
14782
+ startRemoteFleetHostLeaseTimer(nodeId, () => setRemoteFleetHostTarget(true, { quiet: true }));
14783
+ }
14784
+
14510
14785
  if (hasActiveLiveStream) {
14511
14786
  bodyView._remoteFleetLiveRefreshTimer = setInterval(async () => {
14512
14787
  if (!document.body.contains(bodyView)) {