diff --git a/api/.vscode/launch.json b/api/.vscode/launch.json index 21014f1..46c4981 100644 --- a/api/.vscode/launch.json +++ b/api/.vscode/launch.json @@ -17,7 +17,8 @@ // 研发 "-g postgres://FashionAdmin:123456@10.8.30.39:5432/GovernmentDataResourceCenter", // "-g postgres://FashionAdmin:123456@10.8.30.156:5432/gdrcenter", - "-b http://10.8.30.161:31420" + "-b http://10.8.30.161:31420", + "-s http://10.8.30.161:32258" ] }, { diff --git a/api/app/lib/controllers/backups/index.js b/api/app/lib/controllers/backups/index.js index e7ce69f..b2fa242 100644 --- a/api/app/lib/controllers/backups/index.js +++ b/api/app/lib/controllers/backups/index.js @@ -134,6 +134,7 @@ function restore(opts) { const { id, source, databases: { database, host, password, port, user } } = ctx.request.body await models.Backups.update({ state: '恢复中', + restoreStart: moment() }, { where: { id: id } }) //调用后端备份接口 const url = backupsUrl + `/restoreDB?dbHost=${host}&dbPort=${port}&user=${user}&password=${password}&dbName=${database}&backFileName=${source}`; @@ -142,6 +143,7 @@ function restore(opts) { models.Backups.update({ state: code == 200 ? '恢复成功' : '恢复失败', log: code == 200 ? '' : message, + restoreEnd: moment(), restoreDatabases: ctx.request.body.databases }, { where: { id: id } }) if (code != 200) ctx.fs.logger.error(`path: ${ctx.path}, error: ${message}`); diff --git a/api/app/lib/controllers/homepage/index.js b/api/app/lib/controllers/homepage/index.js index 4e1b9f7..84a9ae4 100644 --- a/api/app/lib/controllers/homepage/index.js +++ b/api/app/lib/controllers/homepage/index.js @@ -2,6 +2,7 @@ const moment = require('moment'); const diskinfo = require('diskinfo'); const os = require('os-utils'); +const request = require("superagent"); function getNodeResources(opts) { return async function (ctx, next) { @@ -90,8 +91,73 @@ function getDataTotalTop5(opts) { } + +async function queryToken_(ctx) { + let rslt = null; + try { + const { k8s } = ctx; + const params = { + grant_type: 'password', + username: 'admin', + password: 'Fashion123', + client_id: 'kubesphere', + client_secret: 'kubesphere', + } + const url = k8s + '/oauth/token' + rslt = await request.post(url).send(params) + .set('Content-Type', 'application/x-www-form-urlencoded') + + return (rslt || {}).body || null; + } catch (err) { + throw err + } + +} + +function mathRound(use, total) { + return Math.round(parseFloat(use) / parseFloat(total) * 1000) / 10 +} + +function getClusterInfo(opts) { + return async function (ctx, next) { + const { k8s } = opts; + let errMsg = { message: '获取节点资源失败' } + try { + const token = await queryToken_(opts); + const url = k8s + '/kapis/monitoring.kubesphere.io/v1alpha3/cluster?metrics_filter=cluster_cpu_usage%7Ccluster_cpu_total%7Ccluster_memory_usage_wo_cache%7Ccluster_memory_total%7Ccluster_disk_size_usage%7Ccluster_disk_size_capacity%7Ccluster_pod_running_count%7Ccluster_pod_quota%24' + let res = await request.get(url) + .set('Authorization', `Bearer ${token.access_token}`) + .set('Content-Type', 'application/json;charset=utf-8'); + if (res.body && res.body.results) { + let results = res.body.results; + let cpuUsage = ((results.find(s => s.metric_name == 'cluster_cpu_usage') || {}).data || {}).result[0].value[1]; + let cpuTotal = ((results.find(s => s.metric_name == 'cluster_cpu_total') || {}).data || {}).result[0].value[1]; + let diskUsage = ((results.find(s => s.metric_name == 'cluster_disk_size_usage') || {}).data || {}).result[0].value[1]; + let diskTotal = ((results.find(s => s.metric_name == 'cluster_disk_size_capacity') || {}).data || {}).result[0].value[1]; + let memoryUsage = ((results.find(s => s.metric_name == 'cluster_memory_usage_wo_cache') || {}).data || {}).result[0].value[1]; + let memoryTotal = ((results.find(s => s.metric_name == 'cluster_memory_total') || {}).data || {}).result[0].value[1]; + ctx.status = 200; + ctx.body = { + cpu: mathRound(cpuUsage, cpuTotal), + disk: mathRound(diskUsage, diskTotal), + memory: mathRound(memoryUsage, memoryTotal), + }; + } else { + ctx.status = 400; + ctx.body = errMsg; + } + + + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = errMsg + } + } +} + module.exports = { getNodeResources, - getDataTotalTop5 - + getDataTotalTop5, + getClusterInfo } diff --git a/api/app/lib/routes/homepage/index.js b/api/app/lib/routes/homepage/index.js index 682c55d..1b12fb6 100644 --- a/api/app/lib/routes/homepage/index.js +++ b/api/app/lib/routes/homepage/index.js @@ -6,8 +6,12 @@ module.exports = function (app, router, opts, AuthCode) { app.fs.api.logAttr['GET/homepage/node/resources'] = { content: '获取节点资源信息', visible: true }; router.get('/homepage/node/resources', backups.getNodeResources(opts)) - + app.fs.api.logAttr['GET/homepage/datatotal/top5'] = { content: '获取数据总量和top5', visible: true }; router.get('/homepage/datatotal/top5', backups.getDataTotalTop5(opts)) + app.fs.api.logAttr['GET/homepage/cluters'] = { content: '获取集群资源节点信息', visible: true }; + router.get('/homepage/datatotal/cluters', backups.getClusterInfo(opts)) + + }; diff --git a/api/config.js b/api/config.js index 9e14778..a4be807 100644 --- a/api/config.js +++ b/api/config.js @@ -11,6 +11,7 @@ const dev = process.env.NODE_ENV == 'development'; args.option(['p', 'port'], '启动端口'); args.option(['g', 'pg'], 'postgre 服务 URL'); args.option(['b', 'backups'], '后端数据库备份恢复接口地址'); +args.option(['s', 'kubesphere'], 'kubesphere地址'); const flags = args.parse(process.argv); @@ -23,7 +24,9 @@ const QINIU_AK = process.env.ANXINCLOUD_QINIU_ACCESSKEY || flags.qnak; const QINIU_SK = process.env.ANXINCLOUD_QINIU_SECRETKEY || flags.qnsk; const BACKUPS_URL = process.env.BACKUPS_URL || flags.backups; -if (!DB || !BACKUPS_URL) { +const KUBESPHERE_URL = process.env.KUBESPHERE_URL || flags.kubesphere; + +if (!DB || !BACKUPS_URL || !KUBESPHERE_URL) { console.log('缺少启动参数,异常退出'); args.showHelp(); process.exit(-1); @@ -68,7 +71,8 @@ const product = { } }, pssaRequest: [], - backupsUrl: BACKUPS_URL + backupsUrl: BACKUPS_URL, + k8s: KUBESPHERE_URL } } ], diff --git a/web/client/src/sections/backups/containers/backupTask.js b/web/client/src/sections/backups/containers/backupTask.js index adf2242..669d192 100644 --- a/web/client/src/sections/backups/containers/backupTask.js +++ b/web/client/src/sections/backups/containers/backupTask.js @@ -96,17 +96,19 @@ function Member(props) { title: '恢复数据源', dataIndex: 'restoreDatabases', width: '8%', - render: (text, record) => record?.restoreDatabases?.database + render: (text, record) => record?.restoreDatabases?.displayName }, { title: '恢复开始时间', width: '7%', dataIndex: 'restoreStart', + render: (text, record) => { return record?.restoreStart ? moment(record?.restoreStart).format('YYYY-MM-DD HH:mm:ss') : '-' } }, { title: '恢复结束时间', width: '7%', dataIndex: 'restoreEnd', + render: (text, record) => { return record?.restoreEnd ? moment(record?.restoreEnd).format('YYYY-MM-DD HH:mm:ss') : '-' } }, { title: '异常日志', diff --git a/web/client/src/sections/homePage/components/dataShare.js b/web/client/src/sections/homePage/components/dataShare.js index 020b564..dcae604 100644 --- a/web/client/src/sections/homePage/components/dataShare.js +++ b/web/client/src/sections/homePage/components/dataShare.js @@ -1,8 +1,15 @@ import React, { useEffect, useState } from 'react' import Box from './public/table-card'; - +import { ApiTable, useFsRequest } from '$utils'; +import { mathRound } from './util'; function DataShare(props) { + const { data: dataTotal = {} } = useFsRequest({ + url: 'homepage/datatotal/top5', + pollingInterval: 1000 * 60, + cacheKey: 'datatotal', + }); + const renderItem = (s) => { return