perf: quick wins for pipeline, jobs, httpapi and web ops

- CDN snapshot: batch merge после parallel fetch, без race на persist
- ListRevisionPrefixes: лёгкая проверка revision вместо GetRevision
- Jobs: timeout/cancel context для pipeline и deploy
- HTTP server timeouts; кэш birdc для GET /peers
- ListModules: batch DoH profiles одним запросом
- Web: tab-scoped load на /operations, debounce job search, меньше over-fetch на dashboard

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-05-21 10:42:13 +07:00
co-authored by Cursor
parent 5fca165c69
commit be3d73f374
13 changed files with 425 additions and 34 deletions
+2 -2
View File
@@ -185,8 +185,8 @@
apiJSON<ModulesResponse>('/v1/modules?limit=200'),
apiJSON<PeersResponse>('/v1/peers?limit=200'),
apiJSON<SpeakersResponse>('/v1/speakers?limit=200'),
apiJSON<RevisionsResponse>('/v1/revisions?limit=200'),
apiJSON<JobsResponse>('/v1/jobs?limit=20')
apiJSON<RevisionsResponse>('/v1/revisions?limit=10'),
apiJSON<JobsResponse>('/v1/jobs?limit=10')
]);
const firstReject = [m, p, s, r, j].find((x) => x.status === 'rejected');
+46 -2
View File
@@ -128,6 +128,7 @@
let jobs = $state<JobRow[]>([]);
let jobsLoading = $state(false);
let jobSearchQ = $state('');
let jobSearchDebounced = $state('');
let jobFilterStatus = $state('');
let jobFilterKind = $state('');
let jobFilterModule = $state('');
@@ -302,13 +303,23 @@
return j.kind === 'module_refresh' && mid === jobFilterModule;
});
}
const q = jobSearchQ.trim();
const q = jobSearchDebounced.trim();
if (q) {
list = list.filter((j) => jobMatchesSearch(j, q));
}
return list;
});
let jobSearchTimer: ReturnType<typeof setTimeout> | undefined;
$effect(() => {
const q = jobSearchQ;
clearTimeout(jobSearchTimer);
jobSearchTimer = setTimeout(() => {
jobSearchDebounced = q;
}, 250);
return () => clearTimeout(jobSearchTimer);
});
const jobModuleOptions = $derived(
[...moduleNameById.entries()]
.map(([id, name]) => ({ id, name }))
@@ -340,8 +351,18 @@
onMount(() => {
activeTab = parseOpsTab(page.url.searchParams.get('tab'));
lastLoadedTab = activeTab;
tabSyncReady = true;
void refreshAll(true);
void refreshActiveTab(true);
});
let lastLoadedTab = $state('');
$effect(() => {
if (!tabSyncReady) return;
const tab = activeTab;
if (tab === lastLoadedTab) return;
lastLoadedTab = tab;
void refreshActiveTab();
});
const statAccents = [
@@ -425,6 +446,29 @@
syncTabToUrl(activeTab);
});
async function refreshActiveTab(isInitial = false) {
if (isInitial) initialLoading = true;
else refreshing = true;
switch (activeTab) {
case 'revisions':
await loadRevisions();
break;
case 'jobs':
await Promise.all([loadJobs(), loadModules()]);
break;
case 'diff':
break;
case 'system':
await loadBirdStatus();
break;
default:
await loadRevisions();
}
lastUpdated = new Date();
initialLoading = false;
refreshing = false;
}
async function refreshAll(isInitial = false) {
if (isInitial) initialLoading = true;
else refreshing = true;