127 lines
4.4 KiB
TypeScript
127 lines
4.4 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { useRouter } from "next/navigation";
|
|
import { authFetch, getApiBase, getToken } from "../lib/auth";
|
|
import AdminShell from "../ui/AdminShell";
|
|
import { CONFIG_GROUPS, serviceStatusLabel } from "./configNavigation";
|
|
|
|
type ServiceState = { name: string; status: string };
|
|
|
|
export default function AdminLandingPage() {
|
|
const router = useRouter();
|
|
const [services, setServices] = useState<ServiceState[]>([]);
|
|
const [ready, setReady] = useState(false);
|
|
const [error, setError] = useState("");
|
|
|
|
useEffect(() => {
|
|
let active = true;
|
|
const load = async () => {
|
|
if (!getToken()) {
|
|
router.replace("/login");
|
|
return;
|
|
}
|
|
try {
|
|
const response = await authFetch(`${getApiBase()}/auth/me`);
|
|
if (!response.ok) {
|
|
router.replace("/login");
|
|
return;
|
|
}
|
|
if ((await response.json())?.role !== "admin") {
|
|
router.replace("/");
|
|
return;
|
|
}
|
|
if (!active) return;
|
|
setReady(true);
|
|
const status = await authFetch(`${getApiBase()}/status/services`);
|
|
if (!status.ok) throw new Error("Status unavailable");
|
|
const data = await status.json();
|
|
if (active) setServices(Array.isArray(data.services) ? data.services : []);
|
|
} catch {
|
|
if (active) setError("Connection status is unavailable. Refresh the page to try again.");
|
|
}
|
|
};
|
|
void load();
|
|
return () => {
|
|
active = false;
|
|
};
|
|
}, [router]);
|
|
|
|
return (
|
|
<AdminShell title="Settings" subtitle="Connections, access and the way Magent works.">
|
|
{!ready ? (
|
|
error ? (
|
|
<p className="error-banner" role="alert">
|
|
{error}
|
|
</p>
|
|
) : (
|
|
<p role="status">Loading settings…</p>
|
|
)
|
|
) : (
|
|
<div className="config-directory">
|
|
{error && (
|
|
<p className="error-banner" role="alert">
|
|
{error}
|
|
</p>
|
|
)}
|
|
{CONFIG_GROUPS.filter((group) => !group.advanced).map((group) => (
|
|
<section className="config-directory-region" key={group.title}>
|
|
<header>
|
|
<h2>{group.title}</h2>
|
|
<p>{group.description}</p>
|
|
</header>
|
|
<div className="config-directory-links">
|
|
{group.items.map((item) => {
|
|
const service = services.find((entry) => entry.name.toLowerCase() === item.service?.toLowerCase());
|
|
return (
|
|
<a href={item.href} key={item.href} className="config-directory-link">
|
|
{item.symbol && (
|
|
<span className="config-link-icon" aria-hidden="true">
|
|
{item.symbol}
|
|
</span>
|
|
)}
|
|
<span className="config-link-copy">
|
|
<strong>{item.label}</strong>
|
|
<small>{item.description}</small>
|
|
</span>
|
|
{item.service && (
|
|
<span className={`config-connection-badge is-${service?.status ?? "unknown"}`}>
|
|
{serviceStatusLabel(service?.status)}
|
|
</span>
|
|
)}
|
|
<span className="config-link-arrow" aria-hidden="true">
|
|
→
|
|
</span>
|
|
</a>
|
|
);
|
|
})}
|
|
</div>
|
|
</section>
|
|
))}
|
|
<details className="config-advanced-directory">
|
|
<summary>
|
|
<strong>Advanced tools</strong>
|
|
<span>Hosting, logs, caches and recovery</span>
|
|
</summary>
|
|
<div className="config-directory-links">
|
|
{CONFIG_GROUPS.filter((group) => group.advanced)
|
|
.flatMap((group) => group.items)
|
|
.map((item) => (
|
|
<a href={item.href} key={item.href} className="config-directory-link">
|
|
<span className="config-link-copy">
|
|
<strong>{item.label}</strong>
|
|
<small>{item.description}</small>
|
|
</span>
|
|
<span className="config-link-arrow" aria-hidden="true">
|
|
→
|
|
</span>
|
|
</a>
|
|
))}
|
|
</div>
|
|
</details>
|
|
</div>
|
|
)}
|
|
</AdminShell>
|
|
);
|
|
}
|