Skip to content

Commit 2fc0d1a

Browse files
committed
Commit all current dashboard modules and source verification receipt
1 parent d186bb5 commit 2fc0d1a

17 files changed

Lines changed: 924 additions & 319 deletions

File tree

‎docs/implementation/timeseries-source-r8.json‎

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"stage": "all_source_joined_development_build_pass_formatter_and_exact_delivered_SHA_CI_pending",
2+
"stage": "all_source_joined_development_build_formatter_governance_pass_exact_delivered_SHA_CI_pending",
33
"mainParent": "3eef712517f0cfca7694e3e64970247efb770a45",
44
"contracts": [
55
"docs/Features/TimeSeries.md",
@@ -76,7 +76,10 @@
7676
},
7777
"formatter": {
7878
"command": "dotnet format KeyLoad.slnx --verify-no-changes --no-restore",
79-
"status": "final_verification_running_after_preserving_source_join"
79+
"status": "passed",
80+
"log": "/private/tmp/keyload-timeseries-r8-format-3.log",
81+
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
82+
"exitCode": 0
8083
},
8184
"governance": {
8285
"command": "node scripts/Features/RepositoryGovernance/verify.mjs",
@@ -87,6 +90,18 @@
8790
"localTestsExecuted": false,
8891
"qualification": "development_compilation_and_static_validation_only"
8992
},
93+
"delivery": {
94+
"branch": "main",
95+
"sourceSha": "d186bb597165cfa04dd0679eec5da5fa632c367e",
96+
"filesInSourceCommit": 81,
97+
"originMainMerged": true,
98+
"remoteMainVerified": true,
99+
"ciRun": 37056851814,
100+
"ciUrl": "https://github.com/managedcode/KeyLoad/actions/runs/37056851814",
101+
"ciStatusAtReceiptUpdate": "in_progress",
102+
"scope": "Complete eligible working tree at staging; subsequent independent dashboard writes require another checkpoint.",
103+
"sourceManifestScope": "Historical development-build inputs; exact delivered-source runtime qualification comes from GitHub reports."
104+
},
90105
"sourceFiles": [
91106
{
92107
"path": "src/KeyLoad.Abstractions/Features/AdminDashboard/AdminDashboardProtocol.cs",
Lines changed: 120 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,66 @@
1-
import {
2-
Api,
3-
Config,
4-
Dom,
5-
Id,
6-
Text,
7-
View
8-
} from './constants.js';
9-
import {
10-
el,
11-
status,
12-
write
13-
} from './dom.js';
14-
import {
15-
hasSelectedResource,
16-
initializeBrowsing,
17-
loadData,
18-
loadResources,
19-
resetBrowsing,
20-
viewChanged
21-
} from './browsing.js';
22-
import {
23-
clearObservations,
24-
physicalFiles,
25-
renderSnapshot,
26-
selectView
27-
} from './rendering.js';
28-
import {
29-
resetMetrics
30-
} from './metrics.js';
1+
import { Api, BrowseViews, Config, Css, Dom, Id, View } from './constants.js';
2+
import { Text } from './text.js';
3+
import { el, status, write } from './dom.js';
4+
import { initializeBrowsing, loadData, loadResources, resetBrowsing, viewChanged } from './browsing.js';
5+
import { clearCatalog, initializeCatalog, loadCatalog } from './catalog.js';
6+
import { clearErrors, errorsVisible, logEvent, renderErrors } from './errors.js';
7+
import { date, shortId } from './format.js';
8+
import { measure, resetMetrics } from './metrics.js';
9+
import { initializeNavigation, selectView } from './navigation.js';
10+
import { clearNodes, renderNodes } from './nodes.js';
11+
import { clearOverview, renderOverview } from './overview.js';
12+
import { clearPerformance, renderPerformance } from './performance.js';
13+
import { clearStorage, renderStorage } from './storage.js';
14+
3115
let credential = Text.empty;
3216
let epoch = Config.zero;
3317
let controller = null;
3418
let inFlight = false;
3519
let poll = null;
3620
let view = View.overview;
21+
let observed = null;
22+
3723
function connection(active) {
3824
document.body.classList.toggle(Dom.connected, active);
39-
write(Id.state, active?Text.connected:Text.disconnected);
25+
write(Id.state, active ? Text.connected : Text.disconnected);
26+
write(Id.liveText, active ? Text.live : Text.offline);
4027
el(Id.disconnect).disabled = !active;
4128
el(Id.refresh).disabled = !active;
4229
}
30+
4331
function stopPolling() {
44-
if (poll !== null)clearTimeout(poll);
32+
if (poll !== null) clearTimeout(poll);
4533
poll = null;
4634
}
35+
4736
function schedule() {
4837
stopPolling();
49-
if (credential && !document.hidden)poll = setTimeout(refresh, Config.pollMs);
38+
if (credential && !document.hidden) poll = setTimeout(refresh, Config.pollMs);
5039
}
40+
5141
function cancelPending() {
52-
epoch += Config.one;
42+
epoch += Config.one;
5343
controller?.abort();
5444
controller = null;
5545
inFlight = false;
46+
el(Id.refresh).classList.remove(Dom.spinning);
5647
schedule();
5748
}
49+
50+
function clearObservations() {
51+
observed = null;
52+
resetMetrics();
53+
clearOverview();
54+
clearPerformance();
55+
clearErrors();
56+
clearNodes();
57+
clearStorage();
58+
clearCatalog();
59+
write(Id.captured, Text.awaiting);
60+
el(Id.dialog).close();
61+
el(Id.dialog).querySelector(Dom.pre).textContent = Text.empty;
62+
}
63+
5864
function disconnect(message = Text.disconnectedHint, resetScope = true) {
5965
cancelPending();
6066
credential = Text.empty;
@@ -70,100 +76,144 @@ function disconnect(message = Text.disconnectedHint, resetScope = true) {
7076
resetBrowsing();
7177
status(message, message === Text.unauthorized);
7278
}
79+
80+
function observationFailed() {
81+
resetMetrics();
82+
clearOverview();
83+
clearPerformance();
84+
write(Id.state, Text.observationUnavailable);
85+
write(Id.liveText, Text.paused);
86+
write(Id.captured, Text.stale);
87+
el(Id.refresh).disabled = false;
88+
status(Text.failed, true);
89+
logEvent(Css.bad, Text.eventFailed);
90+
}
91+
92+
async function send(path, body, signal) {
93+
const headers = { [Api.authorization]: Api.bearer + credential };
94+
if (body !== null) headers[Api.contentType] = Api.json;
95+
return fetch(path, {
96+
method: body === null ? Api.get : Api.post,
97+
headers,
98+
body: body === null ? undefined : JSON.stringify(body),
99+
credentials: Api.sameOrigin,
100+
cache: Api.noStore,
101+
signal
102+
});
103+
}
104+
73105
async function request(path, body = null) {
74-
if (!credential || inFlight)return null;
106+
if (!credential || inFlight) return null;
75107
inFlight = true;
76108
const started = epoch;
77109
const current = new AbortController();
78110
controller = current;
79111
stopPolling();
112+
el(Id.refresh).classList.add(Dom.spinning);
80113
try {
81-
const headers = {
82-
[Api.authorization]:Api.bearer+credential
83-
};
84-
if (body !== null)headers[Api.contentType] = Api.json;
85-
const response = await fetch(path, {
86-
method:body === null?Api.get:Api.post,
87-
headers,
88-
body:body === null?undefined:JSON.stringify(body),
89-
credentials:Api.sameOrigin,
90-
cache:Api.noStore,
91-
signal:current.signal
92-
});
93-
if (started !== epoch)return null;
114+
const response = await send(path, body, current.signal);
115+
if (started !== epoch) return null;
94116
if (response.status === Config.unauthenticated || response.status === Config.forbidden) {
95117
disconnect(Text.unauthorized);
96118
return null;
97119
}
98-
if (!response.ok)throw new Error(Text.httpError+response.status+Text.closeParen);
120+
if (!response.ok) throw new Error(Text.httpError + response.status + Text.closeParen);
99121
const result = await response.json();
100-
return started === epoch?result:null;
122+
return started === epoch ? result : null;
101123
} catch (error) {
102-
if (started !== epoch || error.name === Api.abort)return null;
103-
resetMetrics();
104-
write(Id.state, Text.observationUnavailable);
105-
write(Id.captured, Text.stale);
106-
el(Id.refresh).disabled = false;
107-
status(Text.failed, true);
124+
if (started !== epoch || error.name === Api.abort) return null;
125+
observationFailed();
108126
return null;
109127
} finally {
110128
if (started === epoch) {
111129
inFlight = false;
112130
controller = null;
131+
el(Id.refresh).classList.remove(Dom.spinning);
113132
schedule();
114133
}
115134
}
116135
}
117-
async function refresh() {
118-
if (!credential || inFlight || document.hidden)return;
136+
137+
function noteTransitions(snapshot) {
138+
if (!observed) logEvent(Css.good, Text.eventConnected + shortId(snapshot.node.nodeId));
139+
if (!snapshot.node.routingReady && observed?.node.routingReady !== false) logEvent(Css.warn, Text.eventRouting);
140+
if (!snapshot.storage.complete && observed?.storage.complete !== false)
141+
logEvent(Css.warn, Text.eventPartial + (snapshot.storage.notice ?? Text.partial));
142+
observed = snapshot;
143+
}
144+
145+
function render(snapshot) {
146+
if (measure(snapshot)) logEvent(Css.warn, Text.eventReset);
147+
noteTransitions(snapshot);
148+
renderOverview(snapshot);
149+
renderPerformance(snapshot);
150+
renderErrors(snapshot);
151+
renderNodes(snapshot);
152+
renderStorage(snapshot);
153+
write(Id.captured, Text.captured + date(snapshot.capturedAt));
154+
}
155+
156+
/** Automatic polls pause in background tabs; explicit operator actions always refresh. */
157+
async function refresh(manual = false) {
158+
if (!credential || inFlight || (!manual && document.hidden)) return;
119159
status(Text.loading);
120160
const result = await request(Api.snapshot);
121-
if (!result)return;
122-
renderSnapshot(result);
161+
if (!result) return;
123162
connection(true);
163+
render(result);
124164
status(Text.ready);
125-
if (view === View.files && !hasSelectedResource())physicalFiles();
126165
}
166+
127167
async function connect(event) {
128168
event.preventDefault();
129169
const key = el(Id.key).value.trim();
130-
if (!key)return;
170+
if (!key) return;
131171
disconnect(Text.disconnectedHint, false);
132172
credential = key;
133173
el(Id.key).value = Text.empty;
134174
write(Id.state, Text.connecting);
135175
el(Id.disconnect).disabled = false;
136-
await refresh();
176+
await refresh(true);
137177
}
138-
async function navigate(next) {
178+
179+
async function navigate(next, openName = null) {
139180
cancelPending();
140181
view = next;
141182
selectView(view);
142-
viewChanged(view);
183+
viewChanged(view, openName);
184+
errorsVisible(view === View.errors);
143185
schedule();
144-
if (credential && view !== View.overview && view !== View.cluster)await loadResources();
186+
if (!credential) return;
187+
if (view === View.catalog) await loadCatalog();
188+
else if (BrowseViews.includes(view)) await loadResources();
145189
}
190+
146191
function wireBrowsing() {
147192
el(Id.scope).addEventListener(Dom.submit, event => {
148193
event.preventDefault();
149-
loadResources();
194+
if (view === View.catalog) loadCatalog();
195+
else loadResources();
150196
});
151197
el(Id.resourcesFirst).addEventListener(Dom.click, () => loadResources());
152198
el(Id.resourcesNext).addEventListener(Dom.click, () => loadResources(true));
153199
el(Id.dataFirst).addEventListener(Dom.click, () => loadData());
154200
el(Id.dataNext).addEventListener(Dom.click, () => loadData(true));
155201
}
202+
156203
function start() {
157204
initializeBrowsing(request, cancelPending);
205+
initializeCatalog(request, cancelPending, navigate);
206+
initializeNavigation(navigate);
207+
clearObservations();
158208
el(Id.form).addEventListener(Dom.submit, connect);
159209
el(Id.disconnect).addEventListener(Dom.click, () => disconnect());
160-
el(Id.refresh).addEventListener(Dom.click, refresh);
161-
document.querySelectorAll(Dom.nav).forEach(button => button.addEventListener(Dom.click, () => navigate(button.dataset.view)));
210+
el(Id.refresh).addEventListener(Dom.click, () => refresh(true));
162211
el(Id.closeDetails).addEventListener(Dom.click, () => el(Id.dialog).close());
163212
document.addEventListener(Dom.visibility, () => {
164-
if (document.hidden)stopPolling();
213+
if (document.hidden) stopPolling();
165214
else refresh();
166215
});
167216
wireBrowsing();
168217
}
218+
169219
start();

0 commit comments

Comments
 (0)