mirror of
https://github.com/coderaiser/cloudcmd.git
synced 2026-01-23 02:35:49 +00:00
feature(distribute) add ability to import config from remote export server
This commit is contained in:
parent
b8e4f9659a
commit
2ad2fc4023
20 changed files with 856 additions and 22 deletions
|
|
@ -40,6 +40,7 @@
|
|||
"io",
|
||||
"js",
|
||||
"maintainers",
|
||||
"microservice",
|
||||
"minification",
|
||||
"mouseup",
|
||||
"named",
|
||||
|
|
|
|||
92
HELP.md
92
HELP.md
|
|
@ -94,23 +94,34 @@ Cloud Commander supports command line parameters:
|
|||
| `--terminal-path` | set terminal path
|
||||
| `--vim` | enable vim hot keys
|
||||
| `--columns` | set visible columns
|
||||
| `--export` | enable export of config through a server
|
||||
| `--export-token` | authorization token used by export server
|
||||
| `--import` | enable import of config
|
||||
| `--import-token` | authorization token used to connect to export server
|
||||
| `--import-url` | url of an import server
|
||||
| `--import-listen` | enable listen on config updates from import server
|
||||
| `--log` | enable logging
|
||||
| `--no-server` | do not start server
|
||||
| `--no-auth` | disable authorization
|
||||
| `--no-online` | load scripts from local server
|
||||
| `--no-open` | do not open web browser when server started
|
||||
| `--no-name` | set empty tab name in web browser
|
||||
| `--no-one-file-panel` | show two file panels
|
||||
| `--no-name` | set default tab name in web browser
|
||||
| `--no-keys-panel` | hide keys panel
|
||||
| `--no-one-file-panel` | show two file panels
|
||||
| `--no-progress` | do not show progress of file operations
|
||||
| `--no-confirm-copy` | do not confirm copy
|
||||
| `--no-confirm-move` | do not confirm move
|
||||
| `--no-contact` | disable contact
|
||||
| `--no-config-dialog` | disable config dialog
|
||||
| `--no-console` | disable console
|
||||
| `--no-sync-console-path` | do not sync console path
|
||||
| `--no-contact` | disable contact
|
||||
| `--no-terminal` | disable terminal
|
||||
| `--no-vim` | disable vim hot keys
|
||||
| `--no-columns` | set visible default columns
|
||||
| `--no-columns` | set default visible columns
|
||||
| `--no-export` | disable export config through a server
|
||||
| `--no-import` | disable import of config
|
||||
| `--no-import-listen` | disable listen on config updates from import server
|
||||
| `--no-log` | disable logging
|
||||
|
||||
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.
|
||||
|
|
@ -382,6 +393,13 @@ Here is description of options:
|
|||
"terminalPath" : '', /* path of a terminal */
|
||||
"vim" : false, /* disable vim hot keys */
|
||||
"columns" : "name-size-date-owner-mode", /* set visible columns */
|
||||
"export" : false, /* enable export of config through a server */
|
||||
"exportToken" : "root", /* token used by export server */
|
||||
"import" : false, /* enable import of config */
|
||||
"import-url" : "http://localhost:8000", /* url of an export server */
|
||||
"importToken" : "root", /* token used to connect to export server */
|
||||
"importListen" : false, /* listen on config updates from import server */
|
||||
"log" : true /* logging */
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -407,6 +425,72 @@ Some config options can be overridden with `environment variables` such:
|
|||
- `CLOUDCMD_VIM` - enable vim hot keys
|
||||
- `CLOUDCMD_CONFIRM_COPY` - confirm copy
|
||||
- `CLOUDCMD_CONFIRM_MOVE` - confirm move
|
||||
- `CLOUDCMD_EXPORT` - enable export of config through a server
|
||||
- `CLOUDCMD_EXPORT_TOKEN` - authorization token used by export server
|
||||
- `CLOUDCMD_IMPORT` - enable import of config
|
||||
- `CLOUDCMD_IMPORT_TOKEN` - authorization token used to connect to export server
|
||||
- `CLOUDCMD_IMPORT_URL` - url of an import server
|
||||
- `CLOUDCMD_IMPORT_LISTEN`- enable listen on config updates from import server
|
||||
|
||||
### Distribute
|
||||
|
||||
Being able to configure `Cloud Commander` remotely opens the doors to using it as microservice and that's what distribute options set out to do.
|
||||
There is an `export server` and `import client` and they enabled with `--export` and `--import` accordingly. There is a `token` it should be the same
|
||||
in `--import-token` and `export-token`. To use report you should provide `--import-url` to `import client` so it can connect to an `export server`.
|
||||
There is 2 ways `import client` can receive config from an `export server`:
|
||||
|
||||
- full config at startup (default)
|
||||
- get every updated option (with help of `--import-listen` flag)
|
||||
|
||||
There is an example of using distribute options in `Cloud Commander` to get config from remote instance.
|
||||
Here is an `export server`:
|
||||
|
||||
```
|
||||
coderaiser@cloudcmd:~$ cloudcmd --port 1234 --export --export-token=cloudcmd
|
||||
```
|
||||
|
||||
And `import client`:
|
||||
```
|
||||
coderaiser@cloudcmd:~$ cloudcmd --name importer --port 4321 --import-url http://127.0.0.1:1234 --import-token=cloudcmd --no-server --save
|
||||
```
|
||||
|
||||
Here is the log of `export server`:
|
||||
|
||||
```
|
||||
url: http://localhost:1234/
|
||||
2018.08.23 13:41:45 -> export: try to auth from importer [127.0.0.1:4321]
|
||||
2018.08.23 13:41:45 -> export: connected to importer [127.0.0.1:4321]
|
||||
2018.08.23 13:41:45 -> export: disconnected importer [127.0.0.1:4321]
|
||||
```
|
||||
|
||||
And log of `import client`:
|
||||
|
||||
```
|
||||
2018.08.23 13:47:36 -> import: try to auth to http://127.0.0.1:1234
|
||||
2018.08.23 13:47:36 -> import: connected to http://127.0.0.1:1234
|
||||
2018.08.23 13:47:36 -> import: config received from http://localhost:1234
|
||||
2018.08.23 13:47:36 -> import: disconnected from http://127.0.0.1:1234
|
||||
```
|
||||
|
||||
When `import client` uses `--import-listen` persistent connection used and client receives live updates from the `import server`.
|
||||
|
||||
`Export server` omit next configuration fields:
|
||||
|
||||
- `auth`
|
||||
- `username`
|
||||
- `password`
|
||||
- `algo`
|
||||
- `name`
|
||||
- `ip`
|
||||
- `port`
|
||||
- `root`
|
||||
- `import`
|
||||
- `importUrl`
|
||||
- `importToken`
|
||||
- `export`
|
||||
- `exportToken`
|
||||
- `log`
|
||||
- `configDialog`
|
||||
|
||||
Menu
|
||||
---------------
|
||||
|
|
|
|||
31
app.json
31
app.json
|
|
@ -111,6 +111,35 @@
|
|||
"description": "confirm move",
|
||||
"value": "true",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_EXPORT": {
|
||||
"description": "enable export of config through a server",
|
||||
"value": "false",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_EXPORT_TOKEN": {
|
||||
"description": "authorization token used by export server",
|
||||
"value": "root",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_IMPORT": {
|
||||
"description": "enable import of config",
|
||||
"value": "false",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_IMPORT_TOKEN": {
|
||||
"description": "authorization token used to connect to export server",
|
||||
"value": "root",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_IMPORT_URL": {
|
||||
"description": "url of an import server",
|
||||
"value": "http://localhost:8000",
|
||||
"required": false
|
||||
},
|
||||
"CLOUDCMD_IMPORT_LISTEN": {
|
||||
"description": "enable listen on config updates from import server",
|
||||
"value": "false",
|
||||
"required": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,15 @@
|
|||
const Info = require('../package');
|
||||
const DIR_SERVER = '../server/';
|
||||
|
||||
const promisify = require('es6-promisify').promisify;
|
||||
const wraptile = require('wraptile/legacy');
|
||||
|
||||
const exit = require(DIR_SERVER + 'exit');
|
||||
const config = require(DIR_SERVER + 'config');
|
||||
const env = require(DIR_SERVER + 'env');
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
const choose = (a, b) => {
|
||||
if (a === undefined)
|
||||
return b;
|
||||
|
|
@ -30,6 +35,9 @@ const args = require('minimist')(argv.slice(2), {
|
|||
'prefix',
|
||||
'terminal-path',
|
||||
'columns',
|
||||
'import-url',
|
||||
'import-token',
|
||||
'export-token',
|
||||
],
|
||||
boolean: [
|
||||
'auth',
|
||||
|
|
@ -50,6 +58,11 @@ const args = require('minimist')(argv.slice(2), {
|
|||
'show-config',
|
||||
'vim',
|
||||
'keys-panel',
|
||||
'color',
|
||||
'export',
|
||||
'import',
|
||||
'import-listen',
|
||||
'log',
|
||||
],
|
||||
default: {
|
||||
server : true,
|
||||
|
|
@ -68,6 +81,14 @@ const args = require('minimist')(argv.slice(2), {
|
|||
console : choose(env.bool('console'), config('console')),
|
||||
contact : choose(env.bool('contact'), config('contact')),
|
||||
terminal : choose(env.bool('terminal'), config('terminal')),
|
||||
columns : env('columns') || config('columns') || '',
|
||||
vim : choose(env.bool('vim'), config('vim')),
|
||||
log : config('log'),
|
||||
|
||||
'import-url': env('import_url') || config('importUrl'),
|
||||
'import-listen': choose(env.bool('import_listen'), config('importListen')),
|
||||
import : choose(env.bool('import'), config('import')),
|
||||
export : choose(env.bool('export'), config('export')),
|
||||
|
||||
'sync-console-path': choose(env.bool('sync_console_path'), config('syncConsolePath')),
|
||||
'config-dialog': choose(env.bool('config_dialog'), config('configDialog')),
|
||||
|
|
@ -75,9 +96,9 @@ const args = require('minimist')(argv.slice(2), {
|
|||
'one-file-panel': choose(env.bool('one_file_panel'), config('oneFilePanel')),
|
||||
'confirm-copy': choose(env.bool('confirm_copy'), config('confirmCopy')),
|
||||
'confirm-move': choose(env.bool('confirm_move'), config('confirmMove')),
|
||||
'vim': choose(env.bool('vim'), config('vim')),
|
||||
'columns': env('columns') || config('columns') || '',
|
||||
'keys-panel': env.bool('keys_panel') || config('keysPanel'),
|
||||
'import-token': env('import_token') || config('importToken'),
|
||||
'export-token': env('export_token') || config('exportToken'),
|
||||
},
|
||||
alias: {
|
||||
v: 'version',
|
||||
|
|
@ -106,7 +127,6 @@ function main() {
|
|||
repl();
|
||||
|
||||
checkUpdate();
|
||||
|
||||
port(args.port);
|
||||
|
||||
config('name', args.name);
|
||||
|
|
@ -125,11 +145,18 @@ function main() {
|
|||
config('root', args.root);
|
||||
config('vim', args.vim);
|
||||
config('columns', args.columns);
|
||||
config('log', args.log);
|
||||
config('confirmCopy', args['confirm-copy']);
|
||||
config('confirmMove', args['confirm-move']);
|
||||
config('oneFilePanel', args['one-file-panel']);
|
||||
config('configDialog', args['config-dialog']);
|
||||
config('keysPanel', args['keys-panel']);
|
||||
config('export', args.export);
|
||||
config('exportToken', args['export-token']);
|
||||
config('import', args.import);
|
||||
config('importToken', args['import-token']);
|
||||
config('importListen', args['import-listen']);
|
||||
config('importUrl', args['import-url']);
|
||||
|
||||
readConfig(args.config);
|
||||
|
||||
|
|
@ -151,12 +178,14 @@ function main() {
|
|||
if (args['show-config'])
|
||||
showConfig();
|
||||
|
||||
if (!args.save)
|
||||
return start(options);
|
||||
const startWraped = wraptile(start, options);
|
||||
const distribute = require('../server/distribute');
|
||||
const importConfig = promisify(distribute.import);
|
||||
const caller = (fn) => fn();
|
||||
|
||||
config.save(() => {
|
||||
start(options);
|
||||
});
|
||||
importConfig()
|
||||
.then(args.save ? caller(config.save) : noop)
|
||||
.then(startWraped(options));
|
||||
}
|
||||
|
||||
function validateRoot(root) {
|
||||
|
|
@ -166,7 +195,6 @@ function validateRoot(root) {
|
|||
|
||||
function getPassword(password) {
|
||||
const criton = require('criton');
|
||||
|
||||
return criton(password, config('algo'));
|
||||
}
|
||||
|
||||
|
|
@ -241,7 +269,6 @@ function repl() {
|
|||
|
||||
function checkUpdate() {
|
||||
const load = require('package-json');
|
||||
const noop = () => {};
|
||||
|
||||
load(Info.name, 'latest')
|
||||
.then(showUpdateInfo)
|
||||
|
|
|
|||
33
common/datetime.js
Normal file
33
common/datetime.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
'use strict';
|
||||
|
||||
const shortdate = require('shortdate');
|
||||
|
||||
module.exports = (date) => {
|
||||
date = date || new Date();
|
||||
check(date);
|
||||
|
||||
const timeStr = shorttime(date);
|
||||
const dateStr = shortdate(date);
|
||||
|
||||
return `${dateStr} ${timeStr}`;
|
||||
};
|
||||
|
||||
const addZero = (a) => {
|
||||
if (a > 9)
|
||||
return a;
|
||||
|
||||
return `0${a}`;
|
||||
};
|
||||
|
||||
function shorttime(date) {
|
||||
const seconds = addZero(date.getSeconds());
|
||||
const minutes = addZero(date.getMinutes());
|
||||
const hours = addZero(date.getHours());
|
||||
|
||||
return `${hours}:${minutes}:${seconds}`;
|
||||
}
|
||||
|
||||
function check(date) {
|
||||
if (!(date instanceof Date))
|
||||
throw Error('date should be instanceof Date!');
|
||||
}
|
||||
54
common/datetime.spec.js
Normal file
54
common/datetime.spec.js
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
'use strict';
|
||||
|
||||
const test = require('tape');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const datetime = require('./datetime');
|
||||
|
||||
test('common: datetime', (t) => {
|
||||
const dateStr = 'Fri, 17 Aug 2018 10:56:48';
|
||||
const result = datetime(new Date(dateStr));
|
||||
|
||||
const expected = '2018.08.17 10:56:48';
|
||||
|
||||
t.equals(result, expected, 'should equal');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('common: datetime: no arg', (t) => {
|
||||
const {Date} = global;
|
||||
|
||||
let called = false;
|
||||
const myDate = class extends Date {
|
||||
constructor() {
|
||||
super();
|
||||
called = true;
|
||||
}
|
||||
};
|
||||
|
||||
global.Date = myDate;
|
||||
|
||||
datetime();
|
||||
|
||||
global.Date = Date;
|
||||
t.ok(called, 'should call new Date');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('common: 0 before number', (t) => {
|
||||
const dateStr = 'Fri, 17 Aug 2018 10:56:08';
|
||||
const result = datetime(new Date(dateStr));
|
||||
|
||||
const expected = '2018.08.17 10:56:08';
|
||||
|
||||
t.equals(result, expected, 'should equal');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('common: datetime: wrong args', (t) => {
|
||||
const fn = () => datetime({});
|
||||
|
||||
t.throws(fn, /date should be instanceof Date!/, 'should throw');
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
|
@ -29,6 +29,13 @@
|
|||
"terminalPath": "",
|
||||
"showConfig": false,
|
||||
"vim": false,
|
||||
"columns": "name-size-date-owner-mode"
|
||||
"columns": "name-size-date-owner-mode",
|
||||
"export": false,
|
||||
"exportToken": "root",
|
||||
"import": false,
|
||||
"importToken": "root",
|
||||
"importUrl": "http://localhost:8000",
|
||||
"importListen": false,
|
||||
"log": true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,12 +28,19 @@
|
|||
"--terminal-path ": "set terminal path",
|
||||
"--vim ": "enable vim hot keys",
|
||||
"--columns ": "set visible columns",
|
||||
"--export ": "enable export of config through a server",
|
||||
"--export-token ": "authorization token used by export server",
|
||||
"--import ": "enable import of config",
|
||||
"--import-url ": "url of an export server",
|
||||
"--import-token ": "authorization token used to connect to export server",
|
||||
"--import-listen ": "enable listen on config updates from import server",
|
||||
"--log ": "enable logging",
|
||||
"--no-server ": "do not start server",
|
||||
"--no-auth ": "disable authorization",
|
||||
"--no-online ": "load scripts from local server",
|
||||
"--no-open ": "do not open web browser when server started",
|
||||
"--no-name ": "set default tab name in web browser",
|
||||
"--no-one-panel ": "show two file panels",
|
||||
"--no-one-file-panel ": "show two file panels",
|
||||
"--no-keys-panel ": "hide keys panel",
|
||||
"--no-one-file-panel ": "show two file panels",
|
||||
"--no-progress ": "do not show progress of file operations",
|
||||
|
|
@ -45,5 +52,9 @@
|
|||
"--no-contact ": "disable contact",
|
||||
"--no-terminal ": "disable terminal",
|
||||
"--no-vim ": "disable vim hot keys",
|
||||
"--no-columns ": "set default visible columns"
|
||||
"--no-columns ": "set default visible columns",
|
||||
"--no-export ": "disable export config through a server",
|
||||
"--no-import ": "disable import of config",
|
||||
"--no-import-listen ": "disable listen on config updates from import server",
|
||||
"--no-log ": "disable logging"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,13 @@ programs in browser from any computer, mobile or tablet device.
|
|||
--terminal-path set terminal path
|
||||
--vim enable vim hot keys
|
||||
--columns set visible columns
|
||||
--export enable export of config through a server
|
||||
--export-token authorization token used by export server
|
||||
--import enable import of config
|
||||
--import-url url of an import server
|
||||
--import-token authorization token used to connect to export server
|
||||
--import-listen enable listen on config updates from import server
|
||||
--log enable logging
|
||||
--no-auth disable authorization
|
||||
--no-server do not start server
|
||||
--no-online load scripts from local server
|
||||
|
|
@ -68,6 +75,11 @@ programs in browser from any computer, mobile or tablet device.
|
|||
--no-terminal disable terminal
|
||||
--no-vim disable vim hot keys
|
||||
--no-columns set visible default columns
|
||||
--no-export disable export of config through a server
|
||||
--no-import disable import of config
|
||||
--no-import-url url of an import server
|
||||
--no-import-listen disable listen on config updates from import server
|
||||
--no-log disable logging
|
||||
|
||||
.SH RESOURCES AND DOCUMENTATION
|
||||
|
||||
|
|
|
|||
6
now.json
6
now.json
|
|
@ -4,7 +4,11 @@
|
|||
"env": {
|
||||
"cloudcmd_config_dialog": "false",
|
||||
"cloudcmd_terminal": "true",
|
||||
"cloudcmd_terminal_path": "gritty"
|
||||
"cloudcmd_terminal_path": "gritty",
|
||||
"cloudcmd_import": true,
|
||||
"cloudcmd_import_listen": true,
|
||||
"cloudcmd_import_token": "hello-world",
|
||||
"cloudcmd_import_url": "http://config.cloudcmd.io"
|
||||
},
|
||||
"engines": {
|
||||
"node": "8"
|
||||
|
|
|
|||
|
|
@ -142,6 +142,7 @@
|
|||
"mellow": "^2.0.0",
|
||||
"minimist": "^1.2.0",
|
||||
"nomine": "^2.0.0",
|
||||
"object.omit": "^3.0.0",
|
||||
"onezip": "^2.0.0",
|
||||
"opn": "^5.1.0",
|
||||
"package-json": "^4.0.1",
|
||||
|
|
@ -149,7 +150,9 @@
|
|||
"pullout": "^2.0.0",
|
||||
"rendy": "^2.0.0",
|
||||
"restafary": "^3.0.0",
|
||||
"shortdate": "^1.2.0",
|
||||
"socket.io": "^2.0.3",
|
||||
"socket.io-client": "^2.1.1",
|
||||
"squad": "^2.0.0",
|
||||
"table": "^4.0.1",
|
||||
"try-catch": "^2.0.0",
|
||||
|
|
@ -202,11 +205,9 @@
|
|||
"rimraf": "^2.5.4",
|
||||
"scroll-into-view-if-needed": "^2.2.5",
|
||||
"serviceworker-webpack-plugin": "^1.0.1",
|
||||
"shortdate": "^1.0.1",
|
||||
"sinon": "^6.0.4",
|
||||
"sinon-called-with-diff": "^2.0.0",
|
||||
"smalltalk": "^3.1.0",
|
||||
"socket.io-client": "^2.0.1",
|
||||
"style-loader": "^0.22.0",
|
||||
"stylelint": "^9.0.0",
|
||||
"stylelint-config-standard": "^18.0.0",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ const validate = require(DIR + 'validate');
|
|||
const prefixer = require(DIR + 'prefixer');
|
||||
const pluginer = require(DIR + 'plugins');
|
||||
const terminal = require(DIR + 'terminal');
|
||||
const distribute = require(DIR + 'distribute');
|
||||
|
||||
const currify = require('currify/legacy');
|
||||
const apart = require('apart');
|
||||
|
|
@ -147,6 +148,8 @@ function listen(prefix, socket) {
|
|||
auth,
|
||||
prefix: prefix + '/gritty',
|
||||
});
|
||||
|
||||
distribute.export(socket);
|
||||
}
|
||||
|
||||
function cloudcmd(prefix, plugins, modules) {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ const DIR = DIR_SERVER + '../';
|
|||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const Emitter = require('events');
|
||||
|
||||
const exit = require(DIR_SERVER + 'exit');
|
||||
const CloudFunc = require(DIR_COMMON + 'cloudfunc');
|
||||
|
|
@ -35,6 +36,7 @@ const send = swap(ponse.send);
|
|||
const formatMsg = currify((a, b) => CloudFunc.formatMsg(a, b));
|
||||
|
||||
const apiURL = CloudFunc.apiURL;
|
||||
const changeEmitter = new Emitter();
|
||||
|
||||
const ConfigPath = path.join(DIR, 'json/config.json');
|
||||
const ConfigHome = path.join(HOME, '.cloudcmd.json');
|
||||
|
|
@ -59,8 +61,16 @@ const config = Object.assign({}, rootConfig, configHome);
|
|||
const connectionWraped = wraptile(connection);
|
||||
|
||||
module.exports = manage;
|
||||
module.exports.save = _save;
|
||||
module.exports.save = save;
|
||||
module.exports.middle = middle;
|
||||
module.exports.subscribe = (fn) => {
|
||||
changeEmitter.on('change', fn);
|
||||
};
|
||||
|
||||
module.exports.unsubscribe = (fn) => {
|
||||
changeEmitter.removeListener('change', fn);
|
||||
};
|
||||
|
||||
module.exports.listen = (socket, auth) => {
|
||||
check(socket, auth);
|
||||
|
||||
|
|
@ -83,6 +93,10 @@ function manage(key, value) {
|
|||
return config[key];
|
||||
|
||||
config[key] = value;
|
||||
|
||||
changeEmitter.emit('change', key, value);
|
||||
|
||||
return `${key} = ${value}`;
|
||||
}
|
||||
|
||||
function _save(callback) {
|
||||
|
|
|
|||
114
server/distribute/export.js
Normal file
114
server/distribute/export.js
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
'use strict';
|
||||
|
||||
const currify = require('currify/legacy');
|
||||
const wraptile = require('wraptile/legacy');
|
||||
const squad = require('squad/legacy');
|
||||
const omit = require('object.omit');
|
||||
|
||||
const config = require('../config');
|
||||
const log = require('./log');
|
||||
|
||||
const exportStr = log.exportStr;
|
||||
const connectedStr = log.connectedStr;
|
||||
const disconnectedStr = log.disconnectedStr;
|
||||
const authTryStr = log.authTryStr;
|
||||
|
||||
const makeColor = log.makeColor;
|
||||
const getMessage = log.getMessage;
|
||||
const getDescription = log.getDescription;
|
||||
const logWraped = log.logWraped;
|
||||
|
||||
const omitList = [
|
||||
'auth',
|
||||
'username',
|
||||
'password',
|
||||
'algo',
|
||||
'name',
|
||||
'ip',
|
||||
'port',
|
||||
'root',
|
||||
'import',
|
||||
'importUrl',
|
||||
'importToken',
|
||||
'export',
|
||||
'exportToken',
|
||||
'log',
|
||||
'configDialog',
|
||||
];
|
||||
|
||||
const omitConfig = wraptile((config) => omit(config, omitList));
|
||||
|
||||
module.exports = (socket) => {
|
||||
if (!config('export'))
|
||||
return;
|
||||
|
||||
const prefix = config('prefix');
|
||||
const distributePrefix = `${prefix}/distribute`;
|
||||
|
||||
const onError = squad(logWraped(exportStr), getMessage);
|
||||
const onConnectError = squad(logWraped(exportStr), getDescription);
|
||||
|
||||
socket.of(distributePrefix)
|
||||
.on('connection', onConnection(push))
|
||||
.on('error', onError)
|
||||
.on('connect_error', onConnectError);
|
||||
};
|
||||
|
||||
const push = currify((socket, key, value) => {
|
||||
if (omitList.includes(key))
|
||||
return;
|
||||
|
||||
socket.emit('change', key, value);
|
||||
});
|
||||
|
||||
function getHost(socket) {
|
||||
const remoteAddress = socket.request.connection.remoteAddress;
|
||||
const name = socket.handshake.query.name;
|
||||
const port = socket.handshake.query.port;
|
||||
const color = socket.handshake.query.color;
|
||||
|
||||
if (!name)
|
||||
return `${remoteAddress}:${port}`;
|
||||
|
||||
const colorName = makeColor(name, color);
|
||||
|
||||
return `${colorName} [${remoteAddress}:${port}]`;
|
||||
}
|
||||
|
||||
const connectPush = wraptile((push, socket) => {
|
||||
socket.emit('accept');
|
||||
|
||||
const host = getHost(socket);
|
||||
const subscription = push(socket);
|
||||
|
||||
socket.on('disconnect', onDisconnect(subscription, host));
|
||||
|
||||
log(exportStr, `${connectedStr} to ${host}`);
|
||||
socket.emit('config', omitConfig(config('*')));
|
||||
|
||||
config.subscribe(subscription);
|
||||
});
|
||||
|
||||
const onConnection = currify((push, socket) => {
|
||||
const host = getHost(socket);
|
||||
const reject = () => {
|
||||
socket.emit('reject');
|
||||
socket.disconnect();
|
||||
};
|
||||
|
||||
log(exportStr, `${authTryStr} from ${host}`);
|
||||
socket.on('auth', auth(connectPush(push, socket), reject));
|
||||
});
|
||||
|
||||
const auth = currify((fn, reject, token) => {
|
||||
if (token === config('exportToken'))
|
||||
return fn();
|
||||
|
||||
reject();
|
||||
});
|
||||
|
||||
const onDisconnect = wraptile((subscription, host) => {
|
||||
config.unsubscribe(subscription);
|
||||
log(exportStr, `${disconnectedStr} from ${host}`);
|
||||
});
|
||||
|
||||
45
server/distribute/export.spec.js
Normal file
45
server/distribute/export.spec.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
'use strict';
|
||||
|
||||
const {promisify} = require('util');
|
||||
|
||||
const test = require('tape');
|
||||
const io = require('socket.io-client');
|
||||
|
||||
const {connect} = require('../../test/before');
|
||||
const config = require('../config');
|
||||
|
||||
test('distribute: export', async (t) => {
|
||||
const defaultConfig = {
|
||||
export: true,
|
||||
exportToken: 'a',
|
||||
vim: true,
|
||||
log: false,
|
||||
};
|
||||
|
||||
const {port, done} = await connect({
|
||||
config: defaultConfig
|
||||
});
|
||||
|
||||
const url = `http://localhost:${port}/distribute?port=${1111}`;
|
||||
const socket = io.connect(url);
|
||||
|
||||
const name = config('name');
|
||||
|
||||
socket.on('connect', () => {
|
||||
socket.emit('auth', 'a');
|
||||
});
|
||||
|
||||
socket.on('accept', () => {
|
||||
config('vim', false);
|
||||
config('auth', true);
|
||||
});
|
||||
|
||||
socket.on('change', async () => {
|
||||
socket.close();
|
||||
await done();
|
||||
|
||||
t.pass('should emit change');
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
131
server/distribute/import.js
Normal file
131
server/distribute/import.js
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
'use strict';
|
||||
|
||||
const currify = require('currify/legacy');
|
||||
const wraptile = require('wraptile/legacy');
|
||||
const squad = require('squad/legacy');
|
||||
const fullstore = require('fullstore/legacy');
|
||||
|
||||
const io = require('socket.io-client');
|
||||
const forEachKey = currify(require('for-each-key/legacy'));
|
||||
|
||||
const config = require('../config');
|
||||
const log = require('./log');
|
||||
|
||||
const importStr = log.importStr;
|
||||
const connectedStr = log.connectedStr;
|
||||
const disconnectedStr = log.disconnectedStr;
|
||||
const tokenRejectedStr = log.tokenRejectedStr;
|
||||
const authTryStr = log.authTryStr;
|
||||
|
||||
const makeColor = log.makeColor;
|
||||
const stringToRGB = log.stringToRGB;
|
||||
const getMessage = log.getMessage;
|
||||
const getDescription = log.getDescription;
|
||||
const logWraped = log.logWraped;
|
||||
|
||||
const equal = (a, b) => `${a}=${b}`;
|
||||
const append = currify((obj, a, b) => obj.value += b && equal(a, b) + '&');
|
||||
const wrapApply = (f, disconnect) => (status) => () => f(null, {
|
||||
status,
|
||||
disconnect,
|
||||
});
|
||||
|
||||
const closeIfNot = wraptile((socket, is) => !is && socket.close());
|
||||
const addUrl = currify((url, a) => `${url}: ${a}`);
|
||||
|
||||
const getColorUrl = (url, name) => {
|
||||
if (!name)
|
||||
return url;
|
||||
|
||||
return makeColor(url, stringToRGB(name));
|
||||
};
|
||||
|
||||
const rmListeners = wraptile((socket, listeners) => {
|
||||
socket.removeListener('connect', listeners.onConnect);
|
||||
socket.removeListener('config', listeners.onConfig);
|
||||
socket.removeListener('error', listeners.onError);
|
||||
socket.removeListener('connection_error', listeners.onError);
|
||||
});
|
||||
|
||||
const canceled = (f) => f(null, {
|
||||
status: 'canceled',
|
||||
disconnect: () => {},
|
||||
});
|
||||
|
||||
const done = wraptile((fn, store) => fn(null, {
|
||||
status: store()
|
||||
}));
|
||||
|
||||
const emitAuth = wraptile((importUrl, socket) => {
|
||||
log(importStr, `${authTryStr} to ${importUrl}`);
|
||||
socket.emit('auth', config('importToken'));
|
||||
});
|
||||
|
||||
module.exports = (options, fn) => {
|
||||
fn = fn || options;
|
||||
|
||||
if (!config('import'))
|
||||
return canceled(fn);
|
||||
|
||||
const importUrl = config('importUrl');
|
||||
const importListen = config('importListen');
|
||||
const name = config('name');
|
||||
const port = config('port');
|
||||
|
||||
const query = toLine({
|
||||
name,
|
||||
port,
|
||||
});
|
||||
|
||||
const url = `${importUrl}/distribute?${query}`;
|
||||
const socket = io.connect(url, Object.assign({}, {
|
||||
rejectUnauthorized: false,
|
||||
}, options));
|
||||
|
||||
const superFn = wrapApply(fn, socket.close.bind(socket));
|
||||
const colorUrl = getColorUrl(importUrl, name);
|
||||
const close = closeIfNot(socket, importListen);
|
||||
|
||||
const statusStore = fullstore();
|
||||
const statusStoreWraped = wraptile(statusStore);
|
||||
|
||||
const onConfig = squad(close, logWraped(importStr, `config received from ${colorUrl}`), statusStoreWraped('received'), forEachKey(config));
|
||||
const onError = squad(superFn('error'), logWraped(importStr), addUrl(colorUrl), getMessage);
|
||||
const onConnectError = squad(superFn('connect_error'), logWraped(importStr), addUrl(colorUrl), getDescription);
|
||||
const onConnect = emitAuth(importUrl, socket);
|
||||
const onAccept = logWraped(importStr,`${connectedStr} to ${colorUrl}`);
|
||||
const onDisconnect = squad(done(fn, statusStore), logWraped(importStr, `${disconnectedStr} from ${colorUrl}`), rmListeners(socket, {
|
||||
onError,
|
||||
onConnect,
|
||||
onConfig,
|
||||
}));
|
||||
|
||||
const onChange = squad(logWraped(importStr), config);
|
||||
const onReject = squad(superFn('reject'), logWraped(importStr, tokenRejectedStr));
|
||||
|
||||
socket.on('connect', onConnect);
|
||||
socket.on('accept', onAccept);
|
||||
socket.on('disconnect', onDisconnect);
|
||||
socket.on('config', onConfig);
|
||||
socket.on('error', onError);
|
||||
socket.on('connect_error', onConnectError);
|
||||
socket.on('reject', onReject);
|
||||
|
||||
if (config('importListen'))
|
||||
socket.on('change', onChange);
|
||||
};
|
||||
|
||||
function toLine(obj) {
|
||||
const result = {
|
||||
value: '',
|
||||
};
|
||||
|
||||
forEachKey(append(result), obj);
|
||||
|
||||
const start = 0;
|
||||
const end = 1;
|
||||
const backward = -1;
|
||||
|
||||
return result.value.slice(start, backward * end);
|
||||
}
|
||||
|
||||
183
server/distribute/import.spec.js
Normal file
183
server/distribute/import.spec.js
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
'use strict';
|
||||
|
||||
const test = require('tape');
|
||||
const {promisify} = require('util');
|
||||
const tryToCatch = require('try-to-catch');
|
||||
const io = require('socket.io-client');
|
||||
const mockRequire = require('mock-require');
|
||||
|
||||
const {connect} = require('../../test/before');
|
||||
|
||||
const config = require('../config');
|
||||
const distribute = {
|
||||
import: promisify(require('./import')),
|
||||
};
|
||||
|
||||
test('distribute: import: canceled', async (t) => {
|
||||
const {done, port} = await connect({
|
||||
config: {
|
||||
export: false,
|
||||
import: false,
|
||||
importListen: false,
|
||||
log: false,
|
||||
}
|
||||
});
|
||||
|
||||
const {status} = await distribute.import();
|
||||
|
||||
await done();
|
||||
|
||||
t.equal(status, 'canceled', 'should equal');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('distribute: import: received: no error', async (t) => {
|
||||
const {done, port} = await connect({
|
||||
config: {
|
||||
import: true,
|
||||
importListen: false,
|
||||
export: true,
|
||||
log: false,
|
||||
}
|
||||
});
|
||||
|
||||
config('importUrl', `http://localhost:${port}`);
|
||||
|
||||
const [e] = await tryToCatch(distribute.import);
|
||||
|
||||
await done();
|
||||
|
||||
t.notOk(e, 'should not be error');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('distribute: import: received', async (t) => {
|
||||
const {done, port} = await connect({
|
||||
config: {
|
||||
name: 'bill',
|
||||
import: true,
|
||||
importToken: 'a',
|
||||
exportToken: 'a',
|
||||
export: true,
|
||||
importListen: false,
|
||||
log: false,
|
||||
}
|
||||
});
|
||||
|
||||
config('importUrl', `http://localhost:${port}`);
|
||||
|
||||
const {status} = await distribute.import();
|
||||
await done();
|
||||
|
||||
t.equal(status, 'received','should equal');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('distribute: import: received: auth: reject', async (t) => {
|
||||
const {done, port} = await connect({
|
||||
config: {
|
||||
name: 'bill',
|
||||
import: true,
|
||||
importToken: 'xxxxx',
|
||||
exportToken: 'bbbbb',
|
||||
export: true,
|
||||
importListen: false,
|
||||
log: false,
|
||||
}
|
||||
});
|
||||
|
||||
config('importUrl', `http://localhost:${port}`);
|
||||
|
||||
const {status} = await distribute.import();
|
||||
await done();
|
||||
|
||||
t.equal(status, 'reject','should equal');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('distribute: import: received: auth: accept', async (t) => {
|
||||
const {done, port} = await connect({
|
||||
config: {
|
||||
name: 'bill',
|
||||
import: true,
|
||||
importToken: 'xxxxx',
|
||||
exportToken: 'xxxxx',
|
||||
export: true,
|
||||
importListen: false,
|
||||
log: false,
|
||||
}
|
||||
});
|
||||
|
||||
config('importUrl', `http://localhost:${port}`);
|
||||
|
||||
const {status} = await distribute.import();
|
||||
await done();
|
||||
|
||||
t.equal(status, 'received','should equal');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('distribute: import: received: no name', async (t) => {
|
||||
const {done, port} = await connect({
|
||||
config: {
|
||||
name: '',
|
||||
import: true,
|
||||
export: true,
|
||||
importListen: false,
|
||||
log: false,
|
||||
}
|
||||
});
|
||||
|
||||
config('importUrl', `http://localhost:${port}`);
|
||||
|
||||
const {status} = await distribute.import();
|
||||
await done();
|
||||
|
||||
t.equal(status, 'received','should equal');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('distribute: import: error', async (t) => {
|
||||
const {done, port} = await connect({
|
||||
config: {
|
||||
import: true,
|
||||
export: false,
|
||||
importListen: false,
|
||||
log: false,
|
||||
}
|
||||
});
|
||||
|
||||
config('importUrl', `http://localhost:0`);
|
||||
|
||||
const {status} = await distribute.import({
|
||||
reconnection: false,
|
||||
});
|
||||
|
||||
await done();
|
||||
|
||||
t.equal(status, 'connect_error','should equal');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('distribute: import: config:change: no export', async (t) => {
|
||||
const {done, port} = await connect({
|
||||
config: {
|
||||
import: true,
|
||||
export: false,
|
||||
importListen: true,
|
||||
log: false,
|
||||
}
|
||||
});
|
||||
|
||||
const {status} = await distribute.import({
|
||||
reconnection: false,
|
||||
});
|
||||
|
||||
await done();
|
||||
|
||||
t.equal(status, 'connect_error','should equal');
|
||||
t.end();
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', console.log);
|
||||
|
||||
4
server/distribute/index.js
Normal file
4
server/distribute/index.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
'use strict';
|
||||
|
||||
module.exports.import = require('./import');
|
||||
module.exports.export = require('./export');
|
||||
45
server/distribute/log.js
Normal file
45
server/distribute/log.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
'use strict';
|
||||
|
||||
const wraptile = require('wraptile/legacy');
|
||||
const chalk = require('chalk');
|
||||
|
||||
const config = require('../config');
|
||||
const datetime = require('../../common/datetime');
|
||||
|
||||
const log = (name, msg) => config('log') && console.log(`${datetime()} -> ${name}: ${msg}`);
|
||||
const makeColor = (a, color) => chalk.rgb(color || stringToRGB(a))(a);
|
||||
const getMessage = (e) => e.message || e;
|
||||
const getDescription = (e) => `${e.type}: ${e.description}`;
|
||||
|
||||
module.exports = log;
|
||||
module.exports.logWraped = wraptile(log);
|
||||
module.exports.stringToRGB = stringToRGB;
|
||||
module.exports.makeColor = makeColor;
|
||||
module.exports.getMessage = getMessage;
|
||||
module.exports.getDescription = getDescription;
|
||||
|
||||
module.exports.importStr = 'import';
|
||||
module.exports.exportStr = 'export';
|
||||
module.exports.connectedStr = chalk.green('connected');
|
||||
module.exports.disconnectedStr = chalk.red('disconnected');
|
||||
module.exports.tokenRejectedStr = chalk.red('token rejected');
|
||||
module.exports.authTryStr = chalk.yellow('try to auth');
|
||||
|
||||
function stringToRGB(a) {
|
||||
return [
|
||||
a.charCodeAt(0),
|
||||
a.length,
|
||||
crc(a),
|
||||
];
|
||||
}
|
||||
|
||||
const add = (a, b) => {
|
||||
return a + b.charCodeAt(0);
|
||||
};
|
||||
|
||||
function crc(a) {
|
||||
return a
|
||||
.split('')
|
||||
.reduce(add, 0);
|
||||
}
|
||||
|
||||
32
server/distribute/log.spec.js
Normal file
32
server/distribute/log.spec.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
'use strict';
|
||||
|
||||
const test = require('tape');
|
||||
const log = require('./log');
|
||||
const config = require('../config');
|
||||
|
||||
test('distribute: log: getMessage', (t) => {
|
||||
const e = 'hello';
|
||||
const result = log.getMessage(e)
|
||||
|
||||
t.equal(e, result, 'should equal');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('distribute: log: getMessage: message', (t) => {
|
||||
const message = 'hello';
|
||||
const result = log.getMessage({
|
||||
message
|
||||
})
|
||||
|
||||
t.equal(result, message, 'should equal');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('distribute: log: config', (t) => {
|
||||
const logOriginal = config('log');
|
||||
config('log', true);
|
||||
log('log', 'test message');
|
||||
config('log', logOriginal);
|
||||
|
||||
t.end();
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue