Files

136 lines
9.0 KiB
HTML

{{ define "digest" }}
<!DOCTYPE html>
<html lang="en">
<head>
{{ template "head" . }}
{{ template "meta" .Meta }}
</head>
<body>
{{ template "nav" . }}
<main id="main-content" class="container mx-auto px-4 py-8">
<div class="space-y-6">
<!-- Breadcrumb -->
<div class="text-sm breadcrumbs min-w-0">
<ul>
<li class="min-w-0"><a href="/u/{{ .Owner.Handle }}" class="link link-primary truncate">{{ .Owner.Handle }}</a></li>
<li class="min-w-0"><a href="/r/{{ .Owner.Handle }}/{{ .Repository }}" class="link link-primary truncate">{{ .Repository }}</a></li>
<li class="min-w-0"><code class="font-mono text-xs truncate">{{ truncateDigest (trimPrefix "sha256:" .Manifest.Digest) 16 }}</code></li>
</ul>
</div>
<!-- Digest Header -->
<div class="card bg-base-200 shadow-sm border border-base-300 p-6 space-y-3">
<div class="flex flex-wrap items-start justify-between gap-4">
<div class="space-y-2 min-w-0">
{{/* Helm chart with parsed metadata: lead with chart name + version so the page reads as a chart, not a digest. */}}
{{ $helmMeta := "" }}
{{ if and .HelmContent .HelmContent.Meta }}{{ $helmMeta = .HelmContent.Meta }}{{ end }}
<!-- Title -->
<div class="flex flex-wrap items-center gap-2">
{{ if $helmMeta }}
<h1 class="text-xl font-bold flex items-center gap-2 min-w-0">
{{ icon "helm" "size-5 text-base-content/70 shrink-0" }}
<span class="truncate" title="{{ $helmMeta.Name }}">{{ $helmMeta.Name }}</span>
{{ if $helmMeta.Version }}<span class="text-base-content/70 font-normal">{{ $helmMeta.Version }}</span>{{ end }}
</h1>
<span class="badge badge-md badge-soft badge-helm">Helm chart</span>
{{ else if .Manifest.Tags }}
<h1 class="text-xl font-bold flex flex-wrap gap-x-1 items-center min-w-0">{{ range $i, $tag := .Manifest.Tags }}{{ if lt $i 3 }}{{ if $i }}<span aria-hidden="true">,</span>{{ end }}<span class="inline-block max-w-[24ch] truncate align-baseline" title="{{ $tag }}">{{ $tag }}</span>{{ end }}{{ end }}{{ if gt (len .Manifest.Tags) 3 }}<span class="text-sm font-normal text-base-content/60" title="{{ range $i, $tag := .Manifest.Tags }}{{ if $i }}, {{ end }}{{ $tag }}{{ end }}">+{{ sub (len .Manifest.Tags) 3 }} more</span>{{ end }}</h1>
{{ else }}
<h1 class="text-xl font-bold font-mono" title="{{ .Manifest.Digest }}">{{ truncateDigest (trimPrefix "sha256:" .Manifest.Digest) 16 }}</h1>
{{ end }}
{{ if .Manifest.IsManifestList }}
<span class="badge badge-md badge-soft badge-accent">Multi-arch</span>
{{ else if and (eq .Manifest.ArtifactType "helm-chart") (not $helmMeta) }}
<span class="badge badge-md badge-soft badge-helm">{{ icon "helm" "size-3" }} Helm</span>
{{ end }}
{{ if .Manifest.HasAttestations }}
<span class="badge badge-md badge-soft badge-success">{{ icon "shield-check" "size-3" }} Attested</span>
{{ end }}
</div>
{{ if $helmMeta }}
{{/* Subtitle: appVersion + chart type */}}
<div class="text-sm text-base-content/70">
{{ if $helmMeta.AppVersion }}appVersion {{ $helmMeta.AppVersion }}{{ if $helmMeta.Type }} · {{ end }}{{ end }}{{ if $helmMeta.Type }}{{ $helmMeta.Type }} chart{{ end }}
{{ if $helmMeta.Deprecated }}<span class="badge badge-sm badge-warning ml-2">Deprecated</span>{{ end }}
</div>
{{ if .Manifest.Tags }}
<div class="flex flex-wrap gap-1 text-xs">
{{ range .Manifest.Tags }}<span class="badge badge-ghost badge-sm font-mono" title="tag">{{ . }}</span>{{ end }}
</div>
{{ end }}
{{ end }}
<!-- Digest (small) -->
<div class="flex items-center gap-2 text-base-content/70">
<code class="font-mono text-xs" title="{{ .Manifest.Digest }}">{{ truncateDigest (trimPrefix "sha256:" .Manifest.Digest) 16 }}</code>
<button class="btn btn-ghost btn-xs" data-action="copy" data-copy="{{ .Manifest.Digest }}" aria-label="Copy digest">{{ icon "copy" "size-3" }}</button>
</div>
</div>
<div class="flex items-center gap-2 shrink-0">
{{ if not .Manifest.CreatedAt.IsZero }}
<span class="text-base-content text-sm flex items-center gap-1" title="{{ humanizeTime .Manifest.CreatedAt }}">{{ icon "history" "size-4" }}{{ timeAgoShort .Manifest.CreatedAt }}</span>
{{ end }}
</div>
</div>
{{ if .Manifest.IsManifestList }}
<!-- Architecture Dropdown -->
<div class="flex items-center gap-3 pt-2 border-t border-base-300">
<label for="arch-select" class="text-sm font-medium whitespace-nowrap">{{ icon "cpu" "size-4" }} Architecture</label>
<select id="arch-select" class="select select-sm select-bordered"
name="digest"
hx-get="/api/digest-content/{{ .Owner.Handle }}/{{ .Repository }}"
hx-trigger="change"
hx-target="#digest-content"
hx-swap="innerHTML"
hx-indicator="#arch-loading">
{{ range .Manifest.Platforms }}
<option value="{{ .Digest }}"{{ if eq .Digest $.SelectedPlatform }} selected{{ end }}>{{ .OS }}/{{ .Architecture }}{{ if .Variant }}/{{ .Variant }}{{ end }}{{ if .CompressedSize }} ({{ humanizeBytes .CompressedSize }}){{ end }}</option>
{{ end }}
</select>
<span id="arch-loading" class="htmx-indicator" aria-hidden="true">
{{ icon "loader" "size-4 animate-spin" }}
</span>
</div>
{{ end }}
</div>
<!-- Upgrade Banner (HTMX lazy-loaded) -->
<div id="upgrade-banner" role="status" aria-live="polite"
hx-get="/api/upgrade-banner/{{ .Owner.Handle }}/{{ .Repository }}?digest={{ urlquery .Manifest.Digest }}{{ if .Manifest.HoldEndpoint }}&holdEndpoint={{ urlquery .Manifest.HoldEndpoint }}{{ end }}"
hx-trigger="load"
hx-swap="innerHTML">
</div>
<!-- Content: Layers + Vulnerabilities (or chart info for helm) -->
<div id="digest-content" aria-live="polite" aria-busy="false">
{{ if .Manifest.IsManifestList }}
{{ if .Manifest.Platforms }}
<!-- Auto-load selected platform -->
<div hx-get="/api/digest-content/{{ .Owner.Handle }}/{{ .Repository }}?digest={{ urlquery .SelectedPlatform }}"
hx-trigger="load"
hx-target="#digest-content"
hx-swap="innerHTML"
hx-on::response-error="this.innerHTML='<div class=&quot;alert alert-error&quot;>We couldn&rsquo;t load this platform. Try refreshing.</div>'"
class="flex items-center justify-center py-12">
{{ icon "loader" "size-6 animate-spin text-base-content/40" }}
<span class="ml-2 text-base-content/60">Loading layers and vulnerabilities...</span>
</div>
{{ else }}
<p class="py-12 text-center text-base-content/60">No platform manifests found for this image index.</p>
{{ end }}
{{ else if eq .Manifest.ArtifactType "helm-chart" }}
{{ template "helm-digest-content" (dict "Manifest" .Manifest "HelmContent" .HelmContent "RegistryURL" .RegistryURL "OwnerHandle" .Owner.Handle "RepoName" .Repository "OciClient" .OciClient "IsLoggedIn" (ne .User nil)) }}
{{ else }}
{{ template "digest-content" . }}
{{ end }}
</div>
</div>
</main>
{{ template "footer" . }}
</body>
</html>
{{ end }}