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
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:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user