@andrian.yablonskyy/thub-coordinator 1.0.39 → 1.0.40
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/bin/thub-admin.js +23 -0
- package/config.json +4 -0
- package/package.json +2 -2
- package/public/js/actions.js +58 -0
- package/public/js/settings.js +66 -0
- package/src/api/agent.js +27 -15
- package/src/api/resource.js +5 -3
- package/src/config.js +17 -1
- package/src/db/migrations/024_add_settings.sql +10 -0
- package/src/db/migrations/025_add_job_artifacts.sql +4 -0
- package/src/dev/virtual.js +9 -5
- package/src/login-guard.js +111 -0
- package/src/rate-limit.js +137 -0
- package/src/security.js +104 -0
- package/src/server.js +17 -3
- package/src/services/admin-users.js +32 -19
- package/src/services/heartbeat.js +23 -3
- package/src/services/job-artifacts.js +91 -0
- package/src/services/jobs.js +13 -2
- package/src/services/registry.js +34 -0
- package/src/services/settings.js +345 -0
- package/src/web/routes.js +157 -10
- package/test/admin-users.test.js +16 -16
- package/test/agent-visibility.test.js +85 -0
- package/test/groups-tab.test.js +97 -0
- package/test/job-artifacts.test.js +115 -0
- package/test/login-guard.test.js +139 -0
- package/test/logs.test.js +1 -1
- package/test/rate-limit.test.js +106 -0
- package/test/security.test.js +107 -0
- package/test/settings.test.js +206 -0
- package/views/admin/settings.pug +133 -0
- package/views/groups/list.pug +1 -1
- package/views/help/_agent-cli.pug +1 -1
- package/views/help/_agent-setup.pug +4 -1
- package/views/help/_client-setup.pug +1 -1
- package/views/help/_coordinator.pug +9 -1
- package/views/help/_env.pug +1 -0
- package/views/help/_troubleshooting.pug +3 -1
- package/views/jobs/list.pug +3 -3
- package/views/jobs/show.pug +36 -0
- package/views/layout.pug +4 -1
- package/views/mixins/client-config.pug +65 -3
- package/views/mixins/list-controls.pug +1 -1
- package/views/mixins/resource.pug +1 -8
package/views/layout.pug
CHANGED
|
@@ -43,6 +43,8 @@ html(lang="en" data-bs-theme=(user && user.theme) || "auto")
|
|
|
43
43
|
if user.role === 'admin'
|
|
44
44
|
li.nav-item
|
|
45
45
|
a.nav-link(href="/admin/agents" class=active === 'agents' ? 'active' : '') Agents
|
|
46
|
+
li.nav-item
|
|
47
|
+
a.nav-link(href="/admin/settings" class=active === 'settings' ? 'active' : '') Settings
|
|
46
48
|
ul.navbar-nav
|
|
47
49
|
if user && liveTopics
|
|
48
50
|
//- Live-update status, driven by public/js/live.js.
|
|
@@ -64,7 +66,7 @@ html(lang="en" data-bs-theme=(user && user.theme) || "auto")
|
|
|
64
66
|
| Help
|
|
65
67
|
if user && user.role === 'admin'
|
|
66
68
|
li.nav-item.d-flex.align-items-center.me-2
|
|
67
|
-
form(method="post" action="/updates/check"
|
|
69
|
+
form(method="post" action="/updates/check" data-submit-busy)
|
|
68
70
|
input(type="hidden" name="returnTo" value=currentPath)
|
|
69
71
|
button.btn.btn-sm.btn-outline-secondary(
|
|
70
72
|
type="submit"
|
|
@@ -192,6 +194,7 @@ html(lang="en" data-bs-theme=(user && user.theme) || "auto")
|
|
|
192
194
|
script(src="/js/copy-to-clipboard.js")
|
|
193
195
|
script(src="/js/confirm.js")
|
|
194
196
|
script(src="/js/list-search.js")
|
|
197
|
+
script(src="/js/actions.js")
|
|
195
198
|
if user && liveTopics
|
|
196
199
|
script(src="/js/live.js")
|
|
197
200
|
if user && user.role === 'admin' && updates.coordinatorPending
|
|
@@ -88,8 +88,8 @@ mixin capabilitiesStatus(r)
|
|
|
88
88
|
span.badge.text-bg-danger(data-bs-toggle="tooltip" data-bs-title=r.config_error) refused by the Client
|
|
89
89
|
|
|
90
90
|
//- The Client's config, as tabs of the resource card next to Details:
|
|
91
|
-
//- HW — ST-Link, UART, DUT USB (its hw-devices section); both —
|
|
92
|
-
//- schedule, its own Save). An SW Client has no settings of its own. The
|
|
91
|
+
//- HW — ST-Link, UART, DUT USB (its hw-devices section); both — Groups
|
|
92
|
+
//- (membership, its own Save) and Reboot (the schedule, its own Save). An SW Client has no settings of its own. The
|
|
93
93
|
//- device tabs share one form and one "Save capabilities"; a tab with
|
|
94
94
|
//- unsaved changes shows ⚠ (public/js/client-config.js).
|
|
95
95
|
mixin configTabItems(r)
|
|
@@ -97,9 +97,70 @@ mixin configTabItems(r)
|
|
|
97
97
|
if r.type === 'hw'
|
|
98
98
|
each t in [['stlinks', 'ST-Link'], ['uarts', 'UART'], ['usbs', 'DUT USB']]
|
|
99
99
|
+configTab(p, t[0], t[1])
|
|
100
|
+
+configTab(p, 'groups', 'Groups')
|
|
100
101
|
+configTab(p, 'reboot', 'Reboot')
|
|
101
102
|
|
|
102
|
-
|
|
103
|
+
//- Groups tab: which resource groups this Client belongs to (thub run
|
|
104
|
+
//- --group). Saved as a config revision the Client writes to its config
|
|
105
|
+
//- file's `groups` (registry.setClientGroups); in effect on the Coordinator
|
|
106
|
+
//- at once.
|
|
107
|
+
mixin groupsPane(r, returnTo, groupsById)
|
|
108
|
+
- const p = `cfg-${r.id}`
|
|
109
|
+
- const canEdit = user.role === 'admin'
|
|
110
|
+
- const pending = r.config_import && r.config_import.revision > r.config_applied_revision && r.config_import.fields && r.config_import.fields.groups
|
|
111
|
+
- const all = Object.values(groupsById).sort((a, b) => a.name.localeCompare(b.name))
|
|
112
|
+
- const unknown = r.group_ids.filter((id) => !groupsById[id])
|
|
113
|
+
+configPane(p, 'groups')
|
|
114
|
+
if pending || r.config_error
|
|
115
|
+
.d-flex.justify-content-end.gap-1.mb-2
|
|
116
|
+
if pending
|
|
117
|
+
span.badge.text-bg-warning(
|
|
118
|
+
data-bs-toggle="tooltip"
|
|
119
|
+
data-bs-title="In effect on the Coordinator; the Client writes it to its config file on its next heartbeat, then restarts once idle"
|
|
120
|
+
) not written to the Client yet
|
|
121
|
+
if r.config_error
|
|
122
|
+
span.badge.text-bg-danger(data-bs-toggle="tooltip" data-bs-title=r.config_error) refused by the Client
|
|
123
|
+
if !r.client_config_file
|
|
124
|
+
p.small.text-body-secondary This Client is too old to take its groups from the dashboard — update it, or set #[code groups] in its config file.
|
|
125
|
+
if !all.length && !unknown.length
|
|
126
|
+
p.small.text-body-secondary.mb-0
|
|
127
|
+
| No groups exist yet.
|
|
128
|
+
if canEdit
|
|
129
|
+
| Create one on #[a(href="/groups") Groups].
|
|
130
|
+
else
|
|
131
|
+
form(
|
|
132
|
+
method="post"
|
|
133
|
+
action=`/resources/${r.id}/groups`
|
|
134
|
+
data-confirm=`Save the groups of ${r.name}? They apply at once; the Client also writes them to its config file and restarts once it has no job running.`
|
|
135
|
+
data-confirm-title="Save Client groups?"
|
|
136
|
+
data-confirm-ok="Save"
|
|
137
|
+
data-confirm-tone="primary"
|
|
138
|
+
)
|
|
139
|
+
input(type="hidden" name="returnTo" value=returnTo)
|
|
140
|
+
fieldset(disabled=!canEdit || !r.client_config_file)
|
|
141
|
+
.list-group.mb-2
|
|
142
|
+
each g in all
|
|
143
|
+
label.list-group-item.d-flex.gap-2.align-items-start
|
|
144
|
+
input.form-check-input.flex-shrink-0.mt-1(type="checkbox" name="groups" value=g.id checked=r.group_ids.includes(g.id))
|
|
145
|
+
span
|
|
146
|
+
span.fw-medium= g.name
|
|
147
|
+
if g.comment
|
|
148
|
+
span.small.text-body-secondary= ` — ${g.comment}`
|
|
149
|
+
br
|
|
150
|
+
code.small.text-body-secondary= g.id
|
|
151
|
+
each id in unknown
|
|
152
|
+
label.list-group-item.d-flex.gap-2.align-items-start
|
|
153
|
+
input.form-check-input.flex-shrink-0.mt-1(type="checkbox" name="groups" value=id checked)
|
|
154
|
+
span
|
|
155
|
+
span.text-body-secondary (deleted group)
|
|
156
|
+
br
|
|
157
|
+
code.small.text-body-secondary= id
|
|
158
|
+
.form-text A job run with #[code thub run --group <id>] is scheduled only on Clients in that group; one that asks for no group can run on any Client.
|
|
159
|
+
if canEdit && r.client_config_file
|
|
160
|
+
.d-flex.justify-content-end.mt-3
|
|
161
|
+
button.btn.btn-sm.btn-primary(type="submit") Save groups
|
|
162
|
+
|
|
163
|
+
mixin configPanes(r, returnTo, groupsById)
|
|
103
164
|
- const p = `cfg-${r.id}`
|
|
104
165
|
- const current = r.config_desired || r.client_config
|
|
105
166
|
- const canEdit = user.role === 'admin'
|
|
@@ -137,6 +198,7 @@ mixin configPanes(r, returnTo)
|
|
|
137
198
|
+deviceTable('usbs', 'DUT USB devices', current.usbs || [], ['Device', 'udev devpath'])
|
|
138
199
|
.form-text Device: a udev index 1–8 (→ /dev/thub/dut<N>-usb) or a /dev path. udev devpath: the USB port it's plugged into.
|
|
139
200
|
+saveCapabilities(canEdit)
|
|
201
|
+
+groupsPane(r, returnTo, groupsById || {})
|
|
140
202
|
+configPane(p, 'reboot')
|
|
141
203
|
dl.row.mb-0
|
|
142
204
|
+rebootScheduleRow(r, returnTo)
|
|
@@ -11,7 +11,7 @@ mixin pageSizePicker(list)
|
|
|
11
11
|
label.small.text-body-secondary.mb-0.text-nowrap(for="list-page-size") Show
|
|
12
12
|
select.form-select.form-select-sm.w-auto#list-page-size(
|
|
13
13
|
aria-label="Records per page"
|
|
14
|
-
|
|
14
|
+
data-navigate
|
|
15
15
|
)
|
|
16
16
|
each s in list.pageSizes
|
|
17
17
|
option(value=list.urlFor({ size: s }) selected=list.prefs.size === s)= s === 'all' ? 'All' : s
|
|
@@ -324,7 +324,7 @@ mixin resourceCard(r, groupsById, returnTo)
|
|
|
324
324
|
.tab-content
|
|
325
325
|
.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}`)
|
|
326
326
|
+resourceDetails(r, groupsById, returnTo, local, external)
|
|
327
|
-
+configPanes(r, returnTo)
|
|
327
|
+
+configPanes(r, returnTo, groupsById)
|
|
328
328
|
.tab-pane.fade(id=`rc-${r.id}-usb` role="tabpanel" aria-labelledby=`rc-${r.id}-usb-tab` tabindex="0")
|
|
329
329
|
+usbScanPane(r)
|
|
330
330
|
|
|
@@ -509,13 +509,6 @@ mixin resourceDetails(r, groupsById, returnTo, local, external)
|
|
|
509
509
|
span.badge.text-bg-light.border.me-1= l
|
|
510
510
|
else
|
|
511
511
|
span.text-body-secondary —
|
|
512
|
-
dt.col-sm-4 Groups
|
|
513
|
-
dd.col-sm-8
|
|
514
|
-
if r.group_ids.length
|
|
515
|
-
each gid in r.group_ids
|
|
516
|
-
span.badge.text-bg-info.me-1(data-bs-toggle="tooltip" data-bs-title=gid)= groupsById[gid] ? groupsById[gid].name : `(unknown: ${gid})`
|
|
517
|
-
else
|
|
518
|
-
span.text-body-secondary —
|
|
519
512
|
dt.col-sm-4 Busy source / reason
|
|
520
513
|
dd.col-sm-8= r.busy_source ? `${r.busy_source}${r.busy_reason ? ' — ' + r.busy_reason : ''}` : '—'
|
|
521
514
|
dt.col-sm-4 Last heartbeat
|