@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.
- package/package.json +1 -1
- package/remote-hub.js +130 -1
- package/scripts/remote-fleet-render-smoke.mjs +80 -53
- package/scripts/remote-http-smoke.mjs +218 -40
- package/scripts/remote-hub-smoke.mjs +139 -0
- package/server.js +17 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js +18 -1
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +653 -398
- package/wwwroot/_framework/{Microsoft.CSharp.qrvp77qmhs.dll → Microsoft.CSharp.8bsm8vx6su.dll} +0 -0
- package/wwwroot/_framework/MindExecution.Core.6rfnfdndxq.dll +0 -0
- package/wwwroot/_framework/{MindExecution.Kernel.qt0p5apeu2.dll → MindExecution.Kernel.z56elxihok.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Admin.i9eswmhltm.dll → MindExecution.Plugins.Admin.p5cs4ap87v.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Business.1eayoj2kvc.dll → MindExecution.Plugins.Business.s35og5uz44.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Concept.a3850nmm3d.dll → MindExecution.Plugins.Concept.zczca3fsxz.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Directory.l3rmdlu7o7.dll → MindExecution.Plugins.Directory.y74f55e8x3.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.2x5gsi74yi.dll → MindExecution.Plugins.PlanMaster.jpdwbefrh1.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.YouTube.vbl462eegw.dll → MindExecution.Plugins.YouTube.8nz4wv2nsj.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Shared.l2w05i7sd6.dll → MindExecution.Shared.v6ani8nfp8.dll} +0 -0
- package/wwwroot/_framework/MindExecution.Web.0cs29v57jl.dll +0 -0
- package/wwwroot/_framework/{System.Collections.x53e19vfsj.dll → System.Collections.23yxgetbju.dll} +0 -0
- package/wwwroot/_framework/{System.Collections.Concurrent.y1zmvuyipi.dll → System.Collections.Concurrent.vow3rm1dku.dll} +0 -0
- package/wwwroot/_framework/{System.Collections.Immutable.ug3j698qms.dll → System.Collections.Immutable.ap9596utv5.dll} +0 -0
- package/wwwroot/_framework/{System.Collections.NonGeneric.h66hj3863h.dll → System.Collections.NonGeneric.npjkaz40oc.dll} +0 -0
- package/wwwroot/_framework/{System.Collections.Specialized.umr3y27ntj.dll → System.Collections.Specialized.ugkjbs6p02.dll} +0 -0
- package/wwwroot/_framework/{System.ComponentModel.Annotations.tz6gnt4ebt.dll → System.ComponentModel.Annotations.clwo0z2oyu.dll} +0 -0
- package/wwwroot/_framework/{System.ComponentModel.Primitives.j7tiphu4rg.dll → System.ComponentModel.Primitives.end4v0xe2c.dll} +0 -0
- package/wwwroot/_framework/{System.ComponentModel.TypeConverter.ujlztox1gx.dll → System.ComponentModel.TypeConverter.tgtp5sm4iv.dll} +0 -0
- package/wwwroot/_framework/{System.ComponentModel.x9xz0ojfb6.dll → System.ComponentModel.wupoltkk1t.dll} +0 -0
- package/wwwroot/_framework/{System.Console.ijzpqmj7ne.dll → System.Console.9dik0wogo2.dll} +0 -0
- package/wwwroot/_framework/{System.Data.Common.1r0sqffq1p.dll → System.Data.Common.4v8jejsiu0.dll} +0 -0
- package/wwwroot/_framework/{System.Diagnostics.DiagnosticSource.9upoqwq09o.dll → System.Diagnostics.DiagnosticSource.e2q75ondtq.dll} +0 -0
- package/wwwroot/_framework/{System.Diagnostics.Process.m99azzntjm.dll → System.Diagnostics.Process.9736yjnxs8.dll} +0 -0
- package/wwwroot/_framework/{System.Diagnostics.TraceSource.pl7wv26myr.dll → System.Diagnostics.TraceSource.h9al53gbbw.dll} +0 -0
- package/wwwroot/_framework/{System.Diagnostics.Tracing.crlhfx6tut.dll → System.Diagnostics.Tracing.h4bcp2fo98.dll} +0 -0
- package/wwwroot/_framework/{System.Drawing.mi7d8hwowb.dll → System.Drawing.64oovy8qts.dll} +0 -0
- package/wwwroot/_framework/{System.Drawing.Primitives.22e4y9ikq9.dll → System.Drawing.Primitives.o6jiqpgbgl.dll} +0 -0
- package/wwwroot/_framework/{System.Formats.Asn1.jx23sjiqnn.dll → System.Formats.Asn1.rylx5ipd40.dll} +0 -0
- package/wwwroot/_framework/{System.IO.Compression.6fyoii3uej.dll → System.IO.Compression.iceabaupns.dll} +0 -0
- package/wwwroot/_framework/{System.IO.Pipelines.vg77t4cd4d.dll → System.IO.Pipelines.uw8csd3mlz.dll} +0 -0
- package/wwwroot/_framework/{System.Linq.Expressions.24xqiypwdt.dll → System.Linq.Expressions.ty95ava37f.dll} +0 -0
- package/wwwroot/_framework/{System.Linq.Queryable.hvd01d6rsa.dll → System.Linq.Queryable.hs2195jrwy.dll} +0 -0
- package/wwwroot/_framework/{System.Linq.1bkoxlqgmq.dll → System.Linq.hssodjwmlf.dll} +0 -0
- package/wwwroot/_framework/{System.Memory.8dx3lwgym4.dll → System.Memory.1k78n7wdxb.dll} +0 -0
- package/wwwroot/_framework/{System.Net.Http.eitrz660my.dll → System.Net.Http.3br8rfql4c.dll} +0 -0
- package/wwwroot/_framework/{System.Net.Http.Json.3mhdm9l1rf.dll → System.Net.Http.Json.860wbh17d8.dll} +0 -0
- package/wwwroot/_framework/{System.Net.NetworkInformation.3pkuofcv9r.dll → System.Net.NetworkInformation.e3wr00853o.dll} +0 -0
- package/wwwroot/_framework/{System.Net.Ping.8clj5pklrp.dll → System.Net.Ping.r5cw4mf1a4.dll} +0 -0
- package/wwwroot/_framework/{System.Net.Primitives.qrp4wcjz1p.dll → System.Net.Primitives.ksxwiwlvhu.dll} +0 -0
- package/wwwroot/_framework/{System.Net.WebSockets.qp6u31zvm5.dll → System.Net.WebSockets.6rt3n3gl2q.dll} +0 -0
- package/wwwroot/_framework/{System.Net.WebSockets.Client.2u6pv01g69.dll → System.Net.WebSockets.Client.z3usrzo7rz.dll} +0 -0
- package/wwwroot/_framework/{System.Numerics.Vectors.kc7ufp2j4l.dll → System.Numerics.Vectors.lbdzx8reja.dll} +0 -0
- package/wwwroot/_framework/{System.ObjectModel.qv82fot1ib.dll → System.ObjectModel.yct1gdirzf.dll} +0 -0
- package/wwwroot/_framework/{System.Private.CoreLib.rkafq04oma.dll → System.Private.CoreLib.ns29bor93l.dll} +0 -0
- package/wwwroot/_framework/{System.Private.Uri.t9542hmr6j.dll → System.Private.Uri.zp9kmg0z93.dll} +0 -0
- package/wwwroot/_framework/{System.Private.Xml.Linq.n8n3ptrbwu.dll → System.Private.Xml.Linq.lgv2n0akl4.dll} +0 -0
- package/wwwroot/_framework/{System.Private.Xml.rxd3tytisn.dll → System.Private.Xml.dpsk8g304y.dll} +0 -0
- package/wwwroot/_framework/{System.Reflection.Emit.ILGeneration.stxyk8zoo1.dll → System.Reflection.Emit.ILGeneration.1tcuz2cmbk.dll} +0 -0
- package/wwwroot/_framework/{System.Reflection.Emit.Lightweight.6xrd5v8vg0.dll → System.Reflection.Emit.Lightweight.ddt2wylovg.dll} +0 -0
- package/wwwroot/_framework/{System.Reflection.Emit.9tjhp6y0j3.dll → System.Reflection.Emit.d8vkadiwhg.dll} +0 -0
- package/wwwroot/_framework/{System.Reflection.Primitives.wgn8fpwwvv.dll → System.Reflection.Primitives.cpsl71xd1z.dll} +0 -0
- package/wwwroot/_framework/{System.Runtime.InteropServices.te07xr2we9.dll → System.Runtime.InteropServices.31vjgsfk1o.dll} +0 -0
- package/wwwroot/_framework/{System.Runtime.InteropServices.JavaScript.sliym526xh.dll → System.Runtime.InteropServices.JavaScript.pn2wvizzet.dll} +0 -0
- package/wwwroot/_framework/{System.Runtime.InteropServices.RuntimeInformation.oji7zut14z.dll → System.Runtime.InteropServices.RuntimeInformation.l5rk496q70.dll} +0 -0
- package/wwwroot/_framework/{System.Runtime.Intrinsics.507y4h8nzq.dll → System.Runtime.Intrinsics.0zee9qcqfy.dll} +0 -0
- package/wwwroot/_framework/{System.Runtime.Loader.v7gk4bse0k.dll → System.Runtime.Loader.xw2jr9wl92.dll} +0 -0
- package/wwwroot/_framework/{System.Runtime.Numerics.eqy5xjv3nd.dll → System.Runtime.Numerics.ezj1dfyvbj.dll} +0 -0
- package/wwwroot/_framework/{System.Runtime.Serialization.Formatters.zpkrub8lab.dll → System.Runtime.Serialization.Formatters.0y019e9zkr.dll} +0 -0
- package/wwwroot/_framework/{System.Runtime.Serialization.Primitives.vhkpnbxjip.dll → System.Runtime.Serialization.Primitives.bia5wb62c8.dll} +0 -0
- package/wwwroot/_framework/{System.Runtime.jn319d5nyg.dll → System.Runtime.h9cduidfkh.dll} +0 -0
- package/wwwroot/_framework/{System.Security.Claims.0ztig1q9vo.dll → System.Security.Claims.2r18wim2rl.dll} +0 -0
- package/wwwroot/_framework/{System.Security.Cryptography.vttizqc9ho.dll → System.Security.Cryptography.qqgybpoucx.dll} +0 -0
- package/wwwroot/_framework/{System.Text.Encoding.Extensions.utdd47ny8f.dll → System.Text.Encoding.Extensions.9t3hs6kyll.dll} +0 -0
- package/wwwroot/_framework/{System.Text.Encodings.Web.wah8r1zoe0.dll → System.Text.Encodings.Web.wftjfh2crk.dll} +0 -0
- package/wwwroot/_framework/{System.Text.Json.kxlfxj0wrs.dll → System.Text.Json.4s25e3op6i.dll} +0 -0
- package/wwwroot/_framework/{System.Text.RegularExpressions.dbqn58klox.dll → System.Text.RegularExpressions.cvkox11l6l.dll} +0 -0
- package/wwwroot/_framework/{System.Threading.Channels.hfa7j0uv2w.dll → System.Threading.Channels.419493szqu.dll} +0 -0
- package/wwwroot/_framework/{System.Threading.Thread.caul0pdqul.dll → System.Threading.Thread.xhmys87xh5.dll} +0 -0
- package/wwwroot/_framework/{System.Threading.42ao9vi047.dll → System.Threading.b66vzsz9g1.dll} +0 -0
- package/wwwroot/_framework/{System.Transactions.Local.fimi2hamzo.dll → System.Transactions.Local.7zfffdvnwf.dll} +0 -0
- package/wwwroot/_framework/{System.Web.HttpUtility.gq8yz50p2e.dll → System.Web.HttpUtility.9up6xfbtuq.dll} +0 -0
- package/wwwroot/_framework/{System.Xml.Linq.kitin4zjoj.dll → System.Xml.Linq.n5rzv9nbf7.dll} +0 -0
- package/wwwroot/_framework/{System.Xml.ReaderWriter.kzvw3qgxb0.dll → System.Xml.ReaderWriter.ag8pilllob.dll} +0 -0
- package/wwwroot/_framework/{System.Xml.XDocument.c539ki6cuq.dll → System.Xml.XDocument.sn51jas17n.dll} +0 -0
- package/wwwroot/_framework/{System.m05i39uvk9.dll → System.brmz7yk5qh.dll} +0 -0
- package/wwwroot/_framework/blazor.boot.json +161 -161
- package/wwwroot/_framework/dotnet.js +1 -1
- package/wwwroot/_framework/{dotnet.native.vz0adxojrz.wasm → dotnet.native.boem75ye5i.wasm} +0 -0
- package/wwwroot/_framework/{dotnet.native.xsn1d6x2kd.js → dotnet.native.qc8g39g30v.js} +1 -1
- package/wwwroot/_framework/{dotnet.runtime.dstopyvqzi.js → dotnet.runtime.opaiwunc3t.js} +1 -1
- package/wwwroot/_framework/{netstandard.0xet7jg7ky.dll → netstandard.yvr3prsx0x.dll} +0 -0
- package/wwwroot/index.html +1 -1
- package/wwwroot/service-worker-assets.js +166 -166
- package/wwwroot/service-worker.js +1 -1
- package/wwwroot/_framework/MindExecution.Core.ri6sjbi2qk.dll +0 -0
- package/wwwroot/_framework/MindExecution.Web.i4ojmz00kp.dll +0 -0
package/package.json
CHANGED
package/remote-hub.js
CHANGED
|
@@ -15,6 +15,7 @@ const RECENT_TASK_LIMIT = 12;
|
|
|
15
15
|
const RECENT_TASK_BATCH_LIMIT = 16;
|
|
16
16
|
const REMOTE_PROTOCOL_VERSION = 1;
|
|
17
17
|
const MAX_SYNTHETIC_DEVICES = 1000;
|
|
18
|
+
const DEFAULT_HOST_TARGET_LEASE_MS = 30000;
|
|
18
19
|
const SYNTHETIC_FRAME_DATA_URL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAYAAAD0In+KAAAADElEQVR42mP8z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC';
|
|
19
20
|
|
|
20
21
|
function isEnabledValue(value, fallback = true) {
|
|
@@ -219,9 +220,129 @@ export function createRemoteHub(options = {}) {
|
|
|
219
220
|
let started = false;
|
|
220
221
|
let boundPort = requestedPort;
|
|
221
222
|
let lastError = '';
|
|
223
|
+
let hostTarget = null;
|
|
224
|
+
|
|
225
|
+
function getAgentEndpoint() {
|
|
226
|
+
return `${host}:${boundPort || requestedPort}`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function getActiveHostTarget() {
|
|
230
|
+
if (!hostTarget) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const expiresMs = Date.parse(hostTarget.expiresAt || '');
|
|
235
|
+
if (Number.isFinite(expiresMs) && expiresMs <= Date.now()) {
|
|
236
|
+
const expiredTarget = hostTarget;
|
|
237
|
+
hostTarget = null;
|
|
238
|
+
emitRemoteEvent('RemoteHostTargetExpired', null, {
|
|
239
|
+
hostTarget: {
|
|
240
|
+
...expiredTarget,
|
|
241
|
+
active: false
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return hostTarget;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function serializeHostTarget(target = getActiveHostTarget()) {
|
|
251
|
+
if (!target) {
|
|
252
|
+
return {
|
|
253
|
+
active: false,
|
|
254
|
+
nodeId: '',
|
|
255
|
+
leaseId: '',
|
|
256
|
+
endpoint: '',
|
|
257
|
+
activatedAt: '',
|
|
258
|
+
updatedAt: '',
|
|
259
|
+
expiresAt: ''
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
active: true,
|
|
265
|
+
nodeId: target.nodeId,
|
|
266
|
+
leaseId: target.leaseId,
|
|
267
|
+
endpoint: target.endpoint,
|
|
268
|
+
activatedAt: target.activatedAt,
|
|
269
|
+
updatedAt: target.updatedAt,
|
|
270
|
+
expiresAt: target.expiresAt
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function setHostTarget(options = {}) {
|
|
275
|
+
const enabled = options.enabled !== false;
|
|
276
|
+
const nodeId = safeString(options.nodeId, 128);
|
|
277
|
+
if (!enabled) {
|
|
278
|
+
const previous = getActiveHostTarget();
|
|
279
|
+
if (!previous) {
|
|
280
|
+
return {
|
|
281
|
+
ok: true,
|
|
282
|
+
active: false,
|
|
283
|
+
hostTarget: serializeHostTarget(null)
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (nodeId && previous.nodeId && nodeId !== previous.nodeId) {
|
|
288
|
+
return {
|
|
289
|
+
ok: false,
|
|
290
|
+
error: 'host-target-node-mismatch',
|
|
291
|
+
active: true,
|
|
292
|
+
hostTarget: serializeHostTarget(previous)
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
hostTarget = null;
|
|
297
|
+
emitRemoteEvent('RemoteHostTargetCleared', null, {
|
|
298
|
+
hostTarget: {
|
|
299
|
+
...serializeHostTarget(previous),
|
|
300
|
+
active: false
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
return {
|
|
304
|
+
ok: true,
|
|
305
|
+
active: false,
|
|
306
|
+
hostTarget: serializeHostTarget(null)
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (!nodeId) {
|
|
311
|
+
return {
|
|
312
|
+
ok: false,
|
|
313
|
+
error: 'missing-node-id',
|
|
314
|
+
active: false,
|
|
315
|
+
hostTarget: serializeHostTarget()
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const now = new Date();
|
|
320
|
+
const leaseMs = clampNumber(options.leaseMs, 5000, 10 * 60 * 1000, DEFAULT_HOST_TARGET_LEASE_MS);
|
|
321
|
+
const previous = getActiveHostTarget();
|
|
322
|
+
const activeSameNode = previous?.nodeId === nodeId;
|
|
323
|
+
hostTarget = {
|
|
324
|
+
nodeId,
|
|
325
|
+
leaseId: activeSameNode && previous?.leaseId ? previous.leaseId : crypto.randomUUID(),
|
|
326
|
+
endpoint: getAgentEndpoint(),
|
|
327
|
+
activatedAt: activeSameNode && previous?.activatedAt ? previous.activatedAt : now.toISOString(),
|
|
328
|
+
updatedAt: now.toISOString(),
|
|
329
|
+
expiresAt: new Date(now.getTime() + leaseMs).toISOString()
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
emitRemoteEvent('RemoteHostTargetSet', null, {
|
|
333
|
+
hostTarget: serializeHostTarget(hostTarget),
|
|
334
|
+
replacedNodeId: previous && previous.nodeId !== nodeId ? previous.nodeId : ''
|
|
335
|
+
});
|
|
336
|
+
return {
|
|
337
|
+
ok: true,
|
|
338
|
+
active: true,
|
|
339
|
+
hostTarget: serializeHostTarget(hostTarget)
|
|
340
|
+
};
|
|
341
|
+
}
|
|
222
342
|
|
|
223
343
|
function getStatus({ includeSecrets = false } = {}) {
|
|
224
344
|
const connectedDevices = [...devices.values()].filter(device => device.connected).length;
|
|
345
|
+
const activeHostTarget = serializeHostTarget();
|
|
225
346
|
return {
|
|
226
347
|
enabled,
|
|
227
348
|
started,
|
|
@@ -234,13 +355,20 @@ export function createRemoteHub(options = {}) {
|
|
|
234
355
|
managerPackage,
|
|
235
356
|
managerVersion,
|
|
236
357
|
agentPackage: '@mindexec/remote',
|
|
237
|
-
agentEndpoint:
|
|
358
|
+
agentEndpoint: getAgentEndpoint(),
|
|
238
359
|
pairToken: includeSecrets ? pairToken : undefined,
|
|
239
360
|
pairTokenPreview: maskToken(pairToken),
|
|
240
361
|
deviceCount: devices.size,
|
|
241
362
|
connectedDeviceCount: connectedDevices,
|
|
242
363
|
canvasDeviceListMode: 'all-devices',
|
|
243
364
|
canvasPagination: 'none',
|
|
365
|
+
hostTargetActive: activeHostTarget.active,
|
|
366
|
+
hostTargetNodeId: activeHostTarget.nodeId,
|
|
367
|
+
hostTargetLeaseId: activeHostTarget.leaseId,
|
|
368
|
+
hostTargetEndpoint: activeHostTarget.endpoint,
|
|
369
|
+
hostTargetActivatedAt: activeHostTarget.activatedAt,
|
|
370
|
+
hostTargetUpdatedAt: activeHostTarget.updatedAt,
|
|
371
|
+
hostTargetExpiresAt: activeHostTarget.expiresAt,
|
|
244
372
|
externalExposure: host === '0.0.0.0' || host === '::',
|
|
245
373
|
lastError
|
|
246
374
|
};
|
|
@@ -1690,6 +1818,7 @@ export function createRemoteHub(options = {}) {
|
|
|
1690
1818
|
sendCommand,
|
|
1691
1819
|
requestAgentTask,
|
|
1692
1820
|
requestAgentTaskBatch,
|
|
1821
|
+
setHostTarget,
|
|
1693
1822
|
requestThumbnail,
|
|
1694
1823
|
startLiveStream,
|
|
1695
1824
|
stopLiveStream,
|
|
@@ -416,6 +416,9 @@ function buildMonitorNode(devices, hubStatus, latestTaskBatch = null, recentTask
|
|
|
416
416
|
const connected = devices.filter(device => device.Connected).length;
|
|
417
417
|
const endpoint = hubStatus.agentEndpoint || '127.0.0.1:5197';
|
|
418
418
|
const pairToken = hubStatus.pairToken || 'render-smoke-token';
|
|
419
|
+
const hostTargetState = hubStatus.hostTargetActive
|
|
420
|
+
? (hubStatus.hostTargetNodeId === 'remote-fleet-render-smoke' ? 'hosting' : 'other-monitor')
|
|
421
|
+
: 'inactive';
|
|
419
422
|
return {
|
|
420
423
|
id: 'remote-fleet-render-smoke',
|
|
421
424
|
contentType: 'memo',
|
|
@@ -434,6 +437,13 @@ function buildMonitorNode(devices, hubStatus, latestTaskBatch = null, recentTask
|
|
|
434
437
|
RemoteFleetDeviceCount: String(devices.length),
|
|
435
438
|
RemoteFleetConnectedDeviceCount: String(connected),
|
|
436
439
|
RemoteFleetCanvasPagination: 'none',
|
|
440
|
+
RemoteFleetHostTargetState: hostTargetState,
|
|
441
|
+
RemoteFleetHostTargetNodeId: hubStatus.hostTargetNodeId || '',
|
|
442
|
+
RemoteFleetHostTargetLeaseId: hubStatus.hostTargetLeaseId || '',
|
|
443
|
+
RemoteFleetHostTargetEndpoint: hubStatus.hostTargetEndpoint || endpoint,
|
|
444
|
+
RemoteFleetHostTargetActivatedAtUtc: hubStatus.hostTargetActivatedAt || '',
|
|
445
|
+
RemoteFleetHostTargetUpdatedAtUtc: hubStatus.hostTargetUpdatedAt || '',
|
|
446
|
+
RemoteFleetHostTargetExpiresAtUtc: hubStatus.hostTargetExpiresAt || '',
|
|
437
447
|
RemoteFleetDevicesJson: JSON.stringify(devices),
|
|
438
448
|
RemoteFleetLatestTaskBatchJson: latestTaskBatch ? JSON.stringify(latestTaskBatch) : '',
|
|
439
449
|
RemoteFleetRecentTaskBatchesJson: JSON.stringify(recentTaskBatches),
|
|
@@ -645,6 +655,13 @@ try {
|
|
|
645
655
|
};
|
|
646
656
|
}
|
|
647
657
|
|
|
658
|
+
if (methodName === 'SetRemoteFleetHostFromJs') {
|
|
659
|
+
return {
|
|
660
|
+
success: true,
|
|
661
|
+
active: args[1] === true
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
|
|
648
665
|
return { success: true };
|
|
649
666
|
}
|
|
650
667
|
};
|
|
@@ -660,16 +677,69 @@ try {
|
|
|
660
677
|
let cards = bodyView.querySelectorAll('article[data-device-id]');
|
|
661
678
|
assert.equal(cards.length, SYNTHETIC_COUNT);
|
|
662
679
|
assert.equal(bodyView.querySelector('[data-remote-fleet-match-count="true"]')?.textContent, `${SYNTHETIC_COUNT}/${SYNTHETIC_COUNT}`);
|
|
663
|
-
assert.
|
|
664
|
-
|
|
665
|
-
assert.equal(
|
|
680
|
+
assert.ok(bodyView.querySelector('[data-remote-fleet-device-grid="true"]'));
|
|
681
|
+
const initialDetailPanel = bodyView.querySelector('[data-remote-fleet-detail-panel="true"]');
|
|
682
|
+
assert.equal(initialDetailPanel?.dataset.deviceId, focusedDevice.DeviceId);
|
|
683
|
+
assert.equal(bodyView.querySelectorAll('[data-remote-fleet-action="pin-device"]').length, 1);
|
|
684
|
+
assert.equal(bodyView.querySelectorAll('[data-remote-fleet-action="task-device"]').length, 0);
|
|
685
|
+
assert.equal(bodyView.querySelectorAll('[data-remote-fleet-action="thumbnail-device"]').length, focusedDevice.Connected && focusedDevice.ThumbnailEnabled ? 1 : 0);
|
|
686
|
+
assert.ok(initialDetailPanel?.textContent.includes('Seen'));
|
|
687
|
+
assert.ok(initialDetailPanel?.textContent.includes('Uptime'));
|
|
688
|
+
assert.ok(initialDetailPanel?.textContent.includes('Mem'));
|
|
689
|
+
assert.ok(initialDetailPanel?.textContent.includes('Load'));
|
|
690
|
+
assert.ok(cards[0]?.querySelector('[data-remote-fleet-device-preview="tile"]'));
|
|
691
|
+
assert.equal(/\b(Seen|Uptime|Mem|Load)\b/.test(cards[0]?.textContent || ''), false);
|
|
692
|
+
const alternateDevice = devices.find(device => device.DeviceId !== focusedDevice.DeviceId);
|
|
693
|
+
assert.ok(alternateDevice);
|
|
694
|
+
const alternateCard = Array.from(cards).find(card => card.dataset.deviceId === alternateDevice.DeviceId);
|
|
695
|
+
assert.ok(alternateCard);
|
|
696
|
+
alternateCard.dispatchEvent({ type: 'click' });
|
|
697
|
+
assert.equal(bodyView.dataset.remoteFleetSelectedDeviceId, alternateDevice.DeviceId);
|
|
698
|
+
assert.equal(bodyView.querySelector('[data-remote-fleet-detail-panel="true"]')?.dataset.deviceId, alternateDevice.DeviceId);
|
|
666
699
|
const livePanel = bodyView.querySelector('[data-remote-fleet-live-panel="true"]');
|
|
667
700
|
assert.equal(livePanel?.dataset.deviceId, focusedDevice.DeviceId);
|
|
668
|
-
assert.equal(bodyView.querySelector('[data-remote-fleet-action="task-visible"]')
|
|
701
|
+
assert.equal(bodyView.querySelector('[data-remote-fleet-action="task-visible"]'), null);
|
|
702
|
+
assert.equal(bodyView.querySelector('[data-remote-fleet-action="task-connected"]'), null);
|
|
703
|
+
assert.equal(bodyView.querySelector('[data-remote-fleet-task-input="true"]'), null);
|
|
704
|
+
assert.equal(bodyView.querySelector('[data-remote-fleet-ai-toggle="true"]'), null);
|
|
669
705
|
assert.ok(bodyView.textContent.includes('all devices, no paging'));
|
|
706
|
+
assert.equal(bodyView.querySelector('[data-remote-fleet-host-state="true"]')?.textContent.includes('Host: Inactive'), true);
|
|
707
|
+
const initialHostButton = bodyView.querySelector('[data-remote-fleet-action="set-host"]');
|
|
708
|
+
assert.equal(initialHostButton?.textContent, 'Set Host');
|
|
709
|
+
initialHostButton.dispatchEvent({ type: 'click' });
|
|
710
|
+
await wait();
|
|
711
|
+
const setHostCall = dotNetCalls.find(call => call.methodName === 'SetRemoteFleetHostFromJs');
|
|
712
|
+
assert.ok(setHostCall);
|
|
713
|
+
assert.equal(setHostCall.args[0], 'remote-fleet-render-smoke');
|
|
714
|
+
assert.equal(setHostCall.args[1], true);
|
|
715
|
+
|
|
716
|
+
hub.setHostTarget({
|
|
717
|
+
nodeId: 'remote-fleet-render-smoke',
|
|
718
|
+
enabled: true,
|
|
719
|
+
leaseMs: 30000
|
|
720
|
+
});
|
|
721
|
+
manager.renderRemoteFleetMonitorForTest(bodyView, buildMonitorNode(devices, hub.getStatus({ includeSecrets: true }), latestTaskBatch, recentTaskBatches));
|
|
722
|
+
assert.equal(bodyView.querySelector('[data-remote-fleet-host-state="true"]')?.textContent.includes('Host: Active'), true);
|
|
723
|
+
assert.equal(bodyView.querySelector('[data-remote-fleet-action="set-host"]')?.textContent, 'Hosting');
|
|
724
|
+
const stopHostButton = bodyView.querySelector('[data-remote-fleet-action="stop-host"]');
|
|
725
|
+
assert.ok(stopHostButton);
|
|
726
|
+
const stopHostStart = dotNetCalls.length;
|
|
727
|
+
stopHostButton.dispatchEvent({ type: 'click' });
|
|
728
|
+
await wait();
|
|
729
|
+
const stopHostCall = dotNetCalls
|
|
730
|
+
.slice(stopHostStart)
|
|
731
|
+
.find(call => call.methodName === 'SetRemoteFleetHostFromJs');
|
|
732
|
+
assert.ok(stopHostCall);
|
|
733
|
+
assert.equal(stopHostCall.args[0], 'remote-fleet-render-smoke');
|
|
734
|
+
assert.equal(stopHostCall.args[1], false);
|
|
735
|
+
|
|
670
736
|
assert.match(bodyView.querySelector('[data-remote-fleet-manager-version="true"]')?.textContent || '', /^@mindexec\/cli render-smoke-manager - 127\.0\.0\.1:\d+$/);
|
|
671
737
|
assert.ok(livePanel?.textContent.includes('RemoteFast 20 fps'), livePanel?.textContent || bodyView.textContent);
|
|
672
|
-
assert.
|
|
738
|
+
assert.equal(bodyView.querySelector('code'), null);
|
|
739
|
+
assert.equal(bodyView.textContent.includes('npx @mindexec/remote connect'), false);
|
|
740
|
+
assert.equal(bodyView.querySelector('[data-remote-fleet-action="copy-command"]'), null);
|
|
741
|
+
assert.equal(bodyView.querySelector('[data-remote-fleet-action="refresh"]'), null);
|
|
742
|
+
assert.equal(bodyView.querySelector('[data-remote-fleet-auto-toggle="true"]'), null);
|
|
673
743
|
assert.ok(bodyView.textContent.includes('synthetic-render-ai'));
|
|
674
744
|
assert.ok(bodyView.textContent.includes('Task timed out waiting for the agent response.'));
|
|
675
745
|
const batchSummary = bodyView.querySelector('[data-remote-fleet-task-batch="true"]');
|
|
@@ -719,63 +789,20 @@ try {
|
|
|
719
789
|
assert.ok(bodyView.querySelectorAll('[data-remote-fleet-group-header="true"]').length >= 2);
|
|
720
790
|
assert.equal(bodyView.querySelectorAll('article[data-device-id]').length, SYNTHETIC_COUNT);
|
|
721
791
|
|
|
722
|
-
const taskInput = bodyView.querySelector('[data-remote-fleet-task-input="true"]');
|
|
723
|
-
const sendVisibleButton = bodyView.querySelector('[data-remote-fleet-action="task-visible"]');
|
|
724
|
-
assert.ok(taskInput);
|
|
725
|
-
assert.ok(sendVisibleButton);
|
|
726
|
-
taskInput.value = 'Dispatch smoke partial failure';
|
|
727
|
-
sendVisibleButton.dispatchEvent({ type: 'click' });
|
|
728
|
-
await wait();
|
|
729
|
-
const batchCall = dotNetCalls.find(call => call.methodName === 'DispatchRemoteFleetTaskBatchFromJs');
|
|
730
|
-
assert.ok(batchCall);
|
|
731
|
-
const batchTargetIds = Array.isArray(batchCall.args[1]) ? batchCall.args[1] : [];
|
|
732
|
-
assert.equal(batchTargetIds.length, connectedCount);
|
|
733
792
|
const feedback = bodyView.querySelector('[data-remote-fleet-task-feedback="true"]');
|
|
734
793
|
assert.ok(feedback);
|
|
735
|
-
assert.equal(feedback.style.display, '
|
|
736
|
-
assert.match(feedback.textContent, new RegExp(`Queued ${connectedCount - 1}/${connectedCount} visible remote task\\(s\\); 1 failed`));
|
|
737
|
-
assert.match(feedback.textContent, /device-ai-assist-unavailable/);
|
|
794
|
+
assert.equal(feedback.style.display, 'none');
|
|
738
795
|
|
|
739
796
|
const activeSelects = bodyView.querySelectorAll('select');
|
|
740
797
|
assert.equal(activeSelects.length, 4);
|
|
741
798
|
const [aiFilterSelect] = activeSelects;
|
|
742
|
-
|
|
743
|
-
assert.ok(aiToggle);
|
|
799
|
+
assert.equal(bodyView.querySelector('[data-remote-fleet-ai-toggle="true"]'), null);
|
|
744
800
|
aiFilterSelect.value = 'ai';
|
|
745
801
|
aiFilterSelect.dispatchEvent({ type: 'change' });
|
|
746
|
-
aiToggle.checked = true;
|
|
747
|
-
aiToggle.dispatchEvent({ type: 'change' });
|
|
748
802
|
assert.equal(bodyView.querySelector('[data-remote-fleet-match-count="true"]')?.textContent, `${aiCount}/${SYNTHETIC_COUNT}`);
|
|
749
|
-
assert.equal(
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
sendVisibleButton.dispatchEvent({ type: 'click' });
|
|
753
|
-
await wait();
|
|
754
|
-
const aiBatchCall = dotNetCalls
|
|
755
|
-
.slice(aiDispatchCallStart)
|
|
756
|
-
.find(call => call.methodName === 'DispatchRemoteFleetTaskBatchFromJs');
|
|
757
|
-
assert.ok(aiBatchCall);
|
|
758
|
-
const aiTargetIds = Array.isArray(aiBatchCall.args[1]) ? aiBatchCall.args[1] : [];
|
|
759
|
-
const expectedAiTargetIds = devices
|
|
760
|
-
.filter(device => device.Connected && device.AiAssistEnabled)
|
|
761
|
-
.map(device => device.DeviceId)
|
|
762
|
-
.sort();
|
|
763
|
-
assert.deepEqual([...aiTargetIds].sort(), expectedAiTargetIds);
|
|
764
|
-
assert.equal(aiBatchCall.args[3], true);
|
|
765
|
-
|
|
766
|
-
const sendConnectedButton = bodyView.querySelector('[data-remote-fleet-action="task-connected"]');
|
|
767
|
-
assert.ok(sendConnectedButton);
|
|
768
|
-
taskInput.value = 'Dispatch all AI smoke';
|
|
769
|
-
const allAiDispatchCallStart = dotNetCalls.length;
|
|
770
|
-
sendConnectedButton.dispatchEvent({ type: 'click' });
|
|
771
|
-
await wait();
|
|
772
|
-
const allAiCall = dotNetCalls
|
|
773
|
-
.slice(allAiDispatchCallStart)
|
|
774
|
-
.find(call => call.methodName === 'DispatchRemoteFleetTaskFromJs');
|
|
775
|
-
assert.ok(allAiCall);
|
|
776
|
-
assert.equal(allAiCall.args[1], '');
|
|
777
|
-
assert.equal(allAiCall.args[3], true);
|
|
778
|
-
assert.match(feedback.textContent, new RegExp(`Queued ${aiCount}/${aiCount} AI task\\(s\\)`));
|
|
803
|
+
assert.equal(dotNetCalls.some(call =>
|
|
804
|
+
call.methodName === 'DispatchRemoteFleetTaskBatchFromJs'
|
|
805
|
+
|| call.methodName === 'DispatchRemoteFleetTaskFromJs'), false);
|
|
779
806
|
|
|
780
807
|
const deviceBody = document.createElement('div');
|
|
781
808
|
document.body.appendChild(deviceBody);
|
|
@@ -51,11 +51,11 @@ async function fetchJson(url, options = {}) {
|
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
async function waitForBridge(baseUrl, getFailureDetails) {
|
|
54
|
+
async function waitForBridge(baseUrl, getFailureDetails, bridgeToken = BRIDGE_TOKEN) {
|
|
55
55
|
const startedAt = Date.now();
|
|
56
56
|
while (Date.now() - startedAt < 30000) {
|
|
57
57
|
try {
|
|
58
|
-
const result = await fetchJson(`${baseUrl}/api/remote/status`, { token:
|
|
58
|
+
const result = await fetchJson(`${baseUrl}/api/remote/status`, { token: bridgeToken });
|
|
59
59
|
if (result.ok && result.payload?.started === true) {
|
|
60
60
|
return result.payload;
|
|
61
61
|
}
|
|
@@ -70,49 +70,96 @@ async function waitForBridge(baseUrl, getFailureDetails) {
|
|
|
70
70
|
|
|
71
71
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
72
72
|
const bridgeRoot = path.resolve(__dirname, '..');
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
env
|
|
73
|
+
|
|
74
|
+
function spawnBridge({
|
|
75
|
+
bridgePort,
|
|
76
|
+
remoteHubPort,
|
|
77
|
+
bridgeToken = BRIDGE_TOKEN,
|
|
78
|
+
pairToken = PAIR_TOKEN,
|
|
79
|
+
syntheticFleetEnabled = false
|
|
80
|
+
}) {
|
|
81
|
+
const env = {
|
|
82
82
|
...process.env,
|
|
83
83
|
BRIDGE_PORT: String(bridgePort),
|
|
84
|
-
BRIDGE_TOKEN,
|
|
84
|
+
BRIDGE_TOKEN: bridgeToken,
|
|
85
85
|
BRIDGE_REQUIRE_TOKEN: '1',
|
|
86
86
|
MINDEXEC_REMOTE_HUB: '1',
|
|
87
87
|
REMOTE_HUB_HOST: '127.0.0.1',
|
|
88
88
|
REMOTE_HUB_PORT: String(remoteHubPort),
|
|
89
|
-
REMOTE_HUB_PAIR_TOKEN:
|
|
90
|
-
MINDEXEC_REMOTE_SYNTHETIC_FLEET: '1',
|
|
89
|
+
REMOTE_HUB_PAIR_TOKEN: pairToken,
|
|
91
90
|
NO_COLOR: '1'
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
delete env.MINDEXEC_REMOTE_SYNTHETIC_FLEET;
|
|
94
|
+
delete env.REMOTE_HUB_SYNTHETIC_FLEET;
|
|
95
|
+
if (syntheticFleetEnabled) {
|
|
96
|
+
env.MINDEXEC_REMOTE_SYNTHETIC_FLEET = '1';
|
|
92
97
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
98
|
+
|
|
99
|
+
const child = spawn(process.execPath, ['server.js'], {
|
|
100
|
+
cwd: bridgeRoot,
|
|
101
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
102
|
+
windowsHide: true,
|
|
103
|
+
env
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
let stdout = '';
|
|
107
|
+
let stderr = '';
|
|
108
|
+
child.stdout.on('data', chunk => {
|
|
109
|
+
stdout += chunk.toString();
|
|
110
|
+
});
|
|
111
|
+
child.stderr.on('data', chunk => {
|
|
112
|
+
stderr += chunk.toString();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const exitPromise = new Promise(resolve => child.once('exit', resolve));
|
|
116
|
+
const details = () => `stdout=${stdout}\nstderr=${stderr}`;
|
|
117
|
+
const stop = async () => {
|
|
118
|
+
if (child.exitCode === null && !child.killed) {
|
|
119
|
+
child.kill('SIGTERM');
|
|
120
|
+
await Promise.race([
|
|
121
|
+
exitPromise,
|
|
122
|
+
wait(5000)
|
|
123
|
+
]);
|
|
124
|
+
if (child.exitCode === null && !child.killed) {
|
|
125
|
+
child.kill('SIGKILL');
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
baseUrl: `http://127.0.0.1:${bridgePort}`,
|
|
132
|
+
bridgePort,
|
|
133
|
+
remoteHubPort,
|
|
134
|
+
bridgeToken,
|
|
135
|
+
pairToken,
|
|
136
|
+
child,
|
|
137
|
+
details,
|
|
138
|
+
stop
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function runSyntheticEnabledSmoke() {
|
|
143
|
+
const bridgePort = await findFreePort();
|
|
144
|
+
const remoteHubPort = await findFreePort();
|
|
145
|
+
const bridge = spawnBridge({
|
|
146
|
+
bridgePort,
|
|
147
|
+
remoteHubPort,
|
|
148
|
+
syntheticFleetEnabled: true
|
|
149
|
+
});
|
|
150
|
+
const { baseUrl } = bridge;
|
|
151
|
+
const details = bridge.details;
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
const status = await waitForBridge(baseUrl, details, bridge.bridgeToken);
|
|
109
155
|
assert.equal(status.started, true);
|
|
110
156
|
assert.equal(status.port, remoteHubPort);
|
|
111
157
|
assert.equal(status.managerPackage, '@mindexec/cli');
|
|
112
|
-
assert.equal(status.managerVersion, '0.2.
|
|
158
|
+
assert.equal(status.managerVersion, '0.2.25');
|
|
113
159
|
assert.equal(status.agentPackage, '@mindexec/remote');
|
|
114
160
|
assert.equal(status.canvasPagination, 'none');
|
|
115
161
|
assert.equal(status.canvasDeviceListMode, 'all-devices');
|
|
162
|
+
assert.equal(status.hostTargetActive, false);
|
|
116
163
|
assert.equal(status.pairToken, PAIR_TOKEN);
|
|
117
164
|
|
|
118
165
|
const unauthorizedStatus = await fetchJson(`${baseUrl}/api/remote/status`);
|
|
@@ -124,6 +171,58 @@ try {
|
|
|
124
171
|
});
|
|
125
172
|
assert.equal(unauthorizedDelete.status, 401);
|
|
126
173
|
|
|
174
|
+
const unauthorizedHost = await fetchJson(`${baseUrl}/api/remote/host-target`, {
|
|
175
|
+
method: 'POST',
|
|
176
|
+
body: JSON.stringify({
|
|
177
|
+
nodeId: 'remote-fleet-host-unauthorized',
|
|
178
|
+
enabled: true
|
|
179
|
+
})
|
|
180
|
+
});
|
|
181
|
+
assert.equal(unauthorizedHost.status, 401);
|
|
182
|
+
|
|
183
|
+
const setHost = await fetchJson(`${baseUrl}/api/remote/host-target`, {
|
|
184
|
+
method: 'POST',
|
|
185
|
+
token: BRIDGE_TOKEN,
|
|
186
|
+
body: JSON.stringify({
|
|
187
|
+
nodeId: 'remote-fleet-monitor-a',
|
|
188
|
+
enabled: true,
|
|
189
|
+
leaseMs: 30000
|
|
190
|
+
})
|
|
191
|
+
});
|
|
192
|
+
assert.equal(setHost.ok, true, JSON.stringify(setHost.payload));
|
|
193
|
+
assert.equal(setHost.payload?.ok, true);
|
|
194
|
+
assert.equal(setHost.payload?.active, true);
|
|
195
|
+
assert.equal(setHost.payload?.hostTarget?.nodeId, 'remote-fleet-monitor-a');
|
|
196
|
+
assert.equal(setHost.payload?.hostTarget?.endpoint, `127.0.0.1:${remoteHubPort}`);
|
|
197
|
+
|
|
198
|
+
const hostStatus = await fetchJson(`${baseUrl}/api/remote/status`, { token: BRIDGE_TOKEN });
|
|
199
|
+
assert.equal(hostStatus.ok, true, JSON.stringify(hostStatus.payload));
|
|
200
|
+
assert.equal(hostStatus.payload?.hostTargetActive, true);
|
|
201
|
+
assert.equal(hostStatus.payload?.hostTargetNodeId, 'remote-fleet-monitor-a');
|
|
202
|
+
assert.equal(hostStatus.payload?.hostTargetEndpoint, `127.0.0.1:${remoteHubPort}`);
|
|
203
|
+
|
|
204
|
+
const clearHostMismatch = await fetchJson(`${baseUrl}/api/remote/host-target`, {
|
|
205
|
+
method: 'DELETE',
|
|
206
|
+
token: BRIDGE_TOKEN,
|
|
207
|
+
body: JSON.stringify({
|
|
208
|
+
nodeId: 'remote-fleet-monitor-b'
|
|
209
|
+
})
|
|
210
|
+
});
|
|
211
|
+
assert.equal(clearHostMismatch.ok, true, JSON.stringify(clearHostMismatch.payload));
|
|
212
|
+
assert.equal(clearHostMismatch.payload?.ok, false);
|
|
213
|
+
assert.equal(clearHostMismatch.payload?.error, 'host-target-node-mismatch');
|
|
214
|
+
|
|
215
|
+
const clearHost = await fetchJson(`${baseUrl}/api/remote/host-target`, {
|
|
216
|
+
method: 'DELETE',
|
|
217
|
+
token: BRIDGE_TOKEN,
|
|
218
|
+
body: JSON.stringify({
|
|
219
|
+
nodeId: 'remote-fleet-monitor-a'
|
|
220
|
+
})
|
|
221
|
+
});
|
|
222
|
+
assert.equal(clearHost.ok, true, JSON.stringify(clearHost.payload));
|
|
223
|
+
assert.equal(clearHost.payload?.ok, true);
|
|
224
|
+
assert.equal(clearHost.payload?.active, false);
|
|
225
|
+
|
|
127
226
|
const seed = await fetchJson(`${baseUrl}/api/remote/synthetic/seed`, {
|
|
128
227
|
method: 'POST',
|
|
129
228
|
token: BRIDGE_TOKEN,
|
|
@@ -283,14 +382,93 @@ try {
|
|
|
283
382
|
|
|
284
383
|
console.log(`RemoteHub HTTP smoke OK (${SYNTHETIC_COUNT} synthetic devices, bridge ${bridgePort}, remote ${remoteHubPort})`);
|
|
285
384
|
} finally {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
385
|
+
await bridge.stop();
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
async function runSyntheticDisabledSmoke() {
|
|
390
|
+
const bridgePort = await findFreePort();
|
|
391
|
+
const remoteHubPort = await findFreePort();
|
|
392
|
+
const bridgeToken = `${BRIDGE_TOKEN}-disabled`;
|
|
393
|
+
const pairToken = `${PAIR_TOKEN}-disabled`;
|
|
394
|
+
const bridge = spawnBridge({
|
|
395
|
+
bridgePort,
|
|
396
|
+
remoteHubPort,
|
|
397
|
+
bridgeToken,
|
|
398
|
+
pairToken,
|
|
399
|
+
syntheticFleetEnabled: false
|
|
400
|
+
});
|
|
401
|
+
const { baseUrl } = bridge;
|
|
402
|
+
|
|
403
|
+
try {
|
|
404
|
+
const status = await waitForBridge(baseUrl, bridge.details, bridge.bridgeToken);
|
|
405
|
+
assert.equal(status.started, true);
|
|
406
|
+
assert.equal(status.port, remoteHubPort);
|
|
407
|
+
assert.equal(status.pairToken, pairToken);
|
|
408
|
+
assert.equal(status.canvasPagination, 'none');
|
|
409
|
+
assert.equal(status.canvasDeviceListMode, 'all-devices');
|
|
410
|
+
assert.equal(status.hostTargetActive, false);
|
|
411
|
+
|
|
412
|
+
const seedDenied = await fetchJson(`${baseUrl}/api/remote/synthetic/seed`, {
|
|
413
|
+
method: 'POST',
|
|
414
|
+
token: bridge.bridgeToken,
|
|
415
|
+
body: JSON.stringify({
|
|
416
|
+
count: 3,
|
|
417
|
+
replace: true
|
|
418
|
+
})
|
|
419
|
+
});
|
|
420
|
+
assert.equal(seedDenied.status, 403);
|
|
421
|
+
assert.equal(seedDenied.payload?.ok, false);
|
|
422
|
+
assert.equal(seedDenied.payload?.error, 'synthetic-fleet-disabled');
|
|
423
|
+
|
|
424
|
+
const clearDenied = await fetchJson(`${baseUrl}/api/remote/synthetic`, {
|
|
425
|
+
method: 'DELETE',
|
|
426
|
+
token: bridge.bridgeToken
|
|
427
|
+
});
|
|
428
|
+
assert.equal(clearDenied.status, 403);
|
|
429
|
+
assert.equal(clearDenied.payload?.ok, false);
|
|
430
|
+
assert.equal(clearDenied.payload?.error, 'synthetic-fleet-disabled');
|
|
431
|
+
|
|
432
|
+
const devicesResult = await fetchJson(`${baseUrl}/api/remote/devices`, { token: bridge.bridgeToken });
|
|
433
|
+
assert.equal(devicesResult.ok, true, JSON.stringify(devicesResult.payload));
|
|
434
|
+
assert.equal(devicesResult.payload?.total, 0);
|
|
435
|
+
assert.equal(devicesResult.payload?.pagination, 'none');
|
|
436
|
+
assert.equal(devicesResult.payload?.canvasDeviceListMode, 'all-devices');
|
|
437
|
+
assert.equal(devicesResult.payload?.devices?.length, 0);
|
|
438
|
+
|
|
439
|
+
const emptyBatch = await fetchJson(`${baseUrl}/api/remote/tasks`, {
|
|
440
|
+
method: 'POST',
|
|
441
|
+
token: bridge.bridgeToken,
|
|
442
|
+
body: JSON.stringify({
|
|
443
|
+
allConnected: true,
|
|
444
|
+
title: 'HTTP smoke no target batch',
|
|
445
|
+
instruction: 'This should not queue because no remote devices are connected.',
|
|
446
|
+
approvalLevel: 'task-only'
|
|
447
|
+
})
|
|
448
|
+
});
|
|
449
|
+
assert.equal(emptyBatch.ok, true, JSON.stringify(emptyBatch.payload));
|
|
450
|
+
assert.equal(emptyBatch.payload?.ok, false);
|
|
451
|
+
assert.equal(emptyBatch.payload?.total, 0);
|
|
452
|
+
assert.equal(emptyBatch.payload?.queued, 0);
|
|
453
|
+
assert.equal(emptyBatch.payload?.error, 'no-target-devices');
|
|
454
|
+
|
|
455
|
+
const hostTarget = await fetchJson(`${baseUrl}/api/remote/host-target`, {
|
|
456
|
+
method: 'POST',
|
|
457
|
+
token: bridge.bridgeToken,
|
|
458
|
+
body: JSON.stringify({
|
|
459
|
+
nodeId: 'disabled-synthetic-host',
|
|
460
|
+
enabled: true
|
|
461
|
+
})
|
|
462
|
+
});
|
|
463
|
+
assert.equal(hostTarget.ok, true, JSON.stringify(hostTarget.payload));
|
|
464
|
+
assert.equal(hostTarget.payload?.ok, true);
|
|
465
|
+
assert.equal(hostTarget.payload?.active, true);
|
|
466
|
+
|
|
467
|
+
console.log(`RemoteHub HTTP safety smoke OK (synthetic disabled, bridge ${bridgePort}, remote ${remoteHubPort})`);
|
|
468
|
+
} finally {
|
|
469
|
+
await bridge.stop();
|
|
295
470
|
}
|
|
296
471
|
}
|
|
472
|
+
|
|
473
|
+
await runSyntheticEnabledSmoke();
|
|
474
|
+
await runSyntheticDisabledSmoke();
|