mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-09-24 11:14:14 +00:00
265 lines
14 KiB
HTML
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 }}
|