Composer update, Groups frontend, API improvements
This commit is contained in:
parent
0125c83bea
commit
1e33c3b8d4
@ -4,6 +4,8 @@ namespace Core\API {
|
||||
|
||||
use Core\Driver\SQL\Expression\Count;
|
||||
use Core\Objects\Context;
|
||||
use Core\Objects\DatabaseEntity\Group;
|
||||
use Core\Objects\DatabaseEntity\User;
|
||||
|
||||
abstract class GroupsAPI extends Request {
|
||||
|
||||
@ -22,6 +24,30 @@ namespace Core\API {
|
||||
$this->lastError = $sql->getLastError();
|
||||
return $this->success && $res[0]["count"] > 0;
|
||||
}
|
||||
|
||||
protected function getGroup(int $groupId): Group|false {
|
||||
$sql = $this->context->getSQL();
|
||||
$group = Group::find($sql, $groupId);
|
||||
if ($group === false) {
|
||||
return $this->createError("Error fetching group: " . $sql->getLastError());
|
||||
} else if ($group === null) {
|
||||
return $this->createError("This group does not exist.");
|
||||
}
|
||||
|
||||
return $group;
|
||||
}
|
||||
|
||||
protected function getUser(int $userId): User|false {
|
||||
$sql = $this->context->getSQL();
|
||||
$user = User::find($sql, $userId, true);
|
||||
if ($user === false) {
|
||||
return $this->createError("Error fetching user: " . $sql->getLastError());
|
||||
} else if ($user === null) {
|
||||
return $this->createError("This user does not exist.");
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,12 +59,14 @@ namespace Core\API\Groups {
|
||||
use Core\API\Traits\Pagination;
|
||||
use Core\Driver\SQL\Column\Column;
|
||||
use Core\Driver\SQL\Condition\Compare;
|
||||
use Core\Driver\SQL\Condition\CondAnd;
|
||||
use Core\Driver\SQL\Expression\Alias;
|
||||
use Core\Driver\SQL\Expression\Count;
|
||||
use Core\Driver\SQL\Join\InnerJoin;
|
||||
use Core\Driver\SQL\Query\Insert;
|
||||
use Core\Objects\Context;
|
||||
use Core\Objects\DatabaseEntity\Group;
|
||||
use Core\Objects\DatabaseEntity\Route;
|
||||
use Core\Objects\DatabaseEntity\User;
|
||||
|
||||
class Fetch extends GroupsAPI {
|
||||
@ -49,7 +77,7 @@ namespace Core\API\Groups {
|
||||
|
||||
public function __construct(Context $context, $externalCall = false) {
|
||||
parent::__construct($context, $externalCall,
|
||||
self::getPaginationParameters(['id', 'name', 'member_count'])
|
||||
self::getPaginationParameters(['id', 'name', 'memberCount'])
|
||||
);
|
||||
|
||||
$this->groupCount = 0;
|
||||
@ -97,14 +125,9 @@ namespace Core\API\Groups {
|
||||
}
|
||||
|
||||
protected function _execute(): bool {
|
||||
$sql = $this->context->getSQL();
|
||||
$groupId = $this->getParam("id");
|
||||
$group = Group::find($sql, $groupId);
|
||||
if ($group === false) {
|
||||
return $this->createError("Error fetching group: " . $sql->getLastError());
|
||||
} else if ($group === null) {
|
||||
return $this->createError("Group not found");
|
||||
} else {
|
||||
$group = $this->getGroup($groupId);
|
||||
if ($group) {
|
||||
$this->result["group"] = $group->jsonSerialize();
|
||||
}
|
||||
|
||||
@ -157,10 +180,10 @@ namespace Core\API\Groups {
|
||||
|
||||
class Create extends GroupsAPI {
|
||||
public function __construct(Context $context, $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array(
|
||||
parent::__construct($context, $externalCall, [
|
||||
'name' => new StringType('name', 32),
|
||||
'color' => new StringType('color', 10),
|
||||
));
|
||||
]);
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
@ -199,6 +222,55 @@ namespace Core\API\Groups {
|
||||
}
|
||||
}
|
||||
|
||||
class Update extends GroupsAPI {
|
||||
public function __construct(Context $context, $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, [
|
||||
"id" => new Parameter("id", Parameter::TYPE_INT),
|
||||
'name' => new StringType('name', 32),
|
||||
'color' => new StringType('color', 10),
|
||||
]);
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
$sql = $this->context->getSQL();
|
||||
$groupId = $this->getParam("id");
|
||||
$name = $this->getParam("name");
|
||||
if (preg_match("/^[a-zA-Z][a-zA-Z0-9_-]*$/", $name) !== 1) {
|
||||
return $this->createError("Invalid name");
|
||||
}
|
||||
|
||||
$color = $this->getParam("color");
|
||||
if (preg_match("/^#[a-fA-F0-9]{3,6}$/", $color) !== 1) {
|
||||
return $this->createError("Invalid color");
|
||||
}
|
||||
|
||||
$group = $this->getGroup($groupId);
|
||||
if ($group === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$otherGroup = Group::findBy(Group::createBuilder($sql, true)
|
||||
->whereNeq("id", $groupId)
|
||||
->whereEq("name", $name)
|
||||
->first());
|
||||
|
||||
if ($otherGroup) {
|
||||
return $this->createError("This name is already in use");
|
||||
}
|
||||
|
||||
$group->name = $name;
|
||||
$group->color = $color;
|
||||
$this->success = ($group->save($sql) !== FALSE);
|
||||
$this->lastError = $sql->getLastError();
|
||||
|
||||
return $this->success;
|
||||
}
|
||||
|
||||
public static function getDefaultACL(Insert $insert): void {
|
||||
$insert->addRow(self::getEndpoint(), [Group::ADMIN], "Allows users to update existing groups", true);
|
||||
}
|
||||
}
|
||||
|
||||
class Delete extends GroupsAPI {
|
||||
public function __construct(Context $context, $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array(
|
||||
@ -213,16 +285,13 @@ namespace Core\API\Groups {
|
||||
}
|
||||
|
||||
$sql = $this->context->getSQL();
|
||||
$group = Group::find($sql, $id);
|
||||
if ($group === false) {
|
||||
return $this->createError("Error fetching group: " . $sql->getLastError());
|
||||
} else if ($group === null) {
|
||||
return $this->createError("This group does not exist.");
|
||||
} else {
|
||||
$group = $this->getGroup($id);
|
||||
if ($group) {
|
||||
$this->success = ($group->delete($sql) !== FALSE);
|
||||
$this->lastError = $sql->getLastError();
|
||||
return $this->success;
|
||||
}
|
||||
|
||||
return $this->success;
|
||||
}
|
||||
|
||||
public static function getDefaultACL(Insert $insert): void {
|
||||
@ -233,32 +302,31 @@ namespace Core\API\Groups {
|
||||
class AddMember extends GroupsAPI {
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, [
|
||||
new Parameter("id", Parameter::TYPE_INT),
|
||||
new Parameter("userId", Parameter::TYPE_INT)
|
||||
"id" => new Parameter("id", Parameter::TYPE_INT),
|
||||
"userId" => new Parameter("userId", Parameter::TYPE_INT)
|
||||
]);
|
||||
}
|
||||
|
||||
protected function _execute(): bool {
|
||||
$sql = $this->context->getSQL();
|
||||
$groupId = $this->getParam("id");
|
||||
$userId = $this->getParam("userId");
|
||||
$group = Group::find($sql, $groupId);
|
||||
$group = $this->getGroup($groupId);
|
||||
if ($group === false) {
|
||||
return $this->createError("Error fetching group: " . $sql->getLastError());
|
||||
} else if ($group === null) {
|
||||
return $this->createError("This group does not exist.");
|
||||
return false;
|
||||
}
|
||||
|
||||
$user = User::find($sql, $userId, true);
|
||||
$userId = $this->getParam("userId");
|
||||
$currentUser = $this->context->getUser();
|
||||
$user = $this->getUser($userId);
|
||||
if ($user === false) {
|
||||
return $this->createError("Error fetching user: " . $sql->getLastError());
|
||||
} else if ($user === null) {
|
||||
return $this->createError("This user does not exist.");
|
||||
return false;
|
||||
} else if (isset($user->getGroups()[$groupId])) {
|
||||
return $this->createError("This user is already member of this group.");
|
||||
} else if ($groupId === Group::ADMIN && !$currentUser->hasGroup(Group::ADMIN)) {
|
||||
return $this->createError("You cannot add the administrator group to other users.");
|
||||
}
|
||||
|
||||
$user->groups[$groupId] = $group;
|
||||
$sql = $this->context->getSQL();
|
||||
$this->success = $user->save($sql, ["groups"], true);
|
||||
if (!$this->success) {
|
||||
return $this->createError("Error saving user: " . $sql->getLastError());
|
||||
@ -275,32 +343,31 @@ namespace Core\API\Groups {
|
||||
class RemoveMember extends GroupsAPI {
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, [
|
||||
new Parameter("id", Parameter::TYPE_INT),
|
||||
new Parameter("userId", Parameter::TYPE_INT)
|
||||
"id" => new Parameter("id", Parameter::TYPE_INT),
|
||||
"userId" => new Parameter("userId", Parameter::TYPE_INT)
|
||||
]);
|
||||
}
|
||||
|
||||
protected function _execute(): bool {
|
||||
$sql = $this->context->getSQL();
|
||||
$groupId = $this->getParam("id");
|
||||
$userId = $this->getParam("userId");
|
||||
$group = Group::find($sql, $groupId);
|
||||
$group = $this->getGroup($groupId);
|
||||
if ($group === false) {
|
||||
return $this->createError("Error fetching group: " . $sql->getLastError());
|
||||
} else if ($group === null) {
|
||||
return $this->createError("This group does not exist.");
|
||||
return false;
|
||||
}
|
||||
|
||||
$user = User::find($sql, $userId, true);
|
||||
$userId = $this->getParam("userId");
|
||||
$currentUser = $this->context->getUser();
|
||||
$user = $this->getUser($userId);
|
||||
if ($user === false) {
|
||||
return $this->createError("Error fetching user: " . $sql->getLastError());
|
||||
} else if ($user === null) {
|
||||
return $this->createError("This user does not exist.");
|
||||
return false;
|
||||
} else if (!isset($user->getGroups()[$groupId])) {
|
||||
return $this->createError("This user is not member of this group.");
|
||||
} else if ($userId === $currentUser->getId() && $groupId === Group::ADMIN) {
|
||||
return $this->createError("Cannot remove Administrator group from own user.");
|
||||
}
|
||||
|
||||
unset($user->groups[$groupId]);
|
||||
$sql = $this->context->getSQL();
|
||||
$this->success = $user->save($sql, ["groups"], true);
|
||||
if (!$this->success) {
|
||||
return $this->createError("Error saving user: " . $sql->getLastError());
|
||||
|
@ -88,13 +88,12 @@ namespace Core\API\Language {
|
||||
return $this->success;
|
||||
}
|
||||
|
||||
private function updateLanguage(): bool {
|
||||
private function updateLanguage(): void {
|
||||
$sql = $this->context->getSQL();
|
||||
$currentUser = $this->context->getUser();
|
||||
$currentUser->language = $this->language;
|
||||
$this->success = $currentUser->save($sql, ["language"]);
|
||||
$this->lastError = $sql->getLastError();
|
||||
return $this->success;
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
@ -141,12 +140,12 @@ namespace Core\API\Language {
|
||||
}
|
||||
|
||||
$moduleFound = false;
|
||||
foreach (["Site", "Core"] as $baseDir) {
|
||||
$filePath = realpath(implode("/", [$baseDir, "Localization", $code, "$module.php"]));
|
||||
foreach (["Core", "Site"] as $baseDir) {
|
||||
$filePath = realpath(implode("/", [WEBROOT, $baseDir, "Localization", $code, "$module.php"]));
|
||||
if ($filePath && is_file($filePath)) {
|
||||
$moduleFound = true;
|
||||
$moduleEntries = @include_once $filePath;
|
||||
$entries[$module] = $moduleEntries;
|
||||
$entries[$module] = array_merge($entries[$module] ?? [], $moduleEntries);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -106,8 +106,11 @@ class Swagger extends Request {
|
||||
"post" => [
|
||||
"produces" => ["application/json"],
|
||||
"responses" => [
|
||||
"200" => ["description" => ""],
|
||||
"200" => ["description" => "OK!"],
|
||||
"400" => ["description" => "Parameter validation failed"],
|
||||
"401" => ["description" => "Login or 2FA Authorization is required"],
|
||||
"403" => ["description" => "CSRF-Token validation failed or insufficient permissions"],
|
||||
"503" => ["description" => "Function is disabled"],
|
||||
]
|
||||
]
|
||||
];
|
||||
|
@ -136,6 +136,7 @@ namespace Core\API\User {
|
||||
use Core\API\UserAPI;
|
||||
use Core\API\VerifyCaptcha;
|
||||
use Core\Driver\SQL\Condition\CondBool;
|
||||
use Core\Driver\SQL\Condition\CondLike;
|
||||
use Core\Driver\SQL\Condition\CondOr;
|
||||
use Core\Driver\SQL\Expression\Alias;
|
||||
use Core\Driver\SQL\Query\Insert;
|
||||
@ -320,6 +321,39 @@ namespace Core\API\User {
|
||||
}
|
||||
}
|
||||
|
||||
class Search extends UserAPI {
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, [
|
||||
"query" => new StringType("query", 64)
|
||||
]);
|
||||
}
|
||||
|
||||
protected function _execute(): bool {
|
||||
$sql = $this->context->getSQL();
|
||||
$query = $this->getParam("query");
|
||||
|
||||
$users = User::findBy(User::createBuilder($sql, false)
|
||||
->where(new CondOr(
|
||||
new CondLike(new Column("name"), "%$query%"),
|
||||
new CondLike(new Column("full_name"), "%$query%"),
|
||||
new CondLike(new Column("email"), "%$query%"),
|
||||
))
|
||||
->whereTrue("active")
|
||||
);
|
||||
|
||||
if ($users === false) {
|
||||
return $this->createError($sql->getLastError());
|
||||
}
|
||||
|
||||
$this->result["users"] = $users;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function getDefaultACL(Insert $insert): void {
|
||||
$insert->addRow(self::getEndpoint(), "Allows users to search other users", [Group::ADMIN, Group::SUPPORT], true);
|
||||
}
|
||||
}
|
||||
|
||||
class Info extends UserAPI {
|
||||
|
||||
public function __construct(Context $context, $externalCall = false) {
|
||||
|
@ -12,5 +12,6 @@ class Admin extends TemplateDocument {
|
||||
$this->searchable = false;
|
||||
$this->enableCSP();
|
||||
$this->addCSPWhitelist("/react/dist/admin-panel/");
|
||||
$this->languageModules[] = "admin";
|
||||
}
|
||||
}
|
12
Core/External/composer.json
vendored
12
Core/External/composer.json
vendored
@ -1,14 +1,14 @@
|
||||
{
|
||||
"require": {
|
||||
"php-mqtt/client": "^1.1",
|
||||
"twig/twig": "^3.0",
|
||||
"chillerlan/php-qrcode": "^4.3",
|
||||
"php-mqtt/client": "^2.0",
|
||||
"twig/twig": "^3.8",
|
||||
"chillerlan/php-qrcode": "^5.0",
|
||||
"christian-riesen/base32": "^1.6",
|
||||
"spomky-labs/cbor-php": "2.1.0",
|
||||
"web-auth/cose-lib": "3.3.12",
|
||||
"spomky-labs/cbor-php": "^3.0",
|
||||
"web-auth/cose-lib": "^4.0",
|
||||
"html2text/html2text": "^4.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.5"
|
||||
"phpunit/phpunit": "^9.6"
|
||||
}
|
||||
}
|
||||
|
691
Core/External/composer.lock
generated
vendored
691
Core/External/composer.lock
generated
vendored
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
||||
{% extends "base.twig" %}
|
||||
|
||||
{% block head %}
|
||||
<title>{{ site.name }} - {{ L("admin.admin") }}</title>
|
||||
<title>{{ site.name }} - {{ L("admin.title") }}</title>
|
||||
<link rel="stylesheet" href="/css/fontawesome.min.css" nonce="{{ site.csp.nonce }}">
|
||||
{% endblock %}
|
||||
|
||||
|
@ -397,7 +397,7 @@ zend.exception_string_param_max_len = 0
|
||||
; threat in any way, but it makes it possible to determine whether you use PHP
|
||||
; on your server or not.
|
||||
; https://php.net/expose-php
|
||||
expose_php = On
|
||||
expose_php = Off
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;
|
||||
; Resource Limits ;
|
||||
|
@ -1,9 +1,15 @@
|
||||
import {useCallback, useContext, useEffect, useState} from "react";
|
||||
import {Link, useNavigate, useParams} from "react-router-dom";
|
||||
import {LocaleContext} from "shared/locale";
|
||||
import {CircularProgress} from "@material-ui/core";
|
||||
import {Button, CircularProgress} from "@material-ui/core";
|
||||
import * as React from "react";
|
||||
import ColorPicker from "material-ui-color-picker";
|
||||
import {ControlsColumn, DataTable, NumericColumn, StringColumn} from "shared/elements/data-table";
|
||||
import EditIcon from "@mui/icons-material/Edit";
|
||||
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";
|
||||
|
||||
const defaultGroupData = {
|
||||
name: "",
|
||||
@ -11,6 +17,12 @@ const defaultGroupData = {
|
||||
members: []
|
||||
};
|
||||
|
||||
const ButtonBar = styled(Box)((props) => ({
|
||||
"& > button": {
|
||||
marginRight: props.theme.spacing(1)
|
||||
}
|
||||
}));
|
||||
|
||||
export default function EditGroupView(props) {
|
||||
|
||||
const {translate: L, requestModules, currentLocale} = useContext(LocaleContext);
|
||||
@ -18,14 +30,30 @@ export default function EditGroupView(props) {
|
||||
const { groupId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const isNewGroup = groupId === "new";
|
||||
const pagination = usePagination();
|
||||
const api = props.api;
|
||||
|
||||
// data
|
||||
const [fetchGroup, setFetchGroup] = useState(!isNewGroup);
|
||||
const [group, setGroup] = useState(isNewGroup ? defaultGroupData : null);
|
||||
const [members, setMembers] = useState([]);
|
||||
|
||||
// ui
|
||||
const [dialogData, setDialogData] = useState({open: false});
|
||||
const [isSaving, setSaving] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
requestModules(props.api, ["general", "account"], currentLocale).then(data => {
|
||||
if (!data.success) {
|
||||
props.showDialog(data.msg, "Error fetching localization");
|
||||
}
|
||||
});
|
||||
}, [currentLocale]);
|
||||
|
||||
const onFetchGroup = useCallback((force = false) => {
|
||||
if (force || fetchGroup) {
|
||||
setFetchGroup(false);
|
||||
props.api.getGroup(groupId).then(res => {
|
||||
api.getGroup(groupId).then(res => {
|
||||
if (!res.success) {
|
||||
props.showDialog(res.msg, "Error fetching group");
|
||||
navigate("/admin/groups");
|
||||
@ -34,7 +62,51 @@ export default function EditGroupView(props) {
|
||||
}
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
}, [api, fetchGroup]);
|
||||
|
||||
const onFetchMembers = useCallback(async (page, count, orderBy, sortOrder) => {
|
||||
api.fetchGroupMembers(groupId, page, count, orderBy, sortOrder).then((res) => {
|
||||
if (res.success) {
|
||||
setMembers(res.users);
|
||||
pagination.update(res.pagination);
|
||||
} else {
|
||||
props.showDialog(res.msg, "Error fetching group members");
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}, [groupId, api, pagination]);
|
||||
|
||||
const onRemoveMember = useCallback(userId => {
|
||||
api.removeGroupMember(groupId, userId).then(data => {
|
||||
if (data.success) {
|
||||
let newMembers = members.filter(u => u.id !== userId);
|
||||
setMembers(newMembers);
|
||||
} else {
|
||||
props.showDialog(data.msg, "Error removing group member");
|
||||
}
|
||||
});
|
||||
}, [api, groupId, members]);
|
||||
|
||||
const onSave = useCallback(() => {
|
||||
setSaving(true);
|
||||
if (isNewGroup) {
|
||||
api.createGroup(group.name, group.color).then(data => {
|
||||
if (!data.success) {
|
||||
props.showDialog(data.msg, "Error creating group");
|
||||
setSaving(false);
|
||||
} else {
|
||||
navigate(`/admin/groups/${data.id}`)
|
||||
}
|
||||
});
|
||||
} else {
|
||||
api.updateGroup(groupId, group.name, group.color).then(data => {
|
||||
setSaving(false);
|
||||
if (!data.success) {
|
||||
props.showDialog(data.msg, "Error updating group");
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [api, groupId, isNewGroup, group]);
|
||||
|
||||
useEffect(() => {
|
||||
onFetchGroup();
|
||||
@ -65,43 +137,97 @@ export default function EditGroupView(props) {
|
||||
</div>
|
||||
<div className={"content"}>
|
||||
<div className={"row"}>
|
||||
<div className={"col-6 pl-5 pr-5"}>
|
||||
<form role={"form"} onSubmit={(e) => this.submitForm(e)}>
|
||||
<div className={"form-group"}>
|
||||
<label htmlFor={"name"}>{L("account.group_name")}</label>
|
||||
<input type={"text"} className={"form-control"} placeholder={"Name"}
|
||||
name={"name"} id={"name"} maxLength={32} value={group.name}/>
|
||||
</div>
|
||||
<div className={"col-4 pl-5 pr-5"}>
|
||||
<FormGroup className={"my-2"}>
|
||||
<FormLabel htmlFor={"name"}>
|
||||
{L("account.group_name")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<TextField maxLength={32} value={group.name}
|
||||
size={"small"} name={"name"}
|
||||
placeholder={L("account.name")}
|
||||
onChange={e => setGroup({...group, name: e.target.value})}/>
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
|
||||
<div className={"form-group"}>
|
||||
<label htmlFor={"color"}>
|
||||
{L("account.color")}
|
||||
</label>
|
||||
<div>
|
||||
<ColorPicker
|
||||
value={group.color}
|
||||
size={"small"}
|
||||
variant={"outlined"}
|
||||
style={{ backgroundColor: group.color }}
|
||||
floatingLabelText={group.color}
|
||||
onChange={color => setGroup({...group, color: color})}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<FormGroup className={"my-2"}>
|
||||
<FormLabel htmlFor={"color"}>
|
||||
{L("account.color")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<ColorPicker
|
||||
value={group.color}
|
||||
size={"small"}
|
||||
variant={"outlined"}
|
||||
style={{ backgroundColor: group.color }}
|
||||
floatingLabelText={group.color}
|
||||
onChange={color => setGroup({...group, color: color})}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
|
||||
<Link to={"/admin/groups"} className={"btn btn-info mt-2 mr-2"}>
|
||||
{L("general.go_back")}
|
||||
</Link>
|
||||
<button type={"submit"} className={"btn btn-primary mt-2"}>
|
||||
{L("general.submit")}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div className={"col-6"}>
|
||||
<h3>{L("account.members")}</h3>
|
||||
<ButtonBar mt={2}>
|
||||
<Button startIcon={<KeyboardArrowLeft />}
|
||||
variant={"outlined"}
|
||||
onClick={() => navigate("/admin/groups")}>
|
||||
{L("general.cancel")}
|
||||
</Button>
|
||||
<Button startIcon={<Save />} color={"primary"}
|
||||
variant={"outlined"} disabled={isSaving}
|
||||
onClick={onSave}>
|
||||
{isSaving ? L("general.saving") + "…" : L("general.save")}
|
||||
</Button>
|
||||
</ButtonBar>
|
||||
</div>
|
||||
</div>
|
||||
{!isNewGroup && api.hasPermission("groups/getMembers") ?
|
||||
<div className={"m-3"}>
|
||||
<div className={"col-6"}>
|
||||
<DataTable
|
||||
data={members}
|
||||
pagination={pagination}
|
||||
defaultSortOrder={"asc"}
|
||||
defaultSortColumn={0}
|
||||
className={"table table-striped"}
|
||||
fetchData={onFetchMembers}
|
||||
placeholder={L("No members in this group")}
|
||||
title={L("account.members")}
|
||||
columns={[
|
||||
new NumericColumn(L("general.id"), "id"),
|
||||
new StringColumn(L("account.name"), "name"),
|
||||
new StringColumn(L("account.full_name"), "fullName"),
|
||||
new ControlsColumn(L("general.controls"), [
|
||||
{
|
||||
label: L("general.edit"),
|
||||
element: EditIcon,
|
||||
onClick: (entry) => navigate(`/admin/user/${entry.id}`)
|
||||
},
|
||||
{
|
||||
label: L("general.remove"),
|
||||
element: Delete,
|
||||
disabled: !api.hasPermission("groups/removeMember"),
|
||||
onClick: (entry) => setDialogData({
|
||||
open: true,
|
||||
title: L("Remove member"),
|
||||
message: sprintf(L("Do you really want to remove user '%s' from this group?"), entry.fullName || entry.name),
|
||||
onOption: (option) => option === 0 && onRemoveMember(entry.id)
|
||||
})
|
||||
}
|
||||
]),
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
: <></>
|
||||
}
|
||||
</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")]} />
|
||||
</>
|
||||
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import {Link, useNavigate} from "react-router-dom";
|
||||
import {useCallback, useContext, useEffect, useState} from "react";
|
||||
import {LocaleContext} from "shared/locale";
|
||||
import {DataColumn, DataTable, NumericColumn, StringColumn} from "shared/elements/data-table";
|
||||
import {ControlsColumn, DataColumn, DataTable, NumericColumn, StringColumn} from "shared/elements/data-table";
|
||||
import {Button, IconButton} from "@material-ui/core";
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
@ -28,7 +28,6 @@ export default function GroupListView(props) {
|
||||
}, [currentLocale]);
|
||||
|
||||
const onFetchGroups = useCallback(async (page, count, orderBy, sortOrder) => {
|
||||
|
||||
api.fetchGroups(page, count, orderBy, sortOrder).then((res) => {
|
||||
if (res.success) {
|
||||
setGroups(res.groups);
|
||||
@ -40,21 +39,13 @@ export default function GroupListView(props) {
|
||||
});
|
||||
}, [api, pagination]);
|
||||
|
||||
const actionColumn = (() => {
|
||||
let column = new DataColumn(L("general.actions"), null, false);
|
||||
column.renderData = (L, entry) => <>
|
||||
<IconButton size={"small"} title={L("general.edit")} onClick={() => navigate("/admin/group/" + entry.id)}>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
</>
|
||||
return column;
|
||||
})();
|
||||
|
||||
const columnDefinitions = [
|
||||
new NumericColumn(L("general.id"), "id"),
|
||||
new StringColumn(L("account.name"), "name"),
|
||||
new NumericColumn(L("account.member_count"), "memberCount"),
|
||||
actionColumn,
|
||||
new NumericColumn(L("account.member_count"), "memberCount", { align: "center" }),
|
||||
new ControlsColumn(L("general.controls"), [
|
||||
{ label: L("general.edit"), element: EditIcon, onClick: (entry) => navigate(`/admin/group/${entry.id}`) }
|
||||
]),
|
||||
];
|
||||
|
||||
return <>
|
||||
|
@ -153,14 +153,6 @@ export default class API {
|
||||
return this.apiCall("user/fetch", { page: pageNum, count: count, orderBy: orderBy, sortOrder: sortOrder });
|
||||
}
|
||||
|
||||
async fetchGroups(pageNum = 1, count = 20, orderBy = 'id', sortOrder = 'asc') {
|
||||
return this.apiCall("groups/fetch", { page: pageNum, count: count, orderBy: orderBy, sortOrder: sortOrder });
|
||||
}
|
||||
|
||||
async getGroup(id) {
|
||||
return this.apiCall("groups/get", { id: id });
|
||||
}
|
||||
|
||||
async inviteUser(username, email) {
|
||||
return this.apiCall("user/invite", { username: username, email: email });
|
||||
}
|
||||
@ -207,6 +199,39 @@ export default class API {
|
||||
return res;
|
||||
}
|
||||
|
||||
/** Groups API **/
|
||||
async fetchGroups(pageNum = 1, count = 20, orderBy = 'id', sortOrder = 'asc') {
|
||||
return this.apiCall("groups/fetch", { page: pageNum, count: count, orderBy: orderBy, sortOrder: sortOrder });
|
||||
}
|
||||
|
||||
async fetchGroupMembers(groupId, pageNum = 1, count = 20, orderBy = 'id', sortOrder = 'asc') {
|
||||
return this.apiCall("groups/getMembers", { id: groupId, page: pageNum, count: count, orderBy: orderBy, sortOrder: sortOrder });
|
||||
}
|
||||
|
||||
async removeGroupMember (groupId, userId) {
|
||||
return this.apiCall("groups/removeMember", { id: groupId, userId: userId });
|
||||
}
|
||||
|
||||
async addGroupMember (groupId, userId) {
|
||||
return this.apiCall("groups/addMember", { id: groupId, userId: userId });
|
||||
}
|
||||
|
||||
async getGroup(id) {
|
||||
return this.apiCall("groups/get", { id: id });
|
||||
}
|
||||
|
||||
async createGroup(name, color) {
|
||||
return this.apiCall("groups/create", { name: name, color: color });
|
||||
}
|
||||
|
||||
async updateGroup(id, name, color) {
|
||||
return this.apiCall("groups/update", { id: id, name: name, color: color });
|
||||
}
|
||||
|
||||
async deleteGroup(id) {
|
||||
return this.apiCall("groups/delete", { id: id });
|
||||
}
|
||||
|
||||
/** Stats **/
|
||||
async getStats() {
|
||||
return this.apiCall("stats");
|
||||
@ -249,15 +274,6 @@ export default class API {
|
||||
return this.apiCall("routes/update", { id, pattern, type, target, extra, exact, active });
|
||||
}
|
||||
|
||||
/** GroupAPI **/
|
||||
async createGroup(name, color) {
|
||||
return this.apiCall("groups/create", { name: name, color: color });
|
||||
}
|
||||
|
||||
async deleteGroup(id) {
|
||||
return this.apiCall("groups/delete", { id: id });
|
||||
}
|
||||
|
||||
/** SettingsAPI **/
|
||||
async getSettings(key = "") {
|
||||
return this.apiCall("settings/get", { key: key });
|
||||
|
Loading…
Reference in New Issue
Block a user