From db2a58ca44af1728454d11deade8f2e45423f313 Mon Sep 17 00:00:00 2001 From: coderaiser Date: Fri, 3 Mar 2017 17:06:36 +0200 Subject: [PATCH] feature(terminal) add optional --terminal support --- HELP.md | 64 +++++++++++++++++- bin/cloudcmd.js | 8 ++- client/dom/files.js | 12 ++-- client/key.js | 3 + client/modules/terminal.js | 129 +++++++++++++++++++++++++++++++++++++ css/view.css | 1 + img/screen/terminal.png | Bin 0 -> 12353 bytes json/config.json | 4 +- json/help.json | 5 +- json/modules.json | 1 + man/cloudcmd.1 | 3 + server/cloudcmd.js | 25 +++++-- server/terminal.js | 29 +++++++++ webpack.config.js | 1 + 14 files changed, 266 insertions(+), 19 deletions(-) create mode 100644 client/modules/terminal.js create mode 100644 img/screen/terminal.png create mode 100644 server/terminal.js diff --git a/HELP.md b/HELP.md index f88af817..56c26969 100644 --- a/HELP.md +++ b/HELP.md @@ -80,6 +80,8 @@ Cloud Commander supports command line parameters: | `--one-panel-mode` | set one panel mode `--config-dialog` | enable config dialog `--console` | enable console + `--terminal` | enable terminal + `--terminal-path` | set terminal path | `--no-server` | do not start server | `--no-auth` | disable authorization | `--no-online` | load scripts from local server @@ -90,6 +92,7 @@ Cloud Commander supports command line parameters: | `--no-one-panel-mode` | unset one panel mode | `--no-config-dialog` | disable config dialog | `--no-console` | disable console +| `--no-terminal` | disable terminal If no parameters given Cloud Commander reads information from `~/.cloudcmd.json` and use port from it (`8000` default). if port variables `PORT` or `VCAP_APP_PORT` isn't exist. @@ -203,9 +206,62 @@ Console For more details see [console hot keys](https://github.com/cloudcmd/console#hot-keys "Console Hot Keys"). -### Environment Variables +Terminal +--------------- +![Terminal](/img/screen/terminal.png "Terminal") -Every program executed in `console` has these `environment` variables: +### Install + +`Terminal` disabled and not installed by default. To use it you should install [gritty](https://github.com/cloudcmd/gritty "Gritty") with: + +```sh +npm i gritty -g +``` + +And then set the path of a terminal with: + +```sh +cloudcmd --terminal --terminal-path `gritty --path` --save +``` + +### Windows + +On Windows you need to install `windows-build-tools` before: + +```sh +npm install --global windows-build-tools +``` + +Then get path of a `gritty` with: + +```sh +gritty --path +``` +It will returns something like: + +```sh +C:\Users\coderaiser\AppData\Roaming\npm\node_modules\gritty +``` + +Set this path as `--terminal-path` with: + +```sh +cloudcmd --save --terminal --terminal-path "C:\Users\coderaiser\AppData\Roaming\npm\node_modules\gritty" +``` + +After that you can use `terminal` in the same way as a `console`. + +### Hot keys + +|Key |Operation +|:----------------------|:-------------------------------------------- +| `Shift` + `~` | open +| `Shift` + `Esc` | close + +Environment Variables +--------------- + +Every program executed in `console` or `terminal` has these `environment` variables: - `ACTIVE_DIR` - directory that contains cursor - `PASSIVE_DIR` - directory with no cursor @@ -260,7 +316,9 @@ Here is description of options: "htmlDialogs" : true, /* use html dialogs */ "onePanelMode" : false, /* set one panel mode */ "configDialog" : true, /* enable config dialog */ - "console" : true /* enable console */ + "console" : true, /* enable console */ + "terminal" : false, /* disable terminal */ + "terminalPath" : '', /* path of a terminal */ } ``` diff --git a/bin/cloudcmd.js b/bin/cloudcmd.js index 9094d2f4..cfd2a6aa 100755 --- a/bin/cloudcmd.js +++ b/bin/cloudcmd.js @@ -18,7 +18,8 @@ const args = require('minimist')(argv.slice(2), { 'editor', 'packer', 'root', - 'prefix' + 'prefix', + 'terminal-path', ], boolean: [ 'auth', @@ -31,6 +32,7 @@ const args = require('minimist')(argv.slice(2), { 'progress', 'config-dialog', 'console', + 'terminal', 'one-panel-mode', 'html-dialogs' ], @@ -49,7 +51,9 @@ const args = require('minimist')(argv.slice(2), { prefix : config('prefix') || '', progress : config('progress'), console : config('console'), + terminal : config('terminal'), + 'terminal-path': config('terminalPath'), 'config-dialog': config('configDialog'), 'one-panel-mode': config('onePanelMode'), 'html-dialogs': config('htmlDialogs') @@ -88,6 +92,8 @@ if (args.version) { config('username', args.username); config('progress', args.progress); config('console', args.console); + config('terminal', args.terminal); + config('terminalPath', args.terminalPath); config('editor', args.editor); config('prefix', args.prefix); config('root', args.root); diff --git a/client/dom/files.js b/client/dom/files.js index 8d87d9df..44e9f7e1 100644 --- a/client/dom/files.js +++ b/client/dom/files.js @@ -98,12 +98,12 @@ function showError(name) { throw(error); } -function getSystemFile(url, callback) { +function getSystemFile(file, callback) { const prefix = CloudCmd.PREFIX; - if (!Promises[url]) - Promises[url] = new Promise((success, error) => { - url = prefix + url; + if (!Promises[file]) + Promises[file] = new Promise((success, error) => { + const url = prefix + file; load.ajax({ url, @@ -112,10 +112,10 @@ function getSystemFile(url, callback) { }); }); - Promises[url].then((data) => { + Promises[file].then((data) => { callback(null, data); }, (error) => { - Promises[url] = null; + Promises[file] = null; callback(error); }); } diff --git a/client/key.js b/client/key.js index 64e65d2c..6a0721de 100644 --- a/client/key.js +++ b/client/key.js @@ -347,6 +347,9 @@ function KeyProto() { break; case Key.TRA: + if (shift) + return CloudCmd.Terminal.show(); + CloudCmd.Konsole.show(); event.preventDefault(); break; diff --git a/client/modules/terminal.js b/client/modules/terminal.js new file mode 100644 index 00000000..b3148c01 --- /dev/null +++ b/client/modules/terminal.js @@ -0,0 +1,129 @@ +'use strict'; + +/* global CloudCmd, gritty */ + +const exec = require('execon'); +const load = require('../dom/load'); +const DOM = require('../dom'); +const Images = require('../dom/images'); +const {Dialog} = DOM; + +const TITLE = 'Terminal'; + +CloudCmd.Terminal = TerminalProto; + +const {Key} = CloudCmd; + +let Element; +let Loaded; +let Terminal; + +const {config} = CloudCmd; + +function TerminalProto() { + if (!config('terminal')) + return; + + Images.show.load('top'); + + exec.series([ + CloudCmd.View, + loadAll, + create, + show, + ]); + + Element = load({ + name: 'div', + style: 'height: 99%', + className : 'terminal', + }); + + return module.exports; +} + +module.exports.show = show; + +module.exports.hide = hide; + +function hide () { + CloudCmd.View.hide(); +} + +function getPrefix() { + return CloudCmd.PREFIX + '/gritty'; +} + +function getEnv() { + return { + ACTIVE_DIR: DOM.getCurrentDirPath, + PASSIVE_DIR: DOM.getNotCurrentDirPath, + CURRENT_NAME: DOM.getCurrentName, + CURRENT_PATH: DOM.getCurrentPath + }; +} + +function create(callback) { + const options = { + env: getEnv(), + prefix: getPrefix(), + socketPath: CloudCmd.PREFIX, + }; + + const {socket, terminal} = gritty(Element, options); + + terminal.focus(); + + Terminal = terminal; + + terminal.on('key', (char, {keyCode, shiftKey}) => { + if (shiftKey && keyCode === Key.ESC) { + hide(); + } + }); + + socket.on('connect', exec.with(authCheck, socket)); + + exec(callback); +} + +function authCheck(spawn) { + if (!config('auth')) + return; + + spawn.emit('auth', config('username'), config('password')); + + spawn.on('reject', () => { + Dialog.alert(TITLE, 'Wrong credentials!'); + }); +} + +function show(callback) { + if (!Loaded) + return; + + CloudCmd.View.show(Element, { + afterShow: () => { + if (Terminal) { + Terminal.fit(); // lines corrupt without + Terminal.focus(); + } + + exec(callback); + } + }); +} + +function loadAll(callback) { + const prefix = getPrefix(); + const url = prefix + '/gritty.js'; + + DOM.load.js(url, (error) => { + if (error) + return Dialog.alert(TITLE, error.message); + + Loaded = true; + exec(callback); + }); +} + diff --git a/css/view.css b/css/view.css index 0bd628e9..93cd8c4b 100644 --- a/css/view.css +++ b/css/view.css @@ -1,4 +1,5 @@ .view { + height: 100%; font-size: 16px; white-space: pre; } diff --git a/img/screen/terminal.png b/img/screen/terminal.png new file mode 100644 index 0000000000000000000000000000000000000000..911a9d089cb8f39dcd4206d8314f0a369914de91 GIT binary patch literal 12353 zcmY*<1yodB_x>OV0t!frNRQOeAq|2`3@tUJz|bHyARyh{3?U`mF*FEBNDZwt3IaoS zr}H1*@BO~@``>lfiG7~4@40(F=dOLuxe@BB@{b5;2mk=UBSnQ*ngGB(H~@ex_y8Lt z5spi=0RXVxt1D~0##GU0bZ;&uckFZou(7d4vjL(L067@>OwK6ils$YH<-oxMJQCL4gDMS+|R27{hKqjNB7!5IBBO}QA!%)!B5 z@c|lrKP{s-0i#wKjXt2&wwpQV{HuQEpy{uWw#f%@j~0-V0Js+tdVoFv07Pfd2iPG7 zI6TRM+IFfLxcCM*+@gSknH=mi*L#@4!NGkOQGkAE&ddye)&R)Ej^&c!h#7O|0ggWH z-1Ib7fGglhU=BuYNCrUE^}P+CAB}!yQ9!0$0JKOJwD1QI+W`RVWW)f%zb|r-c5gqLVKyMV^ev2vIK&t`5E`Zvs2V({Rz#Xgj>ZO*){O-?**d!V9zN0$% z`%M|i4+0qo5}RMP^Tr&>E_KTv-kHd{7kMOquC*lN$48qW7A!}V$402EKM@U(F^Js* z01g3H>JOYkl#?2|^oMb4>l^)H^92z&`VMQ1lWWq&`UcCtyg*0?a~p>M@Z)VCspFT$ zW@Y-9Z!2umDSZutPAaF~x-AB)_Lu287;b7MEj@ZJttLD*@74e5gWCO@21=y2!XHp8 z$a3UPfNQ-ed`~Z;_IgF=WK6nms68;|rr}C`Ol}&k*PTa7U=A6AtP4!1npJc}qtx+M zuLkMtw=*28$2thzQA2vyu65$F?%Pvl6?C**<+-{GhBLpsFIi?&IL)`t^uu8xI2Ed( zS``piSYpaP&$~0fzR5oBCci)D>%iK@It%W=F~0Ws4O(=kona-V`I%=r?93MAU{fBV za<}k%e@eit#h_HI&~UOeR=W&EUj33aBrZjj=J8+Tb8b&nNk3LW9=p2|6(bQ}Z;*^3 zU3&`!1AkaGEeH!kH~_%^&Q|+MQG_XJis?#*`!N|>JTm((G+fP>ltCE6Bm+f+?g@me z%-5)4)ov*@jxq3G#n+S*CWty}-D*At_vUL`Rp9)i2$Q_qI?RrG*I%w3b5+G0Cf){H zIIorX_1>wC;lomJ`k7blMpV$9*@3-RW@rGfvR?AE|0;Vp+e7uN% z{3W)NjG;&uTH0((-8kH?s1!WNaYEY)w(Xz)UD>a4Yx(@-5Fv8B$~X32LEq)IM%qqX z6@I(ZVXsUJ8&$Bhu(ZC5O6ypU%&UJ8wTs)M?4pz1uCY?5io)oTBoVVvqM5zB}dbt0(hkIRA- z<521Sjw(TRF+}uhyLKZJd-eR%P7K((>c;Fn&wjNT%#7}TKB$gBCIi9o z_xor4dci`>a1{UbP*9nu7OC0JY-P(OzS8M#Qm>-nfRf;ShZL-gM&@MQ1$yIQ2aB^{>g~V6&r-57W=?v4$&=as8?I;Zm^(`SWyCt)n1 zQLCflog9C@Q92Z-3jMGZkW_&G2EpFgZBqM;RA1hz)CPc(+7DG|S+*bjj`Uy(Ett(n z62Uk(0d%C|{Y5)c=OOO0OFm|Dz{OYLqp z>{*GJG^Nt4?U4Vh-K_@N$<6)J@&03w6D0d7(1xu2@b|q4FF6uZj!-u}NY$M_EvpQ6 zRbcz_fIF*Qr6!Dk7oACGaEDx2*MqDF4QiH?-ek!7L`4pHnyQHjU1oVSNV>d>u+ z6@ri?;1VULI6|vh)p6Tu?ARUqmQgL743{iz(L^Q+wP%H3hH~S3nerCCD@z~7k0saLfnSrxm)LA=_XKZzg2(qG3 zH|V}s0A+7;H#^)w21HmyFxSgV{yB`DD?54dT!7$&uU+C$LdNGb_nyH^js%cm^<~1w ze1rw2!y2lNHcby+pnD6DkdK=uW8A&>JjBAoGZqBqi$GRo>A@52UDQc#U(y=*-^l#?Mj;8Iblo#1$eeq^%&fp~R;~K0fFN zRavu*-@iBX5T=MeM?&A>KwR>3tz&iO%kvR9ZFYaR~9uIsD7Ud-eOMU4W2C;SjR##Zro@yz%>=g&i5b=MYD|bEH zHZ;t9Qg65M4*s{S%(nCitIeIrLN?vxUtauWheg_D+v6VY;r!@?24l6ipdz1+}~Sc+%kJU+NdQ+BRu4ZM~^|dla~GpW-a( z!^`{CCKMDJN4OkJn|_Xo@*n1#`YIY-OU0$3j_^jsI=YhrgQO%)c&~KpV++Mw(~E$J z^t|q?551h1;T(*?B(e|wmnev*H!Xk2epwAq85v=o8D83vuYxIXZkkba^*vJ0s2_1Q znCoq%Fi^`8KLO`Zi5EQDl;>(swU96#8p174orij6Bkb?%;Wvw#(1{#=O)ZY6rM`JK z*Y${U$4%T1*fq-t_KK|!faL+Hg?5nGcyyP? z+R(@|l=sHRbNOo!@Xl57alxgUmOdtm+BXE&)vugfA+N~Woo=nzBvI|peLQPC{JH@5 z6(Ra2=T81oE{Zw5UDLlb_dA`{e8Y;E|1iIN!aO|GA#r8_A?hFfHR+`lnBrgA4z!rN z$8YX)*ZR_gZUFIyq$!qbXDOnGAPjLz`Q6A;qpm|D9_Z~EpKL%kixPBVjXmJ{i3kp6 z2m1x|I0RF;6SXLw{7&T5M*|CWefYRlGs9#W_g$Hw$+I2cEI81@BxP+ zkcxaL@v_fJ$CZN!rW?CaE4IDWwRfBwn#VeJpg`az+SO6@eWf5A9Rw;9rNBdgfUC7q zTF-!<)SgO@N|2(JcsH%8QHWdmH2R-!%{gMhaCJ)IC6X?dJ3}MYn(C8!5#0^xcOvC{ z8(oaxh@VQT0|LMGHc{Iyfj(Kq-xMFCu^{y{kiY`!-U-n>ZZJtP=mkaZAwp{#&pA0< z;?;TQ4i^U3BDROe_vKoPHeymT3S{BunX*97m74AQ|rl_1D(!J2t7VL^xki0&Jj7E>s{1?!7-iOSJd4V{r zIUcnIv-{YOb(#%Bn>)=+$dVx@a5gXw<7tL?|F)!wgz?_*jes<489 zotRT$4z)p-e$xiU_Jz)8sd&cX3^|-(apDoiOY}ryB{I4d!a!SAy-V_FpGAcCRm=CQ zYm_VNn+CRW2C%(+LrW;L0@Y7{GyJa>ko{H$n<=S=gCwvu6~Wg*a*lYTVb)1`jf^X` zDVSW3R^F!qa)<|POyZ#xL_`Cv#lg{Qol?9M&7uz$hDH#8exk0Gp$r3Nh1mYHr_Q?eQ!E@~FBo6%0 zFD*f!URl`Gd+yy}Ei$j|L`3@Nj1gIB6kWgXM7+mA>HkZU;H!S-;Hun$205-C2`O%D zX@z^|%@4Vh&Dc;S=eY?4sXpBl{e@_7j>|~5x*0`8CnO4UH*O}bF{yo513j3ts22d}qC zDHnLoI5+dDNy>8`c5r-ipoa#$WSR+VU(Em(B{Xov5ODt&sgDT|GTNh&@@tX`qI6S+ z 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 literal 0 HcmV?d00001 diff --git a/json/config.json b/json/config.json index f18437c7..38638b36 100644 --- a/json/config.json +++ b/json/config.json @@ -24,6 +24,8 @@ "configDialog": true, "onePanelMode": false, "configDialog": true, - "console": true + "console": true, + "terminal": false, + "terminalPath": "" } diff --git a/json/help.json b/json/help.json index bdce15b2..cbb6e27b 100644 --- a/json/help.json +++ b/json/help.json @@ -18,6 +18,8 @@ "--one-panel-mode ": "set one panel mode", "--config-dialog ": "enable config dialog", "--console ": "enable console", + "--terminal ": "enable terminal", + "--terminal-path ": "set terminal path", "--open ": "open web browser when server started", "--no-server ": "do not start server", "--no-auth ": "disable authorization", @@ -28,5 +30,6 @@ "--no-html-dialogs ": "do not use html dialogs", "--no-one-panel-mode ": "unset one panel mode", "--no-config-dialog ": "disable config dialog", - "--no-console ": "disable console" + "--no-console ": "disable console", + "--no-terminal ": "disable terminal" } diff --git a/json/modules.json b/json/modules.json index 85c5b2d8..31ed77a8 100644 --- a/json/modules.json +++ b/json/modules.json @@ -11,6 +11,7 @@ "upload", "operation", "konsole", + "terminal", "cloud", [{ "name": "remote", "data": [{ diff --git a/man/cloudcmd.1 b/man/cloudcmd.1 index 45efcf3b..0cceb1d9 100644 --- a/man/cloudcmd.1 +++ b/man/cloudcmd.1 @@ -42,6 +42,8 @@ programs in browser from any computer, mobile or tablet device. --one-panel-mode set one panel mode --config-dialog enable config dialog --console enable console + --terminal enable terminal + --terminal-path set terminal path --no-auth disable authorization --no-server do not start server --no-online load scripts from local server @@ -52,6 +54,7 @@ programs in browser from any computer, mobile or tablet device. --no-one-panel-mode unset one panel mode --no-config-dialog disable config dialog --no-console disable console + --no-terminal disable terminal .SH RESOURCES AND DOCUMENTATION diff --git a/server/cloudcmd.js b/server/cloudcmd.js index 36ec7fda..1515cc49 100644 --- a/server/cloudcmd.js +++ b/server/cloudcmd.js @@ -13,6 +13,7 @@ const route = require(DIR + 'route'); const validate = require(DIR + 'validate'); const prefixer = require(DIR + 'prefixer'); const pluginer = require(DIR + 'plugins'); +const terminal = require(DIR + 'terminal'); const apart = require('apart'); const join = require('join-io'); @@ -32,8 +33,9 @@ const omnes = require('omnes/legacy'); const criton = require('criton'); const root = () => config('root'); -const emptyFunc = (req, res, next) => next(); -emptyFunc.middle = () => emptyFunc; + +const notEmpty = (a) => a; +const clean = (a) => a.filter(notEmpty); const isDev = process.env.NODE_ENV === 'development'; @@ -44,7 +46,7 @@ function getPrefix(prefix) { return prefix || ''; } -module.exports = function(params) { +module.exports = (params) => { const p = params || {}; const options = p.config || {}; const plugins = p.plugins; @@ -54,7 +56,7 @@ module.exports = function(params) { checkPlugins(plugins); - keys.forEach(function(name) { + keys.forEach((name) => { let value = options[name]; switch(name) { @@ -176,6 +178,11 @@ function listen(prefix, socket) { authCheck, prefix: prefix + '/console', }); + + config('terminal') && terminal.listen(socket, { + authCheck, + prefix: prefix + '/gritty', + }); } function cloudcmd(prefix, plugins) { @@ -191,13 +198,17 @@ function cloudcmd(prefix, plugins) { const ponseStatic = ponse.static(DIR_ROOT, {cache}); - const funcs = [ - konsole({ + const funcs = clean([ + config('console') && konsole({ prefix: prefix + '/console', minify, online, }), + config('terminal') && terminal({ + prefix: prefix + '/gritty', + }), + edward({ prefix : prefix + '/edward', minify, @@ -277,7 +288,7 @@ function cloudcmd(prefix, plugins) { pluginer(plugins), ponseStatic - ]; + ]); return funcs; } diff --git a/server/terminal.js b/server/terminal.js new file mode 100644 index 00000000..bbd6e740 --- /dev/null +++ b/server/terminal.js @@ -0,0 +1,29 @@ +'use strict'; + +const tryCatch = require('try-catch'); +const config = require('./config'); + +const noop = () => {}; +noop.listen = noop; + +module.exports = getTerminal(config('terminal')); + +function getTerminal(term) { + if (!term) + return noop; + + let result; + + const e = tryCatch(() => { + result = require(config('terminalPath')); + }); + + if (!e) + return result; + + config('terminal', false); + console.log(`cloudcmd --terminal: ${e.message}`); + + return noop; +} + diff --git a/webpack.config.js b/webpack.config.js index 13497bae..bcfcf106 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -52,6 +52,7 @@ module.exports = { [modules + '/upload']: `${dirModules}/upload.js`, [modules + '/operation']: `${dirModules}/operation.js`, [modules + '/konsole']: `${dirModules}/konsole.js`, + [modules + '/terminal']: `${dirModules}/terminal.js`, [modules + '/cloud']: `${dirModules}/cloud.js`, [modules + '/polyfill']: `${dirModules}/polyfill.js`, },