localization, context, react stuff

This commit is contained in:
2022-12-01 01:28:38 +01:00
parent a84a51018c
commit cb75e22811
32 changed files with 379 additions and 208 deletions

View File

@@ -25,7 +25,6 @@ class Account extends TemplateDocument {
protected function loadParameters() {
$settings = $this->getSettings();
$templateName = $this->getTemplateName();
$language = $this->getContext()->getLanguage();
$this->parameters["view"] = ["success" => true];
switch ($templateName) {

View File

@@ -14,5 +14,6 @@ class Admin extends TemplateDocument {
$this->searchable = false;
parent::__construct($router, $template, $params);
$this->enableCSP();
$this->addCSPWhitelist("/js/admin-panel/");
}
}

View File

@@ -0,0 +1,11 @@
<?php
return [
"dashboard" => "Dashboard",
"visitor_statistics" => "Besucherstatistiken",
"user_groups" => "Benutzer & Gruppen",
"page_routes" => "Seiten & Routen",
"settings" => "Einstellungen",
"logs" => "Logs",
"help" => "Hilfe",
];

View File

@@ -9,4 +9,5 @@ return [
"submit" => "Absenden",
"language" => "Sprache",
"loading" => "Laden",
"logout" => "Ausloggen",
];

View File

@@ -0,0 +1,11 @@
<?php
return [
"dashboard" => "Dashboard",
"visitor_statistics" => "Visitor Statistics",
"user_groups" => "User & Groups",
"page_routes" => "Pages & Routes",
"settings" => "Settings",
"logs" => "Logs",
"help" => "Help",
];

View File

@@ -9,4 +9,5 @@ return [
"submit" => "Submit",
"language" => "Language",
"loading" => "Loading",
"logout" => "Logout",
];

View File

@@ -7,7 +7,7 @@
{% block body %}
<noscript>You need Javascript enabled to run this app</noscript>
<div class="wrapper" type="module" id="admin-panel"></div>
<div type="module" id="admin-panel"></div>
<script src="/js/admin-panel/index.js" nonce="{{ site.csp.nonce }}"></script>
<link rel="stylesheet" href="/js/admin-panel/index.css" nonce="{{ site.csp.nonce }}"></link>
{% endblock %}

View File

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
!function(){function e(e,t,n,r){Object.defineProperty(e,t,{get:n,set:r,enumerable:!0,configurable:!0})}var t=("undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{}).parcelRequireeec4;t.register("aNQgM",(function(n,r){var o;o=n.exports,Object.defineProperty(o,"__esModule",{value:!0,configurable:!0}),e(n.exports,"default",(function(){return c}));var a=t("lBpE3");t("6cds3");var s=t("1dF7x"),i=t("2eEzJ");function c(e){for(var t=new Date,n=(0,i.default)(t),r=["#ff4444","#ffbb33","#00C851","#33b5e5"];r.length<n;)r=r.concat(r);var o=new Array(n).fill(0);Array.from(Array(n),(function(e,t){return t+1})),format(t,"MMMM");return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)("div",{className:"content-header",children:(0,a.jsx)("div",{className:"container-fluid",children:(0,a.jsxs)("div",{className:"row mb-2",children:[(0,a.jsx)("div",{className:"col-sm-6",children:(0,a.jsx)("h1",{className:"m-0 text-dark",children:"Dashboard"})}),(0,a.jsx)("div",{className:"col-sm-6",children:(0,a.jsxs)("ol",{className:"breadcrumb float-sm-right",children:[(0,a.jsx)("li",{className:"breadcrumb-item",children:(0,a.jsx)(s.Link,{to:"/admin/dashboard",children:"Home"})}),(0,a.jsx)("li",{className:"breadcrumb-item active",children:"Dashboard"})]})})]})})}),(0,a.jsx)("section",{className:"content"})]})}})),t.register("2eEzJ",(function(n,r){e(n.exports,"default",(function(){return s}));var o=t("18SRp"),a=t("3QBsJ");function s(e){(0,a.default)(1,arguments);var t=(0,o.default)(e),n=t.getFullYear(),r=t.getMonth(),s=new Date(0);return s.setFullYear(n,r+1,0),s.setHours(0,0,0,0),s.getDate()}})),t.register("18SRp",(function(n,r){e(n.exports,"default",(function(){return s}));var o=t("3QBsJ");function a(e){return a="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},a(e)}function s(e){(0,o.default)(1,arguments);var t=Object.prototype.toString.call(e);return e instanceof Date||"object"===a(e)&&"[object Date]"===t?new Date(e.getTime()):"number"==typeof e||"[object Number]"===t?new Date(e):("string"!=typeof e&&"[object String]"!==t||"undefined"==typeof console||(console.warn("Starting with v2.0.0-beta.1 date-fns doesn't accept strings as date arguments. Please use `parseISO` to parse strings. See: https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#string-arguments"),console.warn((new Error).stack)),new Date(NaN))}})),t.register("3QBsJ",(function(t,n){function r(e,t){if(t.length<e)throw new TypeError(e+" argument"+(e>1?"s":"")+" required, but only "+t.length+" present")}e(t.exports,"default",(function(){return r}))}))}();
//# sourceMappingURL=overview.01501b20.js.map

View File

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

View File

@@ -4,10 +4,22 @@ const {
override,
removeModuleScopePlugin,
babelInclude,
addWebpackModuleRule,
} = require('customize-cra');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const addMiniCssExtractPlugin = config => {
config.plugins.push(new MiniCssExtractPlugin());
return config;
}
module.exports = override(
removeModuleScopePlugin(),
addMiniCssExtractPlugin,
addWebpackModuleRule({
test: /\.css$/,
use: [ MiniCssExtractPlugin.loader, 'css-loader' ]
}),
babelInclude([
path.resolve(path.join(__dirname, 'src')),
fs.realpathSync(path.join(__dirname, '../shared')),

View File

@@ -1,9 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/fontawesome.min.css">
</head>
<body>
<div id="admin-panel"></div>
<div id="admin-panel"></div>
</body>
</html>

View File

@@ -0,0 +1,79 @@
import React, {lazy, Suspense, useCallback, useState} from "react";
import {BrowserRouter, Route, Routes} from "react-router-dom";
import Header from "./elements/header";
import Sidebar from "./elements/sidebar";
import Dialog from "./elements/dialog";
import Footer from "./elements/footer";
import {useContext, useEffect} from "react";
import {LocaleContext} from "shared/locale";
// css
import './res/adminlte.min.css';
// views
const Overview = lazy(() => import('./views/overview'));
export default function AdminDashboard(props) {
const api = props.api;
const info = props.info;
const [dialog, setDialog] = useState({show: false});
const {currentLocale, requestModules, translate: L} = useContext(LocaleContext);
const showDialog = useCallback((message, title, options=["Close"], onOption = null) => {
setDialog({ show: true, message: message, title: title, options: options, onOption: onOption });
}, []);
const hideDialog = useCallback(() => {
setDialog({show: false});
}, []);
useEffect(() => {
requestModules(api, ["general", "admin"], currentLocale).then(data => {
if (!data.success) {
alert(data.msg);
}
});
}, [currentLocale]);
const controlObj = {
...props,
showDialog: showDialog,
hideDialog: hideDialog
};
return <BrowserRouter>
<Header {...controlObj} />
<Sidebar {...controlObj} />
<div className={"wrapper"}>
<div className={"content-wrapper p-2"}>
<section className={"content"}>
<Suspense fallback={<div>{L("general.loading")}... </div>}>
<Routes>
<Route path={"/admin/dashboard"} element={<Overview {...controlObj} />}/>
</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>
</div>
<Footer info={info} />
</BrowserRouter>
}

View File

@@ -1,102 +1,61 @@
import React from 'react';
import './res/adminlte.min.css';
import './res/index.css';
import React, {useCallback, useContext, useEffect, useMemo, useState} from 'react';
import API from "shared/api";
import Icon from "shared/elements/icon";
import {BrowserRouter, Routes} from "react-router-dom";
import Dialog from "./elements/dialog";
import Footer from "./elements/footer";
import Header from "./elements/header";
import Sidebar from "./elements/sidebar";
import LoginForm from "./views/login";
import {Alert} from "@material-ui/lab";
import {Button} from "@material-ui/core";
import { LocaleContext } from "shared/locale";
import AdminDashboard from "./AdminDashboard";
const L = (key) => {
return "<nope>";
}
export default function App() {
export default class AdminDashboard extends React.Component {
const api = useMemo(() => new API(), []);
const [user, setUser] = useState(null);
const [loaded, setLoaded] = useState(false);
const [info, setInfo] = useState({});
const [error, setError] = useState(null);
const {translate: L} = useContext(LocaleContext);
static contextType = LocaleContext;
constructor(props) {
super(props);
this.api = new API();
this.state = {
loaded: false,
dialog: { onClose: () => this.hideDialog() },
info: { },
error: null
};
}
onUpdate() {
}
showDialog(message, title, options=["Close"], onOption = null) {
const props = { show: true, message: message, title: title, options: options, onOption: onOption };
this.setState({ ...this.state, dialog: { ...this.state.dialog, ...props } });
}
hideDialog() {
this.setState({ ...this.state, dialog: { ...this.state.dialog, show: false } });
}
onInit() {
// return;
this.setState({ ...this.state, loaded: false, error: null });
this.api.getLanguageEntries("general").then(data => {
const fetchUser = useCallback(() => {
api.fetchUser().then(data => {
if (data.success) {
this.api.info().then(data => {
setUser(data.user || null);
setLoaded(true);
} else {
setError(data.msg);
}
});
}, [api]);
const onInit = useCallback((force = false) => {
if (loaded && !force) {
return;
}
setError(false);
setLoaded(false);
api.getLanguageEntries("general").then(data => {
if (data.success) {
api.info().then(data => {
if (data.success) {
this.setState({...this.state, info: data.info })
this.api.fetchUser().then(data => {
if (data.success) {
setInterval(this.onUpdate.bind(this), 60*1000);
this.setState({...this.state, loaded: true});
} else {
this.setState({ ...this.state, error: data.msg })
}
});
setInfo(data.info);
fetchUser();
} else {
this.setState({ ...this.state, error: data.msg })
setError(data.msg);
}
});
} else {
this.setState({ ...this.state, error: data.msg })
setError(data.msg);
}
});
}
}, [api, loaded, fetchUser]);
componentDidMount() {
this.onInit();
}
useEffect(() => {
onInit();
}, []);
onLogin(username, password, rememberMe, callback) {
this.setState({ ...this.state, error: "" });
return this.api.login(username, password, rememberMe).then((res) => {
if (res.success) {
this.api.fetchUser().then(() => {
this.setState({ ...this.state, user: res });
callback(res);
})
} else {
callback(res);
}
});
}
onLogout(callback) {
this.api.logout().then(() => {
this.api.loggedIn = false;
this.setState({ ...this.state, user: { } })
if (callback) callback();
});
}
onTotp2FA(code, callback) {
/*
const onTotp2FA = useCallback((code, callback) => {
this.setState({ ...this.state, error: "" });
return this.api.verifyTotp2FA(code).then((res) => {
if (res.success) {
@@ -108,7 +67,7 @@ export default class AdminDashboard extends React.Component {
callback(res);
}
});
}
}, [api]);
onKey2FA(credentialID, clientDataJson, authData, signature, callback) {
this.setState({ ...this.state, error: "" });
@@ -124,62 +83,24 @@ export default class AdminDashboard extends React.Component {
});
}
render() {
*/
if (!this.state.loaded) {
if (this.state.error) {
return <Alert severity={"error"} title={L("general.error_occurred")}>
<div>{this.state.error}</div>
<Button type={"button"} variant={"outlined"} onClick={() => this.onInit()}>
Retry
</Button>
</Alert>
} else {
return <b>{L("general.loading")} <Icon icon={"spinner"}/></b>
}
console.log(loaded, user, api.loggedIn);
if (!loaded) {
if (error) {
return <Alert severity={"error"} title={L("general.error_occurred")}>
<div>{error}</div>
<Button type={"button"} variant={"outlined"} onClick={() => onInit(true)}>
Retry
</Button>
</Alert>
} else {
return <b>{L("general.loading")} <Icon icon={"spinner"}/></b>
}
this.controlObj = {
showDialog: this.showDialog.bind(this),
api: this.api,
info: this.state.info,
onLogout: this.onLogout.bind(this),
onLogin: this.onLogin.bind(this),
onTotp2FA: this.onTotp2FA.bind(this),
onKey2FA: this.onKey2FA.bind(this),
};
if (!this.api.loggedIn) {
return <LoginForm {...this.controlObj}/>
}
return <BrowserRouter>
<Header {...this.controlObj} />
<Sidebar {...this.controlObj} />
<div className={"content-wrapper p-2"}>
<section className={"content"}>
<Routes>
{/*<Route path={"/admin/dashboard"}><Overview {...this.controlObj} notifications={this.state.notifications} /></Route>
<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>*/}
</Routes>
<Dialog {...this.state.dialog}/>
</section>
</div>
<Footer info={this.state.info} />
</BrowserRouter>
} else if (!user || !api.loggedIn) {
return <LoginForm api={api} info={info} onLogin={fetchUser} />
} else {
return <AdminDashboard api={api} info={info} />
}
}

View File

@@ -4,8 +4,8 @@ export default function Footer(props) {
return (
<footer className={"main-footer"}>
Theme: <strong>Copyright © 2014-2019 <a href={"https://adminlte.io"}>AdminLTE.io</a>. <b>Version</b> 3.0.3</strong>&nbsp;
CMS: <strong><a href={"https://git.romanh.de/Projekte/web-base"}>WebBase</a></strong>. <b>Version</b> {props.info.version}
Theme: <strong>Copyright © 2014-2021 <a href={"https://adminlte.io"}>AdminLTE.io</a>. <b>Version</b> 3.2.0</strong>&nbsp;
Framework: <strong><a href={"https://git.romanh.de/Projekte/web-base"}>WebBase</a></strong>. <b>Version</b> {props.info.version}
</footer>
)
}

View File

@@ -29,7 +29,12 @@ export default function LanguageSelection(props) {
let flags = [];
if (languages === null) {
api.getLanguages().then((res) => {
setLanguages(res.languages);
if (res.success) {
setLanguages(res.languages);
} else {
setLanguages({});
alert(res.msg);
}
});
} else {
for (const language of Object.values(languages)) {

View File

@@ -1,55 +1,51 @@
import React from 'react';
import React, {useCallback, useContext} from 'react';
import {Link, NavLink} from "react-router-dom";
import Icon from "shared/elements/icon";
import {LocaleContext} from "shared/locale";
export default function Sidebar(props) {
let parent = {
showDialog: props.showDialog || function() {},
api: props.api,
};
const api = props.api;
const showDialog = props.showDialog;
const {translate: L} = useContext(LocaleContext);
function onLogout() {
parent.api.logout().then(obj => {
const onLogout = useCallback(() => {
api.logout().then(obj => {
if (obj.success) {
document.location = "/admin";
} else {
parent.showDialog("Error logging out: " + obj.msg, "Error logging out");
showDialog("Error logging out: " + obj.msg, "Error logging out");
}
});
}
}, [api, showDialog]);
const menuItems = {
"dashboard": {
"name": "Dashboard",
"name": "admin.dashboard",
"icon": "tachometer-alt"
},
"visitors": {
"name": "Visitor Statistics",
"name": "admin.visitor_statistics",
"icon": "chart-bar",
},
"users": {
"name": "Users & Groups",
"name": "admin.user_groups",
"icon": "users"
},
"pages": {
"name": "Pages & Routes",
"name": "admin.page_routes",
"icon": "copy",
},
"settings": {
"name": "Settings",
"name": "admin.settings",
"icon": "tools"
},
"logs": {
"name": "Logs & Notifications",
"name": "admin.logs",
"icon": "file-medical-alt"
},
"contact": {
"name": "Contact Requests",
"icon": "comments"
},
"help": {
"name": "Help",
"name": "admin.help",
"icon": "question-circle"
},
};
@@ -61,8 +57,8 @@ export default function Sidebar(props) {
li.push(
<li key={id} className={"nav-item"}>
<NavLink to={"/admin/" + id} className={"nav-link"} activeClassName={"active"}>
<Icon icon={obj.icon} className={"nav-icon"} /><p>{obj.name}{badge}</p>
<NavLink to={"/admin/" + id} className={"nav-link"}>
<Icon icon={obj.icon} className={"nav-icon"} /><p>{L(obj.name)}{badge}</p>
</NavLink>
</li>
);
@@ -71,7 +67,7 @@ export default function Sidebar(props) {
li.push(<li className={"nav-item"} key={"logout"}>
<a href={"#"} onClick={() => onLogout()} className={"nav-link"}>
<Icon icon={"arrow-left"} className={"nav-icon"} />
<p>Logout</p>
<p>{L("general.logout")}</p>
</a>
</li>);
@@ -96,7 +92,7 @@ export default function Sidebar(props) {
<div className={"os-content"} style={{padding: "0px 0px", height: "100%", width: "100%"}}>
<div className="user-panel mt-3 pb-3 mb-3 d-flex">
<div className="info">
<a href="#" className="d-block">Logged in as: {parent.api.user.name}</a>
<a href="#" className="d-block">Logged in as: {api.user.name}</a>
</div>
</div>
<nav className={"mt-2"}>

View File

@@ -1,9 +1,9 @@
import React from "react";
import {createRoot} from "react-dom/client";
import AdminDashboard from "./App";
import App from "./App";
import {LocaleProvider} from "shared/locale";
const root = createRoot(document.getElementById('admin-panel'));
root.render(<LocaleProvider>
<AdminDashboard />
<App />
</LocaleProvider>);

View File

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +0,0 @@
.page-link { color: #222629; }
.page-link:hover { color: black; }
.ReactCollapse--collapse {
transition: height 500ms;
}

View File

@@ -12,7 +12,6 @@ import {
import {makeStyles} from '@material-ui/core/styles';
import {Alert} from '@material-ui/lab';
import React, {useCallback, useContext, useEffect, useState} from "react";
import {Navigate} from "react-router-dom";
import ReplayIcon from '@material-ui/icons/Replay';
import LanguageSelection from "../elements/language-selection";
import {decodeText, encodeText, getParameter, removeParameter} from "shared/util";
@@ -85,10 +84,6 @@ export default function LoginForm(props) {
const {translate: L, currentLocale, requestModules} = useContext(LocaleContext);
const getNextUrl = () => {
return getParameter("next") || "/admin";
}
const onUpdateLocale = useCallback(() => {
requestModules(api, ["general", "account"], currentLocale).then(data => {
setLoaded(true);
@@ -107,17 +102,19 @@ export default function LoginForm(props) {
setError("");
setLoggingIn(true);
removeParameter("success");
props.onLogin(username, password, rememberMe, (res) => {
api.login(username, password, rememberMe).then((res) => {
set2FAState(0);
setLoggingIn(false);
setPassword("");
if (!res.success) {
setEmailConfirmed(res.emailConfirmed);
setError(res.msg);
} else {
props.onLogin();
}
});
}
}, [isLoggingIn, password, props, rememberMe, username]);
}, [api, isLoggingIn, password, props, rememberMe, username]);
const onSubmit2FA = useCallback(() => {
setLoggingIn(true);
@@ -168,14 +165,7 @@ export default function LoginForm(props) {
set2FAError(e.toString());
});
}
}, [api.loggedIn, api.user, tfaState, props])
if (api.loggedIn) {
if (!api.user["2fa"] || !api.user["2fa"].confirmed || api.user["2fa"].authenticated) {
// Redirect by default takes only path names
return <Navigate to={getNextUrl()}/>
}
}
}, [api.loggedIn, api.user, tfaState, props]);
const createForm = () => {

View File

@@ -0,0 +1,119 @@
import * as React from "react";
import {Link} from "react-router-dom";
import {format, getDaysInMonth} from "date-fns";
export default function Overview(props) {
const today = new Date();
const numDays = getDaysInMonth(today);
let colors = [ '#ff4444', '#ffbb33', '#00C851', '#33b5e5' ];
while (colors.length < numDays) {
colors = colors.concat(colors);
}
let data = new Array(numDays).fill(0);
let visitorCount = 0;
/*
for (let date in this.state.visitors) {
if (this.state.visitors.hasOwnProperty(date)) {
let day = parseInt(date.split("/")[2]) - 1;
if (day >= 0 && day < numDays) {
let count = parseInt(this.state.visitors[date]);
data[day] = count;
visitorCount += count;
}
}
}
*/
let labels = Array.from(Array(numDays), (_, i) => i + 1);
let chartOptions = {};
let chartData = {
labels: labels,
datasets: [{
label: 'Unique Visitors ' + format(today, "MMMM"),
borderWidth: 1,
data: data,
backgroundColor: colors,
}]
};
/*
let loadAvg = this.state.server.load_avg;
if (Array.isArray(this.state.server.load_avg)) {
loadAvg = this.state.server.load_avg.join(" ");
}
*/
return <>
<div className={"content-header"}>
<div className={"container-fluid"}>
<div className={"row mb-2"}>
<div className={"col-sm-6"}>
<h1 className={"m-0 text-dark"}>Dashboard</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">Dashboard</li>
</ol>
</div>
</div>
</div>
</div>
<section className={"content"}>
</section>
</>
}
/*
export default class Overview extends React.Component {
constructor(props) {
super(props);
this.state = {
chartVisible : true,
statusVisible : true,
userCount: 0,
notificationCount: 0,
visitorsTotal: 0,
visitors: { },
server: { load_avg: ["Unknown"] },
errors: []
}
}
removeError(i) {
if (i >= 0 && i < this.state.errors.length) {
let errors = this.state.errors.slice();
errors.splice(i, 1);
this.setState({...this.state, errors: errors});
}
}
componentDidMount() {
this.parent.api.getStats().then((res) => {
if(!res.success) {
let errors = this.state.errors.slice();
errors.push({ message: res.msg, title: "Error fetching Stats" });
this.setState({ ...this.state, errors: errors });
} else {
this.setState({
...this.state,
userCount: res.userCount,
pageCount: res.pageCount,
visitors: res.visitors,
visitorsTotal: res.visitorsTotal,
server: res.server
});
}
});
}
render() {
}
}*/

View File

View File

@@ -36,9 +36,13 @@
"@material-ui/core": "^4.12.4",
"@material-ui/icons": "^4.11.3",
"@material-ui/lab": "^4.0.0-alpha.61",
"chart.js": "^4.0.1",
"clsx": "^1.2.1",
"date-fns": "^2.29.3",
"mini-css-extract-plugin": "^2.7.1",
"react": "^18.2.0",
"react-chartjs-2": "^5.0.1",
"react-collapse": "^5.1.1",
"react-dom": "^18.2.0",
"react-router-dom": "^6.4.3"
},

View File

@@ -53,7 +53,12 @@ export default class API {
}
async logout() {
return this.apiCall("user/logout");
const res = await this.apiCall("user/logout");
if (res.success) {
this.loggedIn = false;
}
return res;
}
async getUser(id) {

View File

@@ -1,7 +1,7 @@
import React from 'react';
import {createContext, useCallback, useState} from "react";
const LocaleContext = React.createContext(null);
const LocaleContext = createContext(null);
function LocaleProvider(props) {

View File

@@ -3649,6 +3649,11 @@ char-regex@^2.0.0:
resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-2.0.1.tgz#6dafdb25f9d3349914079f010ba8d0e6ff9cd01e"
integrity sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw==
chart.js@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-4.0.1.tgz#93d5d50ac222a5b3b6ac7488e82e1553ac031592"
integrity sha512-5/8/9eBivwBZK81mKvmIwTb2Pmw4D/5h1RK9fBWZLLZ8mCJ+kfYNmV9rMrGoa5Hgy2/wVDBMLSUDudul2/9ihA==
check-types@^11.1.1:
version "11.2.2"
resolved "https://registry.yarnpkg.com/check-types/-/check-types-11.2.2.tgz#7afc0b6a860d686885062f2dba888ba5710335b4"
@@ -6885,7 +6890,7 @@ mimic-fn@^2.1.0:
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
mini-css-extract-plugin@^2.4.5:
mini-css-extract-plugin@^2.4.5, mini-css-extract-plugin@^2.7.1:
version "2.7.1"
resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.1.tgz#ec924df783cff32ce6183fceb653028f70128643"
integrity sha512-viOoaUFy+Z2w43VsGPbtfwFrr0tKwDctK9dUofG5MBViYhD1noGFUzzDIVw0KPwCGUP+c7zqLxm+acuQs7zLzw==
@@ -8165,6 +8170,16 @@ react-app-rewired@^2.2.1:
dependencies:
semver "^5.6.0"
react-chartjs-2@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/react-chartjs-2/-/react-chartjs-2-5.0.1.tgz#7ef7dd57441903e8d34e1d06a1aead1095d0bca8"
integrity sha512-u38C9OxynlNCBp+79grgXRs7DSJ9w8FuQ5/HO5FbYBbri8HSZW+9SWgjVshLkbXBfXnMGWakbHEtvN0nL2UG7Q==
react-collapse@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/react-collapse/-/react-collapse-5.1.1.tgz#a2fa08ef13f372141b02e6a7d49ef72427bcbc2b"
integrity sha512-k6cd7csF1o9LBhQ4AGBIdxB60SUEUMQDAnL2z1YvYNr9KoKr+nDkhN6FK7uGaBd/rYrYfrMpzpmJEIeHRYogBw==
react-dev-utils@^12.0.1:
version "12.0.1"
resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-12.0.1.tgz#ba92edb4a1f379bd46ccd6bcd4e7bc398df33e73"