frontend fixes
This commit is contained in:
parent
755da257f8
commit
9fc0a19f59
16
Core/Localization/de_DE/logs.php
Normal file
16
Core/Localization/de_DE/logs.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
"title" => "Systemlog",
|
||||
"severity" => "Schweregrad",
|
||||
"timestamp" => "Zeitpunkt",
|
||||
"module" => "Modul",
|
||||
"message" => "Nachricht",
|
||||
"search" => "Suche",
|
||||
"search_query" => "Suchanfrage",
|
||||
"no_entries_placeholder" => "Keine Log-Einträge zum Anzeigen",
|
||||
"timestamp_placeholder" => "Datum und Zeitpunk Auswählen zum Filtern",
|
||||
|
||||
// dialog
|
||||
"fetch_log_error" => "Fehler beim Holen der Log-Einträge",
|
||||
];
|
@ -3,6 +3,7 @@
|
||||
return [
|
||||
"title" => "Berechtigungen",
|
||||
"title_short" => "ACL",
|
||||
"search" => "Suche",
|
||||
"query" => "Suchanfrage",
|
||||
"add_permission" => "Berechtigung hinzufügen",
|
||||
"permission" => "Berechtigung",
|
||||
|
16
Core/Localization/en_US/logs.php
Normal file
16
Core/Localization/en_US/logs.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
"title" => "System Log",
|
||||
"severity" => "Severity",
|
||||
"timestamp" => "Timestamp",
|
||||
"module" => "Module",
|
||||
"message" => "Message",
|
||||
"search" => "Search",
|
||||
"search_query" => "Search query",
|
||||
"no_entries_placeholder" => "No log entries to display",
|
||||
"timestamp_placeholder" => "Select date and time to filter",
|
||||
|
||||
// dialog
|
||||
"fetch_log_error" => "Error fetching log entries",
|
||||
];
|
@ -3,6 +3,7 @@
|
||||
return [
|
||||
"title" => "Permissions",
|
||||
"title_short" => "ACL",
|
||||
"search" => "Search",
|
||||
"query" => "Search query",
|
||||
"add_permission" => "Add permission",
|
||||
"permission" => "Permission",
|
||||
|
@ -9,5 +9,5 @@
|
||||
<noscript>{{ L("general.noscript") }}</noscript>
|
||||
<div type="module" id="admin-panel"></div>
|
||||
<script src="/react/dist/admin-panel/index.js" nonce="{{ site.csp.nonce }}"></script>
|
||||
<link rel="stylesheet" href="/react/dist/admin-panel/index.css" nonce="{{ site.csp.nonce }}"></link>
|
||||
<link rel="stylesheet" href="/react/dist/admin-panel/index.css" nonce="{{ site.csp.nonce }}" />
|
||||
{% endblock %}
|
||||
|
@ -37,8 +37,8 @@ export default function AdminDashboard(props) {
|
||||
|
||||
const showDialog = useCallback((message, title, options=["Close"], onOption = null) => {
|
||||
setDialog({
|
||||
show: true, message:
|
||||
message,
|
||||
show: true,
|
||||
message: message,
|
||||
title: title,
|
||||
options: options,
|
||||
onOption: onOption,
|
||||
@ -80,27 +80,12 @@ export default function AdminDashboard(props) {
|
||||
<Route path={"/admin/groups"} element={<GroupListView {...controlObj} />}/>
|
||||
<Route path={"/admin/group/:groupId"} element={<EditGroupView {...controlObj} />}/>
|
||||
<Route path={"/admin/logs"} element={<LogView {...controlObj} />}/>
|
||||
<Route path={"/admin/acl"} element={<AccessControlList {...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={"*"} element={<View404 />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
{/*<Route exact={true} path={"/admin/users"}><UserOverview {...this.controlObj} /></Route>
|
||||
<Route path={"/admin/user/add"}><CreateUser {...this.controlObj} /></Route>
|
||||
<Route path={"/admin/user/edit/:userId"} render={(props) => {
|
||||
let newProps = {...props, ...this.controlObj};
|
||||
return <EditUser {...newProps} />
|
||||
}}/>
|
||||
<Route path={"/admin/user/permissions"}><PermissionSettings {...this.controlObj}/></Route>
|
||||
<Route path={"/admin/group/add"}><CreateGroup {...this.controlObj} /></Route>
|
||||
<Route exact={true} path={"/admin/contact/"}><ContactRequestOverview {...this.controlObj} /></Route>
|
||||
<Route path={"/admin/visitors"}><Visitors {...this.controlObj} /></Route>
|
||||
<Route path={"/admin/logs"}><Logs {...this.controlObj} notifications={this.state.notifications} /></Route>
|
||||
<Route path={"/admin/settings"}><Settings {...this.controlObj} /></Route>
|
||||
<Route path={"/admin/pages"}><PageOverview {...this.controlObj} /></Route>
|
||||
<Route path={"/admin/help"}><HelpPage {...this.controlObj} /></Route>
|
||||
<Route path={"*"}><View404 /></Route>*/}
|
||||
<Dialog {...dialog}/>
|
||||
</section>
|
||||
</div>
|
||||
|
@ -44,7 +44,7 @@ export default function Sidebar(props) {
|
||||
"name": "admin.settings",
|
||||
"icon": "tools"
|
||||
},
|
||||
"acl": {
|
||||
"permissions": {
|
||||
"name": "admin.acl",
|
||||
"icon": "door-open"
|
||||
},
|
||||
|
@ -12,7 +12,7 @@ import {
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
IconButton, styled
|
||||
IconButton, styled, FormGroup, FormLabel, FormControl, Box
|
||||
} from "@material-ui/core";
|
||||
import {Add, Delete, Edit, Refresh} from "@material-ui/icons";
|
||||
import {USER_GROUP_ADMIN} from "shared/constants";
|
||||
@ -224,39 +224,40 @@ export default function AccessControlList(props) {
|
||||
</div>
|
||||
</div>
|
||||
<div className={"row"}>
|
||||
<div className={"col-6"}>
|
||||
<div className={"form-group"}>
|
||||
<label>{L("permissions.query")}</label>
|
||||
<FormGroup className={"col-6"}>
|
||||
<FormLabel>{L("permissions.search")}</FormLabel>
|
||||
<TextField
|
||||
className={"form-control"}
|
||||
placeholder={L("permissions.query") + "…"}
|
||||
value={query}
|
||||
onChange={e => setQuery(e.target.value)}
|
||||
variant={"outlined"}
|
||||
size={"small"} />
|
||||
</div>
|
||||
</div>
|
||||
</FormGroup>
|
||||
<div className={"col-6 text-right"}>
|
||||
<label>{L("general.controls")}</label>
|
||||
<div className={"form-group"}>
|
||||
<Button variant={"outlined"} color={"primary"} className={"m-1"} startIcon={<Refresh />} onClick={() => onFetchACL(true)}>
|
||||
<Box>
|
||||
<FormLabel>{L("general.controls")}</FormLabel>
|
||||
</Box>
|
||||
<Box mb={2}>
|
||||
<Button variant={"outlined"} color={"primary"} className={"mr-1"}
|
||||
startIcon={<Refresh />} onClick={() => onFetchACL(true)}>
|
||||
{L("general.reload")}
|
||||
</Button>
|
||||
<Button variant={"outlined"} className={"m-1"} startIcon={<Add />} disabled={!props.api.hasGroup(USER_GROUP_ADMIN)}
|
||||
<Button variant={"outlined"} startIcon={<Add />}
|
||||
disabled={!props.api.hasGroup(USER_GROUP_ADMIN)}
|
||||
onClick={() => setDialogData({
|
||||
open: true,
|
||||
title: L("permissions.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") }
|
||||
{ type: "label", value: L("permissions.method") + ":" },
|
||||
{ type: "text", name: "method", value: "", placeholder: L("permissions.method") },
|
||||
{ type: "label", value: L("permissions.description") + ":" },
|
||||
{ type: "text", name: "description", maxLength: 128, placeholder: L("permissions.description") }
|
||||
],
|
||||
onOption: (option, inputData) => option === 0 && onUpdatePermission(inputData, [])
|
||||
})} >
|
||||
{L("general.add")}
|
||||
</Button>
|
||||
</div>
|
||||
</Box>
|
||||
</div>
|
||||
</div>
|
||||
<TableContainer component={Paper} style={{overflowX: "initial"}}>
|
||||
|
@ -62,7 +62,6 @@ export default function GroupListView(props) {
|
||||
<div className={"container-fluid"}>
|
||||
<div className={"row mb-2"}>
|
||||
<div className={"col-sm-6"}>
|
||||
<h1 className={"m-0 text-dark"}>Users</h1>
|
||||
</div>
|
||||
<div className={"col-sm-6"}>
|
||||
<ol className={"breadcrumb float-sm-right"}>
|
||||
|
4
react/admin-panel/src/views/group/index.js
Normal file
4
react/admin-panel/src/views/group/index.js
Normal file
@ -0,0 +1,4 @@
|
||||
import EditGroupView from "./group-edit";
|
||||
import GroupListView from "./group-list";
|
||||
|
||||
export default { EditGroupView, GroupListView };
|
@ -4,12 +4,12 @@ import {Link} from "react-router-dom";
|
||||
import usePagination from "shared/hooks/pagination";
|
||||
import {DataColumn, DataTable, DateTimeColumn, NumericColumn, StringColumn} from "shared/elements/data-table";
|
||||
import {TextField} from "@mui/material";
|
||||
import {DesktopDateTimePicker} from "@mui/x-date-pickers";
|
||||
import {DateTimePicker} from "@mui/x-date-pickers";
|
||||
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
|
||||
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
|
||||
import {API_DATETIME_FORMAT} from "shared/constants";
|
||||
import {format, toDate} from "date-fns";
|
||||
import {Select} from "@material-ui/core";
|
||||
import {FormControl, FormGroup, FormLabel, MenuItem, Select} from "@material-ui/core";
|
||||
|
||||
export default function LogView(props) {
|
||||
|
||||
@ -30,7 +30,7 @@ export default function LogView(props) {
|
||||
const [forceReload, setForceReload] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
requestModules(props.api, ["general"], currentLocale).then(data => {
|
||||
requestModules(props.api, ["general", "logs"], currentLocale).then(data => {
|
||||
if (!data.success) {
|
||||
props.showDialog("Error fetching translations: " + data.msg);
|
||||
}
|
||||
@ -38,16 +38,22 @@ export default function LogView(props) {
|
||||
}, [currentLocale]);
|
||||
|
||||
const onFetchLogs = useCallback((page, count, orderBy, sortOrder) => {
|
||||
let apiTimeStamp = null;
|
||||
try {
|
||||
if (timestamp) {
|
||||
apiTimeStamp = format(timestamp, API_DATETIME_FORMAT);
|
||||
}
|
||||
} catch (e) {
|
||||
apiTimeStamp = null;
|
||||
}
|
||||
|
||||
api.fetchLogEntries(page, count, orderBy, sortOrder,
|
||||
LOG_LEVELS[logLevel],
|
||||
timestamp ? format(timestamp, API_DATETIME_FORMAT) : null,
|
||||
query
|
||||
).then((res) => {
|
||||
LOG_LEVELS[logLevel], apiTimeStamp, query).then((res) => {
|
||||
if (res.success) {
|
||||
setLogEntries(res.logs);
|
||||
pagination.update(res.pagination);
|
||||
} else {
|
||||
showDialog(res.msg, "Error fetching log entries");
|
||||
showDialog(res.msg, L("logs.fetch_logs_error"));
|
||||
return null;
|
||||
}
|
||||
});
|
||||
@ -59,7 +65,7 @@ export default function LogView(props) {
|
||||
}, [query, timestamp, logLevel]);
|
||||
|
||||
const messageColumn = (() => {
|
||||
let column = new DataColumn(L("message"), "message");
|
||||
let column = new DataColumn(L("logs.message"), "message");
|
||||
column.sortable = false;
|
||||
column.renderData = (L, entry) => {
|
||||
return <pre>{entry.message}</pre>
|
||||
@ -69,9 +75,9 @@ export default function LogView(props) {
|
||||
|
||||
const columnDefinitions = [
|
||||
new NumericColumn(L("general.id"), "id"),
|
||||
new StringColumn(L("module"), "module"),
|
||||
new StringColumn(L("severity"), "severity"),
|
||||
new DateTimeColumn(L("timestamp"), "timestamp", { precise: true }),
|
||||
new StringColumn(L("logs.module"), "module"),
|
||||
new StringColumn(L("logs.severity"), "severity"),
|
||||
new DateTimeColumn(L("logs.timestamp"), "timestamp", { precise: true }),
|
||||
messageColumn,
|
||||
];
|
||||
|
||||
@ -80,54 +86,55 @@ export default function LogView(props) {
|
||||
<div className={"container-fluid"}>
|
||||
<div className={"row mb-2"}>
|
||||
<div className={"col-sm-6"}>
|
||||
<h1 className={"m-0 text-dark"}>System Log</h1>
|
||||
<h1 className={"m-0 text-dark"}>{L("logs.title")}</h1>
|
||||
</div>
|
||||
<div className={"col-sm-6"}>
|
||||
<ol className={"breadcrumb float-sm-right"}>
|
||||
<li className={"breadcrumb-item"}><Link to={"/admin/dashboard"}>Home</Link></li>
|
||||
<li className="breadcrumb-item active">System Log</li>
|
||||
<li className="breadcrumb-item active">{L("logs.title")}</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={"content overflow-auto"}>
|
||||
<div className={"row p-2"}>
|
||||
<div className={"col-2"}>
|
||||
<div className={"form-group"}>
|
||||
<label>{L("log.severity")}</label>
|
||||
<Select native className={"form-control"} value={logLevel} onChange={e => setLogLevel(parseInt(e.target.value))}>
|
||||
<FormGroup className={"col-2"}>
|
||||
<FormLabel>{L("logs.severity")}</FormLabel>
|
||||
<FormControl>
|
||||
<TextField select variant={"outlined"} size={"small"} value={logLevel}
|
||||
onChange={e => setLogLevel(parseInt(e.target.value))}
|
||||
inputProps={{ size: "small" }}>
|
||||
{LOG_LEVELS.map((value, index) =>
|
||||
<option key={"option-" + value} value={index}>{value}</option>)
|
||||
<MenuItem key={"option-" + value} value={index}>{value}</MenuItem>)
|
||||
}
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div className={"col-4"}>
|
||||
<div className={"form-group"}>
|
||||
<label>{L("log.timestamp")}</label>
|
||||
</TextField>
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
<FormGroup className={"col-4"}>
|
||||
<FormLabel>{L("logs.timestamp")}</FormLabel>
|
||||
<FormControl>
|
||||
<LocalizationProvider dateAdapter={AdapterDateFns}>
|
||||
<DesktopDateTimePicker className={"form-control"}
|
||||
label={L("Select date time to filter...")}
|
||||
<DateTimePicker label={L("logs.timestamp_placeholder") + "…"}
|
||||
value={timestamp ? toDate(new Date()) : null}
|
||||
format={L("general.datefns_datetime_format_precise")}
|
||||
onChange={(newValue) => setTimestamp(newValue)}
|
||||
slotProps={{ textField: { } }}
|
||||
slotProps={{ textField: { size:'small' } }}
|
||||
sx={{"& .MuiInputBase-input": { height: "23px", padding: 1 }}}
|
||||
/>
|
||||
</LocalizationProvider>
|
||||
</div>
|
||||
</div>
|
||||
<div className={"col-6"}>
|
||||
<div className={"form-group"}>
|
||||
<label>{L("log.query")}</label>
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
<FormGroup className={"col-6"}>
|
||||
<FormLabel>{L("logs.search")}</FormLabel>
|
||||
<FormControl>
|
||||
<TextField
|
||||
className={"form-control"}
|
||||
placeholder={L("log.search_query") + "…"}
|
||||
placeholder={L("logs.search_query") + "…"}
|
||||
value={query}
|
||||
onChange={e => setQuery(e.target.value)}
|
||||
variant={"outlined"}
|
||||
size={"small"}/>
|
||||
</div>
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
</div>
|
||||
<div className={"container-fluid"}>
|
||||
<DataTable
|
||||
@ -138,7 +145,7 @@ export default function LogView(props) {
|
||||
forceReload={forceReload}
|
||||
defaultSortColumn={3}
|
||||
defaultSortOrder={"desc"}
|
||||
placeholder={"No log entries to display"}
|
||||
placeholder={L("logs.no_entries_placeholder")}
|
||||
columns={columnDefinitions} />
|
||||
</div>
|
||||
</div>
|
||||
|
4
react/admin-panel/src/views/route/index.js
Normal file
4
react/admin-panel/src/views/route/index.js
Normal file
@ -0,0 +1,4 @@
|
||||
import RouteEditView from "./route-edit";
|
||||
import RouteListView from "./route-list";
|
||||
|
||||
export default { RouteEditView, RouteListView };
|
@ -1,7 +1,8 @@
|
||||
import {Checkbox, FormControl, FormControlLabel, Select, styled, TextField} from "@material-ui/core";
|
||||
import {Box, Checkbox, FormControl, FormControlLabel, Select, styled, TextField} from "@material-ui/core";
|
||||
import * as React from "react";
|
||||
import {useCallback, useContext, useEffect, useRef} from "react";
|
||||
import {LocaleContext} from "shared/locale";
|
||||
import {CheckCircle, ErrorRounded} from "@material-ui/icons";
|
||||
|
||||
const RouteFormControl = styled(FormControl)((props) => ({
|
||||
"& > label": {
|
||||
@ -85,11 +86,12 @@ export default function RouteForm(props) {
|
||||
);
|
||||
|
||||
if (route.type === "dynamic") {
|
||||
let extraArgs, type;
|
||||
let extraArgs, type, isValid = false;
|
||||
try {
|
||||
extraArgs = JSON.parse(route.extra);
|
||||
type = typeof extraArgs;
|
||||
extraArgs = JSON.stringify(extraArgs, null, 2);
|
||||
isValid = type === "object";
|
||||
} catch (e) {
|
||||
extraArgs = null
|
||||
}
|
||||
@ -97,14 +99,16 @@ export default function RouteForm(props) {
|
||||
<RouteFormControl key={"form-control-extra"} fullWidth={true}>
|
||||
<label htmlFor={"route-extra"}>{L("routes.arguments")}</label>
|
||||
<textarea id={"route-extra"}
|
||||
ref={extraRef}
|
||||
ref={extraRef} style={!isValid ? {borderColor: "red"} : {}}
|
||||
value={extraArgs ?? route.extra}
|
||||
onChange={e => setRoute({...route, extra: minifyJson(e.target.value)})}/>
|
||||
<i>{
|
||||
<Box mt={1} fontStyle={"italic"} display={"grid"} gridTemplateColumns={"30px auto"}>{
|
||||
extraArgs === null ?
|
||||
L("routes.json_err") :
|
||||
(type !== "object" ? L("routes.json_not_obj") : L("routes.json_ok"))
|
||||
}</i>
|
||||
<><ErrorRounded color={"secondary"}/><span>{L("routes.json_err")}</span></> :
|
||||
(type !== "object" ?
|
||||
<><ErrorRounded color={"secondary"}/><span>{L("routes.json_not_object")}</span></> :
|
||||
<><CheckCircle color={"primary"} /><span>{L("routes.json_ok")}</span></>)
|
||||
}</Box>
|
||||
</RouteFormControl>
|
||||
);
|
||||
} else if (route.type === "static") {
|
||||
|
4
react/admin-panel/src/views/user/index.js
Normal file
4
react/admin-panel/src/views/user/index.js
Normal file
@ -0,0 +1,4 @@
|
||||
import UserEditView from "./user-edit";
|
||||
import UserListView from "./user-list";
|
||||
|
||||
export default { UserEditView, UserListView };
|
@ -1,5 +1,5 @@
|
||||
import React, {useState} from "react";
|
||||
import {Box, MenuItem, Select, Pagination as MuiPagination} from "@mui/material";
|
||||
import {Box, Select, Pagination as MuiPagination} from "@mui/material";
|
||||
import {sprintf} from "sprintf-js";
|
||||
import {FormControl} from "@material-ui/core";
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user