@andrian.yablonskyy/thub-coordinator 1.1.11 → 1.1.13
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 +2 -2
- package/public/css/thub.css +74 -2
- package/public/js/tag-picker.js +200 -0
- 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/dev/virtual.js +1 -3
- package/src/services/jobs.js +14 -1
- package/src/services/registry.js +1 -1
- package/src/web/routes.js +1 -1
- package/test/groups-tab.test.js +4 -2
- package/test/list-prefs.test.js +7 -4
- package/test/scheduler.test.js +6 -2
- package/test/search.test.js +2 -3
- package/test/users.test.js +14 -0
- package/views/admin/agents.pug +21 -17
- package/views/admin/users.pug +43 -40
- package/views/groups/list.pug +20 -16
- package/views/help/_agent-cli.pug +11 -15
- package/views/help/_agent-setup.pug +0 -4
- package/views/help/_ci.pug +3 -2
- package/views/help/_client-machines.pug +13 -12
- package/views/help/_client-setup.pug +5 -5
- package/views/help/_docker.pug +29 -92
- package/views/help/_env.pug +11 -19
- package/views/help/_git.pug +36 -123
- package/views/help/_overview.pug +2 -2
- package/views/help/_quick-start.pug +1 -1
- package/views/help/_troubleshooting.pug +3 -1
- package/views/help/index.pug +2 -2
- package/views/index.pug +1 -0
- package/views/mixins/client-config.pug +29 -13
- package/views/mixins/resource.pug +4 -4
- package/views/resources/list.pug +1 -0
package/views/help/_git.pug
CHANGED
|
@@ -1,130 +1,43 @@
|
|
|
1
|
-
+section('git', '
|
|
1
|
+
+section('git', 'Using git in a job', 'git')
|
|
2
2
|
p.
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
The Client doesn't clone anything itself and doesn't need git installed for its own sake. A job that needs a repository
|
|
4
|
+
clones it in #[code --command], into its work directory (#[code $THUB_WORK_DIR], where the command starts), with the
|
|
5
|
+
credentials passed as #[code --env]. #[code --env] values are masked everywhere and dropped when the job ends. Anything
|
|
6
|
+
else (the URL, the command, #[code --meta]) is stored with the job and visible to anyone who can read it, so never put a
|
|
7
|
+
token there.
|
|
6
8
|
|
|
7
|
-
h3.h6
|
|
8
|
-
+code('
|
|
9
|
-
|
|
10
|
-
mkdir -p ci
|
|
11
|
-
cat > ci/test.sh <<'EOF'
|
|
12
|
-
#!/bin/sh
|
|
13
|
-
set -eu
|
|
14
|
-
echo "job $THUB_JOB_ID, suite $THUB_SUITE, commit $THUB_GIT_COMMIT"
|
|
15
|
-
mkdir -p results
|
|
16
|
-
pytest --junitxml=results/junit.xml tests/ # results/*.xml is uploaded and summarized
|
|
17
|
-
EOF
|
|
18
|
-
chmod +x ci/test.sh
|
|
19
|
-
git add . && git commit -m "TestHub entry point"
|
|
20
|
-
git remote add origin git@github.com:yourorg/firmware-tests.git
|
|
21
|
-
git push -u origin main
|
|
22
|
-
p.small.
|
|
23
|
-
The exit code of #[code --command] is the verdict. JUnit files under #[code results/] or #[code artifacts/] are summed into
|
|
24
|
-
the job's test counts. Nothing is uploaded: the workspace is deleted when the job ends, so publish anything you need to keep
|
|
25
|
-
from the command itself (e.g. #[code curl -T report.html "$ARTIFACTORY/…"] with a token passed as #[code --env]).
|
|
26
|
-
|
|
27
|
-
h3.h6 2. Give Clients read access: SSH deploy key (recommended)
|
|
28
|
-
p.small.
|
|
29
|
-
git never prompts on a Client (#[code GIT_TERMINAL_PROMPT=0]), so credentials must already be on the Client host,
|
|
30
|
-
owned by the #[strong Client's service user]. Create a key on each Client host (or one shared key per lab):
|
|
31
|
-
+code('Client host, as the Client\'s user').
|
|
32
|
-
ssh-keygen -t ed25519 -N "" -C "thub-client@$(hostname)" -f ~/.ssh/thub_deploy
|
|
33
|
-
cat ~/.ssh/thub_deploy.pub # → add as a read-only deploy key (below)
|
|
34
|
-
# trust the git server's host key now: the service can't answer prompts
|
|
35
|
-
# (and can't write ~/.ssh under systemd)
|
|
36
|
-
ssh-keyscan github.com bitbucket.org gitlab.com >> ~/.ssh/known_hosts
|
|
37
|
-
# use the key for that server
|
|
38
|
-
cat >> ~/.ssh/config <<'EOF'
|
|
39
|
-
Host github.com
|
|
40
|
-
IdentityFile ~/.ssh/thub_deploy
|
|
41
|
-
IdentitiesOnly yes
|
|
42
|
-
EOF
|
|
43
|
-
chmod 600 ~/.ssh/config
|
|
44
|
-
ssh -T git@github.com # "…successfully authenticated…"
|
|
45
|
-
table.table.table-sm.small
|
|
46
|
-
thead
|
|
47
|
-
tr
|
|
48
|
-
th Git server
|
|
49
|
-
th Where to add the public key
|
|
50
|
-
tbody
|
|
51
|
-
tr
|
|
52
|
-
td GitHub
|
|
53
|
-
td Repository → Settings → Deploy keys → Add deploy key (leave “Allow write access” off). For many repositories, use a machine user's SSH key instead.
|
|
54
|
-
tr
|
|
55
|
-
td GitLab
|
|
56
|
-
td Project → Settings → Repository → Deploy keys.
|
|
57
|
-
tr
|
|
58
|
-
td Bitbucket
|
|
59
|
-
td Repository settings → Security → Access keys.
|
|
60
|
-
tr
|
|
61
|
-
td Gitea / self-hosted
|
|
62
|
-
td Repository → Settings → Deploy Keys, or the machine user's SSH keys.
|
|
63
|
-
p.small.
|
|
64
|
-
Without #[code ~/.ssh/config], pick the key per job with #[code --git-options] (stored with the job, so reference a key
|
|
65
|
-
#[em file], never inline a secret):
|
|
66
|
-
+code('Per-job SSH key / port').
|
|
9
|
+
h3.h6 HTTPS with a token (recommended)
|
|
10
|
+
+code('Token from your CI secret store').
|
|
11
|
+
export GH_TOKEN=… # GitHub: a fine-grained PAT with read access; GitLab: oauth2 token; Bitbucket: x-token-auth
|
|
67
12
|
thub run --type sw \
|
|
68
|
-
--
|
|
69
|
-
--
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
h3.h6 HTTPS with a token
|
|
73
|
-
p.small.
|
|
74
|
-
For #[code https://] repositories, either store a credential helper for the Client's user on the host
|
|
75
|
-
(#[code git config --global credential.helper store]), or pass the token #[strong per job] as a secret. #[code --env]
|
|
76
|
-
variables reach the Client's own git commands, and git reads config from #[code GIT_CONFIG_COUNT]/#[code _KEY_n]/#[code _VALUE_n]:
|
|
77
|
-
+code('Token per job, never stored').
|
|
78
|
-
# GitHub: user x-access-token; GitLab: oauth2; Bitbucket: x-token-auth
|
|
79
|
-
export GIT_CONFIG_VALUE_0="Authorization: Basic $(printf 'x-access-token:%s' "$GH_TOKEN" | base64 | tr -d '\n')"
|
|
80
|
-
thub run --type sw \
|
|
81
|
-
--git-repo https://github.com/yourorg/private-tests.git main \
|
|
82
|
-
--env GIT_CONFIG_COUNT=1,GIT_CONFIG_KEY_0=http.extraHeader --env GIT_CONFIG_VALUE_0 \
|
|
83
|
-
--command ./ci/test.sh --wait
|
|
13
|
+
--env GH_TOKEN \
|
|
14
|
+
--command 'git clone --depth 1 --branch main "https://x-access-token:$GH_TOKEN@github.com/yourorg/firmware-tests.git" . &&
|
|
15
|
+
./ci/test.sh' \
|
|
16
|
+
--wait
|
|
84
17
|
p.small.
|
|
85
|
-
|
|
86
|
-
|
|
18
|
+
The token appears only inside the command's environment. To keep it out of #[code .git/config] as well, pass it as a header:
|
|
19
|
+
#[code git -c http.extraHeader="Authorization: Bearer $GH_TOKEN" clone …].
|
|
87
20
|
|
|
88
|
-
h3.h6
|
|
89
|
-
+code('
|
|
90
|
-
|
|
91
|
-
--
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
tr
|
|
102
|
-
th Default ref
|
|
103
|
-
td The repository's default branch.
|
|
104
|
-
tr
|
|
105
|
-
th Depth
|
|
106
|
-
td Default #[code 1]. #[code 0] = full history. If the server can't do shallow fetches (or the commit is abbreviated), the Client falls back to a full fetch.
|
|
107
|
-
tr
|
|
108
|
-
th Exact commit
|
|
109
|
-
td Logged, and passed to the command as #[code $THUB_GIT_COMMIT]. The requested ref is #[code $JOB_GIT_BRANCH].
|
|
110
|
-
tr
|
|
111
|
-
th Extra git options
|
|
112
|
-
td #[code --git-options] is inserted between #[code git] and its subcommand on every git call, e.g. #[code '-c http.sslVerify=false'] or #[code '-c core.sshCommand="…"'].
|
|
113
|
-
+code('What the Client runs (see it with --dry-run)').
|
|
114
|
-
git -C <work> init -q
|
|
115
|
-
git -C <work> remote add origin <url>
|
|
116
|
-
git -C <work> fetch -q --depth 1 origin <ref>
|
|
117
|
-
git -C <work> checkout -q --detach FETCH_HEAD
|
|
118
|
-
git -C <work> rev-parse HEAD
|
|
21
|
+
h3.h6 SSH with a key from the job
|
|
22
|
+
+code('Private key as --env (base64), used for this job only').
|
|
23
|
+
export DEPLOY_KEY_B64="$(base64 < ~/.ssh/thub_deploy | tr -d '\n')"
|
|
24
|
+
thub run --type hw --label board:nucleo-f401re \
|
|
25
|
+
--env DEPLOY_KEY_B64 \
|
|
26
|
+
--command 'umask 077 && echo "$DEPLOY_KEY_B64" | base64 -d > "$THUB_WORK_DIR/../key" &&
|
|
27
|
+
GIT_SSH_COMMAND="ssh -i $THUB_WORK_DIR/../key -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new" \
|
|
28
|
+
git clone --depth 1 git@github.com:yourorg/firmware-tests.git . &&
|
|
29
|
+
./ci/test.sh' \
|
|
30
|
+
--wait
|
|
31
|
+
p.small.
|
|
32
|
+
The job directory, key included, is deleted when the job ends. Add the public key as a read-only deploy key: GitHub,
|
|
33
|
+
Repository → Settings → Deploy keys; GitLab, Settings → Repository → Deploy keys; Bitbucket, Repository settings → Access keys.
|
|
119
34
|
|
|
120
|
-
h3.h6
|
|
35
|
+
h3.h6 Branch, tag, commit, submodules, LFS
|
|
36
|
+
+code('All in the command').
|
|
37
|
+
git clone --depth 1 --branch v1.4.0 "$REPO" . # a branch or tag
|
|
38
|
+
git init -q . && git remote add origin "$REPO" && git fetch -q --depth 1 origin a1b2c3d && git checkout -q FETCH_HEAD # a commit
|
|
39
|
+
git submodule update --init --recursive --depth 1 && git lfs pull # submodules / LFS
|
|
121
40
|
p.small.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
--command 'git submodule update --init --recursive --depth 1 && git lfs pull && ./ci/test.sh' --wait
|
|
126
|
-
+code('Clone yourself, e.g. inside a container (JOB_GIT_* carry the --git-repo values)').
|
|
127
|
-
thub run --type sw --git-repo git@github.com:yourorg/tests.git main --depth 1 \
|
|
128
|
-
--command 'docker run --rm -v "$HOME/.ssh:/root/.ssh:ro" -e JOB_GIT_REPO_URL -e JOB_GIT_BRANCH -e JOB_GIT_DEPTH \
|
|
129
|
-
--entrypoint sh alpine/git -c "git clone --depth \$JOB_GIT_DEPTH --branch \$JOB_GIT_BRANCH \$JOB_GIT_REPO_URL /src && ls /src"' \
|
|
130
|
-
--wait
|
|
41
|
+
Pass the repository and ref as #[code --env REPO=…,REF=…] (or #[code --meta]) so the same command works for every branch.
|
|
42
|
+
The exit code of #[code --command] is the verdict; JUnit files under #[code results/] or #[code artifacts/] are summed into
|
|
43
|
+
the job's test counts. Nothing is uploaded: publish what you need to keep from the command (#[code curl -T …] with a token in #[code --env]).
|
package/views/help/_overview.pug
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
ol.small
|
|
40
40
|
li #[code thub run …] submits a job spec (type, labels, command, inputs) with your access key (or a pipeline's CI token).
|
|
41
41
|
li The Coordinator queues it (#[code QUEUED]) and the scheduler assigns it to an idle Client whose type, labels and group match.
|
|
42
|
-
li The Client accepts it,
|
|
42
|
+
li The Client accepts it, downloads #[code --download-file] files and prepares the DUT (#[code PREPARING]).
|
|
43
43
|
li It runs #[code --command] with #[code sh -c], and output streams live to the Agent and the dashboard (#[code RUNNING]).
|
|
44
44
|
li The exit code is the verdict: #[code 0] = #[code PASSED], anything else = #[code FAILED]. The Client reports the JUnit test counts; the job's files stay on the Client and are deleted with its workspace.
|
|
45
45
|
li The Agent exits with the verdict code, so a CI step passes or fails with it.
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
-
|
|
103
103
|
const useCases = [
|
|
104
104
|
{ icon: 'cpu', title: 'Hardware-in-the-loop CI', text: 'Every push builds firmware in the cloud, then a GitHub Actions test job runs `thub run --type hw --wait`: a lab Client flashes a real board through ST-Link, captures its UART, runs the tests and fails the pipeline on a non-zero exit.' },
|
|
105
|
-
{ icon: 'pc-display', title: 'Emulator (SW) test farms', text: 'SW Clients on real or virtual Linux machines run each job’s
|
|
105
|
+
{ icon: 'pc-display', title: 'Emulator (SW) test farms', text: 'SW Clients on real or virtual Linux machines run each job’s command, which starts its emulator (QEMU, Renode, a simulator) — in Docker if it likes — and tests it. Scale out by adding VMs.' },
|
|
106
106
|
{ icon: 'person-workspace', title: 'Remote access for developers', text: 'Developers reproduce a CI failure on the exact bench it happened on (`--client lab-hw-01`) from home, with the same command CI ran, and stream the board’s console live.' },
|
|
107
107
|
{ icon: 'lock', title: 'Shared benches without collisions', text: 'The scheduler hands out one job per bench. An engineer who needs a board by hand runs `thub-client lock` so nothing is scheduled there until `unlock`.' },
|
|
108
108
|
{ icon: 'collection', title: 'Dedicated pools', text: 'Groups and labels (`board:nucleo-f401re`, `uart`) route jobs to the right hardware, e.g. a nightly pool separate from the PR pool.' },
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
strong Install a Client
|
|
15
15
|
| on each lab machine, with the same join key (#[a(href="#client-setup") Client setup]):
|
|
16
16
|
+code('Lab machine (Ubuntu 26.04)').
|
|
17
|
-
sudo apt install -y nodejs npm
|
|
17
|
+
sudo apt install -y nodejs npm # + your jobs' tools: stlink-tools openocd, git, docker.io…
|
|
18
18
|
sudo npm i -g @andrian.yablonskyy/thub-client
|
|
19
19
|
nano ~/.config/thub/client.json # coordinatorUrl, type, joinKey
|
|
20
20
|
sudo systemctl enable --now thub-client@client
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
['Name already in use (`409`)', 'Another live Client holds that name. Rename one of them in its config or from the runner card. A name held by an `OUT_OF_SERVICE` runner is reclaimed automatically.'],
|
|
9
9
|
['The job is rejected with `422`', 'No registered Client can ever match its type, labels, group or client. Compare `thub resources` with your `--type` and `--label`s; your group (set on the dashboard, see `thub whoami`) must have a Client of that kind.'],
|
|
10
10
|
['The job stays `QUEUED`', 'Matching Clients are all `BUSY`, locked (`local`), in `MAINTENANCE` or offline, or the job is pinned with `--client`. It times out after `--timeout`.'],
|
|
11
|
-
['`ERROR` during prepare', '
|
|
11
|
+
['`ERROR` during prepare', 'A download failed; the job log names the URL and the reason.'],
|
|
12
|
+
['A git clone or docker pull in the command fails', 'They run in your `--command`, with the credentials you pass as `--env` (see Using git / Using Docker). `git` or `docker` must be installed on that Client host; the Client itself needs neither. Try the command with `--dry-run` to see its environment.'],
|
|
13
|
+
['`--git-repo` / `--docker-image`: unknown option, or the job is refused', 'They were removed: clone the repository and run containers in `--command`, passing tokens with `--env`. An older Agent that still sends them is refused with that message; update it (`thub self-update`).'],
|
|
12
14
|
['Permission denied on `/dev/ttyUSB*` or `docker.sock`', 'Docker or the device groups were added after the Client was installed. Re-run `sudo npm i -g @andrian.yablonskyy/thub-client` so the unit gets the `dialout`/`plugdev`/`docker` groups, then restart.'],
|
|
13
15
|
['A configured device is shown as missing', 'Check the `devpath` with `udevadm info -a -n <dev>`, then `thub-client udev --print`; restart the Client to regenerate the udev rules.'],
|
|
14
16
|
['Job `LOST`', 'The Client missed 3 heartbeats (network, reboot, crash). With `requeueOnLost` the job is retried once on another Client.'],
|
package/views/help/index.pug
CHANGED
|
@@ -11,8 +11,8 @@ block content
|
|
|
11
11
|
{ id: 'agent-setup', label: 'Agent setup' },
|
|
12
12
|
{ id: 'client-setup', label: 'Client setup' },
|
|
13
13
|
{ id: 'client-machines', label: 'Client machines (HW / SW)' },
|
|
14
|
-
{ id: 'docker', label: 'Docker
|
|
15
|
-
{ id: 'git', label: '
|
|
14
|
+
{ id: 'docker', label: 'Using Docker' },
|
|
15
|
+
{ id: 'git', label: 'Using git' },
|
|
16
16
|
{ id: 'agent-cli', label: 'Agent CLI reference' },
|
|
17
17
|
{ id: 'env', label: 'Environment variables' },
|
|
18
18
|
{ id: 'ci', label: 'CI/CD (GitHub Actions)' },
|
package/views/index.pug
CHANGED
|
@@ -49,6 +49,7 @@ block scripts
|
|
|
49
49
|
script(src="/js/resource-card.js")
|
|
50
50
|
script(src="/js/remove-resource.js")
|
|
51
51
|
script(src="/js/client-config.js")
|
|
52
|
+
script(src="/js/tag-picker.js")
|
|
52
53
|
script(src="/js/usb-scan.js")
|
|
53
54
|
script(src="/js/usb-parse.js")
|
|
54
55
|
script(src="/js/usb-import.js")
|
|
@@ -183,19 +183,35 @@ mixin groupsPane(r, returnTo, groupsById)
|
|
|
183
183
|
data-confirm-tone="primary"
|
|
184
184
|
)
|
|
185
185
|
input(type="hidden" name="returnTo" value=returnTo)
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
186
|
+
- const editable = canEdit && r.config_support === 'file'
|
|
187
|
+
fieldset(disabled=!editable)
|
|
188
|
+
//- Chips for its groups, a search box to add more (public/js/tag-picker.js):
|
|
189
|
+
//- each chip carries its hidden `groups` input; ✕ removes it.
|
|
190
|
+
.thub-tags.mb-2(data-tag-picker data-tag-name="groups" data-options=JSON.stringify(all.map((g) => ({ value: g.id, label: g.name, hint: g.comment || '' }))))
|
|
191
|
+
.thub-tags-box.form-control(data-tag-box)
|
|
192
|
+
each id in r.group_ids
|
|
193
|
+
span.thub-tag(data-tag=id)
|
|
194
|
+
span= groupsById[id] ? groupsById[id].name : '(a deleted group)'
|
|
195
|
+
input(type="hidden" name="groups" value=id)
|
|
196
|
+
if editable
|
|
197
|
+
button.thub-tag-remove(type="button" data-tag-remove aria-label=`Remove ${groupsById[id] ? groupsById[id].name : 'deleted group'}`)
|
|
198
|
+
i.bi.bi-x
|
|
199
|
+
if editable
|
|
200
|
+
input.thub-tags-input(
|
|
201
|
+
type="text"
|
|
202
|
+
data-tag-input
|
|
203
|
+
placeholder="Add a group…"
|
|
204
|
+
aria-label=`Add a group to ${r.name}`
|
|
205
|
+
role="combobox"
|
|
206
|
+
aria-autocomplete="list"
|
|
207
|
+
aria-expanded="false"
|
|
208
|
+
aria-controls=`${p}-groups-menu`
|
|
209
|
+
autocomplete="off"
|
|
210
|
+
spellcheck="false"
|
|
211
|
+
)
|
|
212
|
+
ul.dropdown-menu.thub-tags-menu(id=`${p}-groups-menu` role="listbox" data-tag-menu)
|
|
213
|
+
if !r.group_ids.length && !editable
|
|
214
|
+
span.small.text-body-secondary none
|
|
199
215
|
.form-text An agent given a group (on Users or CI tokens) runs its jobs only on Clients in it; an agent without one, on any Client.
|
|
200
216
|
if canEdit && r.config_support === 'file'
|
|
201
217
|
.d-flex.justify-content-end.mt-3
|
|
@@ -135,12 +135,12 @@ mixin capDevice(d, detail)
|
|
|
135
135
|
) missing
|
|
136
136
|
|
|
137
137
|
//- What the Client reported it can drive at registration (HW: udev
|
|
138
|
-
//- devices; SW: nothing of its own —
|
|
138
|
+
//- devices; SW: nothing of its own — a job's command brings what it runs).
|
|
139
139
|
mixin capabilitiesList(caps)
|
|
140
140
|
if !caps
|
|
141
141
|
span.text-body-secondary —
|
|
142
142
|
else if caps.sw
|
|
143
|
-
span.text-body-secondary Runs each job's
|
|
143
|
+
span.text-body-secondary Runs each job's command — nothing of its own to drive
|
|
144
144
|
else if caps.hw
|
|
145
145
|
- const kinds = [['stlinks', 'ST-Link'], ['uarts', 'UART'], ['usbs', 'USB']]
|
|
146
146
|
- const empty = kinds.every(([k]) => !(caps.hw[k] || []).length)
|
|
@@ -332,7 +332,7 @@ mixin resourceCard(r, groupsById, returnTo)
|
|
|
332
332
|
data-bs-target=`#rc-${r.id}-usb`
|
|
333
333
|
aria-controls=`rc-${r.id}-usb`
|
|
334
334
|
aria-selected="false"
|
|
335
|
-
)
|
|
335
|
+
) USB devices
|
|
336
336
|
|
|
337
337
|
.tab-content
|
|
338
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}`)
|
|
@@ -346,7 +346,7 @@ mixin resourceCard(r, groupsById, returnTo)
|
|
|
346
346
|
.tab-pane.fade.thub-usb-pane(id=`rc-${r.id}-usb` role="tabpanel" aria-labelledby=`rc-${r.id}-usb-tab` tabindex="0")
|
|
347
347
|
+usbScanPane(r)
|
|
348
348
|
|
|
349
|
-
//- "
|
|
349
|
+
//- "USB devices" (resource card tab): the Client's `lsusb`, run
|
|
350
350
|
//- only on Refresh (admins; public/js/usb-scan.js fetches and fills it in).
|
|
351
351
|
mixin usbScanPane(r)
|
|
352
352
|
- const offline = r.status === 'OUT_OF_SERVICE' || !r.last_heartbeat_at
|
package/views/resources/list.pug
CHANGED
|
@@ -67,6 +67,7 @@ block scripts
|
|
|
67
67
|
script(src="/js/resource-card.js")
|
|
68
68
|
script(src="/js/remove-resource.js")
|
|
69
69
|
script(src="/js/client-config.js")
|
|
70
|
+
script(src="/js/tag-picker.js")
|
|
70
71
|
script(src="/js/usb-scan.js")
|
|
71
72
|
script(src="/js/usb-parse.js")
|
|
72
73
|
script(src="/js/usb-import.js")
|