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. 24
      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",
"-f http://localhost:4600",
//
// "-g postgres://postgres:123@10.8.30.32:5432/orational_service",
"-g postgres://postgres:123@10.8.30.32:5432/orational_service",
//
"-g postgres://FashionAdmin:123456@10.8.30.156:5432/POMS",
// "-g postgres://FashionAdmin:123456@10.8.30.156:5432/POMS",
"-k node35:6667,node36:6667,node37:6667",
"--iotaProxy http://10.8.30.157:17007",
"--redisHost 10.8.30.112",

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';
const moment = require('moment')
const { alarmConfirmLog } = require('./alarmConfirmLog');
async function inspection(ctx) {
// 巡查
try {
@ -328,7 +328,6 @@ async function confirmApiError(ctx) {
try {
const models = ctx.fs.dc.models;
const { confirm, appAlarmId = [], confirmPost } = ctx.request.body
const { pepUserId, projectCorrelationIds, alarmInfo } = confirmPost
await models.AppAlarm.update({
confirm,
confirmTime: moment().format()
@ -338,29 +337,7 @@ async function confirmApiError(ctx) {
}
})
//存日志
let logDatas = projectCorrelationIds.map(id => {
return {
pepUserId,
projectCorrelationId: id,
alarmInfo,//包含告警id,type,source
confirmTime: moment().format(),
confirmContent: confirm
}
})
let rslt = await models.AlarmConfirmLog.bulkCreate(logDatas, { returning: true });
//存最新动态
let dynamics = rslt.map(r => {
return {
time: r.confirmTime,
alarmConfirmId: r.id,
projectCorrelationId: r.projectCorrelationId,
type: 4//告警确认
}
})
await models.LatestDynamicList.bulkCreate(dynamics);
await alarmConfirmLog(ctx, confirmPost, confirm);//告警确认日志
ctx.status = 204;
} catch (error) {

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

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

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

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

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

@ -1,55 +1,5 @@
'use strict';
const moment = require('moment');
const { alarmList } = require('../alarm/video');
//项目概览
async function getProjectsInfo(ctx) {
try {
const { models } = ctx.fs.dc;
const { clickHouse, utils: { judgeSuper, anxinStrucIdRange } } = ctx.app.fs
const { database: anxinyun } = clickHouse.anxinyun.opts.config
const { alarmId, limit, page, projectCorrelationId, pepProjectId, keywordTarget, keyword } = ctx.query;
const { userInfo } = ctx.fs.api;
// let where = {}
// if (!userInfo.role.includes('SuperAdmin') && !userInfo.role.includes('admin')) {
// where.projectCorrelationId = { $in: userInfo.correlationProject }
// }
// if (projectCorrelationId) {//查指定项目,控制台全局切换
// where.projectCorrelationId = projectCorrelationId
// }
let anxinStruc = await anxinStrucIdRange({
ctx, pepProjectId, keywordTarget, keyword
})
const anxinStrucIds = anxinStruc.map(a => a.strucId);
//先查全部的摄像头
const videoList = anxinStrucIds.length ? await clickHouse.vcmp.query(
`select camera.id,
camera.name,
camera.serial_no from camera where camera.delete=false and camera.recycle_time is null
LEFT JOIN ${anxinyun}.t_video_ipc AS anxinIpc
ON toString(anxinIpc.channel_no) = cameraAlarm.cameraChannelNo
AND anxinIpc.serial_no = cameraAlarm.cameraSerialNo
LEFT JOIN ${anxinyun}.t_structure AS anxinStruc
ON anxinStruc.id = anxinIpc.structure
AND anxinStruc.id IN (${anxinStrucIds.join(',')})
LEFT JOIN ${anxinyun}.t_video_ipc_station AS anxinIpcStation
ON anxinIpcStation.ipc = anxinIpc.id
LEFT JOIN ${anxinyun}.t_sensor AS anxinStation
ON anxinStation.id = anxinIpcStation.station`
).toPromise() : []
ctx.status = 200;
ctx.body = []
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
message: typeof error == 'string' ? error : undefined
}
}
}
//BI分析-数据
async function getDataAlarmsAggDay(ctx) {
@ -143,14 +93,10 @@ async function getAppAlarmsAggDay(ctx) {
attributes: ['id', 'createTime', 'confirmTime'],
include: [{
model: models.App,
attributes: {
exclude: ['projectId']
},
attributes: ['id'],
include: [{
model: models.ProjectCorrelation,
attributes: {
exclude: ['id']
},
attributes: ['id']
}]
}]
}
@ -185,9 +131,99 @@ async function getAppAlarmsAggDay(ctx) {
//BI分析-视频异常
async function getVideoAlarmsAggDay(ctx) {
try {
let videoAlarms = await alarmList(ctx, 'day');
let aggDayMap = [];
const { clickHouse, utils: { anxinStrucIdRange } } = ctx.app.fs
const { database: anxinyun } = clickHouse.anxinyun.opts.config
const { pepProjectId } = ctx.query
let anxinStruc = await anxinStrucIdRange({
ctx, pepProjectId
})
const anxinStrucIds = anxinStruc.map(a => a.strucId)
let statusAlarmWhereOption = []
let start = moment().add(-1, 'year').format('YYYY-MM-DD HH:mm:ss');//最近一年
statusAlarmWhereOption.push(`camera_status_alarm.create_time >= '${start}'`)
const videoAlarms = anxinStrucIds.length ? await clickHouse.vcmp.query(
`SELECT
cameraAlarm.cameraId AS cameraId,
cameraAlarm.alarmId AS alarmId,
cameraAlarm.createTime AS createTime,
cameraAlarm.confirmTime AS confirmTime,
${'cameraAlarm.autoRestore AS autoRestore,'}
anxinStruc.id AS strucId
FROM
(
SELECT
camera.id AS cameraId,
camera_status_alarm.id AS alarmId,
camera_status_alarm.create_time AS createTime,
camera_status_alarm.platform AS platform,
camera_status_alarm.status_id AS statusId,
camera_status_alarm.serial_no AS cameraSerialNo,
camera_status_alarm.channel_no AS cameraChannelNo,
${'camera_status_alarm.auto_restore AS autoRestore,'}
camera_status_alarm.confirm_time AS confirmTime
FROM camera_status_alarm
INNER JOIN camera
ON camera.serial_no = camera_status_alarm.serial_no
AND camera.channel_no = camera_status_alarm.channel_no
WHERE
camera.delete = false
AND camera.recycle_time is null
${statusAlarmWhereOption.length ? 'AND ' + statusAlarmWhereOption.join(' AND ') : ''}
AND alarmId IN (
SELECT camera_status_alarm.id AS alarmId
FROM camera_status_alarm
RIGHT JOIN ${anxinyun}.t_video_ipc
ON toString(${anxinyun}.t_video_ipc.channel_no) = camera_status_alarm.channel_no
AND ${anxinyun}.t_video_ipc.serial_no = camera_status_alarm.serial_no
${`WHERE ${anxinyun}.t_video_ipc.structure IN (${anxinStrucIds.join(',')})`}
)
) AS cameraAlarm
LEFT JOIN camera_status
ON cameraAlarm.platform = camera_status.platform
AND cameraAlarm.statusId = camera_status.id
LEFT JOIN camera_status_resolve
ON camera_status_resolve.status_id = camera_status.id
LEFT JOIN ${anxinyun}.t_video_ipc AS anxinIpc
ON toString(anxinIpc.channel_no) = cameraAlarm.cameraChannelNo
AND anxinIpc.serial_no = cameraAlarm.cameraSerialNo
LEFT JOIN ${anxinyun}.t_structure AS anxinStruc
ON anxinStruc.id = anxinIpc.structure
AND anxinStruc.id IN (${anxinStrucIds.join(',')})
LEFT JOIN ${anxinyun}.t_video_ipc_station AS anxinIpcStation
ON anxinIpcStation.ipc = anxinIpc.id
`).toPromise() : []
let returnD = []
let positionD = {}
// 每个设备一个告警
for (let a of videoAlarms) {
if (positionD[a.cameraId]) {
} else {
let d = {
cameraId: a.cameraId,
autoRestore: a.autoRestore,
createTime: a.createTime,
alarmId: a.alarmId,
confirmTime: a.confirmTime,
}
d.pomsProject = (
anxinStruc.find(as => as.strucId == a.strucId) ||
{
pomsProject: [
]
}
).pomsProject.map(d => d.id)
returnD.push(d)
positionD[a.cameraId] = {
positionReturnD: returnD.length - 1
}
}
}
let aggDayMap = [];
for (let a of returnD) {
let exist = aggDayMap.find(ad => ad.day == moment(a.createTime).format('YYYY-MM-DD'));
if (exist) {
exist.total++;//总数
@ -209,6 +245,26 @@ async function getVideoAlarmsAggDay(ctx) {
}
}
//BI分析-问题处置效率分析
async function getAlarmsHandleStatistics(ctx) {
try {
const { projectCorrelationId } = ctx.query
const models = ctx.fs.dc.models;
const data = await models.AlarmHandleStatistics.findAll({
order: [['time', 'DESC']],
where: projectCorrelationId ? { projectCorrelationId: projectCorrelationId } : {},
limit: 1
})
ctx.status = 200;
ctx.body = data;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
message: typeof error == 'string' ? error : undefined
}
}
}
//最新动态
async function getLatestDynamic(ctx) {
try {
@ -232,7 +288,11 @@ async function getLatestDynamic(ctx) {
}, {
model: models.AlarmAppearRecord
}, {
model: models.EmailSendLog
model: models.EmailSendLog,
include: [{
model: models.AlarmPushConfig,
attributes: ['id', 'name'],
}]
}, {
model: models.AlarmConfirmLog
}],
@ -248,7 +308,9 @@ async function getLatestDynamic(ctx) {
pepPojectIds.add(p.projectCorrelation.pepProjectId);
if (p.emailSendLog) {
notedUserIds.add(p.emailSendLog.toPepUserId);//通知 接收人
p.emailSendLog.toPepUserIds.map(u => {
notedUserIds.add(u);//通知 接收人
})
}
if (p.alarmConfirmLog && p.alarmConfirmLog.pepUserId) {
notedUserIds.add(p.alarmConfirmLog.pepUserId);//确认 操作者
@ -269,14 +331,14 @@ async function getLatestDynamic(ctx) {
if (d.alarmAppearId) {
appear.push({
projectName,
...d.alarmAppearRecord
...d.alarmAppearRecord.dataValues
});
}
if (d.emailSendId) {
notice.push({
userName: userPepRes.find(u => u.id == d.emailSendLog.toPepUserId).name,
userName: userPepRes.find(u => d.emailSendLog.toPepUserIds.indexOf(u.id) != -1),
projectName,
...d.emailSendLog
...d.emailSendLog.dataValues
});
}
if (d.alarmConfirmId) {
@ -303,10 +365,10 @@ async function getLatestDynamic(ctx) {
}
module.exports = {
getProjectsInfo,
getDataAlarmsAggDay,
getAppAlarmsAggDay,
getVideoAlarmsAggDay,
getAlarmsHandleStatistics,
getLatestDynamic
};

24
api/app/lib/controllers/organization/index.js

@ -22,6 +22,27 @@ async function allDeps (ctx) {
}
}
async function allUsers (ctx) {
try {
const { models } = ctx.fs.dc;
const { clickHouse } = ctx.app.fs
const userRes = await clickHouse.pepEmis.query(`
SELECT id, name FROM user
WHERE delete = false
`).toPromise()
ctx.status = 200;
ctx.body = userRes
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: error`);
ctx.status = 400;
ctx.body = {
message: typeof error == 'string' ? error : undefined
}
}
}
async function editUser (ctx) {
try {
const models = ctx.fs.dc.models;
@ -183,7 +204,7 @@ async function user (ctx) {
$not: {
role: { $contained: ['SuperAdmin', 'admin'] }
}
},
},
sequelize.where(sequelize.fn('cardinality', sequelize.col('role')), 0)],
// $not: {
// role: { $contained: ['SuperAdmin', 'admin'] }
@ -311,4 +332,5 @@ module.exports = {
putUser,
delAdmin,
user,
allUsers,
};

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

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

@ -4,38 +4,152 @@ const moment = require('moment')
async function list (ctx) {
try {
const models = ctx.fs.dc.models;
const { keyword, alarmType, state, } = ctx.request.body
const sequelize = ctx.fs.dc.ORM;
const { clickHouse } = ctx.app.fs
const { utils: { anxinStrucIdRange, pomsProjectRange } } = ctx.app.fs
const { keyword, keywordTarget, alarmType, state, tactics, pomsProjectId } = ctx.query
let projectCorrelationWhere = {
del: false,
}
if (state == 'notYet') {
projectCorrelationWhere.pepProjectId = { $ne: null }
}
let findOption = {
where: {
del: false
},
include: [{
model: models.ProjectCorrelation,
where: projectCorrelationWhere,
required: true
}]
}
let anxinStrucsRange = await anxinStrucIdRange({
ctx, pepProjectId: pomsProjectId, keywordTarget, keyword
})
if (keywordTarget && keyword) {
if (keywordTarget == 'tactics') {
findOption.where.name = { $like: `%${keyword}%` }
} else if (keywordTarget == 'struc') {
let bindAnixinStrucRes = await clickHouse.projectManage.query(`
SELECT id, name FROM t_structure
WHERE name LIKE '%${keyword}%'
`).toPromise()
let bindAnixinStrucIds = bindAnixinStrucRes.map(s => s.id)
findOption.where.strucId = { $contains: bindAnixinStrucIds }
} else if (keywordTarget == 'pepProject') {
// 这种情况在 pomsProjectRange 函数值已处理
}
}
const pomsProjectRes = await pomsProjectRange({
ctx, pepProjectId: pomsProjectId, keywordTarget, keyword
})
let pomsProjectIds = pomsProjectRes.map(p => p.id)
findOption.where.pomsProjectId = { $in: pomsProjectIds }
if (keyword) {
findOption.where.$or = [
{
name: { $like: `%${keyword}%` }
},
]
}
if (alarmType) {
findOption.where.alarmType = { $contains: [alarmType] }
}
if (state) {
if (state == 'enable') {
if (state == 'enable' || state == 'notYet' || state == 'takeEffect') {
findOption.where.disable = false
} else if (state == 'disable') {
findOption.where.disable = true
} else if (state == 'overtime') {
}
}
if (tactics) {
findOption.where.tactics = tactics
}
const listRes = await models.AlarmPushConfig.findAll(findOption)
let allStrucIds = new Set()
let allConfigId = []
let allReceiverIds = new Set()
for (let p of listRes) {
for (let sid of p.strucId) {
allStrucIds.add(sid)
}
allConfigId.push(p.id)
for (let uid of p.receiverPepUserId) {
allReceiverIds.add(uid)
}
}
// 查配置所包含的所有结构物
const allStrucRes = allStrucIds.size ? await clickHouse.anxinyun.query(`
SELECT id, name FROM t_structure
WHERE id IN (${[...allStrucIds].join(',')})
`).toPromise() : []
// 查所有配置的推送的次数
const pushLogCountRes = await models.EmailSendLog.findAll({
attributes: [
'pushConfigId',
[sequelize.fn('COUNT', sequelize.col('push_config_id')), 'count']
],
where: {
pushConfigId: { $in: allConfigId }
},
group: ['pushConfigId']
})
// 查询所有的用户信息
const userRes = allReceiverIds.size ? await clickHouse.pepEmis.query(`
SELECT id, name, delete FROM user
WHERE id IN (${[...allReceiverIds].join(',')})
`).toPromise() : []
let returnD = []
for (let { dataValues: p } of listRes) {
// 查对应的 poms 绑定的结构物绑定关系
const corBind = pomsProjectRes.find(ppj => ppj.id == p.pomsProjectId)
if (corBind.pepProjectId) {
if (state == 'notYet') {
if (corBind.pepProject && p.timeType.some(pt => pt == corBind.pepProject.constructionStatusId)) {
continue
}
} else if (state == 'takeEffect') {
if (!corBind.pepProject || !p.timeType.some(pt => pt == corBind.pepProject.constructionStatusId)) {
continue
}
}
}
// 结构物信息
let returnStruc = []
for (let sid of p.strucId) {
// 查这个结构物的信息
let structure = allStrucRes.find(asr => asr.id == sid)
if (structure) {
// 检查当前结构物有没有从已绑定的项目里解绑
// 查对应的可见的结构物信息
const anxinStrucSeen = anxinStrucsRange.find(axs => axs.strucId == sid)
returnStruc.push({
id: sid,
name: structure.name,
unbind: !anxinStrucSeen || !corBind.anxinProjectId.includes(anxinStrucSeen.projectId)
})
} else {
// 这个结构物已删
}
}
const listRes = await models.AlarmPushConfig.findAndCountAll(findOption)
// 查找日志数量
const corLogCount = pushLogCountRes.find(plc => plc.pushConfigId == p.id)
// 查找接收人数据
const corReceiver = userRes.filter(u => p.receiverPepUserId.some(prId => u.id == prId))
returnD.push({
...p,
pomsProject: corBind,
structure: returnStruc,
pushCount: corLogCount ? corLogCount.count : 0,
receiverPepUser: corReceiver
})
}
ctx.status = 200;
ctx.body = listRes
ctx.body = returnD
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
@ -49,10 +163,12 @@ async function edit (ctx) {
try {
const models = ctx.fs.dc.models;
const { userId, pepUserId } = ctx.fs.api
const { pushId, name, pepProjectId = [], alarmType = [], receiverPepUserId = [], timeType = [], disable } = ctx.request.body
const { pushId, name, pomsProjectId, alarmType = [], receiverPepUserId = [], timeType = [], disable,
strucId = [], tactics, tacticsParams } = ctx.request.body
let storageData = {
name, pepProjectId, alarmType, receiverPepUserId, timeType, disable
name, pomsProjectId, alarmType, receiverPepUserId, timeType, disable,
strucId, tactics, tacticsParams
}
if (pushId) {
await models.AlarmPushConfig.update(storageData, {
@ -63,6 +179,7 @@ async function edit (ctx) {
} else {
storageData.createTime = moment().format()
storageData.createUserId = userId
storageData.del = false
await models.AlarmPushConfig.create(storageData)
}
@ -82,22 +199,17 @@ async function state (ctx) {
const { pushId } = ctx.params
const { disable = undefined, del = undefined, } = ctx.request.body
if (del) {
await models.AlarmPushConfig.destroy({
where: {
id: pushId
}
})
} else {
await models.AlarmPushConfig.update({
disable,
}, {
where: {
id: pushId
}
})
let updateData = {
disable,
del,
}
await models.AlarmPushConfig.update(updateData, {
where: {
id: pushId
}
})
ctx.status = 204;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);

8
api/app/lib/index.js

@ -58,7 +58,7 @@ module.exports.models = function (dc) { // dc = { orm: Sequelize对象, ORM: Seq
});
const {
AppInspection, ProjectApp, ProjectCorrelation, AppAlarm, App, AlarmAppearRecord, AlarmConfirmLog, EmailSendLog, LatestDynamicList
AppInspection, ProjectApp, ProjectCorrelation, AppAlarm, App, AlarmAppearRecord, AlarmConfirmLog, EmailSendLog, LatestDynamicList, AlarmPushConfig
} = dc.models;
AppInspection.belongsTo(App, { foreignKey: 'projectAppId', targetKey: 'id' });
@ -77,7 +77,8 @@ module.exports.models = function (dc) { // dc = { orm: Sequelize对象, ORM: Seq
AppAlarm.belongsTo(App, { foreignKey: 'projectAppId', targetKey: 'id' });
App.hasMany(AppAlarm, { foreignKey: 'projectAppId', sourceKey: 'id' });
AlarmPushConfig.belongsTo(ProjectCorrelation, { foreignKey: 'pomsProjectId', targetKey: 'id' });
ProjectCorrelation.hasMany(AlarmPushConfig, { foreignKey: 'pomsProjectId', sourceKey: 'id' });
AlarmAppearRecord.belongsTo(ProjectCorrelation, { foreignKey: 'projectCorrelationId', targetKey: 'id' });
ProjectCorrelation.hasMany(AlarmAppearRecord, { foreignKey: 'projectCorrelationId', sourceKey: 'id' });
@ -88,6 +89,9 @@ module.exports.models = function (dc) { // dc = { orm: Sequelize对象, ORM: Seq
EmailSendLog.belongsTo(ProjectCorrelation, { foreignKey: 'projectCorrelationId', targetKey: 'id' });
ProjectCorrelation.hasMany(EmailSendLog, { foreignKey: 'projectCorrelationId', sourceKey: 'id' });
EmailSendLog.belongsTo(AlarmPushConfig, { foreignKey: 'pushConfigId', targetKey: 'id' });
AlarmPushConfig.hasMany(EmailSendLog, { foreignKey: 'pushConfigId', sourceKey: 'id' });
LatestDynamicList.belongsTo(AlarmAppearRecord, { foreignKey: 'alarmAppearId', targetKey: 'id' });
AlarmAppearRecord.hasMany(LatestDynamicList, { foreignKey: 'alarmAppearId', sourceKey: 'id' });

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';
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const AlarmPushConfig = sequelize.define("alarmPushConfig", {
id: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: true,
field: "id",
autoIncrement: true,
unique: "alarm_push_config_id_uindex"
},
name: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "name",
autoIncrement: false
},
pepProjectId: {
type: DataTypes.ARRAY(DataTypes.INTEGER),
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "pep_project_id",
autoIncrement: false
},
alarmType: {
type: DataTypes.ARRAY(DataTypes.STRING),
allowNull: true,
defaultValue: null,
comment: "监听的告警类型",
primaryKey: false,
field: "alarm_type",
autoIncrement: false
},
receiverPepUserId: {
type: DataTypes.ARRAY(DataTypes.INTEGER),
allowNull: true,
defaultValue: null,
comment: "接收人id 项企",
primaryKey: false,
field: "receiver_pep_user_id",
autoIncrement: false
},
timeType: {
type: DataTypes.ARRAY(DataTypes.STRING),
allowNull: true,
defaultValue: null,
comment: "通知时效",
primaryKey: false,
field: "time_type",
autoIncrement: false
},
createTime: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "create_time",
autoIncrement: false
},
createUserId: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "create_user_id",
autoIncrement: false
},
disable: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "disable",
autoIncrement: false
}
}, {
tableName: "alarm_push_config",
comment: "",
indexes: []
});
dc.models.AlarmPushConfig = AlarmPushConfig;
return AlarmPushConfig;
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const AlarmPushConfig = sequelize.define("alarmPushConfig", {
id: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: true,
field: "id",
autoIncrement: true,
unique: "alarm_push_config_id_uindex"
},
name: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "name",
autoIncrement: false
},
pomsProjectId: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "poms_project_id",
autoIncrement: false
},
alarmType: {
type: DataTypes.ARRAY(DataTypes.STRING),
allowNull: true,
defaultValue: null,
comment: "监听的告警类型",
primaryKey: false,
field: "alarm_type",
autoIncrement: false
},
receiverPepUserId: {
type: DataTypes.ARRAY(DataTypes.INTEGER),
allowNull: true,
defaultValue: null,
comment: "接收人id 项企",
primaryKey: false,
field: "receiver_pep_user_id",
autoIncrement: false
},
timeType: {
type: DataTypes.ARRAY(DataTypes.STRING),
allowNull: true,
defaultValue: null,
comment: "通知时效",
primaryKey: false,
field: "time_type",
autoIncrement: false
},
createTime: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "create_time",
autoIncrement: false
},
createUserId: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "create_user_id",
autoIncrement: false
},
disable: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "disable",
autoIncrement: false
},
strucId: {
type: DataTypes.ARRAY(DataTypes.INTEGER),
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "struc_id",
autoIncrement: false
},
tactics: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: "immediately 即时 / continue 持续 / abnormal_rate 异常率",
primaryKey: false,
field: "tactics",
autoIncrement: false
},
tacticsParams: {
type: DataTypes.JSONB,
allowNull: true,
defaultValue: null,
comment: "推送策略 tactics 的参数",
primaryKey: false,
field: "tactics_params",
autoIncrement: false
},
del: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "del",
autoIncrement: false
}
}, {
tableName: "alarm_push_config",
comment: "",
indexes: []
});
dc.models.AlarmPushConfig = AlarmPushConfig;
return AlarmPushConfig;
};

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

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

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

@ -27,11 +27,6 @@ module.exports = function (app, router, opts) {
router.get('/analysis/problem', analysis.problem);
//项目概览
app.fs.api.logAttr['GET/projects/info'] = { content: '查询项目概览', visible: false };
router.get('/projects/info', csData.getProjectsInfo);
//BI分析模块
app.fs.api.logAttr['GET/data/alarms/agg/day'] = { content: '查询BI分析数据-数据', visible: false };
router.get('/data/alarms/agg/day', csData.getDataAlarmsAggDay);
@ -42,6 +37,9 @@ module.exports = function (app, router, opts) {
app.fs.api.logAttr['GET/video/alarms/agg/day'] = { content: '查询BI分析数据-视频异常', visible: false };
router.get('/video/alarms/agg/day', csData.getVideoAlarmsAggDay);
app.fs.api.logAttr['GET/alarms/handle/statistics'] = { content: '查询BI分析数据-问题处理效率分析', visible: false };
router.get('/alarms/handle/statistics', csData.getAlarmsHandleStatistics);
//最新动态
app.fs.api.logAttr['GET/latest/dynamic'] = { content: '查询最新动态', visible: false };
router.get('/latest/dynamic', csData.getLatestDynamic);

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

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

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 = []
for (let s of undelStrucRes) {
if (!undelStruc.some(us => us.strucId == s.strucId)) {
let pomsProject = []
for (let { dataValues: br } of bindRes) {
if (br.anxinProjectId.some(braId => braId == s.projectId)) {
let corPepProject = pepProjectRes.find(pp => pp.id == br.pepProjectId)
pomsProject.push({
let corStruc = undelStruc.find(us => us.strucId == s.strucId)
if (corStruc) {
if (!corStruc.project.some(cp => cp.id == s.projectId)) {
corStruc.project.push({
id: s.projectId
})
}
} else {
corStruc = {
strucId: s.strucId,
strucName: s.strucName,
projectId: s.projectId,
project: [{
id: s.projectId,
}],
pomsProject: []
}
undelStruc.push(corStruc)
}
for (let { dataValues: br } of bindRes) {
if (br.anxinProjectId.some(braId => braId == s.projectId)) {
let corPepProject = pepProjectRes.find(pp => pp.id == br.pepProjectId)
let corPomsProject = corStruc.pomsProject.find(cp => cp.id == br.id)
if (corPomsProject) {
// poms 的 project 和 pep 的 project 是一对一的关系 所以这个情况不用处理
} else {
corStruc.pomsProject.push({
...br,
pepProject: corPepProject
})
}
}
undelStruc.push({
strucId: s.strucId,
strucName: s.strucName,
projectId: s.projectId,
pomsProject: pomsProject
})
}
}
return undelStruc
}
@ -188,6 +207,16 @@ module.exports = function (app, opts) {
let pomsProject = []
for (let b of bindRes) {
if (
keywordTarget == 'pepProject' && keyword
&& (
!b.name && !(
b.pepProjectId && pepProjectRes.some(pp => pp.id == b.pepProjectId)
)
)
) {
continue
}
if (b.pepProjectId) {
let corPepProject = pepProjectRes.find(pp => pp.id == b.pepProjectId) || {}
pomsProject.push({

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

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>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>
<body>

2
web/client/index.html

@ -11,7 +11,7 @@
<script>LA.init({ id: "Jo4eTlZVqgx3uwqm", ck: "Jo4eTlZVqgx3uwqm" })</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 >
<body>

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

@ -56,7 +56,7 @@ const Roles = (props) => {
let anxinerror = false
let anxinerrorArr = []
for (let i = 0; i < row.correlationProject.length; i++) {
if (row.correlationProject[i].del == -1) {
if (row.correlationProject[i].del == true) {
anxinerror = true
anxinerrorArr.push(row.correlationProject[i].pepProjectName)
}
@ -65,7 +65,7 @@ const Roles = (props) => {
<div style={{ display: 'flex', alignItems: 'center' }}>
{
anxinerror ? (
<Tooltip content={anxinerrorArr.join(',') + ',项目已在【项企PEP】中被删除!'}>
<Tooltip content={anxinerrorArr.join(',') + ',项目已在【项企PEP】或【映射关系】中被删除!'}>
<div style={{ marginRight: 5 }}>
<img src="/assets/images/install/risk.png" alt="" style={{ height: 24, width: 24, }} />
</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 }} />
</div>
<div style={{ color: '#FFFFFF', fontSize: 11, marginRight: 12 }}>
PMOS
POMS
</div>
</div>
)
@ -273,7 +273,7 @@ const Example = (props) => {
修改
</Button>
<Popconfirm
title="删除后对应的项目全局将无法进入和显示,对应的信鸽服务也会被禁用"
title="删除后对应的项目全局将无法进入和显示,对应的信鸽服务也会被删除"
arrowPointAtCenter={false}
showArrow={true}
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 [videoData, setVideoData] = useState({}) //
const [videoToken, setVideoToken] = useState() //token
const [alarmToConfirm, setAlarmToConfirm] = useState(null) //
const TextAreaApi = useRef('')
@ -306,15 +304,14 @@ const DataAlarm = ({ match, dispatch, actions, user, loading, socket, iotVcmpWeb
name: '操作', sort: 25, value: 'text', render: (_, r, index) => {
return <div style={{ width: 195 }}>
{r.State < 3 || route && ['videoAbnormal', 'useAbnormal'].includes(route) && !r.confirmTime ?
<Button theme='borderless' style={{ width: 65 }} onClick={() => {
setConfirm(true)
setSelected([r.key])
setAlarmToConfirm(r)
}}>确认</Button>
: r.State == 3 || r.autoRestore || r.confirmAuto ?
<Button theme='borderless' style={{ width: 65 }} onClick={() => {
setConfirm(true)
setSelected([r.key])
}}>确认</Button>
: 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>
}
{route && ['dataLnterrupt', 'dataAbnormal', 'strategyHit', 'deviceAbnormal'].includes(route) ? <>
<Button theme='borderless' style={{ width: 65 }} disabled>已派单</Button>
{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);
const getAlarmConfirmItem = () => {
let source = route == 'useAbnormal' ? alarmToConfirm.appName : alarmToConfirm.SourceName;
let type = route == 'useAbnormal' ? alarmToConfirm.type : route == 'videoAbnormal' ? alarmToConfirm.AlarmContent : alarmToConfirm.AlarmGroupUnit;
return {
pepUserId: user.pomsUserInfo.pepUserId,
projectCorrelationIds: alarmToConfirm?.projectName?.map(p => p.id),
alarmInfo: {
id: alarmToConfirm.key,
source: source,//
type: type,//
const getAlarmConfirmItems = () => {
let confirmItems = [];
selected.map(s => {
let alarmInfo = tableData.find(td => td.key == s);
let source = route == 'useAbnormal' ? alarmInfo.appName : alarmInfo.SourceName;
let type = route == 'useAbnormal' ? alarmInfo.type : route == 'videoAbnormal' ? alarmInfo.AlarmContent : alarmInfo.AlarmGroupUnit;
let item = {
pepUserId: user.pomsUserInfo.pepUserId,
projectCorrelationIds: alarmInfo?.projectName?.map(p => p.id),
alarmInfo: {
id: alarmInfo.key,
source: source,//
type: type,//
}
}
};
confirmItems.push(item);
})
return confirmItems;
}
return (
@ -457,7 +460,7 @@ const DataAlarm = ({ match, dispatch, actions, user, loading, socket, iotVcmpWeb
width={600}
onCancel={() => setConfirm(false)}
onOk={() => {
let confirmPost = getAlarmConfirmItem();
let confirmPost = getAlarmConfirmItems();
if (route == 'useAbnormal') {
TextAreaApi.current.validate().then((v) => {
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 { 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 './pushModal.less'
function pushModal (props) {
@ -12,47 +13,342 @@ function pushModal (props) {
dispatch,
pepList,
actions,
adminEdit,//
pushEdit,//
editObj,
user
} = props;
const { install } = actions;
const { service } = actions;
const form = useRef();//
const [disablePeople, setDisablePeople] = useState(true); //
const [peopleList, setPeopleList] = useState([]); //List
const [departmentId, setDepartmentId] = useState(); //id
const [peopleId, setPeopleId] = useState(); //id
const [abnormal, setAbnormal] = useState(false); //disable
const [usersList, setUsersList] = useState([]); //
const [structure, setStructure] = useState(true); //disable
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(() => {
getOrganizationUsersList()//
getProjectPomsList()//
if (editObj.id) {
let departmentList = []
for (let i = 0; i < pepList.length; i++) {
if (pepList[i].id == editObj.departments[0].id) {
departmentList = pepList[i].users
}
getProjectStructureList(editObj.pomsProjectId)
if (editObj.pomsProject?.pepProjectId) {
getProjectStatusList()//
}
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 () {
//
form.current
.validate()
.then((values) => {
if (adminEdit) {
dispatch(install.deteleOrganizationAdmin({id:editObj.id,msg:''})).then(
dispatch(install.postOrganizationUser({ role: ['admin'], pepUserId: values.pepUserId, msg: '修改管理员' })).then((res) => {//(PEP)
if (res.success) {
close();
if (pushEdit) {
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({ pushId: editObj.id, ...obj, msg: '编辑推送配置' })).then((res) => {//(PEP)
if (res.success) {
close();
}
})
}
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) {
close();
}
@ -67,60 +363,101 @@ function pushModal (props) {
return (
<>
<Modal
title="管理员设置"
title={pushEdit ? "修改推送策略" : '新增推送策略'}
okText="确定"
cancelText="取消"
visible={visible}
onOk={handleOk}
width={607}
width={860}
onCancel={handleCancel}
>
<div style={{ margin: "0px 25px" }}>
<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>
<div>
<Form
allowEmpty
labelPosition="left"
labelAlign="right"
labelWidth="90px"
labelWidth="120px"
onValueChange={(values, field) => {
console.log('values', values);
for (var key in field) {
if (key == 'department') {
if (values.department >= 0) {
let departmentList = []
for (let i = 0; i < pepList.length; i++) {
if (pepList[i].id == values.department) {
departmentList = pepList[i].users
}
}
setPeopleList(departmentList)
setDisablePeople(false)
form.current.setValue('pepUserId', undefined);
if (key == 'tactics') {
if (values.tactics == 'abnormal_rate') {
form.current.setValue('alarmType', undefined)
setAbnormal(true)
}
else {
setPeopleList([])
setDisablePeople(true)
form.current.setValue('pepUserId', undefined);
setAbnormal(false)
}
}
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)}
>
<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>
<Form.Select
label="选择部门:"
field="department"
placeholder="请选择部门"
style={{ width: 417 }}
rules={[{ required: true, message: "请选择部门" }]}
initValue={departmentId || ""}
label="请选择结构物:"
field="strucId"
placeholder="请选择结构物"
style={{ width: 695 }}
rules={[{ required: true, message: "请选择结构物" }]}
initValue={editObj?.strucId || []}
disabled={structure}
filter
multiple
maxTagCount={3}
showClear
>
{
pepList.map((item, index) => {
projectStructure.map((item, index) => {
return (
<Form.Select.Option key={index} value={item.id}>
{item.name}
@ -130,21 +467,137 @@ function pushModal (props) {
}
</Form.Select>
</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>
<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
label="选择人员:"
field="pepUserId"
placeholder="请选择人员"
style={{ width: 417 }}
rules={[{ required: true, message: "请选择人员" }]}
initValue={peopleId || ""}
label="通知时效:"
field="timeType"
placeholder="请选择通知时效"
style={{ width: 285 }}
rules={[{ required: true, message: "请选择通知时效" }]}
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
disabled={disablePeople}
>
{
peopleList.map((item, index) => {
usersList.map((item, index) => {
return (
<Form.Select.Option key={item.id} value={item.id}>
<Form.Select.Option key={index} value={item.id}>
{item.name}
</Form.Select.Option>
)
@ -152,9 +605,29 @@ function pushModal (props) {
}
</Form.Select>
</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>
</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 { 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 moment from "moment";
import pushModal from '../components/pushModal'
import PushModal from '../components/pushModal'
import '../style.less'
import { Setup } from "$components";
// import { set } from 'nprogress';
@ -18,9 +19,8 @@ const EmPush = (props) => {
const [query, setQuery] = useState({ limit: 10, page: 0 }); //
const [limits, setLimits] = useState()//
const mylimits = useRef(); //
const [selected, setSelected] = useState([]) //
const [pushModal, setPushModal] = useState(false) //
const [systemEdit, setSystemEdit] = useState(false) //
const [pushEdit, setPushEdit] = useState(false) //
const [anxincloudList, setAnxincloudList] = useState([]) //
const [peplist, setPeplist] = useState([]) //PEP
const [appList, setAppList] = useState([]) //
@ -31,20 +31,40 @@ const EmPush = (props) => {
const [appArr, setAppArr] = useState([]) //
const [bindId, setBindId] = useState() //id
const [tableKey, setTableKey] = useState([]) //id
const [editObj, setEditObj] = useState({});//
const [projectStatus, setProjectStatus] = useState([]); //
const page = useRef(query.page);//
const EMPUSH = "empush";
const tableList = [//
{
title: '推送信息',
list: [
{ name: "策略类型", value: "pushWay" },
{ name: "推送机制", value: "noticeWay" },
{ name: "监听设备数量", value: "monitorCount" },
{ name: "累计推送次数", value: "logCount" },
]
},
];
{
title: '推送信息',
list: [
{ name: "关联项目", value: "projectName" },
{ name: "策略名称", value: "name" },
{ name: "创建时间", value: "createTime" },
{ 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) {//
@ -59,15 +79,13 @@ const EmPush = (props) => {
return {};
}
}
const [tableData, setTableData] = useState([]) //
useEffect(() => {
attribute();
dispatch(service.getPush(query)).then((res) => {//
// console.log('res.payload.datares.payload.data',res.payload.data);
// setAnxincloudList(res.payload.data)
})
// dispatch(service.getPush(query)).then((res) => {//
// console.log('res.payload.datares.payload.data',res.payload.data);
// setAnxincloudList(res.payload.data)
// })
// dispatch(install.getProjectPmanage(query)).then((res) => {//PEP
// setPeplist(res.payload.data)
// })
@ -75,52 +93,44 @@ const EmPush = (props) => {
// setAppList(res.payload.data)
// })
localStorage.getItem(EMPUSH) == null
? localStorage.setItem(
EMPUSH,
JSON.stringify(['pushWay','noticeWay','logCount','monitorCount'])
)
: "";
? localStorage.setItem(
EMPUSH,
JSON.stringify(['projectName', 'name', 'createTime', 'receiverPepUser', 'alarmType', 'timeType', 'tactics', 'disable'])
)
: "";
getProjectStatusList()
}, [])
useEffect(() => {
// getProjectPomsList();
getPushList();
}, [query]);
function getProjectPomsList () {
// dispatch(install.getProjectPoms(query)).then((res) => {//
// if (res.success) {
// let mytableData = JSON.parse(JSON.stringify(res.payload.data.rows));
// let mytableKey = []
// for (let index = 0; index < mytableData.length; index++) {
// mytableData[index].key = mytableData[index].id
// mytableKey.push(mytableData[index].id)
// }
// setTableKey(mytableKey)
// setTableData(mytableData)
// setLimits(res.payload.data.count)
// mylimits.current = res.payload.data.rows.length
// }
// })
function getPushList () {
let val = form.current.getValues()
// , ...query
dispatch(service.getPush({ ...val })).then((res) => {//
if (res.success) {
let mytableData = JSON.parse(JSON.stringify(res.payload.data));
for (let index = 0; index < mytableData.length; index++) {
mytableData[index].key = String(mytableData[index].id)
}
setTableData(mytableData)
setLimits(res.payload.data.length)
mylimits.current = res.payload.data.length
}
})
}
const [columns, setColumns] = useState([//
{
title: "策略编号",
dataIndex: "index",
key: 'index',
render: (text, r, index) => {
return index + 1;
},
},
{
title: '策略名称',
dataIndex: "pepProjectName",
key: 'pepProjectName',
render: (_, row) => {
return row.pepProjectName
function getProjectStatusList () {//
dispatch(service.getProjectStatus()).then((res) => {
if (res.success) {
setProjectStatus(res.payload?.data)
attribute(res.payload?.data);
}
},
})
}
const columns = [//
{
title: "操作",
width: "20%",
width: "12%",
dataIndex: "text",
key: 'text',
render: (_, row) => {
@ -129,54 +139,81 @@ const EmPush = (props) => {
<Button
theme="borderless"
// disabled={row.pepProjectIsDelete == 1}
// onClick={() => {
// setPushModal(true);
// setPepname(row.name)
// if (!row.name) {
// setPepProjectId(row.pepProjectId)
// }
// else {
// setPepProjectId()
// }
// let myanxinArr = []
// let anxinErrorList = []
// for (let i = 0; i < row.anxinProject.length; i++) {
// if (row.anxinProject[i].projectState !== -1) {
// myanxinArr.push(row.anxinProject[i].id)
// }
// else {
// anxinErrorList.push(row.anxinProject[i].name)
// }
// }
// setAnxinDelete(anxinErrorList)
// let myapparr = []
// let myarrarrList = JSON.parse(JSON.stringify(row.apps))
// for (let j = 0; j < row.apps.length; j++) {
// delete myarrarrList[j].projectApp
// myapparr.push(JSON.stringify(myarrarrList[j]))
// }
// setAppArr(myapparr)
// setAnxincloudArr(myanxinArr)
// setBindId(row.id)
// setSystemEdit(true)
// }}
onClick={() => {
setEditObj(row)
setPushModal(true);
// setPepname(row.name)
// if (!row.name) {
// setPepProjectId(row.pepProjectId)
// }
// else {
// setPepProjectId()
// }
// let myanxinArr = []
// let anxinErrorList = []
// for (let i = 0; i < row.anxinProject.length; i++) {
// if (row.anxinProject[i].projectState !== -1) {
// myanxinArr.push(row.anxinProject[i].id)
// }
// else {
// anxinErrorList.push(row.anxinProject[i].name)
// }
// }
// setAnxinDelete(anxinErrorList)
// let myapparr = []
// let myarrarrList = JSON.parse(JSON.stringify(row.apps))
// for (let j = 0; j < row.apps.length; j++) {
// delete myarrarrList[j].projectApp
// myapparr.push(JSON.stringify(myarrarrList[j]))
// }
// setAppArr(myapparr)
// setAnxincloudArr(myanxinArr)
// setBindId(row.id)
setPushEdit(true)
}}
>
修改
</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
title="删除后对应的项目全局将无法进入和显示,对应的信鸽服务也会被禁用"
title="删除后通知策略将会失效。"
arrowPointAtCenter={false}
showArrow={true}
position="topRight"
onConfirm={() => {
// dispatch(install.deleteProjectBind({ bindId: row?.id, msg: '' })).then(() => {
// if (page.current > 0 && mylimits.current < 2) {
// setQuery({ limit: 10, page: page.current - 1 })
// } else {
// setQuery({ limit: 10, page: page.current })
// }
// })
dispatch(service.putPushPushId({ pushId: row?.id, del: true, disable: false, msg: '删除推送配置' })).then(() => {
// if (page.current > 0 && mylimits.current < 2) {
// setQuery({ limit: 10, page: page.current - 1 })
// } else {
// setQuery({ limit: 10, page: page.current })
// }
})
}}
>
<Button theme="borderless">删除</Button>
@ -185,74 +222,338 @@ const EmPush = (props) => {
);
},
},
])
const rowSelection = {
selectedRowKeys: selected,
onChange: (selectedRowKeys, selectedRows) => {
setSelected(selectedRows.map(v => v.key))
},
]
function expandRowRender (record, index) {
return (
<div style={{ display: 'flex' }}>
结构物
{
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)
? JSON.parse(localStorage.getItem(EMPUSH))
: [];
const column = [
{
title: "关联项目",
dataIndex: "noticeWay",
key: "noticeWay",
render: (_, r, index) => {
return r.noticeWay;
},
title: '关联项目',
dataIndex: "projectName",
key: "projectName",
render: (_, row) => {
let anxinerror = false
let anxinerrorArr = ''
if (row.pomsProject.del == true) {
anxinerror = true
anxinerrorArr = row.pomsProject.pepProject?.projectName || row.pomsProject.name
}
return (
<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: "创建时间",
dataIndex: "logCount",
key: "logCount",
dataIndex: "createTime",
key: "createTime",
render: (_, r, index) => {
return (r.logCount + '次')
return moment(r.createTime).format('YYYY-MM-DD HH:mm:ss');
},
},
{
title: "接收人",
dataIndex: "monitorCount",
key: "monitorCount",
title: '接收人',
dataIndex: "receiverPepUser",
key: 'receiverPepUser',
render: (_, row) => {
return (
<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) => {
return r.monitorCount
return '邮件通知';
},
},
{
title: "监听问题",
dataIndex: "pushWay",
key: "pushWay",
render: (_, r, index) => {
return r.pushWay=='email' ? '邮件通知' : '短信通知';
title: "监听问题模块",
dataIndex: "alarmType",
key: "alarmType",
render: (_, row) => {
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: "通知时效",
dataIndex: "text1",
key: "text1",
title: "推送机制",
dataIndex: "tactics",
key: "tactics",
render: (_, r, index) => {
return r.text1
return tacticsObj[r.tactics]
},
},
{
title: "启用状态",
dataIndex: "text2",
key: "text2",
render: (_, r, index) => {
return r.text2
dataIndex: "disable",
key: "disable",
render: (_, row, index) => {
let enableType = ''
if (row.disable) {
enableType = '禁用'
}
else {
if (row.timeType.length > 0) {
for (let i = 0; i < row.timeType.length; i++) {
if (row.timeType[i] == row.pomsProject?.pepProject?.constructionStatusId) {
enableType = '已生效'
break;
} else {
enableType = '未生效'
}
}
}
else {
enableType = '已生效'
}
}
return (
<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: "推送次数",
dataIndex: "time",
key: "time",
dataIndex: "pushCount",
key: "pushCount",
render: (_, r, index) => {
return r.time
return r.pushCount + '次'
},
},
];
@ -260,7 +561,7 @@ const EmPush = (props) => {
let colum = column.filter((item) => {
return item.key === arr[i];
});
columns.splice(i + 2, 0, colum[0]);
columns.splice(columns.length - 1, 0, colum[0]);
}
setSetupp(columns);
}
@ -273,7 +574,7 @@ const EmPush = (props) => {
<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>
<div style={{ marginRight: 20, display: 'flex', alignItems: 'center' }} className='empush'>
<div style={{ marginRight: 20, display: 'flex', alignItems: 'center' }} className='myempush'>
<Form
onSubmit={(values) => console.log(values)}
// onValueChange={values=>console.log(values)}
@ -281,47 +582,53 @@ const EmPush = (props) => {
layout="horizontal"
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
label='搜索:'
field='seacth'
maxLength="16"
placeholder="搜项目、结构物或推送策略名"
labelPosition="left"
style={{ width: 292, marginRight: 10, marginBottom: 16 }}
suffix={<IconSearch />}
field="keyword"
pure
showClear
style={{ width: 260, marginLeft: 12, marginRight: 12 }}
placeholder="请输入或选择关键词"
/>
<Form.Select
label='推送类型:'
labelPosition="left"
field='pushType'
field='alarmType'
style={{ width: 116, marginRight: 10, color: "#F9F9F9", }}
placeholder="全部"
filter
showClear
>
{/* {.map((item) => {
return (
<Form.Select.Option key={item.value} value={item.value}>
{item.name}
</Form.Select.Option>
);
})} */}
<Form.Select.Option value="data_outages">数据中断</Form.Select.Option>
<Form.Select.Option value="data_exception">数据异常</Form.Select.Option>
<Form.Select.Option value="strategy_hit">策略命中</Form.Select.Option>
<Form.Select.Option value="video_exception">视频异常</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
label='启用状态:'
labelPosition="left"
field='enableType'
field='state'
style={{ width: 116, marginRight: 10, color: "#F9F9F9", }}
placeholder="全部"
filter
showClear
>
{/* {.map((item) => {
return (
<Form.Select.Option key={item.value} value={item.value}>
{item.name}
</Form.Select.Option>
);
})} */}
<Form.Select.Option value='takeEffect'>已生效</Form.Select.Option>
<Form.Select.Option value='notYet'>未生效</Form.Select.Option>
<Form.Select.Option value='disable'>禁用</Form.Select.Option>
</Form.Select>
</Form>
<Button
@ -332,15 +639,17 @@ const EmPush = (props) => {
height: 32,
borderRadius: 2,
marginRight: 32,
background:'#FFFFFF',
color:'#005ABD',
border:'1px solid #005ABD'
background: '#FFFFFF',
color: '#005ABD',
border: '1px solid #005ABD'
}}
onClick={() => {
getPushList()
}}
// onClick={() => { }}
>
查询
</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)} />
</div>
<Button
@ -353,14 +662,9 @@ const EmPush = (props) => {
marginLeft: 32
}}
onClick={() => {
setEditObj({})
setPushModal(true);
setSystemEdit(false)
setPepProjectId()
setPepname()
setAnxinDelete([])
setAppArr([])
setAnxincloudArr()
setBindId()
setPushEdit(false);
}}
>
添加推送策略
@ -368,7 +672,7 @@ const EmPush = (props) => {
</div>
</div>
<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 style={{ marginTop: 20 }}>
<Skeleton
@ -378,13 +682,15 @@ const EmPush = (props) => {
placeholder={SkeletonScreen()}
>
<Table
rowKey="name"
columns={setupp.filter((s) => s)}
dataSource={tableData}
bordered={false}
hideExpandedColumn={false}
empty="暂无数据"
pagination={false}
expandedRowRender={expandRowRender}
// pagination={false}
onRow={handleRow}
rowSelection={rowSelection}
/>
</Skeleton>
<div
@ -395,35 +701,6 @@ const EmPush = (props) => {
}}
>
<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 style={{ display: 'flex', }}>
<span style={{ lineHeight: "30px", fontSize: 13, color: 'rgba(0,90,189,0.8)' }}>
@ -438,49 +715,39 @@ const EmPush = (props) => {
onChange={(currentPage, pageSize) => {
setQuery({ limit: pageSize, page: currentPage - 1 });
page.current = currentPage - 1
setSelected([])
}}
/>
</div>
</div>
</div>
</div>
{/* {//映射关系弹
{//
pushModal ?
<PushModal
visible={true}
anxincloudList={anxincloudList}
systemEdit={systemEdit}
peplist={peplist}
appList={appList}
pepname={pepname}
anxincloudArr={anxincloudArr}
pepProjectId={pepProjectId}
anxinDelete={anxinDelete}
appArr={appArr}
bindId={bindId}
pushEdit={pushEdit}
editObj={editObj}
cancel={() => {
setPushModal(false);
}}
close={() => {
setPushModal(false);
getProjectPomsList()
getPushList()
}} >
</PushModal> : ''
} */}
}
{setup ? (
<Setup
tableType={EMPUSH}
tableList={tableList}
close={() => {
setSetup(false);
attribute();
// setcameraSetup(false);
}}
/>
) : (
""
)}
<Setup
tableType={EMPUSH}
tableList={tableList}
close={() => {
setSetup(false);
attribute(projectStatus);
}}
/>
) : (
""
)}
</>
)
}

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

@ -30,7 +30,7 @@ export function getNavItem (user, dispatch) {
}, {
itemKey: 'carrierPigeon',
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',
items: [{
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{
margin-bottom: 0px !important;
}

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

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

Loading…
Cancel
Save