Bugfix
This commit is contained in:
parent
f13ab7f9e0
commit
a7dc4c0d2f
@ -75,8 +75,8 @@ namespace Core\API\Routes {
|
||||
use Core\Objects\DatabaseEntity\Group;
|
||||
use Core\Objects\DatabaseEntity\Route;
|
||||
use Core\Objects\Router\ApiRoute;
|
||||
use Core\Objects\Router\EmptyRoute;
|
||||
use Core\Objects\Router\Router;
|
||||
use Core\Objects\Router\StaticRoute;
|
||||
|
||||
class Fetch extends RoutesAPI {
|
||||
|
||||
@ -363,7 +363,7 @@ namespace Core\API\Routes {
|
||||
$path = $this->getParam("path");
|
||||
$pattern = $this->getParam("pattern");
|
||||
$exact = $this->getParam("exact");
|
||||
$route = new StaticRoute($pattern, $exact, "");
|
||||
$route = new EmptyRoute($pattern, $exact, "");
|
||||
$this->result["match"] = $route->match($path);
|
||||
return $this->success;
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ class Logger {
|
||||
}, $debugTrace));
|
||||
}
|
||||
|
||||
public function log(string $message, string $severity, bool $appendStackTrace = true) {
|
||||
public function log(string $message, string $severity, bool $appendStackTrace = true): void {
|
||||
|
||||
if ($appendStackTrace) {
|
||||
$message .= "\n" . $this->getStackTrace();
|
||||
|
@ -44,4 +44,14 @@ class Group extends DatabaseEntity {
|
||||
new Group(Group::SUPPORT, Group::GROUPS[Group::SUPPORT], "#007bff"),
|
||||
];
|
||||
}
|
||||
|
||||
public function delete(SQL $sql): bool {
|
||||
if (parent::delete($sql)) {
|
||||
$handler = User::getHandler($sql);
|
||||
$table = $handler->getNMRelation("groups")->getTableName();
|
||||
return $sql->delete($table)->whereEq("group_id", $this->id)->execute();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -147,7 +147,7 @@ export default function AccessControlList(props) {
|
||||
const permission = acl[index];
|
||||
|
||||
if (query) {
|
||||
if (!permission.method.toLowerCase().includes(query.toLowerCase()) ||
|
||||
if (!permission.method.toLowerCase().includes(query.toLowerCase()) &&
|
||||
!permission.description.toLowerCase().includes(query.toLowerCase())) {
|
||||
continue;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import {useCallback, useContext, useEffect, useState} from "react";
|
||||
import {Link, useNavigate, useParams} from "react-router-dom";
|
||||
import {LocaleContext} from "shared/locale";
|
||||
import SearchField from "shared/elements/search-field";
|
||||
import {Button, CircularProgress} from "@material-ui/core";
|
||||
import * as React from "react";
|
||||
import ColorPicker from "material-ui-color-picker";
|
||||
@ -10,6 +11,7 @@ import usePagination from "shared/hooks/pagination";
|
||||
import {Delete, KeyboardArrowLeft, Save} from "@material-ui/icons";
|
||||
import Dialog from "shared/elements/dialog";
|
||||
import {Box, FormControl, FormGroup, FormLabel, styled, TextField} from "@mui/material";
|
||||
import {Add} from "@mui/icons-material";
|
||||
|
||||
const defaultGroupData = {
|
||||
name: "",
|
||||
@ -37,6 +39,7 @@ export default function EditGroupView(props) {
|
||||
const [fetchGroup, setFetchGroup] = useState(!isNewGroup);
|
||||
const [group, setGroup] = useState(isNewGroup ? defaultGroupData : null);
|
||||
const [members, setMembers] = useState([]);
|
||||
const [selectedUser, setSelectedUser] = useState(null);
|
||||
|
||||
// ui
|
||||
const [dialogData, setDialogData] = useState({open: false});
|
||||
@ -91,11 +94,11 @@ export default function EditGroupView(props) {
|
||||
setSaving(true);
|
||||
if (isNewGroup) {
|
||||
api.createGroup(group.name, group.color).then(data => {
|
||||
setSaving(false);
|
||||
if (!data.success) {
|
||||
props.showDialog(data.msg, "Error creating group");
|
||||
setSaving(false);
|
||||
} else {
|
||||
navigate(`/admin/groups/${data.id}`)
|
||||
navigate(`/admin/group/${data.id}`)
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@ -108,6 +111,41 @@ export default function EditGroupView(props) {
|
||||
}
|
||||
}, [api, groupId, isNewGroup, group]);
|
||||
|
||||
const onSearchUser = useCallback((async (query) => {
|
||||
let data = await api.searchUser(query);
|
||||
if (!data.success) {
|
||||
props.showDialog(data.msg, "Error searching users");
|
||||
return [];
|
||||
}
|
||||
|
||||
return data.users;
|
||||
}), [api]);
|
||||
|
||||
const onAddMember = useCallback(() => {
|
||||
if (selectedUser) {
|
||||
api.addGroupMember(groupId, selectedUser.id).then(data => {
|
||||
if (!data.success) {
|
||||
props.showDialog(data.msg, "Error adding member");
|
||||
} else {
|
||||
let newMembers = [...members];
|
||||
newMembers.push(selectedUser);
|
||||
setMembers(newMembers);
|
||||
}
|
||||
setSelectedUser(null);
|
||||
});
|
||||
}
|
||||
}, [api, groupId, selectedUser])
|
||||
|
||||
const onDeleteGroup = useCallback(() => {
|
||||
api.deleteGroup(groupId).then(data => {
|
||||
if (!data.success) {
|
||||
props.showDialog(data.msg, "Error deleting group");
|
||||
} else {
|
||||
navigate("/admin/groups");
|
||||
}
|
||||
});
|
||||
}, [api, groupId]);
|
||||
|
||||
useEffect(() => {
|
||||
onFetchGroup();
|
||||
}, []);
|
||||
@ -170,19 +208,31 @@ export default function EditGroupView(props) {
|
||||
<Button startIcon={<KeyboardArrowLeft />}
|
||||
variant={"outlined"}
|
||||
onClick={() => navigate("/admin/groups")}>
|
||||
{L("general.cancel")}
|
||||
{L("general.go_back")}
|
||||
</Button>
|
||||
<Button startIcon={<Save />} color={"primary"}
|
||||
variant={"outlined"} disabled={isSaving}
|
||||
variant={"outlined"}
|
||||
disabled={isSaving || (!api.hasPermission(isNewGroup ? "groups/create" : "groups/update"))}
|
||||
onClick={onSave}>
|
||||
{isSaving ? L("general.saving") + "…" : L("general.save")}
|
||||
</Button>
|
||||
{ !isNewGroup &&
|
||||
<Button startIcon={<Delete/>} disabled={!api.hasPermission("groups/delete")}
|
||||
variant={"outlined"} color={"secondary"}
|
||||
onClick={() => setDialogData({
|
||||
open: true,
|
||||
title: L("Delete Group"),
|
||||
message: L("Do you really want to delete this group? This action cannot be undone."),
|
||||
onOption: option => option === 0 && onDeleteGroup()
|
||||
})}>
|
||||
{L("general.delete")}
|
||||
</Button>
|
||||
}
|
||||
</ButtonBar>
|
||||
</div>
|
||||
</div>
|
||||
{!isNewGroup && api.hasPermission("groups/getMembers") ?
|
||||
<div className={"m-3"}>
|
||||
<div className={"col-6"}>
|
||||
<div className={"m-3 col-6"}>
|
||||
<DataTable
|
||||
data={members}
|
||||
pagination={pagination}
|
||||
@ -216,7 +266,26 @@ export default function EditGroupView(props) {
|
||||
]),
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<Button startIcon={<Add />} color={"primary"}
|
||||
variant={"outlined"} disabled={!api.hasPermission("groups/addMember")}
|
||||
onClick={() => setDialogData({
|
||||
open: true,
|
||||
title: L("Add member"),
|
||||
message: "Search a user to add to the group",
|
||||
inputs: [
|
||||
{
|
||||
type: "custom", name: "search", element: SearchField,
|
||||
size: "small", key: "search",
|
||||
onSearch: v => onSearchUser(v),
|
||||
onSelect: u => setSelectedUser(u),
|
||||
displayText: u => u.fullName || u.name
|
||||
}
|
||||
],
|
||||
onOption: (option) => option === 0 ? onAddMember() : setSelectedUser(null)
|
||||
})
|
||||
}>
|
||||
{L("general.add")}
|
||||
</Button>
|
||||
</div>
|
||||
: <></>
|
||||
}
|
||||
|
@ -161,6 +161,10 @@ export default class API {
|
||||
return this.apiCall("user/create", { username: username, email: email, password: password, confirmPassword: confirmPassword });
|
||||
}
|
||||
|
||||
async searchUser(query) {
|
||||
return this.apiCall("user/search", { query : query });
|
||||
}
|
||||
|
||||
async updateProfile(username=null, fullName=null, password=null, confirmPassword = null, oldPassword = null) {
|
||||
let res = await this.apiCall("user/updateProfile", { username: username, fullName: fullName,
|
||||
password: password, confirmPassword: confirmPassword, oldPassword: oldPassword });
|
||||
|
@ -24,7 +24,7 @@ export default function Dialog(props) {
|
||||
if (props.inputs) {
|
||||
let initialData = {};
|
||||
for (const input of props.inputs) {
|
||||
if (input.type !== "label") {
|
||||
if (input.type !== "label" && input.hasOwnProperty("name")) {
|
||||
initialData[input.name] = input.value || "";
|
||||
}
|
||||
}
|
||||
@ -76,6 +76,14 @@ export default function Dialog(props) {
|
||||
{listItems}
|
||||
</List>
|
||||
</Box>);
|
||||
break;
|
||||
case 'custom':
|
||||
let element = inputProps.element;
|
||||
delete inputProps.element;
|
||||
inputElements.push(React.createElement(element, inputProps));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
28
react/shared/elements/search-field.js
Normal file
28
react/shared/elements/search-field.js
Normal file
@ -0,0 +1,28 @@
|
||||
import {Autocomplete, TextField} from "@mui/material";
|
||||
import useAsyncSearch from "../hooks/async-search";
|
||||
|
||||
|
||||
export default function SearchField(props) {
|
||||
|
||||
const { onSearch, displayText, onSelect, ...other } = props;
|
||||
|
||||
const [searchString, setSearchString, results] = useAsyncSearch(props.onSearch, 3);
|
||||
|
||||
return <Autocomplete {...other}
|
||||
getOptionLabel={r => displayText(r)}
|
||||
options={Object.values(results ?? {})}
|
||||
onChange={(e, n) => onSelect(n)}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
value={searchString}
|
||||
onChange={e => setSearchString(e.target.value)}
|
||||
label={"Search input"}
|
||||
InputProps={{
|
||||
...params.InputProps,
|
||||
type: 'search',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>;
|
||||
}
|
Loading…
Reference in New Issue
Block a user