minor ui fixes, update privacy page with manged holds list

This commit is contained in:
Evan Jarrett
2026-08-02 13:38:45 -05:00
parent 2e55352974
commit 6e77311c22
8 changed files with 35 additions and 8 deletions
+1
View File
@@ -37,3 +37,4 @@ pkg/hold/admin/public/css/style.css
Thumbs.db Thumbs.db
node_modules node_modules
.impeccable.md .impeccable.md
.playwright-mcp/
+1
View File
@@ -44,6 +44,7 @@ type BaseUIHandler struct {
// Config // Config
DefaultHoldDID string DefaultHoldDID string
ManagedHolds []string // DIDs of holds this appview operates (server.managed_holds)
CompanyName string CompanyName string
Jurisdiction string Jurisdiction string
ClientName string // Full name: "AT Container Registry" ClientName string // Full name: "AT Container Registry"
+16
View File
@@ -1,6 +1,7 @@
package handlers package handlers
import ( import (
"context"
"net/http" "net/http"
"time" "time"
) )
@@ -12,6 +13,7 @@ type LegalPageData struct {
CompanyName string CompanyName string
Jurisdiction string Jurisdiction string
LastUpdated string LastUpdated string
ManagedHolds []string // Resolved display names of the holds this appview operates
} }
// legalDefaults applies sensible fallbacks for operators who haven't set // legalDefaults applies sensible fallbacks for operators who haven't set
@@ -52,6 +54,19 @@ type PrivacyPolicyHandler struct {
BaseUIHandler BaseUIHandler
} }
// resolveManagedHoldNames maps the configured managed-hold DIDs to friendly
// display names (handle, decoded did:web domain, or truncated did:plc) for
// listing on the privacy page.
func (h *PrivacyPolicyHandler) resolveManagedHoldNames(ctx context.Context) []string {
names := make([]string, 0, len(h.ManagedHolds))
for _, did := range h.ManagedHolds {
if name := resolveHoldDisplayName(ctx, &h.BaseUIHandler, did); name != "" {
names = append(names, name)
}
}
return names
}
func (h *PrivacyPolicyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h *PrivacyPolicyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
meta := NewPageMeta( meta := NewPageMeta(
"Privacy Policy - "+h.ClientShortName, "Privacy Policy - "+h.ClientShortName,
@@ -66,6 +81,7 @@ func (h *PrivacyPolicyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
CompanyName: company, CompanyName: company,
Jurisdiction: jurisdiction, Jurisdiction: jurisdiction,
LastUpdated: formatLegalDate(privacyLastUpdated), LastUpdated: formatLegalDate(privacyLastUpdated),
ManagedHolds: h.resolveManagedHoldNames(r.Context()),
} }
if err := h.Templates.ExecuteTemplate(w, "privacy", data); err != nil { if err := h.Templates.ExecuteTemplate(w, "privacy", data); err != nil {
+2
View File
@@ -40,6 +40,7 @@ type UIDependencies struct {
ReadmeFetcher *readme.Fetcher ReadmeFetcher *readme.Fetcher
Templates *template.Template Templates *template.Template
DefaultHoldDID string DefaultHoldDID string
ManagedHolds []string // DIDs of holds this appview operates (server.managed_holds)
LegalConfig LegalConfig LegalConfig LegalConfig
ClientName string // Full name: "AT Container Registry" ClientName string // Full name: "AT Container Registry"
ClientShortName string // Short name: "ATCR" ClientShortName string // Short name: "ATCR"
@@ -79,6 +80,7 @@ func RegisterUIRoutes(router chi.Router, deps UIDependencies) {
BillingManager: deps.BillingManager, BillingManager: deps.BillingManager,
WebhookDispatcher: deps.WebhookDispatcher, WebhookDispatcher: deps.WebhookDispatcher,
DefaultHoldDID: deps.DefaultHoldDID, DefaultHoldDID: deps.DefaultHoldDID,
ManagedHolds: deps.ManagedHolds,
CompanyName: deps.LegalConfig.CompanyName, CompanyName: deps.LegalConfig.CompanyName,
Jurisdiction: deps.LegalConfig.Jurisdiction, Jurisdiction: deps.LegalConfig.Jurisdiction,
ClientName: deps.ClientName, ClientName: deps.ClientName,
+1
View File
@@ -346,6 +346,7 @@ func NewAppViewServer(cfg *Config, branding *BrandingOverrides) (*AppViewServer,
ReadmeFetcher: s.ReadmeFetcher, ReadmeFetcher: s.ReadmeFetcher,
Templates: s.Templates, Templates: s.Templates,
DefaultHoldDID: defaultHoldDID, DefaultHoldDID: defaultHoldDID,
ManagedHolds: cfg.Server.ManagedHolds,
ClientName: cfg.Server.ClientName, ClientName: cfg.Server.ClientName,
ClientShortName: cfg.Server.ClientShortName, ClientShortName: cfg.Server.ClientShortName,
BillingManager: s.BillingManager, BillingManager: s.BillingManager,
+5 -2
View File
@@ -17,8 +17,11 @@
<link rel="dns-prefetch" href="https://imgs.blue"> <link rel="dns-prefetch" href="https://imgs.blue">
<!-- Preload critical assets. Font list is discovered from the public FS <!-- Preload critical assets. Font list is discovered from the public FS
at startup so renaming a .woff2 doesn't silently 404. --> at startup so renaming a .woff2 doesn't silently 404.
<link rel="preload" href="/icons.svg" as="image" type="image/svg+xml"> icons.svg is intentionally NOT preloaded: it is consumed via SVG
<use href="/icons.svg#id"> references, whose fetch has request
destination "empty" and never matches an as="image" (or any) preload,
so the hint was always dropped as "preloaded but not used". -->
{{ range fontPreloads }} {{ range fontPreloads }}
<link rel="preload" href="{{ . }}" as="font" type="font/woff2" crossorigin> <link rel="preload" href="{{ . }}" as="font" type="font/woff2" crossorigin>
{{ end }} {{ end }}
+6 -3
View File
@@ -1,3 +1,6 @@
{{/* Renders the comma-separated list of managed holds we operate, falling back
to a generic example if none are configured. Expects LegalPageData as dot. */}}
{{ define "managedHoldsList" }}{{ if .ManagedHolds }}{{ range $i, $h := .ManagedHolds }}{{ if $i }}, {{ end }}<code class="bg-base-300 px-1.5 py-0.5 rounded text-sm font-mono">{{ $h }}</code>{{ end }}{{ else }}<code class="bg-base-300 px-1.5 py-0.5 rounded text-sm font-mono">hold01.{{ .SiteURL }}</code>{{ end }}{{ end }}
{{ define "privacy" }} {{ define "privacy" }}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
@@ -21,7 +24,7 @@
<h3 class="text-lg font-medium mt-6">Data Stored on Our Infrastructure</h3> <h3 class="text-lg font-medium mt-6">Data Stored on Our Infrastructure</h3>
<p><strong>Layer Records:</strong> Our hold services (e.g., <code class="bg-base-300 px-1.5 py-0.5 rounded text-sm font-mono">hold01.{{ .SiteURL }}</code>) maintain records in their embedded PDS that reference container image layers you publish. These records are public and link your AT Protocol identity (DID) to content-addressed SHA identifiers.</p> <p><strong>Layer Records:</strong> Our hold services ({{ template "managedHoldsList" . }}) maintain records in their embedded PDS that reference container image layers you publish. These records are public and link your AT Protocol identity (DID) to content-addressed SHA identifiers.</p>
<p><strong>OCI Blobs:</strong> Container image layers are stored in our object storage (S3). These blobs are content-addressed and deduplicated—meaning identical layers uploaded by different users are stored only once.</p> <p><strong>OCI Blobs:</strong> Container image layers are stored in our object storage (S3). These blobs are content-addressed and deduplicated—meaning identical layers uploaded by different users are stored only once.</p>
@@ -58,7 +61,7 @@
</ul> </ul>
<h3 class="text-lg font-medium mt-6">{{ .ClientShortName }}-Hosted Hold Services</h3> <h3 class="text-lg font-medium mt-6">{{ .ClientShortName }}-Hosted Hold Services</h3>
<p>Storage backends we operate (e.g., <code class="bg-base-300 px-1.5 py-0.5 rounded text-sm font-mono">hold01.{{ .SiteURL }}</code>). Each hold has an embedded PDS and stores:</p> <p>Storage backends we operate ({{ template "managedHoldsList" . }}). Each hold has an embedded PDS and stores:</p>
<ul class="list-disc list-inside space-y-1 ml-4"> <ul class="list-disc list-inside space-y-1 ml-4">
<li>OCI blobs (container image layers) in object storage</li> <li>OCI blobs (container image layers) in object storage</li>
<li>Layer records in the hold's embedded PDS linking your DID to blob references</li> <li>Layer records in the hold's embedded PDS linking your DID to blob references</li>
@@ -276,7 +279,7 @@
<p>{{ .CompanyName }} supports "Bring Your Own Storage" where users can deploy their own hold services to store container image blobs. This section explains how BYOS affects your privacy rights.</p> <p>{{ .CompanyName }} supports "Bring Your Own Storage" where users can deploy their own hold services to store container image blobs. This section explains how BYOS affects your privacy rights.</p>
<h3 class="text-lg font-medium mt-4">{{ .CompanyName }}-Hosted Holds</h3> <h3 class="text-lg font-medium mt-4">{{ .CompanyName }}-Hosted Holds</h3>
<p>Hold services on <code class="bg-base-300 px-1.5 py-0.5 rounded text-sm font-mono">*.{{ .SiteURL }}</code> domains (e.g., <code class="bg-base-300 px-1.5 py-0.5 rounded text-sm font-mono">hold01.{{ .SiteURL }}</code>) are operated by us and fully covered by this privacy policy. We can fulfill all data access, export, and deletion requests for these services.</p> <p>Hold services on <code class="bg-base-300 px-1.5 py-0.5 rounded text-sm font-mono">*.{{ .SiteURL }}</code> domains ({{ template "managedHoldsList" . }}) are operated by us and fully covered by this privacy policy. We can fulfill all data access, export, and deletion requests for these services.</p>
<h3 class="text-lg font-medium mt-6">User-Deployed Holds</h3> <h3 class="text-lg font-medium mt-6">User-Deployed Holds</h3>
<p>If you use a hold service not operated by us:</p> <p>If you use a hold service not operated by us:</p>
+3 -3
View File
@@ -216,7 +216,7 @@
<!-- Manifest Delete Confirmation Modal --> <!-- Manifest Delete Confirmation Modal -->
<dialog id="manifest-delete-modal" class="modal" aria-modal="true" aria-labelledby="manifest-delete-title"> <dialog id="manifest-delete-modal" class="modal" aria-modal="true" aria-labelledby="manifest-delete-title">
<div class="modal-box bg-base-200"> <div class="modal-box bg-base-200">
<h3 id="manifest-delete-title" class="text-lg font-bold">Confirm Deletion</h3> <h2 id="manifest-delete-title" class="text-lg font-bold">Confirm Deletion</h2>
<p id="manifest-delete-message" class="py-2">This manifest has associated tags that will also be deleted:</p> <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> <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> <p class="font-bold py-2 text-error">This action cannot be undone.</p>
@@ -233,7 +233,7 @@
<!-- Untagged Manifests Delete Confirmation Modal --> <!-- Untagged Manifests Delete Confirmation Modal -->
<dialog id="untagged-delete-modal" class="modal" aria-modal="true" aria-labelledby="untagged-delete-title"> <dialog id="untagged-delete-modal" class="modal" aria-modal="true" aria-labelledby="untagged-delete-title">
<div class="modal-box bg-base-200"> <div class="modal-box bg-base-200">
<h3 id="untagged-delete-title" class="text-lg font-bold">Delete Untagged Manifests</h3> <h2 id="untagged-delete-title" class="text-lg font-bold">Delete Untagged Manifests</h2>
<p class="py-2">This will delete <strong>all</strong> untagged manifests in this repository, including those not currently visible.</p> <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> <p class="font-bold py-2 text-error">This action cannot be undone.</p>
<div class="modal-action"> <div class="modal-action">
@@ -251,7 +251,7 @@
<!-- Attestation Details Modal --> <!-- Attestation Details Modal -->
<dialog id="attestation-detail-modal" class="modal" aria-modal="true" aria-labelledby="attestation-detail-title"> <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"> <div class="modal-box bg-base-200 max-w-2xl">
<h3 id="attestation-detail-title" class="text-lg font-bold">Attestation Details</h3> <h2 id="attestation-detail-title" class="text-lg font-bold">Attestation Details</h2>
<div id="attestation-modal-body" class="py-4"> <div id="attestation-modal-body" class="py-4">
<span class="loading loading-spinner loading-md"></span> <span class="loading loading-spinner loading-md"></span>
</div> </div>