Files
at-container-registry/pkg/hold/admin/templates/partials/gc_preview.html
T
2026-02-18 21:40:53 -06:00

183 lines
7.6 KiB
HTML

{{define "partials/gc_preview.html"}}
<div class="space-y-6">
<!-- Summary cards -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<div class="stats shadow bg-base-100">
<div class="stat">
<div class="stat-title">Orphaned Records</div>
<div class="stat-value {{if .Preview.OrphanedRecords}}text-error{{end}}">{{len .Preview.OrphanedRecords}}</div>
<div class="stat-desc">Layer records with no manifest</div>
</div>
</div>
<div class="stats shadow bg-base-100">
<div class="stat">
<div class="stat-title">Orphaned Blobs</div>
<div class="stat-value {{if .Preview.OrphanedBlobs}}text-error{{end}}">{{len .Preview.OrphanedBlobs}}</div>
<div class="stat-desc">S3 blobs with no layer record</div>
</div>
</div>
<div class="stats shadow bg-base-100">
<div class="stat">
<div class="stat-title">Missing Records</div>
<div class="stat-value {{if .Preview.MissingRecords}}text-warning{{end}}">{{len .Preview.MissingRecords}}</div>
<div class="stat-desc">Would be reconciled</div>
</div>
</div>
<div class="stats shadow bg-base-100">
<div class="stat">
<div class="stat-title">Referenced Blobs</div>
<div class="stat-value text-success">{{.Preview.ReferencedBlobs}}</div>
<div class="stat-desc">{{formatDuration .Preview.Duration}}</div>
</div>
</div>
</div>
<!-- Meta info -->
<p class="text-sm text-base-content/50">
Checked {{.Preview.UsersChecked}} users, {{.Preview.ManifestsChecked}} manifests, {{.Preview.TotalLayerRecords}} layer records, {{.Preview.TotalBlobs}} S3 blobs.
</p>
<!-- Orphaned Records table -->
{{if .Preview.OrphanedRecords}}
<div class="collapse collapse-arrow bg-base-100 shadow-sm">
<input type="checkbox" />
<div class="collapse-title font-medium">
{{ icon "file-x" "size-4 inline" }} Orphaned Layer Records ({{len .Preview.OrphanedRecords}})
</div>
<div class="collapse-content">
<div class="overflow-x-auto">
<table class="table table-sm">
<thead>
<tr>
<th>RKey</th>
<th>Digest</th>
<th>Manifest</th>
<th>Size</th>
</tr>
</thead>
<tbody>
{{range .Preview.OrphanedRecords}}
<tr>
<td><code class="text-xs font-mono">{{.Rkey}}</code></td>
<td><code class="text-xs font-mono">{{truncate .Digest 24}}</code></td>
<td><code class="text-xs font-mono break-all">{{truncate .ManifestURI 50}}</code></td>
<td class="whitespace-nowrap">{{formatBytes .Size}}</td>
</tr>
{{end}}
</tbody>
</table>
</div>
</div>
</div>
{{end}}
<!-- Orphaned Blobs table -->
{{if .Preview.OrphanedBlobs}}
<div class="collapse collapse-arrow bg-base-100 shadow-sm">
<input type="checkbox" />
<div class="collapse-title font-medium">
{{ icon "trash-2" "size-4 inline" }} Orphaned Blobs ({{len .Preview.OrphanedBlobs}})
</div>
<div class="collapse-content">
<div class="overflow-x-auto">
<table class="table table-sm">
<thead>
<tr>
<th>Digest</th>
<th>Size</th>
</tr>
</thead>
<tbody>
{{range .Preview.OrphanedBlobs}}
<tr>
<td><code class="text-xs font-mono">{{truncate .Digest 30}}</code></td>
<td class="whitespace-nowrap">{{formatBytes .Size}}</td>
</tr>
{{end}}
</tbody>
</table>
</div>
</div>
</div>
{{end}}
<!-- Missing Records table (reconcile mode only) -->
{{if .Preview.MissingRecords}}
<div class="collapse collapse-arrow bg-base-100 shadow-sm">
<input type="checkbox" />
<div class="collapse-title font-medium">
{{ icon "file-plus" "size-4 inline" }} Missing Layer Records ({{len .Preview.MissingRecords}})
</div>
<div class="collapse-content">
<div class="overflow-x-auto">
<table class="table table-sm">
<thead>
<tr>
<th>Digest</th>
<th>Manifest</th>
<th>User</th>
<th>Size</th>
</tr>
</thead>
<tbody>
{{range .Preview.MissingRecords}}
<tr>
<td><code class="text-xs font-mono">{{truncate .Digest 24}}</code></td>
<td><code class="text-xs font-mono break-all">{{truncate .ManifestURI 50}}</code></td>
<td><code class="text-xs font-mono">{{truncate .UserDID 24}}</code></td>
<td class="whitespace-nowrap">{{formatBytes .Size}}</td>
</tr>
{{end}}
</tbody>
</table>
</div>
</div>
</div>
{{end}}
<!-- Individual action buttons -->
{{if or .Preview.OrphanedRecords .Preview.OrphanedBlobs .Preview.MissingRecords}}
<div class="flex flex-wrap items-center gap-3 mt-6">
{{if .Preview.MissingRecords}}
<button class="btn btn-warning gap-2"
hx-post="/admin/api/gc/reconcile"
hx-target="#gc-results"
hx-swap="innerHTML">
{{ icon "file-plus" "size-4" }}
Reconcile {{len .Preview.MissingRecords}} Records
</button>
{{end}}
{{if .Preview.OrphanedRecords}}
<button class="btn btn-error gap-2"
hx-post="/admin/api/gc/delete-records"
hx-target="#gc-results"
hx-swap="innerHTML"
hx-confirm="Delete {{len .Preview.OrphanedRecords}} orphaned layer records?">
{{ icon "file-x" "size-4" }}
Delete {{len .Preview.OrphanedRecords}} Orphaned Records
</button>
{{end}}
{{if .Preview.OrphanedBlobs}}
<button class="btn btn-error gap-2"
hx-post="/admin/api/gc/delete-blobs"
hx-target="#gc-results"
hx-swap="innerHTML"
hx-confirm="Delete {{len .Preview.OrphanedBlobs}} orphaned blobs from S3? This cannot be undone.">
{{ icon "trash-2" "size-4" }}
Delete {{len .Preview.OrphanedBlobs}} Orphaned Blobs
</button>
{{end}}
</div>
<p class="text-sm text-base-content/50 mt-2">Run Scan again after each operation to see updated counts.</p>
{{end}}
<!-- Nothing to clean -->
{{if and (not .Preview.OrphanedRecords) (not .Preview.OrphanedBlobs) (not .Preview.MissingRecords)}}
<div class="alert alert-success">
{{ icon "check-circle" "size-5" }}
<span>Storage is clean. No orphaned data found.</span>
</div>
{{end}}
</div>
{{end}}