diff --git a/api/app/lib/controllers/operationData/index.js b/api/app/lib/controllers/operationData/index.js new file mode 100644 index 0000000..0fed92d --- /dev/null +++ b/api/app/lib/controllers/operationData/index.js @@ -0,0 +1,154 @@ +'use strict'; + +const moment = require('moment') +//const db = require('../') +async function getFailureTime(ctx) { + const sequelize = ctx.fs.dc.orm + try { + const res = await sequelize.query( + `SELECT substring(to_char(w.wmonth,'yyyy-mm'),1,7) wmonth,COALESCE(e.counts,0) count + from ( SELECT COUNT(1) counts,to_char(maintenance_record.occurrence_time, 'yyyy-mm') months + FROM maintenance_record + GROUP BY months + ) e + RIGHT JOIN + ( + SELECT to_date(EXTRACT(YEAR FROM (current_date - INTERVAL '1 month' * (t - 1))) ||'-' + || EXTRACT(MONTH FROM (current_date - INTERVAL '1 month' * (t - 1))),'yyyy-mm')as wmonth + from generate_series(1, 12) as t + ) w on e.months = substring(to_char(w.wmonth,'yyyy-mm'),1,7) + GROUP BY w.wmonth,e.counts + ORDER BY w.wmonth desc` + ) + console.log('111112232', res) + let resList = [] + if (res.length > 0) { + res[0].forEach((item) => { + resList.push(item) + }) + + } + ctx.status = 200 + ctx.body = resList + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + message: '查询故障发生时间失败' + } + } + +} +async function getSystemAvailability(ctx) { + const sequelize = ctx.fs.dc.orm + try { + const res = await sequelize.query( + `SELECT substring(to_char(w.wmonth,'yyyy-mm'),1,7) wmonth, + COALESCE((w.seconds-e.counts)/w.seconds,0) ability + from ( + SELECT to_char(maintenance_record.occurrence_time, 'yyyy-MM') months, + SUM(maintenance_record.interrupt_duration) counts + FROM maintenance_record + where maintenance_record.occurrence_time is not null + GROUP BY months + ) e + RIGHT JOIN + ( + SELECT to_date(EXTRACT(YEAR FROM (current_date - INTERVAL '1 month' * (n - 1))) ||'-'|| EXTRACT(MONTH FROM (current_date - INTERVAL '1 month' * (n - 1))),'yyyy-mm') as wmonth, + (EXTRACT(DAY FROM (DATE_TRUNC('MONTH', current_date - INTERVAL '1 month' * (n - 1) + INTERVAL '1 MONTH') - DATE_TRUNC('MONTH', current_date - INTERVAL '1 month' * (n - 1)) - INTERVAL '1 DAY'))+1)* 24 * 60 * 60 AS seconds + from generate_series(1, 12) AS n + ) w + on e.months = substring(to_char(w.wmonth,'yyyy-mm'),1,7) + GROUP BY w.wmonth,w.seconds,e.counts + ORDER BY w.wmonth DESC;` + ) + let resList = [] + if (res.length > 0) { + res[0].forEach((item) => { + resList.push(item) + }) + } + ctx.status = 200 + ctx.body = resList + + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + message: '查询系统可用性失败' + } + } +} + +async function getProblemType(ctx) { + const sequelize = ctx.fs.dc.orm + try { + const res = await sequelize.query(` + SELECT n.type,COALESCE(m.count,0) count FROM (SELECT t.type,count(1) FROM maintenance_record t GROUP BY t.type) m + RIGHT JOIN (SELECT p.type FROM system_problem p) n + ON m.type=n.type + GROUP BY n.type,m.count + `) + let resList = [] + if (res.length > 0) { + res[0].forEach((item) => { + resList.push(item) + }) + + } + ctx.status = 200 + ctx.body = resList + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + message: '查询故障类型失败' + } + } +} +async function getOperationsPersonnel(ctx) { + const sequelize = ctx.fs.dc.orm + const { clickHouse } = ctx.app.fs + try { + //查询用户id + const res = await sequelize.query(`SELECT t.pep_user_id userId,count(1) FROM maintenance_plan_execute_user t GROUP BY pep_user_id`) + let useList = new Set() + console.log('rss', res) + if (res.length > 0) { + res[0].forEach((item) => { + useList.add(item.userid) + }) + } + let users = useList ? await clickHouse.pepEmis.query(`SELECT DISTINCT user.id AS id, "user"."name" AS name FROM user WHERE user.id IN + (${[...useList].join(',')}, -1)`).toPromise() : [] + let resRecord = [] + if (res.length > 0) { + res[0].forEach((item) => { + resRecord.push(item) + }) + } + let mergedArray = [] + if (users.length > 0 && resRecord.length > 0) { + mergedArray = users.map(item1 => { + const item2 = resRecord.find(item2 => item2.userid === item1.id); + return { + userid: item1.id, + name: item1.name, + count: parseInt(item2.count) + } + }) + } + ctx.status = 200 + ctx.body = mergedArray + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + message: '查询运维人员失败' + } + } +} + +module.exports = { + getFailureTime, getSystemAvailability, getProblemType, getOperationsPersonnel +} \ No newline at end of file diff --git a/api/app/lib/controllers/problemData/index.js b/api/app/lib/controllers/problemData/index.js new file mode 100644 index 0000000..ed3319b --- /dev/null +++ b/api/app/lib/controllers/problemData/index.js @@ -0,0 +1,297 @@ +'use strict'; +const moment = require('moment') + +async function getMaintenceRecordRank(ctx) { + const sequelize = ctx.fs.dc.orm + const Sequelize = ctx.fs.dc.ORM; + const { clickHouse } = ctx.app.fs + const models = ctx.fs.dc.models + const { startTime, endTime } = ctx.query + + console.log(startTime, endTime, ctx.query, '1212312') + try { + const res = await sequelize.query(` + SELECT emrp.project_id,count(1) +FROM equipment_maintenance_record + RIGHT JOIN equipment_maintenance_record_project emrp + on equipment_maintenance_record.id = emrp.equipment_maintenance_record_id + where report_time BETWEEN :startTime AND :endTime +GROUP BY emrp.project_id + ` + , { + replacements: { + startTime: moment(startTime).format('YYYY-MM-DD HH:mm:ss'), + endTime: moment(endTime).format('YYYY-MM-DD HH:mm:ss ') + } + //, type: sequelize.QueryTypes.SELECT + } + ) + //查询equipment_maintenance_record返回的结果[{project_id: 22, count: 1}] + let projectList = [] + //存project的id + let projectIdList = [] + // console.log('resssss', res) + if (res.length > 0) { + res[0].forEach((item) => { + projectList.push({ project_id: item.project_id, count: Number(item.count) }) + projectIdList.push(item.project_id) + }) + } + const projectNameList = await models.ProjectCorrelation.findAll({ + attributes: + ['id', 'name'], + where: { + id: { $in: projectIdList }, + name: { + [Sequelize.Op.not]: null//有name的结果 + } + // del: false + } + }) || [] + //在ProjectCorrelation中查不到名字,去clickHouse中去查 + const projectNameList1 = await models.ProjectCorrelation.findAll({ + attributes: + ['id', 'name', 'pepProjectId'], + where: { + id: { $in: projectIdList }, + name: { + [Sequelize.Op.eq]: null//无name的结果 + } + // del: false + } + }) + //存放需要去查询clickHouse的id + let idList = new Set() + if (projectNameList1.length) { + projectNameList1.forEach((item) => { + idList.add(item.pepProjectId) + }) + } + //pepProject名称 + const projectManageName = idList.size ? await clickHouse.projectManage.query(` + SELECT id,project_name FROM t_pim_project + WHERE id IN (${[...idList].join(',')}, -1) + `).toPromise() : [] + // if (projectList.length) { + // projectList.forEach((item) => { + // projectManageName + // }) + // } + //存的是{id,projectName} + let project = [] + if (projectNameList1.length && projectManageName.length) { + projectManageName.forEach((item) => { + const pepObj = projectNameList1.find((item1) => { return item1.pepProjectId === item.id }) + project.push({ id: pepObj.id, projectName: item.project_name }) + }) + } + const resAll = project.concat(projectNameList) + let mergedArray = [] + if (resAll.length && projectList) { + mergedArray = projectList.map(obj1 => { + const matchingObj = resAll.find(obj2 => obj2.id === obj1.project_id); + return { id: obj1.project_id, pepProjectId: matchingObj.id, projectName: matchingObj.projectName || matchingObj.dataValues.name, count: obj1.count }; + }); + } + // console.log('ididididid', resAll) + // console.log('ididididid', project) + // console.log('ididididid', projectManageName) + // console.log('ididididid', projectNameList) + // console.log('ididididid', projectList) + + ctx.status = 200 + ctx.body = mergedArray + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + message: '查询维修记录排名失败' + } + } +} + + +async function getMaintenceTotal(ctx) { + const sequelize = ctx.fs.dc.orm + const Sequelize = ctx.fs.dc.ORM; + const { clickHouse } = ctx.app.fs + const models = ctx.fs.dc.models + const { startTime, endTime } = ctx.query + try { + //所有维修记录 + const res = await sequelize.query(` + SELECT emrp.project_id, + count(case when record.status in ('维修中','待维修') then record.id end) incomplete, + count(case when record.status in ('维修完成') then record.id end) completed + FROM equipment_maintenance_record record + RIGHT JOIN equipment_maintenance_record_project emrp + on record.id = emrp.equipment_maintenance_record_id + where report_time BETWEEN :startTime AND :endTime + GROUP BY emrp.project_id + ` + , { + replacements: { + startTime: moment(startTime).format('YYYY-MM-DD HH:mm:ss '), + endTime: moment(endTime).format('YYYY-MM-DD HH:mm:ss ') + } + //, type: sequelize.QueryTypes.SELECT + } + ) + //查询equipment_maintenance_record返回的结果[{project_id: 22,status:'' count: 1}] + let projectList = [] + //存project的id + let projectIdList = new Set() + // console.log('resssss', res) + if (res.length > 0) { + res[0].forEach((item) => { + projectList.push({ project_id: item.project_id, 'incomplete': Number(item.incomplete), completed: Number(item.completed) }) + projectIdList.add(item.project_id) + }) + } + // const result = projectList.reduce((acc, curr) => { + // if (curr.status === '待维修' || curr.status === '维修中') { + // const existingItem = acc.find(item => item.project_id === curr.project_id && item.status === '异常数'); + // if (existingItem) { + // existingItem.count += curr.count; + // } else { + // acc.push({ project_id: curr.project_id, status: '异常数', count: curr.count }); + // } + // } else if (curr.status === '维修完成') { + // const existingItem = acc.find(item => item.project_id === curr.project_id && item.status === '维修数'); + // if (existingItem) { + // existingItem.count += curr.count; + // } else { + // acc.push({ project_id: curr.project_id, status: '维修数', count: curr.count }); + // } + // } + // return acc; + // }, []) + //console.log('resssssresult', result) + const projectNameList = await models.ProjectCorrelation.findAll({ + attributes: + ['id', 'name'], + where: { + id: { $in: [...projectIdList] }, + name: { + [Sequelize.Op.not]: null//有name的结果 + } + // del: false + } + }) || [] + //在ProjectCorrelation中查不到名字,去clickHouse中去查 + const projectNameList1 = await models.ProjectCorrelation.findAll({ + attributes: + ['id', 'name', 'pepProjectId'], + where: { + id: { $in: [...projectIdList] }, + name: { + [Sequelize.Op.eq]: null//无name的结果 + } + // del: false + } + }) + //存放需要去查询clickHouse的id + let idList = new Set() + if (projectNameList1.length) { + projectNameList1.forEach((item) => { + idList.add(item.pepProjectId) + }) + } + //pepProject名称 + const projectManageName = idList.size ? await clickHouse.projectManage.query(` + SELECT id,project_name FROM t_pim_project + WHERE id IN (${[...idList].join(',')}, -1) + `).toPromise() : [] + let project = [] + if (projectNameList1.length && projectManageName.length) { + projectManageName.forEach((item) => { + const pepObj = projectNameList1.find((item1) => { return item1.pepProjectId === item.id }) + project.push({ id: pepObj.id, projectName: item.project_name }) + }) + } + //pg的数据和clcikHouse的数据(名字)合并 + const resAll = project.concat(projectNameList) + let mergedArray = [] + if (resAll.length && projectList) { + mergedArray = projectList.map(obj1 => { + const matchingObj = resAll.find(obj2 => obj2.id === obj1.project_id) + return { + id: obj1.project_id, incomplete: obj1.incomplete, completed: obj1.completed, pepProjectId: matchingObj.id, + projectName: matchingObj.projectName || matchingObj.dataValues.name + } + }); + } + + // console.log('ididididid', resAll) + // console.log('ididididid', project) + // console.log('ididididid', projectManageName) + // console.log('ididididid', projectNameList) + // console.log('ididididid', projectList) + ctx.status = 200 + ctx.body = mergedArray + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + message: '查询维修记录统计失败' + } + } + + +} +async function getEquipmentCategory(ctx) { + const { startTime, endTime } = ctx.query + const Sequelize = ctx.fs.dc.ORM + const models = ctx.fs.dc.models + try { + const res = await models.EquipmentMaintenanceRecord.findAll({ + attributes: [ + 'equipment_category', + [Sequelize.fn('COUNT', Sequelize.col('equipment_category')), 'count'] + ], + where: { reportTime: { $between: [moment(startTime).format('YYYY-MM-DD HH:mm:ss '), moment(endTime).format('YYYY-MM-DD HH:mm:ss ')] } }, + group: ['equipment_category'] + }) + ctx.status = 200 + ctx.body = res + + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + message: '查询设备类型失败' + } + } +} + + +async function getStatus(ctx) { + const { startTime, endTime } = ctx.query + const Sequelize = ctx.fs.dc.ORM + const models = ctx.fs.dc.models + try { + const res = await models.EquipmentMaintenanceRecord.findAll({ + attributes: [ + 'status', + [Sequelize.fn('COUNT', Sequelize.col('status')), 'count'] + ], + where: { reportTime: { $between: [moment(startTime).format('YYYY-MM-DD HH:mm:ss '), moment(endTime).format('YYYY-MM-DD HH:mm:ss ')] } }, + group: ['status'] + }) + ctx.status = 200 + ctx.body = res + + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + message: '查询设备类型失败' + } + } +} + + + +module.exports = { + getMaintenceRecordRank, getMaintenceTotal, getEquipmentCategory, getStatus +} \ No newline at end of file diff --git a/api/app/lib/models/system_problem.js b/api/app/lib/models/system_problem.js new file mode 100644 index 0000000..5d7d4fd --- /dev/null +++ b/api/app/lib/models/system_problem.js @@ -0,0 +1,34 @@ +/* eslint-disable*/ + +'use strict'; + +module.exports = dc => { + const DataTypes = dc.ORM; + const sequelize = dc.orm; + const SystemProblem = sequelize.define("systemProblem", { + id: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: true, + field: "id", + autoIncrement: true + }, + type: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + comment: "问题类型:数据库异常、es异常、kafka异常、服务器异常、应用异常、其他", + primaryKey: false, + field: "type", + autoIncrement: false + } + }, { + tableName: "system_problem", + comment: "", + indexes: [] + }); + dc.models.SystemProblem = SystemProblem; + return SystemProblem; +}; \ No newline at end of file diff --git a/api/app/lib/routes/operationData/index.js b/api/app/lib/routes/operationData/index.js new file mode 100644 index 0000000..4021ab0 --- /dev/null +++ b/api/app/lib/routes/operationData/index.js @@ -0,0 +1,16 @@ +'use strict'; +const operationData = require('../../controllers/operationData'); + +module.exports = function (app, router, opts) { + app.fs.api.logAttr['GET/failureTime'] = { content: '获取故障发生时间', visible: true }; + router.get('/failureTime', operationData.getFailureTime) + + app.fs.api.logAttr['GET/systemAvailability'] = { content: '获取系统可用性', visible: true }; + router.get('/systemAvailability', operationData.getSystemAvailability) + + app.fs.api.logAttr['GET/problemType'] = { content: '获取故障类型', visible: true }; + router.get('/problemType', operationData.getProblemType) + + app.fs.api.logAttr['GET/operationsPersonnel'] = { content: '获取运维人员', visible: true }; + router.get('/operationsPersonnel', operationData.getOperationsPersonnel) +} \ No newline at end of file diff --git a/api/app/lib/routes/problemData/index.js b/api/app/lib/routes/problemData/index.js new file mode 100644 index 0000000..29a548f --- /dev/null +++ b/api/app/lib/routes/problemData/index.js @@ -0,0 +1,16 @@ +'use strict'; +const problemData = require('../../controllers/problemData'); + +module.exports = function (app, router, opts) { + app.fs.api.logAttr['GET/maintenceRecordRank'] = { content: '获取维修记录排名', visible: true }; + router.get('/maintenceRecordRank', problemData.getMaintenceRecordRank) + + app.fs.api.logAttr['GET/maintenceTotal'] = { content: '获取维修次数统计', visible: true }; + router.get('/maintenceTotal', problemData.getMaintenceTotal) + + app.fs.api.logAttr['GET/equipmentCategory'] = { content: '获取设备类型', visible: true }; + router.get('/equipmentCategory', problemData.getEquipmentCategory) + + app.fs.api.logAttr['GET/maintenanceStatus'] = { content: '获取状态', visible: true }; + router.get('/maintenanceStatus', problemData.getStatus) +} \ No newline at end of file diff --git a/script/0.28/schema/01create_sysyem_problem.sql b/script/0.28/schema/01create_sysyem_problem.sql new file mode 100644 index 0000000..02c92cd --- /dev/null +++ b/script/0.28/schema/01create_sysyem_problem.sql @@ -0,0 +1,15 @@ +create table public.system_problem( + id serial primary key, + type varchar(20) +); +comment on column public.system_problem.type is '问题类型:数据库异常、es异常、kafka异常、服务器异常、应用异常、其他'; + +insert into system_problem (id, type)values ('es异常'); +insert into system_problem (id, type)values ('数据库异常'); +insert into system_problem (id, type)values ('应用异常'); +insert into system_problem (id, type)values ('kafka异常'); +insert into system_problem (id, type)values ('服务器异常'); +insert into system_problem (id, type)values ('DAC进程异常'); +insert into system_problem (id, type)values ('K8S集群异常'); +insert into system_problem (id, type)values ('redis服务异常'); +insert into system_problem (id, type)values ('其他'); diff --git a/web/client/src/sections/analysis/actions/index.js b/web/client/src/sections/analysis/actions/index.js index 33cdd8b..d41346a 100644 --- a/web/client/src/sections/analysis/actions/index.js +++ b/web/client/src/sections/analysis/actions/index.js @@ -1,7 +1,8 @@ 'use strict'; import * as console from './console' - +import * as operation from './operationData' +import * as maintenceRecord from './maintenceRecordTotal' export default { - ...console + ...console, ...operation, ...maintenceRecord } \ No newline at end of file diff --git a/web/client/src/sections/analysis/actions/maintenceRecordTotal.js b/web/client/src/sections/analysis/actions/maintenceRecordTotal.js new file mode 100644 index 0000000..4b3827b --- /dev/null +++ b/web/client/src/sections/analysis/actions/maintenceRecordTotal.js @@ -0,0 +1,53 @@ +'use strict'; + +import { ApiTable, basicAction } from '$utils' + + +export function getMaintenceRecordRank(query) { + return dispatch => basicAction({ + type: 'get', + dispatch: dispatch, + query: query, + actionType: 'GET_MAINTENCE_RECORD_RANK', + url: ApiTable.getMaintenceRecordRank, + msg: { option: '获取维修记录排名' }, + reducer: { name: 'maintenceRecordRank' } + }) +} + + +export function getMaintenceTotal(query) { + return dispatch => basicAction({ + type: 'get', + dispatch: dispatch, + query: query, + actionType: 'GET_MAINTENCE_TOTAL', + url: ApiTable.getMaintenceTotal, + msg: { option: '获取维修次数统计' }, + reducer: { name: 'maintenceTotal' } + }) +} + +export function getEquipmentCategory(query) { + return dispatch => basicAction({ + type: 'get', + dispatch: dispatch, + query: query, + actionType: 'GET_EQUIPMENT_CATEGORY', + url: ApiTable.getEquipmentCategory, + msg: { option: '获取设备类型' }, + reducer: { name: 'equipmentCategory' } + }) +} + +export function getStatus(query) { + return dispatch => basicAction({ + type: 'get', + dispatch: dispatch, + query: query, + actionType: 'GET_STATUS', + url: ApiTable.getMaintenanceStatus, + msg: { option: '获取维修状态数据' }, + reducer: { name: 'maintenanceStatus' } + }) +} \ No newline at end of file diff --git a/web/client/src/sections/analysis/actions/operationData.js b/web/client/src/sections/analysis/actions/operationData.js new file mode 100644 index 0000000..8eef0bb --- /dev/null +++ b/web/client/src/sections/analysis/actions/operationData.js @@ -0,0 +1,50 @@ +'use strict'; + +import { ApiTable, basicAction } from '$utils' + + +export function getFailureTime() { + return dispatch => basicAction({ + type: 'get', + dispatch: dispatch, + actionType: 'GET_FAILURE_TIME', + url: ApiTable.getFailureTime, + msg: { error: '获取故障发生时间分析' }, + reducer: { name: 'failureTime' } + }) +} + +export function getSystemAvailability() { + return dispatch => basicAction({ + type: 'get', + dispatch: dispatch, + actionType: 'GET_SYSTEM_AVAILABILITY', + url: ApiTable.getSystemAvailability, + msg: { error: '获取可用性分析' }, + reducer: { name: 'systemAvailability' } + }) +} + + +export function getProblemType() { + return dispatch => basicAction({ + type: 'get', + dispatch: dispatch, + actionType: 'GET_PROBLEM_TYPE', + url: ApiTable.getProblemType, + msg: { error: '获取故障类型' }, + reducer: { name: 'probleType' } + }) +} + + +export function getOperationsPersonnel() { + return dispatch => basicAction({ + type: 'get', + dispatch: dispatch, + actionType: 'GET_OPERATIONS_PERSONNEL', + url: ApiTable.getOperationsPersonnel, + msg: { error: '获取故障类型' }, + reducer: { name: 'operationsPersonnel' } + }) +} diff --git a/web/client/src/sections/analysis/containers/operationData.jsx b/web/client/src/sections/analysis/containers/operationData.jsx index 57c7a18..0c9eda9 100644 --- a/web/client/src/sections/analysis/containers/operationData.jsx +++ b/web/client/src/sections/analysis/containers/operationData.jsx @@ -1,48 +1,267 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { connect } from 'react-redux'; -import { Spin, Card } from '@douyinfe/semi-ui'; +import { Spin, Card, Divider, CardGroup } from '@douyinfe/semi-ui'; import '../style.less' +import ReactECharts from 'echarts-for-react'; const { Meta } = Card; const Console = (props) => { - const { dispatch, actions, user, loading, socket } = props - - useEffect(() => { - // ACTION 示例 - // dispatch(actions.example.getMembers(user.orgId)) - }, []) - - // websocket 使用测试 - // useEffect(() => { - // if (socket) { - // socket.on('TEST', function (msg) { - // console.info(msg); - // }); - // return () => { - // socket.off("TEST"); - // } - // } - - // }, [socket]) - - return ( - <> -