Files
at-container-registry/pkg/appview/templates/pages/repository.html
T

265 lines
14 KiB
HTML

{{ define "repository" }}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ if .Repository.Title }}{{ .Repository.Title }}{{ else }}{{ .Owner.Handle }}/{{ .Repository.Name }}{{ end }} - ATCR</title>
<link rel="stylesheet" href="/static/css/style.css">
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
<script src="/static/js/app.js"></script>
</head>
<body>
{{ template "nav" . }}
<main class="container">
<div class="repository-page">
<!-- Repository Header -->
<div class="repository-header">
<div class="repo-hero">
{{ if .Repository.IconURL }}
<img src="{{ .Repository.IconURL }}" alt="{{ .Repository.Name }}" class="repo-hero-icon">
{{ else }}
<div class="repo-hero-icon-placeholder">{{ firstChar .Repository.Name }}</div>
{{ end }}
<div class="repo-hero-info">
<h1>
<a href="/u/{{ .Owner.Handle }}" class="owner-link">{{ .Owner.Handle }}</a>
<span class="repo-separator">/</span>
<span class="repo-name">{{ .Repository.Name }}</span>
</h1>
{{ if .Repository.Description }}
<p class="repo-hero-description">{{ .Repository.Description }}</p>
{{ end }}
</div>
</div>
<!-- Star Button -->
<div class="repo-actions">
<button class="star-btn{{ if .IsStarred }} starred{{ end }}" id="star-btn" onclick="toggleStar('{{ .Owner.Handle }}', '{{ .Repository.Name }}')">
<span class="star-icon" id="star-icon">{{ if .IsStarred }}★{{ else }}☆{{ end }}</span>
<span class="star-count" id="star-count">{{ .StarCount }}</span>
</button>
</div>
<!-- Metadata Section -->
{{ if or .Repository.Licenses .Repository.SourceURL .Repository.DocumentationURL .Repository.Version }}
<div class="repo-metadata">
{{ if .Repository.Version }}
<span class="metadata-badge version-badge" title="Version">
{{ .Repository.Version }}
</span>
{{ end }}
{{ if .Repository.Licenses }}
{{ range parseLicenses .Repository.Licenses }}
{{ if .IsValid }}
<a href="{{ .URL }}" target="_blank" rel="noopener noreferrer" class="metadata-badge license-badge" title="{{ .Name }}">
{{ .SPDXID }}
</a>
{{ else }}
<span class="metadata-badge license-badge" title="Custom license: {{ .Name }}">
{{ .Name }}
</span>
{{ end }}
{{ end }}
{{ end }}
{{ if .Repository.SourceURL }}
<a href="{{ .Repository.SourceURL }}" target="_blank" class="metadata-link">
Source
</a>
{{ end }}
{{ if .Repository.DocumentationURL }}
<a href="{{ .Repository.DocumentationURL }}" target="_blank" class="metadata-link">
Documentation
</a>
{{ end }}
</div>
{{ end }}
<!-- Pull Command -->
<div class="pull-command-section">
<h3>Pull this image</h3>
{{ if .Tags }}
{{ $firstTag := index .Tags 0 }}
<div class="push-command">
<code class="pull-command">docker pull {{ $.RegistryURL }}/{{ $.Owner.Handle }}/{{ $.Repository.Name }}:{{ $firstTag.Tag.Tag }}</code>
<button class="copy-btn" onclick="copyToClipboard('docker pull {{ $.RegistryURL }}/{{ $.Owner.Handle }}/{{ $.Repository.Name }}:{{ $firstTag.Tag.Tag }}')">
Copy
</button>
</div>
{{ else }}
<div class="push-command">
<code class="pull-command">docker pull {{ $.RegistryURL }}/{{ $.Owner.Handle }}/{{ $.Repository.Name }}:latest</code>
<button class="copy-btn" onclick="copyToClipboard('docker pull {{ $.RegistryURL }}/{{ $.Owner.Handle }}/{{ $.Repository.Name }}:latest')">
Copy
</button>
</div>
{{ end }}
</div>
</div>
<!-- README and Tags/Manifests Layout -->
{{ if .ReadmeHTML }}
<div class="repo-content-layout">
<!-- README Section (Left) -->
<div class="readme-section">
<h2>Overview</h2>
<div class="readme-content markdown-body">
{{ .ReadmeHTML }}
</div>
</div>
<!-- Tags and Manifests (Right) -->
<div class="repo-sidebar">
{{ end }}
<!-- Tags Section -->
<div class="repo-section">
<h2>Tags</h2>
{{ if .Tags }}
<div class="tags-list">
{{ range .Tags }}
<div class="tag-item" id="tag-{{ .Tag.Tag }}">
<div class="tag-item-header">
<div>
<span class="tag-name-large">{{ .Tag.Tag }}</span>
{{ if .IsMultiArch }}
<span class="badge-multi">Multi-arch</span>
{{ end }}
</div>
<div style="display: flex; gap: 1rem; align-items: center;">
<time class="tag-timestamp" datetime="{{ .Tag.CreatedAt.Format "2006-01-02T15:04:05Z07:00" }}">
{{ timeAgo .Tag.CreatedAt }}
</time>
{{ if $.IsOwner }}
<button class="delete-btn"
hx-delete="/api/images/{{ $.Repository.Name }}/tags/{{ .Tag.Tag }}"
hx-confirm="Delete tag {{ .Tag.Tag }}?"
hx-target="#tag-{{ .Tag.Tag }}"
hx-swap="outerHTML">
🗑️
</button>
{{ end }}
</div>
</div>
<div class="tag-item-details">
<div style="display: flex; justify-content: space-between; align-items: center;">
<div class="digest-container">
<code class="digest" title="{{ .Tag.Digest }}">{{ .Tag.Digest }}</code>
<button class="digest-copy-btn" onclick="copyToClipboard('{{ .Tag.Digest }}')">📋</button>
</div>
{{ if .Platforms }}
<div class="platforms-inline">
{{ range .Platforms }}
<span class="platform-badge">{{ .OS }}/{{ .Architecture }}{{ if .Variant }}/{{ .Variant }}{{ end }}</span>
{{ end }}
</div>
{{ end }}
</div>
</div>
<div class="push-command">
<code class="pull-command">docker pull {{ $.RegistryURL }}/{{ $.Owner.Handle }}/{{ $.Repository.Name }}:{{ .Tag.Tag }}</code>
<button class="copy-btn" onclick="copyToClipboard('docker pull {{ $.RegistryURL }}/{{ $.Owner.Handle }}/{{ $.Repository.Name }}:{{ .Tag.Tag }}')">
Copy
</button>
</div>
</div>
{{ end }}
</div>
{{ else }}
<p class="empty-message">No tags available</p>
{{ end }}
</div>
<!-- Manifests Section -->
<div class="repo-section">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
<h2>Manifests</h2>
<label class="show-offline-toggle">
<input type="checkbox" id="show-offline-toggle" onchange="toggleOfflineManifests()">
<span>Show offline images</span>
</label>
</div>
{{ if .Manifests }}
<div class="manifests-list">
{{ range .Manifests }}
<div class="manifest-item" id="manifest-{{ sanitizeID .Manifest.Digest }}" data-reachable="{{ .Reachable }}">
<div class="manifest-item-header">
<div>
{{ if .IsManifestList }}
<span class="manifest-type">📦 Multi-arch</span>
{{ else }}
<span class="manifest-type">📄 Image</span>
{{ end }}
{{ if .Pending }}
<span class="checking-badge"
hx-get="/api/manifest-health?endpoint={{ .Manifest.HoldEndpoint | urlquery }}"
hx-trigger="load delay:2s"
hx-swap="outerHTML">
🔄 Checking...
</span>
{{ else if not .Reachable }}
<span class="offline-badge">⚠️ Offline</span>
{{ end }}
<div class="digest-container">
<code class="digest manifest-digest" title="{{ .Manifest.Digest }}">{{ .Manifest.Digest }}</code>
<button class="digest-copy-btn" onclick="copyToClipboard('{{ .Manifest.Digest }}')">📋</button>
</div>
</div>
<div style="display: flex; gap: 1rem; align-items: center;">
<time datetime="{{ .Manifest.CreatedAt.Format "2006-01-02T15:04:05Z07:00" }}">
{{ timeAgo .Manifest.CreatedAt }}
</time>
{{ if $.IsOwner }}
<button class="delete-btn"
hx-delete="/api/images/{{ $.Repository.Name }}/manifests/{{ .Manifest.Digest }}"
hx-confirm="Delete manifest {{ .Manifest.Digest }}? This cannot be undone."
hx-target="#manifest-{{ sanitizeID .Manifest.Digest }}"
hx-swap="outerHTML">
🗑️
</button>
{{ end }}
</div>
</div>
<div class="manifest-item-details">
<div style="display: flex; justify-content: space-between; align-items: center;">
<div>
{{ if .Tags }}
<span class="manifest-detail-label">Tags:</span>
{{ range $index, $tag := .Tags }}{{ if $index }}, {{ end }}{{ $tag }}{{ end }}
{{ else }}
<span class="text-muted">(untagged)</span>
{{ end }}
</div>
{{ if .IsManifestList }}
{{ if .Platforms }}
<div class="platforms-inline">
{{ range .Platforms }}
<span class="platform-badge">{{ .OS }}/{{ .Architecture }}{{ if .Variant }}/{{ .Variant }}{{ end }}</span>
{{ end }}
</div>
{{ end }}
{{ end }}
</div>
</div>
</div>
{{ end }}
</div>
{{ else }}
<p class="empty-message">No manifests available</p>
{{ end }}
</div>
{{ if .ReadmeHTML }}
</div><!-- Close repo-sidebar -->
</div><!-- Close repo-content-layout -->
{{ end }}
</div>
</main>
<!-- Modal container for HTMX -->
<div id="modal"></div>
</body>
</html>
{{ end }}