@lchase/kiln 0.16.0 → 0.17.0
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/assets/serve.css +90 -10
- package/assets/serve.js +72 -20
- package/dist/icons.js +41 -0
- package/dist/serve-page.js +24 -16
- package/package.json +1 -1
package/assets/serve.css
CHANGED
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
--logo: 28px;
|
|
45
45
|
--dot: 8px;
|
|
46
46
|
--icon: 16px;
|
|
47
|
+
--icon-stroke: 2;
|
|
47
48
|
--badge: 20px;
|
|
48
49
|
--focus-width: 2px;
|
|
49
50
|
--focus-offset: 2px;
|
|
@@ -426,7 +427,7 @@ h3 {
|
|
|
426
427
|
position: sticky;
|
|
427
428
|
top: 0;
|
|
428
429
|
height: 100vh;
|
|
429
|
-
overflow:
|
|
430
|
+
overflow: hidden;
|
|
430
431
|
background: var(--panel);
|
|
431
432
|
border-right: var(--hairline) solid var(--line);
|
|
432
433
|
padding: var(--space-4) var(--space-3) var(--space-5);
|
|
@@ -434,6 +435,14 @@ h3 {
|
|
|
434
435
|
flex-direction: column;
|
|
435
436
|
gap: var(--space-3);
|
|
436
437
|
}
|
|
438
|
+
.nav-scroll {
|
|
439
|
+
flex: 1 1 auto;
|
|
440
|
+
min-height: 0;
|
|
441
|
+
overflow: auto;
|
|
442
|
+
display: flex;
|
|
443
|
+
flex-direction: column;
|
|
444
|
+
gap: var(--space-3);
|
|
445
|
+
}
|
|
437
446
|
.brand { display: flex; align-items: center; gap: var(--space-3); padding: 0 var(--space-2); }
|
|
438
447
|
.brand h1 { margin: 0; font-size: var(--text-md); line-height: var(--leading-sm); }
|
|
439
448
|
.logo {
|
|
@@ -448,6 +457,22 @@ h3 {
|
|
|
448
457
|
flex: none;
|
|
449
458
|
}
|
|
450
459
|
.logo svg { width: var(--icon); height: var(--icon); }
|
|
460
|
+
.icon {
|
|
461
|
+
width: var(--icon);
|
|
462
|
+
height: var(--icon);
|
|
463
|
+
flex: none;
|
|
464
|
+
stroke: currentColor;
|
|
465
|
+
fill: none;
|
|
466
|
+
stroke-width: var(--icon-stroke);
|
|
467
|
+
stroke-linecap: round;
|
|
468
|
+
stroke-linejoin: round;
|
|
469
|
+
}
|
|
470
|
+
#notify .icon-bell,
|
|
471
|
+
#pause-toggle .icon-play { display: none; }
|
|
472
|
+
#notify[data-state="on"] .icon-bell { display: block; }
|
|
473
|
+
#notify[data-state="on"] .icon-bell-off { display: none; }
|
|
474
|
+
#pause-toggle[aria-pressed="true"] .icon-pause { display: none; }
|
|
475
|
+
#pause-toggle[aria-pressed="true"] .icon-play { display: block; }
|
|
451
476
|
.ver { display: block; font-size: var(--text-xs); line-height: var(--leading-xs); color: var(--faint); }
|
|
452
477
|
.runstate {
|
|
453
478
|
display: flex;
|
|
@@ -464,15 +489,30 @@ h3 {
|
|
|
464
489
|
background: var(--state-running);
|
|
465
490
|
}
|
|
466
491
|
.live-dot.paused { background: var(--state-needs-you); }
|
|
492
|
+
.spend-row {
|
|
493
|
+
display: flex;
|
|
494
|
+
flex-wrap: wrap;
|
|
495
|
+
align-items: baseline;
|
|
496
|
+
gap: var(--space-2);
|
|
497
|
+
margin: 0;
|
|
498
|
+
padding: 0 var(--space-2);
|
|
499
|
+
}
|
|
467
500
|
.spend, .cap {
|
|
468
501
|
font-family: var(--font-mono);
|
|
469
502
|
font-size: var(--text-xs);
|
|
470
503
|
line-height: var(--leading-xs);
|
|
471
504
|
color: var(--muted);
|
|
472
|
-
padding: 0 var(--space-2);
|
|
473
505
|
}
|
|
474
506
|
.cap { color: var(--state-needs-you); }
|
|
475
507
|
.side { display: flex; flex-direction: column; gap: var(--space-2xs); }
|
|
508
|
+
.side-foot {
|
|
509
|
+
flex: none;
|
|
510
|
+
display: flex;
|
|
511
|
+
flex-direction: column;
|
|
512
|
+
gap: var(--space-2);
|
|
513
|
+
padding-top: var(--space-3);
|
|
514
|
+
border-top: var(--hairline) solid var(--line);
|
|
515
|
+
}
|
|
476
516
|
.nav-link, .nav-sub {
|
|
477
517
|
display: flex;
|
|
478
518
|
align-items: center;
|
|
@@ -480,12 +520,21 @@ h3 {
|
|
|
480
520
|
gap: var(--space-2);
|
|
481
521
|
padding: var(--space-2);
|
|
482
522
|
border-radius: var(--radius-md);
|
|
483
|
-
color: var(--
|
|
523
|
+
color: var(--muted);
|
|
484
524
|
text-decoration: none;
|
|
485
525
|
}
|
|
486
|
-
.nav-
|
|
487
|
-
|
|
488
|
-
|
|
526
|
+
.nav-label {
|
|
527
|
+
display: inline-flex;
|
|
528
|
+
align-items: center;
|
|
529
|
+
gap: var(--space-2);
|
|
530
|
+
min-width: 0;
|
|
531
|
+
}
|
|
532
|
+
.nav-sub { padding-left: calc(var(--icon) + var(--space-4)); font-size: var(--text-sm); }
|
|
533
|
+
.nav-link:hover, .nav-sub:hover { background: var(--card); color: var(--ink); text-decoration: none; }
|
|
534
|
+
.nav-link[aria-current="page"], .nav-sub[aria-current="page"] {
|
|
535
|
+
background: var(--card-hover);
|
|
536
|
+
color: var(--ink);
|
|
537
|
+
}
|
|
489
538
|
.badge {
|
|
490
539
|
min-width: var(--badge);
|
|
491
540
|
height: var(--badge);
|
|
@@ -520,17 +569,47 @@ h3 {
|
|
|
520
569
|
color: var(--ink);
|
|
521
570
|
cursor: pointer;
|
|
522
571
|
}
|
|
523
|
-
.pause button {
|
|
572
|
+
.pause button {
|
|
573
|
+
display: inline-flex;
|
|
574
|
+
align-items: center;
|
|
575
|
+
gap: var(--space-1);
|
|
576
|
+
font: var(--text-xs)/var(--leading-xs) var(--font-mono);
|
|
577
|
+
}
|
|
524
578
|
.pause { margin: 0 0 0 auto; }
|
|
525
579
|
.pause button[aria-pressed="true"] { color: var(--state-needs-you); }
|
|
526
|
-
.add
|
|
580
|
+
.add { margin-top: var(--space-1); text-align: left; }
|
|
527
581
|
.add, .btn.primary, .d-actions .approve button {
|
|
528
582
|
background: var(--primary);
|
|
529
583
|
color: var(--on-primary);
|
|
530
584
|
border-color: transparent;
|
|
531
585
|
font-weight: 500;
|
|
532
586
|
}
|
|
533
|
-
.
|
|
587
|
+
.notify { display: flex; flex-direction: column; gap: var(--space-1); }
|
|
588
|
+
.tool-link {
|
|
589
|
+
display: flex;
|
|
590
|
+
align-items: center;
|
|
591
|
+
gap: var(--space-2);
|
|
592
|
+
width: 100%;
|
|
593
|
+
margin: 0;
|
|
594
|
+
text-align: left;
|
|
595
|
+
background: none;
|
|
596
|
+
color: var(--muted);
|
|
597
|
+
}
|
|
598
|
+
.tool-link:hover { color: var(--ink); background: var(--card); }
|
|
599
|
+
.tool-link[data-state="on"] { color: var(--ink); }
|
|
600
|
+
.tool-link:disabled, .tool-link:disabled:hover {
|
|
601
|
+
color: var(--muted);
|
|
602
|
+
background: none;
|
|
603
|
+
cursor: not-allowed;
|
|
604
|
+
opacity: var(--disabled);
|
|
605
|
+
}
|
|
606
|
+
.notify-note {
|
|
607
|
+
margin: 0;
|
|
608
|
+
padding: 0 var(--space-1);
|
|
609
|
+
color: var(--muted);
|
|
610
|
+
font-size: var(--text-xs);
|
|
611
|
+
line-height: var(--leading-xs);
|
|
612
|
+
}
|
|
534
613
|
.pick, .seg button, .fs-up, .fs-use, .repo-f, .wf-f {
|
|
535
614
|
background: none;
|
|
536
615
|
color: var(--muted);
|
|
@@ -1008,7 +1087,8 @@ body.open .drawer { transform: none; }
|
|
|
1008
1087
|
|
|
1009
1088
|
@media (max-width: 960px) {
|
|
1010
1089
|
.shell { grid-template-columns: 1fr; }
|
|
1011
|
-
.nav { position: relative; height: auto; border-right: 0; border-bottom: var(--hairline) solid var(--line); }
|
|
1090
|
+
.nav { position: relative; height: auto; overflow: visible; border-right: 0; border-bottom: var(--hairline) solid var(--line); }
|
|
1091
|
+
.nav-scroll { overflow: visible; }
|
|
1012
1092
|
.detail { grid-template-columns: 1fr; }
|
|
1013
1093
|
.rail { position: static; }
|
|
1014
1094
|
.d-actions .revise input { min-width: 0; width: 100%; }
|
package/assets/serve.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
var KEY = "kiln-needs-you";
|
|
3
|
+
var NOTIFY_KEY = "kiln-notify";
|
|
3
4
|
var body = document.body;
|
|
4
5
|
var board = document.getElementById("board");
|
|
5
6
|
var count = Number(body.getAttribute("data-needs-you") || "0");
|
|
@@ -12,7 +13,7 @@
|
|
|
12
13
|
if (raw === null) return;
|
|
13
14
|
var prev = Number(raw);
|
|
14
15
|
if (!isFinite(prev) || n <= prev) return;
|
|
15
|
-
if (
|
|
16
|
+
if (notifyMode() !== "on") return;
|
|
16
17
|
var added = n - prev;
|
|
17
18
|
try {
|
|
18
19
|
new Notification("Kiln", { body: added === 1 ? "1 job needs you" : added + " jobs need you" });
|
|
@@ -257,24 +258,63 @@
|
|
|
257
258
|
try { localStorage.removeItem("kiln-drawer"); } catch (err) { /* private mode */ }
|
|
258
259
|
}
|
|
259
260
|
|
|
260
|
-
|
|
261
|
+
var notifyAsking = false;
|
|
262
|
+
|
|
263
|
+
function notifyPref() {
|
|
264
|
+
try { return localStorage.getItem(NOTIFY_KEY) === "off" ? "off" : "on"; }
|
|
265
|
+
catch (err) { return "on"; }
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function setNotifyPref(value) {
|
|
269
|
+
try { localStorage.setItem(NOTIFY_KEY, value); } catch (err) { /* private mode */ }
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function notifyMode() {
|
|
273
|
+
if (typeof Notification === "undefined") return "unavailable";
|
|
274
|
+
if (Notification.permission === "denied") return "blocked";
|
|
275
|
+
if (Notification.permission === "granted") return notifyPref() === "off" ? "off" : "on";
|
|
276
|
+
return "ask";
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function notifyCopy(mode) {
|
|
280
|
+
if (mode === "unavailable") return { label: "Notifications unavailable", note: "" };
|
|
281
|
+
if (mode === "blocked") return { label: "Notifications blocked", note: "Allow notifications for this site in the browser." };
|
|
282
|
+
if (mode === "on") return { label: "Notifications on", note: "" };
|
|
283
|
+
return { label: "Notify me", note: "" };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function renderNotify() {
|
|
261
287
|
var button = document.getElementById("notify");
|
|
262
288
|
if (!button) return;
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
289
|
+
var mode = notifyMode();
|
|
290
|
+
var copy = notifyCopy(mode);
|
|
267
291
|
button.hidden = false;
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
292
|
+
button.disabled = mode === "unavailable";
|
|
293
|
+
button.setAttribute("data-state", mode);
|
|
294
|
+
button.setAttribute("aria-pressed", mode === "on" ? "true" : "false");
|
|
295
|
+
button.setAttribute("aria-label", copy.label);
|
|
296
|
+
if (copy.note) button.setAttribute("aria-describedby", "notify-note");
|
|
297
|
+
else button.removeAttribute("aria-describedby");
|
|
298
|
+
var label = document.getElementById("notify-label");
|
|
299
|
+
if (label) label.textContent = copy.label;
|
|
300
|
+
var note = document.getElementById("notify-note");
|
|
301
|
+
if (note) {
|
|
302
|
+
note.hidden = !copy.note;
|
|
303
|
+
note.textContent = copy.note;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function askNotify() {
|
|
308
|
+
if (notifyAsking || typeof Notification === "undefined" || !Notification.requestPermission) return;
|
|
309
|
+
notifyAsking = true;
|
|
310
|
+
var pending = Notification.requestPermission();
|
|
311
|
+
var done = function () {
|
|
312
|
+
notifyAsking = false;
|
|
313
|
+
if (typeof Notification !== "undefined" && Notification.permission === "granted") setNotifyPref("on");
|
|
314
|
+
renderNotify();
|
|
315
|
+
};
|
|
316
|
+
if (pending && typeof pending.then === "function") pending.then(done, done);
|
|
317
|
+
else done();
|
|
278
318
|
}
|
|
279
319
|
|
|
280
320
|
var titleDirty = false;
|
|
@@ -1490,8 +1530,19 @@
|
|
|
1490
1530
|
return;
|
|
1491
1531
|
}
|
|
1492
1532
|
if (target.closest("#notify")) {
|
|
1493
|
-
|
|
1494
|
-
|
|
1533
|
+
var mode = notifyMode();
|
|
1534
|
+
if (mode === "unavailable" || mode === "blocked") return;
|
|
1535
|
+
if (mode === "on") {
|
|
1536
|
+
setNotifyPref("off");
|
|
1537
|
+
renderNotify();
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
if (mode === "off") {
|
|
1541
|
+
setNotifyPref("on");
|
|
1542
|
+
renderNotify();
|
|
1543
|
+
return;
|
|
1544
|
+
}
|
|
1545
|
+
askNotify();
|
|
1495
1546
|
return;
|
|
1496
1547
|
}
|
|
1497
1548
|
var act = target.closest("[data-act]");
|
|
@@ -1615,7 +1666,8 @@
|
|
|
1615
1666
|
var toggle = document.getElementById("pause-toggle");
|
|
1616
1667
|
var form = document.getElementById("pause-form");
|
|
1617
1668
|
if (toggle && form && data && typeof data.paused === "boolean") {
|
|
1618
|
-
|
|
1669
|
+
var pauseLabel = document.getElementById("pause-label");
|
|
1670
|
+
if (pauseLabel) pauseLabel.textContent = data.paused ? "Resume" : "Pause";
|
|
1619
1671
|
toggle.setAttribute("aria-pressed", data.paused ? "true" : "false");
|
|
1620
1672
|
form.setAttribute("action", data.paused ? "/api/resume" : "/api/pause");
|
|
1621
1673
|
var label = document.getElementById("run-label");
|
|
@@ -1770,7 +1822,7 @@
|
|
|
1770
1822
|
showFresh();
|
|
1771
1823
|
syncValidity();
|
|
1772
1824
|
renderCompact();
|
|
1773
|
-
|
|
1825
|
+
renderNotify();
|
|
1774
1826
|
applyRepo();
|
|
1775
1827
|
stamp(true);
|
|
1776
1828
|
paintUnseen();
|
package/dist/icons.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Board icons from Lucide 1.70.0 (ISC, copyright Lucide Icons and Contributors).
|
|
3
|
+
* https://lucide.dev — https://github.com/lucide-icons/lucide
|
|
4
|
+
*
|
|
5
|
+
* These are the official single-stroke paths. Size, stroke, and color come from
|
|
6
|
+
* CSS (`.icon`: `--icon`, `--icon-stroke`, `currentColor`). No fill.
|
|
7
|
+
*/
|
|
8
|
+
export const ICONS = {
|
|
9
|
+
pipeline: `<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M9 3v18"/><path d="M15 3v18"/>`,
|
|
10
|
+
inbox: `<polyline points="22 12 16 12 14 15 10 15 8 12 2 12"/><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/>`,
|
|
11
|
+
hand: `<path d="M11 12h2a2 2 0 1 0 0-4h-3c-.6 0-1.1.2-1.4.6L3 14"/><path d="m7 18 1.6-1.4c.3-.4.8-.6 1.4-.6h4c1.1 0 2.1-.4 2.8-1.2l4.6-4.4a2 2 0 0 0-2.75-2.91l-4.2 3.9"/><path d="m2 13 6 6"/>`,
|
|
12
|
+
repos: `<path d="M18 19a5 5 0 0 1-5-5v8"/><path d="M9 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v5"/><circle cx="13" cy="12" r="2"/><circle cx="20" cy="19" r="2"/>`,
|
|
13
|
+
history: `<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/><path d="M12 7v5l4 2"/>`,
|
|
14
|
+
stats: `<path d="M3 3v16a2 2 0 0 0 2 2h16"/><path d="M18 17V9"/><path d="M13 17V5"/><path d="M8 17v-3"/>`,
|
|
15
|
+
today: `<path d="M8 2v3"/><path d="M16 2v3"/><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/>`,
|
|
16
|
+
triggers: `<path d="M15.914 4a1.5 1.5 0 00-2.474-1.561l-9 9A1.5 1.5 0 005.5 14h4.002a.5.5 0 01.471.666L8.086 20a1.5 1.5 0 002.475 1.56l9-9A1.5 1.5 0 0018.5 10h-3.997a.5.5 0 01-.472-.667z"/>`,
|
|
17
|
+
settings: `<path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915"/><circle cx="12" cy="12" r="3"/>`,
|
|
18
|
+
pause: `<rect x="14" y="3" width="5" height="18" rx="1"/><rect x="5" y="3" width="5" height="18" rx="1"/>`,
|
|
19
|
+
play: `<path d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z"/>`,
|
|
20
|
+
bell: `<path d="M10.268 21a2 2 0 0 0 3.464 0"/><path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326"/>`,
|
|
21
|
+
"bell-off": `<path d="M10.268 21a2 2 0 0 0 3.464 0"/><path d="M17 17H4a1 1 0 0 1-.74-1.673C4.59 13.956 6 12.499 6 8a6 6 0 0 1 .258-1.742"/><path d="m2 2 20 20"/><path d="M8.668 3.01A6 6 0 0 1 18 8c0 2.687.77 4.653 1.707 6.05"/>`,
|
|
22
|
+
};
|
|
23
|
+
/** Lucide names for the marks above. Repo sublinks stay text and are not listed. */
|
|
24
|
+
export const ICON_LUCIDE = {
|
|
25
|
+
pipeline: "columns-3",
|
|
26
|
+
inbox: "inbox",
|
|
27
|
+
hand: "hand-helping",
|
|
28
|
+
repos: "folder-git-2",
|
|
29
|
+
history: "history",
|
|
30
|
+
stats: "chart-column",
|
|
31
|
+
today: "calendar",
|
|
32
|
+
triggers: "zap",
|
|
33
|
+
settings: "settings",
|
|
34
|
+
pause: "pause",
|
|
35
|
+
play: "play",
|
|
36
|
+
bell: "bell",
|
|
37
|
+
"bell-off": "bell-off",
|
|
38
|
+
};
|
|
39
|
+
export function icon(name, className = "icon") {
|
|
40
|
+
return `<svg class="${className}" viewBox="0 0 24 24" aria-hidden="true">${ICONS[name]}</svg>`;
|
|
41
|
+
}
|
package/dist/serve-page.js
CHANGED
|
@@ -3,6 +3,7 @@ import { join } from "node:path";
|
|
|
3
3
|
import { packageRoot } from "./paths.js";
|
|
4
4
|
import { buildBoard, } from "./board-view.js";
|
|
5
5
|
import { COLUMN_LABEL, PIPELINE_COLUMNS, formatCost, formatDuration, formatLocal, formatRelative, shortSha, stateTone, } from "./job-view.js";
|
|
6
|
+
import { icon } from "./icons.js";
|
|
6
7
|
import { renderMarkdown } from "./markdown.js";
|
|
7
8
|
import { kilnVersion } from "./project.js";
|
|
8
9
|
import { formatSpend } from "./rails.js";
|
|
@@ -25,15 +26,22 @@ export function renderApp(model) {
|
|
|
25
26
|
const body = `<div class="shell">
|
|
26
27
|
<aside class="nav" aria-label="Kiln">
|
|
27
28
|
<div class="brand"><span class="logo" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12V6l5-3 5 3v6l-5 3-5-3z"/><path d="M3 6l5 3 5-3M8 9v6"/></svg></span><div><h1>Kiln</h1>${version}</div></div>
|
|
29
|
+
<div class="nav-scroll">
|
|
30
|
+
${navHtml(model)}
|
|
31
|
+
<button class="add" type="button" id="add-job">Add job</button>
|
|
32
|
+
</div>
|
|
33
|
+
<footer class="side-foot">
|
|
28
34
|
<div class="runstate" id="runstate">
|
|
29
35
|
<span class="live-dot${paused ? " paused" : ""}" id="run-dot" aria-hidden="true"></span>
|
|
30
36
|
<span id="run-label">${paused ? "Paused" : "Running"}</span>
|
|
31
|
-
<form method="post" action="${paused ? "/api/resume" : "/api/pause"}" class="pause" id="pause-form"><input type="hidden" name="csrf" value="${escapeHtml(page.csrf)}"><button type="submit" id="pause-toggle" aria-pressed="${paused ? "true" : "false"}">${paused ? "Resume" : "Pause"}</button></form>
|
|
37
|
+
<form method="post" action="${paused ? "/api/resume" : "/api/pause"}" class="pause" id="pause-form"><input type="hidden" name="csrf" value="${escapeHtml(page.csrf)}"><button type="submit" id="pause-toggle" aria-pressed="${paused ? "true" : "false"}">${icon("pause", "icon icon-pause")}${icon("play", "icon icon-play")}<span id="pause-label">${paused ? "Resume" : "Pause"}</span></button></form>
|
|
32
38
|
</div>
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
<button class="
|
|
36
|
-
<
|
|
39
|
+
<p class="spend-row">${spend}${capNote}</p>
|
|
40
|
+
<div class="notify">
|
|
41
|
+
<button class="tool-link" type="button" id="notify" data-state="ask" aria-pressed="false">${icon("bell", "icon icon-bell")}${icon("bell-off", "icon icon-bell-off")}<span id="notify-label">Notify me</span></button>
|
|
42
|
+
<p class="notify-note" id="notify-note" hidden></p>
|
|
43
|
+
</div>
|
|
44
|
+
</footer>
|
|
37
45
|
</aside>
|
|
38
46
|
<div class="workspace${model.section === "job" ? " with-rail" : ""}">
|
|
39
47
|
${mainHtml(model, page)}
|
|
@@ -87,11 +95,11 @@ export function renderMessagePage(css, title, message, theme = DEFAULT_BOARD_THE
|
|
|
87
95
|
}
|
|
88
96
|
function navHtml(model) {
|
|
89
97
|
const board = model.board;
|
|
90
|
-
const item = (href, id, label, section, badge) => {
|
|
98
|
+
const item = (href, id, label, section, glyph, badge) => {
|
|
91
99
|
const current = model.section === section ? ` aria-current="page"` : "";
|
|
92
100
|
const count = badge !== undefined && badge > 0 ? `<span class="badge">${badge}</span>` : "";
|
|
93
101
|
const extra = id === "open-settings" ? ` id="open-settings"` : "";
|
|
94
|
-
return `<a class="nav-link" href="${href}"${current}${extra}>${escapeHtml(label)}
|
|
102
|
+
return `<a class="nav-link" href="${href}"${current}${extra}><span class="nav-label">${icon(glyph)}${escapeHtml(label)}</span>${count}</a>`;
|
|
95
103
|
};
|
|
96
104
|
const repos = board.repos
|
|
97
105
|
.map((group) => {
|
|
@@ -100,17 +108,17 @@ function navHtml(model) {
|
|
|
100
108
|
})
|
|
101
109
|
.join("");
|
|
102
110
|
return `<nav class="side" aria-label="Sections">
|
|
103
|
-
${item("/", "pipeline", "Pipeline", "pipeline")}
|
|
104
|
-
${item("/inbox", "inbox", "Inbox", "inbox", board.inboxCount)}
|
|
111
|
+
${item("/", "pipeline", "Pipeline", "pipeline", "pipeline")}
|
|
112
|
+
${item("/inbox", "inbox", "Inbox", "inbox", "inbox", board.inboxCount)}
|
|
105
113
|
<p class="unseen" id="inbox-unseen" hidden></p>
|
|
106
|
-
${item("/hand", "hand", "Needs a hand", "hand", board.handCount)}
|
|
107
|
-
${item("/repos", "repos", "Repos", "repos")}
|
|
114
|
+
${item("/hand", "hand", "Needs a hand", "hand", "hand", board.handCount)}
|
|
115
|
+
${item("/repos", "repos", "Repos", "repos", "repos")}
|
|
108
116
|
${repos}
|
|
109
|
-
${item("/history", "history", "History", "history", board.history.length)}
|
|
110
|
-
${item("/stats", "stats", "Stats", "stats")}
|
|
111
|
-
${item("/report", "report", "Today", "report")}
|
|
112
|
-
${item("/triggers", "triggers", "Triggers", "triggers")}
|
|
113
|
-
${item("/settings", "open-settings", "Settings", "settings")}
|
|
117
|
+
${item("/history", "history", "History", "history", "history", board.history.length)}
|
|
118
|
+
${item("/stats", "stats", "Stats", "stats", "stats")}
|
|
119
|
+
${item("/report", "report", "Today", "report", "today")}
|
|
120
|
+
${item("/triggers", "triggers", "Triggers", "triggers", "triggers")}
|
|
121
|
+
${item("/settings", "open-settings", "Settings", "settings", "settings")}
|
|
114
122
|
</nav>`;
|
|
115
123
|
}
|
|
116
124
|
function mainHtml(model, page) {
|