feat(userbot): prereqs at startup, SPA path guard, provision lock
ci / lint-prettier (push) Failing after 8s
ci / lint-ruff (push) Successful in 4s
ci / lint-yaml (push) Failing after 7s
ci / lint-dockerfiles (push) Successful in 4s
ci / validate (push) Successful in 5s
deploy / redeploy (push) Failing after 0s
ci / build (push) Has been skipped
ci / deploy-userbot-panel (push) Has been skipped

- ensure_prerequisites runs on startup, not per-request; kube config
  errors surface as 503 PanelError
- serialize provisioning with a lock; drop per-endpoint prereq checks
- guard SPA fallback against path traversal (relative_to)
- add backend tests for auth flow, k8s service, spa routing; ci comment
  for legacy userbot deployments
This commit is contained in:
forust committed 2026-09-06 20:39:12 +02:00
1 parent 861d89d36a
commit d9f1c8325a
10 files changed
+191 -34

No files matched your search

+28 -2
View File
@@ -26,6 +26,7 @@
let addOpen = false;
let menuOpen = false;
let busyId = '';
let pollTimer: number | null = null;
$: visibleInstances = filterInstances(instances, query, status);
$: running = instances.filter((item) => item.status === 'running').length;
@@ -34,10 +35,35 @@
onMount(() => {
refresh();
const timer = window.setInterval(() => refresh(true), 5000);
return () => window.clearInterval(timer);
startPolling();
document.addEventListener('visibilitychange', handleVisibility);
return () => {
stopPolling();
document.removeEventListener('visibilitychange', handleVisibility);
};
});
function startPolling() {
stopPolling();
pollTimer = window.setInterval(() => refresh(true), 5000);
}
function stopPolling() {
if (pollTimer !== null) {
window.clearInterval(pollTimer);
pollTimer = null;
}
}
function handleVisibility() {
if (document.visibilityState === 'visible') {
refresh(true);
startPolling();
} else {
stopPolling();
}
}
async function refresh(silent = false) {
if (!silent) refreshing = true;
try {
+9 -2
View File
@@ -3,8 +3,15 @@ import type { AccountDraft, AuthResult, Instance } from "./types";
async function request<T>(path: string, init?: RequestInit): Promise<T> {
const response = await fetch(path, init);
if (!response.ok) {
const payload = await response.json().catch(() => ({}));
throw new Error(payload.detail || response.statusText || "Request failed");
let message = response.statusText || "Request failed";
try {
const payload = await response.json();
if (payload?.detail) message = typeof payload.detail === "string" ? payload.detail : JSON.stringify(payload.detail);
} catch {
const text = await response.text().catch(() => "");
if (text) message = text;
}
throw new Error(message);
}
if (response.status === 204) return undefined as T;
return response.json();