mirror of
https://github.com/balzack/databag.git
synced 2025-02-12 03:29:16 +00:00
restructuring webapp
This commit is contained in:
parent
58269ed541
commit
dc2ca2bc68
@ -90,11 +90,11 @@ func Status(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
case <-ticker.C:
|
||||
conn.SetWriteDeadline(time.Now().Add(15 * time.Second))
|
||||
if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
|
||||
ErrMsg(err)
|
||||
return
|
||||
}
|
||||
conn.SetReadDeadline(time.Now().Add(15 * time.Second))
|
||||
case <-wsExit:
|
||||
LogMsg("exiting server")
|
||||
wsExit<-true
|
||||
|
@ -10,6 +10,7 @@
|
||||
"base-64": "^1.0.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-router-dom": "^6.2.2",
|
||||
"react-scripts": "5.0.0",
|
||||
"web-vitals": "^2.1.0"
|
||||
},
|
||||
|
@ -1,256 +1,19 @@
|
||||
import React, { useState, useEffect, useRef } from 'react'
|
||||
import React, { useContext, useState, useEffect, useRef } from 'react'
|
||||
import login from './login.png';
|
||||
import { Input, Button } from 'antd';
|
||||
import { UserOutlined, LockOutlined } from '@ant-design/icons';
|
||||
import 'antd/dist/antd.css';
|
||||
|
||||
var base64 = require('base-64');
|
||||
|
||||
const FETCH_TIMEOUT = 15000;
|
||||
|
||||
function checkResponse(response) {
|
||||
if(response.status >= 400 && response.status < 600) {
|
||||
throw new Error(response.url + " failed");
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchWithTimeout(url, options) {
|
||||
return Promise.race([
|
||||
fetch(url, options).catch(err => { throw new Error(url + ' failed'); }),
|
||||
new Promise((_, reject) => setTimeout(() => reject(new Error(url + ' timeout')), FETCH_TIMEOUT))
|
||||
]);
|
||||
}
|
||||
|
||||
async function getAvailable() {
|
||||
let available = await fetchWithTimeout("/account/available", { method: 'GET', timeout: FETCH_TIMEOUT })
|
||||
checkResponse(available)
|
||||
return await available.json()
|
||||
}
|
||||
|
||||
async function getUsername(name: string) {
|
||||
let available = await fetchWithTimeout('/account/username?name=' + encodeURIComponent(name), { method: 'GET', timeout: FETCH_TIMEOUT })
|
||||
checkResponse(available)
|
||||
return await available.json()
|
||||
}
|
||||
|
||||
async function setLogin(username: string, password: string) {
|
||||
let headers = new Headers()
|
||||
headers.append('Authorization', 'Basic ' + base64.encode(username + ":" + password));
|
||||
let app = { Name: "indicom", Description: "decentralized communication" }
|
||||
let login = await fetchWithTimeout('/account/apps', { method: 'POST', timeout: FETCH_TIMEOUT, body: JSON.stringify(app), headers: headers })
|
||||
checkResponse(login)
|
||||
return await login.json()
|
||||
}
|
||||
|
||||
async function createAccount(username: string, password: string) {
|
||||
let headers = new Headers()
|
||||
headers.append('Credentials', 'Basic ' + base64.encode(username + ":" + password));
|
||||
let profile = await fetchWithTimeout("/account/profile", { method: 'POST', timeout: FETCH_TIMEOUT, headers: headers })
|
||||
checkResponse(profile);
|
||||
return await profile.json()
|
||||
}
|
||||
import { BrowserRouter as Router, Routes, Route, useHistory } from "react-router-dom";
|
||||
import { AppContextProvider } from './context/AppContext';
|
||||
import { Root } from './components/Root';
|
||||
|
||||
function App() {
|
||||
const [available, setAvailable] = useState(false)
|
||||
const [username, setUsername] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const [confirmed, setConfirmed] = useState('')
|
||||
const [mode, setMode] = useState('')
|
||||
const [creatable, setCreatable] = useState(false)
|
||||
const [conflict, setConflict] = useState('')
|
||||
const [token, setToken] = useState('')
|
||||
const debounce = useRef(null)
|
||||
const ws = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
let access = localStorage.getItem("access")
|
||||
console.log("ACCESS", access)
|
||||
|
||||
if (access == null) {
|
||||
setMode('login')
|
||||
getAvailable().then(a => {
|
||||
setAvailable(a > 0)
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
else {
|
||||
setMode('logout')
|
||||
connectStatus(access)
|
||||
}
|
||||
|
||||
}, [])
|
||||
|
||||
const usernameSet = (name) => {
|
||||
setCreatable(false)
|
||||
setUsername(name)
|
||||
clearTimeout(debounce.current)
|
||||
debounce.current = setTimeout(async () => {
|
||||
let valid = await getUsername(name)
|
||||
setCreatable(valid)
|
||||
if (!valid) {
|
||||
setConflict('not available')
|
||||
} else {
|
||||
setConflict('')
|
||||
}
|
||||
setCreatable(await getUsername(name))
|
||||
}, 500)
|
||||
}
|
||||
|
||||
const connectStatus = (access: string) => {
|
||||
ws.current = new WebSocket("wss://" + window.location.host + "/status");
|
||||
ws.current.onmessage = (ev) => {
|
||||
console.log(ev)
|
||||
}
|
||||
ws.current.onclose = () => {
|
||||
console.log('ws close')
|
||||
setTimeout(() => {
|
||||
if (ws.current != null) {
|
||||
ws.current.onmessage = () => {}
|
||||
ws.current.onclose = () => {}
|
||||
ws.current.onopen = () => {}
|
||||
ws.current.onerror = () => {}
|
||||
connectStatus(access)
|
||||
}
|
||||
}, 2000)
|
||||
}
|
||||
ws.current.onopen = () => {
|
||||
ws.current.send(JSON.stringify({ AppToken: access }))
|
||||
}
|
||||
ws.current.error = () => {
|
||||
console.log('ws error')
|
||||
}
|
||||
localStorage.setItem("access", access)
|
||||
}
|
||||
|
||||
const Logout = () => {
|
||||
if (mode === 'logout') {
|
||||
return <Button type="primary" onClick={onLogout} style={{ alignSelf: 'center', marginTop: '16px', width: '33%' }}>Sign Out</Button>
|
||||
}
|
||||
return <></>
|
||||
}
|
||||
|
||||
const Link = () => {
|
||||
if (mode === 'create') {
|
||||
return <Button type="link" onClick={() => setMode('login')} disabled={!available} style={{ marginTop: '4px' }}>Account Sign In</Button>
|
||||
}
|
||||
if (mode === 'login') {
|
||||
return <Button type="link" onClick={() => setMode('create')} disabled={!available} style={{ marginTop: '4px' }}>Create Account</Button>
|
||||
}
|
||||
return <></>
|
||||
}
|
||||
|
||||
const canLogin = () => {
|
||||
return username !== '' && password !== ''
|
||||
}
|
||||
|
||||
const canCreate = () => {
|
||||
return username !== '' && password !== '' && confirmed === password && creatable
|
||||
}
|
||||
|
||||
const onLogin = async () => {
|
||||
try {
|
||||
let access = await setLogin(username, password)
|
||||
connectStatus(access)
|
||||
setMode('logout')
|
||||
console.log(access)
|
||||
}
|
||||
catch(err) {
|
||||
window.alert("failed to sign into account")
|
||||
}
|
||||
}
|
||||
|
||||
const onCreate = async () => {
|
||||
try {
|
||||
let profile = await createAccount(username, password)
|
||||
setMode('created')
|
||||
try {
|
||||
let access = await setLogin(username, password)
|
||||
connectStatus(access)
|
||||
setMode('logout')
|
||||
console.log(access)
|
||||
}
|
||||
catch(err) {
|
||||
window.alert("failed to sign into account")
|
||||
}
|
||||
}
|
||||
catch(err) {
|
||||
window.alert("failed to create account")
|
||||
}
|
||||
}
|
||||
|
||||
const onLogout = () => {
|
||||
ws.current.onclose = () => {}
|
||||
ws.current.close(1000, "bye")
|
||||
ws.current = null
|
||||
localStorage.removeItem("access")
|
||||
setMode('login')
|
||||
}
|
||||
|
||||
if (mode === 'login') {
|
||||
return (
|
||||
<div style={{ width: '100%', height: '100vh', backgroundColor: '#8fbea7' }}>
|
||||
<img src={login} alt="" style={{ position: 'absolute', width: '33%', bottom: 0, right: 0 }}/>
|
||||
<div style={{ position: 'absolute', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', top: 0, left: 0, width: '100%', height: '67%' }}>
|
||||
<div style={{ backgroundColor: '#ffffff', display: 'flex', flexDirection: 'column', padding: '16px', borderRadius: '8px', width: '500px' }}>
|
||||
<div style={{ textAlign: 'center', fontSize: '24px', fontWeight: 'bold', color: '#555555' }}>indicom</div>
|
||||
<div style={{ fontSize: '12px', display: 'flex', borderBottom: '1px solid black', color: '#444444', paddingLeft: '16px', paddingRight: '16px' }}>
|
||||
<span style={{ textAlign: 'center', width: '100%' }}>Communication for the Decentralized Web</span>
|
||||
</div>
|
||||
<Input size="large" spellCheck="false" onChange={(e) => usernameSet(e.target.value)} value={username} placeholder="username" prefix={<UserOutlined />} style={{ marginTop: '16px' }} />
|
||||
<Input.Password size="large" onChange={(e) => setPassword(e.target.value)} value={password} placeholder="password" prefix={<LockOutlined />} style={{ marginTop: '16px' }} />
|
||||
<Button type="primary" onClick={onLogin} disabled={!canLogin()} style={{ alignSelf: 'center', marginTop: '16px', width: '33%' }}>Sign In</Button>
|
||||
</div>
|
||||
<Link />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
if (mode === 'create') {
|
||||
return (
|
||||
<div style={{ width: '100%', height: '100vh', backgroundColor: '#8fbea7' }}>
|
||||
<img src={login} alt="" style={{ position: 'absolute', width: '33%', bottom: 0, right: 0 }}/>
|
||||
<div style={{ position: 'absolute', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', top: 0, left: 0, width: '100%', height: '67%' }}>
|
||||
<div style={{ backgroundColor: '#ffffff', display: 'flex', flexDirection: 'column', padding: '16px', borderRadius: '8px', width: '500px' }}>
|
||||
<div style={{ textAlign: 'center', fontSize: '24px', fontWeight: 'bold', color: '#555555' }}>indicom</div>
|
||||
<div style={{ fontSize: '12px', display: 'flex', borderBottom: '1px solid black', color: '#444444', paddingLeft: '16px', paddingRight: '16px' }}>
|
||||
<span style={{ textAlign: 'center', width: '100%' }}>Communication for the Decentralized Web</span>
|
||||
</div>
|
||||
<Input size="large" spellCheck="false" addonAfter={conflict} onChange={(e) => usernameSet(e.target.value)} value={username} placeholder="username" prefix={<UserOutlined />} style={{ marginTop: '16px' }} />
|
||||
<Input.Password size="large" onChange={(e) => setPassword(e.target.value)} value={password} placeholder="password" prefix={<LockOutlined />} style={{ marginTop: '16px' }} />
|
||||
<Input.Password size="large" onChange={(e) => setConfirmed(e.target.value)} value={confirmed} placeholder="confirm password" prefix={<LockOutlined />} style={{ marginTop: '16px' }} />
|
||||
<Button type="primary" onClick={onCreate} disabled={!canCreate()} style={{ alignSelf: 'center', marginTop: '16px', width: '33%' }}>Create Account</Button>
|
||||
</div>
|
||||
<Link />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
if (mode === 'logout') {
|
||||
return (
|
||||
<div style={{ width: '100%', height: '100vh', backgroundColor: '#8fbea7' }}>
|
||||
<img src={login} alt="" style={{ position: 'absolute', width: '33%', bottom: 0, right: 0 }}/>
|
||||
<div style={{ position: 'absolute', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', top: 0, left: 0, width: '100%', height: '67%' }}>
|
||||
<div style={{ backgroundColor: '#ffffff', display: 'flex', flexDirection: 'column', padding: '16px', borderRadius: '8px', width: '500px' }}>
|
||||
<div style={{ textAlign: 'center', fontSize: '24px', fontWeight: 'bold', color: '#555555' }}>indicom</div>
|
||||
<div style={{ fontSize: '12px', display: 'flex', borderBottom: '1px solid black', color: '#444444', paddingLeft: '16px', paddingRight: '16px' }}>
|
||||
<span style={{ textAlign: 'center', width: '100%' }}>Communication for the Decentralized Web</span>
|
||||
</div>
|
||||
<Button type="primary" onClick={onLogout} style={{ alignSelf: 'center', marginTop: '16px', width: '33%' }}>Sign Out</Button>
|
||||
</div>
|
||||
<Link />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
else {
|
||||
return (
|
||||
<div style={{ width: '100%', height: '100vh', backgroundColor: '#8fbea7' }}>
|
||||
<img src={login} alt="" style={{ position: 'absolute', width: '33%', bottom: 0, right: 0 }}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return <></>
|
||||
<AppContextProvider>
|
||||
<Root />
|
||||
</AppContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
44
net/web/src/components/Root.js
Normal file
44
net/web/src/components/Root.js
Normal file
@ -0,0 +1,44 @@
|
||||
import React, { useContext, useState, useEffect, useRef } from 'react'
|
||||
import { BrowserRouter as Router, Routes, Route, useNavigate } from "react-router-dom";
|
||||
import { AppContext } from '../context/AppContext';
|
||||
|
||||
export function Root() {
|
||||
|
||||
return (
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route path="/about" element={ <About /> } />
|
||||
<Route path="/topic" element={ <Topic /> } />
|
||||
<Route path="/" element={ <Empty /> } />
|
||||
</Routes>
|
||||
</Router>
|
||||
)
|
||||
}
|
||||
|
||||
function About() {
|
||||
return (<div>ABOUT</div>)
|
||||
}
|
||||
|
||||
function Topic() {
|
||||
return (<div>TOPIC</div>)
|
||||
}
|
||||
|
||||
function Empty() {
|
||||
|
||||
const appContext = useContext(AppContext);
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
console.log(appContext)
|
||||
if (appContext.state) {
|
||||
if (appContext.state.appToken) {
|
||||
navigate("/topic")
|
||||
} else {
|
||||
navigate("/about")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return (<div>EMPTY</div>)
|
||||
}
|
||||
|
14
net/web/src/context/AppContext.js
Normal file
14
net/web/src/context/AppContext.js
Normal file
@ -0,0 +1,14 @@
|
||||
import { createContext } from 'react';
|
||||
import useAppContext from './useAppContext.hook';
|
||||
|
||||
export const AppContext = createContext({});
|
||||
|
||||
export function AppContextProvider({ children }) {
|
||||
const { state, actions } = useAppContext();
|
||||
return (
|
||||
<AppContext.Provider value={{ state, actions }}>
|
||||
{children}
|
||||
</AppContext.Provider>
|
||||
);
|
||||
}
|
||||
|
39
net/web/src/context/useAppContext.hook.js
Normal file
39
net/web/src/context/useAppContext.hook.js
Normal file
@ -0,0 +1,39 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export default function useAppContext() {
|
||||
const [state, setState] = useState(null);
|
||||
|
||||
const actions = {
|
||||
setListener: setListener,
|
||||
clearListener: clearListener,
|
||||
login: login,
|
||||
logout: logout,
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem('app_token');
|
||||
if (token) {
|
||||
setState({ appToken: token })
|
||||
} else {
|
||||
setState({ appToken: null })
|
||||
}
|
||||
}, []);
|
||||
return { state, actions };
|
||||
}
|
||||
|
||||
function setListener(name: string, callback: (objectId: string) => void) {
|
||||
return
|
||||
}
|
||||
|
||||
function clearListener(callback: (objectId: string) => void) {
|
||||
return
|
||||
}
|
||||
|
||||
async function login(username: string, password: string) {
|
||||
return
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
return
|
||||
}
|
||||
|
@ -1065,6 +1065,13 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.7.6":
|
||||
version "7.17.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.7.tgz#a5f3328dc41ff39d803f311cfe17703418cf9825"
|
||||
integrity sha512-L6rvG9GDxaLgFjg41K+5Yv9OMrU98sWe+Ykmc6FDJW/+vYZMhdOMKkISgzptMaERHvS2Y2lw9MDRm2gHhlQQoA==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/template@^7.16.7", "@babel/template@^7.3.3":
|
||||
version "7.16.7"
|
||||
resolved "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz"
|
||||
@ -4628,6 +4635,13 @@ he@^1.2.0:
|
||||
resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz"
|
||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||
|
||||
history@^5.2.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/history/-/history-5.3.0.tgz#1548abaa245ba47992f063a0783db91ef201c73b"
|
||||
integrity sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.7.6"
|
||||
|
||||
hoopy@^0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz"
|
||||
@ -7572,6 +7586,21 @@ react-refresh@^0.11.0:
|
||||
resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz"
|
||||
integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==
|
||||
|
||||
react-router-dom@^6.2.2:
|
||||
version "6.2.2"
|
||||
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.2.2.tgz#f1a2c88365593c76b9612ae80154a13fcb72e442"
|
||||
integrity sha512-AtYEsAST7bDD4dLSQHDnk/qxWLJdad5t1HFa1qJyUrCeGgEuCSw0VB/27ARbF9Fi/W5598ujvJOm3ujUCVzuYQ==
|
||||
dependencies:
|
||||
history "^5.2.0"
|
||||
react-router "6.2.2"
|
||||
|
||||
react-router@6.2.2:
|
||||
version "6.2.2"
|
||||
resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.2.2.tgz#495e683a0c04461eeb3d705fe445d6cf42f0c249"
|
||||
integrity sha512-/MbxyLzd7Q7amp4gDOGaYvXwhEojkJD5BtExkuKmj39VEE0m3l/zipf6h2WIB2jyAO0lI6NGETh4RDcktRm4AQ==
|
||||
dependencies:
|
||||
history "^5.2.0"
|
||||
|
||||
react-scripts@5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.0.tgz"
|
||||
|
Loading…
Reference in New Issue
Block a user