未验证 提交 afc15b2c 编写于 作者: P Peter Pan 提交者: GitHub

add public path argument (#624)

* style: lint shell scripts

* fix: translation fix

* feat: support changing public path when start

* v2.0.0-beta.36

* feat: support changing public path when start

* v2.0.0-beta.37

* build: core build fix

* v2.0.0-beta.38

* build: build frontend from source code

* fix: clean static files when exit

* fix: dependency version mismatching

* chore: remove unused api route

* fix: navbar actived link missing

* feat: redirect root

* fix: scalar chart tooltip value error

* feat: dynamic render nav items

* fix: high-dimensional page background missing

* v2.0.0-beta.39

* build: stop using script mode

* v2.0.0-beta.40

* Revert "build: stop using script mode"

This reverts commit 6c1ca297.
上级 f551ca15
...@@ -61,7 +61,6 @@ yarn ...@@ -61,7 +61,6 @@ yarn
[server](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/server/README.md) [server](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/server/README.md)
[serverless](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/serverless/README.md) [serverless](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/serverless/README.md)
[cli](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/cli/README.md) [cli](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/cli/README.md)
[app](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/app/README.md)
[i18n](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/i18n/README.md) [i18n](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/i18n/README.md)
[wasm](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/wasm/README.md) [wasm](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/wasm/README.md)
[mock](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/mock/README.md) [mock](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/mock/README.md)
......
...@@ -61,7 +61,6 @@ yarn ...@@ -61,7 +61,6 @@ yarn
[server](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/server/README.md) [server](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/server/README.md)
[serverless](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/serverless/README.md) [serverless](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/serverless/README.md)
[cli](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/cli/README.md) [cli](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/cli/README.md)
[app](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/app/README.md)
[i18n](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/i18n/README.md) [i18n](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/i18n/README.md)
[wasm](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/wasm/README.md) [wasm](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/wasm/README.md)
[mock](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/mock/README.md) [mock](https://github.com/PaddlePaddle/VisualDL/blob/develop/frontend/packages/mock/README.md)
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
"packages": [ "packages": [
"packages/*" "packages/*"
], ],
"version": "2.0.0-beta.35", "version": "2.0.0-beta.40",
"npmClient": "yarn", "npmClient": "yarn",
"useWorkspaces": true, "useWorkspaces": true,
"command": { "command": {
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
"workspaces": [ "workspaces": [
"packages/*" "packages/*"
], ],
"version": "2.0.0-beta.35", "version": "2.0.0",
"description": "A platform to visualize the deep learning process and result.", "description": "A platform to visualize the deep learning process and result.",
"keywords": [ "keywords": [
"visualdl", "visualdl",
......
../../../LICENSE
\ No newline at end of file
# VisualDL FrontEnd APP
UNDER DEVELOPMENT
appId: org.paddlepaddle.visualdl
productName: VisualDL
files:
- 'index.js'
- 'resources/**/*'
directories:
buildResources: ./resources
mac:
category: public.app-category.developer-tools
darkModeSupport: false
hardenedRuntime: true
gatekeeperAssess: false
target:
- zip
win:
target:
- nsis
verifyUpdateCodeSignature: false
linux:
target:
- AppImage
nsis:
perMachine: true
publish: null
/* eslint-disable no-console */
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require('path');
const http = require('http');
const {app, BrowserWindow, screen, nativeImage} = require('electron');
const pm2 = require('pm2');
const host = 'localhost';
async function getPort() {
return new Promise((resolve, reject) => {
const server = http.createServer();
server.listen(0);
server.on('listening', () => {
resolve(server.address().port);
server.close();
});
server.on('error', reject);
});
}
async function createWindow() {
const {width, height} = screen.getPrimaryDisplay().workAreaSize;
const win = new BrowserWindow({
minWidth: 800,
minHeight: 600,
width,
height,
icon: nativeImage.createFromPath(path.join(__dirname, 'resources/icon.png')),
webPreferences: {
devTools: false,
nodeIntegration: true
},
show: false
});
win.once('ready-to-show', () => {
win.show();
});
return win;
}
function startServer({port, host}) {
return new Promise((resolve, reject) => {
pm2.connect(err => {
if (err) {
reject(err);
}
const app = require('@visualdl/server/ecosystem.config').apps[0];
pm2.start(
{
...app,
instances: 1,
env: {
...app.env,
HOST: host,
PORT: port,
BACKEND: 'http://127.0.0.1:8040'
}
},
err => {
pm2.disconnect();
if (err) {
reject(err);
} else {
console.log(`Server listening at http://${host}:${port}`);
resolve();
}
}
);
});
});
}
function stopServer() {
return new Promise((resolve, reject) => {
pm2.connect(err => {
if (err) {
reject(err);
}
pm2.killDaemon(err => {
pm2.disconnect();
if (err) {
reject(err);
} else {
console.log('Server stopped');
resolve();
}
});
});
});
}
app.on('ready', async () => {
let port = 0;
try {
port = await getPort();
await startServer({port, host});
} catch (e) {
console.error(e);
app.exit(1);
return;
}
try {
const win = await createWindow();
win.loadURL(`http://${host}:${port}`);
} catch (e) {
console.error(e);
app.exit(1);
}
});
// app.on('activate', async () => {
// if (BrowserWindow.getAllWindows().length === 0) {
// try {
// await createWindow();
// } catch (e) {
// console.error(e);
// app.exit(1);
// }
// }
// });
// Quit when all windows are closed.
app.on('window-all-closed', () => {
app.quit();
// if (process.platform !== 'darwin') {
// app.quit();
// }
});
app.on('will-quit', async event => {
event.preventDefault();
try {
await stopServer();
app.exit(0);
} catch (e) {
console.error(e);
app.exit(1);
}
});
{
"name": "@visualdl/app",
"version": "2.0.0-beta.35",
"private": true,
"description": "A platform to visualize the deep learning process and result.",
"keywords": [
"visualdl",
"paddlepaddle",
"visualization",
"deep learning"
],
"homepage": "https://github.com/PaddlePaddle/VisualDL",
"bugs": {
"url": "https://github.com/PaddlePaddle/VisualDL/issues"
},
"license": "Apache-2.0",
"author": "PeterPanZH <littlepanzh@gmail.com> (https://github.com/PeterPanZH)",
"contributors": [
"Niandalu <littlepanzh@gmail.com> (https://github.com/Niandalu)"
],
"repository": {
"type": "git",
"url": "https://github.com/PaddlePaddle/VisualDL.git",
"directory": "frontend/packages/app"
},
"scripts": {
"dev": "electron index.js",
"build": "electron-builder -mwl",
"test": "echo \"Error: no test specified\" && exit 0"
},
"dependencies": {
"@visualdl/server": "2.0.0-beta.35",
"pm2": "4.4.0"
},
"devDependencies": {
"electron": "8.2.5",
"electron-builder": "22.6.0"
},
"engines": {
"node": ">=10",
"npm": ">=6"
},
"publishConfig": {
"access": "public"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
}
}
<svg height="31" viewBox="0 0 50 31" width="50" xmlns="http://www.w3.org/2000/svg"><g fill="#fff" fill-rule="evenodd" transform="translate(0 .0625)"><path d="m2.446115.0742695c-1.32588958 0-2.40434375 1.0740707-2.40434375 2.39411403s1.07845417 2.39382156 2.40434375 2.39382156 2.40434375-1.07377823 2.40434375-2.39382156-1.07845417-2.39411403-2.40434375-2.39411403" transform="translate(23.010417 .023203)"/><path d="m.23204292 23.3006269h3.46595625c.06922708 0 .13199166-.0401668.16068125-.1029517l10.45446458-22.89241762c.0531687-.1163081-.0322146-.24831243-.1605833-.24831243h-3.4659563c-.0691292 0-.1318937.04026424-.1605833.10295168l-10.45456252 22.89241757c-.05307083.1163081.0323125.2483125.16058334.2483125" transform="translate(24.0875 6.652667)"/><path d="m2.48757292.0742695c-1.32588959 0-2.40434375 1.0740707-2.40434375 2.39411403s1.07845416 2.39382156 2.40434375 2.39382156c1.32588958 0 2.40434375-1.07377823 2.40434375-2.39382156s-1.07845417-2.39411403-2.40434375-2.39411403" transform="translate(43.866667 .023203)"/><path d="m17.4206283.05694515h-3.4659562c-.0691292 0-.1318938.04026424-.1605834.10304917l-4.08253745 8.93954011h-8.13765833c-.1075125 0-.2050375.06278493-.24900209.16047204l-1.31139791 2.91052983c-.04513959.1002219.02849375.2135077.13884583.2135077h8.05922708l-4.87203958 10.6682704c-.05307083.1163081.0323125.2483125.16058333.2483125h3.46595625c.06922709 0 .13199167-.0401668.16058334-.1029517l10.45456253-22.89241762c.0531687-.1163081-.0322146-.24831243-.1605834-.24831243" transform="translate(0 6.652667)"/><path d="m29.3354319 12.1665001c-.3381063-2.99856519-2.8049209-5.35027016-5.8800917-5.45507419-.0279062-.00107241-.0550292.00302226-.0813687.00887179-.0140021-.0049721-.0284938-.00887179-.0441605-.00887179h-2.1002145c-.0522875 0-.0995813.03032005-.121123.07779872l-1.2130895 2.67576877c-.0396563.08754793.0247729.18679491.1211229.18679491h2.1414375c.4538437-.00653197.7861729.02330061 1.1568854.08062598 1.7253896.26693343 2.7979687 1.52672661 2.7732937 3.19686421-.0235 1.5783974-1.3395979 2.8354608-2.9250645 2.8354608h-7.669225c-.1306209 0-.2491.0760439-.3029542.1943018l-1.2729167 2.7896396c-.0615896.1347341.0376.2880892.1863354.2880892h8.9637813c3.7159375 0 6.6818333-3.1934519 6.2673521-6.8702698"/><path d="m50.1567746 12.1665001c-.3381063-2.99856519-2.8049209-5.35027016-5.8801896-5.45507419-.0279063-.00107241-.0549313.00302226-.0813688.00887179-.0139041-.0049721-.0283958-.00887179-.0440625-.00887179h-2.1003125c-.0521895 0-.0995812.03032005-.1211229.07779872l-1.2130896 2.67576877c-.0395583.08754793.024675.18679491.1212209.18679491h2.1413396c.4539416-.00653197.7862708.02330061 1.1569833.08062598 1.7252917.26693343 2.7978708 1.52672661 2.7731958 3.19686421-.0235 1.5783974-1.3395979 2.8354608-2.9250646 2.8354608h-7.669127c-.1306209 0-.2491.0760439-.3030521.1943018l-1.2729167 2.7896396c-.0614916.1347341.0376979.2880892.1863354.2880892h8.9638792c3.7158396 0 6.6817354-3.1934519 6.2673521-6.8702698"/></g></svg>
\ No newline at end of file
{ {
"name": "@visualdl/cli", "name": "@visualdl/cli",
"version": "2.0.0-beta.35", "version": "2.0.0-beta.40",
"description": "A platform to visualize the deep learning process and result.", "description": "A platform to visualize the deep learning process and result.",
"keywords": [ "keywords": [
"visualdl", "visualdl",
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
"dist" "dist"
], ],
"dependencies": { "dependencies": {
"@visualdl/server": "2.0.0-beta.35", "@visualdl/server": "2.0.0-beta.40",
"open": "7.0.3", "open": "7.0.3",
"ora": "4.0.4", "ora": "4.0.4",
"pm2": "4.4.0", "pm2": "4.4.0",
...@@ -59,5 +59,5 @@ ...@@ -59,5 +59,5 @@
"pre-commit": "lint-staged" "pre-commit": "lint-staged"
} }
}, },
"gitHead": "d9641c770d9bc4037b2f1cb644fba198bd11a752" "gitHead": "ed915e4590c1527836cc802d7c6f5011a5b9a4a3"
} }
...@@ -8,7 +8,7 @@ import {writeFileSync} from 'fs'; ...@@ -8,7 +8,7 @@ import {writeFileSync} from 'fs';
const next = require.resolve('next/dist/bin/next'); const next = require.resolve('next/dist/bin/next');
export const projectRoot = path.dirname(require.resolve('@visualdl/core')); export const projectRoot = path.dirname(require.resolve('@visualdl/core'));
export default function (action: string, ...args: string[]): Promise<number> { export default function (action: 'build' | 'export', ...args: string[]): Promise<number> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const capitalizedAction = action.replace(/^./, w => w.toUpperCase()); const capitalizedAction = action.replace(/^./, w => w.toUpperCase());
......
...@@ -155,13 +155,13 @@ const ChartPage = <T extends Item>({ ...@@ -155,13 +155,13 @@ const ChartPage = <T extends Item>({
) : ( ) : (
<Empty height={rem(500)}> <Empty height={rem(500)}>
{search ? ( {search ? (
<Trans i18nKey="search-empty"> <Trans i18nKey="common:search-empty">
Nothing found. Please try again with another word. Nothing found. Please try again with another word.
<br /> <br />
Or you can <a onClick={() => setInputValue('')}>see all charts</a>. Or you can <a onClick={() => setInputValue('')}>see all charts</a>.
</Trans> </Trans>
) : ( ) : (
t('empty') t('common:empty')
)} )}
</Empty> </Empty>
)} )}
...@@ -174,14 +174,14 @@ const ChartPage = <T extends Item>({ ...@@ -174,14 +174,14 @@ const ChartPage = <T extends Item>({
<div className={className}> <div className={className}>
<Search> <Search>
<SearchInput <SearchInput
placeholder={t('search-tags')} placeholder={t('common:search-tags')}
rounded rounded
value={inputValue} value={inputValue}
onChange={(value: string) => setInputValue(value)} onChange={(value: string) => setInputValue(value)}
/> />
</Search> </Search>
{searchValue ? ( {searchValue ? (
<ChartCollapse title={t('search-result')} total={matchedTags.length}> <ChartCollapse title={t('common:search-result')} total={matchedTags.length}>
{withCharts(pageMatchedTags, true)} {withCharts(pageMatchedTags, true)}
{pageMatchedTags.length ? <StyledPagination page={page} total={total} onChange={setPage} /> : null} {pageMatchedTags.length ? <StyledPagination page={page} total={total} onChange={setPage} /> : null}
</ChartCollapse> </ChartCollapse>
...@@ -198,7 +198,7 @@ const ChartPage = <T extends Item>({ ...@@ -198,7 +198,7 @@ const ChartPage = <T extends Item>({
)) ))
) : ( ) : (
<Empty height={`calc(100vh - ${headerHeight} - ${rem(96)})`}> <Empty height={`calc(100vh - ${headerHeight} - ${rem(96)})`}>
<Trans i18nKey="unselected-empty"> <Trans i18nKey="common:unselected-empty">
Nothing selected. Nothing selected.
<br /> <br />
Please select display data from right side. Please select display data from right side.
......
...@@ -40,7 +40,7 @@ const PropertyList = styled(DataList)` ...@@ -40,7 +40,7 @@ const PropertyList = styled(DataList)`
const NodeInfo: FunctionComponent<NodeInfoProps> = props => { const NodeInfo: FunctionComponent<NodeInfoProps> = props => {
const {t} = useTranslation('graphs'); const {t} = useTranslation('graphs');
if (!props.node) { if (!props.node) {
return <p>{t('click-node')}</p>; return <p>{t('graphs:click-node')}</p>;
} }
const node = props.node; const node = props.node;
...@@ -50,10 +50,10 @@ const NodeInfo: FunctionComponent<NodeInfoProps> = props => { ...@@ -50,10 +50,10 @@ const NodeInfo: FunctionComponent<NodeInfoProps> = props => {
return ( return (
<PropertyList <PropertyList
items={[ items={[
{key: t('node-type'), value: typeName[node.type]}, {key: t('graphs:node-type'), value: typeName[node.type]},
{key: t('node-name'), value: node.name}, {key: t('graphs:node-name'), value: node.name},
{key: t('node-data-shape'), value: node.shape}, {key: t('graphs:node-data-shape'), value: node.shape},
{key: t('node-data-type'), value: node.data_type} {key: t('graphs:node-data-type'), value: node.data_type}
]} ]}
/> />
); );
...@@ -61,15 +61,15 @@ const NodeInfo: FunctionComponent<NodeInfoProps> = props => { ...@@ -61,15 +61,15 @@ const NodeInfo: FunctionComponent<NodeInfoProps> = props => {
return ( return (
<PropertyList <PropertyList
items={[ items={[
{key: t('node-type'), value: typeName[node.type]}, {key: t('graphs:node-type'), value: typeName[node.type]},
{key: t('input'), value: node.input}, {key: t('graphs:input'), value: node.input},
{key: t('op-type'), value: node.opType}, {key: t('graphs:op-type'), value: node.opType},
{key: t('output'), value: node.output} {key: t('graphs:output'), value: node.output}
]} ]}
/> />
); );
case 'unknown': case 'unknown':
return <PropertyList items={[{key: t('node-type'), value: typeName[node.guessType]}]} />; return <PropertyList items={[{key: t('graphs:node-type'), value: typeName[node.guessType]}]} />;
default: default:
return null; return null;
} }
......
...@@ -101,11 +101,11 @@ const HighDimensionalChart: FunctionComponent<HighDimensionalChartProps> = ({ ...@@ -101,11 +101,11 @@ const HighDimensionalChart: FunctionComponent<HighDimensionalChartProps> = ({
}, [points]); }, [points]);
if (!data && error) { if (!data && error) {
return <Empty>{t('error')}</Empty>; return <Empty>{t('common:error')}</Empty>;
} }
if (!data && !loading) { if (!data && !loading) {
return <Empty>{t('empty')}</Empty>; return <Empty>{t('common:empty')}</Empty>;
} }
return <StyledScatterChart loading={loading} data={chartData} gl={dimension === '3d'} />; return <StyledScatterChart loading={loading} data={chartData} gl={dimension === '3d'} />;
......
...@@ -52,7 +52,7 @@ const Image = React.forwardRef<ImageRef, ImageProps>(({src, cache}, ref) => { ...@@ -52,7 +52,7 @@ const Image = React.forwardRef<ImageRef, ImageProps>(({src, cache}, ref) => {
} }
if (error) { if (error) {
return <div>{t('error')}</div>; return <div>{t('common:error')}</div>;
} }
return <img src={url} />; return <img src={url} />;
......
...@@ -15,19 +15,10 @@ import Icon from '~/components/Icon'; ...@@ -15,19 +15,10 @@ import Icon from '~/components/Icon';
import {InitConfig} from '@visualdl/i18n'; import {InitConfig} from '@visualdl/i18n';
import Language from '~/components/Language'; import Language from '~/components/Language';
import ee from '~/utils/event'; import ee from '~/utils/event';
import intersection from 'lodash/intersection';
import styled from 'styled-components'; import styled from 'styled-components';
import useNavItems from '~/hooks/useNavItems';
import {useRouter} from 'next/router'; import {useRouter} from 'next/router';
const buildNavItems = process.env.NAV_ITEMS;
const allNavItems = ['scalars', 'samples', 'graphs', 'high-dimensional'];
const navItems = buildNavItems
? intersection(
buildNavItems.split(',').map(item => item.trim()),
allNavItems
)
: allNavItems;
const Nav = styled.nav` const Nav = styled.nav`
background-color: ${navbarBackgroundColor}; background-color: ${navbarBackgroundColor};
color: ${textInvertColor}; color: ${textInvertColor};
...@@ -101,7 +92,11 @@ const changeLanguage = () => { ...@@ -101,7 +92,11 @@ const changeLanguage = () => {
const Navbar: FunctionComponent = () => { const Navbar: FunctionComponent = () => {
const {t, i18n} = useTranslation('common'); const {t, i18n} = useTranslation('common');
const {pathname} = useRouter(); const {pathname, basePath} = useRouter();
const navItems = useNavItems();
const path = useMemo(() => pathname.replace(basePath, ''), [pathname, basePath]);
const indexUrl = useMemo(() => { const indexUrl = useMemo(() => {
// TODO: fix type // TODO: fix type
...@@ -125,7 +120,7 @@ const Navbar: FunctionComponent = () => { ...@@ -125,7 +120,7 @@ const Navbar: FunctionComponent = () => {
return ( return (
// https://nextjs.org/docs/api-reference/next/link#if-the-child-is-a-custom-component-that-wraps-an-a-tag // https://nextjs.org/docs/api-reference/next/link#if-the-child-is-a-custom-component-that-wraps-an-a-tag
<Link href={href} key={name} passHref> <Link href={href} key={name} passHref>
<NavItem active={pathname === href}> <NavItem active={path === href}>
<span className="nav-text">{t(name)}</span> <span className="nav-text">{t(name)}</span>
</NavItem> </NavItem>
</Link> </Link>
......
...@@ -56,21 +56,21 @@ const Pagination: FunctionComponent<PaginationProps & WithStyled> = ({page, tota ...@@ -56,21 +56,21 @@ const Pagination: FunctionComponent<PaginationProps & WithStyled> = ({page, tota
<Wrapper className={className}> <Wrapper className={className}>
<div> <div>
<Button disabled={currentPage <= 1} onClick={() => setPage(currentPage - 1)}> <Button disabled={currentPage <= 1} onClick={() => setPage(currentPage - 1)}>
{t('previous-page')} {t('common:previous-page')}
</Button> </Button>
<Button disabled={currentPage >= total} onClick={() => setPage(currentPage + 1)}> <Button disabled={currentPage >= total} onClick={() => setPage(currentPage + 1)}>
{t('next-page')} {t('common:next-page')}
</Button> </Button>
</div> </div>
<div> <div>
<span>{t('total-page', {count: total})}</span> <span>{t('common:total-page', {count: total})}</span>
<Input <Input
value={jumpPage} value={jumpPage}
onChange={value => setJumpPage(value)} onChange={value => setJumpPage(value)}
onKeyDown={e => e.key === 'Enter' && setPage(jumpPage)} onKeyDown={e => e.key === 'Enter' && setPage(jumpPage)}
/> />
<Button onClick={() => setPage(jumpPage)} type="primary"> <Button onClick={() => setPage(jumpPage)} type="primary">
{t('confirm')} {t('common:confirm')}
</Button> </Button>
</div> </div>
</Wrapper> </Wrapper>
......
...@@ -135,16 +135,16 @@ const RunAside: FunctionComponent<RunAsideProps> = ({ ...@@ -135,16 +135,16 @@ const RunAside: FunctionComponent<RunAsideProps> = ({
<Aside> <Aside>
{children} {children}
<section className="run-section"> <section className="run-section">
<Field className="run-select" label={t('select-runs')}> <Field className="run-select" label={t('common:select-runs')}>
<SearchInput <SearchInput
className="search-input" className="search-input"
value={search} value={search}
onChange={setSearch} onChange={setSearch}
placeholder={t('search-runs')} placeholder={t('common:search-runs')}
rounded rounded
/> />
<Checkbox value={selectAll} onChange={toggleSelectAll}> <Checkbox value={selectAll} onChange={toggleSelectAll}>
{t('select-all')} {t('common:select-all')}
</Checkbox> </Checkbox>
<div className="run-list"> <div className="run-list">
{filteredRuns.map((run, index) => ( {filteredRuns.map((run, index) => (
......
...@@ -177,7 +177,7 @@ const SampleChart: FunctionComponent<SampleChartProps> = ({run, tag, brightness, ...@@ -177,7 +177,7 @@ const SampleChart: FunctionComponent<SampleChartProps> = ({run, tag, brightness,
items={[ items={[
{ {
icon: 'download', icon: 'download',
tooltip: t('download-image'), tooltip: t('samples:download-image'),
onClick: saveImage onClick: saveImage
} }
]} ]}
......
...@@ -45,7 +45,7 @@ const StepSlider: FunctionComponent<StepSliderProps> = ({onChange, onChangeCompl ...@@ -45,7 +45,7 @@ const StepSlider: FunctionComponent<StepSliderProps> = ({onChange, onChangeCompl
return ( return (
<> <>
<Label> <Label>
<span>{`${t('step')}: ${steps[step] ?? '...'}`}</span> <span>{`${t('samples:step')}: ${steps[step] ?? '...'}`}</span>
{children && <span>{children}</span>} {children && <span>{children}</span>}
</Label> </Label>
<FullWidthRangeSlider <FullWidthRangeSlider
......
...@@ -205,25 +205,25 @@ const ScalarChart: FunctionComponent<ScalarChartProps> = ({ ...@@ -205,25 +205,25 @@ const ScalarChart: FunctionComponent<ScalarChartProps> = ({
{ {
icon: 'maximize', icon: 'maximize',
activeIcon: 'minimize', activeIcon: 'minimize',
tooltip: t('maximize'), tooltip: t('scalars:maximize'),
activeTooltip: t('minimize'), activeTooltip: t('scalars:minimize'),
toggle: true, toggle: true,
onClick: toggleMaximized onClick: toggleMaximized
}, },
{ {
icon: 'restore-size', icon: 'restore-size',
tooltip: t('restore'), tooltip: t('scalars:restore'),
onClick: () => echart.current?.restore() onClick: () => echart.current?.restore()
}, },
{ {
icon: 'log-axis', icon: 'log-axis',
tooltip: t('axis'), tooltip: t('scalars:axis'),
toggle: true, toggle: true,
onClick: toggleYAxisType onClick: toggleYAxisType
}, },
{ {
icon: 'download', icon: 'download',
tooltip: t('download-image'), tooltip: t('scalars:download-image'),
onClick: () => echart.current?.saveAsImage() onClick: () => echart.current?.saveAsImage()
} }
]} ]}
......
import React, {FunctionComponent, useEffect, useMemo} from 'react'; import React, {FunctionComponent, useEffect, useMemo} from 'react';
import {WithStyled, position, primaryColor, size} from '~/utils/style'; import {WithStyled, backgroundColor, position, primaryColor, size} from '~/utils/style';
import GridLoader from 'react-spinners/GridLoader'; import GridLoader from 'react-spinners/GridLoader';
import styled from 'styled-components'; import styled from 'styled-components';
...@@ -7,6 +7,7 @@ import useECharts from '~/hooks/useECharts'; ...@@ -7,6 +7,7 @@ import useECharts from '~/hooks/useECharts';
const Wrapper = styled.div` const Wrapper = styled.div`
position: relative; position: relative;
background-color: ${backgroundColor};
> .echarts { > .echarts {
height: 100%; height: 100%;
......
...@@ -215,7 +215,7 @@ const Select = <T extends unknown>({ ...@@ -215,7 +215,7 @@ const Select = <T extends unknown>({
? multiple ? multiple
? (value as T[]).map(findLabelByValue).join(' / ') ? (value as T[]).map(findLabelByValue).join(' / ')
: findLabelByValue(value as T) : findLabelByValue(value as T)
: placeholder || t('select'), : placeholder || t('common:select'),
[multiple, value, findLabelByValue, isSelected, placeholder, t] [multiple, value, findLabelByValue, isSelected, placeholder, t]
); );
...@@ -227,7 +227,7 @@ const Select = <T extends unknown>({ ...@@ -227,7 +227,7 @@ const Select = <T extends unknown>({
</Trigger> </Trigger>
<List opened={isOpened} empty={isListEmpty}> <List opened={isOpened} empty={isListEmpty}>
{isListEmpty {isListEmpty
? t('empty') ? t('common:empty')
: list.map((item, index) => { : list.map((item, index) => {
if (multiple) { if (multiple) {
return ( return (
......
import {fetcher} from '~/utils/fetch';
import intersection from 'lodash/intersection';
import {useMemo} from 'react';
import useRequest from '~/hooks/useRequest';
const allNavItems = ['scalars', 'samples', 'high-dimensional'];
export const navMap = {
scalar: 'scalars',
image: 'samples',
embeddings: 'high-dimensional'
} as const;
const useNavItems = () => {
const {data: components} = useRequest<(keyof typeof navMap)[]>('/components', fetcher, {
refreshInterval: 61 * 1000,
dedupingInterval: 29 * 1000,
errorRetryInterval: 29 * 1000,
errorRetryCount: Number.POSITIVE_INFINITY,
revalidateOnFocus: true,
revalidateOnReconnect: true,
refreshWhenHidden: false,
refreshWhenOffline: false
});
const navItems = useMemo(() => intersection(components?.map(component => navMap[component]) ?? [], allNavItems), [
components
]);
return navItems;
};
export default useNavItems;
...@@ -31,7 +31,6 @@ module.exports = { ...@@ -31,7 +31,6 @@ module.exports = {
DEFAULT_LANGUAGE, DEFAULT_LANGUAGE,
LOCALE_PATH, LOCALE_PATH,
LANGUAGES, LANGUAGES,
NAV_ITEMS: process.env.NAV_ITEMS,
PUBLIC_PATH: publicPath, PUBLIC_PATH: publicPath,
API_URL: apiUrl API_URL: apiUrl
}, },
......
{ {
"name": "@visualdl/core", "name": "@visualdl/core",
"version": "2.0.0-beta.35", "version": "2.0.0-beta.40",
"title": "VisualDL", "title": "VisualDL",
"description": "A platform to visualize the deep learning process and result.", "description": "A platform to visualize the deep learning process and result.",
"keywords": [ "keywords": [
...@@ -31,8 +31,8 @@ ...@@ -31,8 +31,8 @@
"test": "echo \"Error: no test specified\" && exit 0" "test": "echo \"Error: no test specified\" && exit 0"
}, },
"dependencies": { "dependencies": {
"@visualdl/i18n": "2.0.0-beta.35", "@visualdl/i18n": "2.0.0-beta.40",
"@visualdl/wasm": "2.0.0-beta.35", "@visualdl/wasm": "2.0.0-beta.40",
"bignumber.js": "9.0.0", "bignumber.js": "9.0.0",
"dagre-d3": "0.6.4", "dagre-d3": "0.6.4",
"echarts": "4.7.0", "echarts": "4.7.0",
...@@ -73,7 +73,7 @@ ...@@ -73,7 +73,7 @@
"@types/react": "16.9.34", "@types/react": "16.9.34",
"@types/react-dom": "16.9.7", "@types/react-dom": "16.9.7",
"@types/styled-components": "5.1.0", "@types/styled-components": "5.1.0",
"@visualdl/mock": "2.0.0-beta.35", "@visualdl/mock": "2.0.0-beta.40",
"babel-plugin-emotion": "10.0.33", "babel-plugin-emotion": "10.0.33",
"babel-plugin-styled-components": "1.10.7", "babel-plugin-styled-components": "1.10.7",
"babel-plugin-typescript-to-proptypes": "1.3.2", "babel-plugin-typescript-to-proptypes": "1.3.2",
...@@ -96,5 +96,5 @@ ...@@ -96,5 +96,5 @@
"pre-commit": "lint-staged" "pre-commit": "lint-staged"
} }
}, },
"gitHead": "d9641c770d9bc4037b2f1cb644fba198bd11a752" "gitHead": "ed915e4590c1527836cc802d7c6f5011a5b9a4a3"
} }
...@@ -14,7 +14,7 @@ const Error = styled.div` ...@@ -14,7 +14,7 @@ const Error = styled.div`
const Error404: NextI18NextPage = () => { const Error404: NextI18NextPage = () => {
const {t} = useTranslation('errors'); const {t} = useTranslation('errors');
return <Error>404 - {t('page-not-found')}</Error>; return <Error>404 - {t('errors:page-not-found')}</Error>;
}; };
// 404 page cannot have getInitialProps or getServerSideProps // 404 page cannot have getInitialProps or getServerSideProps
......
...@@ -48,6 +48,9 @@ export default class VDLDocument extends Document<VDLDocumentProps> { ...@@ -48,6 +48,9 @@ export default class VDLDocument extends Document<VDLDocumentProps> {
<Html lang={language} dir={languageDir}> <Html lang={language} dir={languageDir}>
<Head /> <Head />
<body> <body>
<script
dangerouslySetInnerHTML={{__html: `__vdl_public_path__='${process.env.PUBLIC_PATH}'`}}
></script>
<Main /> <Main />
<NextScript /> <NextScript />
</body> </body>
......
...@@ -18,7 +18,11 @@ interface ErrorProps { ...@@ -18,7 +18,11 @@ interface ErrorProps {
const Error: NextI18NextPage<ErrorProps> = ({statusCode}) => { const Error: NextI18NextPage<ErrorProps> = ({statusCode}) => {
const {t} = useTranslation('errors'); const {t} = useTranslation('errors');
return <ErrorDiv>{statusCode ? t('error-with-status', {statusCode}) : t('error-without-status')}</ErrorDiv>; return (
<ErrorDiv>
{statusCode ? t('errors:error-with-status', {statusCode}) : t('errors:error-without-status')}
</ErrorDiv>
);
}; };
Error.getInitialProps = ({res, err}) => { Error.getInitialProps = ({res, err}) => {
......
...@@ -264,21 +264,21 @@ const Graphs: NextI18NextPage = () => { ...@@ -264,21 +264,21 @@ const Graphs: NextI18NextPage = () => {
<AsideSection> <AsideSection>
<SubSection> <SubSection>
<Button rounded type="primary" icon="download" onClick={downloadImage}> <Button rounded type="primary" icon="download" onClick={downloadImage}>
{t('download-image')} {t('graphs:download-image')}
</Button> </Button>
<Button rounded type="primary" icon="revert" onClick={fitScreen}> <Button rounded type="primary" icon="revert" onClick={fitScreen}>
{t('restore-image')} {t('graphs:restore-image')}
</Button> </Button>
</SubSection> </SubSection>
<SubSection> <SubSection>
<Field label={`${t('scale')}:`}> <Field label={`${t('graphs:scale')}:`}>
<RangeSlider min={MIN_SCALE} max={MAX_SCALE} step={0.1} value={scale} onChange={setScale} /> <RangeSlider min={MIN_SCALE} max={MAX_SCALE} step={0.1} value={scale} onChange={setScale} />
</Field> </Field>
</SubSection> </SubSection>
<SubSection> <SubSection>
<Field label={`${t('node-info')}:`} /> <Field label={`${t('graphs:node-info')}:`} />
<NodeInfo node={currentNode} /> <NodeInfo node={currentNode} />
</SubSection> </SubSection>
</AsideSection> </AsideSection>
......
...@@ -77,13 +77,13 @@ const HighDimensional: NextI18NextPage = () => { ...@@ -77,13 +77,13 @@ const HighDimensional: NextI18NextPage = () => {
</Field> </Field>
<Field> <Field>
<Checkbox value={labelVisibility} onChange={setLabelVisibility}> <Checkbox value={labelVisibility} onChange={setLabelVisibility}>
{t('display-all-label')} {t('high-dimensional:display-all-label')}
</Checkbox> </Checkbox>
</Field> </Field>
<AsideDivider /> <AsideDivider />
<AsideTitle> <AsideTitle>
<StyledIcon type="dimension" /> <StyledIcon type="dimension" />
{t('dimension')} {t('high-dimensional:dimension')}
</AsideTitle> </AsideTitle>
<Field> <Field>
<RadioGroup value={dimension} onChange={value => setDimension(value)}> <RadioGroup value={dimension} onChange={value => setDimension(value)}>
...@@ -97,7 +97,7 @@ const HighDimensional: NextI18NextPage = () => { ...@@ -97,7 +97,7 @@ const HighDimensional: NextI18NextPage = () => {
<AsideDivider /> <AsideDivider />
<AsideTitle> <AsideTitle>
<StyledIcon type="reduction" /> <StyledIcon type="reduction" />
{t('reduction-method')} {t('high-dimensional:reduction-method')}
</AsideTitle> </AsideTitle>
<Field> <Field>
<RadioGroup value={reduction} onChange={value => setReduction(value)}> <RadioGroup value={reduction} onChange={value => setReduction(value)}>
......
import {NextI18NextPage, Router} from '~/utils/i18n'; import {NextI18NextPage, Router} from '~/utils/i18n';
import React, {useEffect} from 'react';
import {headerHeight, primaryColor, size} from '~/utils/style';
import {useEffect} from 'react'; import HashLoader from 'react-spinners/HashLoader';
import styled from 'styled-components';
import useNavItems from '~/hooks/useNavItems';
const Loading = styled.div`
${size(`calc(100vh - ${headerHeight})`, '100vw')}
display: flex;
justify-content: center;
align-items: center;
overscroll-behavior: none;
cursor: progress;
`;
const Index: NextI18NextPage = () => { const Index: NextI18NextPage = () => {
const navItmes = useNavItems();
useEffect(() => { useEffect(() => {
Router.replace('/scalars'); if (navItmes.length) {
}, []); Router.replace(`/${navItmes[0]}`);
}
}, [navItmes]);
return null; return (
<Loading>
<HashLoader size="60px" color={primaryColor} />
</Loading>
);
}; };
Index.getInitialProps = () => { Index.getInitialProps = () => {
return { return {
namespacesRequired: ['common'] namespacesRequired: []
}; };
}; };
......
...@@ -57,16 +57,16 @@ const Samples: NextI18NextPage = () => { ...@@ -57,16 +57,16 @@ const Samples: NextI18NextPage = () => {
> >
<section> <section>
<Checkbox value={showActualSize} onChange={setShowActualSize}> <Checkbox value={showActualSize} onChange={setShowActualSize}>
{t('show-actual-size')} {t('samples:show-actual-size')}
</Checkbox> </Checkbox>
</section> </section>
<section> <section>
<Field label={t('brightness')}> <Field label={t('samples:brightness')}>
<Slider min={0} max={2} step={0.01} value={brightness} onChange={setBrightness} /> <Slider min={0} max={2} step={0.01} value={brightness} onChange={setBrightness} />
</Field> </Field>
</section> </section>
<section> <section>
<Field label={t('contrast')}> <Field label={t('samples:contrast')}>
<Slider min={0} max={2} step={0.01} value={contrast} onChange={setContrast} /> <Slider min={0} max={2} step={0.01} value={contrast} onChange={setContrast} />
</Field> </Field>
</section> </section>
......
...@@ -48,7 +48,6 @@ const Scalars: NextI18NextPage = () => { ...@@ -48,7 +48,6 @@ const Scalars: NextI18NextPage = () => {
const [xAxis, setXAxis] = useState<XAxis>(xAxisValues[0]); const [xAxis, setXAxis] = useState<XAxis>(xAxisValues[0]);
const [tooltipSorting, setTooltipSorting] = useState<TooltipSorting>(toolTipSortingValues[0]); const [tooltipSorting, setTooltipSorting] = useState<TooltipSorting>(toolTipSortingValues[0]);
const onChangeTooltipSorting = (value: TooltipSorting) => setTooltipSorting(value);
const [ignoreOutliers, setIgnoreOutliers] = useState(false); const [ignoreOutliers, setIgnoreOutliers] = useState(false);
...@@ -62,24 +61,24 @@ const Scalars: NextI18NextPage = () => { ...@@ -62,24 +61,24 @@ const Scalars: NextI18NextPage = () => {
> >
<section> <section>
<Checkbox value={ignoreOutliers} onChange={setIgnoreOutliers}> <Checkbox value={ignoreOutliers} onChange={setIgnoreOutliers}>
{t('ignore-outliers')} {t('scalars:ignore-outliers')}
</Checkbox> </Checkbox>
<TooltipSortingDiv> <TooltipSortingDiv>
<span>{t('tooltip-sorting')}</span> <span>{t('scalars:tooltip-sorting')}</span>
<Select <Select
list={toolTipSortingValues.map(value => ({label: t(`tooltip-sorting-value.${value}`), value}))} list={toolTipSortingValues.map(value => ({label: t(`tooltip-sorting-value.${value}`), value}))}
value={tooltipSorting} value={tooltipSorting}
onChange={onChangeTooltipSorting} onChange={setTooltipSorting}
/> />
</TooltipSortingDiv> </TooltipSortingDiv>
</section> </section>
<section> <section>
<Field label={t('smoothing')}> <Field label={t('scalars:smoothing')}>
<Slider min={0} max={0.99} step={0.01} value={smoothing} onChangeComplete={setSmoothing} /> <Slider min={0} max={0.99} step={0.01} value={smoothing} onChangeComplete={setSmoothing} />
</Field> </Field>
</section> </section>
<section> <section>
<Field label={t('x-axis')}> <Field label={t('scalars:x-axis')}>
<RadioGroup value={xAxis} onChange={setXAxis}> <RadioGroup value={xAxis} onChange={setXAxis}>
{xAxisValues.map(value => ( {xAxisValues.map(value => (
<RadioButton key={value} value={value}> <RadioButton key={value} value={value}>
......
...@@ -8,7 +8,6 @@ ...@@ -8,7 +8,6 @@
"search-result": "搜索结果", "search-result": "搜索结果",
"search-empty": "没有找到您期望的内容,你可以尝试其他搜索词<1/>或者点击<3>查看全部图表</3>", "search-empty": "没有找到您期望的内容,你可以尝试其他搜索词<1/>或者点击<3>查看全部图表</3>",
"unselected-empty": "未选中任何数据<1/>请在右侧操作栏选择要展示的数据", "unselected-empty": "未选中任何数据<1/>请在右侧操作栏选择要展示的数据",
"all": "全部",
"empty": "空空如也", "empty": "空空如也",
"select": "请选择", "select": "请选择",
"select-all": "全选", "select-all": "全选",
......
...@@ -205,7 +205,7 @@ export const tooltip = (data: TooltipData[], i18n: I18n) => { ...@@ -205,7 +205,7 @@ export const tooltip = (data: TooltipData[], i18n: I18n) => {
run: item.run, run: item.run,
// use precision then toString to remove trailling 0 // use precision then toString to remove trailling 0
smoothed: new BigNumber(data[indexPropMap.smoothed] ?? Number.NaN).precision(5).toString(), smoothed: new BigNumber(data[indexPropMap.smoothed] ?? Number.NaN).precision(5).toString(),
value: new BigNumber(data[indexPropMap.smoothed] ?? Number.NaN).precision(5).toString(), value: new BigNumber(data[indexPropMap.value] ?? Number.NaN).precision(5).toString(),
step: data[indexPropMap.step], step: data[indexPropMap.step],
time: formatTime(data[indexPropMap.time], i18n.language), time: formatTime(data[indexPropMap.time], i18n.language),
// Relative display value should take easy-read into consideration. // Relative display value should take easy-read into consideration.
......
{ {
"name": "@visualdl/i18n", "name": "@visualdl/i18n",
"version": "2.0.0-beta.35", "version": "2.0.0-beta.40",
"description": "A platform to visualize the deep learning process and result.", "description": "A platform to visualize the deep learning process and result.",
"keywords": [ "keywords": [
"visualdl", "visualdl",
...@@ -73,5 +73,5 @@ ...@@ -73,5 +73,5 @@
"pre-commit": "lint-staged" "pre-commit": "lint-staged"
} }
}, },
"gitHead": "d9641c770d9bc4037b2f1cb644fba198bd11a752" "gitHead": "ed915e4590c1527836cc802d7c6f5011a5b9a4a3"
} }
...@@ -96,9 +96,10 @@ export const createConfig = (userConfig: Config): Config => { ...@@ -96,9 +96,10 @@ export const createConfig = (userConfig: Config): Config => {
/* /*
Set client side backend Set client side backend
*/ */
const publicPath = (window as any).__vdl_public_path__ || '';
combinedConfig.backend = { combinedConfig.backend = {
loadPath: `${process.env.PUBLIC_PATH}/${clientLocalePath}/${localeStructure}.${localeExtension}`, loadPath: `${publicPath}/${clientLocalePath}/${localeStructure}.${localeExtension}`,
addPath: `${process.env.PUBLIC_PATH}/${clientLocalePath}/${localeStructure}.missing.${localeExtension}` addPath: `${publicPath}/${clientLocalePath}/${localeStructure}.missing.${localeExtension}`
}; };
combinedConfig.ns = [combinedConfig.defaultNS]; combinedConfig.ns = [combinedConfig.defaultNS];
......
export default ['scalar', 'image', 'embeddings'];
{ {
"name": "@visualdl/mock", "name": "@visualdl/mock",
"version": "2.0.0-beta.35", "version": "2.0.0-beta.40",
"description": "A platform to visualize the deep learning process and result.", "description": "A platform to visualize the deep learning process and result.",
"keywords": [ "keywords": [
"visualdl", "visualdl",
...@@ -56,5 +56,5 @@ ...@@ -56,5 +56,5 @@
"pre-commit": "lint-staged" "pre-commit": "lint-staged"
} }
}, },
"gitHead": "d9641c770d9bc4037b2f1cb644fba198bd11a752" "gitHead": "ed915e4590c1527836cc802d7c6f5011a5b9a4a3"
} }
{ {
"name": "@visualdl/server", "name": "@visualdl/server",
"version": "2.0.0-beta.35", "version": "2.0.0-beta.40",
"description": "A platform to visualize the deep learning process and result.", "description": "A platform to visualize the deep learning process and result.",
"keywords": [ "keywords": [
"visualdl", "visualdl",
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
}, },
"scripts": { "scripts": {
"dev": "cross-env NODE_ENV=development nodemon --watch index.ts --watch webpack.config.js --exec \"ts-node index.ts\"", "dev": "cross-env NODE_ENV=development nodemon --watch index.ts --watch webpack.config.js --exec \"ts-node index.ts\"",
"build": "cross-env API_URL=/api ts-node build.ts", "build": "cross-env API_URL=/api ts-node --script-mode build.ts",
"start": "pm2-runtime ecosystem.config.js", "start": "pm2-runtime ecosystem.config.js",
"test": "echo \"Error: no test specified\" && exit 0" "test": "echo \"Error: no test specified\" && exit 0"
}, },
...@@ -36,8 +36,8 @@ ...@@ -36,8 +36,8 @@
"ecosystem.config.d.ts" "ecosystem.config.d.ts"
], ],
"dependencies": { "dependencies": {
"@visualdl/core": "2.0.0-beta.35", "@visualdl/core": "2.0.0-beta.40",
"@visualdl/i18n": "2.0.0-beta.35", "@visualdl/i18n": "2.0.0-beta.40",
"express": "4.17.1", "express": "4.17.1",
"http-proxy-middleware": "1.0.3", "http-proxy-middleware": "1.0.3",
"next": "9.3.6", "next": "9.3.6",
...@@ -49,7 +49,7 @@ ...@@ -49,7 +49,7 @@
"@types/shelljs": "0.8.7", "@types/shelljs": "0.8.7",
"@types/webpack": "4.41.12", "@types/webpack": "4.41.12",
"@types/webpack-dev-middleware": "3.7.0", "@types/webpack-dev-middleware": "3.7.0",
"@visualdl/mock": "2.0.0-beta.35", "@visualdl/mock": "2.0.0-beta.40",
"cross-env": "7.0.2", "cross-env": "7.0.2",
"nodemon": "2.0.3", "nodemon": "2.0.3",
"shelljs": "0.8.4", "shelljs": "0.8.4",
...@@ -72,5 +72,5 @@ ...@@ -72,5 +72,5 @@
"pre-commit": "lint-staged" "pre-commit": "lint-staged"
} }
}, },
"gitHead": "d9641c770d9bc4037b2f1cb644fba198bd11a752" "gitHead": "ed915e4590c1527836cc802d7c6f5011a5b9a4a3"
} }
...@@ -28,7 +28,11 @@ async function start() { ...@@ -28,7 +28,11 @@ async function start() {
await builder('build'); await builder('build');
await builder('export', '-o', dist); await builder('export', '-o', dist);
} catch (e) { } catch (e) {
process.exit(e); if ('number' === typeof e) {
process.exit(e);
}
console.error(e);
process.exit(1);
} }
process.exit(0); process.exit(0);
} }
......
{ {
"name": "@visualdl/serverless", "name": "@visualdl/serverless",
"version": "2.0.0-beta.35", "version": "2.0.0-beta.40",
"description": "A platform to visualize the deep learning process and result.", "description": "A platform to visualize the deep learning process and result.",
"keywords": [ "keywords": [
"visualdl", "visualdl",
...@@ -27,13 +27,13 @@ ...@@ -27,13 +27,13 @@
"dist" "dist"
], ],
"scripts": { "scripts": {
"build": "cross-env PUBLIC_PATH=/app API_URL=/api ts-node build.ts", "build": "cross-env ts-node --script-mode build.ts",
"test": "echo \"Error: no test specified\" && exit 0" "test": "echo \"Error: no test specified\" && exit 0"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "13.13.5", "@types/node": "13.13.5",
"@types/rimraf": "3.0.0", "@types/rimraf": "3.0.0",
"@visualdl/core": "2.0.0-beta.35", "@visualdl/core": "2.0.0-beta.40",
"cross-env": "7.0.2", "cross-env": "7.0.2",
"rimraf": "3.0.2", "rimraf": "3.0.2",
"ts-node": "8.10.1", "ts-node": "8.10.1",
...@@ -51,5 +51,5 @@ ...@@ -51,5 +51,5 @@
"pre-commit": "lint-staged" "pre-commit": "lint-staged"
} }
}, },
"gitHead": "d9641c770d9bc4037b2f1cb644fba198bd11a752" "gitHead": "ed915e4590c1527836cc802d7c6f5011a5b9a4a3"
} }
{ {
"name": "@visualdl/wasm", "name": "@visualdl/wasm",
"version": "2.0.0-beta.35", "version": "2.0.0-beta.40",
"title": "VisualDL", "title": "VisualDL",
"description": "A platform to visualize the deep learning process and result.", "description": "A platform to visualize the deep learning process and result.",
"keywords": [ "keywords": [
...@@ -49,5 +49,5 @@ ...@@ -49,5 +49,5 @@
"pre-commit": "lint-staged" "pre-commit": "lint-staged"
} }
}, },
"gitHead": "d9641c770d9bc4037b2f1cb644fba198bd11a752" "gitHead": "ed915e4590c1527836cc802d7c6f5011a5b9a4a3"
} }
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
set -e set -e
WORKING_PATH=`pwd` WORKING_PATH=$(pwd)
SERVER_DIR="packages/server/dist" SERVER_DIR="packages/server/dist"
SERVER_DIR_PATH="$WORKING_PATH/$SERVER_DIR" SERVER_DIR_PATH="$WORKING_PATH/$SERVER_DIR"
SERVERLESS_DIR="packages/serverless/dist" SERVERLESS_DIR="packages/serverless/dist"
...@@ -10,15 +10,31 @@ SERVERLESS_DIR_PATH="$WORKING_PATH/$SERVERLESS_DIR" ...@@ -10,15 +10,31 @@ SERVERLESS_DIR_PATH="$WORKING_PATH/$SERVERLESS_DIR"
OUTPUT="output" OUTPUT="output"
OUTPUT_PATH="$WORKING_PATH/$OUTPUT" OUTPUT_PATH="$WORKING_PATH/$OUTPUT"
# clean
rm -rf "$SERVER_DIR_PATH"
rm -rf "$SERVERLESS_DIR_PATH"
# build # build
npx lerna run build if [ "$SCOPE" = "serverless" ]; then
npx lerna run --scope "@visualdl/serverless" --include-dependencies build
elif [ "$SCOPE" = "server" ]; then
npx lerna run --scope "@visualdl/server" --include-dependencies build
elif [ "$SCOPE" = "cli" ]; then
npx lerna run --scope "@visualdl/cli" --include-dependencies build
else
npx lerna run build
fi
# generate output # generate output
rm -rf $OUTPUT_PATH rm -rf "$OUTPUT_PATH"
mkdir -p $OUTPUT_PATH mkdir -p "$OUTPUT_PATH"
# package server files # package server files
(cd $SERVER_DIR_PATH && tar zcf $OUTPUT_PATH/server.tar.gz .) if [ -d "$SERVER_DIR_PATH" ]; then
(cd "$SERVER_DIR_PATH" && tar zcf "${OUTPUT_PATH}/server.tar.gz" .)
fi
# package serverless files # package serverless files
(cd $SERVERLESS_DIR_PATH && tar zcf $OUTPUT_PATH/serverless.tar.gz .) if [ -d "$SERVERLESS_DIR_PATH" ]; then
(cd "$SERVERLESS_DIR_PATH" && tar zcf "${OUTPUT_PATH}/serverless.tar.gz" .)
fi
...@@ -6,7 +6,7 @@ set -e ...@@ -6,7 +6,7 @@ set -e
# https://rustup.rs/ # https://rustup.rs/
if ! hash rustup 2>/dev/null; then if ! hash rustup 2>/dev/null; then
curl https://sh.rustup.rs -sSf | sh -s -- --no-modify-path --default-toolchain nightly -y curl https://sh.rustup.rs -sSf | sh -s -- --no-modify-path --default-toolchain nightly -y
source $HOME/.cargo/env source "$HOME/.cargo/env"
fi fi
...@@ -18,25 +18,11 @@ fi ...@@ -18,25 +18,11 @@ fi
# fi # fi
# wine
if hash apt 2>/dev/null; then
sudo dpkg --add-architecture i386
wget -nc https://download.opensuse.org/repositories/Emulators:/Wine:/Debian/xUbuntu_18.04/Release.key
sudo apt-key add Release.key
sudo apt-add-repository 'deb https://download.opensuse.org/repositories/Emulators:/Wine:/Debian/xUbuntu_18.04/ ./'
wget -nc https://dl.winehq.org/wine-builds/winehq.key
sudo apt-key add winehq.key
sudo apt-add-repository 'deb https://dl.winehq.org/wine-builds/ubuntu/ bionic main'
sudo apt update
sudo apt install --install-recommends winehq-stable
fi
# yarn # yarn
curl --compressed -o- -L https://yarnpkg.com/install.sh | bash if ! hash yarn 2>/dev/null; then
export PATH="$HOME/.yarn/bin:$HOME/.config/yarn/global/node_modules/.bin:$PATH" curl --compressed -o- -L https://yarnpkg.com/install.sh | bash
export PATH="$HOME/.yarn/bin:$HOME/.config/yarn/global/node_modules/.bin:$PATH"
fi
# yarn install # yarn install
yarn install --frozen-lockfile yarn install --frozen-lockfile
此差异已折叠。
numpy == 1.16.6; python_version < "3.0" numpy
numpy ; python_version >= "3.0" requests
recommonmark >= 0.6.0
scipy == 1.2.3; python_version < "3.0" scipy == 1.2.3; python_version < "3.0"
scipy >= 1.4.1; python_version >= "3.0" scipy >= 1.4.1; python_version >= "3.0"
Sphinx == 1.8.5; python_version < "3.0"
Sphinx >= 2.4.4; python_version >= "3.0"
sphinx-rtd-theme >= 0.4.3
flake8 >= 3.7.9 flake8 >= 3.7.9
Pillow == 6.2.2; python_version < "3.0" Pillow == 6.2.2; python_version < "3.0"
Pillow >= 7.0.0; python_version >= "3.0" Pillow >= 7.0.0; python_version >= "3.0"
...@@ -14,4 +10,4 @@ flask >= 1.1.1 ...@@ -14,4 +10,4 @@ flask >= 1.1.1
Flask-Babel >= 1.0.0 Flask-Babel >= 1.0.0
six >= 1.14.0 six >= 1.14.0
protobuf >= 3.1.0 protobuf >= 3.1.0
opencv-python opencv-python
\ No newline at end of file
...@@ -2,106 +2,45 @@ ...@@ -2,106 +2,45 @@
set -e set -e
TOP_DIR=$(pwd) TOP_DIR=$(pwd)
FRONTEND_DIR=$TOP_DIR/frontend FRONTEND_DIR=${TOP_DIR}/frontend
BACKEND_DIR=$TOP_DIR/visualdl BUILD_DIR=${TOP_DIR}/build
BUILD_DIR=$TOP_DIR/build
mkdir -p $BUILD_DIR mkdir -p "$BUILD_DIR"
build_frontend_fake() { build_frontend_fake() {
mkdir -p "$BUILD_DIR/package/dist" mkdir -p "$BUILD_DIR/package/dist"
} }
build_frontend_from_source() {
build_frontend_fake
cd $FRONTEND_DIR
./scripts/install.sh
./scripts/build.sh
# extract
tar zxf "$FRONTEND_DIR/output/serverless.tar.gz" -C "$BUILD_DIR/package/serverless"
}
build_frontend() { build_frontend() {
local PACKAGE="@visualdl/serverless" mkdir -p "$BUILD_DIR/package/dist"
local NAME=${PACKAGE#*@}
local NAME=${NAME////-}
echo $NAME
local TAG="latest"
local TARBALL="${PACKAGE}@${TAG}"
# get version
local VERSION=`npm view ${TARBALL} dist-tags.${TAG}`
if [ "$?" -ne "0" ]; then
echo "Cannot get version"
exit 1
fi
local FILENAME="${NAME}-${VERSION}.tgz"
# get sha1sum
local SHA1SUM=`npm view ${TARBALL} dist.shasum`
if [ "$?" -ne "0" ]; then
echo "Cannot get sha1sum"
exit 1
fi
rm -f "$BUILD_DIR/${NAME}-*.tgz.sha1"
echo "${SHA1SUM} ${FILENAME}" > "$BUILD_DIR/${FILENAME}.sha1"
local DOWNLOAD="1"
# cached file exists
if [ -f "$BUILD_DIR/$FILENAME" ]; then
# check sha1sum
(cd $BUILD_DIR && sha1sum -c "${FILENAME}.sha1")
# check pass, use chached file
if [ "$?" -eq "0" ]; then
echo "Using cached npm package file ${FILENAME}"
DOWNLOAD="0"
fi
fi
if [ "$DOWNLOAD" -eq "1" ]; then
echo "Donwloading npm package, please wait..."
# remove cache
rm -f "$BUILD_DIR/${NAME}-*.tgz"
# download file
FILENAME=`(cd $BUILD_DIR && npm pack ${TARBALL})`
# check sha1sum of downloaded file cd "$FRONTEND_DIR"
(cd $BUILD_DIR && sha1sum -c "${FILENAME}.sha1") ./scripts/install.sh
if [ "$?" -ne "0" ]; then SCOPE="serverless" PUBLIC_PATH="/{{PUBLIC_PATH}}" API_URL="/{{PUBLIC_PATH}}/api" ./scripts/build.sh
echo "Check sum failed, download may not finish correctly."
exit 1
else
echo "Check sum pass."
fi
fi
# extract # extract
tar zxf "$BUILD_DIR/$FILENAME" -C "$BUILD_DIR" tar zxf "$FRONTEND_DIR/output/serverless.tar.gz" -C "$BUILD_DIR/package/dist"
} }
clean_env() { clean_env() {
rm -rf $TOP_DIR/visualdl/server/dist rm -rf "$TOP_DIR/visualdl/server/dist"
rm -rf $BUILD_DIR/bdist* rm -rf "$BUILD_DIR/bdist*"
rm -rf $BUILD_DIR/lib* rm -rf "$BUILD_DIR/lib*"
rm -rf $BUILD_DIR/temp* rm -rf "$BUILD_DIR/temp*"
rm -rf $BUILD_DIR/scripts* rm -rf "$BUILD_DIR/scripts*"
rm -rf $BUILD_DIR/package rm -rf "$BUILD_DIR/package"
} }
package() { package() {
cp -rf $BUILD_DIR/package/dist $TOP_DIR/visualdl/server/ cp -rf "$BUILD_DIR/package/dist" "$TOP_DIR/visualdl/server/"
} }
ARG=$1 ARG=$1
echo "ARG: " $ARG echo "ARG: ${ARG}"
clean_env clean_env
if [ "$ARG" = "travis-CI" ]; then if [[ "$ARG" = "travis-CI" ]]; then
build_frontend_fake build_frontend_fake
else else
build_frontend build_frontend
......
...@@ -19,6 +19,7 @@ import json ...@@ -19,6 +19,7 @@ import json
import os import os
import time import time
import sys import sys
import signal
import multiprocessing import multiprocessing
import threading import threading
import re import re
...@@ -27,12 +28,11 @@ import requests ...@@ -27,12 +28,11 @@ import requests
from visualdl.reader.reader import LogReader from visualdl.reader.reader import LogReader
from argparse import ArgumentParser from argparse import ArgumentParser
from flask import (Flask, Response, redirect, request, send_file, from flask import (Flask, Response, redirect, request, send_file, send_from_directory)
send_from_directory)
from flask_babel import Babel from flask_babel import Babel
import visualdl.server import visualdl.server
from visualdl.server import lib from visualdl.server import (lib, template)
from visualdl.server.log import logger from visualdl.server.log import logger
from visualdl.python.cache import MemCache from visualdl.python.cache import MemCache
...@@ -45,7 +45,8 @@ support_language = ["en", "zh"] ...@@ -45,7 +45,8 @@ support_language = ["en", "zh"]
default_language = support_language[0] default_language = support_language[0]
server_path = os.path.abspath(os.path.dirname(sys.argv[0])) server_path = os.path.abspath(os.path.dirname(sys.argv[0]))
static_file_path = os.path.join(SERVER_DIR, "./dist") static_file_path = os.path.join(SERVER_DIR, "./static")
template_file_path = os.path.join(SERVER_DIR, "./dist")
mock_data_path = os.path.join(SERVER_DIR, "./mock_data/") mock_data_path = os.path.join(SERVER_DIR, "./mock_data/")
...@@ -56,13 +57,15 @@ class ParseArgs(object): ...@@ -56,13 +57,15 @@ class ParseArgs(object):
port=8040, port=8040,
model_pb="", model_pb="",
cache_timeout=20, cache_timeout=20,
language=None): language=None,
public_path=None):
self.logdir = logdir self.logdir = logdir
self.host = host self.host = host
self.port = port self.port = port
self.model_pb = model_pb self.model_pb = model_pb
self.cache_timeout = cache_timeout self.cache_timeout = cache_timeout
self.language = language self.language = language
self.public_path = public_path
def try_call(function, *args, **kwargs): def try_call(function, *args, **kwargs):
...@@ -120,6 +123,14 @@ def parse_args(): ...@@ -120,6 +123,14 @@ def parse_args():
type=str, type=str,
action="store", action="store",
help="set the default language") help="set the default language")
parser.add_argument(
"-P",
"--public-path",
type=str,
action="store",
default="/app",
help="set public path"
)
args = parser.parse_args() args = parser.parse_args()
if not args.logdir: if not args.logdir:
...@@ -155,94 +166,86 @@ def create_app(args): ...@@ -155,94 +166,86 @@ def create_app(args):
CACHE = MemCache(timeout=args.cache_timeout) CACHE = MemCache(timeout=args.cache_timeout)
cache_get = lib.cache_get(CACHE) cache_get = lib.cache_get(CACHE)
public_path = args.public_path.rstrip('/')
api_path = public_path + '/api'
@babel.localeselector @babel.localeselector
def get_locale(): def get_locale():
language = args.language lang = args.language
if not language or language not in support_language: if not lang or lang not in support_language:
language = request.accept_languages.best_match(support_language) lang = request.accept_languages.best_match(support_language)
return language return lang
@app.route("/") @app.route("/")
def base():
return redirect(public_path, code=302)
@app.route(public_path + "/")
def index(): def index():
language = get_locale() lang = get_locale()
if language == default_language: if lang == default_language:
return redirect('/app/index', code=302) return redirect(public_path + '/index', code=302)
return redirect('/app/' + language + '/index', code=302) return redirect(public_path + '/' + lang + '/index', code=302)
@app.route('/app/<path:filename>') @app.route(public_path + '/<path:filename>')
def serve_static(filename): def serve_static(filename):
return send_from_directory( return send_from_directory(
os.path.join(server_path, static_file_path), filename os.path.join(server_path, static_file_path), filename
if re.search(r'\..+$', filename) else filename + '.html') if re.search(r'\..+$', filename) else filename + '.html')
@app.route('/graphs/image') @app.route(api_path + "/components")
def serve_graph():
return send_file(os.path.join(os.getcwd(), graph_image_path))
@app.route('/api/logdir')
def logdir():
result = gen_result(0, "", {"logdir": args.logdir})
return Response(json.dumps(result), mimetype='application/json')
@app.route('/api/language')
def language():
data = get_locale()
result = gen_result(0, "", data)
return Response(json.dumps(result), mimetype='application/json')
@app.route("/api/components")
def components(): def components():
data = cache_get('/data/components', lib.get_components, log_reader) data = cache_get('/data/components', lib.get_components, log_reader)
result = gen_result(0, "", data) result = gen_result(0, "", data)
return Response(json.dumps(result), mimetype='application/json') return Response(json.dumps(result), mimetype='application/json')
@app.route('/api/runs') @app.route(api_path + '/runs')
def runs(): def runs():
data = cache_get('/data/runs', lib.get_runs, log_reader) data = cache_get('/data/runs', lib.get_runs, log_reader)
result = gen_result(0, "", data) result = gen_result(0, "", data)
return Response(json.dumps(result), mimetype='application/json') return Response(json.dumps(result), mimetype='application/json')
@app.route('/api/tags') @app.route(api_path + '/tags')
def tags(): def tags():
data = cache_get('/data/tags', lib.get_tags, log_reader) data = cache_get('/data/tags', lib.get_tags, log_reader)
result = gen_result(0, "", data) result = gen_result(0, "", data)
return Response(json.dumps(result), mimetype='application/json') return Response(json.dumps(result), mimetype='application/json')
@app.route('/api/logs') @app.route(api_path + '/logs')
def logs(): def logs():
data = cache_get('/data/logs', lib.get_logs, log_reader) data = cache_get('/data/logs', lib.get_logs, log_reader)
result = gen_result(0, "", data) result = gen_result(0, "", data)
return Response(json.dumps(result), mimetype='application/json') return Response(json.dumps(result), mimetype='application/json')
@app.route("/api/scalars/tags") @app.route(api_path + "/scalars/tags")
def scalar_tags(): def scalar_tags():
data = cache_get("/data/plugin/scalars/tags", try_call, data = cache_get("/data/plugin/scalars/tags", try_call,
lib.get_scalar_tags, log_reader) lib.get_scalar_tags, log_reader)
result = gen_result(0, "", data) result = gen_result(0, "", data)
return Response(json.dumps(result), mimetype='application/json') return Response(json.dumps(result), mimetype='application/json')
@app.route("/api/images/tags") @app.route(api_path + "/images/tags")
def image_tags(): def image_tags():
data = cache_get("/data/plugin/images/tags", try_call, data = cache_get("/data/plugin/images/tags", try_call,
lib.get_image_tags, log_reader) lib.get_image_tags, log_reader)
result = gen_result(0, "", data) result = gen_result(0, "", data)
return Response(json.dumps(result), mimetype='application/json') return Response(json.dumps(result), mimetype='application/json')
@app.route("/api/audio/tags") @app.route(api_path + "/audio/tags")
def audio_tags(): def audio_tags():
data = cache_get("/data/plugin/audio/tags", try_call, data = cache_get("/data/plugin/audio/tags", try_call,
lib.get_audio_tags, log_reader) lib.get_audio_tags, log_reader)
result = gen_result(0, "", data) result = gen_result(0, "", data)
return Response(json.dumps(result), mimetype='application/json') return Response(json.dumps(result), mimetype='application/json')
@app.route("/api/embeddings/tags") @app.route(api_path + "/embeddings/tags")
def embeddings_tags(): def embeddings_tags():
data = cache_get("/data/plugin/embeddings/tags", try_call, data = cache_get("/data/plugin/embeddings/tags", try_call,
lib.get_embeddings_tags, log_reader) lib.get_embeddings_tags, log_reader)
result = gen_result(0, "", data) result = gen_result(0, "", data)
return Response(json.dumps(result), mimetype='application/json') return Response(json.dumps(result), mimetype='application/json')
@app.route('/api/scalars/list') @app.route(api_path + '/scalars/list')
def scalars(): def scalars():
run = request.args.get('run') run = request.args.get('run')
tag = request.args.get('tag') tag = request.args.get('tag')
...@@ -251,7 +254,7 @@ def create_app(args): ...@@ -251,7 +254,7 @@ def create_app(args):
result = gen_result(0, "", data) result = gen_result(0, "", data)
return Response(json.dumps(result), mimetype='application/json') return Response(json.dumps(result), mimetype='application/json')
@app.route('/api/images/list') @app.route(api_path + '/images/list')
def images(): def images():
mode = request.args.get('run') mode = request.args.get('run')
tag = request.args.get('tag') tag = request.args.get('tag')
...@@ -263,7 +266,7 @@ def create_app(args): ...@@ -263,7 +266,7 @@ def create_app(args):
return Response(json.dumps(result), mimetype='application/json') return Response(json.dumps(result), mimetype='application/json')
@app.route('/api/images/image') @app.route(api_path + '/images/image')
def individual_image(): def individual_image():
mode = request.args.get('run') mode = request.args.get('run')
tag = request.args.get('tag') # include a index tag = request.args.get('tag') # include a index
...@@ -275,7 +278,7 @@ def create_app(args): ...@@ -275,7 +278,7 @@ def create_app(args):
mode, tag, step_index) mode, tag, step_index)
return Response(data, mimetype="image/png") return Response(data, mimetype="image/png")
@app.route('/api/embeddings/embedding') @app.route(api_path + '/embeddings/embedding')
def embeddings(): def embeddings():
run = request.args.get('run') run = request.args.get('run')
tag = request.args.get('tag', 'default') tag = request.args.get('tag', 'default')
...@@ -288,7 +291,7 @@ def create_app(args): ...@@ -288,7 +291,7 @@ def create_app(args):
result = gen_result(0, "", data) result = gen_result(0, "", data)
return Response(json.dumps(result), mimetype='application/json') return Response(json.dumps(result), mimetype='application/json')
@app.route('/api/audio/list') @app.route(api_path + '/audio/list')
def audio(): def audio():
run = request.args.get('run') run = request.args.get('run')
tag = request.args.get('tag') tag = request.args.get('tag')
...@@ -300,7 +303,7 @@ def create_app(args): ...@@ -300,7 +303,7 @@ def create_app(args):
return Response(json.dumps(result), mimetype='application/json') return Response(json.dumps(result), mimetype='application/json')
@app.route('/api/audio/audio') @app.route(api_path + '/audio/audio')
def individual_audio(): def individual_audio():
run = request.args.get('run') run = request.args.get('run')
tag = request.args.get('tag') # include a index tag = request.args.get('tag') # include a index
...@@ -319,6 +322,7 @@ def create_app(args): ...@@ -319,6 +322,7 @@ def create_app(args):
def _open_browser(app, index_url): def _open_browser(app, index_url):
while True: while True:
# noinspection PyBroadException
try: try:
requests.get(index_url) requests.get(index_url)
break break
...@@ -333,6 +337,7 @@ def _run(logdir, ...@@ -333,6 +337,7 @@ def _run(logdir,
model_pb="", model_pb="",
cache_timeout=20, cache_timeout=20,
language=None, language=None,
public_path="/app",
open_browser=False): open_browser=False):
args = ParseArgs( args = ParseArgs(
logdir=logdir, logdir=logdir,
...@@ -340,10 +345,11 @@ def _run(logdir, ...@@ -340,10 +345,11 @@ def _run(logdir,
port=port, port=port,
model_pb=model_pb, model_pb=model_pb,
cache_timeout=cache_timeout, cache_timeout=cache_timeout,
language=language) language=language,
public_path=public_path)
logger.info(" port=" + str(args.port)) logger.info(" port=" + str(args.port))
app = create_app(args) app = create_app(args)
index_url = "http://" + host + ":" + str(port) index_url = "http://" + host + ":" + str(port) + args.public_path
if open_browser: if open_browser:
threading.Thread( threading.Thread(
target=_open_browser, kwargs={"app": app, target=_open_browser, kwargs={"app": app,
...@@ -357,6 +363,7 @@ def run(logdir, ...@@ -357,6 +363,7 @@ def run(logdir,
model_pb="", model_pb="",
cache_timeout=20, cache_timeout=20,
language=None, language=None,
public_path="/app",
open_browser=False): open_browser=False):
kwarg = { kwarg = {
"logdir": logdir, "logdir": logdir,
...@@ -365,6 +372,7 @@ def run(logdir, ...@@ -365,6 +372,7 @@ def run(logdir,
"model_pb": model_pb, "model_pb": model_pb,
"cache_timeout": cache_timeout, "cache_timeout": cache_timeout,
"language": language, "language": language,
"public_path": public_path,
"open_browser": open_browser "open_browser": open_browser
} }
...@@ -373,16 +381,27 @@ def run(logdir, ...@@ -373,16 +381,27 @@ def run(logdir,
return p.pid return p.pid
def render_template(args):
template.render(
template_file_path,
static_file_path,
PUBLIC_PATH=args.public_path.strip('/'))
def clean_template(signalnum, frame):
template.clean(static_file_path)
sys.exit(0)
def main(): def main():
args = parse_args() args = parse_args()
render_template(args)
for sig in [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]:
signal.signal(sig, clean_template)
logger.info(" port=" + str(args.port)) logger.info(" port=" + str(args.port))
app = create_app(args=args) app = create_app(args=args)
app.run(debug=False, host=args.host, port=args.port, threaded=False) app.run(debug=False, host=args.host, port=args.port, threaded=False)
if __name__ == "__main__": if __name__ == "__main__":
main()
args = parse_args()
logger.info(" port=" + str(args.port))
app = create_app(args=args)
app.run(debug=False, host=args.host, port=args.port, threaded=False)
# Copyright (c) 2017 VisualDL Authors. All Rights Reserve.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# =======================================================================
import os
from shutil import (copytree, rmtree)
def render(path, dest, **context):
clean(dest)
copytree(path, dest)
for root, dirs, files in os.walk(dest):
for file in files:
if file.endswith(".html") or file.endswith(".js") or file.endswith(".css"):
file_path = os.path.join(root, file)
content = ""
with open(file_path, "r") as f:
content = f.read()
for key, value in context.items():
content = content.replace("{{" + key + "}}", value)
with open(file_path, "w") as f:
f.write(content)
def clean(path):
if os.path.exists(path):
rmtree(path)
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册