@andrian.yablonskyy/thub-coordinator 1.0.22 → 1.0.23
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/js/confirm.js +69 -0
- package/views/admin/agents.pug +7 -1
- package/views/groups/list.pug +7 -1
- package/views/jobs/list.pug +4 -2
- package/views/jobs/show.pug +8 -1
- package/views/layout.pug +19 -1
- package/views/mixins/resource.pug +13 -3
- package/views/mixins/updates.pug +4 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andrian.yablonskyy/thub-coordinator",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.23",
|
|
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": {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file packages/coordinator/public/js/confirm.js
|
|
3
|
+
* @description Dashboard: confirmation modal for forms marked data-confirm (replaces the browser's confirm())
|
|
4
|
+
*
|
|
5
|
+
* @author Andrian Yablonskyy
|
|
6
|
+
* @copyright Copyright (c) 2026 Andrian Yablonskyy. All rights reserved.
|
|
7
|
+
*
|
|
8
|
+
* This file is part of TestHub and is proprietary and confidential.
|
|
9
|
+
* Unauthorized copying, modification, distribution, or use of this file,
|
|
10
|
+
* via any medium, is strictly prohibited without prior written permission
|
|
11
|
+
* from AdSystem.PRO.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
16
|
+
// A form with data-confirm="<message>" asks first, in the shared
|
|
17
|
+
// #thub-confirm-modal (layout.pug), and is only submitted on confirm.
|
|
18
|
+
// Optional: data-confirm-title, data-confirm-ok (confirm button label),
|
|
19
|
+
// data-confirm-tone (Bootstrap color: danger — the default —, warning,
|
|
20
|
+
// primary) and data-confirm-dismiss (the other button, default "Cancel").
|
|
21
|
+
// Texts are set as plain text, never HTML — they carry resource/group names.
|
|
22
|
+
(function (){
|
|
23
|
+
const modalEl = document.getElementById('thub-confirm-modal');
|
|
24
|
+
if (!modalEl){
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const modal = bootstrap.Modal.getOrCreateInstance(modalEl),
|
|
28
|
+
field = (name) => modalEl.querySelector(`[data-field="${name}"]`),
|
|
29
|
+
TONES = ['danger', 'warning', 'primary', 'secondary', 'success', 'info'];
|
|
30
|
+
let pending = null; // { form, submitter } awaiting an answer
|
|
31
|
+
|
|
32
|
+
document.addEventListener('submit', (e) => {
|
|
33
|
+
const form = e.target;
|
|
34
|
+
if (!(form instanceof HTMLFormElement) || !form.dataset.confirm){
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (form.dataset.confirmed === '1'){
|
|
38
|
+
delete form.dataset.confirmed; // one confirmation per submit
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
e.preventDefault();
|
|
42
|
+
const tone = TONES.includes(form.dataset.confirmTone) ? form.dataset.confirmTone : 'danger';
|
|
43
|
+
pending = { form, submitter: e.submitter };
|
|
44
|
+
field('title').textContent = form.dataset.confirmTitle || 'Are you sure?';
|
|
45
|
+
field('message').textContent = form.dataset.confirm;
|
|
46
|
+
field('header').className = `modal-header text-bg-${tone}`;
|
|
47
|
+
field('ok').className = `btn btn-${tone}`;
|
|
48
|
+
field('ok').textContent = form.dataset.confirmOk || 'Confirm';
|
|
49
|
+
field('dismiss').textContent = form.dataset.confirmDismiss || 'Cancel';
|
|
50
|
+
modal.show();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
field('ok').addEventListener('click', () => {
|
|
54
|
+
if (!pending){
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const { form, submitter } = pending;
|
|
58
|
+
pending = null;
|
|
59
|
+
form.dataset.confirmed = '1';
|
|
60
|
+
modal.hide();
|
|
61
|
+
// requestSubmit (not submit) so the form's own submit handlers still run.
|
|
62
|
+
form.requestSubmit(submitter && form.contains(submitter) ? submitter : undefined);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
modalEl.addEventListener('shown.bs.modal', () => field('ok').focus());
|
|
66
|
+
modalEl.addEventListener('hidden.bs.modal', () => {
|
|
67
|
+
pending = null;
|
|
68
|
+
});
|
|
69
|
+
})();
|
package/views/admin/agents.pug
CHANGED
|
@@ -37,7 +37,13 @@ block content
|
|
|
37
37
|
if !a.revoked_at
|
|
38
38
|
.d-flex.justify-content-end
|
|
39
39
|
+updateButton(`/admin/agents/${a.id}/update`, a.update_to, '/admin/agents')
|
|
40
|
-
form.ms-1(
|
|
40
|
+
form.ms-1(
|
|
41
|
+
method="post"
|
|
42
|
+
action=`/admin/agents/${a.id}/revoke`
|
|
43
|
+
data-confirm=`Revoke the token of agent "${a.name}"? Everything using it — a CI pipeline, a developer's thub — gets 401 from then on. This cannot be undone.`
|
|
44
|
+
data-confirm-title="Revoke agent token?"
|
|
45
|
+
data-confirm-ok="Revoke"
|
|
46
|
+
)
|
|
41
47
|
button.btn.btn-sm.btn-outline-danger(type="submit") Revoke
|
|
42
48
|
|
|
43
49
|
.modal(tabindex="-1" id="addAgentModal")
|
package/views/groups/list.pug
CHANGED
|
@@ -41,7 +41,13 @@ block content
|
|
|
41
41
|
if canManage
|
|
42
42
|
td
|
|
43
43
|
button.btn.btn-sm.btn-outline-secondary(type="button" data-bs-toggle="modal" data-bs-target=`#editGroupModal-${g.id}`) Edit
|
|
44
|
-
form.d-inline.ms-1(
|
|
44
|
+
form.d-inline.ms-1(
|
|
45
|
+
method="post"
|
|
46
|
+
action=`/groups/${g.id}/delete`
|
|
47
|
+
data-confirm=`Delete group "${g.name}"? Resources listing it just stop matching on it, and jobs targeting it (--group) can no longer be scheduled.`
|
|
48
|
+
data-confirm-title="Delete group?"
|
|
49
|
+
data-confirm-ok="Delete group"
|
|
50
|
+
)
|
|
45
51
|
button.btn.btn-sm.btn-outline-danger(type="submit") Delete
|
|
46
52
|
|
|
47
53
|
.modal(tabindex="-1" id=`editGroupModal-${g.id}`)
|
package/views/jobs/list.pug
CHANGED
|
@@ -55,8 +55,10 @@ block content
|
|
|
55
55
|
form.d-inline(
|
|
56
56
|
method="post"
|
|
57
57
|
action=`/jobs/${j.id}/cancel`
|
|
58
|
-
data-confirm=`Cancel job ${j.id}
|
|
59
|
-
|
|
58
|
+
data-confirm=`Cancel job ${j.id}? It ends as CANCELED; if it's running, its Client stops it.`
|
|
59
|
+
data-confirm-title="Cancel job?"
|
|
60
|
+
data-confirm-ok="Cancel job"
|
|
61
|
+
data-confirm-dismiss="Keep it"
|
|
60
62
|
)
|
|
61
63
|
input(type="hidden" name="returnTo" value=returnTo)
|
|
62
64
|
button.btn.btn-sm.btn-outline-danger(type="submit" aria-label="Cancel" data-bs-toggle="tooltip" data-bs-title="Cancel this job")
|
package/views/jobs/show.pug
CHANGED
|
@@ -9,7 +9,14 @@ block content
|
|
|
9
9
|
|
|
|
10
10
|
+jobBadge(job.state)(class="thub-heading-badge ms-1")
|
|
11
11
|
if canCancel
|
|
12
|
-
form(
|
|
12
|
+
form(
|
|
13
|
+
method="post"
|
|
14
|
+
action=`/jobs/${job.id}/cancel`
|
|
15
|
+
data-confirm=`Cancel job ${job.id}? It ends as CANCELED; if it's running, its Client stops it.`
|
|
16
|
+
data-confirm-title="Cancel job?"
|
|
17
|
+
data-confirm-ok="Cancel job"
|
|
18
|
+
data-confirm-dismiss="Keep it"
|
|
19
|
+
)
|
|
13
20
|
button.btn.btn-outline-danger.btn-sm(type="submit") Cancel
|
|
14
21
|
|
|
15
22
|
dl.row
|
package/views/layout.pug
CHANGED
|
@@ -66,7 +66,10 @@ html(lang="en" data-bs-theme=(user && user.theme) || "auto")
|
|
|
66
66
|
form(
|
|
67
67
|
method="post"
|
|
68
68
|
action="/updates/coordinator"
|
|
69
|
-
|
|
69
|
+
data-confirm=`Update the Coordinator to v${updates.coordinatorUpdate}? It restarts, so the dashboard and API are briefly unavailable — this page reloads by itself once it's back.`
|
|
70
|
+
data-confirm-title="Update the Coordinator?"
|
|
71
|
+
data-confirm-ok="Update and restart"
|
|
72
|
+
data-confirm-tone="warning"
|
|
70
73
|
)
|
|
71
74
|
input(type="hidden" name="returnTo" value=currentPath)
|
|
72
75
|
button.btn.btn-sm.btn-warning(
|
|
@@ -142,11 +145,26 @@ html(lang="en" data-bs-theme=(user && user.theme) || "auto")
|
|
|
142
145
|
)
|
|
143
146
|
main.container.py-4
|
|
144
147
|
block content
|
|
148
|
+
//- Shared confirmation for forms with data-confirm (public/js/confirm.js)
|
|
149
|
+
//- — used instead of the browser's confirm() everywhere.
|
|
150
|
+
if user
|
|
151
|
+
.modal.fade#thub-confirm-modal(tabindex="-1" aria-labelledby="thub-confirm-title" aria-hidden="true")
|
|
152
|
+
.modal-dialog.modal-dialog-centered
|
|
153
|
+
.modal-content
|
|
154
|
+
.modal-header.text-bg-danger(data-field="header")
|
|
155
|
+
h5.modal-title#thub-confirm-title(data-field="title") Are you sure?
|
|
156
|
+
button.btn-close(type="button" data-bs-dismiss="modal" aria-label="Close")
|
|
157
|
+
.modal-body
|
|
158
|
+
p.mb-0(data-field="message" style="white-space: pre-line;")
|
|
159
|
+
.modal-footer
|
|
160
|
+
button.btn.btn-secondary(type="button" data-bs-dismiss="modal" data-field="dismiss") Cancel
|
|
161
|
+
button.btn.btn-danger(type="button" data-field="ok") Confirm
|
|
145
162
|
script(src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js")
|
|
146
163
|
script(src="/js/theme.js")
|
|
147
164
|
script(src="/js/tooltips.js")
|
|
148
165
|
script(src="/js/toasts.js")
|
|
149
166
|
script(src="/js/copy-to-clipboard.js")
|
|
167
|
+
script(src="/js/confirm.js")
|
|
150
168
|
if user && user.role === 'admin' && updates.coordinatorPending
|
|
151
169
|
script(src="/js/update-watch.js")
|
|
152
170
|
block scripts
|
|
@@ -24,7 +24,14 @@ mixin resourceActions(r, returnTo, opts = {})
|
|
|
24
24
|
data-bs-title="Take this resource out of rotation for maintenance — the scheduler will skip it"
|
|
25
25
|
)
|
|
26
26
|
i.bi.bi-tools
|
|
27
|
-
form.d-inline.ms-1(
|
|
27
|
+
form.d-inline.ms-1(
|
|
28
|
+
method="post"
|
|
29
|
+
action=`/resources/${r.id}/rotate-token`
|
|
30
|
+
data-confirm=`Issue a new token for ${r.name}? Its current token stops working at once, and the Client won't reconnect until it has the new one.`
|
|
31
|
+
data-confirm-title="Rotate resource token?"
|
|
32
|
+
data-confirm-ok="Rotate token"
|
|
33
|
+
data-confirm-tone="warning"
|
|
34
|
+
)
|
|
28
35
|
input(type="hidden" name="returnTo" value=returnTo)
|
|
29
36
|
button.btn.btn-sm.btn-outline-warning(
|
|
30
37
|
type="submit"
|
|
@@ -62,7 +69,8 @@ mixin removeResourceButton(r, returnTo)
|
|
|
62
69
|
method="post"
|
|
63
70
|
action=`/resources/${r.id}/remove`
|
|
64
71
|
data-confirm=`Remove ${r.name} from the list? Its job history is kept. A Client that's still running shows up again when it restarts and re-registers.`
|
|
65
|
-
|
|
72
|
+
data-confirm-title=`Remove ${r.name}?`
|
|
73
|
+
data-confirm-ok="Remove"
|
|
66
74
|
)
|
|
67
75
|
input(type="hidden" name="returnTo" value=returnTo)
|
|
68
76
|
button.btn.btn-sm.btn-outline-danger(
|
|
@@ -211,7 +219,9 @@ mixin cancelActivityButton(r, returnTo)
|
|
|
211
219
|
method="post"
|
|
212
220
|
action=`/resources/${r.id}/cancel-activity`
|
|
213
221
|
data-confirm=`Cancel ${what} on ${r.name}?`
|
|
214
|
-
|
|
222
|
+
data-confirm-title=`Cancel ${what}?`
|
|
223
|
+
data-confirm-ok=`Cancel ${what}`
|
|
224
|
+
data-confirm-dismiss="Keep it"
|
|
215
225
|
)
|
|
216
226
|
input(type="hidden" name="returnTo" value=returnTo)
|
|
217
227
|
button.btn.btn-sm.btn-outline-danger(
|
package/views/mixins/updates.pug
CHANGED
|
@@ -37,7 +37,10 @@ mixin updatesToolbar(app, label, returnTo)
|
|
|
37
37
|
form.d-inline(
|
|
38
38
|
method="post"
|
|
39
39
|
action=app === 'agent' ? '/admin/agents/update-all' : '/resources/update-all'
|
|
40
|
-
|
|
40
|
+
data-confirm=`Request a self-update of every ${label} to the latest version${updates.latest[app] ? ` (v${updates.latest[app]})` : ''}?`
|
|
41
|
+
data-confirm-title=`Update all ${label}?`
|
|
42
|
+
data-confirm-ok="Request update"
|
|
43
|
+
data-confirm-tone="primary"
|
|
41
44
|
)
|
|
42
45
|
input(type="hidden" name="returnTo" value=returnTo)
|
|
43
46
|
button.btn.btn-sm.btn-outline-primary(type="submit" disabled=!updates.latest[app])
|