@ -0,0 +1,141 @@ |
|||||
|
# ---> Go |
||||
|
# Binaries for programs and plugins |
||||
|
*.exe |
||||
|
*.exe~ |
||||
|
*.dll |
||||
|
*.so |
||||
|
*.dylib |
||||
|
|
||||
|
# Test binary, built with `go test -c` |
||||
|
*.test |
||||
|
|
||||
|
# Output of the go coverage tool, specifically when used with LiteIDE |
||||
|
*.out |
||||
|
|
||||
|
# Dependency directories (remove the comment below to include it) |
||||
|
# vendor/ |
||||
|
|
||||
|
# ---> Node |
||||
|
# Logs |
||||
|
logs |
||||
|
*.log |
||||
|
npm-debug.log* |
||||
|
yarn-debug.log* |
||||
|
yarn-error.log* |
||||
|
lerna-debug.log* |
||||
|
.pnpm-debug.log* |
||||
|
|
||||
|
# Diagnostic reports (https://nodejs.org/api/report.html) |
||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json |
||||
|
|
||||
|
# Runtime data |
||||
|
pids |
||||
|
*.pid |
||||
|
*.seed |
||||
|
*.pid.lock |
||||
|
|
||||
|
# Directory for instrumented libs generated by jscoverage/JSCover |
||||
|
lib-cov |
||||
|
|
||||
|
# Coverage directory used by tools like istanbul |
||||
|
coverage |
||||
|
*.lcov |
||||
|
|
||||
|
# nyc test coverage |
||||
|
.nyc_output |
||||
|
|
||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) |
||||
|
.grunt |
||||
|
|
||||
|
# Bower dependency directory (https://bower.io/) |
||||
|
bower_components |
||||
|
|
||||
|
# node-waf configuration |
||||
|
.lock-wscript |
||||
|
|
||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html) |
||||
|
build/Release |
||||
|
|
||||
|
# Dependency directories |
||||
|
node_modules/ |
||||
|
jspm_packages/ |
||||
|
|
||||
|
# Snowpack dependency directory (https://snowpack.dev/) |
||||
|
web_modules/ |
||||
|
|
||||
|
# TypeScript cache |
||||
|
*.tsbuildinfo |
||||
|
|
||||
|
# Optional npm cache directory |
||||
|
.npm |
||||
|
|
||||
|
# Optional eslint cache |
||||
|
.eslintcache |
||||
|
|
||||
|
# Microbundle cache |
||||
|
.rpt2_cache/ |
||||
|
.rts2_cache_cjs/ |
||||
|
.rts2_cache_es/ |
||||
|
.rts2_cache_umd/ |
||||
|
|
||||
|
# Optional REPL history |
||||
|
.node_repl_history |
||||
|
|
||||
|
# Output of 'npm pack' |
||||
|
*.tgz |
||||
|
|
||||
|
# Yarn Integrity file |
||||
|
.yarn-integrity |
||||
|
|
||||
|
# dotenv environment variables file |
||||
|
.env |
||||
|
.env.test |
||||
|
.env.production |
||||
|
|
||||
|
# parcel-bundler cache (https://parceljs.org/) |
||||
|
.cache |
||||
|
.parcel-cache |
||||
|
|
||||
|
# Next.js build output |
||||
|
.next |
||||
|
out |
||||
|
|
||||
|
# Nuxt.js build / generate output |
||||
|
.nuxt |
||||
|
dist |
||||
|
|
||||
|
# Gatsby files |
||||
|
.cache/ |
||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js |
||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support |
||||
|
# public |
||||
|
|
||||
|
# vuepress build output |
||||
|
.vuepress/dist |
||||
|
|
||||
|
# Serverless directories |
||||
|
.serverless/ |
||||
|
|
||||
|
# FuseBox cache |
||||
|
.fusebox/ |
||||
|
|
||||
|
# DynamoDB Local files |
||||
|
.dynamodb/ |
||||
|
|
||||
|
# TernJS port file |
||||
|
.tern-port |
||||
|
|
||||
|
# Stores VSCode versions used for testing VSCode extensions |
||||
|
.vscode-test |
||||
|
|
||||
|
# yarn v2 |
||||
|
.yarn/cache |
||||
|
.yarn/unplugged |
||||
|
.yarn/build-state.yml |
||||
|
.yarn/install-state.gz |
||||
|
.pnp.* |
||||
|
|
||||
|
*yarn.lock |
||||
|
*package-lock.json |
||||
|
*log/ |
||||
|
*downloadFiles/ |
@ -0,0 +1,2 @@ |
|||||
|
# FS-IOT |
||||
|
|
@ -0,0 +1,87 @@ |
|||||
|
{ |
||||
|
// 使用 IntelliSense 了解相关属性。 |
||||
|
// 悬停以查看现有属性的描述。 |
||||
|
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 |
||||
|
"version": "0.2.0", |
||||
|
"configurations": [ |
||||
|
{ |
||||
|
"type": "node", |
||||
|
"request": "launch", |
||||
|
"name": "启动 API", |
||||
|
"program": "${workspaceRoot}/server.js", |
||||
|
"env": { |
||||
|
"NODE_ENV": "development" |
||||
|
}, |
||||
|
"args": [ |
||||
|
"-p 4600", |
||||
|
"-f http://localhost:4600", |
||||
|
// 研发 |
||||
|
// "-g postgres://postgres:123@10.8.30.32:5432/orational_service", |
||||
|
// 测试 |
||||
|
"-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", |
||||
|
"--redisPort 6379", |
||||
|
"--axyApiUrl http://127.0.0.1:4100", |
||||
|
// "--apiEmisUrl http://10.8.30.112:14000", |
||||
|
// 测试 |
||||
|
"--apiEmisUrl http://10.8.30.161:1111", |
||||
|
"--apiVcmpUrl http://localhost:4000", |
||||
|
"--apiIotAuth http://localhost:4200", |
||||
|
"--godUrl https://restapi.amap.com/v3", |
||||
|
"--godKey 21c2d970e1646bb9a795900dd00093ce", |
||||
|
"--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", |
||||
|
// click 开发 |
||||
|
// "--clickHouseUrl http://10.8.30.71", |
||||
|
// click 测试 |
||||
|
"--clickHouseUrl http://10.8.30.161", |
||||
|
// "--clickHouseUrl https://clickhouse01.anxinyun.cn/play", |
||||
|
"--clickHousePort 30123", |
||||
|
|
||||
|
// 似乎不能传空 先注释 * 2 |
||||
|
// "--clickHouseUser ", |
||||
|
// "--clickHousePassword ", |
||||
|
|
||||
|
// 研发 |
||||
|
// "--clickHouseAnxincloud anxinyun", |
||||
|
// "--clickHousePepEmis pepca", |
||||
|
// "--clickHouseProjectManage peppm", |
||||
|
// "--clickHouseVcmp video_accrss1", |
||||
|
// "--clickHouseDataAlarm default", |
||||
|
|
||||
|
// 测试 |
||||
|
"--clickHouseAnxincloud Anxinyun13", |
||||
|
"--clickHousePepEmis pepca8", |
||||
|
"--clickHouseProjectManage peppm8", |
||||
|
"--clickHouseVcmp video_access_dev", |
||||
|
"--clickHouseDataAlarm default", |
||||
|
|
||||
|
"--confirmAlarmAnxinUserId 1", |
||||
|
"--vcmpAppId 5048b08d-c449-4d7f-b1ec-f741012aefe8", |
||||
|
"--vcmpAppSecret 5ba8c0ab-9fbd-4f07-9817-c48017c3cbad", |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"type": "node", |
||||
|
"request": "launch", |
||||
|
"name": "run mocha", |
||||
|
"program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", |
||||
|
"stopOnEntry": false, |
||||
|
"args": [ |
||||
|
"app/test/*.test.js", |
||||
|
"--no-timeouts" |
||||
|
], |
||||
|
"cwd": "${workspaceRoot}", |
||||
|
"runtimeExecutable": null, |
||||
|
"env": { |
||||
|
"NODE_ENV": "development" |
||||
|
} |
||||
|
} |
||||
|
] |
||||
|
} |
@ -0,0 +1,36 @@ |
|||||
|
# FROM repository.anxinyun.cn/devops/node:12-dev as builder |
||||
|
|
||||
|
# COPY . /var/app |
||||
|
|
||||
|
# WORKDIR /var/app |
||||
|
|
||||
|
# EXPOSE 8080 |
||||
|
|
||||
|
# RUN npm config set registry=http://10.8.30.22:7000 |
||||
|
# RUN echo "{\"time\":\"$BUILD_TIMESTAMP\",\"build\": \"$BUILD_NUMBER\",\"revision\": \"$SVN_REVISION_1\",\"URL\":\"$SVN_URL_1\"}" > version.json |
||||
|
# RUN npm cache clean -f |
||||
|
# RUN rm -rf package-lock.json |
||||
|
# RUN npm install --registry http://10.8.30.22:7000 |
||||
|
|
||||
|
# FROM registry.cn-hangzhou.aliyuncs.com/fs-devops/node:12 |
||||
|
|
||||
|
# COPY --from=builder --chown=node /var/app /home/node/app |
||||
|
|
||||
|
# WORKDIR /home/node/app |
||||
|
|
||||
|
# CMD ["node", "server.js"] |
||||
|
|
||||
|
|
||||
|
# 旧版本构建方式 |
||||
|
|
||||
|
FROM repository.anxinyun.cn/base-images/nodejs12:20.10.12.2 |
||||
|
|
||||
|
COPY . /var/app |
||||
|
|
||||
|
WORKDIR /var/app |
||||
|
|
||||
|
EXPOSE 8080 |
||||
|
|
||||
|
CMD ["-u", "http://localhost:8088"] |
||||
|
|
||||
|
ENTRYPOINT [ "node", "server.js" ] |
@ -0,0 +1,3 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
module.exports = require('./lib'); |
@ -0,0 +1,119 @@ |
|||||
|
'use strict'; |
||||
|
const Hex = require('crypto-js/enc-hex'); |
||||
|
const MD5 = require('crypto-js/md5'); |
||||
|
const moment = require('moment'); |
||||
|
const uuid = require('uuid'); |
||||
|
|
||||
|
async function login (ctx, next) { |
||||
|
// const transaction = await ctx.fs.dc.orm.transaction();
|
||||
|
try { |
||||
|
const models = ctx.fs.dc.models; |
||||
|
const params = ctx.request.body; |
||||
|
|
||||
|
const emisLoginRes = await ctx.app.fs.emisRequest.post('login', { |
||||
|
data: params |
||||
|
}) |
||||
|
|
||||
|
if (!emisLoginRes) { |
||||
|
throw "无此用户,请使用正确的登录信息" |
||||
|
} else { |
||||
|
const pomsRegisterRes = await models.User.findOne({ |
||||
|
where: { |
||||
|
pepUserId: emisLoginRes.id, |
||||
|
$or: { |
||||
|
deleted: false, |
||||
|
role: { $contains: ['admin'] } |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
if (!pomsRegisterRes) { |
||||
|
throw '你还不是飞尚运维中台成员,请联系管理员添加权限' |
||||
|
} else if ( |
||||
|
pomsRegisterRes.disabled && !pomsRegisterRes.role.includes('admin') |
||||
|
) { |
||||
|
throw '当前账号已禁用' |
||||
|
} |
||||
|
emisLoginRes.authorized = true |
||||
|
emisLoginRes.expired = moment().add(1, 'day') |
||||
|
emisLoginRes.pomsUserInfo = pomsRegisterRes.dataValues |
||||
|
|
||||
|
let userUpdateData = { |
||||
|
lastInTime: moment().format(), |
||||
|
inTimes: pomsRegisterRes.inTimes + 1, |
||||
|
lastInAddress: '' |
||||
|
} |
||||
|
try { |
||||
|
// 获取ip转为地点并记录
|
||||
|
let ip = |
||||
|
// '117.90.39.49' ||
|
||||
|
ctx.ip |
||||
|
console.log(`当前登录用户IP:${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 |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
await ctx.redis.hmset(emisLoginRes.token, { |
||||
|
expired: moment().add(1, 'day'), |
||||
|
userInfo:JSON.stringify(emisLoginRes) |
||||
|
}); |
||||
|
|
||||
|
ctx.status = 200; |
||||
|
ctx.body = emisLoginRes; |
||||
|
} |
||||
|
// await transaction.commit();
|
||||
|
} catch (error) { |
||||
|
// await transaction.rollback();
|
||||
|
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); |
||||
|
ctx.status = 400; |
||||
|
let message = typeof error == 'string' ? error |
||||
|
: error.response.body.message || "登录失败" |
||||
|
if (message == '账号或密码错误') { |
||||
|
message = '无此用户,请使用正确的登录信息' |
||||
|
} |
||||
|
|
||||
|
ctx.body = { |
||||
|
message: message |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async function logout (ctx) { |
||||
|
try { |
||||
|
const models = ctx.fs.dc.models; |
||||
|
const params = ctx.request.body; |
||||
|
|
||||
|
await ctx.app.fs.emisRequest.put('logout', { |
||||
|
data: params |
||||
|
}) |
||||
|
await ctx.redisTools.hdelall(token); |
||||
|
|
||||
|
ctx.status = 204; |
||||
|
} catch (error) { |
||||
|
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); |
||||
|
ctx.status = 400; |
||||
|
ctx.body = { |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = { |
||||
|
login, |
||||
|
logout, |
||||
|
}; |
@ -0,0 +1,79 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
const fs = require('fs'); |
||||
|
const path = require('path'); |
||||
|
const utils = require('./utils') |
||||
|
const routes = require('./routes'); |
||||
|
const redisConnect = require('./service/redis') |
||||
|
const socketConect = require('./service/socket') |
||||
|
const mqttVideoServer = require('./service/mqttServer') |
||||
|
const paasRequest = require('./service/paasRequest'); |
||||
|
const authenticator = require('./middlewares/authenticator'); |
||||
|
const clickHouseClient = require('./service/clickHouseClient') |
||||
|
const kafka = require('./service/kafka') |
||||
|
const schedule = require('./schedule') |
||||
|
// const apiLog = require('./middlewares/api-log');
|
||||
|
|
||||
|
module.exports.entry = function (app, router, opts) { |
||||
|
app.fs.logger.log('info', '[FS-AUTH]', 'Inject auth and api mv into router.'); |
||||
|
|
||||
|
app.fs.api = app.fs.api || {}; |
||||
|
app.fs.utils = app.fs.utils || {}; |
||||
|
app.fs.api.authAttr = app.fs.api.authAttr || {}; |
||||
|
app.fs.api.logAttr = app.fs.api.logAttr || {}; |
||||
|
|
||||
|
// 顺序固定 ↓
|
||||
|
redisConnect(app, opts) |
||||
|
socketConect(app, opts) |
||||
|
mqttVideoServer(app, opts) |
||||
|
|
||||
|
// 实例其他平台请求方法
|
||||
|
paasRequest(app, opts) |
||||
|
|
||||
|
kafka(app, opts) |
||||
|
|
||||
|
// clickHouse 数据库 client
|
||||
|
clickHouseClient(app, opts) |
||||
|
|
||||
|
// 工具类函数
|
||||
|
utils(app, opts) |
||||
|
|
||||
|
// 定时任务
|
||||
|
schedule(app, opts) |
||||
|
|
||||
|
// 鉴权中间件
|
||||
|
router.use(authenticator(app, opts)); |
||||
|
|
||||
|
// 日志记录
|
||||
|
// router.use(apiLog(app, opts));
|
||||
|
|
||||
|
router = routes(app, router, opts); |
||||
|
}; |
||||
|
|
||||
|
module.exports.models = function (dc) { // dc = { orm: Sequelize对象, ORM: Sequelize, models: {} }
|
||||
|
|
||||
|
// 模型关系摘出来 初始化之后再定义关系才行
|
||||
|
fs.readdirSync(path.join(__dirname, '/models')).forEach((filename) => { |
||||
|
require(`./models/${filename}`)(dc) |
||||
|
}); |
||||
|
|
||||
|
// const {
|
||||
|
// AppInspection, ProjectApp, ProjectCorrelation, AppAlarm, App
|
||||
|
// } = dc.models;
|
||||
|
|
||||
|
// AppInspection.belongsTo(App, { foreignKey: 'projectAppId', targetKey: 'id' });
|
||||
|
// App.hasMany(AppInspection, { foreignKey: 'projectAppId', sourceKey: 'id' });
|
||||
|
|
||||
|
// ProjectApp.belongsTo(ProjectCorrelation, { foreignKey: 'projectId', targetKey: 'id' });
|
||||
|
// ProjectCorrelation.hasMany(ProjectApp, { foreignKey: 'projectId', sourceKey: 'id' });
|
||||
|
|
||||
|
// ProjectApp.belongsTo(App, { foreignKey: 'appId', targetKey: 'id' });
|
||||
|
// App.hasMany(ProjectApp, { foreignKey: 'appId', sourceKey: 'id' });
|
||||
|
|
||||
|
// ProjectCorrelation.belongsToMany(App, { through: ProjectApp, foreignKey: 'projectId', otherKey: 'appId' });
|
||||
|
|
||||
|
// App.belongsToMany(ProjectCorrelation, { through: ProjectApp, foreignKey: 'appId', otherKey: 'projectId' });
|
||||
|
|
||||
|
// AppAlarm.belongsTo(App, { foreignKey: 'projectAppId', targetKey: 'id' });
|
||||
|
// App.hasMany(AppAlarm, { foreignKey: 'projectAppId', sourceKey: 'id' });
|
||||
|
}; |
@ -0,0 +1,83 @@ |
|||||
|
/** |
||||
|
* Created by PengPeng on 2017/4/26. |
||||
|
*/ |
||||
|
'use strict'; |
||||
|
|
||||
|
const moment = require('moment'); |
||||
|
const pathToRegexp = require('path-to-regexp'); |
||||
|
|
||||
|
function factory(app, opts) { |
||||
|
async function sendToEsAsync(producer, payloads) { |
||||
|
return new Promise((resolve, reject) => { |
||||
|
producer.send(payloads, function (err) { |
||||
|
if (err) { |
||||
|
reject(err); |
||||
|
} else { |
||||
|
resolve(); |
||||
|
} |
||||
|
}); |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
async function logger(ctx, next) { |
||||
|
const { path, method } = ctx; |
||||
|
const start = Date.now(); |
||||
|
|
||||
|
// 等待路由处理
|
||||
|
await next(); |
||||
|
|
||||
|
try { |
||||
|
let logAttr = null; |
||||
|
for (let prop in app.fs.api.logAttr) { |
||||
|
let keys = []; |
||||
|
let re = pathToRegexp(prop.replace(/\:[A-Za-z_\-]+\b/g, '(\\d+)'), keys); |
||||
|
if (re.test(`${method}${path}`)) { |
||||
|
logAttr = app.fs.api.logAttr[prop]; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
let parameter = null, parameterShow = null, user_id, _token, app_key; |
||||
|
if (ctx.fs.api) { |
||||
|
const { actionParameter, actionParameterShow, userId, token, appKey } = ctx.fs.api; |
||||
|
parameter = actionParameter; |
||||
|
parameterShow = actionParameterShow; |
||||
|
user_id = userId; |
||||
|
_token = token; |
||||
|
app_key = appKey; |
||||
|
} |
||||
|
const producer = ctx.fs.kafka.producer; |
||||
|
|
||||
|
const message = { |
||||
|
log_time: moment().toISOString(), |
||||
|
method: method, |
||||
|
content: logAttr ? logAttr.content : '', |
||||
|
parameter: JSON.stringify(parameter) || JSON.stringify(ctx.request.body), |
||||
|
parameter_show: parameterShow, |
||||
|
visible: logAttr ? logAttr.visible : true, |
||||
|
cost: Date.now() - start, |
||||
|
status_code: ctx.status, |
||||
|
url: ctx.request.url, |
||||
|
user_agent: ctx.request.headers["user-agent"], |
||||
|
user_id: user_id, |
||||
|
session: _token, |
||||
|
app_key: app_key, |
||||
|
header: JSON.stringify(ctx.request.headers), |
||||
|
ip: ctx.request.headers["x-real-ip"] || ctx.ip |
||||
|
}; |
||||
|
|
||||
|
const payloads = [{ |
||||
|
topic: `${opts.kafka.topicPrefix}`, |
||||
|
messages: [JSON.stringify(message)], |
||||
|
partition: 0 |
||||
|
}]; |
||||
|
|
||||
|
// await sendToEsAsync(producer, payloads);
|
||||
|
|
||||
|
} catch (e) { |
||||
|
ctx.fs.logger.error(`日志记录失败: ${e}`); |
||||
|
} |
||||
|
} |
||||
|
return logger; |
||||
|
} |
||||
|
|
||||
|
module.exports = factory; |
@ -0,0 +1,162 @@ |
|||||
|
/** |
||||
|
* Created by PengLing on 2017/3/27. |
||||
|
*/ |
||||
|
'use strict'; |
||||
|
|
||||
|
const pathToRegexp = require('path-to-regexp'); |
||||
|
const util = require('util'); |
||||
|
const moment = require('moment'); |
||||
|
|
||||
|
class ExcludesUrls { |
||||
|
constructor(opts) { |
||||
|
this.allUrls = undefined; |
||||
|
this.reload(opts); |
||||
|
} |
||||
|
|
||||
|
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); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
isExcluded (path, method) { |
||||
|
return this.allUrls.some(function (url) { |
||||
|
return !url.auth |
||||
|
&& url.pregexp.test(path) |
||||
|
&& (url.o === '*' || url.o.indexOf(method) !== -1); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 判断Url是否不鉴权 |
||||
|
* @param {*} opts {exclude: [*] or []},'*'或['*']:跳过所有路由; []:所有路由都要验证 |
||||
|
* @param {*} path 当前request的path |
||||
|
* @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 authorizeToken = async function (ctx, token) { |
||||
|
let startTime = moment() |
||||
|
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'); |
||||
|
|
||||
|
// const authorizeRes = await ctx.app.fs.emisRequest.get('authorize', {
|
||||
|
// query: { token }
|
||||
|
// })
|
||||
|
// const { userInfo, expired } = authorizeRes;
|
||||
|
|
||||
|
|
||||
|
// TODO 从项企 clickhouse 数据库中查 token 并更新
|
||||
|
if (expired && moment().valueOf() <= moment(expired).valueOf()) { |
||||
|
const userInfo = JSON.parse(await ctx.redis.hmget(token, 'userInfo')); |
||||
|
const { pomsUserInfo: pomsUser } = userInfo |
||||
|
|
||||
|
// const pomsUser = await ctx.app.fs.dc.models.User.findOne({
|
||||
|
// where: {
|
||||
|
// pepUserId: userInfo.id
|
||||
|
// }
|
||||
|
// }) || {}
|
||||
|
|
||||
|
rslt = { |
||||
|
'authorized': userInfo.authorized, |
||||
|
'resources': (userInfo || {}).resources || [], |
||||
|
}; |
||||
|
ctx.fs.api.userId = pomsUser.id; |
||||
|
ctx.fs.api.userInfo = pomsUser; |
||||
|
ctx.fs.api.pepUserId = userInfo.id; |
||||
|
ctx.fs.api.pepUserInfo = 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); |
||||
|
}; |
||||
|
|
||||
|
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' } |
||||
|
} |
||||
|
} |
||||
|
} 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; |
@ -0,0 +1,14 @@ |
|||||
|
'use strict'; |
||||
|
const moment = require('moment'); |
||||
|
|
||||
|
async function factory (ctx, next) { |
||||
|
try { |
||||
|
const start = moment() |
||||
|
await next() |
||||
|
ctx.fs.logger.log(`DurationCalc: ${ctx.path} 用时 ${moment().diff(start, 'milliseconds')}`); |
||||
|
} catch (error) { |
||||
|
ctx.fs.logger.error(`DurationCalc, error: ${error}`); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = factory; |
@ -0,0 +1,115 @@ |
|||||
|
/* eslint-disable*/ |
||||
|
'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: "关联的poms的项目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: false, |
||||
|
comment: null, |
||||
|
primaryKey: false, |
||||
|
field: "disabled", |
||||
|
autoIncrement: false |
||||
|
}, |
||||
|
deleted: { |
||||
|
type: DataTypes.BOOLEAN, |
||||
|
allowNull: false, |
||||
|
defaultValue: false, |
||||
|
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; |
||||
|
}; |
@ -0,0 +1,11 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
const auth = require('../../controllers/auth'); |
||||
|
|
||||
|
module.exports = function (app, router, opts) { |
||||
|
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); |
||||
|
}; |
@ -0,0 +1,17 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
const path = require('path'); |
||||
|
const fs = require('fs'); |
||||
|
|
||||
|
module.exports = function (app, router, opts) { |
||||
|
fs.readdirSync(__dirname).forEach((filename) => { |
||||
|
if (filename.indexOf('.') !== 0 && fs.lstatSync(path.join(__dirname, filename)).isDirectory()) { |
||||
|
fs.readdirSync(path.join(__dirname, filename)).forEach((api) => { |
||||
|
if (api.indexOf('.') == 0 || api.indexOf('.js') == -1) return; |
||||
|
require(`./${filename}/${api}`)(app, router, opts); |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
return router; |
||||
|
}; |
@ -0,0 +1,36 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
const fs = require('fs'); |
||||
|
const nodeSchedule = require('node-schedule'); |
||||
|
|
||||
|
// 将定时任务汇集未来可根据需要选取操作
|
||||
|
module.exports = async function (app, opts) { |
||||
|
|
||||
|
const scheduleInit = ({ |
||||
|
interval, immediate, proRun, |
||||
|
}, callback) => { |
||||
|
if (proRun && opts.dev) { |
||||
|
return; |
||||
|
} |
||||
|
const j = nodeSchedule.scheduleJob(interval, callback); |
||||
|
if (immediate && (!proRun || (proRun && !opts.dev))) { |
||||
|
setTimeout(callback, 0) |
||||
|
} |
||||
|
return j; |
||||
|
} |
||||
|
|
||||
|
app.fs.scheduleInit = scheduleInit |
||||
|
|
||||
|
fs.readdirSync(__dirname).forEach((filename) => { |
||||
|
if (!['index.js'].some(f => filename == f)) { |
||||
|
const scheduleList = require(`./${filename}`)(app, opts) |
||||
|
for (let k of Object.keys(scheduleList)) { |
||||
|
console.info(`定时任务 ${k} 启动`); |
||||
|
} |
||||
|
app.fs.schedule = { |
||||
|
...app.fs.schedule, |
||||
|
...scheduleList, |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
}; |
@ -0,0 +1,36 @@ |
|||||
|
'use strict'; |
||||
|
const { ClickHouse } = require('clickhouse'); |
||||
|
|
||||
|
function factory (app, opts) { |
||||
|
if (opts.clickHouse) { |
||||
|
try { |
||||
|
app.fs.clickHouse = {} |
||||
|
const { url, port, user, password, db = [] } = opts.clickHouse |
||||
|
for (let d of db) { |
||||
|
if (d.name && d.db) { |
||||
|
app.fs.clickHouse[d.name] = new ClickHouse({ |
||||
|
url: url, |
||||
|
port: port, |
||||
|
debug: true || opts.dev, |
||||
|
format: "json", |
||||
|
basicAuth: user && password ? { |
||||
|
username: user, |
||||
|
password: password, |
||||
|
} : null, |
||||
|
config: { |
||||
|
database: d.db, |
||||
|
}, |
||||
|
}) |
||||
|
console.info(`ClickHouse ${d.name} 初始化完成`); |
||||
|
} else { |
||||
|
throw 'opts.clickHouse 参数错误!' |
||||
|
} |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error(error) |
||||
|
process.exit(-1); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = factory; |
@ -0,0 +1,20 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
const Kafka = require('kafka-node'); |
||||
|
|
||||
|
module.exports = async function factory (app, opts) { |
||||
|
const client = new Kafka.KafkaClient({ kafkaHost: opts.kafka.rootURL }); |
||||
|
const producer = new Kafka.HighLevelProducer(client); |
||||
|
|
||||
|
producer.on('error', function (err) { |
||||
|
app.fs.logger.log('error', "[FS-KAFKA]", err); |
||||
|
}); |
||||
|
|
||||
|
const kafka = { |
||||
|
producer: producer, |
||||
|
configUpdateMessage: opts.configUpdateMessage || {} |
||||
|
}; |
||||
|
|
||||
|
app.fs.kafka = kafka; |
||||
|
app.fs.logger.log('debug', "[FS-KAFKA]", "Init.Success"); |
||||
|
} |
@ -0,0 +1,23 @@ |
|||||
|
'use strict'; |
||||
|
const mqtt = require('mqtt'); |
||||
|
|
||||
|
module.exports = async function factory (app, opts) { |
||||
|
// console.info(`mqtt connecting ${opts.mqtt.mqttVideoServer}`);
|
||||
|
|
||||
|
// const client = mqtt.connect(opts.mqtt.mqttVideoServer);
|
||||
|
|
||||
|
// client.on('connect', function () {
|
||||
|
// console.info(`mqtt connect success ${opts.mqtt.mqttVideoServer}`);
|
||||
|
// client.subscribe('topic/test', { qos: 0 });//订阅主题为test的消息
|
||||
|
// })
|
||||
|
// client.on('error', function (e) {
|
||||
|
// console.error(`mqtt connect failed ${opts.mqtt.mqttVideoServer}`);
|
||||
|
// app.fs.logger.error('info', '[FS-AUTH-MQTT]', `mqtt connect failed ${opts.mqtt.mqttVideoServer}`);
|
||||
|
// })
|
||||
|
|
||||
|
// client.on('message', async (top, message) => {
|
||||
|
|
||||
|
// });
|
||||
|
|
||||
|
// app.mqttVideoServer = client
|
||||
|
} |
@ -0,0 +1,67 @@ |
|||||
|
'use strict'; |
||||
|
const request = require('superagent') |
||||
|
|
||||
|
class paasRequest { |
||||
|
constructor(root, { query = {} } = {}, option) { |
||||
|
this.root = root; |
||||
|
this.query = query |
||||
|
this.option = option |
||||
|
} |
||||
|
|
||||
|
#buildUrl = (url) => { |
||||
|
return `${this.root}/${url}`; |
||||
|
} |
||||
|
|
||||
|
#resultHandler = (resolve, reject) => { |
||||
|
return (err, res) => { |
||||
|
if (err) { |
||||
|
reject(err); |
||||
|
} else { |
||||
|
resolve(res[this.option.dataWord]); |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
get = (url, { query = {}, header = {} } = {}) => { |
||||
|
return new Promise((resolve, reject) => { |
||||
|
request.get(this.#buildUrl(url)).set(header).query(Object.assign(query, this.query)).end(this.#resultHandler(resolve, reject)); |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
post = (url, { data = {}, query = {}, header = {} } = {}) => { |
||||
|
return new Promise((resolve, reject) => { |
||||
|
request.post(this.#buildUrl(url)).set(header).query(Object.assign(query, this.query)).send(data).end(this.#resultHandler(resolve, reject)); |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
put = (url, { data = {}, header = {}, query = {}, } = {}) => { |
||||
|
return new Promise((resolve, reject) => { |
||||
|
request.put(this.#buildUrl(url)).set(header).query(Object.assign(query, this.query)).send(data).end(this.#resultHandler(resolve, reject)); |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
delete = (url, { header = {}, query = {} } = {}) => { |
||||
|
return new Promise((resolve, reject) => { |
||||
|
request.delete(this.#buildUrl(url)).set(header).query(Object.assign(query, this.query)).end(this.#resultHandler(resolve, reject)); |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function factory (app, opts) { |
||||
|
if (opts.pssaRequest) { |
||||
|
try { |
||||
|
for (let r of opts.pssaRequest) { |
||||
|
if (r.name && r.root) { |
||||
|
app.fs[r.name] = new paasRequest(r.root, { ...(r.params || {}) }, { dataWord: r.dataWord || 'body' }) |
||||
|
} else { |
||||
|
throw 'opts.pssaRequest 参数错误!' |
||||
|
} |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error(error) |
||||
|
process.exit(-1); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = factory; |
@ -0,0 +1,41 @@ |
|||||
|
'use strict'; |
||||
|
// https://github.com/luin/ioredis
|
||||
|
const redis = require("ioredis") |
||||
|
|
||||
|
module.exports = async function factory (app, opts) { |
||||
|
let client = opts.redis.pwd ? |
||||
|
new redis.Cluster([ |
||||
|
{ |
||||
|
host: opts.redis.host, |
||||
|
port: opts.redis.port |
||||
|
} |
||||
|
], { |
||||
|
redisOptions: { |
||||
|
password: opts.redis.pwd, |
||||
|
}, |
||||
|
}) |
||||
|
: new redis(opts.redis.port, opts.redis.host, { |
||||
|
password: opts.redis.pwd, |
||||
|
}); |
||||
|
|
||||
|
client.on("error", function (err) { |
||||
|
app.fs.logger.error('info', '[FS-AUTH-REDIS]', `redis connect error. ${opts.redis.host + ':' + opts.redis.port}`); |
||||
|
// console.error("Error :", err);
|
||||
|
// process.exit(-1);
|
||||
|
}); |
||||
|
|
||||
|
client.on('connect', function () { |
||||
|
console.info(`redis connect success ${opts.redis.host + ':' + opts.redis.port}`); |
||||
|
}) |
||||
|
|
||||
|
// 自定义方法
|
||||
|
async function hdelall (key) { |
||||
|
const obj = await client.hgetall(key); |
||||
|
await client.hdel(key, Object.keys(obj)) |
||||
|
} |
||||
|
|
||||
|
app.redis = client |
||||
|
app.redisTools = { |
||||
|
hdelall, |
||||
|
} |
||||
|
} |
@ -0,0 +1,40 @@ |
|||||
|
'use strict'; |
||||
|
const moment = require('moment') |
||||
|
|
||||
|
module.exports = async function factory (app, opts) { |
||||
|
|
||||
|
app.socket.on('connection', async (socket) => { |
||||
|
console.info('WEB_SOCKET token:' + socket.handshake.query.token + ' 已连接:id ' + socket.id + ' 时间:' + moment(socket.handshake.time).format()); |
||||
|
|
||||
|
socket.on('disconnecting', async (reason) => { |
||||
|
const connectSeconds = moment().diff(moment(socket.handshake.time), 'seconds') |
||||
|
|
||||
|
console.info('WEB_SOCKET token:' + socket.handshake.query.token + ' 已断开连接:' + reason + ' 连接时长:' + connectSeconds + 's'); |
||||
|
|
||||
|
const { models } = app.fs.dc |
||||
|
await models.User.increment({ |
||||
|
onlineDuration: connectSeconds |
||||
|
}, { |
||||
|
where: { |
||||
|
id: socket.handshake.query.pomsUserId |
||||
|
} |
||||
|
}) |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
// 使用测试 保持链接
|
||||
|
// setInterval(async () => {
|
||||
|
// const { connected } = app.socket.sockets
|
||||
|
|
||||
|
// const roomId = 'ROOM_' + Math.random()
|
||||
|
// // if (connected) {
|
||||
|
// // for (let c in connected) {
|
||||
|
// // connected[c].join(roomId)
|
||||
|
// // }
|
||||
|
// // app.socket.to(roomId).emit('TEST', { someProperty: `【星域 ROOM:${roomId}】呼叫自然选择号!!!`, })
|
||||
|
// // }
|
||||
|
|
||||
|
// app.socket.emit('TEST', { someProperty: '【广播】呼叫青铜时代号!!!', })
|
||||
|
|
||||
|
// }, 3000)
|
||||
|
} |
@ -0,0 +1,173 @@ |
|||||
|
'use strict'; |
||||
|
const fs = require('fs'); |
||||
|
const moment = require('moment') |
||||
|
|
||||
|
module.exports = function (app, opts) { |
||||
|
|
||||
|
function judgeSuper (ctx) { |
||||
|
try { |
||||
|
const { userInfo = {} } = ctx.fs.api || {}; |
||||
|
const { role = [] } = userInfo |
||||
|
return role.some(r => r == 'SuperAdmin' || r == 'admin') |
||||
|
} catch (error) { |
||||
|
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async function anxinStrucIdRange ({ ctx, pepProjectId, keywordTarget, keyword }) { |
||||
|
const { models } = ctx.fs.dc; |
||||
|
const { userInfo = {} } = ctx.fs.api || {}; |
||||
|
const { clickHouse } = ctx.app.fs |
||||
|
const { correlationProject = [] } = userInfo |
||||
|
|
||||
|
const isSuper = judgeSuper(ctx) |
||||
|
let findOption = { |
||||
|
where: { |
||||
|
del: false |
||||
|
} |
||||
|
} |
||||
|
if (pepProjectId) { |
||||
|
// 有 特定的项目id 就按此查询
|
||||
|
findOption.where.pepProjectId = pepProjectId |
||||
|
} else if (!isSuper) { |
||||
|
// 还不是超管或管理员就按关联的项目id的数据范围查
|
||||
|
findOption.where.id = { $in: correlationProject } |
||||
|
} |
||||
|
|
||||
|
let pepProjectWhereOptions = [] |
||||
|
if (keywordTarget == 'pepProject' && keyword) { |
||||
|
pepProjectWhereOptions.push(`t_pim_project.project_name LIKE '%${keyword}%'`) |
||||
|
findOption.where.name = { |
||||
|
$or: [{ |
||||
|
$eq: null |
||||
|
}, { |
||||
|
$like: `%${keyword}%` |
||||
|
}] |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// TODO 这儿也许需要判断传进来的 pepProjectId 在不在当前用户的关注范围内
|
||||
|
// 根据 poms 的项目绑定关系查相关联的项企项目、安心云项目id信息
|
||||
|
const bindRes = await models.ProjectCorrelation.findAll(findOption) |
||||
|
|
||||
|
// 获取不重复的 项企项目id
|
||||
|
let pepProjectIds = [] |
||||
|
for (let b of bindRes) { |
||||
|
if (b.pepProjectId) { |
||||
|
pepProjectIds.push(b.pepProjectId) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 查询项企项目的信息
|
||||
|
const pepProjectRes = pepProjectIds.length ? |
||||
|
await clickHouse.projectManage.query( |
||||
|
` |
||||
|
SELECT |
||||
|
t_pim_project.id AS id, |
||||
|
t_pim_project.project_name AS projectName, |
||||
|
t_pim_project.isdelete AS isdelete, |
||||
|
t_pim_project_construction.construction_status_id AS constructionStatusId, |
||||
|
t_pim_project_state.construction_status AS constructionStatus |
||||
|
FROM |
||||
|
t_pim_project |
||||
|
LEFT JOIN t_pim_project_construction |
||||
|
ON t_pim_project.id = t_pim_project_construction.project_id |
||||
|
LEFT JOIN t_pim_project_state |
||||
|
ON t_pim_project_construction.construction_status_id = t_pim_project_state.id |
||||
|
WHERE |
||||
|
id IN (${pepProjectIds.join(',')}) |
||||
|
${pepProjectWhereOptions.length ? `AND ${pepProjectWhereOptions.join(' AND ')}` |
||||
|
: ''} |
||||
|
` |
||||
|
).toPromise() : |
||||
|
[] |
||||
|
|
||||
|
|
||||
|
// 获取不重复的 安心云项目 id
|
||||
|
const anxinProjectIds = [ |
||||
|
...( |
||||
|
keywordTarget == 'pepProject' && keyword ? |
||||
|
bindRes.filter(b => b.name || pepProjectRes.some(pp => pp.id == b.pepProjectId)) : |
||||
|
bindRes |
||||
|
).reduce( |
||||
|
(arr, b) => { |
||||
|
for (let sid of b.anxinProjectId) { |
||||
|
arr.add(sid); |
||||
|
} |
||||
|
return arr; |
||||
|
}, |
||||
|
new Set() |
||||
|
) |
||||
|
] |
||||
|
|
||||
|
|
||||
|
// 查询安心云项目及结构物信息
|
||||
|
let undelStrucWhereOptions = [] |
||||
|
if (keywordTarget && keyword) { |
||||
|
if (keywordTarget == 'struc') { |
||||
|
undelStrucWhereOptions.push(`t_structure.name LIKE '%${keyword}%'`) |
||||
|
} |
||||
|
} |
||||
|
const undelStrucRes = anxinProjectIds.length ? |
||||
|
await clickHouse.anxinyun.query( |
||||
|
` |
||||
|
SELECT |
||||
|
t_project.id AS projectId, |
||||
|
t_structure.id AS strucId, |
||||
|
t_structure.name AS strucName, |
||||
|
project_state |
||||
|
FROM |
||||
|
t_project |
||||
|
LEFT JOIN |
||||
|
t_project_structure |
||||
|
ON t_project_structure.project = t_project.id |
||||
|
LEFT JOIN |
||||
|
t_project_structuregroup |
||||
|
ON t_project_structuregroup.project = t_project.id |
||||
|
LEFT JOIN |
||||
|
t_structuregroup_structure |
||||
|
ON t_structuregroup_structure.structuregroup = t_project_structuregroup.structuregroup |
||||
|
RIGHT JOIN |
||||
|
t_structure |
||||
|
ON t_structure.id = t_project_structure.structure |
||||
|
OR t_structure.id = t_structuregroup_structure.structure |
||||
|
WHERE |
||||
|
project_state != -1 |
||||
|
AND |
||||
|
t_project.id IN (${anxinProjectIds.join(',')}) |
||||
|
${undelStrucWhereOptions.length ? `AND ${undelStrucWhereOptions.join(' AND ')}` : ''} |
||||
|
` |
||||
|
).toPromise() : |
||||
|
[] |
||||
|
|
||||
|
// 构建安心云结构物和项企项目的关系
|
||||
|
// 并保存信息至数据
|
||||
|
let undelStruc = [] |
||||
|
for (let s of undelStrucRes) { |
||||
|
if (!undelStruc.some(us => us.strucId == s.strucId)) { |
||||
|
let pomsProject = [] |
||||
|
for (let { dataValues: br } of bindRes) { |
||||
|
if (br.anxinProjectId.some(braId => braId == s.projectId)) { |
||||
|
let corPepProject = pepProjectRes.find(pp => pp.id == br.pepProjectId) |
||||
|
pomsProject.push({ |
||||
|
...br, |
||||
|
pepProject: corPepProject |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
undelStruc.push({ |
||||
|
strucId: s.strucId, |
||||
|
strucName: s.strucName, |
||||
|
// projectId: s.projectId,
|
||||
|
pomsProject: pomsProject |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
return undelStruc |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
judgeSuper, |
||||
|
anxinStrucIdRange |
||||
|
} |
||||
|
} |
@ -0,0 +1,17 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
const path = require('path'); |
||||
|
const fs = require('fs'); |
||||
|
|
||||
|
module.exports = async function (app, opts) { |
||||
|
fs.readdirSync(__dirname).forEach((filename) => { |
||||
|
if (!['index.js'].some(f => filename == f)) { |
||||
|
const utils = require(`./${filename}`)(app, opts) |
||||
|
console.log(`载入 ${filename} 工具集成功`); |
||||
|
app.fs.utils = { |
||||
|
...app.fs.utils, |
||||
|
...utils, |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
}; |
@ -0,0 +1,21 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
module.exports = function (app, opts) { |
||||
|
|
||||
|
async function kfkSendAsync (payloads) { |
||||
|
const { producer } = app.fs.kafka |
||||
|
return new Promise((resolve, reject) => { |
||||
|
producer.send(payloads, function (err) { |
||||
|
if (err) { |
||||
|
reject(err); |
||||
|
} else { |
||||
|
resolve(); |
||||
|
} |
||||
|
}); |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
kfkSendAsync |
||||
|
} |
||||
|
} |
@ -0,0 +1,30 @@ |
|||||
|
'use strict'; |
||||
|
const moment = require('moment') |
||||
|
|
||||
|
module.exports = function (app, opts) { |
||||
|
|
||||
|
async function vcmpAuth () { |
||||
|
const { vcmp: { app: vcApp } } = opts |
||||
|
const vcmpAuth = await app.redis.hgetall('vcmpAuth'); |
||||
|
if (vcmpAuth.token && moment().isBefore(moment(vcmpAuth.expires))) { |
||||
|
return vcmpAuth.token |
||||
|
} else { |
||||
|
let res = await app.fs.iotAuthRequest.post('oauth2/token', { |
||||
|
data: { |
||||
|
grant_type: 'client_credentials' |
||||
|
}, |
||||
|
header: { |
||||
|
Authorization: `Basic ${Buffer.from(`${encodeURIComponent(vcApp.id)}:${encodeURIComponent(vcApp.secret)}`).toString('base64')}` |
||||
|
} |
||||
|
}) |
||||
|
await app.redis.hmset('vcmpAuth', { |
||||
|
...res |
||||
|
}); |
||||
|
return res.token |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
vcmpAuth |
||||
|
} |
||||
|
} |
@ -0,0 +1,82 @@ |
|||||
|
'use strict'; |
||||
|
const fs = require('fs'); |
||||
|
const xlsx = require('better-xlsx'); |
||||
|
const path = require('path') |
||||
|
const moment = require('moment') |
||||
|
|
||||
|
|
||||
|
module.exports = function (app, opts) { |
||||
|
|
||||
|
//递归创建目录 同步方法
|
||||
|
async function makeDir (dir) { |
||||
|
if (!fs.existsSync(dir)) { |
||||
|
makeDir(path.dirname(dir)) |
||||
|
fs.mkdirSync(dir, function (err) { |
||||
|
if (err) { |
||||
|
throw err |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async function simpleExcelDown ({ data = [], header = [], fileName = moment().format('YYYY-MM-DD HH:mm:ss') } = {}) { |
||||
|
const fileDirPath = path.join(__dirname, `../../downloadFiles`) |
||||
|
makeDir(fileDirPath) |
||||
|
const file = new xlsx.File(); |
||||
|
const sheet_1 = file.addSheet('sheet_1'); |
||||
|
|
||||
|
// header
|
||||
|
const headerStyle = new xlsx.Style(); |
||||
|
headerStyle.align.h = 'center'; |
||||
|
headerStyle.align.v = 'center'; |
||||
|
headerStyle.border.right = 'thin'; |
||||
|
headerStyle.border.rightColor = '#000000'; |
||||
|
headerStyle.border.bottom = 'thin'; |
||||
|
headerStyle.border.bottomColor = '#000000'; |
||||
|
|
||||
|
const headerRow = sheet_1.addRow(); |
||||
|
const indexCell = headerRow.addCell(); |
||||
|
indexCell.value = '序号' |
||||
|
indexCell.style = headerStyle |
||||
|
for (let h of header) { |
||||
|
const cell = headerRow.addCell(); |
||||
|
cell.value = h.title; |
||||
|
cell.style = headerStyle |
||||
|
} |
||||
|
|
||||
|
// data
|
||||
|
const style = new xlsx.Style(); |
||||
|
style.align.h = 'left'; |
||||
|
style.align.v = 'center'; |
||||
|
style.border.right = 'thin'; |
||||
|
style.border.rightColor = '#000000'; |
||||
|
style.border.bottom = 'thin'; |
||||
|
style.border.bottomColor = '#000000'; |
||||
|
for (let i = 0; i < data.length; i++) { |
||||
|
const row = sheet_1.addRow(); |
||||
|
const indexCell = row.addCell(); |
||||
|
indexCell.value = i + 1 |
||||
|
indexCell.style = headerStyle |
||||
|
for (let h of header) { |
||||
|
const cell = row.addCell(); |
||||
|
cell.value = data[i][h.key]; |
||||
|
cell.style = style |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const savePath = path.join(fileDirPath, fileName) |
||||
|
await new Promise(function (resolve, reject) { |
||||
|
file.saveAs() |
||||
|
.pipe(fs.createWriteStream(savePath)) |
||||
|
.on('finish', () => { |
||||
|
resolve() |
||||
|
}); |
||||
|
}) |
||||
|
return savePath |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
simpleExcelDown, |
||||
|
makeDir |
||||
|
} |
||||
|
} |
@ -0,0 +1,298 @@ |
|||||
|
'use strict'; |
||||
|
/*jslint node:true*/ |
||||
|
const path = require('path'); |
||||
|
const os = require('os'); |
||||
|
const moment = require('moment'); |
||||
|
const args = require('args'); |
||||
|
|
||||
|
const dev = process.env.NODE_ENV == 'development'; |
||||
|
|
||||
|
// 启动参数
|
||||
|
args.option(['p', 'port'], '启动端口'); |
||||
|
args.option(['g', 'pg'], 'postgre 服务 URL'); |
||||
|
args.option(['f', 'fileHost'], '文件中心本地化存储: WebApi 服务器地址(必填), 该服务器提供文件上传Web服务'); |
||||
|
args.option(['k', 'kafka'], 'kafka 服务 URL'); |
||||
|
|
||||
|
args.option('iotaProxy', '以太代理') |
||||
|
|
||||
|
args.option('redisHost', 'redisHost'); |
||||
|
args.option('redisPort', 'redisPort'); |
||||
|
args.option('redisPswd', 'redisPassword'); |
||||
|
|
||||
|
args.option('axyApiUrl', '安心云 api'); |
||||
|
args.option('apiEmisUrl', '企业管理 api'); |
||||
|
args.option('apiVcmpUrl', '视频平台 api'); |
||||
|
args.option('apiIotAuth', 'IOT 鉴权平台') |
||||
|
|
||||
|
args.option('godUrl', '高德地图API请求地址'); |
||||
|
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'); |
||||
|
|
||||
|
// clickHouse
|
||||
|
args.option('clickHouseUrl', 'clickHouse Url'); |
||||
|
args.option('clickHousePort', 'clickHouse Port'); |
||||
|
args.option('clickHouseAnxincloud', 'clickHouse 安心云数据库名称'); |
||||
|
args.option('clickHousePepEmis', 'clickHouse 项企数据库名称'); |
||||
|
args.option('clickHouseProjectManage', 'clickHouse 项目管理数据库名称'); |
||||
|
args.option('clickHouseVcmp', 'clickHouse 视频平台数据库名称'); |
||||
|
args.option('clickHouseDataAlarm', 'clickHouse 视频平台数据告警库名称'); |
||||
|
|
||||
|
args.option('confirmAlarmAnxinUserId', '确认告警时保存到 ES 的安心云的用户的 id'); |
||||
|
|
||||
|
// 视频应用秘钥
|
||||
|
args.option('vcmpAppId', '视频平台 应用 id') |
||||
|
args.option('vcmpAppSecret', '视频平台 应用秘钥') |
||||
|
|
||||
|
const flags = args.parse(process.argv); |
||||
|
|
||||
|
const POMS_DB = process.env.POMS_DB || flags.pg; |
||||
|
const LOCAL_SVR_ORIGIN = process.env.LOCAL_SVR_ORIGIN || flags.fileHost; |
||||
|
|
||||
|
// kafka
|
||||
|
const ANXINCLOUD_KAFKA_BROKERS = process.env.ANXINCLOUD_KAFKA_BROKERS || flags.kafka; |
||||
|
|
||||
|
// 以太代理
|
||||
|
const IOT_PROXY = process.env.IOT_PROXY || flags.iotaProxy; |
||||
|
|
||||
|
// Redis 参数
|
||||
|
const IOTA_REDIS_SERVER_HOST = process.env.IOTA_REDIS_SERVER_HOST || flags.redisHost || "localhost";//redis IP
|
||||
|
const IOTA_REDIS_SERVER_PORT = process.env.IOTA_REDIS_SERVER_PORT || flags.redisPort || "6379";//redis 端口
|
||||
|
const IOTA_REDIS_SERVER_PWD = process.env.IOTA_REDIS_SERVER_PWD || flags.redisPswd || "";//redis 密码
|
||||
|
|
||||
|
// 安心云api
|
||||
|
const API_ANXINYUN_URL = process.env.API_ANXINYUN_URL || flags.axyApiUrl; |
||||
|
// 企业管理 api
|
||||
|
const API_EMIS_URL = process.env.API_EMIS_URL || flags.apiEmisUrl; |
||||
|
// 视频平台 api
|
||||
|
const API_VCMP_URL = process.env.API_VCMP_URL || flags.apiVcmpUrl; |
||||
|
// iot鉴权平台 api
|
||||
|
const API_IOT_AUTH = process.env.API_IOT_AUTH || flags.apiIotAuth; |
||||
|
|
||||
|
// 高德地图的参数
|
||||
|
const GOD_URL = process.env.GOD_URL || flags.godUrl || 'https://restapi.amap.com/v3'; |
||||
|
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; |
||||
|
|
||||
|
// clickHouse
|
||||
|
const CLICKHOUST_URL = process.env.CLICKHOUST_URL || flags.clickHouseUrl |
||||
|
const CLICKHOUST_PORT = process.env.CLICKHOUST_PORT || flags.clickHousePort |
||||
|
const CLICKHOUST_USER = process.env.CLICKHOUST_USER || flags.clickHouseUser |
||||
|
const CLICKHOUST_PASSWORD = process.env.CLICKHOUST_PASSWORD || flags.clickHousePassword |
||||
|
const CLICKHOUST_ANXINCLOUD = process.env.CLICKHOUST_ANXINCLOUD || flags.clickHouseAnxincloud |
||||
|
const CLICKHOUST_PEP_EMIS = process.env.CLICKHOUST_PEP_EMIS || flags.clickHousePepEmis |
||||
|
const CLICKHOUST_PROJECT_MANAGE = process.env.CLICKHOUST_PROJECT_MANAGE || flags.clickHouseProjectManage |
||||
|
const CLICKHOUST_VCMP = process.env.CLICKHOUST_VCMP || flags.clickHouseVcmp |
||||
|
const CLICKHOUST_DATA_ALARM = process.env.CLICKHOUST_DATA_ALARM || flags.clickHouseDataAlarm |
||||
|
|
||||
|
const CONFIRM_ALARM_ANXIN_USER_ID = process.env.CONFIRM_ALARM_ANXIN_USER_ID || flags.confirmAlarmAnxinUserId |
||||
|
|
||||
|
const PLATFORM_NAME = process.env.PLATFORM_NAME || flags.platformName || 'anxinyun'; |
||||
|
|
||||
|
// 视频平台应用秘钥
|
||||
|
const VCMP_APP_ID = process.env.VCMP_APP_ID || flags.vcmpAppId |
||||
|
const VCMP_APP_SECRET = process.env.VCMP_APP_SECRET || flags.vcmpAppSecret |
||||
|
|
||||
|
if ( |
||||
|
!POMS_DB |
||||
|
|| !IOTA_REDIS_SERVER_HOST || !IOTA_REDIS_SERVER_PORT |
||||
|
|| !ANXINCLOUD_KAFKA_BROKERS |
||||
|
|| !GOD_KEY |
||||
|
|| !API_ANXINYUN_URL |
||||
|
|| !API_EMIS_URL |
||||
|
|| !API_VCMP_URL |
||||
|
|| !API_IOT_AUTH |
||||
|
|| !QINIU_DOMAIN_QNDMN_RESOURCE || !QINIU_BUCKET_RESOURCE || !QINIU_AK || !QINIU_SK |
||||
|
|| !CLICKHOUST_URL || !CLICKHOUST_PORT |
||||
|
|| !CLICKHOUST_ANXINCLOUD || !CLICKHOUST_PEP_EMIS || !CLICKHOUST_PROJECT_MANAGE || !CLICKHOUST_VCMP || !CLICKHOUST_DATA_ALARM |
||||
|
|| !CONFIRM_ALARM_ANXIN_USER_ID |
||||
|
|| !VCMP_APP_ID || !VCMP_APP_SECRET |
||||
|
) { |
||||
|
console.log('缺少启动参数,异常退出'); |
||||
|
args.showHelp(); |
||||
|
process.exit(-1); |
||||
|
} |
||||
|
|
||||
|
const product = { |
||||
|
port: flags.port || 8080, |
||||
|
staticDirs: ['static'], |
||||
|
mws: [ |
||||
|
{ |
||||
|
entry: require('@fs/attachment').entry, |
||||
|
opts: { |
||||
|
qiniu: { |
||||
|
domain: QINIU_DOMAIN_QNDMN_RESOURCE, |
||||
|
bucket: QINIU_BUCKET_RESOURCE, |
||||
|
accessKey: QINIU_AK, |
||||
|
secretKey: QINIU_SK |
||||
|
}, |
||||
|
maxSize: 104857600, // 100M
|
||||
|
} |
||||
|
}, { |
||||
|
entry: require('./app').entry, |
||||
|
opts: { |
||||
|
dev, |
||||
|
exclude: [ |
||||
|
{ p: '/attachments/:p', o: 'POST' }, |
||||
|
{ p: '/alarm/application/inspection', o: 'POST' }, |
||||
|
{ p: '/project/app_list', o: 'GET' }, |
||||
|
{ p: '/alarm/application/api', o: 'POST' } |
||||
|
], // 不做认证的路由,也可以使用 exclude: ["*"] 跳过所有路由
|
||||
|
anxinCloud: { |
||||
|
confirmAlarmAnxinUserId: CONFIRM_ALARM_ANXIN_USER_ID |
||||
|
}, |
||||
|
vcmp: { |
||||
|
app: { |
||||
|
id: VCMP_APP_ID, |
||||
|
secret: VCMP_APP_SECRET |
||||
|
} |
||||
|
}, |
||||
|
kafka: { |
||||
|
rootURL: ANXINCLOUD_KAFKA_BROKERS, |
||||
|
topicPrefix: PLATFORM_NAME |
||||
|
}, |
||||
|
redis: { |
||||
|
host: IOTA_REDIS_SERVER_HOST, |
||||
|
port: IOTA_REDIS_SERVER_PORT, |
||||
|
pwd: IOTA_REDIS_SERVER_PWD |
||||
|
}, |
||||
|
mqtt: { |
||||
|
mqttVideoServer: MQTT_VIDEO_SERVER, |
||||
|
}, |
||||
|
sms: { |
||||
|
///阿里云-安心云
|
||||
|
accessKey: 'LTAI5tAFdjz7j38aNF2C9Qe8', |
||||
|
accessSecret: '1trYkmiqfBtvZL6BxkNH2uQcQQPs0S' |
||||
|
}, |
||||
|
email: { |
||||
|
enabled: true, |
||||
|
host: 'smtp.exmail.qq.com', |
||||
|
port: 465, |
||||
|
sender: { |
||||
|
name: '运维服务', |
||||
|
address: 'fsiot@free-sun.com.cn', |
||||
|
password: 'Fs2689' |
||||
|
} |
||||
|
}, |
||||
|
pssaRequest: [{// name 会作为一个 request 出现在 ctx.app.fs
|
||||
|
name: 'axyRequest', |
||||
|
root: API_ANXINYUN_URL |
||||
|
}, { |
||||
|
name: 'emisRequest', |
||||
|
root: API_EMIS_URL |
||||
|
}, { |
||||
|
name: 'vcmpRequest', |
||||
|
root: API_VCMP_URL |
||||
|
}, { |
||||
|
name: 'iotAuthRequest', |
||||
|
root: API_IOT_AUTH |
||||
|
}, { |
||||
|
name: 'iotRequest', |
||||
|
root: IOT_PROXY + '/_iota_api' |
||||
|
}, { |
||||
|
name: 'godRequest', |
||||
|
root: GOD_URL, |
||||
|
params: { |
||||
|
query: { |
||||
|
key: GOD_KEY |
||||
|
} |
||||
|
} |
||||
|
},], |
||||
|
clickHouse: { |
||||
|
url: CLICKHOUST_URL, |
||||
|
port: CLICKHOUST_PORT, |
||||
|
user: CLICKHOUST_USER, |
||||
|
password: CLICKHOUST_PASSWORD, |
||||
|
db: [ |
||||
|
{ |
||||
|
name: 'anxinyun', |
||||
|
db: CLICKHOUST_ANXINCLOUD |
||||
|
}, { |
||||
|
name: 'pepEmis', |
||||
|
db: CLICKHOUST_PEP_EMIS |
||||
|
}, { |
||||
|
name: 'projectManage', |
||||
|
db: CLICKHOUST_PROJECT_MANAGE |
||||
|
}, { |
||||
|
name: 'vcmp', |
||||
|
db: CLICKHOUST_VCMP |
||||
|
}, { |
||||
|
name: 'dataAlarm', |
||||
|
db: CLICKHOUST_DATA_ALARM |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
], |
||||
|
dc: { |
||||
|
url: POMS_DB, |
||||
|
opts: { |
||||
|
pool: { |
||||
|
max: 80, |
||||
|
min: 10, |
||||
|
idle: 10000 |
||||
|
}, |
||||
|
define: { |
||||
|
freezeTableName: true, // 固定表名
|
||||
|
timestamps: false // 不含列 "createAt"/"updateAt"/"DeleteAt"
|
||||
|
}, |
||||
|
timezone: '+08:00', |
||||
|
logging: false |
||||
|
}, |
||||
|
models: [require('./app').models] |
||||
|
}, |
||||
|
logger: { |
||||
|
level: 'info', |
||||
|
json: false, |
||||
|
filename: path.join(__dirname, 'log', 'runtime.log'), |
||||
|
colorize: false, |
||||
|
maxsize: 1024 * 1024 * 5, |
||||
|
rotationFormat: false, |
||||
|
zippedArchive: true, |
||||
|
maxFiles: 10, |
||||
|
prettyPrint: true, |
||||
|
label: '', |
||||
|
timestamp: () => moment().format('YYYY-MM-DD HH:mm:ss.SSS'), |
||||
|
eol: os.EOL, |
||||
|
tailable: true, |
||||
|
depth: null, |
||||
|
showLevel: true, |
||||
|
maxRetries: 1 |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const development = { |
||||
|
port: product.port, |
||||
|
staticDirs: product.staticDirs, |
||||
|
mws: product.mws, |
||||
|
dc: product.dc, |
||||
|
logger: product.logger |
||||
|
}; |
||||
|
|
||||
|
if (dev) { |
||||
|
// mws
|
||||
|
for (let mw of development.mws) { |
||||
|
// if (mw.opts.exclude) mw.opts.exclude = ['*']; // 使用 ['*'] 跳过所有路由
|
||||
|
} |
||||
|
// logger
|
||||
|
development.logger.filename = path.join(__dirname, 'log', 'development.log'); |
||||
|
development.logger.level = 'debug'; |
||||
|
development.dc.opts.logging = console.log; |
||||
|
} |
||||
|
|
||||
|
module.exports = dev ? development : product; |
@ -0,0 +1,43 @@ |
|||||
|
{ |
||||
|
"name": "smart-emergency", |
||||
|
"version": "1.0.0", |
||||
|
"description": "fs smart emergency api", |
||||
|
"main": "server.js", |
||||
|
"scripts": { |
||||
|
"test": "set DEBUG=true&&\"node_modules/.bin/mocha\" --harmony --reporter spec app/test/*.test.js", |
||||
|
"start": "set NODE_ENV=development&&node server -p 4000 -g postgres://postgres:123@10.8.30.32:5432/video_access -f http://localhost:4000", |
||||
|
"start:linux": "export NODE_ENV=development&&node server -p 4000 -g postgres://FashionAdmin:123456@10.8.30.39:5432/pm1", |
||||
|
"automate": "sequelize-automate -c sequelize-automate.config.js" |
||||
|
}, |
||||
|
"author": "", |
||||
|
"license": "MIT", |
||||
|
"repository": {}, |
||||
|
"dependencies": { |
||||
|
"@alicloud/pop-core": "^1.7.12", |
||||
|
"@fs/attachment": "^1.0.0", |
||||
|
"args": "^3.0.7", |
||||
|
"better-xlsx": "^0.7.6", |
||||
|
"clickhouse": "^2.6.0", |
||||
|
"crypto-js": "^4.0.0", |
||||
|
"file-saver": "^2.0.2", |
||||
|
"fs-web-server-scaffold": "^2.0.2", |
||||
|
"ioredis": "^5.0.4", |
||||
|
"kafka-node": "^2.2.3", |
||||
|
"koa-convert": "^1.2.0", |
||||
|
"koa-proxy": "^0.9.0", |
||||
|
"moment": "^2.24.0", |
||||
|
"mqtt": "^4.3.7", |
||||
|
"node-schedule": "^2.1.0", |
||||
|
"nodemailer": "^6.7.7", |
||||
|
"path": "^0.12.7", |
||||
|
"path-to-regexp": "^3.0.0", |
||||
|
"pg": "^7.9.0", |
||||
|
"redis": "^3.1.2", |
||||
|
"request": "^2.88.2", |
||||
|
"superagent": "^3.5.2", |
||||
|
"uuid": "^3.3.2" |
||||
|
}, |
||||
|
"devDependencies": { |
||||
|
"mocha": "^6.0.2" |
||||
|
} |
||||
|
} |
@ -0,0 +1,35 @@ |
|||||
|
module.exports = { |
||||
|
// 数据库配置 与 sequelize 相同
|
||||
|
dbOptions: { |
||||
|
database: 'orational_service', |
||||
|
username: 'postgres', |
||||
|
password: '123', |
||||
|
dialect: 'postgres', |
||||
|
host: '10.8.30.32', |
||||
|
port: 5432, |
||||
|
define: { |
||||
|
underscored: false, |
||||
|
freezeTableName: false, |
||||
|
charset: 'utf8mb4', |
||||
|
timezone: '+00: 00', |
||||
|
dialectOptions: { |
||||
|
collate: 'utf8_general_ci', |
||||
|
}, |
||||
|
timestamps: false, |
||||
|
}, |
||||
|
}, |
||||
|
options: { |
||||
|
type: 'freesun', // 指定 models 代码风格
|
||||
|
camelCase: true, // Models 文件中代码是否使用驼峰命名
|
||||
|
modalNameSuffix: false, // 模型名称是否带 ‘Model’ 后缀
|
||||
|
fileNameCamelCase: false, // Model 文件名是否使用驼峰法命名,默认文件名会使用表名,如 `user_post.js`;如果为 true,则文件名为 `userPost.js`
|
||||
|
dir: './app/lib/models', // 指定输出 models 文件的目录
|
||||
|
typesDir: 'models', // 指定输出 TypeScript 类型定义的文件目录,只有 TypeScript / Midway 等会有类型定义
|
||||
|
emptyDir: false, // !!! 谨慎操作 生成 models 之前是否清空 `dir` 以及 `typesDir`
|
||||
|
tables: ['quick_link'], // 指定生成哪些表的 models,如 ['user', 'user_post'];如果为 null,则忽略改属性
|
||||
|
skipTables: [], // 指定跳过哪些表的 models,如 ['user'];如果为 null,则忽略改属性
|
||||
|
tsNoCheck: false, // 是否添加 `@ts-nocheck` 注释到 models 文件中
|
||||
|
ignorePrefix: [], // 生成的模型名称忽略的前缀,因为 项目中有以下表名是以 t_ 开头的,在实际模型中不需要, 可以添加多个 [ 't_data_', 't_',] ,长度较长的 前缀放前面
|
||||
|
attrLength: false, // 在生成模型的字段中 是否生成 如 var(128)这种格式,公司一般使用 String ,则配置为 false
|
||||
|
}, |
||||
|
} |
@ -0,0 +1,12 @@ |
|||||
|
/** |
||||
|
* Created by rain on 2016/1/25. |
||||
|
*/ |
||||
|
|
||||
|
'use strict'; |
||||
|
/*jslint node:true*/ |
||||
|
//from koa
|
||||
|
|
||||
|
const scaffold = require('fs-web-server-scaffold'); |
||||
|
const config = require('./config'); |
||||
|
|
||||
|
module.exports = scaffold(config); |
@ -0,0 +1,19 @@ |
|||||
|
pipeline { |
||||
|
agent { |
||||
|
node{ |
||||
|
label 'jnlp-slave' |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
stages { |
||||
|
stage('Testing poms ......') { |
||||
|
steps { |
||||
|
sh 'switch-auth.sh anxinyun' |
||||
|
buildName "#${BUILD_NUMBER} ~/fs-cloud/${JOB_NAME}:${IMAGE_VERSION}" |
||||
|
buildDescription "registry.cn-hangzhou.aliyuncs.com/${CLOUD}/${JOB_NAME}:${IMAGE_VERSION}" |
||||
|
sh 'docker build -t registry.cn-hangzhou.aliyuncs.com/${CLOUD}/${JOB_NAME}:${IMAGE_VERSION} ./api' |
||||
|
sh 'docker push registry.cn-hangzhou.aliyuncs.com/${CLOUD}/${JOB_NAME}:${IMAGE_VERSION}' |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,19 @@ |
|||||
|
pipeline { |
||||
|
agent { |
||||
|
node{ |
||||
|
label 'jnlp-slave' |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
stages { |
||||
|
stage('Testing poms ......') { |
||||
|
steps { |
||||
|
sh 'switch-auth.sh anxinyun' |
||||
|
buildName "#${BUILD_NUMBER} ~/fs-cloud/${JOB_NAME}:${IMAGE_VERSION}" |
||||
|
buildDescription "registry.cn-hangzhou.aliyuncs.com/${CLOUD}/${JOB_NAME}:${IMAGE_VERSION}" |
||||
|
sh 'docker build -t registry.cn-hangzhou.aliyuncs.com/${CLOUD}/${JOB_NAME}:${IMAGE_VERSION} ./web' |
||||
|
sh 'docker push registry.cn-hangzhou.aliyuncs.com/${CLOUD}/${JOB_NAME}:${IMAGE_VERSION}' |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,17 @@ |
|||||
|
{ |
||||
|
"presets": [ |
||||
|
"@babel/preset-react", |
||||
|
"@babel/preset-env" |
||||
|
], |
||||
|
"plugins": [ |
||||
|
"@babel/plugin-proposal-class-properties", |
||||
|
"@babel/plugin-proposal-object-rest-spread", |
||||
|
// ["import", { |
||||
|
// // "libraryName": "antd", |
||||
|
// "libraryDirectory": "es" |
||||
|
// }] |
||||
|
], |
||||
|
"env": { |
||||
|
"development": {} |
||||
|
} |
||||
|
} |
@ -0,0 +1,5 @@ |
|||||
|
{ |
||||
|
"recommendations": [ |
||||
|
"formulahendry.code-runner" |
||||
|
] |
||||
|
} |
@ -0,0 +1,18 @@ |
|||||
|
{ |
||||
|
"version": "0.2.0", |
||||
|
"configurations": [ |
||||
|
{ |
||||
|
"name": "Server", |
||||
|
"type": "node", |
||||
|
"request": "launch", |
||||
|
"program": "${workspaceRoot}/server.js", |
||||
|
"args": [ |
||||
|
"-u http://127.0.0.1:4000" |
||||
|
], |
||||
|
"outputCapture": "std", |
||||
|
"env": { |
||||
|
"NODE_ENV": "development" |
||||
|
} |
||||
|
} |
||||
|
] |
||||
|
} |
@ -0,0 +1,4 @@ |
|||||
|
// 将设置放入此文件中以覆盖默认值和用户设置。 |
||||
|
{ |
||||
|
"editor.fontSize": 16, |
||||
|
} |
@ -0,0 +1,40 @@ |
|||||
|
# FROM repository.anxinyun.cn/devops/node:12-dev as builder |
||||
|
|
||||
|
# COPY . /var/app |
||||
|
|
||||
|
# WORKDIR /var/app |
||||
|
|
||||
|
# EXPOSE 8080 |
||||
|
|
||||
|
# RUN npm config set registry=http://10.8.30.22:7000 |
||||
|
# RUN echo "{\"time\":\"$BUILD_TIMESTAMP\",\"build\": \"$BUILD_NUMBER\",\"revision\": \"$SVN_REVISION_1\",\"URL\":\"$SVN_URL_1\"}" > version.json |
||||
|
# RUN npm cache clean -f |
||||
|
# RUN npm install --registry http://10.8.30.22:7000 |
||||
|
# RUN npm run build |
||||
|
# RUN rm -rf client/src |
||||
|
# RUN rm -rf node_modules |
||||
|
# RUN npm install --production --registry http://10.8.30.22:7000 |
||||
|
|
||||
|
# FROM registry.cn-hangzhou.aliyuncs.com/fs-devops/node-16:7.22-06-20 |
||||
|
|
||||
|
# COPY --from=builder --chown=node /var/app /home/node/app |
||||
|
|
||||
|
# WORKDIR /home/node/app |
||||
|
|
||||
|
# CMD ["node", "server.js"] |
||||
|
|
||||
|
|
||||
|
# 旧版本构建方式 |
||||
|
|
||||
|
|
||||
|
FROM repository.anxinyun.cn/base-images/nodejs12:20.10.12.2 |
||||
|
|
||||
|
COPY . /var/app |
||||
|
|
||||
|
WORKDIR /var/app |
||||
|
|
||||
|
EXPOSE 8080 |
||||
|
|
||||
|
CMD ["-u", "http://localhost:8088"] |
||||
|
|
||||
|
ENTRYPOINT [ "node", "server.js" ] |
After Width: | Height: | Size: 152 KiB |
After Width: | Height: | Size: 226 KiB |
After Width: | Height: | Size: 791 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 576 B |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 549 B |
After Width: | Height: | Size: 1012 B |
After Width: | Height: | Size: 5.7 KiB |
After Width: | Height: | Size: 694 B |
After Width: | Height: | Size: 924 B |
After Width: | Height: | Size: 66 KiB |
@ -0,0 +1,21 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html> |
||||
|
|
||||
|
<head> |
||||
|
<meta charset="UTF-8"> |
||||
|
<meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" /> |
||||
|
|
||||
|
<!-- <meta content="upgrade-insecure-requests" http-equiv="Content-Security-Policy"> --> |
||||
|
|
||||
|
<link rel="shortcut icon" href="/assets/images/favicon.ico"> |
||||
|
<script charset="UTF-8" id="LA_COLLECT" src="//sdk.51.la/js-sdk-pro.min.js"></script> |
||||
|
<script>LA.init({ id: "Jo4eTlZVqgx3uwqm", ck: "Jo4eTlZVqgx3uwqm" })</script> |
||||
|
|
||||
|
<script src="https://lf1-cdn-tos.bytegoofy.com/obj/iconpark/icons_19077_10.1efd80a22a5e53e48737fd5ab150ffd2.es5.js"></script> |
||||
|
</head> |
||||
|
|
||||
|
<body> |
||||
|
<div id='PomsApp' style="height: 100%;"></div> |
||||
|
</body> |
||||
|
|
||||
|
</html> |
@ -0,0 +1,54 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html> |
||||
|
|
||||
|
<head> |
||||
|
<meta charset="UTF-8"> |
||||
|
<meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" /> |
||||
|
|
||||
|
<link rel="shortcut icon" href="/assets/images/favicon.ico"> |
||||
|
|
||||
|
<script charset="UTF-8" id="LA_COLLECT" src="//sdk.51.la/js-sdk-pro.min.js"></script> |
||||
|
<script>LA.init({ id: "Jo4eTlZVqgx3uwqm", ck: "Jo4eTlZVqgx3uwqm" })</script> |
||||
|
|
||||
|
<script |
||||
|
src="https://lf1-cdn-tos.bytegoofy.com/obj/iconpark/icons_19077_11.27aacefc59cea1cbc86236576463a6d2.es5.js"></script> |
||||
|
</head > |
||||
|
|
||||
|
<body> |
||||
|
<div id='PomsApp' style="height: 100%;"></div> |
||||
|
|
||||
|
<!-- Webpack --> |
||||
|
<script type="text/javascript" src="http://localhost:5601/client/build/app.js"></script> |
||||
|
|
||||
|
<!-- Vite --> |
||||
|
<!-- <script type="module"> |
||||
|
import RefreshRuntime from "http://localhost:5602/@react-refresh" |
||||
|
RefreshRuntime.injectIntoGlobalHook(window) |
||||
|
window.$RefreshReg$ = () => { } |
||||
|
window.$RefreshSig$ = () => (type) => type |
||||
|
window.__vite_plugin_react_preamble_installed__ = true |
||||
|
const global = window |
||||
|
</script> |
||||
|
<script type="module" src="http://localhost:5002/src/index.jsx"></script> --> |
||||
|
<!-- Vite End --> |
||||
|
|
||||
|
<script> |
||||
|
//过滤掉一些无用的警告、没有价值的报错 |
||||
|
//代理console.warn方法 |
||||
|
|
||||
|
// const _consoleWarn = (...rest) => { |
||||
|
// console.error(...rest) |
||||
|
// }; |
||||
|
// console.error = (...rest) => { |
||||
|
// if ( |
||||
|
// ![ |
||||
|
// 'Each child in a list should have a unique "key" prop', |
||||
|
// ].some((item) => rest[0].indexOf(item) !== -1) |
||||
|
// ) { |
||||
|
// _consoleWarn(...rest); |
||||
|
// } |
||||
|
// }; |
||||
|
</script> |
||||
|
</body> |
||||
|
|
||||
|
</html> |
@ -0,0 +1,20 @@ |
|||||
|
/** |
||||
|
* User: liuxinyi/liu.xinyi@free-sun.com.cn |
||||
|
* Date: 2016/2/22 |
||||
|
* Time: 15:29 |
||||
|
* |
||||
|
*/ |
||||
|
'use strict'; |
||||
|
|
||||
|
const views = require('koa-view'); |
||||
|
const path = require('path'); |
||||
|
|
||||
|
module.exports = { |
||||
|
entry: function (app, router, opt) { |
||||
|
app.use(views(__dirname)); |
||||
|
|
||||
|
router.get('(.*)', async function (ctx) { |
||||
|
await ctx.render(path.join(__dirname, './index')); |
||||
|
}); |
||||
|
} |
||||
|
}; |
@ -0,0 +1,39 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
import React, { useEffect } from 'react'; |
||||
|
import Layout from './layout'; |
||||
|
import Auth from './sections/auth'; |
||||
|
import Example from './sections/example'; |
||||
|
import NoMatch from './sections/noMatch'; |
||||
|
|
||||
|
const App = props => { |
||||
|
const { projectName } = props |
||||
|
|
||||
|
useEffect(() => { |
||||
|
document.title = projectName; |
||||
|
|
||||
|
console.log(` |
||||
|
_ _ |
||||
|
/> フ |
||||
|
| _ _ l |
||||
|
/\` ミ_xノ |
||||
|
/ | |
||||
|
/ ヽ ノ |
||||
|
│ | | | |
||||
|
/ ̄| | | | |
||||
|
| ( ̄ヽ__ヽ_)__) |
||||
|
\二つ |
||||
|
`); |
||||
|
}, []) |
||||
|
|
||||
|
return ( |
||||
|
<Layout |
||||
|
title={projectName} |
||||
|
sections={[ |
||||
|
Example,Auth, NoMatch, |
||||
|
]} |
||||
|
/> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
export default App; |
@ -0,0 +1,14 @@ |
|||||
|
'use strict'; |
||||
|
import SimpleFileDownButton from './simpleFileDownButton' |
||||
|
import ReminderBox from './reminderBox' |
||||
|
import Setup from './setup' |
||||
|
import { SkeletonScreen } from './skeletonScreen' |
||||
|
import OutHidden from './outHidden' |
||||
|
|
||||
|
export { |
||||
|
SimpleFileDownButton, |
||||
|
ReminderBox, |
||||
|
Setup, |
||||
|
SkeletonScreen, |
||||
|
OutHidden, |
||||
|
}; |
@ -0,0 +1,40 @@ |
|||||
|
import React, { useState, useEffect } from "react"; |
||||
|
import { Tooltip } from "@douyinfe/semi-ui"; |
||||
|
|
||||
|
function OutHidden ({ name, width, height, color, background, number }) { |
||||
|
|
||||
|
return <> |
||||
|
{number ? |
||||
|
<div style={{ display: 'inline-block' }}> |
||||
|
<Tooltip content={name}> |
||||
|
<div style={{ |
||||
|
width: width, |
||||
|
height: height, |
||||
|
color: color, |
||||
|
background: background, |
||||
|
}}> |
||||
|
{name.length > number ? `${name.substr(0, number)}...` : name} |
||||
|
</div> |
||||
|
</Tooltip> |
||||
|
</div> |
||||
|
|
||||
|
: <div style={{ display: 'inline-block' }}> |
||||
|
<Tooltip content={name}> |
||||
|
<div style={{ |
||||
|
width: width, |
||||
|
height: height, |
||||
|
whiteSpace: 'nowrap', |
||||
|
overflow: 'hidden', |
||||
|
textOverflow: 'ellipsis', |
||||
|
color: color, |
||||
|
background: background, |
||||
|
|
||||
|
}}> |
||||
|
{name} |
||||
|
</div> |
||||
|
</Tooltip> |
||||
|
</div>} |
||||
|
</> |
||||
|
} |
||||
|
|
||||
|
export default OutHidden; |
@ -0,0 +1,50 @@ |
|||||
|
import { Button, Checkbox, Modal } from "@douyinfe/semi-ui"; |
||||
|
import React from "react"; |
||||
|
|
||||
|
const ReminderBox = ({ title, wait, toadd, visible, onOk, close, USER}) => { |
||||
|
return ( |
||||
|
<Modal |
||||
|
title={ |
||||
|
<div style={{ fontSize: 14, lineHeight: "35px", marginLeft: 16 }}> |
||||
|
{title} |
||||
|
</div> |
||||
|
} |
||||
|
onCancel={() => { |
||||
|
close() |
||||
|
}} |
||||
|
icon={ |
||||
|
<img |
||||
|
src="../../assets/images/logo/figure.png" |
||||
|
style={{ width: 40, height: 40 }} |
||||
|
/> |
||||
|
} |
||||
|
footer={ |
||||
|
<div |
||||
|
style={{ |
||||
|
width: "100%", |
||||
|
display: "flex", |
||||
|
justifyContent: "space-between", |
||||
|
}} |
||||
|
> |
||||
|
<Checkbox |
||||
|
onChange={(checked) =>{ |
||||
|
localStorage.setItem( USER, JSON.stringify(checked.target.checked) |
||||
|
) |
||||
|
}} |
||||
|
aria-label="Checkbox 示例" |
||||
|
style={{ width: 100 }} |
||||
|
> |
||||
|
不再提醒 |
||||
|
</Checkbox> |
||||
|
<div> |
||||
|
<Button onClick={() => close()}>{wait}</Button> |
||||
|
<Button theme="solid" onClick={() => onOk()}>{toadd}</Button> |
||||
|
</div> |
||||
|
</div> |
||||
|
} |
||||
|
visible={visible} |
||||
|
></Modal> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default ReminderBox; |
@ -0,0 +1,124 @@ |
|||||
|
import React, { useState, useEffect } from "react"; |
||||
|
import { |
||||
|
Modal, |
||||
|
CheckboxGroup, |
||||
|
Checkbox, |
||||
|
} from "@douyinfe/semi-ui"; |
||||
|
|
||||
|
function Setup(props) { |
||||
|
const { |
||||
|
close, |
||||
|
tableType, |
||||
|
tableList |
||||
|
} = props; |
||||
|
|
||||
|
console.log(tableType, |
||||
|
tableList); |
||||
|
const [check, setCheck] = useState([]); |
||||
|
|
||||
|
const checkboxcss = { width: "25%", height: 16, margin: "0 0 20px 0" }; |
||||
|
|
||||
|
useEffect(() => { |
||||
|
//获取是否勾选信息 |
||||
|
const checkItem = localStorage.getItem(tableType); |
||||
|
setCheck(checkItem?JSON.parse(checkItem) : []) |
||||
|
ischeck(); |
||||
|
}, []); |
||||
|
function ischeck(value) { |
||||
|
if (check.length >= 8) { |
||||
|
if (check.includes(value)) { |
||||
|
return false; |
||||
|
} else { |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return ( |
||||
|
<Modal |
||||
|
title={ |
||||
|
<div> |
||||
|
表格属性设置 |
||||
|
<span |
||||
|
style={{ |
||||
|
width: 50, |
||||
|
lineHeight: "19px", |
||||
|
display: "inline-block", |
||||
|
|
||||
|
color: "white", |
||||
|
textAlign: "center", |
||||
|
marginLeft: 6, |
||||
|
background: |
||||
|
check.length == 8 |
||||
|
? "rgba(40, 123, 255, 1)" |
||||
|
: "rgba(176, 176, 176, 1)", |
||||
|
}} |
||||
|
> |
||||
|
{check.length}/8 |
||||
|
</span> |
||||
|
</div> |
||||
|
} |
||||
|
visible={true} |
||||
|
style={{ width: 600 }} |
||||
|
onOk={() => { |
||||
|
localStorage.setItem(tableType, JSON.stringify(check)); |
||||
|
close(); |
||||
|
}} |
||||
|
onCancel={() => { |
||||
|
close(); |
||||
|
}} |
||||
|
> |
||||
|
<CheckboxGroup |
||||
|
style={{ width: "100%", fontSize: 14 }} |
||||
|
key="primary1" |
||||
|
direction="horizontal" |
||||
|
defaultValue={check} |
||||
|
aria-label="表格属性设置" |
||||
|
onChange={(check) => { |
||||
|
setCheck(check); |
||||
|
ischeck(); |
||||
|
}} |
||||
|
> |
||||
|
{tableList.map((item,index)=>{ |
||||
|
return( |
||||
|
<div |
||||
|
key={index} |
||||
|
style={{ |
||||
|
width: 550, |
||||
|
border: "1px solid #EAEAEA", |
||||
|
padding: "0px 5px", |
||||
|
borderRadius: 4, |
||||
|
marginBottom: "20px", |
||||
|
}} |
||||
|
> |
||||
|
<div |
||||
|
style={{ |
||||
|
borderBottom: "1px solid #EAEAEA", |
||||
|
marginLeft: "10px", |
||||
|
padding: "8px 0px", |
||||
|
}} |
||||
|
> |
||||
|
{item.title} |
||||
|
</div> |
||||
|
<div style={{ padding: "15px 12px", width: 530 }}> |
||||
|
{item.list?.map((itm) => { |
||||
|
return ( |
||||
|
<Checkbox |
||||
|
key={itm.value} |
||||
|
value={itm.value} |
||||
|
style={checkboxcss} |
||||
|
disabled={ischeck(itm.value)} |
||||
|
> |
||||
|
{itm.name} |
||||
|
</Checkbox> |
||||
|
); |
||||
|
})} |
||||
|
</div> |
||||
|
</div> |
||||
|
)})} |
||||
|
</CheckboxGroup> |
||||
|
</Modal> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
export default Setup; |
@ -0,0 +1,40 @@ |
|||||
|
import React, { useState, useEffect, useRef } from "react"; |
||||
|
import { connect } from "react-redux"; |
||||
|
import moment from 'moment' |
||||
|
import { Button, } from "@douyinfe/semi-ui"; |
||||
|
|
||||
|
const SimpleFileDownButton = (props) => { |
||||
|
const { src, user } = props |
||||
|
const [downloadUrl, setDownloadUrl] = useState('') |
||||
|
|
||||
|
return ( |
||||
|
<> |
||||
|
<Button |
||||
|
style={{ |
||||
|
width: 65, |
||||
|
height: 32, |
||||
|
background: "#FFFFFF", |
||||
|
borderRadius: 3, |
||||
|
border: "1px solid #1859C1", |
||||
|
}} |
||||
|
onClick={() => { |
||||
|
setDownloadUrl(`${src}?token=${user.token}×tamp=${moment().valueOf()}`) |
||||
|
}} |
||||
|
> |
||||
|
导出 |
||||
|
</Button> |
||||
|
{ |
||||
|
downloadUrl ? <iframe src={`/_api/${downloadUrl}`} style={{ display: 'none' }} /> : '' |
||||
|
} |
||||
|
</> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
function mapStateToProps (state) { |
||||
|
const { auth } = state; |
||||
|
return { |
||||
|
user: auth.user, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
export default connect(mapStateToProps)(SimpleFileDownButton); |
@ -0,0 +1,18 @@ |
|||||
|
import React, { useState, useEffect } from "react"; |
||||
|
import { Skeleton } from "@douyinfe/semi-ui"; |
||||
|
|
||||
|
|
||||
|
export function SkeletonScreen () { |
||||
|
return <> |
||||
|
<Skeleton.Title style={{ width: "95%", height: 24, margin: "8px 20px" }} /> |
||||
|
<Skeleton.Title style={{ width: "80%", height: 28, margin: "16px 20px" }} /> |
||||
|
<Skeleton.Title style={{ width: "50%", height: 28, margin: "16px 20px" }} /> |
||||
|
<Skeleton.Title style={{ width: "60%", height: 28, margin: "16px 20px" }} /> |
||||
|
<Skeleton.Title style={{ width: "90%", height: 28, margin: "16px 20px" }} /> |
||||
|
<Skeleton.Title style={{ width: "70%", height: 28, margin: "16px 20px" }} /> |
||||
|
<Skeleton.Title style={{ width: "50%", height: 28, margin: "16px 20px" }} /> |
||||
|
<Skeleton.Title style={{ width: "40%", height: 28, margin: "16px 20px" }} /> |
||||
|
<Skeleton.Title style={{ width: "60%", height: 28, margin: "16px 20px" }} /> |
||||
|
<Skeleton.Title style={{ width: "40%", height: 28, margin: "16px 20px" }} /> |
||||
|
</> |
||||
|
} |
@ -0,0 +1,80 @@ |
|||||
|
import React, { useRef, useEffect, useState } from 'react' |
||||
|
import moment from 'moment' |
||||
|
import './textScroll.less' |
||||
|
|
||||
|
function TextScroll (props) { |
||||
|
const { content, duration, roll, videoObj = {} } = props |
||||
|
const [showContent, setShowContent] = useState('') |
||||
|
const showIndex = useRef(0) |
||||
|
const initialization = useRef(false) |
||||
|
const cancel = useRef(false) |
||||
|
let videoObjId = videoObj.id || 0 |
||||
|
useEffect(() => { |
||||
|
if (content.length) { |
||||
|
if (roll) { |
||||
|
let contentParent = document.getElementById("marquee_box" + videoObjId) |
||||
|
document.getElementById('contentPMakeUp' + videoObjId).style.width = contentParent.clientWidth + 'px' |
||||
|
const contentP = document.getElementById('contentP' + videoObjId) |
||||
|
contentP.style.visibility = 'visible' |
||||
|
setShowContent(content[0]) |
||||
|
window.cancelAnimationFrame(cancel.current) |
||||
|
contentParent.scrollLeft = 0 |
||||
|
initialization.current = false |
||||
|
showIndex.current = 0 |
||||
|
} |
||||
|
else { |
||||
|
let repeatTime = moment() |
||||
|
let refreshTime = moment() |
||||
|
const scroll = () => { |
||||
|
let contentParent = document.getElementById("marquee_box" + videoObjId) |
||||
|
document.getElementById('contentPMakeUp' + videoObjId).style.width = contentParent.clientWidth + 'px' |
||||
|
//初始化 |
||||
|
// if(!showContent&&!initialization.current){ |
||||
|
if (!initialization.current) { |
||||
|
const contentP = document.getElementById('contentP' + videoObjId) |
||||
|
contentParent.scrollLeft = 0 |
||||
|
setShowContent(content[showIndex.current]) |
||||
|
showIndex.current = (showIndex.current + 1) % content.length |
||||
|
contentP.style.visibility = 'visible' |
||||
|
initialization.current = true |
||||
|
} |
||||
|
// 控制频率 |
||||
|
if (moment().diff(refreshTime) > 1000 / 60) { |
||||
|
const contentP = document.getElementById('contentP' + videoObjId) |
||||
|
// 静态等待时间 |
||||
|
if (moment().diff(repeatTime) > 1000 * 1.5) { |
||||
|
contentP.style.visibility = 'visible' |
||||
|
} |
||||
|
// 滚动 |
||||
|
if (moment().diff(repeatTime) > 1000 * 3) { |
||||
|
contentParent.scrollLeft = contentParent.scrollLeft + 1 |
||||
|
} |
||||
|
// 滚完 重置 |
||||
|
if (contentParent.scrollLeft >= contentP.clientWidth + 24) { |
||||
|
contentParent.scrollLeft = 0 |
||||
|
repeatTime = moment() |
||||
|
setShowContent(content[showIndex.current]) |
||||
|
showIndex.current = (showIndex.current + 1) % content.length |
||||
|
contentP.style.visibility = 'hidden' |
||||
|
} |
||||
|
refreshTime = moment() |
||||
|
} |
||||
|
let text = null |
||||
|
text = window.requestAnimationFrame(scroll) |
||||
|
cancel.current = text |
||||
|
} |
||||
|
window.requestAnimationFrame(scroll) |
||||
|
} |
||||
|
} |
||||
|
}, [content, roll]) |
||||
|
return ( |
||||
|
<div className="marquee_box" id={"marquee_box" + videoObjId} style={{ overflow: 'hidden', color: '#F9F9F9' }} > |
||||
|
<div style={{ position: 'relative', left: 24 }}> |
||||
|
<div id={'contentP' + videoObjId} style={{ display: 'inline-block', visibility: 'hidden' }}>{showContent}</div> |
||||
|
<div id={'contentPMakeUp' + videoObjId} style={{ width: 0, display: 'inline-block' }}></div> |
||||
|
</div> |
||||
|
</div> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
export default React.memo(TextScroll) |
@ -0,0 +1,19 @@ |
|||||
|
.marquee_box { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
word-break: keep-all; |
||||
|
white-space: nowrap; |
||||
|
// display: flex; |
||||
|
// align-items: center; |
||||
|
} |
||||
|
|
||||
|
.marquee_box p { |
||||
|
// display: inline-block; |
||||
|
padding: 0; |
||||
|
margin: 0; |
||||
|
} |
||||
|
|
||||
|
.marquee_box:hover p { |
||||
|
animation-play-state: paused; |
||||
|
cursor: default; |
||||
|
} |
@ -0,0 +1,9 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
import './public-path' |
||||
|
import React from 'react'; |
||||
|
import { render } from 'react-dom'; |
||||
|
import App from './app'; |
||||
|
import './index.less'; |
||||
|
|
||||
|
render((<App projectName="运维服务" />), document.getElementById('PomsApp')); |
@ -0,0 +1,78 @@ |
|||||
|
// webpack (vite 用 alias 兼容了) |
||||
|
// @import '~@douyinfe/semi-ui/dist/css/semi.min.css'; |
||||
|
@import '~perfect-scrollbar/css/perfect-scrollbar.css'; |
||||
|
@import '~nprogress/nprogress.css'; |
||||
|
|
||||
|
|
||||
|
*, |
||||
|
*::before, |
||||
|
*::after { |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
html, |
||||
|
body { |
||||
|
margin: 0; |
||||
|
height: 100%; |
||||
|
width: 100%; |
||||
|
overflow: hidden; |
||||
|
|
||||
|
#App { |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
.semi-timepicker-panel { |
||||
|
|
||||
|
//时间选择器不显示滚动栏 |
||||
|
::-webkit-scrollbar { |
||||
|
display: none; |
||||
|
/* Chrome Safari */ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
scrollbar-width: none; |
||||
|
/* firefox */ |
||||
|
-ms-overflow-style: none; |
||||
|
/* IE 10+ */ |
||||
|
overflow-x: hidden; |
||||
|
overflow-y: auto; |
||||
|
} |
||||
|
|
||||
|
a:link { |
||||
|
text-decoration: none; |
||||
|
color: unset |
||||
|
} |
||||
|
|
||||
|
a:visited { |
||||
|
text-decoration: none; |
||||
|
color: unset |
||||
|
} |
||||
|
|
||||
|
a:hover { |
||||
|
text-decoration: none; |
||||
|
color: unset |
||||
|
} |
||||
|
|
||||
|
a:active { |
||||
|
text-decoration: none; |
||||
|
color: unset |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// SEMI 全局样式 |
||||
|
.semi-popover-content { |
||||
|
min-width: 300px |
||||
|
} |
||||
|
|
||||
|
.semi-portal-inner { |
||||
|
position: fixed; |
||||
|
} |
||||
|
|
||||
|
@font-face { |
||||
|
font-family: 'YouSheBiaoTiHei'; //这个可以任意取,但是应与后面相对应eg:yxingguang |
||||
|
src: url('/assets/fonts/YouSheBiaoTiHei-2.ttf'); |
||||
|
} |
||||
|
@font-face { |
||||
|
font-family: 'DINExp'; //这个可以任意取,但是应与后面相对应eg:yxingguang |
||||
|
src: url('/assets/fonts/DINExp.ttf'); |
||||
|
} |
@ -0,0 +1,46 @@ |
|||||
|
'use strict'; |
||||
|
// import { RouteRequest } from '@peace/utils';
|
||||
|
|
||||
|
import { RouteTable, RouteRequest } from '$utils' |
||||
|
|
||||
|
export const INIT_LAYOUT = 'INIT_LAYOUT'; |
||||
|
export function initLayout (title, copyright, sections, actions) { |
||||
|
return { |
||||
|
type: INIT_LAYOUT, |
||||
|
payload: { |
||||
|
title, |
||||
|
copyright, |
||||
|
sections, |
||||
|
actions |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
export const RESIZE = 'RESIZE'; |
||||
|
export function resize (clientHeight, clientWidth) { |
||||
|
const headerHeight = 50 |
||||
|
const footerHeight = 0 |
||||
|
return { |
||||
|
type: RESIZE, |
||||
|
payload: { |
||||
|
clientHeight: clientHeight - headerHeight - footerHeight, |
||||
|
clientWidth: clientWidth |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export const INIT_API_ROOT = 'INIT_API_ROOT'; |
||||
|
export function initApiRoot () { |
||||
|
return dispatch => { |
||||
|
return RouteRequest.get(RouteTable.apiRoot).then(res => { |
||||
|
localStorage.setItem('apiRoot', JSON.stringify(res)); |
||||
|
return dispatch({ |
||||
|
type: INIT_API_ROOT, |
||||
|
payload: { |
||||
|
apiRoot: res.root, |
||||
|
iotVcmpWeb:res.iotVcmpWeb, |
||||
|
} |
||||
|
}) |
||||
|
}); |
||||
|
} |
||||
|
} |
@ -0,0 +1,9 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
import * as global from './global' |
||||
|
import * as socket from './webSocket'; |
||||
|
|
||||
|
export default { |
||||
|
...global, |
||||
|
...socket, |
||||
|
}; |
@ -0,0 +1,41 @@ |
|||||
|
'use strict'; |
||||
|
import io from 'socket.io-client'; |
||||
|
|
||||
|
export const INIT_WEB_SOCKET = 'INIT_WEB_SOCKET' |
||||
|
export function initWebSocket ({ ioUrl, token, pomsUserId }) { |
||||
|
if (!ioUrl) { |
||||
|
ioUrl = localStorage.getItem('apiRoot') |
||||
|
ioUrl = JSON.parse(ioUrl).root |
||||
|
} |
||||
|
if (!token) { |
||||
|
let user = sessionStorage.getItem('pomsUser') |
||||
|
if (user) { |
||||
|
user = JSON.parse(user) |
||||
|
token = user.token |
||||
|
pomsUserId = user.pomsUserInfo.id |
||||
|
} |
||||
|
} |
||||
|
if (!ioUrl || !token || !pomsUserId) { |
||||
|
return { |
||||
|
type: '', |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return dispatch => { |
||||
|
const socket = io( |
||||
|
ioUrl |
||||
|
// 'http://10.8.30.7:4000'
|
||||
|
, { |
||||
|
query: { |
||||
|
token: token, |
||||
|
pomsUserId: pomsUserId |
||||
|
}, |
||||
|
}); |
||||
|
dispatch({ |
||||
|
type: INIT_WEB_SOCKET, |
||||
|
payload: { |
||||
|
socket: socket |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
} |
@ -0,0 +1,15 @@ |
|||||
|
'use strict'; |
||||
|
import React from 'react'; |
||||
|
import moment from 'moment' |
||||
|
|
||||
|
export default class Footer extends React.Component { |
||||
|
render () { |
||||
|
// const { } = this.props; |
||||
|
|
||||
|
return ( |
||||
|
<div style={{ textAlign: 'center', lineHeight: '32px',fontSize:13,color:'rgb(139, 139, 139)' }}> |
||||
|
Copyright © {moment().year()} All Rights Reserved 版权所有· 江西飞尚科技有限公司 |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
}; |
@ -0,0 +1,165 @@ |
|||||
|
"use strict"; |
||||
|
import React from "react"; |
||||
|
import { connect } from "react-redux"; |
||||
|
import { SplitButtonGroup, Dropdown, Button, Nav, Avatar } from '@douyinfe/semi-ui'; |
||||
|
import { IconTreeTriangleDown } from '@douyinfe/semi-icons'; |
||||
|
import "./index.less"; |
||||
|
|
||||
|
const Header = (props) => { |
||||
|
const { dispatch, history, user, actions, socket, headerItems, tochange } = props; |
||||
|
|
||||
|
return ( |
||||
|
<> |
||||
|
<div id="top-slider"> |
||||
|
<Nav |
||||
|
mode={"horizontal"} |
||||
|
onClick={({ itemKey }) => { |
||||
|
if (itemKey == "logout") { |
||||
|
dispatch(actions.auth.logout(user)); |
||||
|
if (socket) { |
||||
|
socket.disconnect(); |
||||
|
} |
||||
|
history.push(`/signin`); |
||||
|
} |
||||
|
}} |
||||
|
style={{ |
||||
|
height: 48, |
||||
|
minWidth: 520, |
||||
|
background: '#1D2343', |
||||
|
backgroundSize: "100% 100%", |
||||
|
color: "white", |
||||
|
}} |
||||
|
header={{ |
||||
|
logo: ( |
||||
|
<img |
||||
|
src="/assets/images/install/long_logo.png" |
||||
|
style={{ display: "inline-block", width: 200, height: 40, marginLeft: -24 }} |
||||
|
/> |
||||
|
), |
||||
|
text: ( |
||||
|
<> |
||||
|
|
||||
|
{/* <SplitButtonGroup style={{ marginLeft: 10 }} aria-label="项目操作按钮组"> */} |
||||
|
<Button theme="solid" type="primary" style={{ width: 52, height: 24, background: '#005ABD' }}>全局</Button> |
||||
|
{/* <Dropdown onVisibleChange={(v) => { }} menu={{}} trigger="click" position="bottomRight"> */} |
||||
|
<Button style={{ width: 16, height: 24, background: '#005ABD' }} theme="solid" type="primary" icon={<IconTreeTriangleDown />}></Button> |
||||
|
{/* </Dropdown> */} |
||||
|
{/* </SplitButtonGroup> */} |
||||
|
</> |
||||
|
), |
||||
|
}} |
||||
|
footer={ |
||||
|
<> |
||||
|
{headerItems.map((item, index) => { |
||||
|
if (item.hasOwnProperty('items')) { |
||||
|
return ( |
||||
|
<Nav.Sub |
||||
|
key={index + 'a'} |
||||
|
itemKey={item.itemKey} |
||||
|
text={item.text} |
||||
|
dropdownStyle={{ color: '#F2F3F5' }} |
||||
|
> |
||||
|
{item.hasOwnProperty('items') && item.items.map((ite, idx) => ( |
||||
|
<Nav.Item key={idx + 'd'} itemKey={ite.itemKey} text={ite.text} onClick={() => { tochange(ite); }} /> |
||||
|
))} |
||||
|
</Nav.Sub> |
||||
|
) |
||||
|
} |
||||
|
else { |
||||
|
return ( |
||||
|
<div key='console' style={{ display: 'inline-flex' }}> |
||||
|
<img src="/assets/images/background/console.png" style={{ width: 24, marginRight: -10 }} /> |
||||
|
<Nav.Item key={index + 'a'} itemKey={item.itemKey} text={item.text} onClick={() => { tochange(item) }} /> |
||||
|
</div> |
||||
|
|
||||
|
) |
||||
|
} |
||||
|
})} |
||||
|
<Nav.Sub |
||||
|
itemKey={"user"} |
||||
|
text={ |
||||
|
<div |
||||
|
style={{ |
||||
|
marginLeft: -8, |
||||
|
display: "inline-block", |
||||
|
color: "white", |
||||
|
}} |
||||
|
> |
||||
|
<Avatar size="extra-small" color="light-blue" style={{ marginRight: 4 }}> |
||||
|
{user?.name?.substr(0, 1)} |
||||
|
</Avatar> |
||||
|
<div style={{ |
||||
|
display: "inline-block", position: "relative", |
||||
|
top: 10, |
||||
|
left: 4, |
||||
|
marginRight: 4, |
||||
|
}} |
||||
|
> |
||||
|
</div> |
||||
|
</div> |
||||
|
} |
||||
|
> |
||||
|
<div style={{ width: 133, }}> |
||||
|
<div style={{ display: "flex", alignItems: 'center', background: '#F0F5FF', padding: '8px 0px 8px 12px' }}> |
||||
|
<Avatar size="extra-small" color="light-blue" style={{ marginRight: 4 }}> |
||||
|
{user?.name?.substr(0, 1)} |
||||
|
</Avatar> |
||||
|
<div style={{ fontSize: 12, color: '#4A4A4A' }}> |
||||
|
{user.name} |
||||
|
<div title={user?.department?.map(v => v.name + '、')} style={{ width: 60, overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis', color: '#969799', fontSize: 12 }}>{user?.department?.map(v => v.name + '、')}</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div style={{ display: 'flex', alignItems: 'center', height: 30, borderBottom: '1px solid #F2F3F5', cursor: "pointer" }} |
||||
|
onClick={() => { |
||||
|
history.push(`/userCenter`); |
||||
|
}}> |
||||
|
<div style={{ width: 15, height: 15, marginLeft: 20 }}> |
||||
|
<img src="/assets/images/console/yonghu.png" alt="" style={{ width: '100%', height: '100%' }} /> |
||||
|
</div> |
||||
|
<div style={{ color: '#646566', fontSize: 12, marginLeft: 9 }}> |
||||
|
用户中心 |
||||
|
</div> |
||||
|
</div> |
||||
|
<div style={{ display: 'flex', alignItems: 'center', height: 30, borderBottom: '1px solid #F2F3F5', cursor: "pointer" }}> |
||||
|
<div style={{ width: 15, height: 15, marginLeft: 20 }}> |
||||
|
<img src="/assets/images/console/suo.png" alt="" style={{ width: '100%', height: '100%' }} /> |
||||
|
</div> |
||||
|
<div style={{ color: '#646566', fontSize: 12, marginLeft: 9 }}> |
||||
|
安全认证 |
||||
|
</div> |
||||
|
</div> |
||||
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: 27, color: '#646566', fontSize: 12, cursor: "pointer" }} |
||||
|
onClick={() => { |
||||
|
dispatch(actions.auth.logout(user)); |
||||
|
if (socket) { |
||||
|
socket.disconnect(); |
||||
|
} |
||||
|
history.push(`/signin`); |
||||
|
}}> |
||||
|
退出 |
||||
|
{/* <Nav.Item itemKey={"logout"} text={"退出"} style={{ textAlign: "center" }} /> */} |
||||
|
</div> |
||||
|
|
||||
|
{/* collapseButton collapseText */} |
||||
|
|
||||
|
|
||||
|
</Nav.Sub> |
||||
|
</> |
||||
|
} |
||||
|
/> |
||||
|
</div> |
||||
|
</> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
function mapStateToProps (state) { |
||||
|
const { global, auth, webSocket } = state; |
||||
|
return { |
||||
|
actions: global.actions, |
||||
|
user: auth.user, |
||||
|
socket: webSocket.socket, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
export default connect(mapStateToProps)(Header); |
@ -0,0 +1,18 @@ |
|||||
|
#top-slider{ |
||||
|
.semi-navigation-item-text{ |
||||
|
font-size: 13px; |
||||
|
color: #F2F3F5; |
||||
|
} |
||||
|
.semi-navigation-item-icon{ |
||||
|
color: #F2F3F5; |
||||
|
} |
||||
|
.semi-navigation-item-selected{ |
||||
|
background: none; |
||||
|
} |
||||
|
.semi-navigation-item{ |
||||
|
margin: 0px; |
||||
|
} |
||||
|
.semi-navigation-item-text{ |
||||
|
overflow: inherit; |
||||
|
} |
||||
|
} |
@ -0,0 +1,86 @@ |
|||||
|
import React, { useEffect, useState } from 'react'; |
||||
|
import PerfectScrollbar from 'perfect-scrollbar'; |
||||
|
import { connect } from 'react-redux'; |
||||
|
import { Nav } from '@douyinfe/semi-ui'; |
||||
|
import { push } from 'react-router-redux'; |
||||
|
import { useLocation } from 'react-router'; |
||||
|
import "./index.less"; |
||||
|
|
||||
|
let scrollbar = null |
||||
|
const homePath = '/example/e1/c1' |
||||
|
const Sider = (props) => { |
||||
|
const { collapsed, clientHeight, dispatch, pathname, leftItems, leftChange } = props |
||||
|
const [items, setItems] = useState([]) |
||||
|
const [selectedKeys, setSelectedKeys] = useState([]) |
||||
|
const [openKeys, setOpenKeys] = useState([]) |
||||
|
useEffect(() => { |
||||
|
const { sections, dispatch, user } = props; |
||||
|
let nextItems = leftItems |
||||
|
setItems(nextItems) |
||||
|
scrollbar = new PerfectScrollbar('#page-slider', { suppressScrollX: true }); |
||||
|
if (pathname == '/') { |
||||
|
dispatch(push(homePath)) |
||||
|
} |
||||
|
}, [leftItems]) |
||||
|
let routeSelectedKey = [useLocation().pathname.split('/')[1]]//没有子目录的 |
||||
|
let routeSelectedKeys = [useLocation().pathname.split('/')[2]]//有子目录的 |
||||
|
let routeSelectedKeyss = [useLocation().pathname.split('/')[3]]//有子目录的 |
||||
|
useEffect(() => { |
||||
|
if (routeSelectedKeyss[0]) { |
||||
|
setSelectedKeys(routeSelectedKeyss) |
||||
|
} |
||||
|
else if (routeSelectedKeys[0]) { |
||||
|
setSelectedKeys(routeSelectedKeys) |
||||
|
} |
||||
|
else { |
||||
|
setSelectedKeys(routeSelectedKey) |
||||
|
} |
||||
|
const lastOpenKeys = localStorage.getItem('poms_open_sider') |
||||
|
if (lastOpenKeys) { |
||||
|
setOpenKeys(JSON.parse(lastOpenKeys)) |
||||
|
} |
||||
|
}, [window.localStorage.poms_open_sider, window.localStorage.poms_selected_sider, leftChange]) |
||||
|
|
||||
|
useEffect(() => { |
||||
|
if (scrollbar) { |
||||
|
scrollbar.update(); |
||||
|
} |
||||
|
}) |
||||
|
return ( |
||||
|
<div id={'page-slider'} style={{ height: clientHeight, position: 'relative', background: '#101531' }}> |
||||
|
<Nav |
||||
|
style={{ background: '#101531', padding: 0 }} |
||||
|
selectedKeys={selectedKeys} |
||||
|
bodyStyle={{height:clientHeight-64}} |
||||
|
mode = "vertical" |
||||
|
footer={{ |
||||
|
collapseButton: true, |
||||
|
}} |
||||
|
openKeys={openKeys} |
||||
|
onSelect={({ selectedItems, selectedKeys, }) => { |
||||
|
const selectItem = selectedItems[0] |
||||
|
if (selectItem.to) { |
||||
|
dispatch(push(selectItem.to)) |
||||
|
} |
||||
|
let myfatherKey = selectItem.to |
||||
|
localStorage.setItem('poms_selected_sider', JSON.stringify([myfatherKey.split('/')[1]])) |
||||
|
setSelectedKeys(selectedKeys) |
||||
|
}} |
||||
|
onOpenChange={({ openKeys }) => { |
||||
|
setOpenKeys(openKeys) |
||||
|
localStorage.setItem('poms_open_sider', JSON.stringify(openKeys)) |
||||
|
}} |
||||
|
items={items} |
||||
|
/> |
||||
|
</div> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
function mapStateToProps (state) { |
||||
|
const { global } = state; |
||||
|
return { |
||||
|
clientHeight: global.clientHeight, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
export default connect(mapStateToProps)(Sider); |
@ -0,0 +1,40 @@ |
|||||
|
#page-slider{ |
||||
|
.semi-navigation-sub .semi-navigation-item-selected{ |
||||
|
background: rgba(0, 90, 189, 0.5); |
||||
|
color: #F2F3F5; |
||||
|
} |
||||
|
.semi-navigation-item-text{ |
||||
|
color: #F2F3F5; |
||||
|
} |
||||
|
.semi-navigation-item-icon{ |
||||
|
color:#F2F3F5 |
||||
|
} |
||||
|
.semi-navigation-item-selected{ |
||||
|
background: rgba(0, 90, 189, 0.5); |
||||
|
color: #F2F3F5; |
||||
|
} |
||||
|
.semi-navigation-item-inner{ |
||||
|
font-family: YouSheBiaoTiHei; |
||||
|
font-size: 16px; |
||||
|
letter-spacing:2px; |
||||
|
font-weight: 400; |
||||
|
} |
||||
|
.semi-navigation-sub-title-selected{ |
||||
|
background: #1D2343; |
||||
|
.semi-navigation-item-icon{ |
||||
|
color: #0F7EFB !important; |
||||
|
} |
||||
|
.semi-icon-code{ |
||||
|
color: #0F7EFB !important; |
||||
|
} |
||||
|
} |
||||
|
.semi-navigation{ |
||||
|
width: 180px; |
||||
|
} |
||||
|
.semi-navigation-collapsed{ |
||||
|
width: 48px !important; |
||||
|
} |
||||
|
.semi-button-with-icon{ |
||||
|
color:#F2F3F5 |
||||
|
} |
||||
|
} |
@ -0,0 +1,6 @@ |
|||||
|
'use strict'; |
||||
|
import Layout from './layout'; |
||||
|
import NoMatch from './no-match'; |
||||
|
|
||||
|
export { Layout }; |
||||
|
export { NoMatch }; |
@ -0,0 +1,321 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
import React, { useState, useEffect } from 'react'; |
||||
|
import { connect } from 'react-redux'; |
||||
|
import { Layout, Notification } from '@douyinfe/semi-ui'; |
||||
|
import Sider from '../../components/sider'; |
||||
|
import Header from '../../components/header'; |
||||
|
import Footer from '../../components/footer'; |
||||
|
import { resize } from '../../actions/global'; |
||||
|
import * as NProgress from 'nprogress'; |
||||
|
import PerfectScrollbar from 'perfect-scrollbar'; |
||||
|
import { useLocation } from "react-router"; |
||||
|
|
||||
|
NProgress.configure({ |
||||
|
template: ` |
||||
|
<div class="bar" style="height:2px" role="bar"> |
||||
|
<div class="peg"></div> |
||||
|
</div> |
||||
|
<div class="spinner" role="spinner"> |
||||
|
<div class="spinner-icon"></div> |
||||
|
</div> |
||||
|
` |
||||
|
}); |
||||
|
|
||||
|
let scrollbar |
||||
|
// const location111 = useLocation(); |
||||
|
const LayoutContainer = props => { |
||||
|
const { |
||||
|
dispatch, msg, user, copyright, children, sections, clientWidth, clientHeight, |
||||
|
location, match, routes, history, socket, |
||||
|
} = props |
||||
|
const [collapsed, setCollapsed] = useState(false) |
||||
|
|
||||
|
NProgress.start(); |
||||
|
|
||||
|
const resize_ = () => { |
||||
|
dispatch(resize( |
||||
|
document.getElementById('PomsApp').clientHeight, |
||||
|
document.getElementById('PomsApp').clientWidth - (collapsed ? 120 : 240) |
||||
|
)); |
||||
|
} |
||||
|
function deepCopy (data) {//深拷贝 |
||||
|
//string,number,bool,null,undefined,symbol |
||||
|
//object,array,date |
||||
|
if (data && typeof data === "object") { |
||||
|
//针对函数的拷贝 |
||||
|
if (typeof data === "function") { |
||||
|
let tempFunc = data.bind(null); |
||||
|
tempFunc.prototype = deepCopy(data.prototype); |
||||
|
return tempFunc; |
||||
|
} |
||||
|
|
||||
|
switch (Object.prototype.toString.call(data)) { |
||||
|
case "[object String]": |
||||
|
return data.toString(); |
||||
|
case "[object Number]": |
||||
|
return Number(data.toString()); |
||||
|
case "[object Boolean]": |
||||
|
return new Boolean(data.toString()); |
||||
|
case "[object Date]": |
||||
|
return new Date(data.getTime()); |
||||
|
case "[object Array]": |
||||
|
var arr = []; |
||||
|
for (let i = 0; i < data.length; i++) { |
||||
|
arr[i] = deepCopy(data[i]); |
||||
|
} |
||||
|
return arr; |
||||
|
|
||||
|
//js自带对象或用户自定义类实例 |
||||
|
case "[object Object]": |
||||
|
var obj = {}; |
||||
|
for (let key in data) { |
||||
|
//会遍历原型链上的属性方法,可以用hasOwnProperty来控制 (obj.hasOwnProperty(prop) |
||||
|
obj[key] = deepCopy(data[key]); |
||||
|
} |
||||
|
return obj; |
||||
|
} |
||||
|
} else { |
||||
|
//string,number,bool,null,undefined,symbol |
||||
|
return data; |
||||
|
} |
||||
|
} |
||||
|
const [allItems, setAllItems] = useState([]) |
||||
|
const [headerItems, setHeaderItems] = useState([]) |
||||
|
const [leftItems, setLeftItems] = useState([]) |
||||
|
const [leftChange, setLeftChange] = useState(true) |
||||
|
const [leftShow, setLeftShow] = useState(false) |
||||
|
useEffect(() => { |
||||
|
let topItems = []//头部导航 |
||||
|
const lastSelectedKeys = localStorage.getItem('poms_selected_sider') |
||||
|
let nextItems = []//所有导航 |
||||
|
for (let c of sections) { |
||||
|
if (typeof c.getNavItem == 'function') { |
||||
|
let item = c.getNavItem(user, dispatch); |
||||
|
if (item) { |
||||
|
nextItems.push.apply(nextItems, item) |
||||
|
if (item.length > 0) { |
||||
|
for (let j = 0; j < item.length; j++) { |
||||
|
let itm = deepCopy(item[j]); |
||||
|
if (itm.hasOwnProperty('items')) { |
||||
|
for (let i = 0; i < itm.items.length; i++) { |
||||
|
itm.items[i].fatherKey = itm.itemKey |
||||
|
delete itm.items[i].items |
||||
|
} |
||||
|
topItems.push(itm) |
||||
|
} |
||||
|
else { |
||||
|
topItems.push.apply(topItems, item) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
setAllItems(nextItems) |
||||
|
setHeaderItems(topItems) |
||||
|
if (lastSelectedKeys) {//如果有缓存 |
||||
|
for (let i = 0; i < nextItems.length; i++) { |
||||
|
if (JSON.parse(lastSelectedKeys)[0] == nextItems[i].itemKey) { |
||||
|
|
||||
|
let openArr = [] |
||||
|
for (let j = 0; j < nextItems[i].items.length; j++) { |
||||
|
openArr.push(nextItems[i].items[j].itemKey) |
||||
|
} |
||||
|
localStorage.setItem('poms_open_sider', JSON.stringify(openArr)) |
||||
|
setLeftItems(nextItems[i].items) |
||||
|
} |
||||
|
} |
||||
|
setLeftShow(true) |
||||
|
} |
||||
|
else { |
||||
|
setLeftShow(false) |
||||
|
} |
||||
|
|
||||
|
window.addEventListener('resize', resize_); |
||||
|
return () => { |
||||
|
window.removeEventListener('resize', resize_); |
||||
|
} |
||||
|
}, []) |
||||
|
|
||||
|
useEffect(() => { |
||||
|
let pathnameArr = location.pathname.split('/') |
||||
|
let openArr = [] |
||||
|
for (let i = 0; i < allItems.length; i++) { |
||||
|
if (allItems[i].items) { |
||||
|
for (let j = 0; j < allItems[i].items.length; j++) { |
||||
|
if (allItems[i].items[j].items) { |
||||
|
for (let k = 0; k < allItems[i].items[j].items.length; k++) { |
||||
|
if (allItems[i].items[j].items[k].to == location.pathname) { |
||||
|
for (let o = 0; o < allItems[i].items.length; o++) { |
||||
|
openArr.push(allItems[i].items[o].itemKey) |
||||
|
} |
||||
|
localStorage.setItem('poms_selected_sider', JSON.stringify([pathnameArr[1]])) |
||||
|
localStorage.setItem('poms_open_sider', JSON.stringify(openArr)) |
||||
|
setLeftItems(allItems[i].items) |
||||
|
setLeftShow(true) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}, [location]) |
||||
|
|
||||
|
useEffect(() => { |
||||
|
NProgress.done(); |
||||
|
if ((!user || !user.authorized)) { |
||||
|
history.push('/signin'); |
||||
|
} |
||||
|
if (msg) { |
||||
|
if (msg.done) { |
||||
|
Notification.success({ |
||||
|
// title: msg.done, |
||||
|
content: msg.done, |
||||
|
duration: 2, |
||||
|
}) |
||||
|
} |
||||
|
if (msg.error) { |
||||
|
Notification.error({ |
||||
|
// title: msg.error, |
||||
|
content: msg.error, |
||||
|
duration: 2, |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
const dom = document.getElementById('page-content'); |
||||
|
if (dom) { |
||||
|
if (!scrollbar) { |
||||
|
scrollbar = new PerfectScrollbar('#page-content', { suppressScrollX: true }); |
||||
|
scrollbar.update(); |
||||
|
} else { |
||||
|
scrollbar.update(); |
||||
|
dom.scrollTop = 0; |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
|
||||
|
// websocket 使用测试 |
||||
|
useEffect(() => { |
||||
|
// console.log(socket) |
||||
|
if (socket) { |
||||
|
socket.on('CAMERA_ONLINE', function (msg) { |
||||
|
console.info(msg); |
||||
|
if (msg.online == 'ON') { |
||||
|
Notification.success({ |
||||
|
title: 'Hi', |
||||
|
content: (<div><div>{msg.name}</div><div style={{ marginTop: 5 }}>已上线。</div></div>), |
||||
|
duration: 2, |
||||
|
}) |
||||
|
} |
||||
|
if (msg.online == 'OFF') { |
||||
|
Notification.error({ |
||||
|
title: 'Hi', |
||||
|
content: (<div><div>{msg.name}</div><div style={{ marginTop: 5 }}>发生离线。</div></div>), |
||||
|
duration: 2, |
||||
|
}) |
||||
|
} |
||||
|
}); |
||||
|
return () => { |
||||
|
socket.off("CAMERA_ONLINE"); |
||||
|
} |
||||
|
} |
||||
|
}, [socket]) |
||||
|
|
||||
|
return ( |
||||
|
<Layout id="layout" style={{ height: '100%' }}> |
||||
|
{ |
||||
|
<> |
||||
|
<Layout.Header> |
||||
|
<Header |
||||
|
headerItems={headerItems} |
||||
|
user={user} |
||||
|
pathname={location.pathname} |
||||
|
toggleCollapsed={() => { |
||||
|
setCollapsed(!collapsed); |
||||
|
}} |
||||
|
collapsed={collapsed} |
||||
|
history={history} |
||||
|
tochange={(val) => { |
||||
|
setLeftChange(!leftChange) |
||||
|
if (val.fatherKey) { |
||||
|
for (let i = 0; i < allItems.length; i++) { |
||||
|
if (val.fatherKey == allItems[i].itemKey) { |
||||
|
let openArr = [] |
||||
|
for (let j = 0; j < allItems[i].items.length; j++) { |
||||
|
openArr.push(allItems[i].items[j].itemKey) |
||||
|
} |
||||
|
localStorage.setItem('poms_selected_sider', JSON.stringify([val.fatherKey])) |
||||
|
localStorage.setItem('poms_open_sider', JSON.stringify(openArr)) |
||||
|
setLeftItems(allItems[i].items) |
||||
|
} |
||||
|
} |
||||
|
setLeftShow(true) |
||||
|
} |
||||
|
else { |
||||
|
localStorage.setItem('poms_open_sider', JSON.stringify([])) |
||||
|
localStorage.removeItem('poms_selected_sider') |
||||
|
setLeftShow(false) |
||||
|
} |
||||
|
history.push(val.to); |
||||
|
}} |
||||
|
/> |
||||
|
</Layout.Header> |
||||
|
<Layout style={{ height: 'calc(100% - 50px)' }}> |
||||
|
{leftShow ? (<Layout.Sider> |
||||
|
<Sider |
||||
|
sections={sections} |
||||
|
leftItems={leftItems} |
||||
|
dispatch={dispatch} |
||||
|
user={user} |
||||
|
leftChange={leftChange} |
||||
|
pathname={location.pathname} |
||||
|
collapsed={collapsed} |
||||
|
/> |
||||
|
</Layout.Sider>) : ('')} |
||||
|
<Layout.Content> |
||||
|
<div style={{ |
||||
|
background: "#F2F3F5", |
||||
|
}}> |
||||
|
<div id="page-content" style={{ |
||||
|
height: clientHeight - 12, |
||||
|
minWidth: 520, |
||||
|
position: 'relative', |
||||
|
}}> |
||||
|
<div style={{ |
||||
|
minHeight: clientHeight - 32 - 12, |
||||
|
position: 'relative', |
||||
|
padding: '12px 8px', |
||||
|
}}> |
||||
|
{children} |
||||
|
</div> |
||||
|
<Layout.Footer> |
||||
|
<Footer /> |
||||
|
</Layout.Footer> |
||||
|
</div> |
||||
|
</div> |
||||
|
</Layout.Content> |
||||
|
</Layout> |
||||
|
</> |
||||
|
} |
||||
|
</Layout > |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
function mapStateToProps (state) { |
||||
|
const { global, auth, ajaxResponse, webSocket } = state; |
||||
|
return { |
||||
|
title: global.title, |
||||
|
copyright: global.copyright, |
||||
|
sections: global.sections, |
||||
|
actions: global.actions, |
||||
|
clientWidth: global.clientWidth, |
||||
|
clientHeight: global.clientHeight, |
||||
|
msg: ajaxResponse.msg, |
||||
|
user: auth.user, |
||||
|
socket: webSocket.socket |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
export default connect(mapStateToProps)(LayoutContainer); |
@ -0,0 +1,18 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
import React from 'react'; |
||||
|
import moment from 'moment' |
||||
|
|
||||
|
const NoMatch = props => { |
||||
|
return ( |
||||
|
<div style={{ textAlign: 'center', padding: 120 }}> |
||||
|
<p style={{ fontSize: 80, lineHeight: 1.5 }}>404</p> |
||||
|
<p style={{ fontSize: 32, lineHeight: 2 }}>PAGE NOT FOUND</p> |
||||
|
<p>很遗憾,您暂时无法访问该页面。</p> |
||||
|
<p>请检查您访问的链接地址是否正确。</p> |
||||
|
<p style={{ marginTop: 80 }}>Copyright © {moment().year()} 飞尚</p> |
||||
|
</div> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
export default NoMatch; |
@ -0,0 +1,188 @@ |
|||||
|
'use strict'; |
||||
|
import React, { useEffect, useState } from 'react'; |
||||
|
import moment from 'moment'; |
||||
|
import configStore from './store'; |
||||
|
import { Provider } from 'react-redux'; |
||||
|
import { createBrowserHistory } from 'history'; |
||||
|
import { ConnectedRouter } from 'connected-react-router' |
||||
|
import { Layout, NoMatch } from './containers'; |
||||
|
import { BrowserRouter, Switch, Route } from "react-router-dom"; |
||||
|
import { ConfigProvider } from '@douyinfe/semi-ui'; |
||||
|
import layoutActions from './actions'; |
||||
|
import zhCN from '@douyinfe/semi-ui/lib/es/locale/source/zh_CN'; |
||||
|
import { basicReducer } from '@peace/utils'; |
||||
|
import { push } from 'react-router-redux'; |
||||
|
import 'moment/locale/zh-cn'; |
||||
|
|
||||
|
moment.locale('zh-cn'); |
||||
|
|
||||
|
const { initLayout, initApiRoot, resize, initWebSocket } = layoutActions; |
||||
|
|
||||
|
const Root = props => { |
||||
|
const { sections, title, copyright } = props; |
||||
|
const [history, setHistory] = useState(null) |
||||
|
const [store, setStore] = useState(null) |
||||
|
const [outerRoutes, setOuterRoutes] = useState([]) |
||||
|
const [combineRoutes, setCombineRoutes] = useState([]) |
||||
|
const [innnerRoutes, setInnerRoutes] = useState([]) |
||||
|
|
||||
|
const flatRoutes = (routes) => { |
||||
|
const combineRoutes = []; |
||||
|
|
||||
|
function flat (routes, parentRoute) { |
||||
|
routes.forEach((route, i) => { |
||||
|
let obj = { |
||||
|
path: route.path, |
||||
|
breadcrumb: route.breadcrumb, |
||||
|
component: route.component || null, |
||||
|
authCode: route.authCode || '', |
||||
|
key: route.key |
||||
|
} |
||||
|
if (!route.path.startsWith("/")) { |
||||
|
console.error('路由配置需以 "/" 开始:' + route.path); |
||||
|
} |
||||
|
if (route.path.length > 1 && route.path[route.path.length] == '/') { |
||||
|
console.error('除根路由路由配置不可以以 "/" 结束:' + route.path); |
||||
|
} |
||||
|
if (parentRoute && parentRoute != '/') { |
||||
|
obj.path = parentRoute + route.path; |
||||
|
} |
||||
|
if (route.exact) { |
||||
|
obj.exact = true |
||||
|
} |
||||
|
if (route.hasOwnProperty('childRoutes')) { |
||||
|
combineRoutes.push(obj); |
||||
|
flat(route.childRoutes, obj.path) |
||||
|
} else { |
||||
|
combineRoutes.push(obj) |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
flat(routes); |
||||
|
return combineRoutes; |
||||
|
} |
||||
|
|
||||
|
const initReducer = (reducers, reducerName, action) => { |
||||
|
let reducerParams = {} |
||||
|
const { actionType, initReducer, reducer } = action()() |
||||
|
if (initReducer || reducer) { |
||||
|
if (reducer) { |
||||
|
if (reducer.name) { |
||||
|
reducerName = reducer.name |
||||
|
} |
||||
|
if (reducer.params) { |
||||
|
reducerParams = reducer.params |
||||
|
} |
||||
|
} else { |
||||
|
reducerName = `${reducerName}Rslt` |
||||
|
} |
||||
|
reducers[reducerName] = function (state, action) { |
||||
|
return basicReducer(state, action, Object.assign({ actionType: actionType }, reducerParams)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
useEffect(async () => { |
||||
|
let innerRoutes = [] |
||||
|
let outerRoutes = [] |
||||
|
let reducers = {} |
||||
|
let actions = { |
||||
|
layout: layoutActions |
||||
|
} |
||||
|
|
||||
|
for (let s of sections) { |
||||
|
if (!s.key) console.warn('请给你的section添加一个key值,section name:' + s.name); |
||||
|
for (let r of s.routes) { |
||||
|
if (r.type == 'inner' || r.type == 'home') { |
||||
|
innerRoutes.push(r.route) |
||||
|
} else if (r.type == 'outer') { |
||||
|
outerRoutes.push(r.route) |
||||
|
} |
||||
|
} |
||||
|
if (s.reducers) { |
||||
|
reducers = { ...reducers, ...s.reducers } |
||||
|
} |
||||
|
if (s.actions) { |
||||
|
actions = { ...actions, [s.key]: s.actions } |
||||
|
if (s.key != 'auth') { |
||||
|
for (let ak in s.actions) { |
||||
|
let actions = s.actions[ak] |
||||
|
if (actions && typeof actions == 'object') { |
||||
|
for (let actionName in actions) { |
||||
|
initReducer(reducers, actionName, actions[actionName]) |
||||
|
} |
||||
|
} else if (typeof actions == 'function') { |
||||
|
initReducer(reducers, ak, actions) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
let history = createBrowserHistory(); |
||||
|
let store = configStore(reducers, history); |
||||
|
store.dispatch(initLayout(title, copyright, sections, actions)); |
||||
|
// store.dispatch(resize(document.body.clientHeight, document.body.clientWidth)); |
||||
|
store.dispatch(resize(document.getElementById('PomsApp').clientHeight, document.getElementById('PomsApp').clientWidth)); |
||||
|
store.dispatch(actions.auth.initAuth()); |
||||
|
const resourceRoot = await store.dispatch(initApiRoot()) |
||||
|
store.dispatch(initWebSocket({})) |
||||
|
const combineRoutes = flatRoutes(innerRoutes); |
||||
|
|
||||
|
setInnerRoutes(combineRoutes) |
||||
|
setHistory(history) |
||||
|
setStore(store) |
||||
|
setOuterRoutes(outerRoutes.map(route => ( |
||||
|
<Route |
||||
|
key={route.key} |
||||
|
exact |
||||
|
path={route.path} |
||||
|
component={route.component} |
||||
|
/> |
||||
|
))) |
||||
|
setCombineRoutes(combineRoutes.map(route => ( |
||||
|
<Route |
||||
|
key={route.key} |
||||
|
exact={route.hasOwnProperty('exact') ? route.exact : true} |
||||
|
path={route.path} |
||||
|
component={route.component} |
||||
|
/> |
||||
|
))) |
||||
|
}, []) |
||||
|
|
||||
|
return ( |
||||
|
<> |
||||
|
{ |
||||
|
store ? |
||||
|
<ConfigProvider locale={zhCN}> |
||||
|
<Provider store={store}> |
||||
|
<BrowserRouter basename={window.__MICRO_APP_BASE_ROUTE__ || '/'}> |
||||
|
<ConnectedRouter history={history}> |
||||
|
|
||||
|
<Switch> |
||||
|
{outerRoutes} |
||||
|
<Layout |
||||
|
history={history} |
||||
|
routes={innnerRoutes} |
||||
|
> |
||||
|
{combineRoutes} |
||||
|
</Layout> |
||||
|
<Route |
||||
|
path={'*'} |
||||
|
component={NoMatch} |
||||
|
/> |
||||
|
</Switch> |
||||
|
|
||||
|
</ConnectedRouter> |
||||
|
</BrowserRouter> |
||||
|
</Provider> |
||||
|
</ConfigProvider> |
||||
|
: '' |
||||
|
} |
||||
|
</> |
||||
|
|
||||
|
) |
||||
|
} |
||||
|
|
||||
|
export default Root; |
@ -0,0 +1,28 @@ |
|||||
|
/** |
||||
|
* Created by liu.xinyi |
||||
|
* on 2016/4/1. |
||||
|
*/ |
||||
|
'use strict'; |
||||
|
const initState = { |
||||
|
msg: null |
||||
|
}; |
||||
|
|
||||
|
import Immutable from 'immutable'; |
||||
|
|
||||
|
/** |
||||
|
* 全局ajax响应处理: |
||||
|
* 判断action中是否有done字段,如果有,则修改store中的msg.done |
||||
|
* 判断action中是否有error字段,如果有,则修改store中msg.error |
||||
|
* 在layout中根据msg的值,呈现提示信息。 |
||||
|
*/ |
||||
|
export default function ajaxResponse (state = initState, action) { |
||||
|
if (action.done) { |
||||
|
return Immutable.fromJS(state).set('msg', { done: action.done }).toJS(); |
||||
|
} |
||||
|
|
||||
|
if (action.error) { |
||||
|
return Immutable.fromJS(state).set('msg', { error: action.error }).toJS(); |
||||
|
} |
||||
|
|
||||
|
return { msg: null }; |
||||
|
}; |
@ -0,0 +1,41 @@ |
|||||
|
'use strict'; |
||||
|
import Immutable from 'immutable'; |
||||
|
import { INIT_LAYOUT, RESIZE, INIT_API_ROOT } from '../actions/global'; |
||||
|
|
||||
|
function global (state = { |
||||
|
title: '', |
||||
|
copyright: '', |
||||
|
sections: [], |
||||
|
actions: {}, |
||||
|
plugins: {}, |
||||
|
clientHeight: 768, |
||||
|
clientWidth: 1024, |
||||
|
apiRoot: '', |
||||
|
iotVcmpWeb: '', |
||||
|
}, action) { |
||||
|
const payload = action.payload; |
||||
|
switch (action.type) { |
||||
|
case RESIZE: |
||||
|
return Immutable.fromJS(state).merge({ |
||||
|
clientHeight: payload.clientHeight, |
||||
|
clientWidth: payload.clientWidth |
||||
|
}).toJS(); |
||||
|
case INIT_LAYOUT: |
||||
|
return Immutable.fromJS(state).merge({ |
||||
|
title: payload.title, |
||||
|
copyright: payload.copyright, |
||||
|
sections: payload.sections, |
||||
|
actions: payload.actions, |
||||
|
plugins: payload.plugins, |
||||
|
}).toJS(); |
||||
|
case INIT_API_ROOT: |
||||
|
return Immutable.fromJS(state).merge({ |
||||
|
apiRoot: payload.apiRoot, |
||||
|
iotVcmpWeb: payload.iotVcmpWeb |
||||
|
}).toJS(); |
||||
|
default: |
||||
|
return state; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default global; |
@ -0,0 +1,17 @@ |
|||||
|
/** |
||||
|
* User: liuxinyi/liu.xinyi@free-sun.com.cn |
||||
|
* Date: 2016/1/13 |
||||
|
* Time: 17:52 |
||||
|
* |
||||
|
*/ |
||||
|
'use strict'; |
||||
|
|
||||
|
import global from './global'; |
||||
|
import webSocket from './webSocket' |
||||
|
import ajaxResponse from './ajaxResponse'; |
||||
|
|
||||
|
export default { |
||||
|
global, |
||||
|
webSocket, |
||||
|
ajaxResponse, |
||||
|
}; |
@ -0,0 +1,21 @@ |
|||||
|
'use strict'; |
||||
|
import * as actionTypes from '../actions/webSocket'; |
||||
|
import Immutable from 'immutable'; |
||||
|
|
||||
|
const initState = { |
||||
|
socket: null, |
||||
|
}; |
||||
|
|
||||
|
function webSocket (state = initState, action) { |
||||
|
const payload = action.payload; |
||||
|
switch (action.type) { |
||||
|
case actionTypes.INIT_WEB_SOCKET: |
||||
|
return Immutable.fromJS(state).merge({ |
||||
|
socket: payload.socket, |
||||
|
}).toJS(); |
||||
|
default: |
||||
|
return state; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default webSocket; |
@ -0,0 +1,13 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
import storeProd from './store.prod' |
||||
|
import storeDev from './store.dev' |
||||
|
|
||||
|
let store = null; |
||||
|
if (process.env.NODE_ENV == 'production') { |
||||
|
store = storeProd; |
||||
|
} else { |
||||
|
store = storeDev; |
||||
|
} |
||||
|
|
||||
|
export default store; |
@ -0,0 +1,30 @@ |
|||||
|
/** |
||||
|
* Created by liu.xinyi |
||||
|
* on 2016/4/8. |
||||
|
*/ |
||||
|
'use strict'; |
||||
|
import { createStore, combineReducers, applyMiddleware, compose } from 'redux'; |
||||
|
import reduxThunk from 'redux-thunk'; |
||||
|
import { connectRouter, routerMiddleware } from 'connected-react-router'; |
||||
|
import innerReducers from '../reducers'; |
||||
|
|
||||
|
function configStore(reducers, history) { |
||||
|
const reducer = Object.assign({}, innerReducers, reducers, { |
||||
|
router: connectRouter(history) |
||||
|
}); |
||||
|
|
||||
|
const composeEnhancers = |
||||
|
typeof window === 'object' && |
||||
|
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? |
||||
|
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ |
||||
|
// Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
|
||||
|
}) : compose; |
||||
|
|
||||
|
const enhancers = composeEnhancers( |
||||
|
applyMiddleware(routerMiddleware(history), reduxThunk) |
||||
|
); |
||||
|
|
||||
|
return createStore(combineReducers(reducer), {}, enhancers); |
||||
|
} |
||||
|
|
||||
|
export default configStore; |
@ -0,0 +1,20 @@ |
|||||
|
/** |
||||
|
* Created by liu.xinyi |
||||
|
* on 2016/4/8. |
||||
|
*/ |
||||
|
'use strict'; |
||||
|
|
||||
|
import { createStore, combineReducers, applyMiddleware } from 'redux'; |
||||
|
import reduxThunk from 'redux-thunk'; |
||||
|
import { connectRouter, routerMiddleware } from 'connected-react-router'; |
||||
|
import innerReducers from '../reducers'; |
||||
|
|
||||
|
function configStore(reducers, history){ |
||||
|
const reducer = Object.assign({}, innerReducers, reducers, { |
||||
|
router: connectRouter(history) |
||||
|
}); |
||||
|
|
||||
|
return createStore(combineReducers(reducer), {}, applyMiddleware(routerMiddleware(history), reduxThunk)); |
||||
|
} |
||||
|
|
||||
|
export default configStore; |
@ -0,0 +1,7 @@ |
|||||
|
// __MICRO_APP_ENVIRONMENT__和__MICRO_APP_PUBLIC_PATH__是由micro-app注入的全局变量
|
||||
|
if (window.__MICRO_APP_ENVIRONMENT__) { |
||||
|
// eslint-disable-next-line
|
||||
|
__webpack_public_path__ = window.__MICRO_APP_PUBLIC_PATH__ |
||||
|
} else { |
||||
|
__webpack_public_path__ = '/' |
||||
|
} |
@ -0,0 +1,83 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
import { ApiTable, AxyRequest, EmisRequest } from '$utils' |
||||
|
import { Request } from '@peace/utils'; |
||||
|
|
||||
|
export const INIT_AUTH = 'INIT_AUTH'; |
||||
|
export function initAuth (userData) { |
||||
|
const sessionUser = JSON.parse(sessionStorage.getItem('pomsUser')) |
||||
|
const user = userData || sessionUser || {}; |
||||
|
if (user.authorized && !sessionUser) { |
||||
|
sessionStorage.setItem('pomsUser', JSON.stringify(user)) |
||||
|
} |
||||
|
return { |
||||
|
type: INIT_AUTH, |
||||
|
payload: { |
||||
|
user: user |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
export const REQUEST_LOGIN = 'REQUEST_LOGIN'; |
||||
|
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'; |
||||
|
export const LOGIN_ERROR = 'LOGIN_ERROR'; |
||||
|
export function login (username, password) { |
||||
|
return dispatch => { |
||||
|
dispatch({ type: REQUEST_LOGIN }); |
||||
|
|
||||
|
if (!username || !password) { |
||||
|
dispatch({ |
||||
|
type: LOGIN_ERROR, |
||||
|
payload: { error: '请输入账号名和密码' } |
||||
|
}); |
||||
|
return Promise.resolve(); |
||||
|
} |
||||
|
|
||||
|
// return dispatch({
|
||||
|
// type: LOGIN_SUCCESS,
|
||||
|
// payload: {
|
||||
|
// user: {
|
||||
|
// authorized: true,
|
||||
|
// namePresent: 'TEST'
|
||||
|
// }
|
||||
|
// },
|
||||
|
// });
|
||||
|
|
||||
|
return Request.post(ApiTable.login, { username, password, code: 'POMS' }) |
||||
|
.then(user => { |
||||
|
sessionStorage.setItem('pomsUser', JSON.stringify(user)); |
||||
|
return dispatch({ |
||||
|
type: LOGIN_SUCCESS, |
||||
|
payload: { user: user }, |
||||
|
}); |
||||
|
}, error => { |
||||
|
let { body } = error.response; |
||||
|
return dispatch({ |
||||
|
type: LOGIN_ERROR, |
||||
|
payload: { |
||||
|
error: body && body.message ? body.message : '登录失败' |
||||
|
} |
||||
|
}) |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export const LOGOUT = 'LOGOUT'; |
||||
|
export function logout () { |
||||
|
const user = JSON.parse(sessionStorage.getItem('pomsUser')) |
||||
|
user && user.token ? |
||||
|
Request.put(ApiTable.logout, { |
||||
|
token: user.token, |
||||
|
code: 'POMS' |
||||
|
}) : null; |
||||
|
sessionStorage.removeItem('pomsUser'); |
||||
|
return { |
||||
|
type: LOGOUT |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
export default { |
||||
|
initAuth, |
||||
|
login, |
||||
|
logout |
||||
|
} |
@ -0,0 +1,7 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
import auth from './auth'; |
||||
|
|
||||
|
export default { |
||||
|
...auth |
||||
|
}; |
@ -0,0 +1,4 @@ |
|||||
|
'use strict'; |
||||
|
import Login from './login'; |
||||
|
|
||||
|
export { Login }; |
@ -0,0 +1,143 @@ |
|||||
|
'use strict'; |
||||
|
import React, { useEffect, useRef } from 'react'; |
||||
|
import { connect } from 'react-redux'; |
||||
|
import { push } from 'react-router-redux'; |
||||
|
import { Form, Button, Toast } from '@douyinfe/semi-ui'; |
||||
|
import { login, LOGIN_SUCCESS } from '../actions/auth'; |
||||
|
import { IconLock, IconUser } from '@douyinfe/semi-icons'; |
||||
|
import '../style.less' |
||||
|
|
||||
|
const Login = props => { |
||||
|
const { dispatch, user, error, actions, apiRoot, isRequesting } = props |
||||
|
const form = useRef(); |
||||
|
|
||||
|
useEffect(() => { |
||||
|
if (error) { |
||||
|
Toast.error(error); |
||||
|
form.current.setValue('password', '') |
||||
|
} |
||||
|
}, [error]) |
||||
|
|
||||
|
useEffect(() => { |
||||
|
if (user && user.authorized) { |
||||
|
dispatch(push('/console')); |
||||
|
localStorage.setItem('poms_open_sider', JSON.stringify([])) |
||||
|
localStorage.removeItem('poms_selected_sider') |
||||
|
} |
||||
|
}, [user]) |
||||
|
|
||||
|
return ( |
||||
|
<div style={{ |
||||
|
width: '100%', |
||||
|
height: '100%', |
||||
|
// backgroundImage: "url('/assets/images/background/loginBackground.gif')", |
||||
|
// backgroundSize: 'cover', |
||||
|
// backgroundRepeat: 'no-repeat', |
||||
|
// position: 'relative', |
||||
|
}}> |
||||
|
<video |
||||
|
autoPlay loop muted |
||||
|
style={{ |
||||
|
width: 'calc(100% - 512px)', objectFit: "fill", objectPosition: 'left top', height: '100%' |
||||
|
}} |
||||
|
src="/assets/video/login_bg.mp4" |
||||
|
type="video/mp4" |
||||
|
/> |
||||
|
{/* <img src='/assets/images/background/loginBackground.gif' style={{ |
||||
|
width: "100%", |
||||
|
height: 'calc(100vh - 4px)', |
||||
|
objectFit: "cover", |
||||
|
objectPosition: 'left top', |
||||
|
position: 'absolute', |
||||
|
top: 0, |
||||
|
left: 0, |
||||
|
zIndex: "5" |
||||
|
}} /> */} |
||||
|
<div style={{ |
||||
|
width: 600, |
||||
|
height: 90, |
||||
|
objectFit: "cover", |
||||
|
objectPosition: 'left top', |
||||
|
position: 'absolute', |
||||
|
top: 100, |
||||
|
left: 41, |
||||
|
zIndex: 5, |
||||
|
fontSize: 14, |
||||
|
color: ' #005ABD', |
||||
|
display:'flex', |
||||
|
flexDirection: 'column', |
||||
|
justifyContent: 'space-between', |
||||
|
}}> |
||||
|
<div><span style={{ fontFamily: 'YouSheBiaoTiHei', }}>POMS运维中台管理系统—</span> 为企业数字化转型提供安全、稳定、高效的数字化运维底座</div> |
||||
|
<div><span style={{ fontFamily: 'YouSheBiaoTiHei', }}>高效—</span> 为运维智能化升级提供领先的创新技术和平台管理</div> |
||||
|
<div><span style={{ fontFamily: 'YouSheBiaoTiHei', }}>智能—</span>助力数字化转型和智能化升级一步到位</div> |
||||
|
</div> |
||||
|
<div style={{ |
||||
|
width: 512, |
||||
|
height: '100%', |
||||
|
padding: '45px 60px', |
||||
|
background: 'rgb(255 255 255 / 50%)', |
||||
|
backdropFilter: "saturate(100%) contrast(100%) blur(17px)", |
||||
|
position: 'absolute', |
||||
|
top: 0, |
||||
|
right: 0, |
||||
|
zIndex: "6", |
||||
|
textAlign: 'center', |
||||
|
}}> |
||||
|
<div style={{ |
||||
|
width: 388, |
||||
|
marginTop: "40%" |
||||
|
}}> |
||||
|
<img src="/assets/images/background/user_login.png" alt="" style={{ width: 324, height: 24, marginBottom: 71 }} /> |
||||
|
<Form |
||||
|
onSubmit={values => { |
||||
|
dispatch(login(values.username, values.password)).then(res => { |
||||
|
const data = res.payload.user |
||||
|
dispatch(actions.layout.initWebSocket({ ioUrl: apiRoot, token: data.token, pomsUserId: data.pomsUserInfo.id })) |
||||
|
}) |
||||
|
}} |
||||
|
getFormApi={formApi => form.current = formApi} |
||||
|
> |
||||
|
<Form.Input |
||||
|
className='inputbgc' |
||||
|
field='username' |
||||
|
noLabel={true} |
||||
|
label='用户名' |
||||
|
placeholder='请输入账号' |
||||
|
prefix={<IconUser style={{ color: '#1859C1', marginRight: 14, marginLeft: 8 }} />} |
||||
|
style={{ background: '#FFFFFF', height: 46, marginBottom: 33, border: '1px solid rgb(185 211 239)', borderRadius: '4px' }} |
||||
|
/> |
||||
|
<Form.Input |
||||
|
field='password' |
||||
|
noLabel={true} |
||||
|
mode="password" |
||||
|
autoComplete="" |
||||
|
placeholder='请输入密码' |
||||
|
label='密码' |
||||
|
prefix={<IconLock style={{ color: '#1859C1', marginRight: 14, marginLeft: 8 }} />} |
||||
|
style={{ background: '#FFFFFF', height: 46, border: '1px solid rgb(185 211 239)', borderRadius: '4px' }} |
||||
|
/> |
||||
|
<img src="/assets/images/background/xiangqi.png" style={{ width: 112, height: 14, margin: "4px 0 0 278px" }} /> |
||||
|
<Button htmlType='submit' block theme="solid" loading={isRequesting} style={{ marginTop: 56, height: 46, backgroundColor: '#005ABD', borderRadius: '3px' }}>立即登录</Button> |
||||
|
|
||||
|
</Form> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
function mapStateToProps (state) { |
||||
|
const { auth, global } = state; |
||||
|
return { |
||||
|
user: auth.user, |
||||
|
error: auth.error, |
||||
|
actions: global.actions, |
||||
|
apiRoot: global.apiRoot, |
||||
|
isRequesting: auth.isRequesting |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default connect(mapStateToProps)(Login); |
@ -0,0 +1,12 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
import routes from './routes'; |
||||
|
import reducers from './reducers'; |
||||
|
import actions from './actions'; |
||||
|
|
||||
|
export default { |
||||
|
key: 'auth', |
||||
|
reducers: reducers, |
||||
|
routes: routes, |
||||
|
actions: actions |
||||
|
}; |
@ -0,0 +1,40 @@ |
|||||
|
'use strict'; |
||||
|
import * as actionTypes from '../actions/auth'; |
||||
|
import Immutable from 'immutable'; |
||||
|
|
||||
|
const initState = { |
||||
|
user: {}, |
||||
|
isRequesting: false, |
||||
|
error: null |
||||
|
}; |
||||
|
|
||||
|
function auth (state = initState, action) { |
||||
|
const payload = action.payload; |
||||
|
switch (action.type) { |
||||
|
case actionTypes.INIT_AUTH: |
||||
|
return Immutable.fromJS(state).set('user', payload.user).toJS(); |
||||
|
case actionTypes.REQUEST_LOGIN: |
||||
|
return Immutable.fromJS(state).merge({ |
||||
|
isRequesting: true, |
||||
|
error: null |
||||
|
}).toJS(); |
||||
|
case actionTypes.LOGIN_SUCCESS: |
||||
|
return Immutable.fromJS(state).merge({ |
||||
|
isRequesting: false, |
||||
|
user: payload.user |
||||
|
}).toJS(); |
||||
|
case actionTypes.LOGIN_ERROR: |
||||
|
return Immutable.fromJS(state).merge({ |
||||
|
isRequesting: false, |
||||
|
error: payload.error |
||||
|
}).toJS(); |
||||
|
case actionTypes.LOGOUT: |
||||
|
return Immutable.fromJS(state).merge({ |
||||
|
user: null |
||||
|
}).toJS(); |
||||
|
default: |
||||
|
return state; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default auth; |
@ -0,0 +1,6 @@ |
|||||
|
'use strict'; |
||||
|
import auth from './auth' |
||||
|
|
||||
|
export default { |
||||
|
auth |
||||
|
}; |
@ -0,0 +1,12 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
import { Login } from './containers'; |
||||
|
|
||||
|
export default [{ |
||||
|
type: 'outer', |
||||
|
route: { |
||||
|
key:'signin', |
||||
|
path: "/signin", |
||||
|
component: Login |
||||
|
} |
||||
|
}]; |
@ -0,0 +1,7 @@ |
|||||
|
input:-webkit-autofill{ |
||||
|
-webkit-text-fill-color:black !important; |
||||
|
-webkit-box-shadow: 0 0 0px 1000px transparent inset !important; |
||||
|
box-shadow: 0 0 0px 1000px transparent inset !important; |
||||
|
background-color:transparent; |
||||
|
transition: background-color 50000s ease-in-out 0s; |
||||
|
} |