mirror of
https://gitlab.w-hs.de/an14051/backerup-website.git
synced 2026-07-27 17:35:29 +00:00
984b282a95
- 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.
221 lines
12 KiB
TypeScript
221 lines
12 KiB
TypeScript
import type { Metadata } from "next";
|
|
import { Geist, Geist_Mono } from "next/font/google";
|
|
import Link from "next/link";
|
|
import { LINKS } from "@/lib/links";
|
|
import { getLinks } from "@/lib/links-db";
|
|
import { getSession } from "@/lib/session";
|
|
import LogoutButton from "./components/LogoutButton";
|
|
import ProductTabs from "./components/ProductTabs";
|
|
import "./globals.css";
|
|
|
|
const geistSans = Geist({
|
|
variable: "--font-geist-sans",
|
|
subsets: ["latin"],
|
|
});
|
|
|
|
const geistMono = Geist_Mono({
|
|
variable: "--font-geist-mono",
|
|
subsets: ["latin"],
|
|
});
|
|
|
|
export const metadata: Metadata = {
|
|
title: "Backerup — Docker Container Backup",
|
|
description:
|
|
"Simple, reliable Docker container backups. Share and discover container configs and backup snapshots.",
|
|
};
|
|
|
|
function GitHubIcon({ className }: { className?: string }) {
|
|
return (
|
|
<svg viewBox="0 0 24 24" className={className ?? "w-4 h-4"} fill="currentColor" aria-hidden="true">
|
|
<path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12z" />
|
|
</svg>
|
|
);
|
|
}
|
|
|
|
function DockerIcon({ className }: { className?: string }) {
|
|
return (
|
|
<svg viewBox="0 0 24 24" className={className ?? "w-4 h-4"} fill="currentColor" aria-hidden="true">
|
|
<path d="M13.983 11.078h2.119a.186.186 0 0 0 .186-.185V9.006a.186.186 0 0 0-.186-.186h-2.119a.185.185 0 0 0-.185.185v1.888c0 .102.083.185.185.185m-2.954-5.43h2.118a.186.186 0 0 0 .186-.186V3.574a.186.186 0 0 0-.186-.185h-2.118a.185.185 0 0 0-.185.185v1.888c0 .102.082.185.185.185m0 2.716h2.118a.187.187 0 0 0 .186-.186V6.29a.186.186 0 0 0-.186-.185h-2.118a.185.185 0 0 0-.185.185v1.887c0 .102.082.185.185.186m-2.93 0h2.12a.186.186 0 0 0 .184-.186V6.29a.185.185 0 0 0-.185-.185H8.1a.185.185 0 0 0-.185.185v1.887c0 .102.083.185.185.186m-2.964 0h2.119a.186.186 0 0 0 .185-.186V6.29a.185.185 0 0 0-.185-.185H5.136a.186.186 0 0 0-.186.185v1.887c0 .102.084.185.186.186m5.893 2.715h2.118a.186.186 0 0 0 .186-.185V9.006a.186.186 0 0 0-.186-.186h-2.118a.185.185 0 0 0-.185.185v1.888c0 .102.082.185.185.185m-2.93 0h2.12a.185.185 0 0 0 .184-.185V9.006a.185.185 0 0 0-.184-.186h-2.12a.185.185 0 0 0-.184.185v1.888c0 .102.083.185.185.185m-2.964 0h2.119a.185.185 0 0 0 .185-.185V9.006a.185.185 0 0 0-.184-.186h-2.12a.186.186 0 0 0-.186.186v1.887c0 .102.084.185.186.185m-2.92 0h2.12a.186.186 0 0 0 .184-.185V9.006a.185.185 0 0 0-.184-.186h-2.12a.185.185 0 0 0-.185.186v1.887c0 .102.083.185.185.185M23.763 9.89c-.065-.051-.672-.51-1.954-.51-.338.001-.676.03-1.01.087-.248-1.7-1.653-2.53-1.716-2.566l-.344-.199-.226.327c-.284.438-.49.922-.612 1.43-.23.97-.09 1.882.403 2.661-.595.332-1.55.413-1.744.42H.751a.751.751 0 0 0-.75.748 11.376 11.376 0 0 0 .692 4.062c.545 1.428 1.355 2.48 2.41 3.124 1.18.723 3.1 1.137 5.275 1.137.983.003 1.963-.086 2.93-.266a12.248 12.248 0 0 0 3.823-1.389c.98-.567 1.86-1.288 2.61-2.136 1.252-1.418 1.998-2.997 2.553-4.4h.221c1.372 0 2.215-.549 2.68-1.009.309-.293.55-.65.707-1.046l.098-.288Z" />
|
|
</svg>
|
|
);
|
|
}
|
|
|
|
export default async function RootLayout({
|
|
children,
|
|
}: Readonly<{
|
|
children: React.ReactNode;
|
|
}>) {
|
|
const session = await getSession();
|
|
const links = await getLinks();
|
|
return (
|
|
<html
|
|
lang="en"
|
|
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
|
|
>
|
|
<body className="min-h-full flex flex-col" style={{ background: "#060810", color: "#e2e8f0" }}>
|
|
{/* ── Global ambient background ── */}
|
|
<div className="pointer-events-none fixed inset-0 -z-10 overflow-hidden">
|
|
<div
|
|
className="absolute -top-96 -left-64 w-[900px] h-[900px] rounded-full animate-pulse-slow"
|
|
style={{ background: "radial-gradient(circle, rgba(6,182,212,0.07) 0%, transparent 65%)" }}
|
|
/>
|
|
<div
|
|
className="absolute top-1/3 -right-64 w-[700px] h-[700px] rounded-full animate-pulse-slow"
|
|
style={{ background: "radial-gradient(circle, rgba(139,92,246,0.07) 0%, transparent 65%)", animationDelay: "2.5s" }}
|
|
/>
|
|
<div
|
|
className="absolute -bottom-48 left-1/4 w-[600px] h-[600px] rounded-full animate-pulse-slow"
|
|
style={{ background: "radial-gradient(circle, rgba(6,182,212,0.04) 0%, transparent 65%)", animationDelay: "4s" }}
|
|
/>
|
|
<div className="absolute inset-0 grid-bg" />
|
|
</div>
|
|
|
|
{/* ── Nav ── */}
|
|
<header className="sticky top-0 z-50 glass-heavy">
|
|
<div className="mx-auto flex max-w-6xl items-center justify-between px-6 py-4">
|
|
{/* Logo */}
|
|
<Link href="/" className="flex items-center gap-2.5 group shrink-0">
|
|
<div
|
|
className="w-8 h-8 rounded-lg flex items-center justify-center shrink-0"
|
|
style={{ background: "linear-gradient(135deg, #22d3ee, #0891b2)" }}
|
|
>
|
|
<DockerIcon className="w-4 h-4 text-slate-900" />
|
|
</div>
|
|
<span className="text-base font-bold text-white group-hover:text-cyan-300 transition-colors tracking-tight">
|
|
DoomLabs
|
|
</span>
|
|
</Link>
|
|
|
|
{/* Product tab switcher */}
|
|
<div className="flex-1 flex justify-center px-4">
|
|
<ProductTabs />
|
|
</div>
|
|
|
|
<nav className="flex items-center gap-1 text-sm font-medium shrink-0">
|
|
<Link
|
|
href="/browse"
|
|
className="nav-link px-3 py-2 rounded-lg text-slate-400 hover:text-white hover:bg-white/5 transition-all"
|
|
>
|
|
Browse
|
|
</Link>
|
|
<Link
|
|
href="/share"
|
|
className="nav-link px-3 py-2 rounded-lg text-slate-400 hover:text-white hover:bg-white/5 transition-all"
|
|
>
|
|
Share
|
|
</Link>
|
|
<a
|
|
href={links.gitea}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="nav-link px-3 py-2 rounded-lg text-slate-400 hover:text-white hover:bg-white/5 transition-all flex items-center gap-1.5"
|
|
>
|
|
<GitHubIcon />
|
|
<span className="hidden sm:inline">Gitea</span>
|
|
</a>
|
|
{session?.role === "admin" && (
|
|
<Link
|
|
href="/admin"
|
|
className="nav-link px-3 py-2 rounded-lg text-slate-400 hover:text-white hover:bg-white/5 transition-all"
|
|
>
|
|
Admin
|
|
</Link>
|
|
)}
|
|
{session ? (
|
|
<div className="ml-2 flex items-center gap-2">
|
|
<span className="hidden sm:block text-xs text-slate-500 px-2">
|
|
{session.username}
|
|
</span>
|
|
<LogoutButton />
|
|
</div>
|
|
) : (
|
|
<Link
|
|
href="/login"
|
|
className="ml-2 px-4 py-2 rounded-lg font-semibold text-sm text-slate-300 border border-white/10 hover:border-white/20 hover:text-white transition-all"
|
|
>
|
|
Sign in
|
|
</Link>
|
|
)}
|
|
<Link
|
|
href="/share"
|
|
className="ml-1 px-4 py-2 rounded-lg font-semibold text-slate-900 transition-all hover:brightness-110 active:scale-95"
|
|
style={{ background: "linear-gradient(135deg, #22d3ee, #06b6d4)" }}
|
|
>
|
|
Upload
|
|
</Link>
|
|
</nav>
|
|
</div>
|
|
</header>
|
|
|
|
{/* ── Page content ── */}
|
|
<main className="flex-1">{children}</main>
|
|
|
|
{/* ── Footer ── */}
|
|
<footer className="mt-24 border-t border-white/5">
|
|
<div className="mx-auto max-w-6xl px-6 py-16">
|
|
<div className="grid gap-12 sm:grid-cols-2 lg:grid-cols-4">
|
|
{/* Brand */}
|
|
<div className="lg:col-span-2">
|
|
<div className="flex items-center gap-2.5 mb-5">
|
|
<div
|
|
className="w-8 h-8 rounded-lg flex items-center justify-center"
|
|
style={{ background: "linear-gradient(135deg, #22d3ee, #0891b2)" }}
|
|
>
|
|
<DockerIcon className="w-4 h-4 text-slate-900" />
|
|
</div>
|
|
<span className="font-bold text-white text-lg tracking-tight">Backerup</span>
|
|
</div>
|
|
<p className="text-sm text-slate-500 max-w-xs leading-relaxed">
|
|
Open-source Docker container backup tool. Self-hosted, portable, and reliable.
|
|
Archive volumes, restore configs, and share setups with your team.
|
|
</p>
|
|
<div className="mt-6 flex gap-3">
|
|
<a
|
|
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"
|
|
>
|
|
<GitHubIcon />
|
|
Gitea
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Product */}
|
|
<div>
|
|
<h3 className="text-xs font-semibold uppercase tracking-widest text-slate-500 mb-4">Product</h3>
|
|
<ul className="space-y-3 text-sm text-slate-500">
|
|
<li><Link href="/browse" className="hover:text-cyan-400 transition-colors">Browse configs</Link></li>
|
|
<li><Link href="/share" className="hover:text-cyan-400 transition-colors">Share a backup</Link></li>
|
|
<li><a href={links.docs} className="hover:text-cyan-400 transition-colors">Documentation</a></li>
|
|
<li><a href={links.giteaReleases} className="hover:text-cyan-400 transition-colors">Changelog</a></li>
|
|
</ul>
|
|
</div>
|
|
|
|
{/* Community */}
|
|
<div>
|
|
<h3 className="text-xs font-semibold uppercase tracking-widest text-slate-500 mb-4">Community</h3>
|
|
<ul className="space-y-3 text-sm text-slate-500">
|
|
<li><a href={links.gitea} className="hover:text-cyan-400 transition-colors">Gitea</a></li>
|
|
<li><a href={links.giteaIssues} className="hover:text-cyan-400 transition-colors">Issues</a></li>
|
|
<li><a href={links.giteaDiscussions} className="hover:text-cyan-400 transition-colors">Discussions</a></li>
|
|
<li><a href={links.giteaContributing} className="hover:text-cyan-400 transition-colors">Contributing</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-12 pt-8 border-t border-white/5 flex flex-wrap items-center justify-between gap-4 text-xs text-slate-600">
|
|
<p>© {new Date().getFullYear()} Backerup — Open source.</p>
|
|
<div className="flex items-center gap-2">
|
|
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse" />
|
|
<span>All systems operational</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|