wenlele 3 years ago
parent
commit
1656234d71
  1. 4
      api/.vscode/launch.json
  2. 49
      api/app/lib/controllers/alarm/alarmConfirmLog.js
  3. 27
      api/app/lib/controllers/alarm/app.js
  4. 6
      api/app/lib/controllers/alarm/data.js
  5. 217
      api/app/lib/controllers/alarm/video.js
  6. 192
      api/app/lib/controllers/control/data.js
  7. 22
      api/app/lib/controllers/organization/index.js
  8. 93
      api/app/lib/controllers/project/index.js
  9. 168
      api/app/lib/controllers/push/config.js
  10. 8
      api/app/lib/index.js
  11. 98
      api/app/lib/models/alarm_handle_statistics.js
  12. 220
      api/app/lib/models/alarm_push_config.js
  13. 38
      api/app/lib/models/email_send_log.js
  14. 8
      api/app/lib/routes/control/index.js
  15. 3
      api/app/lib/routes/organization/index.js
  16. 6
      api/app/lib/routes/project/index.js
  17. 2
      api/app/lib/routes/push/index.js
  18. 480
      api/app/lib/schedule/alarms_handle_statistics.js
  19. 465
      api/app/lib/schedule/alarms_push.js
  20. 53
      api/app/lib/utils/dataRange.js
  21. 62
      api/app/lib/utils/push.js
  22. 2
      api/sequelize-automate.config.js
  23. 22
      script/0.0.6/schema/2.alarm_confirm_log.sql
  24. 27
      script/0.0.6/schema/3.email_send_log.sql
  25. 23
      script/0.0.6/schema/4.alarm_appear_record.sql
  26. 28
      script/0.0.6/schema/5.latest_dynamic_list.sql
  27. 22
      script/0.0.6/schema/6.alarm_handle_statistics.sql
  28. 15
      script/0.0.7/schema/1.update_alarm_push.sql
  29. 2
      web/client/index.ejs
  30. 2
      web/client/index.html
  31. 4
      web/client/src/sections/install/containers/roles.jsx
  32. 4
      web/client/src/sections/install/containers/system.jsx
  33. 47
      web/client/src/sections/problem/containers/dataAlarm.jsx
  34. 93
      web/client/src/sections/service/actions/emPush.js
  35. 14
      web/client/src/sections/service/actions/emPush.jsx
  36. 601
      web/client/src/sections/service/components/pushModal.jsx
  37. 10
      web/client/src/sections/service/components/pushModal.less
  38. 727
      web/client/src/sections/service/containers/emPush.jsx
  39. 2
      web/client/src/sections/service/nav-item.jsx
  40. 2
      web/client/src/sections/service/style.less
  41. 5
      web/client/src/utils/webapi.js

4
api/.vscode/launch.json

@ -16,9 +16,9 @@
"-p 4600", "-p 4600",
"-f http://localhost: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", "-k node35:6667,node36:6667,node37:6667",
"--iotaProxy http://10.8.30.157:17007", "--iotaProxy http://10.8.30.157:17007",
"--redisHost 10.8.30.112", "--redisHost 10.8.30.112",

49
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
};

27
api/app/lib/controllers/alarm/app.js

@ -1,7 +1,7 @@
'use strict'; 'use strict';
const moment = require('moment') const moment = require('moment')
const { alarmConfirmLog } = require('./alarmConfirmLog');
async function inspection(ctx) { async function inspection(ctx) {
// 巡查 // 巡查
try { try {
@ -328,7 +328,6 @@ async function confirmApiError(ctx) {
try { try {
const models = ctx.fs.dc.models; const models = ctx.fs.dc.models;
const { confirm, appAlarmId = [], confirmPost } = ctx.request.body const { confirm, appAlarmId = [], confirmPost } = ctx.request.body
const { pepUserId, projectCorrelationIds, alarmInfo } = confirmPost
await models.AppAlarm.update({ await models.AppAlarm.update({
confirm, confirm,
confirmTime: moment().format() confirmTime: moment().format()
@ -338,29 +337,7 @@ async function confirmApiError(ctx) {
} }
}) })
//存日志 await alarmConfirmLog(ctx, confirmPost, confirm);//告警确认日志
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);
ctx.status = 204; ctx.status = 204;
} catch (error) { } catch (error) {

6
api/app/lib/controllers/alarm/data.js

@ -1,6 +1,6 @@
'use strict'; 'use strict';
const moment = require('moment'); const moment = require('moment');
const { alarmConfirmLog } = require('./alarmConfirmLog');
async function groupList (ctx) { async function groupList (ctx) {
try { try {
const { models } = ctx.fs.dc; const { models } = ctx.fs.dc;
@ -248,7 +248,6 @@ function confirm (opts) {
const { utils: { kfkSendAsync } } = ctx.app.fs const { utils: { kfkSendAsync } } = ctx.app.fs
const { clickHouse } = ctx.app.fs const { clickHouse } = ctx.app.fs
const { content = '', alarmId, confirmPost } = ctx.request.body; const { content = '', alarmId, confirmPost } = ctx.request.body;
const { pepUserId, projectCorrelationIds, alarmInfo } = confirmPost;
// 发送告警恢复通知 // 发送告警恢复通知
// Topic: alarm // Topic: alarm
/* /*
@ -295,6 +294,9 @@ function confirm (opts) {
await kfkSendAsync(payloads) await kfkSendAsync(payloads)
} }
await alarmConfirmLog(ctx, confirmPost, content);//告警确认日志
ctx.status = 204; ctx.status = 204;
} catch (error) { } catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);

217
api/app/lib/controllers/alarm/video.js

@ -1,6 +1,6 @@
'use strict'; 'use strict';
const moment = require('moment') const moment = require('moment')
const { alarmConfirmLog } = require('./alarmConfirmLog');
async function deviceType (ctx) { async function deviceType (ctx) {
try { try {
const { models } = ctx.fs.dc; const { models } = ctx.fs.dc;
@ -21,7 +21,7 @@ async function deviceType (ctx) {
} }
} }
async function alarmList(ctx, agg) { async function alarmList (ctx) {
try { try {
const { models } = ctx.fs.dc; const { models } = ctx.fs.dc;
const { clickHouse } = ctx.app.fs 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 SELECT
cameraAlarm.cameraId AS cameraId, cameraAlarm.cameraId AS cameraId,
cameraAlarm.cameraName AS cameraName, cameraAlarm.cameraName AS cameraName,
cameraAlarm.cameraKindId AS cameraKindId, cameraAlarm.cameraKindId AS cameraKindId,
cameraAlarm.venderId AS venderId, cameraAlarm.venderId AS venderId,
cameraAlarm.venderName AS venderName, cameraAlarm.venderName AS venderName,
cameraAlarm.cameraSerialNo AS cameraSerialNo, cameraAlarm.cameraSerialNo AS cameraSerialNo,
cameraAlarm.cameraChannelNo AS cameraChannelNo, cameraAlarm.cameraChannelNo AS cameraChannelNo,
cameraAlarm.alarmId AS alarmId, cameraAlarm.alarmId AS alarmId,
cameraAlarm.createTime AS createTime, cameraAlarm.createTime AS createTime,
cameraAlarm.updateTime AS updateTime, cameraAlarm.updateTime AS updateTime,
cameraAlarm.platform AS platform, cameraAlarm.platform AS platform,
cameraAlarm.confirmContent AS confirmContent, cameraAlarm.confirmContent AS confirmContent,
cameraAlarm.confirmTime AS confirmTime, cameraAlarm.confirmTime AS confirmTime,
${'cameraAlarm.autoRestore AS autoRestore,'} ${'cameraAlarm.autoRestore AS autoRestore,'}
camera_status_resolve.id AS resolveId, camera_status_resolve.id AS resolveId,
camera_status.describe AS statusDescribe, camera_status.describe AS statusDescribe,
camera_status_resolve.resolve AS resolve, camera_status_resolve.resolve AS resolve,
"gbCamera".online AS cameraOnline, "gbCamera".online AS cameraOnline,
secret_yingshi.token AS yingshiToken, secret_yingshi.token AS yingshiToken,
anxinIpc.t_video_ipc.name AS anxinIpcPosition, anxinIpc.t_video_ipc.name AS anxinIpcPosition,
anxinStation.id AS anxinStationId, anxinStation.id AS anxinStationId,
anxinStation.name AS anxinStationName, anxinStation.name AS anxinStationName,
anxinStruc.name AS strucName, anxinStruc.name AS strucName,
anxinStruc.id AS strucId anxinStruc.id AS strucId
FROM FROM
( (
SELECT SELECT
camera.id AS cameraId, camera.id AS cameraId,
camera.gb_id AS gbId, camera.gb_id AS gbId,
camera.name AS cameraName, camera.name AS cameraName,
camera.kind_id AS cameraKindId, camera.kind_id AS cameraKindId,
camera.vender_id AS venderId, camera.vender_id AS venderId,
camera.yingshi_secret_id AS yingshiSecretId, camera.yingshi_secret_id AS yingshiSecretId,
vender.name AS venderName, vender.name AS venderName,
camera_status_alarm.id AS alarmId, camera_status_alarm.id AS alarmId,
camera_status_alarm.create_time AS createTime, camera_status_alarm.create_time AS createTime,
camera_status_alarm.update_time AS updateTime, camera_status_alarm.update_time AS updateTime,
camera_status_alarm.platform AS platform, camera_status_alarm.platform AS platform,
camera_status_alarm.status_id AS statusId, camera_status_alarm.status_id AS statusId,
camera_status_alarm.serial_no AS cameraSerialNo, camera_status_alarm.serial_no AS cameraSerialNo,
camera_status_alarm.channel_no AS cameraChannelNo, camera_status_alarm.channel_no AS cameraChannelNo,
camera_status_alarm.confirm AS confirmContent, camera_status_alarm.confirm AS confirmContent,
${'camera_status_alarm.auto_restore AS autoRestore,'} ${'camera_status_alarm.auto_restore AS autoRestore,'}
camera_status_alarm.confirm_time AS confirmTime 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 FROM camera_status_alarm
INNER JOIN camera RIGHT JOIN ${anxinyun}.t_video_ipc
ON camera.serial_no = camera_status_alarm.serial_no ON toString(${anxinyun}.t_video_ipc.channel_no) = camera_status_alarm.channel_no
AND camera.channel_no = camera_status_alarm.channel_no AND ${anxinyun}.t_video_ipc.serial_no = camera_status_alarm.serial_no
${cameraWhereOption.length ? 'AND ' + cameraWhereOption.join(' AND ') : ''} ${`WHERE ${anxinyun}.t_video_ipc.structure IN (${anxinStrucIds.join(',')})`}
LEFT JOIN vender )
ON vender.id = camera.vender_id ${limit ? 'LIMIT ' + limit : ''}
WHERE ${limit && page ? 'OFFSET ' + parseInt(limit) * parseInt(page) : ''}
camera.delete = false ) AS cameraAlarm
AND camera.recycle_time is null LEFT JOIN camera_status
${statusAlarmWhereOption.length ? 'AND ' + statusAlarmWhereOption.join(' AND ') : ''} ON cameraAlarm.platform = camera_status.platform
AND alarmId IN ( AND cameraAlarm.statusId = camera_status.id
SELECT camera_status_alarm.id AS alarmId LEFT JOIN camera_status_resolve
FROM camera_status_alarm ON camera_status_resolve.status_id = camera_status.id
RIGHT JOIN ${anxinyun}.t_video_ipc LEFT JOIN "gbCamera"
ON toString(${anxinyun}.t_video_ipc.channel_no) = camera_status_alarm.channel_no ON "gbCamera".id = cameraAlarm.gbId
AND ${anxinyun}.t_video_ipc.serial_no = camera_status_alarm.serial_no LEFT JOIN "secret_yingshi"
${`WHERE ${anxinyun}.t_video_ipc.structure IN (${anxinStrucIds.join(',')})`} ON "secret_yingshi".id = cameraAlarm.yingshiSecretId
)
${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 LEFT JOIN ${anxinyun}.t_video_ipc AS anxinIpc
ON toString(anxinIpc.channel_no) = cameraAlarm.cameraChannelNo ON toString(anxinIpc.channel_no) = cameraAlarm.cameraChannelNo
AND anxinIpc.serial_no = cameraAlarm.cameraSerialNo AND anxinIpc.serial_no = cameraAlarm.cameraSerialNo
LEFT JOIN ${anxinyun}.t_structure AS anxinStruc LEFT JOIN ${anxinyun}.t_structure AS anxinStruc
ON anxinStruc.id = anxinIpc.structure ON anxinStruc.id = anxinIpc.structure
AND anxinStruc.id IN (${anxinStrucIds.join(',')}) AND anxinStruc.id IN (${anxinStrucIds.join(',')})
${keywordTarget == 'struc' && keyword ? `AND anxinStruc.name LIKE '%${keyword}%'` : ''} ${keywordTarget == 'struc' && keyword ? `AND anxinStruc.name LIKE '%${keyword}%'` : ''}
LEFT JOIN ${anxinyun}.t_video_ipc_station AS anxinIpcStation LEFT JOIN ${anxinyun}.t_video_ipc_station AS anxinIpcStation
ON anxinIpcStation.ipc = anxinIpc.id ON anxinIpcStation.ipc = anxinIpc.id
LEFT JOIN ${anxinyun}.t_sensor AS anxinStation LEFT JOIN ${anxinyun}.t_sensor AS anxinStation
ON anxinStation.id = anxinIpcStation.station ON anxinStation.id = anxinIpcStation.station
` `
const alarmRes = anxinStrucIds.length ? await clickHouse.vcmp.query(
queryStr
).toPromise() : [] ).toPromise() : []
console.log(queryStr);
let returnD = [] let returnD = []
let positionD = {} let positionD = {}
// 每个设备一个告警 // 每个设备一个告警
@ -249,12 +251,8 @@ async function alarmList(ctx, agg) {
} }
} }
if (agg == 'day') {//控制台 按日聚集 ctx.status = 200;
return returnD ctx.body = returnD
} else {
ctx.status = 200;
ctx.body = returnD
}
} catch (error) { } catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400; ctx.status = 400;
@ -266,9 +264,7 @@ async function alarmList(ctx, agg) {
async function confirm (ctx) { async function confirm (ctx) {
try { try {
const { models } = ctx.fs.dc;
const { alarmId, content, confirmPost } = ctx.request.body; const { alarmId, content, confirmPost } = ctx.request.body;
const { pepUserId, projectCorrelationIds, alarmInfo } = confirmPost;
// TODO: 以视频·应用的秘钥进行鉴权 // TODO: 以视频·应用的秘钥进行鉴权
await ctx.app.fs.vcmpRequest.put('status/alarm/confirm', { await ctx.app.fs.vcmpRequest.put('status/alarm/confirm', {
data: { data: {
@ -276,28 +272,7 @@ async function confirm (ctx) {
} }
}) })
//存日志 await alarmConfirmLog(ctx, confirmPost, content);//告警确认日志
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);
ctx.status = 204; ctx.status = 204;
} catch (error) { } catch (error) {

192
api/app/lib/controllers/control/data.js

@ -1,55 +1,5 @@
'use strict'; 'use strict';
const moment = require('moment'); 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分析-数据 //BI分析-数据
async function getDataAlarmsAggDay(ctx) { async function getDataAlarmsAggDay(ctx) {
@ -143,14 +93,10 @@ async function getAppAlarmsAggDay(ctx) {
attributes: ['id', 'createTime', 'confirmTime'], attributes: ['id', 'createTime', 'confirmTime'],
include: [{ include: [{
model: models.App, model: models.App,
attributes: { attributes: ['id'],
exclude: ['projectId']
},
include: [{ include: [{
model: models.ProjectCorrelation, model: models.ProjectCorrelation,
attributes: { attributes: ['id']
exclude: ['id']
},
}] }]
}] }]
} }
@ -185,9 +131,99 @@ async function getAppAlarmsAggDay(ctx) {
//BI分析-视频异常 //BI分析-视频异常
async function getVideoAlarmsAggDay(ctx) { async function getVideoAlarmsAggDay(ctx) {
try { try {
let videoAlarms = await alarmList(ctx, 'day'); const { clickHouse, utils: { anxinStrucIdRange } } = ctx.app.fs
let aggDayMap = []; 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) { 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')); let exist = aggDayMap.find(ad => ad.day == moment(a.createTime).format('YYYY-MM-DD'));
if (exist) { if (exist) {
exist.total++;//总数 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) { async function getLatestDynamic(ctx) {
try { try {
@ -232,7 +288,11 @@ async function getLatestDynamic(ctx) {
}, { }, {
model: models.AlarmAppearRecord model: models.AlarmAppearRecord
}, { }, {
model: models.EmailSendLog model: models.EmailSendLog,
include: [{
model: models.AlarmPushConfig,
attributes: ['id', 'name'],
}]
}, { }, {
model: models.AlarmConfirmLog model: models.AlarmConfirmLog
}], }],
@ -248,7 +308,9 @@ async function getLatestDynamic(ctx) {
pepPojectIds.add(p.projectCorrelation.pepProjectId); pepPojectIds.add(p.projectCorrelation.pepProjectId);
if (p.emailSendLog) { if (p.emailSendLog) {
notedUserIds.add(p.emailSendLog.toPepUserId);//通知 接收人 p.emailSendLog.toPepUserIds.map(u => {
notedUserIds.add(u);//通知 接收人
})
} }
if (p.alarmConfirmLog && p.alarmConfirmLog.pepUserId) { if (p.alarmConfirmLog && p.alarmConfirmLog.pepUserId) {
notedUserIds.add(p.alarmConfirmLog.pepUserId);//确认 操作者 notedUserIds.add(p.alarmConfirmLog.pepUserId);//确认 操作者
@ -269,14 +331,14 @@ async function getLatestDynamic(ctx) {
if (d.alarmAppearId) { if (d.alarmAppearId) {
appear.push({ appear.push({
projectName, projectName,
...d.alarmAppearRecord ...d.alarmAppearRecord.dataValues
}); });
} }
if (d.emailSendId) { if (d.emailSendId) {
notice.push({ notice.push({
userName: userPepRes.find(u => u.id == d.emailSendLog.toPepUserId).name, userName: userPepRes.find(u => d.emailSendLog.toPepUserIds.indexOf(u.id) != -1),
projectName, projectName,
...d.emailSendLog ...d.emailSendLog.dataValues
}); });
} }
if (d.alarmConfirmId) { if (d.alarmConfirmId) {
@ -303,10 +365,10 @@ async function getLatestDynamic(ctx) {
} }
module.exports = { module.exports = {
getProjectsInfo,
getDataAlarmsAggDay, getDataAlarmsAggDay,
getAppAlarmsAggDay, getAppAlarmsAggDay,
getVideoAlarmsAggDay, getVideoAlarmsAggDay,
getAlarmsHandleStatistics,
getLatestDynamic getLatestDynamic
}; };

22
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) { async function editUser (ctx) {
try { try {
const models = ctx.fs.dc.models; const models = ctx.fs.dc.models;
@ -311,4 +332,5 @@ module.exports = {
putUser, putUser,
delAdmin, delAdmin,
user, user,
allUsers,
}; };

93
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 = { module.exports = {
appList, appList,
projectAnxincloud, projectAnxincloud,
projectPManage, projectPManage,
pomsProject, pomsProject,
pepProjectConstrictionState,
strucWithPomsProject,
}; };

168
api/app/lib/controllers/push/config.js

@ -4,38 +4,152 @@ const moment = require('moment')
async function list (ctx) { async function list (ctx) {
try { try {
const models = ctx.fs.dc.models; 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 = { let findOption = {
where: { 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) { if (alarmType) {
findOption.where.alarmType = { $contains: [alarmType] } findOption.where.alarmType = { $contains: [alarmType] }
} }
if (state) { if (state) {
if (state == 'enable') { if (state == 'enable' || state == 'notYet' || state == 'takeEffect') {
findOption.where.disable = false findOption.where.disable = false
} else if (state == 'disable') { } else if (state == 'disable') {
findOption.where.disable = true 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.status = 200;
ctx.body = listRes ctx.body = returnD
} catch (error) { } catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400; ctx.status = 400;
@ -49,10 +163,12 @@ async function edit (ctx) {
try { try {
const models = ctx.fs.dc.models; const models = ctx.fs.dc.models;
const { userId, pepUserId } = ctx.fs.api 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 = { let storageData = {
name, pepProjectId, alarmType, receiverPepUserId, timeType, disable name, pomsProjectId, alarmType, receiverPepUserId, timeType, disable,
strucId, tactics, tacticsParams
} }
if (pushId) { if (pushId) {
await models.AlarmPushConfig.update(storageData, { await models.AlarmPushConfig.update(storageData, {
@ -63,6 +179,7 @@ async function edit (ctx) {
} else { } else {
storageData.createTime = moment().format() storageData.createTime = moment().format()
storageData.createUserId = userId storageData.createUserId = userId
storageData.del = false
await models.AlarmPushConfig.create(storageData) await models.AlarmPushConfig.create(storageData)
} }
@ -82,22 +199,17 @@ async function state (ctx) {
const { pushId } = ctx.params const { pushId } = ctx.params
const { disable = undefined, del = undefined, } = ctx.request.body const { disable = undefined, del = undefined, } = ctx.request.body
if (del) { let updateData = {
await models.AlarmPushConfig.destroy({ disable,
where: { del,
id: pushId
}
})
} else {
await models.AlarmPushConfig.update({
disable,
}, {
where: {
id: pushId
}
})
} }
await models.AlarmPushConfig.update(updateData, {
where: {
id: pushId
}
})
ctx.status = 204; ctx.status = 204;
} catch (error) { } catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);

8
api/app/lib/index.js

@ -58,7 +58,7 @@ module.exports.models = function (dc) { // dc = { orm: Sequelize对象, ORM: Seq
}); });
const { const {
AppInspection, ProjectApp, ProjectCorrelation, AppAlarm, App, AlarmAppearRecord, AlarmConfirmLog, EmailSendLog, LatestDynamicList AppInspection, ProjectApp, ProjectCorrelation, AppAlarm, App, AlarmAppearRecord, AlarmConfirmLog, EmailSendLog, LatestDynamicList, AlarmPushConfig
} = dc.models; } = dc.models;
AppInspection.belongsTo(App, { foreignKey: 'projectAppId', targetKey: 'id' }); 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' }); AppAlarm.belongsTo(App, { foreignKey: 'projectAppId', targetKey: 'id' });
App.hasMany(AppAlarm, { foreignKey: 'projectAppId', sourceKey: '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' }); AlarmAppearRecord.belongsTo(ProjectCorrelation, { foreignKey: 'projectCorrelationId', targetKey: 'id' });
ProjectCorrelation.hasMany(AlarmAppearRecord, { foreignKey: 'projectCorrelationId', sourceKey: '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' }); EmailSendLog.belongsTo(ProjectCorrelation, { foreignKey: 'projectCorrelationId', targetKey: 'id' });
ProjectCorrelation.hasMany(EmailSendLog, { foreignKey: 'projectCorrelationId', sourceKey: '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' }); LatestDynamicList.belongsTo(AlarmAppearRecord, { foreignKey: 'alarmAppearId', targetKey: 'id' });
AlarmAppearRecord.hasMany(LatestDynamicList, { foreignKey: 'alarmAppearId', sourceKey: 'id' }); AlarmAppearRecord.hasMany(LatestDynamicList, { foreignKey: 'alarmAppearId', sourceKey: 'id' });

98
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;
};

220
api/app/lib/models/alarm_push_config.js

@ -2,96 +2,132 @@
'use strict'; 'use strict';
module.exports = dc => { module.exports = dc => {
const DataTypes = dc.ORM; const DataTypes = dc.ORM;
const sequelize = dc.orm; const sequelize = dc.orm;
const AlarmPushConfig = sequelize.define("alarmPushConfig", { const AlarmPushConfig = sequelize.define("alarmPushConfig", {
id: { id: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: false, allowNull: false,
defaultValue: null, defaultValue: null,
comment: null, comment: null,
primaryKey: true, primaryKey: true,
field: "id", field: "id",
autoIncrement: true, autoIncrement: true,
unique: "alarm_push_config_id_uindex" unique: "alarm_push_config_id_uindex"
}, },
name: { name: {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: false, allowNull: false,
defaultValue: null, defaultValue: null,
comment: null, comment: null,
primaryKey: false, primaryKey: false,
field: "name", field: "name",
autoIncrement: false autoIncrement: false
}, },
pepProjectId: { pomsProjectId: {
type: DataTypes.ARRAY(DataTypes.INTEGER), type: DataTypes.INTEGER,
allowNull: false, allowNull: false,
defaultValue: null, defaultValue: null,
comment: null, comment: null,
primaryKey: false, primaryKey: false,
field: "pep_project_id", field: "poms_project_id",
autoIncrement: false autoIncrement: false
}, },
alarmType: { alarmType: {
type: DataTypes.ARRAY(DataTypes.STRING), type: DataTypes.ARRAY(DataTypes.STRING),
allowNull: true, allowNull: true,
defaultValue: null, defaultValue: null,
comment: "监听的告警类型", comment: "监听的告警类型",
primaryKey: false, primaryKey: false,
field: "alarm_type", field: "alarm_type",
autoIncrement: false autoIncrement: false
}, },
receiverPepUserId: { receiverPepUserId: {
type: DataTypes.ARRAY(DataTypes.INTEGER), type: DataTypes.ARRAY(DataTypes.INTEGER),
allowNull: true, allowNull: true,
defaultValue: null, defaultValue: null,
comment: "接收人id 项企", comment: "接收人id 项企",
primaryKey: false, primaryKey: false,
field: "receiver_pep_user_id", field: "receiver_pep_user_id",
autoIncrement: false autoIncrement: false
}, },
timeType: { timeType: {
type: DataTypes.ARRAY(DataTypes.STRING), type: DataTypes.ARRAY(DataTypes.STRING),
allowNull: true, allowNull: true,
defaultValue: null, defaultValue: null,
comment: "通知时效", comment: "通知时效",
primaryKey: false, primaryKey: false,
field: "time_type", field: "time_type",
autoIncrement: false autoIncrement: false
}, },
createTime: { createTime: {
type: DataTypes.DATE, type: DataTypes.DATE,
allowNull: false, allowNull: false,
defaultValue: null, defaultValue: null,
comment: null, comment: null,
primaryKey: false, primaryKey: false,
field: "create_time", field: "create_time",
autoIncrement: false autoIncrement: false
}, },
createUserId: { createUserId: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: false, allowNull: false,
defaultValue: null, defaultValue: null,
comment: null, comment: null,
primaryKey: false, primaryKey: false,
field: "create_user_id", field: "create_user_id",
autoIncrement: false autoIncrement: false
}, },
disable: { disable: {
type: DataTypes.BOOLEAN, type: DataTypes.BOOLEAN,
allowNull: false, allowNull: false,
defaultValue: null, defaultValue: null,
comment: null, comment: null,
primaryKey: false, primaryKey: false,
field: "disable", field: "disable",
autoIncrement: false autoIncrement: false
} },
}, { strucId: {
tableName: "alarm_push_config", type: DataTypes.ARRAY(DataTypes.INTEGER),
comment: "", allowNull: false,
indexes: [] defaultValue: null,
}); comment: null,
dc.models.AlarmPushConfig = AlarmPushConfig; primaryKey: false,
return AlarmPushConfig; 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;
}; };

38
api/app/lib/models/email_send_log.js

@ -16,40 +16,58 @@ module.exports = dc => {
autoIncrement: true, autoIncrement: true,
unique: "email_send_log_id_uindex" unique: "email_send_log_id_uindex"
}, },
projectCorrelationId: { time: {
type: DataTypes.INTEGER, type: DataTypes.DATE,
allowNull: false, allowNull: false,
defaultValue: null, defaultValue: null,
comment: null, comment: null,
primaryKey: false, primaryKey: false,
field: "project_correlation_id", field: "time",
autoIncrement: false autoIncrement: false
}, },
toPepUserId: { pushConfigId: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: false, allowNull: false,
defaultValue: null, defaultValue: null,
comment: null, comment: null,
primaryKey: false, primaryKey: false,
field: "to_pep_user_id", field: "push_config_id",
autoIncrement: false autoIncrement: false
}, },
by: { tactics: {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: true, allowNull: true,
defaultValue: null, defaultValue: null,
comment: null, comment: null,
primaryKey: false, primaryKey: false,
field: "by", field: "tactics",
autoIncrement: false autoIncrement: false
}, },
time: { tacticsParams: {
type: DataTypes.DATE, type: DataTypes.JSONB,
allowNull: true,
defaultValue: null,
comment: null,
primaryKey: false,
field: "tactics_params",
autoIncrement: false
},
projectCorrelationId: {
type: DataTypes.INTEGER,
allowNull: false, allowNull: false,
defaultValue: null, defaultValue: null,
comment: null, comment: null,
primaryKey: false, 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 autoIncrement: false
} }
}, { }, {

8
api/app/lib/routes/control/index.js

@ -27,11 +27,6 @@ module.exports = function (app, router, opts) {
router.get('/analysis/problem', analysis.problem); router.get('/analysis/problem', analysis.problem);
//项目概览
app.fs.api.logAttr['GET/projects/info'] = { content: '查询项目概览', visible: false };
router.get('/projects/info', csData.getProjectsInfo);
//BI分析模块 //BI分析模块
app.fs.api.logAttr['GET/data/alarms/agg/day'] = { content: '查询BI分析数据-数据', visible: false }; app.fs.api.logAttr['GET/data/alarms/agg/day'] = { content: '查询BI分析数据-数据', visible: false };
router.get('/data/alarms/agg/day', csData.getDataAlarmsAggDay); 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 }; app.fs.api.logAttr['GET/video/alarms/agg/day'] = { content: '查询BI分析数据-视频异常', visible: false };
router.get('/video/alarms/agg/day', csData.getVideoAlarmsAggDay); 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 }; app.fs.api.logAttr['GET/latest/dynamic'] = { content: '查询最新动态', visible: false };
router.get('/latest/dynamic', csData.getLatestDynamic); router.get('/latest/dynamic', csData.getLatestDynamic);

3
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 }; app.fs.api.logAttr['GET/organization/deps'] = { content: '获取全部部门及其下用户', visible: true };
router.get('/organization/deps', organization.allDeps); 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 }; app.fs.api.logAttr['POST/organization/user'] = { content: '编辑成员', visible: true };
router.post('/organization/user', organization.editUser); router.post('/organization/user', organization.editUser);

6
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 }; app.fs.api.logAttr['GET/project/poms'] = { content: '获取已绑定项目', visible: true };
router.get('/project/poms', project.pomsProject); 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);
}; };

2
api/app/lib/routes/push/index.js

@ -7,7 +7,7 @@ module.exports = function (app, router, opts) {
router.get('/push', push.list); router.get('/push', push.list);
app.fs.api.logAttr['POST/push'] = { content: '新增/编辑推送配置', visible: true }; 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 }; app.fs.api.logAttr['PUT/push/:pushId'] = { content: '更改推送配置状态(禁用或删除)', visible: true };
router.put('/push/:pushId', push.state); router.put('/push/:pushId', push.state);

480
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
}
}

465
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,
}
}

53
api/app/lib/utils/dataRange.js

@ -160,24 +160,43 @@ module.exports = function (app, opts) {
// 并保存信息至数据 // 并保存信息至数据
let undelStruc = [] let undelStruc = []
for (let s of undelStrucRes) { for (let s of undelStrucRes) {
if (!undelStruc.some(us => us.strucId == s.strucId)) { let corStruc = undelStruc.find(us => us.strucId == s.strucId)
let pomsProject = [] if (corStruc) {
for (let { dataValues: br } of bindRes) { if (!corStruc.project.some(cp => cp.id == s.projectId)) {
if (br.anxinProjectId.some(braId => braId == s.projectId)) { corStruc.project.push({
let corPepProject = pepProjectRes.find(pp => pp.id == br.pepProjectId) id: s.projectId
pomsProject.push({ })
}
} 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, ...br,
pepProject: corPepProject pepProject: corPepProject
}) })
} }
} }
undelStruc.push({
strucId: s.strucId,
strucName: s.strucName,
projectId: s.projectId,
pomsProject: pomsProject
})
} }
} }
return undelStruc return undelStruc
} }
@ -188,6 +207,16 @@ module.exports = function (app, opts) {
let pomsProject = [] let pomsProject = []
for (let b of bindRes) { for (let b of bindRes) {
if (
keywordTarget == 'pepProject' && keyword
&& (
!b.name && !(
b.pepProjectId && pepProjectRes.some(pp => pp.id == b.pepProjectId)
)
)
) {
continue
}
if (b.pepProjectId) { if (b.pepProjectId) {
let corPepProject = pepProjectRes.find(pp => pp.id == b.pepProjectId) || {} let corPepProject = pepProjectRes.find(pp => pp.id == b.pepProjectId) || {}
pomsProject.push({ pomsProject.push({

62
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,
}
}

2
api/sequelize-automate.config.js

@ -26,7 +26,7 @@ module.exports = {
dir: './app/lib/models', // 指定输出 models 文件的目录 dir: './app/lib/models', // 指定输出 models 文件的目录
typesDir: 'models', // 指定输出 TypeScript 类型定义的文件目录,只有 TypeScript / Midway 等会有类型定义 typesDir: 'models', // 指定输出 TypeScript 类型定义的文件目录,只有 TypeScript / Midway 等会有类型定义
emptyDir: false, // !!! 谨慎操作 生成 models 之前是否清空 `dir` 以及 `typesDir` 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,则忽略改属性 skipTables: [], // 指定跳过哪些表的 models,如 ['user'];如果为 null,则忽略改属性
tsNoCheck: false, // 是否添加 `@ts-nocheck` 注释到 models 文件中 tsNoCheck: false, // 是否添加 `@ts-nocheck` 注释到 models 文件中
ignorePrefix: [], // 生成的模型名称忽略的前缀,因为 项目中有以下表名是以 t_ 开头的,在实际模型中不需要, 可以添加多个 [ 't_data_', 't_',] ,长度较长的 前缀放前面 ignorePrefix: [], // 生成的模型名称忽略的前缀,因为 项目中有以下表名是以 t_ 开头的,在实际模型中不需要, 可以添加多个 [ 't_data_', 't_',] ,长度较长的 前缀放前面

22
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);

27
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);

23
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);

28
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);

22
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);

15
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;

2
web/client/index.ejs

@ -11,7 +11,7 @@
<script charset="UTF-8" id="LA_COLLECT" src="//sdk.51.la/js-sdk-pro.min.js"></script> <script charset="UTF-8" id="LA_COLLECT" src="//sdk.51.la/js-sdk-pro.min.js"></script>
<script>LA.init({ id: "Jo4eTlZVqgx3uwqm", ck: "Jo4eTlZVqgx3uwqm" })</script> <script>LA.init({ id: "Jo4eTlZVqgx3uwqm", ck: "Jo4eTlZVqgx3uwqm" })</script>
<script src="https://lf1-cdn-tos.bytegoofy.com/obj/iconpark/icons_19077_10.1efd80a22a5e53e48737fd5ab150ffd2.es5.js"></script> <script src="https://lf1-cdn-tos.bytegoofy.com/obj/iconpark/icons_19077_11.559b91c217b8ddc76c0c4b1397d84d48.es5.js"></script>
</head> </head>
<body> <body>

2
web/client/index.html

@ -11,7 +11,7 @@
<script>LA.init({ id: "Jo4eTlZVqgx3uwqm", ck: "Jo4eTlZVqgx3uwqm" })</script> <script>LA.init({ id: "Jo4eTlZVqgx3uwqm", ck: "Jo4eTlZVqgx3uwqm" })</script>
<script <script
src="https://lf1-cdn-tos.bytegoofy.com/obj/iconpark/icons_19077_11.27aacefc59cea1cbc86236576463a6d2.es5.js"></script> src="https://lf1-cdn-tos.bytegoofy.com/obj/iconpark/icons_19077_11.559b91c217b8ddc76c0c4b1397d84d48.es5.js"></script>
</head > </head >
<body> <body>

4
web/client/src/sections/install/containers/roles.jsx

@ -56,7 +56,7 @@ const Roles = (props) => {
let anxinerror = false let anxinerror = false
let anxinerrorArr = [] let anxinerrorArr = []
for (let i = 0; i < row.correlationProject.length; i++) { for (let i = 0; i < row.correlationProject.length; i++) {
if (row.correlationProject[i].del == -1) { if (row.correlationProject[i].del == true) {
anxinerror = true anxinerror = true
anxinerrorArr.push(row.correlationProject[i].pepProjectName) anxinerrorArr.push(row.correlationProject[i].pepProjectName)
} }
@ -65,7 +65,7 @@ const Roles = (props) => {
<div style={{ display: 'flex', alignItems: 'center' }}> <div style={{ display: 'flex', alignItems: 'center' }}>
{ {
anxinerror ? ( anxinerror ? (
<Tooltip content={anxinerrorArr.join(',') + ',项目已在【项企PEP】中被删除!'}> <Tooltip content={anxinerrorArr.join(',') + ',项目已在【项企PEP】或【映射关系】中被删除!'}>
<div style={{ marginRight: 5 }}> <div style={{ marginRight: 5 }}>
<img src="/assets/images/install/risk.png" alt="" style={{ height: 24, width: 24, }} /> <img src="/assets/images/install/risk.png" alt="" style={{ height: 24, width: 24, }} />
</div> </div>

4
web/client/src/sections/install/containers/system.jsx

@ -131,7 +131,7 @@ const Example = (props) => {
<img src="/assets/images/install/icon_POMS.png" alt="" style={{ height: 10, width: 10, marginLeft: 4, marginRight: 9 }} /> <img src="/assets/images/install/icon_POMS.png" alt="" style={{ height: 10, width: 10, marginLeft: 4, marginRight: 9 }} />
</div> </div>
<div style={{ color: '#FFFFFF', fontSize: 11, marginRight: 12 }}> <div style={{ color: '#FFFFFF', fontSize: 11, marginRight: 12 }}>
PMOS POMS
</div> </div>
</div> </div>
) )
@ -273,7 +273,7 @@ const Example = (props) => {
修改 修改
</Button> </Button>
<Popconfirm <Popconfirm
title="删除后对应的项目全局将无法进入和显示,对应的信鸽服务也会被禁用" title="删除后对应的项目全局将无法进入和显示,对应的信鸽服务也会被删除"
arrowPointAtCenter={false} arrowPointAtCenter={false}
showArrow={true} showArrow={true}
position="topRight" position="topRight"

47
web/client/src/sections/problem/containers/dataAlarm.jsx

@ -37,8 +37,6 @@ const DataAlarm = ({ match, dispatch, actions, user, loading, socket, iotVcmpWeb
const [videoModal, setVideoModal] = useState(false) // const [videoModal, setVideoModal] = useState(false) //
const [videoData, setVideoData] = useState({}) // const [videoData, setVideoData] = useState({}) //
const [videoToken, setVideoToken] = useState() //token const [videoToken, setVideoToken] = useState() //token
const [alarmToConfirm, setAlarmToConfirm] = useState(null) //
const TextAreaApi = useRef('') const TextAreaApi = useRef('')
@ -306,15 +304,14 @@ const DataAlarm = ({ match, dispatch, actions, user, loading, socket, iotVcmpWeb
name: '操作', sort: 25, value: 'text', render: (_, r, index) => { name: '操作', sort: 25, value: 'text', render: (_, r, index) => {
return <div style={{ width: 195 }}> return <div style={{ width: 195 }}>
{r.State < 3 || route && ['videoAbnormal', 'useAbnormal'].includes(route) && !r.confirmTime ? {r.State < 3 || route && ['videoAbnormal', 'useAbnormal'].includes(route) && !r.confirmTime ?
<Button theme='borderless' style={{ width: 65 }} onClick={() => { <Button theme='borderless' style={{ width: 65 }} onClick={() => {
setConfirm(true) setConfirm(true)
setSelected([r.key]) setSelected([r.key])
setAlarmToConfirm(r) }}>确认</Button>
}}>确认</Button> : r.State == 3 || r.autoRestore || r.confirmAuto ?
: r.State == 3 || r.autoRestore || r.confirmAuto ?
<Button theme='borderless' style={{ width: 65 }} disabled>自动恢复</Button> : <Button theme='borderless' style={{ width: 65 }} disabled>自动恢复</Button> :
<Button theme='borderless' style={{ width: 65 }} disabled>已确认</Button> <Button theme='borderless' style={{ width: 65 }} disabled>已确认</Button>
} }
{route && ['dataLnterrupt', 'dataAbnormal', 'strategyHit', 'deviceAbnormal'].includes(route) ? <> {route && ['dataLnterrupt', 'dataAbnormal', 'strategyHit', 'deviceAbnormal'].includes(route) ? <>
<Button theme='borderless' style={{ width: 65 }} disabled>已派单</Button> <Button theme='borderless' style={{ width: 65 }} disabled>已派单</Button>
{route == 'deviceAbnormal' ? "" : <Button theme='borderless' style={{ width: 65 }} onClick={() => { {route == 'deviceAbnormal' ? "" : <Button theme='borderless' style={{ width: 65 }} onClick={() => {
@ -384,18 +381,24 @@ const DataAlarm = ({ match, dispatch, actions, user, loading, socket, iotVcmpWeb
// console.log(selected); // console.log(selected);
const getAlarmConfirmItem = () => { const getAlarmConfirmItems = () => {
let source = route == 'useAbnormal' ? alarmToConfirm.appName : alarmToConfirm.SourceName; let confirmItems = [];
let type = route == 'useAbnormal' ? alarmToConfirm.type : route == 'videoAbnormal' ? alarmToConfirm.AlarmContent : alarmToConfirm.AlarmGroupUnit; selected.map(s => {
return { let alarmInfo = tableData.find(td => td.key == s);
pepUserId: user.pomsUserInfo.pepUserId, let source = route == 'useAbnormal' ? alarmInfo.appName : alarmInfo.SourceName;
projectCorrelationIds: alarmToConfirm?.projectName?.map(p => p.id), let type = route == 'useAbnormal' ? alarmInfo.type : route == 'videoAbnormal' ? alarmInfo.AlarmContent : alarmInfo.AlarmGroupUnit;
alarmInfo: { let item = {
id: alarmToConfirm.key, pepUserId: user.pomsUserInfo.pepUserId,
source: source,// projectCorrelationIds: alarmInfo?.projectName?.map(p => p.id),
type: type,// alarmInfo: {
id: alarmInfo.key,
source: source,//
type: type,//
}
} }
}; confirmItems.push(item);
})
return confirmItems;
} }
return ( return (
@ -457,7 +460,7 @@ const DataAlarm = ({ match, dispatch, actions, user, loading, socket, iotVcmpWeb
width={600} width={600}
onCancel={() => setConfirm(false)} onCancel={() => setConfirm(false)}
onOk={() => { onOk={() => {
let confirmPost = getAlarmConfirmItem(); let confirmPost = getAlarmConfirmItems();
if (route == 'useAbnormal') { if (route == 'useAbnormal') {
TextAreaApi.current.validate().then((v) => { TextAreaApi.current.validate().then((v) => {
dispatch(problem.postApiConfirm({ appAlarmId: selected, confirm: content, confirmPost })).then(res => { dispatch(problem.postApiConfirm({ appAlarmId: selected, confirm: content, confirmPost })).then(res => {

93
web/client/src/sections/service/actions/emPush.js

@ -0,0 +1,93 @@
'use strict';
import { ApiTable, basicAction } from '$utils'
export function getPush (query) { //获取推送配置列表
return dispatch => basicAction({
type: 'get',
dispatch: dispatch,
query: query,
actionType: 'GET_PUSH',
url: `${ApiTable.getPush}`,
msg: { error: '获取推送配置列表失败' },
reducer: { name: '' }
});
}
export function postPush (data) {//添加/编辑成员
let msg = ''
if (data) {
msg = data.msg
}
return (dispatch) =>
basicAction({
type: "post",
dispatch: dispatch,
data,
actionType: "POST_PUSH",
url: `${ApiTable.postPush}`,
msg: { option: msg }, //添加/编辑成员
reducer: { name: "" },
});
}
export function getOrganizationUsers () { //获取全部未删除用户
return dispatch => basicAction({
type: 'get',
dispatch: dispatch,
actionType: 'GET_ORGANIZATION_USERS',
url: `${ApiTable.getOrganizationUsers}`,
msg: { error: '获取全部未删除用户' },
reducer: { name: '' }
});
}
export function getProjectPoms (query) {//获取已绑定项目
return (dispatch) => basicAction({
type: "get",
dispatch: dispatch,
actionType: "GET_PROJECT_POMS",
query: query,
url: `${ApiTable.getProjectPoms}`,
msg: { option: "获取已绑定项目" },
reducer: { name: "ProjectPoms", params: { noClear: true } },
});
}
export function getProjectStructure (query) {//获取绑定项目下结构物
return (dispatch) => basicAction({
type: "get",
dispatch: dispatch,
actionType: "GET_PROJECT_STRUCTURE",
query: query,
url: `${ApiTable.getProjectStructure}`,
msg: { option: "获取绑定项目下结构物" },
reducer: { name: "ProjectStructure", params: { noClear: true } },
});
}
export function getProjectStatus (query) {//获取项目状态列表
return (dispatch) => basicAction({
type: "get",
dispatch: dispatch,
actionType: "GET_PROJECT_STATUS",
query: query,
url: `${ApiTable.getProjectStatus}`,
msg: { option: "获取项目状态列表" },
reducer: { name: "ProjectStatus", params: { noClear: true } },
});
}
export function putPushPushId (data) {//更改推送配置状态(禁用或删除)
let pushId = ''
let msg = ''
if (data) {
pushId = data.pushId
msg = data.msg
}
return (dispatch) =>
basicAction({
type: "put",
dispatch: dispatch,
data,
actionType: "PUT_PUSH_PUSHID",
url: `${ApiTable.putPushPushId.replace("{pushId}", pushId)}`,
msg: { option: msg }, //更改推送配置状态(禁用或删除)
reducer: {},
});
}

14
web/client/src/sections/service/actions/emPush.jsx

@ -1,14 +0,0 @@
'use strict';
import { ApiTable, basicAction } from '$utils'
export function getPush () { //
return dispatch => basicAction({
type: 'get',
dispatch: dispatch,
actionType: 'GET_PUSH',
url: `${ApiTable.getPush}`,
msg: { error: '获取推送配置列表失败' },
reducer: { name: '' }
});
}

601
web/client/src/sections/service/components/pushModal.jsx

@ -1,7 +1,8 @@
import React, { useState, useRef, useEffect } from "react"; import React, { useState, useRef, useEffect } from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Modal, Form } from "@douyinfe/semi-ui"; import { Modal, Form, Notification } from "@douyinfe/semi-ui";
import { IconAlertCircle } from '@douyinfe/semi-icons'; import { IconAlertCircle } from '@douyinfe/semi-icons';
import './pushModal.less'
function pushModal (props) { function pushModal (props) {
@ -12,47 +13,342 @@ function pushModal (props) {
dispatch, dispatch,
pepList, pepList,
actions, actions,
adminEdit,// pushEdit,//
editObj, editObj,
user
} = props; } = props;
const { install } = actions; const { service } = actions;
const form = useRef();// const form = useRef();//
const [disablePeople, setDisablePeople] = useState(true); // const [abnormal, setAbnormal] = useState(false); //disable
const [peopleList, setPeopleList] = useState([]); //List const [usersList, setUsersList] = useState([]); //
const [departmentId, setDepartmentId] = useState(); //id const [structure, setStructure] = useState(true); //disable
const [peopleId, setPeopleId] = useState(); //id const [projectPoms, setProjectPoms] = useState([]); //
const [projectStructure, setProjectStructure] = useState([]); //
const [timeTypeDis, setTimeTypeDis] = useState(true); //disable
const [projectStatus, setProjectStatus] = useState([]); //
const timeTypePOMS = useRef([]);//
const [interval1, setInterval1] = useState(undefined); //
const [interval2, setInterval2] = useState(undefined); //
const [interval3, setInterval3] = useState(undefined); //
const [deviceProportion, setDeviceProportion] = useState(undefined); //
// //
useEffect(() => { useEffect(() => {
getOrganizationUsersList()//
getProjectPomsList()//
if (editObj.id) { if (editObj.id) {
let departmentList = [] getProjectStructureList(editObj.pomsProjectId)
for (let i = 0; i < pepList.length; i++) { if (editObj.pomsProject?.pepProjectId) {
if (pepList[i].id == editObj.departments[0].id) { getProjectStatusList()//
departmentList = pepList[i].users }
} else {
setProjectStatus([{ construction_status: 'POMS', id: 'POMS' }])
timeTypePOMS.current = ['POMS']
}
if (editObj.tactics == 'immediately') {
setInterval1(editObj.tacticsParams?.interval)
} else if (editObj.tactics == 'continue') {
setInterval2(editObj.tacticsParams?.interval)
} else if (editObj.tactics == 'abnormal_rate') {
setInterval3(editObj.tacticsParams?.interval)
setDeviceProportion(editObj.tacticsParams?.deviceProportion)
} }
setPeopleList(departmentList)
setDepartmentId(editObj.departments[0].id)
setPeopleId(editObj.pepUserId)
setDisablePeople(false)
} }
}, []); }, []);
function getOrganizationUsersList () {//
dispatch(service.getOrganizationUsers()).then((res) => {
if (res.success) {
setUsersList(res.payload.data)
}
})
}
function getProjectPomsList () {//
dispatch(service.getProjectPoms()).then((res) => {
if (res.success) {
setProjectPoms(res.payload?.data?.rows)
}
})
}
function getProjectStructureList (value) {//
dispatch(service.getProjectStructure({ pomsProjectId: value })).then((res) => {
if (res.success) {
let mylist = []
for (let i = 0; i < res.payload?.data.length; i++) {
mylist.push(res.payload?.data[i].id)
}
setProjectStructure(res.payload?.data)
form.current.setValue('strucId', mylist)
form.current.validate(['strucId', 'timeType'])
setStructure(false)
setTimeTypeDis(false)
}
})
}
function getProjectStatusList () {//
dispatch(service.getProjectStatus()).then((res) => {
if (res.success) {
setProjectStatus(res.payload?.data)
let mylist = []
for (let i = 0; i < res.payload?.data.length; i++) {
mylist.push(res.payload?.data[i].id)
}
form.current.setValue('timeType', mylist)
form.current.validate(['strucId', 'timeType'])
}
})
}
function handleOk () { function handleOk () {
// //
form.current form.current
.validate() .validate()
.then((values) => { .then((values) => {
if (adminEdit) { if (pushEdit) {
dispatch(install.deteleOrganizationAdmin({id:editObj.id,msg:''})).then( let obj = JSON.parse(JSON.stringify(values))
dispatch(install.postOrganizationUser({ role: ['admin'], pepUserId: values.pepUserId, msg: '修改管理员' })).then((res) => {//(PEP) if (obj.timeType[0] == 'POMS') {
if (res.success) { obj.timeType = []
close(); }
let regu = /^[0-9]*[1-9][0-9]*$/;
if (obj.tactics == 'immediately') {
if (obj.interval1) {
if (regu.test(obj.interval1)) {
if (obj.interval1 <= 1440) {
obj.tacticsParams = {
interval: obj.interval1
}
delete obj.interval1
delete obj.interval2
delete obj.interval3
delete obj.deviceProportion
} else {
Notification.error({
content: '即时推送时间不能大于1440分钟',
duration: 2,
})
}
} else {
Notification.error({
content: '即时推送时间应为正整数',
duration: 2,
})
}
} else {
Notification.error({
content: '即时推送时间应为正整数',
duration: 2,
})
}
}
else if (obj.tactics == 'continue') {
if (obj.interval2) {
if (regu.test(obj.interval2)) {
if (obj.interval2 <= 1440) {
obj.tacticsParams = {
interval: obj.interval2
}
delete obj.interval1
delete obj.interval2
delete obj.interval3
delete obj.deviceProportion
} else {
Notification.error({
content: '持续时长推送时间不能大于1440分钟',
duration: 2,
})
}
} else {
Notification.error({
content: '持续时长推送时间应为正整数',
duration: 2,
})
}
} else {
Notification.error({
content: '持续时长推送时间应为正整数',
duration: 2,
})
}
}
else {
if (regu.test(obj.interval3) && regu.test(obj.deviceProportion)) {
if (obj.interval3 <= 720 && obj.deviceProportion <= 100) {
obj.tacticsParams = {
interval: obj.interval3,
deviceProportion: obj.deviceProportion
}
delete obj.interval1
delete obj.interval2
delete obj.interval3
delete obj.deviceProportion
} else if (obj.interval3 <= 720 && obj.deviceProportion > 100) {
Notification.error({
content: '异常率推送异常率不能超过100%',
duration: 2,
})
} else if (obj.interval3 > 720 && obj.deviceProportion <= 100) {
Notification.error({
content: '异常率推送时间不能超过720小时',
duration: 2,
})
} else {
Notification.error({
content: '异常率推送时间不能超过720小时',
duration: 2,
})
Notification.error({
content: '异常率推送异常率不能超过100%',
duration: 2,
})
} }
}) } else if (regu.test(obj.interval3) && !regu.test(obj.deviceProportion)) {
) Notification.error({
content: '异常率推送异常率应为正整数',
duration: 2,
})
} else if (!regu.test(obj.interval3) && regu.test(obj.deviceProportion)) {
Notification.error({
content: '异常率推送时间应为正整数',
duration: 2,
})
} else {
Notification.error({
content: '异常率推送异常率应为正整数',
duration: 2,
})
Notification.error({
content: '异常率推送时间应为正整数',
duration: 2,
})
}
}
dispatch(service.postPush({ pushId: editObj.id, ...obj, msg: '编辑推送配置' })).then((res) => {//(PEP)
if (res.success) {
close();
}
})
} }
else { else {
dispatch(install.postOrganizationUser({ role: ['admin'], pepUserId: values.pepUserId, msg: '新增管理员' })).then((res) => {//(PEP) let obj = JSON.parse(JSON.stringify(values))
if (obj.timeType[0] == 'POMS') {
obj.timeType = []
}
let regu = /^[0-9]*[1-9][0-9]*$/;
if (obj.tactics == 'immediately') {
if (obj.interval1) {
if (regu.test(obj.interval1)) {
if (obj.interval1 <= 1440) {
obj.tacticsParams = {
interval: obj.interval1
}
delete obj.interval1
delete obj.interval2
delete obj.interval3
delete obj.deviceProportion
} else {
Notification.error({
content: '即时推送时间不能大于1440分钟',
duration: 2,
})
}
} else {
Notification.error({
content: '即时推送时间应为正整数',
duration: 2,
})
}
} else {
Notification.error({
content: '即时推送时间应为正整数',
duration: 2,
})
}
}
else if (obj.tactics == 'continue') {
if (obj.interval2) {
if (regu.test(obj.interval2)) {
if (obj.interval2 <= 1440) {
obj.tacticsParams = {
interval: obj.interval2
}
delete obj.interval1
delete obj.interval2
delete obj.interval3
delete obj.deviceProportion
} else {
Notification.error({
content: '持续时长推送时间不能大于1440分钟',
duration: 2,
})
}
} else {
Notification.error({
content: '持续时长推送时间应为正整数',
duration: 2,
})
}
} else {
Notification.error({
content: '持续时长推送时间应为正整数',
duration: 2,
})
}
}
else {
if (regu.test(obj.interval3) && regu.test(obj.deviceProportion)) {
if (obj.interval3 <= 720 && obj.deviceProportion <= 100) {
obj.tacticsParams = {
interval: obj.interval3,
deviceProportion: obj.deviceProportion
}
delete obj.interval1
delete obj.interval2
delete obj.interval3
delete obj.deviceProportion
} else if (obj.interval3 <= 720 && obj.deviceProportion > 100) {
Notification.error({
content: '异常率推送异常率不能超过100%',
duration: 2,
})
} else if (obj.interval3 > 720 && obj.deviceProportion <= 100) {
Notification.error({
content: '异常率推送时间不能超过720小时',
duration: 2,
})
} else {
Notification.error({
content: '异常率推送时间不能超过720小时',
duration: 2,
})
Notification.error({
content: '异常率推送异常率不能超过100%',
duration: 2,
})
}
} else if (regu.test(obj.interval3) && !regu.test(obj.deviceProportion)) {
Notification.error({
content: '异常率推送异常率应为正整数',
duration: 2,
})
} else if (!regu.test(obj.interval3) && regu.test(obj.deviceProportion)) {
Notification.error({
content: '异常率推送时间应为正整数',
duration: 2,
})
} else {
Notification.error({
content: '异常率推送异常率应为正整数',
duration: 2,
})
Notification.error({
content: '异常率推送时间应为正整数',
duration: 2,
})
}
}
dispatch(service.postPush({ ...obj, msg: '新增推送配置' })).then((res) => {//(PEP)
if (res.success) { if (res.success) {
close(); close();
} }
@ -67,60 +363,101 @@ function pushModal (props) {
return ( return (
<> <>
<Modal <Modal
title="管理员设置" title={pushEdit ? "修改推送策略" : '新增推送策略'}
okText="确定" okText="确定"
cancelText="取消" cancelText="取消"
visible={visible} visible={visible}
onOk={handleOk} onOk={handleOk}
width={607} width={860}
onCancel={handleCancel} onCancel={handleCancel}
> >
<div style={{ margin: "0px 25px" }}> <div>
<div style={{ width: '100%', height: 32, background: '#F4F8FF', borderRadius: 2, border: '1px solid #C7E1FF', display: 'flex', alignItems: 'center' }}>
<div style={{ display: 'flex', alignItems: 'center', marginLeft: 12 }}><IconAlertCircle style={{ color: '#0F7EFB' }} /></div>
<div style={{ color: '#0F7EFB', fontSize: 12, marginLeft: 8 }}>成员成为管理员后拥有平台所有权限和项目成员的普通角色会被禁用</div>
</div>
<Form <Form
allowEmpty allowEmpty
labelPosition="left" labelPosition="left"
labelAlign="right" labelAlign="right"
labelWidth="90px" labelWidth="120px"
onValueChange={(values, field) => { onValueChange={(values, field) => {
console.log('values', values);
for (var key in field) { for (var key in field) {
if (key == 'department') { if (key == 'tactics') {
if (values.department >= 0) { if (values.tactics == 'abnormal_rate') {
let departmentList = [] form.current.setValue('alarmType', undefined)
for (let i = 0; i < pepList.length; i++) { setAbnormal(true)
if (pepList[i].id == values.department) {
departmentList = pepList[i].users
}
}
setPeopleList(departmentList)
setDisablePeople(false)
form.current.setValue('pepUserId', undefined);
} }
else { else {
setPeopleList([]) setAbnormal(false)
setDisablePeople(true) }
form.current.setValue('pepUserId', undefined); }
if (key == 'pomsProjectId') {
getProjectStructureList(values.pomsProjectId)//
for (let i = 0; i < projectPoms.length; i++) {
if (values.pomsProjectId == projectPoms[i].id) {
if (projectPoms[i].pepProjectId) {
getProjectStatusList()//
}
else {
setProjectStatus([{ construction_status: 'POMS', id: 'POMS' }])
form.current.setValue('timeType', ['POMS'])
form.current.validate()
}
}
} }
} }
} }
}} }}
getFormApi={(formApi) => (form.current = formApi)} getFormApi={(formApi) => (form.current = formApi)}
> >
<div style={{ color: '#4A4A4A', fontSize: 14, fontWeight: 600, marginLeft: 6 }}>
项目信息配置
</div>
<div>
<Form.Input
field="name"
label='策略名称:'
style={{ width: 695 }}
initValue={editObj?.name || ""}
placeholder="请输入策略名称"
showClear
rules={[{ required: true, message: "请输入策略名称" }]} />
</div>
<div>
<Form.Select
label="请选择项目:"
field="pomsProjectId"
placeholder="请选择项目"
style={{ width: 695 }}
rules={[{ required: true, message: "请选择项目" }]}
initValue={editObj?.pomsProjectId || ""}
filter
>
{
projectPoms.map((item, index) => {
return (
<Form.Select.Option key={index} value={item.id}>
{item.pepProjectName || item.name}
</Form.Select.Option>
)
})
}
</Form.Select>
</div>
<div> <div>
<Form.Select <Form.Select
label="选择部门:" label="请选择结构物:"
field="department" field="strucId"
placeholder="请选择部门" placeholder="请选择结构物"
style={{ width: 417 }} style={{ width: 695 }}
rules={[{ required: true, message: "请选择部门" }]} rules={[{ required: true, message: "请选择结构物" }]}
initValue={departmentId || ""} initValue={editObj?.strucId || []}
disabled={structure}
filter
multiple
maxTagCount={3}
showClear showClear
> >
{ {
pepList.map((item, index) => { projectStructure.map((item, index) => {
return ( return (
<Form.Select.Option key={index} value={item.id}> <Form.Select.Option key={index} value={item.id}>
{item.name} {item.name}
@ -130,21 +467,137 @@ function pushModal (props) {
} }
</Form.Select> </Form.Select>
</div> </div>
<div className='pushInput'>
<Form.RadioGroup
field="tactics"
label='推送策略配置:'
type='card'
direction='horizontal'
initValue={editObj?.tactics || ''}
rules={[{ required: true, message: '请选择推送策略' }]}>
<Form.Radio
value='immediately'
extra={
<span style={{ fontSize: 13 }}>
发现在
<Form.Input
field="interval1"
pure
style={{ width: 60, height: 20, color: '#1859C1' }}
initValue={interval1 || "10"}
// rules={[{ pattern: "^[0-9]*[1-9][0-9]*$", message: "" }]}
/>
分钟内有告警源新增则通过信鸽服务发送一条通知信息
</span>
}
style={{ width: 198 }}>
即时推送机制
</Form.Radio>
<Form.Radio
value='continue'
extra={
<span style={{ fontSize: 13 }}>
告警源持续产生时间超过
<Form.Input
field="interval2"
pure
style={{ width: 60, height: 20, color: '#1859C1' }}
initValue={interval2 || "10"}
// rules={[{ pattern: "^[0-9]*[1-9][0-9]*$", message: "" }]}
/>
分钟则通过信鸽服务发送一条通知信息
</span>
}
style={{ width: 198 }}>
持续时长推送机制
</Form.Radio>
<Form.Radio
value='abnormal_rate'
extra={
<span style={{ fontSize: 13 }}>
异常设备数量达到项目或结构物内设备总数量的
<Form.Input
field="deviceProportion"
pure
style={{ width: 60, height: 20, color: '#1859C1' }}
initValue={deviceProportion || "40"}
// rules={[{ pattern: "^[0-9]*[1-9][0-9]*$", message: "" }]}
/>
%且持续时长超过
<Form.Input
field="interval3"
pure
style={{ width: 60, height: 20, color: '#1859C1' }}
initValue={interval3 || "2"}
// rules={[{ pattern: "^[0-9]*[1-9][0-9]*$", message: "" }]}
/>
小时则通过信鸽服务发送一条通知信息
</span>
}
style={{ width: 260 }}>
异常率推送机制
</Form.Radio>
</Form.RadioGroup>
</div>
<div> <div>
<Form.CheckboxGroup
label="监听问题模块:"
field="alarmType"
style={{ width: 695 }}
rules={[{ required: true, message: "监听问题模块" }]}
initValue={editObj?.alarmType || []}
direction='horizontal'
showClear
>
<Form.Checkbox value="data_outages">数据中断</Form.Checkbox>
<Form.Checkbox value="data_exception">数据异常</Form.Checkbox>
<Form.Checkbox value="strategy_hit" disabled={abnormal}>策略命中</Form.Checkbox>
<Form.Checkbox value="video_exception">视频异常</Form.Checkbox>
<Form.Checkbox value="app_exception" disabled={abnormal}>应用异常</Form.Checkbox>
<Form.Checkbox value="device_exception">设备异常</Form.Checkbox>
</Form.CheckboxGroup>
</div>
<div style={{ color: '#4A4A4A', fontSize: 14, fontWeight: 600, marginLeft: 6 }}>
接收信息配置
</div>
<div style={{ display: 'flex' }}>
<Form.Select <Form.Select
label="选择人员:" label="通知时效:"
field="pepUserId" field="timeType"
placeholder="请选择人员" placeholder="请选择通知时效"
style={{ width: 417 }} style={{ width: 285 }}
rules={[{ required: true, message: "请选择人员" }]} rules={[{ required: true, message: "请选择通知时效" }]}
initValue={peopleId || ""} initValue={editObj?.timeType?.length > 0 ? editObj?.timeType : timeTypePOMS.current}
disabled={timeTypeDis}
multiple
maxTagCount={3}
>
{
projectStatus.map((item, index) => {
return (
<Form.Select.Option key={index} value={item.id}>
{item.construction_status}
</Form.Select.Option>
)
})
}
</Form.Select>
<Form.Select
label="接收人:"
field="receiverPepUserId"
placeholder="请选择接收人"
style={{ width: 285 }}
rules={[{ required: true, message: "请选择接收人" }]}
initValue={editObj?.receiverPepUserId || [user.id]}
filter
multiple
maxTagCount={3}
showClear showClear
disabled={disablePeople}
> >
{ {
peopleList.map((item, index) => { usersList.map((item, index) => {
return ( return (
<Form.Select.Option key={item.id} value={item.id}> <Form.Select.Option key={index} value={item.id}>
{item.name} {item.name}
</Form.Select.Option> </Form.Select.Option>
) )
@ -152,9 +605,29 @@ function pushModal (props) {
} }
</Form.Select> </Form.Select>
</div> </div>
<div style={{ marginLeft: 120, color: '#005ABD', fontSize: 14 }}>
不在项目节点的通知策略会自动失效.
</div>
<div style={{ marginTop: 20 }}>
<Form.RadioGroup
field="disable"
pure
direction='horizontal'
style={{ display: 'flex', justifyContent: 'space-evenly' }}
initValue={editObj?.disable || false}
rules={[{ required: true, }]}>
<Form.Radio value={false}>
启用
</Form.Radio>
<Form.Radio value={true}
style={{ marginLeft: 120 }}>
禁用
</Form.Radio>
</Form.RadioGroup>
</div>
</Form> </Form>
</div> </div>
</Modal> </Modal >
</> </>
); );
} }

10
web/client/src/sections/service/components/pushModal.less

@ -0,0 +1,10 @@
.pushInput{
.semi-input-wrapper-default{
height: 20px !important;
line-height: 20px !important;
}
.semi-input-default{
height: 20px !important;
line-height: 20px !important;
}
}

727
web/client/src/sections/service/containers/emPush.jsx

@ -1,9 +1,10 @@
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Skeleton, Button, Pagination, Form, Popconfirm, Table } from '@douyinfe/semi-ui'; import { Skeleton, Button, Pagination, Form, Popconfirm, Table, Tooltip } from '@douyinfe/semi-ui';
import { IconSearch } from '@douyinfe/semi-icons';
import { SkeletonScreen, } from "$components"; import { SkeletonScreen, } from "$components";
import moment from "moment"; import moment from "moment";
import pushModal from '../components/pushModal' import PushModal from '../components/pushModal'
import '../style.less' import '../style.less'
import { Setup } from "$components"; import { Setup } from "$components";
// import { set } from 'nprogress'; // import { set } from 'nprogress';
@ -18,9 +19,8 @@ const EmPush = (props) => {
const [query, setQuery] = useState({ limit: 10, page: 0 }); // const [query, setQuery] = useState({ limit: 10, page: 0 }); //
const [limits, setLimits] = useState()// const [limits, setLimits] = useState()//
const mylimits = useRef(); // const mylimits = useRef(); //
const [selected, setSelected] = useState([]) //
const [pushModal, setPushModal] = useState(false) // const [pushModal, setPushModal] = useState(false) //
const [systemEdit, setSystemEdit] = useState(false) // const [pushEdit, setPushEdit] = useState(false) //
const [anxincloudList, setAnxincloudList] = useState([]) // const [anxincloudList, setAnxincloudList] = useState([]) //
const [peplist, setPeplist] = useState([]) //PEP const [peplist, setPeplist] = useState([]) //PEP
const [appList, setAppList] = useState([]) // const [appList, setAppList] = useState([]) //
@ -31,20 +31,40 @@ const EmPush = (props) => {
const [appArr, setAppArr] = useState([]) // const [appArr, setAppArr] = useState([]) //
const [bindId, setBindId] = useState() //id const [bindId, setBindId] = useState() //id
const [tableKey, setTableKey] = useState([]) //id const [tableKey, setTableKey] = useState([]) //id
const [editObj, setEditObj] = useState({});//
const [projectStatus, setProjectStatus] = useState([]); //
const page = useRef(query.page);// const page = useRef(query.page);//
const EMPUSH = "empush"; const EMPUSH = "empush";
const tableList = [// const tableList = [//
{ {
title: '推送信息', title: '推送信息',
list: [ list: [
{ name: "策略类型", value: "pushWay" }, { name: "关联项目", value: "projectName" },
{ name: "推送机制", value: "noticeWay" }, { name: "策略名称", value: "name" },
{ name: "监听设备数量", value: "monitorCount" }, { name: "创建时间", value: "createTime" },
{ name: "累计推送次数", value: "logCount" }, { name: "接收人", value: "receiverPepUser" },
] { name: "推送方式", value: "pushType" },
}, { name: "监听问题模块", value: "alarmType" },
]; { name: "生效项目节点", value: "timeType" },
{ name: "推送机制", value: "tactics" },
{ name: "启用状态", value: "disable" },
{ name: "推送次数", value: "pushCount" },
]
},
];
const alarmTypeObj = {
data_outages: '数据中断',
data_exception: '数据异常',
strategy_hit: '策略命中',
video_exception: '视频异常',
app_exception: '应用异常',
device_exception: '设备异常',
}
const tacticsObj = {
immediately: '即时推送机制',
continue: '持续时长推送机制',
abnormal_rate: '异常率推送机制',
}
function handleRow (record, index) {// function handleRow (record, index) {//
@ -59,15 +79,13 @@ const EmPush = (props) => {
return {}; return {};
} }
} }
const [tableData, setTableData] = useState([]) // const [tableData, setTableData] = useState([]) //
useEffect(() => { useEffect(() => {
attribute(); // dispatch(service.getPush(query)).then((res) => {//
dispatch(service.getPush(query)).then((res) => {// // console.log('res.payload.datares.payload.data',res.payload.data);
// console.log('res.payload.datares.payload.data',res.payload.data); // setAnxincloudList(res.payload.data)
// setAnxincloudList(res.payload.data) // })
})
// dispatch(install.getProjectPmanage(query)).then((res) => {//PEP // dispatch(install.getProjectPmanage(query)).then((res) => {//PEP
// setPeplist(res.payload.data) // setPeplist(res.payload.data)
// }) // })
@ -75,52 +93,44 @@ const EmPush = (props) => {
// setAppList(res.payload.data) // setAppList(res.payload.data)
// }) // })
localStorage.getItem(EMPUSH) == null localStorage.getItem(EMPUSH) == null
? localStorage.setItem( ? localStorage.setItem(
EMPUSH, EMPUSH,
JSON.stringify(['pushWay','noticeWay','logCount','monitorCount']) JSON.stringify(['projectName', 'name', 'createTime', 'receiverPepUser', 'alarmType', 'timeType', 'tactics', 'disable'])
) )
: ""; : "";
getProjectStatusList()
}, []) }, [])
useEffect(() => { useEffect(() => {
// getProjectPomsList(); getPushList();
}, [query]); }, [query]);
function getProjectPomsList () { function getPushList () {
// dispatch(install.getProjectPoms(query)).then((res) => {// let val = form.current.getValues()
// if (res.success) { // , ...query
// let mytableData = JSON.parse(JSON.stringify(res.payload.data.rows)); dispatch(service.getPush({ ...val })).then((res) => {//
// let mytableKey = [] if (res.success) {
// for (let index = 0; index < mytableData.length; index++) { let mytableData = JSON.parse(JSON.stringify(res.payload.data));
// mytableData[index].key = mytableData[index].id for (let index = 0; index < mytableData.length; index++) {
// mytableKey.push(mytableData[index].id) mytableData[index].key = String(mytableData[index].id)
// } }
// setTableKey(mytableKey) setTableData(mytableData)
// setTableData(mytableData) setLimits(res.payload.data.length)
// setLimits(res.payload.data.count) mylimits.current = res.payload.data.length
// mylimits.current = res.payload.data.rows.length }
// } })
// })
} }
const [columns, setColumns] = useState([// function getProjectStatusList () {//
{ dispatch(service.getProjectStatus()).then((res) => {
title: "策略编号", if (res.success) {
dataIndex: "index", setProjectStatus(res.payload?.data)
key: 'index', attribute(res.payload?.data);
render: (text, r, index) => {
return index + 1;
},
},
{
title: '策略名称',
dataIndex: "pepProjectName",
key: 'pepProjectName',
render: (_, row) => {
return row.pepProjectName
} }
}, })
}
const columns = [//
{ {
title: "操作", title: "操作",
width: "20%", width: "12%",
dataIndex: "text", dataIndex: "text",
key: 'text', key: 'text',
render: (_, row) => { render: (_, row) => {
@ -129,54 +139,81 @@ const EmPush = (props) => {
<Button <Button
theme="borderless" theme="borderless"
// disabled={row.pepProjectIsDelete == 1} // disabled={row.pepProjectIsDelete == 1}
// onClick={() => { onClick={() => {
// setPushModal(true); setEditObj(row)
// setPepname(row.name) setPushModal(true);
// if (!row.name) { // setPepname(row.name)
// setPepProjectId(row.pepProjectId) // if (!row.name) {
// } // setPepProjectId(row.pepProjectId)
// else { // }
// setPepProjectId() // else {
// } // setPepProjectId()
// let myanxinArr = [] // }
// let anxinErrorList = [] // let myanxinArr = []
// for (let i = 0; i < row.anxinProject.length; i++) { // let anxinErrorList = []
// if (row.anxinProject[i].projectState !== -1) { // for (let i = 0; i < row.anxinProject.length; i++) {
// myanxinArr.push(row.anxinProject[i].id) // if (row.anxinProject[i].projectState !== -1) {
// } // myanxinArr.push(row.anxinProject[i].id)
// else { // }
// anxinErrorList.push(row.anxinProject[i].name) // else {
// } // anxinErrorList.push(row.anxinProject[i].name)
// } // }
// setAnxinDelete(anxinErrorList) // }
// let myapparr = [] // setAnxinDelete(anxinErrorList)
// let myarrarrList = JSON.parse(JSON.stringify(row.apps)) // let myapparr = []
// for (let j = 0; j < row.apps.length; j++) { // let myarrarrList = JSON.parse(JSON.stringify(row.apps))
// delete myarrarrList[j].projectApp // for (let j = 0; j < row.apps.length; j++) {
// myapparr.push(JSON.stringify(myarrarrList[j])) // delete myarrarrList[j].projectApp
// } // myapparr.push(JSON.stringify(myarrarrList[j]))
// setAppArr(myapparr) // }
// setAnxincloudArr(myanxinArr) // setAppArr(myapparr)
// setBindId(row.id) // setAnxincloudArr(myanxinArr)
// setSystemEdit(true) // setBindId(row.id)
// }} setPushEdit(true)
}}
> >
修改 修改
</Button> </Button>
<Button theme="borderless">禁用</Button> {row?.disable ? (
<Button
theme="borderless"
style={{ color: '#F31C1C' }}
onClick={() => {
dispatch(service.putPushPushId({ pushId: row?.id, del: false, disable: false, msg: '更改推送配置状态' })).then(() => {
// setQuery({ limit: 10, page: page.current })
})
}}
>
已禁用
</Button>
) : (
<Popconfirm
title="禁用后,通知策略将会失效。"
arrowPointAtCenter={false}
showArrow={true}
position="topRight"
onConfirm={() => {
dispatch(service.putPushPushId({ pushId: row?.id, del: false, disable: true, msg: '更改推送配置状态' })).then(() => {
// setQuery({ limit: 10, page: page.current })
})
}}
>
<Button theme="borderless">已启用</Button>
</Popconfirm>
)}
<Popconfirm <Popconfirm
title="删除后对应的项目全局将无法进入和显示,对应的信鸽服务也会被禁用" title="删除后通知策略将会失效。"
arrowPointAtCenter={false} arrowPointAtCenter={false}
showArrow={true} showArrow={true}
position="topRight" position="topRight"
onConfirm={() => { onConfirm={() => {
// dispatch(install.deleteProjectBind({ bindId: row?.id, msg: '' })).then(() => { dispatch(service.putPushPushId({ pushId: row?.id, del: true, disable: false, msg: '删除推送配置' })).then(() => {
// if (page.current > 0 && mylimits.current < 2) { // if (page.current > 0 && mylimits.current < 2) {
// setQuery({ limit: 10, page: page.current - 1 }) // setQuery({ limit: 10, page: page.current - 1 })
// } else { // } else {
// setQuery({ limit: 10, page: page.current }) // setQuery({ limit: 10, page: page.current })
// } // }
// }) })
}} }}
> >
<Button theme="borderless">删除</Button> <Button theme="borderless">删除</Button>
@ -185,74 +222,338 @@ const EmPush = (props) => {
); );
}, },
}, },
]) ]
const rowSelection = { function expandRowRender (record, index) {
selectedRowKeys: selected, return (
onChange: (selectedRowKeys, selectedRows) => { <div style={{ display: 'flex' }}>
setSelected(selectedRows.map(v => v.key)) 结构物
}, {
record.structure?.map((item, index) => {
return (
<div key={index} style={{ marginRight: 5, padding: '1px 17px', color: '#0F7EFB', background: 'rgba(221,237,255,0.38)' }}>
{item.name}
</div>
)
})
}
</div>
)
} }
// //
function attribute () { function attribute (val) {
const arr = localStorage.getItem(EMPUSH) const arr = localStorage.getItem(EMPUSH)
? JSON.parse(localStorage.getItem(EMPUSH)) ? JSON.parse(localStorage.getItem(EMPUSH))
: []; : [];
const column = [ const column = [
{ {
title: "关联项目", title: '关联项目',
dataIndex: "noticeWay", dataIndex: "projectName",
key: "noticeWay", key: "projectName",
render: (_, r, index) => { render: (_, row) => {
return r.noticeWay; let anxinerror = false
}, let anxinerrorArr = ''
if (row.pomsProject.del == true) {
anxinerror = true
anxinerrorArr = row.pomsProject.pepProject?.projectName || row.pomsProject.name
}
return (
<div style={{ display: 'flex', alignItems: 'center' }}>
{
anxinerror ? (
<Tooltip content={anxinerrorArr + ',项目已在【项企PEP】或【映射关系】中被删除,请重选项目!'}>
<div style={{ marginRight: 5 }}>
<img src="/assets/images/install/risk.png" alt="" style={{ height: 24, width: 24, }} />
</div>
</Tooltip>) : ('')
}
{
<div className='myseparator' style={{ display: 'flex', alignItems: 'center' }}>
<Tooltip content={row.pomsProject.pepProject?.projectName || row.pomsProject.name}>
<div style={{ width: row.pomsProject?.pepProject?.projectName?.length > 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}
</div>
</Tooltip>
</div>
}
{
row.pomsProject?.pepProject?.projectName ? (
<div style={{
height: 18, marginLeft: 4,
background: 'linear-gradient(180deg, #EBF5FF 0%, #EBF5FF 0%, #D3E8FF 100%)',
borderRadius: 2, display: 'flex', alignItems: 'center'
}}>
<div>
<img src="/assets/images/install/icon_zhengque.png" alt="" style={{ height: 10, width: 10, marginLeft: 4, marginRight: 9 }} />
</div>
<div style={{ color: '#0F7EFB', fontSize: 11, marginRight: 12 }}>
{
val.map((ite, idx) => {
return (
<div key={idx}>
{ite.id == row.pomsProject?.pepProject.constructionStatusId ? ite.construction_status : ''}
</div>
)
})
}
</div>
</div>
) : (
<div style={{
height: 18, marginLeft: 4,
background: 'linear-gradient(180deg, #99C7DD 0%, #3048FC 100%)',
borderRadius: 2, display: 'flex', alignItems: 'center'
}}>
<div>
<img src="/assets/images/install/icon_POMS.png" alt="" style={{ height: 10, width: 10, marginLeft: 4, marginRight: 9 }} />
</div>
<div style={{ color: '#FFFFFF', fontSize: 11, marginRight: 12 }}>
POMS
</div>
</div>
)
}
</div>
)
}
},
{
title: '策略名称',
dataIndex: "name",
key: 'name',
render: (_, row) => {
return row.name
}
}, },
{ {
title: "创建时间", title: "创建时间",
dataIndex: "logCount", dataIndex: "createTime",
key: "logCount", key: "createTime",
render: (_, r, index) => { render: (_, r, index) => {
return (r.logCount + '次') return moment(r.createTime).format('YYYY-MM-DD HH:mm:ss');
}, },
}, },
{ {
title: "接收人", title: '接收人',
dataIndex: "monitorCount", dataIndex: "receiverPepUser",
key: "monitorCount", key: 'receiverPepUser',
render: (_, row) => {
return (
<div style={{ display: 'flex', alignItems: 'center' }}>
{
row.receiverPepUser.map((item, index) => {
return (
<div className='myseparator' key={index} style={{ display: 'flex', alignItems: 'center' }}>
<div style={{ display: index > 1 ? 'none' : '', color: '#005ABD' }}>
{item.name}
</div>
<div className='separator' style={{ width: 1, height: 12, border: '1px solid #DCDEE0', margin: '0px 10px', display: index > 0 ? 'none' : '' }}></div>
</div>
)
})
}
{
row.receiverPepUser.length > 2 ? (
<Tooltip content={
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
{
row.receiverPepUser.map((item, index) => {
return (
<div key={index}>
{item.name},
</div>
)
})
}
</div>
} trigger="click" style={{ lineHeight: 2 }}>
<div style={{ fontSize: 14, color: '#005ABD', marginLeft: 8, cursor: "pointer", }}>
+{row.receiverPepUser.length - 2}
</div>
</Tooltip>
) : ('')
}
</div>
)
}
},
{
title: "推送方式",
dataIndex: "pushType",
key: "pushType",
render: (_, r, index) => { render: (_, r, index) => {
return r.monitorCount return '邮件通知';
}, },
}, },
{ {
title: "监听问题", title: "监听问题模块",
dataIndex: "pushWay", dataIndex: "alarmType",
key: "pushWay", key: "alarmType",
render: (_, r, index) => { render: (_, row) => {
return r.pushWay=='email' ? '邮件通知' : '短信通知'; return (
<div style={{ display: 'flex', alignItems: 'center' }}>
{
row.alarmType.map((item, index) => {
return (
<div className='myseparator' key={index} style={{ display: 'flex', alignItems: 'center' }}>
<div style={{ display: index > 1 ? 'none' : '' }}>
{alarmTypeObj[item]}
</div>
<div className='separator' style={{ width: 1, height: 12, border: '1px solid #DCDEE0', margin: '0px 10px', display: index > 0 ? 'none' : '' }}></div>
</div>
)
})
}
{
row.alarmType.length > 2 ? (
<Tooltip content={
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
{
row.alarmType.map((item, index) => {
return (
<div key={index} >
{alarmTypeObj[item]},
</div>
)
})
}
</div>
} trigger="click" style={{ lineHeight: 2 }}>
<div style={{ fontSize: 14, color: '#005ABD', marginLeft: 8, cursor: "pointer", }}>
+{row.alarmType.length - 2}
</div>
</Tooltip>
) : ('')
}
</div>
)
}
},
{
title: "生效项目节点",
dataIndex: "timeType",
key: "timeType",
render: (_, row, index) => {
return (
<div style={{ display: 'flex', alignItems: 'center' }}>
{
row.timeType.length > 0 ? (
row.timeType.map((item, index) => {
return (
<div key={index} style={{
height: 18, marginLeft: 4,
background: 'linear-gradient(180deg, #EBF5FF 0%, #EBF5FF 0%, #D3E8FF 100%)',
borderRadius: 2, display: index > 1 ? 'none' : 'flex', alignItems: 'center'
}}>
<div>
<img src="/assets/images/install/icon_zhengque.png" alt="" style={{ height: 10, width: 10, marginLeft: 4, marginRight: 9 }} />
</div>
<div style={{ color: '#0F7EFB', fontSize: 11, marginRight: 12 }}>
{
val.map((ite, idx) => {
return (
<div key={idx}>
{ite.id == item ? ite.construction_status : ''}
</div>
)
})
}
</div>
</div>
)
})
) : (
<div style={{
height: 18, marginLeft: 4,
background: 'linear-gradient(180deg, #99C7DD 0%, #3048FC 100%)',
borderRadius: 2, display: 'flex', alignItems: 'center'
}}>
<div>
<img src="/assets/images/install/icon_POMS.png" alt="" style={{ height: 10, width: 10, marginLeft: 4, marginRight: 9 }} />
</div>
<div style={{ color: '#FFFFFF', fontSize: 11, marginRight: 12 }}>
POMS
</div>
</div>
)
}
{
row.timeType.length > 2 ? (
<Tooltip content={
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
{
row.timeType.map((item, index) => {
return (
<div key={index}>
{
val.map((ite, idx) => {
return (
<span key={idx}>
{ite.id == item ? ite.construction_status : ''}
</span>
)
})
},
</div>
)
})
}
</div>
} trigger="click" style={{ lineHeight: 2 }}>
<div style={{ fontSize: 14, color: '#005ABD', marginLeft: 8, cursor: "pointer", }}>
+{row.timeType.length - 2}
</div>
</Tooltip>
) : ('')
}
</div>
)
}, },
}, },
{ {
title: "通知时效", title: "推送机制",
dataIndex: "text1", dataIndex: "tactics",
key: "text1", key: "tactics",
render: (_, r, index) => { render: (_, r, index) => {
return r.text1 return tacticsObj[r.tactics]
}, },
}, },
{ {
title: "启用状态", title: "启用状态",
dataIndex: "text2", dataIndex: "disable",
key: "text2", key: "disable",
render: (_, r, index) => { render: (_, row, index) => {
return r.text2 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 (
<div style={{ textAlign: 'center', padding: '1px 17px', color: enableType == '禁用' ? '#FB0F0F' : enableType == '已生效' ? '#0F7EFB' : '#646566', background: enableType == '禁用' ? 'rgba(255,221,221,0.38)' : enableType == '已生效' ? 'rgba(221,237,255,0.38)' : 'rgba(192,192,192,0.38)', }}>
{enableType}
</div>
)
}, },
}, },
{ {
title: "推送次数", title: "推送次数",
dataIndex: "time", dataIndex: "pushCount",
key: "time", key: "pushCount",
render: (_, r, index) => { render: (_, r, index) => {
return r.time return r.pushCount + '次'
}, },
}, },
]; ];
@ -260,7 +561,7 @@ const EmPush = (props) => {
let colum = column.filter((item) => { let colum = column.filter((item) => {
return item.key === arr[i]; return item.key === arr[i];
}); });
columns.splice(i + 2, 0, colum[0]); columns.splice(columns.length - 1, 0, colum[0]);
} }
setSetupp(columns); setSetupp(columns);
} }
@ -273,7 +574,7 @@ const EmPush = (props) => {
<div style={{ fontFamily: "YouSheBiaoTiHei", fontSize: 24, color: '#101531', marginLeft: 8 }}>EM推送</div> <div style={{ fontFamily: "YouSheBiaoTiHei", fontSize: 24, color: '#101531', marginLeft: 8 }}>EM推送</div>
<div style={{ marginLeft: 6, fontSize: 12, color: '#969799', fontFamily: "DINExp", }}>Em push</div> <div style={{ marginLeft: 6, fontSize: 12, color: '#969799', fontFamily: "DINExp", }}>Em push</div>
</div> </div>
<div style={{ marginRight: 20, display: 'flex', alignItems: 'center' }} className='empush'> <div style={{ marginRight: 20, display: 'flex', alignItems: 'center' }} className='myempush'>
<Form <Form
onSubmit={(values) => console.log(values)} onSubmit={(values) => console.log(values)}
// onValueChange={values=>console.log(values)} // onValueChange={values=>console.log(values)}
@ -281,47 +582,53 @@ const EmPush = (props) => {
layout="horizontal" layout="horizontal"
style={{ position: "relative", width: "100%", flex: 1 }} style={{ position: "relative", width: "100%", flex: 1 }}
> >
<Form.Select
pure
field="keywordTarget"
placeholder="请选择搜索类型"
style={{ width: 200 }}
initValue={"pepProject"}
>
<Form.Select.Option value='pepProject'>项目</Form.Select.Option>
<Form.Select.Option value='struc'>结构物</Form.Select.Option>
<Form.Select.Option value='tactics'>策略名</Form.Select.Option>
</Form.Select>
<Form.Input <Form.Input
label='搜索:' suffix={<IconSearch />}
field='seacth' field="keyword"
maxLength="16" pure
placeholder="搜项目、结构物或推送策略名" showClear
labelPosition="left" style={{ width: 260, marginLeft: 12, marginRight: 12 }}
style={{ width: 292, marginRight: 10, marginBottom: 16 }} placeholder="请输入或选择关键词"
/> />
<Form.Select <Form.Select
label='推送类型:' label='推送类型:'
labelPosition="left" labelPosition="left"
field='pushType' field='alarmType'
style={{ width: 116, marginRight: 10, color: "#F9F9F9", }} style={{ width: 116, marginRight: 10, color: "#F9F9F9", }}
placeholder="全部" placeholder="全部"
filter filter
showClear showClear
> >
{/* {.map((item) => { <Form.Select.Option value="data_outages">数据中断</Form.Select.Option>
return ( <Form.Select.Option value="data_exception">数据异常</Form.Select.Option>
<Form.Select.Option key={item.value} value={item.value}> <Form.Select.Option value="strategy_hit">策略命中</Form.Select.Option>
{item.name} <Form.Select.Option value="video_exception">视频异常</Form.Select.Option>
</Form.Select.Option> <Form.Select.Option value="app_exception">应用异常</Form.Select.Option>
); <Form.Select.Option value="device_exception">设备异常</Form.Select.Option>
})} */}
</Form.Select> </Form.Select>
<Form.Select <Form.Select
label='启用状态:' label='启用状态:'
labelPosition="left" labelPosition="left"
field='enableType' field='state'
style={{ width: 116, marginRight: 10, color: "#F9F9F9", }} style={{ width: 116, marginRight: 10, color: "#F9F9F9", }}
placeholder="全部" placeholder="全部"
filter filter
showClear showClear
> >
{/* {.map((item) => { <Form.Select.Option value='takeEffect'>已生效</Form.Select.Option>
return ( <Form.Select.Option value='notYet'>未生效</Form.Select.Option>
<Form.Select.Option key={item.value} value={item.value}> <Form.Select.Option value='disable'>禁用</Form.Select.Option>
{item.name}
</Form.Select.Option>
);
})} */}
</Form.Select> </Form.Select>
</Form> </Form>
<Button <Button
@ -332,15 +639,17 @@ const EmPush = (props) => {
height: 32, height: 32,
borderRadius: 2, borderRadius: 2,
marginRight: 32, marginRight: 32,
background:'#FFFFFF', background: '#FFFFFF',
color:'#005ABD', color: '#005ABD',
border:'1px solid #005ABD' border: '1px solid #005ABD'
}}
onClick={() => {
getPushList()
}} }}
// onClick={() => { }}
> >
查询 查询
</Button> </Button>
<div style={{display: 'flex', alignItems: 'center'}}> <div style={{ display: 'flex', alignItems: 'center' }}>
<img title='设置' src="/assets/images/problem/setup.png" style={{ width: 18, height: 18, cursor: "pointer" }} onClick={() => setSetup(true)} /> <img title='设置' src="/assets/images/problem/setup.png" style={{ width: 18, height: 18, cursor: "pointer" }} onClick={() => setSetup(true)} />
</div> </div>
<Button <Button
@ -353,14 +662,9 @@ const EmPush = (props) => {
marginLeft: 32 marginLeft: 32
}} }}
onClick={() => { onClick={() => {
setEditObj({})
setPushModal(true); setPushModal(true);
setSystemEdit(false) setPushEdit(false);
setPepProjectId()
setPepname()
setAnxinDelete([])
setAppArr([])
setAnxincloudArr()
setBindId()
}} }}
> >
添加推送策略 添加推送策略
@ -368,7 +672,7 @@ const EmPush = (props) => {
</div> </div>
</div> </div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20, marginTop: 5 }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20, marginTop: 5 }}>
<div style={{ fontSize: 12, color: '#8B8B8B' }}>预留预留预留预留预留预留预留预留预留预留预留预留预留预留预留预留预留预留预留预留预留预留</div> <div style={{ fontSize: 12, color: '#8B8B8B' }}>EM推送提供对映射关系组的项目结构物问题的监听和通知服务支持对设备异常率问题持续时间即时响应等策略定义的动态推送</div>
</div> </div>
<div style={{ marginTop: 20 }}> <div style={{ marginTop: 20 }}>
<Skeleton <Skeleton
@ -378,13 +682,15 @@ const EmPush = (props) => {
placeholder={SkeletonScreen()} placeholder={SkeletonScreen()}
> >
<Table <Table
rowKey="name"
columns={setupp.filter((s) => s)} columns={setupp.filter((s) => s)}
dataSource={tableData} dataSource={tableData}
bordered={false} bordered={false}
hideExpandedColumn={false}
empty="暂无数据" empty="暂无数据"
pagination={false} expandedRowRender={expandRowRender}
// pagination={false}
onRow={handleRow} onRow={handleRow}
rowSelection={rowSelection}
/> />
</Skeleton> </Skeleton>
<div <div
@ -395,35 +701,6 @@ const EmPush = (props) => {
}} }}
> >
<div> <div>
<div style={{ display: 'inline-block', lineHeight: '30px', fontSize: 13, color: 'gray' }}>勾选<span style={{ fontWeight: 400, color: '#0F7EFB', }}> {selected.length} </span>信息</div>
<Button onClick={() => {
if (selected.length == tableKey.length) {
setSelected([])
}
else {
setSelected(tableKey)
}
}} style={{ fontSize: 13, width: 93, height: 24, borderRadius: '1px', background: '#FFFFFF', border: '1px solid #0F7EFB', color: '#0F7EFB', fontWeight: 400, margin: '0 10px' }}>
{mylimits.current == selected.length ? '取消全选' : '全选'}
</Button>
<Popconfirm
title="删除后对应的项目全局将无法进入和显示,对应的信鸽服务也会被禁用"
arrowPointAtCenter={false}
showArrow={true}
position="topRight"
onConfirm={() => {
// 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([])
// })
}}
>
<Button type='primary' theme='solid' style={{ fontSize: 13, width: 93, height: 24, borderRadius: '1px', border: '1px solid #0F7EFB', color: '#FFFFFF', fontWeight: 400, }}>批量删除</Button>
</Popconfirm>
</div> </div>
<div style={{ display: 'flex', }}> <div style={{ display: 'flex', }}>
<span style={{ lineHeight: "30px", fontSize: 13, color: 'rgba(0,90,189,0.8)' }}> <span style={{ lineHeight: "30px", fontSize: 13, color: 'rgba(0,90,189,0.8)' }}>
@ -438,49 +715,39 @@ const EmPush = (props) => {
onChange={(currentPage, pageSize) => { onChange={(currentPage, pageSize) => {
setQuery({ limit: pageSize, page: currentPage - 1 }); setQuery({ limit: pageSize, page: currentPage - 1 });
page.current = currentPage - 1 page.current = currentPage - 1
setSelected([])
}} }}
/> />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{/* {//映射关系弹 {//
pushModal ? pushModal ?
<PushModal <PushModal
visible={true} visible={true}
anxincloudList={anxincloudList} pushEdit={pushEdit}
systemEdit={systemEdit} editObj={editObj}
peplist={peplist}
appList={appList}
pepname={pepname}
anxincloudArr={anxincloudArr}
pepProjectId={pepProjectId}
anxinDelete={anxinDelete}
appArr={appArr}
bindId={bindId}
cancel={() => { cancel={() => {
setPushModal(false); setPushModal(false);
}} }}
close={() => { close={() => {
setPushModal(false); setPushModal(false);
getProjectPomsList() getPushList()
}} > }} >
</PushModal> : '' </PushModal> : ''
} */} }
{setup ? ( {setup ? (
<Setup <Setup
tableType={EMPUSH} tableType={EMPUSH}
tableList={tableList} tableList={tableList}
close={() => { close={() => {
setSetup(false); setSetup(false);
attribute(); attribute(projectStatus);
// setcameraSetup(false); }}
}} />
/> ) : (
) : ( ""
"" )}
)}
</> </>
) )
} }

2
web/client/src/sections/service/nav-item.jsx

@ -30,7 +30,7 @@ export function getNavItem (user, dispatch) {
}, { }, {
itemKey: 'carrierPigeon', itemKey: 'carrierPigeon',
text: '信鸽服务', text: '信鸽服务',
icon: <iconpark-icon style={{ width: 20, height: 20 }} name="iconjianshezhong"></iconpark-icon>, icon: <iconpark-icon style={{ width: 20, height: 20 }} name="xingefuwu"></iconpark-icon>,
to: '/service/carrierPigeon/emPush', to: '/service/carrierPigeon/emPush',
items: [{ items: [{
itemKey: 'emPush', to: '/service/carrierPigeon/emPush', text: 'EM推送' itemKey: 'emPush', to: '/service/carrierPigeon/emPush', text: 'EM推送'

2
web/client/src/sections/service/style.less

@ -1,4 +1,4 @@
.empush{ .myempush{
.semi-input-wrapper{ .semi-input-wrapper{
margin-bottom: 0px !important; margin-bottom: 0px !important;
} }

5
web/client/src/utils/webapi.js

@ -50,6 +50,11 @@ export const ApiTable = {
//服务-信鸽服务 //服务-信鸽服务
getPush: "push", //获取推送配置列表 getPush: "push", //获取推送配置列表
postPush: "push", //新增/编辑推送配置
getOrganizationUsers: "organization/users", //获取全部未删除用户
getProjectStructure: "project/structure", //获取绑定项目下结构物
getProjectStatus: "project/status", //获取项目状态列表
putPushPushId: "push/{pushId}", //更改推送配置状态(禁用或删除)
//控制台 //控制台
consoleToollink: 'console/toollink', //常用工具 consoleToollink: 'console/toollink', //常用工具

Loading…
Cancel
Save