localization, context, react stuff
This commit is contained in:
@@ -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')),
|
||||
|
||||
@@ -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>
|
||||
79
react/admin-panel/src/AdminDashboard.jsx
Normal file
79
react/admin-panel/src/AdminDashboard.jsx
Normal 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>
|
||||
}
|
||||
@@ -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} />
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
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>
|
||||
Framework: <strong><a href={"https://git.romanh.de/Projekte/web-base"}>WebBase</a></strong>. <b>Version</b> {props.info.version}
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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"}>
|
||||
|
||||
@@ -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>);
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,6 +0,0 @@
|
||||
.page-link { color: #222629; }
|
||||
.page-link:hover { color: black; }
|
||||
|
||||
.ReactCollapse--collapse {
|
||||
transition: height 500ms;
|
||||
}
|
||||
@@ -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 = () => {
|
||||
|
||||
|
||||
119
react/admin-panel/src/views/overview.js
Normal file
119
react/admin-panel/src/views/overview.js
Normal 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() {
|
||||
|
||||
|
||||
}
|
||||
}*/
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user