Commit 4b765483 authored by Antoor's avatar Antoor Committed by GitHub

Merge pull request #59 from antoor/v2.0-beta-advanced-configure

v2.0-beta Advanced configure
parents 464c23d6 e87446fc
......@@ -9,12 +9,10 @@
const fs = require('fs'),
path = require('path'),
CONF = require('./config'),
// Logger = require('./logger'),
// logger = null,
// logger = require('log4js').getLogger('Cache'),
Datastore = require('nedb');
let logger
var logger;
class Cache {
/**
......
/**
* Shell数据库管理模块
* 更新:2016/04/28
* 作者:蚁逅 <https://github.com/antoor>
* 更新:2016/06/28
*/
'use strict';
......@@ -10,13 +9,10 @@ const fs = require('fs'),
dns = require('dns'),
path = require('path'),
CONF = require('./config'),
// Logger = require('./logger'),
// logger = null,
// logger = require('log4js').getLogger('Database'),
Datastore = require('nedb'),
qqwry = require("geoips").info();
let logger;
var logger;
class Database {
......@@ -80,6 +76,36 @@ class Database {
});
}
/**
* 根据URL解析出IP&&地理位置
* @param {String} url URL地址
* @return {Promise} ip, addr
*/
_url2ip(url) {
return new Promise((res, rej) => {
// 解析domain
const urlArr = url.match(/(\w+):\/\/([\w\.\-]+)[:]?([\d]*)([\s\S]*)/i);
// 无效url
if (!urlArr || urlArr.length < 3) {
return rej('Unable to resolve domain name from URL');
}
// 获取IP
const domain = urlArr[2];
dns.lookup(domain, (err, ip) => {
if (err) {
return rej(err.toString());
}
// 获取地理位置
const _addr = qqwry.searchIP(ip);
return res({
ip: ip,
addr: `${_addr.Country} ${_addr.Area}`
});
})
})
}
/**
* 添加shell数据
* @param {Object} event ipcMain对象
......@@ -87,68 +113,67 @@ class Database {
*/
addShell(event, opts) {
logger.info('addShell', opts);
// 获取目标IP以及地理位置
// 1. 获取域名
let parse = opts['url'].match(/(\w+):\/\/([\w\.\-]+)[:]?([\d]*)([\s\S]*)/i);
if (!parse || parse.length < 3) { return event.returnValue = 'Unable to resolve domain name from URL' };
// 2. 获取域名IP
dns.lookup(parse[2], (err, ip) => {
if (err) { return event.returnValue = err.toString() };
// 3. 查询IP对应物理位置
const addr = qqwry.searchIP(ip);
// 插入数据库
this.cursor.insert({
category: opts['category'] || 'default',
url: opts['url'],
pwd: opts['pwd'],
type: opts['type'],
ip: ip,
addr: `${addr.Country} ${addr.Area}`,
encode: opts['encode'],
encoder: opts['encoder'],
ctime: +new Date,
utime: +new Date
}, (err, ret) => {
event.returnValue = err || ret;
});
});
this._url2ip(opts.base['url'])
.then((ret) => {
this.cursor.insert({
category: opts.base['category'] || 'default',
url: opts.base['url'],
pwd: opts.base['pwd'],
type: opts.base['type'],
ip: ret['ip'],
addr: ret['addr'],
encode: opts.base['encode'],
encoder: opts.base['encoder'],
httpConf: opts.http,
otherConf: opts.other,
ctime: +new Date,
utime: +new Date
}, (_err, _ret) => {
event.returnValue = _err || _ret;
});
})
.catch((_err) => {
event.returnValue = _err;
})
}
/**
* 编辑shell数据
* @param {Object} event ipcMain对象
* @param {Object} opts 数据(url,_id,pwd,type,encode,encoder
* @param {Object} opts 数据(old,new
* @return {[type]} [description]
*/
editShell(event, opts) {
logger.warn('editShell', opts);
// 获取目标IP以及地理位置
// 1. 获取域名
let parse = opts['url'].match(/(\w+):\/\/([\w\.\-]+)[:]?([\d]*)([\s\S]*)/i);
if (!parse || parse.length < 3) { return event.returnValue = 'Unable to resolve domain name from URL' };
// 2. 获取域名IP
dns.lookup(parse[2], (err, ip) => {
if (err) { return event.returnValue = err.toString() };
// 3. 查询IP对应物理位置
const addr = qqwry.searchIP(ip);
// 更新数据库
this.cursor.update({
_id: opts['_id']
}, {
$set: {
ip: ip,
addr: `${addr.Country} ${addr.Area}`,
url: opts['url'],
pwd: opts['pwd'],
type: opts['type'],
encode: opts['encode'],
encoder: opts['encoder'],
utime: +new Date
}
}, (err, num) => {
event.returnValue = err || num;
const _new = opts.new;
const _old = opts.old;
this._url2ip(_new.base['url'])
.then((ret) => {
this.cursor.update({
_id: _old['_id']
}, {
$set: {
ip: ret['ip'],
addr: ret['addr'],
url: _new.base['url'],
pwd: _new.base['pwd'],
type: _new.base['type'],
encode: _new.base['encode'],
encoder: _new.base['encoder'],
httpConf: _new.http,
otherConf: _new.other,
utime: +new Date
}
}, (_err, _ret) => {
event.returnValue = _err || _ret;
})
})
});
.catch((_err) => {
event.returnValue = _err;
});
}
/**
......
......@@ -68,33 +68,6 @@ class Menubar {
click: this.app.quit.bind(this.app)
},
]
}, {
// 数据管理
label: LANG['shell']['title'],
submenu: [
{
label: LANG['shell']['add'],
accelerator: 'Shift+A',
click: event.sender.send.bind(event.sender, 'menubar', 'shell-add')
}, {
label: LANG['shell']['search'],
accelerator: 'Shift+S',
enabled: false
}, {
type: 'separator'
}, {
label: LANG['shell']['import'],
enabled: false
}, {
label: LANG['shell']['dump'],
enabled: false
}, {
type: 'separator'
}, {
label: LANG['shell']['clear'],
enabled: false
}
]
}, {
// 编辑
label: LANG['edit']['title'],
......
......@@ -19,14 +19,6 @@ module.exports = {
update: 'Check update',
quit: 'Quit'
},
shell: {
title: 'Data',
add: 'Add data',
search: 'Search data',
dump: 'Dump data',
import: 'Import data',
clear: 'Clear all data'
},
edit: {
title: 'Edit',
undo: 'Undo',
......@@ -154,6 +146,15 @@ module.exports = {
confirm: 'Are you sure to clear all the cache?',
success: 'Clear all cache success!',
error: (err) => antSword.noxss(`Clear all cache failed!\n${err}`)
},
accordion: {
base: 'Base',
http: 'HTTP',
other: 'Other'
},
otherConf: {
nohttps: 'Ignore HTTPS certificate',
notermcache: "Don't use the terminal's cache"
}
}
},
......
......@@ -20,14 +20,6 @@ module.exports = {
update: '检查更新',
quit: '退出程序'
},
shell: {
title: '数据',
add: '添加数据',
search: '搜索数据',
dump: '导出数据',
import: '导入数据',
clear: '清空数据'
},
edit: {
title: '编辑',
undo: '撤销',
......@@ -155,6 +147,15 @@ module.exports = {
confirm: '确定清空所有缓存数据吗?',
success: '清空全部缓存完毕!',
error: (err) => antSword.noxss(`清空全部缓存失败!\n${err}`)
},
accordion: {
base: '基础配置',
http: '请求信息',
other: '其他设置'
},
otherConf: {
nohttps: '忽略HTTPS证书',
notermcache: "虚拟终端不使用缓存"
}
}
},
......
......@@ -2,7 +2,8 @@
* 右键菜单
*/
const DATA = require('../data');
const Data = require('../data');
const Form = require('./form');
const Terminal = require('../../terminal/');
const Database = require('../../database/');
const FileManager = require('../../filemanager/');
......@@ -41,13 +42,13 @@ class ContextMenu {
],
false,
['add', 'plus-circle', false, this.addData.bind(this)],
['edit', 'edit', selectedData, this.editData.bind(this, id)],
['edit', 'edit', selectedData, this.editData.bind(this, data[0])],
['delete', 'remove', selectedMultiData, this.delData.bind(this, ids)],
false,
['move', 'share-square', selectedMultiData, null, this.parseMoveCategoryMenu(ids)],
['search', 'search', true],
false,
['clearCache', 'trash-o', selectedMultiData, this.clearCache.bind(this, ids)],
['clearCache', 'trash-o', selectedData, this.clearCache.bind(this, id)],
['clearAllCache', 'trash', false, this.clearAllCache.bind(this)]
].map((menu) => {
// 分隔符号
......@@ -182,16 +183,57 @@ class ContextMenu {
* 添加数据
*/
addData() {
new Form({
title: LANG['list']['add']['title'],
icon: 'plus-circle',
text: LANG['list']['add']['toolbar']['add']
}, {}, (data) => {
return new Promise((res, rej) => {
// 获取当前分类
data['base']['category'] = antSword.modules.shellmanager.category.sidebar.getActiveItem();
// 通知后台插入数据
const ret = antSword.ipcRenderer.sendSync('shell-add', data);
if (ret instanceof Object) {
// 重新加载数据
antSword.modules.shellmanager.reloadData({
category: data['base']['category']
});
return res(LANG['list']['add']['success']);
} else {
return rej(LANG['list']['add']['error'](ret.toString()));
}
});
})
}
/**
* 编辑数据
* @param {number} id [description]
* @param {Object} info 当前选中的数据
* @return {[type]} [description]
*/
editData(id) {
editData(info) {
new Form({
title: LANG['list']['edit']['title'](info.url),
icon: 'save',
text: LANG['list']['edit']['toolbar']['save']
}, info, (data) => {
return new Promise((res, rej) => {
// 通知后台更新数据
const ret = antSword.ipcRenderer.sendSync('shell-edit', {
old: info,
new: data
});
if (ret === 1) {
// 重新加载数据
antSword.modules.shellmanager.reloadData({
category: info['category']
});
return res(LANG['list']['edit']['success']);
} else {
return rej(LANG['list']['edit']['error'](ret.toString()));
}
})
})
}
/**
......@@ -200,7 +242,23 @@ class ContextMenu {
* @return {[type]} [description]
*/
delData(ids) {
layer.confirm(
LANG['list']['del']['confirm'](ids.length), {
icon: 2, shift: 6,
title: `<i class="fa fa-trash"></i> ${LANG['list']['del']['title']}`
}, (_) => {
layer.close(_);
const ret = antSword['ipcRenderer'].sendSync('shell-del', ids);
if (typeof(ret) === 'number') {
toastr.success(LANG['list']['del']['success'](ret), LANG_T['success']);
// 更新UI
antSword.modules.shellmanager.reloadData({
category: antSword.modules.shellmanager.category.sidebar.getActiveItem()
});
}else{
toastr.error(LANG['list']['del']['error'](ret.toString()), LANG_T['error']);
}
});
}
/**
......@@ -213,11 +271,29 @@ class ContextMenu {
/**
* 清空缓存
* @param {array} ids [description]
* @param {number} id ID
* @return {[type]} [description]
*/
clearCache(ids) {
clearCache(id) {
layer.confirm(
LANG['list']['clearCache']['confirm'], {
icon: 2, shift: 6,
title: `<i class="fa fa-trash"></i> ${LANG['list']['clearCache']['title']}`
}, (_) => {
layer.close(_);
const ret = antSword['ipcRenderer'].sendSync('cache-clear', {
id: id
});
if (ret === true) {
toastr.success(LANG['list']['clearCache']['success'], LANG_T['success']);
}else{
toastr.error(
LANG['list']['clearCache']['error'](
ret['errno'] === -2 ? 'Not cache file.' : ret['errno']
), LANG_T['error']
);
}
});
}
/**
......@@ -225,7 +301,19 @@ class ContextMenu {
* @return {[type]} [description]
*/
clearAllCache() {
layer.confirm(
LANG['list']['clearAllCache']['confirm'], {
icon: 2, shift: 6,
title: `<i class="fa fa-trash"></i> ${LANG['list']['clearAllCache']['title']}`
}, (_) => {
layer.close(_);
const ret = antSword['ipcRenderer'].sendSync('cache-clearAll');
if (ret === true) {
toastr.success(LANG['list']['clearAllCache']['success'], LANG_T['success']);
}else{
toastr.error(LANG['list']['clearAllCache']['error'](ret), LANG_T['error']);
}
});
}
}
......
/**
* 添加/编辑数据表单
*/
const LANG_T = antSword['language']['toastr'];
const LANG = antSword['language']['shellmanager'];
const ENCODES = require('../../../base/encodes');
class Form {
/**
* 初始化函数
* @param {object} opt ui配置
* @param {object} arg = {} 默认数据
* @param {function} callback 点击按钮后回调数据
*/
constructor(opt, arg = {}, callback = false) {
// 创建win窗口
const win = this._createWin(opt);
// 创建toolbar工具栏
this.toolbar = this._createToolbar(win, opt);
// 创建表单分隔accordion
this.accordion = this._createAccordion(win);
// 创建表单
this.baseForm = this._createBaseForm(arg);
this.httpForm = this._createHttpForm(arg);
this.otherForm = this._createOtherForm(arg);
// toolbar点击事件
this.toolbar.attachEvent('onClick', (id) => {
if (id === 'clear') {
return this.baseForm.clear();
}
// 检测表单数据
if (
!this.baseForm.validate() ||
!this.httpForm.validate() ||
!this.otherForm.validate()
) {
return toastr.warning(LANG['list']['add']['warning'], LANG_T['warning']);
};
// 回调数据
if (callback) {
win.progressOn();
callback(this._parseFormData(
this.baseForm.getValues(),
this.httpForm.getValues(),
this.otherForm.getValues()
)).then((msg) => {
// 添加/保存完毕后回调
win.close();
toastr.success(msg, LANG_T['success']);
}).catch((msg) => {
// 添加/保存错误
win.progressOff();
toastr.error(msg, LANG_T['error']);
});
};
});
}
/**
* 创建win窗口
* @param {object} opts = {} 窗口属性(title,width,height)
* @return {object} win
*/
_createWin(opts = {}) {
let _id = String(Math.random()).substr(5, 10);
// 默认配置
let opt = Object.assign({
title: opts['title'] || 'Window:' + _id,
width: 550, height: 450
}, opts);
// 创建窗口
let win = antSword.modules.shellmanager.list.win;
if (!win) {
win = new dhtmlXWindows();
win.attachViewportTo(antSword.modules.shellmanager.list.cell.cell);
antSword.modules.shellmanager.list.win = win;
}
let _win = win.createWindow(_id, 0, 0, opt['width'], opt['height']);
_win.setText(opt['title']);
_win.centerOnScreen();
_win.button('minmax').show();
_win.button('minmax').enable();
return _win;
}
/**
* 创建工具栏
* @param {object} win [description]
* @param {object} opt ui配置
* @return {[type]} [description]
*/
_createToolbar(win, opt) {
const toolbar = win.attachToolbar();
toolbar.loadStruct([{
id: 'act',
type: 'button',
icon: opt['icon'],
text: opt['text']
}, {
type: 'separator'
}, {
id: 'clear',
type: 'button',
icon: 'remove',
text: LANG['list']['add']['toolbar']['clear']
}]);
return toolbar;
}
/**
* 创建Accordion
* @param {[type]} win [description]
* @return {[type]} [description]
*/
_createAccordion(win) {
const accordion = win.attachAccordion({
items: [{
id: 'base',
text: `<i class="fa fa-file-text"></i> ${LANG['list']['accordion']['base']}`
}, {
id: 'http',
text: `<i class="fa fa-edge"></i> ${LANG['list']['accordion']['http']}`
}, {
id: 'other',
text: `<i class="fa fa-cogs"></i> ${LANG['list']['accordion']['other']}`
}]});
return accordion;
}
/**
* 创建基础表单
* @param {object} arg 默认表单数据
* @return {[type]} [description]
*/
_createBaseForm(arg) {
const opt = Object.assign({}, {
url: '',
pwd: '',
type: 'php',
encode: 'utf8',
encoder: 'default'
}, arg);
const form = this.accordion.cells('base').attachForm([
{ type: 'settings', position: 'label-left', labelWidth: 80, inputWidth: 400 },
{ type: 'block', inputWidth: 'auto', offsetTop: 12, list: [
{
type: 'input', label: LANG['list']['add']['form']['url'],
name: 'url', required: true, value: opt.url
}, {
type: 'input', label: LANG['list']['add']['form']['pwd'],
name: 'pwd', required: true, value: opt.pwd
}, {
type: 'combo', label: LANG['list']['add']['form']['encode'],
readonly: true, name: 'encode', options: this._parseEncodes(opt.encode)
}, {
type: 'combo', label: LANG['list']['add']['form']['type'],
name: 'type', readonly: true, options: this._parseTypes(opt.type, opt.encoder)
}
] }
], true);
return form;
}
/**
* 解析编码列表
* @param {String} _default 默认编码器
* @return {array} [description]
*/
_parseEncodes(_default = 'utf8') {
let ret = [];
ENCODES.map((_) => {
ret.push({
text: _, value: _,
selected: _ === _default.toUpperCase()
});
});
return ret;
}
/**
* 解析脚本支持列表
* @param {String} _default 默认类型
* @param {String} _encoder 默认编码器
* @return {array} [description]
*/
_parseTypes(_default = 'php', _encoder = 'default') {
let ret = [];
for (let c in antSword['core']) {
let encoders = antSword['core'][c].prototype.encoders;
ret.push({
text: c.toUpperCase(), value: c,
selected: c === _default,
list: ((c) => {
let _ = [
{ type: 'settings', position: 'label-right', offsetLeft: 60, labelWidth: 100 },
{ type: 'label', label: LANG['list']['add']['form']['encoder'] },
{ type: 'radio', name: `encoder_${c}`, value: 'default', label: 'default', checked: true }
];
encoders.map((e) => {
_.push({
type: 'radio', name: `encoder_${c}`,
value: e, label: e, checked: e === _encoder
})
});
return _;
})(c)
});
}
return ret;
}
/**
* 解析表单数据
* @param {object} base 原始base数据
* @param {object} http 原始http数据
* @param {object} other 原始other数据
* @return {object} {base,http,other}
*/
_parseFormData(base, http, other) {
// 提取需要的base数据
let _baseData = {
url: base['url'],
pwd: base['pwd'],
type: base['type'],
encode: base['encode'],
encoder: base[`encoder_${base['type']}`]
};
// 提取需要的http数据
let [headers, bodys] = [{}, {}];
for (let _ in http) {
if (_.endsWith('value') || !http[_]) {
continue
}
let _tmp = _.split('-');
if (_tmp[0] === 'header') {
headers[ http[_] ] = http[_.replace(/name$/, 'value')];
} else {
bodys[ http[_] ] = http[_.replace(/name$/, 'value')];
}
}
// 返回处理完毕的数据
return {
base: _baseData,
http: {
body: bodys,
headers: headers
},
other: other
};
}
/**
* 创建其他设置表单
* @param {object} arg 默认配置
* @return {[type]} [description]
*/
_createOtherForm(arg) {
const opt = Object.assign({}, {
'ignore-https': 0,
'terminal-cache': 1
}, arg.otherConf);
const form = this.accordion.cells('other').attachForm([{
type: 'settings', position: 'label-right', inputWidth: 400
}, {
type: 'block', inputWidth: 'auto', offsetTop: 12, list: [
{
type: "checkbox", name: 'ignore-https', label: LANG['list']['otherConf']['nohttps'],
checked: opt['ignore-https'] === 1
}, {
type: "checkbox", name: 'terminal-cache', label: LANG['list']['otherConf']['notermcache'],
checked: opt['terminal-cache'] === 1
}
]}], true);
return form;
}
/**
* 创建HTTP请求表单
* @param {object} arg [description]
* @return {[type]} [description]
*/
_createHttpForm(arg) {
const opt = Object.assign({}, {
headers: {},
body: {}
}, arg.httpConf);
const cell = this.accordion.cells('http');
// 创建toolbar,用于添加数据
const toolbar = cell.attachToolbar();
toolbar.loadStruct([{
id: 'add-header',
type: 'button',
icon: 'plus-square-o',
text: 'Header'
}, {
type: 'separator'
}, {
id: 'add-body',
type: 'button',
icon: 'plus-square-o',
text: 'Body'
}]);
// 创建表单
const form = cell.attachForm([{
type: 'block', inputWidth: 'auto', offsetTop: 12, name: 'header', list: [
{type: "label", label: "HTTP HEADERS"}
]
}, {
type: 'block', inputWidth: 'auto', offsetTop: 12, name: 'body', list: [
{type: "label", label: "HTTP BODY"}
]
}], true);
// 添加Header
let _headerCount = 0;
const _addHeader = (name = '', value = '') => {
_headerCount ++;
form.addItem(
'header',
{
type: "fieldset", label: `#${_headerCount}`, inputWidth: 480, list:[
{type: "input", name: `header-${_headerCount}_name`, inputWidth: 350, labelWidth: 50, label: "Name", value: name},
{type: "input", name: `header-${_headerCount}_value`, inputWidth: 350, labelWidth: 50, label: "Value", value: value}
]
}
)
}
// 添加Body
let _bodyCount = 0;
const _addBody = (name = '', value = '') => {
_bodyCount ++;
form.addItem(
'body',
{
type: "fieldset", label: `#${_bodyCount}`, inputWidth: 480, list:[
{type: "input", name: `body-${_bodyCount}_name`, inputWidth: 350, labelWidth: 50, label: "Name", value: name},
{type: "input", name: `body-${_bodyCount}_value`, inputWidth: 350, labelWidth: 50, label: "Value", value: value}
]
}
)
}
// 监听toolbar事件
toolbar.attachEvent('onClick', (id, e) => {
switch (id) {
case 'add-header':
_addHeader();
break;
case 'add-body':
_addBody();
break;
}
});
// 添加存储的配置
for (let _ in opt.headers) {
_addHeader(_, opt.headers[_]);
}
for (let _ in opt.body) {
_addBody(_, opt.body[_]);
}
// 如果没有配置,则添加空白的输入框
if (_headerCount === 0) {
_addHeader();
}
if (_bodyCount === 0) {
_addBody();
}
return form;
}
}
module.exports = Form;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment