"feat: Added debug server and related components for improved development experience"

This commit is contained in:
2025-05-23 07:24:51 +02:00
parent d457a8d86b
commit 9f6219832c
189 changed files with 35730 additions and 133 deletions

View File

@@ -0,0 +1,141 @@
"use client";
import type { InferResultType } from "@/utils/drizzle";
import type { ColumnDef } from "@tanstack/react-table";
import { BadgeCheckIcon, EyeIcon, HourglassIcon, MoreHorizontal, OctagonXIcon, ShareIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useToast } from "@/components/ui/use-toast";
import type { printers } from "@/server/db/schema";
import type { InferSelectModel } from "drizzle-orm";
import Link from "next/link";
export const columns: ColumnDef<
InferResultType<
"printJobs",
{
printer: true;
}
>
>[] = [
{
accessorKey: "printer",
header: "Drucker",
cell: ({ row }) => {
const printer: InferSelectModel<typeof printers> = row.getValue("printer");
return printer.name;
},
},
{
accessorKey: "startAt",
header: "Startzeitpunkt",
cell: ({ row }) => {
const startAt = new Date(row.original.startAt);
return `${startAt.toLocaleDateString("de-DE", {
dateStyle: "medium",
})} ${startAt.toLocaleTimeString("de-DE")}`;
},
},
{
accessorKey: "durationInMinutes",
header: "Dauer (Minuten)",
},
{
accessorKey: "comments",
header: "Anmerkungen",
cell: ({ row }) => {
const comments = row.original.comments;
if (comments) {
return <span className="text-sm">{comments.slice(0, 50)}</span>;
}
return <span className="text-muted-foreground text-sm">Keine Anmerkungen</span>;
},
},
{
accessorKey: "status",
header: "Status",
cell: ({ row }) => {
const aborted = row.original.aborted;
if (aborted) {
return (
<div className="flex items-center gap-2">
<OctagonXIcon className="w-4 h-4 text-red-500" /> <span className="text-red-600">Abgebrochen</span>
</div>
);
}
const startAt = new Date(row.original.startAt).getTime();
const endAt = startAt + row.original.durationInMinutes * 60 * 1000;
if (Date.now() < endAt) {
return (
<div className="flex items-center gap-2">
<HourglassIcon className="w-4 h-4 text-yellow-500" />
<span className="text-yellow-600">Läuft...</span>
</div>
);
}
return (
<div className="flex items-center gap-2">
<BadgeCheckIcon className="w-4 h-4 text-green-500" />
<span className="text-green-600">Abgeschlossen</span>
</div>
);
},
},
{
id: "actions",
cell: ({ row }) => {
const job = row.original;
const { toast } = useToast();
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<span className="sr-only">Menu öffnen</span>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Aktionen</DropdownMenuLabel>
<DropdownMenuItem
className="flex items-center gap-2"
onClick={() => {
const baseUrl = new URL(window.location.href);
baseUrl.pathname = `/job/${job.id}`;
navigator.clipboard.writeText(baseUrl.toString());
toast({
description: "URL zum Druckauftrag in die Zwischenablage kopiert.",
});
}}
>
<ShareIcon className="w-4 h-4" />
<span>Teilen</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Link href={`/job/${job.id}`} className="flex items-center gap-2">
<EyeIcon className="w-4 h-4" />
<span>Details anzeigen</span>
</Link>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
},
},
];

View File

@@ -0,0 +1,73 @@
"use client";
import {
type ColumnDef,
flexRender,
getCoreRowModel,
getPaginationRowModel,
useReactTable,
} from "@tanstack/react-table";
import { Button } from "@/components/ui/button";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
}
export function JobsTable<TData, TValue>({ columns, data }: DataTableProps<TData, TValue>) {
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
});
return (
<div>
<div className="rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow key={row.id} data-state={row.getIsSelected() && "selected"}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
Keine Ergebnisse gefunden
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<div className="flex items-center justify-end space-x-2 py-4 select-none">
<Button variant="outline" size="sm" onClick={() => table.previousPage()} disabled={!table.getCanPreviousPage()}>
Vorherige Seite
</Button>
<Button variant="outline" size="sm" onClick={() => table.nextPage()} disabled={!table.getCanNextPage()}>
Nächste Seite
</Button>
</div>
</div>
);
}

View File

@@ -0,0 +1,47 @@
import { Badge } from "@/components/ui/badge";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { validateRequest } from "@/server/auth";
import { UserRole, translateUserRole } from "@/server/auth/permissions";
import type { Metadata } from "next";
import { redirect } from "next/navigation";
export const metadata: Metadata = {
title: "Dein Profil",
};
export default async function ProfilePage() {
const { user } = await validateRequest();
if (!user) {
redirect("/");
}
const badgeVariant = {
[UserRole.ADMIN]: "destructive" as const,
[UserRole.USER]: "default" as const,
[UserRole.GUEST]: "secondary" as const,
};
return (
<Card>
<CardHeader className="flex flex-row justify-between items-center">
<div>
<CardTitle>{user?.displayName}</CardTitle>
<CardDescription>
{user?.username} &mdash; {user?.email}
</CardDescription>
</div>
<Badge variant={badgeVariant[user?.role]}>{translateUserRole(user?.role)}</Badge>
</CardHeader>
<CardContent>
<p>
Deine Daten wurden vom <abbr>GitHub Enterprise Server</abbr> importiert und können hier nur angezeigt werden.
</p>
<p>
Solltest Du Änderungen oder eine Löschung deiner Daten von unserem Dienst beantragen wollen, so wende dich
bitte an einen Administrator.
</p>
</CardContent>
</Card>
);
}