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 @@
-
-
-
-
+
+
+
+
-
+