diff --git a/web/console/Wrapper.tsx b/web/console/Wrapper.tsx index d9fe7992a..03a820248 100644 --- a/web/console/Wrapper.tsx +++ b/web/console/Wrapper.tsx @@ -77,6 +77,11 @@ interface RouterConfig { /** 基础的侧边栏导航栏配置 */ const commonRouterConfig: RouterConfig[] = [ + { + url: '/tkestack/overview', + title: '概览', + watchModule: ConsoleModuleEnum.Monitor + }, { url: '/tkestack/cluster', title: '集群管理', @@ -162,12 +167,12 @@ const commonRouterConfig: RouterConfig[] = [ { url: '/tkestack/log', title: '日志采集', - watchModule: ConsoleModuleEnum.LogAgent, + watchModule: ConsoleModuleEnum.LogAgent }, { url: '/tkestack/log/setting', title: '日志组件', - watchModule: ConsoleModuleEnum.LogAgent, + watchModule: ConsoleModuleEnum.LogAgent }, { url: '/tkestack/persistent-event', @@ -229,8 +234,8 @@ const businessCommonRouterConfig: RouterConfig[] = [ url: '/tkestack-project/notify', title: '通知设置', watchModule: ConsoleModuleEnum.Notify - }, - ], + } + ] }, { title: '运维中心', @@ -242,7 +247,7 @@ const businessCommonRouterConfig: RouterConfig[] = [ watchModule: ConsoleModuleEnum.LogAgent } ] - }, + } ]; interface ConsoleWrapperProps { diff --git a/web/console/index.tsx b/web/console/index.tsx index 63e0f61bf..b496810c5 100644 --- a/web/console/index.tsx +++ b/web/console/index.tsx @@ -25,6 +25,7 @@ import { Init_Forbiddent_Config } from './helpers/reduceNetwork'; // 公有云的图表组件为异步加载,这里为了减少路径配置,还是保留为同步加载,预先import即可变成不split import '@tencent/tchart/build/ChartsComponents'; import { BlankPage } from './blankPage'; +import { Overview } from '@src/modules/overview'; insertCSS( 'hidden-checkbox', @@ -141,7 +142,18 @@ Entry.register({ ) }, - + /** + * @url https://{{domain}}/tkestack/overview + */ + overview: { + title: t('概览 - TKEStack'), + container: ( + + + + + ) + }, /** * @url https://{{domain}}/tkestack/cluster */ diff --git a/web/console/public/static/icon/overviewBlack.svg b/web/console/public/static/icon/overviewBlack.svg new file mode 100644 index 000000000..de5ae0754 --- /dev/null +++ b/web/console/public/static/icon/overviewBlack.svg @@ -0,0 +1,25 @@ + + + + Rectangle 7 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/console/public/static/icon/overviewCluster.svg b/web/console/public/static/icon/overviewCluster.svg new file mode 100644 index 000000000..fcfc4963b --- /dev/null +++ b/web/console/public/static/icon/overviewCluster.svg @@ -0,0 +1,38 @@ + + + rongqijiqun + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/console/public/static/icon/overviewGithub.svg b/web/console/public/static/icon/overviewGithub.svg new file mode 100644 index 000000000..4e7a6874d --- /dev/null +++ b/web/console/public/static/icon/overviewGithub.svg @@ -0,0 +1,21 @@ + + + 位图 + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/console/public/static/icon/overviewUser.svg b/web/console/public/static/icon/overviewUser.svg new file mode 100644 index 000000000..157767fc9 --- /dev/null +++ b/web/console/public/static/icon/overviewUser.svg @@ -0,0 +1,36 @@ + + + jiaoseguanli + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/console/src/modules/overview/WebAPI.ts b/web/console/src/modules/overview/WebAPI.ts new file mode 100644 index 000000000..beeb0e0b6 --- /dev/null +++ b/web/console/src/modules/overview/WebAPI.ts @@ -0,0 +1,21 @@ +import { RequestParams } from './../common/models/requestParams'; +import { Method, reduceNetworkRequest } from '@helper/reduceNetwork'; +import { OperationResult, RecordSet, uuid } from '@tencent/ff-redux'; + +export async function fetchClusteroverviews(query) { + let url = 'apis/monitor.tkestack.io/v1/clusteroverviews'; + let params: RequestParams = { + method: Method.post, + url, + data: { + apiVersion: 'monitor.tkestack.io/v1', + kind: 'ClusterOverview' + } + }; + + let response = await reduceNetworkRequest(params); + if (response.code === 0) { + console.log(response); + return response.data.result; + } +} diff --git a/web/console/src/modules/overview/actions/overviewActions.ts b/web/console/src/modules/overview/actions/overviewActions.ts new file mode 100644 index 000000000..9c13cb926 --- /dev/null +++ b/web/console/src/modules/overview/actions/overviewActions.ts @@ -0,0 +1,25 @@ +import { RootState, ClusterOverview, ClusterOverviewFilter } from './../models/RootState'; +import { Dispatch } from 'redux'; +import * as ActionType from '../constants/ActionType'; +import { FetchState, generateFetcherActionCreator, createFFObjectActions } from '@tencent/ff-redux'; + +import { cloneDeep } from '../../common/utils'; +import * as WebAPI from '../WebAPI'; + +type GetState = () => RootState; + +export const overviewActions = { + clusterOverActions: createFFObjectActions({ + actionName: ActionType.ClusterOverview, + fetcher: async (query, getState: GetState) => { + let response = await WebAPI.fetchClusteroverviews(query); + return response; + }, + getRecord: (getState: GetState) => { + return getState().clusterOverview; + }, + onFinish: (record, dispatch, getState: GetState) => {} + }) +}; + +export type OverviewActionsType = typeof overviewActions; diff --git a/web/console/src/modules/overview/components/ClusterDetailPanel.tsx b/web/console/src/modules/overview/components/ClusterDetailPanel.tsx new file mode 100644 index 000000000..88f6d40fa --- /dev/null +++ b/web/console/src/modules/overview/components/ClusterDetailPanel.tsx @@ -0,0 +1,177 @@ +import * as React from 'react'; +import { Card, Row, Col, MetricsBoard, Icon, Text, SearchBox, Button, Bubble, List } from '@tencent/tea-component'; +import { ClusterOverview, ClusterDetail } from '../models/RootState'; +import { clusterStatus } from '../constants/Config'; +export function ClusterDetailPanel(props: { clusterData: ClusterOverview }) { + let { clusterData } = props; + let isLodingDone = !!clusterData; + let [search, setSearch] = React.useState(''); + let clusterList = isLodingDone ? clusterData.clusters : []; + if (search) { + clusterList = clusterList.filter(item => item.clusterID.includes(search)); + } + return ( + + + + {clusterList.map((cluster, index) => _renderClusterCard(cluster, index))} + + + ); +} + +function _renderClusterCard(cluster: ClusterDetail, index: number) { + let masterErrorTips = []; + if (!cluster.etcdHealthy) { + masterErrorTips.push( + + etcd异常 + + ); + } + if (!cluster.schedulerHealthy) { + masterErrorTips.push( + + scheduler异常 + + ); + } + if (!cluster.controllerManagerHealthy) { + masterErrorTips.push( + + controllerManager异常 + + ); + } + + let isUnlink = cluster.clusterPhase !== 'Running'; + let isFailed = cluster.clusterPhase === 'Failed'; + + return ( + + + + {cluster.clusterPhase !== 'Running' && ( + + + + )} + + } + style={{ padding: '15px 10px' }} + > + + +
+ {isFailed ? '-' : cluster.cpuRequestRate} + CPU利用率 +
+ {`总数: ${cluster.cpuCapacity}核 Request已分配: ${ + isFailed ? '-' : cluster.cpuAllocatableRate + }`} +
+
+ + +
+ {isFailed ? '-' : cluster.memRequestRate} + 内存利用率 +
+ {`总数: ${ + isFailed ? '-' : (cluster.memCapacity / 1.0 / 1024 / 1024 / 1024).toPrecision(3) + }GB Request已分配: ${isFailed ? '-' : cluster.memAllocatableRate}`} +
+
+ + + + + 节点( + + ) + + + Workload( + + ) + + {'Master&ETCD'} + + + + + + {cluster.nodeAbnormal > 0 ? ( + <> + 异常 + + + + + ) : ( + 正常 + )} + + + {cluster.workloadAbnormal > 0 ? ( + <> + 异常 + + + + + ) : ( + 正常 + )} + + + {!cluster.etcdHealthy || !cluster.controllerManagerHealthy || !cluster.schedulerHealthy ? ( + <> + 异常 + + + + + ) : ( + 正常 + )} + + + +
+
+
+ ); +} diff --git a/web/console/src/modules/overview/components/ClusterOverview.tsx b/web/console/src/modules/overview/components/ClusterOverview.tsx new file mode 100644 index 000000000..716ec1b73 --- /dev/null +++ b/web/console/src/modules/overview/components/ClusterOverview.tsx @@ -0,0 +1,95 @@ +import * as React from 'react'; +import { Card, Row, Col, MetricsBoard, Icon, Text } from '@tencent/tea-component'; +import { ClusterOverview } from '../models/RootState'; +export function ClusterOverviewPanel(props: { clusterData: ClusterOverview }) { + let { clusterData } = props; + let isLodingDone = !!clusterData; + return ( + + + + + + {clusterData.clusterCount} + {'个'} + {clusterData.clusterAbnormal > 0 && ( + + (异常 {clusterData.clusterAbnormal} 个) + + )} + + ) : ( + '-' + ) + } + /> + + + + {clusterData.nodeCount} + {'个'} + {clusterData.nodeAbnormal > 0 && ( + + (异常 {clusterData.nodeAbnormal} 个) + + )} + + ) : ( + '-' + ) + } + /> + + + + {clusterData.workloadCount} + {'个'} + {clusterData.workloadAbnormal > 0 && ( + + (异常 {clusterData.workloadAbnormal} 个) + + )} + + ) : ( + '-' + ) + } + /> + + + + {clusterData.projectCount} + {'个'} + {clusterData.projectAbnormal > 0 && ( + + (异常 {clusterData.projectAbnormal} 个) + + )} + + ) : ( + '-' + ) + } + /> + + + + + ); +} diff --git a/web/console/src/modules/overview/components/OverviewApp.tsx b/web/console/src/modules/overview/components/OverviewApp.tsx new file mode 100644 index 000000000..ca021e4e8 --- /dev/null +++ b/web/console/src/modules/overview/components/OverviewApp.tsx @@ -0,0 +1,70 @@ +import * as React from 'react'; +import { connect, Provider } from 'react-redux'; + +import { bindActionCreators } from '@tencent/ff-redux'; +import { ContentView, Row, Col } from '@tencent/tea-component'; + +import { ResetStoreAction } from '../../../../helpers'; +import { overviewActions } from '../actions/overviewActions'; +import { router } from '../router'; +import { configStore } from '../stores/RootStore'; +import { OverviewHeadPanel } from './OverviewHeadPanel'; +import { RootState } from '../models/RootState'; +import { ClusterOverviewPanel } from './ClusterOverview'; +import { QuickHelpPanel } from './QuickHelpPanel'; +import { TipsPanel } from './TipsPanel'; +import { ClusterDetailPanel } from './ClusterDetailPanel'; +const store = configStore(); + +export class OverviewAppContainer extends React.Component { + // 页面离开时,清空store + componentWillUnmount() { + store.dispatch({ type: ResetStoreAction }); + } + + render() { + return ( + + + + ); + } +} + +export interface RootProps extends RootState { + actions?: typeof overviewActions; +} + +const mapDispatchToProps = dispatch => + Object.assign({}, bindActionCreators({ actions: overviewActions }, dispatch), { dispatch }); + +@connect(state => state, mapDispatchToProps) +@((router.serve as any)()) +class OverviewApp extends React.Component { + render() { + let { clusterOverview } = this.props; + return ( + + + + + + + + + + + + + + + + + + ); + } +} diff --git a/web/console/src/modules/overview/components/OverviewHeadPanel.tsx b/web/console/src/modules/overview/components/OverviewHeadPanel.tsx new file mode 100644 index 000000000..5dc23a0d2 --- /dev/null +++ b/web/console/src/modules/overview/components/OverviewHeadPanel.tsx @@ -0,0 +1,16 @@ +import * as React from 'react'; + +import { t } from '@tencent/tea-app/lib/i18n'; + +import { RootProps } from './OverviewApp'; + +export class OverviewHeadPanel extends React.Component { + componentDidMount() { + this.props.actions.clusterOverActions.applyFilter({}); + } + render() { + let { actions } = this.props; + + return

{t('概览')}

; + } +} diff --git a/web/console/src/modules/overview/components/QuickHelpPanel.tsx b/web/console/src/modules/overview/components/QuickHelpPanel.tsx new file mode 100644 index 000000000..b5e68c92b --- /dev/null +++ b/web/console/src/modules/overview/components/QuickHelpPanel.tsx @@ -0,0 +1,48 @@ +import * as React from 'react'; +import { Card, List, Button } from '@tencent/tea-component'; +export function QuickHelpPanel() { + return ( + + +

快速入口

+
+ + + + logo + + + + logo + + + + logo + + + + +
+ ); +} diff --git a/web/console/src/modules/overview/components/TipsPanel.tsx b/web/console/src/modules/overview/components/TipsPanel.tsx new file mode 100644 index 000000000..76a2e34c9 --- /dev/null +++ b/web/console/src/modules/overview/components/TipsPanel.tsx @@ -0,0 +1,49 @@ +import * as React from 'react'; +import { Card, List, Icon, Text, Button } from '@tencent/tea-component'; +export function TipsPanel() { + return ( + + +

实用提示

+
+ + + + logo +
+ + 平台实验室 + + 体验平台最新功能 + +
+
+ + logo +
+ + 使用指引 + + 通过创建业务,管理集群资源配额 + +
+
+
+
+
+ ); +} diff --git a/web/console/src/modules/overview/constants/ActionType.ts b/web/console/src/modules/overview/constants/ActionType.ts new file mode 100644 index 000000000..466d6b1db --- /dev/null +++ b/web/console/src/modules/overview/constants/ActionType.ts @@ -0,0 +1 @@ +export const ClusterOverview = 'ClusterOverview'; diff --git a/web/console/src/modules/overview/constants/Config.ts b/web/console/src/modules/overview/constants/Config.ts new file mode 100644 index 000000000..8fd537ac1 --- /dev/null +++ b/web/console/src/modules/overview/constants/Config.ts @@ -0,0 +1,37 @@ +import { t } from '@tencent/tea-app/lib/i18n'; + +export const canNotOperateCluster = { + clusterStatus: ['Initializing', 'Running', 'Failed', 'Upgrading', 'Terminating'] +}; + +/** 集群状态 */ +export const clusterStatus = { + Initializing: { + text: t('创建中'), + classname: 'text-restart' + }, + Running: { + text: t('运行中'), + classname: 'text-success' + }, + Terminating: { + text: t('删除中'), + classname: 'text-restart' + }, + Scaling: { + text: t('规模调整中'), + classname: 'text-restart' + }, + Upgrading: { + text: t('升级中'), + classname: 'text-restart' + }, + Failed: { + text: t('异常'), + classname: 'text-danger' + }, + '-': { + text: '-', + classname: 'text-restart' + } +}; diff --git a/web/console/src/modules/overview/index.tsx b/web/console/src/modules/overview/index.tsx new file mode 100644 index 000000000..d5826d686 --- /dev/null +++ b/web/console/src/modules/overview/index.tsx @@ -0,0 +1,9 @@ +import * as React from 'react'; + +import { OverviewAppContainer } from './components/OverviewApp'; + +export class Overview extends React.Component { + render() { + return ; + } +} diff --git a/web/console/src/modules/overview/models/RootState.ts b/web/console/src/modules/overview/models/RootState.ts new file mode 100644 index 000000000..bb9f23502 --- /dev/null +++ b/web/console/src/modules/overview/models/RootState.ts @@ -0,0 +1,46 @@ +import { FFObjectModel } from './../../../../lib/ff-redux/src/object/Model'; +import { FetcherState } from '@tencent/ff-redux'; + +import { RegionCluster } from '../../common/models'; + +export interface RootState { + clusterOverview?: FFObjectModel; +} + +export interface ClusterOverviewFilter {} + +export interface ClusterOverview { + clusterCount: number; + clusterAbnormal: number; + nodeCount: number; + nodeAbnormal: number; + workloadCount: number; + workloadAbnormal: number; + projectCount: number; + projectAbnormal: number; + clusters: ClusterDetail[]; +} + +export interface ClusterDetail { + clusterID: string; + clusterPhase: string; + nodeCount: number; + nodeAbnormal: number; + workloadCount: number; + workloadAbnormal: number; + cpuRequest: number; + cpuLimit: number; + cpuCapacity: number; + cpuAllocatable: number; + cpuRequestRate: string; + cpuAllocatableRate: string; + memRequest: number; + memLimit: number; + memCapacity: number; + memAllocatable: number; + memRequestRate: string; + memAllocatableRate: string; + schedulerHealthy: boolean; + controllerManagerHealthy: boolean; + etcdHealthy: boolean; +} diff --git a/web/console/src/modules/overview/reducers/RootReducer.ts b/web/console/src/modules/overview/reducers/RootReducer.ts new file mode 100644 index 000000000..dcbe079f4 --- /dev/null +++ b/web/console/src/modules/overview/reducers/RootReducer.ts @@ -0,0 +1,10 @@ +import { ClusterOverview, ClusterOverviewFilter } from './../models/RootState'; +import { combineReducers } from 'redux'; +import * as ActionType from '../constants/ActionType'; +import { router } from '../router'; +import { createFFObjectReducer } from '@tencent/ff-redux'; + +export const RootReducer = combineReducers({ + route: router.getReducer(), + clusterOverview: createFFObjectReducer(ActionType.ClusterOverview) +}); diff --git a/web/console/src/modules/overview/router.ts b/web/console/src/modules/overview/router.ts new file mode 100644 index 000000000..9faeea2f6 --- /dev/null +++ b/web/console/src/modules/overview/router.ts @@ -0,0 +1,10 @@ +import { Router } from '../../../helpers/Router'; + +/** + * @param sub 二级菜单栏的一级导航 + * @param mode 当前的展示内容类型 list | create | update | detail + * @param type 三级菜单栏所对应的资源 resource | service …… + * @param resourceName 资源的名称 deployment 等 + * @param tab tab页面 + */ +export const router = new Router('/tkestack', {}); diff --git a/web/console/src/modules/overview/stores/RootStore.ts b/web/console/src/modules/overview/stores/RootStore.ts new file mode 100644 index 000000000..528dcd48c --- /dev/null +++ b/web/console/src/modules/overview/stores/RootStore.ts @@ -0,0 +1,17 @@ +import { createStore } from '@tencent/ff-redux'; + +import { generateResetableReducer } from '../../../../helpers'; +import { RootReducer } from '../reducers/RootReducer'; + +export function configStore() { + const store = createStore(generateResetableReducer(RootReducer)); + + // hot reloading + if (typeof module !== 'undefined' && (module as any).hot) { + (module as any).hot.accept('../reducers/RootReducer', () => { + store.replaceReducer(generateResetableReducer(require('../reducers/RootReducer').RootReducer)); + }); + } + + return store; +}