acl view done
This commit is contained in:
parent
aa51380055
commit
a8f4c84f60
@ -24,7 +24,7 @@ class ArrayType extends Parameter {
|
||||
}
|
||||
|
||||
public function parseParam($value): bool {
|
||||
if(!is_array($value)) {
|
||||
if (!is_array($value)) {
|
||||
if (!$this->canBeOne) {
|
||||
return false;
|
||||
} else {
|
||||
|
@ -93,7 +93,7 @@ abstract class Request {
|
||||
foreach ($structure as $name => $param) {
|
||||
$value = $values[$name] ?? NULL;
|
||||
|
||||
$isEmpty = (is_string($value) && strlen($value) === 0) || (is_array($value) && empty($value));
|
||||
$isEmpty = is_string($value) && strlen($value) === 0;
|
||||
if (!$param->optional && (is_null($value) || $isEmpty)) {
|
||||
return $this->createError("Missing parameter: $name");
|
||||
}
|
||||
|
@ -1,9 +1,23 @@
|
||||
import {useCallback, useContext, useEffect, useState} from "react";
|
||||
import {LocaleContext} from "shared/locale";
|
||||
import {Link, useNavigate} from "react-router-dom";
|
||||
import {Button, Checkbox, TextField, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow} from "@material-ui/core";
|
||||
import {Add, Refresh} from "@material-ui/icons";
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
TextField,
|
||||
Paper,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
IconButton
|
||||
} from "@material-ui/core";
|
||||
import {Add, Delete, Edit, Refresh} from "@material-ui/icons";
|
||||
import {USER_GROUP_ADMIN} from "shared/constants";
|
||||
import Dialog from "shared/elements/dialog";
|
||||
import {TableFooter} from "@mui/material";
|
||||
|
||||
|
||||
export default function AccessControlList(props) {
|
||||
@ -20,6 +34,9 @@ export default function AccessControlList(props) {
|
||||
// filters
|
||||
const [query, setQuery] = useState("");
|
||||
|
||||
// view
|
||||
const [dialogData, setDialogData] = useState({open: false});
|
||||
|
||||
const onFetchACL = useCallback((force = false) => {
|
||||
if (force || fetchACL) {
|
||||
setFetchACL(false);
|
||||
@ -84,8 +101,33 @@ export default function AccessControlList(props) {
|
||||
}
|
||||
}, [acl]);
|
||||
|
||||
const onDeletePermission = useCallback(method => {
|
||||
props.api.deletePermission(method).then(data => {
|
||||
if (data.success) {
|
||||
let newACL = acl.filter(acl => acl.method.toLowerCase() !== method.toLowerCase());
|
||||
setACL(newACL);
|
||||
} else {
|
||||
props.showDialog("Error deleting permission: " + data.msg);
|
||||
}
|
||||
})
|
||||
}, [acl]);
|
||||
|
||||
const onUpdatePermission = useCallback((inputData, groups) => {
|
||||
props.api.updatePermission(inputData.method, groups, inputData.description).then(data => {
|
||||
if (data.success) {
|
||||
let newACL = acl.filter(acl => acl.method.toLowerCase() !== inputData.method.toLowerCase());
|
||||
newACL.push({method: inputData.method, groups: groups, description: inputData.description});
|
||||
newACL = newACL.sort((a, b) => a.method.localeCompare(b.method))
|
||||
setACL(newACL);
|
||||
} else {
|
||||
props.showDialog("Error updating permission: " + data.msg);
|
||||
}
|
||||
})
|
||||
}, [acl]);
|
||||
|
||||
const isRestricted = (method) => {
|
||||
return ["permissions/update", "permissions/delete"].includes(method.toLowerCase());
|
||||
return ["permissions/update", "permissions/delete"].includes(method.toLowerCase()) &&
|
||||
!props.api.hasGroup(USER_GROUP_ADMIN);
|
||||
}
|
||||
|
||||
const PermissionList = () => {
|
||||
@ -104,13 +146,44 @@ export default function AccessControlList(props) {
|
||||
rows.push(
|
||||
<TableRow key={"perm-" + index}>
|
||||
<TableCell>
|
||||
<b>{permission.method}</b><br />
|
||||
<i>{permission.description}</i>
|
||||
<div style={{display: "grid", gridTemplateColumns: "60px auto"}}>
|
||||
<div style={{alignSelf: "center"}}>
|
||||
<IconButton style={{padding: 0}} size={"small"} color={"primary"}
|
||||
disabled={isRestricted(permission.method)}
|
||||
onClick={() => setDialogData({
|
||||
open: true,
|
||||
title: L("Edit permission"),
|
||||
inputs: [
|
||||
{ type: "label", value: L("general.method") + ":" },
|
||||
{ type: "text", name: "method", value: permission.method, disabled: true },
|
||||
{ type: "label", value: L("general.description") + ":" },
|
||||
{ type: "text", name: "description", value: permission.description, maxLength: 128 }
|
||||
],
|
||||
onOption: (option, inputData) => option === 0 && onUpdatePermission(inputData, permission.groups)
|
||||
})} >
|
||||
<Edit />
|
||||
</IconButton>
|
||||
<IconButton style={{padding: 0}} size={"small"} color={"secondary"}
|
||||
disabled={isRestricted(permission.method)}
|
||||
onClick={() => setDialogData({
|
||||
open: true,
|
||||
title: L("Do you really want to delete this permission?"),
|
||||
message: "Method: " + permission.method,
|
||||
onOption: (option) => option === 0 && onDeletePermission(permission.method)
|
||||
})} >
|
||||
<Delete />
|
||||
</IconButton>
|
||||
</div>
|
||||
<div>
|
||||
<b>{permission.method}</b><br />
|
||||
<i>{permission.description}</i>
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
{groups.map(group => <TableCell key={"perm-" + index + "-group-" + group.id} align={"center"}>
|
||||
<Checkbox checked={!permission.groups.length || permission.groups.includes(group.id)}
|
||||
onChange={(e) => onChangePermission(index, group.id, e.target.checked)}
|
||||
disabled={isRestricted(permission.method) || !props.api.hasGroup(USER_GROUP_ADMIN)} />
|
||||
disabled={isRestricted(permission.method)} />
|
||||
</TableCell>)}
|
||||
</TableRow>
|
||||
);
|
||||
@ -155,18 +228,28 @@ export default function AccessControlList(props) {
|
||||
{L("general.reload")}
|
||||
</Button>
|
||||
<Button variant={"outlined"} className={"m-1"} startIcon={<Add />} disabled={!props.api.hasGroup(USER_GROUP_ADMIN)}
|
||||
onClick={() => navigate("/admin/acl/new")}>
|
||||
onClick={() => setDialogData({
|
||||
open: true,
|
||||
title: L("Add permission"),
|
||||
inputs: [
|
||||
{ type: "label", value: L("general.method") + ":" },
|
||||
{ type: "text", name: "method", value: "", placeholder: L("general.method") },
|
||||
{ type: "label", value: L("general.description") + ":" },
|
||||
{ type: "text", name: "description", maxLength: 128, placeholder: L("general.description") }
|
||||
],
|
||||
onOption: (option, inputData) => option === 0 && onUpdatePermission(inputData, [])
|
||||
})} >
|
||||
{L("general.add")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<TableContainer component={Paper}>
|
||||
<Table size={"small"}>
|
||||
<TableContainer component={Paper} style={{overflowX: "initial"}}>
|
||||
<Table stickyHeader size={"small"} className={"table-striped"}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell sx={{width: "auto"}}>{L("permission")}</TableCell>
|
||||
<TableCell>{L("permission")}</TableCell>
|
||||
{ groups.map(group => <TableCell key={"group-" + group.id} align={"center"}>
|
||||
{group.name}
|
||||
</TableCell>) }
|
||||
@ -178,5 +261,13 @@ export default function AccessControlList(props) {
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</div>
|
||||
<Dialog show={dialogData.open}
|
||||
onClose={() => setDialogData({open: false})}
|
||||
title={dialogData.title}
|
||||
message={dialogData.message}
|
||||
onOption={dialogData.onOption}
|
||||
inputs={dialogData.inputs}
|
||||
options={[L("general.ok"), L("general.cancel")]} />
|
||||
|
||||
</>
|
||||
}
|
@ -7,7 +7,7 @@ import {
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
Input, List, ListItem, TextField
|
||||
List, ListItem, TextField
|
||||
} from "@mui/material";
|
||||
|
||||
export default function Dialog(props) {
|
||||
@ -24,7 +24,9 @@ export default function Dialog(props) {
|
||||
if (props.inputs) {
|
||||
let initialData = {};
|
||||
for (const input of props.inputs) {
|
||||
initialData[input.name] = input.value || "";
|
||||
if (input.type !== "label") {
|
||||
initialData[input.name] = input.value || "";
|
||||
}
|
||||
}
|
||||
setInputData(initialData);
|
||||
}
|
||||
@ -47,6 +49,9 @@ export default function Dialog(props) {
|
||||
delete inputProps.type;
|
||||
|
||||
switch (input.type) {
|
||||
case 'label':
|
||||
inputElements.push(<span {...inputProps}>{input.value}</span>);
|
||||
break;
|
||||
case 'text':
|
||||
case 'password':
|
||||
inputElements.push(<TextField
|
||||
@ -56,6 +61,7 @@ export default function Dialog(props) {
|
||||
size={"small"} fullWidth={true}
|
||||
key={"input-" + input.name}
|
||||
value={inputData[input.name] || ""}
|
||||
defaultValue={input.defaultValue || ""}
|
||||
onChange={e => setInputData({ ...inputData, [input.name]: e.target.value })}
|
||||
/>)
|
||||
break;
|
||||
|
@ -5,6 +5,7 @@ export default function useAsyncSearch(callback, minLength = 1) {
|
||||
|
||||
const [searchString, setSearchString] = useState("");
|
||||
const [results, setResults] = useState(null);
|
||||
const [isSearching, setSearching] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (minLength > 0 && (!searchString || searchString.length < minLength)) {
|
||||
@ -12,9 +13,13 @@ export default function useAsyncSearch(callback, minLength = 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
callback(searchString).then(results => {
|
||||
setResults(results || null);
|
||||
});
|
||||
if (!isSearching) {
|
||||
setSearching(true);
|
||||
callback(searchString).then(results => {
|
||||
setResults(results || null);
|
||||
setSearching(false);
|
||||
});
|
||||
}
|
||||
}, [searchString]);
|
||||
|
||||
return [searchString, setSearchString, results];
|
||||
|
Loading…
Reference in New Issue
Block a user