Files
at-container-registry/pkg/appview/templates/pages/repository.html
T
2026-04-19 17:35:41 -05:00

265 lines
15 KiB
HTML

{{ define "repository" }}
<!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">
<!-- Static Header: Identity + Metadata (does not change with tag) -->
<div class="card bg-base-200 shadow-sm p-6 space-y-4 w-full">
<div class="flex gap-4 items-start">
{{ template "repo-avatar" (dict "IconURL" .Repository.IconURL "RepositoryName" .Repository.Name "IsOwner" .IsOwner) }}
<div class="flex-1 min-w-0">
<h1 class="text-2xl md:text-3xl font-display font-bold tracking-tight">
<a href="/u/{{ .Owner.Handle }}" class="link link-primary">{{ .Owner.Handle }}</a>
<span class="text-base-content/60">/</span>
<span>{{ .Repository.Name }}</span>
</h1>
{{ if .Repository.Description }}
<p class="text-base-content/70 mt-2">{{ .Repository.Description }}</p>
{{ end }}
</div>
</div>
<!-- Metadata Row -->
<div class="flex flex-wrap items-center gap-3">
<div class="flex items-center gap-3">
{{ template "star" (dict "IsStarred" .IsStarred "StarCount" .Stats.StarCount "Interactive" true "Handle" .Owner.Handle "Repository" .Repository.Name) }}
{{ template "pull-count" (dict "PullCount" .Stats.PullCount) }}
{{ if .TagCount }}
<span class="flex items-center gap-1 text-sm text-base-content/70" title="{{ .TagCount }} tags">
{{ icon "tag" "size-4" }} {{ .TagCount }}
</span>
{{ end }}
{{ if .Stats.LastPush }}
<span class="text-sm text-base-content/70" title="Last pushed {{ (derefTime .Stats.LastPush).Format "2006-01-02T15:04:05Z07:00" }}">
Updated {{ timeAgoShort (derefTime .Stats.LastPush) }}
</span>
{{ end }}
</div>
{{ if or .Repository.Licenses .Repository.SourceURL .Repository.DocumentationURL .Repository.Version }}
<div class="flex flex-wrap items-center gap-2 ml-auto">
{{ if .Repository.Version }}
<span class="badge badge-md badge-primary badge-outline" title="Version">
{{ .Repository.Version }}
</span>
{{ end }}
{{ if .Repository.Licenses }}
{{ range parseLicenses .Repository.Licenses }}
{{ if .IsValid }}
<a href="{{ .URL }}" target="_blank" rel="noopener noreferrer" class="badge badge-md badge-soft badge-secondary" title="{{ .Name }}">
{{ .SPDXID }}
</a>
{{ else }}
<span class="badge badge-md badge-soft badge-secondary" title="Custom license: {{ .Name }}">
{{ .Name }}
</span>
{{ end }}
{{ end }}
{{ end }}
{{ if .Repository.SourceURL }}
<a href="{{ .Repository.SourceURL }}" target="_blank" rel="noopener noreferrer" class="link link-primary text-sm flex items-center gap-1" aria-label="View source code (opens in new tab)">
{{ icon "external-link" "size-3" }} Source
</a>
{{ end }}
{{ if .Repository.DocumentationURL }}
<a href="{{ .Repository.DocumentationURL }}" target="_blank" rel="noopener noreferrer" class="link link-primary text-sm flex items-center gap-1" aria-label="View documentation (opens in new tab)">
{{ icon "book-open" "size-3" }} Docs
</a>
{{ end }}
</div>
{{ end }}
</div>
</div>
<!-- Tag Selector (stays in DOM, never swapped) -->
{{ if .SelectedTag }}
<div class="flex flex-wrap items-center gap-3">
<div class="flex items-center gap-2">
{{ icon "tag" "size-6 text-base-content/60" }}
<select id="tag-selector" class="select select-sm select-bordered font-mono"
hx-get="/r/{{ .Owner.Handle }}/{{ .Repository.Name }}"
hx-target="#tag-content"
hx-swap="outerHTML"
hx-push-url="true"
hx-include="this"
name="tag">
{{ range .AllTags }}
<option value="{{ . }}"{{ if eq . $.SelectedTag.Info.Tag.Tag }} selected{{ end }}>{{ . }}</option>
{{ end }}
</select>
{{ if gt (len .AllTags) 1 }}
<div class="dropdown dropdown-end" id="diff-dropdown">
<label tabindex="0" class="btn btn-ghost btn-sm gap-1" title="Compare with another tag">
{{ icon "git-compare" "size-4" }} Diff
</label>
<ul tabindex="0" class="dropdown-content menu bg-base-200 rounded-box z-10 w-56 p-2 shadow max-h-60 overflow-y-auto">
{{ range .AllTags }}
<li><a href="#" data-action="diff-to" data-diff-to="{{ . }}">{{ . }}</a></li>
{{ end }}
</ul>
</div>
{{ end }}
</div>
{{ if .SelectedTag.Info.IsMultiArch }}
<div id="platform-badges" class="flex flex-wrap items-center gap-1">
{{ range .SelectedTag.Info.Platforms }}
<span class="badge badge-sm badge-outline font-mono">{{ .OS }}/{{ .Architecture }}{{ if .Variant }}/{{ .Variant }}{{ end }}</span>
{{ end }}
</div>
{{ else }}
{{ $p := index .SelectedTag.Info.Platforms 0 }}
{{ if $p.OS }}
<div id="platform-badges">
<span class="badge badge-sm badge-outline font-mono">{{ $p.OS }}/{{ $p.Architecture }}{{ if $p.Variant }}/{{ $p.Variant }}{{ end }}</span>
</div>
{{ end }}
{{ end }}
{{ if .SelectedTag.Info.HasAttestations }}
<button class="badge badge-sm badge-soft badge-success cursor-pointer hover:opacity-80"
hx-get="/api/attestation-details?digest={{ .SelectedTag.Info.Digest | urlquery }}&did={{ .Owner.DID | urlquery }}&repo={{ .Repository.Name | urlquery }}"
hx-target="#attestation-modal-body"
hx-swap="innerHTML"
data-action="show-modal" data-modal-id="attestation-detail-modal">
{{ icon "shield-check" "size-3" }} Attested
</button>
{{ end }}
</div>
{{ end }}
<!-- Tag-Scoped Content (swapped via HTMX on tag change) -->
{{ template "repo-tag-section" . }}
<!-- Inline Editor (hidden, owner only) — outside HTMX-swapped section so edits survive tag changes -->
{{ if .IsOwner }}
<div id="overview-edit" class="card bg-base-200 shadow-sm p-6 hidden">
<!-- Write/Preview tabs -->
<div class="border-b border-base-300 mb-4">
<nav class="flex gap-0" role="tablist">
<button class="editor-tab px-4 py-2 text-sm font-medium border-b-2 border-primary text-primary"
data-tab="write" data-action="switch-editor-tab">
Write
</button>
<button class="editor-tab px-4 py-2 text-sm font-medium border-b-2 border-transparent text-base-content/60"
data-tab="preview" data-action="switch-editor-tab">
Preview
</button>
</nav>
</div>
<!-- Write panel -->
<div id="editor-write" class="editor-panel">
<!-- Toolbar -->
<div class="flex flex-wrap gap-1 mb-2 p-1 bg-base-200 rounded-lg">
<button type="button" class="btn btn-ghost btn-sm md:btn-xs" data-action="insert-md" data-md-type="heading" title="Heading">
{{ icon "heading" "size-4" }}
</button>
<button type="button" class="btn btn-ghost btn-sm md:btn-xs" data-action="insert-md" data-md-type="bold" title="Bold">
{{ icon "bold" "size-4" }}
</button>
<button type="button" class="btn btn-ghost btn-sm md:btn-xs" data-action="insert-md" data-md-type="italic" title="Italic">
{{ icon "italic" "size-4" }}
</button>
<div class="divider divider-horizontal mx-0"></div>
<button type="button" class="btn btn-ghost btn-sm md:btn-xs" data-action="insert-md" data-md-type="link" title="Link">
{{ icon "link" "size-4" }}
</button>
<button type="button" class="btn btn-ghost btn-sm md:btn-xs" data-action="insert-md" data-md-type="image" title="Image">
{{ icon "image" "size-4" }}
</button>
<div class="divider divider-horizontal mx-0"></div>
<button type="button" class="btn btn-ghost btn-sm md:btn-xs" data-action="insert-md" data-md-type="ul" title="Bulleted list">
{{ icon "list" "size-4" }}
</button>
<button type="button" class="btn btn-ghost btn-sm md:btn-xs" data-action="insert-md" data-md-type="ol" title="Numbered list">
{{ icon "list-ordered" "size-4" }}
</button>
<button type="button" class="btn btn-ghost btn-sm md:btn-xs" data-action="insert-md" data-md-type="code" title="Code">
{{ icon "code" "size-4" }}
</button>
</div>
<textarea id="md-editor"
class="textarea textarea-bordered w-full font-mono text-sm leading-relaxed"
rows="20"
maxlength="102400"
data-owner-did="{{ .Owner.DID }}"
data-repository="{{ .Repository.Name }}"
placeholder="Write your repository description in Markdown...">{{ .RawDescription }}</textarea>
</div>
<!-- Preview panel -->
<div id="editor-preview" class="editor-panel hidden">
<div id="preview-content" class="prose prose-sm max-w-none min-h-[20rem] p-4 border border-base-300 rounded-lg">
<p class="text-base-content/60">Nothing to preview</p>
</div>
</div>
<!-- Actions -->
<div class="flex justify-end gap-2 mt-4">
<button class="btn btn-sm btn-ghost" data-action="toggle-editor" data-show="false">Cancel</button>
<button class="btn btn-sm btn-primary" id="save-overview-btn" data-action="save-overview">Save</button>
</div>
</div>
{{ end }}
</div>
</main>
<!-- Manifest Delete Confirmation Modal -->
<dialog id="manifest-delete-modal" class="modal" aria-modal="true" aria-labelledby="manifest-delete-title">
<div class="modal-box bg-base-200">
<h3 id="manifest-delete-title" class="text-lg font-bold">Confirm Deletion</h3>
<p id="manifest-delete-message" class="py-2">This manifest has associated tags that will also be deleted:</p>
<ul id="manifest-delete-tags" class="list-disc list-inside text-sm space-y-1"></ul>
<p class="font-bold py-2 text-error">This action cannot be undone.</p>
<div class="modal-action">
<button class="btn" data-action="close-manifest-delete-modal">Cancel</button>
<button class="btn btn-error" id="confirm-manifest-delete-btn">Delete All</button>
</div>
</div>
<form method="dialog" class="modal-backdrop">
<button data-action="close-manifest-delete-modal">close</button>
</form>
</dialog>
<!-- Untagged Manifests Delete Confirmation Modal -->
<dialog id="untagged-delete-modal" class="modal" aria-modal="true" aria-labelledby="untagged-delete-title">
<div class="modal-box bg-base-200">
<h3 id="untagged-delete-title" class="text-lg font-bold">Delete Untagged Manifests</h3>
<p class="py-2">This will delete <strong>all</strong> untagged manifests in this repository, including those not currently visible.</p>
<p class="font-bold py-2 text-error">This action cannot be undone.</p>
<div class="modal-action">
<form method="dialog"><button class="btn">Cancel</button></form>
<button class="btn btn-error" id="confirm-untagged-delete-btn"
data-action="delete-untagged" data-repo="{{ .Repository.Name }}">
Delete All Untagged
</button>
</div>
</div>
<form method="dialog" class="modal-backdrop"><button>close</button></form>
</dialog>
<!-- Attestation Details Modal -->
<dialog id="attestation-detail-modal" class="modal" aria-modal="true" aria-labelledby="attestation-detail-title">
<div class="modal-box bg-base-200 max-w-2xl">
<h3 id="attestation-detail-title" class="text-lg font-bold">Attestation Details</h3>
<div id="attestation-modal-body" class="py-4">
<span class="loading loading-spinner loading-md"></span>
</div>
<div class="modal-action">
<form method="dialog"><button class="btn">Close</button></form>
</div>
</div>
<form method="dialog" class="modal-backdrop"><button>close</button></form>
</dialog>
{{ template "footer" . }}
</body>
</html>
{{ end }}