edited ci file
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useState } from 'react'
|
||||
import { useParams, Link } from 'react-router-dom'
|
||||
import { useRunDetail, useJobLogs, useCancelRun, useRetryJob } from '../api/queries/pipelines'
|
||||
import { useRunSBOM, getRunSBOMDocumentURL, useGenerateSBOM } from '../api/queries/sbom'
|
||||
import { Skeleton } from '../ui/Skeleton'
|
||||
import { cn } from '../lib/utils'
|
||||
import type { PipelineJob, PipelineStep, RunStatus } from '../types/api'
|
||||
@@ -27,6 +28,87 @@ function duration(start: string | null, end: string | null): string {
|
||||
return `${m}m ${s % 60}s`
|
||||
}
|
||||
|
||||
// ── SBOM section ──────────────────────────────────────────────────────────────
|
||||
|
||||
function SBOMSection({ owner, repo, runId, runStatus, triggerSha }: {
|
||||
owner: string
|
||||
repo: string
|
||||
runId: number
|
||||
runStatus: RunStatus
|
||||
triggerSha: string
|
||||
}) {
|
||||
const { data: sbom, isLoading } = useRunSBOM(owner, repo, runId)
|
||||
const generateSBOM = useGenerateSBOM(owner, repo)
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<section className="border border-[var(--c-border)] rounded-lg bg-[var(--c-surface)] p-4">
|
||||
<h2 className="text-xs font-semibold uppercase tracking-wider text-[var(--c-muted)] mb-3">
|
||||
SBOM
|
||||
</h2>
|
||||
<Skeleton className="h-5 w-64 rounded" />
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
if (sbom) {
|
||||
return (
|
||||
<section className="border border-[var(--c-border)] rounded-lg bg-[var(--c-surface)] p-4">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<h2 className="text-xs font-semibold uppercase tracking-wider text-[var(--c-muted)] mb-1">
|
||||
SBOM — CycloneDX
|
||||
</h2>
|
||||
<div className="flex items-center gap-3 text-xs text-[var(--c-muted)]">
|
||||
<span>{sbom.componentCount} components</span>
|
||||
<span className="font-mono">{sbom.sha.slice(0, 7)}</span>
|
||||
<span>{new Date(sbom.generatedAt).toLocaleString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
<a
|
||||
href={getRunSBOMDocumentURL(owner, repo, runId)}
|
||||
download="bom.json"
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium border border-[var(--c-border)] rounded-lg text-[var(--c-text)] hover:bg-[var(--c-surface-muted)] transition-colors shrink-0"
|
||||
>
|
||||
<svg width="12" height="12" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3" />
|
||||
</svg>
|
||||
Download BOM
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
// No SBOM yet — show generate option for completed/failed runs
|
||||
if (runStatus === 'succeeded' || runStatus === 'failed') {
|
||||
return (
|
||||
<section className="border border-[var(--c-border)] rounded-lg bg-[var(--c-surface)] p-4">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<h2 className="text-xs font-semibold uppercase tracking-wider text-[var(--c-muted)] mb-1">
|
||||
SBOM
|
||||
</h2>
|
||||
<p className="text-xs text-[var(--c-muted)]">No SBOM generated for this run.</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => generateSBOM.mutate({ ref: triggerSha, runId })}
|
||||
disabled={generateSBOM.isPending}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium border border-[var(--c-border)] rounded-lg text-[var(--c-text)] hover:bg-[var(--c-surface-muted)] transition-colors disabled:opacity-50 shrink-0"
|
||||
>
|
||||
{generateSBOM.isPending ? 'Generating…' : 'Generate SBOM'}
|
||||
</button>
|
||||
</div>
|
||||
{generateSBOM.isError && (
|
||||
<p className="mt-2 text-xs text-[var(--c-danger)]">{(generateSBOM.error as Error).message}</p>
|
||||
)}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function shortRef(ref: string): string {
|
||||
return ref.replace('refs/heads/', '').replace('refs/tags/', '')
|
||||
}
|
||||
@@ -172,7 +254,7 @@ function topoColumns(jobs: JobWithSteps[]): JobWithSteps[][] {
|
||||
const job = nameToJob.get(name)
|
||||
if (!job) return 0
|
||||
let needs: string[] = []
|
||||
try { needs = JSON.parse(job.needs || '[]') } catch { needs = [] }
|
||||
try { needs = JSON.parse(job.needs || '[]') ?? [] } catch { needs = [] }
|
||||
const d = needs.length === 0 ? 0 : 1 + Math.max(...needs.map(n => getDepth(n, new Set(visited))))
|
||||
depth.set(name, d)
|
||||
return d
|
||||
@@ -357,6 +439,11 @@ export default function PipelineRunPage() {
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{/* SBOM section */}
|
||||
{!isLoading && run && (
|
||||
<SBOMSection owner={owner} repo={repo} runId={runIdNum} runStatus={run.status as RunStatus} triggerSha={run.triggerSha} />
|
||||
)}
|
||||
|
||||
{/* DAG + log viewer */}
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
|
||||
|
||||
Reference in New Issue
Block a user