diff --git a/ui/src/components/run-detail/FilesPanel.tsx b/ui/src/components/run-detail/FilesPanel.tsx index 6b12254b6..1a0fa3ac3 100644 --- a/ui/src/components/run-detail/FilesPanel.tsx +++ b/ui/src/components/run-detail/FilesPanel.tsx @@ -7,7 +7,7 @@ import { FolderOpen, Folder, Check, Copy, Download, List, ChevronDown, ChevronRi import type { PostTestRPCCallConfig, TestEntry } from '@/api/types' import { fetchHead, type HeadResult } from '@/api/client' import { formatBytes } from '@/utils/format' -import { getDataUrl, isS3Mode, loadRuntimeConfig, toAbsoluteUrl } from '@/config/runtime' +import { getNavigableDataUrl, loadRuntimeConfig, toAbsoluteUrl } from '@/config/runtime' import { useAuth } from '@/hooks/useAuth' import { Modal } from '@/components/shared/Modal' @@ -589,25 +589,20 @@ export function FilesPanel({ runId, tests, postTestRPCCalls, showDownloadList, d const downloadListText = useMemo(() => { if (!runtimeConfig || downloadEntries.length === 0) return '' - const s3 = isS3Mode(runtimeConfig) const needsAuth = requiresAuth && runtimeConfig.api?.baseUrl if (downloadFormat === 'urls') { - return downloadEntries.map((e) => { - let url = getDataUrl(e.path, runtimeConfig) - if (s3) url += `${url.includes('?') ? '&' : '?'}redirect=true` - return toAbsoluteUrl(url) - }).join('\n') + return downloadEntries.map((e) => + toAbsoluteUrl(getNavigableDataUrl(e.path, runtimeConfig)), + ).join('\n') } // Build curl script with progress. const authFlag = needsAuth ? ' -H "Authorization: Bearer $BENCHMARKOOR_API_KEY"' : '' const total = downloadEntries.length - const urls = downloadEntries.map((e) => { - let url = getDataUrl(e.path, runtimeConfig) - if (s3) url += `${url.includes('?') ? '&' : '?'}redirect=true` - return ` '${toAbsoluteUrl(url)}' '${e.outputPath}'` - }) + const urls = downloadEntries.map( + (e) => ` '${toAbsoluteUrl(getNavigableDataUrl(e.path, runtimeConfig))}' '${e.outputPath}'`, + ) const lines: string[] = [] diff --git a/ui/src/components/run-detail/StateActorConfiguration.tsx b/ui/src/components/run-detail/StateActorConfiguration.tsx index b64c814f8..c383542f7 100644 --- a/ui/src/components/run-detail/StateActorConfiguration.tsx +++ b/ui/src/components/run-detail/StateActorConfiguration.tsx @@ -10,7 +10,7 @@ import { fetchText } from '@/api/client' import type { StateActorManifest } from '@/api/types' import { Badge } from '@/components/shared/Badge' import { Card } from '@/components/shared/Card' -import { getDataUrl, loadRuntimeConfig } from '@/config/runtime' +import { getNavigableDataUrl, loadRuntimeConfig } from '@/config/runtime' import { formatBytes, formatNumber } from '@/utils/format' interface StateActorConfigurationProps { @@ -77,7 +77,9 @@ function RawFile({ runId, name }: { runId: string; name: string }) { }) const text = file?.data ?? '' - const url = config ? getDataUrl(`runs/${runId}/.state-actor/${name}`, config) : undefined + const url = config + ? getNavigableDataUrl(`runs/${runId}/.state-actor/${name}`, config) + : undefined return (
diff --git a/ui/src/components/suite-detail/EESTMetadata.tsx b/ui/src/components/suite-detail/EESTMetadata.tsx index 1512fa09c..1e4feafe5 100644 --- a/ui/src/components/suite-detail/EESTMetadata.tsx +++ b/ui/src/components/suite-detail/EESTMetadata.tsx @@ -5,7 +5,7 @@ import { ExternalLink } from 'lucide-react' import { fetchText } from '@/api/client' import { Card } from '@/components/shared/Card' -import { getDataUrl, loadRuntimeConfig } from '@/config/runtime' +import { getNavigableDataUrl, loadRuntimeConfig } from '@/config/runtime' interface EESTMetadataProps { suiteHash: string @@ -76,10 +76,10 @@ export function EESTMetadata({ suiteHash }: EESTMetadataProps) { } const reportUrl = config - ? getDataUrl(`suites/${suiteHash}/.eest-meta/report_fill.html`, config) + ? getNavigableDataUrl(`suites/${suiteHash}/.eest-meta/report_fill.html`, config) : undefined const indexUrl = config - ? getDataUrl(`suites/${suiteHash}/.eest-meta/index.json`, config) + ? getNavigableDataUrl(`suites/${suiteHash}/.eest-meta/index.json`, config) : undefined return ( diff --git a/ui/src/config/runtime.ts b/ui/src/config/runtime.ts index 9c7461f61..639507d2e 100644 --- a/ui/src/config/runtime.ts +++ b/ui/src/config/runtime.ts @@ -108,6 +108,22 @@ export function getDataUrl(path: string, config: RuntimeConfig): string { return `${base}/${path}` } +// getNavigableDataUrl returns a URL safe to open directly in the browser +// (anchor href, iframe src, download link). In S3 + API mode the /files +// endpoint returns a JSON {"url":...} envelope by default — great for +// programmatic fetch, but a plain navigation would just render that JSON. +// Appending ?redirect=true makes the endpoint 302 to the presigned URL so the +// browser loads the file itself. Local mode serves the bytes directly, so no +// redirect is needed there. +export function getNavigableDataUrl(path: string, config: RuntimeConfig): string { + const url = getDataUrl(path, config) + if (isS3Mode(config) && config.api?.baseUrl) { + return `${url}${url.includes('?') ? '&' : '?'}redirect=true` + } + + return url +} + export function toAbsoluteUrl(url: string): string { if (url.startsWith('http://') || url.startsWith('https://')) return url return `${window.location.origin}${url.startsWith('/') ? '' : '/'}${url}`