diff --git a/api/.vscode/launch.json b/api/.vscode/launch.json index c06ba73..689d295 100644 --- a/api/.vscode/launch.json +++ b/api/.vscode/launch.json @@ -22,7 +22,12 @@ "--apiEmisUrl http://10.8.30.112:14000", "--godUrl https://restapi.amap.com/v3", "--godKey 21c2d970e1646bb9a795900dd00093ce", - "--mqttVideoServer mqtt://10.8.30.71:30883" + "--mqttVideoServer mqtt://10.8.30.71:30883", + "--qnak XuDgkao6cL0HidoMAPnA5OB10Mc_Ew08mpIfRJK5", + "--qnsk yewcieZLzKZuDfig0wLZ9if9jKp2P_1jd3CMJPSa", + "--qnbkt dev-operational-service", + // "--qndmn http://resources.anxinyun.cn", + "--qndmn http://rhvqdivo5.hn-bkt.clouddn.com", ] }, { diff --git a/api/app/lib/controllers/alarm/app.js b/api/app/lib/controllers/alarm/app.js new file mode 100644 index 0000000..0a13f29 --- /dev/null +++ b/api/app/lib/controllers/alarm/app.js @@ -0,0 +1,128 @@ +'use strict'; + +const moment = require('moment') + +async function inspection (ctx) { + try { + const models = ctx.fs.dc.models; + const { projectAppId, screenshot = [] } = ctx.request.body + + const now = moment().format() + const storageData = screenshot.map(s => { + return { + projectAppId, + screenshot: [s], + createTime: now, + } + }) + + await models.AppInspection.bulkCreate(storageData) + + 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 notedInspection (ctx) { + try { + const models = ctx.fs.dc.models; + const { inspectionId } = ctx.request.body + const { userId } = ctx.fs.api + + await models.AppInspection.update({ + notedPepUserId: userId, + notedTime: moment().format() + }, { + where: { + id: inspectionId + } + }) + + 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 apiError (ctx) { + try { + const models = ctx.fs.dc.models; + const { projectAppId, alarmContent, router, statusCode } = ctx.request.body + const now = moment().format() + + let storageData = { + projectAppId, alarmContent, router, statusCode + } + const existRes = await models.AppAlarm.findOne({ + where: { + projectAppId, alarmContent, router, statusCode, + confirm: null + } + }) + if (existRes) { + await models.AppAlarm.update({ + updateTime: now + }, { + where: { + id: existRes.id + } + }) + } else { + const existCount = await models.AppAlarm.count({ + where: { + + } + }) + storageData.serialNumber = 'WEB' + (existCount < 9 ? '0' + (existCount + 1) : existCount) + storageData.createTime = now + await models.AppAlarm.create(storageData) + } + + 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 + } + } +} + +async function confirmApiError (ctx) { + try { + const models = ctx.fs.dc.models; + const { confirm, appAlarmId } = ctx.request.body + + await models.AppAlarm.update({ + confirm, + }, { + where: { + id: appAlarmId + } + }) + + 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 = { + inspection, + notedInspection, + apiError, + confirmApiError, +}; \ No newline at end of file diff --git a/api/app/lib/controllers/auth/index.js b/api/app/lib/controllers/auth/index.js index 2f4823d..4a751d5 100644 --- a/api/app/lib/controllers/auth/index.js +++ b/api/app/lib/controllers/auth/index.js @@ -19,18 +19,48 @@ async function login (ctx, next) { } else { const pomsRegisterRes = await models.User.findOne({ where: { - pepUserId: emisLoginRes.id + pepUserId: emisLoginRes.id, + $or: { + deleted: false, + role: { $contains: ['admin'] } + } } }) if (!pomsRegisterRes) { throw '当前账号尚未在此系统启用' + } else if ( + pomsRegisterRes.disable && (!pomsRegisterRes.role || !pomsRegisterRes.role.includes('admin')) + ) { + throw '当前账号已禁用' } emisLoginRes.pomsUserInfo = pomsRegisterRes.dataValues - await models.User.update({ + let userUpdateData = { lastInTime: moment().format(), - inTimes: pomsRegisterRes.inTimes + 1 - }, { + inTimes: pomsRegisterRes.inTimes + 1, + lastInAddress: '' + } + try { + // 获取ip转为地点并记录 + let ip = + // '117.90.39.49' || + ctx.ip + if (ip && /^(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|[1-9])\.(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)\.(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)\.(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)$/.test(ip)) { + const ipLocationRes = await ctx.app.fs.godRequest.post('ip', { + query: { + ip, + } + }) + if (ipLocationRes) { + userUpdateData.lastInAddress = ipLocationRes.province + ipLocationRes.city + } + } + } catch (error) { + ctx.fs.logger.error(`IP GET, error: ${error}`); + } + + + await models.User.update(userUpdateData, { where: { id: emisLoginRes.id } diff --git a/api/app/lib/controllers/organization/index.js b/api/app/lib/controllers/organization/index.js new file mode 100644 index 0000000..36e2e70 --- /dev/null +++ b/api/app/lib/controllers/organization/index.js @@ -0,0 +1,157 @@ +'use strict'; + +async function allDeps (ctx) { + try { + const models = ctx.fs.dc.models; + const { redis } = ctx.app + + let depRes = await redis.get('allDepartments') + if (depRes) { + depRes = JSON.parse(depRes) + depRes = depRes.departments + } + + ctx.status = 200; + ctx.body = depRes || [] + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + + } + } +} + +async function editUser (ctx) { + try { + const models = ctx.fs.dc.models; + const { pepUserId, role = [], correlationProject = [] } = ctx.request.body + + const existUserRes = await models.User.findOne({ + where: { + pepUserId + } + }) + + let storageData = { + pepUserId, + role, + correlationProject, + updateTime: moment().format() + } + if (existUserRes) { + // 存在且传递id 或者 不传id也存在 + // 修改 update + if ( + role.includes('admin') + ) { + storageData.role = [...new Set([...existUserRes.role, ...role])] + storageData.disabled = false + } + await models.User.update(storageData, { + where: { + pepUserId + } + }) + } else { + // 新增 + await models.User.create(storageData) + } + + ctx.status = 200 + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + + } + } +} + +async function putUser (ctx) { + try { + const models = ctx.fs.dc.models; + const { pomsUserId } = ctx.params + const { disabled = undefined, deleted = undefined } = ctx.request.body + const updateData = { + disabled, + deleted, + } + for (let k in updateData) { + if (updateData[k] == undefined) { + delete updateData[k] + } + } + await models.User.update(updateData, { + where: { + id: pomsUserId + } + }) + ctx.status = 204 + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + + } + } +} + +async function user (ctx) { + try { + const models = ctx.fs.dc.models; + const { role, limit, page, } = ctx.query + + const excludeField = ['lastInTime', 'inTimes', 'onlineDuration', 'lastInAddress', 'deleted', 'updateTime'] + + let findOption = { + attributes: { + exclude: excludeField, + }, + where: { + deleted: false, + }, + order: [['updateTime', 'DESC']] + } + if (role) { + findOption.where.role = { $contains: [role] } + } + if (limit) { + findOption.limit = limit + } + if (page && limit) { + findOption.offset = page * limit + } + + const userRes = await models.User.findAndCountAll(findOption) + + const adminRes = await models.User.findAll({ + where: { + role: { $contains: ['admin'] } + }, + attributes: { + exclude: excludeField, + }, + order: [['updateTime', 'DESC']] + }) + + ctx.status = 200 + ctx.body = { + admin: adminRes, + users: userRes + } + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + + } + } +} + +module.exports = { + allDeps, + editUser, + putUser, + user, +}; \ No newline at end of file diff --git a/api/app/lib/controllers/project/index.js b/api/app/lib/controllers/project/index.js new file mode 100644 index 0000000..744b3b7 --- /dev/null +++ b/api/app/lib/controllers/project/index.js @@ -0,0 +1,25 @@ +'use strict'; + +async function appList (ctx) { + try { + const models = ctx.fs.dc.models; + + const appRes = await models.ProjectApp.findAll({ + attributes: { + exclude: ['projectId'] + } + }) + ctx.status = 200; + ctx.body = appRes + } 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 +}; \ No newline at end of file diff --git a/api/app/lib/middlewares/authenticator.js b/api/app/lib/middlewares/authenticator.js index 53e8c5b..ac08b3c 100644 --- a/api/app/lib/middlewares/authenticator.js +++ b/api/app/lib/middlewares/authenticator.js @@ -8,42 +8,42 @@ const util = require('util'); const moment = require('moment'); class ExcludesUrls { - constructor(opts) { - this.allUrls = undefined; - this.reload(opts); - } + constructor(opts) { + this.allUrls = undefined; + this.reload(opts); + } - sanitizePath (path) { - if (!path) return '/'; - const p = '/' + path.replace(/^\/+/i, '').replace(/\/+$/, '').replace(/\/{2,}/, '/'); - return p; - } + sanitizePath (path) { + if (!path) return '/'; + const p = '/' + path.replace(/^\/+/i, '').replace(/\/+$/, '').replace(/\/{2,}/, '/'); + return p; + } - reload (opts) { - // load all url - if (!this.allUrls) { - this.allUrls = opts; - let that = this; - this.allUrls.forEach(function (url, i, arr) { - if (typeof url === "string") { - url = { p: url, o: '*' }; - arr[i] = url; - } - const keys = []; - let eachPath = url.p; - url.p = (!eachPath || eachPath === '(.*)' || util.isRegExp(eachPath)) ? eachPath : that.sanitizePath(eachPath); - url.pregexp = pathToRegexp(eachPath, keys); - }); - } - } + reload (opts) { + // load all url + if (!this.allUrls) { + this.allUrls = opts; + let that = this; + this.allUrls.forEach(function (url, i, arr) { + if (typeof url === "string") { + url = { p: url, o: '*' }; + arr[i] = url; + } + const keys = []; + let eachPath = url.p; + url.p = (!eachPath || eachPath === '(.*)' || util.isRegExp(eachPath)) ? eachPath : that.sanitizePath(eachPath); + url.pregexp = pathToRegexp(eachPath, keys); + }); + } + } - isExcluded (path, method) { - return this.allUrls.some(function (url) { - return !url.auth - && url.pregexp.test(path) - && (url.o === '*' || url.o.indexOf(method) !== -1); - }); - } + isExcluded (path, method) { + return this.allUrls.some(function (url) { + return !url.auth + && url.pregexp.test(path) + && (url.o === '*' || url.o.indexOf(method) !== -1); + }); + } } /** @@ -53,92 +53,94 @@ class ExcludesUrls { * @param {*} method 当前request的method */ let isPathExcluded = function (opts, path, method) { - let excludeAll = Boolean(opts.exclude && opts.exclude.length && opts.exclude[0] == '*'); - let excludes = null; - if (!excludeAll) { - let excludeOpts = opts.exclude || []; - excludeOpts.push({ p: '/login', o: 'POST' }); - excludeOpts.push({ p: '/logout', o: 'PUT' }); - excludes = new ExcludesUrls(excludeOpts); - } - let excluded = excludeAll || excludes.isExcluded(path, method); - return excluded; + let excludeAll = Boolean(opts.exclude && opts.exclude.length && opts.exclude[0] == '*'); + let excludes = null; + if (!excludeAll) { + let excludeOpts = opts.exclude || []; + excludeOpts.push({ p: '/login', o: 'POST' }); + excludeOpts.push({ p: '/logout', o: 'PUT' }); + excludes = new ExcludesUrls(excludeOpts); + } + let excluded = excludeAll || excludes.isExcluded(path, method); + return excluded; }; let authorizeToken = async function (ctx, token) { - let rslt = null; - const tokenFormatRegexp = /^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$/g; - if (token && tokenFormatRegexp.test(token)) { - try { - const expired = await ctx.redis.hget(token, 'expired'); + let rslt = null; + const tokenFormatRegexp = /^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$/g; + if (token && tokenFormatRegexp.test(token)) { + try { - if (expired && moment().valueOf() <= moment(expired).valueOf()) { - const userInfo = JSON.parse(await ctx.redis.hget(token, 'userInfo')); - rslt = { - 'authorized': userInfo.authorized, - 'resources': (userInfo || {}).resources || [], - }; - ctx.fs.api.userId = userInfo.id; - ctx.fs.api.userInfo = userInfo; - ctx.fs.api.token = token; - } - } catch (err) { - const { error } = err.response || {}; - ctx.fs.logger.log('[anxinyun]', '[AUTH] failed', (error || {}).message || `cannot GET /users/${token}`); - } - } - return rslt; + const authorizeRes = await ctx.app.fs.emisRequest.get('authorize', { + query: { token } + }) + const { userInfo, expired } = authorizeRes; + if (expired && moment().valueOf() <= moment(expired).valueOf()) { + rslt = { + 'authorized': userInfo.authorized, + 'resources': (userInfo || {}).resources || [], + }; + ctx.fs.api.userId = userInfo.id; + ctx.fs.api.userInfo = userInfo; + ctx.fs.api.token = token; + } + } catch (err) { + const { error } = err.response || {}; + ctx.fs.logger.log('[anxinyun]', '[AUTH] failed', (error || {}).message || `cannot GET /users/${token}`); + } + } + return rslt; }; let isResourceAvailable = function (resources, options) { - let authCode = null; - // authorize user by authorization attribute - const { authAttr, method, path } = options; - for (let prop in authAttr) { - let keys = []; - let re = pathToRegexp(prop.replace(/\:[A-Za-z_\-]+\b/g, '(\\d+)'), keys); - if (re.test(`${method}${path}`)) { - authCode = authAttr[prop]; - break; - } - } - return !authCode || (resources || []).some(code => code === authCode); + let authCode = null; + // authorize user by authorization attribute + const { authAttr, method, path } = options; + for (let prop in authAttr) { + let keys = []; + let re = pathToRegexp(prop.replace(/\:[A-Za-z_\-]+\b/g, '(\\d+)'), keys); + if (re.test(`${method}${path}`)) { + authCode = authAttr[prop]; + break; + } + } + return !authCode || (resources || []).some(code => code === authCode); }; function factory (app, opts) { - return async function auth (ctx, next) { - const { path, method, header, query } = ctx; - ctx.fs.logger.log('[AUTH] start', path, method); - ctx.fs.api = ctx.fs.api || {}; - ctx.fs.port = opts.port; - ctx.redis = app.redis; - ctx.redisTools = app.redisTools; - let error = null; - if (path) { - if (!isPathExcluded(opts, path, method)) { - const user = await authorizeToken(ctx, header.token || query.token); - if (user && user.authorized) { - // if (!isResourceAvailable(user.resources, { authAttr: app.fs.auth.authAttr, path, method })) { - // error = { status: 403, name: 'Forbidden' } - // } else { - // error = { status: 401, name: 'Unauthorized' } - // } - } else { - error = { status: 401, name: 'Unauthorized' } - } + return async function auth (ctx, next) { + const { path, method, header, query } = ctx; + ctx.fs.logger.log('[AUTH] start', path, method); + ctx.fs.api = ctx.fs.api || {}; + ctx.fs.port = opts.port; + ctx.redis = app.redis; + ctx.redisTools = app.redisTools; + let error = null; + if (path) { + if (!isPathExcluded(opts, path, method)) { + const user = await authorizeToken(ctx, header.token || query.token); + if (user && user.authorized) { + // if (!isResourceAvailable(user.resources, { authAttr: app.fs.auth.authAttr, path, method })) { + // error = { status: 403, name: 'Forbidden' } + // } else { + // error = { status: 401, name: 'Unauthorized' } + // } + } else { + error = { status: 401, name: 'Unauthorized' } } - } else { - error = { status: 401, name: 'Unauthorized' }; - } - if (error) { - ctx.fs.logger.log('[AUTH] failed', path, method); - ctx.status = error.status; - ctx.body = error.name; - } else { - ctx.fs.logger.log('[AUTH] passed', path, method); - await next(); - } - } + } + } else { + error = { status: 401, name: 'Unauthorized' }; + } + if (error) { + ctx.fs.logger.log('[AUTH] failed', path, method); + ctx.status = error.status; + ctx.body = error.name; + } else { + ctx.fs.logger.log('[AUTH] passed', path, method); + await next(); + } + } } module.exports = factory; diff --git a/api/app/lib/models/app_alarm.js b/api/app/lib/models/app_alarm.js index f179e2b..6d6bdbb 100644 --- a/api/app/lib/models/app_alarm.js +++ b/api/app/lib/models/app_alarm.js @@ -24,18 +24,18 @@ module.exports = dc => { field: "serial_number", autoIncrement: false }, - pepProjectId: { + projectAppId: { type: DataTypes.INTEGER, allowNull: true, defaultValue: null, comment: "对应的项目id", primaryKey: false, - field: "pep_project_id", + field: "project_app_id", autoIncrement: false }, appDomain: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, defaultValue: null, comment: "应用域名", primaryKey: false, @@ -77,6 +77,24 @@ module.exports = dc => { primaryKey: false, field: "confirm", autoIncrement: false + }, + router: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + comment: "路由", + primaryKey: false, + field: "router", + autoIncrement: false + }, + statusCode: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + comment: "状态码", + primaryKey: false, + field: "status_code", + autoIncrement: false } }, { tableName: "app_alarm", diff --git a/api/app/lib/models/app_inspection.js b/api/app/lib/models/app_inspection.js index 550d1b4..84d6235 100644 --- a/api/app/lib/models/app_inspection.js +++ b/api/app/lib/models/app_inspection.js @@ -15,14 +15,18 @@ module.exports = dc => { autoIncrement: true, unique: "app_inspection_id_uindex" }, - pepProjectId: { + projectAppId: { type: DataTypes.INTEGER, allowNull: true, defaultValue: null, comment: null, primaryKey: false, - field: "pep_project_id", - autoIncrement: false + field: "project_app_id", + autoIncrement: false, + references: { + key: "id", + model: "projectApp" + } }, createTime: { type: DataTypes.DATE, @@ -41,6 +45,24 @@ module.exports = dc => { primaryKey: false, field: "screenshot", autoIncrement: false + }, + notedPepUserId: { + type: DataTypes.INTEGER, + allowNull: true, + defaultValue: null, + comment: "核验人员", + primaryKey: false, + field: "noted_pep_user_id", + autoIncrement: false + }, + notedTime: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: null, + comment: null, + primaryKey: false, + field: "noted_time", + autoIncrement: false } }, { tableName: "app_inspection", diff --git a/api/app/lib/models/user.js b/api/app/lib/models/user.js index 67c002d..9f1461e 100644 --- a/api/app/lib/models/user.js +++ b/api/app/lib/models/user.js @@ -2,87 +2,114 @@ 'use strict'; module.exports = dc => { - const DataTypes = dc.ORM; - const sequelize = dc.orm; - const User = sequelize.define("user", { - id: { - type: DataTypes.INTEGER, - allowNull: false, - defaultValue: null, - comment: null, - primaryKey: true, - field: "id", - autoIncrement: true, - unique: "user_id_uindex" - }, - pepUserId: { - type: DataTypes.INTEGER, - allowNull: false, - defaultValue: null, - comment: "项企对应用户id", - primaryKey: false, - field: "pep_user_id", - autoIncrement: false - }, - role: { - type: DataTypes.ARRAY(DataTypes.STRING), - allowNull: true, - defaultValue: null, - comment: "角色 也对应权限 admin 管理员 / all 全部角色 / data_analyst 数据分析 / after_sale 售后运维 / resource_manage 资源管理 / customer_service 客户服务", - primaryKey: false, - field: "role", - autoIncrement: false - }, - correlationProject: { - type: DataTypes.ARRAY(DataTypes.INTEGER), - allowNull: true, - defaultValue: null, - comment: "关联的项目管理的项目id", - primaryKey: false, - field: "correlation_project", - autoIncrement: false - }, - lastInTime: { - type: DataTypes.DATE, - allowNull: true, - defaultValue: null, - comment: null, - primaryKey: false, - field: "last_in_time", - autoIncrement: false - }, - inTimes: { - type: DataTypes.INTEGER, - allowNull: false, - defaultValue: "0", - comment: null, - primaryKey: false, - field: "in_times", - autoIncrement: false - }, - onlineDuration: { - type: DataTypes.INTEGER, - allowNull: true, - defaultValue: null, - comment: "在线时长 单位 s", - primaryKey: false, - field: "online_duration", - autoIncrement: false - }, - lastInAddress: { - type: DataTypes.STRING, - allowNull: true, - defaultValue: null, - comment: "上次登录地点", - primaryKey: false, - field: "last_in_address", - autoIncrement: false - } - }, { - tableName: "user", - comment: "", - indexes: [] - }); - dc.models.User = User; - return User; + const DataTypes = dc.ORM; + const sequelize = dc.orm; + const User = sequelize.define("user", { + id: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: true, + field: "id", + autoIncrement: true, + unique: "user_id_uindex" + }, + pepUserId: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: null, + comment: "项企对应用户id", + primaryKey: false, + field: "pep_user_id", + autoIncrement: false + }, + role: { + type: DataTypes.ARRAY(DataTypes.STRING), + allowNull: true, + defaultValue: null, + comment: "角色 也对应权限 admin 管理员 / all 全部角色 / data_analyst 数据分析 / after_sale 售后运维 / resource_manage 资源管理 / customer_service 客户服务", + primaryKey: false, + field: "role", + autoIncrement: false + }, + correlationProject: { + type: DataTypes.ARRAY(DataTypes.INTEGER), + allowNull: true, + defaultValue: null, + comment: "关联的项目管理的项目id", + primaryKey: false, + field: "correlation_project", + autoIncrement: false + }, + lastInTime: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: null, + comment: null, + primaryKey: false, + field: "last_in_time", + autoIncrement: false + }, + inTimes: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: "0", + comment: null, + primaryKey: false, + field: "in_times", + autoIncrement: false + }, + onlineDuration: { + type: DataTypes.INTEGER, + allowNull: true, + defaultValue: "0", + comment: "在线时长 单位 s", + primaryKey: false, + field: "online_duration", + autoIncrement: false + }, + lastInAddress: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + comment: "上次登录地点", + primaryKey: false, + field: "last_in_address", + autoIncrement: false + }, + disabled: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "disabled", + autoIncrement: false + }, + deleted: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: null, + comment: null, + primaryKey: false, + field: "deleted", + autoIncrement: false + }, + updateTime: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: sequelize.fn('now'), + comment: null, + primaryKey: false, + field: "update_time", + autoIncrement: false + } + }, { + tableName: "user", + comment: "", + indexes: [] + }); + dc.models.User = User; + return User; }; \ No newline at end of file diff --git a/api/app/lib/routes/alarm/index.js b/api/app/lib/routes/alarm/index.js new file mode 100644 index 0000000..50dc0cb --- /dev/null +++ b/api/app/lib/routes/alarm/index.js @@ -0,0 +1,20 @@ + + +'use strict'; + +const application = require('../../controllers/alarm/app'); + +module.exports = function (app, router, opts) { + // 应用告警 + app.fs.api.logAttr['POST/alarm/application/inspection'] = { content: '保存应用巡检信息', visible: true }; + router.post('/alarm/application/inspection', application.inspection); + + app.fs.api.logAttr['PUT/alarm/application/noted'] = { content: '保存检验状态', visible: true }; + router.put('/alarm/application/noted', application.notedInspection); + + app.fs.api.logAttr['POST/alarm/application/api'] = { content: '保存应用接口错误信息', visible: true }; + router.post('/alarm/application/api', application.apiError); + + app.fs.api.logAttr['POST/alarm/application/api_confirm'] = { content: '确认应用接口错误信息', visible: true }; + router.post('/alarm/application/api_confirm', application.confirmApiError); +}; diff --git a/api/app/lib/routes/auth/index.js b/api/app/lib/routes/auth/index.js index 6ed595d..c4df619 100644 --- a/api/app/lib/routes/auth/index.js +++ b/api/app/lib/routes/auth/index.js @@ -3,15 +3,9 @@ const auth = require('../../controllers/auth'); module.exports = function (app, router, opts) { - /** - * @api {Post} login 登录. - * @apiVersion 1.0.0 - * @apiGroup Auth - */ app.fs.api.logAttr['POST/login'] = { content: '登录', visible: true }; router.post('/login', auth.login); app.fs.api.logAttr['PUT/logout'] = { content: '登出', visible: false }; router.put('/logout', auth.logout); - -}; +}; \ No newline at end of file diff --git a/api/app/lib/routes/organization/index.js b/api/app/lib/routes/organization/index.js new file mode 100644 index 0000000..d7010d9 --- /dev/null +++ b/api/app/lib/routes/organization/index.js @@ -0,0 +1,17 @@ +'use strict'; + +const organization = require('../../controllers/organization'); + +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['POST/organization/user'] = { content: '编辑成员', visible: true }; + router.post('/organization/user', organization.editUser); + + app.fs.api.logAttr['PUT/organization/user/:pomsUserId'] = { content: '修改成员状态', visible: true }; + router.put('/organization/user/:pomsUserId', organization.putUser); + + app.fs.api.logAttr['GET/organization/user'] = { content: '获取成员列表', visible: true }; + router.get('/organization/user', organization.user); +}; diff --git a/api/app/lib/routes/project/index.js b/api/app/lib/routes/project/index.js new file mode 100644 index 0000000..a3dbe3e --- /dev/null +++ b/api/app/lib/routes/project/index.js @@ -0,0 +1,8 @@ +'use strict'; + +const project = require('../../controllers/project'); + +module.exports = function (app, router, opts) { + app.fs.api.logAttr['GET/project/app_list'] = { content: '获取应用列表', visible: true }; + router.get('/project/app_list', project.appList); +}; \ No newline at end of file diff --git a/api/config.js b/api/config.js index 93ccaa6..500b4af 100644 --- a/api/config.js +++ b/api/config.js @@ -24,6 +24,12 @@ args.option('godKey', '高德地图API key'); args.option('mqttVideoServer', '视频后台 mqtt 服务 URL'); +// 七牛云存储参数 +args.option('qnak', 'qiniuAccessKey'); +args.option('qnsk', 'qiniuSecretKey'); +args.option('qnbkt', 'qiniuBucket'); +args.option('qndmn', 'qiniuDomain'); + const flags = args.parse(process.argv); const POMS_DB = process.env.POMS_DB || flags.pg; @@ -46,11 +52,19 @@ const GOD_KEY = process.env.GOD_KEY || flags.godKey; // 视频后台 mqtt 信息推送地址 const MQTT_VIDEO_SERVER = process.env.MQTT_VIDEO_SERVER || flags.mqttVideoServer; +// 七牛云存储参数 +const QINIU_DOMAIN_QNDMN_RESOURCE = process.env.ANXINCLOUD_QINIU_DOMAIN_QNDMN_RESOURCE || flags.qndmn; +const QINIU_BUCKET_RESOURCE = process.env.ANXINCLOUD_QINIU_BUCKET_RESOURCE || flags.qnbkt; +const QINIU_AK = process.env.ANXINCLOUD_QINIU_ACCESSKEY || flags.qnak; +const QINIU_SK = process.env.ANXINCLOUD_QINIU_SECRETKEY || flags.qnsk; + + if ( !POMS_DB || !IOTA_REDIS_SERVER_HOST || !IOTA_REDIS_SERVER_PORT || !GOD_KEY || !MQTT_VIDEO_SERVER || !AXY_API_URL || - !API_EMIS_URL + !API_EMIS_URL || + !QINIU_DOMAIN_QNDMN_RESOURCE || !QINIU_BUCKET_RESOURCE || !QINIU_AK || !QINIU_SK ) { console.log('缺少启动参数,异常退出'); args.showHelp(); @@ -64,10 +78,11 @@ const product = { { entry: require('@fs/attachment').entry, opts: { - local: { - origin: IOT_VIDEO_ACCESS_LOCAL_SVR_ORIGIN || `http://localhost:${flags.port || 8080}`, - rootPath: 'static', - childPath: 'upload', + qiniu: { + domain: QINIU_DOMAIN_QNDMN_RESOURCE, + bucket: QINIU_BUCKET_RESOURCE, + accessKey: QINIU_AK, + secretKey: QINIU_SK }, maxSize: 104857600, // 100M } @@ -76,8 +91,9 @@ const product = { opts: { dev, exclude: [ - { p: '/camera', o: 'GET' }, // 暂时滴 - { p: '/application/check', o: 'GET' }, // 暂时滴 + { p: '/attachments/:p', o: 'POST' }, + { p: '/alarm/application/inspection', o: 'POST' }, + { p: '/project/app_list', o: 'GET' } ], // 不做认证的路由,也可以使用 exclude: ["*"] 跳过所有路由 redis: { host: IOTA_REDIS_SERVER_HOST, diff --git a/api/sequelize-automate.config.js b/api/sequelize-automate.config.js index 4631443..e34d53b 100644 --- a/api/sequelize-automate.config.js +++ b/api/sequelize-automate.config.js @@ -26,7 +26,7 @@ module.exports = { dir: './app/lib/models', // 指定输出 models 文件的目录 typesDir: 'models', // 指定输出 TypeScript 类型定义的文件目录,只有 TypeScript / Midway 等会有类型定义 emptyDir: false, // !!! 谨慎操作 生成 models 之前是否清空 `dir` 以及 `typesDir` - tables: null, // 指定生成哪些表的 models,如 ['user', 'user_post'];如果为 null,则忽略改属性 + tables: ['app_alarm'], // 指定生成哪些表的 models,如 ['user', 'user_post'];如果为 null,则忽略改属性 skipTables: [], // 指定跳过哪些表的 models,如 ['user'];如果为 null,则忽略改属性 tsNoCheck: false, // 是否添加 `@ts-nocheck` 注释到 models 文件中 ignorePrefix: [], // 生成的模型名称忽略的前缀,因为 项目中有以下表名是以 t_ 开头的,在实际模型中不需要, 可以添加多个 [ 't_data_', 't_',] ,长度较长的 前缀放前面 diff --git a/web/client/assets/images/problem/setup.png b/web/client/assets/images/problem/setup.png new file mode 100644 index 0000000..58aa294 Binary files /dev/null and b/web/client/assets/images/problem/setup.png differ diff --git a/web/client/src/app.jsx b/web/client/src/app.jsx index 064eeb2..e1a94b4 100644 --- a/web/client/src/app.jsx +++ b/web/client/src/app.jsx @@ -14,6 +14,19 @@ const App = props => { useEffect(() => { document.title = projectName; + + console.log(` + _ _ +        />  フ +       |  _  _ l +       /\` ミ_xノ +      /      | +     /  ヽ   ノ +     │  | | | +  / ̄|   | | | +  | ( ̄ヽ__ヽ_)__) +  \二つ ​ + `); }, []) return ( diff --git a/web/client/src/components/setup.jsx b/web/client/src/components/setup.jsx index 9ff3a55..c4cd070 100644 --- a/web/client/src/components/setup.jsx +++ b/web/client/src/components/setup.jsx @@ -11,6 +11,9 @@ function Setup(props) { tableType, tableList } = props; + + console.log(tableType, + tableList); const [check, setCheck] = useState([]); const checkboxcss = { width: "25%", height: 16, margin: "0 0 20px 0" }; @@ -98,7 +101,7 @@ function Setup(props) { {item.title}
- {item.list.map((itm) => { + {item.list?.map((itm) => { return ( { }} /> - + {leftShow ? ( { + + + return ( +
+
+
数据异常统计
+ +
+
+
暂未开放敬请期待
+
暂未开放敬请期待
+
暂未开放敬请期待
+
暂未开放敬请期待
+ +
+
+ ) +} + + +function mapStateToProps (state) { + const { auth, global, members } = state; + return { + // user: auth.user, + // actions: global.actions, + // global: global, + // members: members.data, + }; +} +export default connect(mapStateToProps)(Statistics); diff --git a/web/client/src/sections/problem/components/tableData.jsx b/web/client/src/sections/problem/components/tableData.jsx new file mode 100644 index 0000000..17eddf2 --- /dev/null +++ b/web/client/src/sections/problem/components/tableData.jsx @@ -0,0 +1,167 @@ +import React, { useState, useEffect, useRef } from "react"; +import { connect } from "react-redux"; +import { Button, Form, Modal, Skeleton, Pagination, Table } from "@douyinfe/semi-ui"; +import { SkeletonScreen, } from "$components"; + + +const TableData = ({ dispatch, actions, route, collectData, setSetup }) => { + + const api = useRef(); + + useEffect(() => { + + }, [route]) + + + + return ( + <> +
+
+
+
console.log(values)} + // onValueChange={values=>console.log(values)} + getFormApi={(formApi) => (api.current = formApi)} + layout="horizontal" + style={{ position: "relative", width: "100%", flex: 1 }} + > + {(() => { + let frame = [] + collectData[route]?.map((v, index) => { + if (index == 0) { + frame.push() + } else { + frame.push( + {v.data?.map((item) => { + return ( + + {item.name} + + ); + })} + ) + } + }) + frame.push( console.log(v)} + />) + return frame + })()} + +
+ setSetup(true)} /> + +
+
+
+ + { + if (index % 1 === 0) { + return { style: { background: '' } } + } + }} + /> + +
+ + 共{100}个设备 + + { + // setQuery({ limit: pageSize, page: currentPage - 1 }); + // page.current = currentPage - 1 + }} + /> +
+ + + ) +} + + +function mapStateToProps (state) { + const { auth, global, members } = state; + return { + // user: auth.user, + // actions: global.actions, + // global: global, + // members: members.data, + }; +} +export default connect(mapStateToProps)(TableData); diff --git a/web/client/src/sections/problem/containers/dataAlarm.jsx b/web/client/src/sections/problem/containers/dataAlarm.jsx index 720ab94..5d9206d 100644 --- a/web/client/src/sections/problem/containers/dataAlarm.jsx +++ b/web/client/src/sections/problem/containers/dataAlarm.jsx @@ -1,35 +1,261 @@ -import React, { useEffect, useState } from 'react'; -import { connect } from 'react-redux'; -import { IconAlertCircle } from '@douyinfe/semi-icons'; +import React, { useEffect, useState } from 'react' +import { connect } from 'react-redux' +import { IconAlertCircle } from '@douyinfe/semi-icons' +import Statistics from '../components/statistics' +import TableData from '../components/tableData' +import { Setup } from "$components"; + import '../style.less' -const DataAlarm = (props) => { - const { dispatch, actions, user, loading, socket } = props - const [abnormalLenght, setAbnormalLenght] = useState(1) //异常数量 +const DataAlarm = ({ match, dispatch, actions, user, loading, socket }) => { + + const [route, setRoute] = useState('') //子页面路由 + const [abnormalLenght, setAbnormalLenght] = useState(0) //异常数量 + const [collect, setCollect] = useState([]) //搜索结构 + const [setup, setSetup] = useState(false); //表格设置是否显现 + const [tableSetup, setTableSetup] = useState([]); //单一表格设置信息 + + const tableType = { dataLnterrupt: 'dataLnterrupt', dataAbnormal: 'dataAbnormal', strategyHit: 'strategyHit', videoAbnormal: 'videoAbnormal', useAbnormal: 'useAbnormal', deviceAbnormal: 'deviceAbnormal' } + useEffect(() => { - console.log(props); + setRoute(match.url.substring(match.url.lastIndexOf("/") + 1, match.url.length)) + console.log(match.url) + console.log(tableType); }, []) + + useEffect(() => { + attribute(tableType[route], route); + }, [route]) + + + //搜索结构 + const collectData = { + dataLnterrupt: [ //数据中断(dataLnterrupt) + { name: '搜索', field: '1' }, + { + name: '中断类型', field: '2', + data: [ + { name: '服务异常', value: '11' }, + { name: '链接中断', value: '22' }, + { name: '设备异常', value: '33' }] + }, + { + name: '中断状态', field: '3', + data: [ + { name: '当前', value: '11' }, + { name: '历史', value: '22' }] + }], + dataAbnormal: [ //数据异常(dataAbnormal) + { name: '搜索', field: '1' }, + { + name: '异常类型', field: '2', + data: [ + { name: '超远程', value: '11' }, + { name: '超限幅', value: '22' }] + }, + { + name: '异常状态', field: '3', + data: [ + { name: '当前', value: '11' }, + { name: '历史', value: '22' }] + }], + strategyHit: [ // 策略命中(strategyHit) + { name: '搜索', field: '1' }, + { + name: '策略类型', field: '2', + data: [ + { name: '超阀值', value: '11' }, + { name: '防驶入告警', value: '22' }, + { name: '设备关闭', value: '33' }, + { name: '超围栏', value: '44' }] + }, + { + name: '命中状态', field: '3', + data: [ + { name: '当前', value: '11' }, + { name: '历史', value: '22' }] + }], + videoAbnormal: [ // 视频异常(videoAbnormal) + { name: '搜索', field: '1' }, + { + name: '设备类型', field: '2', + data: [ + { name: '枪机', value: '11' }, + { name: '球机', value: '22' }, + { name: '其他', value: '33' }] + }, + { + name: '异常状态', field: '3', + data: [ + { name: '当前', value: '11' }, + { name: '历史', value: '22' }] + }], + useAbnormal: [ // 应用异常(useAbnormal) + { name: '搜索', field: '1' }, + { + name: '异常类型', field: '2', + data: [ + { name: '接口报错', value: '11' }, + { name: '加载超时', value: '22' }, + { name: '元素异常', value: '33' }] + }, + { + name: '异常状态', field: '3', + data: [ + { name: '当前', value: '11' }, + { name: '历史', value: '22' }] + }], + deviceAbnormal: [ // 设备告警(deviceAbnormal) + { name: '搜索', field: '1' }, + { + name: '设备类型', field: '2', + data: [ + { name: '传感器', value: '11' }, + { name: 'DTU', value: '22' }, + { name: '服务器', value: '33' }] + }, + { + name: '异常状态', field: '3', + data: [ + { name: '当前', value: '11' }, + { name: '历史', value: '22' }] + }, + { + name: '异常类型', field: '4', + data: [ + { name: '离线', value: '11' }] + }], + common: { + name: '持续时间', + field: '5' + } + } + + //表格设置信息 + const tableList = { + dataLnterrupt: ['1', '2', '3', '4', '6', '9', '7', '5', '10', '8', '11', '12', '13', '14'], + dataAbnormal: ['1', '2', '3', '4', '15', '16', '9', '7', '5', '10', '8', '11', '12', '13'], + strategyHit: ['1', '2', '3', '17', '18', '7', '5', '10', '8', '11', '12', '13'], + videoAbnormal: ['1', '2', '3', '19', '20', '21', '22', '5', '6', '7', '8', '12', '13'], + useAbnormal: ['1', '23', '24', '15', '25', '7', '8', '12', '13'], + deviceAbnormal: ['1', '2', '3', '19', '15', '21', '5', '6', '7', '8', '12', '13'], + } + //表格默认配置信息 + const columns = { + dataLnterrupt: ['0', '1', '2', '3', '4', '5', '6', '7', '8',], + dataAbnormal: ['0', '1', '2', '3', '15', '5', '6', '7', '8'], + strategyHit: ['0', '1', '2', '3', '17', '5', '10', '11', '7', '8'], + videoAbnormal: ['0', '1', '2', '3', '21', '20', '5', '7', '8'], + useAbnormal: ['0', '1', '23', '24', '15', '25', '7', '8'], + deviceAbnormal: ['0', '1', '2', '3', '15', '19', '5', '7', '8'], + } + //所有表格信息 + const columnAll = [ + { name: '问题编号', value: '0' }, + { name: '项目名称', value: '1' }, + { name: '结构物名称', value: '2' }, + { name: '告警源', value: '3' }, + { name: '中断类型', value: '4' }, + { name: '告警信息', value: '5' }, + { name: '常见原因', value: '6' }, + { name: '产生时间', value: '7' }, + { name: '更新时间', value: '8' }, + { name: '服务器地址', value: '9' }, + { name: '告警等级', value: '10' }, + { name: '产生次数', value: '11' }, + { name: '确认信息', value: '12' }, + { name: '确认/恢复时间', value: '13' }, + { name: '项目阶段', value: '14' }, + { name: '异常类型', value: '15' }, + { name: '异常原因', value: '16' }, + { name: '策略类型', value: '17' }, + { name: '命中状态', value: '18' }, + { name: '位置信息', value: '19' }, + { name: '设备类型', value: '20' }, + { name: '设备厂家', value: '21' }, + { name: '接入方式', value: '22' }, + { name: '应用名称', value: '23' }, + { name: 'URL地址', value: '24' }, + { name: '异常信息', value: '25' }, + ] + + + + const attribute = (tableType, route) => { + let arr = localStorage.getItem(tableType) + ? JSON.parse(localStorage.getItem(tableType)) + : []; + // if (route) { + // console.log(tableList[route]); + // console.log([...[1,2,3],...[2,3]]); + // let setup = tableList[route].map(v => columnAll.find(vv => v == vv.value)) + // console.log(setup); + // setTableSetup([{ list: setup }]) + // } + + // for (let i = 0; i < arr.length; i++) { + // let colum = column.filter((item) => { + // return item.key === arr[i]; + // }); + // columns.splice(i + 2, 0, colum[0]); + // } + // setSetupp(columns); + } + + + + + + const distinguish = (route) => { + switch (route) { + case value: + + break; + + default: + break; + } + } return ( - <> +
+ {/* 滞留提醒 */}
- {abnormalLenght > 0 ? : ""} + {abnormalLenght > 0 ?
当前滞留5个工单即将超时,请尽快处理!
: ""}
- - + + + {setup ? ( + { + setSetup(false); + attribute(tableType[route]); + // setcameraSetup(false); + }} + /> + ) : ( + "" + )} +
) } function mapStateToProps (state) { - const { auth, global, members, webSocket } = state; + const { auth, global, members, webSocket } = state return { // loading: members.isRequesting, user: auth.user, actions: global.actions, // members: members.data, // socket: webSocket.socket - }; + } } -export default connect(mapStateToProps)(DataAlarm); +export default connect(mapStateToProps)(DataAlarm) diff --git a/web/client/src/sections/problem/nav-item.jsx b/web/client/src/sections/problem/nav-item.jsx index 279dcdc..dc1d7d3 100644 --- a/web/client/src/sections/problem/nav-item.jsx +++ b/web/client/src/sections/problem/nav-item.jsx @@ -7,7 +7,7 @@ export function getNavItem (user, dispatch) { itemKey: 'problem', text: '问题', items: [ { - itemKey: 'dataAlarm', text: '数据告警', icon: , + itemKey: 'dataAlarm', text: '数据告警', to: '/problem/dataAlarm/dataLnterrupt', icon: , items: [ { itemKey: 'dataLnterrupt', to: '/problem/dataAlarm/dataLnterrupt', text: '数据中断' }, { itemKey: 'dataAbnormal', to: '/problem/dataAlarm/dataAbnormal', text: '数据异常' }, @@ -15,12 +15,12 @@ export function getNavItem (user, dispatch) { { itemKey: 'videoAbnormal', to: '/problem/dataAlarm/videoAbnormal', text: '视频异常' }, ] }, { - itemKey: 'useAlarm', text: '应用告警', icon: , + itemKey: 'useAlarm', text: '应用告警', to: '/problem/useAlarm/useAbnormal', icon: , items: [ { itemKey: 'useAbnormal', to: '/problem/useAlarm/useAbnormal', text: '应用异常' }, ] }, { - itemKey: 'deviceAlarm', text: '设备告警', icon: , + itemKey: 'deviceAlarm', text: '设备告警', to: '/problem/deviceAlarm/deviceAbnormal', icon: , items: [ { itemKey: 'deviceAbnormal', to: '/problem/deviceAlarm/deviceAbnormal', text: '设备异常' }, ]