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

feat: support model graph from backend (#662)

* feat: support model graph from backend

* feat: add filename in graph api

* feat: support api token key in visualdl service
上级 3e8604ba
...@@ -9,14 +9,11 @@ import { ...@@ -9,14 +9,11 @@ import {
textColor, textColor,
textLightColor, textLightColor,
textLighterColor, textLighterColor,
tooltipBackgroundColor,
tooltipTextColor,
transitionProps transitionProps
} from '~/utils/style'; } from '~/utils/style';
import Icon from '~/components/Icon'; import Icon from '~/components/Icon';
import ReactTooltip from 'react-tooltip'; import Tippy from '@tippyjs/react';
import {nanoid} from 'nanoid';
import styled from 'styled-components'; import styled from 'styled-components';
const Toolbox = styled.div<{reversed?: boolean}>` const Toolbox = styled.div<{reversed?: boolean}>`
...@@ -65,17 +62,15 @@ type ToggleChartToolboxItem = { ...@@ -65,17 +62,15 @@ type ToggleChartToolboxItem = {
export type ChartTooboxItem = NormalChartToolboxItem | ToggleChartToolboxItem; export type ChartTooboxItem = NormalChartToolboxItem | ToggleChartToolboxItem;
type ChartToolboxProps = { type ChartToolboxProps = {
cid?: string;
items: ChartTooboxItem[]; items: ChartTooboxItem[];
reversed?: boolean; reversed?: boolean;
tooltipPlace?: 'top' | 'bottom' | 'left' | 'right'; tooltipPlacement?: 'top' | 'bottom' | 'left' | 'right';
}; };
const ChartToolbox: FunctionComponent<ChartToolboxProps & WithStyled> = ({ const ChartToolbox: FunctionComponent<ChartToolboxProps & WithStyled> = ({
cid, tooltipPlacement,
items, items,
reversed, reversed,
tooltipPlace,
className className
}) => { }) => {
const [activeStatus, setActiveStatus] = useState<boolean[]>(new Array(items.length).fill(false)); const [activeStatus, setActiveStatus] = useState<boolean[]>(new Array(items.length).fill(false));
...@@ -96,37 +91,39 @@ const ChartToolbox: FunctionComponent<ChartToolboxProps & WithStyled> = ({ ...@@ -96,37 +91,39 @@ const ChartToolbox: FunctionComponent<ChartToolboxProps & WithStyled> = ({
[items, activeStatus] [items, activeStatus]
); );
const [id] = useState(`chart-toolbox-tooltip-${cid || nanoid()}`); const getToolboxItem = useCallback(
(item: ChartTooboxItem, index: number) => (
<ToolboxItem
key={index}
reversed={reversed}
active={item.toggle && !item.activeIcon && activeStatus[index]}
onClick={() => onClick(index)}
>
<Icon type={item.toggle ? (activeStatus[index] && item.activeIcon) || item.icon : item.icon} />
</ToolboxItem>
),
[activeStatus, onClick, reversed]
);
return ( return (
<> <>
<Toolbox className={className} reversed={reversed}> <Toolbox className={className} reversed={reversed}>
{items.map((item, index) => ( {items.map((item, index) =>
<ToolboxItem item.tooltip ? (
key={index} <Tippy
reversed={reversed} content={
active={item.toggle && !item.activeIcon && activeStatus[index]} item.toggle ? (activeStatus[index] && item.activeTooltip) || item.tooltip : item.tooltip
onClick={() => onClick(index)} }
data-for={item.tooltip ? id : null} placement={tooltipPlacement || 'top'}
data-tip={ key={index}
item.tooltip >
? item.toggle {getToolboxItem(item, index)}
? (activeStatus[index] && item.activeTooltip) || item.tooltip </Tippy>
: item.tooltip ) : (
: null getToolboxItem(item, index)
} )
> )}
<Icon type={item.toggle ? (activeStatus[index] && item.activeIcon) || item.icon : item.icon} />
</ToolboxItem>
))}
</Toolbox> </Toolbox>
<ReactTooltip
id={id}
place={tooltipPlace ?? 'top'}
textColor={tooltipTextColor}
backgroundColor={tooltipBackgroundColor}
effect="solid"
/>
</> </>
); );
}; };
......
...@@ -42,6 +42,8 @@ const Wrapper = styled.div` ...@@ -42,6 +42,8 @@ const Wrapper = styled.div`
} }
`; `;
const reload = () => window.location.reload();
const Error: FunctionComponent<WithStyled> = ({className, children}) => { const Error: FunctionComponent<WithStyled> = ({className, children}) => {
const {t} = useTranslation('errors'); const {t} = useTranslation('errors');
...@@ -79,7 +81,7 @@ const Error: FunctionComponent<WithStyled> = ({className, children}) => { ...@@ -79,7 +81,7 @@ const Error: FunctionComponent<WithStyled> = ({className, children}) => {
<li> <li>
<Trans i18nKey="errors:common.3"> <Trans i18nKey="errors:common.3">
Log files are generated and data is writte. Please try to&nbsp; Log files are generated and data is writte. Please try to&nbsp;
<a href="javascript:location.reload()">Refresh</a>. <a onClick={reload}>Refresh</a>.
</Trans> </Trans>
</li> </li>
<li> <li>
......
...@@ -5,6 +5,7 @@ import {backgroundColor, borderColor, contentHeight, position, primaryColor, rem ...@@ -5,6 +5,7 @@ import {backgroundColor, borderColor, contentHeight, position, primaryColor, rem
import ChartToolbox from '~/components/ChartToolbox'; import ChartToolbox from '~/components/ChartToolbox';
import HashLoader from 'react-spinners/HashLoader'; import HashLoader from 'react-spinners/HashLoader';
import styled from 'styled-components'; import styled from 'styled-components';
import {toast} from 'react-toastify';
import {useTranslation} from '~/utils/i18n'; import {useTranslation} from '~/utils/i18n';
const toolboxHeight = rem(40); const toolboxHeight = rem(40);
...@@ -80,7 +81,7 @@ export type GraphRef = { ...@@ -80,7 +81,7 @@ export type GraphRef = {
}; };
type GraphProps = { type GraphProps = {
files: FileList | null; files: FileList | File[] | null;
uploader: JSX.Element; uploader: JSX.Element;
showAttributes: boolean; showAttributes: boolean;
showInitializers: boolean; showInitializers: boolean;
...@@ -135,6 +136,12 @@ const Graph = React.forwardRef<GraphRef, GraphProps>( ...@@ -135,6 +136,12 @@ const Graph = React.forwardRef<GraphRef, GraphProps>(
return; return;
case 'search': case 'search':
return onSearch?.(data); return onSearch?.(data);
case 'cancel':
return setLoading(false);
case 'error':
toast(data);
setLoading(false);
return;
case 'show-model-properties': case 'show-model-properties':
return onShowModelProperties?.(data); return onShowModelProperties?.(data);
case 'show-node-properties': case 'show-node-properties':
...@@ -146,13 +153,6 @@ const Graph = React.forwardRef<GraphRef, GraphProps>( ...@@ -146,13 +153,6 @@ const Graph = React.forwardRef<GraphRef, GraphProps>(
}, },
[onRendered, onSearch, onShowModelProperties, onShowNodeProperties, onShowNodeDocumentation] [onRendered, onSearch, onShowModelProperties, onShowNodeProperties, onShowNodeDocumentation]
); );
useEffect(() => {
if (process.browser) {
window.addEventListener('message', handler);
return () => window.removeEventListener('message', handler);
}
}, [handler]);
const dispatch = useCallback((type: string, data?: unknown) => { const dispatch = useCallback((type: string, data?: unknown) => {
if (process.browser) { if (process.browser) {
iframe.current?.contentWindow?.postMessage( iframe.current?.contentWindow?.postMessage(
...@@ -164,11 +164,28 @@ const Graph = React.forwardRef<GraphRef, GraphProps>( ...@@ -164,11 +164,28 @@ const Graph = React.forwardRef<GraphRef, GraphProps>(
); );
} }
}, []); }, []);
useEffect(() => {
if (process.browser) {
window.addEventListener('message', handler);
dispatch('ready');
return () => {
window.removeEventListener('message', handler);
};
}
}, [handler, dispatch]);
useEffect(() => dispatch('change-files', files), [dispatch, files]); useEffect(() => (ready && dispatch('change-files', files)) || undefined, [dispatch, files, ready]);
useEffect(() => dispatch('toggle-attributes', showAttributes), [dispatch, showAttributes]); useEffect(() => (ready && dispatch('toggle-attributes', showAttributes)) || undefined, [
useEffect(() => dispatch('toggle-initializers', showInitializers), [dispatch, showInitializers]); dispatch,
useEffect(() => dispatch('toggle-names', showNames), [dispatch, showNames]); showAttributes,
ready
]);
useEffect(() => (ready && dispatch('toggle-initializers', showInitializers)) || undefined, [
dispatch,
showInitializers,
ready
]);
useEffect(() => (ready && dispatch('toggle-names', showNames)) || undefined, [dispatch, showNames, ready]);
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
export(type) { export(type) {
...@@ -225,7 +242,7 @@ const Graph = React.forwardRef<GraphRef, GraphProps>( ...@@ -225,7 +242,7 @@ const Graph = React.forwardRef<GraphRef, GraphProps>(
} }
]} ]}
reversed reversed
tooltipPlace="bottom" tooltipPlacement="bottom"
/> />
<Content> <Content>
<iframe <iframe
......
...@@ -15,6 +15,7 @@ import Icon from '~/components/Icon'; ...@@ -15,6 +15,7 @@ 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 {getApiToken} from '~/utils/fetch';
import styled from 'styled-components'; import styled from 'styled-components';
import useNavItems from '~/hooks/useNavItems'; import useNavItems from '~/hooks/useNavItems';
import {useRouter} from 'next/router'; import {useRouter} from 'next/router';
...@@ -105,7 +106,14 @@ const Navbar: FunctionComponent = () => { ...@@ -105,7 +106,14 @@ const Navbar: FunctionComponent = () => {
if (subpath) { if (subpath) {
path += `/${subpath}`; path += `/${subpath}`;
} }
return `${path}/index`; path += '/index';
if (process.env.API_TOKEN_KEY) {
const id = getApiToken();
if (id) {
path += `?${process.env.API_TOKEN_KEY}=${id}`;
}
}
return path;
}, [i18n.options, i18n.language]); }, [i18n.options, i18n.language]);
return ( return (
......
...@@ -4,12 +4,26 @@ import Head from 'next/head'; ...@@ -4,12 +4,26 @@ import Head from 'next/head';
type PreloaderProps = { type PreloaderProps = {
url: string; url: string;
as?:
| 'audio'
| 'document'
| 'embed'
| 'fetch'
| 'font'
| 'image'
| 'object'
| 'script'
| 'style'
| 'track'
| 'worker'
| 'video';
}; };
const Preloader: FunctionComponent<PreloaderProps> = ({url}) => ( const Preloader: FunctionComponent<PreloaderProps> = ({url, as}) =>
<Head> process.env.API_TOKEN_KEY ? null : (
<link rel="preload" href={process.env.API_URL + url} as="fetch" crossOrigin="anonymous" /> <Head>
</Head> <link rel="preload" href={process.env.API_URL + url} crossOrigin="anonymous" as={as || 'fetch'} />
); </Head>
);
export default Preloader; export default Preloader;
...@@ -2,8 +2,7 @@ import React, {FunctionComponent, useEffect, useState} from 'react'; ...@@ -2,8 +2,7 @@ import React, {FunctionComponent, useEffect, useState} from 'react';
import {WithStyled, rem} from '~/utils/style'; import {WithStyled, rem} from '~/utils/style';
import Button from '~/components/Button'; import Button from '~/components/Button';
import ReactTooltip from 'react-tooltip'; import Tippy from '@tippyjs/react';
import {nanoid} from 'nanoid';
import styled from 'styled-components'; import styled from 'styled-components';
import {useTranslation} from '~/utils/i18n'; import {useTranslation} from '~/utils/i18n';
...@@ -40,23 +39,16 @@ const RunningToggle: FunctionComponent<RunningToggleProps & WithStyled> = ({runn ...@@ -40,23 +39,16 @@ const RunningToggle: FunctionComponent<RunningToggleProps & WithStyled> = ({runn
onToggle?.(state); onToggle?.(state);
}, [onToggle, state]); }, [onToggle, state]);
const [id] = useState(`running-toggle-tooltip-${nanoid()}`);
return ( return (
<Wrapper className={className}> <Wrapper className={className}>
<span>{t(state ? 'running' : 'stopped')}</span> <span>{t(state ? 'running' : 'stopped')}</span>
<div data-for={id} data-tip> <Tippy content={t(state ? 'stop-realtime-refresh' : 'start-realtime-refresh') + ''} hideOnClick={false}>
<StyledButton onClick={() => setState(s => !s)} type={state ? 'danger' : 'primary'} rounded> <div>
{t(state ? 'stop' : 'run')} <StyledButton onClick={() => setState(s => !s)} type={state ? 'danger' : 'primary'} rounded>
</StyledButton> {t(state ? 'stop' : 'run')}
</div> </StyledButton>
<ReactTooltip </div>
id={id} </Tippy>
place="top"
type="dark"
effect="solid"
getContent={() => t(state ? 'stop-realtime-refresh' : 'start-realtime-refresh')}
/>
</Wrapper> </Wrapper>
); );
}; };
......
...@@ -31,6 +31,7 @@ module.exports = { ...@@ -31,6 +31,7 @@ module.exports = {
DEFAULT_LANGUAGE, DEFAULT_LANGUAGE,
LOCALE_PATH, LOCALE_PATH,
LANGUAGES, LANGUAGES,
API_TOKEN_KEY: process.env.API_TOKEN_KEY || '',
PUBLIC_PATH: publicPath, PUBLIC_PATH: publicPath,
API_URL: apiUrl API_URL: apiUrl
}, },
......
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
"test": "echo \"Error: no test specified\" && exit 0" "test": "echo \"Error: no test specified\" && exit 0"
}, },
"dependencies": { "dependencies": {
"@tippyjs/react": "4.0.2",
"@visualdl/i18n": "2.0.0-beta.43", "@visualdl/i18n": "2.0.0-beta.43",
"@visualdl/netron": "2.0.0-beta.43", "@visualdl/netron": "2.0.0-beta.43",
"@visualdl/wasm": "2.0.0-beta.43", "@visualdl/wasm": "2.0.0-beta.43",
...@@ -44,22 +45,22 @@ ...@@ -44,22 +45,22 @@
"lodash": "4.17.15", "lodash": "4.17.15",
"mime-types": "2.1.27", "mime-types": "2.1.27",
"moment": "2.26.0", "moment": "2.26.0",
"nanoid": "3.1.9",
"next": "9.4.4", "next": "9.4.4",
"nprogress": "0.2.0", "nprogress": "0.2.0",
"polished": "3.6.4", "polished": "3.6.4",
"prop-types": "15.7.2", "prop-types": "15.7.2",
"query-string": "6.13.0", "query-string": "6.13.1",
"react": "16.13.1", "react": "16.13.1",
"react-dom": "16.13.1", "react-dom": "16.13.1",
"react-hooks-worker": "0.9.0", "react-hooks-worker": "0.9.0",
"react-input-range": "1.3.0", "react-input-range": "1.3.0",
"react-is": "16.13.1", "react-is": "16.13.1",
"react-spinners": "0.8.3", "react-spinners": "0.8.3",
"react-tooltip": "4.2.6", "react-toastify": "6.0.5",
"save-svg-as-png": "1.4.17", "save-svg-as-png": "1.4.17",
"styled-components": "5.1.1", "styled-components": "5.1.1",
"swr": "0.2.2" "swr": "0.2.2",
"tippy.js": "6.2.3"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.10.2", "@babel/core": "7.10.2",
...@@ -69,7 +70,7 @@ ...@@ -69,7 +70,7 @@
"@types/mime-types": "2.1.0", "@types/mime-types": "2.1.0",
"@types/node": "14.0.13", "@types/node": "14.0.13",
"@types/nprogress": "0.2.0", "@types/nprogress": "0.2.0",
"@types/react": "16.9.35", "@types/react": "16.9.36",
"@types/react-dom": "16.9.8", "@types/react-dom": "16.9.8",
"@types/styled-components": "5.1.0", "@types/styled-components": "5.1.0",
"@visualdl/mock": "2.0.0-beta.43", "@visualdl/mock": "2.0.0-beta.43",
......
import App, {AppContext, AppProps} from 'next/app';
import {GlobalStyle, iconFontPath} from '~/utils/style';
import {Router, appWithTranslation} from '~/utils/i18n'; import {Router, appWithTranslation} from '~/utils/i18n';
import {fetcher, getApiToken, setApiToken} from '~/utils/fetch';
import App from 'next/app';
import {GlobalStyle} from '~/utils/style';
import Head from 'next/head'; import Head from 'next/head';
import Layout from '~/components/Layout'; import Layout from '~/components/Layout';
import NProgress from 'nprogress'; import NProgress from 'nprogress';
import Preloader from '~/components/Preloader';
import React from 'react'; import React from 'react';
import {SWRConfig} from 'swr'; import {SWRConfig} from 'swr';
import {fetcher} from '~/utils/fetch'; import {ToastContainer} from 'react-toastify';
import queryString from 'query-string';
Router.events.on('routeChangeStart', () => NProgress.start()); import {withRouter} from 'next/router';
Router.events.on('routeChangeComplete', () => NProgress.done());
Router.events.on('routeChangeError', () => NProgress.done());
class VDLApp extends App { class VDLApp extends App {
constructor(props: AppProps) {
super(props);
if (process.browser && process.env.API_TOKEN_KEY) {
const query = queryString.parse(window.location.search);
setApiToken(query[process.env.API_TOKEN_KEY]);
}
}
componentDidMount() {
Router.events.on('routeChangeStart', () => NProgress.start());
Router.events.on('routeChangeComplete', (url: string) => {
NProgress.done();
if (process.env.API_TOKEN_KEY) {
const id = getApiToken();
const parsed = queryString.parseUrl(url);
if (id && !parsed.query[process.env.API_TOKEN_KEY]) {
this.props.router.replace(
queryString.stringifyUrl({
url: parsed.url,
query: {
...parsed.query,
[process.env.API_TOKEN_KEY]: id
}
}),
undefined,
{shallow: true}
);
}
}
});
Router.events.on('routeChangeError', () => NProgress.done());
}
render() { render() {
const {Component, pageProps} = this.props; const {Component, pageProps} = this.props;
return ( return (
<> <>
{['ttf', 'woff', 'svg'].map(ext => (
<Preloader url={`${iconFontPath}.${ext}`} as="font" key={ext} />
))}
<Head> <Head>
<title>{process.env.title}</title> <title>{process.env.title}</title>
<link rel="shortcut icon" href={`${process.env.PUBLIC_PATH}/favicon.ico`} /> <link rel="shortcut icon" href={`${process.env.PUBLIC_PATH}/favicon.ico`} />
...@@ -41,10 +77,20 @@ class VDLApp extends App { ...@@ -41,10 +77,20 @@ class VDLApp extends App {
<Layout> <Layout>
<Component {...pageProps} /> <Component {...pageProps} />
</Layout> </Layout>
<ToastContainer position="top-center" hideProgressBar closeOnClick={false} />
</SWRConfig> </SWRConfig>
</> </>
); );
} }
static async getInitialProps(appContext: AppContext) {
const appProps = await App.getInitialProps(appContext);
if (process.env.API_TOKEN_KEY) {
setApiToken(appContext.router.query[process.env.API_TOKEN_KEY]);
}
return {...appProps};
}
} }
export default appWithTranslation(VDLApp); export default appWithTranslation(withRouter(VDLApp));
import Aside, {AsideSection} from '~/components/Aside'; import Aside, {AsideSection} from '~/components/Aside';
import {BlobResponse, blobFetcher} from '~/utils/fetch';
import {Documentation, Properties, SearchItem, SearchResult} from '~/resource/graphs/types'; import {Documentation, Properties, SearchItem, SearchResult} from '~/resource/graphs/types';
import Graph, {GraphRef} from '~/components/GraphsPage/Graph'; import Graph, {GraphRef} from '~/components/GraphsPage/Graph';
import {NextI18NextPage, useTranslation} from '~/utils/i18n'; import {NextI18NextPage, useTranslation} from '~/utils/i18n';
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {primaryColor, rem, size} from '~/utils/style';
import Button from '~/components/Button'; import Button from '~/components/Button';
import Checkbox from '~/components/Checkbox'; import Checkbox from '~/components/Checkbox';
import Content from '~/components/Content'; import Content from '~/components/Content';
import Field from '~/components/Field'; import Field from '~/components/Field';
import HashLoader from 'react-spinners/HashLoader';
import ModelPropertiesDialog from '~/components/GraphsPage/ModelPropertiesDialog'; import ModelPropertiesDialog from '~/components/GraphsPage/ModelPropertiesDialog';
import NodeDocumentationSidebar from '~/components/GraphsPage/NodeDocumentationSidebar'; import NodeDocumentationSidebar from '~/components/GraphsPage/NodeDocumentationSidebar';
import NodePropertiesSidebar from '~/components/GraphsPage/NodePropertiesSidebar'; import NodePropertiesSidebar from '~/components/GraphsPage/NodePropertiesSidebar';
import Search from '~/components/GraphsPage/Search'; import Search from '~/components/GraphsPage/Search';
import Title from '~/components/Title'; import Title from '~/components/Title';
import Uploader from '~/components/GraphsPage/Uploader'; import Uploader from '~/components/GraphsPage/Uploader';
import {rem} from '~/utils/style';
import styled from 'styled-components'; import styled from 'styled-components';
import useRequest from '~/hooks/useRequest';
const FullWidthButton = styled(Button)` const FullWidthButton = styled(Button)`
width: 100%; width: 100%;
...@@ -45,12 +48,26 @@ const SearchSection = styled(AsideSection)` ...@@ -45,12 +48,26 @@ const SearchSection = styled(AsideSection)`
} }
`; `;
const Loading = styled.div`
${size('100%', '100%')}
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
overscroll-behavior: none;
cursor: progress;
font-size: ${rem(16)};
line-height: ${rem(60)};
`;
const Graphs: NextI18NextPage = () => { const Graphs: NextI18NextPage = () => {
const {t} = useTranslation(['graphs', 'common']); const {t} = useTranslation(['graphs', 'common']);
const {data, loading} = useRequest<BlobResponse>('/graphs/graph', blobFetcher);
const graph = useRef<GraphRef>(null); const graph = useRef<GraphRef>(null);
const file = useRef<HTMLInputElement>(null); const file = useRef<HTMLInputElement>(null);
const [files, setFiles] = useState<FileList | null>(null); const [files, setFiles] = useState<FileList | File[] | null>(null);
const onClickFile = useCallback(() => { const onClickFile = useCallback(() => {
if (file.current) { if (file.current) {
file.current.value = ''; file.current.value = '';
...@@ -63,6 +80,11 @@ const Graphs: NextI18NextPage = () => { ...@@ -63,6 +80,11 @@ const Graphs: NextI18NextPage = () => {
setFiles(target.files); setFiles(target.files);
} }
}, []); }, []);
useEffect(() => {
if (data?.data.size) {
setFiles([new File([data.data], data.filename || 'unknwon_model')]);
}
}, [data]);
const [search, setSearch] = useState(''); const [search, setSearch] = useState('');
const [searching, setSearching] = useState(false); const [searching, setSearching] = useState(false);
...@@ -84,7 +106,10 @@ const Graphs: NextI18NextPage = () => { ...@@ -84,7 +106,10 @@ const Graphs: NextI18NextPage = () => {
const [nodeData, setNodeData] = useState<Properties | null>(null); const [nodeData, setNodeData] = useState<Properties | null>(null);
const [nodeDocumentation, setNodeDocumentation] = useState<Documentation | null>(null); const [nodeDocumentation, setNodeDocumentation] = useState<Documentation | null>(null);
useEffect(() => setSearch(''), [showAttributes, showInitializers, showNames]); useEffect(() => {
setSearch('');
setSearchResult({text: '', result: []});
}, [files, showAttributes, showInitializers, showNames]);
const bottom = useMemo( const bottom = useMemo(
() => () =>
...@@ -99,7 +124,7 @@ const Graphs: NextI18NextPage = () => { ...@@ -99,7 +124,7 @@ const Graphs: NextI18NextPage = () => {
const [rendered, setRendered] = useState(false); const [rendered, setRendered] = useState(false);
const aside = useMemo(() => { const aside = useMemo(() => {
if (!rendered) { if (!rendered || loading) {
return null; return null;
} }
if (nodeDocumentation) { if (nodeDocumentation) {
...@@ -186,6 +211,7 @@ const Graphs: NextI18NextPage = () => { ...@@ -186,6 +211,7 @@ const Graphs: NextI18NextPage = () => {
showInitializers, showInitializers,
showNames, showNames,
rendered, rendered,
loading,
nodeData, nodeData,
nodeDocumentation nodeDocumentation
]); ]);
...@@ -197,22 +223,28 @@ const Graphs: NextI18NextPage = () => { ...@@ -197,22 +223,28 @@ const Graphs: NextI18NextPage = () => {
<Title>{t('common:graphs')}</Title> <Title>{t('common:graphs')}</Title>
<ModelPropertiesDialog data={modelData} onClose={() => setModelData(null)} /> <ModelPropertiesDialog data={modelData} onClose={() => setModelData(null)} />
<Content aside={aside}> <Content aside={aside}>
<Graph {loading ? (
ref={graph} <Loading>
files={files} <HashLoader size="60px" color={primaryColor} />
uploader={uploader} </Loading>
showAttributes={showAttributes} ) : (
showInitializers={showInitializers} <Graph
showNames={showNames} ref={graph}
onRendered={() => setRendered(true)} files={files}
onSearch={data => setSearchResult(data)} uploader={uploader}
onShowModelProperties={data => setModelData(data)} showAttributes={showAttributes}
onShowNodeProperties={data => { showInitializers={showInitializers}
setNodeData(data); showNames={showNames}
setNodeDocumentation(null); onRendered={() => setRendered(true)}
}} onSearch={data => setSearchResult(data)}
onShowNodeDocumentation={data => setNodeDocumentation(data)} onShowModelProperties={data => setModelData(data)}
/> onShowNodeProperties={data => {
setNodeData(data);
setNodeDocumentation(null);
}}
onShowNodeDocumentation={data => setNodeDocumentation(data)}
/>
)}
<input <input
ref={file} ref={file}
type="file" type="file"
......
declare global {
interface Window {
__visualdl_instance_id__?: string;
}
namespace globalThis {
// eslint-disable-next-line no-var
var __visualdl_instance_id__: string | undefined;
}
}
declare namespace NodeJS {
interface Global {
__visualdl_instance_id__?: string;
}
}
export {};
// TODO: use this instead // TODO: use this instead
// https://github.com/zeit/swr/blob/master/examples/axios-typescript/libs/useRequest.ts // https://github.com/zeit/swr/blob/master/examples/axios-typescript/libs/useRequest.ts
/* eslint-disable @typescript-eslint/no-explicit-any */
import fetch from 'isomorphic-unfetch'; import fetch from 'isomorphic-unfetch';
export const fetcher = async <T = any>(url: string, options?: any): Promise<T> => { const API_TOKEN_HEADER = 'X-VisualDL-Instance-ID';
const res = await fetch(process.env.API_URL + url, options);
function addApiToken(options?: RequestInit): RequestInit | undefined {
if (!process.env.API_TOKEN_KEY || !globalThis.__visualdl_instance_id__) {
return options;
}
const {headers, ...rest} = options || {};
return {
...rest,
headers: {
...(headers || {}),
[API_TOKEN_HEADER]: globalThis.__visualdl_instance_id__
}
};
}
export function setApiToken(id?: string | string[] | null) {
const instanceId = Array.isArray(id) ? id[0] : id;
globalThis.__visualdl_instance_id__ = instanceId || '';
}
export function getApiToken() {
return globalThis.__visualdl_instance_id__ || '';
}
export const fetcher = async <T = unknown>(url: string, options?: RequestInit): Promise<T> => {
const res = await fetch(process.env.API_URL + url, addApiToken(options));
const response = await res.json(); const response = await res.json();
return response && 'data' in response ? response.data : response; return response && 'data' in response ? response.data : response;
...@@ -15,14 +38,23 @@ export const fetcher = async <T = any>(url: string, options?: any): Promise<T> = ...@@ -15,14 +38,23 @@ export const fetcher = async <T = any>(url: string, options?: any): Promise<T> =
export type BlobResponse = { export type BlobResponse = {
data: Blob; data: Blob;
type: string | null; type: string | null;
filename: string | null;
}; };
export const blobFetcher = async (url: string, options?: any): Promise<BlobResponse> => { export const blobFetcher = async (url: string, options?: RequestInit): Promise<BlobResponse> => {
const res = await fetch(process.env.API_URL + url, options); const res = await fetch(process.env.API_URL + url, addApiToken(options));
const data = await res.blob(); const data = await res.blob();
return {data, type: res.headers.get('Content-Type')}; const disposition = res.headers.get('Content-Disposition');
let filename: string | null = null;
if (disposition && disposition.indexOf('attachment') !== -1) {
const matches = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(disposition);
if (matches != null && matches[1]) {
filename = matches[1].replace(/['"]/g, '');
}
}
return {data, type: res.headers.get('Content-Type'), filename};
}; };
export const cycleFetcher = async <T = any>(urls: string[], options?: any): Promise<T[]> => { export const cycleFetcher = async <T = unknown>(urls: string[], options?: RequestInit): Promise<T[]> => {
return await Promise.all(urls.map(url => fetcher<T>(url, options))); return await Promise.all(urls.map(url => fetcher<T>(url, options)));
}; };
...@@ -33,6 +33,7 @@ export const {i18n, config, appWithTranslation, withTranslation, useTranslation, ...@@ -33,6 +33,7 @@ export const {i18n, config, appWithTranslation, withTranslation, useTranslation,
// from ~/node_modules/next/types/index.d.ts // from ~/node_modules/next/types/index.d.ts
// https://gitlab.com/kachkaev/website-frontend/-/blob/master/src/i18n.ts#L64-68 // https://gitlab.com/kachkaev/website-frontend/-/blob/master/src/i18n.ts#L64-68
// eslint-disable-next-line @typescript-eslint/ban-types
export type NextI18NextPage<P = {}, IP = P> = NextComponentType< export type NextI18NextPage<P = {}, IP = P> = NextComponentType<
NextPageContext, NextPageContext,
IP & {namespacesRequired: string[]}, IP & {namespacesRequired: string[]},
......
...@@ -3,6 +3,8 @@ import * as polished from 'polished'; ...@@ -3,6 +3,8 @@ import * as polished from 'polished';
import {createGlobalStyle, keyframes} from 'styled-components'; import {createGlobalStyle, keyframes} from 'styled-components';
import {css} from 'styled-components'; import {css} from 'styled-components';
import tippy from '!!css-loader!tippy.js/dist/tippy.css';
import toast from '!!css-loader!react-toastify/dist/ReactToastify.css';
import vdlIcon from '!!css-loader!~/public/style/vdl-icon.css'; import vdlIcon from '!!css-loader!~/public/style/vdl-icon.css';
export {default as styled} from 'styled-components'; export {default as styled} from 'styled-components';
...@@ -13,6 +15,8 @@ export {borderRadius as borderRadiusShortHand, borderColor as borderColorShortHa ...@@ -13,6 +15,8 @@ export {borderRadius as borderRadiusShortHand, borderColor as borderColorShortHa
const {math, size, lighten, darken, normalize, fontFace, transitions, border, position} = polished; const {math, size, lighten, darken, normalize, fontFace, transitions, border, position} = polished;
export const iconFontPath = `${process.env.PUBLIC_PATH}/style/fonts/vdl-icon`;
// sizes // sizes
const fontSize = '14px'; const fontSize = '14px';
export const rem = (pxval: string | number): string => polished.rem(pxval, fontSize); export const rem = (pxval: string | number): string => polished.rem(pxval, fontSize);
...@@ -124,7 +128,7 @@ export const GlobalStyle = createGlobalStyle` ...@@ -124,7 +128,7 @@ export const GlobalStyle = createGlobalStyle`
${fontFace({ ${fontFace({
fontFamily: 'vdl-icon', fontFamily: 'vdl-icon',
fontFilePath: `${process.env.PUBLIC_PATH}/style/fonts/vdl-icon`, fontFilePath: iconFontPath,
fileFormats: ['ttf', 'woff', 'svg'], fileFormats: ['ttf', 'woff', 'svg'],
fontWeight: 'normal', fontWeight: 'normal',
fontStyle: 'normal', fontStyle: 'normal',
...@@ -132,6 +136,8 @@ export const GlobalStyle = createGlobalStyle` ...@@ -132,6 +136,8 @@ export const GlobalStyle = createGlobalStyle`
})} })}
${vdlIcon.toString()} ${vdlIcon.toString()}
${toast.toString()}
${tippy.toString()}
html { html {
font-size: ${fontSize}; font-size: ${fontSize};
...@@ -207,4 +213,39 @@ export const GlobalStyle = createGlobalStyle` ...@@ -207,4 +213,39 @@ export const GlobalStyle = createGlobalStyle`
.nprogress-custom-parent #nprogress .bar { .nprogress-custom-parent #nprogress .bar {
position: absolute; position: absolute;
} }
.Toastify__toast-container {
z-index: 10001;
.Toastify__toast {
border-radius: ${borderRadius};
}
.Toastify__toast--default {
color: ${textColor};
}
.Toastify__toast-body {
padding: 0 1.428571429em;
}
}
.tippy-box {
z-index: 10002;
color: ${tooltipTextColor};
background-color: ${tooltipBackgroundColor};
&[data-placement^='top'] > .tippy-arrow::before {
border-top-color: ${tooltipBackgroundColor};
}
&[data-placement^='bottom'] > .tippy-arrow::before {
border-bottom-color: ${tooltipBackgroundColor};
}
&[data-placement^='left'] > .tippy-arrow::before {
border-left-color: ${tooltipBackgroundColor};
}
&[data-placement^='right'] > .tippy-arrow::before {
border-right-color: ${tooltipBackgroundColor};
}
}
`; `;
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
"i18next-browser-languagedetector": "4.2.0", "i18next-browser-languagedetector": "4.2.0",
"i18next-fs-backend": "1.0.6", "i18next-fs-backend": "1.0.6",
"i18next-http-backend": "1.0.15", "i18next-http-backend": "1.0.15",
"i18next-http-middleware": "2.1.0", "i18next-http-middleware": "2.1.2",
"path-match": "1.2.4", "path-match": "1.2.4",
"prop-types": "15.7.2", "prop-types": "15.7.2",
"react-i18next": "11.5.0", "react-i18next": "11.5.0",
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
"@types/express": "4.17.6", "@types/express": "4.17.6",
"@types/hoist-non-react-statics": "3.3.1", "@types/hoist-non-react-statics": "3.3.1",
"@types/node": "14.0.13", "@types/node": "14.0.13",
"@types/react": "16.9.35", "@types/react": "16.9.36",
"@types/react-dom": "16.9.8", "@types/react-dom": "16.9.8",
"typescript": "3.9.5" "typescript": "3.9.5"
}, },
......
...@@ -50,6 +50,7 @@ export type Trans = (props: TransProps) => any; ...@@ -50,6 +50,7 @@ export type Trans = (props: TransProps) => any;
export type Link = React.ComponentClass<LinkProps>; export type Link = React.ComponentClass<LinkProps>;
export type Router = SingletonRouter; export type Router = SingletonRouter;
export type UseTranslation = typeof useTranslation; export type UseTranslation = typeof useTranslation;
// eslint-disable-next-line @typescript-eslint/ban-types
export type AppWithTranslation = <P extends object>(Component: React.ComponentType<P> | React.ElementType<P>) => any; export type AppWithTranslation = <P extends object>(Component: React.ComponentType<P> | React.ElementType<P>) => any;
export type TFunction = I18NextTFunction; export type TFunction = I18NextTFunction;
export type I18n = i18n; export type I18n = i18n;
......
...@@ -17,6 +17,7 @@ host.BrowserHost = class { ...@@ -17,6 +17,7 @@ host.BrowserHost = class {
} }
this._type = this._meta.type ? this._meta.type[0] : 'Browser'; this._type = this._meta.type ? this._meta.type[0] : 'Browser';
this._version = this._meta.version ? this._meta.version[0] : null; this._version = this._meta.version ? this._meta.version[0] : null;
this._ready = false;
} }
get document() { get document() {
...@@ -69,12 +70,18 @@ host.BrowserHost = class { ...@@ -69,12 +70,18 @@ host.BrowserHost = class {
return this._view.showModelProperties(); return this._view.showModelProperties();
case 'show-node-documentation': case 'show-node-documentation':
return this._view.showNodeDocumentation(data); return this._view.showNodeDocumentation(data);
case 'ready':
if (this._ready) {
return this.status('ready');
}
return;
} }
} }
}, },
false false
); );
this._ready = true;
this.status('ready'); this.status('ready');
} }
...@@ -89,11 +96,15 @@ host.BrowserHost = class { ...@@ -89,11 +96,15 @@ host.BrowserHost = class {
} }
error(message, detail) { error(message, detail) {
alert((message == 'Error' ? '' : message + ' ') + detail); this.message('error', (message === 'Error' ? '' : message + ' ') + detail);
} }
confirm(message, detail) { confirm(message, detail) {
return confirm(message + ' ' + detail); const result = confirm(message + ' ' + detail);
if (!result) {
this.message('cancel');
}
return result;
} }
require(id) { require(id) {
...@@ -147,6 +158,11 @@ host.BrowserHost = class { ...@@ -147,6 +158,11 @@ host.BrowserHost = class {
_changeFiles(files) { _changeFiles(files) {
if (files && files.length) { if (files && files.length) {
files = Array.from(files); files = Array.from(files);
const file = files.find(file => this._view.accept(file.name));
if (!file) {
this.error('Error opening file.', 'Cannot open file ' + files[0].name);
return;
}
this._open( this._open(
files.find(file => this._view.accept(file.name)), files.find(file => this._view.accept(file.name)),
files files
......
...@@ -58,6 +58,9 @@ view.View = class { ...@@ -58,6 +58,9 @@ view.View = class {
} }
toggleAttributes(toggle) { toggleAttributes(toggle) {
if (toggle != null && !(toggle ^ this._showAttributes)) {
return;
}
this._showAttributes = toggle == null ? !this._showAttributes : toggle; this._showAttributes = toggle == null ? !this._showAttributes : toggle;
this._reload(); this._reload();
} }
...@@ -67,6 +70,9 @@ view.View = class { ...@@ -67,6 +70,9 @@ view.View = class {
} }
toggleInitializers(toggle) { toggleInitializers(toggle) {
if (toggle != null && !(toggle ^ this._showInitializers)) {
return;
}
this._showInitializers = toggle == null ? !this._showInitializers : toggle; this._showInitializers = toggle == null ? !this._showInitializers : toggle;
this._reload(); this._reload();
} }
...@@ -76,6 +82,9 @@ view.View = class { ...@@ -76,6 +82,9 @@ view.View = class {
} }
toggleNames(toggle) { toggleNames(toggle) {
if (toggle != null && !(toggle ^ this._showNames)) {
return;
}
this._showNames = toggle == null ? !this._showNames : toggle; this._showNames = toggle == null ? !this._showNames : toggle;
this._reload(); this._reload();
} }
......
...@@ -1042,6 +1042,13 @@ ...@@ -1042,6 +1042,13 @@
dependencies: dependencies:
regenerator-runtime "^0.13.4" regenerator-runtime "^0.13.4"
"@babel/runtime@^7.8.7":
version "7.10.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.2.tgz#d103f21f2602497d38348a32e008637d506db839"
integrity sha512-6sF3uQw2ivImfVIl62RZ7MXhO2tap69WeWK57vAaimT6AZbE4FbqjdEJIN1UqoD6wI6B+1n9UiagafH1sxjOtg==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/template@^7.10.1": "@babel/template@^7.10.1":
version "7.10.1" version "7.10.1"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.1.tgz#e167154a94cb5f14b28dc58f5356d2162f539811" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.1.tgz#e167154a94cb5f14b28dc58f5356d2162f539811"
...@@ -2224,6 +2231,11 @@ ...@@ -2224,6 +2231,11 @@
dependencies: dependencies:
debug "^4.1.1" debug "^4.1.1"
"@popperjs/core@^2.3.2":
version "2.4.2"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.4.2.tgz#7c6dc4ecef16149fd7a736710baa1b811017fdca"
integrity sha512-JlGTGRYHC2QK+DDbePyXdBdooxFq2+noLfWpRqJtkxcb/oYWzOF0kcbfvvbWrwevCC1l6hLUg1wHYT+ona5BWQ==
"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
version "1.1.2" version "1.1.2"
resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf"
...@@ -2289,6 +2301,14 @@ ...@@ -2289,6 +2301,14 @@
dependencies: dependencies:
defer-to-connect "^1.0.1" defer-to-connect "^1.0.1"
"@tippyjs/react@4.0.2":
version "4.0.2"
resolved "https://registry.yarnpkg.com/@tippyjs/react/-/react-4.0.2.tgz#fadd14f1e36dd4f63f5f241cc78f3a1bb8394250"
integrity sha512-iAKTjUmrXqTTJ4HZRDgmvVfUiv9pTzJoDjPLDbmvB6vttkuYvZ/o8NhHa72vMFgHpiMFNoYWtB8OCRR6x5Zs8w==
dependencies:
prop-types "^15.6.2"
tippy.js "^6.2.0"
"@types/anymatch@*": "@types/anymatch@*":
version "1.3.1" version "1.3.1"
resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a"
...@@ -2495,7 +2515,7 @@ ...@@ -2495,7 +2515,7 @@
dependencies: dependencies:
"@types/react" "*" "@types/react" "*"
"@types/react@*", "@types/react@16.9.35": "@types/react@*":
version "16.9.35" version "16.9.35"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.35.tgz#a0830d172e8aadd9bd41709ba2281a3124bbd368" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.35.tgz#a0830d172e8aadd9bd41709ba2281a3124bbd368"
integrity sha512-q0n0SsWcGc8nDqH2GJfWQWUOmZSJhXV64CjVN5SvcNti3TdEaA3AH0D8DwNmMdzjMAC/78tB8nAZIlV8yTz+zQ== integrity sha512-q0n0SsWcGc8nDqH2GJfWQWUOmZSJhXV64CjVN5SvcNti3TdEaA3AH0D8DwNmMdzjMAC/78tB8nAZIlV8yTz+zQ==
...@@ -2503,6 +2523,14 @@ ...@@ -2503,6 +2523,14 @@
"@types/prop-types" "*" "@types/prop-types" "*"
csstype "^2.2.0" csstype "^2.2.0"
"@types/react@16.9.36":
version "16.9.36"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.36.tgz#ade589ff51e2a903e34ee4669e05dbfa0c1ce849"
integrity sha512-mGgUb/Rk/vGx4NCvquRuSH0GHBQKb1OqpGS9cT9lFxlTLHZgkksgI60TuIxubmn7JuCb+sENHhQciqa0npm0AQ==
dependencies:
"@types/prop-types" "*"
csstype "^2.2.0"
"@types/rimraf@3.0.0": "@types/rimraf@3.0.0":
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-3.0.0.tgz#b9d03f090ece263671898d57bb7bb007023ac19f" resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-3.0.0.tgz#b9d03f090ece263671898d57bb7bb007023ac19f"
...@@ -4032,7 +4060,7 @@ class-utils@^0.3.5: ...@@ -4032,7 +4060,7 @@ class-utils@^0.3.5:
isobject "^3.0.0" isobject "^3.0.0"
static-extend "^0.1.1" static-extend "^0.1.1"
classnames@2.2.6: classnames@2.2.6, classnames@^2.2.6:
version "2.2.6" version "2.2.6"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
...@@ -4820,7 +4848,7 @@ csso@^4.0.2: ...@@ -4820,7 +4848,7 @@ csso@^4.0.2:
dependencies: dependencies:
css-tree "1.0.0-alpha.39" css-tree "1.0.0-alpha.39"
csstype@^2.2.0, csstype@^2.5.7: csstype@^2.2.0, csstype@^2.5.7, csstype@^2.6.7:
version "2.6.10" version "2.6.10"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.10.tgz#e63af50e66d7c266edb6b32909cfd0aabe03928b" resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.10.tgz#e63af50e66d7c266edb6b32909cfd0aabe03928b"
integrity sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w== integrity sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w==
...@@ -5370,6 +5398,14 @@ dom-converter@^0.2: ...@@ -5370,6 +5398,14 @@ dom-converter@^0.2:
dependencies: dependencies:
utila "~0.4" utila "~0.4"
dom-helpers@^5.0.1:
version "5.1.4"
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.1.4.tgz#4609680ab5c79a45f2531441f1949b79d6587f4b"
integrity sha512-TjMyeVUvNEnOnhzs6uAn9Ya47GmMo3qq7m+Lr/3ON0Rs5kHvb8I+SQYjLUSYn7qhEm0QjW0yrBkvz9yOrwwz1A==
dependencies:
"@babel/runtime" "^7.8.7"
csstype "^2.6.7"
dom-serializer@0, dom-serializer@^0.2.1: dom-serializer@0, dom-serializer@^0.2.1:
version "0.2.2" version "0.2.2"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51"
...@@ -7175,10 +7211,10 @@ i18next-http-backend@1.0.15: ...@@ -7175,10 +7211,10 @@ i18next-http-backend@1.0.15:
dependencies: dependencies:
node-fetch "2.6.0" node-fetch "2.6.0"
i18next-http-middleware@2.1.0: i18next-http-middleware@2.1.2:
version "2.1.0" version "2.1.2"
resolved "https://registry.yarnpkg.com/i18next-http-middleware/-/i18next-http-middleware-2.1.0.tgz#0396971fe6d9fcf82c109f6ee72268d89776d7d1" resolved "https://registry.yarnpkg.com/i18next-http-middleware/-/i18next-http-middleware-2.1.2.tgz#ec6a50a64b3c12ef5791acd6c1bb56b4fa3cbf2d"
integrity sha512-OgtOFDjsIL9R4eXWrpge9TCMc52L2kL1d/9HsErPA14DyqfHc7NVCampFIuqTfpmc4janwcG66r5r5p9zdrS+Q== integrity sha512-pW0gvRn1pqNswnmEocCWBSRAAyvT8/3wvJ5EKrwzqb+ZlJFh7GlYNC+vhYIJuLWKZE1G8bE69/EPSoih0UAM6Q==
i18next@19.4.5: i18next@19.4.5:
version "19.4.5" version "19.4.5"
...@@ -8878,11 +8914,6 @@ nan@^2.12.1: ...@@ -8878,11 +8914,6 @@ nan@^2.12.1:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01"
integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==
nanoid@3.1.9:
version "3.1.9"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.9.tgz#1f148669c70bb2072dc5af0666e46edb6cd31fb2"
integrity sha512-fFiXlFo4Wkuei3i6w9SQI6yuzGRTGi8Z2zZKZpUxv/bQlBi4jtbVPBSNFZHQA9PNjofWqtIa8p+pnsc0kgZrhQ==
nanomatch@^1.2.9: nanomatch@^1.2.9:
version "1.2.13" version "1.2.13"
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
...@@ -10666,10 +10697,10 @@ qs@~6.5.2: ...@@ -10666,10 +10697,10 @@ qs@~6.5.2:
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
query-string@6.13.0: query-string@6.13.1:
version "6.13.0" version "6.13.1"
resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.0.tgz#8d875f66581c854d7480ac79478abb847de742f6" resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.1.tgz#d913ccfce3b4b3a713989fe6d39466d92e71ccad"
integrity sha512-KJe8p8EUcixhPCp4cJoTYVfmgKHjnAB/Pq3fiqlmyNHvpHnOL5U4YE7iI2PYivGHp4HFocWz300906BAQX0H7g== integrity sha512-RfoButmcK+yCta1+FuU8REvisx1oEzhMKwhLUNcepQTPGcNMp1sIqjnfCtfnvGSQZQEhaBHvccujtWoUV3TTbA==
dependencies: dependencies:
decode-uri-component "^0.2.0" decode-uri-component "^0.2.0"
split-on-first "^1.0.0" split-on-first "^1.0.0"
...@@ -10801,13 +10832,24 @@ react-spinners@0.8.3: ...@@ -10801,13 +10832,24 @@ react-spinners@0.8.3:
dependencies: dependencies:
"@emotion/core" "^10.0.15" "@emotion/core" "^10.0.15"
react-tooltip@4.2.6: react-toastify@6.0.5:
version "4.2.6" version "6.0.5"
resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-4.2.6.tgz#a3d5f0d1b0c597c0852ba09c5e2af0019b7cfc70" resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-6.0.5.tgz#6435b2bf6a298863bc71342dcc88e8283cdb4630"
integrity sha512-KX/zCsPFCI8RuulzBX86U+Ur7FvgGNRBdb7dUu0ndo8Urinn48nANq9wfq4ABlehweQjPzLl7XdNAtLKza+I3w== integrity sha512-1YXSb6Jr478c1TJEyVpxLHFvtmeXGMvdpZc0fke/7lK+MoLBC+NFgB74bq+C2SZe6LdK+K1voEURJoY88WqWvA==
dependencies: dependencies:
classnames "^2.2.6"
prop-types "^15.7.2" prop-types "^15.7.2"
uuid "^7.0.3" react-transition-group "^4.4.1"
react-transition-group@^4.4.1:
version "4.4.1"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9"
integrity sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==
dependencies:
"@babel/runtime" "^7.5.5"
dom-helpers "^5.0.1"
loose-envify "^1.4.0"
prop-types "^15.6.2"
react@16.13.1: react@16.13.1:
version "16.13.1" version "16.13.1"
...@@ -12464,6 +12506,13 @@ timsort@^0.3.0: ...@@ -12464,6 +12506,13 @@ timsort@^0.3.0:
resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4"
integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=
tippy.js@6.2.3, tippy.js@^6.2.0:
version "6.2.3"
resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.2.3.tgz#0a5db67dc6bd9129233b26052b7ae2b2047fd73e"
integrity sha512-MzqHMrr2C0IC8ZUnG5kLQPxonWJ7V+Usqiy2W5b+dCvAfousio0mA85h+Ea5wRq94AQGd8mbFGeciRgkP+F+7w==
dependencies:
"@popperjs/core" "^2.3.2"
tmp@^0.0.33: tmp@^0.0.33:
version "0.0.33" version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
...@@ -12989,11 +13038,6 @@ uuid@^3.0.0, uuid@^3.0.1, uuid@^3.2.1, uuid@^3.3.2: ...@@ -12989,11 +13038,6 @@ uuid@^3.0.0, uuid@^3.0.1, uuid@^3.2.1, uuid@^3.3.2:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
uuid@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b"
integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==
v8-compile-cache@2.0.3: v8-compile-cache@2.0.3:
version "2.0.3" version "2.0.3"
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe"
......
...@@ -37,14 +37,18 @@ def gen_result(data=None, status=0, msg=''): ...@@ -37,14 +37,18 @@ def gen_result(data=None, status=0, msg=''):
} }
def result(mimetype='application/json'): def result(mimetype='application/json', headers=None):
def decorator(func): def decorator(func):
@functools.wraps(func) @functools.wraps(func)
def wrapper(*args, **kwargs): def wrapper(self, *args, **kwargs):
data = func(*args, **kwargs) data = func(self, *args, **kwargs)
if mimetype == 'application/json': if mimetype == 'application/json':
data = json.dumps(gen_result(data)) data = json.dumps(gen_result(data))
return data, mimetype if callable(headers):
headers_output = headers(self)
else:
headers_output = headers
return data, mimetype, headers_output
return wrapper return wrapper
return decorator return decorator
...@@ -60,6 +64,7 @@ class Api(object): ...@@ -60,6 +64,7 @@ class Api(object):
def __init__(self, logdir, model, cache_timeout): def __init__(self, logdir, model, cache_timeout):
self._reader = LogReader(logdir) self._reader = LogReader(logdir)
self._reader.model = model self._reader.model = model
self.model_name = os.path.basename(model)
# use a memory cache to reduce disk reading frequency. # use a memory cache to reduce disk reading frequency.
cache = MemCache(timeout=cache_timeout) cache = MemCache(timeout=cache_timeout)
...@@ -145,7 +150,7 @@ class Api(object): ...@@ -145,7 +150,7 @@ class Api(object):
key = os.path.join('data/plugin/embeddings/embeddings', run, tag) key = os.path.join('data/plugin/embeddings/embeddings', run, tag)
return self._get_with_retry(key, lib.get_embeddings, run, tag) return self._get_with_retry(key, lib.get_embeddings, run, tag)
@result('application/octet-stream') @result('application/octet-stream', lambda s: {"Content-Disposition": 'attachment; filename="%s"' % s.model_name} if len(s.model_name) else None)
def graphs_graph(self): def graphs_graph(self):
key = os.path.join('data/plugin/graphs/graph') key = os.path.join('data/plugin/graphs/graph')
return self._get_with_retry(key, lib.get_graph) return self._get_with_retry(key, lib.get_graph)
......
...@@ -98,9 +98,8 @@ def create_app(args): ...@@ -98,9 +98,8 @@ def create_app(args):
@app.route(api_path + '/<path:method>') @app.route(api_path + '/<path:method>')
def serve_api(method): def serve_api(method):
data, mimetype = api_call(method, request.args) data, mimetype, headers = api_call(method, request.args)
return make_response(Response(data, mimetype=mimetype)) return make_response(Response(data, mimetype=mimetype, headers=headers))
return app return app
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册