mirror of
https://github.com/balzack/databag.git
synced 2025-03-13 00:50:03 +00:00
refactor of admin screen
This commit is contained in:
parent
d2fc9de4af
commit
625f3a31de
@ -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={
|
||||
|
@ -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>
|
||||
)}
|
||||
|
58
net/web/src/access/admin/Admin.jsx
Normal file
58
net/web/src/access/admin/Admin.jsx
Normal 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>
|
||||
);
|
||||
};
|
||||
|
@ -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;
|
@ -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 };
|
||||
}
|
@ -14,7 +14,6 @@ export function useAccess() {
|
||||
const app = useContext(AppContext);
|
||||
const viewport = useContext(ViewportContext);
|
||||
|
||||
|
||||
const updateState = (value) => {
|
||||
setState((s) => ({ ...s, ...value }));
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
`;
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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 };
|
||||
}
|
||||
|
@ -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 />}
|
@ -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};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -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 };
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user