* support native openstack login.

This commit is contained in:
baiziyu 2021-04-09 18:20:27 +08:00
parent 94ac8e6764
commit bf188151e8
4 changed files with 132 additions and 474 deletions

View File

@ -1,31 +1,94 @@
const SessionHelper = require('../lib/SessionHelper');
const urlApi = require('url');
const request = require('request');
const Consts = require('../constants');
const { getOEM } = require('../lib/util');
const logger = require('log4js').getLogger('INDEX');
const uuid = require('uuid');
exports.enabled = true;
exports.mapping = '/index.html';
exports.middlewares = function(router) {
let __DEV__ = process.env.NODE_ENV === 'development';
let serviceAddr = context.getResource('serviceAddr.json');
return [
//获取OEM信息
function(req, res, next) {
getOEM(serviceAddr['keystone']).then(res => {
let session = req.session;
session = Object.assign(session, res);
next();
}).catch(e => {
next();
});
async function getProjects(endpoint, unscopedToken) {
let options = {
url: endpoint + '/v3/auth/projects',
method: 'GET',
json: true,
body: {},
headers: {
'X-Auth-Token': unscopedToken
}
};
return new Promise((resolve, reject) => {
request(options, (error, response, body) => {
if (error) {
reject(error);
} else {
resolve(body);
}
});
});
}
async function getScopedToken(endpoint, unscopedToken, projectId) {
let options = {
url: endpoint + '/v3/auth/tokens',
method: 'POST',
json: true,
body: {
"auth": {
"identity": {
"methods": [
"token"
],
"token": {
"id": unscopedToken
}
},
"scope": {
"project": {
"id": projectId
}
}
}
},
//获取项目列表
function(req, res, next) {
let session = req.session;
let restUrl = "/v3/auth/projects";
headers: {
'Content-Type': 'application/json',
'X-Auth-Token': unscopedToken
}
};
return new Promise((resolve, reject) => {
request(options, (error, response, body) => {
if (error) {
reject(error);
} else {
resolve({ response, body });
}
});
});
}
async function getMenus() {
return { "m.systemmanage": true, "m.systemmanage.departmentmanage": true, "m.systemmanage.groupmanage": true, "m.systemmanage.ipsetting": true, "m.systemmanage.opreatelog": true, "m.systemmanage.paramsetting": true, "m.systemmanage.paramsetting.businesstemplate": true, "m.systemmanage.paramsetting.copyRightSetting": true, "m.systemmanage.paramsetting.logsetting": true, "m.systemmanage.paramsetting.metadata": true, "m.systemmanage.paramsetting.moduleswitch": true, "m.systemmanage.paramsetting.systemsetting": true, "m.systemmanage.projectmanage": true, "m.systemmanage.rolemanage": true, "m.systemmanage.serialnumber": true, "m.systemmanage.systemstatus": true, "m.systemmanage.usermanage": true, "m.network": true, "m.network.balance": true, "m.network.firewall": true, "m.network.flaotingip": true, "m.network.network": true, "m.network.router": true, "m.network.securitygroup": true, "m.network.topo": true, "m.network.vpn": true, "m.docker_server": true, "m.docker_server.clusters": true, "m.docker_server.images": true, "m.docker_server.instances": true, "m.docker_server.volume": true, "m.heat": true, "m.heat.stack": true, "m.heat.template": true, "m.calculate": true, "m.calculate.disk": true, "m.calculate.host": true, "m.calculate.instance": true, "m.calculate.keypair": true, "m.calculate.media": true, "m.calculate.resourcespec": true, "m.calculate.snapshot": true, "m.senlin": true, "m.workflow": true, "m.workflow.task": true, "m.operationmanage": true, "m.operationmanage.fault": true, "m.operationmanage.flowmanage": true, "m.operationmanage.orderapprove": true, "m.operationmanage.orderlist": true, "m.calculate.fileStorage": true, "m.calculate.physical": true, "m.database": true, "m.calculate.baremetal": true, "m.systemmanage.usermanage.add": true, "m.systemmanage.usermanage.assignrole": true, "m.systemmanage.usermanage.delete": true, "m.systemmanage.usermanage.disable": true, "m.systemmanage.usermanage.edit": true, "m.systemmanage.usermanage.enable": true, "m.systemmanage.usermanage.resetpassword": true, "m.systemmanage.usermanage.unlock": true, "m.systemmanage.usermanage.userroledetail": true, "m.systemmanage.rolemanage.add": true, "m.systemmanage.rolemanage.delete": true, "m.systemmanage.rolemanage.detail": true, "m.systemmanage.rolemanage.edit": true, "m.systemmanage.groupmanage.add": true, "m.systemmanage.groupmanage.assignrole": true, "m.systemmanage.groupmanage.delete": true, "m.systemmanage.groupmanage.edit": true, "m.systemmanage.groupmanage.personmanage": true, "m.systemmanage.ipsetting.add": true, "m.systemmanage.ipsetting.delete": true, "m.systemmanage.ipsetting.edit": true, "m.systemmanage.projectmanage.create": true, "m.systemmanage.projectmanage.delete": true, "m.systemmanage.projectmanage.edit": true, "m.systemmanage.projectmanage.roledetail": true, "m.systemmanage.projectmanage.updategroup": true, "m.systemmanage.projectmanage.updateperson": true, "m.systemmanage.projectmanage.updatequota": true, "m.systemmanage.projectmanage.usage": true, "m.startpage": true, "m.operationmanage.cost": true, "m.operationmanage.pricesetting": true, "m.operationmanage.report": true, "m.network.qos": true, "m.calculate.logictopo": true, "m.monitor": true, "m.monitor.AlertMange": true, "m.monitor.monitorset": true, "m.monitor.resourcemonitor": true, "m.systemmanage.paramsetting.pimsetting": true, "m.operationAnalysis": true, "m.operationAnalysis.TrendPredict": true, "m.monitor.CustomGraph": true, "m.operationAnalysis.HealthStatus": true, "m.operationAnalysis.logSearch": true, "m.operationAnalysis.logAnalysis": true, "m.operationAnalysis.logError": true, "m.docker_server.kbclusters": true, "m.calculate.recycleBin": true, "m.monitor.EventMonitor": true, "m.operationAnalysis.noThresholdAnomalyDetection": true, "m.systemmanage.certManage": true, "m.operationAnalysis.CLUSTER_RESOURCE_PREDICTION": true, "m.sahara": true, "m.sahara.cluster": true, "m.sahara.clustertemplate": true, "m.sahara.image": true, "m.sahara.nodegroup": true };
}
async function getRegions() {
return [{ "region": "RegionOne", "region_id": "RegionOne", "active": true }];
}
function cutEndpointUrl(url) {
url = new URL(url);
let result = url.origin;
if (url.pathname !== '') {
result += '/'+url.pathname.split('/')[1];
}
return result;
}
exports.middlewares = function (router) {
const __DEV__ = process.env.NODE_ENV === 'development';
const serviceAddr = context.getResource('serviceAddr.json');
const endpoint = `http://${serviceAddr.keystone}`;
return [
async function (req, res, next) {
const session = req.session;
session.poc = !!serviceAddr.poc;
session.objectStorageType = serviceAddr.objectStorageType || 'S3';
session.S3SuperUser = serviceAddr.S3SuperUser || {};
@ -36,432 +99,54 @@ exports.middlewares = function(router) {
session.forceCheckCurrentUser = 'forceCheckCurrentUser' in serviceAddr ? serviceAddr.forceCheckCurrentUser : false;
session.forceVNCPassword = 'forceVNCPassword' in serviceAddr ? serviceAddr.forceVNCPassword : false;
session.arch = serviceAddr.arch || 'x86';
let host = serviceAddr['keystone'];
let url = urlApi.format({
protocol: Consts.HTTP,
host: host
});
let options = {
url: url+restUrl,
//if you expect binary data, you should set encoding: null
// encoding : null, //让body 直接是buffer
method: Consts.GET,
json: true,
body: {},
qs: {},
headers: {
[Consts.KEY_CONTENT_TYPE]: Consts.CONTENT_TYPE,
[Consts.KEY_ACCEPT]: Consts.ACCEPT,
'X-Auth-Token': session[Consts.PROJECT_TOKEN]?session[Consts.PROJECT_TOKEN]:(session[Consts.KEY_TOKEN] || ""),
'language': session[Consts.KEY_LANGUAGE] || ""
}
};
request(options, function(error, response, body) {
if (body && 'error' in body && String(body.error.code) === "401") {
let loginUrl = req.baseUrl ? (req.baseUrl + '/timeout.html') : '/timeout.html';
res.redirect(loginUrl+"?__="+new Date().getTime());
return;
}
let projectList = [];
let projects = body.projects || [];
let flg = false;
let roleId = req.cookies.roleType;
let projectName;
let projectId = req.cookies.pid;
let roleType = req.cookies.roleType;
projects.forEach((item, key) => {
//用户cookie project id可用
//考虑切换环境情况
if (item.project_id === projectId&&item.role_type === parseInt(roleType)) {
flg = true;
roleId = item.role_id;
projectName = item.project_name;
}
projectList.push({
name: item.project_name,
id: item.project_id,
roleId:item.role_id,
roleType:item.role_type,
uuid:item.project_id+"&"+item.role_type,
active: (item.project_id === projectId && item.role_type == roleType)
});
});
if (!flg) {
projectId =(projectList[0] || {}).id;
roleType = (projectList[0] || {}).roleType;
projectName = (projectList[0] || {}).project_name;
roleId = (projectList[0] || {}).roleId;
(projectList[0] || {}).active = true;
}
let options = {
// secure: !__DEV__,
maxAge: 1000 * 60 * 60 * 24, // would expire after 1 day
httpOnly: false, // The cookie only accessible by the web server
signed: false // Indicates if the cookie should be signed
};
// Set cookie
res.cookie('pid', projectId||"", options); // options is optional
res.cookie('roleType', roleType==undefined?"":roleType, options);
res.cookie('roleId', roleId==undefined?"":roleId, options);
req.body['pid'] = projectId;
req.body['roleType'] = roleType;
req.body['projectName'] = projectName;
req.session['pid'] = projectId;
req.session['roleId'] = roleId;
req.session[Consts.KEY_ROLE_TYPE] = roleType;
req.session[Consts.KEY_PROJECT_LIST] = projectList;
req.session[Consts.KEY_ROLE_ID] = roleId;
req.session.save(function () {
next();
});
});
},
//获取菜单码
function(req, res, next) {
let session = req.session;
var body = req.body;
let restUrl = "/v3/inspur/auth/menus/"+(body.pid?(body.roleType==0?'-':body.pid):"null");
let serviceAddr = context.getResource('serviceAddr.json');
let host = serviceAddr['keystone'];
let url = urlApi.format({
protocol: Consts.HTTP,
host: host
});
let options = {
url: url+restUrl,
//if you expect binary data, you should set encoding: null
// encoding : null, //让body 直接是buffer
method: Consts.GET,
json: true,
body: {},
qs: {},
headers: {
[Consts.KEY_CONTENT_TYPE]: Consts.CONTENT_TYPE,
[Consts.KEY_ACCEPT]: Consts.ACCEPT,
'X-Auth-Token': session[Consts.PROJECT_TOKEN]?session[Consts.PROJECT_TOKEN]:(session[Consts.KEY_TOKEN] || ""),
'language': session[Consts.KEY_LANGUAGE] || ""
}
};
request(options, function(error, response, body) {
if ( (body.error || {}).code + "" === "400") {
let loginUrl = req.baseUrl ? (req.baseUrl + '/timeout.html?serialnumber=no') : '/timeout.html?serialnumber=no';
res.redirect(loginUrl);
return;
}
if ( (body.error || {}).code + "" === "401") {
let loginUrl = req.baseUrl ? (req.baseUrl + '/timeout.html#') : '/timeout.html#';
res.redirect(loginUrl);
return;
}
let opeCodeList = {};
if (body.menus && body.menus.length>0) {
body.menus.forEach(function(item) {
let menuCode = item.menu_code;
opeCodeList[menuCode] = true;
});
}
req.session[Consts.KEY_MENUCODE_LIST] = opeCodeList;
next();
});
},
function (req, res, next) { // 如果用户没有任何项目则无法通过获取ptoken获取endpoints此时使用内置的leo账号获取leo的endpoint
let pid = req.body.pid;
if (pid) {
next();
const unscopedToken = session['token'];
// 获取项目
let projects = (await getProjects(endpoint, unscopedToken)).projects;
let projectId = '';
if (projects.length > 0) {
projectId = projects[0].id;
projects[0].active = true;
}
// 通过项目获取pToken并且获取roleId和roleType
let { body: tokenBody, response: tokenResponse } = await getScopedToken(endpoint, unscopedToken, projectId);
let scopedToken = tokenResponse.headers['x-subject-token'];
let roles = tokenBody.token.roles;
let catalog = tokenBody.token.catalog;
let services = {};
for (let catalogItem of catalog) {
let endpointItem = catalogItem.endpoints[0];
services[catalogItem['name']] = cutEndpointUrl(endpointItem.url);
}
let region = 'RegionOne';
let roleType = '';
let roleId = '';
let adminRole = roles.find(item => item.name === 'admin');
if (adminRole) {
roleType = 0;
roleId = adminRole.id;
} else {
let serviceAddr = context.getResource('serviceAddr.json');
let leoKeystonePassword = serviceAddr.leoKeystonePassword || '';
let host = urlApi.format({
protocol: Consts.HTTP,
host: serviceAddr['keystone']
});
let options = {
url: `${host}/v3/auth/tokens`,
method: Consts.POST,
json: true,
body: {
"auth": {
"identity": {
"methods": [
"password"
],
"password": {
"user": {
"name": "leo",
"domain": {
"name": "default"
},
"password": leoKeystonePassword
}
}
}
}
},
qs: {},
headers: {
[Consts.KEY_CONTENT_TYPE]: Consts.CONTENT_TYPE,
[Consts.KEY_ACCEPT]: Consts.ACCEPT
}
};
request(options, function(error, response, body) {
req.session[Consts.KEY_SERVICES] = {};
req.session[Consts.KEY_REGION_SERVICES] = {};
req.session[Consts.KEY_REGIONS] = [];
if (error) {
next();
} else if (body.error) {
next();
} else {
let catalog = body.token.catalog;
let regionId = req.cookies.region_id;
let activeRegionId = '';
let regionServiceObj = {};
let regions = [];
let leoIndex = body.token.catalog.findIndex((item) => {
return item.name === 'leo';
});
catalog[leoIndex].endpoints.filter((endpoint) => {
return endpoint.interface === 'admin';
}).forEach(region => {
regions.push({
region: region.region,
region_id: region.region_id,
active: region.region_id === regionId
});
if (region.region_id === regionId) {
activeRegionId = regionId;
}
});
if (activeRegionId === '') {
activeRegionId = regions[0].region_id;
regions[0].active = true;
}
body.token.catalog.forEach((service, index) => {
service.endpoints.forEach((endpoint, index1) => {
let urlRegExp = /^(https?:\/\/.*:\d*)\/?/;
if (endpoint.interface === 'admin') {
let matches = endpoint.url.match(urlRegExp);
if (matches) {
if (!regionServiceObj[endpoint.region_id]) {
regionServiceObj[endpoint.region_id] = {};
}
regionServiceObj[endpoint.region_id][service.name] = matches[1];
}
}
});
});
req.session[Consts.KEY_SERVICES] = regionServiceObj[activeRegionId];
req.session[Consts.KEY_REGION_SERVICES] = regionServiceObj;
req.session[Consts.KEY_REGIONS] = regions;
next();
}
});
}
},
//获取ptoken
function(req, res, next) {
let session = req.session;
let domainList = session.domainList;
let projectList = session.projectList;
let body = req.body;
let restUrl = "/v3/auth/tokens";
let serviceAddr = context.getResource('serviceAddr.json');
let host = serviceAddr['keystone'];
let url = urlApi.format({
protocol: Consts.HTTP,
host: host
});
let getPIDTraceId = 'req-'+uuid.v1();
let options = {
url: url+restUrl,
//if you expect binary data, you should set encoding: null
// encoding : null, //让body 直接是buffer
method: Consts.POST,
json: true,
body: {},
qs: {},
headers: {
[Consts.KEY_CONTENT_TYPE]: Consts.CONTENT_TYPE,
[Consts.KEY_ACCEPT]: Consts.ACCEPT,
'X-Auth-Token': session[Consts.PROJECT_TOKEN]?session[Consts.PROJECT_TOKEN]:(session[Consts.KEY_TOKEN] || ""),
'language': session[Consts.KEY_LANGUAGE] || "",
'X-Openstack-Request-Id': getPIDTraceId
}
};
let pid = body.pid;
let roleType = body.roleType;
let projectName = body.projectName;
let isLogin = false;
if (!pid) { // 无pid情况只记录登录
next();
isLogin = true;
reordLog('', session['loginTraceId']);
} else {
let bodyParam = {
"auth": {
"identity": {
"methods": [
"token"
],
"token": {
"id": session[Consts.KEY_TOKEN]
}
},
"scope": {
project: {
"id": pid
}
}
}
};
options.body = bodyParam;
let traceId = getPIDTraceId;
isLogin = !session[Consts.PROJECT_TOKEN];
if (isLogin) { // 如果是登陆则使用登陆的追踪ID
traceId = session['loginTraceId'];
}
request(options, function(error, response, body) {
if ( (body.error || {}).code + "" === "401") {
let loginUrl = req.baseUrl ? (req.baseUrl + '/timeout.html#') : '/timeout.html#';
res.redirect(loginUrl);
return;
}
let pToken = response.headers["x-subject-token"];
let serviceObj = {};
let regionServiceObj = {};
let regionList = [];
let regionId = req.cookies.region_id;
let activeRegionId = "";
if (body.token&&body.token.catalog) {
//先查看有几个region并记录下来
let keystoneIndex = body.token.catalog.findIndex((item) => {
return item.name === "keystone";
});
let regions = body.token.catalog[keystoneIndex].endpoints.filter((item) => {
return item.interface === "admin";
});
let flg = false;
regions.forEach((item, index) => {
regionList.push({
region: item.region,
region_id: item.region_id,
active: item.region_id === regionId
});
if (item.region_id === regionId) {
activeRegionId = item.region_id;
flg = true;
}
});
if (!flg) {
regionList[0].active = true;
activeRegionId = regionList[0].region_id;
}
let options = {
// secure: !__DEV__,
maxAge: 1000 * 60 * 60 * 24, // would expire after 1 day
httpOnly: false, // The cookie only accessible by the web server
signed: false // Indicates if the cookie should be signed
};
// Set cookie
res.cookie('region_id', activeRegionId, options); // options is optional
//过滤
body.token.catalog.forEach((service, index) => {
service.endpoints.forEach((endpoint, index1) => {
let urlRegExp = /^(https?:\/\/.*:\d*)\/?/;
if (endpoint.interface === 'admin') {
let matches = endpoint.url.match(urlRegExp);
if (matches) {
if (!regionServiceObj[endpoint.region_id]) {
regionServiceObj[endpoint.region_id] = {};
}
regionServiceObj[endpoint.region_id][service.name] = matches[1];
}
}
});
});
}
req.session[Consts.PROJECT_TOKEN] = pToken;
req.session[Consts.KEY_SERVICES] = regionServiceObj[activeRegionId];
req.session[Consts.KEY_REGION_SERVICES] = regionServiceObj;
req.session[Consts.KEY_REGIONS] = regionList;
next();
reordLog(activeRegionId, traceId);
});
}
function reordLog(regionId, traceId) {
//记录登陆和切换项目日志
let o = {
url: url+"/v3/inspur/logs",
method: Consts.POST,
json: true,
body: {},
qs: {},
headers: {
[Consts.KEY_CONTENT_TYPE]: Consts.CONTENT_TYPE,
[Consts.KEY_ACCEPT]: Consts.ACCEPT,
'language': session[Consts.KEY_LANGUAGE] || "",
'X-Auth-Token':session[Consts.PROJECT_TOKEN]?session[Consts.PROJECT_TOKEN]:(session[Consts.KEY_TOKEN] || "")
}
}
let date = new Date();
let time = date.getTime()+date.getTimezoneOffset()*60*1000;
o.body = {
logs:[{
user_name: session[Consts.KEY_USER_NAME],
user_id:session[Consts.KEY_USER_ID],
region_id:regionId||"",
project_id:pid?(roleType==0?"":pid):"",
target:"user",
log_level:"info",
ip:session[Consts.KEY_IP],
create_time:time,
description:"user"+session[Consts.KEY_USER_NAME]+" login",
language:"en",
trace_id: traceId
}, {
user_name: session[Consts.KEY_USER_NAME],
user_id:session[Consts.KEY_USER_ID],
region_id:regionId||"",
project_id:pid?(roleType==0?"":pid):"",
target:"用户",
log_level:"信息",
ip:session[Consts.KEY_IP],
create_time:time,
description:"用户:"+session[Consts.KEY_USER_NAME]+"登录",
language:"zh_cn",
trace_id: traceId
}]
}
if (!isLogin) { //代表切换项目
if (req.cookies.switch!="region") {
if (roleType==0) {
o.body.logs[0].description = "user:"+session[Consts.KEY_USER_NAME]+" enter all";
o.body.logs[1].description = "用户:"+session[Consts.KEY_USER_NAME]+" 进入所有";
} else {
o.body.logs[0].description = "user:"+session[Consts.KEY_USER_NAME]+" enter project:"+(projectName||"");
o.body.logs[1].description = "用户:"+session[Consts.KEY_USER_NAME]+" 进入项目:"+(projectName||"");
}
} else if (req.cookies.switch=="region"){
let region = req.cookies.region_id;
o.body.logs[0].description = "user:"+session[Consts.KEY_USER_NAME]+" enter region:"+(region||"");
o.body.logs[1].description = "用户:"+session[Consts.KEY_USER_NAME]+" 进入region:"+(region||"");
}
res.cookie('switch', "", {
// secure: !__DEV__,
maxAge: 1000 * 60 * 60 * 24,
httpOnly: false,
signed: false
});
request(o, function () {})
} else { //如果没有ptoken代表刚登陆进来
o.body.logs[0].description = "user:"+session[Consts.KEY_USER_NAME]+" login";
o.body.logs[1].description = "用户:"+session[Consts.KEY_USER_NAME]+"登录";
request(o, function () {})
let memberRole = roles.find(item => item.name === 'member');
if (memberRole) {
roleType = 2;
roleId = memberRole.id;
}
}
req.session[Consts.KEY_PROJECT_LIST] = projects;
req.session[Consts.PROJECT_TOKEN] = scopedToken;
res.cookie('pid', projectId || "");
res.cookie('roleType', roleType);
res.cookie('roleId', roleId);
req.session['pid'] = projectId;
req.session['roleId'] = roleId;
// 获取菜单
req.session[Consts.KEY_MENUCODE_LIST] = await getMenus();
req.session[Consts.KEY_SERVICES] = services;
req.session[Consts.KEY_REGION_SERVICES] = [];
req.session[Consts.KEY_REGIONS] = await getRegions();
next();
}
];
};
@ -473,7 +158,7 @@ exports.middlewares = function(router) {
* @param {{}} res [response]
* @return {{}}
*/
exports.get = function(req, res) {
exports.get = function (req, res) {
// let options = {
// maxAge: 1000 * 60 * 15, // would expire after 15 minutes
// httpOnly: true, // The cookie only accessible by the web server

View File

@ -62,33 +62,7 @@ module.exports = function (options, context) {
}
async function setIPContinuousWrongNumber(ip, success) {
try {
const connection = await mysql.createConnection({
host: serviceAddr.mysql.host,
port: serviceAddr.mysql.port,
user: serviceAddr.mysql.user,
password: serviceAddr.mysql.password,
database: serviceAddr.mysql.database
});
let existingResults = await connection.query(`SELECT * FROM login_ip_logs WHERE ip = ?`, [ip]);
if (existingResults.length > 0) {
if (success) {
await connection.query(`UPDATE login_ip_logs SET continuous_wrong_numbers = ? WHERE id = ?`, [0, existingResults[0].id]);
} else {
await connection.query(`UPDATE login_ip_logs SET continuous_wrong_numbers = continuous_wrong_numbers + 1 WHERE id = ?`, [existingResults[0].id]);
}
} else {
if (success) {
await connection.query(`INSERT INTO login_ip_logs (ip, continuous_wrong_numbers, update_time) VALUES (?, ?, ?)`, [ip, 0, new Date()]);
} else {
await connection.query(`INSERT INTO login_ip_logs (ip, continuous_wrong_numbers, update_time) VALUES (?, ?, ?)`, [ip, 1, new Date()]);
}
}
await connection.end();
return true;
} catch (e) {
throw e;
}
return true;
}
function getSessionCount(req) {

View File

@ -65,7 +65,6 @@ ElementUI.Select.props.placeholder.default = Vue.t('base.pleaseSelect');
ElementUI.Cascader.props.placeholder.default = Vue.t('base.pleaseSelect');
//绑定logTarget
console.log(logTarget);
Vue.logTarget = logTarget;
// 4. 创建和挂载根实例。

View File

@ -2,10 +2,10 @@ import rules from "./rules";
const Schema = require('async-validator');
export default {
init() {
rules.forEach((item, index) => {
for (let item of rules) {
Schema.messages[item.type] = Vue.t(item.message);
Schema.register(item.type, item.validator);
});
}
}
};