feat: frontend use router

This commit is contained in:
Samuel N Cui
2022-12-13 20:02:29 +08:00
parent b9355ad892
commit cda9244e8e
10 changed files with 167 additions and 62 deletions

View File

@@ -96,15 +96,14 @@ func main() {
fs := http.FileServer(http.Dir("./frontend/assets"))
mux.Handle("/assets/", http.StripPrefix("/assets/", fs))
indexBuf, err := ioutil.ReadFile("./frontend/index.html")
if err != nil {
panic(err)
}
indexBuf = bytes.ReplaceAll(indexBuf, []byte("%%API_BASE%%"), []byte(fmt.Sprintf("%s/services", conf.Domain)))
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
indexBuf, err := ioutil.ReadFile("./frontend/index.html")
if err != nil {
panic(err)
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Write(indexBuf)
w.Write(bytes.ReplaceAll(indexBuf, []byte("%%API_BASE%%"), []byte(fmt.Sprintf("%s/services", conf.Domain))))
})
srv := &http.Server{

View File

@@ -84,7 +84,7 @@ func (a *jobArchiveExecutor) handle(ctx context.Context, param *entity.JobArchiv
}
tools.Working()
go tools.Wrap(ctx, func() {
go tools.WrapWithLogger(ctx, a.logger, func() {
defer tools.Done()
if err := a.makeTape(tools.ShutdownContext, p.Device, p.Barcode, p.Name); err != nil {
a.logger.WithContext(ctx).WithError(err).Errorf("make type has error, barcode= '%s' name= '%s'", p.Barcode, p.Name)
@@ -246,7 +246,7 @@ func (a *jobArchiveExecutor) makeTape(ctx context.Context, device, barcode, name
a.logger.WithContext(ctx).WithError(err).Warnf("open report file fail, barcode= '%s'", barcode)
} else {
defer reportFile.Close()
tools.Wrap(ctx, func() {
tools.WrapWithLogger(ctx, a.logger, func() {
reportFile.Write([]byte(report.ToJSONString(false)))
})
}

View File

@@ -26,12 +26,16 @@
"fast-text-encoding": "^1.0.6",
"filesize": "^10.0.5",
"format-duration": "^2.0.0",
"localforage": "^1.10.0",
"match-sorter": "^6.3.1",
"moment": "^2.29.4",
"react": "^18.2.0",
"react-dnd": "^11.1.3",
"react-dnd-html5-backend": "^11.1.3",
"react-dom": "^18.2.0",
"react-is": "^18.2.0"
"react-is": "^18.2.0",
"react-router-dom": "^6.4.5",
"sort-by": "^1.2.0"
},
"devDependencies": {
"@protobuf-ts/plugin": "^2.8.2",

View File

@@ -23,6 +23,8 @@ specifiers:
filesize: ^10.0.5
format-duration: ^2.0.0
less: ^4.1.3
localforage: ^1.10.0
match-sorter: ^6.3.1
moment: ^2.29.4
prettier: 2.7.1
react: ^18.2.0
@@ -30,6 +32,8 @@ specifiers:
react-dnd-html5-backend: ^11.1.3
react-dom: ^18.2.0
react-is: ^18.2.0
react-router-dom: ^6.4.5
sort-by: ^1.2.0
tsdef: ^0.0.14
typescript: ^4.6.4
vite: ^3.1.0
@@ -51,12 +55,16 @@ dependencies:
fast-text-encoding: 1.0.6
filesize: 10.0.5
format-duration: 2.0.0
localforage: 1.10.0
match-sorter: 6.3.1
moment: 2.29.4
react: 18.2.0
react-dnd: 11.1.3_biqbaboplfbrettd7655fr4n2y
react-dnd-html5-backend: 11.1.3
react-dom: 18.2.0_react@18.2.0
react-is: 18.2.0
react-router-dom: 6.4.5_biqbaboplfbrettd7655fr4n2y
sort-by: 1.2.0
devDependencies:
'@protobuf-ts/plugin': 2.8.2
@@ -1025,6 +1033,11 @@ packages:
reselect: 4.1.6
dev: false
/@remix-run/router/1.0.5:
resolution: {integrity: sha512-my0Mycd+jruq/1lQuO5LBB6WTlL/e8DTCYWp44DfMTDcXz8DcTlgF0ISaLsGewt+ctHN+yA8xMq3q/N7uWJPug==}
engines: {node: '>=14'}
dev: false
/@types/classnames/2.3.1:
resolution: {integrity: sha512-zeOWb0JGBoVmlQoznvqXbE0tEC/HONsnoUNH19Hc96NFsTAwTXbTqb8FMYkru1F/iqp7a18Ws3nWJvtA1sHD1A==}
deprecated: This is a stub types definition. classnames provides its own type definitions, so you do not need this installed.
@@ -1739,6 +1752,10 @@ packages:
dev: true
optional: true
/immediate/3.0.6:
resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
dev: false
/immer/9.0.15:
resolution: {integrity: sha512-2eB/sswms9AEUSkOm4SbV5Y7Vmt/bKRwByd52jfLkW4OLYeaTP3EEiJ9agqU0O/tq6Dk62Zfj+TJSqfm1rLVGQ==}
dev: false
@@ -1935,10 +1952,22 @@ packages:
- supports-color
dev: true
/lie/3.1.1:
resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==}
dependencies:
immediate: 3.0.6
dev: false
/lines-and-columns/1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
dev: false
/localforage/1.10.0:
resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==}
dependencies:
lie: 3.1.1
dev: false
/lodash/4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
dev: false
@@ -1967,6 +1996,13 @@ packages:
dev: true
optional: true
/match-sorter/6.3.1:
resolution: {integrity: sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==}
dependencies:
'@babel/runtime': 7.20.6
remove-accents: 0.4.2
dev: false
/memoize-one/5.2.1:
resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==}
dev: false
@@ -2029,6 +2065,11 @@ packages:
engines: {node: '>= 10.12.0'}
dev: false
/object-path/0.6.0:
resolution: {integrity: sha512-fxrwsCFi3/p+LeLOAwo/wyRMODZxdGBtUlWRzsEpsUVrisZbEfZ21arxLGfaWfcnqb8oHPNihIb4XPE8CQPN5A==}
engines: {node: '>=0.8.0'}
dev: false
/parent-module/1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
@@ -2225,6 +2266,29 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/react-router-dom/6.4.5_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-a7HsgikBR0wNfroBHcZUCd9+mLRqZS8R5U1Z1mzLWxFXEkUT3vR1XXmSIVoVpxVX8Bar0nQYYYc9Yipq8dWwAA==}
engines: {node: '>=14'}
peerDependencies:
react: '>=16.8'
react-dom: '>=16.8'
dependencies:
'@remix-run/router': 1.0.5
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
react-router: 6.4.5_react@18.2.0
dev: false
/react-router/6.4.5_react@18.2.0:
resolution: {integrity: sha512-1RQJ8bM70YEumHIlNUYc6mFfUDoWa5EgPDenK/fq0bxD8DYpQUi/S6Zoft+9DBrh2xmtg92N5HMAJgGWDhKJ5Q==}
engines: {node: '>=14'}
peerDependencies:
react: '>=16.8'
dependencies:
'@remix-run/router': 1.0.5
react: 18.2.0
dev: false
/react-transition-group/4.4.5_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
peerDependencies:
@@ -2294,6 +2358,10 @@ packages:
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
dev: false
/remove-accents/0.4.2:
resolution: {integrity: sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==}
dev: false
/reselect/4.1.6:
resolution: {integrity: sha512-ZovIuXqto7elwnxyXbBtCPo9YFEr3uJqj2rRbcOOog1bmu2Ag85M4hixSwFWyaBMKXNgvPaJ9OSu9SkBPIeJHQ==}
dev: false
@@ -2360,6 +2428,12 @@ packages:
nanoid: 2.1.11
dev: false
/sort-by/1.2.0:
resolution: {integrity: sha512-aRyW65r3xMnf4nxJRluCg0H/woJpksU1dQxRtXYzau30sNBOmf5HACpDd9MZDhKh7ALQ5FgSOfMPwZEtUmMqcg==}
dependencies:
object-path: 0.6.0
dev: false
/source-map-js/1.0.2:
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
engines: {node: '>=0.10.0'}

View File

@@ -1,6 +1,5 @@
import { useEffect } from "react";
import { useState, useCallback } from "react";
import { ChangeEvent } from "react";
import { Fragment, useCallback, ChangeEvent } from "react";
import { Routes, Route, Link, useNavigate, Navigate, useLocation } from "react-router-dom";
import Tabs from "@mui/material/Tabs";
import Tab from "@mui/material/Tab";
@@ -13,62 +12,58 @@ import { JobsBrowser, JobsType } from "./jobs";
import "./app.less";
import { sleep } from "./api";
import { Nullable } from "tsdef";
import { Job } from "./entity";
import { useEffect } from "react";
import { useState } from "react";
// import reactLogo from './assets/react.svg'
// <img src={reactLogo} className="logo react" alt="React logo" />
const theme = createTheme({});
const typeToElement = (type: string) => {
switch (type) {
case FileBrowserType:
return <FileBrowser />;
case BackupType:
return <BackupBrowser />;
case JobsType:
return <JobsBrowser />;
default:
return null;
}
const Delay = ({ inner }: { inner: JSX.Element }) => {
const [ok, setOK] = useState(false);
useEffect(() => {
setOK(false);
(async () => {
await sleep(0);
setOK(true);
})();
return () => {
setOK(false);
};
}, [inner]);
return ok ? inner : null;
};
const App = () => {
const [tabValue, setTabValue] = useState(FileBrowserType);
const [inner, setInner] = useState<Nullable<JSX.Element>>(null);
const setType = useCallback(
(newValue: string) => {
(async () => {
setTabValue(newValue);
setInner(null);
await sleep(0);
setInner(typeToElement(newValue));
})();
},
[setTabValue, setInner]
);
const location = useLocation();
const navigate = useNavigate();
const handleTabChange = useCallback(
(_: ChangeEvent<{}>, newValue: string) => {
setType(newValue);
navigate("/" + newValue);
},
[setTabValue]
[navigate]
);
useEffect(() => {
setType(FileBrowserType);
}, []);
return (
<div id="app">
<ThemeProvider theme={theme}>
<Tabs className="tabs" value={tabValue} onChange={handleTabChange} indicatorColor="secondary">
<Tabs className="tabs" value={location.pathname.slice(1)} onChange={handleTabChange} indicatorColor="secondary">
<Tab label="File" value={FileBrowserType} />
<Tab label="Source" value={BackupType} />
<Tab label="Jobs" value={JobsType} />
</Tabs>
<Routes>
<Route path="/*">
<Route path={FileBrowserType} element={<Delay inner={<FileBrowser />} />} />
<Route path={BackupType} element={<Delay inner={<BackupBrowser />} />} />
<Route path={JobsType} element={<Delay inner={<JobsBrowser />} />} />
<Route path="*" element={<Navigate to={"/" + FileBrowserType} replace />} />
</Route>
</Routes>
</ThemeProvider>
{inner}
</div>
);
};

View File

@@ -2,7 +2,7 @@ import { useState, useRef, useEffect, useMemo, useCallback } from "react";
import Grid from "@mui/material/Grid";
import Box from "@mui/material/Box";
import { FullFileBrowser, FileBrowserHandle, FileArray } from "chonky";
import { FullFileBrowser, FileBrowserProps, FileBrowserHandle, FileArray } from "chonky";
import { ChonkyActions, ChonkyFileActionData } from "chonky";
import "./app.less";
@@ -32,7 +32,7 @@ const useDualSide = () => {
return { instances, refreshAll };
};
const useFileBrowser = (refreshAll: () => Promise<void>, openDetailModel: (detail: FileGetReply) => void) => {
const useFileBrowser = (storageKey: string, refreshAll: () => Promise<void>, openDetailModel: (detail: FileGetReply) => void) => {
const [files, setFiles] = useState<FileArray>(Array(1).fill(null));
const [folderChain, setFolderChan] = useState<FileArray>([Root]);
const currentID = useMemo(() => {
@@ -48,15 +48,28 @@ const useFileBrowser = (refreshAll: () => Promise<void>, openDetailModel: (detai
return last.id;
}, [folderChain]);
const openFolder = useCallback((id: string) => {
(async () => {
const [file, folderChain] = await Promise.all([cli.fileGet({ id: BigInt(id) }).response, cli.fileListParents({ id: BigInt(id) }).response]);
const openFolder = useCallback(async (id: string) => {
const [file, folderChain] = await Promise.all([cli.fileGet({ id: BigInt(id) }).response, cli.fileListParents({ id: BigInt(id) }).response]);
setFiles(convertFiles(file.children));
setFolderChan([Root, ...convertFiles(folderChain.parents)]);
setFiles(convertFiles(file.children));
setFolderChan([Root, ...convertFiles(folderChain.parents)]);
localStorage.setItem(storageKey, id);
}, []);
useEffect(() => {
(async () => {
const storagedID = localStorage.getItem(storageKey);
if (storagedID) {
try {
await openFolder(storagedID);
return;
} catch (e) {
console.log("open storaged id fail, err= ", e);
}
}
openFolder(Root.id);
})();
}, []);
useEffect(() => openFolder(Root.id), []);
const onFileAction = useCallback(
(data: ChonkyFileActionData) => {
@@ -157,8 +170,8 @@ export const FileBrowser = () => {
const { instances, refreshAll } = useDualSide();
const { detail, openDetailModel, closeDetailModel } = useDetailModal();
const leftProps = useFileBrowser(refreshAll, openDetailModel);
const rightProps = useFileBrowser(refreshAll, openDetailModel);
const leftProps = useFileBrowser("file_browser:left:current_id", refreshAll, openDetailModel);
const rightProps = useFileBrowser("file_browser:right:current_id", refreshAll, openDetailModel);
useEffect(() => {
Object.values(instances).map((inst) => inst.current?.requestFileAction(ChonkyActions.ToggleHiddenFiles, {}));
@@ -167,6 +180,7 @@ export const FileBrowser = () => {
}, 10000);
return () => clearInterval(interval);
}, []);
useEffect(() => {});
return (
<Box className="browser-box">

View File

@@ -348,10 +348,18 @@ const ViewLogDialog = ({ jobID }: { jobID: bigint }) => {
const LogConsole = ({ jobId }: { jobId: bigint }) => {
const [log, setLog] = useState<string>("");
const bottom = useRef(null);
const refreshLog = useCallback(async () => {
const reply = await cli.jobGetLog({ jobId, offset: BigInt(log.length) }).response;
setLog(log + new TextDecoder().decode(reply.logs));
}, [log, setLog]);
if (log.length === 0 && reply.logs.length > 0 && bottom && bottom.current) {
await sleep(10);
(bottom.current as HTMLElement).scrollIntoView(true);
await sleep(10);
(bottom.current as HTMLElement).parentElement?.scrollBy(0, 100);
}
}, [log, setLog, bottom]);
useEffect(() => {
let closed = false;
(async () => {
@@ -366,7 +374,12 @@ const LogConsole = ({ jobId }: { jobId: bigint }) => {
};
}, [refreshLog]);
return <pre>{log || "loading..."}</pre>;
return (
<Fragment>
<pre>{log || "loading..."}</pre>
<div ref={bottom} />
</Fragment>
);
};
const ArchiveViewFilesDialog = ({ sources }: { sources: SourceState[] }) => {

View File

@@ -1,10 +1,13 @@
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./app";
import "./index.css";
import "./init";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<App />
<BrowserRouter>
<App />
</BrowserRouter>
);

View File

@@ -4,7 +4,6 @@ import react from "@vitejs/plugin-react";
// https://vitejs.dev/config/
export default defineConfig(({ command, mode }) => {
const env = loadEnv(mode, process.cwd(), "");
console.log(env);
return {
plugins: [react()],

View File

@@ -9,6 +9,10 @@ import (
)
func Wrap(ctx context.Context, f func()) {
WrapWithLogger(ctx, logrus.StandardLogger(), f)
}
func WrapWithLogger(ctx context.Context, logger *logrus.Logger, f func()) {
defer func() {
e := recover()
if e == nil {
@@ -23,7 +27,7 @@ func Wrap(ctx context.Context, f func()) {
err = fmt.Errorf("%v", err)
}
logrus.WithContext(ctx).WithError(err).Errorf("panic: %s", debug.Stack())
logger.WithContext(ctx).WithError(err).Errorf("panic: %s", debug.Stack())
}()
f()