diff --git a/Core/API/Stats.class.php b/Core/API/Stats.class.php index 589941d..b514219 100644 --- a/Core/API/Stats.class.php +++ b/Core/API/Stats.class.php @@ -71,6 +71,18 @@ class Stats extends Request { return false; } + $req = new \Core\API\Logs\Get($this->context, false); + $success = $req->execute([ + "since" => (new \DateTime())->modify("-48 hours"), + "severity" => "error" + ]); + + if ($success) { + $errorCount = $req->getResult()["pagination"]["total"]; + } else { + $errorCount = "Unknown"; + } + $loadAvg = "Unknown"; if (function_exists("sys_getloadavg")) { $loadAvg = sys_getloadavg(); @@ -86,6 +98,7 @@ class Stats extends Request { "groupCount" => $groupCount, "visitors" => $visitorStatistics, "visitorsTotal" => $visitorCount, + "errorCount" => $errorCount, "server" => [ "version" => WEBBASE_VERSION, "server" => $_SERVER["SERVER_SOFTWARE"] ?? "Unknown", diff --git a/Core/Localization/de_DE/account.php b/Core/Localization/de_DE/account.php index 0286d18..601b887 100644 --- a/Core/Localization/de_DE/account.php +++ b/Core/Localization/de_DE/account.php @@ -69,6 +69,7 @@ return [ "logged_in_as" => "Eingeloggt als", "active" => "Aktiv", "group" => "Gruppe", + "no_members" => "Keine Mitglieder in dieser Gruppe", # dialogs "fetch_group_members_error" => "Fehler beim Holen der Gruppenmitglieder", diff --git a/Core/Localization/de_DE/admin.php b/Core/Localization/de_DE/admin.php index ea9bcd3..a150525 100644 --- a/Core/Localization/de_DE/admin.php +++ b/Core/Localization/de_DE/admin.php @@ -17,6 +17,7 @@ return [ "users_registered" => "Benutzer registriert", "available_groups" => "verfügbare Gruppen", "routes_defined" => "Routen definiert", + "error_count" => "Fehler in den letzten 48 Stunden", # Dialogs "fetch_stats_error" => "Fehler beim Holen der Stats", diff --git a/Core/Localization/de_DE/routes.php b/Core/Localization/de_DE/routes.php index aa127a9..110efec 100644 --- a/Core/Localization/de_DE/routes.php +++ b/Core/Localization/de_DE/routes.php @@ -33,6 +33,8 @@ return [ # dialogs "fetch_routes_error" => "Fehler beim Holen der Routen", + "fetch_route_error" => "Fehler beim Holen der Route", + "save_route_error" => "Fehler beim Speichern der Route", "enable_route_error" => "Fehler beim Aktivieren der Route", "disable_route_error" => "Fehler beim Deaktivieren der Route", "remove_route_error" => "Fehler beim Entfernen der Route", diff --git a/Core/Localization/en_US/account.php b/Core/Localization/en_US/account.php index 509b18c..8777a72 100644 --- a/Core/Localization/en_US/account.php +++ b/Core/Localization/en_US/account.php @@ -69,6 +69,7 @@ return [ "logged_in_as" => "Logged in as", "active" => "Active", "group" => "Group", + "no_members" => "No members in this group", # dialogs "fetch_group_members_error" => "Error fetching group members", diff --git a/Core/Localization/en_US/admin.php b/Core/Localization/en_US/admin.php index 14269fd..28e60e5 100644 --- a/Core/Localization/en_US/admin.php +++ b/Core/Localization/en_US/admin.php @@ -17,6 +17,7 @@ return [ "users_registered" => "Users registered", "available_groups" => "available Groups", "routes_defined" => "Routes defined", + "error_count" => "Errors in the past 48 hours", # Dialogs "fetch_stats_error" => "Error fetching stats", diff --git a/Core/Localization/en_US/routes.php b/Core/Localization/en_US/routes.php index 3fbf838..bce2ce7 100644 --- a/Core/Localization/en_US/routes.php +++ b/Core/Localization/en_US/routes.php @@ -33,6 +33,8 @@ return [ # dialogs "fetch_routes_error" => "Error fetching routes", + "fetch_route_error" => "Error fetching route", + "save_route_error" => "Error saving route", "enable_route_error" => "Error enabling route", "disable_route_error" => "Error disabling route", "remove_route_error" => "Error removing route", diff --git a/react/admin-panel/src/App.jsx b/react/admin-panel/src/App.jsx index 2190100..86c1f22 100644 --- a/react/admin-panel/src/App.jsx +++ b/react/admin-panel/src/App.jsx @@ -2,8 +2,8 @@ import React, {useCallback, useContext, useEffect, useMemo, useState} from 'reac import API from "shared/api"; import Icon from "shared/elements/icon"; import LoginForm from "shared/views/login"; -import {Alert} from "@material-ui/lab"; -import {Button} from "@material-ui/core"; +import {Alert} from "@mui/lab"; +import {Button} from "@mui/material"; import {LocaleContext} from "shared/locale"; import AdminDashboard from "./AdminDashboard"; diff --git a/react/admin-panel/src/elements/language-selection.js b/react/admin-panel/src/elements/language-selection.js index 0b86ae0..a3e20ca 100644 --- a/react/admin-panel/src/elements/language-selection.js +++ b/react/admin-panel/src/elements/language-selection.js @@ -1,8 +1,8 @@ import React, {useCallback, useContext, useState} from 'react'; -import {Box} from "@material-ui/core"; -import {makeStyles} from "@material-ui/core/styles"; +import {Box} from "@mui/material"; import {LocaleContext} from "shared/locale"; +/* const useStyles = makeStyles((theme) => ({ languageFlag: { margin: theme.spacing(0.2), @@ -10,11 +10,13 @@ const useStyles = makeStyles((theme) => ({ border: 0, } })); +*/ export default function LanguageSelection(props) { const api = props.api; - const classes = useStyles(); + // const classes = useStyles(); + const classes = {}; const [languages, setLanguages] = useState(null); const {translate: L, setLanguageByCode} = useContext(LocaleContext); diff --git a/react/admin-panel/src/views/access-control-list.js b/react/admin-panel/src/views/access-control-list.js index 78924d3..06eef97 100644 --- a/react/admin-panel/src/views/access-control-list.js +++ b/react/admin-panel/src/views/access-control-list.js @@ -12,9 +12,9 @@ import { TableContainer, TableHead, TableRow, - IconButton, styled, FormGroup, FormLabel, FormControl, Box -} from "@material-ui/core"; -import {Add, Delete, Edit, Refresh} from "@material-ui/icons"; + IconButton, styled, FormGroup, FormLabel, Box +} from "@mui/material"; +import {Add, Delete, Edit, Refresh} from "@mui/icons-material"; import {USER_GROUP_ADMIN} from "shared/constants"; import Dialog from "shared/elements/dialog"; @@ -238,11 +238,11 @@ export default function AccessControlList(props) { {L("general.controls")} - - : <> } diff --git a/react/admin-panel/src/views/group/group-list.js b/react/admin-panel/src/views/group/group-list.js index 71cd077..97fffde 100644 --- a/react/admin-panel/src/views/group/group-list.js +++ b/react/admin-panel/src/views/group/group-list.js @@ -1,10 +1,8 @@ import {Link, useNavigate} from "react-router-dom"; import {useCallback, useContext, useEffect, useState} from "react"; import {LocaleContext} from "shared/locale"; -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'; +import {ControlsColumn, DataTable, NumericColumn, StringColumn} from "shared/elements/data-table"; +import {Add, Edit} from "@mui/icons-material"; import usePagination from "shared/hooks/pagination"; @@ -44,7 +42,7 @@ export default function GroupListView(props) { new StringColumn(L("account.name"), "name"), 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}`) } + { label: L("general.edit"), element: Edit, onClick: (entry) => navigate(`/admin/group/${entry.id}`) } ]), ]; @@ -53,6 +51,7 @@ export default function GroupListView(props) {
+

{L("account.groups")}

    @@ -64,11 +63,6 @@ export default function GroupListView(props) {
- - - + columns={columnDefinitions} + buttons={[{ + key: "btn-create-group", + color: "primary", + startIcon: , + onClick: () => navigate("/admin/group/new"), + disabled: !api.hasPermission("groups/create"), + children: L("general.create_new") + }]}/>
diff --git a/react/admin-panel/src/views/group/index.js b/react/admin-panel/src/views/group/index.js deleted file mode 100644 index f9ac230..0000000 --- a/react/admin-panel/src/views/group/index.js +++ /dev/null @@ -1,4 +0,0 @@ -import EditGroupView from "./group-edit"; -import GroupListView from "./group-list"; - -export default { EditGroupView, GroupListView }; \ No newline at end of file diff --git a/react/admin-panel/src/views/log-view.js b/react/admin-panel/src/views/log-view.js index 0320956..186cbe7 100644 --- a/react/admin-panel/src/views/log-view.js +++ b/react/admin-panel/src/views/log-view.js @@ -3,14 +3,13 @@ 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 {TextField} from "@mui/material"; +import {Box, FormControl, FormGroup, FormLabel, IconButton, MenuItem, 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'; import {API_DATETIME_FORMAT} from "shared/constants"; import {format, toDate} from "date-fns"; -import {Box, FormControl, FormGroup, FormLabel, IconButton, MenuItem, Select} from "@material-ui/core"; -import {ExpandLess, ExpandMore} from "@material-ui/icons"; +import {ExpandLess, ExpandMore} from "@mui/icons-material"; export default function LogView(props) { diff --git a/react/admin-panel/src/views/login.jsx b/react/admin-panel/src/views/login.jsx index 1fbd39d..6445db4 100644 --- a/react/admin-panel/src/views/login.jsx +++ b/react/admin-panel/src/views/login.jsx @@ -7,17 +7,17 @@ import { Link, TextField, Typography -} from "@material-ui/core"; +} from "@mui/material"; -import {makeStyles} from '@material-ui/core/styles'; -import {Alert} from '@material-ui/lab'; +import {Alert} from '@mui/lab'; import React, {useCallback, useContext, useEffect, useState} from "react"; -import ReplayIcon from '@material-ui/icons/Replay'; +import ReplayIcon from '@mui/icons-material'; import LanguageSelection from "../elements/language-selection"; import {decodeText, encodeText, getParameter, removeParameter} from "shared/util"; import Icon from "shared/elements/icon"; import {LocaleContext} from "shared/locale"; +/* const useStyles = makeStyles((theme) => ({ paper: { marginTop: theme.spacing(8), @@ -66,11 +66,13 @@ const useStyles = makeStyles((theme) => ({ } } })); + */ export default function LoginForm(props) { const api = props.api; - const classes = useStyles(); + // const classes = useStyles(); + const classes = {}; let [username, setUsername] = useState(""); let [password, setPassword] = useState(""); let [rememberMe, setRememberMe] = useState(true); diff --git a/react/admin-panel/src/views/overview.js b/react/admin-panel/src/views/overview.js index 64c8f95..4503ac8 100644 --- a/react/admin-panel/src/views/overview.js +++ b/react/admin-panel/src/views/overview.js @@ -1,11 +1,10 @@ import * as React from "react"; import {Link} from "react-router-dom"; import {format, getDaysInMonth} from "date-fns"; -import {CircularProgress} from "@material-ui/core"; import {useCallback, useContext, useEffect, useState} from "react"; import {LocaleContext} from "shared/locale"; -import {LibraryBooks, People} from "@material-ui/icons"; -import {ArrowCircleRight, Groups} from "@mui/icons-material"; +import {ArrowCircleRight, BugReport, Groups, LibraryBooks, People} from "@mui/icons-material"; +import {CircularProgress} from "@mui/material"; const StatBox = (props) =>
@@ -131,6 +130,10 @@ export default function Overview(props) { text={L("admin.routes_defined")} icon={} link={"/admin/routes"} /> + } + link={"/admin/logs"} />
diff --git a/react/admin-panel/src/views/route/index.js b/react/admin-panel/src/views/route/index.js deleted file mode 100644 index f63697e..0000000 --- a/react/admin-panel/src/views/route/index.js +++ /dev/null @@ -1,4 +0,0 @@ -import RouteEditView from "./route-edit"; -import RouteListView from "./route-list"; - -export default { RouteEditView, RouteListView }; \ No newline at end of file diff --git a/react/admin-panel/src/views/route/route-edit.js b/react/admin-panel/src/views/route/route-edit.js index a3cd422..0bf7b48 100644 --- a/react/admin-panel/src/views/route/route-edit.js +++ b/react/admin-panel/src/views/route/route-edit.js @@ -4,12 +4,12 @@ import {LocaleContext} from "shared/locale"; import { Box, Button, + TextField, CircularProgress, styled, -} from "@material-ui/core"; +} from "@mui/material"; import * as React from "react"; import RouteForm from "./route-form"; -import {KeyboardArrowLeft, Save} from "@material-ui/icons"; -import {TextField} from "@mui/material"; +import {KeyboardArrowLeft, Save} from "@mui/icons-material"; const ButtonBar = styled(Box)((props) => ({ "& > button": { @@ -74,7 +74,7 @@ export default function RouteEditView(props) { setFetchRoute(false); api.getRoute(routeId).then((res) => { if (!res.success) { - showDialog(res.msg, L("Error fetching route")); + showDialog(res.msg, L("routes.fetch_route_error")); navigate("/admin/routes"); } else { setRoute(res.route); @@ -93,7 +93,7 @@ export default function RouteEditView(props) { if (res.success) { navigate("/admin/routes/" + res.routeId); } else { - showDialog(res.msg, L("Error saving route")); + showDialog(res.msg, L("routes.save_route_error")); } }); } else { @@ -101,7 +101,7 @@ export default function RouteEditView(props) { api.updateRoute(...args).then(res => { setSaving(false); if (!res.success) { - showDialog(res.msg, L("Error saving route")); + showDialog(res.msg, L("routes.save_route_error")); } }); } diff --git a/react/admin-panel/src/views/route/route-form.js b/react/admin-panel/src/views/route/route-form.js index 12e6835..8e96dae 100644 --- a/react/admin-panel/src/views/route/route-form.js +++ b/react/admin-panel/src/views/route/route-form.js @@ -1,8 +1,8 @@ -import {Box, Checkbox, FormControl, FormControlLabel, Select, styled, TextField} from "@material-ui/core"; +import {Box, Checkbox, FormControl, FormControlLabel, Select, styled, TextField} from "@mui/material"; import * as React from "react"; import {useCallback, useContext, useEffect, useRef} from "react"; import {LocaleContext} from "shared/locale"; -import {CheckCircle, ErrorRounded} from "@material-ui/icons"; +import {CheckCircle, ErrorRounded} from "@mui/icons-material"; const RouteFormControl = styled(FormControl)((props) => ({ "& > label": { diff --git a/react/admin-panel/src/views/route/route-list.js b/react/admin-panel/src/views/route/route-list.js index f6c7954..adf6236 100644 --- a/react/admin-panel/src/views/route/route-list.js +++ b/react/admin-panel/src/views/route/route-list.js @@ -8,11 +8,12 @@ import { TableContainer, TableHead, TableRow, - IconButton, Button, Checkbox -} from "@material-ui/core"; + Button, + IconButton, Checkbox +} from "@mui/material"; import {useCallback, useContext, useEffect, useState} from "react"; import {LocaleContext} from "shared/locale"; -import {Add, Cached, Delete, Edit, Refresh} from "@material-ui/icons"; +import {Add, Cached, Delete, Edit, Refresh} from "@mui/icons-material"; import Dialog from "shared/elements/dialog"; const RouteTableRow = styled(TableRow)((props) => ({ @@ -136,15 +137,16 @@ export default function RouteListView(props) {
- - - - + columns={columnDefinitions} + buttons={[{ + key: "btn-create", + color: "primary", + startIcon: , + children: L("general.create_new"), + disabled: !api.hasPermission("user/create") && !api.hasPermission("user/invite"), + onClick: () => navigate("/admin/user/new") + }]}/>
-
+ } \ No newline at end of file diff --git a/react/package.json b/react/package.json index 428a172..ab8b0a4 100644 --- a/react/package.json +++ b/react/package.json @@ -34,17 +34,15 @@ "dependencies": { "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.0", - "@material-ui/core": "^4.12.4", - "@material-ui/icons": "^4.11.3", - "@material-ui/lab": "^4.0.0-alpha.61", "@mui/icons-material": "^5.11.0", + "@mui/lab": "^5.0.0-alpha.170", "@mui/material": "^5.15.14", "@mui/x-date-pickers": "^7.0.0", "chart.js": "^4.0.1", "clsx": "^1.2.1", "date-fns": "^2.29.3", - "material-ui-color-picker": "^3.5.1", "mini-css-extract-plugin": "^2.7.1", + "mui-color-input": "^2.0.3", "pako": "^2.1.0", "react": "^18.2.0", "react-chartjs-2": "^5.0.1", diff --git a/react/shared/elements/data-table.css b/react/shared/elements/data-table.css index a63fb9e..729955f 100644 --- a/react/shared/elements/data-table.css +++ b/react/shared/elements/data-table.css @@ -48,4 +48,9 @@ margin-left: 4px; margin-right: 4px; cursor: pointer; +} + +.data-table-button-bar button { + margin-right: 8px; + margin-top: 8px; } \ No newline at end of file diff --git a/react/shared/elements/data-table.js b/react/shared/elements/data-table.js index 49d8722..e78f1a5 100644 --- a/react/shared/elements/data-table.js +++ b/react/shared/elements/data-table.js @@ -1,14 +1,11 @@ -import {Table, TableBody, TableCell, TableHead, TableRow} from "@material-ui/core"; -import ArrowUpwardIcon from "@material-ui/icons/ArrowUpward"; -import ArrowDownwardIcon from "@material-ui/icons/ArrowDownward"; import React, {useCallback, useContext, useEffect, useState} from "react"; import "./data-table.css"; import {LocaleContext} from "../locale"; import clsx from "clsx"; -import {Box, IconButton, Select, TextField} from "@mui/material"; +import {Box, Button, Select, TextField, Table, TableBody, TableCell, TableHead, TableRow} from "@mui/material"; import {formatDate, formatDateTime} from "../util"; -import CachedIcon from "@material-ui/icons/Cached"; import {isNumber} from "chart.js/helpers"; +import {ArrowUpward, ArrowDownward, Refresh} from "@mui/icons-material"; export function DataTable(props) { @@ -18,7 +15,8 @@ export function DataTable(props) { fetchData, onClick, onFilter, defaultSortColumn, defaultSortOrder, forceReload, - title, ...other } = props; + buttons, + ...other } = props; const {translate: L} = useContext(LocaleContext); @@ -84,7 +82,7 @@ export function DataTable(props) { onClick={() => onChangeSort(index, column)} align={column.align}> {sortColumn === index ? - (sortAscending ? : ) : + (sortAscending ? : ) : <> } {column.renderHead(index)} @@ -124,17 +122,13 @@ export function DataTable(props) { } return - {title ? -

- {fetchData ? - onFetchData(true)} title={L("general.reload")}> -   - - : <> - } - {title} -

: <> - } + + + {(buttons || []).map(b =>