mirror of
https://github.com/balzack/databag.git
synced 2025-02-12 03:29:16 +00:00
restoring admin dashboard from main
This commit is contained in:
parent
9db4c93602
commit
5f69780d81
@ -1,20 +1,49 @@
|
||||
import { UserOutlined } from '@ant-design/icons';
|
||||
import { AdminWrapper } from './Admin.styled';
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { AppContext } from 'context/AppContext';
|
||||
import { ViewportContext } from 'context/ViewportContext';
|
||||
import { useAdmin } from './useAdmin.hook';
|
||||
import { AdminWrapper } from './Admin.styled';
|
||||
import { Prompt } from './prompt/Prompt';
|
||||
import { Dashboard } from './dashboard/Dashboard';
|
||||
|
||||
export function Admin() {
|
||||
import login from 'images/login.png'
|
||||
|
||||
export function Admin({ mode }) {
|
||||
|
||||
const { state, actions } = useAdmin();
|
||||
|
||||
return (
|
||||
<AdminWrapper>
|
||||
<div class="app-title">
|
||||
<span>Databag</span>
|
||||
<div class="user" onClick={() => actions.onUser()}>
|
||||
<UserOutlined />
|
||||
{ (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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
@ -2,30 +2,48 @@ import styled from 'styled-components';
|
||||
import Colors from 'constants/Colors';
|
||||
|
||||
export const AdminWrapper = styled.div`
|
||||
max-width: 400px;
|
||||
width: 90%;
|
||||
height: 90%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
.app-title {
|
||||
font-size: 24px;
|
||||
.full-layout {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 8px;
|
||||
|
||||
.center {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
background: ${Colors.formBackground};
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
color: ${Colors.grey};
|
||||
}
|
||||
}
|
||||
|
||||
.user {
|
||||
color: ${Colors.grey};
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
margin: 16px;
|
||||
.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;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
|
91
net/web/src/admin/dashboard/Dashboard.jsx
Normal file
91
net/web/src/admin/dashboard/Dashboard.jsx
Normal file
@ -0,0 +1,91 @@
|
||||
import { DashboardWrapper, SettingsButton, AddButton, SettingsLayout, CreateLayout } from './Dashboard.styled';
|
||||
import { Tooltip, Button, Modal, Input, InputNumber, Space, List } from 'antd';
|
||||
import { SettingOutlined, CopyOutlined, UserAddOutlined, LogoutOutlined, ReloadOutlined } from '@ant-design/icons';
|
||||
import { useDashboard } from './useDashboard.hook';
|
||||
import { AccountItem } from './accountItem/AccountItem';
|
||||
|
||||
export function Dashboard({ token, config, logout }) {
|
||||
|
||||
const { state, actions } = useDashboard(token, config);
|
||||
|
||||
const onClipboard = (value) => {
|
||||
navigator.clipboard.writeText(value);
|
||||
};
|
||||
|
||||
const createLink = () => {
|
||||
return window.location.origin + '/#/create?add=' + state.createToken;
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardWrapper>
|
||||
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<div class="label">Accounts</div>
|
||||
<div class="settings">
|
||||
<Tooltip placement="topRight" title="Reload Accounts">
|
||||
<SettingsButton type="text" size="small" icon={<ReloadOutlined />}
|
||||
onClick={() => actions.getAccounts()}></SettingsButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="settings">
|
||||
<Tooltip placement="topRight" title="Configure Server">
|
||||
<SettingsButton type="text" size="small" icon={<SettingOutlined />}
|
||||
onClick={() => actions.setShowSettings(true)}></SettingsButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="settings">
|
||||
<Tooltip placement="topRight" title="Logout">
|
||||
<SettingsButton type="text" size="small" icon={<LogoutOutlined />}
|
||||
onClick={() => logout()}></SettingsButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="add">
|
||||
<Tooltip placement="topRight" title="Create Account Link">
|
||||
<AddButton type="text" size="large" icon={<UserAddOutlined />}
|
||||
loading={state.createBusy} onClick={() => actions.setCreateLink()}></AddButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="body">
|
||||
<List
|
||||
locale={{ emptyText: '' }}
|
||||
itemLayout="horizontal"
|
||||
dataSource={state.accounts}
|
||||
loading={state.loading}
|
||||
renderItem={item => (<AccountItem token={token} item={item}
|
||||
remove={actions.removeAccount}/>)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Modal title="Settings" visible={state.showSettings} centered
|
||||
okText="Save" onOk={() => actions.setSettings()} onCancel={() => actions.setShowSettings(false)}>
|
||||
<SettingsLayout direction="vertical">
|
||||
<div class="host">
|
||||
<div>Federated Host: </div>
|
||||
<Input placeholder="domain:port/app" onChange={(e) => actions.setHost(e.target.value)}
|
||||
value={state.host} />
|
||||
</div>
|
||||
<div class="storage">
|
||||
<div>Storage Limit (GB) / Account: </div>
|
||||
<InputNumber defaultValue={8} onChange={(e) => actions.setStorage(e)}
|
||||
placeholder="0 for unrestricted" value={state.storage} />
|
||||
</div>
|
||||
</SettingsLayout>
|
||||
</Modal>
|
||||
<Modal title="Create Account Link" visible={state.showCreate} centered width="fitContent"
|
||||
footer={[ <Button type="primary" onClick={() => actions.setShowCreate(false)}>OK</Button> ]}
|
||||
onCancel={() => actions.setShowCreate(false)}>
|
||||
<CreateLayout>
|
||||
<div>{createLink()}</div>
|
||||
<Button icon={<CopyOutlined />} size="small"
|
||||
onClick={() => onClipboard(createLink())}
|
||||
/>
|
||||
</CreateLayout>
|
||||
</Modal>
|
||||
</DashboardWrapper>
|
||||
);
|
||||
}
|
||||
|
89
net/web/src/admin/dashboard/Dashboard.styled.js
Normal file
89
net/web/src/admin/dashboard/Dashboard.styled.js
Normal file
@ -0,0 +1,89 @@
|
||||
import { Button, Space } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const DashboardWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: 16px;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
border-radius: 4px;
|
||||
max-width: 100%;
|
||||
max-height: 80%;
|
||||
|
||||
.header {
|
||||
color: #444444;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size: 20px;
|
||||
border-bottom: 1px solid #aaaaaa;
|
||||
}
|
||||
|
||||
.body {
|
||||
padding-top: 8px;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
border-bottom: 1px solid #aaaaaa;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.label {
|
||||
padding-right: 8px;
|
||||
padding-left: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.settings {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.add {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const AddButton = styled(Button)`
|
||||
color: #1890ff;
|
||||
`;
|
||||
|
||||
export const SettingsButton = styled(Button)`
|
||||
color: #1890ff;
|
||||
`;
|
||||
|
||||
export const SettingsLayout = styled(Space)`
|
||||
width: 100%;
|
||||
|
||||
.host {
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.storage {
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
`;
|
||||
|
||||
export const CreateLayout = styled(Space)`
|
||||
white-space: nowrap;
|
||||
`
|
69
net/web/src/admin/dashboard/accountItem/AccountItem.jsx
Normal file
69
net/web/src/admin/dashboard/accountItem/AccountItem.jsx
Normal file
@ -0,0 +1,69 @@
|
||||
import { Logo } from 'logo/Logo';
|
||||
import { AccountItemWrapper, AccessLayout, DeleteButton, EnableButton, DisableButton, ResetButton } from './AccountItem.styled';
|
||||
import { useAccountItem } from './useAccountItem.hook';
|
||||
import { CopyOutlined, UserDeleteOutlined, UnlockOutlined, CloseCircleOutlined, CheckCircleOutlined } from '@ant-design/icons';
|
||||
import { Modal, Tooltip, Button } from 'antd';
|
||||
|
||||
export function AccountItem({ token, item, remove }) {
|
||||
|
||||
const { state, actions } = useAccountItem(token, item, remove);
|
||||
|
||||
const onClipboard = (value) => {
|
||||
navigator.clipboard.writeText(value);
|
||||
};
|
||||
|
||||
const Enable = () => {
|
||||
if (state.disabled) {
|
||||
return (
|
||||
<Tooltip placement="topLeft" title="Enable Account">
|
||||
<EnableButton type="text" size="large" icon={<CheckCircleOutlined />}
|
||||
loading={state.statusBusy} onClick={() => actions.setStatus(false)}></EnableButton>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Tooltip placement="topLeft" title="Disable Account">
|
||||
<DisableButton type="text" size="large" icon={<CloseCircleOutlined />}
|
||||
loading={state.statusBusy} onClick={() => actions.setStatus(true)}></DisableButton>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
const accessLink = () => {
|
||||
return window.location.origin + '/#/login?access=' + state.accessToken;
|
||||
};
|
||||
|
||||
return (
|
||||
<AccountItemWrapper>
|
||||
<div class="avatar">
|
||||
<Logo url={state.imageUrl} width={32} height={32} radius={4} />
|
||||
</div>
|
||||
<div class={state.activeClass}>
|
||||
<div class="handle">{ state.handle }</div>
|
||||
<div class="guid">{ state.guid }</div>
|
||||
</div>
|
||||
<div class="control">
|
||||
<Tooltip placement="topLeft" title="Account Login Link">
|
||||
<ResetButton type="text" size="large" icon={<UnlockOutlined />}
|
||||
loading={state.accessBusy} onClick={() => actions.setAccessLink()}></ResetButton>
|
||||
</Tooltip>
|
||||
<Enable />
|
||||
<Tooltip placement="topLeft" title="Delete Account">
|
||||
<DeleteButton type="text" size="large" icon={<UserDeleteOutlined />}
|
||||
loading={state.removeBusy} onClick={() => actions.remove()}></DeleteButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Modal title="Access Account Link" visible={state.showAccess} centered width="fitContent"
|
||||
footer={[ <Button type="primary" onClick={() => actions.setShowAccess(false)}>OK</Button> ]}
|
||||
onCancel={() => actions.setShowAccess(false)}>
|
||||
<AccessLayout>
|
||||
<div>{accessLink()}</div>
|
||||
<Button icon={<CopyOutlined />} size="small"
|
||||
onClick={() => onClipboard(accessLink())}
|
||||
/>
|
||||
</AccessLayout>
|
||||
</Modal>
|
||||
</AccountItemWrapper>
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,87 @@
|
||||
import { Space, Button } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const AccountItemWrapper = styled.div`
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
border-bottom: 1px solid #eeeeee;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.inactive {
|
||||
padding-left: 16px;
|
||||
padding-right: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
color: #cccccc;
|
||||
}
|
||||
|
||||
.active {
|
||||
padding-left: 16px;
|
||||
padding-right: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.handle {
|
||||
font-size: 0.8em;
|
||||
font-weight: bold;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.guid {
|
||||
font-size: 0.8em;
|
||||
font-weight: bold;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.control {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
`;
|
||||
|
||||
export const EnableButton = styled(Button)`
|
||||
color: orange;
|
||||
`;
|
||||
|
||||
export const DisableButton = styled(Button)`
|
||||
color: orange;
|
||||
`;
|
||||
|
||||
export const ResetButton = styled(Button)`
|
||||
color: #1890ff;
|
||||
`;
|
||||
|
||||
export const DeleteButton = styled(Button)`
|
||||
color: red;
|
||||
`
|
||||
|
||||
export const AccessLayout = styled(Space)`
|
||||
white-space: nowrap;
|
||||
`
|
@ -0,0 +1,78 @@
|
||||
import { useContext, useState, useEffect } from 'react';
|
||||
import { getAccountImageUrl } from 'api/getAccountImageUrl';
|
||||
import { setAccountStatus } from 'api/setAccountStatus';
|
||||
import { addAccountAccess } from 'api/addAccountAccess';
|
||||
|
||||
export function useAccountItem(token, item, remove) {
|
||||
|
||||
const [state, setState] = useState({
|
||||
statusBusy: false,
|
||||
removeBusy: false,
|
||||
accessBusy: false,
|
||||
showAccess: false,
|
||||
});
|
||||
|
||||
const updateState = (value) => {
|
||||
setState((s) => ({ ...s, ...value }));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
updateState({
|
||||
disabled: item?.disabled,
|
||||
activeClass: item?.disabled ? 'inactive' : 'active',
|
||||
accountId: item?.accountId,
|
||||
name: item?.name,
|
||||
guid: item?.guid,
|
||||
handle: item?.handle,
|
||||
imageUrl: item?.imageSet ? getAccountImageUrl(token, item?.accountId) : null,
|
||||
});
|
||||
}, [token, item]);
|
||||
|
||||
const actions = {
|
||||
setAccessLink: async () => {
|
||||
if (!state.accessBusy) {
|
||||
updateState({ accessBusy: true });
|
||||
try {
|
||||
let access = await addAccountAccess(token, item.accountId);
|
||||
updateState({ accessToken: access, showAccess: true });
|
||||
}
|
||||
catch (err) {
|
||||
window.alert(err);
|
||||
}
|
||||
updateState({ accessBusy: false });
|
||||
}
|
||||
},
|
||||
setShowAccess: (showAccess) => {
|
||||
updateState({ showAccess });
|
||||
},
|
||||
remove: async () => {
|
||||
if (!state.removeBusy) {
|
||||
updateState({ removeBusy: true });
|
||||
try {
|
||||
await remove(state.accountId);
|
||||
}
|
||||
catch(err) {
|
||||
console.log(err);
|
||||
window.alert(err);
|
||||
}
|
||||
updateState({ removeBusy: false });
|
||||
}
|
||||
},
|
||||
setStatus: async (disabled) => {
|
||||
if (!state.statusBusy) {
|
||||
updateState({ statusBusy: true });
|
||||
try {
|
||||
await setAccountStatus(token, item.accountId, disabled);
|
||||
updateState({ disabled, activeClass: disabled ? 'inactive' : 'active' });
|
||||
}
|
||||
catch(err) {
|
||||
console.log(err);
|
||||
window.alert(err);
|
||||
}
|
||||
updateState({ statusBusy: false });
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
return { state, actions };
|
||||
}
|
105
net/web/src/admin/dashboard/useDashboard.hook.js
Normal file
105
net/web/src/admin/dashboard/useDashboard.hook.js
Normal file
@ -0,0 +1,105 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { setNodeConfig } from 'api/setNodeConfig';
|
||||
import { getNodeAccounts } from 'api/getNodeAccounts';
|
||||
import { removeAccount } from 'api/removeAccount';
|
||||
import { addAccountCreate } from 'api/addAccountCreate';
|
||||
|
||||
export function useDashboard(token, config) {
|
||||
|
||||
const [state, setState] = useState({
|
||||
host: "",
|
||||
storage: null,
|
||||
showSettings: false,
|
||||
busy: false,
|
||||
loading: false,
|
||||
accounts: [],
|
||||
createBusy: false,
|
||||
showCreate: false,
|
||||
});
|
||||
|
||||
const updateState = (value) => {
|
||||
setState((s) => ({ ...s, ...value }));
|
||||
}
|
||||
|
||||
const actions = {
|
||||
setCreateLink: async () => {
|
||||
if (!state.createBusy) {
|
||||
updateState({ createBusy: true });
|
||||
try {
|
||||
let create = await addAccountCreate(token)
|
||||
updateState({ createToken: create, showCreate: true });
|
||||
}
|
||||
catch (err) {
|
||||
window.alert(err);
|
||||
}
|
||||
updateState({ createBusy: false });
|
||||
}
|
||||
},
|
||||
setShowCreate: (showCreate) => {
|
||||
updateState({ showCreate });
|
||||
},
|
||||
removeAccount: async (accountId) => {
|
||||
await removeAccount(token, accountId);
|
||||
actions.getAccounts();
|
||||
},
|
||||
setHost: (value) => {
|
||||
updateState({ host: value });
|
||||
},
|
||||
setStorage: (value) => {
|
||||
updateState({ storage: value });
|
||||
},
|
||||
setShowSettings: (value) => {
|
||||
updateState({ showSettings: value });
|
||||
},
|
||||
setSettings: async () => {
|
||||
if (!state.busy) {
|
||||
updateState({ busy: true });
|
||||
try {
|
||||
await setNodeConfig(token,
|
||||
{ ...state.config, domain: state.host, accountStorage: state.storage * 1073741824 });
|
||||
updateState({ showSettings: false });
|
||||
}
|
||||
catch(err) {
|
||||
console.log(err);
|
||||
window.alert(err);
|
||||
}
|
||||
updateState({ busy: false });
|
||||
}
|
||||
},
|
||||
getAccounts: async () => {
|
||||
if (!state.loading) {
|
||||
updateState({ loading: true });
|
||||
try {
|
||||
let accounts = await getNodeAccounts(token);
|
||||
accounts.sort((a, b) => {
|
||||
if (a.handle < b.handle) {
|
||||
return -1;
|
||||
}
|
||||
if (a.handle > b.handle) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
updateState({ accounts });
|
||||
}
|
||||
catch(err) {
|
||||
console.log(err);
|
||||
window.alert(err);
|
||||
}
|
||||
updateState({ loading: false });
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let storage = config.accountStorage / 1073741824;
|
||||
if (storage > 1) {
|
||||
storage = Math.ceil(storage);
|
||||
}
|
||||
updateState({ host: config.domain, storage: storage });
|
||||
actions.getAccounts();
|
||||
}, []);
|
||||
|
||||
return { state, actions };
|
||||
}
|
||||
|
59
net/web/src/admin/prompt/Prompt.jsx
Normal file
59
net/web/src/admin/prompt/Prompt.jsx
Normal file
@ -0,0 +1,59 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
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="Admin Password" 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>
|
||||
);
|
||||
};
|
||||
|
58
net/web/src/admin/prompt/Prompt.styled.js
Normal file
58
net/web/src/admin/prompt/Prompt.styled.js
Normal file
@ -0,0 +1,58 @@
|
||||
import styled from 'styled-components';
|
||||
import Colors from 'constants/Colors';
|
||||
|
||||
export const PromptWrapper = styled.div`
|
||||
max-width: 400px;
|
||||
width: 90%;
|
||||
height: 90%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.app-title {
|
||||
font-size: 24px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
color: ${Colors.grey};
|
||||
|
||||
.user {
|
||||
color: ${Colors.grey};
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
margin: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-title {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.form-form {
|
||||
flex: 2;
|
||||
|
||||
.form-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.form-login {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-submit {
|
||||
background-color: #444444;
|
||||
}
|
||||
`;
|
||||
|
||||
|
75
net/web/src/admin/prompt/usePrompt.hook.js
Normal file
75
net/web/src/admin/prompt/usePrompt.hook.js
Normal file
@ -0,0 +1,75 @@
|
||||
import { useContext, useState, useEffect } from 'react';
|
||||
import { AppContext } from 'context/AppContext';
|
||||
import { useNavigate, useLocation } from "react-router-dom";
|
||||
import { getNodeStatus } from 'api/getNodeStatus';
|
||||
import { setNodeStatus } from 'api/setNodeStatus';
|
||||
import { getNodeConfig } from 'api/getNodeConfig';
|
||||
|
||||
export function usePrompt() {
|
||||
|
||||
const [state, setState] = useState({
|
||||
password: null,
|
||||
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({ uncliamed: status });
|
||||
}
|
||||
catch(err) {
|
||||
console.log("failed to check node status");
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
checkStatus();
|
||||
}, []);
|
||||
|
||||
const actions = {
|
||||
setPassword: (password) => {
|
||||
updateState({ password });
|
||||
},
|
||||
onUser: () => {
|
||||
navigate('/login');
|
||||
},
|
||||
onLogin: async () => {
|
||||
if (!state.busy) {
|
||||
try {
|
||||
updateState({ busy: true });
|
||||
if (state.unclaimed === true) {
|
||||
await setNodeStatus(state.password);
|
||||
return await getNodeConfig(state.password);
|
||||
}
|
||||
else {
|
||||
return await getNodeConfig(state.password);
|
||||
}
|
||||
updateState({ busy: false });
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
updateState({ busy: false });
|
||||
throw new Error("access denied");
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new Error("operation in progress");
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
}, [app, navigate])
|
||||
|
||||
return { state, actions };
|
||||
}
|
||||
|
@ -1,22 +1,28 @@
|
||||
import { useContext, useState } from 'react';
|
||||
import { AppContext } from 'context/AppContext';
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useContext, useState, useEffect } from 'react';
|
||||
import { ViewportContext } from 'context/ViewportContext';
|
||||
|
||||
export function useAdmin() {
|
||||
|
||||
const [state, setState] = useState({
|
||||
display: null,
|
||||
});
|
||||
|
||||
const navigate = useNavigate();
|
||||
const app = useContext(AppContext);
|
||||
const viewport = useContext(ViewportContext);
|
||||
|
||||
const updateState = (value) => {
|
||||
setState((s) => ({ ...s, ...value }));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
updateState({ display: viewport.state.display });
|
||||
}, [viewport]);
|
||||
|
||||
const actions = {
|
||||
onUser: () => {
|
||||
navigate('/login');
|
||||
login: (token, config) => {
|
||||
updateState({ token, config });
|
||||
},
|
||||
logout: () => {
|
||||
updateState({ token: null, config: null });
|
||||
},
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user