@andrian.yablonskyy/thub-coordinator 1.1.10 → 1.1.12
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/public/css/thub.css +3 -3
- package/public/js/usb-import.js +1 -1
- package/public/js/usb-scan.js +1 -1
- package/src/db/migrations/020_add_usb_scan.sql +1 -1
- package/src/services/registry.js +1 -1
- package/src/web/routes.js +1 -1
- package/test/groups-tab.test.js +18 -0
- package/views/help/_client-machines.pug +2 -2
- package/views/help/_client-setup.pug +1 -1
- package/views/help/_docker.pug +1 -1
- package/views/mixins/resource.pug +20 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andrian.yablonskyy/thub-coordinator",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.12",
|
|
4
4
|
"description": "TestHub Coordinator — job queue, resource registry, scheduler, heartbeat monitor, log/artifact store and web dashboard",
|
|
5
5
|
"main": "src/server.js",
|
|
6
6
|
"engines": {
|
package/public/css/thub.css
CHANGED
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
.resource-capabilities {
|
|
59
|
-
font-size: 0.
|
|
59
|
+
font-size: 0.875rem;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
/* An overlay inside a modal (e.g. "Import to config"'s review note on the
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/* The client details modal has up to 7 tabs (Details, the config tabs,
|
|
83
|
-
|
|
83
|
+
USB devices) — tighter padding keeps them on one row, ⚠ icons
|
|
84
84
|
included, at the modal-lg width. */
|
|
85
85
|
.thub-resource-card .nav-tabs .nav-link {
|
|
86
86
|
padding: 0.45rem 0.65rem;
|
|
@@ -126,7 +126,7 @@
|
|
|
126
126
|
pointer-events: auto;
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
/*
|
|
129
|
+
/* USB devices: the lsusb output takes the rest of the modal's
|
|
130
130
|
height — what the tallest other tab sets, at least 12rem — and scrolls in
|
|
131
131
|
it. Its 0 basis keeps it from making the modal taller. */
|
|
132
132
|
.thub-resource-card .tab-content .tab-pane.thub-usb-pane {
|
package/public/js/usb-import.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file packages/coordinator/public/js/usb-import.js
|
|
3
|
-
* @description Dashboard: "Import to config" on the resource card's
|
|
3
|
+
* @description Dashboard: "Import to config" on the resource card's USB devices tab — updates the Config
|
|
4
4
|
* sub-tabs' device lists from the last `lsusb -tvv`, opens the first changed one, and asks to verify
|
|
5
5
|
*
|
|
6
6
|
* @author Andrian Yablonskyy
|
package/public/js/usb-scan.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file packages/coordinator/public/js/usb-scan.js
|
|
3
|
-
* @description Dashboard: the resource card's "
|
|
3
|
+
* @description Dashboard: the resource card's "USB devices" tab — Refresh asks the Client for `lsusb`,
|
|
4
4
|
* then polls until its answer arrives and shows it in place
|
|
5
5
|
*
|
|
6
6
|
* @author Andrian Yablonskyy
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
-- "
|
|
1
|
+
-- "USB devices" (resource card tab, README §10): the Client runs
|
|
2
2
|
-- `lsusb` only when an admin presses Refresh — a scan-usb command with its
|
|
3
3
|
-- next heartbeat — and posts the output back. `usb_scan` is the last result
|
|
4
4
|
-- (JSON {output, error, at}); `usb_scan_requested_at`/`_request_id` the
|
package/src/services/registry.js
CHANGED
|
@@ -625,7 +625,7 @@ function createRegistryService(db, { bus, events }){
|
|
|
625
625
|
return db.prepare('SELECT * FROM resources WHERE remove_requested_at IS NOT NULL').all().map(rowToResource);
|
|
626
626
|
}
|
|
627
627
|
|
|
628
|
-
// "
|
|
628
|
+
// "USB devices" tab: Refresh asks the Client to run `lsusb` on
|
|
629
629
|
// its next heartbeat (a scan-usb command, delivered by the caller's bus
|
|
630
630
|
// via commands). Only while it's connected — it would otherwise run
|
|
631
631
|
// whenever it next reconnects.
|
package/src/web/routes.js
CHANGED
|
@@ -616,7 +616,7 @@ function createWebRouter({ services, config }){
|
|
|
616
616
|
res.redirect(returnTo(req, '/runners'));
|
|
617
617
|
});
|
|
618
618
|
|
|
619
|
-
// "
|
|
619
|
+
// "USB devices" tab (public/js/usb-scan.js): Refresh queues a
|
|
620
620
|
// scan-usb for the Client's next heartbeat; the tab then polls the GET
|
|
621
621
|
// until the answer is in. JSON both ways — the modal stays open.
|
|
622
622
|
function usbScanView(req, r){
|
package/test/groups-tab.test.js
CHANGED
|
@@ -340,3 +340,21 @@ test('Labels tab: in effect at once, sent to the Client as its next config revis
|
|
|
340
340
|
const old = register(services, 'lab-old-30', { configFile: null });
|
|
341
341
|
assert.throws(() => services.registry.setClientLabels(old, ['x'], { by: 'x' }), /too old/);
|
|
342
342
|
});
|
|
343
|
+
|
|
344
|
+
test('runner card: capabilities have their own tab, next to Details', async (t) => {
|
|
345
|
+
const { base, services, admin } = await start(t),
|
|
346
|
+
{ resourceId } = services.registry.registerAuto({
|
|
347
|
+
clientId: 'hw8', name: 'lab-hw-08', type: 'hw', labels: [],
|
|
348
|
+
capabilities: { hw: { stlinks: [{ index: 1, path: '/dev/thub/dut1-stlink', present: false }], uarts: [], usbs: [] } }
|
|
349
|
+
}),
|
|
350
|
+
html = await (await fetch(`${base}/runners`, { headers: { cookie: admin } })).text(),
|
|
351
|
+
card = html.slice(html.indexOf(`id="resource-card-${resourceId}"`)),
|
|
352
|
+
details = card.slice(card.indexOf(`id="rc-${resourceId}-details"`), card.indexOf(`id="rc-${resourceId}-caps"`)),
|
|
353
|
+
caps = card.slice(card.indexOf(`id="rc-${resourceId}-caps"`)),
|
|
354
|
+
// The tab right after Details.
|
|
355
|
+
tabs = [...card.slice(0, card.indexOf('</ul>')).matchAll(/<button class="nav-link[^"]*" id="rc-[^"]*-(\w+)-tab"/g)].map((m) => m[1]);
|
|
356
|
+
assert.deepEqual(tabs.slice(0, 2), ['details', 'caps']);
|
|
357
|
+
assert.doesNotMatch(details, />Capabilities</); // no longer a Details row
|
|
358
|
+
assert.match(caps, new RegExp(`^id="rc-${resourceId}-caps"[^>]*data-live="rc-caps-${resourceId}"`)); // updates live
|
|
359
|
+
assert.match(caps, /\/dev\/thub\/dut1-stlink<\/code><span class="badge text-bg-danger[^"]*"[^>]*>missing/);
|
|
360
|
+
});
|
|
@@ -40,14 +40,14 @@
|
|
|
40
40
|
lsusb -t # USB tree
|
|
41
41
|
udevadm info -a -n /dev/ttyUSB0 | grep -m1 'ATTRS{devpath}'
|
|
42
42
|
st-info --probe # ST-Link serials
|
|
43
|
-
| Or open the runner card → #[strong
|
|
43
|
+
| Or open the runner card → #[strong USB devices] → #[strong Refresh], then #[strong Import to config].
|
|
44
44
|
li
|
|
45
45
|
| Put the devpaths into #[code hw-devices] and restart. On every start the Client writes
|
|
46
46
|
| #[code /etc/udev/rules.d/99-thub-<instance>.rules], so the devices appear as #[code /dev/thub/dut<N>-stlink|uart|usb]:
|
|
47
47
|
+code('HW machine').
|
|
48
48
|
sudo systemctl restart thub-client@client
|
|
49
49
|
ls -l /dev/thub/
|
|
50
|
-
li Check the runner card's #[strong Capabilities]: each configured device must be present (a missing one is flagged).
|
|
50
|
+
li Check the runner card's #[strong Capabilities] tab: each configured device must be present (a missing one is flagged).
|
|
51
51
|
li Your job's #[code --command] flashes the board. The Client never flashes by itself; it passes the ST-Link serials and the downloaded files in the environment (#[a(href="#env") Environment variables]).
|
|
52
52
|
+code('A typical HW job').
|
|
53
53
|
thub run --type hw --label board:nucleo-f401re \
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
SW Clients have no settings of their own: they run whatever DUT image a job brings (#[code --docker-image]).
|
|
104
104
|
Admins can also edit an HW Client's devices from its runner card (#[strong ST-Link | UART | DUT USB]
|
|
105
105
|
tabs). The tabs show the config the Client runs, as it reported it at its last start, or a saved
|
|
106
|
-
change it hasn't applied yet. #[strong
|
|
106
|
+
change it hasn't applied yet. #[strong USB devices → Import to config] fills them from the host's #[code lsusb].
|
|
107
107
|
The card's #[strong Export] and #[strong Import] icons download a Client's config file (never its
|
|
108
108
|
#[code joinKey]) and apply one to it. An older HW Client takes only the file's #[code hw-devices].
|
|
109
109
|
There's no ST-Link serial field: the job's #[code --command] picks the probe
|
package/views/help/_docker.pug
CHANGED
|
@@ -121,4 +121,4 @@
|
|
|
121
121
|
td Files written to #[code /work] stay owned by the Client user, so the workspace can be cleaned up.
|
|
122
122
|
+note('info').
|
|
123
123
|
Docker access is effectively root access on that host. The Client host must have Docker and the service user must be in the
|
|
124
|
-
#[code docker] group. The runner card's #[strong Capabilities]
|
|
124
|
+
#[code docker] group. The runner card's #[strong Capabilities] tab shows whether a Client can use Docker, and why not.
|
|
@@ -298,8 +298,9 @@ mixin resourceCard(r, groupsById, returnTo)
|
|
|
298
298
|
//- data-config-root: where public/js/client-config.js tracks unsaved
|
|
299
299
|
//- changes (⚠ on the config tabs).
|
|
300
300
|
.modal-body.pt-2(data-config-root)
|
|
301
|
-
//- Details: what the Client is and reports
|
|
302
|
-
//- (client-config.pug; read-only for
|
|
301
|
+
//- Details: what the Client is and reports; Capabilities: what it can
|
|
302
|
+
//- drive. Then its config tabs (client-config.pug; read-only for
|
|
303
|
+
//- viewers), then its USB devices.
|
|
303
304
|
ul.nav.nav-tabs.mb-3(role="tablist")
|
|
304
305
|
li.nav-item(role="presentation")
|
|
305
306
|
button.nav-link.active(
|
|
@@ -311,6 +312,16 @@ mixin resourceCard(r, groupsById, returnTo)
|
|
|
311
312
|
aria-controls=`rc-${r.id}-details`
|
|
312
313
|
aria-selected="true"
|
|
313
314
|
) Details
|
|
315
|
+
li.nav-item(role="presentation")
|
|
316
|
+
button.nav-link(
|
|
317
|
+
id=`rc-${r.id}-caps-tab`
|
|
318
|
+
type="button"
|
|
319
|
+
role="tab"
|
|
320
|
+
data-bs-toggle="tab"
|
|
321
|
+
data-bs-target=`#rc-${r.id}-caps`
|
|
322
|
+
aria-controls=`rc-${r.id}-caps`
|
|
323
|
+
aria-selected="false"
|
|
324
|
+
) Capabilities
|
|
314
325
|
+configTabItems(r)
|
|
315
326
|
li.nav-item(role="presentation")
|
|
316
327
|
button.nav-link(
|
|
@@ -321,16 +332,21 @@ mixin resourceCard(r, groupsById, returnTo)
|
|
|
321
332
|
data-bs-target=`#rc-${r.id}-usb`
|
|
322
333
|
aria-controls=`rc-${r.id}-usb`
|
|
323
334
|
aria-selected="false"
|
|
324
|
-
)
|
|
335
|
+
) USB devices
|
|
325
336
|
|
|
326
337
|
.tab-content
|
|
327
338
|
.tab-pane.fade.show.active(id=`rc-${r.id}-details` role="tabpanel" aria-labelledby=`rc-${r.id}-details-tab` tabindex="0" data-live=`rc-details-${r.id}`)
|
|
328
339
|
+resourceDetails(r, groupsById, returnTo, local, external)
|
|
340
|
+
//- Live too: a Client that restarts reports them again.
|
|
341
|
+
.tab-pane.fade(id=`rc-${r.id}-caps` role="tabpanel" aria-labelledby=`rc-${r.id}-caps-tab` tabindex="0" data-live=`rc-caps-${r.id}`)
|
|
342
|
+
p.small.text-body-secondary.mb-2 What the Client reported it can drive at its last start, checked against its host.
|
|
343
|
+
.resource-capabilities
|
|
344
|
+
+capabilitiesList(r.capabilities)
|
|
329
345
|
+configPanes(r, returnTo, groupsById)
|
|
330
346
|
.tab-pane.fade.thub-usb-pane(id=`rc-${r.id}-usb` role="tabpanel" aria-labelledby=`rc-${r.id}-usb-tab` tabindex="0")
|
|
331
347
|
+usbScanPane(r)
|
|
332
348
|
|
|
333
|
-
//- "
|
|
349
|
+
//- "USB devices" (resource card tab): the Client's `lsusb`, run
|
|
334
350
|
//- only on Refresh (admins; public/js/usb-scan.js fetches and fills it in).
|
|
335
351
|
mixin usbScanPane(r)
|
|
336
352
|
- const offline = r.status === 'OUT_OF_SERVICE' || !r.last_heartbeat_at
|
|
@@ -496,9 +512,6 @@ mixin resourceDetails(r, groupsById, returnTo, local, external)
|
|
|
496
512
|
data-bs-toggle="tooltip"
|
|
497
513
|
data-bs-title="The address the Coordinator last saw this Client connect from"
|
|
498
514
|
) external
|
|
499
|
-
dt.col-sm-4 Capabilities
|
|
500
|
-
dd.col-sm-8.resource-capabilities
|
|
501
|
-
+capabilitiesList(r.capabilities)
|
|
502
515
|
dt.col-sm-4 Labels
|
|
503
516
|
dd.col-sm-8
|
|
504
517
|
if r.labels.length
|