diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9ac1e3e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,10 @@ +0.0.9 +**Breaking Changes** +- Using `.ini` config file instead of `.json`. + +**Bugs** +- Warning messages of initialization. +- Running behind proxy / sub-domain. + +**Enhancements** +- Restart / Delete / Stop / Save all processes. \ No newline at end of file diff --git a/README.md b/README.md index e0ce82f..01d6946 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ An elegant web interface for Unitech/PM2. - [Configs](#cli_confs) - [Authorization](#auth) - [UI/UX](#ui) +- [Serving apps locally with nginx and custom domain](#serv) # Features @@ -65,6 +66,12 @@ $ npm install -g pm2-gui Start the web server, by specific port (8090): $ pm2-gui start 8090 + Start the web server, by specific configuration file (pm2-gui.ini): + $ pm2-gui start --config + + Start the web server, by specific configuration file: + $ pm2-gui start --config my-config.ini + ``` @@ -75,41 +82,47 @@ $ npm install -g pm2-gui Options: -h, --help output usage information - --config [file] pass JSON config file with options - --no-debug hide stdout/stderr information - --config path to custom .json config. Default value pm2-gui.json + --config [file] pass ".ini" configuration file (with options) + --no-debug hide stdout / stderr information +``` + +**Daemonic:** +```bash +# start +$ nohup pm2-gui start > /dev/null 2>&1 & echo $! > /path/to/pm2-gui.pid +# stop +$ kill -9 `cat /path/to/pm2-gui.pid` ``` ## Configs -```javascript -{ - "refresh": 3000, - "manipulation": true, - "pm2": "~/.pm2", - "port": 8088 -} +```ini +pm2 = ~/.pm2 +refresh = 5000 +debug = false +port = 8088 ``` - **refresh** The heartbeat duration of monitor (backend), `5000` by default. -- **manipulation** A value indicates whether the client has permission to restart/stop processes, `true` by default. - **pm2** Root directory of Unitech/PM2, `~/.pm2` by default. - **port** Port of web interface. +- **debug** A value indicates whether show the debug information, `true` by default. - **password** The encrypted authentication code, if this config is set, users need to be authorized before accessing the index page, `password` could only be set by `pm2-gui set password [password]` ([authorization](#authorization)). -### Config file -You can quit set configurations by `pm2-gui start --config [file]`, the `[file]` must be an valid JSON, and can including all the above keys. +### File +You can quick set configurations by `pm2-gui start --config [file]`, the `[file]` must be a valid **ini** file, and can include all the above keys. Example ```bash -# Load the JSON configured file which is named as `pm2-gui.json` in current directory. +# Load the configuration file which is named as `pm2-gui.ini` in current directory. $ pm2-gui start --config -# Load the specific JSON configured file in current directory. -$ pm2-gui start --config conf.json +# Load the specific configuration file under current directory, `.ini` postfix is optional. +$ pm2-gui start --config conf +$ pm2-gui start --config conf.ini ``` -### Set Config +### Set Usage ```bash $ pm2-gui set @@ -122,7 +135,7 @@ $ pm2-gui set refresh 2000 Above command will set `refresh` to 2 seconds. -### Remove Config +### Remove Usage ```bash $ pm2-gui rm @@ -135,6 +148,18 @@ $ pm2-gui rm refresh Above command will remove `refresh` config and it will be set to `5000` (milliseconds) by default. +### Update via `vi` +```bash +$ vi $PM2_ROOT/.pm2/pm2-gui.ini +``` + +### Cleanup +```bash +$ rm $PM2_ROOT/.pm2/pm2-gui.ini +``` + +> The value of `$PM2_ROOT` is `~/` by default. + # Authorization Run the following commands: @@ -188,6 +213,47 @@ Tail Logs ![image](screenshots/logs.jpg) + +# Serving apps locally with nginx and custom domain + +```ini +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +upstream pm2-gui { + server 127.0.0.1:8000; +} + +server { + listen 80; + server_name pm2-gui.dev; + + #useless but can not get rid of. + root /path/to/pm2-gui/web/public; + + try_files $uri/index.html $uri.html $uri @app; + + # paths + location @app { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $http_host; + proxy_redirect off; + + proxy_pass http://pm2-gui; + } + + # socket.io + location /socket.io { + proxy_pass http://pm2-gui; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + } +} +``` + ## Test ```bash $ npm test diff --git a/bin/pm2-gui b/bin/pm2-gui index d7e5463..b56f166 100755 --- a/bin/pm2-gui +++ b/bin/pm2-gui @@ -8,6 +8,7 @@ var commander = require('commander'), pkg = require('../package.json'), Monitor = require('../lib/mon'), crypto = require('crypto'), + conf = require('../lib/util/conf'), interface = require('../web/index'); commander.version(pkg.version, '-v, --version') @@ -19,7 +20,13 @@ commander.on('--help', function(){ chalk.grey(' $ pm2-gui start\n') + '\n' + ' Start the web server, by specific port (8090):\n' + - chalk.grey(' $ pm2-gui start 8090\n') + chalk.grey(' $ pm2-gui start 8090\n') + + '\n' + + ' Start the web server, by specific configuration file (pm2-gui.ini):\n' + + chalk.grey(' $ pm2-gui start --config\n') + + '\n' + + ' Start the web server, by specific configuration file:\n' + + chalk.grey(' $ pm2-gui start --config my-config.ini\n') ); }); @@ -27,27 +34,30 @@ commander.on('--help', function(){ * Run web interface. */ commander.command('start [port]') - .option('--config [file]', 'pass JSON config file with options') + .option('--config [file]', 'pass ".ini" configuration file (with options)') .option('--no-debug', 'hide stdout / stderr information') .description('Launch the web server, port default by 8088') .action(function(port, cmd){ if (cmd.config) { var jsonFile; if (typeof cmd.config != 'string') { - jsonFile = 'pm2-gui.json'; + jsonFile = 'pm2-gui.ini'; } else { jsonFile = cmd.config; + if (jsonFile.indexOf('.') < 0) { + jsonFile += '.ini'; + } } if (!fs.existsSync(jsonFile)) { - console.log(chalk.red('✘ JSON configured file does not exist!\n')); + console.log(chalk.red('✘ .ini configuration file does not exist!\n')); process.exit(); } try { - var config = JSON.parse(fs.readFileSync(jsonFile, {encoding: 'utf-8'})); + var config = conf.File(path.resolve(process.cwd(), jsonFile)).loadSync().valueOf(); setConfig(config); } catch (err) { - console.log(chalk.red('✘ JSON configured file is invalid!\n')); + console.log(chalk.red('✘ .ini configuration file is invalid!\n')); process.exit(); } } @@ -90,7 +100,7 @@ if (process.argv.length == 2) { function setConfig(key, value){ var mon = Monitor(), acceptKeys = Object.keys(mon.options).filter(function(key){ - return !~['socketio', 'pm2Conf', 'debug'].indexOf(key); + return !~['socketio', 'pm2Conf'].indexOf(key); }); acceptKeys.push('password'); @@ -124,7 +134,7 @@ function showConfigs(cmd, mon){ if (!mon) { mon = Monitor(); } - var storage = mon._config.store, prints = ''; + var storage = mon._config.valueOf(), prints = ''; var maxLen = 0; for (var k in storage) { maxLen = Math.max(k.length, maxLen); diff --git a/lib/mon.js b/lib/mon.js index cf9b571..01040d9 100644 --- a/lib/mon.js +++ b/lib/mon.js @@ -1,6 +1,5 @@ var fs = require('fs'), path = require('path'), - nconf = require('nconf'), Debug = require('./util/debug'), stat = require('./stat'), _ = require('lodash'), @@ -9,16 +8,11 @@ var fs = require('fs'), pm = require('./pm'), totalmem = require('os').totalmem(), pidusage = require('pidusage'), - defConf = require('../pm2-gui.conf'); + conf = require('./util/conf'), + defConf; module.exports = Monitor; -var NSP = { - SYS : '/sys', - LOG : '/log', - PROC: '/proc' -}; - /** * Monitor of project monitor web. * @param options @@ -47,7 +41,7 @@ Monitor.prototype._resolveHome = function(pm2Home){ // Make sure exist. if (!pm2Home || !fs.existsSync(pm2Home)) { - throw new Error('PM2 root can not be located, try to set env by `export PM2_HOME=[ROOT]`.'); + throw new Error('PM2 root can not be located, try to initialize PM2 by executing `pm2 ls` or set env by `export PM2_HOME=[ROOT]`.'); } } return pm2Home; @@ -60,6 +54,7 @@ Monitor.prototype._init = function(options){ options = options || {}; // bind default options. + defConf = conf.File(path.resolve(__dirname, '..', 'pm2-gui.ini')).loadSync().valueOf(); options = _.defaults(defConf, options); options.pm2 = this._resolveHome(options.pm2); @@ -86,23 +81,19 @@ Monitor.prototype._init = function(options){ Object.freeze(this.options); // Initialize configurations. - this._config = new nconf.File({file: path.resolve(this.options.pm2, 'pm2-gui.json')}); + this._config = new conf.File(path.resolve(this.options.pm2, 'pm2-gui.ini')); // Set configurations. this.config('pm2', this._resolveHome(this.config('pm2')) || this.options.pm2); this.config('refresh', this.config('refresh') || this.options.refresh); this.config('port', this.config('port') || this.options.port || 8088); - - var mani = false; - if (typeof (mani = this.config('manipulation')) == 'undefined' && typeof (mani = this.options.manipulation) == 'undefined') { - mani = true; - } - this.config('manipulation', mani); + var debugable = this.config('debug'); + this.config('debug', typeof debugable == 'undefined' ? (debugable = !!this.options.debug) : !!debugable); // Logger. this._log = Debug({ namespace: 'monitor', - debug : !!this.options.debug + debug : debugable }); }; @@ -117,36 +108,11 @@ Monitor.prototype._init = function(options){ * @returns {*} */ Monitor.prototype.config = function(key, value){ - if (!key) { - return; + var def; + if (value == null) { + def = defConf[key]; } - // Load config from File. - this._config.loadSync(); - - if (typeof value == 'undefined') { - // Get config. - return this._config.get(key); - } else if (value == null) { - // Clear config. - this._config.clear(key); - // Reset to default if necessary. - var value = defConf[key]; - (typeof value != 'undefined') && this._config.set(key, value); - return this._config.saveSync(); - } - - // Make sure value in a correct type. - if (typeof value != 'boolean') { - if (!isNaN(value)) { - value = parseFloat(value); - } else if (/^(true|false)$/.test(value)) { - value = (value == 'true'); - } - } - - this._config.set(key, value); - // Save it. - this._config.saveSync(); + return this._config.val(key, value, def); }; /** @@ -165,9 +131,9 @@ Monitor.prototype.run = function(){ this._startWatching(); // Listen connection event. - this._sockio.of(NSP.SYS).on('connection', this._connectSysSock.bind(this)); - this._sockio.of(NSP.LOG).on('connection', this._connectLogSock.bind(this)); - this._sockio.of(NSP.PROC).on('connection', this._connectProcSock.bind(this)); + this._sockio.of(conf.NSP.SYS).on('connection', this._connectSysSock.bind(this)); + this._sockio.of(conf.NSP.LOG).on('connection', this._connectLogSock.bind(this)); + this._sockio.of(conf.NSP.PROC).on('connection', this._connectProcSock.bind(this)); } /** @@ -181,12 +147,12 @@ Monitor.prototype._connectSysSock = function(socket){ socket.on('disconnect', function(){ // Check connecting client. - this._noClient = this._sockio.of(NSP.SYS).sockets.length == 0; + this._noClient = this._sockio.of(conf.NSP.SYS).sockets.length == 0; }.bind(this)); // Trigger actions of process. socket.on('action', function(action, id){ - this._log.i('action', chalk.magenta(action), 'received', id); + this._log.i('action', chalk.magenta(action), ' ', id); pm.action(this.options.pm2Conf.DAEMON_RPC_PORT, action, id, function(err, forceRefresh){ if (err) { this._log.e(action, err.message); @@ -217,7 +183,7 @@ Monitor.prototype._connectSysSock = function(socket){ Monitor.prototype._connectLogSock = function(socket){ // Broadcast tailed logs. function broadcast(data){ - var socks = this._sockio.of(NSP.LOG).sockets; + var socks = this._sockio.of(conf.NSP.LOG).sockets; if (!socks || socks.length == 0) { return; } @@ -238,7 +204,7 @@ Monitor.prototype._connectLogSock = function(socket){ // Clean up `tail` after socket disconnected. socket.on('disconnect', function(){ - var socks = this._sockio.of(NSP.LOG).sockets, + var socks = this._sockio.of(conf.NSP.LOG).sockets, canNotBeDeleted = {}; if (socks && socks.length > 0) { socks.forEach(function(sock){ @@ -303,7 +269,7 @@ Monitor.prototype._connectLogSock = function(socket){ Monitor.prototype._connectProcSock = function(socket){ // Broadcast memory/CPU usage of process. function broadcast(data){ - var socks = this._sockio.of(NSP.PROC).sockets; + var socks = this._sockio.of(conf.NSP.PROC).sockets; if (!socks || socks.length == 0) { return; } @@ -324,7 +290,7 @@ Monitor.prototype._connectProcSock = function(socket){ // Clean up `proc` timer after socket disconnected. socket.on('disconnect', function(){ - var socks = this._sockio.of(NSP.PROC).sockets, + var socks = this._sockio.of(conf.NSP.PROC).sockets, canNotBeDeleted = {}; if (socks && socks.length > 0) { socks.forEach(function(sock){ @@ -497,5 +463,5 @@ Monitor.prototype._pm2Ver = function(socket){ * @private */ Monitor.prototype._broadcast = function(event, data, nsp){ - this._sockio.of(nsp || NSP.SYS).emit(event, data); + this._sockio.of(nsp || conf.NSP.SYS).emit(event, data); }; \ No newline at end of file diff --git a/lib/pm.js b/lib/pm.js index 517b017..1cd7f5e 100644 --- a/lib/pm.js +++ b/lib/pm.js @@ -23,8 +23,8 @@ var re_blank = /^[\s\r\t]*$/, * @param {Function} cb */ pm.sub = function(sockPath, cb){ - var sub = axon.socket('sub-emitter'), - sub_sock = sub.connect(sockPath); + var sub = axon.socket('sub-emitter'); + sub.connect(sockPath); // Once awake from sleeping. sub.on('log:*', function(e, d){ @@ -152,12 +152,27 @@ pm._findById = function(sockPath, id, cb){ * @param {Function} cb */ pm.action = function(sockPath, action, id, cb){ - pm._findById(sockPath, id, function(err, proc){ - if (err) { - return cb(err); - } - pm._actionByPMId(sockPath, proc, action, cb); - }) + if (id == 'all') { + pm.list(sockPath, function(err, procs){ + if (err) { + return cb(err); + } + if (!procs || procs.length == 0) { + return cb(new Error('No PM2 process running, the sockPath is "' + sockPath + '", please make sure it is existing!')); + } + + async.map(procs, function(proc, next){ + pm._actionByPMId(sockPath, proc, action, next.bind(null, null)); + }, cb); + }); + } else { + pm._findById(sockPath, id, function(err, proc){ + if (err) { + return cb(err); + } + pm._actionByPMId(sockPath, proc, action, cb); + }); + } }; /** @@ -173,7 +188,8 @@ pm._actionByPMId = function(sockPath, proc, action, cb){ pm_id = proc.pm_id; action += 'ProcessId'; - var watchEvent = ['stopWatch', action, {id: pm_id}, function(err, success){}]; + var watchEvent = ['stopWatch', action, {id: pm_id}, function(err, success){ + }]; if (!!~['restart'].indexOf(action)) { watchEvent.splice(0, 1, 'restartWatch'); @@ -277,4 +293,4 @@ pm._tailLogs = function(proc, cb){ tailLog(ls); })(logs); return tails; -} +} \ No newline at end of file diff --git a/lib/util/conf.js b/lib/util/conf.js new file mode 100644 index 0000000..3efb585 --- /dev/null +++ b/lib/util/conf.js @@ -0,0 +1,157 @@ +var fs = require('fs'), + _ = require('lodash'); + +/** + * Namespaces of socket.io + * @type {{SYS: string, LOG: string, PROC: string}} + */ +exports.NSP = { + SYS : '/sys', + LOG : '/log', + PROC: '/proc' +}; + +exports.File = File; + +/** + * Configurations store in a disk file. + * @param {Object} options + * @constructor + */ +function File(options){ + if(!(this instanceof File)){ + return new File(options); + } + + if (typeof options == 'string') { + options = {file: options}; + } + options = _.assign({}, options || {}); + if (!options.file) { + throw new Error('`file` is required.'); + } + Object.freeze(options); + this.options = options; +} + +/** + * Load data from file (sync). + */ +File.prototype.loadSync = function(){ + if (!fs.existsSync(this.options.file)) { + this._data = {}; + return this; + } + var re_comment = /^\s*;/, + re_setion = /^\s*\[([^\]]+)\]\s*$/, + re_kv = /^([^=]+)=(.*)$/, + re_boolean = /^(true|false)$/i; + + var json = {}, sec; + fs.readFileSync(this.options.file, {encoding: 'utf8'}) + .split(/[\r\n]/).forEach(function(line){ + // Empty line. + if(!line){ + sec = null; + return; + } + // Remove comments. + if(re_comment.test(line)){ + return; + } + + var ms; + // Sections. + if((ms = line.match(re_setion)) && ms.length == 2){ + json[sec = ms[1].trim()] = {}; + return; + } + + // Key-value pairs. + if((ms = line.match(re_kv)) && ms.length == 3){ + var key = ms[1].trim(), + value = ms[2].trim(); + + // Parse boolean and number. + if(!isNaN(value)){ + value = parseFloat(value); + }else if(re_boolean.test(value)){ + value = value.toLowerCase() == 'true'; + } + if(sec){ + var data = {}; + data[key] = value; + key = sec; + value = data; + } + json[key] = value; + } + }); + this._data = json; + return this; +}; + +/** + * Save data to a disk file (sync). + * @returns {File} + */ +File.prototype.saveSync = function(){ + function wrapValue(key, value){ + return key + ' = ' + (typeof value == 'string' ? value : JSON.stringify(value)) + '\n'; + } + var ini = ''; + for(var key in this._data){ + var value = this._data[key]; + // TODO: Array type. + if(typeof value == 'object'){ + ini += '[ ' + key + ' ]\n'; + for(var subKey in value){ + ini += wrapValue(subKey, value[subKey]) + } + ini += '\n'; + } + ini += wrapValue(key, value); + } + fs.writeFileSync(this.options.file, ini); + return this; +}; + +/** + * Get data. + * @returns {{}|*} + */ +File.prototype.valueOf = function(){ + return this._data; +}; + +/** + * Get/set/remove key-value pairs. + * @param {String} key + * @param {Mixed} value + * @param {Mixed} def + * @returns {*} + */ +File.prototype.val = function(key, value, def){ + if (!key) { + return; + } + // Load config from File. + this.loadSync(); + + if (typeof value == 'undefined') { + // Get config. + return this._data[key]; + } else if (value == null) { + // Clear config. + delete this._data[key]; + // Reset to default if necessary. + (typeof def != 'undefined') && (this._data[key] = def); + return this.saveSync(); + } + + this._data[key] = value; + + // Save it. + this.saveSync(); + return this; +}; \ No newline at end of file diff --git a/package.json b/package.json index 67937c0..0cf62fb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pm2-gui", - "version": "0.0.8", + "version": "0.0.9", "description": "An elegant web interface for Unitech/PM2.", "scripts": { "test": "NODE_ENV=test bash test/index.sh" @@ -37,12 +37,11 @@ "async": "~0.9.0", "lodash": "~2.4.1", "commander": "~2.5.0", - "chalk": "~0.5.1", + "chalk": "~1.0.0", "express": "~4.10.1", "swig": "~1.4.2", "windows-cpu": "~0.1.1", "socket.io": "~1.2.0", - "nconf": "~0.6.9", "pm2-axon": "~2.0.7", "pm2-axon-rpc": "~0.3.6", "ansi-html": "~0.0.4", diff --git a/pm2-gui.conf b/pm2-gui.conf deleted file mode 100644 index d458811..0000000 --- a/pm2-gui.conf +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - "pm2": "~/.pm2", - "refresh": 5000, - "manipulation": true, - "port": 8088 -}; \ No newline at end of file diff --git a/pm2-gui.ini b/pm2-gui.ini new file mode 100644 index 0000000..bd5838b --- /dev/null +++ b/pm2-gui.ini @@ -0,0 +1,4 @@ +pm2 = ~/.pm2 +refresh = 5000 +debug = true +port = 8088 \ No newline at end of file diff --git a/test.js b/test.js new file mode 100644 index 0000000..838dfe2 --- /dev/null +++ b/test.js @@ -0,0 +1 @@ +console.log('ni'.slice(-4)); \ No newline at end of file diff --git a/test/bash/config.sh b/test/bash/config.sh index 2898df9..d17f112 100644 --- a/test/bash/config.sh +++ b/test/bash/config.sh @@ -5,6 +5,12 @@ source "${SRC}/include.sh" cd $fixtures +head "set config (Encrypt)(password)" +$pg set password "passw@rd" > /dev/null +val=$(config "password:" "password:\s*([^\s]+)" | grep -o "[^ ]\+\( \+[^ ]\+\)*") +[ "$val" = "ebc4c06b266b84263efafa47003244cc" ] || fail "expect the password to be encrypted, but current not" +success "the password should be encrypted" + head "set config (Number)(refresh)" $pg set refresh 4000 > /dev/null val=$(config "refresh:" "^[^0-9]*([0-9]+).*") @@ -20,8 +26,8 @@ val=$(config "port:" "^[^0-9]*([0-9]+).*") success "the value should be 9000" head "set config (Boolean)" -$pg set manipulation false > /dev/null -val=$(config "manipulation:" ".*(true|false).*") +$pg set debug false > /dev/null +val=$(config "debug:" ".*(true|false).*") [ "$val" = false ] || fail "expect the value to be false, but current is $val" success "the value should be false" diff --git a/test/bash/include.sh b/test/bash/include.sh index 54a3eaa..f11f59e 100644 --- a/test/bash/include.sh +++ b/test/bash/include.sh @@ -23,6 +23,7 @@ function success { function fail { echo -e "######## \033[31m ✘ $1\033[0m" + ps aux | grep pm2-gui | grep node | xargs kill -9 exit 1 } diff --git a/test/bash/interface.sh b/test/bash/interface.sh index 7b3a2ad..44e85e9 100644 --- a/test/bash/interface.sh +++ b/test/bash/interface.sh @@ -44,16 +44,12 @@ ret=$(port 9000) success "127.0.0.1:9000 should be disconnected" head "run web server (--config verify)" -ret=`$pg start --config not_exist.json | grep "does not exist" | wc -c` +ret=`$pg start --config not_exist.ini | grep "does not exist" | wc -c` [ "$ret" -gt 0 ] || fail "expect throw out error message" -success "JSON file does not exist" - -ret=`$pg start --config invalid.conf | grep "invalid" | wc -c` -[ "$ret" -gt 0 ] || fail "expect throw out error message" -success "JSON file invalid" +success ".ini file does not exist" head "run web server (--config specific file)" -nohup $pg start --config pm2-gui-cp.conf > /dev/null 2>&1 & +nohup $pg start --config pm2-gui-cp > /dev/null 2>&1 & pid=$! sleep 1 ret=$(port 27130) @@ -70,9 +66,9 @@ success "127.0.0.1:27130 should be disconnected" val=$(config "refresh:" "^[^0-9]*([0-9]+).*") [ "$val" -eq 3000 ] || fail "expect the value of refresh to be 3000, but current is $val" success "the value of refresh should be 3000" -val=$(config "manipulation:" ".*(true|false).*") -[ "$val" = false ] || fail "expect the value of manipulation to be false, but current is $val" -success "the value of manipulation should be false" +val=$(config "debug:" ".*(true|false).*") +[ "$val" = false ] || fail "expect the value of debug to be false, but current is $val" +success "the value of debug should be false" val=$(config "pm2:" ".*(\/.+).*") [ ! "$val" = "/tmp/.pm2" ] || fail "expect the value of pm2 to be /tmp/.pm2" success "the value of pm2 should be /tmp/.pm2" @@ -95,9 +91,9 @@ success "127.0.0.1:8088 should be disconnected" val=$(config "refresh:" "^[^0-9]*([0-9]+).*") [ "$val" -eq 5000 ] || fail "expect the value of refresh to be 5000, but current is $val" success "the value of refresh should be 3000" -val=$(config "manipulation:" ".*(true|false).*") -[ "$val" = true ] || fail "expect the value of manipulation to be true, but current is $val" -success "the value of manipulation should be true" +val=$(config "debug:" ".*(true|false).*") +[ "$val" = true ] || fail "expect the value of debug to be true, but current is $val" +success "the value of debug should be true" root="~/.pm2" if [ ! -z "$PM2_HOME" ]; then root="$PM2_HOME" diff --git a/test/fixtures/invalid.conf b/test/fixtures/invalid.conf deleted file mode 100644 index 77d5015..0000000 --- a/test/fixtures/invalid.conf +++ /dev/null @@ -1,5 +0,0 @@ -{ - "pm2": "/tmp/.pm2", - "refresh": 3000, - "manipulation": -} \ No newline at end of file diff --git a/test/fixtures/pm2-gui-cp.conf b/test/fixtures/pm2-gui-cp.conf deleted file mode 100644 index fef69bb..0000000 --- a/test/fixtures/pm2-gui-cp.conf +++ /dev/null @@ -1,6 +0,0 @@ -{ - "pm2": "/tmp/.pm2", - "refresh": 3000, - "manipulation": false, - "port": 27130 -} \ No newline at end of file diff --git a/test/fixtures/pm2-gui-cp.ini b/test/fixtures/pm2-gui-cp.ini new file mode 100644 index 0000000..54a7f95 --- /dev/null +++ b/test/fixtures/pm2-gui-cp.ini @@ -0,0 +1,4 @@ +pm2 = /tmp/.pm2 +refresh = 3000 +port = 27130 +debug = false \ No newline at end of file diff --git a/test/fixtures/pm2-gui.ini b/test/fixtures/pm2-gui.ini new file mode 100644 index 0000000..bd5838b --- /dev/null +++ b/test/fixtures/pm2-gui.ini @@ -0,0 +1,4 @@ +pm2 = ~/.pm2 +refresh = 5000 +debug = true +port = 8088 \ No newline at end of file diff --git a/test/fixtures/pm2-gui.json b/test/fixtures/pm2-gui.json deleted file mode 100644 index 84e35f3..0000000 --- a/test/fixtures/pm2-gui.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "pm2": "~/.pm2", - "refresh": 5000, - "manipulation": true, - "port": 8088 -} \ No newline at end of file diff --git a/test/fixtures/startup.json b/test/fixtures/startup.json new file mode 100644 index 0000000..81ec07c --- /dev/null +++ b/test/fixtures/startup.json @@ -0,0 +1,48 @@ +{ + "apps": [ + { + "name": "Colorful", + "script": "colorful.js", + "cwd": "test/fixtures", + "args": [ + "--color" + ] + }, + { + "name": "Exit Immediately", + "script": "exit.js", + "cwd": "test/fixtures" + }, + { + "name": "Fibonacci", + "script": "fib-slow.js", + "cwd": "test/fixtures" + }, + { + "name": "Fibonacci", + "script": "fib-slow.js", + "cwd": "test/fixtures" + }, + { + "name": "Randomize", + "script": "rand.js", + "instances": 0, + "cwd": "test/fixtures" + }, + { + "name": "Throw Error", + "script": "throw.js", + "cwd": "test/fixtures" + }, + { + "name": "Tick", + "script": "tick.js", + "cwd": "test/fixtures" + }, + { + "name": "Tock", + "script": "tock.js", + "cwd": "test/fixtures" + } + ] +} \ No newline at end of file diff --git a/web/index.js b/web/index.js index b5ff82c..bfaf571 100644 --- a/web/index.js +++ b/web/index.js @@ -29,13 +29,17 @@ function runServer(debug){ var server = require('http').Server(app); var io = require('socket.io')(server); - var mon = Monitor({ - sockio: io, - debug : !!debug - }); - var port = mon.config('port'); - server.listen(port); - log.i('http', 'Web server of', chalk.bold.underline('Unitech/PM2'), 'is listening on port', chalk.bold(port)); + try { + var mon = Monitor({ + sockio: io, + debug : !!debug + }); + var port = mon.config('port'); + server.listen(port); + log.i('http', 'Web server of', chalk.bold.underline('Unitech/PM2'), 'is listening on port', chalk.bold(port)); - mon.run(); + mon.run(); + }catch(err){ + log.e(chalk.red(err.message)); + } } \ No newline at end of file diff --git a/web/public/css/auth.css b/web/public/css/auth.css index bfd5129..e01af98 100644 --- a/web/public/css/auth.css +++ b/web/public/css/auth.css @@ -67,91 +67,6 @@ html, body { -webkit-animation-delay: 1s; } -.auth-form div > * { - position: absolute; - background: transparent; - display: block; - top: 0; - border: solid 4px #06ff00; -} - -.auth-form div > *:before, -.auth-form div > *:after { - content: ''; - position: absolute; - display: block; - background-color: #06ff00; -} - -.auth-form div .G { - width: 32px; - height: 32px; - border-radius: 16px; - border-right-color: transparent; - left: 0; - transform: rotate(-45deg); - -moz-transform: rotate(-45deg); - -webkit-transform: rotate(-45deg); - -o-transform: rotate(-45deg); -} - -.auth-form div .G:before { - left: 23px; - top: 12px; - width: 4px; - height: 17px; - transform: rotate(-45deg); - -moz-transform: rotate(-45deg); - -webkit-transform: rotate(-45deg); - -o-transform: rotate(-45deg); -} - -.auth-form div .G:after, -.auth-form div .U:after { - width: 4px; - height: 4px; - border-radius: 2px; - left: 10px; - top: 10px; - background-color: #68bdff; - box-shadow: inset 0px 1px 4px 0px rgba(250, 250, 250, 0.5), - 0px 0px 4px 2px #888; -} - -.auth-form div .U { - width: 32px; - height: 32px; - border-radius: 16px; - border-left-color: transparent; - border-top-color: transparent; - left: 36px; - transform: rotate(45deg); - -moz-transform: rotate(45deg); - -webkit-transform: rotate(45deg); - -o-transform: rotate(45deg); -} - -.auth-form div .I { - width: 4px; - height: 28px; - border: 0; - background-color: #06ff00; - left: 74px; - top: 4px; -} - -.auth-form div .I:before { - width: 4px; - height: 11px; - left: -10px; -} - -.auth-form div .I:after { - width: 10px; - height: 4px; - left: -6px; -} - .auth-form input { top: 50px; margin-left: -150px; diff --git a/web/public/css/main.css b/web/public/css/main.css index 8bef9d9..b31df05 100644 --- a/web/public/css/main.css +++ b/web/public/css/main.css @@ -546,4 +546,12 @@ END OF REPO && VER /***************** END OF GRAPH -******************/ \ No newline at end of file +******************/ + +#procs_action{ + position: absolute; + left: 50%; + margin-left: -140px; + bottom: -145px; + display: none; +} \ No newline at end of file diff --git a/web/public/img/delete.png b/web/public/img/delete.png new file mode 100644 index 0000000..e2aac44 Binary files /dev/null and b/web/public/img/delete.png differ diff --git a/web/public/img/restart.png b/web/public/img/restart.png new file mode 100644 index 0000000..f4341d8 Binary files /dev/null and b/web/public/img/restart.png differ diff --git a/web/public/img/save.png b/web/public/img/save.png new file mode 100644 index 0000000..4500d7b Binary files /dev/null and b/web/public/img/save.png differ diff --git a/web/public/img/stop.png b/web/public/img/stop.png new file mode 100644 index 0000000..3ef9045 Binary files /dev/null and b/web/public/img/stop.png differ diff --git a/web/public/js/auth.html.js b/web/public/js/auth.html.js index 42634cf..0bfea96 100644 --- a/web/public/js/auth.html.js +++ b/web/public/js/auth.html.js @@ -12,6 +12,8 @@ $(window).ready(function(){ } }); btn = $('a').click(login); + + drawLogo(); }); // Login event. @@ -29,20 +31,20 @@ function login(){ // Post data to server. lightUp(); $.ajax({ - url : '/auth_api?t=' + Math.random(), - data : { + url : 'auth_api?t=' + Math.random(), + data : { pwd: val }, dataType: 'json', - error : function(){ + error : function(){ info('Can not get response from server, it is an internal error.'); lightOff(); }, - success: function(res){ + success : function(res){ lightOff(); - if(res.error){ + if (res.error) { return info(res.error); - }else{ + } else { window.location.href = '/'; } } @@ -75,4 +77,57 @@ function info(msg){ icon : './img/info.png', useAnimateCss: true }); +} + +function drawLogo(){ + var w = 80, + h = 32; + + var svg = d3.select('#logo') + .append('svg') + .attr('width', w) + .attr('height', h); + + var filter = svg.append("defs") + .append("filter") + .attr("id", "dropshadow") + + filter.append("feGaussianBlur") + .attr("in", "SourceAlpha") + .attr("stdDeviation", 1) + .attr("result", "blur"); + filter.append("feOffset") + .attr("in", "blur") + .attr("dx", 4) + .attr("dy", 4) + .attr("result", "offsetBlur") + filter.append("feFlood") + .attr("in", "offsetBlur") + .attr("flood-color", "#0a6506") + .attr("flood-opacity", "0.9") + .attr("result", "offsetColor"); + filter.append("feComposite") + .attr("in", "offsetColor") + .attr("in2", "offsetBlur") + .attr("operator", "in") + .attr("result", "offsetBlur"); + + var feMerge = filter.append("feMerge"); + + feMerge.append("feMergeNode") + .attr("in", "offsetBlur") + feMerge.append("feMergeNode") + .attr("in", "SourceGraphic"); + + var vis = svg + .append('g') + .attr('width', w) + .attr('height', h); + + vis.append('path') + .style("fill", "none") + .style("stroke", "#fff") + .style("stroke-width", 2) + .attr('d', 'M24,12 T16,8 T4,16 T16,28 T24,20 T18,20 T28,18 T30,16 T44,24 T48,16 T58,8 L58,28 T62,16 T68,16 T72,16 T76,16') + .attr("filter", "url(#dropshadow)"); } \ No newline at end of file diff --git a/web/public/js/fanavi.min.js b/web/public/js/fanavi.min.js new file mode 100644 index 0000000..6bfd1ff --- /dev/null +++ b/web/public/js/fanavi.min.js @@ -0,0 +1,8 @@ +/** + * fanavi 0.0.1 + * https://github.com/Tjatse/fanavi + * + * Apache, License 2.0 + * Copyright (C) 2015 Tjatse + */ +function d3menu(d3){return function(selector){var _defOptions={outerRadius:100,innerRadius:30,spacing:4,margin:40,bounce:0.1,startAngle:-90,endAngle:270,padAngle:2,menuItemStrokeWidth:2,backgroundColor:"#50b44c",buttonStrokeWidth:4,buttonForegroundColor:"#fff",buttonAutoShape:true,shadow:{x:2,y:2,blur:3,opacity:0.5,color:"#054502"},iconSize:32,speed:500,hideTooltip:false};function menu(_selector){if(!(this instanceof menu)){return new menu(_selector)}this.options=_mixin({selector:_selector||"body",_id:Math.random()},_defOptions);this._listeners={};this._updateVars()}menu.prototype.load=function(items){if(this._loaded||!items||items.length==0){return this}this._loaded=true;this.options._items=[].concat(items);Object.freeze(this.options);this._visualize()._drawMenuButton();return this};menu.prototype.option=function(key,value){if(typeof value=="undefined"){if(typeof key=="object"){this.options=_mixin({},_defOptions,this.options,key);return this._updateVars()}return this.options[key]}else{if(value==null){delete this.options[key]}else{this.options[key]=value}}this.options=_mixin({},_defOptions,this.options);return this._updateVars()};menu.prototype._updateVars=function(){this.options.width=this.options.height=_getSize(this.options);(typeof this.options.backgroundColor=="string")&&(this.options.backgroundColor=d3.rgb(this.options.backgroundColor));(typeof this.options.buttonForegroundColor=="string")&&(this.options.buttonForegroundColor=d3.rgb(this.options.buttonForegroundColor));return this};menu.prototype._visualize=function(){var options=this.options;var attrs={width:options.width,height:options.height};options.className&&(attrs["class"]=options.className);options.id&&(attrs.id=options.id);this.svg=d3.select(options.selector).append("svg").attr(attrs).style("opacity",0.95);this.defs=this.svg.append("defs");var stopColors=[{offset:"0%",color:options.backgroundColor.brighter(0.5)},{offset:"70%",color:options.backgroundColor},{offset:"100%",color:options.backgroundColor.brighter(0.1)}];this.defs.append("radialGradient").attr({id:"gradBg"+options._id,gradientUnits:"userSpaceOnUse",cx:"0",cy:"0",r:options.innerRadius-options.buttonStrokeWidth,spreadMethod:"pad"}).selectAll("stop").data(stopColors).enter().append("stop").attr({offset:function(d){return d.offset},"stop-color":function(d){return d.color},"stop-opacity":function(d){return d.opacity||1}});var filter=this.defs.append("filter").attr({id:"dropshadow"+options._id,x:"-50%",y:"-50%",width:"200%",height:"200%"});filter.append("feGaussianBlur").attr({"in":"SourceAlpha",stdDeviation:options.shadow.blur,result:"blur"});filter.append("feOffset").attr({"in":"blur",dx:options.shadow.x,dy:options.shadow.y,result:"offsetBlur"});filter.append("feFlood").attr({"flood-opacity":options.shadow.opacity,"flood-color":options.shadow.color,result:"offsetColor"});filter.append("feComposite").attr({"in":"offsetColor",in2:"offsetBlur",operator:"in",result:"offsetBlur"});var feMerge=filter.append("feMerge");feMerge.append("feMergeNode").attr("in","offsetBlur");feMerge.append("feMergeNode").attr("in","SourceGraphic");return this};menu.prototype._drawMenuButton=function(){var that=this,options=this.options;var gMenu=this.svg.append("g").attr({transform:"translate("+(options.width/2-options.innerRadius)+", "+(options.height/2-options.innerRadius)+")","class":"menuButton",id:"gMenu"+options._id});var startAngle=0,endAngle=2*Math.PI,rectFactor=false;if(options.buttonAutoShape){var settings=this._getMenuSettings();if(!settings.circular&&(rectFactor=!!~[90,180].indexOf(Math.abs(settings.diffAngle)))){startAngle=settings.startArc.startAngle;endAngle=_degToRad(options.endAngle)}}var arc=d3.svg.arc().innerRadius(0).outerRadius(options.innerRadius).startAngle(startAngle).endAngle(endAngle);gMenu.append("path").style({fill:"url(#gradBg"+options._id+")",stroke:options.backgroundColor,"stroke-width":options.buttonStrokeWidth+"px",cursor:"pointer"}).attr({filter:"url(#dropshadow"+options._id+")",d:arc,transform:"translate("+options.innerRadius+", "+options.innerRadius+")","class":"menuButtonBg"});var menuSize=options.innerRadius/Math.sin(Math.PI/4)*0.75*(rectFactor?0.6:1),offset=(options.innerRadius-menuSize/2),ord=d3.scale.ordinal(),pad=0.2;ord.domain(d3.range(3)).rangeBands([0,menuSize],pad,0);var rectSize=ord.rangeBand(),padSize=pad*rectSize,offsetSize=rectSize*(1+pad)/2,rects=d3.range(9).map(function(n,i){var col=i%3,row=Math.floor(i/3);return{x:offset+ord(col),y:offset+ord(row),r:row,c:col}});var attrs={"class":"menuRects"};if(rectFactor){var deg=(options.startAngle+settings.diffAngle/2)%360,angleX=_degToRad(deg),angleY=angleX,factX=1,factY=1,radius=options.innerRadius*0.35,tx=0,ty=0;if(deg%180==0){factY=-1}if(deg%90!=0){angleX=options.startAngle%180==0?options.endAngle:options.startAngle;angleY=options.startAngle%180==0?options.startAngle:options.endAngle;if(angleX%180==0){factX=-1}if(angleY%180==0){factY=-1}angleX=_degToRad(angleX);angleY=_degToRad(angleY)}tx=factX*Math.sin(angleX)*radius;ty=factY*Math.cos(angleY)*radius;attrs.transform="translate("+tx+", "+ty+")"}var gRects=gMenu.append("g").attr(attrs);gRects.selectAll(".menuRect").data(rects).enter().append("rect").style({cursor:"pointer",fill:options.buttonForegroundColor,opacity:0.001}).attr({"class":"menuRect",width:rectSize,height:rectSize,transform:"translate("+(options.innerRadius-rectSize/2)+","+(options.innerRadius-rectSize/2)+")"}).transition().delay(function(d,i){return i*20}).style("opacity",1).attr("transform",function(d){return"translate("+d.x+","+d.y+")"});gMenu.on("mouseenter",mouseenter).on("mouseleave",mouseleave).on("mouseup",mouseup);function mouseenter(){if(gMenu.attr("enter")){return}gMenu.attr("enter","Y");if(gMenu.attr("press")){return}gMenu.selectAll(".menuRect").interrupt().style("opacity",1).transition().delay(function(d,i){return i*20}).attr("transform",function(d){return"translate("+(d.x+(d.c-1))+","+(d.y+(d.r-1))+") scale(1.1)"})}function mouseleave(){gMenu.attr("enter",null);if(gMenu.attr("press")){return}gMenu.selectAll(".menuRect").interrupt().transition().delay(function(d,i){return(8-i)*20}).attr("transform",function(d){return"translate("+d.x+","+d.y+")"})}function mouseup(){if(!gMenu.attr("enter")||gMenu.attr("animate")=="Y"){return}gMenu.attr("animate","Y");setTimeout(function(){gMenu.attr("animate",null)},options.speed+5);if(gMenu.attr("press")){return that.dismiss()}gMenu.attr("press","Y");gMenu.selectAll(".menuRect").interrupt().transition().duration(options.speed).attr("transform",function(d,i){var x,y;if((d.c!=1&&d.r!=1)||(d.c==1&&d.r==1)){x=rects[4].x;y=rects[4].y}else{if(d.r==1){var factor=(d.c-1);x=d.x-factor*(offsetSize-padSize*2);y=d.y+factor*(offsetSize+padSize*2)}else{var factor=d.r-1;x=d.x-factor*(offsetSize+padSize*2);y=d.y-factor*(offsetSize-padSize*2)}}return"translate("+x+","+y+") rotate(45 "+(rectSize/2)+","+(rectSize/2)+") scale("+(1+pad)+")"});that.show()}this.gMenu=gMenu;return this};menu.prototype._getMenuSettings=function(){if(this._menuSettings){return this._menuSettings}var options=this.options,items=options._items,len=items.length,isImage=!options.fontFamily,diffAngle=options.endAngle-options.startAngle,circular=Math.abs(diffAngle)==360,clockFactor=diffAngle<0?-1:1,startAngle=_degToRad(options.startAngle),stepAngle=clockFactor*_degToRad(Math.abs(diffAngle)+(circular?0:options.padAngle*2))/len,actualStepAngle=stepAngle-clockFactor*(_degToRad(options.padAngle)+_degToRad(options.menuItemStrokeWidth)),actualInnerRadius=options.innerRadius+options.spacing+options.buttonStrokeWidth/2,actualOuterRadius=options.outerRadius-options.menuItemStrokeWidth/2;var arcs={path:{arc:d3.svg.arc(),center:[options.width/2,options.height/2]}};arcs[isImage?"image":"text"]={arc:d3.svg.arc().innerRadius(actualInnerRadius).outerRadius(actualOuterRadius),center:[(options.width-options.iconSize)/2,(options.height-options.iconSize)/2]};return this._menuSettings={isImage:isImage,angles:items.map(function(n,i){var start=startAngle+stepAngle*i;return _mixin({startAngle:start,endAngle:start+actualStepAngle,outerRadius:actualOuterRadius},n)}),circular:circular,diffAngle:diffAngle,arcs:arcs,startArc:{startAngle:startAngle,endAngle:startAngle,innerRadius:actualInnerRadius,outerRadius:actualInnerRadius}}};menu.prototype.show=function(){var options=this.options,triggered=-1;var color=function(d,i,factor){return options.backgroundColor[i%2==0?"darker":"brighter"](factor||0.2)};var settings=this._getMenuSettings();var menuItems=this.svg.selectAll(".menuItem").data(settings.angles);menuItems.enter().append("g").on("mouseenter",mouseenter).on("mouseleave",mouseleave).on("mouseup",mouseup).attr("class","menuItem").style("cursor","pointer");menuItems.append("path").attr({transform:"translate("+settings.arcs.path.center+")",filter:"url(#dropshadow"+options._id+")"}).style({fill:color,stroke:function(d,i){return color(d,i).darker(0.1)},"stroke-width":options.menuItemStrokeWidth+"px"}).transition().duration(options.speed).attrTween("d",function(d,i){var interpolate=d3.interpolate(settings.startArc,d);this._current=interpolate(0);return function(t){return settings.arcs.path.arc(interpolate(t))}}).each("end",function(d,i){var g=this.parentNode;g.setAttribute("active","Y");!options.hideTooltip&&addTooltip.call(g,d,i);if(triggered==i){mouseenter.call(g,d,i)}});var iconStyles={"opacity":0.001},iconAttrs={width:options.iconSize,height:options.iconSize},iconNodeName="image";if(!settings.isImage){iconNodeName="text";_mixin(iconStyles,{opacity:1,fill:options.iconColor||options.backgroundColor.brighter(1),"font-family":options.fontFamily,"font-size":options.iconSize+"px"});iconAttrs["dominant-baseline"]="central"}menuItems.append(iconNodeName).style(iconStyles).attr(iconAttrs).each(function(d,i){var ele=d3.select(this),extAttr="";if(settings.isImage){ele.attr("xlink:href",d.icon);extAttr=" scale(0.01)"}else{ele.text(d.icon).style("fill",options.buttonForegroundColor);this._center=[(options.width-this.getBBox().width)/2,options.height/2]}ele.attr("transform","translate("+(this._center||settings.arcs[iconNodeName].center)+")"+extAttr)}).transition().duration(options.speed).delay(1).tween("ta",function(d,i){var interpolate=d3.interpolate(settings.startArc,d);this._current=interpolate(1);return function(t){var center=settings.arcs[iconNodeName].arc.centroid(interpolate(t)),ocenter=this._center||settings.arcs[iconNodeName].center,styles={opacity:t},extAttr="";if(settings.isImage){extAttr=" scale("+t+")"}else{styles["font-size"]=t*options.iconSize+"px"}d3.select(this).style(styles).attr("transform","translate("+(ocenter[0]+center[0]*t)+", "+(ocenter[1]+center[1]*t)+")"+extAttr)}});function mouseenter(d,i){triggered=i;_hover.call(this,true,function(ele){return(!ele.attr("active")||ele.attr("enter")=="Y")})}function mouseleave(){triggered=-1;_hover.call(this,false,function(ele){return(!ele.attr("active")||!ele.attr("enter"))})}var that=this;function mouseup(d,i){var ele=d3.select(this);if(!ele.attr("active")||ele.attr("press")=="Y"){return}ele.attr("press","Y");var entered=ele.attr("enter")=="Y";if(entered){menuItems.filter(function(_d,_i){return i!=_i}).transition().duration(options.speed).style("opacity",0.2)}ele.select("path").transition().duration(Math.ceil(options.speed/3)).ease("bounce").style("fill",function(){return color(d,i,0.5)}).each("end",function(){if(entered){mouseleave.call(this.parentNode,d,i,true)}menuItems.attr("active",null);that.trigger("click",i,d);that.gMenu.attr("animate","Y");setTimeout(function(){that.dismiss(true)},options.speed)})}function _hover(enter,abortPred,np){var ele=d3.select(this);if(!np&&abortPred(ele)){return}var children=this.childNodes,len=children.length;if(len==0){return}ele.attr("enter",enter?"Y":null);for(var k in children){var child=children[k];if(child._current){var nodeName=child.nodeName,center=settings.arcs[nodeName].arc.centroid(child._current),relCenter=child._center||settings.arcs[nodeName].center,isIconEle=nodeName==iconNodeName,actualCenter=[(relCenter[0]+(isIconEle?center[0]:0)),(relCenter[1]+(isIconEle?center[1]:0))];if(enter){actualCenter[0]+=center[0]*options.bounce;actualCenter[1]+=center[1]*options.bounce}d3.select(child).transition().duration(options.speed).ease("bounce").attr("transform","translate("+actualCenter+")")}else{if(child.nodeName=="g"){d3.select(child).transition().duration(options.speed/2).style("opacity",enter?0.8:0.001)}}}}var getTipCenter=function(d,i,f){if(typeof f=="undefined"){f=1}var center=settings.arcs[iconNodeName].arc.centroid(d);return[f*settings.arcs.path.center[0]+center[0]*(1+options.bounce),f*settings.arcs.path.center[1]+center[1]*(1+options.bounce)]},getTipPos=function(d,i,index){var angle=Math.atan2.apply(null,getTipCenter(d,i,0).reverse());return settings.arcs.path.center[index]+Math[index==0?"cos":"sin"](angle)*options.outerRadius*(0.8+options.bounce)},tooltipColor=options.tooltipColor||function(d,i){return color(d,i).darker(1)},tooltipBg=function(x,y,w,h,r){return"M"+(x+r)+","+y+"h"+(w-r)+"a"+r+","+r+" 0 0 1 "+r+","+r+"v"+(h-2*r)+"a"+r+","+r+" 0 0 1 "+-r+","+r+"h"+(r-w)+"a"+r+","+r+" 0 0 1 "+-r+","+-r+"v"+(2*r-h)+"a"+r+","+r+" 0 0 1 "+r+","+-r+"z"};function addTooltip(d,i){var tg=d3.select(this).append("g").attr("opacity",0.001);tg.append("circle").attr({x:0,y:0,r:2,transform:function(d,i){return"translate("+getTipCenter(d,i)+")"}}).style({fill:tooltipColor});tg.append("line").attr({x1:function(d,i){return getTipCenter(d,i)[0]},y1:function(d,i){return getTipCenter(d,i)[1]},x2:function(d,i){return getTipPos(d,i,0)},y2:function(d,i){return getTipPos(d,i,1)}}).style({stroke:tooltipColor,"stroke-width":"1px"});var txtBg=tg.append("path");var txtEle=tg.append("text").attr({x:function(d,i){return getTipPos(d,i,0)},y:function(d,i){return getTipPos(d,i,1)}}).style({"text-anchor":"middle",fill:tooltipColor}).text(function(d){return d.title});var rect=txtEle.node().getBBox();txtBg.attr({"d":tooltipBg(rect.x-10,rect.y-5,rect.width+10,rect.height+10,10),filter:"url(#dropshadow"+options._id+")"}).style({fill:"#f0f0f0",stroke:tooltipColor,"stroke-width":"1px"})}};menu.prototype.dismiss=function(inactive){var options=this.options;var settings=this._getMenuSettings();var menuItems=this.svg.selectAll(".menuItem");!inactive&&menuItems.attr("active",null);menuItems.selectAll("path").data([]).exit().transition().duration(options.speed).attrTween("d",function(d,i){var interpolate=d3.interpolate(d,settings.startArc);return function(t){return settings.arcs.path.arc(interpolate(t))}}).each("end",function(){d3.select(this.parentNode).remove()});var iconNodeName="image";if(!settings.isImage){iconNodeName="text"}menuItems.selectAll(iconNodeName).data([]).exit().transition().duration(options.speed).delay(1).tween("ta",function(d,i){var interpolate=d3.interpolate(d,settings.startArc);return function(t1){var t=1-t1;var center=settings.arcs[iconNodeName].arc.centroid(interpolate(t1)),ocenter=this._center||settings.arcs[iconNodeName].center,styles={opacity:t},extAttr="";if(settings.isImage){extAttr=" scale("+t+")"}else{styles["font-size"]=t*options.iconSize+"px"}d3.select(this).style(styles).attr("transform","translate("+(ocenter[0]+center[0]*t)+", "+(ocenter[1]+center[1]*t)+")"+extAttr)}});this.gMenu.selectAll(".menuRect").transition().duration(options.speed).attr("transform",function(d){return"translate("+d.x+","+d.y+")"});setTimeout(function(that){that.gMenu.attr({"press":null,"animate":null})},options.speed+5,this)};menu.prototype.trigger=function(n){var e=this._listeners[n];if(!e){return}var args=Array.prototype.slice.call(arguments,1);for(var i=0;i=0;i--){if(es===String(ls[i])){ls.splice(i,1)}}return this};function _getSize(options){return(options.outerRadius+options.margin)*2}function _mixin(){if(arguments.length==0){return{}}var options=arguments[0];for(var i=1;i 0 && !isVisible){ + eles.procsAction.css({ + opacity: 0.01, + display: 'inherit' + }).stop().animate({ + opacity: 1 + }); + }else if(procs.data.length == 0 && isVisible){ + eles.procsAction.stop().animate({ + opacity: 0.01 + }, function(){ + $(this).css('display', 'none'); + }); + } +} + /** * Render polar charset of usage (CPU and memory). */ @@ -324,6 +388,8 @@ function onProcsChange(_procs){ // Update processes' layout on fullPage 2. updateProcsLayout(); } + + resetFanavi(); } /** diff --git a/web/routes/index.js b/web/routes/index.js index 16fe3ce..8834f00 100644 --- a/web/routes/index.js +++ b/web/routes/index.js @@ -36,5 +36,5 @@ action(function auth_api(req, res){ req.session['auth_code'] = encryptedPwd; return res.json({status: 200}); } - return res.json({error: 'Authorize failed, password is not correct.'}); + return res.json({error: 'Authorize failed, password is incorrect.'}); }); \ No newline at end of file diff --git a/web/views/auth.html b/web/views/auth.html index 39b37b6..0950f9e 100644 --- a/web/views/auth.html +++ b/web/views/auth.html @@ -6,20 +6,18 @@ - - - - + + + + - +
-
- - - + @@ -27,9 +25,10 @@
- - - + + + + \ No newline at end of file diff --git a/web/views/index.html b/web/views/index.html index e700a05..e7af39c 100644 --- a/web/views/index.html +++ b/web/views/index.html @@ -6,12 +6,12 @@ - - - - - - + + + + + + @@ -33,6 +33,7 @@
+
@@ -44,16 +45,17 @@
- - - - - - - - - - + + + + + + + + + + +