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 ( - <> -
- + const { dispatch, actions, user, loading, socket, clientHeight } = props + const [left1List, setLeft1List] = useState([])//故障发生时间分析的数据 + const [right1List, setRight1List] = useState([])//系统可用性分析的数据 + const [left2List, setLeft2List] = useState([])//故障类型分析的数据 + const [right2List, setRight2List] = useState([])//运维人员分析的数据 + const { analysis } = actions + console.log('actions', actions) + useEffect(() => { + // ACTION 示例 + // dispatch(actions.example.getMembers(user.orgId)) + }, []) + useEffect(() => { + //故障发生时间分析接口 + dispatch(analysis.getFailureTime()).then((res) => { + if (res.success) setLeft1List(res.payload.data) + }) + //系统可用性分析接口 + dispatch(analysis.getSystemAvailability()).then((res) => { + if (res.success) setRight1List(res.payload.data) + }) + //故障类型接口 + dispatch(analysis.getProblemType()).then((res) => { + if (res.success) setLeft2List(res.payload.data) + }) + //运维人员 + dispatch(analysis.getOperationsPersonnel()).then((res) => { + if (res.success) setRight2List(res.payload.data) + }) + + }, []) + + + // websocket 使用测试 + // useEffect(() => { + // if (socket) { + // socket.on('TEST', function (msg) { + // console.info(msg); + // }); + // return () => { + // socket.off("TEST"); + // } + // } + + // }, [socket]) + + return ( + <> + {/* */} +
+
+
+
+
维修记录统计
+
MAINTAIN RECORDS STATISTICAL
+
- - ) +
+ + { + [{ + t: '故障发生时间分析', + echartOption: { + xAxis: { + type: 'category', + data: left1List.map((item) => { + return item.wmonth + }), + axisLabel: { + interval: 0,//x轴强制显示每个 + rotate: 40 + }, + }, + yAxis: { + type: 'value', + name: "单位:次数", + // nameLocation: "end", + // nameGap: 20 + }, + legend: { + orient: 'vertical', + right: '5%', + top: '0%', + data: ['异常数'] + }, + tooltip: {}, + grid: { + top: '10%', + right: '5%', + left: '5%', + bottom: '10%', + containLabel: true + }, + + series: [ + { + data: left1List.map((item) => { + return Number(item.count) + }), + type: 'bar', + name: '异常数', + } + ] + }, + }, { + t: '系统可用性分析', + echartOption: { + xAxis: { + type: 'category', + data: right1List.map((item) => { + return item.wmonth + }), + axisLabel: { + interval: 0,//x轴强制显示每个 + rotate: 40 + }, + }, + yAxis: { + type: 'value', + name: "单位:%", + // nameLocation: "end", + // nameGap: 20 + }, + legend: {}, + tooltip: {}, + grid: { + //left: '3%', + //right: '3%', + bottom: '1%', + // top: '24%', + containLabel: true + }, + + series: [ + { + data: right1List.map((item) => { + return Math.round(item.ability * 10000) / 100 + + }), + type: 'line', + } + ] + }, + }, { + t: '故障类型分析', + echartOption: { + xAxis: { + type: 'category', + data: left2List.map((item) => { + return item.type + }), + axisLabel: { + interval: 0,//x轴强制显示每个 + rotate: 40 + }, + }, + yAxis: { + type: 'value', + name: "单位:个", + nameLocation: "end", + nameGap: 20 + }, + legend: {}, + tooltip: {}, + grid: { + left: '3%', + right: '4%', + bottom: '1%', + // top: '24%', + containLabel: true + }, + series: [ + { + data: left2List.map((item) => { + return Number(item.count) + }), + type: 'bar', + //name: '发起数' + } + + ] + }, + }, { + t: '运维人员分析', + echartOption: { + tooltip: {}, + series: [ + { + // name: right2List?.map((item)=>{ + // return item.name + // }), + type: 'pie', + radius: '80%',//饼图大小 + data: right2List?.map((item) => { + return { value: item.count, name: item.name } + }), + label: { + + } + } + ], + media: [ + { + query: { minAspectRatio: 1 }, + option: { + series: [ + { center: ['50%', '50%'] }, + { center: ['50%', '50%'] } + ] + } + }, + ] + }, + },].map((c, idx) => ( + + + + + )) + } + +
+
+ + + + ) } -function mapStateToProps (state) { - const { auth, global, members, webSocket } = state; - return { - // loading: members.isRequesting, - // user: auth.user, - // actions: global.actions, - // members: members.data, - // socket: webSocket.socket - }; +function mapStateToProps(state) { + const { auth, global, members, webSocket } = state; + return { + // loading: members.isRequesting, + // user: auth.user, + actions: global.actions, + // members: members.data, + // socket: webSocket.socket + clientHeight: global.clientHeight + + }; } export default connect(mapStateToProps)(Console); diff --git a/web/client/src/sections/analysis/containers/problemData.jsx b/web/client/src/sections/analysis/containers/problemData.jsx index 0ac898b..343152e 100644 --- a/web/client/src/sections/analysis/containers/problemData.jsx +++ b/web/client/src/sections/analysis/containers/problemData.jsx @@ -1,17 +1,83 @@ -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, CardGroup, DatePicker, Space, RadioGroup, Radio, Divider } from '@douyinfe/semi-ui'; import '../style.less' +import ReactECharts from 'echarts-for-react'; +import moment from 'moment' + + const { Meta } = Card; const Console = (props) => { - const { dispatch, actions, user, loading, socket } = props - + const { dispatch, actions, user, loading, socket, clientHeight } = props + const [startTime, setStartTime] = useState('1970-1-1')//开始时间 + const [endTime, setEndTime] = useState('2099-12-31')//结束时间 + const [mode, setMode] = useState('')//今日、本周、本月、全年是否选中 + const [dateValue, setDateValue] = useState([])//时间选择框的值 + const [left1List, setLeft1List] = useState([])//维修记录排名的数据 + const [right1List, setRight1List] = useState([])//维修次数统计的数据 + const [left2List, setLeft2List] = useState([])//设备类型分析的数据 + const [right2List, setRight2List] = useState([])//维修状态的数据 + const { analysis } = actions + const getData = (query = { startTime, endTime }) => { + //请求维修记录排名的数据 + dispatch(analysis.getMaintenceRecordRank(query)).then((res) => { + if (res.success) setLeft1List(res.payload.data) + }) + //请求维修次数统计的数据 + dispatch(analysis.getMaintenceTotal(query)).then((res) => { + if (res.success) setRight1List(res.payload.data) + }) + //请求设备类型分析的数据 + dispatch(analysis.getEquipmentCategory(query)).then((res) => { + if (res.success) setLeft2List(res.payload.data) + }) + //请求维修状态的数据 + dispatch(analysis.getStatus(query)).then((res) => { + if (res.success) setRight2List(res.payload.data) + }) + } useEffect(() => { - // ACTION 示例 - // dispatch(actions.example.getMembers(user.orgId)) - }, []) + getData() + }, [startTime,endTime]) + //datepicker选择日期之后赋值 + const onChangeDate = (e) => { + // alert(111) + setStartTime(moment(e[0]).format('YYYY-MM-DD HH:mm:ss')) + setEndTime(moment(e[1]).format('YYYY-MM-DD')+' '+'23:59:59') + setDateValue(e) + // const query={ startTime:e[0]+'', endTime:e[1]+'' } + // getData(query) + setMode('') + } + const clearHandler=()=>{ + setStartTime(moment('1970-1-1').startOf('day').format('YYYY-MM-DD')) + setEndTime(moment('2099-12-31').startOf('day').format('YYYY-MM-DD')) + // const query={ startTime:'1970-1-1', endTime:'2099-12-31' } + // getData(query) + } + //选择今日、本周、本月、全年之后的逻辑 + const onSelect = (e) => { + // console.log('21',e) + // console.log('111',moment().startOf('day').format('YYYY-MM-DD ')) + setMode(e.target.value) + if (e.target.value === 'day') { + setStartTime(moment().startOf('day').format('YYYY-MM-DD HH:mm:ss')) + setEndTime(moment().endOf('day').format('YYYY-MM-DD HH:mm:ss')) + } else if (e.target.value === 'week') { + setStartTime(moment().startOf('week').format('YYYY-MM-DD HH:mm:ss')) + setEndTime(moment().endOf('week').format('YYYY-MM-DD HH:mm:ss')) + } else if (e.target.value === 'month') { + setStartTime(moment().startOf('month').format('YYYY-MM-DD HH:mm:ss')) + setEndTime(moment().endOf('month').format('YYYY-MM-DD HH:mm:ss')) + } else if (e.target.value === 'year') { + setStartTime(moment().startOf('year').format('YYYY-MM-DD HH:mm:ss')) + setEndTime(moment().endOf('year').format('YYYY-MM-DD HH:mm:ss')) + } + setDateValue([]) + } + const data = [3, 2, 1, 0, 1, 2, 3]; // websocket 使用测试 // useEffect(() => { // console.log(socket) @@ -28,21 +94,303 @@ const Console = (props) => { return ( <> -
- + {/* */} +
+
+
+
+
维修记录统计
+
MAINTAIN RECORDS STATISTICAL
+
+
+ + +
+ 时间选择 + onChangeDate(e)} + density="compact" + style={{ width: 400 }} + value={dateValue} + onClear={clearHandler} + /> + onSelect(e)} + value={mode} + type="button"> + 今日 + 本周 + 本月 + 全年 + +
+
+ + { + [{ + t: '维修记录排名', + echartOption: { + xAxis: { + type: 'value', + // axisLabel: { + // interval: 0,//x轴强制显示每个 + // rotate: 40 + // }, + }, + yAxis: { + type: 'category', + data: left1List.map((item) => { + return item.projectName + }), + axisLabel:{formatter: function (arg) { + return arg.length>8?arg.substring(0,8)+'...':arg + }}, + + // nameLocation: "end", + // nameGap: 20 + }, + legend: { + orient: 'vertical', + right: '5%', + top: '0%', + icon: 'circle' + }, + tooltip: {}, + grid: { + top: '10%', + right: '5%', + left: '5%', + bottom: '10%', + containLabel: true + }, + + series: [ + { + data: left1List.map((item) => { + return item.count + }), + type: 'bar', + name: '异常数', + itemStyle: { + color: 'green', + + } + + } + ] + }, + }, { + t: '维修次数统计', + echartOption: { + xAxis: { + type: 'category', + data: right1List.map((item=>{ + return item.projectName + })), + axisLabel: { + interval: 0,//x轴强制显示每个 + rotate: 40, + formatter: function (arg) { + return arg.length>8?arg.substring(0,8)+'...':arg + } + }, + + }, + yAxis: [{ + type: 'value', + name: '单位:次数', + axisLabel: { + formatter: (value) => { + // 负数取反 显示的就是正数了 + if (value < 0) return -value + else return value + } + }, + // min: function (value) { + // //最小值设置,其中-value.max * 1.05 乘以1.05 ,主要是使图的边界小于最小的值,给边界留空间 + // return (Math.abs(value.min) < value.max ? -value.max * 1.05 : value.min * 1.05).toFixed(2); + // }, + // max: function (value) { + // //最大值设置,其中-value.min * 1.05 乘以1.05 ,主要是使图的边界大于最大于值,给边界留空间 + // return (Math.abs(value.min) < value.max ? value.max * 1.05 : -value.min * 1.05).toFixed(2); + // } + }, + ], + legend: { + //orient: 'vertical',//竖着显示 + right: '0%', + // top: '-5%', + icon: 'circle' + }, + grid: { + top: '10%', + right: '5%', + left: '5%', + bottom: '10%', + containLabel: true + }, + tooltip: { + show:true, + formatter: function (params) { + //console.log('params',params) + return '
'+params.name+'\n'+params.seriesName+':'+Math.abs(params.value)+'
' + } + }, + + itemStyle: { + borderRadius: 5 // 设置单个数值 + }, + series: [ + { + // barWidth: 20, // 设置柱状图的宽度 + name: '异常数', + type: 'bar', + stack: '总量', //注意这里也要添加,要不然对不齐 + data: right1List.map((item=>{ + return item.incomplete + })), + label: { + normal: { + show: false, + formatter: (params) => { + const value = params.value; + return '异常数'+Math.abs(value) + }//返回绝对值 + } + } + }, + { + // barWidth: 20, // 设置柱状图的宽度 + name: '维修数', + type: 'bar', + stack: '总量', //注意这里也要添加,要不然对不齐 + label: { + normal: { + show: false, + //data: [1, 2, 3], + formatter: (params) => { + const value = params.value; + return '维修数'+Math.abs(value) + }//返回绝对值 + } + }, + data:right1List.map((item=>{ + return ~item.completed+1 + })) + } + ], + + }, + }, { + t: '设备类型分析', + echartOption: { + xAxis: { + type: 'category', + data:left2List.map((item)=>{ + return item.equipment_category + }), + axisLabel: { + interval: 0,//x轴强制显示每个 + rotate: 40 + }, + }, + yAxis: { + type: 'value', + // name: "单位:个", + // nameLocation: "end", + nameGap: 20 + }, + legend: {}, + tooltip: {}, + grid: { + left: '3%', + right: '4%', + bottom: '1%', + // top: '24%', + containLabel: true + }, + series: [ + { + data: left2List.map((item)=>{ + return Number(item.count) + }), + type: 'bar', + //name: '发起数' + } + + ] + }, + }, { + t: '维修状态分析', + echartOption: { + tooltip: {}, + series: [ + { + name:'维修状态分析', + type: 'pie', + radius: '80%',//饼图大小 + data:right2List.map((item=>{ + return {value: Number(item.count), name: item.status} + })), + label: { + // name: right2List.map((item=>{ + // return{} + // })) + } + } + ], + media: [ + { + query: { minAspectRatio: 1 }, + option: { + series: [ + { center: ['50%', '50%'] }, + { center: ['50%', '50%'] } + ] + } + }, + ] + }, + },].map((c, idx) => ( + + + + + )) + } +
+
) } -function mapStateToProps (state) { +function mapStateToProps(state) { const { auth, global, members, webSocket } = state; return { // loading: members.isRequesting, // user: auth.user, - // actions: global.actions, + actions: global.actions, // members: members.data, - // socket: webSocket.socket + // socket: webSocket.socket, + clientHeight: global.clientHeight + }; } diff --git a/web/client/src/sections/service/components/maintenanceRecordModal.jsx b/web/client/src/sections/service/components/maintenanceRecordModal.jsx index b80f3f1..2822816 100644 --- a/web/client/src/sections/service/components/maintenanceRecordModal.jsx +++ b/web/client/src/sections/service/components/maintenanceRecordModal.jsx @@ -104,7 +104,7 @@ const MaintenanceRecordModal = (props) => { rules={[{ required: true, message: '请选择上报时间' }]} > - + 待维修 维修中 维修完成 diff --git a/web/client/src/sections/service/components/recordModal.jsx b/web/client/src/sections/service/components/recordModal.jsx index f69c67f..293f20d 100644 --- a/web/client/src/sections/service/components/recordModal.jsx +++ b/web/client/src/sections/service/components/recordModal.jsx @@ -110,12 +110,15 @@ const okHandler=()=>{ })} )})} - + es异常 数据库异常 应用异常 kafka异常 服务器异常 + DAC进程异常 + K8S集群异常 + redis服务异常 其他 diff --git a/web/client/src/utils/webapi.js b/web/client/src/utils/webapi.js index 454e9f2..128e531 100644 --- a/web/client/src/utils/webapi.js +++ b/web/client/src/utils/webapi.js @@ -112,7 +112,24 @@ export const ApiTable = { //编辑服务器信息维护记录(编辑和新增) editServerInfoMaintenanceRecord: 'serverInfoMaintenanceRecord', //删除服务器信息维护记录 - delServerInfoMaintenanceRecord: 'serverInfoMaintenanceRecord/{id}' + delServerInfoMaintenanceRecord: 'serverInfoMaintenanceRecord/{id}', + //运维分析-运维数据 + //故障发生时间分析 + getFailureTime: 'failureTime', + //系统可用性分析 + getSystemAvailability: 'systemAvailability', + //故障类型 + getProblemType: 'problemType', + //y运维人员 + getOperationsPersonnel: 'operationsPersonnel', + //维修记录排名 + getMaintenceRecordRank: 'maintenceRecordRank', + //维修数统计 + getMaintenceTotal: 'maintenceTotal', + //获取设备类型 + getEquipmentCategory: 'equipmentCategory', + //获取状态数据 + getMaintenanceStatus: 'maintenanceStatus' }; // 项企的接口