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 7a5be17..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) { @@ -60,8 +61,103 @@ async function getCPUUsage() { }); } +//查询后端同步数据库数据量总量和top5 +function getDataTotalTop5(opts) { + return async function (ctx, next) { + const models = ctx.fs.dc.models; + try { + let total = await models.DbStatistics.sum('dbRecordCount') + let top5 = await models.DbStatistics.findAll({ + order: [["dbRecordCount", "desc"]], + limit: 5, + offset: 0, + include: [{ + model: models.DataSource, + include: [ + { + model: models.ResourceCatalog, + attributes: ['id', 'name'], + }] + }], + }) + ctx.status = 200; + ctx.body = { total, top5 }; + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { message: '查询后端同步数据库数据量总量和top5' } + } + } +} + + + +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, + getClusterInfo } diff --git a/api/app/lib/controllers/latestMetadata/index.js b/api/app/lib/controllers/latestMetadata/index.js index 46d8ed8..c4f6a95 100644 --- a/api/app/lib/controllers/latestMetadata/index.js +++ b/api/app/lib/controllers/latestMetadata/index.js @@ -3,7 +3,7 @@ const moment = require("moment/moment"); //获取资源目录 -async function getResourceCatalog (ctx) { +async function getResourceCatalog(ctx) { try { const models = ctx.fs.dc.models; const rslt = await models.ResourceCatalog.findAll({ @@ -20,7 +20,7 @@ async function getResourceCatalog (ctx) { } } //新建资源目录 -async function postResourceCatalog (ctx) { +async function postResourceCatalog(ctx) { try { const { name, code } = ctx.request.body; const models = ctx.fs.dc.models; @@ -49,7 +49,7 @@ async function postResourceCatalog (ctx) { } } //修改资源目录 -async function putResourceCatalog (ctx) { +async function putResourceCatalog(ctx) { try { const { id } = ctx.params; const { name, code, description } = ctx.request.body; @@ -78,7 +78,7 @@ async function putResourceCatalog (ctx) { } } //删除资源目录 -async function delResourceCatalog (ctx) { +async function delResourceCatalog(ctx) { const transaction = await ctx.fs.dc.orm.transaction(); try { const models = ctx.fs.dc.models; @@ -117,7 +117,7 @@ async function delResourceCatalog (ctx) { } } //获取库表元数据列表 -async function getMetadataDatabases (ctx) { +async function getMetadataDatabases(ctx) { try { const models = ctx.fs.dc.models; const { catalog, limit, offset, keywords, orderBy = 'createAt', orderDirection = 'desc', id = null, resourceId } = ctx.query; @@ -169,7 +169,7 @@ async function getMetadataDatabases (ctx) { } } //获取文件元数据列表 -async function getMetadataFiles (ctx) { +async function getMetadataFiles(ctx) { try { const models = ctx.fs.dc.models; const { catalog, limit, offset, keywords, orderBy = 'updateAt', orderDirection = 'desc', resourceId } = ctx.query; @@ -214,7 +214,7 @@ async function getMetadataFiles (ctx) { } } //获取接口元数据列表 -async function getMetadataRestapis (ctx) { +async function getMetadataRestapis(ctx) { try { const models = ctx.fs.dc.models; const { catalog, limit, offset, keywords, orderBy = 'createAt', orderDirection = 'desc', resourceId } = ctx.query; @@ -257,7 +257,7 @@ async function getMetadataRestapis (ctx) { } } //获取元数据模型 -async function getMetadataModels (ctx) { +async function getMetadataModels(ctx) { try { const models = ctx.fs.dc.models; const { modelTypes } = ctx.query; @@ -281,7 +281,7 @@ async function getMetadataModels (ctx) { } //新建库表元数据 -async function postMetadataDatabases (ctx) { +async function postMetadataDatabases(ctx) { try { const { name, code, catalog, parent } = ctx.request.body; const models = ctx.fs.dc.models; @@ -318,7 +318,7 @@ async function postMetadataDatabases (ctx) { } //修改库表元数据 -async function putMetadataDatabases (ctx) { +async function putMetadataDatabases(ctx) { try { const { id } = ctx.params; const { catalog, name, code } = ctx.request.body; @@ -354,7 +354,7 @@ async function putMetadataDatabases (ctx) { } } //删除库表元数据 -async function delMetadataDatabases (ctx) { +async function delMetadataDatabases(ctx) { const transaction = await ctx.fs.dc.orm.transaction(); try { const models = ctx.fs.dc.models; @@ -409,7 +409,7 @@ async function delMetadataDatabases (ctx) { } } //获取库表元数据基本信息 -async function getMetadataDatabasesById (ctx) { +async function getMetadataDatabasesById(ctx) { try { const models = ctx.fs.dc.models; const { id } = ctx.params; @@ -440,7 +440,7 @@ async function getMetadataDatabasesById (ctx) { } //打标元数据 -async function postTagMetadata (ctx) { +async function postTagMetadata(ctx) { const transaction = await ctx.fs.dc.orm.transaction(); try { const { tags, database, file, restapi } = ctx.request.body; @@ -481,7 +481,7 @@ async function postTagMetadata (ctx) { } //获取元数据已打标数据 -async function getTagMetadata (ctx) { +async function getTagMetadata(ctx) { try { const models = ctx.fs.dc.models; const { id } = ctx.params; @@ -525,7 +525,7 @@ async function getTagMetadata (ctx) { } } //申请资源 -async function postMetadataResourceApplications (ctx) { +async function postMetadataResourceApplications(ctx) { try { const { resourceName, applyBy, resourceType, resourceId } = ctx.request.body; if (!resourceName || !applyBy || !resourceType || !resourceId) { @@ -555,7 +555,7 @@ async function postMetadataResourceApplications (ctx) { } //获取元数据资源申请记录 -async function getMetadataResourceApplications (ctx) { +async function getMetadataResourceApplications(ctx) { try { const models = ctx.fs.dc.models; const { resourceNames, type } = ctx.query; @@ -577,7 +577,7 @@ async function getMetadataResourceApplications (ctx) { } //新建文件元数据 -async function postMetadataFiles (ctx) { +async function postMetadataFiles(ctx) { try { const { name, catalog, type, fileName } = ctx.request.body; const models = ctx.fs.dc.models; @@ -606,7 +606,7 @@ async function postMetadataFiles (ctx) { } } //修改文件元数据 -async function putMetadataFiles (ctx) { +async function putMetadataFiles(ctx) { try { const { id } = ctx.params; const { updateFileName } = ctx.query; @@ -646,7 +646,7 @@ async function putMetadataFiles (ctx) { } } //删除文件元数据 -async function delMetadataFiles (ctx) { +async function delMetadataFiles(ctx) { const transaction = await ctx.fs.dc.orm.transaction(); try { const models = ctx.fs.dc.models; @@ -694,7 +694,7 @@ async function delMetadataFiles (ctx) { } //新建接口元数据 -async function postMetadataRestapis (ctx) { +async function postMetadataRestapis(ctx) { try { const { name, catalog, method, url } = ctx.request.body; const models = ctx.fs.dc.models; @@ -724,7 +724,7 @@ async function postMetadataRestapis (ctx) { } //修改接口元数据 -async function putMetadataRestapis (ctx) { +async function putMetadataRestapis(ctx) { try { const { id } = ctx.params; const { catalog, name, method, url } = ctx.request.body; @@ -758,7 +758,7 @@ async function putMetadataRestapis (ctx) { } } //删除接口元数据 -async function delMetadataRestapis (ctx) { +async function delMetadataRestapis(ctx) { const transaction = await ctx.fs.dc.orm.transaction(); try { const models = ctx.fs.dc.models; @@ -806,7 +806,7 @@ async function delMetadataRestapis (ctx) { } //获取对表的库与字段信息 -async function listStructuredData (ctx) { +async function listStructuredData(ctx) { try { const models = ctx.fs.dc.models; const { id, parent } = ctx.query; diff --git a/api/app/lib/index.js b/api/app/lib/index.js index 8341704..644fe31 100644 --- a/api/app/lib/index.js +++ b/api/app/lib/index.js @@ -13,86 +13,88 @@ const schedule = require('./schedule') // const apiLog = require('./middlewares/api-log'); module.exports.entry = function (app, router, opts) { - app.fs.logger.log('info', '[FS-AUTH]', 'Inject auth and api mv into router.'); + app.fs.logger.log('info', '[FS-AUTH]', 'Inject auth and api mv into router.'); - app.fs.api = app.fs.api || {}; - app.fs.opts = opts || {}; - app.fs.utils = app.fs.utils || {}; - app.fs.api.authAttr = app.fs.api.authAttr || {}; - app.fs.api.logAttr = app.fs.api.logAttr || {}; + app.fs.api = app.fs.api || {}; + app.fs.opts = opts || {}; + app.fs.utils = app.fs.utils || {}; + app.fs.api.authAttr = app.fs.api.authAttr || {}; + app.fs.api.logAttr = app.fs.api.logAttr || {}; - // 顺序固定 ↓ - //redisConnect(app, opts) - socketConect(app, opts) + // 顺序固定 ↓ + //redisConnect(app, opts) + socketConect(app, opts) - // 实例其他平台请求方法 - paasRequest(app, opts) + // 实例其他平台请求方法 + paasRequest(app, opts) - // clickHouse 数据库 client - // clickHouseClient(app, opts) + // clickHouse 数据库 client + // clickHouseClient(app, opts) - // 工具类函数 - utils(app, opts) + // 工具类函数 + utils(app, opts) - // 定时任务 - schedule(app, opts) + // 定时任务 + schedule(app, opts) - //鉴权中间件 - router.use(authenticator(app, opts)); + //鉴权中间件 + router.use(authenticator(app, opts)); - // 日志记录 - // router.use(apiLog(app, opts)); + // 日志记录 + // router.use(apiLog(app, opts)); - router = routes(app, router, opts); + router = routes(app, router, opts); }; module.exports.models = function (dc) { - // dc = { orm: Sequelize对象, ORM: Sequelize, models: {} } - - // 模型关系摘出来 初始化之后再定义关系才行 - fs.readdirSync(path.join(__dirname, '/models')).forEach((filename) => { - require(`./models/${filename}`)(dc) - }); - - const { - DataSource, AcquisitionTask, Adapter, User, MetadataDatabase, MetadataFile, MetadataRestapi, AcquisitionLog, ResourceCatalog, - BusinessMetadataDatabase, BusinessMetadataFile, BusinessMetadataRestapi,ResourceConsumption,BusinessRule,StandardDoc,RestfulApi,RestfulApiRecord - } = dc.models; - - AcquisitionTask.belongsTo(DataSource, { foreignKey: 'dataSourceId', targetKey: 'id' }); - DataSource.hasMany(AcquisitionTask, { foreignKey: 'dataSourceId', sourceKey: 'id' }); - AcquisitionLog.belongsTo(AcquisitionTask, { foreignKey: 'task', targetKey: 'id' }); - AcquisitionTask.hasMany(AcquisitionLog, { foreignKey: 'task', sourceKey: 'id' }); - DataSource.belongsTo(ResourceCatalog, { foreignKey: 'mountPath', targetKey: 'id' }); - ResourceCatalog.hasMany(DataSource, { foreignKey: 'mountPath', sourceKey: 'id' }); - - DataSource.belongsTo(Adapter, { foreignKey: 'adapterId', targetKey: 'id' }); - Adapter.hasMany(DataSource, { foreignKey: 'adapterId', sourceKey: 'id' }); - - MetadataDatabase.belongsTo(User, { foreignKey: 'createBy', targetKey: 'id' }); - MetadataFile.belongsTo(User, { foreignKey: 'createBy', targetKey: 'id' }); - MetadataRestapi.belongsTo(User, { foreignKey: 'createBy', targetKey: 'id' }); - - BusinessMetadataDatabase.belongsTo(MetadataDatabase, { foreignKey: 'metadataDatabaseId', targetKey: 'id' }); - MetadataDatabase.hasMany(BusinessMetadataDatabase, { foreignKey: 'metadataDatabaseId', sourceKey: 'id' }); - - BusinessMetadataFile.belongsTo(MetadataFile, { foreignKey: 'metadataFileId', targetKey: 'id' }); - MetadataFile.hasMany(BusinessMetadataFile, { foreignKey: 'metadataFileId', sourceKey: 'id' }); - - BusinessMetadataRestapi.belongsTo(MetadataRestapi, { foreignKey: 'metadataRestapiId', targetKey: 'id' }); - MetadataRestapi.hasMany(BusinessMetadataRestapi, { foreignKey: 'metadataRestapiId', sourceKey: 'id' }); - - BusinessMetadataDatabase.belongsTo(User, { foreignKey: 'createBy', targetKey: 'id' }); - BusinessMetadataFile.belongsTo(User, { foreignKey: 'createBy', targetKey: 'id' }); - BusinessMetadataRestapi.belongsTo(User, { foreignKey: 'createBy', targetKey: 'id' }); - - ResourceConsumption.belongsTo(User, { foreignKey: 'applyBy', targetKey: 'id' ,as:"applyUser"}); - ResourceConsumption.belongsTo(User, { foreignKey: 'approveBy', targetKey: 'id',as:'approveUser' }); - + // dc = { orm: Sequelize对象, ORM: Sequelize, models: {} } + + // 模型关系摘出来 初始化之后再定义关系才行 + fs.readdirSync(path.join(__dirname, '/models')).forEach((filename) => { + require(`./models/${filename}`)(dc) + }); + + const { + DataSource, AcquisitionTask, Adapter, User, MetadataDatabase, MetadataFile, MetadataRestapi, AcquisitionLog, ResourceCatalog, + BusinessMetadataDatabase, BusinessMetadataFile, BusinessMetadataRestapi, ResourceConsumption, BusinessRule, StandardDoc, DbStatistics + , RestfulApi, RestfulApiRecord + } = dc.models; + + AcquisitionTask.belongsTo(DataSource, { foreignKey: 'dataSourceId', targetKey: 'id' }); + DataSource.hasMany(AcquisitionTask, { foreignKey: 'dataSourceId', sourceKey: 'id' }); + AcquisitionLog.belongsTo(AcquisitionTask, { foreignKey: 'task', targetKey: 'id' }); + AcquisitionTask.hasMany(AcquisitionLog, { foreignKey: 'task', sourceKey: 'id' }); + DataSource.belongsTo(ResourceCatalog, { foreignKey: 'mountPath', targetKey: 'id' }); + ResourceCatalog.hasMany(DataSource, { foreignKey: 'mountPath', sourceKey: 'id' }); + + DataSource.belongsTo(Adapter, { foreignKey: 'adapterId', targetKey: 'id' }); + Adapter.hasMany(DataSource, { foreignKey: 'adapterId', sourceKey: 'id' }); + + MetadataDatabase.belongsTo(User, { foreignKey: 'createBy', targetKey: 'id' }); + MetadataFile.belongsTo(User, { foreignKey: 'createBy', targetKey: 'id' }); + MetadataRestapi.belongsTo(User, { foreignKey: 'createBy', targetKey: 'id' }); + + BusinessMetadataDatabase.belongsTo(MetadataDatabase, { foreignKey: 'metadataDatabaseId', targetKey: 'id' }); + MetadataDatabase.hasMany(BusinessMetadataDatabase, { foreignKey: 'metadataDatabaseId', sourceKey: 'id' }); + + BusinessMetadataFile.belongsTo(MetadataFile, { foreignKey: 'metadataFileId', targetKey: 'id' }); + MetadataFile.hasMany(BusinessMetadataFile, { foreignKey: 'metadataFileId', sourceKey: 'id' }); + + BusinessMetadataRestapi.belongsTo(MetadataRestapi, { foreignKey: 'metadataRestapiId', targetKey: 'id' }); + MetadataRestapi.hasMany(BusinessMetadataRestapi, { foreignKey: 'metadataRestapiId', sourceKey: 'id' }); + + BusinessMetadataDatabase.belongsTo(User, { foreignKey: 'createBy', targetKey: 'id' }); + BusinessMetadataFile.belongsTo(User, { foreignKey: 'createBy', targetKey: 'id' }); + BusinessMetadataRestapi.belongsTo(User, { foreignKey: 'createBy', targetKey: 'id' }); + + ResourceConsumption.belongsTo(User, { foreignKey: 'applyBy', targetKey: 'id', as: "applyUser" }); + ResourceConsumption.belongsTo(User, { foreignKey: 'approveBy', targetKey: 'id', as: 'approveUser' }); + BusinessRule.belongsTo(StandardDoc, { foreignKey: 'ruleBasis', targetKey: 'id' }); StandardDoc.hasMany(BusinessRule, { foreignKey: 'ruleBasis', targetKey: 'id' }); - RestfulApi.belongsTo(RestfulApiRecord, { foreignKey: 'restServiceId', targetKey: 'id' }); - RestfulApiRecord.belongsTo(RestfulApi, { foreignKey: 'restServiceId', targetKey: 'id' }); + DbStatistics.belongsTo(DataSource, { foreignKey: 'sourceId', targetKey: 'id' }); + DataSource.hasMany(DbStatistics, { foreignKey: 'sourceId', sourceKey: 'id' }); + RestfulApiRecord.belongsTo(RestfulApi, { foreignKey: 'restServiceId', targetKey: 'id' }); }; diff --git a/api/app/lib/models/dbStatistics.js b/api/app/lib/models/dbStatistics.js new file mode 100644 index 0000000..07b2103 --- /dev/null +++ b/api/app/lib/models/dbStatistics.js @@ -0,0 +1,70 @@ +/* eslint-disable*/ + +'use strict'; + +module.exports = dc => { + const DataTypes = dc.ORM; + const sequelize = dc.orm; + const DbStatistics = sequelize.define("dbStatistics", { + id: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: "nextval(\"dbStatistics_id_seq\"::regclass)", + comment: null, + primaryKey: true, + field: "id", + autoIncrement: false + }, + dbName: { + type: DataTypes.STRING, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "dbName", + autoIncrement: false + }, + sourceId: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "sourceId", + autoIncrement: false + }, + dbRecordCount: { + type: DataTypes.BIGINT, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "dbRecordCount", + autoIncrement: false + }, + time: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "time", + autoIncrement: false + }, + description: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + comment: null, + primaryKey: false, + field: "description", + autoIncrement: false + } + }, { + tableName: "dbStatistics", + comment: "", + indexes: [] + }); + dc.models.DbStatistics = DbStatistics; + return DbStatistics; +}; \ No newline at end of file diff --git a/api/app/lib/routes/homepage/index.js b/api/app/lib/routes/homepage/index.js index eafcb5d..1b12fb6 100644 --- a/api/app/lib/routes/homepage/index.js +++ b/api/app/lib/routes/homepage/index.js @@ -7,4 +7,11 @@ 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/api/sequelize-automate.config.js b/api/sequelize-automate.config.js index 58703a6..b0ae7dd 100644 --- a/api/sequelize-automate.config.js +++ b/api/sequelize-automate.config.js @@ -26,7 +26,7 @@ module.exports = { dir: './app/lib/models', // 指定输出 models 文件的目录 typesDir: 'models', // 指定输出 TypeScript 类型定义的文件目录,只有 TypeScript / Midway 等会有类型定义 emptyDir: false, // !!! 谨慎操作 生成 models 之前是否清空 `dir` 以及 `typesDir` - // tables: ['safety_cultivate'], // 指定生成哪些表的 models,如 ['user', 'user_post'];如果为 null,则忽略改属性 + tables: ['dbStatistics'], // 指定生成哪些表的 models,如 ['user', 'user_post'];如果为 null,则忽略改属性 skipTables: [], // 指定跳过哪些表的 models,如 ['user'];如果为 null,则忽略改属性 tsNoCheck: false, // 是否添加 `@ts-nocheck` 注释到 models 文件中 ignorePrefix: ['t_',], // 生成的模型名称忽略的前缀,因为 项目中有以下表名是以 t_ 开头的,在实际模型中不需要, 可以添加多个 [ 't_data_', 't_',] ,长度较长的 前缀放前面 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
@@ -14,8 +21,9 @@ function DataShare(props) {
} + const leftData = [ - { key: '1', data: 2000, unit: '条', title: '共享库数据总量' }, + { key: '1', data: mathRound(dataTotal?.total), unit: '万条', title: '共享库数据总量' }, { key: '2', data: 2000, unit: '次', title: '访问接口总次数' }, { key: '3', data: 2000, unit: '个', title: '访问接口用户总数' }] const rightData = [ diff --git a/web/client/src/sections/homePage/components/dataTop5.js b/web/client/src/sections/homePage/components/dataTop5.js index b9ff15a..d3c3325 100644 --- a/web/client/src/sections/homePage/components/dataTop5.js +++ b/web/client/src/sections/homePage/components/dataTop5.js @@ -2,32 +2,25 @@ import React, { useEffect, useState } from 'react' import Box from './public/table-card'; import ReactEcharts from 'echarts-for-react'; import './style.less'; +import { useFsRequest } from '$utils'; +import { mathRound } from './util'; function DataTop5(props) { const { cardContentHeight } = props; + const { data: dataTotal = {} } = useFsRequest({ + url: 'homepage/datatotal/top5', + pollingInterval: 1000 * 60, + cacheKey: 'datatotal', + }); + const renderBody = () => { - let chartData = [ - { - name: '工商局', - value: 12245, - }, - { - name: '人社局', - value: 11211, - }, - { - name: '市政单位', - value: 11165, - }, - { - name: '住建局', - value: 11145, - }, - { - name: '环保局', - value: 11128, - }, - ] + let chartData = dataTotal?.top5?.map(x => { + return { + name: x?.dataSource?.resourceCatalog?.name, + value: mathRound(x.dbRecordCount), + } + }) || [] + let options = { xAxis: { splitLine: { @@ -44,6 +37,27 @@ function DataTop5(props) { show: false, }, }, + tooltip: { + confine: true, + trigger: 'axis', + axisPointer: { + type: 'shadow', + }, + backgroundColor: 'rgba(13,30,44, 0.7)', + borderColor: 'rgba(3, 65, 118, 0.8)', + textStyle: { + color: '#fff', + }, + formatter: function (params) { + var name = params[0].name + if (name.length > 20) { + name = name.replace(/(.{20})/g, '$1
') // 每 30 个字符添加一个换行符 + } + var content = name + + return content + ' : ' + params[0].value + '万条' + } + }, grid: { top: 13, bottom: -10, @@ -64,14 +78,15 @@ function DataTop5(props) { }, formatter(value, index) { let str = '', num = 'TOP' + (index + 1) + let valueHandle = value.length > 10 ? value.substring(0, 10) + '...' : value if (index === 0) { - str = '{a| ' + num + '}{title| ' + value + '}' + str = '{a| ' + num + '}{title| ' + valueHandle + '}' } else if (index === 1) { - str = '{b| ' + num + '}{title| ' + value + '}' + str = '{b| ' + num + '}{title| ' + valueHandle + '}' } else if (index === 2) { - str = '{c| ' + num + '}{title| ' + value + '}' + str = '{c| ' + num + '}{title| ' + valueHandle + '}' } else { - str = '{d| ' + num + '}{title| ' + value + '}' + str = '{d| ' + num + '}{title| ' + valueHandle + '}' } return str }, diff --git a/web/client/src/sections/homePage/components/nodeResource.js b/web/client/src/sections/homePage/components/nodeResource.js index 981c240..e142a1e 100644 --- a/web/client/src/sections/homePage/components/nodeResource.js +++ b/web/client/src/sections/homePage/components/nodeResource.js @@ -1,22 +1,27 @@ import React, { useEffect, useState } from 'react' import Box from './public/table-card'; +import { ApiTable, useFsRequest } from '$utils'; import './style.less'; function NodeResource(props) { + const { data: cluters = {} } = useFsRequest({ + url: 'homepage/datatotal/cluters', + pollingInterval: 1000 * 20, + }); const renderBody = () => { return
-
97.2%
+
{cluters?.disk}%
硬盘
-
97.2%
+
{cluters?.memory}%
内存
-
97.2%
+
{cluters?.cpu}%
CPU
diff --git a/web/client/src/sections/homePage/components/style.less b/web/client/src/sections/homePage/components/style.less index d71fa30..e3274cb 100644 --- a/web/client/src/sections/homePage/components/style.less +++ b/web/client/src/sections/homePage/components/style.less @@ -123,7 +123,7 @@ .data_total { position: absolute; bottom: 13%; - right: 7%; + left: 79%; .data_number { line-height: 25px; diff --git a/web/client/src/sections/homePage/components/util.js b/web/client/src/sections/homePage/components/util.js new file mode 100644 index 0000000..1a4c117 --- /dev/null +++ b/web/client/src/sections/homePage/components/util.js @@ -0,0 +1,3 @@ +export const mathRound = (number) => { + return number ? Math.round(number / 1000) / 10 : 0 +} \ No newline at end of file