kryptheon 0.1.21 → 0.1.22
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/README.md +4 -0
- package/kryptheon-archivo.woff2 +0 -0
- package/kryptheon-dashboard-page.js +159 -560
- package/kryptheon-dashboard.html +341 -446
- package/kryptheon-dashboard.js +13 -6
- package/kryptheon-fonts-OFL.txt +98 -0
- package/kryptheon-spacemono.woff2 +0 -0
- package/package.json +4 -1
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
// Every value on this page can hold text from the app being checked - a line
|
|
4
4
|
// of its code, the heading of a page that broke. So nothing here is ever
|
|
5
5
|
// written as HTML: every node is made with createElement (or createElementNS
|
|
6
|
-
// for the
|
|
7
|
-
//
|
|
8
|
-
// must come back with nothing to report.
|
|
6
|
+
// for the icons, from fixed path data) and filled with textContent. This file
|
|
7
|
+
// is read by kryptheon-code.js in its own check, and must come back clean.
|
|
9
8
|
|
|
10
9
|
(function () {
|
|
11
10
|
'use strict';
|
|
@@ -15,9 +14,6 @@
|
|
|
15
14
|
var NS = 'http://www.w3.org/2000/svg';
|
|
16
15
|
var state = null;
|
|
17
16
|
var busy = false;
|
|
18
|
-
var statusFilter = 'all';
|
|
19
|
-
var range = 'all';
|
|
20
|
-
var query = '';
|
|
21
17
|
var openRow = null;
|
|
22
18
|
var allLooks = false;
|
|
23
19
|
|
|
@@ -34,20 +30,8 @@
|
|
|
34
30
|
Object.keys(attrs || {}).forEach(function (k) { node.setAttribute(k, String(attrs[k])); });
|
|
35
31
|
return node;
|
|
36
32
|
}
|
|
37
|
-
function clear(node) {
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
function byId(id) {
|
|
41
|
-
return document.getElementById(id);
|
|
42
|
-
}
|
|
43
|
-
// A sentence with its important words marked: parts are plain strings, or
|
|
44
|
-
// { hl: text } for the words someone skimming must not miss.
|
|
45
|
-
function rich(node, parts) {
|
|
46
|
-
parts.forEach(function (p) {
|
|
47
|
-
node.appendChild(typeof p === 'string' ? document.createTextNode(p) : el('span', 'hl', p.hl));
|
|
48
|
-
});
|
|
49
|
-
return node;
|
|
50
|
-
}
|
|
33
|
+
function clear(node) { while (node.firstChild) node.removeChild(node.firstChild); }
|
|
34
|
+
function byId(id) { return document.getElementById(id); }
|
|
51
35
|
|
|
52
36
|
// Line icons from fixed path data only.
|
|
53
37
|
var ICONS = {
|
|
@@ -57,10 +41,7 @@
|
|
|
57
41
|
wrench: 'M14.7 6.3a4 4 0 00-5.4 5.4L3 18l3 3 6.3-6.3a4 4 0 005.4-5.4l-2.6 2.6-2.4-.6-.6-2.4 2.6-2.6z',
|
|
58
42
|
toggle: 'M8 7h8a5 5 0 010 10H8A5 5 0 018 7zm0 3a2 2 0 100 4 2 2 0 000-4z',
|
|
59
43
|
play: 'M7 5l12 7-12 7V5z',
|
|
60
|
-
shield: 'M12 2l8 3v6c0 5-3.4 9.4-8 11-4.6-1.6-8-6-8-11V5l8-3z',
|
|
61
44
|
box: 'M4 7l8-4 8 4v10l-8 4-8-4V7zm0 0l8 4 8-4M12 11v10',
|
|
62
|
-
search: 'M11 18a7 7 0 100-14 7 7 0 000 14zm9 3l-4.3-4.3',
|
|
63
|
-
bell: 'M6 16V11a6 6 0 1112 0v5l2 2H4l2-2zm4 4h4',
|
|
64
45
|
alert: 'M12 3l10 18H2L12 3zm0 7v5m0 3v.5',
|
|
65
46
|
eye: 'M2 12s3.6-7 10-7 10 7 10 7-3.6 7-10 7S2 12 2 12zm10 3a3 3 0 100-6 3 3 0 000 6z',
|
|
66
47
|
check: 'M5 12.5l4.5 4.5L19 7.5',
|
|
@@ -69,7 +50,8 @@
|
|
|
69
50
|
refresh: 'M20 12a8 8 0 10-2.3 5.7M20 12V6m0 6h-6',
|
|
70
51
|
arrow: 'M5 12h14m-6-6l6 6-6 6',
|
|
71
52
|
sun: 'M12 16a4 4 0 100-8 4 4 0 000 8zM12 2v2m0 16v2M4.9 4.9l1.4 1.4m11.4 11.4l1.4 1.4M2 12h2m16 0h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4',
|
|
72
|
-
|
|
53
|
+
terminal: 'M4 5h16v14H4zM7 9l3 3-3 3M13 15h4',
|
|
54
|
+
dot: 'M12 12h.01',
|
|
73
55
|
};
|
|
74
56
|
function icon(name, size) {
|
|
75
57
|
var s = svgEl('svg', {
|
|
@@ -85,10 +67,6 @@
|
|
|
85
67
|
var d = new Date(iso);
|
|
86
68
|
return isNaN(d.getTime()) ? String(iso) : d.toLocaleString();
|
|
87
69
|
}
|
|
88
|
-
function shortDate(iso) {
|
|
89
|
-
var d = new Date(iso);
|
|
90
|
-
return isNaN(d.getTime()) ? '' : d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
|
|
91
|
-
}
|
|
92
70
|
function ago(iso) {
|
|
93
71
|
var d = new Date(iso);
|
|
94
72
|
if (isNaN(d.getTime())) return '';
|
|
@@ -98,9 +76,7 @@
|
|
|
98
76
|
if (s < 86400) return Math.round(s / 3600) + ' h ago';
|
|
99
77
|
return Math.round(s / 86400) + ' d ago';
|
|
100
78
|
}
|
|
101
|
-
function plural(n, one, many) {
|
|
102
|
-
return n + ' ' + (n === 1 ? one : many || one + 's');
|
|
103
|
-
}
|
|
79
|
+
function plural(n, one, many) { return n + ' ' + (n === 1 ? one : many || one + 's'); }
|
|
104
80
|
|
|
105
81
|
function toast(text) {
|
|
106
82
|
var t = byId('toast');
|
|
@@ -127,13 +103,11 @@
|
|
|
127
103
|
busy = on;
|
|
128
104
|
Array.prototype.forEach.call(document.querySelectorAll('[data-action]'), function (b) { b.disabled = on; });
|
|
129
105
|
}
|
|
130
|
-
|
|
131
106
|
function copy(text, done) {
|
|
132
107
|
navigator.clipboard.writeText(text).then(function () { toast(done); }, function () {
|
|
133
108
|
toast('Could not copy - select the text instead');
|
|
134
109
|
});
|
|
135
110
|
}
|
|
136
|
-
|
|
137
111
|
function button(cls, iconName, text, onClick) {
|
|
138
112
|
var b = el('button', 'btn' + (cls ? ' ' + cls : ''));
|
|
139
113
|
b.type = 'button';
|
|
@@ -145,67 +119,35 @@
|
|
|
145
119
|
|
|
146
120
|
var CHECK_NAME = { regression: 'Recorded flows', frontend: 'Frontend code', data: 'Database' };
|
|
147
121
|
var CHECK_TONE = { regression: 'sun', frontend: 'amber', data: 'dusk' };
|
|
148
|
-
var CHECK_COLOR = { regression: '#ec7433', frontend: '#d49b33', data: '#6d4a7c' };
|
|
149
122
|
|
|
150
123
|
function counts() {
|
|
151
124
|
var confirmed = state.findings.filter(function (f) { return f.status === 'confirmed'; }).length;
|
|
152
|
-
return {
|
|
153
|
-
confirmed: confirmed,
|
|
154
|
-
toVerify: state.findings.length - confirmed,
|
|
155
|
-
lastRun: state.runs[0],
|
|
156
|
-
lastLook: (state.looks || [])[0],
|
|
157
|
-
proven: (state.proven || []).length,
|
|
158
|
-
built: state.checks.filter(function (c) { return c.available; }).length,
|
|
159
|
-
};
|
|
125
|
+
return { confirmed: confirmed, toVerify: state.findings.length - confirmed, proven: (state.proven || []).length };
|
|
160
126
|
}
|
|
161
127
|
|
|
162
128
|
/* ----------------------------------- views ----------------------------------- */
|
|
163
129
|
|
|
164
|
-
// One section on screen at a time; the address keeps it, so a reload
|
|
165
|
-
//
|
|
130
|
+
// One section on screen at a time; the address keeps it, so a reload lands
|
|
131
|
+
// on the same one.
|
|
166
132
|
var VIEWS = [
|
|
167
|
-
{ id: 'dashboard', title: 'Dashboard', icon: 'grid'
|
|
168
|
-
{ id: 'findings', title: 'Findings', icon: 'list'
|
|
169
|
-
{ id: 'changes', title: 'What changed', icon: 'change'
|
|
170
|
-
{ id: 'fixes', title: 'Proven fixes', icon: 'wrench'
|
|
171
|
-
{ id: 'checks', title: 'Checks', icon: 'toggle'
|
|
172
|
-
{ id: 'runs', title: 'Runs', icon: 'play'
|
|
173
|
-
{ id: '
|
|
174
|
-
{ id: 'storage', title: 'Where this lives', icon: 'box', group: 'navWorkspace' },
|
|
133
|
+
{ id: 'dashboard', title: 'Dashboard', icon: 'grid' },
|
|
134
|
+
{ id: 'findings', title: 'Findings', icon: 'list' },
|
|
135
|
+
{ id: 'changes', title: 'What changed', icon: 'change' },
|
|
136
|
+
{ id: 'fixes', title: 'Proven fixes', icon: 'wrench' },
|
|
137
|
+
{ id: 'checks', title: 'Checks', icon: 'toggle' },
|
|
138
|
+
{ id: 'runs', title: 'Runs', icon: 'play' },
|
|
139
|
+
{ id: 'storage', title: 'Where this lives', icon: 'box' },
|
|
175
140
|
];
|
|
176
141
|
|
|
177
|
-
// What each section is for, with the words that decide it marked.
|
|
178
|
-
var WHEN = {
|
|
179
|
-
findings: ['Open this when something ', { hl: 'broke or looks risky' }, '. Click a row, ', { hl: 'copy the fix prompt' },
|
|
180
|
-
' into your AI tool, then press ', { hl: 'Re-check' }, ' - it says FIXED only when it is.'],
|
|
181
|
-
changes: ['Open this to see ', { hl: 'what your AI changed' }, ' - every file, and what broke or got fixed with it. ',
|
|
182
|
-
'Kryptheon looks by itself while this page is open.'],
|
|
183
|
-
fixes: ['Open this for ', { hl: 'proof' }, '. A fix is listed only after a ', { hl: 're-check passed' },
|
|
184
|
-
' - never just because the code changed.'],
|
|
185
|
-
checks: ['Open this to ', { hl: 'switch checks on or off' }, ', or run one now. Checks that are not built yet are listed, and ',
|
|
186
|
-
{ hl: 'never counted as passed' }, '.'],
|
|
187
|
-
runs: ['Open this when a ', { hl: 'recorded flow' }, ' - sign-up, login, checkout - ', { hl: 'stopped working' },
|
|
188
|
-
'. Each run replays your recordings against your app.'],
|
|
189
|
-
verification: ['Open this for the ', { hl: 'Kryptheon Verified' }, ' badge. It is ', { hl: 'not built yet' },
|
|
190
|
-
', so nothing here is verified.'],
|
|
191
|
-
storage: ['Open this to see ', { hl: 'where your records are kept' }, '. Nothing is sent anywhere, and ',
|
|
192
|
-
{ hl: 'nothing is written into your project' }, '.'],
|
|
193
|
-
};
|
|
194
|
-
|
|
195
142
|
function currentView() {
|
|
196
143
|
var id = (location.hash || '').replace(/^#/, '');
|
|
197
144
|
return VIEWS.some(function (v) { return v.id === id; }) ? id : 'dashboard';
|
|
198
145
|
}
|
|
199
|
-
|
|
200
146
|
function go(id, filter) {
|
|
201
|
-
if (filter) {
|
|
202
|
-
statusFilter = filter;
|
|
203
|
-
if (state) { renderStatusFilter(); renderFindings(); }
|
|
204
|
-
}
|
|
147
|
+
if (filter) { statusFilter = filter; if (state) { renderStatusFilter(); renderFindings(); } }
|
|
205
148
|
if (currentView() === id) return showView();
|
|
206
149
|
location.hash = id;
|
|
207
150
|
}
|
|
208
|
-
|
|
209
151
|
function showView() {
|
|
210
152
|
var id = currentView();
|
|
211
153
|
VIEWS.forEach(function (v) { byId('view-' + v.id).hidden = v.id !== id; });
|
|
@@ -213,8 +155,6 @@
|
|
|
213
155
|
byId('viewTitle').textContent = view.title;
|
|
214
156
|
document.title = (id === 'dashboard' ? '' : view.title + ' - ') + 'Kryptheon';
|
|
215
157
|
if (state) renderSide();
|
|
216
|
-
// The chart measures itself, so it is drawn again once it is on screen.
|
|
217
|
-
if (state && id === 'dashboard') renderChart();
|
|
218
158
|
window.scrollTo(0, 0);
|
|
219
159
|
}
|
|
220
160
|
window.addEventListener('hashchange', showView);
|
|
@@ -228,351 +168,122 @@
|
|
|
228
168
|
findings: [state.findings.length, c.confirmed > 0],
|
|
229
169
|
changes: [(state.looks || []).length, false],
|
|
230
170
|
fixes: [c.proven, false],
|
|
231
|
-
runs: [state.runs.length, !!(
|
|
171
|
+
runs: [state.runs.length, !!(state.runs[0] && state.runs[0].failed)],
|
|
232
172
|
};
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
box.appendChild(b);
|
|
246
|
-
});
|
|
173
|
+
var box = byId('nav');
|
|
174
|
+
clear(box);
|
|
175
|
+
VIEWS.forEach(function (v) {
|
|
176
|
+
var b = el('button', active === v.id ? 'on' : '');
|
|
177
|
+
b.type = 'button';
|
|
178
|
+
b.appendChild(icon(v.icon, 18));
|
|
179
|
+
b.appendChild(el('span', null, v.title));
|
|
180
|
+
var n = badge[v.id];
|
|
181
|
+
if (n && n[0]) b.appendChild(el('span', 'n-count' + (n[1] ? ' hot' : ''), n[0]));
|
|
182
|
+
if (active === v.id) b.setAttribute('aria-current', 'page');
|
|
183
|
+
b.addEventListener('click', function () { go(v.id); });
|
|
184
|
+
box.appendChild(b);
|
|
247
185
|
});
|
|
248
|
-
|
|
249
|
-
byId('
|
|
250
|
-
byId('
|
|
186
|
+
var built = state.checks.filter(function (x) { return x.available; }).length;
|
|
187
|
+
byId('builtText').textContent = built + ' / ' + state.checks.length;
|
|
188
|
+
byId('builtBar').style.width = Math.round((built / state.checks.length) * 100) + '%';
|
|
189
|
+
byId('builtNote').textContent = (state.checks.length - built) + ' more are listed as not available - never as passed.';
|
|
251
190
|
var name = state.project.name || 'Project';
|
|
252
|
-
byId('avatar').textContent = name.replace(/[^A-Za-z0-9]/g, '').slice(0, 2).toUpperCase() || 'K';
|
|
253
|
-
byId('projName').textContent = name;
|
|
254
|
-
byId('projPath').textContent = state.project.root;
|
|
255
|
-
byId('projPath').title = state.project.root;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/* ---------------------------------- topbar ---------------------------------- */
|
|
259
|
-
|
|
260
|
-
function renderTop() {
|
|
261
191
|
var last = (state.looks || [])[0];
|
|
262
|
-
byId('subline').textContent = last ? '
|
|
263
|
-
var si = byId('searchIcon');
|
|
264
|
-
clear(si);
|
|
265
|
-
si.appendChild(icon('search', 16));
|
|
266
|
-
var bell = byId('bell');
|
|
267
|
-
clear(bell);
|
|
268
|
-
bell.appendChild(icon('bell', 20));
|
|
269
|
-
var fresh = last && !last.first ? last.newFindings : 0;
|
|
270
|
-
if (fresh) bell.appendChild(el('span', 'bdot'));
|
|
271
|
-
bell.title = fresh ? plural(fresh, 'new finding') + ' in the last look - open What changed' : 'Nothing new in the last look';
|
|
272
|
-
var run = byId('runAll');
|
|
273
|
-
if (!busy) {
|
|
274
|
-
clear(run);
|
|
275
|
-
run.appendChild(icon('play', 15));
|
|
276
|
-
run.appendChild(el('span', null, 'Run checks'));
|
|
277
|
-
}
|
|
192
|
+
byId('subline').textContent = name + ' - ' + (last ? 'watching, last look ' + ago(last.at) : 'watching for changes');
|
|
278
193
|
}
|
|
279
194
|
|
|
280
|
-
/*
|
|
195
|
+
/* ------------------------------ right now + steps ------------------------------ */
|
|
281
196
|
|
|
282
|
-
function
|
|
283
|
-
var c = counts();
|
|
197
|
+
function renderNow() {
|
|
284
198
|
var sky = byId('sky');
|
|
285
199
|
var ic = byId('skyIcon');
|
|
286
200
|
var title = byId('skyTitle');
|
|
287
201
|
var sub = byId('skySub');
|
|
288
202
|
var actions = byId('skyActions');
|
|
289
|
-
clear(ic);
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
if (c.confirmed) {
|
|
203
|
+
clear(ic); clear(title); clear(sub); clear(actions);
|
|
204
|
+
var next = state.next || { kind: 'quiet', title: 'Nothing open' };
|
|
205
|
+
byId('skyEyebrow').textContent = next.kind === 'quiet' ? 'ALL QUIET' : 'DO THIS NEXT';
|
|
206
|
+
if (next.kind === 'fix') {
|
|
294
207
|
sky.className = 'sky bad';
|
|
295
208
|
ic.style.color = 'var(--ember)';
|
|
296
209
|
ic.appendChild(icon('alert', 28));
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
rich(sub, ['Proven by a check that ran. Open ', { hl: 'Findings' }, ', copy the fix prompt into your AI tool, then press ',
|
|
300
|
-
{ hl: 'Re-check' }, '.' + (c.toVerify ? ' ' + c.toVerify + (c.toVerify === 1 ? ' more needs' : ' more need') + ' a look.' : '')]);
|
|
210
|
+
title.textContent = next.title;
|
|
211
|
+
sub.textContent = 'Proven by a check that ran. Open it, copy the fix prompt into your AI tool, then press Re-check - it says FIXED only when it is.';
|
|
301
212
|
actions.appendChild(button('sun', 'arrow', 'Open Findings', function () { go('findings', 'confirmed'); }));
|
|
302
|
-
} else if (
|
|
213
|
+
} else if (next.kind === 'step') {
|
|
214
|
+
sky.className = 'sky';
|
|
215
|
+
ic.style.color = 'var(--sun-deep)';
|
|
216
|
+
ic.appendChild(icon('arrow', 28));
|
|
217
|
+
title.textContent = next.title;
|
|
218
|
+
sub.textContent = 'The next step in setting Kryptheon up. Do it below in Start here.';
|
|
219
|
+
actions.appendChild(button('sun', 'arrow', 'Go to Start here', function () { go('dashboard'); scrollToId('steps'); }));
|
|
220
|
+
} else if (next.kind === 'verify') {
|
|
303
221
|
sky.className = 'sky';
|
|
304
222
|
ic.style.color = 'var(--amber)';
|
|
305
223
|
ic.appendChild(icon('eye', 28));
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
rich(sub, ['Nothing confirmed broken. These were spotted ', { hl: 'without running anything' },
|
|
309
|
-
' - each needs a person to decide.']);
|
|
224
|
+
title.textContent = next.title;
|
|
225
|
+
sub.textContent = 'Spotted without running anything. Each needs a person to decide.';
|
|
310
226
|
actions.appendChild(button('sun', 'arrow', 'Open Findings', function () { go('findings', 'verify'); }));
|
|
311
227
|
} else {
|
|
312
228
|
sky.className = 'sky ok';
|
|
313
229
|
ic.style.color = 'var(--leaf)';
|
|
314
230
|
ic.appendChild(icon('sun', 28));
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
' - ' + (state.checks.length - c.built) + ' checks are not built yet.']);
|
|
319
|
-
actions.appendChild(button('sun', 'arrow', 'See the checks', function () { go('checks'); }));
|
|
320
|
-
}
|
|
321
|
-
if (c.lastLook && !c.lastLook.first && c.lastLook.fileCount) {
|
|
322
|
-
actions.appendChild(button('', 'change', 'What changed', function () { go('changes'); }));
|
|
231
|
+
title.textContent = 'Nothing open';
|
|
232
|
+
sub.textContent = 'Nothing open from the checks that are on and have run. That is not the same as nothing wrong - see Checks for what is not built yet.';
|
|
233
|
+
actions.appendChild(button('', 'toggle', 'See the checks', function () { go('checks'); }));
|
|
323
234
|
}
|
|
324
235
|
}
|
|
325
236
|
|
|
326
|
-
function
|
|
327
|
-
var
|
|
328
|
-
|
|
329
|
-
var top = el('div', 'kpi-top');
|
|
330
|
-
var chip = el('div', 'ichip ' + tone);
|
|
331
|
-
chip.appendChild(icon(iconName, 18));
|
|
332
|
-
top.appendChild(chip);
|
|
333
|
-
top.appendChild(el('div', 'kpi-label', label));
|
|
334
|
-
box.appendChild(top);
|
|
335
|
-
box.appendChild(el('div', 'kpi-num', value));
|
|
336
|
-
var f = el('div', 'kpi-foot');
|
|
337
|
-
foot.forEach(function (part) { f.appendChild(part); });
|
|
338
|
-
box.appendChild(f);
|
|
339
|
-
box.appendChild(el('div', 'kpi-go', goText + ' →'));
|
|
340
|
-
box.addEventListener('click', onOpen);
|
|
341
|
-
return box;
|
|
342
|
-
}
|
|
343
|
-
function delta(kind, text) {
|
|
344
|
-
return el('span', 'delta ' + kind, (kind === 'up' ? '▲ ' : kind === 'down' ? '▼ ' : '') + text);
|
|
237
|
+
function scrollToId(id) {
|
|
238
|
+
var t = byId(id);
|
|
239
|
+
if (t) t.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
345
240
|
}
|
|
346
241
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
clear(box);
|
|
350
|
-
var c = counts();
|
|
351
|
-
var newFound = c.lastLook && !c.lastLook.first ? c.lastLook.newFindings : 0;
|
|
352
|
-
var week = Date.now() - 7 * 86400000;
|
|
353
|
-
var fixedWeek = (state.proven || []).filter(function (p) { return new Date(p.at).getTime() > week; }).length;
|
|
354
|
-
box.appendChild(kpi('CONFIRMED', c.confirmed, 'alert', 'ember', newFound
|
|
355
|
-
? [delta('down', newFound + ' new'), el('span', null, 'in the last look')]
|
|
356
|
-
: c.confirmed ? [el('span', null, 'open now, each with a fix prompt')] : [delta('up', 'none open')],
|
|
357
|
-
'Open Findings', function () { go('findings', 'confirmed'); }));
|
|
358
|
-
box.appendChild(kpi('TO VERIFY', c.toVerify, 'eye', 'amber', [el('span', null, 'spotted without running anything')],
|
|
359
|
-
'Open Findings', function () { go('findings', 'verify'); }));
|
|
360
|
-
box.appendChild(kpi('PROVEN FIXES', c.proven, 'wrench', 'leaf', fixedWeek ? [delta('up', fixedWeek + ' this week')] : [delta('flat', 'none this week')],
|
|
361
|
-
'Open Proven fixes', function () { go('fixes'); }));
|
|
362
|
-
var r = c.lastRun;
|
|
363
|
-
box.appendChild(kpi('FLOWS HELD', r ? r.passed + ' / ' + (r.passed + r.failed) : '-', 'film', 'sun', r
|
|
364
|
-
? [delta(r.failed ? 'down' : 'up', r.failed ? r.failed + ' broke' : 'all held'), el('span', null, ago(r.runAt))]
|
|
365
|
-
: [el('span', null, plural(state.recordings, 'recording') + ', never run')],
|
|
366
|
-
'Open Runs', function () { go('runs'); }));
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
function renderGuide() {
|
|
370
|
-
var box = byId('guide');
|
|
371
|
-
clear(box);
|
|
372
|
-
var c = counts();
|
|
373
|
-
var looks = state.looks || [];
|
|
374
|
-
var items = [
|
|
375
|
-
{ view: 'findings', parts: ['Something ', { hl: 'broke or looks risky' }, ' after your AI changed code'],
|
|
376
|
-
state: state.findings.length ? c.confirmed + ' confirmed, ' + c.toVerify + ' to verify' : 'nothing open',
|
|
377
|
-
tone: c.confirmed ? 'hot' : c.toVerify ? 'warm' : 'cool' },
|
|
378
|
-
{ view: 'runs', parts: ['A ', { hl: 'recorded flow' }, ' (sign-up, login, checkout) stopped working'],
|
|
379
|
-
state: c.lastRun ? (c.lastRun.failed ? plural(c.lastRun.failed, 'flow') + ' broke in the last run' : 'last run held') : 'never replayed',
|
|
380
|
-
tone: c.lastRun ? (c.lastRun.failed ? 'hot' : 'cool') : '' },
|
|
381
|
-
{ view: 'changes', parts: ['You want to see ', { hl: 'what your AI touched' }],
|
|
382
|
-
state: looks.length ? plural(looks.length, 'look') + ', last ' + ago(looks[0].at) : 'no look yet',
|
|
383
|
-
tone: c.lastLook && !c.lastLook.first && c.lastLook.newFindings ? 'hot' : looks.length ? 'cool' : '' },
|
|
384
|
-
{ view: 'fixes', parts: ['You fixed something and want ', { hl: 'proof it is fixed' }],
|
|
385
|
-
state: c.proven ? plural(c.proven, 'fix', 'fixes') + ' proven' : 'none proven yet', tone: c.proven ? 'cool' : '' },
|
|
386
|
-
{ view: 'checks', parts: ['You want to ', { hl: 'switch a check' }, ' on or off, or run one'],
|
|
387
|
-
state: c.built + ' of ' + state.checks.length + ' built', tone: '' },
|
|
388
|
-
{ view: 'verification', parts: ['You want the ', { hl: 'Kryptheon Verified' }, ' badge'],
|
|
389
|
-
state: state.verification.state, tone: 'warm' },
|
|
390
|
-
{ view: 'storage', parts: ['You want to know ', { hl: 'where your records are kept' }],
|
|
391
|
-
state: 'on this machine only', tone: 'cool' },
|
|
392
|
-
];
|
|
393
|
-
items.forEach(function (it) {
|
|
394
|
-
var v = VIEWS.filter(function (x) { return x.id === it.view; })[0];
|
|
395
|
-
var row = el('button', 'g-row');
|
|
396
|
-
row.type = 'button';
|
|
397
|
-
row.appendChild(el('span', 'g-dot' + (it.tone ? ' ' + it.tone : '')));
|
|
398
|
-
var text = el('span');
|
|
399
|
-
rich(text, it.parts);
|
|
400
|
-
text.appendChild(el('span', 'g-state', it.state));
|
|
401
|
-
row.appendChild(text);
|
|
402
|
-
var to = el('span', 'g-to');
|
|
403
|
-
to.appendChild(el('span', 'ul', v.title));
|
|
404
|
-
to.appendChild(icon('arrow', 15));
|
|
405
|
-
row.appendChild(to);
|
|
406
|
-
row.addEventListener('click', function () { go(it.view); });
|
|
407
|
-
box.appendChild(row);
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
/* ----------------------------------- chart ----------------------------------- */
|
|
242
|
+
var STEP_WORD = { done: 'Done', todo: 'To do', waiting: 'Waiting', off: 'Switched off' };
|
|
243
|
+
var STEP_TONE = { done: 'leaf', todo: 'sun', waiting: 'amber', off: 'muted' };
|
|
412
244
|
|
|
413
|
-
function
|
|
414
|
-
var box = byId('
|
|
245
|
+
function renderSteps() {
|
|
246
|
+
var box = byId('steps');
|
|
415
247
|
clear(box);
|
|
416
|
-
|
|
417
|
-
var
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
var y = function (v) { return T + (H - T - B) * (1 - v / top); };
|
|
448
|
-
var svg = svgEl('svg', { viewBox: '0 0 ' + W + ' ' + H, preserveAspectRatio: 'none', role: 'img', 'aria-label': 'Recorded flows that held, per run' });
|
|
449
|
-
var defs = svgEl('defs');
|
|
450
|
-
var grad = svgEl('linearGradient', { id: 'kfill', x1: 0, y1: 0, x2: 0, y2: 1 });
|
|
451
|
-
grad.appendChild(svgEl('stop', { offset: '0%', 'stop-color': '#f39a5c', 'stop-opacity': 0.42 }));
|
|
452
|
-
grad.appendChild(svgEl('stop', { offset: '100%', 'stop-color': '#fbd3b5', 'stop-opacity': 0.04 }));
|
|
453
|
-
defs.appendChild(grad);
|
|
454
|
-
var stroke = svgEl('linearGradient', { id: 'kline', x1: 0, y1: 0, x2: 1, y2: 0 });
|
|
455
|
-
stroke.appendChild(svgEl('stop', { offset: '0%', 'stop-color': '#d49b33' }));
|
|
456
|
-
stroke.appendChild(svgEl('stop', { offset: '100%', 'stop-color': '#e5652b' }));
|
|
457
|
-
defs.appendChild(stroke);
|
|
458
|
-
svg.appendChild(defs);
|
|
459
|
-
var steps = top <= 4 ? top : 4;
|
|
460
|
-
for (var g = 0; g <= steps; g++) {
|
|
461
|
-
var v = Math.round((top * g) / steps);
|
|
462
|
-
svg.appendChild(svgEl('line', { x1: L, x2: W - R, y1: y(v), y2: y(v), stroke: 'rgba(138,109,94,0.14)', 'stroke-width': 1 }));
|
|
463
|
-
var lab = svgEl('text', { x: L - 10, y: y(v) + 4, 'text-anchor': 'end', 'font-size': 11, fill: '#8a6d5e' });
|
|
464
|
-
lab.textContent = String(v);
|
|
465
|
-
svg.appendChild(lab);
|
|
466
|
-
}
|
|
467
|
-
var pts = runs.map(function (r, i) { return [x(i), y(r.passed || 0)]; });
|
|
468
|
-
var line = pts.map(function (p, i) { return (i ? 'L' : 'M') + p[0].toFixed(1) + ' ' + p[1].toFixed(1); }).join(' ');
|
|
469
|
-
var area = line + ' L' + pts[pts.length - 1][0].toFixed(1) + ' ' + (H - B) + ' L' + pts[0][0].toFixed(1) + ' ' + (H - B) + ' Z';
|
|
470
|
-
svg.appendChild(svgEl('path', { d: area, fill: 'url(#kfill)' }));
|
|
471
|
-
svg.appendChild(svgEl('path', { d: line, fill: 'none', stroke: 'url(#kline)', 'stroke-width': 3, 'stroke-linejoin': 'round', 'stroke-linecap': 'round' }));
|
|
472
|
-
runs.forEach(function (r, i) {
|
|
473
|
-
if (r.failed) svg.appendChild(svgEl('circle', { cx: pts[i][0], cy: pts[i][1], r: 5, fill: '#c2412d', stroke: '#fffaf5', 'stroke-width': 2 }));
|
|
474
|
-
});
|
|
475
|
-
// All on one day: the date says nothing, the time does.
|
|
476
|
-
var sameDay = shortDate(runs[0].runAt) === shortDate(runs[runs.length - 1].runAt);
|
|
477
|
-
var labels = Math.min(runs.length, 6);
|
|
478
|
-
var lastLabel = '';
|
|
479
|
-
for (var k = 0; k < labels; k++) {
|
|
480
|
-
var idx = labels === 1 ? 0 : Math.round((k * (runs.length - 1)) / (labels - 1));
|
|
481
|
-
var text = sameDay
|
|
482
|
-
? new Date(runs[idx].runAt).toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' })
|
|
483
|
-
: shortDate(runs[idx].runAt);
|
|
484
|
-
// Two runs in the same minute (or day) would print the same label twice.
|
|
485
|
-
if (text === lastLabel) continue;
|
|
486
|
-
lastLabel = text;
|
|
487
|
-
var t = svgEl('text', { x: x(idx), y: H - 6, 'text-anchor': 'middle', 'font-size': 11, fill: '#8a6d5e' });
|
|
488
|
-
t.textContent = text;
|
|
489
|
-
svg.appendChild(t);
|
|
490
|
-
}
|
|
491
|
-
var ring = svgEl('circle', { r: 7, fill: '#fffaf5', stroke: '#e5652b', 'stroke-width': 3 });
|
|
492
|
-
svg.appendChild(ring);
|
|
493
|
-
box.appendChild(svg);
|
|
494
|
-
var stem = el('div', 'stem');
|
|
495
|
-
var tip = el('div', 'tip');
|
|
496
|
-
var tDate = el('div', 't-date');
|
|
497
|
-
var tVal = el('div', 't-val');
|
|
498
|
-
tip.appendChild(tDate);
|
|
499
|
-
tip.appendChild(tVal);
|
|
500
|
-
box.appendChild(stem);
|
|
501
|
-
box.appendChild(tip);
|
|
502
|
-
|
|
503
|
-
function point(i) {
|
|
504
|
-
var r = runs[i];
|
|
505
|
-
ring.setAttribute('cx', pts[i][0]);
|
|
506
|
-
ring.setAttribute('cy', pts[i][1]);
|
|
507
|
-
ring.setAttribute('stroke', r.failed ? '#c2412d' : '#e5652b');
|
|
508
|
-
var rect = svg.getBoundingClientRect();
|
|
509
|
-
var px = (pts[i][0] / W) * rect.width;
|
|
510
|
-
var py = (pts[i][1] / H) * rect.height;
|
|
511
|
-
tDate.textContent = when(r.runAt);
|
|
512
|
-
clear(tVal);
|
|
513
|
-
var dot = el('i');
|
|
514
|
-
dot.style.background = r.failed ? '#c2412d' : '#3b8657';
|
|
515
|
-
tVal.appendChild(dot);
|
|
516
|
-
tVal.appendChild(el('span', null, (r.passed || 0) + ' held' + (r.failed ? ', ' + r.failed + ' broke' : '')));
|
|
517
|
-
tip.style.left = px + 'px';
|
|
518
|
-
tip.style.top = Math.max(0, py - 22) + 'px';
|
|
519
|
-
stem.style.left = px + 'px';
|
|
520
|
-
stem.style.top = Math.max(0, py - 22) + 'px';
|
|
521
|
-
stem.style.height = '14px';
|
|
522
|
-
}
|
|
523
|
-
svg.addEventListener('mousemove', function (e) {
|
|
524
|
-
var rect = svg.getBoundingClientRect();
|
|
525
|
-
var mx = ((e.clientX - rect.left) / rect.width) * W;
|
|
526
|
-
var best = 0;
|
|
527
|
-
pts.forEach(function (p, i) { if (Math.abs(p[0] - mx) < Math.abs(pts[best][0] - mx)) best = i; });
|
|
528
|
-
point(best);
|
|
529
|
-
});
|
|
530
|
-
requestAnimationFrame(function () { if (!byId('view-dashboard').hidden) point(runs.length - 1); });
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
/* ----------------------------------- donut ----------------------------------- */
|
|
534
|
-
|
|
535
|
-
function renderDonut() {
|
|
536
|
-
var box = byId('donut');
|
|
537
|
-
clear(box);
|
|
538
|
-
var by = { regression: 0, frontend: 0, data: 0 };
|
|
539
|
-
state.findings.forEach(function (f) { if (by[f.check] !== undefined) by[f.check]++; });
|
|
540
|
-
var total = state.findings.length;
|
|
541
|
-
var svg = svgEl('svg', { viewBox: '0 0 120 120', role: 'img', 'aria-label': 'Open findings by check' });
|
|
542
|
-
var R = 44;
|
|
543
|
-
var C = 2 * Math.PI * R;
|
|
544
|
-
svg.appendChild(svgEl('circle', { cx: 60, cy: 60, r: R, fill: 'none', stroke: '#f7e4d4', 'stroke-width': 16 }));
|
|
545
|
-
var offset = 0;
|
|
546
|
-
Object.keys(by).forEach(function (k) {
|
|
547
|
-
if (!by[k]) return;
|
|
548
|
-
var len = (by[k] / total) * C;
|
|
549
|
-
svg.appendChild(svgEl('circle', {
|
|
550
|
-
cx: 60, cy: 60, r: R, fill: 'none', stroke: CHECK_COLOR[k], 'stroke-width': 16,
|
|
551
|
-
'stroke-dasharray': Math.max(0, len - 2).toFixed(2) + ' ' + (C - Math.max(0, len - 2)).toFixed(2),
|
|
552
|
-
'stroke-dashoffset': (-offset).toFixed(2), transform: 'rotate(-90 60 60)', 'stroke-linecap': 'butt',
|
|
553
|
-
}));
|
|
554
|
-
offset += len;
|
|
555
|
-
});
|
|
556
|
-
box.appendChild(svg);
|
|
557
|
-
var center = el('div', 'center');
|
|
558
|
-
center.appendChild(el('b', null, total));
|
|
559
|
-
center.appendChild(el('span', null, 'open'));
|
|
560
|
-
box.appendChild(center);
|
|
561
|
-
var legend = byId('legend');
|
|
562
|
-
clear(legend);
|
|
563
|
-
Object.keys(by).forEach(function (k) {
|
|
564
|
-
var row = el('div', 'row');
|
|
565
|
-
var dot = el('i');
|
|
566
|
-
dot.style.background = CHECK_COLOR[k];
|
|
567
|
-
row.appendChild(dot);
|
|
568
|
-
row.appendChild(el('span', null, CHECK_NAME[k]));
|
|
569
|
-
row.appendChild(el('span', 'v', by[k]));
|
|
570
|
-
legend.appendChild(row);
|
|
248
|
+
(state.steps || []).forEach(function (s, i) {
|
|
249
|
+
var row = el('div', 'step ' + s.state);
|
|
250
|
+
var num = el('div', 'step-num');
|
|
251
|
+
if (s.state === 'done') num.appendChild(icon('check', 18));
|
|
252
|
+
else num.textContent = String(i + 1).padStart(2, '0');
|
|
253
|
+
row.appendChild(num);
|
|
254
|
+
var body = el('div', 'step-body');
|
|
255
|
+
var head = el('div', 'step-head');
|
|
256
|
+
head.appendChild(el('div', 'step-title', s.title));
|
|
257
|
+
head.appendChild(el('span', 'tag ' + STEP_TONE[s.state], STEP_WORD[s.state] || s.state));
|
|
258
|
+
body.appendChild(head);
|
|
259
|
+
body.appendChild(el('div', 'step-why', s.why + (s.at ? ' - last ' + ago(s.at) : '') + (s.count ? ' - ' + plural(s.count, 'recording') + ' held' : '')));
|
|
260
|
+
if (s.action && s.action.kind === 'run') {
|
|
261
|
+
var run = button('sun sm', 'play', s.action.label, function () { runChecks([s.action.check], run); });
|
|
262
|
+
run.setAttribute('data-action', 'run');
|
|
263
|
+
run.disabled = busy;
|
|
264
|
+
body.appendChild(run);
|
|
265
|
+
} else if (s.action && s.action.kind === 'command') {
|
|
266
|
+
var cmd = el('div', 'cmd');
|
|
267
|
+
cmd.appendChild(icon('terminal', 15));
|
|
268
|
+
cmd.appendChild(el('code', null, s.action.command));
|
|
269
|
+
var cp = el('button', 'cmd-copy');
|
|
270
|
+
cp.type = 'button';
|
|
271
|
+
cp.title = 'Copy';
|
|
272
|
+
cp.appendChild(icon('copy', 14));
|
|
273
|
+
cp.addEventListener('click', function () { copy(s.action.command, 'Command copied - run it in your terminal'); });
|
|
274
|
+
cmd.appendChild(cp);
|
|
275
|
+
body.appendChild(cmd);
|
|
276
|
+
}
|
|
277
|
+
row.appendChild(body);
|
|
278
|
+
box.appendChild(row);
|
|
571
279
|
});
|
|
572
280
|
}
|
|
573
281
|
|
|
574
282
|
/* ---------------------------------- findings ---------------------------------- */
|
|
575
283
|
|
|
284
|
+
var statusFilter = 'all';
|
|
285
|
+
var query = '';
|
|
286
|
+
|
|
576
287
|
function renderStatusFilter() {
|
|
577
288
|
var box = byId('statusFilter');
|
|
578
289
|
clear(box);
|
|
@@ -583,7 +294,6 @@
|
|
|
583
294
|
box.appendChild(b);
|
|
584
295
|
});
|
|
585
296
|
}
|
|
586
|
-
|
|
587
297
|
function statusTag(f) {
|
|
588
298
|
var t = el('span', 'tag ' + (f.status === 'confirmed' ? 'ember' : 'amber'));
|
|
589
299
|
t.appendChild(el('span', f.status === 'confirmed' ? 'g-dot' : 'g-half'));
|
|
@@ -594,23 +304,10 @@
|
|
|
594
304
|
var tone = /CRITICAL/.test(sev) ? 'ember' : sev === 'HIGH' ? 'sun' : sev === 'MEDIUM' ? 'amber' : 'leaf';
|
|
595
305
|
return el('span', 'tag ' + tone, sev);
|
|
596
306
|
}
|
|
597
|
-
function confidence(level) {
|
|
598
|
-
var box = el('div', 'conf');
|
|
599
|
-
var m = el('div', 'meter sunken');
|
|
600
|
-
var bar = el('i');
|
|
601
|
-
bar.style.width = (level === 'high' ? 100 : level === 'medium' ? 66 : 33) + '%';
|
|
602
|
-
m.appendChild(bar);
|
|
603
|
-
box.appendChild(m);
|
|
604
|
-
box.appendChild(el('span', null, level));
|
|
605
|
-
return box;
|
|
606
|
-
}
|
|
607
307
|
|
|
608
308
|
var VERDICT_TONE = { fixed: 'leaf', 'still open': 'ember', 'gone with its file': 'amber', 'could not confirm': 'amber' };
|
|
609
309
|
|
|
610
|
-
function
|
|
611
|
-
var tr = el('tr', 'detail');
|
|
612
|
-
var td = el('td');
|
|
613
|
-
td.colSpan = 6;
|
|
310
|
+
function detailBox(f) {
|
|
614
311
|
var box = el('div', 'detail-box');
|
|
615
312
|
box.appendChild(el('div', 'd-step', 'WHAT IS WRONG'));
|
|
616
313
|
box.appendChild(el('div', 'd-text', f.detail || f.headline));
|
|
@@ -635,70 +332,53 @@
|
|
|
635
332
|
if (f.attempts && f.attempts.length) {
|
|
636
333
|
var list = el('div', 'attempts');
|
|
637
334
|
f.attempts.slice(-4).reverse().forEach(function (a) {
|
|
638
|
-
var
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
list.appendChild(row);
|
|
335
|
+
var r = el('div');
|
|
336
|
+
r.appendChild(el('span', 'tag ' + (VERDICT_TONE[a.verdict] || ''), a.verdict));
|
|
337
|
+
r.appendChild(el('span', null, 'Re-checked ' + ago(a.at) + (a.why ? ' - ' + a.why : '') + (a.newProblems ? ' - ' + plural(a.newProblems, 'new problem') : '')));
|
|
338
|
+
list.appendChild(r);
|
|
643
339
|
});
|
|
644
340
|
box.appendChild(list);
|
|
645
341
|
}
|
|
646
|
-
|
|
647
|
-
tr.appendChild(td);
|
|
648
|
-
return tr;
|
|
342
|
+
return box;
|
|
649
343
|
}
|
|
650
344
|
|
|
651
345
|
function renderFindings() {
|
|
652
|
-
var body = byId('
|
|
346
|
+
var body = byId('findingList');
|
|
653
347
|
clear(body);
|
|
654
|
-
var q = query.trim().toLowerCase();
|
|
655
348
|
var shown = state.findings.filter(function (f) {
|
|
656
349
|
if (statusFilter === 'confirmed' && f.status !== 'confirmed') return false;
|
|
657
350
|
if (statusFilter === 'verify' && f.status === 'confirmed') return false;
|
|
658
|
-
|
|
659
|
-
return [f.headline, f.where, f.detail, f.check, f.id].join(' ').toLowerCase().indexOf(q) !== -1;
|
|
351
|
+
return true;
|
|
660
352
|
});
|
|
661
353
|
byId('findingBadge').textContent = shown.length + (shown.length !== state.findings.length ? ' of ' + state.findings.length : '');
|
|
662
354
|
if (!shown.length) {
|
|
663
|
-
|
|
664
|
-
var td = el('td', 'empty', state.findings.length ? 'Nothing matches.' : 'Nothing found by the checks that are on and have run. That is not the same as nothing wrong - see which checks are not built yet.');
|
|
665
|
-
td.colSpan = 6;
|
|
666
|
-
tr.appendChild(td);
|
|
667
|
-
body.appendChild(tr);
|
|
355
|
+
body.appendChild(el('p', 'empty', state.findings.length ? 'Nothing matches.' : 'Nothing found by the checks that are on and have run. That is not the same as nothing wrong - see which checks are not built yet.'));
|
|
668
356
|
return;
|
|
669
357
|
}
|
|
670
358
|
shown.forEach(function (f) {
|
|
671
|
-
var
|
|
672
|
-
var
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
var
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
tr.appendChild(c6);
|
|
689
|
-
tr.addEventListener('click', function () {
|
|
690
|
-
openRow = openRow === f.id ? null : f.id;
|
|
691
|
-
renderFindings();
|
|
692
|
-
});
|
|
693
|
-
body.appendChild(tr);
|
|
694
|
-
if (openRow === f.id) body.appendChild(detailRow(f));
|
|
359
|
+
var card = el('div', 'finding ' + (f.status === 'confirmed' ? 'bad' : 'warn'));
|
|
360
|
+
var head = el('button', 'finding-head');
|
|
361
|
+
head.type = 'button';
|
|
362
|
+
var left = el('div', 'finding-left');
|
|
363
|
+
left.appendChild(el('div', 'f-title', f.headline));
|
|
364
|
+
var meta = el('div', 'f-meta');
|
|
365
|
+
meta.appendChild(sevTag(f.severity));
|
|
366
|
+
meta.appendChild(statusTag(f));
|
|
367
|
+
meta.appendChild(el('span', 'tag ' + (CHECK_TONE[f.check] || ''), CHECK_NAME[f.check] || f.check));
|
|
368
|
+
meta.appendChild(el('span', 'f-where', f.where));
|
|
369
|
+
left.appendChild(meta);
|
|
370
|
+
head.appendChild(left);
|
|
371
|
+
head.appendChild(icon(openRow === f.id ? 'change' : 'arrow', 18));
|
|
372
|
+
head.addEventListener('click', function () { openRow = openRow === f.id ? null : f.id; renderFindings(); });
|
|
373
|
+
card.appendChild(head);
|
|
374
|
+
if (openRow === f.id) card.appendChild(detailBox(f));
|
|
375
|
+
body.appendChild(card);
|
|
695
376
|
});
|
|
696
377
|
}
|
|
697
378
|
|
|
698
379
|
/* -------------------------------- other views -------------------------------- */
|
|
699
380
|
|
|
700
381
|
var LOOKS_SHOWN = 12;
|
|
701
|
-
|
|
702
382
|
function renderTimeline() {
|
|
703
383
|
var box = byId('timeline');
|
|
704
384
|
clear(box);
|
|
@@ -730,11 +410,7 @@
|
|
|
730
410
|
box.appendChild(item);
|
|
731
411
|
});
|
|
732
412
|
if (looks.length > LOOKS_SHOWN) {
|
|
733
|
-
|
|
734
|
-
allLooks = !allLooks;
|
|
735
|
-
renderTimeline();
|
|
736
|
-
});
|
|
737
|
-
box.appendChild(more);
|
|
413
|
+
box.appendChild(button('sm', null, allLooks ? 'Show the latest ' + LOOKS_SHOWN : 'Show all ' + looks.length, function () { allLooks = !allLooks; renderTimeline(); }));
|
|
738
414
|
}
|
|
739
415
|
}
|
|
740
416
|
|
|
@@ -743,23 +419,19 @@
|
|
|
743
419
|
clear(box);
|
|
744
420
|
var list = state.proven || [];
|
|
745
421
|
byId('fixBadge').textContent = String(list.length);
|
|
746
|
-
if (!list.length) {
|
|
747
|
-
box.appendChild(el('p', 'empty', 'No fix has been proven yet. Open a finding, fix it, and press Re-check.'));
|
|
748
|
-
return;
|
|
749
|
-
}
|
|
422
|
+
if (!list.length) { box.appendChild(el('p', 'empty', 'No fix has been proven yet. Open a finding, fix it, and press Re-check.')); return; }
|
|
750
423
|
list.forEach(function (p) {
|
|
751
|
-
var row = el('div', '
|
|
424
|
+
var row = el('div', 'line-row');
|
|
752
425
|
row.appendChild(el('span', 'tag leaf', 'FIXED'));
|
|
753
|
-
var text = el('div', '
|
|
426
|
+
var text = el('div', 'lr-text');
|
|
754
427
|
text.appendChild(el('div', null, p.headline));
|
|
755
|
-
text.appendChild(el('div', '
|
|
428
|
+
text.appendChild(el('div', 'lr-sub', p.where + ' - proven ' + ago(p.at) + (p.newProblems ? ' - ' + plural(p.newProblems, 'new problem') + ' at the time' : '')));
|
|
756
429
|
row.appendChild(text);
|
|
757
430
|
box.appendChild(row);
|
|
758
431
|
});
|
|
759
432
|
}
|
|
760
433
|
|
|
761
434
|
var STATE_TONE = { 'problems found': 'ember', 'things to check': 'amber', 'last run held': 'leaf', 'nothing found': 'leaf' };
|
|
762
|
-
|
|
763
435
|
function checkSwitch(c) {
|
|
764
436
|
var sw = el('label', 'switch');
|
|
765
437
|
var input = el('input');
|
|
@@ -772,7 +444,6 @@
|
|
|
772
444
|
sw.appendChild(el('span', 'track'));
|
|
773
445
|
return sw;
|
|
774
446
|
}
|
|
775
|
-
|
|
776
447
|
function renderChecks() {
|
|
777
448
|
var box = byId('checkList');
|
|
778
449
|
clear(box);
|
|
@@ -782,31 +453,31 @@
|
|
|
782
453
|
state.checks.forEach(function (c) {
|
|
783
454
|
if (!c.available) {
|
|
784
455
|
missing++;
|
|
785
|
-
var row = el('div', 'na
|
|
456
|
+
var row = el('div', 'na');
|
|
786
457
|
row.appendChild(checkSwitch(c));
|
|
787
|
-
var
|
|
788
|
-
|
|
789
|
-
row.appendChild(
|
|
458
|
+
var nm = el('div', null, c.label);
|
|
459
|
+
nm.appendChild(el('span', 'na-lock', 'NOT AVAILABLE'));
|
|
460
|
+
row.appendChild(nm);
|
|
790
461
|
na.appendChild(row);
|
|
791
462
|
return;
|
|
792
463
|
}
|
|
793
|
-
var card = el('div', 'check
|
|
464
|
+
var card = el('div', 'check');
|
|
794
465
|
var top = el('div', 'c-top');
|
|
795
466
|
top.appendChild(el('div', 'c-name', c.label));
|
|
796
467
|
top.appendChild(checkSwitch(c));
|
|
797
468
|
card.appendChild(top);
|
|
798
|
-
|
|
469
|
+
var st = el('div');
|
|
470
|
+
st.appendChild(el('span', 'tag ' + (STATE_TONE[c.state] || ''), c.state));
|
|
471
|
+
card.appendChild(st);
|
|
799
472
|
if (c.what) card.appendChild(el('div', 'c-what', c.what));
|
|
800
|
-
var foot = el('div', 'c-foot');
|
|
801
473
|
if (c.runnable && c.enabled) {
|
|
802
474
|
var run = button('sm', 'play', 'Run now', function () { runChecks([c.id], run); });
|
|
803
475
|
run.setAttribute('data-action', 'run');
|
|
804
476
|
run.disabled = busy;
|
|
805
|
-
|
|
477
|
+
card.appendChild(run);
|
|
806
478
|
} else if (!c.enabled) {
|
|
807
|
-
|
|
479
|
+
card.appendChild(el('div', 'c-what', 'Switched off - it will not run.'));
|
|
808
480
|
}
|
|
809
|
-
card.appendChild(foot);
|
|
810
481
|
box.appendChild(card);
|
|
811
482
|
});
|
|
812
483
|
byId('naBadge').textContent = String(missing);
|
|
@@ -816,43 +487,19 @@
|
|
|
816
487
|
var box = byId('runList');
|
|
817
488
|
clear(box);
|
|
818
489
|
byId('runBadge').textContent = String(state.runs.length);
|
|
819
|
-
if (!state.runs.length) {
|
|
820
|
-
box.appendChild(el('p', 'empty', 'No recorded flows have been replayed yet. Record one with npx kryptheon record, then press Run checks.'));
|
|
821
|
-
return;
|
|
822
|
-
}
|
|
490
|
+
if (!state.runs.length) { box.appendChild(el('p', 'empty', 'No recorded flows have been replayed yet. Record one with npx kryptheon record, then press Run checks.')); return; }
|
|
823
491
|
state.runs.slice(0, 30).forEach(function (r) {
|
|
824
|
-
var row = el('div', '
|
|
492
|
+
var row = el('div', 'line-row');
|
|
825
493
|
row.appendChild(el('span', 'tag ' + (r.failed ? 'ember' : 'leaf'), r.failed ? r.failed + ' broke' : 'held'));
|
|
826
|
-
var text = el('div', '
|
|
494
|
+
var text = el('div', 'lr-text');
|
|
827
495
|
text.appendChild(el('div', null, (r.passed || 0) + ' held, ' + (r.failed || 0) + ' broke'));
|
|
828
|
-
text.appendChild(el('div', '
|
|
496
|
+
text.appendChild(el('div', 'lr-sub', when(r.runAt)));
|
|
829
497
|
row.appendChild(text);
|
|
830
498
|
if (r.failed) row.appendChild(button('sm', 'arrow', 'See the finding', function () { go('findings', 'confirmed'); }));
|
|
831
499
|
box.appendChild(row);
|
|
832
500
|
});
|
|
833
501
|
}
|
|
834
502
|
|
|
835
|
-
var BADGE_STATES = [
|
|
836
|
-
['Active', 'This exact version was checked and held.'],
|
|
837
|
-
['Outdated', 'The project changed after it was verified.'],
|
|
838
|
-
['Revalidation required', 'A check found something new - verify again.'],
|
|
839
|
-
['Revoked', 'Withdrawn. The badge says so publicly.'],
|
|
840
|
-
];
|
|
841
|
-
|
|
842
|
-
function renderVerification() {
|
|
843
|
-
byId('verifyBadge').textContent = state.verification.state;
|
|
844
|
-
byId('verifyText').textContent = 'Not verified. ' + state.verification.why +
|
|
845
|
-
' When it is built, a verification will stand for one version of this project, and a changed version will need it again. These are the states it will have:';
|
|
846
|
-
var box = byId('verifyStates');
|
|
847
|
-
clear(box);
|
|
848
|
-
BADGE_STATES.forEach(function (s) {
|
|
849
|
-
var card = el('div', 'state-card sunken');
|
|
850
|
-
card.appendChild(el('b', null, s[0]));
|
|
851
|
-
card.appendChild(el('span', null, s[1]));
|
|
852
|
-
box.appendChild(card);
|
|
853
|
-
});
|
|
854
|
-
}
|
|
855
|
-
|
|
856
503
|
var STORE_FILES = [
|
|
857
504
|
['baselines.json', 'what each page looked like the last time it worked'],
|
|
858
505
|
['history.jsonl', 'every run of your recorded flows'],
|
|
@@ -864,10 +511,8 @@
|
|
|
864
511
|
['fixes.jsonl', 'every re-check and its verdict'],
|
|
865
512
|
['config.json', 'which checks are switched on'],
|
|
866
513
|
];
|
|
867
|
-
|
|
868
514
|
function renderStorage() {
|
|
869
|
-
byId('storeNote').textContent = 'Everything on this dashboard is read from this folder on your machine. Nothing is sent anywhere. '
|
|
870
|
-
'Your project keeps only its recordings in tests/.';
|
|
515
|
+
byId('storeNote').textContent = 'Everything on this dashboard is read from this folder on your machine. Nothing is sent anywhere. Your project keeps only its recordings in tests/.';
|
|
871
516
|
byId('storePath').textContent = state.project.storeDir;
|
|
872
517
|
var box = byId('storeFiles');
|
|
873
518
|
clear(box);
|
|
@@ -879,15 +524,6 @@
|
|
|
879
524
|
});
|
|
880
525
|
}
|
|
881
526
|
|
|
882
|
-
function renderWhen() {
|
|
883
|
-
Object.keys(WHEN).forEach(function (id) {
|
|
884
|
-
var box = byId('when-' + id);
|
|
885
|
-
clear(box);
|
|
886
|
-
box.appendChild(el('b', 'lbl', 'THIS SECTION'));
|
|
887
|
-
box.appendChild(rich(el('span'), WHEN[id]));
|
|
888
|
-
});
|
|
889
|
-
}
|
|
890
|
-
|
|
891
527
|
/* ---------------------------------- actions ---------------------------------- */
|
|
892
528
|
|
|
893
529
|
function recheck(f, btn) {
|
|
@@ -898,21 +534,13 @@
|
|
|
898
534
|
btn.appendChild(el('span', null, f.check === 'regression' ? 'Replaying...' : 'Re-checking...'));
|
|
899
535
|
post('/api/recheck', { id: f.id }).then(function (r) {
|
|
900
536
|
var words = { fixed: 'FIXED', 'still open': 'STILL OPEN', 'gone with its file': 'GONE WITH ITS FILE - not the same as fixed', 'could not confirm': 'COULD NOT CONFIRM' };
|
|
901
|
-
toast((words[r.verdict] || r.verdict) + (r.why ? ' - ' + r.why : '') +
|
|
902
|
-
|
|
903
|
-
}).catch(function (err) {
|
|
904
|
-
toast('Re-check failed: ' + err.message);
|
|
905
|
-
}).then(function () {
|
|
906
|
-
setBusy(false);
|
|
907
|
-
return load();
|
|
908
|
-
});
|
|
537
|
+
toast((words[r.verdict] || r.verdict) + (r.why ? ' - ' + r.why : '') + (r.newProblems && r.newProblems.length ? ' - ' + plural(r.newProblems.length, 'new problem') : ''));
|
|
538
|
+
}).catch(function (err) { toast('Re-check failed: ' + err.message); }).then(function () { setBusy(false); return load(); });
|
|
909
539
|
}
|
|
910
540
|
|
|
911
541
|
function runChecks(ids, btn) {
|
|
912
542
|
if (busy || !ids.length) return;
|
|
913
543
|
setBusy(true);
|
|
914
|
-
var error = byId('checkError');
|
|
915
|
-
error.hidden = true;
|
|
916
544
|
clear(btn);
|
|
917
545
|
btn.appendChild(el('span', 'spin'));
|
|
918
546
|
btn.appendChild(el('span', null, 'Running...'));
|
|
@@ -920,84 +548,55 @@
|
|
|
920
548
|
return chain.then(function () { return post('/api/run', { id: id }); });
|
|
921
549
|
}, Promise.resolve()).then(function () {
|
|
922
550
|
toast('Finished: ' + ids.map(function (id) { return CHECK_NAME[id] || id; }).join(', '));
|
|
923
|
-
}).catch(function (err) {
|
|
924
|
-
error.textContent = 'Run failed: ' + err.message;
|
|
925
|
-
error.hidden = false;
|
|
926
|
-
toast('Run failed: ' + err.message);
|
|
927
|
-
}).then(function () {
|
|
928
|
-
setBusy(false);
|
|
929
|
-
return load();
|
|
930
|
-
});
|
|
551
|
+
}).catch(function (err) { toast('Run failed: ' + err.message); }).then(function () { setBusy(false); return load(); });
|
|
931
552
|
}
|
|
932
553
|
|
|
933
554
|
function setCheck(id, on, input) {
|
|
934
|
-
var error = byId('checkError');
|
|
935
|
-
error.hidden = true;
|
|
936
555
|
post('/api/check', { id: id, on: on }).then(function () {
|
|
937
556
|
toast((on ? 'Switched on: ' : 'Switched off: ') + (CHECK_NAME[id] || id));
|
|
938
557
|
return load();
|
|
939
|
-
}).catch(function (err) {
|
|
940
|
-
input.checked = !on;
|
|
941
|
-
error.textContent = 'Not saved: ' + err.message;
|
|
942
|
-
error.hidden = false;
|
|
943
|
-
});
|
|
558
|
+
}).catch(function (err) { input.checked = !on; toast('Not saved: ' + err.message); });
|
|
944
559
|
}
|
|
945
560
|
|
|
946
|
-
byId('search').addEventListener('input', function (e) {
|
|
947
|
-
query = e.target.value;
|
|
948
|
-
if (query.trim() && currentView() !== 'findings') go('findings');
|
|
949
|
-
if (state) renderFindings();
|
|
950
|
-
});
|
|
951
|
-
byId('bell').addEventListener('click', function () { go('changes'); });
|
|
952
561
|
byId('runAll').addEventListener('click', function () {
|
|
953
562
|
if (!state) return;
|
|
954
563
|
var ids = state.checks.filter(function (c) { return c.runnable && c.enabled; }).map(function (c) { return c.id; });
|
|
955
|
-
if (!ids.length) {
|
|
956
|
-
toast('Nothing can run from here yet - switch a check on, or record a flow.');
|
|
957
|
-
return;
|
|
958
|
-
}
|
|
564
|
+
if (!ids.length) { toast('Nothing can run from here yet - switch a check on, or record a flow.'); return; }
|
|
959
565
|
runChecks(ids, byId('runAll'));
|
|
960
566
|
});
|
|
961
567
|
|
|
962
568
|
/* ----------------------------------- load ----------------------------------- */
|
|
963
569
|
|
|
570
|
+
function renderTop() {
|
|
571
|
+
var run = byId('runAll');
|
|
572
|
+
if (!busy) { clear(run); run.appendChild(icon('play', 15)); run.appendChild(el('span', null, 'Run checks')); }
|
|
573
|
+
}
|
|
574
|
+
|
|
964
575
|
function render(next) {
|
|
965
576
|
if (!next) return;
|
|
966
577
|
state = next;
|
|
967
578
|
renderSide();
|
|
968
579
|
renderTop();
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
renderGuide();
|
|
972
|
-
renderRange();
|
|
973
|
-
renderChart();
|
|
974
|
-
renderDonut();
|
|
580
|
+
renderNow();
|
|
581
|
+
renderSteps();
|
|
975
582
|
renderStatusFilter();
|
|
976
583
|
renderFindings();
|
|
977
584
|
renderTimeline();
|
|
978
585
|
renderFixes();
|
|
979
586
|
renderChecks();
|
|
980
587
|
renderRuns();
|
|
981
|
-
renderVerification();
|
|
982
588
|
renderStorage();
|
|
983
589
|
}
|
|
984
590
|
|
|
985
591
|
function load() {
|
|
986
592
|
return fetch('/api/state', { cache: 'no-store' })
|
|
987
|
-
.then(function (res) {
|
|
988
|
-
if (!res.ok) throw new Error('the dashboard answered ' + res.status);
|
|
989
|
-
return res.json();
|
|
990
|
-
})
|
|
593
|
+
.then(function (res) { if (!res.ok) throw new Error('the dashboard answered ' + res.status); return res.json(); })
|
|
991
594
|
.then(render)
|
|
992
|
-
.catch(function (err) {
|
|
993
|
-
byId('subline').textContent = 'Could not load: ' + err.message;
|
|
994
|
-
});
|
|
595
|
+
.catch(function (err) { byId('subline').textContent = 'Could not load: ' + err.message; });
|
|
995
596
|
}
|
|
996
597
|
|
|
997
|
-
renderWhen();
|
|
998
598
|
showView();
|
|
999
599
|
load();
|
|
1000
|
-
// Not while something runs, and not while a finding is open being read
|
|
1001
|
-
// a re-render would take the spinner away or close what is being read.
|
|
600
|
+
// Not while something runs, and not while a finding is open being read.
|
|
1002
601
|
setInterval(function () { if (!busy && !openRow) load(); }, 5000);
|
|
1003
602
|
})();
|