feat: add diagnostic log endpoint for revisions and implement download functionality
CI / changes (push) Successful in 7s
CI / openapi (push) Has been skipped
CI / go (push) Successful in 38s
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, , evobgp-web) (push) Successful in 1m3s
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, evobgp-all, evobgp-web-all) (push) Successful in 1m2s
CI / docker-bird (push) Has been skipped
CI / bird2 (push) Successful in 18s
CI / docker-go-prime (push) Successful in 22s
CI / docker-go (deploy/docker/evobgp-agent/Dockerfile, , evobgp-agent) (push) Successful in 59s
CI / docker-go (evobgp-all, 1, deploy/docker/gobinary/Dockerfile, , evobgp-all) (push) Successful in 3m10s
CI / docker-go (evobgp-api, 1, deploy/docker/gobinary/Dockerfile, , evobgp-api) (push) Successful in 1m22s
CI / docker-go (evobgp-deploy, 0, deploy/docker/gobinary/Dockerfile, , evobgp-deploy) (push) Successful in 1m20s
CI / docker-go (evobgp-ingest, 0, deploy/docker/gobinary/Dockerfile, , evobgp-ingest) (push) Successful in 1m20s
CI / docker-go (evobgp-node, 0, deploy/docker/gobinary/Dockerfile, , evobgp-node) (push) Successful in 1m6s
CI / docker-go (evobgp-render, 0, deploy/docker/gobinary/Dockerfile, , evobgp-render) (push) Successful in 1m22s
CI / docker-go (evobgp-scheduler, 0, deploy/docker/gobinary/Dockerfile, , evobgp-scheduler) (push) Successful in 1m21s

Introduced a new endpoint `GET /revisions/{revision_id}/diagnostic-log` to retrieve detailed diagnostic logs for specific revisions. Implemented the corresponding handler to generate and return the log in a structured format. Additionally, updated the frontend to include a download button for the diagnostic log, enhancing user accessibility to revision data.
This commit is contained in:
Denozordec
2026-04-08 13:12:30 +07:00
parent 6d0e214655
commit 2260ecf74a
4 changed files with 327 additions and 1 deletions
@@ -13,6 +13,7 @@
import RefreshCw from '@lucide/svelte/icons/refresh-cw';
import Undo from '@lucide/svelte/icons/undo';
import Eye from '@lucide/svelte/icons/eye';
import Download from '@lucide/svelte/icons/download';
type Props = {
revisions: RevisionRow[];
@@ -20,10 +21,19 @@
onReload: () => void;
onOpenPreview: (rev: RevisionRow) => void;
onRollbackRequest: (rev: RevisionRow) => void;
onDownloadDiagnosticLog: (rev: RevisionRow) => void;
formatDate: (d?: string | null) => string;
};
let { revisions, revLoading, onReload, onOpenPreview, onRollbackRequest, formatDate }: Props = $props();
let {
revisions,
revLoading,
onReload,
onOpenPreview,
onRollbackRequest,
onDownloadDiagnosticLog,
formatDate
}: Props = $props();
</script>
<Card>
@@ -65,6 +75,14 @@
<Button variant="ghost" size="icon-sm" onclick={() => onRollbackRequest(rev)}>
<Undo class="size-3.5" />
</Button>
<Button
variant="ghost"
size="icon-sm"
title="Скачать диагностический лог"
onclick={() => onDownloadDiagnosticLog(rev)}
>
<Download class="size-3.5" />
</Button>
</div>
</TableCell>
</TableRow>
+43
View File
@@ -2,6 +2,7 @@
import { onMount } from 'svelte';
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
import {
apiFetch,
apiJSON,
apiMutate,
fetchModuleSourceCatalog,
@@ -172,6 +173,47 @@
}
}
function parseDiagnosticLogFilename(contentDisposition: string | null, rev: RevisionRow): string {
const fallback = `revision-${rev.id.slice(0, 8)}-diagnostic.log`;
if (!contentDisposition) return fallback;
const utf8Match = contentDisposition.match(/filename\*\s*=\s*UTF-8''([^;]+)/i);
if (utf8Match?.[1]) {
try {
return decodeURIComponent(utf8Match[1]);
} catch {
return utf8Match[1];
}
}
const quotedMatch = contentDisposition.match(/filename\s*=\s*"([^"]+)"/i);
if (quotedMatch?.[1]) return quotedMatch[1];
const plainMatch = contentDisposition.match(/filename\s*=\s*([^;]+)/i);
if (plainMatch?.[1]) return plainMatch[1].trim();
return fallback;
}
async function downloadRevisionDiagnosticLog(rev: RevisionRow) {
try {
const res = await apiFetch(`/v1/revisions/${rev.id}/diagnostic-log`, { method: 'GET' });
if (!res.ok) {
const message = (await res.text()) || `HTTP ${res.status}`;
throw new Error(`Не удалось скачать диагностический лог: ${message}`);
}
const blob = await res.blob();
const objectUrl = URL.createObjectURL(blob);
const filename = parseDiagnosticLogFilename(res.headers.get('Content-Disposition'), rev);
const link = document.createElement('a');
link.href = objectUrl;
link.download = filename;
link.style.display = 'none';
document.body.appendChild(link);
link.click();
link.remove();
URL.revokeObjectURL(objectUrl);
} catch (e) {
toast.error(e instanceof Error ? e.message : String(e));
}
}
async function loadJobs() {
jobsLoading = true;
try {
@@ -730,6 +772,7 @@
onReload={loadRevisions}
onOpenPreview={openPreview}
onRollbackRequest={(rev) => (rollbackTarget = rev)}
onDownloadDiagnosticLog={downloadRevisionDiagnosticLog}
{formatDate}
/>
</TabsContent>