diff --git a/ApiCore-Frontend/src/app/(app)/settings/layout.tsx b/ApiCore-Frontend/src/app/(app)/settings/layout.tsx index 85a0ee3..672e872 100644 --- a/ApiCore-Frontend/src/app/(app)/settings/layout.tsx +++ b/ApiCore-Frontend/src/app/(app)/settings/layout.tsx @@ -3,10 +3,23 @@ import { ReactNode } from "react"; import { useAuth } from "@/lib/auth/auth-context"; +const SETTINGS_AREA_PERMISSIONS = [ + "users:read", + "roles:read", + "audit:read", + "webhooks:read", + "automation:read", + "task-templates:read", + "import:execute", +]; + export default function SettingsLayout({ children }: { children: ReactNode }) { const { user } = useAuth(); + const hasSettingsAccess = SETTINGS_AREA_PERMISSIONS.some((permission) => + user?.permissions.includes(permission), + ); - if (user?.role !== "ADMIN") { + if (!hasSettingsAccess) { return (

diff --git a/ApiCore-Frontend/src/app/(app)/settings/roles/create-role-dialog.tsx b/ApiCore-Frontend/src/app/(app)/settings/roles/create-role-dialog.tsx new file mode 100644 index 0000000..6b69f03 --- /dev/null +++ b/ApiCore-Frontend/src/app/(app)/settings/roles/create-role-dialog.tsx @@ -0,0 +1,140 @@ +"use client"; + +import { FormEvent, useState } from "react"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { createRole } from "@/lib/roles/api"; +import { fetchPermissionCatalog } from "@/lib/roles/api"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { PermissionMatrix } from "./permission-matrix"; + +function normalizeKey(value: string): string { + return value + .toUpperCase() + .replace(/[^A-Z0-9]+/g, "_") + .replace(/^_+|_+$/g, ""); +} + +export function CreateRoleDialog() { + const queryClient = useQueryClient(); + const [open, setOpen] = useState(false); + const [key, setKey] = useState(""); + const [name, setName] = useState(""); + const [description, setDescription] = useState(""); + const [selected, setSelected] = useState>(new Set()); + const [error, setError] = useState(null); + + const { data: catalog } = useQuery({ + queryKey: ["permission-catalog"], + queryFn: fetchPermissionCatalog, + enabled: open, + }); + + function reset() { + setKey(""); + setName(""); + setDescription(""); + setSelected(new Set()); + setError(null); + } + + const mutation = useMutation({ + mutationFn: createRole, + onSuccess: () => { + void queryClient.invalidateQueries({ queryKey: ["roles"] }); + setOpen(false); + reset(); + }, + onError: () => setError("Rolle konnte nicht angelegt werden."), + }); + + function handleSubmit(event: FormEvent) { + event.preventDefault(); + setError(null); + mutation.mutate({ + key, + name, + description: description || undefined, + permissionKeys: Array.from(selected), + }); + } + + return ( +

{ + setOpen(next); + if (!next) reset(); + }} + > + + + + Neue Rolle + +
+
+ + { + setName(event.target.value); + setKey((current) => + current === "" || current === normalizeKey(name) + ? normalizeKey(event.target.value) + : current, + ); + }} + /> +
+
+ + setKey(normalizeKey(event.target.value))} + /> +
+
+ +