diff --git a/api/.vscode/launch.json b/api/.vscode/launch.json index f9f2031..69a9028 100644 --- a/api/.vscode/launch.json +++ b/api/.vscode/launch.json @@ -16,9 +16,9 @@ "-p 4600", "-f http://localhost:4600", // 研发 - // "-g postgres://postgres:123@10.8.30.32:5432/orational_service", + "-g postgres://postgres:123@10.8.30.32:5432/orational_service", // 测试 - "-g postgres://FashionAdmin:123456@10.8.30.156:5432/POMS", + // "-g postgres://FashionAdmin:123456@10.8.30.156:5432/POMS", "-k node35:6667,node36:6667,node37:6667", "--iotaProxy http://10.8.30.157:17007", "--redisHost 10.8.30.112", diff --git a/api/app/lib/controllers/alarm/alarmConfirmLog.js b/api/app/lib/controllers/alarm/alarmConfirmLog.js new file mode 100644 index 0000000..5a91aea --- /dev/null +++ b/api/app/lib/controllers/alarm/alarmConfirmLog.js @@ -0,0 +1,49 @@ +'use strict'; + +const moment = require('moment') + +async function alarmConfirmLog(ctx, confirmPost, content) { + try { + const { models } = ctx.fs.dc; + //存日志 + let logDatas = []; + confirmPost.map(cp => { + let { pepUserId, projectCorrelationIds, alarmInfo } = cp; + projectCorrelationIds.map(id => { + logDatas.push({ + pepUserId, + projectCorrelationId: id, + alarmInfo,//包含告警id,type,source + confirmTime: moment().format(), + confirmContent: content + }) + }) + }) + let rslt = await models.AlarmConfirmLog.bulkCreate(logDatas, { returning: true }); + + //存最新动态 + let dynamics = rslt.map(r => { + return { + time: r.confirmTime, + alarmConfirmId: r.id, + projectCorrelationId: r.projectCorrelationId, + type: 4//告警确认 + } + }) + await models.LatestDynamicList.bulkCreate(dynamics); + + //TODO 消息推送到前端 + + + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + message: typeof error == 'string' ? error : undefined + } + } +} + +module.exports = { + alarmConfirmLog +}; \ No newline at end of file diff --git a/api/app/lib/controllers/alarm/app.js b/api/app/lib/controllers/alarm/app.js index 723435e..4472c04 100644 --- a/api/app/lib/controllers/alarm/app.js +++ b/api/app/lib/controllers/alarm/app.js @@ -1,7 +1,7 @@ 'use strict'; const moment = require('moment') - +const { alarmConfirmLog } = require('./alarmConfirmLog'); async function inspection(ctx) { // 巡查 try { @@ -328,7 +328,6 @@ async function confirmApiError(ctx) { try { const models = ctx.fs.dc.models; const { confirm, appAlarmId = [], confirmPost } = ctx.request.body - const { pepUserId, projectCorrelationIds, alarmInfo } = confirmPost await models.AppAlarm.update({ confirm, confirmTime: moment().format() @@ -338,29 +337,7 @@ async function confirmApiError(ctx) { } }) - //存日志 - let logDatas = projectCorrelationIds.map(id => { - return { - pepUserId, - projectCorrelationId: id, - alarmInfo,//包含告警id,type,source - confirmTime: moment().format(), - confirmContent: confirm - } - }) - let rslt = await models.AlarmConfirmLog.bulkCreate(logDatas, { returning: true }); - - //存最新动态 - let dynamics = rslt.map(r => { - return { - time: r.confirmTime, - alarmConfirmId: r.id, - projectCorrelationId: r.projectCorrelationId, - type: 4//告警确认 - } - }) - await models.LatestDynamicList.bulkCreate(dynamics); - + await alarmConfirmLog(ctx, confirmPost, confirm);//告警确认日志 ctx.status = 204; } catch (error) { diff --git a/api/app/lib/controllers/alarm/data.js b/api/app/lib/controllers/alarm/data.js index 595cd60..13f42fd 100644 --- a/api/app/lib/controllers/alarm/data.js +++ b/api/app/lib/controllers/alarm/data.js @@ -1,6 +1,6 @@ 'use strict'; const moment = require('moment'); - +const { alarmConfirmLog } = require('./alarmConfirmLog'); async function groupList (ctx) { try { const { models } = ctx.fs.dc; @@ -248,7 +248,6 @@ function confirm (opts) { const { utils: { kfkSendAsync } } = ctx.app.fs const { clickHouse } = ctx.app.fs const { content = '', alarmId, confirmPost } = ctx.request.body; - const { pepUserId, projectCorrelationIds, alarmInfo } = confirmPost; // 发送告警恢复通知 // Topic: alarm /* @@ -295,6 +294,9 @@ function confirm (opts) { await kfkSendAsync(payloads) } + + await alarmConfirmLog(ctx, confirmPost, content);//告警确认日志 + ctx.status = 204; } catch (error) { ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); diff --git a/api/app/lib/controllers/alarm/video.js b/api/app/lib/controllers/alarm/video.js index 0e546cb..46fa882 100644 --- a/api/app/lib/controllers/alarm/video.js +++ b/api/app/lib/controllers/alarm/video.js @@ -1,6 +1,6 @@ 'use strict'; const moment = require('moment') - +const { alarmConfirmLog } = require('./alarmConfirmLog'); async function deviceType (ctx) { try { const { models } = ctx.fs.dc; @@ -21,7 +21,7 @@ async function deviceType (ctx) { } } -async function alarmList(ctx, agg) { +async function alarmList (ctx) { try { const { models } = ctx.fs.dc; const { clickHouse } = ctx.app.fs @@ -67,99 +67,101 @@ async function alarmList(ctx, agg) { ) `) } - const alarmRes = anxinStrucIds.length ? await clickHouse.vcmp.query( - ` + + const queryStr = ` SELECT - cameraAlarm.cameraId AS cameraId, - cameraAlarm.cameraName AS cameraName, - cameraAlarm.cameraKindId AS cameraKindId, - cameraAlarm.venderId AS venderId, - cameraAlarm.venderName AS venderName, - cameraAlarm.cameraSerialNo AS cameraSerialNo, - cameraAlarm.cameraChannelNo AS cameraChannelNo, - cameraAlarm.alarmId AS alarmId, - cameraAlarm.createTime AS createTime, - cameraAlarm.updateTime AS updateTime, - cameraAlarm.platform AS platform, - cameraAlarm.confirmContent AS confirmContent, - cameraAlarm.confirmTime AS confirmTime, - ${'cameraAlarm.autoRestore AS autoRestore,'} - camera_status_resolve.id AS resolveId, - camera_status.describe AS statusDescribe, - camera_status_resolve.resolve AS resolve, - "gbCamera".online AS cameraOnline, - secret_yingshi.token AS yingshiToken, - anxinIpc.t_video_ipc.name AS anxinIpcPosition, - anxinStation.id AS anxinStationId, - anxinStation.name AS anxinStationName, - anxinStruc.name AS strucName, - anxinStruc.id AS strucId - FROM - ( - SELECT - camera.id AS cameraId, - camera.gb_id AS gbId, - camera.name AS cameraName, - camera.kind_id AS cameraKindId, - camera.vender_id AS venderId, - camera.yingshi_secret_id AS yingshiSecretId, - vender.name AS venderName, - camera_status_alarm.id AS alarmId, - camera_status_alarm.create_time AS createTime, - camera_status_alarm.update_time AS updateTime, - camera_status_alarm.platform AS platform, - camera_status_alarm.status_id AS statusId, - camera_status_alarm.serial_no AS cameraSerialNo, - camera_status_alarm.channel_no AS cameraChannelNo, - camera_status_alarm.confirm AS confirmContent, - ${'camera_status_alarm.auto_restore AS autoRestore,'} - camera_status_alarm.confirm_time AS confirmTime + cameraAlarm.cameraId AS cameraId, + cameraAlarm.cameraName AS cameraName, + cameraAlarm.cameraKindId AS cameraKindId, + cameraAlarm.venderId AS venderId, + cameraAlarm.venderName AS venderName, + cameraAlarm.cameraSerialNo AS cameraSerialNo, + cameraAlarm.cameraChannelNo AS cameraChannelNo, + cameraAlarm.alarmId AS alarmId, + cameraAlarm.createTime AS createTime, + cameraAlarm.updateTime AS updateTime, + cameraAlarm.platform AS platform, + cameraAlarm.confirmContent AS confirmContent, + cameraAlarm.confirmTime AS confirmTime, + ${'cameraAlarm.autoRestore AS autoRestore,'} + camera_status_resolve.id AS resolveId, + camera_status.describe AS statusDescribe, + camera_status_resolve.resolve AS resolve, + "gbCamera".online AS cameraOnline, + secret_yingshi.token AS yingshiToken, + anxinIpc.t_video_ipc.name AS anxinIpcPosition, + anxinStation.id AS anxinStationId, + anxinStation.name AS anxinStationName, + anxinStruc.name AS strucName, + anxinStruc.id AS strucId + FROM + ( + SELECT + camera.id AS cameraId, + camera.gb_id AS gbId, + camera.name AS cameraName, + camera.kind_id AS cameraKindId, + camera.vender_id AS venderId, + camera.yingshi_secret_id AS yingshiSecretId, + vender.name AS venderName, + camera_status_alarm.id AS alarmId, + camera_status_alarm.create_time AS createTime, + camera_status_alarm.update_time AS updateTime, + camera_status_alarm.platform AS platform, + camera_status_alarm.status_id AS statusId, + camera_status_alarm.serial_no AS cameraSerialNo, + camera_status_alarm.channel_no AS cameraChannelNo, + camera_status_alarm.confirm AS confirmContent, + ${'camera_status_alarm.auto_restore AS autoRestore,'} + camera_status_alarm.confirm_time AS confirmTime + FROM camera_status_alarm + INNER JOIN camera + ON camera.serial_no = camera_status_alarm.serial_no + AND camera.channel_no = camera_status_alarm.channel_no + ${cameraWhereOption.length ? 'AND ' + cameraWhereOption.join(' AND ') : ''} + LEFT JOIN vender + ON vender.id = camera.vender_id + WHERE + camera.delete = false + AND camera.recycle_time is null + ${statusAlarmWhereOption.length ? 'AND ' + statusAlarmWhereOption.join(' AND ') : ''} + AND alarmId IN ( + SELECT camera_status_alarm.id AS alarmId FROM camera_status_alarm - INNER JOIN camera - ON camera.serial_no = camera_status_alarm.serial_no - AND camera.channel_no = camera_status_alarm.channel_no - ${cameraWhereOption.length ? 'AND ' + cameraWhereOption.join(' AND ') : ''} - LEFT JOIN vender - ON vender.id = camera.vender_id - WHERE - camera.delete = false - AND camera.recycle_time is null - ${statusAlarmWhereOption.length ? 'AND ' + statusAlarmWhereOption.join(' AND ') : ''} - AND alarmId IN ( - SELECT camera_status_alarm.id AS alarmId - FROM camera_status_alarm - RIGHT JOIN ${anxinyun}.t_video_ipc - ON toString(${anxinyun}.t_video_ipc.channel_no) = camera_status_alarm.channel_no - AND ${anxinyun}.t_video_ipc.serial_no = camera_status_alarm.serial_no - ${`WHERE ${anxinyun}.t_video_ipc.structure IN (${anxinStrucIds.join(',')})`} - ) - ${limit ? 'LIMIT ' + limit : ''} - ${limit && page ? 'OFFSET ' + parseInt(limit) * parseInt(page) : ''} - ) AS cameraAlarm - LEFT JOIN camera_status - ON cameraAlarm.platform = camera_status.platform - AND cameraAlarm.statusId = camera_status.id - LEFT JOIN camera_status_resolve - ON camera_status_resolve.status_id = camera_status.id - LEFT JOIN "gbCamera" - ON "gbCamera".id = cameraAlarm.gbId - LEFT JOIN "secret_yingshi" - ON "secret_yingshi".id = cameraAlarm.yingshiSecretId + RIGHT JOIN ${anxinyun}.t_video_ipc + ON toString(${anxinyun}.t_video_ipc.channel_no) = camera_status_alarm.channel_no + AND ${anxinyun}.t_video_ipc.serial_no = camera_status_alarm.serial_no + ${`WHERE ${anxinyun}.t_video_ipc.structure IN (${anxinStrucIds.join(',')})`} + ) + ${limit ? 'LIMIT ' + limit : ''} + ${limit && page ? 'OFFSET ' + parseInt(limit) * parseInt(page) : ''} + ) AS cameraAlarm + LEFT JOIN camera_status + ON cameraAlarm.platform = camera_status.platform + AND cameraAlarm.statusId = camera_status.id + LEFT JOIN camera_status_resolve + ON camera_status_resolve.status_id = camera_status.id + LEFT JOIN "gbCamera" + ON "gbCamera".id = cameraAlarm.gbId + LEFT JOIN "secret_yingshi" + ON "secret_yingshi".id = cameraAlarm.yingshiSecretId - LEFT JOIN ${anxinyun}.t_video_ipc AS anxinIpc - ON toString(anxinIpc.channel_no) = cameraAlarm.cameraChannelNo - AND anxinIpc.serial_no = cameraAlarm.cameraSerialNo - LEFT JOIN ${anxinyun}.t_structure AS anxinStruc - ON anxinStruc.id = anxinIpc.structure - AND anxinStruc.id IN (${anxinStrucIds.join(',')}) - ${keywordTarget == 'struc' && keyword ? `AND anxinStruc.name LIKE '%${keyword}%'` : ''} - LEFT JOIN ${anxinyun}.t_video_ipc_station AS anxinIpcStation - ON anxinIpcStation.ipc = anxinIpc.id - LEFT JOIN ${anxinyun}.t_sensor AS anxinStation - ON anxinStation.id = anxinIpcStation.station - ` + LEFT JOIN ${anxinyun}.t_video_ipc AS anxinIpc + ON toString(anxinIpc.channel_no) = cameraAlarm.cameraChannelNo + AND anxinIpc.serial_no = cameraAlarm.cameraSerialNo + LEFT JOIN ${anxinyun}.t_structure AS anxinStruc + ON anxinStruc.id = anxinIpc.structure + AND anxinStruc.id IN (${anxinStrucIds.join(',')}) + ${keywordTarget == 'struc' && keyword ? `AND anxinStruc.name LIKE '%${keyword}%'` : ''} + LEFT JOIN ${anxinyun}.t_video_ipc_station AS anxinIpcStation + ON anxinIpcStation.ipc = anxinIpc.id + LEFT JOIN ${anxinyun}.t_sensor AS anxinStation + ON anxinStation.id = anxinIpcStation.station + ` + const alarmRes = anxinStrucIds.length ? await clickHouse.vcmp.query( + queryStr ).toPromise() : [] - + console.log(queryStr); let returnD = [] let positionD = {} // 每个设备一个告警 @@ -249,12 +251,8 @@ async function alarmList(ctx, agg) { } } - if (agg == 'day') {//控制台 按日聚集 - return returnD - } else { - ctx.status = 200; - ctx.body = returnD - } + ctx.status = 200; + ctx.body = returnD } catch (error) { ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); ctx.status = 400; @@ -266,9 +264,7 @@ async function alarmList(ctx, agg) { async function confirm (ctx) { try { - const { models } = ctx.fs.dc; const { alarmId, content, confirmPost } = ctx.request.body; - const { pepUserId, projectCorrelationIds, alarmInfo } = confirmPost; // TODO: 以视频·应用的秘钥进行鉴权 await ctx.app.fs.vcmpRequest.put('status/alarm/confirm', { data: { @@ -276,28 +272,7 @@ async function confirm (ctx) { } }) - //存日志 - let logDatas = projectCorrelationIds.map(id => { - return { - pepUserId, - projectCorrelationId: id, - alarmInfo,//包含告警id,type,source - confirmTime: moment().format(), - confirmContent: content - } - }) - let rslt = await models.AlarmConfirmLog.bulkCreate(logDatas, { returning: true }); - - //存最新动态 - let dynamics = rslt.map(r => { - return { - time: r.confirmTime, - alarmConfirmId: r.id, - projectCorrelationId: r.projectCorrelationId, - type: 4//告警确认 - } - }) - await models.LatestDynamicList.bulkCreate(dynamics); + await alarmConfirmLog(ctx, confirmPost, content);//告警确认日志 ctx.status = 204; } catch (error) { diff --git a/api/app/lib/controllers/control/data.js b/api/app/lib/controllers/control/data.js index 26f1fcf..f894575 100644 --- a/api/app/lib/controllers/control/data.js +++ b/api/app/lib/controllers/control/data.js @@ -1,55 +1,5 @@ 'use strict'; const moment = require('moment'); -const { alarmList } = require('../alarm/video'); - -//项目概览 -async function getProjectsInfo(ctx) { - try { - const { models } = ctx.fs.dc; - const { clickHouse, utils: { judgeSuper, anxinStrucIdRange } } = ctx.app.fs - const { database: anxinyun } = clickHouse.anxinyun.opts.config - const { alarmId, limit, page, projectCorrelationId, pepProjectId, keywordTarget, keyword } = ctx.query; - const { userInfo } = ctx.fs.api; - // let where = {} - // if (!userInfo.role.includes('SuperAdmin') && !userInfo.role.includes('admin')) { - // where.projectCorrelationId = { $in: userInfo.correlationProject } - // } - // if (projectCorrelationId) {//查指定项目,控制台全局切换 - // where.projectCorrelationId = projectCorrelationId - // } - let anxinStruc = await anxinStrucIdRange({ - ctx, pepProjectId, keywordTarget, keyword - }) - const anxinStrucIds = anxinStruc.map(a => a.strucId); - //先查全部的摄像头 - const videoList = anxinStrucIds.length ? await clickHouse.vcmp.query( - `select camera.id, - camera.name, - camera.serial_no from camera where camera.delete=false and camera.recycle_time is null - - LEFT JOIN ${anxinyun}.t_video_ipc AS anxinIpc - ON toString(anxinIpc.channel_no) = cameraAlarm.cameraChannelNo - AND anxinIpc.serial_no = cameraAlarm.cameraSerialNo - LEFT JOIN ${anxinyun}.t_structure AS anxinStruc - ON anxinStruc.id = anxinIpc.structure - AND anxinStruc.id IN (${anxinStrucIds.join(',')}) - LEFT JOIN ${anxinyun}.t_video_ipc_station AS anxinIpcStation - ON anxinIpcStation.ipc = anxinIpc.id - LEFT JOIN ${anxinyun}.t_sensor AS anxinStation - ON anxinStation.id = anxinIpcStation.station` - ).toPromise() : [] - - - ctx.status = 200; - ctx.body = [] - } catch (error) { - ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); - ctx.status = 400; - ctx.body = { - message: typeof error == 'string' ? error : undefined - } - } -} //BI分析-数据 async function getDataAlarmsAggDay(ctx) { @@ -143,14 +93,10 @@ async function getAppAlarmsAggDay(ctx) { attributes: ['id', 'createTime', 'confirmTime'], include: [{ model: models.App, - attributes: { - exclude: ['projectId'] - }, + attributes: ['id'], include: [{ model: models.ProjectCorrelation, - attributes: { - exclude: ['id'] - }, + attributes: ['id'] }] }] } @@ -185,9 +131,99 @@ async function getAppAlarmsAggDay(ctx) { //BI分析-视频异常 async function getVideoAlarmsAggDay(ctx) { try { - let videoAlarms = await alarmList(ctx, 'day'); - let aggDayMap = []; + const { clickHouse, utils: { anxinStrucIdRange } } = ctx.app.fs + const { database: anxinyun } = clickHouse.anxinyun.opts.config + const { pepProjectId } = ctx.query + let anxinStruc = await anxinStrucIdRange({ + ctx, pepProjectId + }) + const anxinStrucIds = anxinStruc.map(a => a.strucId) + let statusAlarmWhereOption = [] + let start = moment().add(-1, 'year').format('YYYY-MM-DD HH:mm:ss');//最近一年 + statusAlarmWhereOption.push(`camera_status_alarm.create_time >= '${start}'`) + + const videoAlarms = anxinStrucIds.length ? await clickHouse.vcmp.query( + `SELECT + cameraAlarm.cameraId AS cameraId, + cameraAlarm.alarmId AS alarmId, + cameraAlarm.createTime AS createTime, + cameraAlarm.confirmTime AS confirmTime, + ${'cameraAlarm.autoRestore AS autoRestore,'} + anxinStruc.id AS strucId + FROM + ( + SELECT + camera.id AS cameraId, + camera_status_alarm.id AS alarmId, + camera_status_alarm.create_time AS createTime, + camera_status_alarm.platform AS platform, + camera_status_alarm.status_id AS statusId, + camera_status_alarm.serial_no AS cameraSerialNo, + camera_status_alarm.channel_no AS cameraChannelNo, + ${'camera_status_alarm.auto_restore AS autoRestore,'} + camera_status_alarm.confirm_time AS confirmTime + FROM camera_status_alarm + INNER JOIN camera + ON camera.serial_no = camera_status_alarm.serial_no + AND camera.channel_no = camera_status_alarm.channel_no + WHERE + camera.delete = false + AND camera.recycle_time is null + ${statusAlarmWhereOption.length ? 'AND ' + statusAlarmWhereOption.join(' AND ') : ''} + AND alarmId IN ( + SELECT camera_status_alarm.id AS alarmId + FROM camera_status_alarm + RIGHT JOIN ${anxinyun}.t_video_ipc + ON toString(${anxinyun}.t_video_ipc.channel_no) = camera_status_alarm.channel_no + AND ${anxinyun}.t_video_ipc.serial_no = camera_status_alarm.serial_no + ${`WHERE ${anxinyun}.t_video_ipc.structure IN (${anxinStrucIds.join(',')})`} + ) + ) AS cameraAlarm + LEFT JOIN camera_status + ON cameraAlarm.platform = camera_status.platform + AND cameraAlarm.statusId = camera_status.id + LEFT JOIN camera_status_resolve + ON camera_status_resolve.status_id = camera_status.id + + LEFT JOIN ${anxinyun}.t_video_ipc AS anxinIpc + ON toString(anxinIpc.channel_no) = cameraAlarm.cameraChannelNo + AND anxinIpc.serial_no = cameraAlarm.cameraSerialNo + LEFT JOIN ${anxinyun}.t_structure AS anxinStruc + ON anxinStruc.id = anxinIpc.structure + AND anxinStruc.id IN (${anxinStrucIds.join(',')}) + LEFT JOIN ${anxinyun}.t_video_ipc_station AS anxinIpcStation + ON anxinIpcStation.ipc = anxinIpc.id + `).toPromise() : [] + + let returnD = [] + let positionD = {} + // 每个设备一个告警 for (let a of videoAlarms) { + if (positionD[a.cameraId]) { + } else { + let d = { + cameraId: a.cameraId, + autoRestore: a.autoRestore, + createTime: a.createTime, + alarmId: a.alarmId, + confirmTime: a.confirmTime, + } + d.pomsProject = ( + anxinStruc.find(as => as.strucId == a.strucId) || + { + pomsProject: [ + + ] + } + ).pomsProject.map(d => d.id) + returnD.push(d) + positionD[a.cameraId] = { + positionReturnD: returnD.length - 1 + } + } + } + let aggDayMap = []; + for (let a of returnD) { let exist = aggDayMap.find(ad => ad.day == moment(a.createTime).format('YYYY-MM-DD')); if (exist) { exist.total++;//总数 @@ -209,6 +245,26 @@ async function getVideoAlarmsAggDay(ctx) { } } +//BI分析-问题处置效率分析 +async function getAlarmsHandleStatistics(ctx) { + try { + const { projectCorrelationId } = ctx.query + const models = ctx.fs.dc.models; + const data = await models.AlarmHandleStatistics.findAll({ + order: [['time', 'DESC']], + where: projectCorrelationId ? { projectCorrelationId: projectCorrelationId } : {}, + limit: 1 + }) + ctx.status = 200; + ctx.body = data; + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + message: typeof error == 'string' ? error : undefined + } + } +} //最新动态 async function getLatestDynamic(ctx) { try { @@ -232,7 +288,11 @@ async function getLatestDynamic(ctx) { }, { model: models.AlarmAppearRecord }, { - model: models.EmailSendLog + model: models.EmailSendLog, + include: [{ + model: models.AlarmPushConfig, + attributes: ['id', 'name'], + }] }, { model: models.AlarmConfirmLog }], @@ -248,7 +308,9 @@ async function getLatestDynamic(ctx) { pepPojectIds.add(p.projectCorrelation.pepProjectId); if (p.emailSendLog) { - notedUserIds.add(p.emailSendLog.toPepUserId);//通知 接收人 + p.emailSendLog.toPepUserIds.map(u => { + notedUserIds.add(u);//通知 接收人 + }) } if (p.alarmConfirmLog && p.alarmConfirmLog.pepUserId) { notedUserIds.add(p.alarmConfirmLog.pepUserId);//确认 操作者 @@ -269,14 +331,14 @@ async function getLatestDynamic(ctx) { if (d.alarmAppearId) { appear.push({ projectName, - ...d.alarmAppearRecord + ...d.alarmAppearRecord.dataValues }); } if (d.emailSendId) { notice.push({ - userName: userPepRes.find(u => u.id == d.emailSendLog.toPepUserId).name, + userName: userPepRes.find(u => d.emailSendLog.toPepUserIds.indexOf(u.id) != -1), projectName, - ...d.emailSendLog + ...d.emailSendLog.dataValues }); } if (d.alarmConfirmId) { @@ -303,10 +365,10 @@ async function getLatestDynamic(ctx) { } module.exports = { - getProjectsInfo, - getDataAlarmsAggDay, getAppAlarmsAggDay, getVideoAlarmsAggDay, + getAlarmsHandleStatistics, + getLatestDynamic }; \ No newline at end of file diff --git a/api/app/lib/controllers/organization/index.js b/api/app/lib/controllers/organization/index.js index 7643065..a742c49 100644 --- a/api/app/lib/controllers/organization/index.js +++ b/api/app/lib/controllers/organization/index.js @@ -22,6 +22,27 @@ async function allDeps (ctx) { } } +async function allUsers (ctx) { + try { + const { models } = ctx.fs.dc; + const { clickHouse } = ctx.app.fs + + const userRes = await clickHouse.pepEmis.query(` + SELECT id, name FROM user + WHERE delete = false + `).toPromise() + + ctx.status = 200; + ctx.body = userRes + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: error`); + ctx.status = 400; + ctx.body = { + message: typeof error == 'string' ? error : undefined + } + } +} + async function editUser (ctx) { try { const models = ctx.fs.dc.models; @@ -183,7 +204,7 @@ async function user (ctx) { $not: { role: { $contained: ['SuperAdmin', 'admin'] } } - }, + }, sequelize.where(sequelize.fn('cardinality', sequelize.col('role')), 0)], // $not: { // role: { $contained: ['SuperAdmin', 'admin'] } @@ -311,4 +332,5 @@ module.exports = { putUser, delAdmin, user, + allUsers, }; \ No newline at end of file diff --git a/api/app/lib/controllers/project/index.js b/api/app/lib/controllers/project/index.js index 83797ba..0abf675 100644 --- a/api/app/lib/controllers/project/index.js +++ b/api/app/lib/controllers/project/index.js @@ -145,9 +145,102 @@ async function projectPManage (ctx) { } } +async function pepProjectConstrictionState (ctx) { + try { + const { models } = ctx.fs.dc; + const { clickHouse } = ctx.app.fs + + const cRes = await clickHouse.projectManage.query(` + SELECT * FROM t_pim_project_state ORDER BY id + `).toPromise() + + ctx.status = 200; + ctx.body = cRes + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: error`); + ctx.status = 400; + ctx.body = { + message: typeof error == 'string' ? error : undefined + } + } +} + +async function strucWithPomsProject (ctx) { + try { + const { models } = ctx.fs.dc; + const { clickHouse } = ctx.app.fs + const { pomsProjectId } = ctx.query + + const bindRes = await models.ProjectCorrelation.findOne({ + where: { + id: pomsProjectId + } + }) + + let undelStruc = [] + if (bindRes) { + const undelStrucRes = bindRes.anxinProjectId.length ? + await clickHouse.anxinyun.query( + ` + SELECT + t_structure.id AS strucId, + t_structure.name AS strucName + FROM + t_project + LEFT JOIN + t_project_structure + ON t_project_structure.project = t_project.id + LEFT JOIN + t_project_structuregroup + ON t_project_structuregroup.project = t_project.id + LEFT JOIN + t_structuregroup_structure + ON t_structuregroup_structure.structuregroup = t_project_structuregroup.structuregroup + LEFT JOIN + t_project_construction + ON t_project_construction.project = t_project.id + LEFT JOIN + t_structure_site + ON t_structure_site.siteid = t_project_construction.construction + RIGHT JOIN + t_structure + ON t_structure.id = t_project_structure.structure + OR t_structure.id = t_structuregroup_structure.structure + OR t_structure.id = t_structure_site.structid + WHERE + project_state != -1 + AND + t_project.id IN (${ bindRes.anxinProjectId.join(',')}) + ORDER BY strucId + ` + ).toPromise() : + [] + for (let s of undelStrucRes) { + if (!undelStruc.some(us => us.id == s.strucId)) { + undelStruc.push({ + id: s.strucId, + name: s.strucName, + }) + } + } + } + + ctx.status = 200; + ctx.body = undelStruc + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: error`); + ctx.status = 400; + ctx.body = { + message: typeof error == 'string' ? error : undefined + } + } +} + module.exports = { appList, projectAnxincloud, projectPManage, pomsProject, + pepProjectConstrictionState, + strucWithPomsProject, }; \ No newline at end of file diff --git a/api/app/lib/controllers/push/config.js b/api/app/lib/controllers/push/config.js index f4cba4f..3feedea 100644 --- a/api/app/lib/controllers/push/config.js +++ b/api/app/lib/controllers/push/config.js @@ -4,38 +4,152 @@ const moment = require('moment') async function list (ctx) { try { const models = ctx.fs.dc.models; - const { keyword, alarmType, state, } = ctx.request.body + const sequelize = ctx.fs.dc.ORM; + const { clickHouse } = ctx.app.fs + const { utils: { anxinStrucIdRange, pomsProjectRange } } = ctx.app.fs + const { keyword, keywordTarget, alarmType, state, tactics, pomsProjectId } = ctx.query + let projectCorrelationWhere = { + del: false, + } + if (state == 'notYet') { + projectCorrelationWhere.pepProjectId = { $ne: null } + } let findOption = { where: { + del: false + }, + include: [{ + model: models.ProjectCorrelation, + where: projectCorrelationWhere, + required: true + }] + } + let anxinStrucsRange = await anxinStrucIdRange({ + ctx, pepProjectId: pomsProjectId, keywordTarget, keyword + }) + if (keywordTarget && keyword) { + if (keywordTarget == 'tactics') { + findOption.where.name = { $like: `%${keyword}%` } + } else if (keywordTarget == 'struc') { + let bindAnixinStrucRes = await clickHouse.projectManage.query(` + SELECT id, name FROM t_structure + WHERE name LIKE '%${keyword}%' + `).toPromise() + let bindAnixinStrucIds = bindAnixinStrucRes.map(s => s.id) + findOption.where.strucId = { $contains: bindAnixinStrucIds } + } else if (keywordTarget == 'pepProject') { + // 这种情况在 pomsProjectRange 函数值已处理 } } + const pomsProjectRes = await pomsProjectRange({ + ctx, pepProjectId: pomsProjectId, keywordTarget, keyword + }) + let pomsProjectIds = pomsProjectRes.map(p => p.id) + findOption.where.pomsProjectId = { $in: pomsProjectIds } - if (keyword) { - findOption.where.$or = [ - { - name: { $like: `%${keyword}%` } - }, - ] - } if (alarmType) { findOption.where.alarmType = { $contains: [alarmType] } } if (state) { - if (state == 'enable') { + if (state == 'enable' || state == 'notYet' || state == 'takeEffect') { findOption.where.disable = false } else if (state == 'disable') { findOption.where.disable = true - } else if (state == 'overtime') { + } + } + if (tactics) { + findOption.where.tactics = tactics + } + const listRes = await models.AlarmPushConfig.findAll(findOption) + let allStrucIds = new Set() + let allConfigId = [] + let allReceiverIds = new Set() + for (let p of listRes) { + for (let sid of p.strucId) { + allStrucIds.add(sid) + } + allConfigId.push(p.id) + for (let uid of p.receiverPepUserId) { + allReceiverIds.add(uid) } } + // 查配置所包含的所有结构物 + const allStrucRes = allStrucIds.size ? await clickHouse.anxinyun.query(` + SELECT id, name FROM t_structure + WHERE id IN (${[...allStrucIds].join(',')}) + `).toPromise() : [] + + // 查所有配置的推送的次数 + const pushLogCountRes = await models.EmailSendLog.findAll({ + attributes: [ + 'pushConfigId', + [sequelize.fn('COUNT', sequelize.col('push_config_id')), 'count'] + ], + where: { + pushConfigId: { $in: allConfigId } + }, + group: ['pushConfigId'] + }) + + // 查询所有的用户信息 + const userRes = allReceiverIds.size ? await clickHouse.pepEmis.query(` + SELECT id, name, delete FROM user + WHERE id IN (${[...allReceiverIds].join(',')}) + `).toPromise() : [] + + let returnD = [] + for (let { dataValues: p } of listRes) { + // 查对应的 poms 绑定的结构物绑定关系 + const corBind = pomsProjectRes.find(ppj => ppj.id == p.pomsProjectId) + if (corBind.pepProjectId) { + if (state == 'notYet') { + if (corBind.pepProject && p.timeType.some(pt => pt == corBind.pepProject.constructionStatusId)) { + continue + } + } else if (state == 'takeEffect') { + if (!corBind.pepProject || !p.timeType.some(pt => pt == corBind.pepProject.constructionStatusId)) { + continue + } + } + } + + // 结构物信息 + let returnStruc = [] + for (let sid of p.strucId) { + // 查这个结构物的信息 + let structure = allStrucRes.find(asr => asr.id == sid) + if (structure) { + // 检查当前结构物有没有从已绑定的项目里解绑 + // 查对应的可见的结构物信息 + const anxinStrucSeen = anxinStrucsRange.find(axs => axs.strucId == sid) + returnStruc.push({ + id: sid, + name: structure.name, + unbind: !anxinStrucSeen || !corBind.anxinProjectId.includes(anxinStrucSeen.projectId) + }) + } else { + // 这个结构物已删 + } + } - const listRes = await models.AlarmPushConfig.findAndCountAll(findOption) + // 查找日志数量 + const corLogCount = pushLogCountRes.find(plc => plc.pushConfigId == p.id) + // 查找接收人数据 + const corReceiver = userRes.filter(u => p.receiverPepUserId.some(prId => u.id == prId)) + returnD.push({ + ...p, + pomsProject: corBind, + structure: returnStruc, + pushCount: corLogCount ? corLogCount.count : 0, + receiverPepUser: corReceiver + }) + } ctx.status = 200; - ctx.body = listRes + ctx.body = returnD } catch (error) { ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); ctx.status = 400; @@ -49,10 +163,12 @@ async function edit (ctx) { try { const models = ctx.fs.dc.models; const { userId, pepUserId } = ctx.fs.api - const { pushId, name, pepProjectId = [], alarmType = [], receiverPepUserId = [], timeType = [], disable } = ctx.request.body + const { pushId, name, pomsProjectId, alarmType = [], receiverPepUserId = [], timeType = [], disable, + strucId = [], tactics, tacticsParams } = ctx.request.body let storageData = { - name, pepProjectId, alarmType, receiverPepUserId, timeType, disable + name, pomsProjectId, alarmType, receiverPepUserId, timeType, disable, + strucId, tactics, tacticsParams } if (pushId) { await models.AlarmPushConfig.update(storageData, { @@ -63,6 +179,7 @@ async function edit (ctx) { } else { storageData.createTime = moment().format() storageData.createUserId = userId + storageData.del = false await models.AlarmPushConfig.create(storageData) } @@ -82,22 +199,17 @@ async function state (ctx) { const { pushId } = ctx.params const { disable = undefined, del = undefined, } = ctx.request.body - if (del) { - await models.AlarmPushConfig.destroy({ - where: { - id: pushId - } - }) - } else { - await models.AlarmPushConfig.update({ - disable, - }, { - where: { - id: pushId - } - }) + let updateData = { + disable, + del, } + await models.AlarmPushConfig.update(updateData, { + where: { + id: pushId + } + }) + ctx.status = 204; } catch (error) { ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); diff --git a/api/app/lib/index.js b/api/app/lib/index.js index 10a1b33..dcc655c 100644 --- a/api/app/lib/index.js +++ b/api/app/lib/index.js @@ -58,7 +58,7 @@ module.exports.models = function (dc) { // dc = { orm: Sequelize对象, ORM: Seq }); const { - AppInspection, ProjectApp, ProjectCorrelation, AppAlarm, App, AlarmAppearRecord, AlarmConfirmLog, EmailSendLog, LatestDynamicList + AppInspection, ProjectApp, ProjectCorrelation, AppAlarm, App, AlarmAppearRecord, AlarmConfirmLog, EmailSendLog, LatestDynamicList, AlarmPushConfig } = dc.models; AppInspection.belongsTo(App, { foreignKey: 'projectAppId', targetKey: 'id' }); @@ -77,7 +77,8 @@ module.exports.models = function (dc) { // dc = { orm: Sequelize对象, ORM: Seq AppAlarm.belongsTo(App, { foreignKey: 'projectAppId', targetKey: 'id' }); App.hasMany(AppAlarm, { foreignKey: 'projectAppId', sourceKey: 'id' }); - + AlarmPushConfig.belongsTo(ProjectCorrelation, { foreignKey: 'pomsProjectId', targetKey: 'id' }); + ProjectCorrelation.hasMany(AlarmPushConfig, { foreignKey: 'pomsProjectId', sourceKey: 'id' }); AlarmAppearRecord.belongsTo(ProjectCorrelation, { foreignKey: 'projectCorrelationId', targetKey: 'id' }); ProjectCorrelation.hasMany(AlarmAppearRecord, { foreignKey: 'projectCorrelationId', sourceKey: 'id' }); @@ -88,6 +89,9 @@ module.exports.models = function (dc) { // dc = { orm: Sequelize对象, ORM: Seq EmailSendLog.belongsTo(ProjectCorrelation, { foreignKey: 'projectCorrelationId', targetKey: 'id' }); ProjectCorrelation.hasMany(EmailSendLog, { foreignKey: 'projectCorrelationId', sourceKey: 'id' }); + EmailSendLog.belongsTo(AlarmPushConfig, { foreignKey: 'pushConfigId', targetKey: 'id' }); + AlarmPushConfig.hasMany(EmailSendLog, { foreignKey: 'pushConfigId', sourceKey: 'id' }); + LatestDynamicList.belongsTo(AlarmAppearRecord, { foreignKey: 'alarmAppearId', targetKey: 'id' }); AlarmAppearRecord.hasMany(LatestDynamicList, { foreignKey: 'alarmAppearId', sourceKey: 'id' }); diff --git a/api/app/lib/models/alarm_handle_statistics.js b/api/app/lib/models/alarm_handle_statistics.js new file mode 100644 index 0000000..ad5ef7d --- /dev/null +++ b/api/app/lib/models/alarm_handle_statistics.js @@ -0,0 +1,98 @@ +/* eslint-disable*/ + +'use strict'; + +module.exports = dc => { + const DataTypes = dc.ORM; + const sequelize = dc.orm; + const AlarmHandleStatistics = sequelize.define("alarmHandleStatistics", { + id: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: true, + field: "id", + autoIncrement: true, + unique: "alarm_handle_statistics_id_uindex" + }, + time: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "time", + autoIncrement: false + }, + projectCorrelationId: { + type: DataTypes.INTEGER, + allowNull: true, + defaultValue: null, + comment: null, + primaryKey: false, + field: "project_correlation_id", + autoIncrement: false + }, + day1: { + type: DataTypes.DOUBLE, + allowNull: true, + defaultValue: null, + comment: null, + primaryKey: false, + field: "day1", + autoIncrement: false + }, + day3: { + type: DataTypes.DOUBLE, + allowNull: true, + defaultValue: null, + comment: null, + primaryKey: false, + field: "day3", + autoIncrement: false + }, + day7: { + type: DataTypes.DOUBLE, + allowNull: true, + defaultValue: null, + comment: null, + primaryKey: false, + field: "day7", + autoIncrement: false + }, + day15: { + type: DataTypes.DOUBLE, + allowNull: true, + defaultValue: null, + comment: null, + primaryKey: false, + field: "day15", + autoIncrement: false + }, + day30: { + type: DataTypes.DOUBLE, + allowNull: true, + defaultValue: null, + comment: null, + primaryKey: false, + field: "day30", + autoIncrement: false + }, + day30m: { + type: DataTypes.DOUBLE, + allowNull: true, + defaultValue: null, + comment: null, + primaryKey: false, + field: "day30m", + autoIncrement: false + } + }, { + tableName: "alarm_handle_statistics", + comment: "", + indexes: [] + }); + dc.models.AlarmHandleStatistics = AlarmHandleStatistics; + return AlarmHandleStatistics; +}; \ No newline at end of file diff --git a/api/app/lib/models/alarm_push_config.js b/api/app/lib/models/alarm_push_config.js index 296d53d..7ea7a19 100644 --- a/api/app/lib/models/alarm_push_config.js +++ b/api/app/lib/models/alarm_push_config.js @@ -2,96 +2,132 @@ 'use strict'; module.exports = dc => { - const DataTypes = dc.ORM; - const sequelize = dc.orm; - const AlarmPushConfig = sequelize.define("alarmPushConfig", { - id: { - type: DataTypes.INTEGER, - allowNull: false, - defaultValue: null, - comment: null, - primaryKey: true, - field: "id", - autoIncrement: true, - unique: "alarm_push_config_id_uindex" - }, - name: { - type: DataTypes.STRING, - allowNull: false, - defaultValue: null, - comment: null, - primaryKey: false, - field: "name", - autoIncrement: false - }, - pepProjectId: { - type: DataTypes.ARRAY(DataTypes.INTEGER), - allowNull: false, - defaultValue: null, - comment: null, - primaryKey: false, - field: "pep_project_id", - autoIncrement: false - }, - alarmType: { - type: DataTypes.ARRAY(DataTypes.STRING), - allowNull: true, - defaultValue: null, - comment: "监听的告警类型", - primaryKey: false, - field: "alarm_type", - autoIncrement: false - }, - receiverPepUserId: { - type: DataTypes.ARRAY(DataTypes.INTEGER), - allowNull: true, - defaultValue: null, - comment: "接收人id 项企", - primaryKey: false, - field: "receiver_pep_user_id", - autoIncrement: false - }, - timeType: { - type: DataTypes.ARRAY(DataTypes.STRING), - allowNull: true, - defaultValue: null, - comment: "通知时效", - primaryKey: false, - field: "time_type", - autoIncrement: false - }, - createTime: { - type: DataTypes.DATE, - allowNull: false, - defaultValue: null, - comment: null, - primaryKey: false, - field: "create_time", - autoIncrement: false - }, - createUserId: { - type: DataTypes.INTEGER, - allowNull: false, - defaultValue: null, - comment: null, - primaryKey: false, - field: "create_user_id", - autoIncrement: false - }, - disable: { - type: DataTypes.BOOLEAN, - allowNull: false, - defaultValue: null, - comment: null, - primaryKey: false, - field: "disable", - autoIncrement: false - } - }, { - tableName: "alarm_push_config", - comment: "", - indexes: [] - }); - dc.models.AlarmPushConfig = AlarmPushConfig; - return AlarmPushConfig; + const DataTypes = dc.ORM; + const sequelize = dc.orm; + const AlarmPushConfig = sequelize.define("alarmPushConfig", { + id: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: true, + field: "id", + autoIncrement: true, + unique: "alarm_push_config_id_uindex" + }, + name: { + type: DataTypes.STRING, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "name", + autoIncrement: false + }, + pomsProjectId: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "poms_project_id", + autoIncrement: false + }, + alarmType: { + type: DataTypes.ARRAY(DataTypes.STRING), + allowNull: true, + defaultValue: null, + comment: "监听的告警类型", + primaryKey: false, + field: "alarm_type", + autoIncrement: false + }, + receiverPepUserId: { + type: DataTypes.ARRAY(DataTypes.INTEGER), + allowNull: true, + defaultValue: null, + comment: "接收人id 项企", + primaryKey: false, + field: "receiver_pep_user_id", + autoIncrement: false + }, + timeType: { + type: DataTypes.ARRAY(DataTypes.STRING), + allowNull: true, + defaultValue: null, + comment: "通知时效", + primaryKey: false, + field: "time_type", + autoIncrement: false + }, + createTime: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "create_time", + autoIncrement: false + }, + createUserId: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "create_user_id", + autoIncrement: false + }, + disable: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "disable", + autoIncrement: false + }, + strucId: { + type: DataTypes.ARRAY(DataTypes.INTEGER), + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "struc_id", + autoIncrement: false + }, + tactics: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + comment: "immediately 即时 / continue 持续 / abnormal_rate 异常率", + primaryKey: false, + field: "tactics", + autoIncrement: false + }, + tacticsParams: { + type: DataTypes.JSONB, + allowNull: true, + defaultValue: null, + comment: "推送策略 tactics 的参数", + primaryKey: false, + field: "tactics_params", + autoIncrement: false + }, + del: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "del", + autoIncrement: false + } + }, { + tableName: "alarm_push_config", + comment: "", + indexes: [] + }); + dc.models.AlarmPushConfig = AlarmPushConfig; + return AlarmPushConfig; }; \ No newline at end of file diff --git a/api/app/lib/models/email_send_log.js b/api/app/lib/models/email_send_log.js index 7f75ea1..e1d95b6 100644 --- a/api/app/lib/models/email_send_log.js +++ b/api/app/lib/models/email_send_log.js @@ -16,40 +16,58 @@ module.exports = dc => { autoIncrement: true, unique: "email_send_log_id_uindex" }, - projectCorrelationId: { - type: DataTypes.INTEGER, + time: { + type: DataTypes.DATE, allowNull: false, defaultValue: null, comment: null, primaryKey: false, - field: "project_correlation_id", + field: "time", autoIncrement: false }, - toPepUserId: { + pushConfigId: { type: DataTypes.INTEGER, allowNull: false, defaultValue: null, comment: null, primaryKey: false, - field: "to_pep_user_id", + field: "push_config_id", autoIncrement: false }, - by: { + tactics: { type: DataTypes.STRING, allowNull: true, defaultValue: null, comment: null, primaryKey: false, - field: "by", + field: "tactics", autoIncrement: false }, - time: { - type: DataTypes.DATE, + tacticsParams: { + type: DataTypes.JSONB, + allowNull: true, + defaultValue: null, + comment: null, + primaryKey: false, + field: "tactics_params", + autoIncrement: false + }, + projectCorrelationId: { + type: DataTypes.INTEGER, allowNull: false, defaultValue: null, comment: null, primaryKey: false, - field: "time", + field: "project_correlation_id", + autoIncrement: false + }, + toPepUserIds: { + type: DataTypes.ARRAY(DataTypes.INTEGER), + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "to_pep_user_ids", autoIncrement: false } }, { diff --git a/api/app/lib/routes/control/index.js b/api/app/lib/routes/control/index.js index c8962b1..a129002 100644 --- a/api/app/lib/routes/control/index.js +++ b/api/app/lib/routes/control/index.js @@ -27,11 +27,6 @@ module.exports = function (app, router, opts) { router.get('/analysis/problem', analysis.problem); - - //项目概览 - app.fs.api.logAttr['GET/projects/info'] = { content: '查询项目概览', visible: false }; - router.get('/projects/info', csData.getProjectsInfo); - //BI分析模块 app.fs.api.logAttr['GET/data/alarms/agg/day'] = { content: '查询BI分析数据-数据', visible: false }; router.get('/data/alarms/agg/day', csData.getDataAlarmsAggDay); @@ -42,6 +37,9 @@ module.exports = function (app, router, opts) { app.fs.api.logAttr['GET/video/alarms/agg/day'] = { content: '查询BI分析数据-视频异常', visible: false }; router.get('/video/alarms/agg/day', csData.getVideoAlarmsAggDay); + app.fs.api.logAttr['GET/alarms/handle/statistics'] = { content: '查询BI分析数据-问题处理效率分析', visible: false }; + router.get('/alarms/handle/statistics', csData.getAlarmsHandleStatistics); + //最新动态 app.fs.api.logAttr['GET/latest/dynamic'] = { content: '查询最新动态', visible: false }; router.get('/latest/dynamic', csData.getLatestDynamic); diff --git a/api/app/lib/routes/organization/index.js b/api/app/lib/routes/organization/index.js index 4b26221..5c8106e 100644 --- a/api/app/lib/routes/organization/index.js +++ b/api/app/lib/routes/organization/index.js @@ -5,6 +5,9 @@ module.exports = function (app, router, opts) { app.fs.api.logAttr['GET/organization/deps'] = { content: '获取全部部门及其下用户', visible: true }; router.get('/organization/deps', organization.allDeps); + app.fs.api.logAttr['GET/organization/users'] = { content: '获取全部未删除用户', visible: true }; + router.get('/organization/users', organization.allUsers); + app.fs.api.logAttr['POST/organization/user'] = { content: '编辑成员', visible: true }; router.post('/organization/user', organization.editUser); diff --git a/api/app/lib/routes/project/index.js b/api/app/lib/routes/project/index.js index 241aaf3..aa3a5a7 100644 --- a/api/app/lib/routes/project/index.js +++ b/api/app/lib/routes/project/index.js @@ -21,4 +21,10 @@ module.exports = function (app, router, opts) { app.fs.api.logAttr['GET/project/poms'] = { content: '获取已绑定项目', visible: true }; router.get('/project/poms', project.pomsProject); + + app.fs.api.logAttr['GET/project/status'] = { content: '获取项目状态列表', visible: true }; + router.get('/project/status', project.pepProjectConstrictionState); + + app.fs.api.logAttr['GET/project/structure'] = { content: '获取绑定项目下结构物', visible: true }; + router.get('/project/structure', project.strucWithPomsProject); }; \ No newline at end of file diff --git a/api/app/lib/routes/push/index.js b/api/app/lib/routes/push/index.js index 560784d..f62393d 100644 --- a/api/app/lib/routes/push/index.js +++ b/api/app/lib/routes/push/index.js @@ -7,7 +7,7 @@ module.exports = function (app, router, opts) { router.get('/push', push.list); app.fs.api.logAttr['POST/push'] = { content: '新增/编辑推送配置', visible: true }; - router.get('/push', push.edit); + router.post('/push', push.edit); app.fs.api.logAttr['PUT/push/:pushId'] = { content: '更改推送配置状态(禁用或删除)', visible: true }; router.put('/push/:pushId', push.state); diff --git a/api/app/lib/schedule/alarms_handle_statistics.js b/api/app/lib/schedule/alarms_handle_statistics.js new file mode 100644 index 0000000..04fab12 --- /dev/null +++ b/api/app/lib/schedule/alarms_handle_statistics.js @@ -0,0 +1,480 @@ +'use strict'; +const moment = require('moment'); + +module.exports = function (app, opts) { + const { models } = app.fs.dc + const { clickHouse } = app.fs + const { database: anxinyun } = clickHouse.anxinyun.opts.config + const alarmHandleStatistics = app.fs.scheduleInit( + { + interval: '0 58 9 * * *', + // immediate: true, + //proRun: true, + }, + async () => { + try { + let anxinStruc = await getAxyStructs() + let pomsProject = await pomsProjectRange() + if (anxinStruc.length) { + let dataAlarms = await getDataAlarms(anxinStruc);//数据告警 + let appAlarms = await getAppAlarms(pomsProject);//应用告警 + let videoAlarms = await getVideoAlarms(anxinStruc);//视频告警 + + + let time = moment().format() + //算全局 + let dataArrToSave = [] + let dataMap = calculate(dataAlarms, appAlarms, videoAlarms) + let sum = dataAlarms.length + appAlarms.length + videoAlarms.length; + if (sum) { + dataArrToSave.push({ + time: time, + projectCorrelationId: null,//全局 + day1: parseFloat((100 * dataMap.day1 / sum).toFixed(2)), + day3: parseFloat((100 * dataMap.day3 / sum).toFixed(2)), + day7: parseFloat((100 * dataMap.day7 / sum).toFixed(2)), + day15: parseFloat((100 * dataMap.day15 / sum).toFixed(2)), + day30: parseFloat((100 * dataMap.day30 / sum).toFixed(2)), + day30m: parseFloat((100 * dataMap.day30m / sum).toFixed(2)), + }) + } + + //算单个项目 + pomsProject.map(p => { + let pid = p.id; + let pDataAlarms = dataAlarms.filter(da => da.pomsProject.indexOf(pid) != -1) + let pAppAlarms = appAlarms.filter(aa => aa.app.projectCorrelations.map(ap => ap.id).indexOf(pid) != -1) + let pVideoAlarms = videoAlarms.filter(va => va.pomsProject.indexOf(pid) != -1) + + let pDataMap = calculate(pDataAlarms, pAppAlarms, pVideoAlarms) + let sm = pDataAlarms.length + pAppAlarms.length + pVideoAlarms.length; + if (sm) { + dataArrToSave.push({ + time: time, + projectCorrelationId: pid,//单个项目 + day1: parseFloat((100 * pDataMap.day1 / sum).toFixed(2)), + day3: parseFloat((100 * pDataMap.day3 / sum).toFixed(2)), + day7: parseFloat((100 * pDataMap.day7 / sum).toFixed(2)), + day15: parseFloat((100 * pDataMap.day15 / sum).toFixed(2)), + day30: parseFloat((100 * pDataMap.day30 / sum).toFixed(2)), + day30m: parseFloat((100 * pDataMap.day30m / sum).toFixed(2)), + }) + } + }) + await models.AlarmHandleStatistics.bulkCreate(dataArrToSave) + } + } catch (error) { + console.error(error); + } + } + ) + + function calculate(dataAlarms, appAlarms, videoAlarms) { + try { + //算全局 + let dataMap = { + day1: 0,//当日处理 + day3: 0,//3日内 + day7: 0,//7日内 + day15: 0,//15日内 + day30: 0,//30日内 + day30m: 0//超过30日 + } + dataAlarms.filter(d => d.State > 3).map(da => { + let range = moment(da.confirmTime).diff(moment(da.StartTime), 'day') + if (range <= 1) { + dataMap.day1++ + } else if (range > 1 && range <= 3) { + dataMap.day3++ + } else if (range > 3 && range <= 7) { + dataMap.day7++ + } else if (range > 7 && range <= 15) { + dataMap.day15++ + } else if (range > 15 && range <= 30) { + dataMap.day30++ + } else if (range > 30) { + dataMap.day30m++ + } + }) + appAlarms.filter(d => d.confirmTime).map(da => { + let range = moment(da.confirmTime).diff(moment(da.createTime), 'day') + if (range < 1) { + dataMap.day1++ + } else if (range >= 1 && range < 3) { + dataMap.day3++ + } else if (range >= 3 && range < 7) { + dataMap.day7++ + } else if (range >= 7 && range < 15) { + dataMap.day15++ + } else if (range >= 15 && range < 30) { + dataMap.day30++ + } else if (range >= 30) { + dataMap.day30m++ + } + }) + videoAlarms.filter(d => d.confirmTime || d.autoRestore).map(da => { + let range = moment(da.confirmTime).diff(moment(da.createTime), 'day') + if (range < 1) { + dataMap.day1++ + } else if (range >= 1 && range < 3) { + dataMap.day3++ + } else if (range >= 3 && range < 7) { + dataMap.day7++ + } else if (range >= 7 && range < 15) { + dataMap.day15++ + } else if (range >= 15 && range < 30) { + dataMap.day30++ + } else if (range >= 30) { + dataMap.day30m++ + } + }) + return dataMap; + } catch (error) { + console.error(error); + } + } + + async function getDataAlarms(anxinStruc) { + try { + const anxinStrucIds = anxinStruc.map(a => a.strucId) + let whereOption = [] + whereOption.push(`alarms.StructureId IN (${anxinStrucIds.join(",")})`) + + let start = moment().add(-1, 'year').format('YYYY-MM-DD HH:mm:ss');//最近一年 + whereOption.push(`alarms.StartTime >= '${start}'`) + + let alarmQueryOptionStr = `FROM alarms + LEFT JOIN ${anxinyun}.t_structure + ON ${anxinyun}.t_structure.id = alarms.StructureId + ${whereOption.length ? 'WHERE ' + whereOption.join(' AND ') : ''}` + + console.log('开始查数据-数据-数据类告警---' + moment().format('YYYY-MM-DD HH:mm:ss')) + + const alarmRes = await clickHouse.dataAlarm.query(` + SELECT + alarms.AlarmId AS AlarmId, + alarms.State AS State, + alarms.StructureId AS StructureId, + StartTime, EndTime + ${alarmQueryOptionStr}`).toPromise(); + + console.log('数据-数据-数据告警查询结束---' + moment().format('YYYY-MM-DD HH:mm:ss') + `---一共${alarmRes.length}条`) + + const confirmedAlarm = alarmRes.filter(ar => ar.State && ar.State > 2).map(ar => "'" + ar.AlarmId + "'"); + const confirmedAlarmDetailMax = confirmedAlarm.length ? + await clickHouse.dataAlarm.query(` + SELECT + max(Time) AS Time, AlarmId + FROM + alarm_details + WHERE + AlarmId IN (${confirmedAlarm.join(',')}) + GROUP BY AlarmId + `).toPromise() : []; + + alarmRes.forEach(ar => { + ar.pomsProject = ( + anxinStruc.find(as => as.strucId == ar.StructureId) || + { + pomsProject: [ + // TODO: 开发临时添加 + ] + } + ).pomsProject.map(p => p.id) + + // 最新告警详情 - 确认信息 + let corConfirmedData = (confirmedAlarmDetailMax.find(cdm => cdm.AlarmId == ar.AlarmId) || {}); + ar.confirmTime = corConfirmedData.Time || ar.EndTime + }) + return alarmRes; + } catch (error) { + console.error(error); + } + } + + async function getAppAlarms(pomsProject) { + try { + const pomsProjectIds = pomsProject.map(p => p.id) + let findOption = { + where: { + createTime: { $gte: moment().add(-1, 'year').format() },//最近一年 + }, + attributes: ['id', 'createTime', 'confirmTime'], + include: [{ + model: models.App, + attributes: ['id'], + include: [{ + model: models.ProjectCorrelation, + attributes: ['id'] + }] + }] + } + findOption.where['$app->projectCorrelations.id$'] = { + $in: pomsProjectIds + } + + console.log('开始查应用-应用-应用告警---' + moment().format('YYYY-MM-DD HH:mm:ss')) + const listRes = await models.AppAlarm.findAll(findOption) + console.log('应用-应用-应用告警查询结束---' + moment().format('YYYY-MM-DD HH:mm:ss') + `---一共${listRes.length}条`) + + return listRes + } catch (error) { + console.error(error); + } + } + async function getVideoAlarms(anxinStruc) { + try { + const anxinStrucIds = anxinStruc.map(a => a.strucId) + let statusAlarmWhereOption = [] + let start = moment().add(-1, 'year').format('YYYY-MM-DD HH:mm:ss');//最近一年 + statusAlarmWhereOption.push(`camera_status_alarm.create_time >= '${start}'`) + + console.log('开始查视频-视频-视频告警---' + moment().format('YYYY-MM-DD HH:mm:ss')) + const alarmRes = anxinStrucIds.length ? await clickHouse.vcmp.query( + ` + SELECT + cameraAlarm.cameraId AS cameraId, + cameraAlarm.alarmId AS alarmId, + cameraAlarm.createTime AS createTime, + cameraAlarm.confirmTime AS confirmTime, + ${'cameraAlarm.autoRestore AS autoRestore,'} + anxinStruc.id AS strucId + FROM + ( + SELECT + camera.id AS cameraId, + camera_status_alarm.id AS alarmId, + camera_status_alarm.create_time AS createTime, + camera_status_alarm.platform AS platform, + camera_status_alarm.status_id AS statusId, + camera_status_alarm.serial_no AS cameraSerialNo, + camera_status_alarm.channel_no AS cameraChannelNo, + ${'camera_status_alarm.auto_restore AS autoRestore,'} + camera_status_alarm.confirm_time AS confirmTime + FROM camera_status_alarm + INNER JOIN camera + ON camera.serial_no = camera_status_alarm.serial_no + AND camera.channel_no = camera_status_alarm.channel_no + WHERE + camera.delete = false + AND camera.recycle_time is null + ${statusAlarmWhereOption.length ? 'AND ' + statusAlarmWhereOption.join(' AND ') : ''} + AND alarmId IN ( + SELECT camera_status_alarm.id AS alarmId + FROM camera_status_alarm + RIGHT JOIN ${anxinyun}.t_video_ipc + ON toString(${anxinyun}.t_video_ipc.channel_no) = camera_status_alarm.channel_no + AND ${anxinyun}.t_video_ipc.serial_no = camera_status_alarm.serial_no + ${`WHERE ${anxinyun}.t_video_ipc.structure IN (${anxinStrucIds.join(',')})`} + ) + ) AS cameraAlarm + LEFT JOIN camera_status + ON cameraAlarm.platform = camera_status.platform + AND cameraAlarm.statusId = camera_status.id + LEFT JOIN camera_status_resolve + ON camera_status_resolve.status_id = camera_status.id + + LEFT JOIN ${anxinyun}.t_video_ipc AS anxinIpc + ON toString(anxinIpc.channel_no) = cameraAlarm.cameraChannelNo + AND anxinIpc.serial_no = cameraAlarm.cameraSerialNo + LEFT JOIN ${anxinyun}.t_structure AS anxinStruc + ON anxinStruc.id = anxinIpc.structure + AND anxinStruc.id IN (${anxinStrucIds.join(',')}) + LEFT JOIN ${anxinyun}.t_video_ipc_station AS anxinIpcStation + ON anxinIpcStation.ipc = anxinIpc.id + `).toPromise() : [] + + console.log('视频-视频-视频告警查询结束---' + moment().format('YYYY-MM-DD HH:mm:ss') + `---一共${alarmRes.length}条`) + let returnD = [] + let positionD = {} + // 每个设备一个告警 + for (let a of alarmRes) { + if (positionD[a.cameraId]) { + } else { + let d = { + cameraId: a.cameraId, + autoRestore: a.autoRestore, + createTime: a.createTime, + alarmId: a.alarmId, + confirmTime: a.confirmTime, + } + // pep 项目 + d.pomsProject = ( + anxinStruc.find(as => as.strucId == a.strucId) || + { + pomsProject: [ + + ] + } + ).pomsProject.map(d => d.id) + returnD.push(d) + positionD[a.cameraId] = { + positionReturnD: returnD.length - 1 + } + } + } + return returnD; + } catch (error) { + console.error(error); + } + } + async function pomsProjectRange() { + try { + const { pepProjectRes, bindRes } = await pomsWithPepRangeParams() + let pomsProject = [] + for (let b of bindRes) { + if (b.pepProjectId) { + let corPepProject = pepProjectRes.find(pp => pp.id == b.pepProjectId) || {} + pomsProject.push({ + ...b.dataValues, + pepProject: corPepProject + }) + } else { + pomsProject.push({ + ...b.dataValues + }) + } + } + return pomsProject + } catch (error) { + console.error(error); + } + } + + async function pomsWithPepRangeParams() { + try { + const bindRes = await models.ProjectCorrelation.findAll({ where: { del: false } }); + // 获取不重复的 项企项目id + let pepProjectIds = [] + for (let b of bindRes) { + if (b.pepProjectId) { + pepProjectIds.push(b.pepProjectId) + } + } + // 查询项企项目的信息 + const pepProjectRes = pepProjectIds.length ? + await clickHouse.projectManage.query( + `SELECT + t_pim_project.id AS id, + t_pim_project.project_name AS projectName, + t_pim_project.isdelete AS isdelete, + t_pim_project_construction.construction_status_id AS constructionStatusId, + t_pim_project_state.construction_status AS constructionStatus + FROM + t_pim_project + LEFT JOIN t_pim_project_construction + ON t_pim_project.id = t_pim_project_construction.project_id + LEFT JOIN t_pim_project_state + ON t_pim_project_construction.construction_status_id = t_pim_project_state.id + WHERE + id IN (${pepProjectIds.join(',')})` + ).toPromise() : []; + + return { + pepProjectRes, bindRes + } + + } catch (error) { + console.error(error); + } + } + + async function getAxyStructs() { + try { + const { pepProjectRes, bindRes } = await pomsWithPepRangeParams() + // 获取不重复的 安心云项目 id + const anxinProjectIds = [ + ...(bindRes).reduce( + (arr, b) => { + for (let sid of b.anxinProjectId) { + arr.add(sid); + } + return arr; + }, + new Set() + ) + ] + // 查询安心云项目及结构物信息 + const undelStrucRes = anxinProjectIds.length ? + await clickHouse.anxinyun.query( + `SELECT + t_project.id AS projectId, + t_structure.id AS strucId, + t_structure.name AS strucName, + project_state + FROM + t_project + LEFT JOIN + t_project_structure + ON t_project_structure.project = t_project.id + LEFT JOIN + t_project_structuregroup + ON t_project_structuregroup.project = t_project.id + LEFT JOIN + t_structuregroup_structure + ON t_structuregroup_structure.structuregroup = t_project_structuregroup.structuregroup + LEFT JOIN + t_project_construction + ON t_project_construction.project = t_project.id + LEFT JOIN + t_structure_site + ON t_structure_site.siteid = t_project_construction.construction + RIGHT JOIN + t_structure + ON t_structure.id = t_project_structure.structure + OR t_structure.id = t_structuregroup_structure.structure + OR t_structure.id = t_structure_site.structid + WHERE + project_state != -1 + AND + t_project.id IN (${anxinProjectIds.join(',')})`).toPromise() : [] + + // 构建安心云结构物和项企项目的关系 + // 并保存信息至数据 + let undelStruc = [] + for (let s of undelStrucRes) { + let corStruc = undelStruc.find(us => us.strucId == s.strucId) + if (corStruc) { + if (!corStruc.project.some(cp => cp.id == s.projectId)) { + corStruc.project.push({ + id: s.projectId + }) + } + } else { + corStruc = { + strucId: s.strucId, + strucName: s.strucName, + projectId: s.projectId, + project: [{ + id: s.projectId, + }], + pomsProject: [] + } + undelStruc.push(corStruc) + } + for (let { dataValues: br } of bindRes) { + if (br.anxinProjectId.some(braId => braId == s.projectId)) { + let corPepProject = pepProjectRes.find(pp => pp.id == br.pepProjectId) + let corPomsProject = corStruc.pomsProject.find(cp => cp.id == br.id) + + if (corPomsProject) { + // poms 的 project 和 pep 的 project 是一对一的关系 所以这个情况不用处理 + } else { + corStruc.pomsProject.push({ + ...br, + pepProject: corPepProject + }) + } + + } + } + } + return undelStruc + } catch (error) { + console.error(error); + } + } + return { + alarmHandleStatistics + } +} \ No newline at end of file diff --git a/api/app/lib/schedule/alarms_push.js b/api/app/lib/schedule/alarms_push.js new file mode 100644 index 0000000..eec2f63 --- /dev/null +++ b/api/app/lib/schedule/alarms_push.js @@ -0,0 +1,465 @@ +const moment = require('moment') + +module.exports = function (app, opts) { + const alarmsPush = app.fs.scheduleInit( + { + interval: '12 */1 * * * *', + immediate: true, // dev + proRun: true, + }, + async () => { + try { + const { models, ORM: sequelize } = app.fs.dc + const { clickHouse } = app.fs + const { database: anxinyun } = clickHouse.anxinyun.opts.config + const { pushBySms, pushByEmail } = app.fs.utils + const curMinOfYear = moment().diff(moment().startOf('year'), 'minutes') + const configListRes = await models.AlarmPushConfig.findAll({ + where: { + del: false + }, + include: [{ + model: models.ProjectCorrelation, + where: { + del: false + }, + required: true + }] + }) + let pepProjectIds = new Set() + for (let { dataValues: c } of configListRes) { + if (c.projectCorrelation.pepProjectId) { + pepProjectIds.add(c.projectCorrelation.pepProjectId) + } + } + const pepProjectRes = pepProjectIds.size ? + await clickHouse.projectManage.query( + ` + SELECT + t_pim_project.id AS id, + t_pim_project.project_name AS project_name, + t_pim_project.isdelete AS isdelete, + t_pim_project_construction.construction_status_id AS construction_status_id, + t_pim_project_state.construction_status AS construction_status + FROM t_pim_project + LEFT JOIN t_pim_project_construction + ON t_pim_project.id = t_pim_project_construction.project_id + LEFT JOIN t_pim_project_state + ON t_pim_project_construction.construction_status_id = t_pim_project_state.id + WHERE id IN (${[...pepProjectIds].join(',')}) + ` + ).toPromise() : + [] + + for (let { dataValues: c } of configListRes) { + if (c.tacticsParams && c.tactics) { + const { projectCorrelation, strucId, pomsProjectId, } = c + const { interval, deviceProportion } = c.tacticsParams + + if ( + curMinOfYear % parseInt(interval) == 0 + ) { + const corPepProject = projectCorrelation.pepProjectId ? + pepProjectRes.find(p => p.id == projectCorrelation.pepProjectId) + : null + if ( + !projectCorrelation.pepProjectId + || ( + corPepProject + && c.timeType.some(ct => ct == corPepProject.construction_status_id) + ) + ) { + const { anxinProjectId, pepProjectId } = projectCorrelation + + // 查当前 poms 下的结构物 并把不包含的去掉 + // 可能有结构物已解绑 + const strucListRes = strucId.length && anxinProjectId.length ? + await clickHouse.anxinyun.query( + ` + SELECT + DISTINCT id, + t_structure.id AS id, + t_structure.name AS name, + t_structure.iota_thing_id AS iotaThingId + FROM + t_project + LEFT JOIN + t_project_structure + ON t_project_structure.project = t_project.id + LEFT JOIN + t_project_structuregroup + ON t_project_structuregroup.project = t_project.id + LEFT JOIN + t_structuregroup_structure + ON t_structuregroup_structure.structuregroup = t_project_structuregroup.structuregroup + LEFT JOIN + t_project_construction + ON t_project_construction.project = t_project.id + LEFT JOIN + t_structure_site + ON t_structure_site.siteid = t_project_construction.construction + RIGHT JOIN + t_structure + ON t_structure.id = t_project_structure.structure + OR t_structure.id = t_structuregroup_structure.structure + OR t_structure.id = t_structure_site.structid + WHERE + project_state != -1 + AND t_project.id IN (${anxinProjectId.join(',')}) + AND t_structure.id IN (${strucId.join(',')}) + + ` + ).toPromise() : + [] + let searchStrucIds = strucListRes.map(s => s.id) + if (searchStrucIds.length) { + searchStrucIds.unshift(-1) + } + let pepProjectName = pepProjectId ? + pepProjectRes.find(p => p.id == pepProjectId).project_name + : projectCorrelation.name + let emailTitle = `${pepProjectName}-${c.name}-` + let emailSubTitle = '' + + let dataAlarmOption = [] + let dataAlarmGroupOption = [] + let dataAlarms = [] + + let videoAlarmWhereOption = [] + let videoAlarms = [] + + let appAlarmWhereOption = { + confirmTime: null, + } + let appAlarms = [] + + let deviceCount = 0 + let alarmDeviceCount = 0 + let cameraCount = 0 + let alarmCameraCount = 0 + // 判断推送策略 + let nowTime = moment().startOf('minute') + let pointTime = moment().subtract(parseInt(interval), 'minute').startOf('minute').format('YYYY-MM-DD HH:mm:ss') + if (c.tactics == 'immediately') { + // dataAlarmOption.push(`StartTime >= '${pointTime}'`); + // appAlarmWhereOption.createTime = { $gte: pointTime } + // videoAlarmWhereOption.push(`camera_status_alarm.create_time >= '${pointTime}'`) + emailTitle += `即时推送服务` + emailSubTitle += `截止${moment(pointTime).format('YYYY年MM月DD日 HH时mm分')}-${moment(nowTime).format('HH时mm分')}:` + } else if (c.tactics == 'continue') { + // dataAlarmOption.push(`StartTime <= '${pointTime}'`); + // appAlarmWhereOption.createTime = { $lte: pointTime } + // videoAlarmWhereOption.push(`camera_status_alarm.create_time <= '${pointTime}'`) + emailTitle += `持续时长推送服务` + emailSubTitle += `告警持续时长超${interval}分钟的告警源,详情如下:` + } else if (c.tactics == 'abnormal_rate') { + // dataAlarmOption.push(`StartTime <= '${pointTime}'`); + if (c.alarmType.includes('data_outages') || c.alarmType.includes('data_exception')) { + // 查了设备异常率 去安心云查当前项目下的设备数量 + // TODO 等同步以太数据再查 + deviceCount = 9999 + // await clickHouse.anxinyun.query(` + // SELECT count(*) FROM + // `).toPromise() + } + if (c.alarmType.includes('video_exception')) { + // 查了视频异常 去安心云查 接入的 萤石 设备数量 + cameraCount = searchStrucIds.length ? + (await clickHouse.anxinyun.query(` + SELECT count(*) AS count FROM t_video_ipc + WHERE structure IN (${searchStrucIds.join(',')}) + `).toPromise())[0].count + : 0 + } + // appAlarmWhereOption.createTime = { $lte: pointTime } + // videoAlarmWhereOption.push(`camera_status_alarm.create_time <= '${pointTime}'`) + emailTitle += `异常率推送服务` + emailSubTitle += `持续产生时间超过${interval}分钟的异常设备数量${interval}个,异常率达到项目或结构物内设备总数量${deviceCount + cameraCount}个的${interval}%,详情如下` + } + emailTitle += '——POMS飞尚运维中台' + + // 判断告警数据范围 + if (c.alarmType.includes('data_outages')) { + dataAlarmGroupOption.push(1) + } + if (c.alarmType.includes('data_exception')) { + dataAlarmGroupOption.push(2) + } + if (c.alarmType.includes('strategy_hit')) { + dataAlarmGroupOption.push(3) + } + if (c.alarmType.includes('video_exception')) { + videoAlarms = searchStrucIds.length ? await clickHouse.vcmp.query( + ` + SELECT + cameraAlarm.cameraId AS cameraId, + cameraAlarm.cameraName AS cameraName, + cameraAlarm.cameraSerialNo AS cameraSerialNo, + cameraAlarm.cameraChannelNo AS cameraChannelNo, + cameraAlarm.alarmId AS alarmId, + cameraAlarm.createTime AS createTime, + cameraAlarm.confirmContent AS confirmContent, + cameraAlarm.confirmTime AS confirmTime, + cameraAlarm.autoRestore AS autoRestore, + camera_status.describe AS statusDescribe, + "gbCamera".online AS cameraOnline, + anxinIpc.t_video_ipc.name AS anxinIpcPosition, + anxinStation.id AS anxinStationId, + anxinStation.name AS anxinStationName, + anxinStruc.name AS strucName, + anxinStruc.id AS strucId + FROM ( + SELECT + camera.id AS cameraId, + camera.gb_id AS gbId, + camera.name AS cameraName, + camera_status_alarm.id AS alarmId, + camera_status_alarm.create_time AS createTime, + camera_status_alarm.status_id AS statusId, + camera_status_alarm.serial_no AS cameraSerialNo, + camera_status_alarm.channel_no AS cameraChannelNo, + camera_status_alarm.confirm AS confirmContent, + camera_status_alarm.auto_restore AS autoRestore, + camera_status_alarm.confirm_time AS confirmTime + FROM camera_status_alarm + INNER JOIN ${anxinyun}.t_video_ipc + ON toString(${anxinyun}.t_video_ipc.channel_no) = camera_status_alarm.channel_no + AND ${anxinyun}.t_video_ipc.serial_no = camera_status_alarm.serial_no + AND ${anxinyun}.t_video_ipc.structure IN (${searchStrucIds.join(',')}) + INNER JOIN camera + ON camera.serial_no = camera_status_alarm.serial_no + AND camera.channel_no = camera_status_alarm.channel_no + AND camera.delete = false + AND camera.recycle_time is null + WHERE + camera_status_alarm.confirm_time IS null + ${videoAlarmWhereOption.length ? ` AND ${videoAlarmWhereOption.join(' AND ')}` : ''} + ) AS cameraAlarm + LEFT JOIN camera_status + ON cameraAlarm.platform = camera_status.platform + AND cameraAlarm.statusId = camera_status.id + LEFT JOIN "gbCamera" + ON "gbCamera".id = cameraAlarm.gbId + + LEFT JOIN ${anxinyun}.t_video_ipc AS anxinIpc + ON toString(anxinIpc.channel_no) = cameraAlarm.cameraChannelNo + AND anxinIpc.serial_no = cameraAlarm.cameraSerialNo + LEFT JOIN ${anxinyun}.t_structure AS anxinStruc + ON anxinStruc.id = anxinIpc.structure + AND anxinStruc.id IN (${searchStrucIds.join(',')}) + LEFT JOIN ${anxinyun}.t_video_ipc_station AS anxinIpcStation + ON anxinIpcStation.ipc = anxinIpc.id + LEFT JOIN ${anxinyun}.t_sensor AS anxinStation + ON anxinStation.id = anxinIpcStation.station + ` + ).toPromise() : [] + + let returnD = [] + let positionD = {} + // 每个设备一个告警 + for (let a of videoAlarms) { + if (positionD[a.cameraId]) { + let curD = returnD[positionD[a.cameraId].positionReturnD] + + if (a.strucId && !curD.struc.some(s => s.id == a.strucId)) { + curD.struc.push({ + id: a.strucId, + projectId: a.projectId, + name: a.strucName + }) + } + if (a.anxinStationId && !curD.station.some(s => s.id == a.anxinStationId)) { + curD.station.push({ + id: a.anxinStationId, + name: a.anxinStationName, + position: a.anxinIpcPosition + }) + } + } else { + let d = { + cameraId: a.cameraId, + cameraName: a.cameraName, + camerOnline: a.cameraOnline, + cameraSerialNo: a.cameraSerialNo, + cameraChannelNo: a.cameraChannelNo, + autoRestore: a.autoRestore, + createTime: a.createTime, + updateTime: a.updateTime, + platform: a.platform, + statusDescribe: a.statusDescribe, + alarmId: a.alarmId, + confirmContent: a.confirmContent, + confirmTime: a.confirmTime, + struc: [], + station: [] + } + + if (a.strucId) { + d.struc.push({ + id: a.strucId, + projectId: a.projectId, + name: a.strucName + }) + } + if (a.anxinStationId) { + d.station.push({ + id: a.anxinStationId, + name: a.anxinStationName, + position: a.anxinIpcPosition + }) + } + returnD.push(d) + positionD[a.cameraId] = { + positionReturnD: returnD.length - 1 + } + } + } + let p = 1 + } + if (c.alarmType.includes('app_exception')) { + appAlarms = await models.AppAlarm.findAll({ + where: appAlarmWhereOption, + include: [{ + model: models.App, + include: [{ + model: models.ProjectApp, + where: { + projectId: pomsProjectId + }, + }] + }] + }) + let a = 2 + } + if (c.alarmType.includes('device_exception')) { + dataAlarmGroupOption.push(4) + dataAlarmGroupOption.push(5) + } + + // 查数据告警 三警合一 + if (dataAlarmGroupOption.length && searchStrucIds.length) { + dataAlarmGroupOption.push(-1) + dataAlarmOption.push(`AlarmGroup IN (${dataAlarmGroupOption.join(',')})`) + dataAlarms = await clickHouse.dataAlarm.query(` + SELECT * FROM alarms + WHERE + State NOT IN (3, 4) + AND StructureId IN (${searchStrucIds.join(',')}) + ${dataAlarmOption.length ? ' AND ' + dataAlarmOption.join(' AND ') : ''} + `).toPromise() + console.log(dataAlarms); + } + + let dataAlarmTitle = [{ + n: '项目', + k: '', + v: pepProjectName + }, { + n: '结构物', + k: '', + f: (d) => { + return (strucListRes.find(s => s.id == d.StructureId) || {}).name + } + }, { + n: '告警源名称', + k: 'SourceName' + }, { + n: '告警源类型', + k: '', + f: (d) => { + switch (d.SourceTypeId) { + case 0: + return 'DTU' + case 1: + return '传感器' + case 2: + return '测点' + default: + return '' + } + } + }, { + n: '告警信息', + k: 'AlarmContent' + }, { + n: '告警等级(当前)', + k: '', + f: (d) => { + switch (d.CurrentLevel) { + case 1: + return '一级' + case 2: + return '二级' + case 3: + return '三级' + default: + return '' + } + } + }, { + n: '持续时间', + k: '', + f: (d) => { + return d.StartTime ? + '超过' + moment().diff(moment(d.StartTime), 'minutes') + '分钟' : '' + } + },] + + let html = '' + let tableTitlePostfix = ',详情如下:' + function packageTableTitle (tArr) { + + } + if (c.alarmType.includes('data_outages')) { + let tableTitlePrefix = '数据中断告警源' + let newAddCount = 0 + let alarmHtml = '' + + if (c.tactics == 'immediately') { + for (let a of dataAlarms.filter(d => d.AlarmGroup == 1)) { + alarmHtml += 1 + } + tableTitlePrefix += 1 + } else if (c.tactics == 'continue') { + + } else if (c.tactics == 'abnormal_rate') { + + } + } + if (c.alarmType.includes('data_exception')) { + + } + if (c.alarmType.includes('strategy_hit')) { + + } + if (c.alarmType.includes('video_exception')) { + + } + if (c.alarmType.includes('app_exception')) { + + } + if (c.alarmType.includes('device_exception')) { + + } + + await pushByEmail({ + email: ['1650192445@qq.com'], + title: emailTitle, + text: '', + html: '' + }) + } + } + } + } + + } catch (error) { + console.error(error); + } + } + ) + + return { + alarmsPush, + } +} \ No newline at end of file diff --git a/api/app/lib/utils/dataRange.js b/api/app/lib/utils/dataRange.js index ea1c4c6..407c80e 100644 --- a/api/app/lib/utils/dataRange.js +++ b/api/app/lib/utils/dataRange.js @@ -160,24 +160,43 @@ module.exports = function (app, opts) { // 并保存信息至数据 let undelStruc = [] for (let s of undelStrucRes) { - if (!undelStruc.some(us => us.strucId == s.strucId)) { - let pomsProject = [] - for (let { dataValues: br } of bindRes) { - if (br.anxinProjectId.some(braId => braId == s.projectId)) { - let corPepProject = pepProjectRes.find(pp => pp.id == br.pepProjectId) - pomsProject.push({ + let corStruc = undelStruc.find(us => us.strucId == s.strucId) + if (corStruc) { + if (!corStruc.project.some(cp => cp.id == s.projectId)) { + corStruc.project.push({ + id: s.projectId + }) + } + } else { + corStruc = { + strucId: s.strucId, + strucName: s.strucName, + projectId: s.projectId, + project: [{ + id: s.projectId, + }], + pomsProject: [] + } + undelStruc.push(corStruc) + } + + for (let { dataValues: br } of bindRes) { + if (br.anxinProjectId.some(braId => braId == s.projectId)) { + let corPepProject = pepProjectRes.find(pp => pp.id == br.pepProjectId) + let corPomsProject = corStruc.pomsProject.find(cp => cp.id == br.id) + + if (corPomsProject) { + // poms 的 project 和 pep 的 project 是一对一的关系 所以这个情况不用处理 + } else { + corStruc.pomsProject.push({ ...br, pepProject: corPepProject }) } + } - undelStruc.push({ - strucId: s.strucId, - strucName: s.strucName, - projectId: s.projectId, - pomsProject: pomsProject - }) } + } return undelStruc } @@ -188,6 +207,16 @@ module.exports = function (app, opts) { let pomsProject = [] for (let b of bindRes) { + if ( + keywordTarget == 'pepProject' && keyword + && ( + !b.name && !( + b.pepProjectId && pepProjectRes.some(pp => pp.id == b.pepProjectId) + ) + ) + ) { + continue + } if (b.pepProjectId) { let corPepProject = pepProjectRes.find(pp => pp.id == b.pepProjectId) || {} pomsProject.push({ diff --git a/api/app/lib/utils/push.js b/api/app/lib/utils/push.js new file mode 100644 index 0000000..a0cf5ed --- /dev/null +++ b/api/app/lib/utils/push.js @@ -0,0 +1,62 @@ +'use strict'; + +const moment = require('moment') +const Core = require('@alicloud/pop-core'); +const nodemailer = require('nodemailer') + +module.exports = function (app, opts) { + const pushBySms = async ({ phone = [], templateCode, templateParam } = {}) => { + try { + if (phone.length) { + const client = new Core({ + accessKeyId: opts.sms.accessKey, + accessKeySecret: opts.sms.accessSecret, + endpoint: 'http://dysmsapi.aliyuncs.com',//固定 + apiVersion: '2017-05-25'//固定 + }); + const SendSmsRes = await client.request('SendSms', { + "PhoneNumbers": phone.join(','),//接收短信的手机号码。 + "SignName": "飞尚尚视",//短信签名名称。必须是已添加、并通过审核的短信签名。 + "TemplateCode": templateCode,//短信模板ID。必须是已添加、并通过审核的短信签名;且发送国际/港澳台消息时,请使用国际/港澳台短信模版。 + "TemplateParam": JSON.stringify(templateParam)//短信模板变量对应的实际值,JSON格式。 + }, { + method: 'POST' + }); + return SendSmsRes + } + } catch (error) { + throw error + } + } + + const pushByEmail = async ({ email = [], title, text = '', html = '', attachments = undefined, } = {}) => { + try { + let transporter = nodemailer.createTransport({ + host: opts.email.host, + port: opts.email.port, + secure: true, + auth: { + user: opts.email.sender.address, + pass: opts.email.sender.password, + } + }); + + // send mail with defined transport object + await transporter.sendMail({ + from: `${opts.email.sender.name}<${opts.email.sender.address}>`, // sender address + to: email.join(','), // list of receivers 逗号分隔字符串 + subject: title, // Subject line + text: text, // plain text body + html: html, // html body + attachments: attachments + }); + } catch (error) { + throw error + } + } + + return { + pushByEmail, + pushBySms, + } +} \ No newline at end of file diff --git a/api/sequelize-automate.config.js b/api/sequelize-automate.config.js index e404aeb..0b063a4 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: ['quick_link'], // 指定生成哪些表的 models,如 ['user', 'user_post'];如果为 null,则忽略改属性 + tables: ['alarm_push_config'], // 指定生成哪些表的 models,如 ['user', 'user_post'];如果为 null,则忽略改属性 skipTables: [], // 指定跳过哪些表的 models,如 ['user'];如果为 null,则忽略改属性 tsNoCheck: false, // 是否添加 `@ts-nocheck` 注释到 models 文件中 ignorePrefix: [], // 生成的模型名称忽略的前缀,因为 项目中有以下表名是以 t_ 开头的,在实际模型中不需要, 可以添加多个 [ 't_data_', 't_',] ,长度较长的 前缀放前面 diff --git a/script/0.0.6/schema/2.alarm_confirm_log.sql b/script/0.0.6/schema/2.alarm_confirm_log.sql new file mode 100644 index 0000000..6b32c4a --- /dev/null +++ b/script/0.0.6/schema/2.alarm_confirm_log.sql @@ -0,0 +1,22 @@ + + +create table alarm_confirm_log +( + id serial not null, + pep_user_id int null, + project_correlation_id int not null, + alarm_info json not null, + confirm_time timestamp not null, + confirm_content varchar not null +); + +comment on table alarm_confirm_log is '告警确认日志表'; + +create unique index alarm_confirm_log_id_uindex + on alarm_confirm_log (id); + +alter table alarm_confirm_log + add constraint alarm_confirm_log_pk + primary key (id); + + diff --git a/script/0.0.6/schema/3.email_send_log.sql b/script/0.0.6/schema/3.email_send_log.sql new file mode 100644 index 0000000..c02c700 --- /dev/null +++ b/script/0.0.6/schema/3.email_send_log.sql @@ -0,0 +1,27 @@ + + +create table if not exists email_send_log +( + id serial not null + constraint email_send_log_pk + primary key, + time timestamp not null, + push_config_id integer not null, + tactics varchar(32), + tactics_params jsonb, + project_correlation_id integer not null, + to_pep_user_ids integer[] not null +); + +comment on table email_send_log is 'EM推送日志'; + +alter table email_send_log owner to postgres; + +create unique index if not exists email_send_log_id_uindex + on email_send_log (id); + + + + + + diff --git a/script/0.0.6/schema/4.alarm_appear_record.sql b/script/0.0.6/schema/4.alarm_appear_record.sql new file mode 100644 index 0000000..d492466 --- /dev/null +++ b/script/0.0.6/schema/4.alarm_appear_record.sql @@ -0,0 +1,23 @@ + + +create table alarm_appear_record +( + id serial not null, + project_correlation_id int not null, + alarm_info json not null, + time timestamp, + type varchar null +); + +comment on table alarm_appear_record is '告警出现记录到日志'; + +create unique index alarm_appear_record_id_uindex + on alarm_appear_record (id); + +alter table alarm_appear_record + add constraint alarm_appear_record_pk + primary key (id); + + + + diff --git a/script/0.0.6/schema/5.latest_dynamic_list.sql b/script/0.0.6/schema/5.latest_dynamic_list.sql new file mode 100644 index 0000000..c73106f --- /dev/null +++ b/script/0.0.6/schema/5.latest_dynamic_list.sql @@ -0,0 +1,28 @@ + + +create table latest_dynamic_list +( + id serial not null, + time timestamp not null, + project_correlation_id int not null, + alarm_appear_id int, + email_send_id int, + alarm_confirm_id int, + type int +); + +comment on table latest_dynamic_list is '最新动态表'; +comment on column latest_dynamic_list.type is '1:发现,2:通知,3:处置,4:确认'; + +create unique index latest_dynamic_list_id_uindex + on latest_dynamic_list (id); + +alter table latest_dynamic_list + add constraint latest_dynamic_list_pk + primary key (id); + + + + + + diff --git a/script/0.0.6/schema/6.alarm_handle_statistics.sql b/script/0.0.6/schema/6.alarm_handle_statistics.sql new file mode 100644 index 0000000..54b5292 --- /dev/null +++ b/script/0.0.6/schema/6.alarm_handle_statistics.sql @@ -0,0 +1,22 @@ + + +create table alarm_handle_statistics +( + id serial not null, + time timestamp not null, + project_correlation_id int, + day1 float, + day3 float, + day7 float, + day15 float, + day30 float, + day30m float +); + +create unique index alarm_handle_statistics_id_uindex + on alarm_handle_statistics (id); + +alter table alarm_handle_statistics + add constraint alarm_handle_statistics_pk + primary key (id); + diff --git a/script/0.0.7/schema/1.update_alarm_push.sql b/script/0.0.7/schema/1.update_alarm_push.sql new file mode 100644 index 0000000..ddc1a89 --- /dev/null +++ b/script/0.0.7/schema/1.update_alarm_push.sql @@ -0,0 +1,15 @@ +alter table alarm_push_config + add struc_id int[] not null; + +alter table alarm_push_config + add tactics varchar(32); + +comment on column alarm_push_config.tactics is 'immediately 即时 / continue 持续 / abnormal_rate 异常率'; + +alter table alarm_push_config + add tactics_params jsonb; + +comment on column alarm_push_config.tactics_params is '推送策略 tactics 的参数'; + +alter table alarm_push_config + add del bool default false not null; diff --git a/web/client/index.ejs b/web/client/index.ejs index 9cbe876..71b028e 100644 --- a/web/client/index.ejs +++ b/web/client/index.ejs @@ -11,7 +11,7 @@ - + diff --git a/web/client/index.html b/web/client/index.html index 162fe74..302a7e4 100644 --- a/web/client/index.html +++ b/web/client/index.html @@ -11,7 +11,7 @@ + src="https://lf1-cdn-tos.bytegoofy.com/obj/iconpark/icons_19077_11.559b91c217b8ddc76c0c4b1397d84d48.es5.js"> diff --git a/web/client/src/sections/install/containers/roles.jsx b/web/client/src/sections/install/containers/roles.jsx index 08227fc..95a0f86 100644 --- a/web/client/src/sections/install/containers/roles.jsx +++ b/web/client/src/sections/install/containers/roles.jsx @@ -56,7 +56,7 @@ const Roles = (props) => { let anxinerror = false let anxinerrorArr = [] for (let i = 0; i < row.correlationProject.length; i++) { - if (row.correlationProject[i].del == -1) { + if (row.correlationProject[i].del == true) { anxinerror = true anxinerrorArr.push(row.correlationProject[i].pepProjectName) } @@ -65,7 +65,7 @@ const Roles = (props) => {
{ anxinerror ? ( - +
diff --git a/web/client/src/sections/install/containers/system.jsx b/web/client/src/sections/install/containers/system.jsx index 7874a0e..69a80d1 100644 --- a/web/client/src/sections/install/containers/system.jsx +++ b/web/client/src/sections/install/containers/system.jsx @@ -131,7 +131,7 @@ const Example = (props) => {
- PMOS + POMS
) @@ -273,7 +273,7 @@ const Example = (props) => { 修改 { return
{r.State < 3 || route && ['videoAbnormal', 'useAbnormal'].includes(route) && !r.confirmTime ? - - : r.State == 3 || r.autoRestore || r.confirmAuto ? + + : r.State == 3 || r.autoRestore || r.confirmAuto ? : - - } + + } {route && ['dataLnterrupt', 'dataAbnormal', 'strategyHit', 'deviceAbnormal'].includes(route) ? <> {route == 'deviceAbnormal' ? "" : - + {row?.disable ? ( + + ) : ( + { + dispatch(service.putPushPushId({ pushId: row?.id, del: false, disable: true, msg: '更改推送配置状态' })).then(() => { + // setQuery({ limit: 10, page: page.current }) + }) + }} + > + + + )} { - // dispatch(install.deleteProjectBind({ bindId: row?.id, msg: '删除安心云、项目管理项目绑定关系' })).then(() => { - // if (page.current > 0 && mylimits.current < 2) { - // setQuery({ limit: 10, page: page.current - 1 }) - // } else { - // setQuery({ limit: 10, page: page.current }) - // } - // }) + dispatch(service.putPushPushId({ pushId: row?.id, del: true, disable: false, msg: '删除推送配置' })).then(() => { + // if (page.current > 0 && mylimits.current < 2) { + // setQuery({ limit: 10, page: page.current - 1 }) + // } else { + // setQuery({ limit: 10, page: page.current }) + // } + }) }} > @@ -185,74 +222,338 @@ const EmPush = (props) => { ); }, }, - ]) - const rowSelection = { - selectedRowKeys: selected, - onChange: (selectedRowKeys, selectedRows) => { - setSelected(selectedRows.map(v => v.key)) - }, + ] + function expandRowRender (record, index) { + return ( +
+ 结构物: + { + record.structure?.map((item, index) => { + return ( +
+ {item.name} +
+ ) + }) + } +
+ ) } //获取表格属性设置 - function attribute () { + function attribute (val) { const arr = localStorage.getItem(EMPUSH) ? JSON.parse(localStorage.getItem(EMPUSH)) : []; - const column = [ { - title: "关联项目", - dataIndex: "noticeWay", - key: "noticeWay", - render: (_, r, index) => { - return r.noticeWay; - }, + title: '关联项目', + dataIndex: "projectName", + key: "projectName", + render: (_, row) => { + let anxinerror = false + let anxinerrorArr = '' + if (row.pomsProject.del == true) { + anxinerror = true + anxinerrorArr = row.pomsProject.pepProject?.projectName || row.pomsProject.name + } + return ( +
+ { + anxinerror ? ( + +
+ +
+
) : ('') + } + { +
+ +
7 || row.pomsProject?.name?.length > 7 ? '112px' : '', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', color: row.pomsProject.del ? '#F93920' : '' }}> + {row.pomsProject.pepProject?.projectName || row.pomsProject.name} +
+
+
+ } + { + row.pomsProject?.pepProject?.projectName ? ( +
+
+ +
+
+ { + val.map((ite, idx) => { + return ( +
+ {ite.id == row.pomsProject?.pepProject.constructionStatusId ? ite.construction_status : ''} +
+ ) + }) + } +
+
+ ) : ( +
+
+ +
+
+ POMS +
+
+ ) + } +
+ ) + } + }, + { + title: '策略名称', + dataIndex: "name", + key: 'name', + render: (_, row) => { + return row.name + } }, { title: "创建时间", - dataIndex: "logCount", - key: "logCount", + dataIndex: "createTime", + key: "createTime", render: (_, r, index) => { - return (r.logCount + '次') + return moment(r.createTime).format('YYYY-MM-DD HH:mm:ss'); }, }, { - title: "接收人", - dataIndex: "monitorCount", - key: "monitorCount", + title: '接收人', + dataIndex: "receiverPepUser", + key: 'receiverPepUser', + render: (_, row) => { + return ( +
+ { + row.receiverPepUser.map((item, index) => { + return ( +
+
1 ? 'none' : '', color: '#005ABD' }}> + {item.name} +
+
0 ? 'none' : '' }}>
+
+ ) + }) + } + { + row.receiverPepUser.length > 2 ? ( + + { + row.receiverPepUser.map((item, index) => { + return ( +
+ {item.name}, +
+ ) + }) + } +
+ } trigger="click" style={{ lineHeight: 2 }}> +
+ +{row.receiverPepUser.length - 2} +
+ + ) : ('') + } +
+ ) + } + }, + { + title: "推送方式", + dataIndex: "pushType", + key: "pushType", render: (_, r, index) => { - return r.monitorCount + return '邮件通知'; }, }, { - title: "监听问题", - dataIndex: "pushWay", - key: "pushWay", - render: (_, r, index) => { - return r.pushWay=='email' ? '邮件通知' : '短信通知'; + title: "监听问题模块", + dataIndex: "alarmType", + key: "alarmType", + render: (_, row) => { + return ( +
+ { + row.alarmType.map((item, index) => { + return ( +
+
1 ? 'none' : '' }}> + {alarmTypeObj[item]} +
+
0 ? 'none' : '' }}>
+
+ ) + }) + } + { + row.alarmType.length > 2 ? ( + + { + row.alarmType.map((item, index) => { + return ( +
+ {alarmTypeObj[item]}, +
+ ) + }) + } +
+ } trigger="click" style={{ lineHeight: 2 }}> +
+ +{row.alarmType.length - 2} +
+ + ) : ('') + } + + ) + } + }, + { + title: "生效项目节点", + dataIndex: "timeType", + key: "timeType", + render: (_, row, index) => { + return ( +
+ { + row.timeType.length > 0 ? ( + row.timeType.map((item, index) => { + return ( +
1 ? 'none' : 'flex', alignItems: 'center' + }}> +
+ +
+
+ { + val.map((ite, idx) => { + return ( +
+ {ite.id == item ? ite.construction_status : ''} +
+ ) + }) + } +
+
+ ) + }) + ) : ( +
+
+ +
+
+ POMS +
+
+ ) + } + { + row.timeType.length > 2 ? ( + + { + row.timeType.map((item, index) => { + return ( +
+ { + val.map((ite, idx) => { + return ( + + {ite.id == item ? ite.construction_status : ''} + + ) + }) + }, +
+ ) + }) + } +
+ } trigger="click" style={{ lineHeight: 2 }}> +
+ +{row.timeType.length - 2} +
+ + ) : ('') + } + + ) }, }, { - title: "通知时效", - dataIndex: "text1", - key: "text1", + title: "推送机制", + dataIndex: "tactics", + key: "tactics", render: (_, r, index) => { - return r.text1 + return tacticsObj[r.tactics] }, }, { title: "启用状态", - dataIndex: "text2", - key: "text2", - render: (_, r, index) => { - return r.text2 + dataIndex: "disable", + key: "disable", + render: (_, row, index) => { + let enableType = '' + if (row.disable) { + enableType = '禁用' + } + else { + if (row.timeType.length > 0) { + for (let i = 0; i < row.timeType.length; i++) { + if (row.timeType[i] == row.pomsProject?.pepProject?.constructionStatusId) { + enableType = '已生效' + break; + } else { + enableType = '未生效' + } + } + } + else { + enableType = '已生效' + } + } + return ( +
+ {enableType} +
+ ) }, }, { title: "推送次数", - dataIndex: "time", - key: "time", + dataIndex: "pushCount", + key: "pushCount", render: (_, r, index) => { - return r.time + return r.pushCount + '次' }, }, ]; @@ -260,7 +561,7 @@ const EmPush = (props) => { let colum = column.filter((item) => { return item.key === arr[i]; }); - columns.splice(i + 2, 0, colum[0]); + columns.splice(columns.length - 1, 0, colum[0]); } setSetupp(columns); } @@ -273,7 +574,7 @@ const EmPush = (props) => {
EM推送
Em push
-
+
console.log(values)} // onValueChange={values=>console.log(values)} @@ -281,47 +582,53 @@ const EmPush = (props) => { layout="horizontal" style={{ position: "relative", width: "100%", flex: 1 }} > + + 项目 + 结构物 + 策略名 + } + field="keyword" + pure + showClear + style={{ width: 260, marginLeft: 12, marginRight: 12 }} + placeholder="请输入或选择关键词" /> - {/* {.map((item) => { - return ( - - {item.name} - - ); - })} */} + 数据中断 + 数据异常 + 策略命中 + 视频异常 + 应用异常 + 设备异常 - {/* {.map((item) => { - return ( - - {item.name} - - ); - })} */} + 已生效 + 未生效 + 禁用 -
+
setSetup(true)} />
-
预留预留预留预留预留预留预留预留预留预留预留预留预留预留预留预留预留预留预留预留预留预留
+
EM推送提供对映射关系组的项目、结构物问题的监听和通知服务,支持对设备异常率、问题持续时间、即时响应等策略定义的动态推送。
{ placeholder={SkeletonScreen()} > s)} dataSource={tableData} bordered={false} + hideExpandedColumn={false} empty="暂无数据" - pagination={false} + expandedRowRender={expandRowRender} + // pagination={false} onRow={handleRow} - rowSelection={rowSelection} />
{ }} >
-
勾选 {selected.length}条 信息
- - { - // dispatch(install.deleteProjectBind({ bindId: selected.join(','), msg: '删除安心云、项目管理项目绑定关系' })).then(() => { - // if (page.current > 0 && mylimits.current == selected.length) { - // setQuery({ limit: 10, page: page.current - 1 }) - // } else { - // setQuery({ limit: 10, page: page.current }) - // } - // setSelected([]) - // }) - }} - > - -
@@ -438,49 +715,39 @@ const EmPush = (props) => { onChange={(currentPage, pageSize) => { setQuery({ limit: pageSize, page: currentPage - 1 }); page.current = currentPage - 1 - setSelected([]) }} />
- {/* {//映射关系弹框 + {//推送配置弹框 pushModal ? { setPushModal(false); }} close={() => { setPushModal(false); - getProjectPomsList() + getPushList() }} > : '' - } */} + } {setup ? ( - { - setSetup(false); - attribute(); - // setcameraSetup(false); - }} - /> - ) : ( - "" - )} + { + setSetup(false); + attribute(projectStatus); + }} + /> + ) : ( + "" + )} ) } diff --git a/web/client/src/sections/service/nav-item.jsx b/web/client/src/sections/service/nav-item.jsx index 694b817..cd770ad 100644 --- a/web/client/src/sections/service/nav-item.jsx +++ b/web/client/src/sections/service/nav-item.jsx @@ -30,7 +30,7 @@ export function getNavItem (user, dispatch) { }, { itemKey: 'carrierPigeon', text: '信鸽服务', - icon: , + icon: , to: '/service/carrierPigeon/emPush', items: [{ itemKey: 'emPush', to: '/service/carrierPigeon/emPush', text: 'EM推送' diff --git a/web/client/src/sections/service/style.less b/web/client/src/sections/service/style.less index b98d242..d26536b 100644 --- a/web/client/src/sections/service/style.less +++ b/web/client/src/sections/service/style.less @@ -1,4 +1,4 @@ -.empush{ +.myempush{ .semi-input-wrapper{ margin-bottom: 0px !important; } diff --git a/web/client/src/utils/webapi.js b/web/client/src/utils/webapi.js index f937fcb..1ba6232 100644 --- a/web/client/src/utils/webapi.js +++ b/web/client/src/utils/webapi.js @@ -50,6 +50,11 @@ export const ApiTable = { //服务-信鸽服务 getPush: "push", //获取推送配置列表 + postPush: "push", //新增/编辑推送配置 + getOrganizationUsers: "organization/users", //获取全部未删除用户 + getProjectStructure: "project/structure", //获取绑定项目下结构物 + getProjectStatus: "project/status", //获取项目状态列表 + putPushPushId: "push/{pushId}", //更改推送配置状态(禁用或删除) //控制台 consoleToollink: 'console/toollink', //常用工具