2024-08-14 16:33:40 -04:00
|
|
|
import axios from "axios";
|
2024-08-25 17:35:46 -04:00
|
|
|
import { useState } from "react";
|
2024-08-14 16:33:40 -04:00
|
|
|
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";
|
2024-08-25 17:35:46 -04:00
|
|
|
import { Label } from "@/components/ui/label";
|
|
|
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
|
|
import { z } from "zod";
|
|
|
|
import { Trash2Icon } from "lucide-react";
|
2024-08-14 16:33:40 -04:00
|
|
|
|
|
|
|
interface Permission {
|
2024-08-25 17:35:46 -04:00
|
|
|
id: string | undefined
|
2024-08-14 16:33:40 -04:00
|
|
|
path: string
|
2024-08-25 17:35:46 -04:00
|
|
|
allow: boolean | null
|
2024-08-14 16:33:40 -04:00
|
|
|
groupId: string
|
|
|
|
edit: boolean
|
|
|
|
showEdit: boolean
|
|
|
|
isNew: boolean
|
|
|
|
permissionPaths: { path: string, description: string }[]
|
2024-08-25 17:35:46 -04:00
|
|
|
adder: (id: string, path: string, allow: boolean | null) => void
|
|
|
|
remover: (redemption: { id: string, path: string, allow: boolean | null }) => void,
|
|
|
|
contains: (path: string) => boolean
|
2024-08-14 16:33:40 -04:00
|
|
|
}
|
|
|
|
|
2024-08-25 17:35:46 -04:00
|
|
|
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()
|
|
|
|
|
2024-08-14 16:33:40 -04:00
|
|
|
const GroupPermission = ({
|
|
|
|
id,
|
|
|
|
path,
|
|
|
|
allow,
|
|
|
|
groupId,
|
|
|
|
edit,
|
|
|
|
showEdit,
|
|
|
|
isNew,
|
|
|
|
permissionPaths,
|
|
|
|
adder,
|
2024-08-25 17:35:46 -04:00
|
|
|
remover,
|
|
|
|
contains
|
2024-08-14 16:33:40 -04:00
|
|
|
}: Permission) => {
|
|
|
|
const [pathOpen, setPathOpen] = useState(false)
|
|
|
|
const [isEditable, setIsEditable] = useState(edit)
|
2024-08-25 17:35:46 -04:00
|
|
|
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)
|
2024-08-14 16:33:40 -04:00
|
|
|
|
|
|
|
function Save() {
|
2024-08-25 17:35:46 -04:00
|
|
|
setError(undefined)
|
2024-08-14 16:33:40 -04:00
|
|
|
if (!permission || !permission.path)
|
|
|
|
return
|
|
|
|
|
2024-08-25 17:35:46 -04:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-08-14 16:33:40 -04:00
|
|
|
if (isNew) {
|
2024-08-25 17:35:46 -04:00
|
|
|
if (contains(permission.path)) {
|
|
|
|
setError("Permission already exists.")
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-08-14 16:33:40 -04:00
|
|
|
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 {
|
2024-08-25 17:35:46 -04:00
|
|
|
if (!contains(permission.path)) {
|
|
|
|
setError("Permission does not exists.")
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-08-14 16:33:40 -04:00
|
|
|
axios.put("/api/settings/groups/permissions", {
|
|
|
|
id: permission.id,
|
|
|
|
path: permission.path,
|
|
|
|
allow: permission.allow
|
|
|
|
}).then(d => {
|
|
|
|
setIsEditable(false)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function Cancel() {
|
|
|
|
if (!oldData)
|
|
|
|
return
|
2024-08-25 17:35:46 -04:00
|
|
|
|
|
|
|
setError(undefined)
|
2024-08-14 16:33:40 -04:00
|
|
|
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) => {
|
2024-08-25 17:35:46 -04:00
|
|
|
setPermission({ ...permission, path: permissionPaths.find(v => v.path.toLowerCase() == value.toLowerCase())?.path ?? value.toLowerCase() })
|
2024-08-14 16:33:40 -04:00
|
|
|
setPathOpen(false)
|
|
|
|
}}>
|
|
|
|
<div>
|
|
|
|
<div className="text-lg">
|
|
|
|
{p.path}
|
|
|
|
</div>
|
|
|
|
<div className="text-xs text-green-200">
|
|
|
|
{p.description}
|
|
|
|
</div>
|
|
|
|
</div>
|
2024-08-25 17:35:46 -04:00
|
|
|
|
2024-08-14 16:33:40 -04:00
|
|
|
</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>
|
2024-08-25 17:35:46 -04:00
|
|
|
{error &&
|
|
|
|
<p className="w-[380px] text-red-600 text-wrap text-sm">
|
|
|
|
{error}
|
|
|
|
</p>
|
|
|
|
}
|
2024-08-14 16:33:40 -04:00
|
|
|
<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;
|