v2.4.2: frontend enhancements, user/get fix
This commit is contained in:
parent
150e4eb195
commit
df4582c7e5
@ -237,7 +237,7 @@ namespace Core\API\User {
|
|||||||
|
|
||||||
public function __construct(Context $context, $externalCall = false) {
|
public function __construct(Context $context, $externalCall = false) {
|
||||||
parent::__construct($context, $externalCall,
|
parent::__construct($context, $externalCall,
|
||||||
self::getPaginationParameters(['id', 'name', 'fullName', 'email', 'groups', 'registeredAt', 'active', 'confirmed'],
|
self::getPaginationParameters(['id', 'name', 'fullName', 'email', 'groups', 'lastOnline', 'registeredAt', 'active', 'confirmed'],
|
||||||
'id', 'asc')
|
'id', 'asc')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -316,20 +316,17 @@ namespace Core\API\User {
|
|||||||
} else if ($user === null) {
|
} else if ($user === null) {
|
||||||
return $this->createError("User not found");
|
return $this->createError("User not found");
|
||||||
} else {
|
} else {
|
||||||
|
// allow access to unconfirmed users only when we have administrative privileges, or we are querying ourselves
|
||||||
$queriedUser = $user->jsonSerialize();
|
|
||||||
$currentUser = $this->context->getUser();
|
$currentUser = $this->context->getUser();
|
||||||
|
|
||||||
// full info only when we have administrative privileges, or we are querying ourselves
|
|
||||||
$fullInfo = ($userId === $currentUser->getId() ||
|
$fullInfo = ($userId === $currentUser->getId() ||
|
||||||
$currentUser->hasGroup(Group::ADMIN) ||
|
$currentUser->hasGroup(Group::ADMIN) ||
|
||||||
$currentUser->hasGroup(Group::SUPPORT));
|
$currentUser->hasGroup(Group::SUPPORT));
|
||||||
|
|
||||||
if (!$fullInfo && !$queriedUser["confirmed"]) {
|
if (!$fullInfo && !$user->isConfirmed()) {
|
||||||
return $this->createError("No permissions to access this user");
|
return $this->createError("No permissions to access this user");
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->result["user"] = $queriedUser;
|
$this->result["user"] = $user->jsonSerialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->success;
|
return $this->success;
|
||||||
|
@ -10,7 +10,7 @@ if (is_file($autoLoad)) {
|
|||||||
require_once $autoLoad;
|
require_once $autoLoad;
|
||||||
}
|
}
|
||||||
|
|
||||||
const WEBBASE_VERSION = "2.4.1";
|
const WEBBASE_VERSION = "2.4.2";
|
||||||
|
|
||||||
spl_autoload_extensions(".php");
|
spl_autoload_extensions(".php");
|
||||||
spl_autoload_register(function ($class) {
|
spl_autoload_register(function ($class) {
|
||||||
|
@ -88,7 +88,7 @@ export default function AdminDashboard(props) {
|
|||||||
<Route path={"/admin/logs"} element={<LogView {...controlObj} />}/>
|
<Route path={"/admin/logs"} element={<LogView {...controlObj} />}/>
|
||||||
<Route path={"/admin/permissions"} element={<AccessControlList {...controlObj} />}/>
|
<Route path={"/admin/permissions"} element={<AccessControlList {...controlObj} />}/>
|
||||||
<Route path={"/admin/routes"} element={<RouteListView {...controlObj} />}/>
|
<Route path={"/admin/routes"} element={<RouteListView {...controlObj} />}/>
|
||||||
<Route path={"/admin/routes/:routeId"} element={<RouteEditView {...controlObj} />}/>
|
<Route path={"/admin/route/:routeId"} element={<RouteEditView {...controlObj} />}/>
|
||||||
<Route path={"/admin/settings"} element={<SettingsView {...controlObj} />}/>
|
<Route path={"/admin/settings"} element={<SettingsView {...controlObj} />}/>
|
||||||
<Route path={"/admin/profile"} element={<ProfileView {...controlObj} />}/>
|
<Route path={"/admin/profile"} element={<ProfileView {...controlObj} />}/>
|
||||||
<Route path={"*"} element={<View404 />} />
|
<Route path={"*"} element={<View404 />} />
|
||||||
|
@ -15,6 +15,6 @@ export default function ProfileLink(props) {
|
|||||||
|
|
||||||
return <Box display={"grid"} sx={newSx} gridTemplateColumns={size + "px auto"} alignItems={"center"} {...other}>
|
return <Box display={"grid"} sx={newSx} gridTemplateColumns={size + "px auto"} alignItems={"center"} {...other}>
|
||||||
<ProfilePicture user={user} size={size} />
|
<ProfilePicture user={user} size={size} />
|
||||||
{text ? text : (user.fullName || user.name)}
|
{typeof text === "string" ? text : (user.fullName || user.name)}
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
@ -23,8 +23,7 @@ const DrawerHeader = styled('div')(({ theme }) => ({
|
|||||||
padding: theme.spacing(0, 1),
|
padding: theme.spacing(0, 1),
|
||||||
...theme.mixins.toolbar,
|
...theme.mixins.toolbar,
|
||||||
"& > button": {
|
"& > button": {
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
marginLeft: "auto",
|
|
||||||
},
|
},
|
||||||
"& > img": {
|
"& > img": {
|
||||||
width: 30,
|
width: 30,
|
||||||
@ -122,22 +121,25 @@ export default function Sidebar(props) {
|
|||||||
onFetchLanguages();
|
onFetchLanguages();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const menuItems = {
|
const menuItems= {
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"name": "admin.dashboard",
|
"name": "admin.dashboard",
|
||||||
"icon": <QueryStats />
|
"icon": <QueryStats />
|
||||||
},
|
},
|
||||||
"users": {
|
"users": {
|
||||||
"name": "admin.users",
|
"name": "admin.users",
|
||||||
"icon": <People />
|
"icon": <People />,
|
||||||
|
"match": /\/admin\/(users|user\/.*)/
|
||||||
},
|
},
|
||||||
"groups": {
|
"groups": {
|
||||||
"name": "admin.groups",
|
"name": "admin.groups",
|
||||||
"icon": <Groups />
|
"icon": <Groups />,
|
||||||
|
"match": /\/admin\/(groups|group\/.*)/
|
||||||
},
|
},
|
||||||
"routes": {
|
"routes": {
|
||||||
"name": "admin.page_routes",
|
"name": "admin.page_routes",
|
||||||
"icon": <Route />
|
"icon": <Route />,
|
||||||
|
"match": /\/admin\/(routes|route\/.*)/
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"name": "admin.settings",
|
"name": "admin.settings",
|
||||||
@ -172,8 +174,15 @@ export default function Sidebar(props) {
|
|||||||
|
|
||||||
let li = [];
|
let li = [];
|
||||||
for (const [id, menuItem] of Object.entries(menuItems)) {
|
for (const [id, menuItem] of Object.entries(menuItems)) {
|
||||||
|
|
||||||
|
let active;
|
||||||
|
if (menuItem.hasOwnProperty("match")) {
|
||||||
|
active = !!menuItem.match.exec(currentPath);
|
||||||
|
} else {
|
||||||
const match= /^\/admin\/(.*)$/.exec(currentPath);
|
const match= /^\/admin\/(.*)$/.exec(currentPath);
|
||||||
const active = match?.length >= 2 && match[1] === id;
|
active = match?.length >= 2 && match[1] === id;
|
||||||
|
}
|
||||||
|
|
||||||
li.push(<NavbarItem key={id} {...menuItem} active={active} onClick={() => navigate(`/admin/${id}`)} />);
|
li.push(<NavbarItem key={id} {...menuItem} active={active} onClick={() => navigate(`/admin/${id}`)} />);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,14 +197,16 @@ export default function Sidebar(props) {
|
|||||||
<img src={"/img/icons/logo.png"} alt={"Logo"} />
|
<img src={"/img/icons/logo.png"} alt={"Logo"} />
|
||||||
<span>WebBase</span>
|
<span>WebBase</span>
|
||||||
</>}
|
</>}
|
||||||
<IconButton onClick={() => setDrawerOpen(!drawerOpen)}>
|
<IconButton sx={{marginLeft: drawerOpen ? "auto" : 0}} onClick={() => setDrawerOpen(!drawerOpen)}>
|
||||||
{drawerOpen ? <ChevronLeftIcon/> : <ChevronRightIcon/>}
|
{drawerOpen ? <ChevronLeftIcon/> : <ChevronRightIcon/>}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</DrawerHeader>
|
</DrawerHeader>
|
||||||
<Divider/>
|
<Divider/>
|
||||||
<ListItem sx={{display: 'block'}}>
|
<ListItem sx={{display: 'block'}}>
|
||||||
<Box sx={{opacity: drawerOpen ? 1 : 0}}>{L("account.logged_in_as")}:</Box>
|
<Box sx={{opacity: drawerOpen ? 1 : 0}}>{L("account.logged_in_as")}:</Box>
|
||||||
<ProfileLink user={api.user} size={30} sx={{marginTop: 1, gridGap: 16, fontWeight: "bold" }}
|
<ProfileLink text={drawerOpen ? null : ""}
|
||||||
|
user={api.user} size={30}
|
||||||
|
sx={{marginTop: 1, gridGap: 16, fontWeight: "bold" }}
|
||||||
onClick={() => navigate("/admin/profile")} />
|
onClick={() => navigate("/admin/profile")} />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<Divider/>
|
<Divider/>
|
||||||
@ -216,7 +227,6 @@ export default function Sidebar(props) {
|
|||||||
: <ListItemButton sx={{
|
: <ListItemButton sx={{
|
||||||
minHeight: 48,
|
minHeight: 48,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
px: 2.5,
|
|
||||||
}}>
|
}}>
|
||||||
<Dropdown>
|
<Dropdown>
|
||||||
<ListItemIcon onClick={e => setAnchorEl(e.currentTarget)} sx={{
|
<ListItemIcon onClick={e => setAnchorEl(e.currentTarget)} sx={{
|
||||||
|
@ -3,7 +3,7 @@ import {LocaleContext} from "shared/locale";
|
|||||||
import {Link} from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import usePagination from "shared/hooks/pagination";
|
import usePagination from "shared/hooks/pagination";
|
||||||
import {DataColumn, DataTable, DateTimeColumn, NumericColumn, StringColumn} from "shared/elements/data-table";
|
import {DataColumn, DataTable, DateTimeColumn, NumericColumn, StringColumn} from "shared/elements/data-table";
|
||||||
import {Box, FormControl, FormGroup, FormLabel, Grid, IconButton, MenuItem, TextField} from "@mui/material";
|
import {Box, FormControl, FormGroup, FormLabel, Grid, IconButton, MenuItem, styled, TextField} from "@mui/material";
|
||||||
import {DateTimePicker} from "@mui/x-date-pickers";
|
import {DateTimePicker} from "@mui/x-date-pickers";
|
||||||
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
|
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
|
||||||
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
|
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
|
||||||
@ -12,12 +12,19 @@ import {format, toDate} from "date-fns";
|
|||||||
import {ExpandLess, ExpandMore} from "@mui/icons-material";
|
import {ExpandLess, ExpandMore} from "@mui/icons-material";
|
||||||
import ViewContent from "../elements/view-content";
|
import ViewContent from "../elements/view-content";
|
||||||
|
|
||||||
|
const StyledLogMessage = styled(Box)(props => ({
|
||||||
|
alignSelf: "center",
|
||||||
|
"& pre": {
|
||||||
|
whiteSpace: "break-spaces"
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
export default function LogView(props) {
|
export default function LogView(props) {
|
||||||
|
|
||||||
// meta
|
// meta
|
||||||
const api = props.api;
|
const api = props.api;
|
||||||
const showDialog = props.showDialog;
|
const showDialog = props.showDialog;
|
||||||
const {translate: L, requestModules, currentLocale} = useContext(LocaleContext);
|
const {translate: L, requestModules, currentLocale, toDateFns} = useContext(LocaleContext);
|
||||||
const pagination = usePagination();
|
const pagination = usePagination();
|
||||||
|
|
||||||
// data
|
// data
|
||||||
@ -89,11 +96,11 @@ export default function LogView(props) {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
}
|
}
|
||||||
</Box>
|
</Box>
|
||||||
<Box alignSelf={"center"}>
|
<StyledLogMessage>
|
||||||
<pre>
|
<pre>
|
||||||
{entry.showDetails ? entry.message : lines[0]}
|
{entry.showDetails ? entry.message : lines[0]}
|
||||||
</pre>
|
</pre>
|
||||||
</Box>
|
</StyledLogMessage>
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
return column;
|
return column;
|
||||||
@ -130,7 +137,7 @@ export default function LogView(props) {
|
|||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>{L("logs.timestamp")}</FormLabel>
|
<FormLabel>{L("logs.timestamp")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<LocalizationProvider dateAdapter={AdapterDateFns}>
|
<LocalizationProvider dateAdapter={AdapterDateFns} adapterLocale={toDateFns()}>
|
||||||
<DateTimePicker label={L("logs.timestamp_placeholder") + "…"}
|
<DateTimePicker label={L("logs.timestamp_placeholder") + "…"}
|
||||||
value={timestamp ? toDate(new Date()) : null}
|
value={timestamp ? toDate(new Date()) : null}
|
||||||
format={L("general.datefns_datetime_format_precise")}
|
format={L("general.datefns_datetime_format_precise")}
|
||||||
|
@ -88,7 +88,7 @@ export default function RouteEditView(props) {
|
|||||||
api.addRoute(...args).then(res => {
|
api.addRoute(...args).then(res => {
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
navigate("/admin/routes/" + res.routeId);
|
navigate("/admin/route/" + res.routeId);
|
||||||
} else {
|
} else {
|
||||||
showDialog(res.msg, L("routes.save_route_error"));
|
showDialog(res.msg, L("routes.save_route_error"));
|
||||||
}
|
}
|
||||||
|
@ -131,7 +131,7 @@ export default function RouteListView(props) {
|
|||||||
</Button>
|
</Button>
|
||||||
<Button variant={"outlined"} color={"success"} startIcon={<Add />} size={"small"}
|
<Button variant={"outlined"} color={"success"} startIcon={<Add />} size={"small"}
|
||||||
disabled={!props.api.hasPermission("routes/add")}
|
disabled={!props.api.hasPermission("routes/add")}
|
||||||
onClick={() => navigate("/admin/routes/new")} >
|
onClick={() => navigate("/admin/route/new")} >
|
||||||
{L("general.add")}
|
{L("general.add")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant={"outlined"} color={"info"} startIcon={<Cached />} size={"small"}
|
<Button variant={"outlined"} color={"info"} startIcon={<Cached />} size={"small"}
|
||||||
@ -173,7 +173,7 @@ export default function RouteListView(props) {
|
|||||||
<IconButton size={"small"} title={L("general.edit")}
|
<IconButton size={"small"} title={L("general.edit")}
|
||||||
disabled={!api.hasPermission("routes/add")}
|
disabled={!api.hasPermission("routes/add")}
|
||||||
color={"primary"}
|
color={"primary"}
|
||||||
onClick={() => navigate("/admin/routes/" + id)}>
|
onClick={() => navigate("/admin/route/" + id)}>
|
||||||
<Edit />
|
<Edit />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton size={"small"} title={L("general.delete")}
|
<IconButton size={"small"} title={L("general.delete")}
|
||||||
|
@ -82,7 +82,8 @@ export default function GpgKeyInput(props) {
|
|||||||
}, [showDialog]);
|
}, [showDialog]);
|
||||||
|
|
||||||
return <StyledGpgKeyInput {...other}>
|
return <StyledGpgKeyInput {...other}>
|
||||||
<IconButton onClick={onOpenDialog}>
|
<IconButton onClick={onOpenDialog}
|
||||||
|
disabled={!api.hasPermission(isConfigured ? "settings/removeGPG" : "settings/importGPG")}>
|
||||||
{ isConfigured ? <Delete color={"error"} /> : <Upload color={"success"} /> }
|
{ isConfigured ? <Delete color={"error"} /> : <Upload color={"success"} /> }
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<VisuallyHiddenInput ref={fileInputRef} type={"file"} onChange={e => {
|
<VisuallyHiddenInput ref={fileInputRef} type={"file"} onChange={e => {
|
||||||
|
@ -73,6 +73,7 @@ export default function UserListView(props) {
|
|||||||
new StringColumn(L("account.email"), "email"),
|
new StringColumn(L("account.email"), "email"),
|
||||||
groupColumn,
|
groupColumn,
|
||||||
new DateTimeColumn(L("account.registered_at"), "registeredAt"),
|
new DateTimeColumn(L("account.registered_at"), "registeredAt"),
|
||||||
|
new DateTimeColumn(L("account.last_online"), "lastOnline"),
|
||||||
new BoolColumn(L("account.active"), "active", { align: "center" }),
|
new BoolColumn(L("account.active"), "active", { align: "center" }),
|
||||||
new BoolColumn(L("account.confirmed"), "confirmed", { align: "center" }),
|
new BoolColumn(L("account.confirmed"), "confirmed", { align: "center" }),
|
||||||
new ControlsColumn(L("general.controls"), [
|
new ControlsColumn(L("general.controls"), [
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.pagination-controls {
|
.pagination-controls {
|
||||||
margin-top: 6px;
|
margin-top: 12px;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 75px auto;
|
grid-template-columns: 75px auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
Loading…
Reference in New Issue
Block a user