feat: add admin links management and updaterup page

- Implemented AdminLinksPage for managing link entries with admin role check.
- Created API routes for fetching, updating, and deleting link entries.
- Added ProductTabs component for navigation between Backerup and Updaterup.
- Developed UpdaterupPage with detailed features, setup instructions, and statistics.
- Introduced links database functionality for runtime-editable link overrides.
This commit is contained in:
Anders Böttcher
2026-05-13 10:53:21 +02:00
parent 33e339bde6
commit 984b282a95
16 changed files with 1342 additions and 51 deletions
+131 -5
View File
@@ -4,6 +4,7 @@ import AnimateIn from "./components/AnimateIn";
import CountUp from "./components/CountUp";
import SpotlightCard from "./components/SpotlightCard";
import { LINKS } from "@/lib/links";
import { getLinks } from "@/lib/links-db";
/* ── SVG Icons ─────────────────────────────────────────────── */
const CubeIcon = () => (
@@ -149,7 +150,8 @@ const stats = [
];
/* ── Page ───────────────────────────────────────────────────── */
export default function LandingPage() {
export default async function LandingPage() {
const links = await getLinks();
return (
<>
{/* ═══ Hero ═══════════════════════════════════════════════ */}
@@ -245,7 +247,7 @@ export default function LandingPage() {
</svg>
</Link>
<a
href={LINKS.gitea}
href={links.gitea}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 rounded-xl border border-white/8 bg-white/3 px-7 py-3.5 text-sm font-semibold text-slate-300 transition-all hover:bg-white/8 hover:text-white hover:scale-105"
@@ -278,7 +280,7 @@ export default function LandingPage() {
</div>
</div>
<div className="p-6 min-h-[260px]">
<TerminalTyper />
<TerminalTyper links={{ dockerImage: links.dockerImage, uiPort: links.uiPort }} />
</div>
</div>
</div>
@@ -508,13 +510,137 @@ export default function LandingPage() {
<span className="ml-3 text-xs font-mono text-slate-500">docker-compose setup</span>
</div>
<div className="p-6">
<TerminalTyper mode="docker-compose" />
<TerminalTyper mode="docker-compose" links={{ dockerImage: links.dockerImage, uiPort: links.uiPort }} />
</div>
</div>
</div>
</AnimateIn>
</section>
{/* ═══ More tools ═════════════════════════════════════════ */}
<section className="px-6 py-24">
<div className="mx-auto max-w-6xl">
<AnimateIn variant="fade-up">
<div className="mx-auto max-w-2xl text-center">
<span className="inline-block rounded-full border border-violet-500/30 bg-violet-500/10 px-4 py-1 text-xs font-semibold uppercase tracking-widest text-violet-400">
More tools
</span>
<h2 className="mt-5 text-4xl font-bold tracking-tight text-white">
Built for Docker operators
</h2>
<p className="mt-4 text-slate-400 leading-relaxed">
Backerup is part of a growing suite of self-hosted Docker management tools all
open-source, dark-mode-first, and designed to run alongside your containers.
</p>
</div>
</AnimateIn>
<div className="mt-14 grid gap-6 lg:grid-cols-2">
{/* Backerup card */}
<AnimateIn variant="fade-up" delay={0}>
<SpotlightCard className="rounded-2xl border border-cyan-500/20 bg-gradient-to-br from-cyan-500/10 to-blue-600/10 p-8 h-full card-hover flex flex-col">
<div className="flex items-start justify-between gap-4">
<div className="inline-flex items-center justify-center w-12 h-12 rounded-xl bg-cyan-500/10 text-cyan-400 shrink-0">
<CubeIcon />
</div>
<span className="inline-flex items-center gap-1.5 rounded-full border border-cyan-500/30 bg-cyan-500/10 px-3 py-1 text-xs font-semibold text-cyan-400">
<span className="w-1.5 h-1.5 rounded-full bg-cyan-400" />
Current
</span>
</div>
<h3 className="mt-5 text-xl font-bold text-white">Backerup</h3>
<p className="mt-2 text-sm text-slate-400 leading-relaxed">
Full Docker container backup manager. Archive volumes, bind mounts, and config
into a single portable <code className="font-mono text-cyan-300 text-xs">.backerup</code> bundle.
Restore, migrate, or share all from a self-hosted UI.
</p>
<ul className="mt-5 space-y-2 text-sm text-slate-400 flex-1">
{[
"Full volume + config archives",
"Config-only export for fast migrations",
"One-click restore or container recreation",
"Cron-based schedules with retention policies",
].map((f) => (
<li key={f} className="flex items-center gap-2.5">
<svg viewBox="0 0 24 24" className="w-4 h-4 shrink-0 text-cyan-500" fill="none" stroke="currentColor" strokeWidth="2.5" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" d="M4.5 12.75l6 6 9-13.5" />
</svg>
{f}
</li>
))}
</ul>
<div className="mt-7 flex gap-3">
<a
href={links.gitea}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 rounded-xl border border-white/10 bg-white/5 px-5 py-2.5 text-xs font-semibold text-slate-300 hover:bg-white/10 hover:text-white transition-all"
>
<GitHubIcon />
Gitea
</a>
</div>
</SpotlightCard>
</AnimateIn>
{/* Updaterup card */}
<AnimateIn variant="fade-up" delay={100}>
<SpotlightCard className="rounded-2xl border border-violet-500/20 bg-gradient-to-br from-violet-500/10 to-purple-600/10 p-8 h-full card-hover flex flex-col">
<div className="flex items-start justify-between gap-4">
<div className="inline-flex items-center justify-center w-12 h-12 rounded-xl bg-violet-500/10 text-violet-400 shrink-0">
<RefreshIcon />
</div>
<span className="inline-flex items-center gap-1.5 rounded-full border border-violet-500/30 bg-violet-500/10 px-3 py-1 text-xs font-semibold text-violet-400">
<span className="w-1.5 h-1.5 rounded-full bg-violet-400" />
New
</span>
</div>
<h3 className="mt-5 text-xl font-bold text-white">Updaterup</h3>
<p className="mt-2 text-sm text-slate-400 leading-relaxed">
Docker container update manager. Continuously checks your running containers for
new image versions and lets you update them with a single click or automatically
on a cron schedule.
</p>
<ul className="mt-5 space-y-2 text-sm text-slate-400 flex-1">
{[
"Digest-based update detection",
"One-click update — preserves all original config",
"Batch check & update all containers",
"Named cron schedules with full audit history",
"Live log viewer with level filtering",
].map((f) => (
<li key={f} className="flex items-center gap-2.5">
<svg viewBox="0 0 24 24" className="w-4 h-4 shrink-0 text-violet-500" fill="none" stroke="currentColor" strokeWidth="2.5" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" d="M4.5 12.75l6 6 9-13.5" />
</svg>
{f}
</li>
))}
</ul>
<div className="mt-7 flex gap-3">
<Link
href="/updaterup"
className="flex items-center gap-2 rounded-xl px-5 py-2.5 text-xs font-bold text-white transition-all hover:brightness-110"
style={{ background: "linear-gradient(135deg, #a78bfa, #7c3aed)" }}
>
Explore Updaterup
</Link>
<a
href={links.updaterupGitea}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 rounded-xl border border-white/10 bg-white/5 px-5 py-2.5 text-xs font-semibold text-slate-300 hover:bg-white/10 hover:text-white transition-all"
>
<GitHubIcon />
Gitea
</a>
</div>
</SpotlightCard>
</AnimateIn>
</div>
</div>
</section>
{/* ═══ Final CTA ══════════════════════════════════════════ */}
<section className="px-6 py-24">
<div className="mx-auto max-w-4xl">
@@ -540,7 +666,7 @@ export default function LandingPage() {
</p>
<div className="mt-10 flex flex-wrap justify-center gap-4">
<a
href={LINKS.gitea}
href={links.gitea}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 px-4 py-2 rounded-lg border border-white/10 bg-white/3 text-xs text-slate-400 hover:text-white hover:border-white/20 transition-all"