diff --git a/api/.vscode/launch.json b/api/.vscode/launch.json index 2995642..42d30dc 100644 --- a/api/.vscode/launch.json +++ b/api/.vscode/launch.json @@ -16,9 +16,9 @@ "-p 4600", "-f http://localhost:4600", // 研发 - // "-g postgres://postgres:123@10.8.30.32:5432/orational_service", + "-g postgres://postgres:123@10.8.30.32:5432/orational_service", // 测试 - "-g postgres://FashionAdmin:123456@10.8.30.156:5432/POMS", + //"-g postgres://FashionAdmin:123456@10.8.30.156:5432/POMS", "-k node35:6667,node36:6667,node37:6667", "--iotaProxy http://10.8.30.157:17007", "--redisHost 10.8.30.112", @@ -56,7 +56,7 @@ // "--clickHouseDataAlarm default", // 测试 - "--clickHouseAnxincloud Anxinyun13", + "--clickHouseAnxincloud Anxinyun21", "--clickHousePepEmis pepca8", "--clickHouseProjectManage peppm8", "--clickHouseVcmp video_access_dev", diff --git a/api/app/lib/controllers/alarm/app.js b/api/app/lib/controllers/alarm/app.js index 43d73fc..5279241 100644 --- a/api/app/lib/controllers/alarm/app.js +++ b/api/app/lib/controllers/alarm/app.js @@ -2,7 +2,7 @@ const moment = require('moment') -async function inspection (ctx) { +async function inspection(ctx) { // 巡查 try { const models = ctx.fs.dc.models; @@ -31,7 +31,7 @@ async function inspection (ctx) { } } -async function inspectionList (ctx) { +async function inspectionList(ctx) { try { const models = ctx.fs.dc.models; const { clickHouse } = ctx.app.fs @@ -116,7 +116,7 @@ async function inspectionList (ctx) { } } -async function notedInspection (ctx) { +async function notedInspection(ctx) { try { const models = ctx.fs.dc.models; const { inspectionId } = ctx.request.body @@ -141,7 +141,7 @@ async function notedInspection (ctx) { } } -async function apiError (ctx) { +async function apiError(ctx) { try { const models = ctx.fs.dc.models; const { projectAppId, alarmContent, router, statusCode, screenshot = '', type } = ctx.request.body @@ -212,7 +212,7 @@ async function apiError (ctx) { } } -async function apiErrorList (ctx) { +async function apiErrorList(ctx) { try { const models = ctx.fs.dc.models; const { clickHouse } = ctx.app.fs @@ -324,11 +324,11 @@ async function apiErrorList (ctx) { } } -async function confirmApiError (ctx) { +async function confirmApiError(ctx) { try { const models = ctx.fs.dc.models; - const { confirm, appAlarmId = [] } = ctx.request.body - + const { confirm, appAlarmId = [], confirmPost } = ctx.request.body + const { pepUserId, projectCorrelationIds, alarmInfo } = confirmPost await models.AppAlarm.update({ confirm, confirmTime: moment().format() @@ -338,6 +338,30 @@ 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); + + ctx.status = 204; } catch (error) { ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); diff --git a/api/app/lib/controllers/alarm/data.js b/api/app/lib/controllers/alarm/data.js index ae135b5..595cd60 100644 --- a/api/app/lib/controllers/alarm/data.js +++ b/api/app/lib/controllers/alarm/data.js @@ -247,7 +247,8 @@ function confirm (opts) { const { models } = ctx.fs.dc; const { utils: { kfkSendAsync } } = ctx.app.fs const { clickHouse } = ctx.app.fs - const { content = '', alarmId } = ctx.request.body + const { content = '', alarmId, confirmPost } = ctx.request.body; + const { pepUserId, projectCorrelationIds, alarmInfo } = confirmPost; // 发送告警恢复通知 // Topic: alarm /* diff --git a/api/app/lib/controllers/alarm/video.js b/api/app/lib/controllers/alarm/video.js index 7ec27a3..0e546cb 100644 --- a/api/app/lib/controllers/alarm/video.js +++ b/api/app/lib/controllers/alarm/video.js @@ -21,7 +21,7 @@ async function deviceType (ctx) { } } -async function alarmList (ctx) { +async function alarmList(ctx, agg) { try { const { models } = ctx.fs.dc; const { clickHouse } = ctx.app.fs @@ -69,7 +69,7 @@ async function alarmList (ctx) { } const alarmRes = anxinStrucIds.length ? await clickHouse.vcmp.query( ` - SELECT + SELECT cameraAlarm.cameraId AS cameraId, cameraAlarm.cameraName AS cameraName, cameraAlarm.cameraKindId AS cameraKindId, @@ -249,8 +249,12 @@ async function alarmList (ctx) { } } - ctx.status = 200; - ctx.body = returnD + if (agg == 'day') {//控制台 按日聚集 + return returnD + } else { + ctx.status = 200; + ctx.body = returnD + } } catch (error) { ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); ctx.status = 400; @@ -263,8 +267,8 @@ async function alarmList (ctx) { async function confirm (ctx) { try { const { models } = ctx.fs.dc; - const { alarmId, content } = ctx.request.body; - + const { alarmId, content, confirmPost } = ctx.request.body; + const { pepUserId, projectCorrelationIds, alarmInfo } = confirmPost; // TODO: 以视频·应用的秘钥进行鉴权 await ctx.app.fs.vcmpRequest.put('status/alarm/confirm', { data: { @@ -272,6 +276,29 @@ 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); + ctx.status = 204; } catch (error) { ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); diff --git a/api/app/lib/controllers/console/toolLink.js b/api/app/lib/controllers/console/toolLink.js deleted file mode 100644 index 4c475a5..0000000 --- a/api/app/lib/controllers/console/toolLink.js +++ /dev/null @@ -1,115 +0,0 @@ -'use strict'; - -async function list (ctx) { - try { - const { models } = ctx.fs.dc; - const { userId, pepUserId } = ctx.fs.api - const linkListRes = await models.QuickLink.findAll({ - attributes: { exclude: ['userId'] }, - where: { - userId, - } - }) - - ctx.status = 200; - ctx.body = linkListRes - } catch (error) { - ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); - ctx.status = 400; - ctx.body = { - message: typeof error == 'string' ? error : undefined - } - } -} - -async function edit (ctx) { - try { - const { models } = ctx.fs.dc; - const { userId, pepUserId } = ctx.fs.api - const { linkId, name, link } = ctx.request.body - - if (!name || !link) { - throw '请将参数填写完整' - } - - let findOption = { - where: { - userId: userId, - $or: [{ - name, - }, { - link, - }] - } - } - if (linkId) { - findOption.where.id = { $ne: linkId } - } - const existRes = await models.QuickLink.findOne({ - where: { - userId: userId, - $or: [{ - name, - }, { - link, - }] - } - }) - if (existRes) { - throw '已有相同名称/地址的工具' - } - if (linkId) { - await models.QuickLink.update({ - name, - link, - }, { - where: { - id: linkId - } - }) - - } else { - await models.QuickLink.create({ - userId, - name, - link, - }) - - } - - ctx.status = 204; - } catch (error) { - ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); - ctx.status = 400; - ctx.body = { - message: typeof error == 'string' ? error : undefined - } - } -} - -async function del (ctx) { - try { - const { models } = ctx.fs.dc; - const { userId, pepUserId } = ctx.fs.api - const { linkId } = ctx.params - - await models.QuickLink.destroy({ - where: { - id: linkId, - userId, - } - }) - - ctx.status = 204; - } catch (error) { - ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); - ctx.status = 400; - ctx.body = { - message: typeof error == 'string' ? error : undefined - } - } -} - -module.exports = { - list, edit, del, -}; \ No newline at end of file diff --git a/api/app/lib/controllers/control/analysis.js b/api/app/lib/controllers/control/analysis.js new file mode 100644 index 0000000..e6e741f --- /dev/null +++ b/api/app/lib/controllers/control/analysis.js @@ -0,0 +1,90 @@ +'use strict'; +const moment = require('moment'); + +async function dataList (ctx) { + try { + const { models } = ctx.fs.dc; + const { userId, pepUserId, userInfo = {}, pepUserInfo } = ctx.fs.api + const { clickHouse } = ctx.app.fs + const { utils: { judgeSuper, anxinStrucIdRange, pomsProjectRange } } = ctx.app.fs + const { database: anxinyun } = clickHouse.anxinyun.opts.config + const { pepProjectId } = ctx.request.query + + + let anxinStruc = await anxinStrucIdRange({ + ctx, pepProjectId + }) + let pomsProject = await pomsProjectRange({ + ctx, pepProjectId, + }) + const pomsProjectIds = pomsProject.map(p => p.id) + + if (anxinStruc.length) { + + const anxinStrucIds = anxinStruc.map(a => a.strucId) || [] + + const dataAlarm = await clickHouse.dataAlarm.query(` + SELECT + formatDateTime(alarmData.StartTime,'%F %H') hours, count(AlarmId) count + FROM + ( SELECT + AlarmId,State,StartTime + FROM + alarms + WHERE + alarms.StructureId IN (${anxinStrucIds.join(",")}) + AND + AlarmGroup = 3) AS alarmData + GROUP BY hours + `).toPromise(); + + // const confirmedAlarm = dataAlarm + // // TODO: 开发临时注释 + // .filter(ar => ar.State && ar.State > 2) + // .map(ar => "'" + ar.AlarmId + "'") + + // // formatDateTime(Time,'%F %H') hours, count(AlarmId) count + + // const dataConfirme = confirmedAlarm.length ? + // await clickHouse.dataAlarm.query(` + // SELECT + // max(Time) AS Time, AlarmId + // FROM + // alarm_details + // WHERE + // AlarmId IN (${confirmedAlarm.join(',')}) + // GROUP BY AlarmId + // `).toPromise() : + // []; + + + // dataAlarm.forEach(ar => { + // ar.confirme = + // dataConfirme.find(as => as.AlarmId == ar.AlarmId) || {} + + // }) + ctx.status = 200 + ctx.body = dataAlarm + } else { + ctx.status = 200 + ctx.body = { + dataAlarm: 0, + } + } + + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + message: typeof error == 'string' ? error : undefined + } + } +} + + + + + +module.exports = { + dataList, +}; \ No newline at end of file diff --git a/api/app/lib/controllers/control/data.js b/api/app/lib/controllers/control/data.js new file mode 100644 index 0000000..2285e66 --- /dev/null +++ b/api/app/lib/controllers/control/data.js @@ -0,0 +1,195 @@ +'use strict'; +const moment = require('moment'); +const { alarmList } = require('../alarm/video'); +//工作台 +async function getWorkbench(ctx) { + try { + const { models } = ctx.fs.dc; + const { clickHouse } = ctx.app.fs + const { alarmId, limit, page } = ctx.query + 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 + } + } +} + +//项目概览 +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 getVideoAlarmsAggDay(ctx) { + try { + let videoAlarms = await alarmList(ctx, 'day'); + let aggDayMap = []; + for (let a of videoAlarms) { + let exist = aggDayMap.find(ad => ad.day == moment(a.createTime).format('YYYY-MM-DD')); + if (exist) { + exist.total++;//总数 + if (a.confirmTime || a.autoRestore) { + exist.done++;//已恢复 + } + } else { + aggDayMap.push({ day: moment(a.createTime).format('YYYY-MM-DD'), total: 1, done: a.confirmTime || a.autoRestore ? 1 : 0 }); + } + } + ctx.status = 200; + ctx.body = aggDayMap; + } 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 { + const { models } = ctx.fs.dc; + const { limit, page, projectCorrelationId, types } = ctx.query; + const { userInfo } = ctx.fs.api; + const { clickHouse } = ctx.app.fs; + + let where = { type: { $in: types.split(',') } }//传类型选择 + if (!userInfo.role.includes('SuperAdmin') && !userInfo.role.includes('admin')) { + where.projectCorrelationId = { $in: userInfo.correlationProject } + } + if (projectCorrelationId) {//查指定项目,控制台全局切换 + where.projectCorrelationId = projectCorrelationId + } + let news = await models.LatestDynamicList.findAll({//最新动态 + include: [{ + model: models.ProjectCorrelation, + where: { del: false }, + attributes: ['id', 'name', 'pepProjectId'], + }, { + model: models.AlarmAppearRecord + }, { + model: models.EmailSendLog + }, { + model: models.AlarmConfirmLog + }], + where: where, + offset: Number(page) * Number(limit), + limit: Number(limit), + order: [['time', 'desc']], + }); + + //查项目名称 查用户名 + let pepPojectIds = new Set(), notedUserIds = new Set(); + for (let p of news) { + pepPojectIds.add(p.projectCorrelation.pepProjectId); + + if (p.emailSendLog) { + notedUserIds.add(p.emailSendLog.toPepUserId);//通知 接收人 + } + if (p.alarmConfirmLog && p.alarmConfirmLog.pepUserId) { + notedUserIds.add(p.alarmConfirmLog.pepUserId);//确认 操作者 + } + } + let pepProjects = pepPojectIds.size ? await clickHouse.projectManage.query(` + SELECT id, project_name FROM t_pim_project WHERE id IN (${[...pepPojectIds]}) + `).toPromise() : []; + + let userPepRes = notedUserIds.size ? await clickHouse.pepEmis.query( + `SELECT DISTINCT user.id AS id, "user"."name" AS name FROM user WHERE user.id IN (${[...notedUserIds].join(',')}) + `).toPromise() : [] + + + let appear = [], notice = [], confirm = []; + news.map(d => { + let projectName = d.projectCorrelation.name || pepProjects.find(pp => pp.id == d.projectCorrelation.pepProjectId).project_name; + if (d.alarmAppearId) { + appear.push({ + projectName, + ...d.alarmAppearRecord + }); + } + if (d.emailSendId) { + notice.push({ + userName: userPepRes.find(u => u.id == d.emailSendLog.toPepUserId).name, + projectName, + ...d.emailSendLog + }); + } + if (d.alarmConfirmId) { + confirm.push({ + userName: d.alarmConfirmLog.pepUserId ? userPepRes.find(u => u.id == d.alarmConfirmLog.pepUserId).name : '自动恢复', + projectName, + ...d.alarmConfirmLog.dataValues + }); + } + }) + ctx.status = 200; + ctx.body = { + appear,//发现 + notice,//通知 + confirm//确认 + }; + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + message: typeof error == 'string' ? error : undefined + } + } +} + +module.exports = { + getWorkbench, + getProjectsInfo, + getVideoAlarmsAggDay, + getLatestDynamic +}; \ No newline at end of file diff --git a/api/app/lib/controllers/control/toolLink.js b/api/app/lib/controllers/control/toolLink.js new file mode 100644 index 0000000..c61abbc --- /dev/null +++ b/api/app/lib/controllers/control/toolLink.js @@ -0,0 +1,367 @@ +'use strict'; +const moment = require('moment'); + +async function list (ctx) { + try { + const { models } = ctx.fs.dc; + const { userId, pepUserId } = ctx.fs.api + const linkListRes = await models.QuickLink.findAll({ + attributes: { exclude: ['userId'] }, + where: { + userId, + } + }) + + ctx.status = 200; + ctx.body = linkListRes + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + message: typeof error == 'string' ? error : undefined + } + } +} + +async function edit (ctx) { + try { + const { models } = ctx.fs.dc; + const { userId, pepUserId } = ctx.fs.api + const { linkId, name, link } = ctx.request.body + + if (!name || !link) { + throw '请将参数填写完整' + } + + let findOption = { + where: { + userId: userId, + $or: [{ + name, + }, { + link, + }] + } + } + if (linkId) { + findOption.where.id = { $ne: linkId } + } + const existRes = await models.QuickLink.findOne({ + where: { + userId: userId, + $or: [{ + name, + }, { + link, + }] + } + }) + if (existRes) { + throw '已有相同名称/地址的工具' + } + if (linkId) { + await models.QuickLink.update({ + name, + link, + }, { + where: { + id: linkId + } + }) + + } else { + await models.QuickLink.create({ + userId, + name, + link, + }) + + } + + ctx.status = 204; + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + message: typeof error == 'string' ? error : undefined + } + } +} + +async function del (ctx) { + try { + const { models } = ctx.fs.dc; + const { userId, pepUserId } = ctx.fs.api + const { linkId } = ctx.params + + await models.QuickLink.destroy({ + where: { + id: linkId, + userId, + } + }) + + ctx.status = 204; + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + message: typeof error == 'string' ? error : undefined + } + } +} + + + + +async function count (ctx) { + try { + const { models } = ctx.fs.dc; + const { userId, pepUserId, userInfo = {}, pepUserInfo } = ctx.fs.api + const { clickHouse } = ctx.app.fs + const { utils: { judgeSuper, anxinStrucIdRange, pomsProjectRange } } = ctx.app.fs + const { database: anxinyun } = clickHouse.anxinyun.opts.config + const { pepProjectId } = ctx.request.query + + + let anxinStruc = await anxinStrucIdRange({ + ctx, pepProjectId + }) + let pomsProject = await pomsProjectRange({ + ctx, pepProjectId, + }) + const pomsProjectIds = pomsProject.map(p => p.id) + + if (anxinStruc.length) { + + const anxinStrucIds = anxinStruc.map(a => a.strucId) || [] + + const dataAlarm = await clickHouse.dataAlarm.query(` + SELECT + AlarmId,State,StartTime,AlarmGroup + FROM + alarms + WHERE + alarms.StructureId IN (${anxinStrucIds.join(",")}) + `).toPromise(); + + const confirmedAlarm = dataAlarm + // TODO: 开发临时注释 + .filter(ar => ar.State && ar.State > 2) + .map(ar => "'" + ar.AlarmId + "'") + + //剩余数据告警 + const dataSurplus = dataAlarm.filter(ar => ar.State && ar.State < 3 && ar.AlarmGroup < 4).length || 0 + //剩余设备告警 + const toolSurplus = dataAlarm.filter(ar => ar.State && ar.State < 3 && ar.AlarmGroup > 3).length || 0 + + //今日新增数据告警 + const dataNewAdd = dataAlarm.filter(r => moment(moment().startOf('day').format('YYYY-MM-DD HH:mm:ss')).isBefore(r.StartTime) && moment(r.createTime).isBefore(moment().endOf('day').format('YYYY-MM-DD HH:mm:ss')) && r.AlarmGroup < 4).length || 0 + const toolNewAdd = dataAlarm.filter(r => moment(moment().startOf('day').format('YYYY-MM-DD HH:mm:ss')).isBefore(r.StartTime) && moment(r.createTime).isBefore(moment().endOf('day').format('YYYY-MM-DD HH:mm:ss')) && r.AlarmGroup > 3).length || 0 + + //今日确认数据告警 + const dataConfirme = confirmedAlarm.length ? + await clickHouse.dataAlarm.query(` + SELECT + max(Time) AS Time, AlarmId , max(Content) AS Content, + alarms.AlarmGroup AS AlarmGroup + FROM + alarm_details + LEFT JOIN alarms + ON alarm_details.AlarmId=alarms.AlarmId + WHERE + AlarmId IN (${confirmedAlarm.join(',')}) + AND + alarm_details.Time >= '${moment().startOf('day').format('YYYY-MM-DD HH:mm:ss')}' + AND + alarm_details.Time <= '${moment().endOf('day').format('YYYY-MM-DD HH:mm:ss')}' + GROUP BY AlarmId,AlarmGroup + `).toPromise() : + []; + + + let findOption = { + where: { + '$app->projectCorrelations.id$': { + $in: pomsProjectIds + } + }, + attributes: ['createTime', 'confirmTime'], + include: [{ + model: models.App, + where: { + + }, + attributes: ['id', 'name'], + include: [{ + model: models.ProjectCorrelation, + where: { + + }, + attributes: ['id'], + }] + }] + } + + //应用总告警 + const listRes = await models.AppAlarm.findAndCountAll(findOption) + //剩余应用告警 + const appSurplus = listRes.rows.filter(r => !r.confirmTime).length || 0 + //今日新增应用告警 + const appNewAdd = listRes.rows.filter(r => moment(moment().startOf('day').format('YYYY-MM-DD HH:mm:ss')).isBefore(r.createTime) && moment(r.createTime).isBefore(moment().endOf('day').format('YYYY-MM-DD HH:mm:ss'))).length || 0 + //今日确认应用告警 + const appConfirme = listRes.rows.filter(r => moment(moment().startOf('day').format('YYYY-MM-DD HH:mm:ss')).isBefore(r.confirmTime) && moment(r.confirmTime).isBefore(moment().endOf('day').format('YYYY-MM-DD HH:mm:ss'))).length || 0 + + const alarmRes = anxinStrucIds.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.platform AS platform, + cameraAlarm.confirmTime AS confirmTime, + camera_status_resolve.id AS resolveId, + camera_status.describe AS statusDescribe, + camera_status_resolve.resolve AS resolve, + "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.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_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 + LEFT JOIN vender + ON vender.id = camera.vender_id + WHERE + camera.delete = false + AND camera.recycle_time is null + 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 "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 (${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() : [] + + let returnD = [] + let positionD = {} + // 每个设备一个告警 + for (let a of alarmRes) { + if (positionD[a.cameraId]) { + let curD = returnD[positionD[a.cameraId].positionReturnD] + + } else { + let d = { + cameraId: a.cameraId, + cameraName: a.cameraName, + createTime: a.createTime, + alarmId: a.alarmId, + confirmTime: a.confirmTime, + } + + returnD.push(d) + positionD[a.cameraId] = { + positionReturnD: returnD.length - 1 + } + } + } + + //剩余视频告警 + const videoSurplus = returnD.filter(r => !r.confirmTime).length || 0 + //今日新增视频告警 + const videoNewAdd = returnD.filter(r => moment(moment().startOf('day').format('YYYY-MM-DD HH:mm:ss')).isBefore(r.createTime) && moment(r.createTime).isBefore(moment().endOf('day').format('YYYY-MM-DD HH:mm:ss'))).length || 0 + //今日确认视频告警 + const videoConfirme = returnD.filter(r => moment(moment().startOf('day').format('YYYY-MM-DD HH:mm:ss')).isBefore(r.confirmTime) && moment(r.confirmTime).isBefore(moment().endOf('day').format('YYYY-MM-DD HH:mm:ss'))).length || 0 + + let findOptions = { + where: { + del: false + }, + attributes: [] + } + if (!userInfo.role.includes('SuperAdmin') && !userInfo.role.includes('admin')) { + findOptions.where.id = { $in: userInfo.correlationProject } + } + const projects = await models.ProjectCorrelation.findAndCountAll(findOptions) + + ctx.status = 200; + ctx.body = { + + dataSurplus: dataSurplus + videoSurplus, + dataNewAdd: dataNewAdd + videoNewAdd, + dataConfirme: appConfirme + dataConfirme.filter(r => r.AlarmGroup < 4).length, + + + toolSurplus: toolSurplus, + toolNewAdd: toolNewAdd, + toolConfirme: dataConfirme.filter(r => r.AlarmGroup > 3).length, + + + appSurplus: appSurplus, + appNewAdd: appNewAdd, + appConfirme: appConfirme, + + projects: projects.count, + + } + } else { + ctx.body = { + dataAlarm: 0, + } + } + ctx.status = 200; + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + message: typeof error == 'string' ? error : undefined + } + } +} + + + +module.exports = { + list, edit, del, count +}; \ No newline at end of file diff --git a/api/app/lib/index.js b/api/app/lib/index.js index bd5cde2..10a1b33 100644 --- a/api/app/lib/index.js +++ b/api/app/lib/index.js @@ -58,7 +58,7 @@ module.exports.models = function (dc) { // dc = { orm: Sequelize对象, ORM: Seq }); const { - AppInspection, ProjectApp, ProjectCorrelation, AppAlarm, App + AppInspection, ProjectApp, ProjectCorrelation, AppAlarm, App, AlarmAppearRecord, AlarmConfirmLog, EmailSendLog, LatestDynamicList } = dc.models; AppInspection.belongsTo(App, { foreignKey: 'projectAppId', targetKey: 'id' }); @@ -76,4 +76,27 @@ module.exports.models = function (dc) { // dc = { orm: Sequelize对象, ORM: Seq AppAlarm.belongsTo(App, { foreignKey: 'projectAppId', targetKey: 'id' }); App.hasMany(AppAlarm, { foreignKey: 'projectAppId', sourceKey: 'id' }); + + + + AlarmAppearRecord.belongsTo(ProjectCorrelation, { foreignKey: 'projectCorrelationId', targetKey: 'id' }); + ProjectCorrelation.hasMany(AlarmAppearRecord, { foreignKey: 'projectCorrelationId', sourceKey: 'id' }); + + AlarmConfirmLog.belongsTo(ProjectCorrelation, { foreignKey: 'projectCorrelationId', targetKey: 'id' }); + ProjectCorrelation.hasMany(AlarmConfirmLog, { foreignKey: 'projectCorrelationId', sourceKey: 'id' }); + + EmailSendLog.belongsTo(ProjectCorrelation, { foreignKey: 'projectCorrelationId', targetKey: 'id' }); + ProjectCorrelation.hasMany(EmailSendLog, { foreignKey: 'projectCorrelationId', sourceKey: 'id' }); + + LatestDynamicList.belongsTo(AlarmAppearRecord, { foreignKey: 'alarmAppearId', targetKey: 'id' }); + AlarmAppearRecord.hasMany(LatestDynamicList, { foreignKey: 'alarmAppearId', sourceKey: 'id' }); + + LatestDynamicList.belongsTo(EmailSendLog, { foreignKey: 'emailSendId', targetKey: 'id' }); + EmailSendLog.hasMany(LatestDynamicList, { foreignKey: 'emailSendId', sourceKey: 'id' }); + + LatestDynamicList.belongsTo(AlarmConfirmLog, { foreignKey: 'alarmConfirmId', targetKey: 'id' }); + AlarmConfirmLog.hasMany(LatestDynamicList, { foreignKey: 'alarmConfirmId', sourceKey: 'id' }); + + LatestDynamicList.belongsTo(ProjectCorrelation, { foreignKey: 'projectCorrelationId', targetKey: 'id' }); + ProjectCorrelation.hasMany(LatestDynamicList, { foreignKey: 'projectCorrelationId', sourceKey: 'id' }); }; diff --git a/api/app/lib/models/alarm_appear_record.js b/api/app/lib/models/alarm_appear_record.js new file mode 100644 index 0000000..3a6dcfa --- /dev/null +++ b/api/app/lib/models/alarm_appear_record.js @@ -0,0 +1,62 @@ +/* eslint-disable*/ + +'use strict'; + +module.exports = dc => { + const DataTypes = dc.ORM; + const sequelize = dc.orm; + const AlarmAppearRecord = sequelize.define("alarmAppearRecord", { + id: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: true, + field: "id", + autoIncrement: true, + unique: "alarm_appear_record_id_uindex" + }, + projectCorrelationId: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "project_correlation_id", + autoIncrement: false + }, + alarmInfo: { + type: DataTypes.JSON, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "alarm_info", + autoIncrement: false + }, + time: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: null, + comment: null, + primaryKey: false, + field: "time", + autoIncrement: false + }, + type: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + comment: "数据告警:data,设备告警:device,应用告警:application", + primaryKey: false, + field: "type", + autoIncrement: false + } + }, { + tableName: "alarm_appear_record", + comment: "", + indexes: [] + }); + dc.models.AlarmAppearRecord = AlarmAppearRecord; + return AlarmAppearRecord; +}; \ No newline at end of file diff --git a/api/app/lib/models/alarm_confirm_log.js b/api/app/lib/models/alarm_confirm_log.js new file mode 100644 index 0000000..f850a83 --- /dev/null +++ b/api/app/lib/models/alarm_confirm_log.js @@ -0,0 +1,71 @@ +/* eslint-disable*/ + +'use strict'; + +module.exports = dc => { + const DataTypes = dc.ORM; + const sequelize = dc.orm; + const AlarmConfirmLog = sequelize.define("alarmConfirmLog", { + id: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: true, + field: "id", + autoIncrement: true, + unique: "alarm_confirm_log_id_uindex" + }, + pepUserId: { + type: DataTypes.INTEGER, + allowNull: true, + defaultValue: null, + comment: null, + primaryKey: false, + field: "pep_user_id", + autoIncrement: false + }, + projectCorrelationId: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "project_correlation_id", + autoIncrement: false + }, + alarmInfo: { + type: DataTypes.JSON, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "alarm_info", + autoIncrement: false + }, + confirmTime: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "confirm_time", + autoIncrement: false + }, + confirmContent: { + type: DataTypes.STRING, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "confirm_content", + autoIncrement: false + } + }, { + tableName: "alarm_confirm_log", + comment: "", + indexes: [] + }); + dc.models.AlarmConfirmLog = AlarmConfirmLog; + return AlarmConfirmLog; +}; \ No newline at end of file diff --git a/api/app/lib/models/email_send_log.js b/api/app/lib/models/email_send_log.js new file mode 100644 index 0000000..7f75ea1 --- /dev/null +++ b/api/app/lib/models/email_send_log.js @@ -0,0 +1,62 @@ +/* eslint-disable*/ + +'use strict'; + +module.exports = dc => { + const DataTypes = dc.ORM; + const sequelize = dc.orm; + const EmailSendLog = sequelize.define("emailSendLog", { + id: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: true, + field: "id", + autoIncrement: true, + unique: "email_send_log_id_uindex" + }, + projectCorrelationId: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "project_correlation_id", + autoIncrement: false + }, + toPepUserId: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "to_pep_user_id", + autoIncrement: false + }, + by: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + comment: null, + primaryKey: false, + field: "by", + autoIncrement: false + }, + time: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "time", + autoIncrement: false + } + }, { + tableName: "email_send_log", + comment: "", + indexes: [] + }); + dc.models.EmailSendLog = EmailSendLog; + return EmailSendLog; +}; \ No newline at end of file diff --git a/api/app/lib/models/latest_dynamic_list.js b/api/app/lib/models/latest_dynamic_list.js new file mode 100644 index 0000000..5b53051 --- /dev/null +++ b/api/app/lib/models/latest_dynamic_list.js @@ -0,0 +1,80 @@ +/* eslint-disable*/ + +'use strict'; + +module.exports = dc => { + const DataTypes = dc.ORM; + const sequelize = dc.orm; + const LatestDynamicList = sequelize.define("latestDynamicList", { + id: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: true, + field: "id", + autoIncrement: true, + unique: "latest_dynamic_list_id_uindex" + }, + time: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "time", + autoIncrement: false + }, + alarmAppearId: { + type: DataTypes.INTEGER, + allowNull: true, + defaultValue: null, + comment: null, + primaryKey: false, + field: "alarm_appear_id", + autoIncrement: false + }, + emailSendId: { + type: DataTypes.INTEGER, + allowNull: true, + defaultValue: null, + comment: null, + primaryKey: false, + field: "email_send_id", + autoIncrement: false + }, + alarmConfirmId: { + type: DataTypes.INTEGER, + allowNull: true, + defaultValue: null, + comment: null, + primaryKey: false, + field: "alarm_confirm_id", + autoIncrement: false + }, + projectCorrelationId: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "project_correlation_id", + autoIncrement: false + }, + type: { + type: DataTypes.INTEGER, + allowNull: true, + defaultValue: null, + comment: '1:发现,2:通知,3:处置,4:确认', + primaryKey: false, + field: "type", + autoIncrement: false + } + }, { + tableName: "latest_dynamic_list", + comment: "", + indexes: [] + }); + dc.models.LatestDynamicList = LatestDynamicList; + return LatestDynamicList; +}; \ No newline at end of file diff --git a/api/app/lib/routes/console/index.js b/api/app/lib/routes/console/index.js deleted file mode 100644 index a281614..0000000 --- a/api/app/lib/routes/console/index.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -const toolLink = require('../../controllers/console/toolLink'); - -module.exports = function (app, router, opts) { - app.fs.api.logAttr['GET/console/toollink'] = { content: '获取常用工具', visible: true }; - router.get('/console/toollink', toolLink.list); - - app.fs.api.logAttr['PUT/console/toollink'] = { content: '编辑常用工具', visible: true }; - router.put('/console/toollink', toolLink.edit); - - app.fs.api.logAttr['DEL/console/toollink'] = { content: '删除常用工具', visible: true }; - router.del('/console/toollink/:linkId', toolLink.del); -}; \ No newline at end of file diff --git a/api/app/lib/routes/control/index.js b/api/app/lib/routes/control/index.js new file mode 100644 index 0000000..162f7f6 --- /dev/null +++ b/api/app/lib/routes/control/index.js @@ -0,0 +1,36 @@ +'use strict'; + +const toolLink = require('../../controllers/control/toolLink'); +const analysis = require('../../controllers/control/analysis'); +const csData = require('../../controllers/control/data'); + +module.exports = function (app, router, opts) { + app.fs.api.logAttr['GET/console/toollink'] = { content: '获取常用工具', visible: true }; + router.get('/console/toollink', toolLink.list); + + app.fs.api.logAttr['PUT/console/toollink'] = { content: '编辑常用工具', visible: true }; + router.put('/console/toollink', toolLink.edit); + + app.fs.api.logAttr['DEL/console/toollink'] = { content: '删除常用工具', visible: true }; + router.del('/console/toollink/:linkId', toolLink.del); + + app.fs.api.logAttr['GET/console/count'] = { content: '查询告警数量', visible: true }; + router.get('/console/count', toolLink.count); + + app.fs.api.logAttr['GET/analysis/dataList'] = { content: '查询数据告警产生,确认数量', visible: true }; + router.get('/analysis/dataList', analysis.dataList); + + + + //项目概览 + app.fs.api.logAttr['GET/projects/info'] = { content: '查询项目概览', visible: false }; + router.get('/projects/info', csData.getProjectsInfo); + + //BI分析模块 + 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/latest/dynamic'] = { content: '查询最新动态', visible: false }; + router.get('/latest/dynamic', csData.getLatestDynamic); +}; \ No newline at end of file diff --git a/api/app/lib/utils/dataRange.js b/api/app/lib/utils/dataRange.js index cc0a332..ea1c4c6 100644 --- a/api/app/lib/utils/dataRange.js +++ b/api/app/lib/utils/dataRange.js @@ -136,10 +136,17 @@ module.exports = function (app, opts) { 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 diff --git a/api/package.json b/api/package.json index cffb191..4d3e893 100644 --- a/api/package.json +++ b/api/package.json @@ -34,6 +34,7 @@ "pg": "^7.9.0", "redis": "^3.1.2", "request": "^2.88.2", + "sequelize-automate-freesun": "^1.2.2", "superagent": "^3.5.2", "uuid": "^3.3.2" }, diff --git a/web/client/assets/images/problem/shield.png b/web/client/assets/images/problem/shield.png new file mode 100644 index 0000000..2b20cfd Binary files /dev/null and b/web/client/assets/images/problem/shield.png differ diff --git a/web/client/src/app.jsx b/web/client/src/app.jsx index ac767ea..b11c920 100644 --- a/web/client/src/app.jsx +++ b/web/client/src/app.jsx @@ -8,7 +8,7 @@ import Analysis from './sections/analysis'; import Install from './sections/install'; import Problem from './sections/problem'; import NoMatch from './sections/noMatch'; -import Console from './sections/console'; +import Control from './sections/control'; import Facility from './sections/facility'; import Service from './sections/service'; import WorkOrder from './sections/workOrder'; @@ -40,7 +40,7 @@ const App = props => { title={projectName} sections={[//Example, Analysis, Install, Data, Facility, Service, Problem, WorkOrder,Means, - Auth, NoMatch, Console + Auth, NoMatch, Control ]} /> ) diff --git a/web/client/src/layout/components/header/index.jsx b/web/client/src/layout/components/header/index.jsx index b76c43d..4f855b3 100644 --- a/web/client/src/layout/components/header/index.jsx +++ b/web/client/src/layout/components/header/index.jsx @@ -97,12 +97,13 @@ const Header = (props) => { setScrollbar(!Scrollbar) setKeyword('') }} + clickToHide={true} render={
- } onChange={(v) => setKeyword(v)} showClear> + } onChange={(v) => setKeyword(v)} showClear onClick={(e)=>e.stopPropagation()}>
{pomsList?.length > 0 ? pomsList.filter(u => u.pepProjectName?.includes(keyword))?.map(v => { @@ -114,9 +115,8 @@ const Header = (props) => { v.pepProjectName?.length > 15 ? {v.pepProjectName}
}>
{ - console.log(v.pepProjectId); setPomsName(v.pepProjectName) - setPepProjectId(v.id) + setPepProjectId(v.pepProjectId) }}> {v.pepProjectName?.length > 15 ? `${v.pepProjectName?.substr(0, 15)}` : v.pepProjectName}
diff --git a/web/client/src/sections/auth/containers/login.jsx b/web/client/src/sections/auth/containers/login.jsx index 4b81829..060c402 100644 --- a/web/client/src/sections/auth/containers/login.jsx +++ b/web/client/src/sections/auth/containers/login.jsx @@ -20,7 +20,7 @@ const Login = props => { useEffect(() => { if (user && user.authorized) { - dispatch(push('/console')); + dispatch(push('/control')); localStorage.setItem('poms_open_sider', JSON.stringify([])) localStorage.removeItem('poms_selected_sider') } diff --git a/web/client/src/sections/console/actions/console.js b/web/client/src/sections/console/actions/console.js deleted file mode 100644 index 8be2887..0000000 --- a/web/client/src/sections/console/actions/console.js +++ /dev/null @@ -1,36 +0,0 @@ -'use strict'; - -import { ApiTable ,basicAction} from '$utils' - -export function ConsoleToollink () { //获取常用工具 - return dispatch => basicAction({ - type: 'get', - dispatch: dispatch, - actionType: 'GET_CONSLE_TOOLLINK', - url: `${ApiTable.consoleToollink}`, - msg: { option: '获取常用工具' }, - reducer: { name: '' } - }); -} - -export function putConsoleToollink (data) { //编辑常用工具 - return dispatch => basicAction({ - type: 'put', - dispatch: dispatch, - data, - actionType: 'PUT_CONSLE_TOOLLINK', - url: `${ApiTable.consoleToollink}`, - msg: { option: '编辑常用工具' }, - reducer: { name: '' } - }); -} -export function deleteConsoleToollink (orgId) { //删除常用工具 - return dispatch => basicAction({ - type: 'delete', - dispatch: dispatch, - actionType: 'DELETE_CONSLE_TOOLLINK', - url: `${ApiTable.consoleToollink.replace('{linkId}', orgId)}`, - msg: { option: '删除常用工具' }, - reducer: { name: '' } - }); -} \ No newline at end of file diff --git a/web/client/src/sections/console/actions/index.js b/web/client/src/sections/console/actions/index.js deleted file mode 100644 index 33cdd8b..0000000 --- a/web/client/src/sections/console/actions/index.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -import * as console from './console' - -export default { - ...console -} \ No newline at end of file diff --git a/web/client/src/sections/console/containers/index.js b/web/client/src/sections/console/containers/index.js deleted file mode 100644 index d290d54..0000000 --- a/web/client/src/sections/console/containers/index.js +++ /dev/null @@ -1,6 +0,0 @@ -'use strict'; - -import Console from './console'; -import UserCenter from './userCenter'; - -export { Console, UserCenter }; \ No newline at end of file diff --git a/web/client/src/sections/control/actions/control.js b/web/client/src/sections/control/actions/control.js new file mode 100644 index 0000000..83db79c --- /dev/null +++ b/web/client/src/sections/control/actions/control.js @@ -0,0 +1,48 @@ +'use strict'; + +import { ApiTable, basicAction } from '$utils' + +export function getConsoleToollink () { //获取常用工具 + return dispatch => basicAction({ + type: 'get', + dispatch: dispatch, + actionType: 'GET_CONSLE_TOOLLINK', + url: `${ApiTable.consoleToollink}`, + msg: { option: '获取常用工具' }, + reducer: { name: '' } + }); +} + +export function putConsoleToollink (data) { //编辑常用工具 + return dispatch => basicAction({ + type: 'put', + dispatch: dispatch, + data, + actionType: 'PUT_CONSLE_TOOLLINK', + url: `${ApiTable.consoleToollink}`, + msg: { option: '编辑常用工具' }, + reducer: { name: '' } + }); +} +export function deleteConsoleToollink (orgId) { //删除常用工具 + return dispatch => basicAction({ + type: 'delete', + dispatch: dispatch, + actionType: 'DELETE_CONSLE_TOOLLINK', + url: `${ApiTable.deleteConsoleToollink.replace('{linkId}', orgId)}`, + msg: { option: '删除常用工具' }, + reducer: { name: '' } + }); +} + +export function geteteConsoleCount (query) { //工作台数量查询 + return dispatch => basicAction({ + type: 'get', + dispatch: dispatch, + query, + actionType: 'GET_CONSLE_COUNT', + url: `${ApiTable.geteteConsoleCount}`, + msg: { option: '工作台数量查询' }, + reducer: { name: '' } + }); +} \ No newline at end of file diff --git a/web/client/src/sections/control/actions/index.js b/web/client/src/sections/control/actions/index.js new file mode 100644 index 0000000..df81c6c --- /dev/null +++ b/web/client/src/sections/control/actions/index.js @@ -0,0 +1,8 @@ +'use strict'; + +import * as control from './control' + +export default { + ...control + +} \ No newline at end of file diff --git a/web/client/src/sections/console/containers/console.jsx b/web/client/src/sections/control/containers/control.jsx similarity index 74% rename from web/client/src/sections/console/containers/console.jsx rename to web/client/src/sections/control/containers/control.jsx index f2a65e1..ed5b649 100644 --- a/web/client/src/sections/console/containers/console.jsx +++ b/web/client/src/sections/control/containers/control.jsx @@ -1,6 +1,6 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useRef } from 'react'; import { connect } from 'react-redux'; -import { Timeline, Card, Button } from '@douyinfe/semi-ui'; +import { Timeline, Card, Button, Modal, Form } from '@douyinfe/semi-ui'; import { push } from 'react-router-redux'; import '../style.less' import PerfectScrollbar from "perfect-scrollbar"; @@ -17,8 +17,9 @@ let problemsScrollbar; let alarmScrollbar; -const Console = (props) => { - const { dispatch, actions, user, loading, socket } = props +const Control = (props) => { + const { dispatch, actions, user, loading, socket ,pepProjectId} = props + const { control } = actions const stationList = [ 'url(/assets/images/console/lan_1.png)', 'url(/assets/images/console/lv_1.png)', @@ -36,12 +37,37 @@ const Console = (props) => { const [problemsList, setProblemsList] = useState(['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''])//异常&问题列表 const [setup, setSetup] = useState(false); //设置是否显现 const [tableType, setTableType] = useState(''); //localStorage存储名 + const [tool, setTool] = useState(false); //工具添加修改弹窗 + const [alter, setAlter] = useState(false); //工具添加或编辑 + const [compile, setCompile] = useState({}); //工具编辑的内容 + const [toolShow, setToolShow] = useState([]); //工具展示 + const [tableSetup, setTableSetup] = useState([]); //单一表格设置信息 + const [exhibition, setExhibition] = useState({ workbench: [] }); //页面结构 + const [workData, setWorkData] = useState({}); //我的工作台数据 + const FormApi = useRef() + useEffect(() => { + consoleToollink() + + //初始化表格显示设置 + let data = ['workbench'] + data.map(v => { + localStorage.getItem(v) == null + ? localStorage.setItem(v, JSON.stringify(show[v])) + : ""; + attribute(v) + }) }, []) + useEffect(() => { + dispatch(control.geteteConsoleCount({pepProjectId:pepProjectId})).then(res => { + if (res.success) setWorkData(res.payload.data) + }) + }, [pepProjectId]) + useEffect(() => { newScrollbar = new PerfectScrollbar("#news", { suppressScrollX: true, @@ -97,6 +123,13 @@ const Console = (props) => { // dispatch(actions.example.getMembers(user.orgId)) }) + + const consoleToollink = () => { + dispatch(control.getConsoleToollink()).then(res => { + if (res.success) setToolShow(res.payload.data) + }) + } + let Select = { workbench: ['project', 'data', 'app', 'device'], statistical: [], @@ -111,40 +144,36 @@ const Console = (props) => { } let listAll = [ - { name: '关注的项目', sort: 1, key: 'project', data: [], img: 'url(/assets/images/console/lan_1.png)' }, - { name: '数据告警', sort: 2, key: 'data', data: [], img: 'url(/assets/images/console/lv_1.png)' }, - { name: '应用告警', sort: 2, key: 'app', data: [], img: 'url(/assets/images/console/hong_1.png)' }, - { name: '设备告警', sort: 2, key: 'device', data: [], img: 'url(/assets/images/console/hong_1.png)' }, + { name: '关注的项目', sort: 1, key: 'project', data: workData?.projects, img: 'url(/assets/images/console/lan_1.png)' }, + { name: '数据告警', sort: 2, key: 'data', data: workData?.dataSurplus, img: 'url(/assets/images/console/lv_1.png)' }, + { name: '应用告警', sort: 2, key: 'app', data: workData?.appSurplus, img: 'url(/assets/images/console/hong_1.png)' }, + { name: '设备告警', sort: 2, key: 'device', data: workData?.toolSurplus, img: 'url(/assets/images/console/hong_1.png)' }, ] + console.log(workData); + console.log(listAll); + useEffect(() => { + attribute('workbench') + }, [workData]) + const attribute = (title) => { + let take = localStorage.getItem(title) + ? JSON.parse(localStorage.getItem(title)) + : []; + let data = Select[title].map(v => { + let dataTitle = listAll.find(vv => v == vv.key) || {} + return { name: dataTitle.name, value: dataTitle.key } + }) + let TableDisplay = take?.map(v => listAll?.find(vv => v == vv.key)) + TableDisplay.sort((a, b) => a.sort - b.sort) + setExhibition({ ...exhibition, [title]: TableDisplay }) + setTableSetup([{ list: data }]) - // const attribute = (name, route) => { - // let arr = localStorage.getItem(name) - // ? JSON.parse(localStorage.getItem(name)) - // : []; - // // console.log(arr); - // if (route) { - // let setup = tableList[route].map(v => columnAll.find(vv => v == vv.value)) - - // let data = [] - // data.splice(1, 0, ...arr, 'text') - - // let TableDisplay = data?.map(v => { - // let datas = columnAll?.find(vv => v == vv.value) - // if (datas) { - // return { title: datas.name, sort: datas.sort, dataIndex: datas.value, rowKey: datas.value, render: datas.render } - // } - - // }) - // TableDisplay.sort((a, b) => a.sort - b.sort) - // setExhibition(TableDisplay) - // setTableSetup([{ list: setup }]) - // } - // } + } return ( + // 11 ? : <>
{/* 头部 */} @@ -188,7 +217,7 @@ const Console = (props) => { 剩余问题:
- 122 + {(workData?.appSurplus + workData?.dataSurplus + workData?.toolSurplus) || 0}
@@ -197,7 +226,7 @@ const Console = (props) => {
- 12223 + {(workData?.appNewAdd + workData?.dataNewAdd + workData?.toolNewAdd) || 0}
@@ -209,23 +238,23 @@ const Console = (props) => { 今日处理:
- 3 + {(workData?.appConfirme + workData?.dataConfirme + workData?.toolConfirme) || 0}
{/* 循环类型 */}
- {stationList.map((item, index) => { + {exhibition['workbench']?.map((item, index) => { return ( -
+
- 关注的项目 - (个) + {item.name} + {item.name == '关注的项目' ? ' ( 个 )' : ' ( 条 )'}
-
112
-
进行中
+
{item.data}
+ {item.name == '关注的项目' ? '' :
待处理
}
@@ -461,7 +490,7 @@ const Console = (props) => {
{/* 我常用的工具 */} -
+
@@ -469,66 +498,140 @@ const Console = (props) => {
MY USUAL TOOLS
-
{ - console.log(111111); - document.getElementById('aa').style.display = 'none' - }} - style={{ marginTop: 24, position: 'relative', display: "inline-block" }}> - -
-
{ - + {toolShow.length > 0 ? + toolShow?.map(v => +
{ + document.getElementById(v.id + 'name').style.display = 'none' }} - >编辑
-
{ + id={v.id + v.name} + style={{ marginTop: 24, position: 'relative', display: "inline-block", cursor: 'pointer' }}> + + + +
+
{ + setTool(true) + setAlter(true) + setCompile({ id: v.id, name: v.name, link: v.link, }) + }} + >编辑
+
{ + dispatch(control.deleteConsoleToollink(v.id)).then(res => { + if (res.success) consoleToollink() + }) + }} + >删除
+
+
) : ""} - }} - >删除
-
-
+
-
75 + - {setup ? ( - { - setSetup(false); - attribute(tableType[route], route); - setTableType('') - }} - /> - ) : ( - "" - )} + {/* 页面各个设置弹窗 */} + { + setup ? ( + { + setSetup(false); + attribute(tableType); + setTableType('') + }} + /> + ) : ( + "" + ) + } + {/* 工具添加修改弹窗 */} + {tool ? { + setTool(false) + setAlter(false) + setCompile({}) + }} + onOk={() => { + FormApi.current.validate().then((v) => { + console.log(v); + dispatch(control.putConsoleToollink({ id: compile?.id, name: v.name, link: v.link })).then(res => { + if (res.success) { + setTool(false) + setAlter(false) + setCompile({}) + consoleToollink() + } + }) + }) + }} + > +
+
console.log(values)} + getFormApi={(formApi) => (FormApi.current = formApi)} + > + + + +
+
: "" + } ) } @@ -538,10 +641,11 @@ function mapStateToProps (state) { return { // loading: members.isRequesting, user: auth.user, - // actions: global.actions, + actions: global.actions, + pepProjectId: global.pepProjectId, // members: members.data, // socket: webSocket.socket }; } -export default connect(mapStateToProps)(Console); +export default connect(mapStateToProps)(Control); diff --git a/web/client/src/sections/control/containers/index.js b/web/client/src/sections/control/containers/index.js new file mode 100644 index 0000000..cfeb914 --- /dev/null +++ b/web/client/src/sections/control/containers/index.js @@ -0,0 +1,6 @@ +'use strict'; + +import Control from './control'; +import UserCenter from './userCenter'; + +export { Control, UserCenter }; \ No newline at end of file diff --git a/web/client/src/sections/console/containers/userCenter.jsx b/web/client/src/sections/control/containers/userCenter.jsx similarity index 100% rename from web/client/src/sections/console/containers/userCenter.jsx rename to web/client/src/sections/control/containers/userCenter.jsx diff --git a/web/client/src/sections/console/index.js b/web/client/src/sections/control/index.js similarity index 92% rename from web/client/src/sections/console/index.js rename to web/client/src/sections/control/index.js index 062b5bd..1c3db38 100644 --- a/web/client/src/sections/console/index.js +++ b/web/client/src/sections/control/index.js @@ -6,7 +6,7 @@ import actions from './actions'; import { getNavItem } from './nav-item'; export default { - key: 'console', + key: 'control', name: '控制台', reducers: reducers, routes: routes, diff --git a/web/client/src/sections/console/nav-item.jsx b/web/client/src/sections/control/nav-item.jsx similarity index 79% rename from web/client/src/sections/console/nav-item.jsx rename to web/client/src/sections/control/nav-item.jsx index 86797e6..f3c9d19 100644 --- a/web/client/src/sections/console/nav-item.jsx +++ b/web/client/src/sections/control/nav-item.jsx @@ -5,9 +5,9 @@ export function getNavItem (user, dispatch) { return ( [ { - itemKey: 'console', + itemKey: 'control', text: '控制台', - to: '/console', + to: '/control', // icon: , }, ] diff --git a/web/client/src/sections/console/reducers/index.js b/web/client/src/sections/control/reducers/index.js similarity index 100% rename from web/client/src/sections/console/reducers/index.js rename to web/client/src/sections/control/reducers/index.js diff --git a/web/client/src/sections/console/routes.js b/web/client/src/sections/control/routes.js similarity index 71% rename from web/client/src/sections/console/routes.js rename to web/client/src/sections/control/routes.js index 7a9c027..a7904ed 100644 --- a/web/client/src/sections/console/routes.js +++ b/web/client/src/sections/control/routes.js @@ -1,13 +1,13 @@ 'use strict'; -import { Console,UserCenter } from './containers'; +import { Control,UserCenter } from './containers'; export default [{ type: 'inner', route: { - path: '/console', - key: 'console', + path: '/control', + key: 'control', breadcrumb: '控制台', - component: Console, + component: Control, // 不设置 component 则面包屑禁止跳转 } }, { diff --git a/web/client/src/sections/console/style.less b/web/client/src/sections/control/style.less similarity index 100% rename from web/client/src/sections/console/style.less rename to web/client/src/sections/control/style.less diff --git a/web/client/src/sections/problem/components/tableData.jsx b/web/client/src/sections/problem/components/tableData.jsx index 2a1e911..1f4fa02 100644 --- a/web/client/src/sections/problem/components/tableData.jsx +++ b/web/client/src/sections/problem/components/tableData.jsx @@ -26,8 +26,8 @@ const TableData = ({ route, dispatch, actions, collectData, setSetup, exhibition let typeData = { element: "元素异常", apiError: "接口报错 ", timeout: "加载超时" } let tableDatas = res.payload.data?.rows.map(v => ({ key: v.id, - projectName: v.app?.projectCorrelations?.map(r => (r.name ? { name: r.name, state: 'PMOS' } : { - name: r.pepProject?.projectName, state: r.pepProject?.constructionStatus + projectName: v.app?.projectCorrelations?.map(r => (r.name ? { id: r.id, name: r.name, state: 'PMOS' } : { + id: r.id, name: r.pepProject?.projectName, state: r.pepProject?.constructionStatus }))?.filter(c => c), appName: v.app?.name, url: v.app?.url, @@ -59,8 +59,8 @@ const TableData = ({ route, dispatch, actions, collectData, setSetup, exhibition let tableDatas = res.payload.data?.map(v => ({ key: v.alarmId, StructureName: v.struc, - projectName: v.pomsProject?.map(r => (r.name ? { name: r.name, state: 'PMOS' } : { - name: r.pepProject?.projectName, state: r.pepProject?.constructionStatus + projectName: v.pomsProject?.map(r => (r.name ? { id: r.id, name: r.name, state: 'PMOS' } : { + id: r.id, name: r.pepProject?.projectName, state: r.pepProject?.constructionStatus }))?.filter(c => c), createTime: v.createTime ? moment(v.createTime).format("YYYY-MM-DD HH:mm:ss") : "", updateTime: v.updateTime ? moment(v.updateTime).format("YYYY-MM-DD HH:mm:ss") : "", @@ -107,8 +107,8 @@ const TableData = ({ route, dispatch, actions, collectData, setSetup, exhibition let tableDatas = res.payload.data?.rows?.map(v => ({ key: v.AlarmId, StructureName: v.StructureName, - projectName: v.pomsProject?.map(r => (r.name ? { name: r.name, state: 'PMOS' } : { - name: r.pepProject?.projectName, state: r.pepProject?.constructionStatus + projectName: v.pomsProject?.map(r => (r.name ? { id: r.id, name: r.name, state: 'PMOS' } : { + id: r.id, name: r.pepProject?.projectName, state: r.pepProject?.constructionStatus }))?.filter(c => c), createTime: v.StartTime ? moment(v.StartTime).format("YYYY-MM-DD HH:mm:ss") : "", updateTime: v.EndTime ? moment(v.EndTime).format("YYYY-MM-DD HH:mm:ss") : "", @@ -187,6 +187,7 @@ const TableData = ({ route, dispatch, actions, collectData, setSetup, exhibition labelPosition="left" field={v.field} key={v.field} + maxLength="10" style={{ width: 116, marginRight: 16, color: "#F9F9F9", }} placeholder="全部" filter @@ -263,7 +264,7 @@ const TableData = ({ route, dispatch, actions, collectData, setSetup, exhibition columns={exhibition} dataSource={route == 'useAbnormal' || route == 'videoAbnormal' ? tableData.slice(query.page * query.limit, (query.page + 1) * query.limit) || [] : tableData} bordered={false} - empty="暂无数据" + empty={
暂无告警数据
} style={{}} pagination={false} onRow={(record, index) => { diff --git a/web/client/src/sections/problem/containers/dataAlarm.jsx b/web/client/src/sections/problem/containers/dataAlarm.jsx index e0599dc..92689e8 100644 --- a/web/client/src/sections/problem/containers/dataAlarm.jsx +++ b/web/client/src/sections/problem/containers/dataAlarm.jsx @@ -37,7 +37,7 @@ 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('') @@ -136,10 +136,10 @@ const DataAlarm = ({ match, dispatch, actions, user, loading, socket, iotVcmpWeb }], deviceAbnormal: [ // 设备告警(deviceAbnormal) { name: '搜索', field: '1' }, - { - name: '设备类型', field: 'groupUnitId', - data: genre - }, + // { + // name: '设备类型', field: 'groupUnitId', + // data: genre + // }, { name: '异常状态', field: 'state', data: [ @@ -161,11 +161,11 @@ const DataAlarm = ({ match, dispatch, actions, user, loading, socket, iotVcmpWeb //表格设置信息 const tableList = { dataLnterrupt: ['index', 'projectName', 'StructureName', 'SourceName', 'AlarmGroupUnit', 'AlarmCodeName', 'sustainTime', 'createTime', 'AlarmContent', 'CurrentLevel', 'updateTime', 'detailCount', 'confirm', 'confirmTime',], - dataAbnormal: ['index', 'projectName', 'StructureName', 'SourceName', 'type', 'createTime', 'sustainTime', 'AlarmContent', 'CurrentLevel', 'updateTime', 'detailCount', 'confirm', 'confirmTime'], + dataAbnormal: ['index', 'projectName', 'StructureName', 'SourceName', 'type', 'createTime', 'sustainTime', 'AlarmContent', 'CurrentLevel', 'updateTime', 'detailCount', 'confirm', 'confirmTime'], strategyHit: ['index', 'projectName', 'StructureName', 'SourceName', 'Strategy', 'State', 'createTime', 'sustainTime', 'AlarmContent', 'CurrentLevel', 'updateTime', 'detailCount', 'confirm', 'confirmTime'], - videoAbnormal: ['index', 'projectName', 'StructureName', 'SourceName', 'station', 'cameraKindId', 'sustainTime', 'venderName', 'point', 'cameraSerialNo', 'cameraChannelNo', 'platform', 'AlarmContent', 'resolve', 'createTime', 'updateTime', 'confirm', 'confirmTime', 'camerOnline'], + videoAbnormal: ['index', 'projectName', 'StructureName', 'SourceName', 'station', 'cameraKindId', 'sustainTime', 'venderName', 'point', 'cameraSerialNo', 'cameraChannelNo', 'platform', 'AlarmContent', 'resolve', 'createTime', 'updateTime', 'confirm', 'confirmTime',], useAbnormal: ['index', 'projectName', 'appName', 'url', 'type', 'alarmContent', 'createTime', 'sustainTime', 'updateTime', 'confirm', 'confirmTime'], - deviceAbnormal: ['index', 'projectName', 'StructureName', 'SourceName', 'station', 'type', 'cameraKindId', 'sustainTime', 'venderName', 'AlarmContent', 'AlarmCodeName', 'createTime', 'updateTime', 'confirm', 'confirmTime'], + deviceAbnormal: ['index', 'projectName', 'StructureName', 'SourceName', 'station', 'sustainTime', 'venderName', 'AlarmContent', 'AlarmCodeName', 'createTime', 'updateTime', 'confirm', 'confirmTime'], } //表格默认配置信息 const columns = { @@ -309,6 +309,7 @@ const DataAlarm = ({ match, dispatch, actions, user, loading, socket, iotVcmpWeb : r.State == 3 || r.autoRestore || r.confirmAuto ? : @@ -383,7 +384,19 @@ 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,//异常类型 + } + }; + } return ( // route=='dataLnterrupt'|| route=='dataAbnormal'|| route=='strategyHit'? @@ -444,10 +457,10 @@ const DataAlarm = ({ match, dispatch, actions, user, loading, socket, iotVcmpWeb width={600} onCancel={() => setConfirm(false)} onOk={() => { - + let confirmPost = getAlarmConfirmItem(); if (route == 'useAbnormal') { TextAreaApi.current.validate().then((v) => { - dispatch(problem.postApiConfirm({ appAlarmId: selected, confirm: content })).then(res => { + dispatch(problem.postApiConfirm({ appAlarmId: selected, confirm: content, confirmPost })).then(res => { if (res.success) { setConfirm(false) setSelected([]) @@ -457,7 +470,7 @@ const DataAlarm = ({ match, dispatch, actions, user, loading, socket, iotVcmpWeb }) } else if (route == 'videoAbnormal') { TextAreaApi.current.validate().then((v) => { - dispatch(problem.putAlarmVideoConfirm({ alarmId: selected, content: content })).then(res => { + dispatch(problem.putAlarmVideoConfirm({ alarmId: selected, content: content, confirmPost })).then(res => { if (res.success) { setConfirm(false) setSelected([]) @@ -476,7 +489,7 @@ const DataAlarm = ({ match, dispatch, actions, user, loading, socket, iotVcmpWeb }) } else { TextAreaApi.current.validate().then((v) => { - dispatch(problem.putAlarmdataConfirm({ alarmId: selected, content: content })).then(res => { + dispatch(problem.putAlarmdataConfirm({ alarmId: selected, content: content, confirmPost })).then(res => { if (res.success) { setConfirm(false) let data = tableData?.map(v => { @@ -524,6 +537,7 @@ const DataAlarm = ({ match, dispatch, actions, user, loading, socket, iotVcmpWeb : ""} + {/* 视频的播放 */} {videoModal ?