initial admin
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
import { DataTable } from "@/components/tantable";
|
||||
import { db } from "@/lib/db";
|
||||
import { cdn } from "@/lib/db/schema";
|
||||
import { cdnColumns } from "./table";
|
||||
|
||||
export default async function CdnManagerPage() {
|
||||
const files = await db
|
||||
.select({ id: cdn.id, name: cdn.name, size: cdn.size })
|
||||
.from(cdn)
|
||||
.orderBy(cdn.name);
|
||||
return <DataTable columns={cdnColumns} data={files} />;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
"use client";
|
||||
|
||||
import type { ColumnDef } from "@tanstack/react-table";
|
||||
|
||||
export const cdnColumns: ColumnDef<{
|
||||
id: string;
|
||||
name: string | null;
|
||||
size: string;
|
||||
}>[] = [
|
||||
{
|
||||
accessorFn: (row) => row.name || `[${row.id}]`,
|
||||
header: "Name",
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => b2s(Number(row.size)),
|
||||
header: "Size",
|
||||
},
|
||||
];
|
||||
|
||||
const b2s = (t: number) => {
|
||||
let e = (Math.log2(t) / 10) | 0;
|
||||
// biome-ignore lint/suspicious/noAssignInExpressions: copied
|
||||
return `${(t / 1024 ** (e = e <= 0 ? 0 : e)).toFixed(1)}${" KMGP"[e]}B`;
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export function VersionCreateDialogForm() {
|
||||
return <form>Todo</form>;
|
||||
}
|
||||
|
||||
export function RandomWelcomeMessage({ list }: { list?: string[] }) {
|
||||
const messages = list || [
|
||||
"What are we cooking today?",
|
||||
"What are we doing today?",
|
||||
"Why are you here?",
|
||||
"The food is ready sir.",
|
||||
"Who left the artifact stats like that?",
|
||||
"Don’t forget to hydrate, boss.",
|
||||
"Another day, another reroll.",
|
||||
"Still reviewing artifacts? Respect.",
|
||||
"Let's make these builds shine.",
|
||||
"You came back… impressive.",
|
||||
"Processing luck… 0.3% found.",
|
||||
"Someone's gotta keep this event alive.",
|
||||
"New submissions incoming!",
|
||||
"Welcome back, brave reviewer.",
|
||||
"Did you bring Mora this time?",
|
||||
"Your approval power is unmatched.",
|
||||
"Remember: critique kindly, roast fairly.",
|
||||
"Time to bless—or curse—some artifacts.",
|
||||
"Don’t worry, no 5★ drama yet.",
|
||||
"Admin mode: activated.",
|
||||
];
|
||||
|
||||
const [message, setMessage] = useState("");
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: false
|
||||
useEffect(
|
||||
() => setMessage(messages[Math.floor(Math.random() * messages.length)]),
|
||||
[messages.length],
|
||||
);
|
||||
|
||||
return message;
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
import {
|
||||
Badge,
|
||||
BookUser,
|
||||
Columns3Cog,
|
||||
Computer,
|
||||
Database,
|
||||
GitGraph,
|
||||
ListTree,
|
||||
MoreHorizontal,
|
||||
Package,
|
||||
PlusCircle,
|
||||
ScrollText,
|
||||
Settings,
|
||||
SquareUserRound,
|
||||
UserRoundSearch,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import type { ReactNode } from "react";
|
||||
import { SimpleTooltip } from "@/components/tooltip";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarFooter,
|
||||
SidebarGroup,
|
||||
SidebarGroupAction,
|
||||
SidebarGroupContent,
|
||||
SidebarGroupLabel,
|
||||
SidebarHeader,
|
||||
SidebarInset,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarProvider,
|
||||
} from "@/components/ui/sidebar";
|
||||
import { NavUser } from "@/components/user";
|
||||
import { db } from "@/lib/db";
|
||||
import { versions } from "@/lib/db/schema";
|
||||
import { VersionCreateDialogForm } from "./client";
|
||||
|
||||
export default async function AdminLayout({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const vers = await db
|
||||
.select()
|
||||
.from(versions)
|
||||
.catch(() => [{ id: "..", name: "Error Fetching List." }]);
|
||||
const health: { database: boolean; enka: boolean; amber: boolean } =
|
||||
await fetch("http://localhost:3000/api/health").then((r) => r.json());
|
||||
|
||||
return (
|
||||
<SidebarProvider>
|
||||
<Sidebar variant="floating">
|
||||
<SidebarHeader>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<Link href="/admin">
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
className="data-[slot=sidebar-menu-button]:!p-1.5"
|
||||
>
|
||||
<span>
|
||||
<Computer className="!size-5" />
|
||||
<span className="text-base font-semibold">Buzzy Inc.</span>
|
||||
</span>
|
||||
</SidebarMenuButton>
|
||||
</Link>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarHeader>
|
||||
<SidebarContent>
|
||||
<SidebarGroup className="pb-0">
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton>
|
||||
<div className="flex justify-between w-full">
|
||||
<div className="flex gap-1 [&>svg]:size-4 [&>svg]:shrink-0">
|
||||
{/* Database */}
|
||||
<SimpleTooltip text="Database">
|
||||
<Database
|
||||
className={
|
||||
health.database
|
||||
? "text-emerald-400"
|
||||
: "text-red-400"
|
||||
}
|
||||
/>
|
||||
</SimpleTooltip>{" "}
|
||||
{/* Enka */}
|
||||
<SimpleTooltip text="Enka Network API">
|
||||
<UserRoundSearch
|
||||
className={
|
||||
health.enka ? "text-emerald-400" : "text-red-400"
|
||||
}
|
||||
/>
|
||||
</SimpleTooltip>{" "}
|
||||
{/* Amber */}
|
||||
<SimpleTooltip text="Project Amber">
|
||||
<BookUser
|
||||
className={
|
||||
health.amber ? "text-emerald-400" : "text-red-400"
|
||||
}
|
||||
/>
|
||||
</SimpleTooltip>
|
||||
</div>
|
||||
<span>
|
||||
{Object.values(health).some((x) => !x)
|
||||
? `${{ database: "ฐานข้อมูล", enka: "Enka ", amber: "Amber " }[Object.entries(health).find(([, v]) => !v)![0]]}มีปัญหา`
|
||||
: "ปกติทุกอย่าง"}
|
||||
</span>
|
||||
</div>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>Tierlist</SidebarGroupLabel>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<Link href="/admin/tl/ver">
|
||||
<SidebarMenuButton>
|
||||
<GitGraph />
|
||||
Versions
|
||||
</SidebarMenuButton>
|
||||
</Link>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<Link href="/admin/tl/badges">
|
||||
<SidebarMenuButton>
|
||||
<Badge />
|
||||
Badges
|
||||
</SidebarMenuButton>
|
||||
</Link>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<Link href="/admin/tl/layout">
|
||||
<SidebarMenuButton>
|
||||
<Columns3Cog />
|
||||
Layout
|
||||
</SidebarMenuButton>
|
||||
</Link>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>Versions</SidebarGroupLabel>
|
||||
<Dialog>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarGroupAction>
|
||||
<MoreHorizontal />
|
||||
</SidebarGroupAction>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DialogTrigger asChild>
|
||||
<DropdownMenuItem>
|
||||
<PlusCircle /> Create...
|
||||
</DropdownMenuItem>
|
||||
</DialogTrigger>
|
||||
<Link href="/admin/ver/types">
|
||||
<DropdownMenuItem>
|
||||
<ListTree /> Types
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create Version</DialogTitle>
|
||||
</DialogHeader>
|
||||
<VersionCreateDialogForm />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{vers.map((v) => (
|
||||
<SidebarMenuItem key={v.id}>
|
||||
<Link href={`/admin/ver/${v.id}`}>
|
||||
<SidebarMenuButton>{v.name}</SidebarMenuButton>
|
||||
</Link>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
</SidebarContent>
|
||||
<SidebarFooter>
|
||||
<SidebarGroup className="mt-auto">
|
||||
<SidebarGroupLabel>Global</SidebarGroupLabel>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<Link href="/admin/char">
|
||||
<SidebarMenuButton>
|
||||
<SquareUserRound />
|
||||
Characters
|
||||
</SidebarMenuButton>
|
||||
</Link>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<Link href="/admin/cdn">
|
||||
<SidebarMenuButton>
|
||||
<Package />
|
||||
CDN
|
||||
</SidebarMenuButton>
|
||||
</Link>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem className="mt-auto">
|
||||
<Link href="/admin/log">
|
||||
<SidebarMenuButton>
|
||||
<ScrollText />
|
||||
Audit Log
|
||||
</SidebarMenuButton>
|
||||
</Link>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<Link href="/admin/settings">
|
||||
<SidebarMenuButton>
|
||||
<Settings />
|
||||
Settings
|
||||
</SidebarMenuButton>
|
||||
</Link>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
<NavUser />
|
||||
</SidebarFooter>
|
||||
</Sidebar>
|
||||
<SidebarInset className="bg-transparent">{children}</SidebarInset>
|
||||
</SidebarProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
@@ -0,0 +1,14 @@
|
||||
import { RandomWelcomeMessage } from "./client";
|
||||
|
||||
export default function AdminPage() {
|
||||
return (
|
||||
<div className="flex h-full w-full justify-center items-center">
|
||||
<div className="flex flex-col">
|
||||
<span className="opacity-80 text-4xl font-bold">Welcome back!</span>
|
||||
<span>
|
||||
<RandomWelcomeMessage />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user