From 29acf06f75bd8a33c5ca2b830bde32de4f787ea6 Mon Sep 17 00:00:00 2001 From: fuomag9 <1580624+fuomag9@users.noreply.github.com> Date: Fri, 31 Oct 2025 21:03:02 +0100 Subject: [PATCH] =?UTF-8?q?Swapped=20the=20entire=20UI=20to=20Material?= =?UTF-8?q?=E2=80=AFUI,=20applied=20a=20global=20dark=20theme,=20and=20rem?= =?UTF-8?q?oved=20all=20of=20the=20old=20styled-jsx/CSS-module=20styling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(auth)/login/LoginClient.tsx | 38 + app/(auth)/login/page.tsx | 69 +- app/(dashboard)/DashboardLayoutClient.tsx | 86 + app/(dashboard)/OverviewClient.tsx | 85 + .../access-lists/AccessListsClient.tsx | 140 ++ app/(dashboard)/access-lists/actions.ts | 10 +- app/(dashboard)/access-lists/page.tsx | 204 +- app/(dashboard)/audit-log/AuditLogClient.tsx | 41 + app/(dashboard)/audit-log/page.tsx | 74 +- .../certificates/CertificatesClient.tsx | 169 ++ app/(dashboard)/certificates/actions.ts | 6 +- app/(dashboard)/certificates/page.tsx | 217 +- .../dead-hosts/DeadHostsClient.tsx | 155 ++ app/(dashboard)/dead-hosts/actions.ts | 6 +- app/(dashboard)/dead-hosts/page.tsx | 198 +- app/(dashboard)/layout.tsx | 65 +- app/(dashboard)/nav-links.tsx | 50 - app/(dashboard)/page.tsx | 162 +- .../proxy-hosts/ProxyHostsClient.tsx | 217 ++ app/(dashboard)/proxy-hosts/actions.ts | 6 +- app/(dashboard)/proxy-hosts/page.tsx | 282 +-- app/(dashboard)/redirects/RedirectsClient.tsx | 157 ++ app/(dashboard)/redirects/actions.ts | 6 +- app/(dashboard)/redirects/page.tsx | 214 +- app/(dashboard)/settings/SettingsClient.tsx | 108 + app/(dashboard)/settings/actions.ts | 6 +- app/(dashboard)/settings/page.tsx | 180 +- app/(dashboard)/streams/StreamsClient.tsx | 134 ++ app/(dashboard)/streams/actions.ts | 6 +- app/(dashboard)/streams/page.tsx | 206 +- app/api/auth/callback/route.ts | 2 +- app/api/auth/logout/route.ts | 2 +- app/layout.tsx | 9 +- app/providers.tsx | 52 + app/setup/oauth/SetupClient.tsx | 62 + app/setup/oauth/page.tsx | 119 +- next-env.d.ts | 3 +- package-lock.json | 1887 +++++++++++++---- package.json | 26 +- src/lib/auth/session.ts | 68 +- tsconfig.json | 40 +- 41 files changed, 3122 insertions(+), 2445 deletions(-) create mode 100644 app/(auth)/login/LoginClient.tsx create mode 100644 app/(dashboard)/DashboardLayoutClient.tsx create mode 100644 app/(dashboard)/OverviewClient.tsx create mode 100644 app/(dashboard)/access-lists/AccessListsClient.tsx create mode 100644 app/(dashboard)/audit-log/AuditLogClient.tsx create mode 100644 app/(dashboard)/certificates/CertificatesClient.tsx create mode 100644 app/(dashboard)/dead-hosts/DeadHostsClient.tsx delete mode 100644 app/(dashboard)/nav-links.tsx create mode 100644 app/(dashboard)/proxy-hosts/ProxyHostsClient.tsx create mode 100644 app/(dashboard)/redirects/RedirectsClient.tsx create mode 100644 app/(dashboard)/settings/SettingsClient.tsx create mode 100644 app/(dashboard)/streams/StreamsClient.tsx create mode 100644 app/providers.tsx create mode 100644 app/setup/oauth/SetupClient.tsx diff --git a/app/(auth)/login/LoginClient.tsx b/app/(auth)/login/LoginClient.tsx new file mode 100644 index 00000000..9bbfaace --- /dev/null +++ b/app/(auth)/login/LoginClient.tsx @@ -0,0 +1,38 @@ +"use client"; + +import Link from "next/link"; +import { Alert, Box, Button, Card, CardContent, Stack, Typography } from "@mui/material"; + +export default function LoginClient({ oauthConfigured, startOAuth }: { oauthConfigured: boolean; startOAuth: (formData: FormData) => void }) { + return ( + + + + + + + Caddy Proxy Manager + + + Sign in with your organization's OAuth2 provider to continue. + + + + {oauthConfigured ? ( + + + + ) : ( + + The system administrator needs to configure OAuth2 settings before logins are allowed. If this is a fresh installation, + start with the OAuth setup wizard. + + )} + + + + + ); +} diff --git a/app/(auth)/login/page.tsx b/app/(auth)/login/page.tsx index 81b0045c..3b37162c 100644 --- a/app/(auth)/login/page.tsx +++ b/app/(auth)/login/page.tsx @@ -2,9 +2,10 @@ import { redirect } from "next/navigation"; import { getSession } from "@/src/lib/auth/session"; import { buildAuthorizationUrl } from "@/src/lib/auth/oauth"; import { getOAuthSettings } from "@/src/lib/settings"; +import LoginClient from "./LoginClient"; export default async function LoginPage() { - const session = getSession(); + const session = await getSession(); if (session) { redirect("/"); } @@ -17,69 +18,5 @@ export default async function LoginPage() { redirect(target); } - return ( -
-

Caddy Proxy Manager

-

Sign in with your organization's OAuth2 provider to continue.

- {oauthConfigured ? ( -
- -
- ) : ( -
-

- The system administrator needs to configure OAuth2 settings before logins are allowed. If this is a fresh installation, start - with the{" "} - - OAuth setup wizard - - . -

-
- )} - -
- ); + return ; } diff --git a/app/(dashboard)/DashboardLayoutClient.tsx b/app/(dashboard)/DashboardLayoutClient.tsx new file mode 100644 index 00000000..bb6cd139 --- /dev/null +++ b/app/(dashboard)/DashboardLayoutClient.tsx @@ -0,0 +1,86 @@ +"use client"; + +import { ReactNode } from "react"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { Box, Button, Divider, List, ListItemButton, ListItemText, Stack, Typography } from "@mui/material"; +import type { UserRecord } from "@/src/lib/auth/session"; + +const NAV_ITEMS = [ + { href: "/", label: "Overview" }, + { href: "/proxy-hosts", label: "Proxy Hosts" }, + { href: "/redirects", label: "Redirects" }, + { href: "/dead-hosts", label: "Dead Hosts" }, + { href: "/streams", label: "Streams" }, + { href: "/access-lists", label: "Access Lists" }, + { href: "/certificates", label: "Certificates" }, + { href: "/settings", label: "Settings" }, + { href: "/audit-log", label: "Audit Log" } +] as const; + +export default function DashboardLayoutClient({ user, children }: { user: UserRecord; children: ReactNode }) { + const pathname = usePathname(); + + return ( + + + + + Caddy Proxy Manager + + + {user.name ?? user.email} + + + + + + + {NAV_ITEMS.map((item) => { + const selected = pathname === item.href; + return ( + + + + ); + })} + + +
+ +
+
+ + + {children} + +
+ ); +} diff --git a/app/(dashboard)/OverviewClient.tsx b/app/(dashboard)/OverviewClient.tsx new file mode 100644 index 00000000..9f0587f3 --- /dev/null +++ b/app/(dashboard)/OverviewClient.tsx @@ -0,0 +1,85 @@ +"use client"; + +import Link from "next/link"; +import { Card, CardActionArea, CardContent, Grid, Paper, Stack, Typography } from "@mui/material"; + +type StatCard = { + label: string; + icon: string; + count: number; + href: string; +}; + +type RecentEvent = { + summary: string; + created_at: string; +}; + +export default function OverviewClient({ + userName, + stats, + recentEvents +}: { + userName: string; + stats: StatCard[]; + recentEvents: RecentEvent[]; +}) { + return ( + + + + Welcome back, {userName} + + + Manage your Caddy reverse proxies, TLS certificates, and services with confidence. + + + + + {stats.map((stat) => ( + + + + + + {stat.icon} + + + {stat.count} + + {stat.label} + + + + + ))} + + + + + Recent Activity + + {recentEvents.length === 0 ? ( + + No activity recorded yet. + + ) : ( + + {recentEvents.map((event, index) => ( + + {event.summary} + + {new Date(event.created_at).toLocaleString()} + + + ))} + + )} + + + ); +} diff --git a/app/(dashboard)/access-lists/AccessListsClient.tsx b/app/(dashboard)/access-lists/AccessListsClient.tsx new file mode 100644 index 00000000..5bf7cf21 --- /dev/null +++ b/app/(dashboard)/access-lists/AccessListsClient.tsx @@ -0,0 +1,140 @@ +"use client"; + +import { + Box, + Button, + Card, + CardContent, + Divider, + IconButton, + List, + ListItem, + ListItemSecondaryAction, + ListItemText, + Stack, + TextField, + Typography +} from "@mui/material"; +import DeleteIcon from "@mui/icons-material/Delete"; +import type { AccessList } from "@/src/lib/models/access-lists"; +import { + addAccessEntryAction, + createAccessListAction, + deleteAccessEntryAction, + deleteAccessListAction, + updateAccessListAction +} from "./actions"; + +type Props = { + lists: AccessList[]; +}; + +export default function AccessListsClient({ lists }: Props) { + return ( + + + + Access Lists + + Protect proxy hosts with HTTP basic authentication credentials. + + + + {lists.map((list) => ( + + + updateAccessListAction(list.id, formData)} spacing={2}> + + Access List + + + + + + + + + + + + + Accounts + {list.entries.length === 0 ? ( + No credentials configured. + ) : ( + + {list.entries.map((entry) => ( + + + +
+ + + +
+
+
+ ))} +
+ )} +
+ + + + addAccessEntryAction(list.id, formData)} spacing={1.5} direction={{ xs: "column", sm: "row" }}> + + + + +
+
+ ))} +
+ + + + Create access list + + + + + + + + + + + + + + +
+ ); +} diff --git a/app/(dashboard)/access-lists/actions.ts b/app/(dashboard)/access-lists/actions.ts index 57ab8219..3bbf6122 100644 --- a/app/(dashboard)/access-lists/actions.ts +++ b/app/(dashboard)/access-lists/actions.ts @@ -11,7 +11,7 @@ import { } from "@/src/lib/models/access-lists"; export async function createAccessListAction(formData: FormData) { - const { user } = requireUser(); + const { user } = await requireUser(); const rawUsers = String(formData.get("users") ?? ""); const accounts = rawUsers .split("\n") @@ -35,7 +35,7 @@ export async function createAccessListAction(formData: FormData) { } export async function updateAccessListAction(id: number, formData: FormData) { - const { user } = requireUser(); + const { user } = await requireUser(); await updateAccessList( id, { @@ -48,13 +48,13 @@ export async function updateAccessListAction(id: number, formData: FormData) { } export async function deleteAccessListAction(id: number) { - const { user } = requireUser(); + const { user } = await requireUser(); await deleteAccessList(id, user.id); revalidatePath("/access-lists"); } export async function addAccessEntryAction(id: number, formData: FormData) { - const { user } = requireUser(); + const { user } = await requireUser(); await addAccessListEntry( id, { @@ -67,7 +67,7 @@ export async function addAccessEntryAction(id: number, formData: FormData) { } export async function deleteAccessEntryAction(accessListId: number, entryId: number) { - const { user } = requireUser(); + const { user } = await requireUser(); await removeAccessListEntry(accessListId, entryId, user.id); revalidatePath("/access-lists"); } diff --git a/app/(dashboard)/access-lists/page.tsx b/app/(dashboard)/access-lists/page.tsx index c8e84054..5af14275 100644 --- a/app/(dashboard)/access-lists/page.tsx +++ b/app/(dashboard)/access-lists/page.tsx @@ -1,207 +1,7 @@ +import AccessListsClient from "./AccessListsClient"; import { listAccessLists } from "@/src/lib/models/access-lists"; -import { addAccessEntryAction, createAccessListAction, deleteAccessEntryAction, deleteAccessListAction, updateAccessListAction } from "./actions"; export default function AccessListsPage() { const lists = listAccessLists(); - - return ( -
-
-

Access Lists

-

Protect proxy hosts with HTTP basic authentication credentials.

-
- -
- {lists.map((list) => ( -
-
updateAccessListAction(list.id, formData)} className="header"> -
- -