v2.4.2: frontend enhancements, user/get fix

This commit is contained in:
Roman 2024-05-13 20:32:31 +02:00
parent 150e4eb195
commit df4582c7e5
11 changed files with 47 additions and 31 deletions

@ -237,7 +237,7 @@ namespace Core\API\User {
public function __construct(Context $context, $externalCall = false) {
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')
);
}
@ -316,20 +316,17 @@ namespace Core\API\User {
} else if ($user === null) {
return $this->createError("User not found");
} else {
$queriedUser = $user->jsonSerialize();
// allow access to unconfirmed users only when we have administrative privileges, or we are querying ourselves
$currentUser = $this->context->getUser();
// full info only when we have administrative privileges, or we are querying ourselves
$fullInfo = ($userId === $currentUser->getId() ||
$currentUser->hasGroup(Group::ADMIN) ||
$currentUser->hasGroup(Group::SUPPORT));
if (!$fullInfo && !$queriedUser["confirmed"]) {
if (!$fullInfo && !$user->isConfirmed()) {
return $this->createError("No permissions to access this user");
}
$this->result["user"] = $queriedUser;
$this->result["user"] = $user->jsonSerialize();
}
return $this->success;

@ -10,7 +10,7 @@ if (is_file($autoLoad)) {
require_once $autoLoad;
}
const WEBBASE_VERSION = "2.4.1";
const WEBBASE_VERSION = "2.4.2";
spl_autoload_extensions(".php");
spl_autoload_register(function ($class) {

@ -88,7 +88,7 @@ export default function AdminDashboard(props) {
<Route path={"/admin/logs"} element={<LogView {...controlObj} />}/>
<Route path={"/admin/permissions"} element={<AccessControlList {...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/profile"} element={<ProfileView {...controlObj} />}/>
<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}>
<ProfilePicture user={user} size={size} />
{text ? text : (user.fullName || user.name)}
{typeof text === "string" ? text : (user.fullName || user.name)}
</Box>
}

@ -23,8 +23,7 @@ const DrawerHeader = styled('div')(({ theme }) => ({
padding: theme.spacing(0, 1),
...theme.mixins.toolbar,
"& > button": {
display: 'flex',
marginLeft: "auto",
display: "flex",
},
"& > img": {
width: 30,
@ -122,22 +121,25 @@ export default function Sidebar(props) {
onFetchLanguages();
}, []);
const menuItems = {
const menuItems= {
"dashboard": {
"name": "admin.dashboard",
"icon": <QueryStats />
},
"users": {
"name": "admin.users",
"icon": <People />
"icon": <People />,
"match": /\/admin\/(users|user\/.*)/
},
"groups": {
"name": "admin.groups",
"icon": <Groups />
"icon": <Groups />,
"match": /\/admin\/(groups|group\/.*)/
},
"routes": {
"name": "admin.page_routes",
"icon": <Route />
"icon": <Route />,
"match": /\/admin\/(routes|route\/.*)/
},
"settings": {
"name": "admin.settings",
@ -172,8 +174,15 @@ export default function Sidebar(props) {
let li = [];
for (const [id, menuItem] of Object.entries(menuItems)) {
const match= /^\/admin\/(.*)$/.exec(currentPath);
const active = match?.length >= 2 && match[1] === id;
let active;
if (menuItem.hasOwnProperty("match")) {
active = !!menuItem.match.exec(currentPath);
} else {
const match= /^\/admin\/(.*)$/.exec(currentPath);
active = match?.length >= 2 && match[1] === 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"} />
<span>WebBase</span>
</>}
<IconButton onClick={() => setDrawerOpen(!drawerOpen)}>
<IconButton sx={{marginLeft: drawerOpen ? "auto" : 0}} onClick={() => setDrawerOpen(!drawerOpen)}>
{drawerOpen ? <ChevronLeftIcon/> : <ChevronRightIcon/>}
</IconButton>
</DrawerHeader>
<Divider/>
<ListItem sx={{display: 'block'}}>
<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")} />
</ListItem>
<Divider/>
@ -216,7 +227,6 @@ export default function Sidebar(props) {
: <ListItemButton sx={{
minHeight: 48,
justifyContent: 'center',
px: 2.5,
}}>
<Dropdown>
<ListItemIcon onClick={e => setAnchorEl(e.currentTarget)} sx={{

@ -3,7 +3,7 @@ import {LocaleContext} from "shared/locale";
import {Link} from "react-router-dom";
import usePagination from "shared/hooks/pagination";
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 { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
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 ViewContent from "../elements/view-content";
const StyledLogMessage = styled(Box)(props => ({
alignSelf: "center",
"& pre": {
whiteSpace: "break-spaces"
}
}));
export default function LogView(props) {
// meta
const api = props.api;
const showDialog = props.showDialog;
const {translate: L, requestModules, currentLocale} = useContext(LocaleContext);
const {translate: L, requestModules, currentLocale, toDateFns} = useContext(LocaleContext);
const pagination = usePagination();
// data
@ -89,11 +96,11 @@ export default function LogView(props) {
</IconButton>
}
</Box>
<Box alignSelf={"center"}>
<StyledLogMessage>
<pre>
{entry.showDetails ? entry.message : lines[0]}
</pre>
</Box>
</StyledLogMessage>
</Box>
}
return column;
@ -130,7 +137,7 @@ export default function LogView(props) {
<FormGroup>
<FormLabel>{L("logs.timestamp")}</FormLabel>
<FormControl>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<LocalizationProvider dateAdapter={AdapterDateFns} adapterLocale={toDateFns()}>
<DateTimePicker label={L("logs.timestamp_placeholder") + "…"}
value={timestamp ? toDate(new Date()) : null}
format={L("general.datefns_datetime_format_precise")}

@ -88,7 +88,7 @@ export default function RouteEditView(props) {
api.addRoute(...args).then(res => {
setSaving(false);
if (res.success) {
navigate("/admin/routes/" + res.routeId);
navigate("/admin/route/" + res.routeId);
} else {
showDialog(res.msg, L("routes.save_route_error"));
}

@ -131,7 +131,7 @@ export default function RouteListView(props) {
</Button>
<Button variant={"outlined"} color={"success"} startIcon={<Add />} size={"small"}
disabled={!props.api.hasPermission("routes/add")}
onClick={() => navigate("/admin/routes/new")} >
onClick={() => navigate("/admin/route/new")} >
{L("general.add")}
</Button>
<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")}
disabled={!api.hasPermission("routes/add")}
color={"primary"}
onClick={() => navigate("/admin/routes/" + id)}>
onClick={() => navigate("/admin/route/" + id)}>
<Edit />
</IconButton>
<IconButton size={"small"} title={L("general.delete")}

@ -82,7 +82,8 @@ export default function GpgKeyInput(props) {
}, [showDialog]);
return <StyledGpgKeyInput {...other}>
<IconButton onClick={onOpenDialog}>
<IconButton onClick={onOpenDialog}
disabled={!api.hasPermission(isConfigured ? "settings/removeGPG" : "settings/importGPG")}>
{ isConfigured ? <Delete color={"error"} /> : <Upload color={"success"} /> }
</IconButton>
<VisuallyHiddenInput ref={fileInputRef} type={"file"} onChange={e => {

@ -73,6 +73,7 @@ export default function UserListView(props) {
new StringColumn(L("account.email"), "email"),
groupColumn,
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.confirmed"), "confirmed", { align: "center" }),
new ControlsColumn(L("general.controls"), [

@ -32,7 +32,7 @@
}
.pagination-controls {
margin-top: 6px;
margin-top: 12px;
display: grid;
grid-template-columns: 75px auto;
align-items: center;