@mindexec/cli 0.2.24 → 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 +130 -1
  3. package/scripts/remote-fleet-render-smoke.mjs +80 -53
  4. package/scripts/remote-http-smoke.mjs +218 -40
  5. package/scripts/remote-hub-smoke.mjs +139 -0
  6. package/server.js +17 -0
  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 +653 -398
  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.qt0p5apeu2.dll → MindExecution.Kernel.z56elxihok.dll} +0 -0
  12. package/wwwroot/_framework/{MindExecution.Plugins.Admin.i9eswmhltm.dll → MindExecution.Plugins.Admin.p5cs4ap87v.dll} +0 -0
  13. package/wwwroot/_framework/{MindExecution.Plugins.Business.1eayoj2kvc.dll → MindExecution.Plugins.Business.s35og5uz44.dll} +0 -0
  14. package/wwwroot/_framework/{MindExecution.Plugins.Concept.a3850nmm3d.dll → MindExecution.Plugins.Concept.zczca3fsxz.dll} +0 -0
  15. package/wwwroot/_framework/{MindExecution.Plugins.Directory.l3rmdlu7o7.dll → MindExecution.Plugins.Directory.y74f55e8x3.dll} +0 -0
  16. package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.2x5gsi74yi.dll → MindExecution.Plugins.PlanMaster.jpdwbefrh1.dll} +0 -0
  17. package/wwwroot/_framework/{MindExecution.Plugins.YouTube.vbl462eegw.dll → MindExecution.Plugins.YouTube.8nz4wv2nsj.dll} +0 -0
  18. package/wwwroot/_framework/{MindExecution.Shared.l2w05i7sd6.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.ri6sjbi2qk.dll +0 -0
  95. package/wwwroot/_framework/MindExecution.Web.i4ojmz00kp.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,
@@ -13007,15 +13176,22 @@
13007
13176
  const endpoint = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetHubEndpoint', '127.0.0.1:5197');
13008
13177
  const managerPackage = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetManagerPackage', '@mindexec/cli');
13009
13178
  const managerVersion = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetManagerVersion', '');
13010
- const command = getRemoteFleetMetadataValue(
13011
- nodeModel,
13012
- 'RemoteFleetConnectCommand',
13013
- `npx @mindexec/remote connect --manager ${endpoint} --pair <pair-token>`);
13014
13179
  const hubStatus = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetHubStatus', 'offline');
13015
13180
  const refreshedAt = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetLastRefreshAtUtc', '');
13016
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
+ }
13017
13192
 
13018
13193
  bodyView.dataset.src = `remote-fleet:${refreshedAt}:${devices.length}:${connected}`;
13194
+ bodyView.dataset.remoteFleetHostState = hostTargetState;
13019
13195
  bodyView.classList.add('map-node-remote-fleet__body');
13020
13196
  bodyView.innerHTML = '';
13021
13197
  bodyView.style.cssText = `
@@ -13030,19 +13206,22 @@
13030
13206
  background: linear-gradient(180deg, rgba(248, 250, 252, 0.96), rgba(241, 245, 249, 0.92));
13031
13207
  `;
13032
13208
 
13033
- const top = document.createElement('div');
13034
- top.style.cssText = `
13035
- display: grid;
13036
- grid-template-columns: repeat(4, minmax(0, 1fr));
13037
- 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;
13038
13215
  flex: 0 0 auto;
13216
+ min-width: 0;
13217
+ `;
13218
+ const headerMeta = document.createElement('div');
13219
+ headerMeta.style.cssText = `
13220
+ min-width: 0;
13221
+ display: flex;
13222
+ flex-direction: column;
13223
+ gap: 4px;
13039
13224
  `;
13040
- top.appendChild(createRemoteFleetStat('Hub', hubStatus === 'online' ? 'Online' : 'Offline', hubStatus === 'online' ? 'online' : 'default'));
13041
- top.appendChild(createRemoteFleetStat('Connected', `${Number.isFinite(connected) ? connected : 0}/${Number.isFinite(total) ? total : devices.length}`, connected > 0 ? 'online' : 'default'));
13042
- top.appendChild(createRemoteFleetStat('Task', String(taskCapableCount), taskCapableCount > 0 ? 'online' : 'default'));
13043
- top.appendChild(createRemoteFleetStat('AI', String(aiCapableCount), aiCapableCount > 0 ? 'online' : 'default'));
13044
- bodyView.appendChild(top);
13045
-
13046
13225
  const managerRow = document.createElement('div');
13047
13226
  managerRow.dataset.remoteFleetManagerVersion = 'true';
13048
13227
  managerRow.textContent = `${managerPackage}${managerVersion ? ` ${managerVersion}` : ''} - ${endpoint}`;
@@ -13059,75 +13238,84 @@
13059
13238
  line-height: 1.2;
13060
13239
  letter-spacing: 0;
13061
13240
  `;
13062
- bodyView.appendChild(managerRow);
13063
13241
 
13064
- const commandRow = document.createElement('div');
13065
- commandRow.style.cssText = `
13066
- display: grid;
13067
- grid-template-columns: minmax(0, 1fr) auto auto auto;
13068
- gap: 8px;
13069
- align-items: center;
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 = `
13070
13257
  flex: 0 0 auto;
13071
- `;
13072
-
13073
- const commandText = document.createElement('code');
13074
- commandText.textContent = command;
13075
- commandText.style.cssText = `
13076
13258
  min-width: 0;
13077
13259
  overflow: hidden;
13078
13260
  text-overflow: ellipsis;
13079
13261
  white-space: nowrap;
13080
- padding: 8px 10px;
13081
- border-radius: 7px;
13082
- background: rgba(15, 23, 42, 0.92);
13083
- color: #e2e8f0;
13084
- font-family: ui-monospace, SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;
13085
- font-size: 11px;
13262
+ color: ${isHostingTarget ? '#047857' : otherMonitorHosting ? '#7c2d12' : '#64748b'};
13263
+ font-size: 10px;
13264
+ font-weight: 900;
13086
13265
  line-height: 1.2;
13266
+ letter-spacing: 0;
13087
13267
  `;
13088
- commandText.title = command;
13268
+ headerMeta.appendChild(managerRow);
13269
+ headerMeta.appendChild(hostStatusRow);
13089
13270
 
13090
- const copyButton = createRemoteFleetButton('Copy', 'Copy agent command', 'copy-command');
13091
- const refreshButton = createRemoteFleetButton('Refresh', 'Refresh remote devices', 'refresh');
13092
- const autoMonitorLabel = document.createElement('label');
13093
- autoMonitorLabel.title = 'Refresh stale thumbnails on a bounded timer';
13094
- autoMonitorLabel.style.cssText = `
13271
+ const hostActions = document.createElement('div');
13272
+ hostActions.style.cssText = `
13273
+ flex: 0 0 auto;
13095
13274
  display: inline-flex;
13096
13275
  align-items: center;
13097
- justify-content: center;
13276
+ justify-content: flex-end;
13098
13277
  gap: 6px;
13099
- height: 34px;
13100
- padding: 0 9px;
13101
- border-radius: 7px;
13102
- border: 1px solid rgba(14, 165, 233, 0.28);
13103
- background: ${autoMonitorState ? 'rgba(240, 249, 255, 0.92)' : 'rgba(248, 250, 252, 0.92)'};
13104
- color: ${autoMonitorState ? '#0369a1' : '#475569'};
13105
- font-size: 11px;
13106
- font-weight: 900;
13107
- letter-spacing: 0;
13108
- cursor: pointer;
13109
- pointer-events: auto;
13110
- user-select: none;
13111
13278
  `;
13112
- const autoMonitorToggle = document.createElement('input');
13113
- autoMonitorToggle.type = 'checkbox';
13114
- autoMonitorToggle.checked = autoMonitorState;
13115
- autoMonitorToggle.dataset.remoteFleetAutoToggle = 'true';
13116
- autoMonitorToggle.style.cssText = `
13117
- width: 14px;
13118
- height: 14px;
13119
- margin: 0;
13120
- 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;
13121
13313
  `;
13122
- const autoMonitorText = document.createElement('span');
13123
- autoMonitorText.textContent = 'Auto';
13124
- autoMonitorLabel.appendChild(autoMonitorToggle);
13125
- autoMonitorLabel.appendChild(autoMonitorText);
13126
- commandRow.appendChild(commandText);
13127
- commandRow.appendChild(autoMonitorLabel);
13128
- commandRow.appendChild(copyButton);
13129
- commandRow.appendChild(refreshButton);
13130
- 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);
13131
13319
 
13132
13320
  const filterRow = document.createElement('div');
13133
13321
  filterRow.style.cssText = `
@@ -13217,7 +13405,7 @@
13217
13405
 
13218
13406
  const taskRow = document.createElement('div');
13219
13407
  taskRow.style.cssText = `
13220
- display: grid;
13408
+ display: none;
13221
13409
  grid-template-columns: minmax(0, 1fr) auto auto auto;
13222
13410
  gap: 8px;
13223
13411
  align-items: stretch;
@@ -13225,7 +13413,7 @@
13225
13413
  `;
13226
13414
  const taskInput = document.createElement('textarea');
13227
13415
  taskInput.dataset.remoteFleetTaskInput = 'true';
13228
- taskInput.placeholder = 'Task for remote agents';
13416
+ taskInput.placeholder = '';
13229
13417
  taskInput.rows = 2;
13230
13418
  taskInput.style.cssText = `
13231
13419
  width: 100%;
@@ -13287,7 +13475,7 @@
13287
13475
  taskRow.appendChild(aiToggleLabel);
13288
13476
  taskRow.appendChild(sendVisibleButton);
13289
13477
  taskRow.appendChild(sendConnectedButton);
13290
- bodyView.appendChild(taskRow);
13478
+ taskRow.dataset.remoteFleetTaskComposer = 'hidden';
13291
13479
 
13292
13480
  const taskFeedback = document.createElement('div');
13293
13481
  taskFeedback.dataset.remoteFleetTaskFeedback = 'true';
@@ -13626,14 +13814,299 @@
13626
13814
  bodyView.appendChild(errorEl);
13627
13815
  }
13628
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
+
13629
14102
  const grid = document.createElement('div');
14103
+ grid.dataset.remoteFleetDeviceGrid = 'true';
13630
14104
  grid.style.cssText = `
13631
- flex: 1 1 auto;
13632
14105
  min-height: 0;
13633
14106
  overflow-y: auto;
13634
14107
  overflow-x: hidden;
13635
14108
  display: grid;
13636
- 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))'};
13637
14110
  align-content: start;
13638
14111
  gap: 8px;
13639
14112
  padding-right: 4px;
@@ -13679,32 +14152,15 @@
13679
14152
  devices.forEach(device => {
13680
14153
  const connectedDevice = isRemoteFleetDeviceConnected(device);
13681
14154
  const name = getRemoteFleetDeviceName(device);
13682
- const platform = [device?.platform || device?.Platform, device?.arch || device?.Arch]
13683
- .filter(Boolean)
13684
- .join(' / ') || 'unknown';
13685
- const release = String(device?.release || device?.Release || '');
13686
14155
  const deviceId = getRemoteFleetDeviceId(device);
13687
- const thumbnailEnabled = device?.thumbnailEnabled === true || device?.ThumbnailEnabled === true;
13688
- const thumbnailDataUrl = String(device?.thumbnailDataUrl || device?.ThumbnailDataUrl || '');
13689
- const thumbnailCapturedAt = String(device?.thumbnailCapturedAt || device?.ThumbnailCapturedAt || '');
13690
14156
  const liveStreamEnabled = device?.liveStreamEnabled === true || device?.LiveStreamEnabled === true;
13691
14157
  const liveStreamActive = isRemoteFleetLiveActive(device);
13692
- const liveStreamId = String(device?.liveStreamId || device?.LiveStreamId || '');
13693
- const liveFrameDataUrl = String(device?.liveFrameDataUrl || device?.LiveFrameDataUrl || '');
13694
- const liveFrameReceivedAt = String(device?.liveFrameReceivedAt || device?.LiveFrameReceivedAt || '');
13695
14158
  const hasThumbnail = hasRemoteFleetThumbnail(device);
13696
14159
  const hasLiveFrame = hasRemoteFleetLiveFrame(device);
13697
14160
  const taskEnabled = isRemoteFleetDeviceTaskCapable(device);
13698
14161
  const aiAssistEnabled = isRemoteFleetDeviceAiCapable(device);
13699
- const aiModel = String(device?.aiModel || device?.AiModel || '');
13700
14162
  const latestTaskStatus = String(device?.latestTaskStatus || device?.LatestTaskStatus || '');
13701
- const latestTaskTitle = String(device?.latestTaskTitle || device?.LatestTaskTitle || '');
13702
- const latestTaskApproval = String(device?.latestTaskApprovalLevel || device?.LatestTaskApprovalLevel || '');
13703
- const latestTaskUpdatedAt = String(device?.latestTaskUpdatedAt || device?.LatestTaskUpdatedAt || '');
13704
14163
  const latestTaskError = String(device?.latestTaskError || device?.LatestTaskError || '');
13705
- const latestTaskResultModel = String(device?.latestTaskResultModel || device?.LatestTaskResultModel || '');
13706
- const latestTaskResultResponseId = String(device?.latestTaskResultResponseId || device?.LatestTaskResultResponseId || '');
13707
- const latestTaskResult = String(device?.latestTaskResultSummary || device?.LatestTaskResultSummary || '');
13708
14164
  const groupInfo = getRemoteFleetGroupInfo(device, groupState);
13709
14165
  if (groupState !== 'none' && groupInfo.key && groupInfo.key !== lastGroupKey) {
13710
14166
  const stat = groupStats.get(groupInfo.key) || { label: groupInfo.label, total: 0, connected: 0 };
@@ -13739,6 +14195,8 @@
13739
14195
  grid.appendChild(groupHeader);
13740
14196
  lastGroupKey = groupInfo.key;
13741
14197
  }
14198
+ const selectedDeviceId = selectedDevice ? getRemoteFleetDeviceId(selectedDevice) : '';
14199
+ const isSelected = deviceId && deviceId === selectedDeviceId;
13742
14200
  const card = document.createElement('article');
13743
14201
  card.dataset.deviceId = deviceId;
13744
14202
  card.dataset.remoteFleetSearchText = buildRemoteFleetSearchText(device);
@@ -13750,292 +14208,28 @@
13750
14208
  card.dataset.remoteFleetLiveCapable = liveStreamEnabled ? 'true' : 'false';
13751
14209
  card.dataset.remoteFleetLiveActive = liveStreamActive ? 'true' : 'false';
13752
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;
13753
14217
  card.style.cssText = `
14218
+ position: relative;
13754
14219
  display: flex;
13755
- flex-direction: column;
13756
- gap: ${densityState === 'dense' ? '6px' : '8px'};
14220
+ align-items: stretch;
13757
14221
  min-width: 0;
13758
- min-height: ${densityState === 'dense' ? '92px' : '134px'};
13759
- padding: ${densityState === 'dense' ? '8px' : '10px'};
14222
+ min-height: ${densityState === 'dense' ? '66px' : '84px'};
14223
+ padding: 2px;
13760
14224
  border-radius: 8px;
13761
14225
  background: #ffffff;
13762
- border: 1px solid ${connectedDevice ? 'rgba(16, 185, 129, 0.34)' : 'rgba(148, 163, 184, 0.28)'};
13763
- box-shadow: 0 8px 20px rgba(15, 23, 42, 0.06);
13764
- `;
13765
-
13766
- if (densityState !== 'dense') {
13767
- const previewDataUrl = hasLiveFrame ? liveFrameDataUrl : thumbnailDataUrl;
13768
- const previewAt = hasLiveFrame ? liveFrameReceivedAt : thumbnailCapturedAt;
13769
- const preview = document.createElement('div');
13770
- preview.style.cssText = `
13771
- position: relative;
13772
- width: 100%;
13773
- aspect-ratio: 16 / 9;
13774
- overflow: hidden;
13775
- border-radius: 7px;
13776
- background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
13777
- border: 1px solid rgba(15, 23, 42, 0.12);
13778
- `;
13779
- if (hasLiveFrame || hasThumbnail) {
13780
- const image = document.createElement('img');
13781
- image.src = previewDataUrl;
13782
- image.alt = hasLiveFrame ? `${name} live frame` : `${name} thumbnail`;
13783
- image.loading = 'lazy';
13784
- image.decoding = 'async';
13785
- image.style.cssText = `
13786
- width: 100%;
13787
- height: 100%;
13788
- object-fit: cover;
13789
- display: block;
13790
- `;
13791
- preview.appendChild(image);
13792
- } else {
13793
- const placeholder = document.createElement('div');
13794
- placeholder.textContent = thumbnailEnabled ? 'No frame yet' : 'Status only';
13795
- placeholder.style.cssText = `
13796
- position: absolute;
13797
- inset: 0;
13798
- display: flex;
13799
- align-items: center;
13800
- justify-content: center;
13801
- color: rgba(226, 232, 240, 0.78);
13802
- font-size: 11px;
13803
- font-weight: 900;
13804
- letter-spacing: 0;
13805
- `;
13806
- preview.appendChild(placeholder);
13807
- }
13808
- if (previewAt) {
13809
- const badge = document.createElement('span');
13810
- badge.textContent = hasLiveFrame
13811
- ? `LIVE ${formatRemoteFleetAge(previewAt)}`
13812
- : formatRemoteFleetAge(previewAt);
13813
- badge.style.cssText = `
13814
- position: absolute;
13815
- right: 6px;
13816
- bottom: 6px;
13817
- max-width: calc(100% - 12px);
13818
- padding: 3px 6px;
13819
- border-radius: 999px;
13820
- background: ${hasLiveFrame ? 'rgba(220, 38, 38, 0.82)' : 'rgba(15, 23, 42, 0.74)'};
13821
- color: #e2e8f0;
13822
- font-size: 9px;
13823
- font-weight: 900;
13824
- line-height: 1;
13825
- overflow: hidden;
13826
- text-overflow: ellipsis;
13827
- white-space: nowrap;
13828
- `;
13829
- preview.appendChild(badge);
13830
- }
13831
- card.appendChild(preview);
13832
- }
13833
-
13834
- const cardHeader = document.createElement('div');
13835
- cardHeader.style.cssText = 'display:flex;align-items:flex-start;gap:8px;min-width:0;';
13836
- const dot = document.createElement('span');
13837
- dot.style.cssText = `
13838
- flex: 0 0 auto;
13839
- width: 9px;
13840
- height: 9px;
13841
- margin-top: 4px;
13842
- border-radius: 999px;
13843
- background: ${connectedDevice ? '#10b981' : '#94a3b8'};
13844
- box-shadow: 0 0 0 4px ${connectedDevice ? 'rgba(16,185,129,0.14)' : 'rgba(148,163,184,0.14)'};
13845
- `;
13846
-
13847
- const titleBox = document.createElement('div');
13848
- titleBox.style.cssText = 'min-width:0;display:flex;flex-direction:column;gap:2px;';
13849
- const title = document.createElement('strong');
13850
- title.textContent = name;
13851
- title.title = name;
13852
- title.style.cssText = `
13853
- color: #0f172a;
13854
- font-size: 13px;
13855
- font-weight: 900;
13856
- line-height: 1.2;
13857
- overflow: hidden;
13858
- text-overflow: ellipsis;
13859
- white-space: nowrap;
13860
- letter-spacing: 0;
13861
- `;
13862
- const subtitle = document.createElement('span');
13863
- subtitle.textContent = release ? `${platform} ${release}` : platform;
13864
- subtitle.title = subtitle.textContent;
13865
- subtitle.style.cssText = `
13866
- color: #64748b;
13867
- font-size: 11px;
13868
- line-height: 1.2;
13869
- overflow: hidden;
13870
- text-overflow: ellipsis;
13871
- white-space: nowrap;
13872
- letter-spacing: 0;
13873
- `;
13874
- titleBox.appendChild(title);
13875
- titleBox.appendChild(subtitle);
13876
- cardHeader.appendChild(dot);
13877
- cardHeader.appendChild(titleBox);
13878
- card.appendChild(cardHeader);
13879
-
13880
- const metrics = document.createElement('div');
13881
- metrics.style.cssText = `
13882
- display: grid;
13883
- grid-template-columns: repeat(2, minmax(0, 1fr));
13884
- gap: 6px;
13885
- `;
13886
- const addMetric = (label, value) => {
13887
- const metric = document.createElement('div');
13888
- metric.style.cssText = `
13889
- min-width: 0;
13890
- padding: 6px 7px;
13891
- border-radius: 7px;
13892
- background: rgba(241, 245, 249, 0.84);
13893
- `;
13894
- const labelEl = document.createElement('div');
13895
- labelEl.textContent = label;
13896
- labelEl.style.cssText = 'color:#64748b;font-size:9px;font-weight:800;letter-spacing:0;text-transform:uppercase;';
13897
- const valueEl = document.createElement('div');
13898
- valueEl.textContent = value;
13899
- valueEl.title = value;
13900
- valueEl.style.cssText = 'color:#0f172a;font-size:12px;font-weight:900;letter-spacing:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;';
13901
- metric.appendChild(labelEl);
13902
- metric.appendChild(valueEl);
13903
- metrics.appendChild(metric);
13904
- };
13905
- addMetric('Seen', formatRemoteFleetAge(device?.lastSeenAt || device?.LastSeenAt));
13906
- addMetric('Uptime', formatRemoteFleetDuration(device?.uptimeSec ?? device?.UptimeSec));
13907
- addMetric('Mem', formatRemoteFleetPercent(device?.usedMemRatio ?? device?.UsedMemRatio));
13908
- addMetric('Load', formatRemoteFleetNumber(device?.load1 ?? device?.Load1, 2));
13909
- if (densityState === 'dense') {
13910
- const denseMeta = document.createElement('div');
13911
- denseMeta.textContent = `Seen ${formatRemoteFleetAge(device?.lastSeenAt || device?.LastSeenAt)} - Mem ${formatRemoteFleetPercent(device?.usedMemRatio ?? device?.UsedMemRatio)} - Load ${formatRemoteFleetNumber(device?.load1 ?? device?.Load1, 2)}`;
13912
- denseMeta.style.cssText = `
13913
- color: #475569;
13914
- font-size: 10px;
13915
- font-weight: 750;
13916
- line-height: 1.2;
13917
- overflow: hidden;
13918
- text-overflow: ellipsis;
13919
- white-space: nowrap;
13920
- letter-spacing: 0;
13921
- `;
13922
- card.appendChild(denseMeta);
13923
- } else {
13924
- card.appendChild(metrics);
13925
- }
13926
-
13927
- if (latestTaskStatus || latestTaskTitle || latestTaskResult || latestTaskError) {
13928
- const taskBox = document.createElement('div');
13929
- const taskTone = latestTaskError || latestTaskStatus === 'failed'
13930
- ? 'error'
13931
- : (latestTaskStatus === 'completed' ? 'done' : 'pending');
13932
- taskBox.style.cssText = `
13933
- display: flex;
13934
- flex-direction: column;
13935
- gap: 3px;
13936
- min-width: 0;
13937
- padding: 7px 8px;
13938
- border-radius: 7px;
13939
- background: ${taskTone === 'error' ? 'rgba(248, 113, 113, 0.12)' : taskTone === 'done' ? 'rgba(16, 185, 129, 0.10)' : 'rgba(37, 99, 235, 0.08)'};
13940
- 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)'};
13941
- `;
13942
- const taskLine = document.createElement('div');
13943
- const taskModeLabel = latestTaskApproval === 'ai-assist' ? 'AI' : 'Task';
13944
- taskLine.textContent = `${taskModeLabel} ${latestTaskStatus || 'task'}${latestTaskUpdatedAt ? ` - ${formatRemoteFleetAge(latestTaskUpdatedAt)}` : ''}${latestTaskResultModel ? ` - ${latestTaskResultModel}` : ''}`;
13945
- taskLine.title = latestTaskResultResponseId
13946
- ? `${taskLine.textContent} (${latestTaskResultResponseId})`
13947
- : taskLine.textContent;
13948
- taskLine.style.cssText = `
13949
- color: ${taskTone === 'error' ? '#991b1b' : taskTone === 'done' ? '#047857' : '#1d4ed8'};
13950
- font-size: 10px;
13951
- font-weight: 900;
13952
- line-height: 1.2;
13953
- overflow: hidden;
13954
- text-overflow: ellipsis;
13955
- white-space: nowrap;
13956
- letter-spacing: 0;
13957
- `;
13958
- const taskSummary = document.createElement('div');
13959
- taskSummary.textContent = formatRemoteFleetTaskError(latestTaskError) || latestTaskResult || latestTaskTitle || 'Task queued';
13960
- taskSummary.title = taskSummary.textContent;
13961
- taskSummary.style.cssText = `
13962
- color: #334155;
13963
- font-size: 10px;
13964
- font-weight: 700;
13965
- line-height: 1.25;
13966
- overflow: hidden;
13967
- display: -webkit-box;
13968
- -webkit-line-clamp: 2;
13969
- -webkit-box-orient: vertical;
13970
- letter-spacing: 0;
13971
- `;
13972
- taskBox.appendChild(taskLine);
13973
- taskBox.appendChild(taskSummary);
13974
- card.appendChild(taskBox);
13975
- }
13976
-
13977
- const actions = document.createElement('div');
13978
- actions.style.cssText = 'display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-top:auto;';
13979
- const status = document.createElement('span');
13980
- status.textContent = connectedDevice
13981
- ? (liveStreamActive ? 'Live' : (aiAssistEnabled ? `AI ${aiModel || 'ready'}` : 'Connected'))
13982
- : 'Offline';
13983
- status.style.cssText = `
13984
- min-width: 0;
13985
- color: ${liveStreamActive ? '#b91c1c' : (connectedDevice ? '#047857' : '#64748b')};
13986
- font-size: 11px;
13987
- font-weight: 900;
13988
- overflow: hidden;
13989
- text-overflow: ellipsis;
13990
- 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;
13991
14231
  `;
13992
- actions.appendChild(status);
13993
- if (deviceId) {
13994
- const pinButton = createRemoteFleetButton('Pin', 'Pin this device as a canvas node', 'pin-device');
13995
- pinButton.dataset.deviceId = deviceId;
13996
- pinButton.style.height = '24px';
13997
- pinButton.style.fontSize = '10px';
13998
- actions.appendChild(pinButton);
13999
- }
14000
- if (connectedDevice && deviceId) {
14001
- const focusButton = createRemoteFleetButton('Focus', 'Show this device in focused live panel', 'live-focus');
14002
- focusButton.dataset.deviceId = deviceId;
14003
- focusButton.style.height = '24px';
14004
- focusButton.style.fontSize = '10px';
14005
- actions.appendChild(focusButton);
14006
- if (liveStreamEnabled) {
14007
- const liveButton = createRemoteFleetButton(liveStreamActive ? 'Stop' : 'Live', liveStreamActive ? 'Stop live stream' : 'Start focused live stream', liveStreamActive ? 'live-stop' : 'live-start');
14008
- liveButton.dataset.deviceId = deviceId;
14009
- liveButton.dataset.streamId = liveStreamId;
14010
- liveButton.style.height = '24px';
14011
- liveButton.style.fontSize = '10px';
14012
- if (liveStreamActive) {
14013
- liveButton.style.borderColor = 'rgba(220, 38, 38, 0.32)';
14014
- liveButton.style.color = '#b91c1c';
14015
- }
14016
- actions.appendChild(liveButton);
14017
- }
14018
- if (taskEnabled) {
14019
- const taskButton = createRemoteFleetButton('Task', 'Dispatch task to this device', 'task-device');
14020
- taskButton.dataset.deviceId = deviceId;
14021
- taskButton.style.height = '24px';
14022
- taskButton.style.fontSize = '10px';
14023
- actions.appendChild(taskButton);
14024
- }
14025
- if (thumbnailEnabled) {
14026
- const thumbnailButton = createRemoteFleetButton('Shot', 'Request thumbnail', 'thumbnail-device');
14027
- thumbnailButton.dataset.deviceId = deviceId;
14028
- thumbnailButton.style.height = '24px';
14029
- thumbnailButton.style.fontSize = '10px';
14030
- actions.appendChild(thumbnailButton);
14031
- }
14032
- const pingButton = createRemoteFleetButton('Ping', 'Ping device', 'ping-device');
14033
- pingButton.dataset.deviceId = deviceId;
14034
- pingButton.style.height = '24px';
14035
- pingButton.style.fontSize = '10px';
14036
- actions.appendChild(pingButton);
14037
- }
14038
- card.appendChild(actions);
14232
+ card.appendChild(createDevicePreview(device, 'tile'));
14039
14233
  grid.appendChild(card);
14040
14234
  });
14041
14235
 
@@ -14058,7 +14252,9 @@
14058
14252
  grid.appendChild(noMatch);
14059
14253
  }
14060
14254
 
14061
- bodyView.appendChild(grid);
14255
+ monitorWorkspace.appendChild(grid);
14256
+ monitorWorkspace.appendChild(createSelectedDevicePanel(selectedDevice));
14257
+ bodyView.appendChild(monitorWorkspace);
14062
14258
 
14063
14259
  const footer = document.createElement('div');
14064
14260
  footer.textContent = `Endpoint ${endpoint} - all devices, no paging - group ${groupState} - ${autoMonitorState ? 'auto monitor' : 'manual'} - refreshed ${formatRemoteFleetAge(refreshedAt)}`;
@@ -14074,12 +14270,6 @@
14074
14270
  `;
14075
14271
  bodyView.appendChild(footer);
14076
14272
 
14077
- copyButton.addEventListener('click', event => {
14078
- event.preventDefault();
14079
- event.stopPropagation();
14080
- navigator.clipboard?.writeText?.(command).catch(() => { });
14081
- });
14082
-
14083
14273
  const setTaskFeedback = (message, tone = 'info') => {
14084
14274
  taskFeedback.textContent = message || '';
14085
14275
  taskFeedback.style.display = message ? 'block' : 'none';
@@ -14284,9 +14474,12 @@
14284
14474
  sendConnectedButton.title = allTargetCount > 0
14285
14475
  ? `Dispatch to ${allTargetCount} connected target(s)`
14286
14476
  : 'No connected targetable devices';
14287
- grid.querySelectorAll('[data-remote-fleet-action="task-device"]').forEach(button => {
14288
- const card = button.closest('article[data-device-id]');
14289
- 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;
14290
14483
  button.title = button.disabled ? 'AI assist is not enabled on this device' : 'Dispatch task to this device';
14291
14484
  });
14292
14485
 
@@ -14303,19 +14496,34 @@
14303
14496
  control.addEventListener(eventName, event => event.stopPropagation());
14304
14497
  });
14305
14498
  });
14306
- [autoMonitorLabel, autoMonitorToggle].forEach(control => {
14499
+ [hostButton, stopHostButton].forEach(control => {
14500
+ if (!control) return;
14307
14501
  ['mousedown', 'mouseup', 'click', 'dblclick', 'keydown'].forEach(eventName => {
14308
14502
  control.addEventListener(eventName, event => event.stopPropagation());
14309
14503
  });
14310
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
+ });
14311
14523
 
14312
14524
  searchInput.addEventListener('input', applyRemoteFleetFilters);
14313
14525
  filterSelect.addEventListener('change', applyRemoteFleetFilters);
14314
14526
  aiToggle.addEventListener('change', applyRemoteFleetFilters);
14315
- autoMonitorToggle.addEventListener('change', () => {
14316
- bodyView.dataset.remoteFleetAutoMonitor = autoMonitorToggle.checked ? 'true' : 'false';
14317
- renderRemoteFleetMonitor(bodyView, nodeModel);
14318
- });
14319
14527
  sortSelect.addEventListener('change', () => {
14320
14528
  bodyView.dataset.remoteFleetSort = String(sortSelect.value || 'status');
14321
14529
  renderRemoteFleetMonitor(bodyView, nodeModel);
@@ -14387,18 +14595,61 @@
14387
14595
  }
14388
14596
  });
14389
14597
 
14390
- refreshButton.addEventListener('click', async event => {
14391
- event.preventDefault();
14392
- event.stopPropagation();
14393
- 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
+ }
14394
14608
  try {
14395
- 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;
14396
14631
  } finally {
14397
- refreshButton.disabled = false;
14632
+ if (!quiet && activeButton) {
14633
+ activeButton.disabled = false;
14634
+ }
14398
14635
  }
14636
+ };
14637
+
14638
+ hostButton.addEventListener('click', async event => {
14639
+ event.preventDefault();
14640
+ event.stopPropagation();
14641
+ await setRemoteFleetHostTarget(true);
14399
14642
  });
14400
14643
 
14401
- 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 => {
14402
14653
  button.addEventListener('click', async event => {
14403
14654
  event.preventDefault();
14404
14655
  event.stopPropagation();
@@ -14476,7 +14727,7 @@
14476
14727
  });
14477
14728
  });
14478
14729
 
14479
- grid.querySelectorAll('[data-remote-fleet-action="ping-device"]').forEach(button => {
14730
+ bodyView.querySelectorAll('[data-remote-fleet-action="ping-device"]').forEach(button => {
14480
14731
  button.addEventListener('click', async event => {
14481
14732
  event.preventDefault();
14482
14733
  event.stopPropagation();
@@ -14487,7 +14738,7 @@
14487
14738
  });
14488
14739
  });
14489
14740
 
14490
- grid.querySelectorAll('[data-remote-fleet-action="task-device"]').forEach(button => {
14741
+ bodyView.querySelectorAll('[data-remote-fleet-action="task-device"]').forEach(button => {
14491
14742
  button.addEventListener('click', async event => {
14492
14743
  event.preventDefault();
14493
14744
  event.stopPropagation();
@@ -14516,7 +14767,7 @@
14516
14767
  });
14517
14768
  });
14518
14769
 
14519
- grid.querySelectorAll('[data-remote-fleet-action="thumbnail-device"]').forEach(button => {
14770
+ bodyView.querySelectorAll('[data-remote-fleet-action="thumbnail-device"]').forEach(button => {
14520
14771
  button.addEventListener('click', async event => {
14521
14772
  event.preventDefault();
14522
14773
  event.stopPropagation();
@@ -14527,6 +14778,10 @@
14527
14778
  });
14528
14779
  });
14529
14780
 
14781
+ if (isHostingTarget) {
14782
+ startRemoteFleetHostLeaseTimer(nodeId, () => setRemoteFleetHostTarget(true, { quiet: true }));
14783
+ }
14784
+
14530
14785
  if (hasActiveLiveStream) {
14531
14786
  bodyView._remoteFleetLiveRefreshTimer = setInterval(async () => {
14532
14787
  if (!document.body.contains(bodyView)) {