diff --git a/.eslintrc.js b/.eslintrc.js index ee6ddf35..ef579c06 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -177,7 +177,7 @@ module.exports = { 'error', 10 ], - 'no-bitwise': 'error', + 'no-bitwise': 'off', 'no-caller': 'error', 'no-cond-assign': 'error', 'no-console': [ diff --git a/app-v2/AuthorizedRoute.tsx b/app-v2/AuthorizedRoute.tsx new file mode 100644 index 00000000..54661d73 --- /dev/null +++ b/app-v2/AuthorizedRoute.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { Redirect, Route } from 'react-router-dom'; +import { observer } from 'mobx-react-lite'; + +import { useStore } from '@appv2/stores'; +interface IProps { + component?: any; + render?: any +} + +const AuthorizedRoute = (props: IProps) => { + const { component: Component, render, ...rest } = props; + const { global: { host, username } } = useStore(); + if (host && username) { + return Component ? ( + } /> + ) : ( + + ); + } else { + return ; + } +}; + +export default observer(AuthorizedRoute); diff --git a/app-v2/common.less b/app-v2/common.less new file mode 100644 index 00000000..02167d4d --- /dev/null +++ b/app-v2/common.less @@ -0,0 +1,32 @@ +@grayDark: #36383D; +@dark: rgba(0,0,0,.9); +@disableGray: rgba(255, 255, 255, 0.3); +@promptGray: #8C8C8C; +@blue: #0091ff; +@errorRed: #E02020; +@disableBlue: #375d7a; + +@font-face { + font-family: 'Roboto-Black'; + src: url(~@appv2/static/fonts/Roboto-Black.ttf); +} + +@font-face { + font-family: 'Roboto-Bold'; + src: url(~@appv2/static/fonts/Roboto-Bold.ttf); +} + +@font-face { + font-family: 'Roboto-Light'; + src: url(~@appv2/static/fonts/Roboto-Light.ttf); +} + +@font-face { + font-family: 'Roboto-Medium'; + src: url(~@appv2/static/fonts/Roboto-Medium.ttf); +} + +@font-face { + font-family: 'Roboto-Regular'; + src: url(~@appv2/static/fonts/Roboto-Regular.ttf); +} diff --git a/app-v2/components/Avatar/index.tsx b/app-v2/components/Avatar/index.tsx new file mode 100644 index 00000000..dd499af5 --- /dev/null +++ b/app-v2/components/Avatar/index.tsx @@ -0,0 +1,33 @@ +import _ from 'lodash'; +import React, { useEffect, useState } from 'react'; +import { Avatar as AntAvatar } from 'antd'; + +interface IProps { + username?: string, + size: 'small'|'large'|'default' +} + +const getColorIndex = (value: string) => { + const index = value.toLowerCase().charCodeAt(0) - 96; + return Math.floor(index / Math.floor(26 / RANDOM_COLOR_PICKER.length + 1)); +}; + +const RANDOM_COLOR_PICKER = ['#345EDA', '#0C89BE', '#1D9E96', '#219A1F', '#D4A600', '#B36235', '#C54262']; + +const Avatar = (props: IProps) => { + const { username, size } = props; + + const [avatarColor, setAvatarColor] = useState(RANDOM_COLOR_PICKER[0]); + useEffect(() => { + if(username) { + const colorIndex = getColorIndex(username[0]); + setAvatarColor(RANDOM_COLOR_PICKER[colorIndex]); + } + }, [username] + ); + return ( + {username && username[0]?.toUpperCase()} + ); +}; + +export default Avatar; \ No newline at end of file diff --git a/app-v2/components/Breadcrumb/index.less b/app-v2/components/Breadcrumb/index.less new file mode 100644 index 00000000..2fc9bba6 --- /dev/null +++ b/app-v2/components/Breadcrumb/index.less @@ -0,0 +1,28 @@ +.nebula-breadcrumb { + height: 60px; + background-color: #fff; + box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1); + font-size: 18px; + align-items: center; + display: flex; + padding-left: 40px; + .arrow-icon { + width: 23px; + height: 23px; + margin-right: 15px; + } + .ant-breadcrumb { + font-size: 18px; + & > span { + a, span { + font-weight: 300; + } + } + & > span:last-child { + a, span { + color: #465B7A; + font-weight: 600; + } + } + } +} diff --git a/app-v2/components/Breadcrumb/index.tsx b/app-v2/components/Breadcrumb/index.tsx new file mode 100644 index 00000000..2c9848fa --- /dev/null +++ b/app-v2/components/Breadcrumb/index.tsx @@ -0,0 +1,46 @@ +import { PageHeader } from 'antd'; +import React from 'react'; +import { Link } from 'react-router-dom'; + +import './index.less'; + +interface IProps { + routes: { + path: string; + breadcrumbName: string; + }[]; + ExtraNode?: JSX.Element; +} + +const itemRender = (route, _params, routes, _paths) => { + const last = routes.indexOf(route) === routes.length - 1; + const first = routes.indexOf(route) === 0; + return last ? ( + {route.breadcrumbName} + ) : ( + + {first ? ( + <> + {/* */} + {route.breadcrumbName} + + ) : ( + route.breadcrumbName + )} + + ); +}; + +const Breadcrumb: React.FC = (props: IProps) => { + const { routes, ExtraNode } = props; + return ( + + ); +}; + +export default Breadcrumb; diff --git a/app-v2/components/CSVPreviewLink/index.less b/app-v2/components/CSVPreviewLink/index.less new file mode 100644 index 00000000..9b8e92de --- /dev/null +++ b/app-v2/components/CSVPreviewLink/index.less @@ -0,0 +1,32 @@ +.popover-preview { + max-width: 80%; +} +.csv-preview { + padding: 16px 8px 50px; + overflow: auto; + + table { + td, + th { + text-align: center; + } + } + + > .operation { + padding-bottom: 25px; + text-align: center; + position: absolute; + bottom: 0; + left: 50%; + transform: translateX(-50%); + } + + .csv-select-index { + margin-right: 10px; + } + + .anticon { + font-size: 16px; + } +} + diff --git a/app-v2/components/CSVPreviewLink/index.tsx b/app-v2/components/CSVPreviewLink/index.tsx new file mode 100644 index 00000000..40542505 --- /dev/null +++ b/app-v2/components/CSVPreviewLink/index.tsx @@ -0,0 +1,73 @@ +import { Button, Popover, Table } from 'antd'; +import React, { useState } from 'react'; +import intl from 'react-intl-universal'; +import './index.less'; +import { v4 as uuidv4 } from 'uuid'; + +interface IProps { + file: any; + children: string; + onMapping?: (index) => void; +} + +const CSVPreviewLink = (props: IProps) => { + const { onMapping, file: { content }, children } = props; + const [visible, setVisible] = useState(false); + const handleLinkClick = e => { + e.stopPropagation(); + setVisible(true); + }; + + const handleMapping = index => { + onMapping && onMapping(index); + setVisible(false); + }; + const columns = content.length + ? content[0].map((_, index) => { + const textIndex = index; + return { + title: onMapping ? ( + + ) : ( + `Column ${textIndex}` + ), + dataIndex: index, + }; + }) + : []; + return ( + setVisible(visible)} + content={
+ uuidv4()} + /> +
+ {onMapping && ( + + )} +
+ } + > + + + ); +}; + +export default CSVPreviewLink; diff --git a/app-v2/components/Icon/index.less b/app-v2/components/Icon/index.less new file mode 100644 index 00000000..d2cf6081 --- /dev/null +++ b/app-v2/components/Icon/index.less @@ -0,0 +1,4 @@ +svg.icon { + width: 30px; + height: 30px; +} diff --git a/app-v2/components/Icon/index.tsx b/app-v2/components/Icon/index.tsx new file mode 100644 index 00000000..026d330f --- /dev/null +++ b/app-v2/components/Icon/index.tsx @@ -0,0 +1,15 @@ +import React, { HTMLProps } from 'react'; + +interface IIconFontProps extends HTMLProps { + type: string; + className?: string +} + +const IconFont = (props: IIconFontProps) => { + const { type, className, ...others } = props; + return ( + + ); +}; + +export default IconFont; diff --git a/app-v2/components/Modal.tsx b/app-v2/components/Modal.tsx new file mode 100644 index 00000000..bd67a3fb --- /dev/null +++ b/app-v2/components/Modal.tsx @@ -0,0 +1,79 @@ +import { Modal as AntModal } from 'antd'; +import { ModalProps } from 'antd/lib/modal'; +import React, { Component } from 'react'; + +interface IModalState { + visible: boolean; +} + +interface IModalHandler { + show: (callback?: any) => void; + hide: (callback?: any) => void; +} + +interface IModalProps extends ModalProps { + /** + * use this hook you can get the handler of Modal + * handlerRef => ({ visible, show, hide }) + */ + handlerRef?: (handler: IModalHandler) => void; + children?: any; +} +export default class Modal extends Component { + constructor(props: IModalProps) { + super(props); + this.state = { + visible: false, + }; + } + componentDidMount() { + if (this.props.handlerRef) { + this.props.handlerRef({ + show: this.show, + hide: this.hide, + }); + } + } + + show = (callback?: any) => { + this.setState( + { + visible: true, + }, + () => { + if (callback) { + callback(); + } + }, + ); + }; + + hide = (callback?: any) => { + this.setState( + { + visible: false, + }, + () => { + if (callback) { + callback(); + } + }, + ); + }; + + render() { + return ( + this.state.visible && ( + { + this.hide(); + }} + {...this.props} + > + {this.props.children} + + ) + ); + } +} diff --git a/app-v2/components/index.ts b/app-v2/components/index.ts new file mode 100644 index 00000000..c6b35681 --- /dev/null +++ b/app-v2/components/index.ts @@ -0,0 +1 @@ +export { default as Modal } from './Modal'; diff --git a/app-v2/config/codeLog.ts b/app-v2/config/codeLog.ts new file mode 100644 index 00000000..75651aac --- /dev/null +++ b/app-v2/config/codeLog.ts @@ -0,0 +1 @@ +export const codeLog: string[] = ['请求成功']; diff --git a/app-v2/config/constants.ts b/app-v2/config/constants.ts new file mode 100644 index 00000000..0a773f02 --- /dev/null +++ b/app-v2/config/constants.ts @@ -0,0 +1,18 @@ +import enUS from './locale/en-US.json'; +import zhCN from './locale/zh-CN.json'; + +export const INTL_LOCALE_SELECT = { + EN_US: { + TEXT: 'English', + NAME: 'EN_US', + }, + ZH_CN: { + TEXT: '中文', + NAME: 'ZH_CN', + }, +}; + +export const INTL_LOCALES = { + EN_US: enUS, + ZH_CN: zhCN, +}; diff --git a/app-v2/config/explore.ts b/app-v2/config/explore.ts new file mode 100644 index 00000000..e7461073 --- /dev/null +++ b/app-v2/config/explore.ts @@ -0,0 +1,228 @@ +import BigNumber from 'bignumber.js'; +import JSONBigint from 'json-bigint'; +import json2csv from 'json2csv'; + +import { INode, IPath } from '#app/utils/interface'; + +export const MIN_SCALE = 0.3; +export const MAX_SCALE = 1; + +export const HOT_KEYS = intl => [ + { + operation: `Shift + 'Enter'`, + desc: intl.get('explore.expand'), + }, + { + operation: `Shift + '-'`, + desc: intl.get('common.zoomOut'), + }, + { + operation: `Shift + '+'`, + desc: intl.get('common.zoomIn'), + }, + { + operation: `Shift + 'l'`, + desc: intl.get('common.show'), + }, + { + operation: `Shift + 'z'`, + desc: intl.get('common.rollback'), + }, + { + operation: intl.get('common.selected') + ` + Shift + 'del'`, + desc: intl.get('common.delete'), + }, +]; + +export const GRAPH_ALOGORITHM = intl => [ + { + label: intl.get('explore.allPath'), + value: 'ALL', + }, + { + label: intl.get('explore.shortestPath'), + value: 'SHORTEST', + }, + { + label: intl.get('explore.noLoopPath'), + value: 'NOLOOP', + }, +]; + +export const DEFAULT_COLOR_PICKER = '#5CDBD3'; +export const DEFAULT_COLOR_MIX = + 'linear-gradient(225deg, #32C5FF 0%, #B620E0 51%, #F7B500 100%)'; +export const COLOR_PICK_LIST = [ + '#B93431', + '#B95C31', + '#B98031', + '#B9B031', + '#68B931', + '#31B9B1', + '#3180B9', + '#7331B9', + '#FF7875', + '#FF9C6E', + '#FFC069', + '#FFF566', + '#95DE64', + '#5CDBD3', + '#69C0FF', + '#B37FEB', + '#FFB9B8', + '#FFCEB8', + '#FFE1B8', + '#FFFAB8', + '#D7F2C4', + '#C5F2EF', + '#B8E1FF', + '#DAC1F5', + '#FFE6E6', + '#FFEEE6', + '#FFF4E6', + '#FFFDE6', + '#F1FBEA', + '#EAFAF9', + '#E6F4FF', + '#F2E9FC', +]; +export const DEFAULT_COLOR_PICK_LIST = [ + '#FF7875', + '#FF9C6E', + '#FFC069', + '#FFF566', + '#95DE64', + '#5CDBD3', + '#69C0FF', + '#B37FEB', + '#FFB9B8', + '#FFCEB8', + '#FFE1B8', + '#FFFAB8', + '#D7F2C4', + '#C5F2EF', + '#B8E1FF', + '#DAC1F5', + '#FFE6E6', + '#FFEEE6', + '#FFF4E6', + '#FFFDE6', + '#F1FBEA', + '#EAFAF9', + '#E6F4FF', + '#F2E9FC', + '#B93431', + '#B95C31', + '#B98031', + '#B9B031', + '#68B931', + '#31B9B1', + '#3180B9', + '#7331B9', +]; + +export const flattenData = data => { + const result = {}; + const fieldData = [] as any; + function recurse(cur: any, prop) { + if (Object(cur) !== cur) { + fieldData.push(prop); + result[prop] = cur; + } else if (Array.isArray(cur)) { + for (let i = 0, l = cur.length; i < l; i++) { + recurse(cur[i], prop ? prop + '.' + i : '' + i); + if (l === 0) { + result[prop] = []; + } + } + } else if (BigNumber.isBigNumber(cur)) { + result[prop] = cur; + } else { + let isEmpty = true; + Object.keys(cur).forEach(p => { + isEmpty = false; + recurse(cur[p], prop ? prop + '.' + p : p); + if (isEmpty) { + result[prop] = {}; + } + }); + } + } + recurse(data, ''); + return { result, fieldData }; +}; + +export const downloadCSVFiles = ({ headers, tables, title }) => { + try { + const result = json2csv.parse(tables, { + fields: headers, + }); + // Determine browser type + if ( + (navigator.userAgent.indexOf('compatible') > -1 && + navigator.userAgent.indexOf('MSIE') > -1) || + navigator.userAgent.indexOf('Edge') > -1 + ) { + // IE10 or Edge browsers + const BOM = '\uFEFF'; + const csvData = new Blob([BOM + result], { type: 'text/csv' }); + // @ts-ignore + navigator.msSaveBlob(csvData, `test.csv`); + } else { + // Non-Internet Explorer + const csvContent = 'data:text/csv;charset=utf-8,\uFEFF' + result; + // Use the download property of the A tag to implement the download function + const link = document.createElement('a'); + link.href = encodeURI(csvContent); + link.download = `${title}.csv`; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } + } catch (err) { + alert(err); + } +}; + +export const parseData = (data: INode[] | IPath[], type: 'vertex' | 'edge') => { + const fields = + type === 'vertex' + ? ['vid', 'attributes'] + : ['type', 'srcId', 'dstId', 'rank', 'attributes']; + const tables: any = []; + data.forEach((item: any) => { + const _result = {} as any; + const properties = + type === 'vertex' ? item.nodeProp.properties : item.edgeProp.properties; + const { result } = flattenData(properties) as any; + if (type === 'vertex') { + _result.vid = item.name; + _result.attributes = JSONBigint.stringify(result); + tables.push(_result); + } else if (type === 'edge') { + _result.type = item.type; + _result.srcId = item.source.name; + _result.dstId = item.target.name; + _result.rank = item.rank; + _result.attributes = JSONBigint.stringify(result); + tables.push(_result); + } + }); + return { tables, headers: fields }; +}; + +export const exportDataToCSV = ( + data: INode[] | IPath[], + type: 'vertex' | 'edge', +) => { + const { headers, tables } = parseData(data, type); + downloadCSVFiles({ headers, tables, title: type }); +}; +export const DEFAULT_EXPLORE_RULES = { + edgeTypes: [], + edgeDirection: 'outgoing', + stepsType: 'single', + step: 1, + vertexStyle: 'colorGroupByTag', + quantityLimit: 100, +}; diff --git a/app-v2/config/index.ts b/app-v2/config/index.ts new file mode 100644 index 00000000..31c4dee0 --- /dev/null +++ b/app-v2/config/index.ts @@ -0,0 +1,5 @@ +/** + * this folder for config + */ + +export * from './constants'; diff --git a/app-v2/config/locale/en-US.json b/app-v2/config/locale/en-US.json new file mode 100644 index 00000000..f4bba64f --- /dev/null +++ b/app-v2/config/locale/en-US.json @@ -0,0 +1,372 @@ +{ + "common": { + "requestError": "Request Error", + "currentSpace": "Current Graph Space", + "languageSelect": "Language", + "seeTheHistory": "History", + "table": "Table", + "log": "Log", + "record": "Record", + "sorryNGQLCannotBeEmpty": "Sorry, nGQL cannot be empty", + "disablesUseToSwitchSpace": "Switching space from console is not allowed by current role", + "NGQLHistoryList": "nGQL History", + "spaceTip": "Only required when you need to execute queries in a specified space.", + "empty": "Clear", + "run": "Run", + "console": "Console", + "explore": "Explore", + "ok": "OK", + "success": "Success", + "fail": "Fail", + "noData": "There is no data", + "cancel": "Cancel", + "confirm": "Confirm", + "import": "Import", + "ask": "Are you sure to proceed?", + "output": "Export CSV File", + "openInExplore": "Open In Explore", + "schema": "Schema", + "create": "Create", + "serialNumber": "No.", + "name": "Name", + "operation": "Operations", + "delete": "Delete", + "optionalParameters": "Optional Parameters", + "exportNGQL": "View nGQL", + "field": "Field", + "relatedProperties": "Related Properties", + "type": "Type", + "edit": "Edit", + "deleteSuccess": "Deleted successfully", + "propertyName": "Property Name", + "dataType": "Data Type", + "allowNull": "Allow Null", + "defaults": "Defaults", + "addProperty": "Add Property", + "updateSuccess": "Updated Successfully", + "add": "Add", + "tag": "Tag", + "edge": "Edge Type", + "index": "Index", + "list": "List", + "yes": "Yes", + "no": "No", + "graph": "Graph", + "description": "Description", + "zoomOut": "Zoom Out", + "zoomIn": "Zoom In", + "move": "Move", + "rollback": "Rollback", + "unlock": "Unlock", + "lock": "Lock", + "moreSuggestion": "More Suggestions", + "algorithm": "Algorithm", + "viewDocs": "View Docs", + "hotKeys": "Shortcut Keys", + "show": "Show", + "selected": "Selected", + "search": "Search", + "color": "Color", + "icon": "Icon", + "copy": "Copy", + "copySuccess": "Copied to clipboard", + "expansionConditions": "Expansion conditions", + "total": "Total", + "exportSelectVertexes": "Export Vertexes to CSV", + "exportSelectEdges": "Export Edges to CSV", + "noSelectedData": "No data currently selected", + "namePlaceholder": "Please enter a search name", + "comment": "Comment", + "space": "Space", + "version": "Version" + }, + "NGQLOutput": { + "success": "Execution successful!" + }, + "warning": { + "configServer": "Please configure the nebula server", + "connectError": "Connection refused, please configure server again" + }, + "configServer": { + "connect": "Connect", + "host": "Host", + "username": "Username", + "password": "Password", + "success": "succeed", + "fail": "Failed", + "clear": "Clear Connection", + "title": "Configure Server" + }, + "formRules": { + "hostRequired": "Host Required", + "usernameRequired": "Username Required", + "passwordRequired": "Password Required", + "nodeIdError": "The format is invalid, should be one node per line, split by \\n like: \nid1\nid2\nid3", + "idRequired": "The id field is mandatory", + "positiveIntegerRequired": "Please enter a non-negative integer", + "nameValidate": "The name must start with a letter, and it only supports English letters, numbers and underscores", + "nameRequired": "Please enter the name", + "numberRequired": "Please enter a positive integer", + "replicaLimit": "Replica factor must not exceed the number of your current online machines({number})", + "propertyRequired": "Please enter the property name", + "defaultRequired": "Please enter the default value", + "ttlRequired": "Please select the corresponding property, and the data type of the property must be integer or timestamp", + "ttlDurationRequired": "Please enter the time (in seconds)", + "dataTypeRequired": "Please select the data type", + "fixedStringLength": "Fixed String length must be a positive integer" + }, + "console": { + "cost": "Cost", + "execTime": "Execution Time", + "exportVertex": "Please choose the column representing vertex IDs from the table", + "exportEdge": "Please choose the columns representing source vertex ID, destination vertex ID, and rank of an edge", + "showSubgraphs": "View Subgraphs", + "deleteHistory": "Clear History", + "parameterDisplay": "Custom Parameters Display" + }, + "explore": { + "clear": "Clear", + "clearTip": "Are you sure to proceed the cleanup of the renderred graph view?", + "startWithVertices": "Start with Vertices", + "addConfirm": "Add", + "expand": "Expand", + "unExpand": "Undo Expand", + "undo": "Undo", + "deleteSelectNodes": "Remove Selected Nodes", + "fileImport": "Import File", + "sampleImport": "Import Sample", + "importPlaceholder": "Enter VIDs or other data for VID generation, one data per line, and split them by pressing the Enter key. Here is an example:\nstring1\nstring2\nstring3", + "outgoing": "Outgoing", + "incoming": "Incoming", + "bidirect": "Bidirect", + "filter": "Custom filter conditions", + "operator": "Operator", + "value": "Value", + "selectSpace": "Please select the space", + "selectReminder": "The selection of space will cleanup the renderred graph view, are you sure to proceed?", + "zoom": "Zoom", + "showTags": "Show Tags", + "showEdges": "Show Edges", + "confirm": "Confirm", + "vertexStyle": "Vertex Color/Icon", + "quantityLimit": "Query Limit", + "colorGroupByTag": "Group by vertex tag", + "noVertexPrompt": "No vertices on the board. ", + "search": "Start graph exploration", + "queryById": "Query by VID", + "queryByIndex": "Query by Index", + "queryByCustom": "Custom Query", + "idToBeQueried": "Specify Vertex ID", + "idPretreatment": "Pre-process Vertex IDs", + "indexQueryPrompt_prefix": "In the ", + "indexQueryPrompt_suffix": " space, no tag indexes are found, so query by index is not available.", + "indexQueryPrompt2": "Please create indexes on the tags. Here is an example:", + "runCodeInConsole": "Execute queries in the console", + "indexLink": "For more information about index, see its ", + "documentIntroduction": "documentation", + "selectIndex": "Select a Index", + "paramFilter": "Use Index", + "relationship": "Logical Operator", + "operationConfirm": "This Delete operation will clear the following fields. Are you sure you want to continue the operation?", + "quiry": "Query", + "customQueryDescription": "Enter the statements in the console. When the results are returned, click the Open in Explore button as shown in the preceding figure to explore the graph.", + "openInConsole": "Go to Console", + "insertMethodSelect": "How do you like to render the new data with the existing graph view, incrementally or wipe the graph view first?", + "incrementalInsertion": "Incrementally Render", + "insertAfterClear": "Wipe and Render", + "emptyIndex": "No Index", + "indexConditionDescription": "To use a composite index for a query, we should either filter all fields or the left matching contiguous fields in sequence. That is, the first field is mandatory and skipping field is not allowed", + "timestampInput": "Only numbers are supported for the timestamp field", + "documentIntroductionUrl": "https://docs.nebula-graph.io/2.6.1/3.ngql-guide/14.native-index-statements/", + "customQueryUrl": "https://cloud-cdn.nebula-graph.com.cn/studio-resource/go-to-explore_en.png", + "pretreatmentExplaination": "Hash can pre-process data of the bool, double, int, or string type to generate VIDs, but UUID can pre-process data of the string type only. To generate VIDs by pre-processing strings, enclose each string with single or double quotes.", + "exportToImg": "Export Graph", + "exportToCSV":"Export CSV", + "export":"Export", + "toBlobError": "Export failed. The current canvas size is too large. Please zoom out to retry.", + "expandTip": "Double-click any vertex to explore its related vertices and edges.", + "hotKeysInstructions": "Shortcut Keys Instruction", + "graphAlgorithm": "Graph Algorithm", + "srcId": "Src ID", + "dstId": "Dst ID", + "relation": "Relation", + "direction": "Direction", + "stepLimit": "Step Limit", + "allPath": "All path", + "shortestPath": "Shortest Path", + "noLoopPath": "NoLoop Path", + "algorithmParams": "Algorithm Parameters", + "steps": "Steps", + "singleStep": "Single", + "rangeStep": "Range", + "addCondition": "Add condition", + "customStyle": "Custom Color/Icon", + "nodeSearch":"Artboard node search", + "searchEmpty": "No data found", + "selectedVertexes": "Selected Vertexes", + "selectedEdges": "Selected Edges", + "viewDetails": "View Details", + "expandItem": "Expand", + "collapseItem": "Collapse", + "searchTip": "The following comparison operators are currently supported [=, >, <, !=, <>, <=, >=]", + "expressionError": "Expression error", + "expandTips": "Double-click the vertex to quickly expand according to the current configuration by default", + "missingParams": "Missing parameters", + "emptyIndexTips": "No attribute index currently does not support the data query function in Explore, Please select index with attribute for query", + "docForFindPath": "https://docs.nebula-graph.io/2.5.0/3.ngql-guide/16.subgraph-and-path/2.find-path/" + }, + "import": { + "vertexID": "Vertex ID", + "import": "Import", + "selectSpace": "Select Space", + "uploadFile": "Upload Files", + "uploadSuccess": "Upload Successfully.", + "importData": "Import Data", + "createTask": "New Import", + "uploadTemp": "Import Template", + "downloadConfig": "Download Config", + "viewLogs": "View Logs", + "details": "Details", + "lines": "Lines", + "taskList": "Task List", + "taskName": "Task Name", + "vertices": "Map Vertices", + "edge": "Map Edges", + "runImport": "Import", + "next": "Next", + "goback": "Prev", + "mountPath": "Mount Path", + "importConfigValidationSuccess": "The configuration validation was successful", + "mountPathPlaceholder": "Please input the docker data mount path", + "fileName": "File Name", + "withHeader": "Header", + "fileType": "Type", + "fileSize": "Size", + "fileTitle": "Select Files", + "fileSizeErrorMsg": "File must smaller than 100 MB", + "preview": "Preview", + "bindDatasource": "Bind Datasource", + "confirm": "Confirm", + "stopImportFailed": "Stop Import Failed", + "uploadFailed": "Upload Failed", + "importResults": "Import Information", + "newImport": "New Import", + "endImport": "Stop Import", + "againImport": "Import Again", + "prop": "Prop", + "propTip": "{name}'s Property", + "mapping": "CSV Index", + "mappingTip": "The index of the csv file", + "typeTip": "Prop Type", + "setVertexId": "Set ID", + "setVertexIdTip": "Set current prop as vertex id", + "useHash": "ID Hash", + "useHashTip": "VertexId Process Method", + "unset": "Original ID", + "uuid": "UUID", + "hash": "Hash", + "setSrc": "Set SrcId", + "setSrcTip": "Set field's value as edge source id", + "setDst": "Set DstId", + "setDstTip": "Set field's value as edge destination id", + "setRank": "Set Rank", + "setRankTip": "Set field's value as edge rank", + "edgeText": "Edge", + "choose": "Mapping", + "ignore": "Ignore", + "vertexText": "Vertex", + "importErrorInfo": "Error importing data. Please check the configuration or data file", + "clearAllConfigInfo": "Confirm to clear all config?", + "promptConfigInfo": "The configuration cannot be empty", + "configFile": "Configuration File: ", + "logFile":"Log File: ", + "vertexesFile": "Vertices Files: ", + "vertexFile": "The Vertex File: ", + "vertexErrorFilePath": "Error Vertex File: ", + "edgesFilePath": "Edges Files: ", + "edgeFilePath": "The Edge Files: ", + "edgeErrorFilePath": "Error Edge File Path: ", + "clearoAllConfigInfo": "Confirm to clear all config?", + "all": "All", + "mountPathWarning": "Import data need to config the WORKING_DIR env variable before starting.", + "notExist": "Not exist", + "importError": "Import Error", + "importMappingError": "The data file configuration map import failed", + "importFormatError": "Data file format is not uniform", + "importFileConfigError": "Data file configuration related error", + "importFileDownloadError": "Data file download failed", + "importFileError": "File related error", + "importNebulaError": "Error associated with instance interaction", + "datasource": "DataSource", + "indexNotEmpty": "column index can't be null.", + "reset": "Reset", + "importFinished": "Import task has ended.", + "enterPassword": "Please enter your nebula account password", + "isEmpty": "is empty", + "startImporting": "Start importing", + "stopImportingSuccess": "Stop import successfully.", + "deleteSuccess": "Delete task successfully", + "batchSize": "Batch Size", + "importCompleted": "Import completed", + "importStopped": "Import stopped", + "importFailed": "Failed", + "notImported": "{total} lines not imported", + "readFailed": "{total} lines read failed", + "selectFile": "Select bind source file", + "addTag": "Add Tag" + }, + "schema": { + "spaceList": "Graph Space List", + "backToSpaceList": "Graph Space List", + "useSpaceErrTip": "Space not found. Trying to use a newly created graph space may fail because the creation is implemented asynchronously. To make sure the follow-up operations work as expected, Wait for two heartbeat cycles, i.e., 20 seconds.", + "partitionNumDescription": "partition_num specifies the number of partitions in one replica. The default value is 100. It is usually 5 times the number of hard disks in the cluster.", + "replicaFactorDescription": "replica_factor specifies the number of replicas in the cluster. The default replica factor is 1. The suggested number is 3 in cluster. It is usually 3 in production. Due to the majority voting principle, it must set to be odd.", + "charsetDescription": "charset is short for character set. A character set is a set of symbols and encodings. The default value is utf8.", + "collateDescription": "A collation is a set of rules for comparing characters in a character set. The default value is utf8_bin.", + "vidTypeDescription": "Specifies the data type of vertex IDs (VIDs) in a graph space. ", + "createSuccess": "Create Successfully", + "defineFields": "Define Properties", + "setTTL": "Set TTL", + "uniqProperty": "Property name cannot be duplicated", + "timestampFormat": "Supported data inserting methods:
1. call function now()
2. call function timestamp(), for example: timestamp('2021-07-05T06:18:43.984000')
3. Input the timestamp directly, namely the number of seconds from 1970-01-01 00:00:00", + "dateFormat": "Supported data inserting methods:
Call function date(), for example: date('2021-03-17')", + "timeFormat": "Supported data inserting methods:
Call function time(), for example: time('17:53:59')", + "datetimeFormat": "Supported data inserting methods:
Call function datetime(), for example: datetime('2021-03-17T17:53:59')", + "geographyFormat": "Supported data inserting methods:
Call function ST_GeogFromText(), for example:ST_GeogFromText('POINT(6 10)')", + "geography(point)Format": "Supported data inserting methods:
Call function ST_GeogFromText('POINT()'), for example:ST_GeogFromText('POINT(6 10)')", + "geography(linestring)Format": "Supported data inserting methods:
Call function ST_GeogFromText('LINESTRING()'), for example:ST_GeogFromText('LINESTRING(3 4,10 50,20 25)')", + "geography(polygon)Format": "Supported data inserting methods:
Call function ST_GeogFromText('POLYGON()'), for example:ST_GeogFromText('POLYGON((1 1,5 1,5 5,1 5,1 1),(2 2,2 3,3 3,3 2,2 2))')", + "durationFormat": "Supported data inserting methods:
Call function duration(), for example:duration({years: 1, seconds: 0})", + "cancelOperation": "Do you want to close this panel", + "cancelPropmt": "If you close the panel, the configuration will be deleted automatically. Are you sure that you want to close the panel?", + "fieldDisabled": "A TTL configuration is set for this property, so it cannot be edited. If you want to edit this property, delete the TTL configuration.", + "ttlRequired": "ttl_col and ttl_duration are required.", + "fieldRequired": "Property name and its data type are required", + "indexExist": "An index exists, so TTL configuration is not permitted. A tag or edge type cannot have both an index and TTL configuration.", + "indexType": "Index Type", + "indexName": "Index Name", + "indexFields": "Indexed Properties", + "dragSorting": "(Drag to Sort)", + "selectFields": "Choose Property", + "indexedLength": "Indexed length", + "indexedLengthDescription": "Set the indexed string length. If you are indexing fixed strings, you must not set this option.", + "indexedLengthRequired": "Indexed length must be a positive integer", + "backToTagList": "Back to Tag List", + "backToEdgeList": "Back to Edge Type List", + "backToIndexList": "Back to Index List", + "leavePage": "Whether to leave the current page?", + "leavePagePrompt": "You have unsaved changes to the record on this tab. If you leave this tab without saving the changes, they will be lost. Are you sure that you want to leave?" + }, + "menu": { + "use": "Use Manual", + "release": "New Version", + "forum": "Help Forum", + "nGql": "nGQL" + }, + "link": { + "nGQLHref": "https://docs.nebula-graph.io/3.0.0/3.ngql-guide/1.nGQL-overview/1.overview/", + "mannualHref": "https://docs.nebula-graph.io/3.0.0/nebula-studio/about-studio/st-ug-what-is-graph-studio/", + "versionLogHref": "https://docs.nebula-graph.io/3.0.0/nebula-studio/about-studio/st-ug-release-note/", + "forumLink": "https://discuss.nebula-graph.io/" + } +} diff --git a/app-v2/config/locale/zh-CN.json b/app-v2/config/locale/zh-CN.json new file mode 100644 index 00000000..d56bfb8f --- /dev/null +++ b/app-v2/config/locale/zh-CN.json @@ -0,0 +1,368 @@ +{ + "common": { + "requestError": "请求错误", + "currentSpace": "当前图空间", + "languageSelect": "语言" , + "seeTheHistory":"查看历史", + "table": "表格", + "log":"日志", + "record": "记录", + "sorryNGQLCannotBeEmpty": "对不起,nGQL语句不能为空", + "disablesUseToSwitchSpace": "禁止使用命令切换Space", + "NGQLHistoryList": "nGQL历史列表", + "spaceTip": "仅对某个Space进行操作时需要", + "empty": "清空", + "run":"运行", + "console": "控制台", + "explore": "图探索", + "ok": "确认", + "success": "成功", + "fail": "失败", + "noData": "没有相应数据", + "cancel": "取消", + "confirm": "确认", + "import": "导入", + "ask": "确定进行当前操作?", + "output":"导出CSV文件", + "openInExplore": "导入图探索", + "schema": "Schema", + "create": "创建", + "serialNumber": "序号", + "name": "名称", + "operation": "操作", + "delete": "删除", + "optionalParameters": "可选参数", + "exportNGQL": "对应的nGQL语句", + "field": "字段", + "relatedProperties": "相关属性", + "type": "类型", + "edit": "编辑", + "deleteSuccess": "删除成功", + "propertyName": "属性名称", + "dataType": "数据类型", + "allowNull": "允许空值", + "defaults": "默认值", + "addProperty": "添加属性", + "updateSuccess": "更新成功", + "add": "添加", + "tag": "标签", + "edge": "边类型", + "index": "索引", + "list": "列表", + "yes": "确定", + "no": "取消", + "graph": "可视化", + "description": "说明", + "zoomOut": "缩小", + "zoomIn": "放大", + "move": "移动", + "rollback": "撤销", + "unlock": "解锁", + "lock": "锁定", + "moreSuggestion": "更多建议", + "algorithm": "算法", + "viewDocs": "查看文档", + "hotKeys": "快捷键", + "show": "显示", + "selected": "选中", + "search": "查询", + "color": "颜色", + "icon": "图标", + "copy": "复制", + "copySuccess": "已复制到剪切板", + "total": "共计", + "exportSelectVertexes": "导出选中点CSV", + "exportSelectEdges": "导出选中边CSV", + "noSelectedData": "当前没有选中数据", + "namePlaceholder":"请输入搜索名称", + "comment": "描述", + "space": "图空间", + "version": "版本" + }, + "warning": { + "configServer": "请先配置服务器", + "connectError": "数据库连接有误,请重新配置" + }, + "NGQLOutput": { + "success": "执行成功" + }, + "configServer": { + "connect": "连接", + "host": "Host", + "username": "用户名", + "password": "密码", + "success": "配置成功", + "fail": "配置失败", + "clear": "清除连接", + "title": "配置数据库" + }, + "formRules": { + "hostRequired": "请填写数据库服务器的IP地址", + "usernameRequired": "请填写用户名", + "passwordRequired": "请填写密码", + "nodeIdError": "格式错误,一行1个VID,按回车键分隔", + "idRequired": "请输入导入的节点id", + "positiveIntegerRequired": "请输入一个非负整数", + "nameValidate": "命名必须以字母开头,且只支持输入英文字母、数字以及下划线_", + "nameRequired": "请输入名称", + "numberRequired": "请输入正整数", + "replicaLimit": "副本数量不得超过你当前 online 机器数量({number})", + "propertyRequired": "请输入属性名称", + "defaultRequired": "请输入默认值", + "ttlRequired": "请选择TTL指定的属性, 且属性的数据类型需为integer或timestamp", + "ttlDurationRequired": "请输入时间(s)", + "dataTypeRequired": "请选择数据类型", + "fixedStringLength": "Fixed String 长度需为正整数" + }, + "console": { + "cost": "开销", + "execTime": "执行时间消耗", + "exportVertex": "请选择表中代表点VID的列", + "exportEdge": "请选择结果中分别代表边的起点(src_vid)、终点(dst_vid)和权重(rank)的列", + "showSubgraphs": "查看子图", + "deleteHistory": "清除历史", + "parameterDisplay": "自定义参数 展示" + }, + "explore": { + "clear": "清除", + "clearTip": "是否清除当前视图?", + "startWithVertices": "开始探索", + "addConfirm": "确认添加", + "undo": "回退", + "deleteSelectNodes": "删除选中", + "expand": "拓展", + "unExpand": "取消拓展", + "fileImport": "文件导入", + "sampleImport": "样本导入", + "importPlaceholder": "输入VID或者用于生成VID的数据,一行一个数据,按回车键断开。格式示例如下:\nstring1\nstring2\nstring3", + "outgoing": "流出", + "incoming": "流入", + "bidirect": "双向", + "filter": "自定义筛选条件", + "operator": "运算符", + "value": "值", + "selectSpace": "请选择Space", + "selectReminder": "切换Space会清除当前显示的数据,您确定要切换吗?", + "zoom": "缩放", + "showTags": "显示点", + "showEdges": "显示边", + "confirm": "确定", + "vertexStyle": "节点颜色/图标", + "quantityLimit": "结果数量限制", + "colorGroupByTag": "按标签类型分类", + "noVertexPrompt": "当前画板没有点数据,请", + "search": "探索", + "queryById": "按VID查询", + "queryByIndex": "按索引查询", + "queryByCustom": "自定义查询", + "idToBeQueried": "指定VID", + "idPretreatment": "VID预处理", + "indexQueryPrompt_prefix": "当前Space ", + "indexQueryPrompt_suffix": "下,没有任何标签的索引,无法进行索引查询", + "indexQueryPrompt2": "请按如下示例创建标签索引", + "runCodeInConsole": "去控制台运行语句", + "indexLink": "关于索引的更多信息,请查看对应的", + "documentIntroduction": "文档介绍", + "selectIndex": "选择索引", + "paramFilter": "使用索引", + "relationship": "组合关系", + "operationConfirm": "删除操作会清空后续筛选条件。请确认是否继续执行", + "quiry": "查询", + "customQueryDescription": "可在控制台输入相应nGQL语句,查询得到结果后,点击上图中的“导入图探索”按钮,进行可视化探索", + "openInConsole": "去控制台", + "insertMethodSelect": "当前画板存在部分数据,请选择新增查询结果的插入方式", + "incrementalInsertion": "增量插入", + "insertAfterClear": "清除插入", + "emptyIndex": "索引为空", + "indexConditionDescription": "匹配字段时,必须以索引中左边第一个字段开始,如果需要匹配多个字段,不得跳过字段,但是可以省略后续字段。", + "timestampInput": "时间戳字段只支持输入数字", + "documentIntroductionUrl": "https://docs.nebula-graph.com.cn/2.5.0/3.ngql-guide/14.native-index-statements/", + "customQueryUrl": "https://cloud-cdn.nebula-graph.com.cn/studio-resource/go-to-explore_zh.png", + "pretreatmentExplaination": "Hash能预处理bool、double、int、string类型的数据生成VID,但是UUID仅支持预处理string类型的数据。如果您需要使用Hash或UUID预处理string生成VID,则使用单引号或双引号标示每个string。", + "exportToImg": "导出图形", + "exportToCSV":"导出CSV", + "export":"导出", + "toBlobError": "导出失败。当前画布尺寸过大,请缩放画布尺寸后重试。", + "expandTip": "双击任意点也可实现该点的拓展。", + "hotKeysInstructions": "图探索快捷键说明", + "graphAlgorithm": "图算法", + "allPath": "全路径", + "shortestPath": "最短路径", + "noLoopPath": "非循环路径", + "algorithmParams": "算法参数", + "srcId": "起点", + "dstId": "终点", + "relation": "关系", + "direction": "方向", + "stepLimit": "步数限制", + "steps": "步数", + "singleStep": "单步", + "rangeStep": "范围", + "addCondition": "添加条件", + "expansionConditions": "拓展条件", + "customStyle": "自定义颜色/图标", + "nodeSearch": "画板节点搜索", + "searchEmpty": "未查询到相应数据", + "selectedVertexes": "选中的点", + "selectedEdges": "选中的边", + "viewDetails": "查看详情", + "expandItem": "展开", + "collapseItem": "收起", + "searchTip": "当前支持以下比较符 [=, >, <, !=, <>, <=, >=]", + "expressionError": "表达式错误", + "expandTips": "双击节点默认按当前配置快捷展开", + "missingParams": "参数缺失", + "emptyIndexTips": "无属性索引暂不支持查询数据功能,建议选择带属性索引查询", + "docForFindPath": "https://docs.nebula-graph.com.cn/2.5.0/3.ngql-guide/16.subgraph-and-path/2.find-path/" + }, + "import": { + "vertexID": "Vertex ID", + "import":"导入", + "selectSpace": "选择 Space", + "uploadFile": "上传文件", + "uploadSuccess": "上传文件成功", + "importData": "导入数据", + "createTask": "创建导入任务", + "uploadTemp": "导入模板", + "downloadConfig": "下载配置文件", + "viewLogs": "查看日志", + "details": "详情", + "lines": "行", + "taskList": "任务列表", + "taskName": "任务名称", + "vertices": "关联点", + "edge": "关联边", + "runImport": "导入", + "next": "下一步", + "goback":"上一步", + "mountPath": "挂载路径", + "importConfigValidationSuccess": "配置验证成功", + "mountPathPlaceholder": "请输入docker启动的数据挂载路径", + "fileName": "文件名", + "withHeader": "头字段", + "fileType": "类型", + "fileSize": "大小", + "fileTitle": "文件列表", + "fileSizeErrorMsg": "文件必须小于100MB", + "preview": "预览", + "bindDatasource": "绑定数据源", + "confirm": "确认", + "importResults": "导入信息", + "newImport": "新建导入", + "endImport": "终止导入", + "againImport": "再次导入", + "prop": "属性", + "propTip": "{name}中拥有的属性", + "mapping": "对应列标", + "mappingTip": "属性字段对应csv文件的哪一列", + "typeTip": "属性字段对应的数据类型", + "setVertexId": "设为ID", + "setVertexIdTip": "当前字段是否作为Vertex Id", + "useHash": "ID Hash", + "useHashTip": "id字段对应值插入数据库中所做的处理", + "unset": "保持原值", + "uuid": "UUID", + "hash": "Hash", + "setSrc": "设为起点", + "setSrcTip": "将当前字段值作为起点", + "setDst": "设为终点", + "setDstTip": "将当前字段值作为终点", + "setRank": "设为Rank", + "setRankTip": "将当前字段值作为rank", + "edgeText": "边", + "choose": "选择", + "ignore": "忽略", + "vertexText": "点", + "importErrorInfo": "导入数据错误,请检查配置或数据文件", + "clearAllConfigInfo": "是否确定清空所有配置?", + "configFile": "配置文件:", + "logFile": "日志文件:", + "vertexesFile": "点相关文件:", + "vertexFile": "该点配置文件:", + "vertexErrorFile": "该点错误数据文件:", + "edgesFilePath": "边配置文件:", + "edgeFilePath": "该边配置文件:", + "edgeErrorFilePath": "该边错误数据文件:", + "all": "全部", + "mountPathWarning": "导入数据需在应用启动时配置WORKING_DIR环境变量,否则无法进行。", + "notExist": "不存在", + "importError": "未知错误", + "importMappingError": "数据文件配置映射导入失败", + "importFormatError": "数据文件格式不统一", + "importFileConfigError": "数据文件配置相关错误", + "importFileDownloadError": "数据文件下载失败", + "importFileError": "文件相关错误", + "importNebulaError": "与实例交互相关错误", + "datasource": "数据源", + "indexNotEmpty": "对应列标不能为空", + "reset": "重置", + "importFinished": "导入任务已结束", + "enterPassword": "请输入 nebula 账号密码", + "isEmpty": "为空", + "startImporting": "开始导入", + "stopImportingSuccess": "已停止导入", + "deleteSuccess": "已删除任务记录", + "batchSize": "批处理量", + "importCompleted": "导入完成", + "importStopped": "导入中止", + "importFailed": "导入失败", + "notImported": "{total}行未导入", + "readFailed": "{total}行读取失败", + "selectFile": "选择绑定文件", + "addTag": "添加 Tag" + }, + "schema": { + "spaceList": "图空间列表", + "backToSpaceList": "图空间列表", + "useSpaceErrTip": "图空间未找到。立刻尝试使用刚创建的图空间可能会失败,因为创建是异步实现的。为确保数据同步,后续操作能顺利进行,请等待 2 个心跳周期(20 秒)。", + "partitionNumDescription": "partition_num 表示数据分片数量。默认值为 100。建议为硬盘数量的 5 倍。", + "replicaFactorDescription": "replica_factor 表示副本数量。默认值是 1,生产集群建议为 3。由于采用多数表决原理,因此需为奇数。", + "charsetDescription": "charset 表示字符集,定义了字符以及字符的编码,默认为 utf8。", + "collateDescription": "collate 表示字符序,定义了字符的比较规则,默认为 utf8_bin。", + "vidTypeDescription": "vid type 指定图空间中点 ID(VID)的数据类型。", + "createSuccess": "创建成功", + "defineFields": "定义属性", + "setTTL": "设置TTL", + "uniqProperty": "属性名称不允许重名", + "timestampFormat": "时间类型支持插入方式:
1. 调用函数 now()
2. 调用函数 timestamp(),例如:timestamp('2021-07-05T06:18:43.984000')
3. 直接输入时间戳,即从 1970-01-01 00:00:00 开始的秒数", + "dateFormat": "日期类型支持插入方式:
调用函数 date(),例如:date('2021-03-17')", + "timeFormat": "时间类型支持插入方式:
调用函数 time(),例如time('17:53:59')", + "datetimeFormat": "日期时间类型支持插入方式:
调用函数 datetime(),例如:datetime('2021-03-17T17:53:59')", + "geographyFormat": "geo 类型支持插入方式:
调用函数 ST_GeogFromText(),例如:ST_GeogFromText('POINT(6 10)')", + "geography(point)Format": "geo(point) 类型支持插入方式:
调用函数 ST_GeogFromText('POINT()'),例如:ST_GeogFromText('POINT(6 10)')", + "geography(linestring)Format": "geo(linestring) 类型支持插入方式:
调用函数 ST_GeogFromText('LINESTRING()'),例如:ST_GeogFromText('LINESTRING(3 4,10 50,20 25)')", + "geography(polygon)Format": "geo(polygon) 类型支持插入方式:
调用函数 ST_GeogFromText('POLYGON()'),例如:ST_GeogFromText('POLYGON((1 1,5 1,5 5,1 5,1 1),(2 2,2 3,3 3,3 2,2 2))')", + "durationFormat": "duration 类型支持插入方式:
调用函数 duration(),例如:duration({years: 1, seconds: 0})", + "cancelOperation": "是否取消配置并关闭面板", + "cancelPropmt": "关闭面板将删除所有属性,是否继续?", + "fieldDisabled": "该属性被 ttl_col 引用,不支持更改操作,如要更改,请先更新 ttl", + "ttlRequired": "请填写完整 ttl 关联的属性以及持续时间", + "fieldRequired": "请填写完整属性名称及数据类型", + "indexExist": "已拥有索引,无法同时配置 TTL", + "indexType": "索引类型", + "indexName": "索引名称", + "indexFields": "索引属性", + "dragSorting": "(可拖拽排序)", + "selectFields": "选择关联的属性", + "indexedLength": "索引长度", + "indexedLengthDescription": "设置索引字符串的长度。如果索引定长字符串,则索引长度无法修改。", + "indexedLengthRequired": "索引长度应为正整数", + "backToTagList": "返回标签列表", + "backToEdgeList": "返回边类型列表", + "backToIndexList": "返回索引列表", + "leavePage": "是否离开当前页面", + "leavePagePrompt": "离开当前页面后,未保存的记录将丢失" + }, + "menu": { + "use": "使用手册", + "release": "新发布", + "forum": "求助论坛", + "nGql": "nGQL" + }, + "link": { + "nGQLHref": "https://docs.nebula-graph.com.cn/3.0.0/3.ngql-guide/1.nGQL-overview/1.overview/", + "mannualHref": "https://docs.nebula-graph.com.cn/3.0.0/nebula-studio/about-studio/st-ug-what-is-graph-studio/", + "versionLogHref": "https://docs.nebula-graph.com.cn/3.0.0/nebula-studio/about-studio/st-ug-release-note/", + "forumLink": "https://discuss.nebula-graph.com.cn/" + } +} \ No newline at end of file diff --git a/app-v2/config/nebulaQL.ts b/app-v2/config/nebulaQL.ts new file mode 100644 index 00000000..950fdbaf --- /dev/null +++ b/app-v2/config/nebulaQL.ts @@ -0,0 +1,231 @@ +const nebulaWordsUppercase = [ + 'ADD', + 'ALTER', + 'AND', + 'AS', + 'ASC', + 'BALANCE', + 'BIGINT', + 'BOOL', + 'BY', + 'CHANGE', + 'COMPACT', + 'CREATE', + 'DELETE', + 'DESC', + 'DESCRIBE', + 'DISTINCT', + 'DOUBLE', + 'DOWNLOAD', + 'DROP', + 'EDGE', + 'EDGES', + 'EXISTS', + 'FETCH', + 'FIND', + 'FLUSH', + 'FROM', + 'GET', + 'GO', + 'GRANT', + 'IF', + 'IN', + 'INDEX', + 'INDEXES', + 'INGEST', + 'INSERT', + 'INT', + 'INTERSECT', + 'IS', + 'LIMIT', + 'LOOKUP', + 'MATCH', + 'MINUS', + 'NO', + 'NOT', + 'NULL', + 'OF', + 'OFFSET', + 'ON', + 'OR', + 'ORDER', + 'OVER', + 'OVERWRITE', + 'PROP', + 'REBUILD', + 'RECOVER', + 'REMOVE', + 'RETURN', + 'REVERSELY', + 'REVOKE', + 'SET', + 'SHOW', + 'STEPS', + 'STOP', + 'STRING', + 'SUBMIT', + 'TAG', + 'TAGS', + 'TIMESTAMP', + 'TO', + 'UNION', + 'UPDATE', + 'UPSERT', + 'UPTO', + 'USE', + 'VERTEX', + 'WHEN', + 'WHERE', + 'WITH', + 'XOR', + 'YIELD', + 'ACCOUNT', + 'ADMIN', + 'ALL', + 'AVG', + 'BIDIRECT', + 'BIT_AND', + 'BIT_OR', + 'BIT_XOR', + 'CHARSET', + 'COLLATE', + 'COLLATION', + 'CONFIGS', + 'COUNT', + 'COUNT_DISTINCT', + 'DATA', + 'DBA', + 'DEFAULT', + 'FORCE', + 'GOD', + 'GRAPH', + 'GROUP', + 'GUEST', + 'HDFS', + 'HOSTS', + 'JOB', + 'JOBS', + 'LEADER', + 'MAX', + 'META', + 'MIN', + 'OFFLINE', + 'PART', + 'PARTITION_NUM', + 'PARTS', + 'PASSWORD', + 'PATH', + 'REPLICA_FACTOR', + 'ROLE', + 'ROLES', + 'SHORTEST', + 'SNAPSHOT', + 'SNAPSHOTS', + 'SPACE', + 'SPACES', + 'STATUS', + 'STD', + 'STORAGE', + 'SUM', + 'TTL_COL', + 'TTL_DURATION', + 'USER', + 'USERS', + 'UUID', + 'VALUES', + 'COMMENT', + ':PARAM', + ':PARAMS', +]; + +export const ban = ['use', 'USE']; + +export const operators = [ + // Bitwise Operator + '&', + '|', + '^', + // Math + 'abs', + 'floor', + 'ceil', + 'round', + 'sqrt', + 'cbrt', + 'hypot', + 'pow', + 'exp', + 'exp2', + 'log', + 'log2', + 'sin', + 'asin', + 'cos', + 'acos', + 'tan', + 'atan', + 'rand32', + 'rand64', + // String + 'strcasecmp', + 'lower', + 'upper', + 'length', + 'trim', + 'ltrim', + 'rtrim', + 'left', + 'right', + 'lpad', + 'rpad', + 'substr', + 'hash', + // Timestamp + 'now', + // Comparison Functions And Operators + '=', + '/', + '==', + '!=', + '<', + '<=', + '-', + '%', + '+', + '*', + '-', + 'udf_is_in', + // Aggregate + 'AVG', + 'COUNT', + 'MAX', + 'MIN', + 'STD', + 'SUM', + // Logical Operator + '&&', + '!', + '||', + 'XOR', + // Order by Function + 'ORDER', + 'BY', + 'DESC', + 'ASC', + // Limit + 'LIMIT', + // Set Operations + 'UNION', + 'INTERSECT', + 'MINUS', + // uuid + 'uuid', + // assignment + '=>', +]; + +const nebulaWordsLowercase = nebulaWordsUppercase.map(w => w.toLowerCase()); + +export const keyWords = [...nebulaWordsUppercase, ...nebulaWordsLowercase]; + +export const maxLineNum = 20; diff --git a/app-v2/config/rules.ts b/app-v2/config/rules.ts new file mode 100644 index 00000000..5fe31f41 --- /dev/null +++ b/app-v2/config/rules.ts @@ -0,0 +1,68 @@ +import { NAME_REGEX, POSITIVE_INTEGER_REGEX } from '#app/utils/constant'; + +export const hostRulesFn = intl => [ + { + required: true, + message: intl.get('formRules.hostRequired'), + }, +]; + +export const usernameRulesFn = intl => [ + { + required: true, + message: intl.get('formRules.usernameRequired'), + }, +]; + +export const passwordRulesFn = intl => [ + { + required: true, + message: intl.get('formRules.passwordRequired'), + }, +]; + +export const nodeIdRulesFn = intl => [ + { + required: true, + message: intl.get('formRules.idRequired'), + }, + { + pattern: /^(.+)*(\n.+)*(\n)*$/, + message: intl.get('formRules.nodeIdError'), + }, +]; + +export const nameRulesFn = intl => [ + { + required: true, + message: intl.get('formRules.nameRequired'), + }, + { + pattern: NAME_REGEX, + message: intl.get('formRules.nameValidate'), + }, +]; + +export const numberRulesFn = intl => [ + { + pattern: POSITIVE_INTEGER_REGEX, + message: intl.get('formRules.numberRequired'), + }, +]; + +export const replicaRulesFn = (intl, activeMachineNum) => [ + { + pattern: POSITIVE_INTEGER_REGEX, + message: intl.get('formRules.numberRequired'), + }, + { + validator(_rule, value, callback) { + if (value && Number(value) > activeMachineNum) { + callback( + intl.get('formRules.replicaLimit', { number: activeMachineNum }), + ); + } + callback(); + }, + }, +]; diff --git a/app-v2/config/service.ts b/app-v2/config/service.ts new file mode 100644 index 00000000..19ac734b --- /dev/null +++ b/app-v2/config/service.ts @@ -0,0 +1,48 @@ +import { _delete, get, post, put } from '../utils/http'; + +const execNGQL = post('/api-nebula/db/exec'); + +const connectDB = post('/api-nebula/db/connect'); + +const disconnectDB = post('/api-nebula/db/disconnect'); + +const importData = post('/api-nebula/task/import'); + +const handleImportAction = post('/api-nebula/task/import/action'); + +const getLog = get('/api/import/log'); +const finishImport = post('/api/import/finish'); + +const getImportWokingDir = get('/api/import/working_dir'); +const getUploadDir = get('/api/import/working_dir'); +const getTaskDir = get('/api/import/task_dir'); + +const deteleFile = params => { + const { filename } = params; + return _delete(`/api/files/${filename}`)(); +}; +const getFiles = get('/api/files'); +const getAppInfo = get('/api/app'); +const uploadFiles = (params?, config?) => + put('/api/files')(params, { + ...config, + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); +export default { + execNGQL, + connectDB, + disconnectDB, + importData, + finishImport, + handleImportAction, + getLog, + getImportWokingDir, + getUploadDir, + getTaskDir, + deteleFile, + getFiles, + getAppInfo, + uploadFiles, +}; diff --git a/app-v2/context/LanguageContext.ts b/app-v2/context/LanguageContext.ts new file mode 100644 index 00000000..4f4c0644 --- /dev/null +++ b/app-v2/context/LanguageContext.ts @@ -0,0 +1,14 @@ +import React from 'react'; +import intl from 'react-intl-universal'; +import Cookie from 'js-cookie'; +import { INTL_LOCALES, INTL_LOCALE_SELECT } from '@appv2/config/constants'; +export const LanguageContext = React.createContext({ + currentLocale: INTL_LOCALE_SELECT.EN_US.NAME, + toggleLanguage: (currentLocale: string) => { + Cookie.set('lang', currentLocale); + intl.init({ + currentLocale, + locales: INTL_LOCALES, + }); + }, +}); diff --git a/app-v2/context/index.ts b/app-v2/context/index.ts new file mode 100644 index 00000000..960a3c9e --- /dev/null +++ b/app-v2/context/index.ts @@ -0,0 +1,5 @@ +/** + * this folder for React context api + */ + +export * from './LanguageContext'; diff --git a/app-v2/index.html b/app-v2/index.html new file mode 100644 index 00000000..ca279552 --- /dev/null +++ b/app-v2/index.html @@ -0,0 +1,84 @@ + + + + + + + + Nebula Graph Studio + + + + + + +
+
+
+
+
+ + + diff --git a/app-v2/index.tsx b/app-v2/index.tsx new file mode 100644 index 00000000..e971431b --- /dev/null +++ b/app-v2/index.tsx @@ -0,0 +1,76 @@ +import { hot } from 'react-hot-loader/root'; +import { Spin } from 'antd'; +import React, { Suspense, lazy, useState } from 'react'; +import ReactDom from 'react-dom'; +import { Route, BrowserRouter as Router, Switch, useHistory } from 'react-router-dom'; +import { observer } from 'mobx-react-lite'; +import rootStore, { StoreProvider } from './stores'; +import dayjs from 'dayjs'; +import intl from 'react-intl-universal'; +import duration from 'dayjs/plugin/duration'; +import AuthorizedRoute from './AuthorizedRoute'; +import '@appv2/static/fonts/iconfont.css'; +import Cookie from 'js-cookie'; +import { INTL_LOCALES } from '@appv2/config/constants'; +import { LanguageContext } from '@appv2/context'; +import './common.less'; +const Login = lazy(() => import('@appv2/pages/Login')); +const MainPage = lazy(() => import('@appv2/pages/MainPage')); + +dayjs.extend(duration); + +const defaultLanguage = Cookie.get('lang') || document.documentElement.getAttribute('lang'); +intl.init({ + currentLocale: defaultLanguage || 'EN_US', + locales: INTL_LOCALES, +}); + + +const PageRoot = observer(() => { + const [currentLocale, setCurrentLocale] = useState( + defaultLanguage || 'EN-US', + ); + + const toggleLanguage = (locale: string) => { + Cookie.set('lang', locale); + setCurrentLocale(locale); + intl + .init({ + currentLocale: locale, + locales: INTL_LOCALES, + }); + }; + + return ( + + + + + + + + ); +}); + +const App = () => { + const history = useHistory(); + rootStore.global.history = history; + + return ( + + }> + + + + + ); +}; + +const HotPageRoot = hot(PageRoot); + +ReactDom.render(, document.getElementById('app')); diff --git a/app-v2/interfaces/import.ts b/app-v2/interfaces/import.ts new file mode 100644 index 00000000..7c888935 --- /dev/null +++ b/app-v2/interfaces/import.ts @@ -0,0 +1,45 @@ + +export enum ITaskStatus { + 'statusFinished' = 'statusFinished', + 'statusStoped' = 'statusStoped', + 'statusProcessing' = 'statusProcessing', + 'statusNotExisted' = 'statusNotExisted', + 'statusAborted' = 'statusAborted', +} + +export interface ITaskStats { + totalLine: number; + totalCount: number; + numFailed: number; + numReadFailed: number; +} +export interface ITaskItem { + taskID: number; + nebulaAddress: string; + space: string; + name: string; + createdTime: number; + updatedTime: number; + user: string; + taskStatus: ITaskStatus; + taskMessage: string; + statsQuery: ITaskStats; +} + +export interface IVerticesConfig { + name: string; + file: any; + tags: any[]; + idMapping: any; +} +export interface IEdgeConfig { + name: string; + file: any; + props: any[]; + type: string; +} + +export interface IBasicConfig { + taskName: string; + batchSize?: string; +} \ No newline at end of file diff --git a/app-v2/interfaces/schema.ts b/app-v2/interfaces/schema.ts new file mode 100644 index 00000000..fe21dbe2 --- /dev/null +++ b/app-v2/interfaces/schema.ts @@ -0,0 +1,57 @@ +export interface IIndex { + indexName: string; + props: IField[]; +} +export interface ITree { + name: string; + indexes: IIndex[]; +} + +export interface IField { + Field: string; + Type: string; +} + +export interface ISpace { + serialNumber: number; + Name: string; + ID: number; + Charset: string; + Collate: string; + 'Partition Number': string; + 'Replica Factor': string; +} + +export interface ITag { + name: string; + fields: IField[]; +} +export interface IEdge { + name: string; + fields: IField[]; +} +export interface IIndexList { + name: string; + owner: string; + comment?: string; + fields: IField[]; +} + +export interface IProperty { + name: string; + type: string; + value?: string; + allowNull?: boolean; + fixedLength?: string; +} + +export type IndexType = 'TAG' | 'EDGE'; +export type AlterType = 'ADD' | 'DROP' | 'CHANGE' | 'TTL' | 'COMMENT'; +export interface IAlterConfig { + fields?: IProperty[]; + comment?: string; + ttl?: { + col?: string; + duration?: string; + }; +} \ No newline at end of file diff --git a/app-v2/pages/Import/FileUpload/index.less b/app-v2/pages/Import/FileUpload/index.less new file mode 100644 index 00000000..5a1262f6 --- /dev/null +++ b/app-v2/pages/Import/FileUpload/index.less @@ -0,0 +1,16 @@ +.nebula-file-upload { + .upload-btn { + margin: 15px 0; + } + .file-list { + margin-top: 10px; + h3 { + color: #172F52; + font-weight: bold; + font-size: 18px; + padding-bottom: 12px; + border-bottom: 1px solid #D5DDEB; + margin-bottom: 20px; + } + } +} \ No newline at end of file diff --git a/app-v2/pages/Import/FileUpload/index.tsx b/app-v2/pages/Import/FileUpload/index.tsx new file mode 100644 index 00000000..bef2a233 --- /dev/null +++ b/app-v2/pages/Import/FileUpload/index.tsx @@ -0,0 +1,118 @@ +import { Button, Checkbox, Popconfirm, Table, Upload } from 'antd'; +import _ from 'lodash'; +import React, { useEffect, useState } from 'react'; +import intl from 'react-intl-universal'; + +import CSVPreviewLink from '@appv2/components/CSVPreviewLink'; +import { observer } from 'mobx-react-lite'; + +import { useStore } from '@appv2/stores'; +import { getFileSize } from '@appv2/utils/file'; +import { trackPageView } from '@appv2/utils/stat'; + +import './index.less'; + +const FileUpload = () => { + const { files } = useStore(); + const { fileList, uploadDir, asyncDeleteFile, asyncGetFiles, asyncUploadFile, asyncGetUploadDir } = files; + const [loading, setLoading] = useState(false); + const transformFile = async file => { + file.path = `${uploadDir}/${file.name}`; + file.withHeader = false; + return file; + }; + + const handleUpdate = async(options: any) => { + setLoading(true); + const data = new FormData(); + data.append('file', options.file); + const config = { + headers: { + 'content-type': 'multipart/form-data', + }, + }; + await asyncUploadFile({ data, config }).then(_ => { + asyncGetFiles(); + }); + setLoading(false); + }; + useEffect(() => { + asyncGetFiles(); + asyncGetUploadDir(); + trackPageView('/import/files'); + }, []); + const columns = [ + { + title: intl.get('import.fileName'), + dataIndex: 'name', + }, + { + title: intl.get('import.withHeader'), + key: 'withHeader', + render: () => , + }, + { + title: intl.get('import.fileSize'), + key: 'size', + dataIndex: 'size', + render: size => getFileSize(size), + }, + { + title: intl.get('common.operation'), + key: 'operation', + render: (_1, file, index) => { + if (file.content) { + return ( +
+
+ + {intl.get('import.preview')} + + asyncDeleteFile(index)} + title={intl.get('common.ask')} + okText={intl.get('common.ok')} + cancelText={intl.get('common.cancel')} + > + + +
+
+ ); + } + }, + }, + ]; + return ( +
+ + + +
+

{intl.get('import.fileTitle')}

+
+ + + ); +}; + +export default observer(FileUpload); diff --git a/app-v2/pages/Import/TaskCreate/FileSelect/index.less b/app-v2/pages/Import/TaskCreate/FileSelect/index.less new file mode 100644 index 00000000..5cdfc7a4 --- /dev/null +++ b/app-v2/pages/Import/TaskCreate/FileSelect/index.less @@ -0,0 +1,15 @@ +.popover-file-select { + margin-bottom: 20px; + .ant-popover-title { + border-bottom: none; + } +} +.file-select-form { + .file-select { + width: 280px; + } +} + +.btn-bind-source { + margin-bottom: 20px; +} diff --git a/app-v2/pages/Import/TaskCreate/FileSelect/index.tsx b/app-v2/pages/Import/TaskCreate/FileSelect/index.tsx new file mode 100644 index 00000000..2f807ab7 --- /dev/null +++ b/app-v2/pages/Import/TaskCreate/FileSelect/index.tsx @@ -0,0 +1,79 @@ +import { Button, Form, Popover, Select } from 'antd'; +import React, { useEffect, useState } from 'react'; +import intl from 'react-intl-universal'; +import { observer } from 'mobx-react-lite'; +import { useStore } from '@appv2/stores'; +import { v4 as uuidv4 } from 'uuid'; +import './index.less'; + +const Option = Select.Option; + +interface IProps { + type: 'vertices' | 'edge'; +} +const FormItem = Form.Item; + +const FileSelect = (props: IProps) => { + const { type } = props; + const { files, dataImport: { update, verticesConfig, edgesConfig } } = useStore(); + const { fileList, asyncGetFiles } = files; + const [visible, setVisible] = useState(false); + const onFinish = (value) => { + const file = fileList.filter(item => item.name === value.name)[0]; + if(type === 'vertices') { + update({ + verticesConfig: [...verticesConfig, { + name: uuidv4(), + file, + tags: [], + idMapping: null, + }] + }); + } else { + update({ + edgesConfig: [...edgesConfig, { + name: uuidv4(), + file, + props: [], + type: '', + }] + }); + } + setVisible(false); + }; + useEffect(() => { + asyncGetFiles(); + }, []); + return ( + setVisible(visible)} + content={
+ + + + + + + } + title={intl.get('import.selectFile')} + > + +
+ ); +}; + +export default observer(FileSelect); diff --git a/app-v2/pages/Import/TaskCreate/PasswordInputModal/index.less b/app-v2/pages/Import/TaskCreate/PasswordInputModal/index.less new file mode 100644 index 00000000..db51f5ce --- /dev/null +++ b/app-v2/pages/Import/TaskCreate/PasswordInputModal/index.less @@ -0,0 +1,10 @@ +.password-modal { + .btns { + margin: 30px 0 0; + display: flex; + justify-content: center; + .ant-btn:not(:last-child) { + margin-right: 20px; + } + } +} diff --git a/app-v2/pages/Import/TaskCreate/PasswordInputModal/index.tsx b/app-v2/pages/Import/TaskCreate/PasswordInputModal/index.tsx new file mode 100644 index 00000000..a804ded9 --- /dev/null +++ b/app-v2/pages/Import/TaskCreate/PasswordInputModal/index.tsx @@ -0,0 +1,46 @@ +import { Button, Input, Modal } from 'antd'; +import _ from 'lodash'; +import React, { useState } from 'react'; +import intl from 'react-intl-universal'; + +import './index.less'; +interface IProps { + visible: boolean; + onConfirm: (password?: string) => void +} +const PasswordInputModal = (props: IProps) => { + const [password, setPassword] = useState(''); + const { visible, onConfirm } = props; + const handleConfirm = (password?: string) => { + onConfirm(password); + setPassword(''); + }; + return ( + onConfirm()} + className="password-modal" + footer={false} + > + setPassword(e.target.value)} + /> +
+ + +
+
+ ); +}; + +export default PasswordInputModal; diff --git a/app-v2/pages/Import/TaskCreate/SchemaConfig/EdgeConfig/index.less b/app-v2/pages/Import/TaskCreate/SchemaConfig/EdgeConfig/index.less new file mode 100644 index 00000000..ecf41382 --- /dev/null +++ b/app-v2/pages/Import/TaskCreate/SchemaConfig/EdgeConfig/index.less @@ -0,0 +1,21 @@ +.tag-config-container { + padding: 10px 15px; + margin-bottom: 15px; + background: #FFFFFF; + border: 1px solid #D5DDEB; + box-sizing: border-box; + border-radius: 3px; + .tag-select-row { + display: flex; + justify-content: space-between; + align-items: center; + .left { + display: flex; + align-items: center; + .tag-select { + min-width: 60px; + } + } + } + +} \ No newline at end of file diff --git a/app-v2/pages/Import/TaskCreate/SchemaConfig/EdgeConfig/index.tsx b/app-v2/pages/Import/TaskCreate/SchemaConfig/EdgeConfig/index.tsx new file mode 100644 index 00000000..46e5138e --- /dev/null +++ b/app-v2/pages/Import/TaskCreate/SchemaConfig/EdgeConfig/index.tsx @@ -0,0 +1,100 @@ +import { Select, Table } from 'antd'; +import _ from 'lodash'; +import React from 'react'; +import intl from 'react-intl-universal'; +import { CloseOutlined } from '@ant-design/icons'; +import { observer } from 'mobx-react-lite'; +import { useStore } from '@appv2/stores'; +import CSVPreviewLink from '@appv2/components/CSVPreviewLink'; +import './index.less'; +const Option = Select.Option; + +interface IProps { + configIndex: number; + edge: any; +} +const EdgeConfig = (configProps: IProps) => { + const { configIndex, edge: { type, props, file } } = configProps; + const { dataImport, schema } = useStore(); + const { updateEdgeConfig, updateEdgePropMapping } = dataImport; + const { edgeTypes } = schema; + + const handleEdgeChange = (index: number, value: string) => { + updateEdgeConfig({ index, edgeType: value }); + }; + + const handleRemoveEdge = () => { + updateEdgePropMapping({ configIndex }); + }; + const handlePropChange = (index, field, value) => { + updateEdgePropMapping({ + configIndex, + propIndex: index, + field, + value + }); + }; + + const columns = [ + { + title: intl.get('import.prop'), + dataIndex: 'name', + }, + { + title: intl.get('import.mapping'), + dataIndex: 'mapping', + render: (mappingIndex, prop, propIndex) => ( +
+ {!prop.isDefault && prop.name !== 'rank' && ( + * + )} + + handlePropChange(propIndex, 'mapping', columnIndex) + } + file={file} + > + {mappingIndex === null ? intl.get('import.choose') : `Column ${mappingIndex}`} + +
+ ), + }, + { + title: intl.get('common.type'), + dataIndex: 'type', + }, + ]; + + return ( +
+
+
+ Edge Type + +
+ +
+ {type &&
} + + ); +}; + +export default observer(EdgeConfig); diff --git a/app-v2/pages/Import/TaskCreate/SchemaConfig/TagConfig/index.less b/app-v2/pages/Import/TaskCreate/SchemaConfig/TagConfig/index.less new file mode 100644 index 00000000..ecf41382 --- /dev/null +++ b/app-v2/pages/Import/TaskCreate/SchemaConfig/TagConfig/index.less @@ -0,0 +1,21 @@ +.tag-config-container { + padding: 10px 15px; + margin-bottom: 15px; + background: #FFFFFF; + border: 1px solid #D5DDEB; + box-sizing: border-box; + border-radius: 3px; + .tag-select-row { + display: flex; + justify-content: space-between; + align-items: center; + .left { + display: flex; + align-items: center; + .tag-select { + min-width: 60px; + } + } + } + +} \ No newline at end of file diff --git a/app-v2/pages/Import/TaskCreate/SchemaConfig/TagConfig/index.tsx b/app-v2/pages/Import/TaskCreate/SchemaConfig/TagConfig/index.tsx new file mode 100644 index 00000000..4c1327f0 --- /dev/null +++ b/app-v2/pages/Import/TaskCreate/SchemaConfig/TagConfig/index.tsx @@ -0,0 +1,101 @@ +import { Select, Table } from 'antd'; +import _ from 'lodash'; +import React from 'react'; +import intl from 'react-intl-universal'; +import { CloseOutlined } from '@ant-design/icons'; +import { observer } from 'mobx-react-lite'; +import { useStore } from '@appv2/stores'; +import CSVPreviewLink from '@appv2/components/CSVPreviewLink'; +import './index.less'; +const Option = Select.Option; + +interface IProps { + tag: any; + tagIndex: number; + configIndex: number; + file: any; +} +const VerticesConfig = (props: IProps) => { + const { tag, tagIndex, configIndex, file } = props; + const { dataImport, schema } = useStore(); + const { asyncUpdateTagConfig, updateTagPropMapping } = dataImport; + const { tags } = schema; + + const handleTagChange = (configIndex: number, tagIndex: number, value: string) => { + asyncUpdateTagConfig({ configIndex, tagIndex, tag: value }); + }; + + const handlePropChange = (index, field, value) => { + updateTagPropMapping({ + configIndex, + tagIndex, + propIndex: index, + field, + value + }); + }; + + const columns = [ + { + title: intl.get('import.prop'), + dataIndex: 'name', + }, + { + title: intl.get('import.mapping'), + dataIndex: 'mapping', + render: (mappingIndex, prop, propIndex) => ( +
+ {!prop.isDefault && *} + + handlePropChange(propIndex, 'mapping', columnIndex) + } + file={file} + > + {mappingIndex === null ? intl.get('import.choose') : `Column ${mappingIndex}`} + +
+ ), + }, + { + title: intl.get('common.type'), + dataIndex: 'type', + }, + ]; + + const handleRemoveTag = () => { + updateTagPropMapping({ configIndex, tagIndex }); + }; + return ( +
+
+
+ Tag + +
+ +
+ {tag.name &&
} + + ); +}; + +export default observer(VerticesConfig); diff --git a/app-v2/pages/Import/TaskCreate/SchemaConfig/index.less b/app-v2/pages/Import/TaskCreate/SchemaConfig/index.less new file mode 100644 index 00000000..e35695c1 --- /dev/null +++ b/app-v2/pages/Import/TaskCreate/SchemaConfig/index.less @@ -0,0 +1,33 @@ +.config-collapse { + background: #F3F6F9; + border: 1px solid #D5DDEB; + box-sizing: border-box; + border-radius: 6px; + margin-bottom: 20px; + .ant-collapse-item > .ant-collapse-header { + display: flex; + align-items: center; + font-size: 14px; + color: #465B7A; + } + .btn-preview { + font-weight: bold; + color: #172F52; + } + .config-item { + .id-row { + padding: 10px 15px; + background: #FFFFFF; + border: 1px solid #D5DDEB; + box-sizing: border-box; + border-radius: 3px; + margin-bottom: 10px; + } + .btns { + text-align: center; + } + } + .btn-close { + cursor: pointer; + } +} \ No newline at end of file diff --git a/app-v2/pages/Import/TaskCreate/SchemaConfig/index.tsx b/app-v2/pages/Import/TaskCreate/SchemaConfig/index.tsx new file mode 100644 index 00000000..e6c5c94a --- /dev/null +++ b/app-v2/pages/Import/TaskCreate/SchemaConfig/index.tsx @@ -0,0 +1,84 @@ +import { Button, Collapse } from 'antd'; +import _ from 'lodash'; +import React from 'react'; +import { CloseOutlined } from '@ant-design/icons'; +import { observer } from 'mobx-react-lite'; +import { useStore } from '@appv2/stores'; +import CSVPreviewLink from '@appv2/components/CSVPreviewLink'; +import TagConfig from './TagConfig'; +import intl from 'react-intl-universal'; +import EdgeConfig from './EdgeConfig'; +const { Panel } = Collapse; +import './index.less'; + +interface IProps { + type: 'vertices' | 'edge' + data: any; + configIndex: number; +} +const SchemaConfig = (props: IProps) => { + const { type, data, configIndex } = props; + const { dataImport } = useStore(); + const { verticesConfig, updateVerticesConfig, updateEdgeConfig } = dataImport; + + const addTag = index => { + updateVerticesConfig({ + index, + key: 'tags', + value: [...verticesConfig[index].tags, { + name: '', + props: [] + }] + }); + }; + + const handleRemove = (event, index: number) => { + event.stopPropagation(); + if(type === 'vertices') { + updateVerticesConfig({ index }); + } else { + updateEdgeConfig({ index }); + } + }; + return ( + + + {type} {configIndex + 1} + + {data.file.name} + + } key="default" extra={ handleRemove(e, configIndex)} />}> +
+ {type === 'vertices' &&
+ vertexID + + updateVerticesConfig({ + index: configIndex, + key: 'idMapping', + value: columnIndex + }) + } + file={data.file} + > + {data.idMapping === null ? 'Select CSV Index' : `Column ${data.idMapping}`} + +
} + {type === 'vertices' && data.tags.map((tag, tagIndex) => )} + {type === 'edge' && } + {type === 'vertices' &&
+ +
} +
+
+
+ ); +}; + +export default observer(SchemaConfig); diff --git a/app-v2/pages/Import/TaskCreate/index.less b/app-v2/pages/Import/TaskCreate/index.less new file mode 100644 index 00000000..51c20338 --- /dev/null +++ b/app-v2/pages/Import/TaskCreate/index.less @@ -0,0 +1,45 @@ +.nebula-import-create { + .create-form { + padding: 32px 35px 100px; + .basic-config { + border-bottom: 1px solid #D5DDEB; + } + .container { + background: #FFFFFF; + box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1); + border-radius: 6px; + padding: 25px 30px; + } + } + .map-config { + display: flex; + margin-top: 25px; + .config-column { + flex: 1; + } + .config-column:not(:last-child) { + margin-right: 20px; + } + } + .footer { + position: fixed; + bottom: 0; + width: 100%; + height: 98px; + display: flex; + align-items: center; + justify-content: center; + background: #FFFFFF; + box-shadow: 0px -4px 4px rgba(0, 0, 0, 0.1); + button { + width: 236px; + &:not(:last-child) { + margin-right: 50px; + } + } + } + .label::after { + content: ':'; + padding-right: 5px; + } +} \ No newline at end of file diff --git a/app-v2/pages/Import/TaskCreate/index.tsx b/app-v2/pages/Import/TaskCreate/index.tsx new file mode 100644 index 00000000..af5d21ba --- /dev/null +++ b/app-v2/pages/Import/TaskCreate/index.tsx @@ -0,0 +1,196 @@ +import { Button, Col, Form, Input, Row, Select, message } from 'antd'; +import _ from 'lodash'; +import React, { useEffect, useState } from 'react'; +import Breadcrumb from '@appv2/components/Breadcrumb'; +import { observer } from 'mobx-react-lite'; +import { useStore } from '@appv2/stores'; +import { trackPageView } from '@appv2/utils/stat'; +import FileSelect from './FileSelect'; +import SchemaConfig from './SchemaConfig'; +import PasswordInputModal from './PasswordInputModal'; +import { configToJson } from '@appv2/utils/import'; +import intl from 'react-intl-universal'; +import './index.less'; +import { useHistory } from 'react-router-dom'; +import { POSITIVE_INTEGER_REGEX } from '@appv2/utils/constant'; +const Option = Select.Option; +const formItemLayout = { + labelCol: { + span: 6, + }, + wrapperCol: { + span: 11, + }, +}; +const TaskCreate = () => { + const { dataImport, schema, global } = useStore(); + const { taskDir, asyncGetTaskDir, basicConfig, verticesConfig, edgesConfig, updateBasicConfig, importTask } = dataImport; + const { spaces, spaceVidType, getSpaces, updateSpaceInfo, currentSpace } = schema; + const { host, username } = global; + const { batchSize } = basicConfig; + const [modalVisible, setVisible] = useState(false); + const history = useHistory(); + const routes = [ + { + path: '/import/tasks', + breadcrumbName: intl.get('import.taskList'), + }, + { + path: '#', + breadcrumbName: intl.get('import.createTask'), + }, + ]; + + const openPasswordModal = () => { + try { + check(); + setVisible(true); + } catch (err) { + console.log('err', err); + } + }; + const handleStartImport = async(password?: string) => { + setVisible(false); + if(password) { + const config: any = configToJson({ + ...basicConfig, + space: currentSpace, + taskDir, + verticesConfig, + edgesConfig, + host, + username, + password, + spaceVidType }); + const code = await importTask(config, basicConfig.taskName); + if(code === 0) { + message.success(intl.get('import.startImporting')); + history.push('/import/tasks'); + } + } + }; + + + const check = () => { + verticesConfig.forEach(config => { + if(config.idMapping === null) { + message.error(`vertexId ${intl.get('import.indexNotEmpty')}`); + throw new Error(); + } + if(config.tags.length === 0) { + message.error(`Tag Mapping ${intl.get('import.isEmpty')}`); + throw new Error(); + } + config.tags.forEach(tag => { + if (!tag.name) { + message.error(`Tag ${intl.get('import.isEmpty')}`); + throw new Error(); + } + tag.props.forEach(prop => { + if (prop.mapping === null && !prop.isDefault) { + message.error(`${prop.name} ${intl.get('import.indexNotEmpty')}`); + throw new Error(); + } + }); + }); + }); + edgesConfig.forEach(edge => { + if (!edge.type) { + message.error(`edgeType ${intl.get('import.isEmpty')}`); + throw new Error(); + } + edge.props.forEach(prop => { + if (prop.mapping === null && prop.name !== 'rank' && !prop.isDefault) { + message.error(`${prop.name} ${intl.get('import.indexNotEmpty')}`); + throw new Error(); + } + }); + }); + if(batchSize && !batchSize.match(POSITIVE_INTEGER_REGEX)) { + message.error(`${intl.get('import.batchSize')} ${intl.get('formRules.numberRequired')}`); + throw new Error(); + } + }; + + const clearConfig = () => { + dataImport.update({ + basicConfig: { taskName: '' }, + verticesConfig: [], + edgesConfig: [] + }); + }; + useEffect(() => { + asyncGetTaskDir(); + getSpaces(); + if(currentSpace) { + updateSpaceInfo(currentSpace); + } + trackPageView('/import/create'); + return () => clearConfig(); + }, []); + return ( +
+ +
+
+ +
+ + + + + + + updateBasicConfig('taskName', e.target.value)} /> + + + + + + + updateBasicConfig('batchSize', e.target.value)} /> + + + + +
+
+ +
+ + {verticesConfig.map((item, index) => )} +
+
+ +
+ +
+ + {edgesConfig.map((item, index) => )} +
+
+ +
+ +
+ + +
+ + + ); +}; + +export default observer(TaskCreate); diff --git a/app-v2/pages/Import/TaskList/TaskItem/index.less b/app-v2/pages/Import/TaskList/TaskItem/index.less new file mode 100644 index 00000000..8eb41680 --- /dev/null +++ b/app-v2/pages/Import/TaskList/TaskItem/index.less @@ -0,0 +1,63 @@ +.task-item { + background: #FFFFFF; + box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.15); + border-radius: 8px; + height: 125px; + margin-top: 20px; + padding: 20px 30px; + .row { + display: flex; + justify-content: space-between; + font-size: 14px; + line-height: 16px; + color: #8697B0; + margin-bottom: 15px; + align-items: center; + .ant-btn-link { + color: #8697B0; + } + } + .progress { + flex: 1; + margin-right: 30px; + .progress-info { + display: flex; + justify-content: space-between; + margin-bottom: 12px; + .task-name { + font-family: Roboto-Bold, serif; + font-style: normal; + font-size: 14px; + color: #172F52; + & > span { + margin-left: 12px; + } + .complete-info { + color: #27AE60; + & > span { + margin-right: 6px + } + } + .err-info { + color: #EB5757 + } + } + .more-info { + & > span:not(:last-child) { + margin-right: 30px; + } + } + } + .ant-progress-text { + color: #172F52; + } + } + .operations { + .ant-btn { + height: 38px; + } + & > .ant-btn:not(:last-child) { + margin-right: 15px; + } + } +} \ No newline at end of file diff --git a/app-v2/pages/Import/TaskList/TaskItem/index.tsx b/app-v2/pages/Import/TaskList/TaskItem/index.tsx new file mode 100644 index 00000000..795120b9 --- /dev/null +++ b/app-v2/pages/Import/TaskList/TaskItem/index.tsx @@ -0,0 +1,138 @@ +import { Button, Popconfirm, Progress } from 'antd'; +import { CheckCircleFilled } from '@ant-design/icons'; +import React, { useEffect, useState } from 'react'; +import intl from 'react-intl-universal'; +import './index.less'; +import { ITaskItem, ITaskStatus } from '@appv2/interfaces/import'; +import dayjs from 'dayjs'; +import { floor } from 'lodash'; +interface IProps { + data: ITaskItem; + handleStop: (id: number) => void; + handleDelete: (id: number) => void; + handleDownload: (id: number) => void; +} + + +const COLOR_MAP = { + 'success': { + from: '#8EDD3F', + to: '#27AE60', + }, + 'normal': { + from: '#8EDD3F', + to: '#27AE60', + }, + 'execption': { + from: '#EB5757', + to: '#EB5757', + }, + 'active': { + from: '#58D7FF', + to: '#2F80ED', + }, +}; +const TaskItem = (props: IProps) => { + const { + data: { + space, + taskID, + name, + statsQuery: { totalCount, totalLine, numFailed, numReadFailed }, + taskStatus, + taskMessage, + updatedTime, + createdTime + }, + handleDownload, + handleStop, + handleDelete } = props; + const [status, setStatus] = useState<'success' | 'active' | 'normal' | 'exception' | undefined>(undefined); + const [extraMsg, setExtraMsg] = useState(''); + useEffect(() => { + if(taskStatus === ITaskStatus.statusFinished) { + setStatus('success'); + } else if(taskStatus === ITaskStatus.statusProcessing) { + setStatus('active'); + const info: string[] = []; + if(numFailed > 0) { + info.push(intl.get('import.notImported', { numFailed })); + } + if(numReadFailed > 0) { + info.push(intl.get('import.readFailed', { numReadFailed })); + } + info.length > 0 && setExtraMsg(info.join(', ')); + } else { + setStatus('exception'); + if(taskMessage) { + setExtraMsg(taskMessage); + } + } + }, [taskStatus]); + return ( +
+
+ {intl.get('common.space')}: {space} + +
+
+
+
+ + {name} + {taskStatus === ITaskStatus.statusFinished && + + {intl.get('import.importCompleted')} + {extraMsg && ` (${extraMsg})`} + } + {taskStatus === ITaskStatus.statusAborted && + {intl.get('import.importFailed')} + {extraMsg && ` (${extraMsg})`} + } + {taskStatus === ITaskStatus.statusStoped && + {intl.get('import.importStopped')} + } + +
+ + {taskStatus !== ITaskStatus.statusFinished && `${totalCount} ${intl.get('import.lines')} / `} + {totalLine}{' '}{intl.get('import.lines')} + + {dayjs.duration(dayjs.unix(updatedTime).diff(dayjs.unix(createdTime))).format('HH:mm:ss')} +
+
+ +
+
+ + + {taskStatus === ITaskStatus.statusProcessing && + handleStop(taskID)} + okText={intl.get('common.confirm')} + cancelText={intl.get('common.cancel')} + > + + } + {taskStatus !== ITaskStatus.statusProcessing && + handleDelete(taskID)} + okText={intl.get('common.confirm')} + cancelText={intl.get('common.cancel')} + > + + } +
+
+
+ ); +}; + +export default TaskItem; diff --git a/app-v2/pages/Import/TaskList/index.less b/app-v2/pages/Import/TaskList/index.less new file mode 100644 index 00000000..3149f735 --- /dev/null +++ b/app-v2/pages/Import/TaskList/index.less @@ -0,0 +1,16 @@ +.nebula-data-import { + .task-btns { + margin: 15px 0 20px; + & > button:not(:last-child) { + margin-right: 15px; + } + } + .task-header { + color: #172F52; + font-weight: bold; + font-size: 18px; + padding-bottom: 12px; + border-bottom: 1px solid #D5DDEB; + margin-bottom: 20px; + } +} \ No newline at end of file diff --git a/app-v2/pages/Import/TaskList/index.tsx b/app-v2/pages/Import/TaskList/index.tsx new file mode 100644 index 00000000..a2357d88 --- /dev/null +++ b/app-v2/pages/Import/TaskList/index.tsx @@ -0,0 +1,82 @@ +import { Button, message } from 'antd'; +import _ from 'lodash'; +import React, { useCallback, useEffect, useRef } from 'react'; +import TaskItem from './TaskItem'; +import { useHistory } from 'react-router-dom'; +import intl from 'react-intl-universal'; +import { observer } from 'mobx-react-lite'; + +import { useStore } from '@appv2/stores'; +import { trackPageView } from '@appv2/utils/stat'; + +import './index.less'; +import { ITaskStatus } from '@appv2/interfaces/import'; + +let isMounted = true; + +const TaskList = () => { + const timer = useRef(null); + const { dataImport } = useStore(); + const history = useHistory(); + const { taskList, asyncGetTaskList, stopTask, deleteTask, downloadTaskConfig } = dataImport; + const handleTaskStop = useCallback(async(id: number) => { + clearTimeout(timer.current); + const { code } = await stopTask(id); + if(code === 0) { + message.success(intl.get('import.stopImportingSuccess')); + await asyncGetTaskList(); + } + }, []); + const handleTaskDelete = useCallback(async(id: number) => { + clearTimeout(timer.current); + const { code } = await deleteTask(id); + if(code === 0) { + message.success(intl.get('import.deleteSuccess')); + await asyncGetTaskList(); + } + }, []); + useEffect(() => { + isMounted = true; + asyncGetTaskList(); + trackPageView('/import/tasks'); + return () => { + isMounted = false; + clearTimeout(timer.current); + }; + }, []); + useEffect(() => { + const needRefresh = taskList.filter(item => item.taskStatus === ITaskStatus.statusProcessing).length > 0; + if(needRefresh && isMounted) { + timer.current = setTimeout(asyncGetTaskList, 2000); + } else { + clearTimeout(timer.current); + } + }, [taskList]); + return ( +
+
+ + +
+

{intl.get('import.taskList')} ({taskList.length})

+ {taskList.map(item => ( + + ))} +
+ ); +}; + +export default observer(TaskList); diff --git a/app-v2/pages/Import/index.less b/app-v2/pages/Import/index.less new file mode 100644 index 00000000..6b93d3c3 --- /dev/null +++ b/app-v2/pages/Import/index.less @@ -0,0 +1,29 @@ +.nebua-import-page { + padding: 25px 35px; + .tab-header { + display: flex; + justify-content: center; + padding-bottom: 16px; + border-bottom: 1px solid #D5DDEB; + .import-tab { + background: #E9EDEF; + border-radius: 20px; + padding: 4px; + width: 400px; + display: flex; + justify-content: center; + .ant-radio-button-wrapper { + border-radius: 20px; + flex: 1; + text-align: center; + border: none; + &:not(.ant-radio-button-wrapper-checked) { + background: none; + } + &::before { + width: 0; + } + } + } + } +} \ No newline at end of file diff --git a/app-v2/pages/Import/index.tsx b/app-v2/pages/Import/index.tsx new file mode 100644 index 00000000..cb639d24 --- /dev/null +++ b/app-v2/pages/Import/index.tsx @@ -0,0 +1,53 @@ +import { Radio } from 'antd'; +import React, { useEffect, useState } from 'react'; +import { Route, useHistory, useLocation } from 'react-router-dom'; +import intl from 'react-intl-universal'; +import { trackPageView } from '@appv2/utils/stat'; + +import FileUpload from './FileUpload'; +import './index.less'; +import TaskList from './TaskList'; + +const NewImport = () => { + const history = useHistory(); + const location = useLocation(); + const [tab, setTab] = useState('files'); + useEffect(() => { + const path = location.pathname; + setTab(path.includes('files') ? 'files' : 'tasks'); + trackPageView('/import'); + }, []); + const handleTabChange = e => { + setTab(e.target.value); + history.push(`/import/${e.target.value}`); + }; + return ( +
+
+ + {intl.get('import.uploadFile')} + {intl.get('import.importData')} + +
+
+ + +
+
+ ); +}; + +export default NewImport; diff --git a/app-v2/pages/Login/LanguageSelect/index.less b/app-v2/pages/Login/LanguageSelect/index.less new file mode 100644 index 00000000..1ca5d60e --- /dev/null +++ b/app-v2/pages/Login/LanguageSelect/index.less @@ -0,0 +1,47 @@ +// @import '@appv2/common.less'; + +.select-label { + display: flex; + font-size: 12px; + align-items: center; + position: relative; + top: -4px; +} + +.select-lang .ant-select-arrow { + display: flex; + align-items: center; +} + +.dark.ant-select-single { + width: fit-content; + + .ant-select-selector { + background: #2f3436; + border-color: #2f3436; + color: white; + } + + svg { + fill: #fff; + } +} + +.dark-options { + background: #2f3436; + font-size: 12px; + + .ant-select-item-option-selected { + background: #2f3436; + } + + .ant-select-item-option-active:not(.ant-select-item-option-disabled) { + // background: @blue; + color: #fff; + } + + .ant-select-item-option-content { + color: #fff; + font-size: 12px; + } +} diff --git a/app-v2/pages/Login/LanguageSelect/index.tsx b/app-v2/pages/Login/LanguageSelect/index.tsx new file mode 100644 index 00000000..64791bd3 --- /dev/null +++ b/app-v2/pages/Login/LanguageSelect/index.tsx @@ -0,0 +1,39 @@ +import React, { useContext } from 'react'; +import { Select } from 'antd'; +import { INTL_LOCALE_SELECT } from '@appv2/config'; +import Icon from '@appv2/components/Icon'; +import { LanguageContext } from '@appv2/context'; + +import './index.less'; +const Option = Select.Option; + +const LanguageSelect: React.FC = () => { + const { currentLocale, toggleLanguage } = useContext(LanguageContext); + return ( + + ); +}; + +export default LanguageSelect; diff --git a/app-v2/pages/Login/index.less b/app-v2/pages/Login/index.less new file mode 100644 index 00000000..13be81c9 --- /dev/null +++ b/app-v2/pages/Login/index.less @@ -0,0 +1,86 @@ +.studio-login { + width: 100%; + height: 100vh; + display: flex; + flex-direction: row-reverse; + justify-content: space-between; + background-image: url('~@appv2/static/images/background_login.png'); + background-size: cover; + background-repeat: no-repeat; + align-items: center; + .content { + width: 360px; + height: 100vh; + background-color: #fff; + padding: 70px 34px 0; + text-align: center; + display: flex; + flex-direction: column; + overflow-y: auto; + .header { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + + .logo { + width: 128px; + height: 128px; + margin-bottom: 25px; + } + + .title { + font-weight: 700; + margin-bottom: 70px; + text-align: center; + font-family: Futura-Bold, serif; + font-size: 24px; + } + } + + .login-form { + .form-title { + font-size: 24px; + line-height: 28px; + text-align: center; + margin-bottom: 50px; + display: block; + } + + .ant-form-item { + border-bottom: 1px solid #bfbfbf; + } + + .btn-submit { + width: 100%; + margin-top: 40px; + } + } + } + + .footer { + flex: 1; + flex-direction: column; + margin: 20px 0; + display: flex; + justify-content: flex-end; + .info { + display: flex; + justify-content: center; + align-items: center; + } + + .version { + margin-right: 15px; + } + + .copyright { + display: block; + margin-top: 15px; + font-size: 14px; + line-height: 16px; + text-align: center; + color: #6d7278; + } + } +} diff --git a/app-v2/pages/Login/index.tsx b/app-v2/pages/Login/index.tsx new file mode 100644 index 00000000..9f2f8f8a --- /dev/null +++ b/app-v2/pages/Login/index.tsx @@ -0,0 +1,78 @@ +import { Button, Form, Input } from 'antd'; +import React, { useEffect, useState } from 'react'; +import intl from 'react-intl-universal'; +import { useHistory, useLocation } from 'react-router-dom'; +import { hostRulesFn, passwordRulesFn, usernameRulesFn } from '@appv2/config/rules'; +import { observer } from 'mobx-react-lite'; +import { trackPageView } from '@appv2/utils/stat'; +import { useStore } from '@appv2/stores'; +import nebulaLogo from '@appv2/static/images/nebula_logo.png'; +import LanguageSelect from './LanguageSelect'; + +import './index.less'; + +const FormItem = Form.Item; + +const fomrItemLayout = { + wrapperCol: { + span: 24, + }, +}; + +const LoginPage: React.FC = () => { + const { global } = useStore(); + const { version } = global; + const history = useHistory(); + const location = useLocation(); + const [loading, setLoading] = useState(false); + const onConfig = async(values: any) => { + setLoading(true); + const ok = await global.login(values); + setLoading(false); + setTimeout(() => { + ok && history.replace(`/console${location.search}`); + }, 300); + }; + + useEffect(() => { + trackPageView('/login'); + }, []); + + return ( +
+
+
+ + Nebula Studio +
+
+ + {intl.get('configServer.title')} + + + + + + + + + + + + +
+
+ + {intl.get('common.version')}:{version} + + +
+ Copyright © vesoft inc. - A product of vesoft inc. +
+
+
+ ); +}; +export default observer(LoginPage); diff --git a/app-v2/pages/MainPage/Header/HelpMenu/index.less b/app-v2/pages/MainPage/Header/HelpMenu/index.less new file mode 100644 index 00000000..e69de29b diff --git a/app-v2/pages/MainPage/Header/HelpMenu/index.tsx b/app-v2/pages/MainPage/Header/HelpMenu/index.tsx new file mode 100644 index 00000000..e6232a3b --- /dev/null +++ b/app-v2/pages/MainPage/Header/HelpMenu/index.tsx @@ -0,0 +1,97 @@ +import React, { useContext } from 'react'; +import { Menu } from 'antd'; +import intl from 'react-intl-universal'; +import Icon from '@appv2/components/Icon'; +import Avatar from '@appv2/components/Avatar'; +import { LanguageContext } from '@appv2/context'; +import { INTL_LOCALE_SELECT } from '@appv2/config'; +import { observer } from 'mobx-react-lite'; +import { useStore } from '@appv2/stores'; +import './index.less'; +import { trackPageView } from '@appv2/utils/stat'; + +const HelpMenu = () => { + const { toggleLanguage } = useContext(LanguageContext); + const { global: { username, logout, version } } = useStore(); + const DOC_LIST = [ + { + link: 'link.mannualHref', + icon: 'iconimage-icon12', + title: 'menu.use', + track: '/user-mannual' + }, + { + link: 'link.nGQLHref', + icon: 'iconimage-icon12', + title: 'menu.nGql', + track: '/nebula-doc' + }, + { + link: 'link.forumLink', + icon: 'iconimage-icon12', + title: 'menu.forum', + track: '/form' + }, + ]; + return + }> + {Object.keys(INTL_LOCALE_SELECT).map(locale => { + return ( + toggleLanguage(INTL_LOCALE_SELECT[locale].NAME)}> + {INTL_LOCALE_SELECT[locale].TEXT} + + ); + })} + + }> + {DOC_LIST.map(item => trackPageView(item.track)}> + + + {intl.get(item.title)} + + )} + + + + }> + + + + {intl.get('menu.release')} + + + + + + {intl.get('configServer.clear')} + + + + v{version} + + + ; +}; + +export default observer(HelpMenu); diff --git a/app-v2/pages/MainPage/Header/index.less b/app-v2/pages/MainPage/Header/index.less new file mode 100644 index 00000000..c27d5d95 --- /dev/null +++ b/app-v2/pages/MainPage/Header/index.less @@ -0,0 +1,26 @@ +.studio-header { + padding: 0 24px; + background: #2F3A4A; + height: 60px; + display: flex; + justify-content: space-between; + .nebula-logo { + display: flex; + align-items: center; + font-family: Gilroy-Heavy, serif; + font-weight: 600; + font-size: 22px; + line-height: 26px; + color: #FFFFFF; + margin-right: 10px; + } + .main-menu { + flex: auto; + font-family: Roboto-Bold, serif; + font-style: normal; + font-size: 16px; + .nebula-studio-icon { + margin-right: 5px; + } + } +} \ No newline at end of file diff --git a/app-v2/pages/MainPage/Header/index.tsx b/app-v2/pages/MainPage/Header/index.tsx new file mode 100644 index 00000000..63f74237 --- /dev/null +++ b/app-v2/pages/MainPage/Header/index.tsx @@ -0,0 +1,74 @@ +import React, { useEffect, useState } from 'react'; +import { Layout, Menu } from 'antd'; +import { Link, useLocation } from 'react-router-dom'; +import intl from 'react-intl-universal'; +import HelpMenu from './HelpMenu'; +import Icon from '@appv2/components/Icon'; +import { observer } from 'mobx-react-lite'; +import { useStore } from '@appv2/stores'; +import './index.less'; +const { Header } = Layout; + +interface IMenuItem { + key: string; + path: string; + track: { + category: string; + action: string; + label: string; + }, + icon: string, + intlKey: string +} + +interface IProps { + menus: IMenuItem[] +} + +const PageHeader = (props: IProps) => { + const { menus } = props; + const [activeKey, setActiveKey] = useState(''); + const { global: { username, host } } = useStore(); + const location = useLocation(); + const handleMenuClick = async({ key }) => { + setActiveKey(key); + }; + + + useEffect(() => { + const { pathname } = location; + const activeKey = pathname.split('/')[1]; + setActiveKey(activeKey); + }, []); + + return
+
+ Nebula Studio +
+ {host && username ? <> + + {menus.map(item => + + + {intl.get(item.intlKey)} + + )} + + + + : } +
; +}; + +export default observer(PageHeader); diff --git a/app-v2/pages/MainPage/index.less b/app-v2/pages/MainPage/index.less new file mode 100644 index 00000000..e69de29b diff --git a/app-v2/pages/MainPage/index.tsx b/app-v2/pages/MainPage/index.tsx new file mode 100644 index 00000000..2f1e2167 --- /dev/null +++ b/app-v2/pages/MainPage/index.tsx @@ -0,0 +1,30 @@ +import React, { Suspense } from 'react'; +import { Layout, Spin } from 'antd'; +import { + Redirect, + Route, + Switch, +} from 'react-router-dom'; +import { MENU_LIST, RoutesList } from './routes'; +import './index.less'; + +import Header from './Header'; +const { Content } = Layout; + + +const MainPage = () => { + return +
+ + {RoutesList.map(route => <> + }> + + + + + } key={route.path} exact={route.exact} />)} + + + ; +}; +export default MainPage; diff --git a/app-v2/pages/MainPage/routes.tsx b/app-v2/pages/MainPage/routes.tsx new file mode 100644 index 00000000..ac605da8 --- /dev/null +++ b/app-v2/pages/MainPage/routes.tsx @@ -0,0 +1,53 @@ +import { lazy } from 'react'; + +const Import = lazy(() => import('@appv2/pages/Import')); +const TaskCreate = lazy(() => import('@appv2/pages/Import/TaskCreate')); + + +export const RoutesList = [ + { + path: '/import/create', + component: TaskCreate, + exact: true, + }, + { + path: '/import/:type', + component: Import, + }, +]; + +export const MENU_LIST = [ + { + key: 'schema', + path: '/schema', + track: { + category: 'navigation', + action: 'view_schema', + label: 'from_navigation' + }, + icon: 'iconnav-model', + intlKey: 'common.schema' + }, + { + key: 'import', + path: '/import/files', + track: { + category: 'navigation', + action: 'view_import', + label: 'from_navigation' + }, + icon: 'iconnav-model', + intlKey: 'common.import' + }, + { + key: 'console', + path: '/console', + track: { + category: 'navigation', + action: 'view_console', + label: 'from_navigation' + }, + icon: 'iconnav-model', + intlKey: 'common.console' + }, +]; \ No newline at end of file diff --git a/app-v2/static/fonts/Roboto-Black.ttf b/app-v2/static/fonts/Roboto-Black.ttf new file mode 100644 index 00000000..43a00e0d Binary files /dev/null and b/app-v2/static/fonts/Roboto-Black.ttf differ diff --git a/app-v2/static/fonts/Roboto-Bold.ttf b/app-v2/static/fonts/Roboto-Bold.ttf new file mode 100644 index 00000000..37424579 Binary files /dev/null and b/app-v2/static/fonts/Roboto-Bold.ttf differ diff --git a/app-v2/static/fonts/Roboto-Light.ttf b/app-v2/static/fonts/Roboto-Light.ttf new file mode 100644 index 00000000..0e977514 Binary files /dev/null and b/app-v2/static/fonts/Roboto-Light.ttf differ diff --git a/app-v2/static/fonts/Roboto-Medium.ttf b/app-v2/static/fonts/Roboto-Medium.ttf new file mode 100644 index 00000000..e89b0b79 Binary files /dev/null and b/app-v2/static/fonts/Roboto-Medium.ttf differ diff --git a/app-v2/static/fonts/Roboto-Regular.ttf b/app-v2/static/fonts/Roboto-Regular.ttf new file mode 100644 index 00000000..3d6861b4 Binary files /dev/null and b/app-v2/static/fonts/Roboto-Regular.ttf differ diff --git a/app-v2/static/fonts/iconfont.css b/app-v2/static/fonts/iconfont.css new file mode 100644 index 00000000..2942796c --- /dev/null +++ b/app-v2/static/fonts/iconfont.css @@ -0,0 +1,487 @@ +@font-face { + font-family: "nebula-studio-icon"; /* Project id 1846875 */ + src: url('iconfont.woff2?t=1631006849129') format('woff2'), + url('iconfont.woff?t=1631006849129') format('woff'), + url('iconfont.ttf?t=1631006849129') format('truetype'); +} + +.nebula-studio-icon { + font-family: "nebula-studio-icon" !important; + font-size: 16px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.iconimage-iconUnselect:before { + content: "\e71a"; +} + +.iconimage-icon17:before { + content: "\e70a"; +} + +.iconimage-icon16:before { + content: "\e70b"; +} + +.iconimage-icon21:before { + content: "\e70c"; +} + +.iconimage-icon20:before { + content: "\e70d"; +} + +.iconimage-icon15:before { + content: "\e70e"; +} + +.iconimage-icon22:before { + content: "\e70f"; +} + +.iconimage-icon19:before { + content: "\e710"; +} + +.iconimage-icon23:before { + content: "\e711"; +} + +.iconimage-icon18:before { + content: "\e712"; +} + +.iconimage-icon25:before { + content: "\e713"; +} + +.iconimage-icon14:before { + content: "\e714"; +} + +.iconimage-icon24:before { + content: "\e715"; +} + +.iconimage-icon26:before { + content: "\e716"; +} + +.iconimage-icon28:before { + content: "\e717"; +} + +.iconimage-icon29:before { + content: "\e718"; +} + +.iconimage-icon27:before { + content: "\e719"; +} + +.iconimage-icon1:before { + content: "\e6fd"; +} + +.iconimage-icon3:before { + content: "\e6fe"; +} + +.iconimage-icon2:before { + content: "\e6ff"; +} + +.iconimage-icon4:before { + content: "\e700"; +} + +.iconimage-icon5:before { + content: "\e701"; +} + +.iconimage-icon6:before { + content: "\e702"; +} + +.iconimage-icon7:before { + content: "\e703"; +} + +.iconimage-icon8:before { + content: "\e704"; +} + +.iconimage-icon9:before { + content: "\e705"; +} + +.iconimage-icon10:before { + content: "\e706"; +} + +.iconimage-icon12:before { + content: "\e707"; +} + +.iconimage-icon13:before { + content: "\e708"; +} + +.iconimage-icon11:before { + content: "\e709"; +} + +.iconstudio-unexpand:before { + content: "\e67d"; +} + +.iconDefault-image-left:before { + content: "\e658"; +} + +.iconstudio-unlock:before { + content: "\e656"; +} + +.iconstudio-lock:before { + content: "\e657"; +} + +.iconstudio-zoomout:before { + content: "\e651"; +} + +.iconstudio-zoomin:before { + content: "\e652"; +} + +.iconstudio-down:before { + content: "\e640"; +} + +.iconstudio-expandcondition:before { + content: "\e641"; +} + +.iconstudio-delete:before { + content: "\e642"; +} + +.iconstudio-back:before { + content: "\e643"; +} + +.iconstudio-indentright:before { + content: "\e644"; +} + +.iconstudio-moving:before { + content: "\e645"; +} + +.iconstudio-hotkey:before { + content: "\e646"; +} + +.iconstudio-expand:before { + content: "\e647"; +} + +.iconstudio-indentleft:before { + content: "\e648"; +} + +.iconstudio-search:before { + content: "\e649"; +} + +.iconstudio-exportcsv:before { + content: "\e64a"; +} + +.iconstudio-edge:before { + content: "\e64b"; +} + +.iconstudio-show:before { + content: "\e64c"; +} + +.iconstudio-vertex:before { + content: "\e64d"; +} + +.iconstudio-seletexpand:before { + content: "\e64e"; +} + +.iconstudio-remake:before { + content: "\e64f"; +} + +.iconstudio-seletdelete:before { + content: "\e650"; +} + +.iconstudio-exportimage:before { + content: "\e653"; +} + +.iconstudio-window:before { + content: "\e654"; +} + +.iconstudio-seletup:before { + content: "\e655"; +} + +.iconfee-rmb:before { + content: "\e63d"; +} + +.iconfee-usd:before { + content: "\e63f"; +} + +.iconlogout:before { + content: "\e63b"; +} + +.iconbell:before { + content: "\e63c"; +} + +.iconuser:before { + content: "\e63e"; +} + +.iconcard-discover:before { + content: "\e635"; +} + +.iconcard-defaultcard:before { + content: "\e634"; +} + +.iconcard-jcb:before { + content: "\e636"; +} + +.iconcard-american-express:before { + content: "\e637"; +} + +.iconcard-visa:before { + content: "\e638"; +} + +.iconcard-unionpay:before { + content: "\e639"; +} + +.iconcard-mastercard:before { + content: "\e63a"; +} + +.icondetail:before { + content: "\e630"; +} + +.iconcheck:before { + content: "\e631"; +} + +.icondownload:before { + content: "\e632"; +} + +.iconclose:before { + content: "\e633"; +} + +.iconlogo-vesoft:before { + content: "\e62f"; +} + +.iconblog-right-icon:before { + content: "\e62e"; +} + +.iconblog-rss:before { + content: "\e622"; +} + +.iconblog-share:before { + content: "\e624"; +} + +.iconbanner-left:before { + content: "\e61b"; +} + +.iconbackup:before { + content: "\e61c"; +} + +.iconbanner-live:before { + content: "\e61d"; +} + +.iconblog-calendar:before { + content: "\e61e"; +} + +.iconbanner-right:before { + content: "\e61f"; +} + +.iconbanner-report:before { + content: "\e620"; +} + +.iconblog-tag:before { + content: "\e621"; +} + +.iconblog-writer:before { + content: "\e623"; +} + +.icondownload-exchange:before { + content: "\e625"; +} + +.icondownload-csv:before { + content: "\e626"; +} + +.icondownload-Docker:before { + content: "\e627"; +} + +.icondownload-Kubernetes:before { + content: "\e628"; +} + +.icondownload-Source:before { + content: "\e629"; +} + +.iconreadmore:before { + content: "\e62a"; +} + +.iconlogo-cloud:before { + content: "\e62b"; +} + +.iconlogo-nebula:before { + content: "\e62c"; +} + +.icondownload-linux:before { + content: "\e62d"; +} + +.iconzhihu:before { + content: "\e61a"; +} + +.iconbilibili:before { + content: "\e619"; +} + +.iconnav-order:before { + content: "\e618"; +} + +.iconnav-message:before { + content: "\e616"; +} + +.iconnav-model:before { + content: "\e615"; +} + +.iconteam:before { + content: "\e614"; +} + +.iconteam-newmember:before { + content: "\e613"; +} + +.iconteam-member:before { + content: "\e612"; +} + +.iconteam-owner:before { + content: "\e611"; +} + +.iconteam-admin:before { + content: "\e610"; +} + +.iconsele-down:before { + content: "\e60f"; +} + +.iconfilters:before { + content: "\e60e"; +} + +.iconserver-icon:before { + content: "\e60d"; +} + +.iconweibo:before { + content: "\e60b"; +} + +.iconassistantwechat:before { + content: "\e601"; +} + +.iconforum:before { + content: "\e602"; +} + +.iconfacebook:before { + content: "\e603"; +} + +.icongitHub:before { + content: "\e604"; +} + +.iconmail:before { + content: "\e605"; +} + +.iconlinkedin:before { + content: "\e606"; +} + +.iconphone:before { + content: "\e607"; +} + +.icontwitter:before { + content: "\e608"; +} + +.iconslack:before { + content: "\e609"; +} + +.iconwechat:before { + content: "\e60a"; +} + +.iconyoutube:before { + content: "\e60c"; +} + diff --git a/app-v2/static/fonts/iconfont.ttf b/app-v2/static/fonts/iconfont.ttf new file mode 100644 index 00000000..014770f1 Binary files /dev/null and b/app-v2/static/fonts/iconfont.ttf differ diff --git a/app-v2/static/fonts/iconfont.woff b/app-v2/static/fonts/iconfont.woff new file mode 100644 index 00000000..6d8ac6e5 Binary files /dev/null and b/app-v2/static/fonts/iconfont.woff differ diff --git a/app-v2/static/fonts/iconfont.woff2 b/app-v2/static/fonts/iconfont.woff2 new file mode 100644 index 00000000..11629b11 Binary files /dev/null and b/app-v2/static/fonts/iconfont.woff2 differ diff --git a/app-v2/static/images/background_login.png b/app-v2/static/images/background_login.png new file mode 100644 index 00000000..d0349dd1 Binary files /dev/null and b/app-v2/static/images/background_login.png differ diff --git a/app-v2/static/images/nebula_logo.png b/app-v2/static/images/nebula_logo.png new file mode 100644 index 00000000..df4c7dbb Binary files /dev/null and b/app-v2/static/images/nebula_logo.png differ diff --git a/app-v2/static/import.d.ts b/app-v2/static/import.d.ts new file mode 100644 index 00000000..d0cd11ed --- /dev/null +++ b/app-v2/static/import.d.ts @@ -0,0 +1,7 @@ +declare module '*.svg'; +declare module '*.png'; +declare module '*.jpg'; +declare module '*.jpeg'; +declare module '*.gif'; +declare module '*.bmp'; +declare module '*.tiff'; diff --git a/app-v2/stores/console.ts b/app-v2/stores/console.ts new file mode 100644 index 00000000..84859ef7 --- /dev/null +++ b/app-v2/stores/console.ts @@ -0,0 +1,40 @@ +import { makeAutoObservable, observable } from 'mobx'; +import service from '@appv2/config/service'; + +export class ConsoleStore { + runGQLLoading = false; + currentGQL = 'SHOW SPACES;'; + result = {}; + + constructor() { + makeAutoObservable(this, { + result: observable.ref, + }); + } + + update = (param: Partial) => { + Object.keys(param).forEach(key => (this[key] = param[key])); + }; + + asyncRunGQL = async(gql: string) => { + this.update({ runGQLLoading: true }); + try { + const result = await service.execNGQL( + { gql }, + { + trackEventConfig: { + category: 'console', + action: 'run_gql', + }, + }, + ); + this.update({ result, currentGQL: gql }); + } finally { + window.setTimeout(() => this.update({ runGQLLoading: false }), 300); + } + }; +} + +const consoleStore = new ConsoleStore(); + +export default consoleStore; diff --git a/app-v2/stores/files.ts b/app-v2/stores/files.ts new file mode 100644 index 00000000..7c93e912 --- /dev/null +++ b/app-v2/stores/files.ts @@ -0,0 +1,63 @@ +import { action, makeAutoObservable, observable, runInAction } from 'mobx'; +import service from '@appv2/config/service'; +import { message } from 'antd'; +import intl from 'react-intl-universal'; + +export class FilesStore { + uploadDir: string = ''; + fileList: any[] = []; + constructor() { + makeAutoObservable(this, { + uploadDir: observable, + fileList: observable, + + update: action, + }); + } + + update = (payload: Record) => { + Object.keys(payload).forEach(key => Object.prototype.hasOwnProperty.call(this, key) && (this[key] = payload[key])); + }; + + asyncGetFiles = async() => { + const { code, data } = (await service.getFiles()) as any; + if (code === 0 && data) { + this.update({ + fileList: data, + }); + } + }; + asyncUploadFile = async(payload: Record) => { + const { data, config } = payload; + const res = (await service.uploadFiles(data, config)) as any; + return res; + }; + + asyncDeleteFile = async(index: number) => { + const res: any = await service.deteleFile({ + filename: this.fileList[index].name, + }); + if (res.code === 0) { + message.success(intl.get('common.deleteSuccess')); + runInAction(() => { + this.fileList = this.fileList.filter((_, i) => i !== index); + }); + } + }; + + + asyncGetUploadDir = async() => { + const { code, data } = (await service.getUploadDir()) as any; + if (code === 0) { + const { uploadDir } = data; + this.update({ + uploadDir, + }); + } + }; +} + +const filesStore = new FilesStore(); + +export default filesStore; + diff --git a/app-v2/stores/global.ts b/app-v2/stores/global.ts new file mode 100644 index 00000000..6049361f --- /dev/null +++ b/app-v2/stores/global.ts @@ -0,0 +1,86 @@ +import { action, makeObservable, observable } from 'mobx'; +import cookies from 'js-cookie'; +import { message } from 'antd'; +import intl from 'react-intl-universal'; +import service from '@appv2/config/service'; +import { BrowserHistory } from 'history'; +import { NebulaVersion } from './types'; +import { getRootStore } from '.'; + +export class GlobalStore { + history: BrowserHistory; + username = cookies.get('nu'); + host = cookies.get('nh'); + version = process.env.VERSION; + nebulaVersion?: NebulaVersion = cookies.get('NebulaVersion'); + constructor() { + makeObservable(this, { + username: observable, + host: observable, + update: action, + }); + } + + + get rootStore() { + return getRootStore(); + } + + logout = async() => { + await service.disconnectDB( + {}, + { + trackEventConfig: { + category: 'user', + action: 'sign_out', + }, + }, + ); + this.update({ + host: '', + username: '', + currentSpace: '', + }); + cookies.remove('nh'); + cookies.remove('nu'); + }; + + update = (payload: Record) => { + Object.keys(payload).forEach(key => Object.prototype.hasOwnProperty.call(this, key) && (this[key] = payload[key])); + }; + + login = async(payload: { host: string; username: string; password: string }) => { + const { host, username, password } = payload; + const [address, port] = host.replace(/^https?:\/\//, '').split(':'); + const { code, data } = (await service.connectDB( + { + address, + port: +port, + username, + password, + }, + { + trackEventConfig: { + category: 'user', + action: 'sign_in', + }, + }, + )) as any; + if (code === 0) { + message.success(intl.get('configServer.success')); + cookies.set('nh', host); + cookies.set('nu', username); + this.update({ host, username, nebulaVersion: data.version }); + return true; + } + + this.update({ host: '', username: '' }); + cookies.remove('nh'); + cookies.remove('nu'); + return false; + }; +} + +const globalStore = new GlobalStore(); + +export default globalStore; diff --git a/app-v2/stores/import.ts b/app-v2/stores/import.ts new file mode 100644 index 00000000..724bee8b --- /dev/null +++ b/app-v2/stores/import.ts @@ -0,0 +1,255 @@ +import { action, makeAutoObservable, observable, runInAction } from 'mobx'; +import service from '@appv2/config/service'; +import { IBasicConfig, IEdgeConfig, ITaskItem, IVerticesConfig } from '@appv2/interfaces/import'; +import { getRootStore } from '.'; +export class ImportStore { + taskList: ITaskItem[] = []; + taskDir: string = ''; + verticesConfig: IVerticesConfig[] = []; + edgesConfig: IEdgeConfig[] = []; + + basicConfig: IBasicConfig = { taskName: '' }; + constructor() { + makeAutoObservable(this, { + taskList: observable, + taskDir: observable, + verticesConfig: observable, + edgesConfig: observable, + basicConfig: observable, + + update: action, + updateVerticesConfig: action, + updateTagPropMapping: action, + updateBasicConfig: action, + }); + } + + get rootStore() { + return getRootStore(); + } + + update = (payload: Record) => { + Object.keys(payload).forEach(key => Object.prototype.hasOwnProperty.call(this, key) && (this[key] = payload[key])); + }; + + asyncGetTaskList = async() => { + const { code, data } = await service.handleImportAction({ + taskAction: 'actionQueryAll', + }); + if (code === 0 && data) { + this.update({ + taskList: data.results || [], + }); + } + }; + + asyncGetTaskDir = async() => { + const { code, data } = (await service.getTaskDir()) as any; + if (code === 0) { + const { taskDir } = data; + this.update({ + taskDir, + }); + } + }; + + importTask = async(config, name) => { + const { code, data } = (await service.importData({ + configBody: config, + configPath: '', + name + })) as any; + if (code === 0) { + this.update({ + taskId: data[0], + }); + } + return code; + } + + stopTask = async(taskID: number) => { + const res = await service.handleImportAction({ + taskID: taskID.toString(), + taskAction: 'actionStop', + }); + return res; + } + + deleteTask = async(taskID: number) => { + const res = await service.handleImportAction({ + taskID: taskID.toString(), + taskAction: 'actionDel', + }); + return res; + } + + downloadTaskConfig = async(taskID: number) => { + const link = document.createElement('a'); + link.href = `/api-nebula/task/import/config/${taskID}`; + link.download = `config.yml`; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } + + asyncUpdateTagConfig = async(payload: { + tag: string; + tagIndex: number; + configIndex: number; + }) => { + const { schema } = this.rootStore; + const { getTagInfo, getTagDetail } = schema; + const { tag, tagIndex, configIndex } = payload; + const { code, data } = await getTagInfo(tag); + const createTag = await getTagDetail(tag); + const defaultValueFields: any[] = []; + if (!!createTag) { + const res = + (createTag.data.tables && createTag.data.tables[0]['Create Tag']) || + ''; + const fields = res.split(/\n|\r\n/); + fields.forEach(field => { + const fieldArr = field.trim().split(/\s|\s+/); + if (fieldArr.includes('default') || fieldArr.includes('DEFAULT')) { + let defaultField = fieldArr[0]; + if (defaultField.includes('`')) { + defaultField = defaultField.replace(/`/g, ''); + } + defaultValueFields.push(defaultField); + } + }); + } + if (code === 0) { + const props = data.tables.map(attr => { + return { + name: attr.Field, + type: attr.Type === 'int64' ? 'int' : attr.Type, // HACK: exec return int64 but importer only use int + isDefault: defaultValueFields.includes(attr.Field), + mapping: null, + }; + }); + runInAction(() => { + this.verticesConfig[configIndex].tags[tagIndex] = { + name: tag, + props + }; + }); + } + } + + updateBasicConfig = (key: string, value: any) => { + this.basicConfig[key] = value; + } + + updateEdgeConfig = async(payload: { edgeType?: string, index: number; }) => { + const { edgeType, index } = payload; + if(!edgeType) { + this.edgesConfig = this.edgesConfig.splice(index, 1); + } else { + const { schema } = this.rootStore; + const { getEdgeInfo, getEdgeDetail, spaceVidType } = schema; + const { code, data } = await getEdgeInfo(edgeType); + const createTag = await getEdgeDetail(edgeType); + const defaultValueFields: any[] = []; + if (!!createTag) { + const res = + (createTag.data.tables && createTag.data.tables[0]['Create Edge']) || + ''; + const fields = res.split(/\n|\r\n/); + fields.forEach(field => { + const fieldArr = field.trim().split(/\s|\s+/); + if (field.includes('default') || fieldArr.includes('DEFAULT')) { + let defaultField = fieldArr[0]; + if (defaultField.includes('`')) { + defaultField = defaultField.replace(/`/g, ''); + } + defaultValueFields.push(defaultField); + } + }); + } + if (code === 0) { + const props = data.tables.map(item => { + return { + name: item.Field, + type: item.Type === 'int64' ? 'int' : item.Type, + isDefault: defaultValueFields.includes(item.Field), + mapping: null, + }; + }); + + runInAction(() => { + this.edgesConfig[index].type = edgeType; + this.edgesConfig[index].props = [ + // each edge must have the three special prop srcId, dstId, rank,put them ahead + { + name: 'srcId', + type: spaceVidType === 'INT64' ? 'int' : 'string', + mapping: null, + }, + { + name: 'dstId', + type: spaceVidType === 'INT64' ? 'int' : 'string', + mapping: null, + }, + { + name: 'rank', + type: 'int', + mapping: null, + }, + ...props, + ]; + }); + } + } + } + + updateVerticesConfig = (payload: { + index: number + key?: string, + value?: any + }) => { + const { index, key, value } = payload; + if(key) { + this.verticesConfig[index][key] = value; + } else { + this.verticesConfig.splice(index, 1); + } + } + + updateTagPropMapping = (payload: { + configIndex: number, + tagIndex: number, + propIndex?: number, + field?: string, + value?: any + }) => { + const { configIndex, tagIndex, propIndex, field, value } = payload; + if(propIndex === undefined) { + const tags = this.verticesConfig[configIndex].tags; + tags.splice(tagIndex, 1); + } else { + const tag = this.verticesConfig[configIndex].tags[tagIndex]; + tag.props[propIndex][field!] = value; + } + } + + updateEdgePropMapping = (payload: { + configIndex, + propIndex?, + field?, + value? + }) => { + const { configIndex, propIndex, field, value } = payload; + if(propIndex === undefined) { + this.edgesConfig[configIndex].type = ''; + this.edgesConfig[configIndex].props = []; + } else { + this.edgesConfig[configIndex].props[propIndex][field] = value; + } + } +} + +const importStore = new ImportStore(); + +export default importStore; + diff --git a/app-v2/stores/index.ts b/app-v2/stores/index.ts new file mode 100644 index 00000000..2df993d3 --- /dev/null +++ b/app-v2/stores/index.ts @@ -0,0 +1,21 @@ +import { createContext, useContext } from 'react'; +import global from './global'; +import files from './files'; +import console from './console'; +import dataImport from './import'; +import schema from './schema'; + +const rootStore = { global, files, console, dataImport, schema }; +const rootStoreRef = { current: rootStore }; +// @ts-ignore +window.rootStore = rootStore; +export const getRootStore = () => rootStoreRef.current; + +export const storeContext = createContext(rootStore); +export const StoreProvider = storeContext.Provider; +export function useStore() { + const store = useContext(storeContext); + return store; +} + +export default rootStore; diff --git a/app-v2/stores/schema.ts b/app-v2/stores/schema.ts new file mode 100644 index 00000000..8d11e47c --- /dev/null +++ b/app-v2/stores/schema.ts @@ -0,0 +1,201 @@ +import { action, makeAutoObservable, observable } from 'mobx'; +import service from '@appv2/config/service'; +import { IEdge, IIndexList, ISpace, ITag, ITree } from '@appv2/interfaces/schema'; +import { handleKeyword } from '@appv2/utils/function'; +import { findIndex } from 'lodash'; +export class SchemaStore { + spaces: string[] = []; + currentSpace: string = sessionStorage.getItem('currentSpace') || ''; + spaceVidType: string; + edgeTypes: string[] = []; + tagsFields: any[] = []; + edgesFields: any[] = []; + indexes: any[] = []; + tags: any[] = []; + tagIndexTree: ITree[] = []; + edgeIndexTree: ITree[] = []; + spaceList: ISpace[] = []; + activeMachineNum: number; + tagList: ITag[] = []; + edgeList: IEdge[] = []; + indexList: IIndexList[] = []; + constructor() { + makeAutoObservable(this, { + spaces: observable, + currentSpace: observable, + spaceVidType: observable, + edgeTypes: observable, + tagsFields: observable, + edgesFields: observable, + indexes: observable, + tags: observable, + tagIndexTree: observable, + edgeIndexTree: observable, + spaceList: observable, + activeMachineNum: observable, + tagList: observable, + edgeList: observable, + indexList: observable, + update: action + }); + } + + update = (payload: Record) => + Object.keys(payload).forEach(key => Object.prototype.hasOwnProperty.call(this, key) && (this[key] = payload[key])); + + + // switch space + updateSpaceInfo = async(space: string) => { + await this.switchSpace(space); + await this.getSchemaInfo(); + } + + switchSpace = async(space: string) => { + const { code } = (await service.execNGQL({ + // HACK: Processing keyword + gql: 'use' + '`' + space + '`;', + })) as any; + + if (code === 0) { + const { data } = await this.getSpaceInfo(space); + this.update({ + currentSpace: space, + spaceVidType: data?.tables?.[0]?.['Vid Type'] || 'FIXED_STRING(8)', + }); + sessionStorage.setItem('currentSpace', space); + } + }; + + getSchemaInfo = async() => { + const [tags, edges] = await Promise.all([this.getTags(), this.getEdges()]); + this.update({ tags, edgeTypes: edges }); + } + + getSpaces = async() => { + const { code, data } = (await service.execNGQL({ + gql: 'show spaces;', + })) as any; + if (code === 0) { + const spaces = data.tables.map(item => item.Name).sort(); + this.update({ + spaces, + }); + return { code, data: spaces }; + } else { + return { code, data }; + } + }; + + async getSpaceInfo(space: string) { + const { code, data } = (await service.execNGQL({ + gql: `DESCRIBE SPACE ${handleKeyword(space)}`, + })) as any; + return { code, data }; + } + + asyncGetSpacesList = async(_payload) => { + const res = await this.getSpaces(); + if (res.data) { + const spaces: ISpace[] = []; + await Promise.all( + res.data.map(async(item, i) => { + const { code, data } = await this.getSpaceInfo( + item, + ); + if (code === 0) { + const space = (data.tables && data.tables[0]) || {}; + space.serialNumber = i + 1; + spaces.push(space); + } + }), + ); + this.update({ + spaceList: spaces.sort((a, b) => a.serialNumber - b.serialNumber), + }); + } + } + + // edges + getEdges = async() => { + const { code, data } = (await service.execNGQL({ + gql: ` + show edges; + `, + })) as any; + if (code === 0) { + const edgeTypes = data.tables.map(item => item.Name); + this.update({ edgeTypes }); + return edgeTypes; + } + } + + getEdgeInfo = async(edge: string) => { + const { code, data } = (await service.execNGQL({ + gql: 'desc edge' + '`' + edge + '`;', + })) as any; + return { code, data }; + } + + getEdgeDetail = async(name: string) => { + const gql = `SHOW CREATE EDGE ${handleKeyword(name)}`; + const { code, data, message } = (await service.execNGQL({ + gql, + })) as any; + return { code, data, message }; + } + + + // tags + async getTags() { + const { code, data } = (await service.execNGQL({ + gql: ` + SHOW TAGS; + `, + })) as any; + + if (code === 0) { + const tags = data.tables.map(item => item.Name); + return tags; + } + } + + addTagsName = (payload: any) => { + const { tag, tagFields } = payload; + const index = findIndex(this.tagsFields, item => item.tag === tag); + this.tagsFields[!~index ? this.tagsFields.length : index] = { tag, fields: tagFields }; + }; + + getTagsFields = async(payload: { tags: any[] }) => { + const { tags } = payload; + await Promise.all( + tags.map(async item => { + const { code, data } = await this.getTagInfo(item); + if (code === 0) { + const tagFields = data.tables.map(item => ({ + field: item.Field, + type: item.Type, + })); + this.addTagsName({ tag: item, tagFields }); + } + }), + ); + }; + + getTagInfo = async(tag: string) => { + const { code, data } = (await service.execNGQL({ + gql: 'desc tag ' + '`' + tag + '`;', + })) as any; + return { code, data }; + } + + getTagDetail = async(name: string) => { + const gql = `SHOW CREATE TAG ${handleKeyword(name)}`; + const { code, data, message } = (await service.execNGQL({ + gql, + })) as any; + return { code, data, message }; + } +} + +const schemaStore = new SchemaStore(); +export default schemaStore; diff --git a/app-v2/stores/types.d.ts b/app-v2/stores/types.d.ts new file mode 100644 index 00000000..60729a60 --- /dev/null +++ b/app-v2/stores/types.d.ts @@ -0,0 +1,5 @@ +export enum NebulaVersion { + V2_5 = 'v2.5', + V2_6 = 'v2.6', + V3_0 = 'v3.0', +} diff --git a/app-v2/utils/constant.ts b/app-v2/utils/constant.ts new file mode 100644 index 00000000..0c65e14d --- /dev/null +++ b/app-v2/utils/constant.ts @@ -0,0 +1,213 @@ +export const ENUM_OF_COMPARE = { + int64: [ + { + label: '==', + value: '==', + }, + { + label: '!=', + value: '!=', + }, + { + label: '>', + value: '>', + }, + { + label: '>=', + value: '>=', + }, + { + label: '<', + value: '<', + }, + { + label: '<=', + value: '<=', + }, + ], + string: [ + { + label: '==', + value: '==', + }, + { + label: 'CONTAINS', + value: 'CONTAINS', + }, + { + label: 'STARTS WITH', + value: 'STARTS WITH', + }, + { + label: 'ENDS WITH', + value: 'ENDS WITH', + }, + ], + bool: [ + { + label: '==', + value: '==', + }, + ], + double: [ + { + label: '==', + value: '==', + }, + { + label: '!=', + value: '!=', + }, + { + label: '>', + value: '>', + }, + { + label: '>=', + value: '>=', + }, + { + label: '<', + value: '<', + }, + { + label: '<=', + value: '<=', + }, + ], + timestamp: [ + { + label: '==', + value: '==', + }, + { + label: '!=', + value: '!=', + }, + { + label: '>', + value: '>', + }, + { + label: '>=', + value: '>=', + }, + { + label: '<', + value: '<', + }, + { + label: '<=', + value: '<=', + }, + ], +}; + +export const DATA_TYPE = [ + { + value: 'int', + label: 'int', + }, + { + value: 'bool', + label: 'bool', + }, + { + value: 'string', + label: 'string', + }, + { + value: 'fixed_string', + label: 'fixed_string', + }, + { + value: 'double', + label: 'double', + }, + { + value: 'int32', + label: 'int32', + }, + { + value: 'int16', + label: 'int16', + }, + { + value: 'int8', + label: 'int8', + }, + { + value: 'float', + label: 'float', + }, + { + value: 'date', + label: 'date', + }, + { + value: 'time', + label: 'time', + }, + { + value: 'datetime', + label: 'datetime', + }, + { + value: 'timestamp', + label: 'timestamp', + }, + { + value: 'geography', + label: 'geography', + }, + { + value: 'geography(point)', + label: 'geography(point)', + }, + { + value: 'geography(linestring)', + label: 'geography(linestring)', + }, + { + value: 'geography(polygon)', + label: 'geography(polygon)', + }, + { + value: 'duration', + label: 'duration', + }, +]; + +export const RELATION_OPERATORS = [ + { + label: 'NOT', + value: 'NOT', + }, + { + label: 'AND', + value: 'AND', + }, + { + label: 'OR', + value: 'OR', + }, + { + label: 'XOR', + value: 'XOR', + }, +]; + +export const EXPLAIN_DATA_TYPE = [ + 'date', + 'time', + 'datetime', + 'timestamp', + 'geography', + 'geography(point)', + 'geography(linestring)', + 'geography(polygon)', + 'duration', +]; + +export const NAME_REGEX = /^[a-zA-Z][a-zA-Z0-9_]*$/; +export const POSITIVE_INTEGER_REGEX = /^[1-9]\d*$/g; diff --git a/app-v2/utils/fetch.ts b/app-v2/utils/fetch.ts new file mode 100644 index 00000000..b6b2b54b --- /dev/null +++ b/app-v2/utils/fetch.ts @@ -0,0 +1,83 @@ +import service from '#app/config/service'; +import { handleVidStringName } from '#app/utils/function'; +import { getExploreGQLWithIndex } from '#app/utils/gql'; + +interface IMatchVertex { + vid?: string; + tags?: string[]; + properties?: Record; +} + +export async function fetchEdgeProps(payload: { + idRoutes: string[]; + type: string; + edgeFields?: any; +}) { + const { idRoutes, edgeFields, type } = payload; + const edgeType = '`' + type + '`'; + let gql = `fetch prop on ${edgeType} ${idRoutes.join(', ')}`; + if (edgeFields) { + gql += ` yield ${edgeType}._src, ${edgeType}._dst `; + edgeFields[type].forEach(edgeField => { + if (edgeField !== 'type') { + gql += `,${edgeType}.${edgeField}`; + } + }); + } else { + gql += ' YIELD edge as `edges_`'; + } + + const { data } = (await service.execNGQL({ + gql, + })) as any; + return data; +} + +export async function fetchVertexPropsWithIndex(payload: { + tag: string; + filters: any[]; + quantityLimit: number | null; +}) { + const gql = getExploreGQLWithIndex(payload); + const { code, data, message } = (await service.execNGQL({ + gql, + })) as any; + return { code, data, message }; +} + +export async function fetchVertexProps(payload: { + ids: string[]; + spaceVidType: string; +}) { + const { ids, spaceVidType } = payload; + const _ids = ids.map(id => handleVidStringName(id, spaceVidType)).join(', '); + const gql = `MATCH (n) WHERE id(n) IN [${_ids}] RETURN n`; + const { data, code, message } = (await service.execNGQL({ + gql, + })) as any; + if (code === 0) { + const vertexList = data.tables.map(i => i._verticesParsedList).flat(); + const vertexes = vertexList.map(vertex => { + const _vertex: IMatchVertex = {}; + _vertex.vid = vertex.vid || ''; + _vertex.tags = vertex.tags || []; + _vertex.properties = vertex.properties || {}; + return _vertex; + }); + return { data: vertexes, code, message }; + } + return { data, code, message }; +} + +export async function fetchBidirectVertexes(payload: { + ids: string[]; + spaceVidType: string; +}) { + const { ids, spaceVidType } = payload; + const _ids = ids.map(id => handleVidStringName(id, spaceVidType)).join(', '); + const gql = `GO FROM ${_ids} OVER * BIDIRECT yield edge as \`_edge\``; + const { code, data, message } = (await service.execNGQL({ + gql, + })) as any; + return { code, data, message }; +} diff --git a/app-v2/utils/file.ts b/app-v2/utils/file.ts new file mode 100644 index 00000000..c40a1ab6 --- /dev/null +++ b/app-v2/utils/file.ts @@ -0,0 +1,20 @@ +export function readFileContent(file) { + const reader = new FileReader(); + return new Promise((resolve, reject) => { + reader.onload = (event: any) => resolve(event.target.result); + reader.onerror = error => reject(error); + reader.readAsText(file); + }); +} + +export function getFileSize(size: number) { + if (size < 1000) { + return `${size} B`; + } else if (size < 1000000) { + return `${(size / 1000).toFixed(2)} KB`; + } else if (size < 1000000000) { + return `${(size / 1000000).toFixed(2)} MB`; + } else { + return `${(size / 1000000000).toFixed(2)} GB`; + } +} diff --git a/app-v2/utils/function.ts b/app-v2/utils/function.ts new file mode 100644 index 00000000..7cf87b6e --- /dev/null +++ b/app-v2/utils/function.ts @@ -0,0 +1,39 @@ +import { BigNumber } from 'bignumber.js'; +import _ from 'lodash'; + +import { keyWords } from '#app/config/nebulaQL'; + +export const handleKeyword = (name: string) => { + return keyWords.includes(name.toLowerCase()) ? `\`${name}\`` : name; +}; + +export const handleVidStringName = (name: string, spaceVidType?: string) => { + if (spaceVidType && spaceVidType === 'INT64') { + return convertBigNumberToString(name); + } + if (name.indexOf(`"`) > -1 && name.indexOf(`'`) === -1) { + return `'${name}'`; + } else { + return `"${name}"`; + } +}; + +export const convertBigNumberToString = (value: any) => { + // int precision length in nebula is longer than in javascript + return BigNumber.isBigNumber(value) ? value.toString() : value; +}; + +export const sortByFieldAndFilter = (payload: { + field: string; + searchVal: string; + list: any[]; +}) => { + const { searchVal, list, field } = payload; + if (searchVal) { + return _.orderBy(list, [field], ['asc']).filter((item: any) => + item.name.includes(searchVal), + ); + } else { + return _.orderBy(list, [field], ['asc']); + } +}; diff --git a/app-v2/utils/gql.ts b/app-v2/utils/gql.ts new file mode 100644 index 00000000..3f85a4d5 --- /dev/null +++ b/app-v2/utils/gql.ts @@ -0,0 +1,293 @@ +import { handleKeyword, handleVidStringName } from '#app/utils/function'; +interface IField { + name: string; + type: string; + value?: string; + allowNull?: boolean; + fixedLength?: string; + comment?: string; +} + +type IndexType = 'TAG' | 'EDGE'; +type AlterType = 'ADD' | 'DROP' | 'CHANGE' | 'TTL' | 'COMMENT'; +interface IAlterConfig { + fields?: IField[]; + comment?: string; + ttl?: { + col?: string; + duration?: string; + }; +} + +export const getExploreMatchGQL = (params: { + selectVertexes: any[]; + edgeTypes: string[]; + edgeDirection?: string; + filters?: any[]; + quantityLimit?: number | null; + spaceVidType: string; + stepsType?: string; + step?: string; + minStep?: string; + maxStep?: string; +}) => { + const { + selectVertexes, + edgeTypes, + edgeDirection, + filters, + quantityLimit, + spaceVidType, + stepsType, + step, + minStep, + maxStep, + } = params; + let _step = ''; + if (stepsType === 'single') { + _step = `*${step || 1}`; + } else if (stepsType === 'range' && minStep && maxStep) { + _step = `*${minStep}..${maxStep}`; + } + const _filters = filters + ? filters + .map(filter => `${filter.relation || ''} l.${filter.expression}`) + .join(`\n`) + : ''; + const wheres = _filters ? `AND ALL(l IN e WHERE ${_filters})` : ''; + const gql = `MATCH p=(v)${ + edgeDirection === 'incoming' ? '<-' : '-' + }[e${edgeTypes.map(edge => `:${handleKeyword(edge)}`).join('|')}${_step}]${ + edgeDirection === 'outgoing' ? '->' : '-' + }(v2) +WHERE id(v) IN [${selectVertexes + .map(i => handleVidStringName(i.name, spaceVidType)) + .join(', ')}] + ${wheres} RETURN p LIMIT ${quantityLimit ? quantityLimit : 100}`; + return gql; +}; + +export const getExploreGQLWithIndex = (params: { + tag: string; + filters: any[]; + quantityLimit: number | null; +}) => { + const { tag, filters, quantityLimit } = params; + const tagName = '`' + tag + '`'; + const wheres = filters + .filter( + filter => + filter.field && + filter.operator && + !['', undefined, null].includes(filter.value), + ) + .map(filter => { + const value = + filter.type === 'string' || filter.type.startsWith('fixed_string') + ? handleVidStringName(filter.value) + : filter.value; + return `${filter.relation ? filter.relation : ''} ${tagName}.${ + filter.field + } ${filter.operator} ${value}`; + }) + .join(`\n`); + const gql = + `LOOKUP ON + ${handleKeyword(tag)} ${wheres ? `\nWHERE ${wheres}` : ''} + ` + + ` yield vertex as \`vertex_\` | LIMIT ${ + quantityLimit ? quantityLimit : 100 + }`; + + return gql; +}; + +export const getSpaceCreateGQL = (params: { + name: string; + comment?: string | undefined; + options: { + partition_num: string | undefined; + replica_factor: string | undefined; + vid_type: string; + }; +}) => { + const { name, options, comment } = params; + const optionsStr = Object.keys(options) + .filter(i => options[i] !== undefined && options[i] !== '') + .map(i => { + return `${i} = ${options[i]}`; + }) + .join(', '); + const gql = `CREATE SPACE ${handleKeyword(name)} ${ + optionsStr ? `(${optionsStr})` : '' + } ${comment ? `COMMENT = "${comment}"` : ''}`; + return gql; +}; + +export const getTagOrEdgeCreateGQL = (params: { + type: 'TAG' | 'EDGE'; + name: string; + comment?: string; + fields?: IField[]; + ttlConfig?: { + ttl_col: string; + ttl_duration: number; + }; +}) => { + const { type, name, fields, ttlConfig, comment } = params; + const fieldsStr = fields + ? fields + .map(item => { + let valueStr = ''; + if (item.value) { + switch (item.type) { + case 'string': + case 'fixed_string': + valueStr = `DEFAULT "${item.value}"`; + break; + case 'timestamp': + const timestampReg = /^(\d{4})-(\d{2})-(\d{2})\s(\d{2}):(\d{2}):(\d{2})$/; + valueStr = timestampReg.test(item.value) + ? `DEFAULT "${item.value}"` + : `DEFAULT ${item.value}`; + break; + default: + valueStr = `DEFAULT ${item.value}`; + } + } + const _type = + item.type !== 'fixed_string' + ? item.type + : item.type + `(${item.fixedLength ? item.fixedLength : ''})`; + const _null = item.allowNull ? 'NULL' : 'NOT NULL'; + const _comment = item.comment ? `COMMENT "${item.comment}"` : ''; + const conbine = [ + handleKeyword(item.name), + _type, + _null, + valueStr, + _comment, + ]; + return conbine.join(' '); + }) + .join(', ') + : ''; + const ttlStr = ttlConfig + ? `TTL_DURATION = ${ttlConfig.ttl_duration || + ''}, TTL_COL = "${ttlConfig.ttl_col || ''}"` + : ''; + const gql = `CREATE ${type} ${handleKeyword(name)} ${ + fieldsStr.length > 0 ? `(${fieldsStr})` : '()' + } ${ttlStr} ${ + comment ? `${ttlStr.length > 0 ? ', ' : ''}COMMENT = "${comment}"` : '' + }`; + return gql; +}; + +export const getAlterGQL = (params: { + type: IndexType; + name: string; + action: AlterType; + config: IAlterConfig; +}) => { + let content; + const { type, name, action, config } = params; + if (action === 'TTL' && config.ttl) { + const { ttl } = config; + content = `TTL_DURATION = ${ttl.duration || 0}, TTL_COL = "${ttl.col}"`; + } else if (action === 'COMMENT' && config.comment) { + content = `COMMENT="${config.comment}"`; + } else if (action !== 'TTL' && action !== 'COMMENT' && config.fields) { + const date = config.fields + .map(item => { + const { name, type, value, fixedLength, allowNull, comment } = item; + const propertyName = handleKeyword(name); + if (action === 'DROP') { + return propertyName; + } + let str = `${propertyName} ${ + type !== 'fixed_string' + ? type + : type + `(${fixedLength ? item.fixedLength : ''})` + } ${allowNull ? 'NULL' : 'NOT NULL'}`; + if (value) { + switch (type) { + case 'string': + case 'fixed_string': + str += ` DEFAULT "${value}"`; + break; + case 'timestamp': + const timestampReg = /^(\d{4})-(\d{2})-(\d{2})\s(\d{2}):(\d{2}):(\d{2})$/; + str += timestampReg.test(value) + ? ` DEFAULT "${value}"` + : ` DEFAULT ${value}`; + break; + default: + str += ` DEFAULT ${value}`; + } + } + if (comment) { + str += ` COMMENT "${comment}"`; + } + return str; + }) + .join(', '); + content = `${action} (${date})`; + } + const gql = `ALTER ${type} ${handleKeyword(name)} ${content}`; + return gql; +}; + +export const getIndexCreateGQL = (params: { + type: IndexType; + name: string; + associate: string; + comment?: string; + fields: string[]; +}) => { + const { type, name, associate, fields, comment } = params; + const combine = associate + ? `on ${handleKeyword(associate)}(${fields.join(', ')})` + : ''; + const gql = `CREATE ${type} INDEX ${handleKeyword(name)} ${combine} ${ + comment ? `COMMENT = "${comment}"` : '' + }`; + return gql; +}; + +export const getPathGQL = (params: { + type: string; + srcId: string[]; + dstId: string[]; + relation?: string[]; + direction?: string; + stepLimit?: number | null; + quantityLimit?: number | null; + spaceVidType: string; +}) => { + const { + type, + srcId, + dstId, + relation, + direction, + stepLimit, + quantityLimit, + spaceVidType, + } = params; + const _srcIds = srcId + .map(item => handleVidStringName(item, spaceVidType)) + .join(', '); + const _dstIds = dstId + .map(item => handleVidStringName(item, spaceVidType)) + .join(', '); + const _relation = relation && relation.length > 0 ? relation.join(', ') : '*'; + const gql = + `FIND ${type} PATH FROM ${_srcIds} TO ${_dstIds} over ${_relation}` + + `${direction ? ` ${direction}` : ''}` + + `${stepLimit ? ' UPTO ' + stepLimit + ' STEPS' : ''}` + + ' yield path as `paths_`' + + `${quantityLimit ? ' | LIMIT ' + quantityLimit : ''}`; + + return gql; +}; diff --git a/app-v2/utils/http.ts b/app-v2/utils/http.ts new file mode 100644 index 00000000..db7c48ee --- /dev/null +++ b/app-v2/utils/http.ts @@ -0,0 +1,112 @@ +import { message } from 'antd'; +import axios from 'axios'; +import JSONBigint from 'json-bigint'; +import intl from 'react-intl-universal'; + +import { trackEvent } from './stat'; +import { store } from '#app/store'; + + +const service = axios.create({ + transformResponse: [ + data => { + try { + const _data = JSONBigint.parse(data); + return _data; + } catch (err) { + try { + return JSON.parse(data); + } catch (e) { + return data; + } + } + }, + ], +}); + +service.interceptors.request.use(config => { + config.headers['Content-Type'] = 'application/json'; + + return config; +}); + +service.interceptors.response.use( + (response: any) => { + const { code, message: errMsg } = response.data; + // if connection refused, login again + if ( + code === -1 && + errMsg && + (errMsg.includes('connection refused') || + errMsg.includes('broken pipe') || + errMsg.includes('session expired') || + errMsg.includes('an existing connection was forcibly closed')) + ) { + message.warning(intl.get('warning.connectError')); + store.dispatch({ + type: 'nebula/asyncClearConfigServer', + }); + } else if (code === -1 && errMsg) { + message.warning(errMsg); + } + return response.data; + }, + (error: any) => { + if (error.response && error.response.status) { + message.error( + `${intl.get('common.requestError')}: ${error.response.status} ${ + error.response.statusText + }`, + ); + return error.response; + } else { + message.error(`${intl.get('common.requestError')}: ${error}`); + return error; + } + }, +); + +const sendRequest = async(type: string, api: string, params?, config?) => { + const { trackEventConfig, ...otherConfig } = config; + let res; + switch (type) { + case 'get': + res = (await service.get(api, { params, ...otherConfig })) as any; + break; + case 'post': + res = (await service.post(api, params, otherConfig)) as any; + break; + case 'put': + res = (await service.put(api, params, otherConfig)) as any; + break; + case 'delete': + res = (await service.delete(api, params)) as any; + break; + default: + break; + } + + if (res && trackEventConfig) { + trackService(res, trackEventConfig); + } + return res; +}; + +const trackService = (res, config) => { + const { category, action } = config; + trackEvent(category, action, res.code === 0 ? 'ajax_success' : 'ajax_failed'); +}; + +const get = (api: string) => (params?: object, config = {}) => + sendRequest('get', api, params, config); + +const post = (api: string) => (params?: object, config = {} as any) => + sendRequest('post', api, params, config); + +const put = (api: string) => (params?: object, config = {}) => + sendRequest('put', api, params, config); + +const _delete = (api: string) => (params?: object, config = {}) => + sendRequest('delete', api, params, config); + +export { get, post, put, _delete }; diff --git a/app-v2/utils/import.ts b/app-v2/utils/import.ts new file mode 100644 index 00000000..30e1ffb4 --- /dev/null +++ b/app-v2/utils/import.ts @@ -0,0 +1,286 @@ +import { message } from 'antd'; +import _ from 'lodash'; +import intl from 'react-intl-universal'; + +import { handleVidStringName } from './function'; + +export function configToJson(payload) { + const { + space, + username, + password, + host, + verticesConfig, + edgesConfig, + taskDir, + spaceVidType, + batchSize + } = payload; + const vertexToJSON = vertexDataToJSON( + verticesConfig, + taskDir, + spaceVidType, + batchSize + ); + const edgeToJSON = edgeDataToJSON( + edgesConfig, + taskDir, + spaceVidType, + batchSize + ); + const files: any[] = [...vertexToJSON, ...edgeToJSON]; + const configJson = { + version: 'v2', + description: 'web console import', + clientSettings: { + retry: 3, + concurrency: 10, + channelBufferSize: 128, + space, + connection: { + user: username, + password, + address: host, + }, + }, + logPath: `${taskDir}/import.log`, + files, + }; + return configJson; +} + +export function edgeDataToJSON( + config: any, + taskDir: string, + spaceVidType: string, + batchSize?: string, +) { + const files = config.map(edge => { + const edgePorps: any[] = []; + _.sortBy(edge.props, t => t.mapping).forEach(prop => { + switch (prop.name) { + case 'rank': + if (prop.mapping !== null) { + edge.rank = { + index: prop.mapping, + }; + } + break; + case 'srcId': + edge.srcVID = { + index: indexJudge(prop.mapping, prop.name), + type: spaceVidType === 'INT64' ? 'int' : 'string', + }; + break; + case 'dstId': + edge.dstVID = { + index: indexJudge(prop.mapping, prop.name), + type: spaceVidType === 'INT64' ? 'int' : 'string', + }; + break; + default: + if (prop.mapping === null && prop.isDefault) { + break; + } + const _prop = { + name: prop.name, + type: prop.type, + index: indexJudge(prop.mapping, prop.name), + }; + edgePorps.push(_prop); + } + }); + const fileName = edge.file.name.replace('.csv', ''); + const edgeConfig = { + path: edge.file.path, + failDataPath: `${taskDir}/err/${fileName}Fail.csv`, + batchSize: Number(batchSize) || 60, + type: 'csv', + csv: { + withHeader: false, + withLabel: false, + }, + schema: { + type: 'edge', + edge: { + name: edge.type, + srcVID: edge.srcVID, + dstVID: edge.dstVID, + rank: edge.rank, + withRanking: edge.rank?.index !== undefined, + props: edgePorps, + }, + }, + }; + return edgeConfig; + }); + return files; +} + +export function vertexDataToJSON( + config: any, + taskDir: string, + spaceVidType: string, + batchSize?: string +) { + const files = config.map(vertex => { + const tags = vertex.tags.map(tag => { + const props = tag.props + .sort((p1, p2) => p1.mapping - p2.mapping) + .map(prop => { + if (prop.mapping === null && prop.isDefault) { + return null; + } + return { + name: prop.name, + type: prop.type, + index: indexJudge(prop.mapping, prop.name), + }; + }); + const _tag = { + name: tag.name, + props: props.filter(prop => prop), + }; + return _tag; + }); + const fileName = vertex.file.name.replace('.csv', ''); + const vertexConfig: any = { + path: vertex.file.path, + failDataPath: `${taskDir}/err/${fileName}Fail.csv`, + batchSize: Number(batchSize) || 60, + type: 'csv', + csv: { + withHeader: false, + withLabel: false, + }, + schema: { + type: 'vertex', + vertex: { + vid: { + index: indexJudge(vertex.idMapping, 'vertexId'), + type: spaceVidType === 'INT64' ? 'int' : 'string', + }, + tags, + }, + }, + }; + return vertexConfig; + }); + return files; +} + +export function indexJudge(index: number | null, name: string) { + if (index === null) { + message.error(`${name} ${intl.get('import.indexNotEmpty')}`); + throw new Error(); + } + return index; +} + +export function getStringByteLength(str: string) { + let bytesCount = 0; + const len = str.length; + for (let i = 0, n = len; i < n; i++) { + const c = str.charCodeAt(i); + if ((c >= 0x0001 && c <= 0x007e) || (c >= 0xff60 && c <= 0xff9f)) { + bytesCount += 1; + } else { + bytesCount += 2; + } + } + return bytesCount; +} + +export function createTaskID(instanceId: string) { + return `${instanceId}.${new Date().getTime()}`; +} + +export function getGQLByConfig(payload) { + const { verticesConfig, edgesConfig, spaceVidType } = payload; + const NGQL: string[] = []; + verticesConfig.forEach(vertexConfig => { + if (vertexConfig.idMapping === null) { + message.error(`vertexId ${intl.get('import.indexNotEmpty')}`); + throw new Error(); + } + const csvTable = vertexConfig.file.content; + vertexConfig.tags.forEach(tag => { + csvTable.forEach(columns => { + const tagField: string[] = []; + const values: any[] = []; + if (!tag.name) { + message.error(`Tag ${intl.get('import.notEmpty')}`); + throw new Error(); + } + tag.props.forEach(prop => { + if (prop.mapping === null && !prop.isDefault) { + message.error(`${prop.name} ${intl.get('import.indexNotEmpty')}`); + throw new Error(); + } + if (prop.mapping !== null) { + // HACK: Processing keyword + tagField.push(`\`${prop.name}\``); + const value = + prop.type === 'string' + ? `"${columns[prop.mapping]}"` + : columns[prop.mapping]; + values.push(value); + } + }); + NGQL.push( + `${'INSERT VERTEX ' + '`'}${tag.name}\`` + + `(${tagField}) VALUES ${handleVidStringName( + columns[vertexConfig.idMapping], + spaceVidType, + )}:(${values})`, + ); + }); + }); + }); + edgesConfig.forEach(edgeConfig => { + const csvTable = edgeConfig.file.content; + csvTable.forEach(columns => { + const edgeField: string[] = []; + const values: any[] = []; + if (!edgeConfig.type) { + message.error(`edgeType ${intl.get('import.notEmpty')}`); + throw new Error(); + } + edgeConfig.props.forEach(prop => { + if (prop.mapping === null && prop.name !== 'rank' && !prop.isDefault) { + message.error(`${prop.name} ${intl.get('import.indexNotEmpty')}`); + throw new Error(); + } + if ( + prop.name !== 'srcId' && + prop.name !== 'dstId' && + prop.name !== 'rank' && + prop.mapping !== null + ) { + // HACK: Processing keyword + edgeField.push(`\`${prop.name}\``); + const value = + prop.type === 'string' + ? `"${columns[prop.mapping]}"` + : columns[prop.mapping]; + values.push(value); + } + }); + const rank = + edgeConfig.props[2].mapping === null + ? '' + : `@${columns[edgeConfig.props[2].mapping]}`; + NGQL.push( + `${'INSERT EDGE ' + '`'}${edgeConfig.type}\`` + + `(${edgeField.join(',')}) VALUES ${handleVidStringName( + columns[edgeConfig.props[0].mapping], + spaceVidType, + )} -> ${handleVidStringName( + columns[edgeConfig.props[1].mapping], + spaceVidType, + )} ${rank}:(${values})`, + ); + }); + }); + return NGQL; +} diff --git a/app-v2/utils/index.ts b/app-v2/utils/index.ts new file mode 100644 index 00000000..6162b763 --- /dev/null +++ b/app-v2/utils/index.ts @@ -0,0 +1,4 @@ +/** + * this folder for utils + */ +export * from './url'; diff --git a/app-v2/utils/interface.ts b/app-v2/utils/interface.ts new file mode 100644 index 00000000..1f1607e9 --- /dev/null +++ b/app-v2/utils/interface.ts @@ -0,0 +1,17 @@ +import * as d3 from 'd3'; +export interface INode extends d3.SimulationNodeDatum { + name: string; + group: number; + uuid: string; + color: string; + icon: string; +} + +export interface IPath extends d3.SimulationLinkDatum { + id: string; + source: INode; + target: INode; + size: number; + type: string; + uuid: string; +} diff --git a/app-v2/utils/parseData.ts b/app-v2/utils/parseData.ts new file mode 100644 index 00000000..6f2a43db --- /dev/null +++ b/app-v2/utils/parseData.ts @@ -0,0 +1,196 @@ +import _ from 'lodash'; + +import { handleVidStringName } from '#app/utils/function'; + +export function setLinkNumbers(group, type) { + const len = group.length; + if (len === 0) { + return; + } + const linksA: any = []; + const linksB: any = []; + for (let i = 0; i < len; i++) { + const link = group[i]; + if (link.source.name) { + if (link.source.name < link.target.name) { + linksA.push(link); + } else { + linksB.push(link); + } + } else { + if (link.source < link.target) { + linksA.push(link); + } else { + linksB.push(link); + } + } + } + let maxLinkNumber = 0; + if (type === 'self') { + maxLinkNumber = len; + } else { + maxLinkNumber = len % 2 === 0 ? len / 2 : (len + 1) / 2; + } + const linksALen = linksA.length; + const linksBLen = linksB.length; + if (linksALen === linksBLen) { + let startLinkNumber = 1; + for (let i = 0; i < linksALen; i++) { + linksA[i].linknum = startLinkNumber++; + } + startLinkNumber = 1; + for (let i = 0; i < linksBLen; i++) { + linksB[i].linknum = startLinkNumber++; + } + } else { + let biggerLinks: any[] = []; + let smallerLinks: any[] = []; + if (linksA.length > linksB.length) { + biggerLinks = linksA; + smallerLinks = linksB; + } else { + biggerLinks = linksB; + smallerLinks = linksA; + } + let startLinkNumber = maxLinkNumber; + const smallerLinksLen = smallerLinks.length; + for (let i = 0; i < smallerLinksLen; i++) { + smallerLinks[i].linknum = startLinkNumber--; + } + const tmpNumber = startLinkNumber; + startLinkNumber = 1; + let p = 0; + while (startLinkNumber <= maxLinkNumber) { + biggerLinks[p++].linknum = startLinkNumber++; + } + startLinkNumber = 0 - tmpNumber; + for (let i = p; i < biggerLinks.length; i++) { + biggerLinks[i].linknum = startLinkNumber++; + } + } +} + +export function setLinkName(link) { + if (link.source.name) { + return link.source.name < link.target.name + ? link.source.name + ':' + link.target.name + : link.target.name + ':' + link.source.name; + } else { + return link.source < link.target + ? link.source + ':' + link.target + : link.target + ':' + link.source; + } +} + +export function setLink(edges) { + const linkGroup = {}; + // statistical linkMap linkGroup + edges.forEach((link: any) => { + const key = setLinkName(link); + if (!linkGroup.hasOwnProperty(key)) { + linkGroup[key] = []; + } + linkGroup[key].push(link); + }); + // assign linknum to each link + edges.forEach((link: any) => { + const key = setLinkName(link); + link.size = linkGroup[key].length; + const group = linkGroup[key]; + const keyPair = key.split(':'); + let type = 'noself'; + if (keyPair[0] === keyPair[1]) { + type = 'self'; + } + if (group[group.length - 1] === link) { + setLinkNumbers(group, type); + } + }); +} + +export function parsePathToGraph(data, spaceVidType) { + const vertexes: any = []; + const edges: any = []; + const relationships = data + .map(i => i._pathsParsedList) + .flat() + .map(i => i.relationships) + .flat(); + relationships.forEach(relationship => { + const { + srcID: srcId, + dstID: dstId, + edgeName: edgeType, + rank, + } = relationship; + vertexes.push(srcId); + vertexes.push(dstId); + edges.push({ + srcId, + dstId, + edgeType, + rank, + id: `${edgeType} ${handleVidStringName( + srcId, + spaceVidType, + )}->${handleVidStringName(dstId, spaceVidType)}@${rank}}`, + }); + }); + return { vertexes, edges }; +} + +export function parseSubGraph(data, spaceVidType) { + const vertexes: any = []; + const edges: any = []; + data.forEach(row => { + const { _verticesParsedList, _edgesParsedList, _pathsParsedList } = row; + if (_verticesParsedList) { + _verticesParsedList.forEach(vertex => { + vertexes.push(vertex.vid); + }); + } + if (_edgesParsedList) { + _edgesParsedList.forEach(edge => { + const { dstID: dstId, srcID: srcId, rank, edgeName: edgeType } = edge; + edges.push({ + srcId, + dstId, + edgeType, + rank, + id: `${edgeType} ${handleVidStringName( + srcId, + spaceVidType, + )}->${handleVidStringName(dstId, spaceVidType)}@${rank}}`, + }); + vertexes.push(srcId); + vertexes.push(dstId); + }); + } + if (_pathsParsedList) { + _pathsParsedList.forEach(path => { + const relationships = path.relationships; + relationships.forEach(relationship => { + const { + srcID: srcId, + dstID: dstId, + edgeName: edgeType, + rank, + } = relationship; + vertexes.push(srcId); + vertexes.push(dstId); + edges.push({ + srcId, + dstId, + edgeType, + rank, + id: `${edgeType} ${handleVidStringName( + srcId, + spaceVidType, + )}->${handleVidStringName(dstId, spaceVidType)}@${rank}}`, + }); + }); + }); + } + }); + return { vertexes, edges }; +} diff --git a/app-v2/utils/stat.ts b/app-v2/utils/stat.ts new file mode 100644 index 00000000..38215b88 --- /dev/null +++ b/app-v2/utils/stat.ts @@ -0,0 +1,79 @@ +const win = window as any; +export const trackPageView = (url: string) => { + if (win._hmt) { + try { + win._hmt.push(['_trackPageview', url]); + } catch (e) { + console.log(e); + } + } + + if (win.gtag) { + try { + win.gtag('event', 'screen_view', { + screen_name: url, + app_name: 'nebula-graph-stutio', + }); + } catch (e) { + console.log(e); + } + } +}; + +interface IGtag { + event_category: string; + event_label?: string; + value?: number; +} + +export const trackEvent = ( + category: string, + action: string, + label?: string, + value?: number, +) => { + // google analytics + if (win.gtag) { + try { + const params: IGtag = { + event_category: category, + }; + if (label) { + params.event_label = label; + } + if (value) { + params.value = value; + } + win.gtag('event', action, params); + } catch (e) { + console.log(e); + } + } +}; + +export const handleTrackEvent = event => { + let target; + if (event.target && event.target.dataset.trackCategory) { + target = event.target; + } else { + const _parentNode = event.target.parentNode; + if (_parentNode && _parentNode.dataset.trackCategory) { + target = _parentNode; + } else if ( + _parentNode.tagName.toLowerCase() === 'svg' && + _parentNode.parentNode.tagName.toLowerCase() === 'i' && + _parentNode.parentNode.dataset.trackCategory + ) { + target = _parentNode.parentNode; + } + } + if (target) { + const { + trackCategory, + trackAction, + trackLabel, + trackValue, + } = target.dataset; + trackEvent(trackCategory, trackAction, trackLabel, trackValue); + } +}; diff --git a/app-v2/utils/url.ts b/app-v2/utils/url.ts new file mode 100644 index 00000000..b4d3f175 --- /dev/null +++ b/app-v2/utils/url.ts @@ -0,0 +1,9 @@ +export function updateQueryStringParameter(uri, key, value) { + const re = new RegExp('([?&])' + key + '=.*?(&|$)', 'i'); + const separator = uri.indexOf('?') !== -1 ? '&' : '?'; + if (uri.match(re)) { + return uri.replace(re, '$1' + key + '=' + value + '$2'); + } else { + return uri + separator + key + '=' + value; + } +} diff --git a/app/App.less b/app/App.less index bb7b2a96..71fe90c6 100644 --- a/app/App.less +++ b/app/App.less @@ -137,7 +137,7 @@ p { top: 0; border-bottom: none; - .nebula-cloud-icon { + .nebula-studio-icon { margin-right: 10px; } diff --git a/app/components/Icon/index.tsx b/app/components/Icon/index.tsx index 3556a408..51ed40a6 100644 --- a/app/components/Icon/index.tsx +++ b/app/components/Icon/index.tsx @@ -7,7 +7,7 @@ interface IIconFontProps extends HTMLProps { const IconFont = (props: IIconFontProps) => { const { type, className, ...others } = props; return ( - + ); }; diff --git a/app/components/IconPicker/index.less b/app/components/IconPicker/index.less index 4022752b..9bf21553 100644 --- a/app/components/IconPicker/index.less +++ b/app/components/IconPicker/index.less @@ -9,7 +9,7 @@ margin: 5px; color: #000; - .nebula-cloud-icon { + .nebula-studio-icon { font-size: 30px; display: inline-block; margin-top: -6px; diff --git a/app/components/NebulaD3/index.tsx b/app/components/NebulaD3/index.tsx index edc1d25f..12c74db4 100644 --- a/app/components/NebulaD3/index.tsx +++ b/app/components/NebulaD3/index.tsx @@ -336,7 +336,7 @@ class NebulaD3 extends React.Component { .attr('dominant-baseline', 'central') .attr('stroke', 'black') .attr('stroke-width', '0.00001%') - .attr('font-family', 'nebula-cloud-icon') + .attr('font-family', 'nebula-studio-icon') .attr('x', (d: any) => d.x) .attr('y', (d: any) => d.y) .attr('id', (d: any) => d.uuid) diff --git a/app/modules/Explore/NebulaGraph/Menu/index.less b/app/modules/Explore/NebulaGraph/Menu/index.less index d32578e6..1c15ee36 100644 --- a/app/modules/Explore/NebulaGraph/Menu/index.less +++ b/app/modules/Explore/NebulaGraph/Menu/index.less @@ -21,7 +21,7 @@ left: 12px; } - .nebula-cloud-icon { + .nebula-studio-icon { width: auto; margin-left: 2px; font-size: 20px; @@ -33,7 +33,7 @@ margin-left: 3px; } - .nebula-cloud-icon { + .nebula-studio-icon { margin-left: 0; font-size: 16px; } diff --git a/app/modules/Explore/NebulaGraph/Panel/index.less b/app/modules/Explore/NebulaGraph/Panel/index.less index abbb70e5..3abb2f9d 100644 --- a/app/modules/Explore/NebulaGraph/Panel/index.less +++ b/app/modules/Explore/NebulaGraph/Panel/index.less @@ -21,7 +21,7 @@ display: inline-flex; align-items: center; - > .nebula-cloud-icon { + > .nebula-studio-icon { font-size: 25px; } diff --git a/app/modules/Import/Tasks/Import.tsx b/app/modules/Import/Tasks/Import.tsx index dcca3dfa..defe7a1d 100644 --- a/app/modules/Import/Tasks/Import.tsx +++ b/app/modules/Import/Tasks/Import.tsx @@ -147,7 +147,7 @@ class Import extends React.Component { } }; - checkTaskStatus = async () => { + checkTaskStatus = async() => { const { taskId } = this.props; const { code, data, message: errMsg } = await this.props.checkImportStatus({ taskID: taskId, @@ -176,7 +176,7 @@ class Import extends React.Component { } }; - checkLogFinished = async () => { + checkLogFinished = async() => { const { update } = this.props; const empty = await this.readlog(); if (empty) { diff --git a/app/static/fonts/iconfont.css b/app/static/fonts/iconfont.css index 862ffbca..2942796c 100644 --- a/app/static/fonts/iconfont.css +++ b/app/static/fonts/iconfont.css @@ -1,12 +1,12 @@ @font-face { - font-family: "nebula-cloud-icon"; /* Project id 1846875 */ + font-family: "nebula-studio-icon"; /* Project id 1846875 */ src: url('iconfont.woff2?t=1631006849129') format('woff2'), url('iconfont.woff?t=1631006849129') format('woff'), url('iconfont.ttf?t=1631006849129') format('truetype'); } -.nebula-cloud-icon { - font-family: "nebula-cloud-icon" !important; +.nebula-studio-icon { + font-family: "nebula-studio-icon" !important; font-size: 16px; font-style: normal; -webkit-font-smoothing: antialiased; diff --git a/config/webpack.base.ts b/config/webpack.base.ts index ec6a56bb..b489e9ea 100644 --- a/config/webpack.base.ts +++ b/config/webpack.base.ts @@ -39,6 +39,10 @@ const commonConfig: Configuration = { options: { lessOptions: { javascriptEnabled: true, + modifyVars: { + 'menu-dark-bg': '#2F3A4A', + 'primary-color': '#2F80ED', + } } }, }, @@ -91,7 +95,7 @@ const commonConfig: Configuration = { new HtmlWebpackPlugin({ filename: 'index.html', - template: path.join(__dirname, '../app/index.html'), + template: path.join(__dirname, '../app-v2/index.html'), favicon: resolve(__dirname, '../favicon.ico'), minify: { collapseWhitespace: true, @@ -108,6 +112,7 @@ const commonConfig: Configuration = { extensions: ['.ts', '.tsx', '.js', '.jsx', '.json', '.css', '.woff', '.woff2', 'ttf'], alias: { '#app': path.join(__dirname, '../app/'), + '@appv2': path.join(__dirname, '../app-v2/'), 'react-dom': '@hot-loader/react-dom', }, }, diff --git a/config/webpack.dev2.ts b/config/webpack.dev2.ts new file mode 100644 index 00000000..682bbb2b --- /dev/null +++ b/config/webpack.dev2.ts @@ -0,0 +1,71 @@ +import path from 'path'; +import { mergeWithCustomize } from 'webpack-merge'; +import commonConfig from './webpack.base'; + +const devConfig = { + devtool: 'inline-source-map', + entry: { + app: [ + 'react-hot-loader/patch', + path.join(__dirname, '../app-v2/index.tsx'), + ], + }, + + output: { + filename: '[name].js', + publicPath: 'http://127.0.0.1:7002/', + }, + + module: { + rules: [ + { + test: /\.css$/, + use: ['style-loader', 'css-loader'], + }, + ], + }, + + devServer: { + port: 7002, + headers: { 'Access-Control-Allow-Origin': '*' }, + historyApiFallback: true, + host: 'localhost', + allowedHosts: 'all', + hot: true, + client: { + overlay: { + errors: true, + warnings: false, + } + }, + static: { + staticOptions: { + directory: path.resolve(__dirname, '../dist'), + publicPath: '/', + // redirect: true, + serveIndex: true, + }, + }, + proxy: [ + { + path: '/api-nebula/**', + target: 'http://127.0.0.1:9000', + changeOrigin: true, + }, + { + path: '/api/**', + target: 'http://127.0.0.1:9000', + changeOrigin: true, + }, + ] + }, +}; + +module.exports = mergeWithCustomize({ + customizeArray(_, b, key) { + if (key === 'entry.app') { + return b; + } + return undefined; + }, +})(commonConfig, devConfig); diff --git a/package-lock.json b/package-lock.json index de5e1d12..998c587c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -694,7 +694,7 @@ }, "@babel/plugin-syntax-optional-chaining": { "version": "7.8.3", - "resolved": "https://registry.nlark.com/@babel/plugin-syntax-optional-chaining/download/@babel/plugin-syntax-optional-chaining-7.8.3.tgz", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-syntax-optional-chaining/download/@babel/plugin-syntax-optional-chaining-7.8.3.tgz", "integrity": "sha1-T2nCq5UWfgGAzVM2YT+MV4j31Io=", "dev": true, "requires": { @@ -1901,13 +1901,13 @@ }, "@types/cookiejar": { "version": "2.1.2", - "resolved": "https://registry.npmmirror.com/@types/cookiejar/download/@types/cookiejar-2.1.2.tgz", + "resolved": "https://registry.nlark.com/@types/cookiejar/download/@types/cookiejar-2.1.2.tgz?cache=0&sync_timestamp=1621240742756&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40types%2Fcookiejar%2Fdownload%2F%40types%2Fcookiejar-2.1.2.tgz", "integrity": "sha1-Zq2TMfY/6KPT2djG45Bt0Q9kRug=", "dev": true }, "@types/d3": { "version": "5.16.4", - "resolved": "https://registry.npmmirror.com/@types/d3/download/@types/d3-5.16.4.tgz", + "resolved": "https://registry.nlark.com/@types/d3/download/@types/d3-5.16.4.tgz", "integrity": "sha1-p9wko9wcGZIu7nK6FhRP1bzqmHo=", "dev": true, "requires": { @@ -1946,13 +1946,13 @@ }, "@types/d3-array": { "version": "1.2.9", - "resolved": "https://registry.npmmirror.com/@types/d3-array/download/@types/d3-array-1.2.9.tgz?cache=0&sync_timestamp=1637265725836&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40types%2Fd3-array%2Fdownload%2F%40types%2Fd3-array-1.2.9.tgz", + "resolved": "https://registry.nlark.com/@types/d3-array/download/@types/d3-array-1.2.9.tgz", "integrity": "sha1-x9x4mSzYylyFAkOiZf0lfqVt8fo=", "dev": true }, "@types/d3-axis": { "version": "1.0.16", - "resolved": "https://registry.npmmirror.com/@types/d3-axis/download/@types/d3-axis-1.0.16.tgz?cache=0&sync_timestamp=1637265774338&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40types%2Fd3-axis%2Fdownload%2F%40types%2Fd3-axis-1.0.16.tgz", + "resolved": "https://registry.nlark.com/@types/d3-axis/download/@types/d3-axis-1.0.16.tgz", "integrity": "sha1-k9eih5XC+LDi/VUPzE0pt/F05pM=", "dev": true, "requires": { @@ -1961,7 +1961,7 @@ }, "@types/d3-brush": { "version": "1.1.5", - "resolved": "https://registry.npmmirror.com/@types/d3-brush/download/@types/d3-brush-1.1.5.tgz", + "resolved": "https://registry.nlark.com/@types/d3-brush/download/@types/d3-brush-1.1.5.tgz", "integrity": "sha1-x8+1jey/1TrT5H8DdjReNkCmgYY=", "dev": true, "requires": { @@ -1970,25 +1970,25 @@ }, "@types/d3-chord": { "version": "1.0.11", - "resolved": "https://registry.npmmirror.com/@types/d3-chord/download/@types/d3-chord-1.0.11.tgz", + "resolved": "https://registry.nlark.com/@types/d3-chord/download/@types/d3-chord-1.0.11.tgz", "integrity": "sha1-V2B2XbGxpLk2wNk1WoId3p3SXaI=", "dev": true }, "@types/d3-collection": { "version": "1.0.10", - "resolved": "https://registry.npmmirror.com/@types/d3-collection/download/@types/d3-collection-1.0.10.tgz?cache=0&sync_timestamp=1637265772796&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40types%2Fd3-collection%2Fdownload%2F%40types%2Fd3-collection-1.0.10.tgz", + "resolved": "https://registry.nlark.com/@types/d3-collection/download/@types/d3-collection-1.0.10.tgz", "integrity": "sha1-vKFh4zYVaWjyZ8B39/K/qP8iTlg=", "dev": true }, "@types/d3-color": { "version": "1.4.2", - "resolved": "https://registry.npmmirror.com/@types/d3-color/download/@types/d3-color-1.4.2.tgz?cache=0&sync_timestamp=1637265773098&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40types%2Fd3-color%2Fdownload%2F%40types%2Fd3-color-1.4.2.tgz", + "resolved": "https://registry.nlark.com/@types/d3-color/download/@types/d3-color-1.4.2.tgz", "integrity": "sha1-lE8oHQSg8G4TTqlq27aDA1FbJ4Q=", "dev": true }, "@types/d3-contour": { "version": "1.3.3", - "resolved": "https://registry.npmmirror.com/@types/d3-contour/download/@types/d3-contour-1.3.3.tgz?cache=0&sync_timestamp=1637265773552&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40types%2Fd3-contour%2Fdownload%2F%40types%2Fd3-contour-1.3.3.tgz", + "resolved": "https://registry.nlark.com/@types/d3-contour/download/@types/d3-contour-1.3.3.tgz", "integrity": "sha1-RFKdSYu8HbeLGV114cm7iJ7dZHo=", "dev": true, "requires": { @@ -1998,13 +1998,13 @@ }, "@types/d3-dispatch": { "version": "1.0.9", - "resolved": "https://registry.npmmirror.com/@types/d3-dispatch/download/@types/d3-dispatch-1.0.9.tgz?cache=0&sync_timestamp=1637265775034&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40types%2Fd3-dispatch%2Fdownload%2F%40types%2Fd3-dispatch-1.0.9.tgz", + "resolved": "https://registry.nlark.com/@types/d3-dispatch/download/@types/d3-dispatch-1.0.9.tgz?cache=0&sync_timestamp=1625155658592&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40types%2Fd3-dispatch%2Fdownload%2F%40types%2Fd3-dispatch-1.0.9.tgz", "integrity": "sha1-xaGA8eJR3oU7OZz7+7bdf4v4Qq4=", "dev": true }, "@types/d3-drag": { "version": "1.2.5", - "resolved": "https://registry.npmmirror.com/@types/d3-drag/download/@types/d3-drag-1.2.5.tgz?cache=0&sync_timestamp=1637265774572&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40types%2Fd3-drag%2Fdownload%2F%40types%2Fd3-drag-1.2.5.tgz", + "resolved": "https://registry.nlark.com/@types/d3-drag/download/@types/d3-drag-1.2.5.tgz", "integrity": "sha1-CxuFLLQVdwdapiWuYUk3nqbDTf0=", "dev": true, "requires": { @@ -2013,7 +2013,7 @@ }, "@types/d3-dsv": { "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/@types/d3-dsv/download/@types/d3-dsv-1.2.1.tgz?cache=0&sync_timestamp=1637265775366&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40types%2Fd3-dsv%2Fdownload%2F%40types%2Fd3-dsv-1.2.1.tgz", + "resolved": "https://registry.nlark.com/@types/d3-dsv/download/@types/d3-dsv-1.2.1.tgz?cache=0&sync_timestamp=1624464478121&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40types%2Fd3-dsv%2Fdownload%2F%40types%2Fd3-dsv-1.2.1.tgz", "integrity": "sha1-FST+6fGdaJwvdqoOJOIwdiv5aZQ=", "dev": true }, @@ -2025,7 +2025,7 @@ }, "@types/d3-fetch": { "version": "1.2.2", - "resolved": "https://registry.npmmirror.com/@types/d3-fetch/download/@types/d3-fetch-1.2.2.tgz?cache=0&sync_timestamp=1637265775840&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40types%2Fd3-fetch%2Fdownload%2F%40types%2Fd3-fetch-1.2.2.tgz", + "resolved": "https://registry.nlark.com/@types/d3-fetch/download/@types/d3-fetch-1.2.2.tgz", "integrity": "sha1-uTv+JIuLdhr4L02sV5WcmJ9n2j4=", "dev": true, "requires": { @@ -2034,19 +2034,19 @@ }, "@types/d3-force": { "version": "1.2.4", - "resolved": "https://registry.npmmirror.com/@types/d3-force/download/@types/d3-force-1.2.4.tgz?cache=0&sync_timestamp=1637265776111&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40types%2Fd3-force%2Fdownload%2F%40types%2Fd3-force-1.2.4.tgz", + "resolved": "https://registry.nlark.com/@types/d3-force/download/@types/d3-force-1.2.4.tgz", "integrity": "sha1-bidMciiMLbCPvbj1uHuaqD5Vqeg=", "dev": true }, "@types/d3-format": { "version": "1.4.2", - "resolved": "https://registry.npmmirror.com/@types/d3-format/download/@types/d3-format-1.4.2.tgz?cache=0&sync_timestamp=1637265774811&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40types%2Fd3-format%2Fdownload%2F%40types%2Fd3-format-1.4.2.tgz", + "resolved": "https://registry.nlark.com/@types/d3-format/download/@types/d3-format-1.4.2.tgz", "integrity": "sha1-6he/VZtx2a/Vaa6b/kxUTauGO6o=", "dev": true }, "@types/d3-geo": { "version": "1.12.3", - "resolved": "https://registry.npmmirror.com/@types/d3-geo/download/@types/d3-geo-1.12.3.tgz", + "resolved": "https://registry.nlark.com/@types/d3-geo/download/@types/d3-geo-1.12.3.tgz", "integrity": "sha1-US6+c1yxzfX4etWWCEFuLp6GjFo=", "dev": true, "requires": { @@ -2055,13 +2055,13 @@ }, "@types/d3-hierarchy": { "version": "1.1.8", - "resolved": "https://registry.npmmirror.com/@types/d3-hierarchy/download/@types/d3-hierarchy-1.1.8.tgz", + "resolved": "https://registry.nlark.com/@types/d3-hierarchy/download/@types/d3-hierarchy-1.1.8.tgz", "integrity": "sha1-UGV/Qg1WWgbAuVCkuC7uCjafLeo=", "dev": true }, "@types/d3-interpolate": { "version": "1.4.2", - "resolved": "https://registry.npmmirror.com/@types/d3-interpolate/download/@types/d3-interpolate-1.4.2.tgz?cache=0&sync_timestamp=1637265775596&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40types%2Fd3-interpolate%2Fdownload%2F%40types%2Fd3-interpolate-1.4.2.tgz", + "resolved": "https://registry.nlark.com/@types/d3-interpolate/download/@types/d3-interpolate-1.4.2.tgz?cache=0&sync_timestamp=1625148186412&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40types%2Fd3-interpolate%2Fdownload%2F%40types%2Fd3-interpolate-1.4.2.tgz", "integrity": "sha1-iJAqIF9oJ3OlF2EimaRGmShe7Xs=", "dev": true, "requires": { @@ -2070,31 +2070,31 @@ }, "@types/d3-path": { "version": "1.0.9", - "resolved": "https://registry.npmmirror.com/@types/d3-path/download/@types/d3-path-1.0.9.tgz", + "resolved": "https://registry.nlark.com/@types/d3-path/download/@types/d3-path-1.0.9.tgz", "integrity": "sha1-c1JrFQ0UzZbnAVl8vzRs/R/UpYw=", "dev": true }, "@types/d3-polygon": { "version": "1.0.8", - "resolved": "https://registry.npmmirror.com/@types/d3-polygon/download/@types/d3-polygon-1.0.8.tgz", + "resolved": "https://registry.nlark.com/@types/d3-polygon/download/@types/d3-polygon-1.0.8.tgz", "integrity": "sha1-En7oP8zaW/VzhAEdqQ8xNn/qFTA=", "dev": true }, "@types/d3-quadtree": { "version": "1.0.9", - "resolved": "https://registry.npmmirror.com/@types/d3-quadtree/download/@types/d3-quadtree-1.0.9.tgz?cache=0&sync_timestamp=1637265777561&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40types%2Fd3-quadtree%2Fdownload%2F%40types%2Fd3-quadtree-1.0.9.tgz", + "resolved": "https://registry.nlark.com/@types/d3-quadtree/download/@types/d3-quadtree-1.0.9.tgz", "integrity": "sha1-x8O3lbWvBuWwQ9HTTnVKQ0s7rlk=", "dev": true }, "@types/d3-random": { "version": "1.1.3", - "resolved": "https://registry.npmmirror.com/@types/d3-random/download/@types/d3-random-1.1.3.tgz?cache=0&sync_timestamp=1637265776335&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40types%2Fd3-random%2Fdownload%2F%40types%2Fd3-random-1.1.3.tgz", + "resolved": "https://registry.nlark.com/@types/d3-random/download/@types/d3-random-1.1.3.tgz?cache=0&sync_timestamp=1625128372714&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40types%2Fd3-random%2Fdownload%2F%40types%2Fd3-random-1.1.3.tgz", "integrity": "sha1-j3/cI/ktFWHgaU60lWfoq1BTehk=", "dev": true }, "@types/d3-scale": { "version": "2.2.6", - "resolved": "https://registry.npmmirror.com/@types/d3-scale/download/@types/d3-scale-2.2.6.tgz?cache=0&sync_timestamp=1637265729398&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40types%2Fd3-scale%2Fdownload%2F%40types%2Fd3-scale-2.2.6.tgz", + "resolved": "https://registry.nlark.com/@types/d3-scale/download/@types/d3-scale-2.2.6.tgz?cache=0&sync_timestamp=1625148814895&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40types%2Fd3-scale%2Fdownload%2F%40types%2Fd3-scale-2.2.6.tgz", "integrity": "sha1-KFQLTfyZ2XiXDoc+QTimvqLqarg=", "dev": true, "requires": { @@ -2103,19 +2103,19 @@ }, "@types/d3-scale-chromatic": { "version": "1.5.1", - "resolved": "https://registry.npmmirror.com/@types/d3-scale-chromatic/download/@types/d3-scale-chromatic-1.5.1.tgz?cache=0&sync_timestamp=1637265776809&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40types%2Fd3-scale-chromatic%2Fdownload%2F%40types%2Fd3-scale-chromatic-1.5.1.tgz", + "resolved": "https://registry.nlark.com/@types/d3-scale-chromatic/download/@types/d3-scale-chromatic-1.5.1.tgz?cache=0&sync_timestamp=1624707716067&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40types%2Fd3-scale-chromatic%2Fdownload%2F%40types%2Fd3-scale-chromatic-1.5.1.tgz", "integrity": "sha1-4rfDQB5cE4CfgxkR64IORE9Pxno=", "dev": true }, "@types/d3-selection": { "version": "1.4.3", - "resolved": "https://registry.npmmirror.com/@types/d3-selection/download/@types/d3-selection-1.4.3.tgz", + "resolved": "https://registry.nlark.com/@types/d3-selection/download/@types/d3-selection-1.4.3.tgz?cache=0&sync_timestamp=1628111026484&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40types%2Fd3-selection%2Fdownload%2F%40types%2Fd3-selection-1.4.3.tgz", "integrity": "sha1-NpKLvmTrjgu8uqAfsFwh/2xx+pM=", "dev": true }, "@types/d3-shape": { "version": "1.3.8", - "resolved": "https://registry.npmmirror.com/@types/d3-shape/download/@types/d3-shape-1.3.8.tgz?cache=0&sync_timestamp=1637265778061&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40types%2Fd3-shape%2Fdownload%2F%40types%2Fd3-shape-1.3.8.tgz", + "resolved": "https://registry.nlark.com/@types/d3-shape/download/@types/d3-shape-1.3.8.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40types%2Fd3-shape%2Fdownload%2F%40types%2Fd3-shape-1.3.8.tgz", "integrity": "sha1-w8Fex0NrTOJOON5RdYaFDx/qjok=", "dev": true, "requires": { @@ -2124,25 +2124,25 @@ }, "@types/d3-time": { "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/@types/d3-time/download/@types/d3-time-1.1.1.tgz?cache=0&sync_timestamp=1637265777314&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40types%2Fd3-time%2Fdownload%2F%40types%2Fd3-time-1.1.1.tgz", + "resolved": "https://registry.nlark.com/@types/d3-time/download/@types/d3-time-1.1.1.tgz", "integrity": "sha1-bPOkJCw7usAEQN+4uniE8Wvt/L8=", "dev": true }, "@types/d3-time-format": { "version": "2.3.1", - "resolved": "https://registry.npmmirror.com/@types/d3-time-format/download/@types/d3-time-format-2.3.1.tgz", + "resolved": "https://registry.nlark.com/@types/d3-time-format/download/@types/d3-time-format-2.3.1.tgz", "integrity": "sha1-h6MORRO50dU7kgMno2H4clW/M3I=", "dev": true }, "@types/d3-timer": { "version": "1.0.10", - "resolved": "https://registry.npmmirror.com/@types/d3-timer/download/@types/d3-timer-1.0.10.tgz", + "resolved": "https://registry.nlark.com/@types/d3-timer/download/@types/d3-timer-1.0.10.tgz", "integrity": "sha1-MpxRwskx9E7QrP94uMhFcazw7SE=", "dev": true }, "@types/d3-transition": { "version": "1.3.2", - "resolved": "https://registry.npmmirror.com/@types/d3-transition/download/@types/d3-transition-1.3.2.tgz?cache=0&sync_timestamp=1637265777806&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40types%2Fd3-transition%2Fdownload%2F%40types%2Fd3-transition-1.3.2.tgz", + "resolved": "https://registry.nlark.com/@types/d3-transition/download/@types/d3-transition-1.3.2.tgz?cache=0&sync_timestamp=1625159655594&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40types%2Fd3-transition%2Fdownload%2F%40types%2Fd3-transition-1.3.2.tgz", "integrity": "sha1-7Vm+yntNZ5z6UviKalDlu+t+Cjw=", "dev": true, "requires": { @@ -2151,13 +2151,13 @@ }, "@types/d3-voronoi": { "version": "1.1.9", - "resolved": "https://registry.npmmirror.com/@types/d3-voronoi/download/@types/d3-voronoi-1.1.9.tgz?cache=0&sync_timestamp=1637265778798&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40types%2Fd3-voronoi%2Fdownload%2F%40types%2Fd3-voronoi-1.1.9.tgz", + "resolved": "https://registry.nlark.com/@types/d3-voronoi/download/@types/d3-voronoi-1.1.9.tgz", "integrity": "sha1-e7whCBijpcXguvsFFCDfIGYXyeU=", "dev": true }, "@types/d3-zoom": { "version": "1.8.3", - "resolved": "https://registry.npmmirror.com/@types/d3-zoom/download/@types/d3-zoom-1.8.3.tgz?cache=0&sync_timestamp=1637265779271&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40types%2Fd3-zoom%2Fdownload%2F%40types%2Fd3-zoom-1.8.3.tgz", + "resolved": "https://registry.nlark.com/@types/d3-zoom/download/@types/d3-zoom-1.8.3.tgz?cache=0&sync_timestamp=1625159655716&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40types%2Fd3-zoom%2Fdownload%2F%40types%2Fd3-zoom-1.8.3.tgz", "integrity": "sha1-ACN5AMb9wrtP6CZ57k1064++ezw=", "dev": true, "requires": { @@ -2216,7 +2216,7 @@ }, "@types/geojson": { "version": "7946.0.8", - "resolved": "https://registry.npmmirror.com/@types/geojson/download/@types/geojson-7946.0.8.tgz", + "resolved": "https://registry.nlark.com/@types/geojson/download/@types/geojson-7946.0.8.tgz", "integrity": "sha1-MHRK/bOF4pReIvOwM/iX92sfEso=", "dev": true }, @@ -2263,7 +2263,7 @@ }, "@types/json-schema": { "version": "7.0.9", - "resolved": "https://registry.npmmirror.com/@types/json-schema/download/@types/json-schema-7.0.9.tgz?cache=0&sync_timestamp=1637266073261&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40types%2Fjson-schema%2Fdownload%2F%40types%2Fjson-schema-7.0.9.tgz", + "resolved": "https://registry.nlark.com/@types/json-schema/download/@types/json-schema-7.0.9.tgz", "integrity": "sha1-l+3JA36gw4WFMgsolk3eOznkZg0=", "dev": true }, @@ -2296,13 +2296,13 @@ }, "@types/minimatch": { "version": "3.0.5", - "resolved": "https://registry.npmmirror.com/@types/minimatch/download/@types/minimatch-3.0.5.tgz?cache=0&sync_timestamp=1637267454587&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40types%2Fminimatch%2Fdownload%2F%40types%2Fminimatch-3.0.5.tgz", + "resolved": "https://registry.nlark.com/@types/minimatch/download/@types/minimatch-3.0.5.tgz", "integrity": "sha1-EAHMXmo3BLg8I2An538vWOoBD0A=", "dev": true }, "@types/minimist": { "version": "1.2.2", - "resolved": "https://registry.npmmirror.com/@types/minimist/download/@types/minimist-1.2.2.tgz?cache=0&sync_timestamp=1637267454418&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40types%2Fminimist%2Fdownload%2F%40types%2Fminimist-1.2.2.tgz", + "resolved": "https://registry.nlark.com/@types/minimist/download/@types/minimist-1.2.2.tgz?cache=0&sync_timestamp=1625616230224&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40types%2Fminimist%2Fdownload%2F%40types%2Fminimist-1.2.2.tgz", "integrity": "sha1-7nceK6Sz3Fs3KTXVSf2WF780W4w=", "dev": true }, @@ -2314,7 +2314,7 @@ }, "@types/normalize-package-data": { "version": "2.4.1", - "resolved": "https://registry.npmmirror.com/@types/normalize-package-data/download/@types/normalize-package-data-2.4.1.tgz?cache=0&sync_timestamp=1637268952162&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40types%2Fnormalize-package-data%2Fdownload%2F%40types%2Fnormalize-package-data-2.4.1.tgz", + "resolved": "https://registry.nlark.com/@types/normalize-package-data/download/@types/normalize-package-data-2.4.1.tgz", "integrity": "sha1-0zV0eaD9/dWQf+Z+F+CoXJBuEwE=", "dev": true }, @@ -2326,7 +2326,7 @@ }, "@types/prop-types": { "version": "15.7.4", - "resolved": "https://registry.npmmirror.com/@types/prop-types/download/@types/prop-types-15.7.4.tgz", + "resolved": "https://registry.nlark.com/@types/prop-types/download/@types/prop-types-15.7.4.tgz", "integrity": "sha1-/PcgXCXf95Xuea8eMNosl5CAjxE=", "dev": true }, @@ -2394,7 +2394,7 @@ }, "@types/scheduler": { "version": "0.16.2", - "resolved": "https://registry.npmmirror.com/@types/scheduler/download/@types/scheduler-0.16.2.tgz", + "resolved": "https://registry.nlark.com/@types/scheduler/download/@types/scheduler-0.16.2.tgz?cache=0&sync_timestamp=1625590236866&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40types%2Fscheduler%2Fdownload%2F%40types%2Fscheduler-0.16.2.tgz", "integrity": "sha1-GmL4lSVyPd4kuhsBsJK/XfitTTk=", "dev": true }, @@ -2438,7 +2438,7 @@ }, "@types/supertest": { "version": "2.0.11", - "resolved": "https://registry.npmmirror.com/@types/supertest/download/@types/supertest-2.0.11.tgz", + "resolved": "https://registry.nlark.com/@types/supertest/download/@types/supertest-2.0.11.tgz?cache=0&sync_timestamp=1621243778166&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40types%2Fsupertest%2Fdownload%2F%40types%2Fsupertest-2.0.11.tgz", "integrity": "sha1-LnD2nyILx3tPZg1ywuGkIx9Ep30=", "dev": true, "requires": { @@ -2447,7 +2447,7 @@ }, "@types/unist": { "version": "2.0.6", - "resolved": "https://registry.npmmirror.com/@types/unist/download/@types/unist-2.0.6.tgz", + "resolved": "https://registry.nlark.com/@types/unist/download/@types/unist-2.0.6.tgz", "integrity": "sha1-JQp7FsO5H2cqJFUuxkZ47rHToI0=", "dev": true }, @@ -2872,7 +2872,7 @@ }, "ajv-keywords": { "version": "3.5.2", - "resolved": "https://registry.npmmirror.com/ajv-keywords/download/ajv-keywords-3.5.2.tgz?cache=0&sync_timestamp=1637523824664&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fajv-keywords%2Fdownload%2Fajv-keywords-3.5.2.tgz", + "resolved": "https://registry.nlark.com/ajv-keywords/download/ajv-keywords-3.5.2.tgz", "integrity": "sha1-MfKdpatuANHC0yms97WSlhTVAU0=", "dev": true }, @@ -2986,7 +2986,7 @@ }, "aria-query": { "version": "4.2.2", - "resolved": "https://registry.npmmirror.com/aria-query/download/aria-query-4.2.2.tgz", + "resolved": "https://registry.npm.taobao.org/aria-query/download/aria-query-4.2.2.tgz?cache=0&sync_timestamp=1592594101553&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Faria-query%2Fdownload%2Faria-query-4.2.2.tgz", "integrity": "sha1-DSymyazrVriXfp/tau1+FbvS+Ds=", "dev": true, "requires": { @@ -3032,7 +3032,7 @@ }, "array-uniq": { "version": "1.0.3", - "resolved": "https://registry.nlark.com/array-uniq/download/array-uniq-1.0.3.tgz", + "resolved": "https://registry.nlark.com/array-uniq/download/array-uniq-1.0.3.tgz?cache=0&sync_timestamp=1620042045402&other_urls=https%3A%2F%2Fregistry.nlark.com%2Farray-uniq%2Fdownload%2Farray-uniq-1.0.3.tgz", "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", "dev": true }, @@ -3145,13 +3145,13 @@ }, "axobject-query": { "version": "2.2.0", - "resolved": "https://registry.npmmirror.com/axobject-query/download/axobject-query-2.2.0.tgz", + "resolved": "https://registry.npm.taobao.org/axobject-query/download/axobject-query-2.2.0.tgz?cache=0&sync_timestamp=1592784702868&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Faxobject-query%2Fdownload%2Faxobject-query-2.2.0.tgz", "integrity": "sha1-lD1H4QwLcEqkInXiDt83ImSJib4=", "dev": true }, "babel-helper-builder-react-jsx": { "version": "6.26.0", - "resolved": "https://registry.nlark.com/babel-helper-builder-react-jsx/download/babel-helper-builder-react-jsx-6.26.0.tgz", + "resolved": "https://registry.npm.taobao.org/babel-helper-builder-react-jsx/download/babel-helper-builder-react-jsx-6.26.0.tgz", "integrity": "sha1-Of+DE7dci2Xc7/HzHTg+D/KkCKA=", "dev": true, "requires": { @@ -3174,7 +3174,7 @@ "dependencies": { "schema-utils": { "version": "2.7.1", - "resolved": "https://registry.npmmirror.com/schema-utils/download/schema-utils-2.7.1.tgz?cache=0&sync_timestamp=1637075928327&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fschema-utils%2Fdownload%2Fschema-utils-2.7.1.tgz", + "resolved": "https://registry.nlark.com/schema-utils/download/schema-utils-2.7.1.tgz?cache=0&sync_timestamp=1626694835325&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fschema-utils%2Fdownload%2Fschema-utils-2.7.1.tgz", "integrity": "sha1-HKTzLRskxZDCA7jnpQvw6kzTlNc=", "dev": true, "requires": { @@ -3196,7 +3196,7 @@ }, "babel-plugin-import": { "version": "1.13.3", - "resolved": "https://registry.npmmirror.com/babel-plugin-import/download/babel-plugin-import-1.13.3.tgz", + "resolved": "https://registry.npm.taobao.org/babel-plugin-import/download/babel-plugin-import-1.13.3.tgz?cache=0&sync_timestamp=1606209888079&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbabel-plugin-import%2Fdownload%2Fbabel-plugin-import-1.13.3.tgz", "integrity": "sha1-nbu6fRrHK9QSkXqDDUReAJQdJtc=", "dev": true, "requires": { @@ -3359,7 +3359,7 @@ }, "bail": { "version": "1.0.5", - "resolved": "https://registry.npmmirror.com/bail/download/bail-1.0.5.tgz?cache=0&sync_timestamp=1636274632406&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fbail%2Fdownload%2Fbail-1.0.5.tgz", + "resolved": "https://registry.nlark.com/bail/download/bail-1.0.5.tgz?cache=0&sync_timestamp=1621397637411&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fbail%2Fdownload%2Fbail-1.0.5.tgz", "integrity": "sha1-tvoTNASjksvB+MS/Y/WVM1Hnp3Y=", "dev": true }, @@ -3376,7 +3376,7 @@ }, "big.js": { "version": "5.2.2", - "resolved": "https://registry.npmmirror.com/big.js/download/big.js-5.2.2.tgz", + "resolved": "https://registry.nlark.com/big.js/download/big.js-5.2.2.tgz?cache=0&sync_timestamp=1620132748267&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fbig.js%2Fdownload%2Fbig.js-5.2.2.tgz", "integrity": "sha1-ZfCvOC9Xi83HQr2cKB6cstd2gyg=" }, "bignumber.js": { @@ -3560,7 +3560,7 @@ }, "camelcase-keys": { "version": "6.2.2", - "resolved": "https://registry.npmmirror.com/camelcase-keys/download/camelcase-keys-6.2.2.tgz?cache=0&sync_timestamp=1633333078823&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fcamelcase-keys%2Fdownload%2Fcamelcase-keys-6.2.2.tgz", + "resolved": "https://registry.nlark.com/camelcase-keys/download/camelcase-keys-6.2.2.tgz?cache=0&sync_timestamp=1624609060222&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fcamelcase-keys%2Fdownload%2Fcamelcase-keys-6.2.2.tgz", "integrity": "sha1-XnVda6UaoiPsfT1S8ld4IQ+dw8A=", "dev": true, "requires": { @@ -3588,19 +3588,19 @@ }, "character-entities": { "version": "1.2.4", - "resolved": "https://registry.npmmirror.com/character-entities/download/character-entities-1.2.4.tgz?cache=0&sync_timestamp=1635868930605&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fcharacter-entities%2Fdownload%2Fcharacter-entities-1.2.4.tgz", + "resolved": "https://registry.npm.taobao.org/character-entities/download/character-entities-1.2.4.tgz", "integrity": "sha1-4Sw5Obfq9OWxXnrUxeKOHUjFsWs=", "dev": true }, "character-entities-legacy": { "version": "1.1.4", - "resolved": "https://registry.npmmirror.com/character-entities-legacy/download/character-entities-legacy-1.1.4.tgz?cache=0&sync_timestamp=1635911807340&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fcharacter-entities-legacy%2Fdownload%2Fcharacter-entities-legacy-1.1.4.tgz", + "resolved": "https://registry.npm.taobao.org/character-entities-legacy/download/character-entities-legacy-1.1.4.tgz?cache=0&sync_timestamp=1615373299115&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcharacter-entities-legacy%2Fdownload%2Fcharacter-entities-legacy-1.1.4.tgz", "integrity": "sha1-lLwYRdznClu50uzHSHJWYSk9j8E=", "dev": true }, "character-reference-invalid": { "version": "1.1.4", - "resolved": "https://registry.npmmirror.com/character-reference-invalid/download/character-reference-invalid-1.1.4.tgz?cache=0&sync_timestamp=1636446304022&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fcharacter-reference-invalid%2Fdownload%2Fcharacter-reference-invalid-1.1.4.tgz", + "resolved": "https://registry.npm.taobao.org/character-reference-invalid/download/character-reference-invalid-1.1.4.tgz", "integrity": "sha1-CDMpzaDq4nKrPbvzfpo4LBOvFWA=", "dev": true }, @@ -3701,7 +3701,7 @@ }, "color-convert": { "version": "1.9.3", - "resolved": "https://registry.npmmirror.com/color-convert/download/color-convert-1.9.3.tgz", + "resolved": "https://registry.npm.taobao.org/color-convert/download/color-convert-1.9.3.tgz", "integrity": "sha1-u3GFBpDh8TZWfeYp0tVHHe2kweg=", "dev": true, "requires": { @@ -3883,7 +3883,7 @@ }, "cookie-signature": { "version": "1.0.6", - "resolved": "https://registry.nlark.com/cookie-signature/download/cookie-signature-1.0.6.tgz", + "resolved": "https://registry.npm.taobao.org/cookie-signature/download/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", "dev": true }, @@ -4120,7 +4120,7 @@ }, "d3-array": { "version": "1.2.4", - "resolved": "https://registry.npmmirror.com/d3-array/download/d3-array-1.2.4.tgz", + "resolved": "https://registry.nlark.com/d3-array/download/d3-array-1.2.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fd3-array%2Fdownload%2Fd3-array-1.2.4.tgz", "integrity": "sha1-Y1zk1e6nWfb2BYY9vPww7cc39x8=" }, "d3-axis": { @@ -4219,7 +4219,7 @@ }, "d3-format": { "version": "1.4.5", - "resolved": "https://registry.npmmirror.com/d3-format/download/d3-format-1.4.5.tgz", + "resolved": "https://registry.nlark.com/d3-format/download/d3-format-1.4.5.tgz", "integrity": "sha1-N08roTIONxfrdKk1bGfa7hen7bQ=" }, "d3-geo": { @@ -4248,7 +4248,7 @@ }, "d3-hierarchy": { "version": "1.1.9", - "resolved": "https://registry.npmmirror.com/d3-hierarchy/download/d3-hierarchy-1.1.9.tgz", + "resolved": "https://registry.nlark.com/d3-hierarchy/download/d3-hierarchy-1.1.9.tgz", "integrity": "sha1-L2vuJMqupD+Nw3VF+gFihVlkeoM=" }, "d3-interpolate": { @@ -4281,7 +4281,7 @@ }, "d3-scale": { "version": "2.2.2", - "resolved": "https://registry.npmmirror.com/d3-scale/download/d3-scale-2.2.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fd3-scale%2Fdownload%2Fd3-scale-2.2.2.tgz", + "resolved": "https://registry.nlark.com/d3-scale/download/d3-scale-2.2.2.tgz?cache=0&sync_timestamp=1622924716548&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fd3-scale%2Fdownload%2Fd3-scale-2.2.2.tgz", "integrity": "sha1-TogOCydFrKrd0+3iap6Qip4XuB8=", "requires": { "d3-array": "^1.2.0", @@ -4308,7 +4308,7 @@ }, "d3-shape": { "version": "1.3.7", - "resolved": "https://registry.npmmirror.com/d3-shape/download/d3-shape-1.3.7.tgz", + "resolved": "https://registry.nlark.com/d3-shape/download/d3-shape-1.3.7.tgz", "integrity": "sha1-32OAG+B7yYa8VPY3ibT+UCmStdc=", "requires": { "d3-path": "1" @@ -4321,7 +4321,7 @@ }, "d3-time-format": { "version": "2.3.0", - "resolved": "https://registry.npmmirror.com/d3-time-format/download/d3-time-format-2.3.0.tgz", + "resolved": "https://registry.nlark.com/d3-time-format/download/d3-time-format-2.3.0.tgz", "integrity": "sha1-EHvcAoZneIqJJLoED68fvM1aeFA=", "requires": { "d3-time": "1" @@ -4381,7 +4381,7 @@ }, "dayjs": { "version": "1.10.7", - "resolved": "https://registry.npmmirror.com/dayjs/download/dayjs-1.10.7.tgz", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.10.7.tgz", "integrity": "sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==" }, "debug": { @@ -4403,13 +4403,13 @@ }, "decamelize": { "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/decamelize/download/decamelize-1.2.0.tgz?cache=0&sync_timestamp=1633055760479&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fdecamelize%2Fdownload%2Fdecamelize-1.2.0.tgz", + "resolved": "https://registry.npm.taobao.org/decamelize/download/decamelize-1.2.0.tgz?cache=0&sync_timestamp=1610348716845&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdecamelize%2Fdownload%2Fdecamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, "decamelize-keys": { "version": "1.1.0", - "resolved": "https://registry.nlark.com/decamelize-keys/download/decamelize-keys-1.1.0.tgz", + "resolved": "https://registry.npm.taobao.org/decamelize-keys/download/decamelize-keys-1.1.0.tgz", "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", "dev": true, "requires": { @@ -4419,7 +4419,7 @@ "dependencies": { "map-obj": { "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/map-obj/download/map-obj-1.0.1.tgz?cache=0&sync_timestamp=1634552719803&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fmap-obj%2Fdownload%2Fmap-obj-1.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/map-obj/download/map-obj-1.0.1.tgz?cache=0&sync_timestamp=1617771341569&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmap-obj%2Fdownload%2Fmap-obj-1.0.1.tgz", "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", "dev": true } @@ -4557,7 +4557,7 @@ }, "dns-packet": { "version": "1.3.4", - "resolved": "https://registry.npmmirror.com/dns-packet/download/dns-packet-1.3.4.tgz", + "resolved": "https://registry.nlark.com/dns-packet/download/dns-packet-1.3.4.tgz?cache=0&sync_timestamp=1625480066754&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fdns-packet%2Fdownload%2Fdns-packet-1.3.4.tgz", "integrity": "sha1-40VQZYJKJQe6iGxVqJljuxB97G8=", "dev": true, "requires": { @@ -4686,7 +4686,7 @@ }, "emoji-regex": { "version": "8.0.0", - "resolved": "https://registry.npmmirror.com/emoji-regex/download/emoji-regex-8.0.0.tgz?cache=0&sync_timestamp=1632751333727&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Femoji-regex%2Fdownload%2Femoji-regex-8.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/emoji-regex/download/emoji-regex-8.0.0.tgz?cache=0&sync_timestamp=1614682798745&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Femoji-regex%2Fdownload%2Femoji-regex-8.0.0.tgz", "integrity": "sha1-6Bj9ac5cz8tARZT4QpY79TFkzDc=", "dev": true }, @@ -5029,7 +5029,7 @@ }, "find-up": { "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/find-up/download/find-up-2.1.0.tgz?cache=0&sync_timestamp=1633618631704&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Ffind-up%2Fdownload%2Ffind-up-2.1.0.tgz", + "resolved": "https://registry.npm.taobao.org/find-up/download/find-up-2.1.0.tgz?cache=0&sync_timestamp=1597169795121&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffind-up%2Fdownload%2Ffind-up-2.1.0.tgz", "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { @@ -5038,7 +5038,7 @@ }, "locate-path": { "version": "2.0.0", - "resolved": "https://registry.nlark.com/locate-path/download/locate-path-2.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/locate-path/download/locate-path-2.0.0.tgz", "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "dev": true, "requires": { @@ -5057,7 +5057,7 @@ }, "p-locate": { "version": "2.0.0", - "resolved": "https://registry.nlark.com/p-locate/download/p-locate-2.0.0.tgz?cache=0&sync_timestamp=1629892721671&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fp-locate%2Fdownload%2Fp-locate-2.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/p-locate/download/p-locate-2.0.0.tgz?cache=0&sync_timestamp=1597081369770&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fp-locate%2Fdownload%2Fp-locate-2.0.0.tgz", "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { @@ -5066,7 +5066,7 @@ }, "p-try": { "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/p-try/download/p-try-1.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/p-try/download/p-try-1.0.0.tgz", "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", "dev": true }, @@ -5173,7 +5173,7 @@ "dependencies": { "emoji-regex": { "version": "9.2.2", - "resolved": "https://registry.npmmirror.com/emoji-regex/download/emoji-regex-9.2.2.tgz?cache=0&sync_timestamp=1632751333727&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Femoji-regex%2Fdownload%2Femoji-regex-9.2.2.tgz", + "resolved": "https://registry.npm.taobao.org/emoji-regex/download/emoji-regex-9.2.2.tgz?cache=0&sync_timestamp=1614682798745&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Femoji-regex%2Fdownload%2Femoji-regex-9.2.2.tgz", "integrity": "sha1-hAyIA7DYBH9P8M+WMXazLU7z7XI=", "dev": true } @@ -5233,7 +5233,7 @@ }, "resolve": { "version": "2.0.0-next.3", - "resolved": "https://registry.npmmirror.com/resolve/download/resolve-2.0.0-next.3.tgz", + "resolved": "https://registry.npm.taobao.org/resolve/download/resolve-2.0.0-next.3.tgz", "integrity": "sha1-1BAWKT1KhYajnKXZtfFcvqH1XkY=", "dev": true, "requires": { @@ -5335,7 +5335,7 @@ }, "estraverse": { "version": "4.3.0", - "resolved": "https://registry.npmmirror.com/estraverse/download/estraverse-4.3.0.tgz", + "resolved": "https://registry.nlark.com/estraverse/download/estraverse-4.3.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.nlark.com%2Festraverse%2Fdownload%2Festraverse-4.3.0.tgz", "integrity": "sha1-OYrT88WiSUi+dyXoPRGn3ijNvR0=", "dev": true }, @@ -5358,7 +5358,7 @@ }, "events": { "version": "3.3.0", - "resolved": "https://registry.npmmirror.com/events/download/events-3.3.0.tgz?cache=0&sync_timestamp=1636449286836&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fevents%2Fdownload%2Fevents-3.3.0.tgz", + "resolved": "https://registry.npm.taobao.org/events/download/events-3.3.0.tgz?cache=0&sync_timestamp=1614444817773&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fevents%2Fdownload%2Fevents-3.3.0.tgz", "integrity": "sha1-Mala0Kkk4tLEGagTrrLE6HjqdAA=", "dev": true }, @@ -5708,14 +5708,14 @@ }, "fsevents": { "version": "2.3.2", - "resolved": "https://registry.npmmirror.com/fsevents/download/fsevents-2.3.2.tgz", + "resolved": "https://registry.npm.taobao.org/fsevents/download/fsevents-2.3.2.tgz?cache=0&sync_timestamp=1612536512306&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffsevents%2Fdownload%2Ffsevents-2.3.2.tgz", "integrity": "sha1-ilJveLj99GI7cJ4Ll1xSwkwC/Ro=", "dev": true, "optional": true }, "function-bind": { "version": "1.1.1", - "resolved": "https://registry.nlark.com/function-bind/download/function-bind-1.1.1.tgz", + "resolved": "https://registry.npm.taobao.org/function-bind/download/function-bind-1.1.1.tgz", "integrity": "sha1-pWiZ0+o8m6uHS7l3O3xe3pL0iV0=", "dev": true }, @@ -5727,7 +5727,7 @@ }, "gensync": { "version": "1.0.0-beta.2", - "resolved": "https://registry.nlark.com/gensync/download/gensync-1.0.0-beta.2.tgz", + "resolved": "https://registry.npm.taobao.org/gensync/download/gensync-1.0.0-beta.2.tgz", "integrity": "sha1-MqbudsPX9S1GsrGuXZP+qFgKJeA=", "dev": true }, @@ -5862,7 +5862,7 @@ }, "globals": { "version": "11.12.0", - "resolved": "https://registry.npmmirror.com/globals/download/globals-11.12.0.tgz", + "resolved": "https://registry.nlark.com/globals/download/globals-11.12.0.tgz", "integrity": "sha1-q4eVM4hooLq9hSV1gBjCp+uVxC4=", "dev": true }, @@ -5924,7 +5924,7 @@ }, "has": { "version": "1.0.3", - "resolved": "https://registry.nlark.com/has/download/has-1.0.3.tgz", + "resolved": "https://registry.npm.taobao.org/has/download/has-1.0.3.tgz", "integrity": "sha1-ci18v8H2qoJB8W3YFOAR4fQeh5Y=", "dev": true, "requires": { @@ -5965,16 +5965,11 @@ "dev": true }, "history": { - "version": "4.10.1", - "resolved": "https://registry.npmmirror.com/history/download/history-4.10.1.tgz", - "integrity": "sha1-MzcaZeOoOyZ0NOKz87G0xYqtTPM=", + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/history/-/history-5.2.0.tgz", + "integrity": "sha512-uPSF6lAJb3nSePJ43hN3eKj1dTWpN9gMod0ZssbFTIsen+WehTmEadgL+kg78xLJFdRfrrC//SavDzmRVdE+Ig==", "requires": { - "@babel/runtime": "^7.1.2", - "loose-envify": "^1.2.0", - "resolve-pathname": "^3.0.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0", - "value-equal": "^1.0.1" + "@babel/runtime": "^7.7.6" } }, "hoist-non-react-statics": { @@ -6088,7 +6083,7 @@ }, "htmlparser2": { "version": "6.1.0", - "resolved": "https://registry.npmmirror.com/htmlparser2/download/htmlparser2-6.1.0.tgz?cache=0&sync_timestamp=1636640841489&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fhtmlparser2%2Fdownload%2Fhtmlparser2-6.1.0.tgz", + "resolved": "https://registry.nlark.com/htmlparser2/download/htmlparser2-6.1.0.tgz?cache=0&sync_timestamp=1629489805558&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fhtmlparser2%2Fdownload%2Fhtmlparser2-6.1.0.tgz", "integrity": "sha1-xNditsM3GgXb5l6UrkOp+EX7j7c=", "dev": true, "requires": { @@ -6178,7 +6173,7 @@ }, "image-size": { "version": "0.5.5", - "resolved": "https://registry.npmmirror.com/image-size/download/image-size-0.5.5.tgz", + "resolved": "https://registry.npm.taobao.org/image-size/download/image-size-0.5.5.tgz?cache=0&sync_timestamp=1618424661730&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fimage-size%2Fdownload%2Fimage-size-0.5.5.tgz", "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", "dev": true, "optional": true @@ -6224,7 +6219,7 @@ }, "imurmurhash": { "version": "0.1.4", - "resolved": "https://registry.nlark.com/imurmurhash/download/imurmurhash-0.1.4.tgz", + "resolved": "https://registry.npm.taobao.org/imurmurhash/download/imurmurhash-0.1.4.tgz", "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, @@ -6320,13 +6315,13 @@ }, "is-alphabetical": { "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/is-alphabetical/download/is-alphabetical-1.0.4.tgz", + "resolved": "https://registry.npm.taobao.org/is-alphabetical/download/is-alphabetical-1.0.4.tgz?cache=0&sync_timestamp=1615453703061&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-alphabetical%2Fdownload%2Fis-alphabetical-1.0.4.tgz", "integrity": "sha1-nn1rlJFr4iFTdF0YTCmMv5hqaG0=", "dev": true }, "is-alphanumerical": { "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/is-alphanumerical/download/is-alphanumerical-1.0.4.tgz?cache=0&sync_timestamp=1636009316820&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fis-alphanumerical%2Fdownload%2Fis-alphanumerical-1.0.4.tgz", + "resolved": "https://registry.npm.taobao.org/is-alphanumerical/download/is-alphanumerical-1.0.4.tgz?cache=0&sync_timestamp=1615453948466&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-alphanumerical%2Fdownload%2Fis-alphanumerical-1.0.4.tgz", "integrity": "sha1-frmiQx+FX2se8aeOMm31FWlsTb8=", "dev": true, "requires": { @@ -6410,7 +6405,7 @@ }, "is-decimal": { "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/is-decimal/download/is-decimal-1.0.4.tgz?cache=0&sync_timestamp=1636008960795&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fis-decimal%2Fdownload%2Fis-decimal-1.0.4.tgz", + "resolved": "https://registry.npm.taobao.org/is-decimal/download/is-decimal-1.0.4.tgz", "integrity": "sha1-ZaOllYocW2OnBuGzM9fNn2MNP6U=", "dev": true }, @@ -6441,7 +6436,7 @@ }, "is-hexadecimal": { "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/is-hexadecimal/download/is-hexadecimal-1.0.4.tgz", + "resolved": "https://registry.npm.taobao.org/is-hexadecimal/download/is-hexadecimal-1.0.4.tgz?cache=0&sync_timestamp=1615464641587&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-hexadecimal%2Fdownload%2Fis-hexadecimal-1.0.4.tgz", "integrity": "sha1-zDXJdYjaS9Saju3WvECC1E3LI6c=", "dev": true }, @@ -6522,7 +6517,7 @@ }, "is-regexp": { "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/is-regexp/download/is-regexp-2.1.0.tgz", + "resolved": "https://registry.npm.taobao.org/is-regexp/download/is-regexp-2.1.0.tgz?cache=0&sync_timestamp=1617816768041&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-regexp%2Fdownload%2Fis-regexp-2.1.0.tgz", "integrity": "sha1-zXNKVoZOI7lWv058ZsOWpMCyLC0=", "dev": true }, @@ -6593,7 +6588,7 @@ }, "is-what": { "version": "3.14.1", - "resolved": "https://registry.npmmirror.com/is-what/download/is-what-3.14.1.tgz?cache=0&sync_timestamp=1634283398540&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fis-what%2Fdownload%2Fis-what-3.14.1.tgz", + "resolved": "https://registry.nlark.com/is-what/download/is-what-3.14.1.tgz", "integrity": "sha1-4SIvRt3ahd6tD9HJ3xMXYOd3VcE=", "dev": true }, @@ -7015,7 +7010,7 @@ }, "longest-streak": { "version": "2.0.4", - "resolved": "https://registry.npmmirror.com/longest-streak/download/longest-streak-2.0.4.tgz?cache=0&sync_timestamp=1636446210498&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Flongest-streak%2Fdownload%2Flongest-streak-2.0.4.tgz", + "resolved": "https://registry.npm.taobao.org/longest-streak/download/longest-streak-2.0.4.tgz", "integrity": "sha1-uFmZV9pbXatk3uP+MW+ndFl9kOQ=", "dev": true }, @@ -7081,7 +7076,7 @@ }, "mathml-tag-names": { "version": "2.1.3", - "resolved": "https://registry.npmmirror.com/mathml-tag-names/download/mathml-tag-names-2.1.3.tgz?cache=0&sync_timestamp=1635891536078&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fmathml-tag-names%2Fdownload%2Fmathml-tag-names-2.1.3.tgz", + "resolved": "https://registry.npm.taobao.org/mathml-tag-names/download/mathml-tag-names-2.1.3.tgz", "integrity": "sha1-TdrdZzCOeAzxakdoWHjuJ7c2oKM=", "dev": true }, @@ -7279,7 +7274,7 @@ }, "min-indent": { "version": "1.0.1", - "resolved": "https://registry.npm.taobao.org/min-indent/download/min-indent-1.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/min-indent/download/min-indent-1.0.1.tgz?cache=0&sync_timestamp=1590694405535&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmin-indent%2Fdownload%2Fmin-indent-1.0.1.tgz", "integrity": "sha1-pj9oFnOzBXH76LwlaGrnRu76mGk=", "dev": true }, @@ -7374,12 +7369,22 @@ }, "mkdirp": { "version": "0.5.5", - "resolved": "https://registry.npmmirror.com/mkdirp/download/mkdirp-0.5.5.tgz", + "resolved": "https://registry.npm.taobao.org/mkdirp/download/mkdirp-0.5.5.tgz?cache=0&sync_timestamp=1587535418745&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmkdirp%2Fdownload%2Fmkdirp-0.5.5.tgz", "integrity": "sha1-2Rzv1i0UNsoPQWIOJRKI1CAJne8=", "requires": { "minimist": "^1.2.5" } }, + "mobx": { + "version": "6.3.13", + "resolved": "https://registry.npmmirror.com/mobx/-/mobx-6.3.13.tgz", + "integrity": "sha512-zDDKDhYUk9QCHQUdLG+wb4Jv/nXutSLt/P8kkwHyjdbrJO4OZS6QTEsrOnrKM39puqXSrJZHdB6+yRys2NBFFA==" + }, + "mobx-react-lite": { + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/mobx-react-lite/-/mobx-react-lite-3.2.3.tgz", + "integrity": "sha512-7exWp1FV0M9dP08H9PIeHlJqDw4IdkQVRMfLYaZFMmlbzSS6ZU6p/kx392KN+rVf81hH3IQYewvRGQ70oiwmbw==" + }, "moment": { "version": "2.29.1", "resolved": "https://registry.npmmirror.com/moment/download/moment-2.29.1.tgz", @@ -7398,7 +7403,7 @@ }, "multicast-dns": { "version": "6.2.3", - "resolved": "https://registry.npmmirror.com/multicast-dns/download/multicast-dns-6.2.3.tgz?cache=0&sync_timestamp=1633354862495&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fmulticast-dns%2Fdownload%2Fmulticast-dns-6.2.3.tgz", + "resolved": "https://registry.nlark.com/multicast-dns/download/multicast-dns-6.2.3.tgz?cache=0&sync_timestamp=1621890647706&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fmulticast-dns%2Fdownload%2Fmulticast-dns-6.2.3.tgz", "integrity": "sha1-oOx72QVcQoL3kMPIL04o2zsxsik=", "dev": true, "requires": { @@ -7753,7 +7758,7 @@ }, "p-try": { "version": "2.2.0", - "resolved": "https://registry.npmmirror.com/p-try/download/p-try-2.2.0.tgz", + "resolved": "https://registry.npm.taobao.org/p-try/download/p-try-2.2.0.tgz", "integrity": "sha1-yyhoVA4xPWHeWPr741zpAE1VQOY=", "dev": true }, @@ -7777,7 +7782,7 @@ }, "parent-module": { "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/parent-module/download/parent-module-1.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/parent-module/download/parent-module-1.0.1.tgz", "integrity": "sha1-aR0nCeeMefrjoVZiJFLQB2LKqqI=", "dev": true, "requires": { @@ -7800,7 +7805,7 @@ }, "parse-json": { "version": "5.2.0", - "resolved": "https://registry.npmmirror.com/parse-json/download/parse-json-5.2.0.tgz?cache=0&sync_timestamp=1637475717072&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fparse-json%2Fdownload%2Fparse-json-5.2.0.tgz", + "resolved": "https://registry.npm.taobao.org/parse-json/download/parse-json-5.2.0.tgz?cache=0&sync_timestamp=1610966631829&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fparse-json%2Fdownload%2Fparse-json-5.2.0.tgz", "integrity": "sha1-x2/Gbe5UIxyWKyK8yKcs8vmXU80=", "dev": true, "requires": { @@ -7917,7 +7922,7 @@ }, "pkg-dir": { "version": "4.2.0", - "resolved": "https://registry.npmmirror.com/pkg-dir/download/pkg-dir-4.2.0.tgz?cache=0&sync_timestamp=1633498133295&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fpkg-dir%2Fdownload%2Fpkg-dir-4.2.0.tgz", + "resolved": "https://registry.npm.taobao.org/pkg-dir/download/pkg-dir-4.2.0.tgz?cache=0&sync_timestamp=1602859056682&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpkg-dir%2Fdownload%2Fpkg-dir-4.2.0.tgz", "integrity": "sha1-8JkTPfft5CLoHR2ESCcO6z5CYfM=", "dev": true, "requires": { @@ -7959,7 +7964,7 @@ }, "postcss-html": { "version": "0.36.0", - "resolved": "https://registry.npmmirror.com/postcss-html/download/postcss-html-0.36.0.tgz", + "resolved": "https://registry.npm.taobao.org/postcss-html/download/postcss-html-0.36.0.tgz", "integrity": "sha1-tAkT+U6qzCRT/TChMnrW7h+IsgQ=", "dev": true, "requires": { @@ -7998,7 +8003,7 @@ }, "domhandler": { "version": "2.4.2", - "resolved": "https://registry.npmmirror.com/domhandler/download/domhandler-2.4.2.tgz", + "resolved": "https://registry.nlark.com/domhandler/download/domhandler-2.4.2.tgz", "integrity": "sha1-iAUJfpM9ZehVRvcm1g9euItE+AM=", "dev": true, "requires": { @@ -8007,7 +8012,7 @@ }, "domutils": { "version": "1.7.0", - "resolved": "https://registry.nlark.com/domutils/download/domutils-1.7.0.tgz?cache=0&sync_timestamp=1630106606599&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fdomutils%2Fdownload%2Fdomutils-1.7.0.tgz", + "resolved": "https://registry.nlark.com/domutils/download/domutils-1.7.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fdomutils%2Fdownload%2Fdomutils-1.7.0.tgz", "integrity": "sha1-Vuo0HoNOBuZ0ivehyyXaZ+qfjCo=", "dev": true, "requires": { @@ -8023,7 +8028,7 @@ }, "htmlparser2": { "version": "3.10.1", - "resolved": "https://registry.npmmirror.com/htmlparser2/download/htmlparser2-3.10.1.tgz?cache=0&sync_timestamp=1636640841489&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fhtmlparser2%2Fdownload%2Fhtmlparser2-3.10.1.tgz", + "resolved": "https://registry.nlark.com/htmlparser2/download/htmlparser2-3.10.1.tgz?cache=0&sync_timestamp=1629489805558&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fhtmlparser2%2Fdownload%2Fhtmlparser2-3.10.1.tgz", "integrity": "sha1-vWedw/WYl7ajS7EHSchVu1OpOS8=", "dev": true, "requires": { @@ -8039,7 +8044,7 @@ }, "postcss-less": { "version": "3.1.4", - "resolved": "https://registry.npmmirror.com/postcss-less/download/postcss-less-3.1.4.tgz", + "resolved": "https://registry.nlark.com/postcss-less/download/postcss-less-3.1.4.tgz", "integrity": "sha1-Np9YZCtZKO+Jj/vBpuk8lYMExa0=", "dev": true, "requires": { @@ -8186,7 +8191,7 @@ }, "postcss-scss": { "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/postcss-scss/download/postcss-scss-2.1.1.tgz", + "resolved": "https://registry.nlark.com/postcss-scss/download/postcss-scss-2.1.1.tgz?cache=0&sync_timestamp=1623651597829&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fpostcss-scss%2Fdownload%2Fpostcss-scss-2.1.1.tgz", "integrity": "sha1-7Dp1+imlXgFrkL8yaQJsU8HSs4M=", "dev": true, "requires": { @@ -8361,7 +8366,7 @@ }, "quick-lru": { "version": "4.0.1", - "resolved": "https://registry.npmmirror.com/quick-lru/download/quick-lru-4.0.1.tgz", + "resolved": "https://registry.nlark.com/quick-lru/download/quick-lru-4.0.1.tgz", "integrity": "sha1-W4h48ROlgheEjGSCAmxz4bpXcn8=", "dev": true }, @@ -8818,7 +8823,7 @@ }, "react-color": { "version": "2.19.3", - "resolved": "https://registry.npmmirror.com/react-color/download/react-color-2.19.3.tgz", + "resolved": "https://registry.npm.taobao.org/react-color/download/react-color-2.19.3.tgz", "integrity": "sha1-7GxrRWgxKjxqGEIKsEcuFGqlaD0=", "requires": { "@icons/material": "^0.2.4", @@ -8832,7 +8837,7 @@ }, "react-copy-to-clipboard": { "version": "5.0.4", - "resolved": "https://registry.npmmirror.com/react-copy-to-clipboard/download/react-copy-to-clipboard-5.0.4.tgz", + "resolved": "https://registry.nlark.com/react-copy-to-clipboard/download/react-copy-to-clipboard-5.0.4.tgz?cache=0&sync_timestamp=1629206753921&other_urls=https%3A%2F%2Fregistry.nlark.com%2Freact-copy-to-clipboard%2Fdownload%2Freact-copy-to-clipboard-5.0.4.tgz", "integrity": "sha1-QuxRmwPrlBOxGK+S0XgMQDpfGb8=", "requires": { "copy-to-clipboard": "^3", @@ -8882,12 +8887,12 @@ }, "react-is": { "version": "16.13.1", - "resolved": "https://registry.npmmirror.com/react-is/download/react-is-16.13.1.tgz", + "resolved": "https://registry.nlark.com/react-is/download/react-is-16.13.1.tgz", "integrity": "sha1-eJcppNw23imZ3BVt1sHZwYzqVqQ=" }, "react-lifecycles-compat": { "version": "3.0.4", - "resolved": "https://registry.npmmirror.com/react-lifecycles-compat/download/react-lifecycles-compat-3.0.4.tgz", + "resolved": "https://registry.npm.taobao.org/react-lifecycles-compat/download/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha1-TxonOv38jzSIqMUWv9p4+HI1I2I=" }, "react-loadable": { @@ -8925,6 +8930,21 @@ "react-is": "^16.6.0", "tiny-invariant": "^1.0.2", "tiny-warning": "^1.0.0" + }, + "dependencies": { + "history": { + "version": "4.10.1", + "resolved": "https://registry.npmmirror.com/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "requires": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + } } }, "react-router-dom": { @@ -8939,6 +8959,21 @@ "react-router": "5.2.1", "tiny-invariant": "^1.0.2", "tiny-warning": "^1.0.0" + }, + "dependencies": { + "history": { + "version": "4.10.1", + "resolved": "https://registry.npmmirror.com/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "requires": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + } } }, "reactcss": { @@ -8963,7 +8998,7 @@ "dependencies": { "hosted-git-info": { "version": "2.8.9", - "resolved": "https://registry.npmmirror.com/hosted-git-info/download/hosted-git-info-2.8.9.tgz", + "resolved": "https://registry.nlark.com/hosted-git-info/download/hosted-git-info-2.8.9.tgz", "integrity": "sha1-3/wL+aIcAiCQkPKqaUKeFBTa8/k=", "dev": true }, @@ -8987,7 +9022,7 @@ }, "type-fest": { "version": "0.6.0", - "resolved": "https://registry.npmmirror.com/type-fest/download/type-fest-0.6.0.tgz", + "resolved": "https://registry.nlark.com/type-fest/download/type-fest-0.6.0.tgz?cache=0&sync_timestamp=1628211344858&other_urls=https%3A%2F%2Fregistry.nlark.com%2Ftype-fest%2Fdownload%2Ftype-fest-0.6.0.tgz", "integrity": "sha1-jSojcNPfiG61yQraHFv2GIrPg4s=", "dev": true } @@ -8995,7 +9030,7 @@ }, "read-pkg-up": { "version": "7.0.1", - "resolved": "https://registry.npmmirror.com/read-pkg-up/download/read-pkg-up-7.0.1.tgz?cache=0&sync_timestamp=1634147799745&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fread-pkg-up%2Fdownload%2Fread-pkg-up-7.0.1.tgz", + "resolved": "https://registry.nlark.com/read-pkg-up/download/read-pkg-up-7.0.1.tgz?cache=0&sync_timestamp=1618846971516&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fread-pkg-up%2Fdownload%2Fread-pkg-up-7.0.1.tgz", "integrity": "sha1-86YTV1hFlzOuK5VjgFbhhU5+9Qc=", "dev": true, "requires": { @@ -9006,7 +9041,7 @@ "dependencies": { "type-fest": { "version": "0.8.1", - "resolved": "https://registry.npmmirror.com/type-fest/download/type-fest-0.8.1.tgz", + "resolved": "https://registry.nlark.com/type-fest/download/type-fest-0.8.1.tgz?cache=0&sync_timestamp=1628211344858&other_urls=https%3A%2F%2Fregistry.nlark.com%2Ftype-fest%2Fdownload%2Ftype-fest-0.8.1.tgz", "integrity": "sha1-CeJJ696FHTseSNJ8EFREZn8XuD0=", "dev": true } @@ -9126,7 +9161,7 @@ }, "regjsgen": { "version": "0.5.2", - "resolved": "https://registry.npmmirror.com/regjsgen/download/regjsgen-0.5.2.tgz", + "resolved": "https://registry.npm.taobao.org/regjsgen/download/regjsgen-0.5.2.tgz", "integrity": "sha1-kv8pX7He7L9uzaslQ9IH6RqjNzM=", "dev": true }, @@ -9260,8 +9295,8 @@ }, "resolve-pathname": { "version": "3.0.0", - "resolved": "https://registry.npm.taobao.org/resolve-pathname/download/resolve-pathname-3.0.0.tgz", - "integrity": "sha1-mdAiJNPPJjaJvsuzk7xWAxMCXc0=" + "resolved": "https://registry.npmmirror.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" }, "retry": { "version": "0.13.1", @@ -9277,7 +9312,7 @@ }, "rimraf": { "version": "2.7.1", - "resolved": "https://registry.npmmirror.com/rimraf/download/rimraf-2.7.1.tgz", + "resolved": "https://registry.npm.taobao.org/rimraf/download/rimraf-2.7.1.tgz?cache=0&sync_timestamp=1581257110269&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Frimraf%2Fdownload%2Frimraf-2.7.1.tgz", "integrity": "sha1-NXl/E6f9rcVmFCwp1PB8ytSD4+w=", "requires": { "glob": "^7.1.3" @@ -9294,7 +9329,7 @@ }, "rw": { "version": "1.3.3", - "resolved": "https://registry.npmmirror.com/rw/download/rw-1.3.3.tgz", + "resolved": "https://registry.npm.taobao.org/rw/download/rw-1.3.3.tgz", "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" }, "safe-buffer": { @@ -9326,7 +9361,7 @@ }, "schema-utils": { "version": "3.1.1", - "resolved": "https://registry.npmmirror.com/schema-utils/download/schema-utils-3.1.1.tgz?cache=0&sync_timestamp=1637075928327&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fschema-utils%2Fdownload%2Fschema-utils-3.1.1.tgz", + "resolved": "https://registry.nlark.com/schema-utils/download/schema-utils-3.1.1.tgz?cache=0&sync_timestamp=1626694835325&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fschema-utils%2Fdownload%2Fschema-utils-3.1.1.tgz", "integrity": "sha1-vHTEtraZXB2I92qLd76nIZ4MgoE=", "dev": true, "requires": { @@ -9396,7 +9431,7 @@ "dependencies": { "ms": { "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/ms/download/ms-2.0.0.tgz", + "resolved": "https://registry.nlark.com/ms/download/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true } @@ -9439,7 +9474,7 @@ }, "http-errors": { "version": "1.6.3", - "resolved": "https://registry.npmmirror.com/http-errors/download/http-errors-1.6.3.tgz", + "resolved": "https://registry.npm.taobao.org/http-errors/download/http-errors-1.6.3.tgz?cache=0&sync_timestamp=1593407676273&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhttp-errors%2Fdownload%2Fhttp-errors-1.6.3.tgz", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "dev": true, "requires": { @@ -9674,7 +9709,7 @@ }, "spdy-transport": { "version": "3.0.0", - "resolved": "https://registry.nlark.com/spdy-transport/download/spdy-transport-3.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/spdy-transport/download/spdy-transport-3.0.0.tgz", "integrity": "sha1-ANSGOmQArXXfkzYaFghgXl3NzzE=", "dev": true, "requires": { @@ -9688,7 +9723,7 @@ }, "specificity": { "version": "0.4.1", - "resolved": "https://registry.npmmirror.com/specificity/download/specificity-0.4.1.tgz", + "resolved": "https://registry.nlark.com/specificity/download/specificity-0.4.1.tgz?cache=0&sync_timestamp=1620301057306&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fspecificity%2Fdownload%2Fspecificity-0.4.1.tgz", "integrity": "sha1-qrXmRQEtsIuhguFRFlc40AiHsBk=", "dev": true }, @@ -10068,7 +10103,7 @@ }, "supports-color": { "version": "5.5.0", - "resolved": "https://registry.npmmirror.com/supports-color/download/supports-color-5.5.0.tgz", + "resolved": "https://registry.nlark.com/supports-color/download/supports-color-5.5.0.tgz", "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", "dev": true, "requires": { @@ -10172,7 +10207,7 @@ }, "through": { "version": "2.3.8", - "resolved": "https://registry.nlark.com/through/download/through-2.3.8.tgz?cache=0&sync_timestamp=1618847037651&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fthrough%2Fdownload%2Fthrough-2.3.8.tgz", + "resolved": "https://registry.npm.taobao.org/through/download/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, @@ -10362,7 +10397,7 @@ "ts-node": { "version": "10.4.0", "resolved": "https://registry.npmmirror.com/ts-node/download/ts-node-10.4.0.tgz", - "integrity": "sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==", + "integrity": "sha1-aA+IlFiF9ObPRQ5/DWIj3UBIlfc=", "dev": true, "requires": { "@cspotcode/source-map-support": "0.7.0", @@ -10424,7 +10459,7 @@ }, "type-fest": { "version": "0.18.1", - "resolved": "https://registry.npmmirror.com/type-fest/download/type-fest-0.18.1.tgz", + "resolved": "https://registry.nlark.com/type-fest/download/type-fest-0.18.1.tgz?cache=0&sync_timestamp=1628211344858&other_urls=https%3A%2F%2Fregistry.nlark.com%2Ftype-fest%2Fdownload%2Ftype-fest-0.18.1.tgz", "integrity": "sha1-20vBUaSiz07r+a3V23VQjbbMhB8=", "dev": true }, @@ -10562,7 +10597,7 @@ }, "url-loader": { "version": "4.1.1", - "resolved": "https://registry.npmmirror.com/url-loader/download/url-loader-4.1.1.tgz", + "resolved": "https://registry.nlark.com/url-loader/download/url-loader-4.1.1.tgz", "integrity": "sha1-KFBekFyuFYzwfJLKYi1/I35wpOI=", "dev": true, "requires": { @@ -10639,12 +10674,12 @@ }, "value-equal": { "version": "1.0.1", - "resolved": "https://registry.npm.taobao.org/value-equal/download/value-equal-1.0.1.tgz", - "integrity": "sha1-Hgt5THNMXAyt4XnEN9NW2TGjTWw=" + "resolved": "https://registry.npmmirror.com/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" }, "vary": { "version": "1.1.2", - "resolved": "https://registry.nlark.com/vary/download/vary-1.1.2.tgz", + "resolved": "https://registry.npm.taobao.org/vary/download/vary-1.1.2.tgz", "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", "dev": true }, @@ -10672,7 +10707,7 @@ }, "viz.js": { "version": "1.8.2", - "resolved": "https://registry.npmmirror.com/viz.js/download/viz.js-1.8.2.tgz", + "resolved": "https://registry.npm.taobao.org/viz.js/download/viz.js-1.8.2.tgz", "integrity": "sha1-2cwEzZn5jsmGv5BU23amy83F2Xo=" }, "watchpack": { @@ -11058,7 +11093,7 @@ }, "websocket-driver": { "version": "0.7.4", - "resolved": "https://registry.nlark.com/websocket-driver/download/websocket-driver-0.7.4.tgz", + "resolved": "https://registry.npm.taobao.org/websocket-driver/download/websocket-driver-0.7.4.tgz", "integrity": "sha1-ia1Slbv2S0gKvLox5JU6ynBvV2A=", "dev": true, "requires": { diff --git a/package.json b/package.json index 94b2987f..db43009d 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,9 @@ "private": true, "scripts": { "dev": "webpack-dev-server --config ./config/webpack.dev.ts --mode development --color --progress --hot", - "lint": "eslint 'app/**/*.{ts,tsx}' && stylelint **/*.{html,less}", - "lint-fix": "eslint 'app/**/*.{ts,tsx}' --fix && stylelint **/*.{html,less} --fix", + "dev2": "webpack-dev-server --config ./config/webpack.dev2.ts --mode development --color --progress --hot", + "lint": "eslint 'app-v2/**/*.{ts,tsx}' && stylelint **/*.{html,less}", + "lint-fix": "eslint 'app-v2/**/*.{ts,tsx}' --fix && stylelint **/*.{html,less} --fix", "build": "webpack --config config/webpack.prod.ts" }, "dependencies": { @@ -24,7 +25,9 @@ "csvtojson": "^2.0.10", "d3": "^5.12.0", "d3-graphviz": "^2.6.1", + "dayjs": "^1.10.7", "file-saver": "^2.0.5", + "history": "^5.1.0", "http-proxy-middleware": "^0.20.0", "immer": "^9.0.12", "js-cookie": "^2.2.1", @@ -32,6 +35,8 @@ "json2csv": "^5.0.6", "koa2-connect": "^1.0.2", "lodash": "^4.17.15", + "mobx": "^6.3.8", + "mobx-react-lite": "^3.2.2", "mz-modules": "^2.1.0", "react": "^17.0.0", "react-beautiful-dnd": "^13.0.0", diff --git a/tsconfig.json b/tsconfig.json index 9507663f..09ca2479 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -27,7 +27,8 @@ "module": "esnext", "baseUrl": ".", "paths": { - "#app/*": ["app/*"] + "@public/*": ["public/*"], + "@appv2/*": ["app-v2/*"] } }, "ts-node": {