@shardworks/spider-apparatus 0.1.153 → 0.1.154
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 +8 -8
- package/src/static/spider-ui.test.ts +174 -0
- package/src/static/spider.js +116 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shardworks/spider-apparatus",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.154",
|
|
4
4
|
"license": "ISC",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -19,13 +19,13 @@
|
|
|
19
19
|
"hono": "^4.7.11",
|
|
20
20
|
"yaml": "^2.0.0",
|
|
21
21
|
"zod": "4.3.6",
|
|
22
|
-
"@shardworks/nexus-core": "0.1.
|
|
23
|
-
"@shardworks/
|
|
24
|
-
"@shardworks/clerk-apparatus": "0.1.
|
|
25
|
-
"@shardworks/
|
|
26
|
-
"@shardworks/tools-apparatus": "0.1.
|
|
27
|
-
"@shardworks/
|
|
28
|
-
"@shardworks/
|
|
22
|
+
"@shardworks/nexus-core": "0.1.154",
|
|
23
|
+
"@shardworks/stacks-apparatus": "0.1.154",
|
|
24
|
+
"@shardworks/clerk-apparatus": "0.1.154",
|
|
25
|
+
"@shardworks/fabricator-apparatus": "0.1.154",
|
|
26
|
+
"@shardworks/tools-apparatus": "0.1.154",
|
|
27
|
+
"@shardworks/animator-apparatus": "0.1.154",
|
|
28
|
+
"@shardworks/codexes-apparatus": "0.1.154"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/node": "25.5.0"
|
|
@@ -220,3 +220,177 @@ describe('index.html status filter includes cancelled', () => {
|
|
|
220
220
|
assert.ok(cancelledIdx > blockedIdx, 'cancelled option should appear after blocked');
|
|
221
221
|
});
|
|
222
222
|
});
|
|
223
|
+
|
|
224
|
+
// ── Rig auto-polling ────────────────────────────────────────────────────
|
|
225
|
+
|
|
226
|
+
describe('spider.js rig list polling', () => {
|
|
227
|
+
it('declares rigListPollTimer state variable', () => {
|
|
228
|
+
assert.match(
|
|
229
|
+
spiderJs,
|
|
230
|
+
/var rigListPollTimer\s*=\s*null/,
|
|
231
|
+
'should declare rigListPollTimer state variable',
|
|
232
|
+
);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('declares currentRigPollTimer state variable', () => {
|
|
236
|
+
assert.match(
|
|
237
|
+
spiderJs,
|
|
238
|
+
/var currentRigPollTimer\s*=\s*null/,
|
|
239
|
+
'should declare currentRigPollTimer state variable',
|
|
240
|
+
);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('declares RIG_POLL_INTERVAL constant', () => {
|
|
244
|
+
assert.match(
|
|
245
|
+
spiderJs,
|
|
246
|
+
/var RIG_POLL_INTERVAL\s*=\s*2000/,
|
|
247
|
+
'should declare RIG_POLL_INTERVAL at 2000ms',
|
|
248
|
+
);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('defines isRigInFlight helper checking running and blocked', () => {
|
|
252
|
+
assert.match(
|
|
253
|
+
spiderJs,
|
|
254
|
+
/function isRigInFlight\(rig\)/,
|
|
255
|
+
'should define isRigInFlight helper',
|
|
256
|
+
);
|
|
257
|
+
assert.match(
|
|
258
|
+
spiderJs,
|
|
259
|
+
/rig\.status === 'running' \|\| rig\.status === 'blocked'/,
|
|
260
|
+
'isRigInFlight should check running or blocked',
|
|
261
|
+
);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('defines stopRigListPoll that clears the interval', () => {
|
|
265
|
+
assert.match(
|
|
266
|
+
spiderJs,
|
|
267
|
+
/function stopRigListPoll\(\)/,
|
|
268
|
+
'should define stopRigListPoll',
|
|
269
|
+
);
|
|
270
|
+
assert.match(
|
|
271
|
+
spiderJs,
|
|
272
|
+
/clearInterval\(rigListPollTimer\)/,
|
|
273
|
+
'stopRigListPoll should clear rigListPollTimer',
|
|
274
|
+
);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('defines stopCurrentRigPoll that clears the interval', () => {
|
|
278
|
+
assert.match(
|
|
279
|
+
spiderJs,
|
|
280
|
+
/function stopCurrentRigPoll\(\)/,
|
|
281
|
+
'should define stopCurrentRigPoll',
|
|
282
|
+
);
|
|
283
|
+
assert.match(
|
|
284
|
+
spiderJs,
|
|
285
|
+
/clearInterval\(currentRigPollTimer\)/,
|
|
286
|
+
'stopCurrentRigPoll should clear currentRigPollTimer',
|
|
287
|
+
);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('starts rig list polling after fetchRigs succeeds', () => {
|
|
291
|
+
// The fetchRigs promise chain should call startRigListPollIfNeeded
|
|
292
|
+
const fetchRigsBlock = spiderJs.match(
|
|
293
|
+
/function fetchRigs\([\s\S]*?\.catch/,
|
|
294
|
+
);
|
|
295
|
+
assert.ok(fetchRigsBlock, 'should find fetchRigs function');
|
|
296
|
+
assert.match(
|
|
297
|
+
fetchRigsBlock[0],
|
|
298
|
+
/startRigListPollIfNeeded\(\)/,
|
|
299
|
+
'fetchRigs should call startRigListPollIfNeeded after rendering',
|
|
300
|
+
);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('starts current rig polling in showRigDetail', () => {
|
|
304
|
+
const showRigBlock = spiderJs.match(
|
|
305
|
+
/function showRigDetail\(rig\)[\s\S]*?startCurrentRigPoll\(\)/,
|
|
306
|
+
);
|
|
307
|
+
assert.ok(
|
|
308
|
+
showRigBlock,
|
|
309
|
+
'showRigDetail should call startCurrentRigPoll',
|
|
310
|
+
);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('stops current rig poll when navigating back to list', () => {
|
|
314
|
+
const backBlock = spiderJs.match(
|
|
315
|
+
/function backToList\(\)[\s\S]*?stopCurrentRigPoll\(\)/,
|
|
316
|
+
);
|
|
317
|
+
assert.ok(
|
|
318
|
+
backBlock,
|
|
319
|
+
'backToList should call stopCurrentRigPoll',
|
|
320
|
+
);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('stops current rig poll when selecting a new rig', () => {
|
|
324
|
+
const showRigBlock = spiderJs.match(
|
|
325
|
+
/function showRigDetail\(rig\)[\s\S]*?stopCurrentRigPoll\(\)/,
|
|
326
|
+
);
|
|
327
|
+
assert.ok(
|
|
328
|
+
showRigBlock,
|
|
329
|
+
'showRigDetail should call stopCurrentRigPoll before starting new poll',
|
|
330
|
+
);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('fetchCurrentRigQuiet fetches /api/rig/show with rig id', () => {
|
|
334
|
+
assert.match(
|
|
335
|
+
spiderJs,
|
|
336
|
+
/function fetchCurrentRigQuiet\(\)/,
|
|
337
|
+
'should define fetchCurrentRigQuiet',
|
|
338
|
+
);
|
|
339
|
+
assert.match(
|
|
340
|
+
spiderJs,
|
|
341
|
+
/\/api\/rig\/show\?id=/,
|
|
342
|
+
'fetchCurrentRigQuiet should fetch /api/rig/show',
|
|
343
|
+
);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it('fetchCurrentRigQuiet preserves selectedEngineId across polls', () => {
|
|
347
|
+
// The poll handler should find the updated engine by selectedEngineId.
|
|
348
|
+
// Use a greedy match to capture the whole function body up to the next
|
|
349
|
+
// top-level function declaration.
|
|
350
|
+
const pollBlock = spiderJs.match(
|
|
351
|
+
/function fetchCurrentRigQuiet[\s\S]*?(?=\n function )/,
|
|
352
|
+
);
|
|
353
|
+
assert.ok(pollBlock, 'should find fetchCurrentRigQuiet');
|
|
354
|
+
assert.match(
|
|
355
|
+
pollBlock[0],
|
|
356
|
+
/selectedEngineId/,
|
|
357
|
+
'should reference selectedEngineId to preserve selection',
|
|
358
|
+
);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('fetchCurrentRigQuiet stops polling on terminal status', () => {
|
|
362
|
+
const pollBlock = spiderJs.match(
|
|
363
|
+
/function fetchCurrentRigQuiet[\s\S]*?(?=\n function )/,
|
|
364
|
+
);
|
|
365
|
+
assert.ok(pollBlock, 'should find fetchCurrentRigQuiet');
|
|
366
|
+
assert.match(
|
|
367
|
+
pollBlock[0],
|
|
368
|
+
/isRigInFlight/,
|
|
369
|
+
'should check isRigInFlight and stop when terminal',
|
|
370
|
+
);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it('refresh button resets rig list poll cycle', () => {
|
|
374
|
+
const refreshBlock = spiderJs.match(
|
|
375
|
+
/refresh-btn[\s\S]*?fetchRigs/,
|
|
376
|
+
);
|
|
377
|
+
assert.ok(refreshBlock, 'should find refresh button handler');
|
|
378
|
+
assert.match(
|
|
379
|
+
refreshBlock[0],
|
|
380
|
+
/stopRigListPoll\(\)/,
|
|
381
|
+
'refresh button should stop existing poll before fetching',
|
|
382
|
+
);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it('rig list quiet poll stops itself when no in-flight rigs remain', () => {
|
|
386
|
+
const quietBlock = spiderJs.match(
|
|
387
|
+
/function fetchRigListQuiet[\s\S]*?stopRigListPoll/,
|
|
388
|
+
);
|
|
389
|
+
assert.ok(quietBlock, 'should find fetchRigListQuiet');
|
|
390
|
+
assert.match(
|
|
391
|
+
quietBlock[0],
|
|
392
|
+
/hasInFlight/,
|
|
393
|
+
'should check for in-flight rigs and stop poll if none',
|
|
394
|
+
);
|
|
395
|
+
});
|
|
396
|
+
});
|
package/src/static/spider.js
CHANGED
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
var sessionPollTimer = null;
|
|
19
19
|
var sessionEventSource = null;
|
|
20
20
|
var selectedTemplateName = null;
|
|
21
|
+
var rigListPollTimer = null;
|
|
22
|
+
var currentRigPollTimer = null;
|
|
23
|
+
|
|
24
|
+
var RIG_POLL_INTERVAL = 2000;
|
|
21
25
|
|
|
22
26
|
// ── Badge mapping ──────────────────────────────────────────────────────
|
|
23
27
|
|
|
@@ -129,6 +133,112 @@
|
|
|
129
133
|
stopSessionPoll();
|
|
130
134
|
}
|
|
131
135
|
|
|
136
|
+
// ── Rig polling helpers ────────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
function isRigInFlight(rig) {
|
|
139
|
+
return rig.status === 'running' || rig.status === 'blocked';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function stopRigListPoll() {
|
|
143
|
+
if (rigListPollTimer !== null) {
|
|
144
|
+
clearInterval(rigListPollTimer);
|
|
145
|
+
rigListPollTimer = null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function stopCurrentRigPoll() {
|
|
150
|
+
if (currentRigPollTimer !== null) {
|
|
151
|
+
clearInterval(currentRigPollTimer);
|
|
152
|
+
currentRigPollTimer = null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function startRigListPollIfNeeded() {
|
|
157
|
+
stopRigListPoll();
|
|
158
|
+
var hasInFlight = rigs.some(isRigInFlight);
|
|
159
|
+
if (!hasInFlight) return;
|
|
160
|
+
rigListPollTimer = setInterval(function () {
|
|
161
|
+
fetchRigListQuiet();
|
|
162
|
+
}, RIG_POLL_INTERVAL);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Refetch rig list without resetting filters — silent background refresh. */
|
|
166
|
+
function fetchRigListQuiet() {
|
|
167
|
+
var rigUrl = '/api/rig/list?limit=100';
|
|
168
|
+
if (currentStatusFilter) {
|
|
169
|
+
rigUrl += '&status=' + encodeURIComponent(currentStatusFilter);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
var rigPromise = fetch(rigUrl).then(function (r) { return r.json(); });
|
|
173
|
+
var writPromise = fetch('/api/writ/list?limit=100').then(function (r) { return r.json(); });
|
|
174
|
+
|
|
175
|
+
Promise.all([rigPromise, writPromise]).then(function (results) {
|
|
176
|
+
rigs = Array.isArray(results[0]) ? results[0] : [];
|
|
177
|
+
buildWritLookup(Array.isArray(results[1]) ? results[1] : []);
|
|
178
|
+
renderRigList();
|
|
179
|
+
|
|
180
|
+
// Re-evaluate whether polling should continue
|
|
181
|
+
var hasInFlight = rigs.some(isRigInFlight);
|
|
182
|
+
if (!hasInFlight) {
|
|
183
|
+
stopRigListPoll();
|
|
184
|
+
}
|
|
185
|
+
}).catch(function (err) {
|
|
186
|
+
console.error('[spider] rig list poll error:', err);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function startCurrentRigPoll() {
|
|
191
|
+
stopCurrentRigPoll();
|
|
192
|
+
if (!currentRig || !isRigInFlight(currentRig)) return;
|
|
193
|
+
currentRigPollTimer = setInterval(function () {
|
|
194
|
+
fetchCurrentRigQuiet();
|
|
195
|
+
}, RIG_POLL_INTERVAL);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Refetch the currently-viewed rig and update detail + pipeline in place. */
|
|
199
|
+
function fetchCurrentRigQuiet() {
|
|
200
|
+
if (!currentRig) { stopCurrentRigPoll(); return; }
|
|
201
|
+
fetch('/api/rig/show?id=' + encodeURIComponent(currentRig.id))
|
|
202
|
+
.then(function (r) { return r.json(); })
|
|
203
|
+
.then(function (rig) {
|
|
204
|
+
if (!currentRig || currentRig.id !== rig.id) return; // navigated away
|
|
205
|
+
currentRig = rig;
|
|
206
|
+
|
|
207
|
+
// Update the meta table
|
|
208
|
+
var metaTable = document.getElementById('detail-meta');
|
|
209
|
+
if (metaTable) {
|
|
210
|
+
metaTable.innerHTML =
|
|
211
|
+
'<tbody>' +
|
|
212
|
+
'<tr><th>ID</th><td>' + esc(rig.id) + '</td></tr>' +
|
|
213
|
+
'<tr><th>Writ</th><td><a href="/pages/clerk/?writ=' + esc(rig.writId) + '">' + esc(rig.writId) + '</a></td></tr>' +
|
|
214
|
+
'<tr><th>Status</th><td>' + badgeHtml(rig.status) + '</td></tr>' +
|
|
215
|
+
'<tr><th>Created</th><td>' + esc(formatDate(rig.createdAt)) + '</td></tr>' +
|
|
216
|
+
'</tbody>';
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Re-render pipeline, preserving selection
|
|
220
|
+
renderPipeline(rig);
|
|
221
|
+
|
|
222
|
+
// If an engine was selected, update engine detail with fresh data
|
|
223
|
+
if (selectedEngineId) {
|
|
224
|
+
var updatedEngine = (rig.engines || []).find(function (e) {
|
|
225
|
+
return e.id === selectedEngineId;
|
|
226
|
+
});
|
|
227
|
+
if (updatedEngine) {
|
|
228
|
+
showEngineDetail(updatedEngine);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Stop polling once terminal
|
|
233
|
+
if (!isRigInFlight(rig)) {
|
|
234
|
+
stopCurrentRigPoll();
|
|
235
|
+
}
|
|
236
|
+
})
|
|
237
|
+
.catch(function (err) {
|
|
238
|
+
console.error('[spider] current rig poll error:', err);
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
132
242
|
// ── Fetch rigs ─────────────────────────────────────────────────────────
|
|
133
243
|
|
|
134
244
|
function fetchRigs(statusFilter) {
|
|
@@ -145,6 +255,7 @@
|
|
|
145
255
|
rigs = Array.isArray(results[0]) ? results[0] : [];
|
|
146
256
|
buildWritLookup(Array.isArray(results[1]) ? results[1] : []);
|
|
147
257
|
renderRigList();
|
|
258
|
+
startRigListPollIfNeeded();
|
|
148
259
|
}).catch(function (err) {
|
|
149
260
|
console.error('Failed to fetch rigs/writs:', err);
|
|
150
261
|
rigs = [];
|
|
@@ -236,6 +347,7 @@
|
|
|
236
347
|
selectedEngineId = null;
|
|
237
348
|
|
|
238
349
|
stopSessionStream();
|
|
350
|
+
stopCurrentRigPoll();
|
|
239
351
|
|
|
240
352
|
document.getElementById('rig-list-view').style.display = 'none';
|
|
241
353
|
document.getElementById('rig-detail-view').style.display = '';
|
|
@@ -279,6 +391,8 @@
|
|
|
279
391
|
|
|
280
392
|
var engineDetail = document.getElementById('engine-detail');
|
|
281
393
|
if (engineDetail) engineDetail.style.display = 'none';
|
|
394
|
+
|
|
395
|
+
startCurrentRigPoll();
|
|
282
396
|
}
|
|
283
397
|
|
|
284
398
|
// ── Render pipeline (generic) ──────────────────────────────────────────
|
|
@@ -646,6 +760,7 @@
|
|
|
646
760
|
|
|
647
761
|
function backToList() {
|
|
648
762
|
stopSessionStream();
|
|
763
|
+
stopCurrentRigPoll();
|
|
649
764
|
currentRig = null;
|
|
650
765
|
selectedEngineId = null;
|
|
651
766
|
document.getElementById('rig-detail-view').style.display = 'none';
|
|
@@ -877,6 +992,7 @@
|
|
|
877
992
|
var refreshBtn = document.getElementById('refresh-btn');
|
|
878
993
|
if (refreshBtn) {
|
|
879
994
|
refreshBtn.addEventListener('click', function () {
|
|
995
|
+
stopRigListPoll();
|
|
880
996
|
fetchRigs(currentStatusFilter);
|
|
881
997
|
});
|
|
882
998
|
}
|