hermes-web/components/elements/group-permission.tsx

252 lines
9.4 KiB
TypeScript

import axios from "axios";
import { useState } from "react";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Label } from "@/components/ui/label";
import { Checkbox } from "@/components/ui/checkbox";
import { z } from "zod";
import { Trash2Icon } from "lucide-react";
interface Permission {
id: string | undefined
path: string
allow: boolean | null
groupId: string
edit: boolean
showEdit: boolean
isNew: boolean
permissionPaths: { path: string, description: string }[]
adder: (id: string, path: string, allow: boolean | null) => void
remover: (redemption: { id: string, path: string, allow: boolean | null }) => void,
contains: (path: string) => boolean
}
const permissionPathSchema = z.string({
required_error: "Permission path should be available.",
invalid_type_error: "Permission path must be a string"
}).regex(/^[\w\-\.]{1,64}$/, "Permission path must contain only letters, numbers, dashes, periods.")
const permissionAllowSchema = z.boolean({
required_error: "Permission should be allowed, revoked or inherited from parent.",
invalid_type_error: "Something went wrong."
}).nullable()
const GroupPermission = ({
id,
path,
allow,
groupId,
edit,
showEdit,
isNew,
permissionPaths,
adder,
remover,
contains
}: Permission) => {
const [pathOpen, setPathOpen] = useState(false)
const [isEditable, setIsEditable] = useState(edit)
const [oldData, setOldData] = useState<{ path: string, allow: boolean | null } | undefined>(undefined)
const [permission, setPermission] = useState<{ id: string | undefined, path: string, allow: boolean | null }>({ id, path, allow });
const [error, setError] = useState<string | undefined>(undefined)
function Save() {
setError(undefined)
if (!permission || !permission.path)
return
if (!permissionPaths.some(p => p.path == permission.path)) {
setError('Permission does not exist.')
return
}
const permissionPathValidation = permissionPathSchema.safeParse(permission.path)
if (!permissionPathValidation.success) {
setError(JSON.parse(permissionPathValidation.error['message'])[0].message)
return
}
const permissionAllowValidation = permissionAllowSchema.safeParse(permission.allow)
if (!permissionAllowValidation.success) {
setError(JSON.parse(permissionAllowValidation.error['message'])[0].message)
return
}
if (isNew) {
if (contains(permission.path)) {
setError("Permission already exists.")
return;
}
axios.post("/api/settings/groups/permissions", {
path: permission.path,
allow: permission.allow,
groupId: groupId
}).then(d => {
if (!d || !d.data)
return
adder(d.data.id, permission.path, permission.allow)
setPermission({ id: undefined, path: "", allow: true })
})
} else {
if (!contains(permission.path)) {
setError("Permission does not exists.")
return;
}
axios.put("/api/settings/groups/permissions", {
id: permission.id,
path: permission.path,
allow: permission.allow
}).then(d => {
setIsEditable(false)
})
}
}
function Cancel() {
if (!oldData)
return
setError(undefined)
setPermission({ ...oldData, id: permission.id })
setIsEditable(false)
setOldData(undefined)
}
function Delete() {
axios.delete("/api/settings/groups/permissions?id=" + permission.id)
.then(d => {
remover(d.data)
})
}
return (
<div
className="bg-green-400 p-2 border-2 border-green-500 rounded-lg grid grid-flow-row">
<div
className="pb-3 flex grow">
{!isEditable &&
<Input
className="flex grow ml-1"
id="path"
value={permission.path}
readOnly />
|| isEditable &&
<Popover
open={pathOpen}
onOpenChange={setPathOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
id="path"
role="combobox"
className="flex grow justify-between"
>{!permission.path ? "Select a permission" : permission.path}</Button>
</PopoverTrigger>
<PopoverContent>
<Command>
<CommandInput
placeholder="Search..."
inputMode="search"
autoFocus={true} />
<CommandList>
<CommandEmpty>No permission found.</CommandEmpty>
<CommandGroup>
{permissionPaths.map((p) => (
<CommandItem
value={p.path}
key={p.path}
onSelect={(value) => {
setPermission({ ...permission, path: permissionPaths.find(v => v.path.toLowerCase() == value.toLowerCase())?.path ?? value.toLowerCase() })
setPathOpen(false)
}}>
<div>
<div className="text-lg">
{p.path}
</div>
<div className="text-xs text-green-200">
{p.description}
</div>
</div>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
}
</div>
<div
className="grid grid-cols-2 gap-1">
<Label>
Inherit from parent
</Label>
<Checkbox
checked={permission.allow === null}
onCheckedChange={(e) => {
if (permission.allow === null)
setPermission({ ...permission, allow: false })
else
setPermission({ ...permission, allow: null })
}}
disabled={!isEditable} />
<Label
htmlFor="inherit">
Allow
</Label>
<Checkbox
id="inherit"
checked={permission.allow === true}
onCheckedChange={(e) => {
setPermission({ ...permission, allow: !permission.allow })
}}
disabled={!isEditable || permission === undefined} />
</div>
{error &&
<p className="w-[380px] text-red-600 text-wrap text-sm">
{error}
</p>
}
<div>
{isEditable &&
<Button
className="m-3"
onClick={() => Save()}>
{isNew ? "Add" : "Save"}
</Button>
}
{isEditable && !isNew &&
<Button
className="m-3"
onClick={() => Cancel()}>
Cancel
</Button>
}
{showEdit && !isEditable &&
<Button
className="m-3"
onClick={() => {
setOldData({ ...permission })
setIsEditable(true)
}}>
Edit
</Button>
}
{!isEditable &&
<Button
className="m-3 bg-red-500 hover:bg-red-600 align-bottom"
onClick={() => Delete()}>
<Trash2Icon />
</Button>
}
</div>
</div>
);
}
export default GroupPermission;