@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andrian.yablonskyy/thub-coordinator",
3
- "version": "1.0.22",
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
+ })();
@@ -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(method="post" action=`/admin/agents/${a.id}/revoke` onsubmit="return confirm('Revoke this agent token?')")
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")
@@ -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(method="post" action=`/groups/${g.id}/delete` onsubmit="return confirm('Delete this group? Any resource listing it will just stop matching on it.')")
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}`)
@@ -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
- onsubmit="return confirm(this.dataset.confirm)"
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")
@@ -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(method="post" action=`/jobs/${job.id}/cancel` onsubmit=`return confirm('Cancel job ${job.id}?')`)
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
- onsubmit=`return confirm('Update the Coordinator to v${updates.coordinatorUpdate}? It restarts, and the dashboard and API are briefly unavailable.')`
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(method="post" action=`/resources/${r.id}/rotate-token` onsubmit="return confirm('Rotate token? The Client will need the new one.')")
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
- onsubmit="return confirm(this.dataset.confirm)"
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
- onsubmit="return confirm(this.dataset.confirm)"
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(
@@ -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
- onsubmit=`return confirm('Request a self-update of every ${label} to the latest version?')`
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])