fix(xiaomi): URL MiMo + header api-key + fallback polling admin SSE
CI / lint (push) Successful in 37s
CI / security (push) Successful in 32s
CI / test (push) Successful in 48s
CI / build (push) Successful in 22s
CI / e2e (push) Successful in 6m1s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s
CI / lint (push) Successful in 37s
CI / security (push) Successful in 32s
CI / test (push) Successful in 48s
CI / build (push) Successful in 22s
CI / e2e (push) Successful in 6m1s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s
1. **fix(xiaomi): la clé Xiaomi échouait avec 'Name or service not known'** - URL précédente api.xiaomi.com n'existe pas (DNS fail) - Vraie URL: https://api.xiaomimimo.com/v1/models - Xiaomi MiMo utilise un header custom 'api-key:' (PAS 'Authorization: Bearer') - Modèles par défaut mis à jour: mimo-v2.5-pro, mimo-v2.5, mimo-v2.5-asr, etc. - Le chat dans ai.py supporte maintenant un header custom via auth_header_name 2. **fix(admin): EventSource 503 → fallback polling** - Ajout d'un fallback: si EventSource ne reçoit jamais le premier event (cookie expiré, réseau bloqué, proxy timeout), on bascule sur du polling /api/admin/stats toutes les 5s. Le UI continue de se mettre à jour. - Cleanup correct du timer dans disconnectSSE 3. **fix(test_ai_models)**: 2 nouveaux tests de régression - test_xiaomi_uses_api_key_header_not_bearer: vérifie URL + header - test_xiaomi_test_endpoint_uses_real_url: vérifie /api/config/ai-keys/test - Patche backend.ai ET backend.main (import local) - Header case-insensitive (urllib normalise à 'Api-key', HTTP est insensible) Vérifié : - pytest : 504 passed (502 + 2 nouveaux Xiaomi) - frontend unit : 7 passed - validate-imports : 30 modules / 204 exports - pane-manager JSDOM : 9/9 - ruff check backend/ : All checks passed
This commit is contained in:
+40
-1
@@ -22,6 +22,7 @@ import { t, getLocale } from "./i18n.js";
|
||||
|
||||
// ── Module state ─────────────────────────────────────────────────────────
|
||||
let _eventSource = null;
|
||||
let _pollTimer = null;
|
||||
let _auditFilters = { user: "", action: "" };
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────────
|
||||
@@ -161,15 +162,28 @@ export async function loadStatsOnce() {
|
||||
/**
|
||||
* Open a Server-Sent Events connection on /api/admin/stream.
|
||||
* The endpoint emits `event: stats\ndata: {...}` every 5 seconds.
|
||||
*
|
||||
* The browser's EventSource doesn't support custom headers, so we rely on
|
||||
* the httpOnly cookie (set by /api/auth/login with samesite=lax) to authenticate.
|
||||
* `withCredentials: true` ensures the cookie is sent.
|
||||
*
|
||||
* If the connection fails to open (4xx/5xx or network), we fall back to
|
||||
* periodic polling of /api/admin/stats every 5s so the UI keeps updating.
|
||||
*/
|
||||
export function connectSSE() {
|
||||
if (_eventSource) {
|
||||
try { _eventSource.close(); } catch { /* */ }
|
||||
_eventSource = null;
|
||||
}
|
||||
if (_pollTimer) {
|
||||
clearInterval(_pollTimer);
|
||||
_pollTimer = null;
|
||||
}
|
||||
let sseReady = false;
|
||||
try {
|
||||
_eventSource = new EventSource("/api/admin/stream", { withCredentials: true });
|
||||
_eventSource.addEventListener("stats", (ev) => {
|
||||
sseReady = true;
|
||||
try {
|
||||
const data = JSON.parse(ev.data);
|
||||
renderStatsWidget(data);
|
||||
@@ -177,13 +191,34 @@ export function connectSSE() {
|
||||
console.warn("admin SSE parse error", err);
|
||||
}
|
||||
});
|
||||
_eventSource.onopen = () => {
|
||||
sseReady = true;
|
||||
// Stop the polling fallback once SSE works.
|
||||
if (_pollTimer) { clearInterval(_pollTimer); _pollTimer = null; }
|
||||
};
|
||||
_eventSource.onerror = () => {
|
||||
// EventSource auto-reconnects. We just log quietly.
|
||||
// EventSource auto-reconnects. Only start polling fallback if we
|
||||
// never received the first event yet (e.g. 401/403/network blocked).
|
||||
if (!sseReady) {
|
||||
_startPollingFallback();
|
||||
}
|
||||
console.warn("admin SSE error (will retry)");
|
||||
};
|
||||
} catch (err) {
|
||||
console.warn("admin SSE init failed", err);
|
||||
_startPollingFallback();
|
||||
}
|
||||
// Kick off a single initial fetch right away so the UI doesn't show
|
||||
// zeros for 5 seconds waiting for the first SSE event.
|
||||
loadStatsOnce();
|
||||
}
|
||||
|
||||
function _startPollingFallback() {
|
||||
if (_pollTimer) return;
|
||||
console.warn("admin: falling back to polling /api/admin/stats every 5s");
|
||||
_pollTimer = setInterval(() => {
|
||||
loadStatsOnce().catch(() => { /* ignore — error already shown */ });
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
/** Close the SSE connection if any (useful before navigation). */
|
||||
@@ -192,6 +227,10 @@ export function disconnectSSE() {
|
||||
try { _eventSource.close(); } catch { /* */ }
|
||||
_eventSource = null;
|
||||
}
|
||||
if (_pollTimer) {
|
||||
clearInterval(_pollTimer);
|
||||
_pollTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Audit log ────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user