Docs / Validate documentation (push) Canceled after 0s
Docs / Deploy GitHub Pages (push) Canceled after 0s
CI Final Summary (AI) / AI Final Summary (push) Canceled after 0s
CI Final Summary (AI) / Push/PR event feed (push) Canceled after 0s
CI / CI Integrity and Change Classification (push) Canceled after 0s
CI / Code Quality & Tests (push) Canceled after 0s
Dependency Lock & Migration Safety / Dependency drift (lock vs pyproject) (push) Canceled after 0s
Dependency Lock & Migration Safety / Migration safety (fail-loud + version postconditions) (push) Canceled after 0s
JS Tests / Frontend JS Unit Tests (push) Canceled after 0s
Security / CodeQL Analysis (push) Canceled after 0s
Security / Trivy Vulnerability Scan (push) Canceled after 0s
Tests (OS Matrix) / Py Tests (macos-latest) (push) Canceled after 0s
Tests (OS Matrix) / Py Tests (windows-latest) (push) Canceled after 0s
CI / Extended Validation (Tier 2) (push) Canceled after 0s
CI / Heavy CI (database-provider) (push) Canceled after 0s
CI / Heavy CI (e2e) (push) Canceled after 0s
CI / Heavy CI (integration) (push) Canceled after 0s
CI / Heavy CI (model-validation) (push) Canceled after 0s
CI / Heavy CI (research-backtest) (push) Canceled after 0s
CI / Aggregate CI Results (push) Canceled after 0s
Full project import for the self-hosted code hosting (https://code.aifunny.ltd/gavindiaz/my-NexusTradingForexBot.git). Includes the live-freshness + scaler-hash fixes for the permanent STALE dashboard (tick_pipeline now_utc stamps; infer_probabilities computes model_input_hash on the actual scaled 70D tensor), the data-fetch default-path fix, the g29 web-auth test fixes, and a Chinese user manual (使用手册.md) for the web dashboard. Scratch diagnostic scripts (check*.py, contain a live web-auth token) and the .kilo/ agent-worktree directory are gitignored, not committed. Co-Authored-By: Claude Code <noreply@anthropic.com>
106 lines
4.9 KiB
JavaScript
106 lines
4.9 KiB
JavaScript
/* =========================================================================
|
|
* NEXUS CONTROL CENTER — Central API Client & Error Contract (app.js part 1)
|
|
* -------------------------------------------------------------------------
|
|
* Every HTTP call in the dashboard goes through `nx.api`. It:
|
|
* - attaches a correlation request_id (X-Request-ID) to every request,
|
|
* - parses the SAFE server error envelope {error:{code,message,request_id}},
|
|
* - logs a [UI_ERROR] diagnostic line on failure,
|
|
* - presents a user-friendly message and preserves request_id,
|
|
* - dedupes in-flight requests per key (no duplicate polling),
|
|
* - never fabricates data: no fake PnL, no random fallbacks.
|
|
* ========================================================================= */
|
|
window.NX = window.NX || {};
|
|
|
|
(function () {
|
|
'use strict';
|
|
|
|
let seq = 0;
|
|
function rid() {
|
|
seq += 1;
|
|
return 'req_' + Date.now().toString(36) + seq.toString(36);
|
|
}
|
|
|
|
function uiError(component, action, endpoint, status, requestId, extra) {
|
|
const line = '[UI_ERROR] component=' + component + ' action=' + action +
|
|
' endpoint=' + endpoint + ' status=' + (status || 'network') +
|
|
' request_id=' + (requestId || '-') + (extra ? ' ' + extra : '');
|
|
console.warn(line);
|
|
}
|
|
|
|
const inflight = {};
|
|
|
|
// BUG-212: network failures must reach the connectivity controller. The SSE
|
|
// path flips the banner via NXConn.setDown, but a browser that holds a
|
|
// half-open EventSource fires NO error event, and boot-time REST polling is
|
|
// the only periodic traffic — so the UI stayed visually UP while every
|
|
// request failed. Bounded: 2 consecutive failures arm the banner; any OK
|
|
// request resets (no flap on a single dropped call).
|
|
let connFailStreak = 0;
|
|
function noteConnFailure(kind) {
|
|
connFailStreak += 1;
|
|
if (window.NXConn && connFailStreak >= 2 && window.NXConn.state() !== 'DOWN') {
|
|
window.NXConn.setDown(kind === 'net' ? 'Network request failed. ' : ('Server error (HTTP ' + arguments[1] + '). '));
|
|
}
|
|
}
|
|
function noteConnOk() { connFailStreak = 0; if (window.NXConn) window.NXConn.setUp(); }
|
|
|
|
|
|
async function request(url, opts, key) {
|
|
const reqId = opts.headers && opts.headers['X-Request-ID'] || rid();
|
|
const headers = Object.assign({ 'X-Request-ID': reqId }, (opts.headers || {}));
|
|
const init = Object.assign({}, opts, { headers });
|
|
if (opts.signal) init.signal = opts.signal; // AbortController passthrough (cc_state timeout budget)
|
|
const cacheKey = key || url + '|' + (opts.method || 'GET');
|
|
if (opts.method === 'GET' || !opts.method) {
|
|
if (inflight[cacheKey]) return inflight[cacheKey];
|
|
}
|
|
const p = (async () => {
|
|
let res;
|
|
try {
|
|
res = await fetch(url, init);
|
|
} catch (err) {
|
|
uiError(opts.component || 'api', opts.action || 'FETCH', url, 0, reqId, String(err && err.message || err));
|
|
noteConnFailure('net');
|
|
return { ok: false, status: 0, error: { code: 'NETWORK_ERROR', message: 'Network request failed.', request_id: reqId } };
|
|
}
|
|
let body = null;
|
|
try { body = await res.json(); } catch (e) { body = null; }
|
|
if (!res.ok) {
|
|
let code = 'INTERNAL_ERROR', message = 'The server could not complete this request.';
|
|
if (body && body.error) { code = body.error.code || code; message = body.error.message || message; }
|
|
else if (body && body.detail) { message = typeof body.detail === 'string' ? body.detail : 'Request failed.'; }
|
|
uiError(opts.component || 'api', opts.action || 'FETCH', url, res.status, reqId, 'code=' + code);
|
|
if (res.status >= 500) noteConnFailure('srv', res.status);
|
|
return { ok: false, status: res.status, error: { code, message, request_id: reqId } };
|
|
}
|
|
noteConnOk();
|
|
return { ok: true, status: res.status, body: body || {}, request_id: reqId };
|
|
})();
|
|
if (opts.method === 'GET' || !opts.method) {
|
|
inflight[cacheKey] = p;
|
|
p.finally(() => { if (inflight[cacheKey] === p) delete inflight[cacheKey]; });
|
|
}
|
|
return p;
|
|
}
|
|
|
|
window.NX.api = {
|
|
get(url, opts) { return request(url, Object.assign({ method: 'GET' }, opts), url); },
|
|
post(url, body, opts) {
|
|
return request(url, Object.assign({ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body || {}) }, opts));
|
|
},
|
|
put(url, body, opts) {
|
|
return request(url, Object.assign({ method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body || {}) }, opts));
|
|
},
|
|
del(url, opts) {
|
|
return request(url, Object.assign({ method: 'DELETE' }, opts));
|
|
},
|
|
// User-facing error presentation: shows request_id so the operator can
|
|
// correlate with the server log.
|
|
msg(result, fallback) {
|
|
if (result && result.error) {
|
|
return (result.error.message || fallback || 'Request failed.') + (result.error.request_id ? ' (request ' + result.error.request_id + ')' : '');
|
|
}
|
|
return fallback || '';
|
|
}
|
|
};
|
|
})(); |