refactor of admin screen

This commit is contained in:
balzack 2023-01-13 15:05:28 -08:00
parent d2fc9de4af
commit 625f3a31de
16 changed files with 152 additions and 264 deletions

View File

@ -16,7 +16,7 @@ import { AppWrapper } from 'App.styled';
import { Root } from './root/Root';
import { Access } from './access/Access';
import { Session } from './session/Session';
import { Admin } from './admin/Admin';
import { Dashboard } from './dashboard/Dashboard';
function App() {
@ -33,7 +33,8 @@ function App() {
<Router>
<Routes>
<Route path="/" element={ <Root /> } />
<Route path="/admin" element={ <Admin /> } />
<Route path="/dashboard" element={ <Dashboard /> } />
<Route path="/admin" element={ <Access mode="admin" /> } />
<Route path="/login" element={ <Access mode="login" /> } />
<Route path="/create" element={ <Access mode="create" /> } />
<Route path="/session" element={

View File

@ -1,6 +1,7 @@
import { useAccess } from './useAccess.hook';
import { AccessWrapper } from './Access.styled';
import { Login } from './login/Login';
import { Admin } from './admin/Admin';
import { CreateAccount } from './createAccount/CreateAccount';
import login from 'images/login.png'
@ -23,6 +24,9 @@ export function Access({ mode }) {
{ mode === 'create' && (
<CreateAccount />
)}
{ mode === 'admin' && (
<Admin />
)}
</div>
</div>
)}
@ -35,6 +39,9 @@ export function Access({ mode }) {
{ mode === 'create' && (
<CreateAccount />
)}
{ mode === 'admin' && (
<Admin />
)}
</div>
</div>
)}

View File

@ -0,0 +1,58 @@
import { Button, Modal, Form, Input } from 'antd';
import { SettingOutlined, LockOutlined } from '@ant-design/icons';
import { AdminWrapper } from './Admin.styled';
import { useAdmin } from './useAdmin.hook';
export function Admin() {
const { state, actions } = useAdmin();
const login = async () => {
try {
await actions.login();
}
catch(err) {
Modal.error({
title: 'Login Error',
content: 'Please confirm your password.',
});
}
}
const keyDown = (e) => {
if (e.key === 'Enter') {
login()
}
}
return (
<AdminWrapper>
<div className="app-title">
<span>Databag</span>
<div className="settings" onClick={() => actions.navUser()}>
<SettingOutlined />
</div>
</div>
<div className="form-title">Admin</div>
<div className="form-form">
<Form name="basic" wrapperCol={{ span: 24, }}>
<Form.Item name="password">
<Input.Password placeholder={ state.unclaimed ? 'New Password' : 'Password' } spellCheck="false"
onChange={(e) => actions.setPassword(e.target.value)} autoComplete="current-password"
onKeyDown={(e) => keyDown(e)} prefix={<LockOutlined />} size="large" />
</Form.Item>
<div className="form-button">
<div className="form-login">
<Button type="primary" block onClick={login} size="middle" loading={state.busy}
disabled={!state.password}>Login</Button>
</div>
</div>
</Form>
</div>
</AdminWrapper>
);
};

View File

@ -1,7 +1,7 @@
import styled from 'styled-components';
import Colors from 'constants/Colors';
export const PromptWrapper = styled.div`
export const AdminWrapper = styled.div`
max-width: 400px;
width: 90%;
height: 90%;
@ -16,7 +16,7 @@ export const PromptWrapper = styled.div`
flex: 1;
color: ${Colors.grey};
.user {
.settings {
color: ${Colors.grey};
position: absolute;
top: 0px;

View File

@ -1,73 +1,66 @@
import { useContext, useState, useEffect } from 'react';
import { AppContext } from 'context/AppContext';
import { useNavigate } from "react-router-dom";
import { getNodeStatus } from 'api/getNodeStatus';
import { setNodeStatus } from 'api/setNodeStatus';
import { getNodeConfig } from 'api/getNodeConfig';
export function usePrompt() {
export function useAdmin() {
const [state, setState] = useState({
password: null,
password: '',
placeholder: '',
unclaimed: null,
busy: false,
});
const navigate = useNavigate();
const app = useContext(AppContext);
const updateState = (value) => {
setState((s) => ({ ...s, ...value }));
}
const checkStatus = async () => {
try {
let status = await getNodeStatus();
updateState({ unclaimed: status });
}
catch(err) {
console.log("failed to check node status");
}
};
useEffect(() => {
checkStatus();
// eslint-disable-next-line
const check = async () => {
try {
const unclaimed = await getNodeStatus();
updateState({ unclaimed });
}
catch(err) {
console.log("getNodeStatus failed");
}
};
check();
}, []);
const actions = {
setPassword: (password) => {
updateState({ password });
},
onUser: () => {
navUser: () => {
navigate('/login');
},
onLogin: async () => {
login: async () => {
if (!state.busy) {
try {
updateState({ busy: true });
if (state.unclaimed === true) {
await setNodeStatus(state.password);
}
const config = await getNodeConfig(state.password);
const status = await getNodeConfig(state.password);
const config = encodeURIComponent(JSON.stringify(config));
const pass= encodeURIComponent(state.password);
updateState({ busy: false });
return config;
navigate(`/dashboard?config=${status}&pass=${pass}`);
}
catch (err) {
catch(err) {
console.log(err);
updateState({ busy: false });
throw new Error("access denied");
throw new Error("login failed");
}
}
else {
throw new Error("operation in progress");
}
},
};
useEffect(() => {
}, [app, navigate])
}
return { state, actions };
}

View File

@ -14,7 +14,6 @@ export function useAccess() {
const app = useContext(AppContext);
const viewport = useContext(ViewportContext);
const updateState = (value) => {
setState((s) => ({ ...s, ...value }));
}

View File

@ -1,45 +0,0 @@
import { useAdmin } from './useAdmin.hook';
import { AdminWrapper } from './Admin.styled';
import { Prompt } from './prompt/Prompt';
import { Dashboard } from './dashboard/Dashboard';
import login from 'images/login.png'
export function Admin({ mode }) {
const { state, actions } = useAdmin();
return (
<AdminWrapper>
{ (state.display === 'large' || state.display === 'xlarge') && (
<div class="split-layout">
<div class="left">
<img class="splash" src={login} alt="Databag Splash" />
</div>
<div class="right">
{ state.token == null && (
<Prompt login={actions.login} />
)}
{ state.token != null && (
<Dashboard token={state.token} config={state.config} logout={actions.logout} />
)}
</div>
</div>
)}
{ (state.display === 'medium' || state.display === 'small') && (
<div class="full-layout">
<div class="center">
{ state.token == null && (
<Prompt login={actions.login} />
)}
{ state.token != null && (
<Dashboard token={state.token} config={state.config} logout={actions.logout} />
)}
</div>
</div>
)}
</AdminWrapper>
);
}

View File

@ -1,49 +0,0 @@
import styled from 'styled-components';
import Colors from 'constants/Colors';
export const AdminWrapper = styled.div`
height: 100%;
.full-layout {
width: 100%;
height: 100%;
padding: 8px;
.center {
width: 100%;
height: 100%;
border-radius: 4px;
background: ${Colors.formBackground};
display: flex;
align-items: center;
justify-content: center;
}
}
.split-layout {
display: flex;
flex-direction: row;
height: 100%;
.left {
width: 50%;
height: 100%;
padding: 32px;
.splash {
width: 100%;
height: 100%;
object-fit: contain;
}
}
.right {
width: 50%;
height: 100%;
background: ${Colors.formBackground};
display: flex;
align-items: center;
justify-content: center;
}
}
`;

View File

@ -1,69 +0,0 @@
import { Button, Modal, Form, Input } from 'antd';
import { UserOutlined, LockOutlined } from '@ant-design/icons';
import { PromptWrapper } from './Prompt.styled';
import { usePrompt } from './usePrompt.hook';
export function Prompt({ login }) {
const { state, actions } = usePrompt();
const setLogin = async () => {
try {
let config = await actions.onLogin();
login(state.password, config);
}
catch(err) {
Modal.error({
title: 'Prompt Error',
content: 'Please confirm your admin password.',
});
}
}
const keyDown = (e) => {
if (e.key === 'Enter') {
login()
}
}
const placeholder = () => {
if (state.unclaimed === true) {
return 'Set Access Token';
}
if (state.unclaimed === false) {
return 'Access Token';
}
return '';
}
return (
<PromptWrapper>
<div class="app-title">
<span>Databag</span>
<div class="user" onClick={() => actions.onUser()}>
<UserOutlined />
</div>
</div>
<div class="form-title">Admin Login</div>
<div class="form-form">
<Form name="basic" wrapperCol={{ span: 24, }}>
<Form.Item name="password">
<Input.Password placeholder={placeholder()} spellCheck="false" onChange={(e) => actions.setPassword(e.target.value)}
autocomplete="current-password" onKeyDown={(e) => keyDown(e)} prefix={<LockOutlined />} size="large" />
</Form.Item>
<div class="form-button">
<div class="form-login">
<Button type="primary" block onClick={setLogin} size="middle" loading={state.busy}>
Login
</Button>
</div>
</div>
</Form>
</div>
</PromptWrapper>
);
};

View File

@ -1,31 +0,0 @@
import { useContext, useState, useEffect } from 'react';
import { ViewportContext } from 'context/ViewportContext';
export function useAdmin() {
const [state, setState] = useState({
display: null,
});
const viewport = useContext(ViewportContext);
const updateState = (value) => {
setState((s) => ({ ...s, ...value }));
}
useEffect(() => {
updateState({ display: viewport.state.display });
}, [viewport]);
const actions = {
login: (token, config) => {
updateState({ token, config });
},
logout: () => {
updateState({ token: null, config: null });
},
};
return { state, actions };
}

View File

@ -1,6 +1,6 @@
import { DashboardWrapper, SettingsButton, AddButton, SettingsLayout, CreateLayout } from './Dashboard.styled';
import { Tooltip, Switch, Select, Button, Modal, Input, InputNumber, List } from 'antd';
import { SettingOutlined, CopyOutlined, UserAddOutlined, LogoutOutlined, ReloadOutlined } from '@ant-design/icons';
import { ExclamationCircleOutlined, SettingOutlined, CopyOutlined, UserAddOutlined, LogoutOutlined, ReloadOutlined } from '@ant-design/icons';
import { useDashboard } from './useDashboard.hook';
import { AccountItem } from './accountItem/AccountItem';
@ -18,7 +18,6 @@ export function Dashboard({ token, config, logout }) {
return (
<DashboardWrapper>
<div class="container">
<div class="header">
<div class="label">Accounts</div>
@ -36,6 +35,11 @@ export function Dashboard({ token, config, logout }) {
<SettingsButton type="text" size="small" icon={<LogoutOutlined />}
onClick={() => logout()}></SettingsButton>
</div>
{ state.errorMessage && (
<div class="alert">
<ExclamationCircleOutlined />
</div>
)}
<div class="add">
<AddButton type="text" size="large" icon={<UserAddOutlined />}
loading={state.createBusy} onClick={() => actions.setCreateLink()}></AddButton>
@ -62,6 +66,13 @@ export function Dashboard({ token, config, logout }) {
onClick={() => logout()}></SettingsButton>
</Tooltip>
</div>
{ state.errorMessage && (
<Tooltip placement="topRight" title={state.errorMessage}>
<div class="alert">
<ExclamationCircleOutlined />
</div>
</Tooltip>
)}
<div class="add">
<Tooltip placement="topRight" title="Create Account Link">
<AddButton type="text" size="large" icon={<UserAddOutlined />}

View File

@ -1,5 +1,6 @@
import { Button, Space } from 'antd';
import styled from 'styled-components';
import Colors from 'constants/Colors';
export const DashboardWrapper = styled.div`
display: flex;
@ -57,6 +58,12 @@ export const DashboardWrapper = styled.div`
justify-content: flex-end;
flex-grow: 1;
}
.alert {
display: flex;
align-items: center;
color: ${Colors.alert};
}
}
`;

View File

@ -1,11 +1,11 @@
import { useContext, useState, useEffect } from 'react';
import { useContext, useRef, useState, useEffect } from 'react';
import { setNodeConfig } from 'api/setNodeConfig';
import { getNodeAccounts } from 'api/getNodeAccounts';
import { removeAccount } from 'api/removeAccount';
import { addAccountCreate } from 'api/addAccountCreate';
import { ViewportContext } from 'context/ViewportContext';
import { useNavigation, useLocation } from 'react-router-dom';
export function useDashboard(token, config) {
export function useDashboard() {
const [state, setState] = useState({
domain: "",
@ -15,15 +15,18 @@ export function useDashboard(token, config) {
enableImage: null,
enableAudio: null,
enableVideo: null,
errorMessage: null,
createToken: null,
showSettings: false,
busy: false,
loading: false,
accounts: [],
createBusy: false,
showCreate: false,
busy: false,
accounts: [],
});
const viewport = useContext(ViewportContext);
const navigate = useNavigation();
const location = useLocation();
const token = useRef();
const updateState = (value) => {
setState((s) => ({ ...s, ...value }));
@ -32,15 +35,15 @@ export function useDashboard(token, config) {
const actions = {
setCreateLink: async () => {
if (!state.createBusy) {
updateState({ createBusy: true });
updateState({ busy: true });
try {
let create = await addAccountCreate(token)
const create = await addAccountCreate(token.current)
updateState({ createToken: create, showCreate: true });
}
catch (err) {
window.alert(err);
}
updateState({ createBusy: false });
updateState({ busy: false });
}
},
setShowCreate: (showCreate) => {
@ -79,23 +82,23 @@ export function useDashboard(token, config) {
updateState({ busy: true });
try {
const { domain, keyType, accountStorage, pushSupported, enableImage, enableAudio, enableVideo } = state;
await setNodeConfig(token,
{ domain, accountStorage: accountStorage * 1073741824,
keyType, enableImage, enableAudio, enableVideo, pushSupported });
updateState({ showSettings: false });
const storage = accountStorage * 1073741824;
const config = { domain, accountStorage: storage, keyType, enableImage, enableAudio, enableVideo, pushSupported };
await setNodeConfig(token.current, config);
updateState({ busy: false, showSettings: false });
}
catch(err) {
console.log(err);
window.alert(err);
updateState({ busy: false });
throw new Error("failed to set settings");
}
updateState({ busy: false });
}
},
getAccounts: async () => {
if (!state.loading) {
updateState({ loading: true });
if (!state.busy) {
updateState({ busy: true });
try {
let accounts = await getNodeAccounts(token);
let accounts = await getNodeAccounts(token.current);
accounts.sort((a, b) => {
if (a.handle < b.handle) {
return -1;
@ -105,29 +108,32 @@ export function useDashboard(token, config) {
}
return 0;
});
updateState({ accounts });
updateState({ busy: false, accounts });
}
catch(err) {
console.log(err);
window.alert(err);
updateState({ busy: false, errorMessage: 'failed to load accounts' });
}
updateState({ loading: false });
}
},
};
useEffect(() => {
updateState({ display: viewport.state.display });
}, [viewport]);
useEffect(() => {
const { accountStorage, domain, keyType, pushSupported, enableImage, enableAudio, enableVideo } = config;
updateState({ domain, accountStorage: Math.ceil(accountStorage / 1073741824), keyType,
enableImage, enableAudio, enableVideo, pushSupported });
actions.getAccounts();
// eslint-disable-next-line
}, [config]);
const params = new URLSearchParams(location);
const pass = params.get("pass");
if (!pass) {
navigate('/admin');
}
else {
token.current = pass;
const config = JSON.parse(params.get("config"));
const { storage, domain, keyType, pushSupported, enableImage, enableAudio, enableVideo } = config;
const accountStorage = Math.ceil(accountStorage / 1073741824);
updateState({ domain, accountStorage, keyType, enableImage, enableAudio, enableVideo, pushSupported });
actions.getAccounts();
}
}, []);
return { state, actions };
}