mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-08-29 04:06:58 +00:00
more visual tweaks
This commit is contained in:
@@ -2,6 +2,9 @@ root = "."
|
||||
tmp_dir = "tmp"
|
||||
|
||||
[build]
|
||||
# Use polling for Docker volume mounts (inotify doesn't work across mounts)
|
||||
poll = true
|
||||
poll_interval = 500
|
||||
# Pre-build: generate assets if missing (each string is a shell command)
|
||||
pre_cmd = ["go generate ./..."]
|
||||
cmd = "go build -buildvcs=false -o ./tmp/atcr-appview ./cmd/appview"
|
||||
@@ -12,7 +15,7 @@ exclude_regex = ["_test\\.go$", "cbor_gen\\.go$", "\\.min\\.js$"]
|
||||
delay = 3000
|
||||
stop_on_error = true
|
||||
send_interrupt = true
|
||||
kill_delay = 2000
|
||||
kill_delay = 3000
|
||||
|
||||
[log]
|
||||
time = false
|
||||
|
||||
+15
-2
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
@@ -464,9 +465,21 @@ func serveRegistry(cmd *cobra.Command, args []string) error {
|
||||
TangledRepo: cfg.CredentialHelper.TangledRepo,
|
||||
})
|
||||
|
||||
// Create listener with SO_REUSEADDR for hot-reload support
|
||||
lc := net.ListenConfig{
|
||||
Control: func(network, address string, c syscall.RawConn) error {
|
||||
return c.Control(func(fd uintptr) {
|
||||
_ = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
|
||||
})
|
||||
},
|
||||
}
|
||||
listener, err := lc.Listen(context.Background(), "tcp", cfg.Server.Addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create listener: %w", err)
|
||||
}
|
||||
|
||||
// Create HTTP server
|
||||
server := &http.Server{
|
||||
Addr: cfg.Server.Addr,
|
||||
Handler: mainRouter,
|
||||
}
|
||||
|
||||
@@ -478,7 +491,7 @@ func serveRegistry(cmd *cobra.Command, args []string) error {
|
||||
errChan := make(chan error, 1)
|
||||
go func() {
|
||||
slog.Info("Starting registry server", "addr", cfg.Server.Addr)
|
||||
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
if err := server.Serve(listener); err != nil && err != http.ErrServerClosed {
|
||||
errChan <- err
|
||||
}
|
||||
}()
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -23,14 +23,16 @@
|
||||
@plugin "daisyui/theme" {
|
||||
name: "light";
|
||||
default: true;
|
||||
--color-primary: oklch(75% 0.12 175); /* #4ECDC4 teal */
|
||||
--color-accent: oklch(68% 0.18 25); /* #FF6B6B coral */
|
||||
--color-primary: oklch(75% 0.12 175); /* #4ECDC4 teal (body) */
|
||||
--color-secondary: oklch(68% 0.18 25); /* #FF6B6B coral (cheek) */
|
||||
--color-accent: oklch(52% 0.08 185); /* #2B7A78 dark teal (mane) */
|
||||
}
|
||||
|
||||
@plugin "daisyui/theme" {
|
||||
name: "dark";
|
||||
--color-primary: oklch(78% 0.12 175); /* #5ED4CB slightly brighter */
|
||||
--color-accent: oklch(72% 0.16 25); /* #FF8080 */
|
||||
--color-primary: oklch(78% 0.12 175); /* #5ED4CB teal */
|
||||
--color-secondary: oklch(72% 0.16 25); /* #FF8080 coral */
|
||||
--color-accent: oklch(58% 0.09 185); /* #3D8B89 dark teal */
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
@@ -38,12 +40,12 @@
|
||||
======================================== */
|
||||
:root {
|
||||
--shadow-card-hover:
|
||||
0 8px 25px oklch(75% 0.12 175 / 0.15), 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
0 8px 25px oklch(75% 0.12 175 / 0.2), 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
--shadow-card-hover:
|
||||
0 8px 25px oklch(78% 0.12 175 / 0.1), 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
0 8px 25px oklch(78% 0.12 175 / 0.15), 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
@@ -62,6 +64,19 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
STICKY FOOTER LAYOUT
|
||||
======================================== */
|
||||
@layer base {
|
||||
body {
|
||||
@apply min-h-screen flex flex-col;
|
||||
}
|
||||
|
||||
main {
|
||||
@apply flex-1;
|
||||
}
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
TYPOGRAPHY (PROSE) THEME INTEGRATION
|
||||
======================================== */
|
||||
@@ -121,6 +136,25 @@
|
||||
@apply w-62 opacity-100;
|
||||
}
|
||||
|
||||
/* ----------------------------------------
|
||||
HELM BRAND COLOR (official Helm blue #0F1689)
|
||||
---------------------------------------- */
|
||||
.text-helm {
|
||||
@apply text-[#0F1689];
|
||||
}
|
||||
|
||||
[data-theme="dark"] .text-helm {
|
||||
@apply text-[#6B7FFF];
|
||||
}
|
||||
|
||||
.badge-helm {
|
||||
--badge-color: #0F1689;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .badge-helm {
|
||||
--badge-color: #6B7FFF;
|
||||
}
|
||||
|
||||
/* ----------------------------------------
|
||||
CARD EXTENSIONS
|
||||
---------------------------------------- */
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{{ define "footer" }}
|
||||
<footer class="footer footer-center bg-base-200 text-base-content p-6 mt-auto">
|
||||
<footer class="footer footer-center bg-base-200 text-base-content p-6 pt-20 mt-auto relative">
|
||||
<img src="/static/wave-pattern.svg" alt="" class="absolute top-0 left-0 w-full h-16 pointer-events-none rotate-180" aria-hidden="true">
|
||||
<nav class="flex flex-wrap justify-center items-center gap-x-2 gap-y-1 text-sm">
|
||||
<a href="/privacy" class="link link-hover">Privacy</a>
|
||||
<span class="text-base-content/30">·</span>
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
{{ template "star" (dict "IsStarred" .IsStarred "StarCount" .StarCount) }}
|
||||
{{ template "pull-count" (dict "PullCount" .PullCount) }}
|
||||
{{ if eq .ArtifactType "helm-chart" }}
|
||||
<span class="badge badge-sm badge-soft badge-primary" title="Helm chart"><i data-lucide="anchor"></i></span>
|
||||
<i data-lucide="anchor" class="size-5 text-helm" title="Helm chart"></i>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ if not .LastUpdated.IsZero }}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
Display mode: renders as span (default)
|
||||
*/}}
|
||||
{{ if .Interactive }}
|
||||
<button class="btn btn-sm gap-2{{ if .IsStarred }} btn-primary{{ else }} btn-ghost{{ end }}"
|
||||
<button class="btn btn-sm gap-2 btn-ghost group border border-transparent hover:border-primary{{ if .IsStarred }} border-amber-400!{{ end }}"
|
||||
id="star-btn"
|
||||
{{ if .IsStarred }}
|
||||
hx-delete="/api/stars/{{ .Handle }}/{{ .Repository }}"
|
||||
@@ -18,7 +18,7 @@
|
||||
hx-swap="outerHTML"
|
||||
hx-on::before-request="this.disabled=true"
|
||||
hx-on::after-request="if(event.detail.xhr.status===401) window.location='/auth/oauth/login'">
|
||||
<i data-lucide="star" class="size-4 text-amber-400 stroke-amber-400{{ if .IsStarred }} fill-amber-400{{ end }}" id="star-icon"></i>
|
||||
<i data-lucide="star" class="size-4 text-amber-400 stroke-amber-400 transition-transform group-hover:scale-110{{ if .IsStarred }} fill-amber-400{{ end }}" id="star-icon"></i>
|
||||
<span id="star-count">{{ .StarCount }}</span>
|
||||
</button>
|
||||
{{ else }}
|
||||
|
||||
@@ -75,11 +75,11 @@
|
||||
{{ if .Repository.Licenses }}
|
||||
{{ range parseLicenses .Repository.Licenses }}
|
||||
{{ if .IsValid }}
|
||||
<a href="{{ .URL }}" target="_blank" rel="noopener noreferrer" class="badge badge-md badge-secondary" title="{{ .Name }}">
|
||||
<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-secondary" title="Custom license: {{ .Name }}">
|
||||
<span class="badge badge-md badge-soft badge-secondary" title="Custom license: {{ .Name }}">
|
||||
{{ .Name }}
|
||||
</span>
|
||||
{{ end }}
|
||||
@@ -149,12 +149,12 @@
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<span class="font-mono font-semibold text-lg">{{ .Tag.Tag }}</span>
|
||||
{{ if eq .ArtifactType "helm-chart" }}
|
||||
<span class="badge badge-md badge-soft badge-primary"><i data-lucide="anchor" class="size-3"></i> Helm</span>
|
||||
<span class="badge badge-md badge-soft badge-helm"><i data-lucide="anchor" class="size-3"></i> Helm</span>
|
||||
{{ else if .IsMultiArch }}
|
||||
<span class="badge badge-md badge-primary">Multi-arch</span>
|
||||
<span class="badge badge-md badge-soft badge-primary">Multi-arch</span>
|
||||
{{ end }}
|
||||
{{ if .HasAttestations }}
|
||||
<span class="badge badge-md badge-success"><i data-lucide="shield-check" class="size-3"></i> Attestations</span>
|
||||
<span class="badge badge-md badge-soft badge-success"><i data-lucide="shield-check" class="size-3"></i> Attestations</span>
|
||||
{{ end }}
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -181,7 +181,7 @@
|
||||
{{ if .Platforms }}
|
||||
<div class="flex flex-wrap gap-1">
|
||||
{{ range .Platforms }}
|
||||
<span class="badge badge-sm badge-secondary">{{ .OS }}/{{ .Architecture }}{{ if .Variant }}/{{ .Variant }}{{ end }}</span>
|
||||
<span class="badge badge-sm badge-soft badge-secondary">{{ .OS }}/{{ .Architecture }}{{ if .Variant }}/{{ .Variant }}{{ end }}</span>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
@@ -219,12 +219,12 @@
|
||||
{{ if .IsManifestList }}
|
||||
<span class="flex items-center gap-1 font-medium"><i data-lucide="package" class="size-4"></i> Multi-arch</span>
|
||||
{{ else if eq .ArtifactType "helm-chart" }}
|
||||
<span class="flex items-center gap-1 font-medium text-primary"><i data-lucide="anchor" class="size-4"></i> Helm Chart</span>
|
||||
<span class="flex items-center gap-1 font-medium text-helm"><i data-lucide="anchor" class="size-4"></i> Helm Chart</span>
|
||||
{{ else }}
|
||||
<span class="flex items-center gap-1 font-medium"><i data-lucide="box" class="size-4"></i> Image</span>
|
||||
{{ end }}
|
||||
{{ if .HasAttestations }}
|
||||
<span class="badge badge-md badge-success"><i data-lucide="shield-check" class="size-3"></i> Attestations</span>
|
||||
<span class="badge badge-md badge-soft badge-success"><i data-lucide="shield-check" class="size-3"></i> Attestations</span>
|
||||
{{ end }}
|
||||
{{ if .Pending }}
|
||||
<span class="badge badge-sm badge-info"
|
||||
@@ -268,7 +268,7 @@
|
||||
{{ if .Platforms }}
|
||||
<div class="flex flex-wrap gap-1">
|
||||
{{ range .Platforms }}
|
||||
<span class="badge badge-sm badge-secondary">{{ .OS }}/{{ .Architecture }}{{ if .Variant }}/{{ .Variant }}{{ end }}</span>
|
||||
<span class="badge badge-sm badge-soft badge-secondary">{{ .OS }}/{{ .Architecture }}{{ if .Variant }}/{{ .Variant }}{{ end }}</span>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<span class="text-base-content/60 mx-1">:</span>
|
||||
<span class="text-base-content/60">{{ .Tag }}</span>
|
||||
{{ if eq .ArtifactType "helm-chart" }}
|
||||
<span class="badge badge-xs badge-soft badge-primary"><i data-lucide="anchor"></i></span>
|
||||
<span class="badge badge-xs badge-soft badge-helm"><i data-lucide="anchor"></i></span>
|
||||
{{ end }}
|
||||
</div>
|
||||
<div class="flex items-center gap-4 shrink-0">
|
||||
|
||||
+26
-22
@@ -573,29 +573,33 @@ func TestTemplateExecution_RepoCard(t *testing.T) {
|
||||
|
||||
// Sample data for repo-card template
|
||||
data := struct {
|
||||
OwnerHandle string
|
||||
Repository string
|
||||
IconURL string
|
||||
Description string
|
||||
StarCount int
|
||||
PullCount int
|
||||
IsStarred bool
|
||||
ArtifactType string
|
||||
Tag string
|
||||
Digest string
|
||||
LastUpdated time.Time
|
||||
OwnerHandle string
|
||||
OwnerAvatarURL string
|
||||
Repository string
|
||||
IconURL string
|
||||
Description string
|
||||
StarCount int
|
||||
PullCount int
|
||||
IsStarred bool
|
||||
ArtifactType string
|
||||
Tag string
|
||||
Digest string
|
||||
LastUpdated time.Time
|
||||
RegistryURL string
|
||||
}{
|
||||
OwnerHandle: "alice.bsky.social",
|
||||
Repository: "myapp",
|
||||
IconURL: "",
|
||||
Description: "A cool container image",
|
||||
StarCount: 42,
|
||||
PullCount: 1337,
|
||||
IsStarred: true,
|
||||
ArtifactType: "container-image",
|
||||
Tag: "latest",
|
||||
Digest: "sha256:abc123def456",
|
||||
LastUpdated: time.Now().Add(-24 * time.Hour),
|
||||
OwnerHandle: "alice.bsky.social",
|
||||
OwnerAvatarURL: "",
|
||||
Repository: "myapp",
|
||||
IconURL: "",
|
||||
Description: "A cool container image",
|
||||
StarCount: 42,
|
||||
PullCount: 1337,
|
||||
IsStarred: true,
|
||||
ArtifactType: "container-image",
|
||||
Tag: "latest",
|
||||
Digest: "sha256:abc123def456",
|
||||
LastUpdated: time.Now().Add(-24 * time.Hour),
|
||||
RegistryURL: "atcr.io",
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
Reference in New Issue
Block a user