diff --git a/client/src/components/cards/EmptyCard.tsx b/client/src/components/cards/EmptyCard.tsx index ec458095..e2c239e0 100644 --- a/client/src/components/cards/EmptyCard.tsx +++ b/client/src/components/cards/EmptyCard.tsx @@ -1,5 +1,11 @@ -import React, { FC } from 'react'; -import { makeStyles, Theme, Typography } from '@material-ui/core'; +import { FC } from 'react'; +import { + makeStyles, + Theme, + Typography, + Card, + CardContent, +} from '@material-ui/core'; import StatusIcon from '../status/StatusIcon'; import { ChildrenStatusType } from '../status/Types'; import { EmptyCardProps } from './Types'; @@ -8,6 +14,7 @@ const useStyles = makeStyles((theme: Theme) => ({ wrapper: { backgroundColor: '#fff', flexDirection: 'column', + textAlign: 'center', }, text: { marginTop: theme.spacing(2), @@ -34,14 +41,16 @@ const EmptyCard: FC = ({ const classes = useStyles(); return ( -
- {loading && } - {icon} - {text} - {subText} -
+ + {loading && } + {icon} + {text} + {subText} + + ); }; diff --git a/client/src/components/menu/CommunityBtn.tsx b/client/src/components/menu/CommunityBtn.tsx index 0b08f156..b3a9df96 100644 --- a/client/src/components/menu/CommunityBtn.tsx +++ b/client/src/components/menu/CommunityBtn.tsx @@ -40,7 +40,6 @@ const getStyles = makeStyles((theme: Theme) => ({ position: 'absolute', width: '360px', overflow: 'hidden', - fontFamily: 'Roboto', }, head: { backgroundColor: theme.palette.primary.main, @@ -54,7 +53,6 @@ const getStyles = makeStyles((theme: Theme) => ({ fontSize: theme.spacing(2), lineHeight: theme.spacing(3), letterSpacing: '-0.01em', - fontFamily: 'Roboto' }, titleDesc: { color: '#f0f4f9', diff --git a/client/src/context/Data.tsx b/client/src/context/Data.tsx index 7923b9af..b55faee4 100644 --- a/client/src/context/Data.tsx +++ b/client/src/context/Data.tsx @@ -1,10 +1,13 @@ import { createContext, useEffect, useState } from 'react'; -import { DatabaseHttp } from '@/http'; +import { DatabaseHttp, UserHttp, MilvusHttp } from '@/http'; +import { parseJson, getNode, getSystemConfigs } from '@/utils'; +import { MILVUS_NODE_TYPE } from '@/consts'; import { DataContextType } from './Types'; export const dataContext = createContext({ database: 'default', databases: ['default'], + data: {}, setDatabase: () => {}, setDatabaseList: () => {}, }); @@ -13,23 +16,84 @@ const { Provider } = dataContext; export const DataProvider = (props: { children: React.ReactNode }) => { const [database, setDatabase] = useState('default'); const [databases, setDatabases] = useState(['default']); + const [data, setData] = useState({}); - const fetchDatabases = async () => { + const fetchData = async () => { try { - const res = await DatabaseHttp.getDatabases(); - setDatabases(res.db_names); + // fetch all data + const [databases, metrics, users, roles] = await Promise.all([ + DatabaseHttp.getDatabases(), + MilvusHttp.getMetrics(), + UserHttp.getUsers(), + UserHttp.getRoles(), + ]); + + // parse data + const parsedJson = parseJson(metrics); + + // get query nodes + const queryNodes = getNode( + parsedJson.allNodes, + MILVUS_NODE_TYPE.QUERYNODE + ); + + // get data nodes + const dataNodes = getNode( + parsedJson.allNodes, + MILVUS_NODE_TYPE.DATANODE + ); + + // get data nodes + const indexNodes = getNode( + parsedJson.allNodes, + MILVUS_NODE_TYPE.INDEXNODE + ); + + // get root coord + const rootCoord = getNode( + parsedJson.allNodes, + MILVUS_NODE_TYPE.ROOTCOORD + )[0]; + + + // get system config + const systemConfig = getSystemConfigs(parsedJson.workingNodes); + const deployMode = rootCoord.infos.system_info.deploy_mode; + const systemInfo = rootCoord.infos.system_info; + + const data = { + users: users.usernames, + roles: roles.results, + queryNodes, + dataNodes, + indexNodes, + rootCoord, + deployMode, + parsedJson, + systemConfig, + systemInfo, + }; + + console.log(data); + + // store databases + setDatabases(databases.db_names); + // store other datas + setData(data); } catch (error) { // do nothing + console.log('fetch data error', error); } }; useEffect(() => { - fetchDatabases(); + fetchData(); }, []); return ( >; setDatabaseList: Dispatch>; + data?: any; }; export type PrometheusContextType = { diff --git a/client/src/i18n/en/overview.ts b/client/src/i18n/en/overview.ts index e00c49f4..b29e6b03 100644 --- a/client/src/i18n/en/overview.ts +++ b/client/src/i18n/en/overview.ts @@ -1,9 +1,18 @@ const overviewTrans = { load: 'Loaded Collections', all: 'All Collections', - data: 'Entites', + data: 'Entities', rows: '{{number}}', loading: 'Loading Collections', + sysInfo: 'System Info', + database: 'Database', + milvusVersion: 'Milvus Version', + upTime: 'Up Time', + deployMode: 'Deploy Mode', + databases: 'Databases', + users: 'Users', + roles: 'Roles', + days: 'days', }; export default overviewTrans; diff --git a/client/src/index.css b/client/src/index.css index 678014f2..e356def1 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -2,8 +2,8 @@ body { margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', + 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; diff --git a/client/src/pages/overview/Overview.tsx b/client/src/pages/overview/Overview.tsx index a3225580..a56c8470 100644 --- a/client/src/pages/overview/Overview.tsx +++ b/client/src/pages/overview/Overview.tsx @@ -1,6 +1,15 @@ -import { makeStyles, Theme, Typography, useTheme } from '@material-ui/core'; import { useCallback, useContext, useEffect, useMemo, useState } from 'react'; +import { + makeStyles, + Theme, + Typography, + useTheme, + Card, + CardContent, +} from '@material-ui/core'; +import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; +import dayjs from 'dayjs'; import { rootContext, webSocketContext, dataContext } from '@/context'; import EmptyCard from '@/components/cards/EmptyCard'; import icons from '@/components/icons/Icons'; @@ -13,22 +22,92 @@ import CollectionCard from './collectionCard/CollectionCard'; import StatisticsCard from './statisticsCard/StatisticsCard'; const useStyles = makeStyles((theme: Theme) => ({ + overviewContainer: { + display: 'flex', + flexDirection: 'row', + gap: theme.spacing(4), + }, collectionTitle: { margin: theme.spacing(2, 0), - lineHeight: '20px', - fontSize: '14px', - color: theme.palette.attuGrey.dark, + fontSize: 16, + fontWeight: 'bold', }, cardsWrapper: { display: 'grid', - gridTemplateColumns: 'repeat(auto-fill, minmax(380px, 1fr))', + gridTemplateColumns: 'repeat(auto-fill, minmax(240px, 1fr))', + gap: theme.spacing(2), + }, + sysCardsWrapper: { + display: 'grid', + gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))', gap: theme.spacing(2), }, + h2: { + fontWeight: 'bold', + fontSize: '22px', + margin: theme.spacing(1, 0, 2, 0), + }, + dbWrapper: { + width: '55%', + order: 1, + padding: theme.spacing(1, 0, 0), + }, + emptyCard: { + minHeight: '50vh', + color: 'transparent', + }, + sysWrapper: { + width: '45%', + background: 'rgb(239, 239, 239)', + padding: theme.spacing(1, 2, 2), + order: 1, + borderRadius: 8, + }, + sysCard: { + '& p': { + fontSize: '24px', + margin: 0, + }, + '& h3': { + margin: 0, + fontSize: '14px', + color: theme.palette.attuGrey.dark, + }, + '& a': { + textDecoration: 'none', + color: '#000', + }, + }, })); +const SysCard = (data: { + title: string; + count: number | string; + des?: string; + link?: string; +}) => { + const classes = useStyles(); + + const content = ( + <> + {data.count} + {data.title} + {data.des ? {data.des} : null} + + ); + + return ( + + + {data.link ? {content} : content} + + + ); +}; + const Overview = () => { useNavigationHook(ALL_ROUTER_TYPES.OVERVIEW); - const { database } = useContext(dataContext); + const { database, databases, data } = useContext(dataContext); const classes = useStyles(); const theme = useTheme(); const { t: overviewTrans } = useTranslation('overview'); @@ -106,33 +185,93 @@ const Overview = () => { const CollectionIcon = icons.navCollection; + // calculation diff to the rootCoord create time + const duration = useMemo(() => { + let rootCoordCreatedTime = data.rootCoord?.infos?.created_time; + + let duration = '0'; + if (rootCoordCreatedTime) { + rootCoordCreatedTime = rootCoordCreatedTime.substring( + 0, + rootCoordCreatedTime.lastIndexOf('m=') + ); + + const rootCoordCreatedTimeObj = dayjs(rootCoordCreatedTime); + + const now = dayjs(); + duration = now.diff(rootCoordCreatedTimeObj, 'day', true).toFixed(2); + } + + return `${duration} ${overviewTrans('days')}`; + }, [data.rootCoord]); + return ( -
- - - {overviewTrans('load')} - - - {loadCollections.length > 0 ? ( -
- {loadCollections.map(collection => ( - +
+ + {overviewTrans('database')} {database} + + + + + {overviewTrans('load')} + + + {loadCollections.length > 0 ? ( +
+ {loadCollections.map(collection => ( + + ))} +
+ ) : ( + : undefined} + text={ + loading ? overviewTrans('loading') : collectionTrans('noLoadData') + } + /> + )} +
+ + {data?.systemInfo ? ( +
+ + {overviewTrans('sysInfo')} + +
+ + + + + + - ))} -
- ) : ( - : undefined} - text={ - loading ? overviewTrans('loading') : collectionTrans('noLoadData') - } - /> - )} +
+
+ ) : null} ); }; diff --git a/client/src/pages/overview/collectionCard/CollectionCard.tsx b/client/src/pages/overview/collectionCard/CollectionCard.tsx index 7e8db5e3..2bef791d 100644 --- a/client/src/pages/overview/collectionCard/CollectionCard.tsx +++ b/client/src/pages/overview/collectionCard/CollectionCard.tsx @@ -1,4 +1,11 @@ -import { makeStyles, Theme, Typography, Divider } from '@material-ui/core'; +import { + makeStyles, + Theme, + Typography, + Divider, + Card, + CardContent, +} from '@material-ui/core'; import { FC, useContext } from 'react'; import CustomButton from '@/components/customButton/CustomButton'; import icons from '@/components/icons/Icons'; @@ -13,7 +20,6 @@ import { CollectionCardProps } from './Types'; const useStyles = makeStyles((theme: Theme) => ({ wrapper: { - padding: theme.spacing(2), textAlign: 'end', '& .link': { display: 'flex', @@ -115,45 +121,50 @@ const CollectionCard: FC = ({ }; return ( -
-
- -
- - {name} - - -
    - {_replicas && _replicas.length > 1 ? ( + +
    + +
    + + {name} + + +
      + {_replicas && _replicas.length > 1 ? ( +
    • + {collectionTrans('replicaNum')}: + + {_replicas.length} + +
    • + ) : null}
    • - {collectionTrans('replicaNum')}: - - {_replicas.length} - + {collectionTrans('rowCount')}: + {rowCount}
    • - ) : null} -
    • - {collectionTrans('rowCount')}: - {rowCount} -
    • -
    - - - - {btnTrans('vectorSearch')} - - - - -
+ + + + + {btnTrans('vectorSearch')} + + + + + + ); }; diff --git a/client/src/pages/overview/statisticsCard/StatisticsCard.tsx b/client/src/pages/overview/statisticsCard/StatisticsCard.tsx index 41c3d221..55a6854c 100644 --- a/client/src/pages/overview/statisticsCard/StatisticsCard.tsx +++ b/client/src/pages/overview/statisticsCard/StatisticsCard.tsx @@ -1,17 +1,18 @@ -import { makeStyles, Theme, Typography } from '@material-ui/core'; +import { + makeStyles, + Theme, + Typography, + Card, + CardContent, +} from '@material-ui/core'; import { FC } from 'react'; import { StatisticsCardProps } from './Types'; const useStyles = makeStyles((theme: Theme) => ({ wrapper: { display: `grid`, - gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))', + gridTemplateColumns: 'repeat(auto-fill, minmax(160px, 1fr))', columnGap: theme.spacing(2), - padding: theme.spacing(3), - }, - itemWrapper: { - paddingLeft: theme.spacing(1), - borderLeft: '3px solid #f0f4f9', }, label: { fontSize: '12px', @@ -32,19 +33,21 @@ const StatisticsCard: FC = ({ const classes = useStyles(); return ( -
- {data.map(item => ( -
- {item.label} - - {item.value} - -
- ))} -
+ + + {data.map(item => ( +
+ {item.label} + + {item.value} + +
+ ))} +
+
); }; diff --git a/client/src/pages/system/MiniTopology.tsx b/client/src/pages/system/MiniTopology.tsx index 96bc6b22..40338b65 100644 --- a/client/src/pages/system/MiniTopology.tsx +++ b/client/src/pages/system/MiniTopology.tsx @@ -96,7 +96,6 @@ const MiniTopo: FC = props => { stroke={theme.palette.primary.main} /> = props => { /> ({ root: { - fontFamily: 'Roboto', margin: '14px 40px', display: 'grid', gridTemplateColumns: 'auto 400px', diff --git a/client/src/pages/system/SystemView.tsx b/client/src/pages/system/SystemView.tsx index 93627e44..10d393ce 100644 --- a/client/src/pages/system/SystemView.tsx +++ b/client/src/pages/system/SystemView.tsx @@ -14,7 +14,6 @@ import DataCard from './DataCard'; const getStyles = makeStyles((theme: Theme) => ({ root: { - fontFamily: 'Roboto', margin: '12px 24px', position: 'relative', height: 'fit-content', diff --git a/client/src/pages/system/Topology.tsx b/client/src/pages/system/Topology.tsx index d71f76dd..34ced2cb 100644 --- a/client/src/pages/system/Topology.tsx +++ b/client/src/pages/system/Topology.tsx @@ -384,7 +384,6 @@ const Topo = (props: any) => { )} { /> { stroke={theme.palette.primary.main} /> ({ root: { - fontFamily: 'Roboto', margin: '16px 40px', position: 'relative', height: '88%', diff --git a/client/src/pages/systemHealthy/Topology.tsx b/client/src/pages/systemHealthy/Topology.tsx index 1670af60..9c744387 100644 --- a/client/src/pages/systemHealthy/Topology.tsx +++ b/client/src/pages/systemHealthy/Topology.tsx @@ -207,7 +207,6 @@ const Topology = ({ )} { (node: any) => node?.infos?.has_error !== true ); + const allNodes = jsonData?.response?.nodes_info; + workingNodes.forEach((node: any) => { const type = node?.infos?.type; if (node.connected) { @@ -40,7 +42,7 @@ export const parseJson = (jsonData: any) => { system.disk += info.disk; system.diskUsage += info.disk_usage; }); - return { nodes, childNodes, system, workingNodes }; + return { nodes, childNodes, system, workingNodes, allNodes }; }; // get nodes diff --git a/server/src/collections/collections.controller.ts b/server/src/collections/collections.controller.ts index 6e237831..7eba2321 100644 --- a/server/src/collections/collections.controller.ts +++ b/server/src/collections/collections.controller.ts @@ -89,7 +89,7 @@ export class CollectionController { try { const result = type === 1 - ? await this.collectionsService.getLoadedColletions() + ? await this.collectionsService.getLoadedCollections() : await this.collectionsService.getAllCollections(); res.send(result); } catch (error) { diff --git a/server/src/collections/collections.service.ts b/server/src/collections/collections.service.ts index 25b0b3f2..863a2878 100644 --- a/server/src/collections/collections.service.ts +++ b/server/src/collections/collections.service.ts @@ -213,7 +213,7 @@ export class CollectionsService { return data; } - async getLoadedColletions() { + async getLoadedCollections() { const data = []; const res = await this.getCollections({ type: ShowCollectionsType.Loaded,