mirror of
https://github.com/affaan-m/ECC.git
synced 2026-09-18 15:50:25 +02:00
fix(control-pane): reject failed views and coalesce projection sampling
This commit is contained in:
@@ -231,7 +231,7 @@ function projectPairs(links, options = {}) {
|
||||
const minWindow = Number.isFinite(options.minWindowForZscore) ? options.minWindowForZscore : PROJECTION_DEFAULTS.minWindowForZscore;
|
||||
|
||||
const raw = list.map(l => channelVector(l.channels));
|
||||
if (window) for (const vec of raw) window.push(vec);
|
||||
if (window && options.sample !== false) for (const vec of raw) window.push(vec);
|
||||
|
||||
let stats = null;
|
||||
let normalization = 'raw';
|
||||
|
||||
@@ -207,10 +207,13 @@ function renderControlPlaneViewHtml() {
|
||||
}
|
||||
|
||||
function apply(data) {
|
||||
view = data || view;
|
||||
view.tasks = view.tasks || []; view.lanes = view.lanes || []; view.pairs = view.pairs || [];
|
||||
view.events = view.events || []; view.projection = view.projection || { agents: [] };
|
||||
view.thresholds = view.thresholds || { ta: 0.35, ra: 0.7 };
|
||||
if (!data || data.schemaVersion !== 'ecc.control-plane.view.v1' ||
|
||||
!['tasks', 'lanes', 'pairs', 'events'].every(function (key) { return Array.isArray(data[key]); }) ||
|
||||
!data.projection || !Array.isArray(data.projection.agents) || !data.thresholds ||
|
||||
!Number.isFinite(data.thresholds.ta) || !Number.isFinite(data.thresholds.ra)) {
|
||||
throw new Error('Invalid control-plane view');
|
||||
}
|
||||
view = Object.assign({}, data);
|
||||
renderEvents(); renderLanes(); draw();
|
||||
var c = view.counts || {};
|
||||
document.getElementById('status').textContent =
|
||||
@@ -220,7 +223,10 @@ function renderControlPlaneViewHtml() {
|
||||
}
|
||||
|
||||
function poll() {
|
||||
fetch('/api/control-plane').then(function (r) { return r.json(); }).then(apply).catch(function () {
|
||||
fetch('/api/control-plane').then(function (r) {
|
||||
if (!r.ok) throw new Error('Control-plane request failed');
|
||||
return r.json();
|
||||
}).then(apply).catch(function () {
|
||||
document.getElementById('status').textContent = 'offline';
|
||||
});
|
||||
}
|
||||
|
||||
@@ -225,6 +225,7 @@ function buildControlPlaneView(snapshot, options = {}) {
|
||||
const projection = projectPairs(prox.links || [], {
|
||||
window: options.window,
|
||||
channelWeights: options.channelWeights,
|
||||
sample: options.sample,
|
||||
minWindowForZscore: options.minWindowForZscore
|
||||
});
|
||||
const pointByAgent = new Map(projection.agents.map(a => [a.agentId, a]));
|
||||
@@ -319,11 +320,30 @@ function buildControlPlaneView(snapshot, options = {}) {
|
||||
*/
|
||||
function createControlPlaneViewSource(deps = {}) {
|
||||
const window = deps.window || createProjectionWindow(deps.projection || {});
|
||||
const clock = deps.clock || Date.now;
|
||||
const interval = deps.sampleIntervalMs === undefined ? 5000 : deps.sampleIntervalMs;
|
||||
if (!Number.isFinite(interval) || interval <= 0) throw new Error('sampleIntervalMs must be positive and finite');
|
||||
let cached = null;
|
||||
let pending = null;
|
||||
let expiresAt = 0;
|
||||
async function refresh() {
|
||||
const snapshot = await deps.buildSnapshot();
|
||||
const view = buildControlPlaneView(snapshot, { ...deps.viewOptions, window });
|
||||
cached = { snapshot, view };
|
||||
expiresAt = clock() + interval;
|
||||
return cached;
|
||||
}
|
||||
return {
|
||||
window,
|
||||
async build(extra = {}) {
|
||||
const snapshot = await deps.buildSnapshot();
|
||||
return buildControlPlaneView(snapshot, { ...deps.viewOptions, ...extra, window });
|
||||
if (!cached || clock() >= expiresAt) {
|
||||
if (!pending) pending = refresh().finally(() => { pending = null; });
|
||||
await pending;
|
||||
}
|
||||
if (Object.keys(extra).length === 0) return cached.view;
|
||||
return buildControlPlaneView(cached.snapshot, {
|
||||
...deps.viewOptions, ...extra, now: extra.now || cached.view.generatedAt, window, sample: false
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user