diff --git a/.gitignore b/.gitignore
index b512c09..78f8ebe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,3 @@
-node_modules
\ No newline at end of file
+node_modules
+logs
+*.pid
\ No newline at end of file
diff --git a/.npmignore b/.npmignore
index a267a83..58a7bcb 100644
--- a/.npmignore
+++ b/.npmignore
@@ -1,3 +1,5 @@
node_modules
.gitignore
-screenshots
\ No newline at end of file
+screenshots
+logs
+*.pid
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index be4d592..ec191e6 100755
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,3 +5,4 @@ branches:
node_js:
- 0.10
- 0.11
+ - 0.12
diff --git a/README.md b/README.md
index 3c19e91..6d20617 100644
--- a/README.md
+++ b/README.md
@@ -13,10 +13,8 @@ An elegant web & terminal interface for Unitech/PM2.
- [Cautions](#cauts)
- [Installation](#ins)
- [CLI](#cli)
- - [Curses-like dashboard](#dashboard)
- [Run Web Interface](#cli_web)
- - [Daemonic](#daemonic)
- - [Configs](#cli_confs)
+ - [Curses-like dashboard](#dashboard)
- [Authorization](#auth)
- [UI/UX](#ui)
- [Serving apps locally with nginx and custom domain](#serv)
@@ -103,8 +101,8 @@ $ npm install -g pm2-gui
--no-debug hide stdout / stderr information
```
-
-## Daemonic
+
+## daemonize
```bash
# start
$ nohup pm2-gui start > /dev/null 2>&1 & echo $! > /path/to/pm2-gui.pid
@@ -121,79 +119,6 @@ debug = false
port = 8088
```
-- **refresh** The heartbeat duration of monitor (backend), `5000` 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)).
-
-### 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 configuration file which is named as `pm2-gui.ini` in current directory.
-$ pm2-gui start --config
-
-# Load the specific configuration file under current directory, `.ini` postfix is optional.
-$ pm2-gui start --config conf
-$ pm2-gui start --config conf.ini
-```
-
-### Set
-Usage
-```bash
-$ pm2-gui set
-```
-
-Example
-```bash
-$ pm2-gui set refresh 2000
-```
-
-Above command will set `refresh` to 2 seconds.
-
-### Remove
-Usage
-```bash
-$ pm2-gui rm
-```
-
-Example
-```bash
-$ 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:
-```bash
-$ pm2-gui set password 1234
-$ pm2-gui start
-```
-
-When you visiting `http://[domain]:8088` in your browser, it will be redirected to `http://[domain]:8088/auth`, and you need to typo the password (`1234`) to login.
-
-Otherwise, if you do not want to deny anybody, just simply remove it:
-```bash
-$ pm2-gui rm password
-$ pm2-gui start
-```
-
# UI/UX
- Amazing and smooth animations.
diff --git a/bin/pm2-gui b/bin/pm2-gui
deleted file mode 100755
index 07b1006..0000000
--- a/bin/pm2-gui
+++ /dev/null
@@ -1,165 +0,0 @@
-#!/usr/bin/env node
-
-var commander = require('commander'),
- path = p = require('path'),
- fs = require('fs'),
- chalk = require('chalk'),
- _ = require('lodash'),
- 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')
- .usage('[cmd] [options]');
-
-commander.on('--help', function(){
- console.log(' Basic Examples:\n\n' +
- ' Start the web server, by default port (8088):\n' +
- chalk.grey(' $ pm2-gui start\n') +
- '\n' +
- ' Start the web server, by specific port (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')
- );
-});
-
-/**
- * Run web interface.
- */
-commander.command('start [port]')
- .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.ini';
- } else {
- jsonFile = cmd.config;
- if (jsonFile.indexOf('.') < 0) {
- jsonFile += '.ini';
- }
- }
- if (!fs.existsSync(jsonFile)) {
- console.log(chalk.red('✘ .ini configuration file does not exist!\n'));
- process.exit();
- }
-
- try {
- var config = conf.File(path.resolve(process.cwd(), jsonFile)).loadSync().valueOf();
- setConfig(config);
- } catch (err) {
- console.log(chalk.red('✘ .ini configuration file is invalid!\n'));
- process.exit();
- }
- }
- port && setConfig('port', port);
- interface(cmd.debug);
- });
-
-commander.command('mon')
- .description('curses-like dashboard')
- .action(function(){
- var mon = Monitor({
- debug: false
- });
- mon.dashboard();
- });
-
-/**
- * Show configurations.
- */
-commander.command('config')
- .description('show all configs')
- .action(showConfigs);
-
-/**
- * Set config by key-value pairs.
- */
-commander.command('set ')
- .description('set config by key-value pairs')
- .action(function(key, value, cmd){
- var mon = setConfig(key, value);
- mon && showConfigs(cmd, mon);
- });
-
-/**
- * Unset config by key.
- */
-commander.command('rm ')
- .description('remove config by key')
- .action(function(key, cmd){
- var mon = Monitor();
- mon.config(key, null);
- showConfigs(cmd, mon);
- });
-
-commander.parse(process.argv);
-
-if (process.argv.length == 2) {
- commander.outputHelp();
- process.exit(0);
-}
-
-/**
- * Set configuration.
- * @param key
- * @param value
- * @returns {*}
- */
-function setConfig(key, value){
- var mon = Monitor(),
- acceptKeys = Object.keys(mon.options).filter(function(key){
- return !~['socketio', 'pm2Conf'].indexOf(key);
- });
-
- acceptKeys.push('password');
-
- (function config(pairs){
- if (pairs.length == 0) {
- return;
- }
- var pair = pairs.shift();
- if (!~acceptKeys.indexOf(pair[0])) {
- console.log(chalk.bold.yellow('[WARN]'), chalk.cyan(pair[0]), 'is not an acceptable configuration.');
- return config(pairs);
- }
- if (pair[0] == 'password') {
- var md5 = crypto.createHash('md5');
- md5.update(pair[1]);
- pair[1] = md5.digest('hex');
- }
- mon.config(pair[0], pair[1]);
- config(pairs);
- })(typeof key == 'object' ? _.pairs(key) : [[key, value]]);
-
- return mon;
-}
-/**
- * Show all configurations.
- * @param cmd
- * @param mon
- */
-function showConfigs(cmd, mon){
- if (!mon) {
- mon = Monitor();
- }
- var storage = mon._config.valueOf(), prints = '';
- var maxLen = 0;
- for (var k in storage) {
- maxLen = Math.max(k.length, maxLen);
- }
- maxLen += 1;
- for (var k in storage) {
- prints += chalk.bold(Array(maxLen - k.length).join(' ') + k + ': ') + ' ' + chalk.blue(storage[k] + '\n');
- }
- console.log(prints);
-}
\ No newline at end of file
diff --git a/lib/daemon.js b/lib/daemon.js
new file mode 100644
index 0000000..75cb3cf
--- /dev/null
+++ b/lib/daemon.js
@@ -0,0 +1,144 @@
+var chalk = require('chalk'),
+ path = require('path'),
+ fs = require('fs'),
+ async = require('async'),
+ cp = require('child_process'),
+ fork = cp.fork,
+ spawn = cp.spawn,
+ Monitor = require('./monitor'),
+ Log = require('./util/log');
+
+var processDirname = process.cwd(),
+ confFile = './pm2-gui.ini';
+
+if (process.argv.length > 3) {
+ var confFile = process.argv[3];
+}
+
+confFile = path.resolve(processDirname, confFile);
+
+if (!fs.existsSync(confFile)) {
+ console.error(chalk.bold(confFile), chalk.red('does not exist!'));
+ return process.exit(0);
+}
+
+var monitor = Monitor({
+ confFile: confFile
+});
+
+Log(monitor.options.log);
+
+var pidfile = path.resolve(processDirname, './pm2-gui.pid');
+
+var Daemon = {
+ restarts: 0,
+ init: function (next) {
+ process.on('SIGTERM', Daemon.stop);
+ process.on('SIGINT', Daemon.stop);
+ process.on('SIGHUP', Daemon.restart);
+ next && next();
+ },
+ start: function (next) {
+ Daemon.worker = Daemon.fork();
+ next && next();
+ },
+ restart: function () {
+ console.info('Restarting...');
+ Daemon.kill();
+ Daemon.start();
+ },
+ stop: function () {
+ console.info('Stopping...');
+ Daemon.kill();
+ fs.unlinkSync(pidfile);
+ },
+ kill: function () {
+ if (Daemon.worker) {
+ Daemon.worker.suicide = true;
+ Daemon.worker.kill();
+ }
+ },
+ fork: function () {
+ console.info('Forking slave...');
+ var worker = fork(path.resolve(processDirname, 'pm2-gui.js'), [confFile, '--color'], {
+ silent: monitor.options.daemonize,
+ env: process.env
+ });
+ worker.on('exit', function (code, signal) {
+ if (code != 0) {
+ if (Daemon.restarts < 10) {
+ Daemon.restarts++;
+ setTimeout(function () {
+ Daemon.restarts--;
+ }, 20000);
+ } else {
+ console.error(Daemon.restarts + ' restarts in 20 seconds, view the logs to investigate the crash problem.');
+ return process.exit(0);
+ }
+ }
+ if (!(worker.suicide || code === 0)) {
+ setTimeout(Daemon.fork, 3000);
+ }
+ });
+
+ worker.on('message', function (message) {
+ if (typeof message == 'object' && message.action)
+ if (message.action == 'restart') {
+ Daemon.restart();
+ }
+ });
+
+ var logDir = monitor.options.log.dir,
+ stdout = 'pm2-gui.out',
+ stderr = 'pm2-gui.err';
+
+ if (!logDir) {
+ logDir = './logs';
+ }
+ if (!fs.existsSync(logDir)) {
+ fs.mkdirSync(logDir);
+ }
+
+ if (monitor.options.daemonize) {
+ stdout = fs.createWriteStream(path.resolve(processDirname, logDir, stdout));
+ stderr = fs.createWriteStream(path.resolve(processDirname, logDir, stderr));
+ worker.stdout.pipe(stdout);
+ worker.stderr.pipe(stderr);
+ }
+
+ fs.writeFile(pidfile, worker.pid);
+ return worker;
+ },
+ daemonize: function () {
+ if (process.env.daemonized) {
+ console.info('Daemonized with pid [' + process.pid + '].');
+ return;
+ }
+ console.info('Spawning daemon...');
+ var args = [].concat(process.argv);
+ args.shift();
+ var env = process.env;
+ env.daemonized = true;
+ var child = spawn(process.execPath, args, {
+ env: env,
+ detached: false,
+ cwd: processDirname,
+ stdio: ['ignore', process.stdout, process.stderr]
+ });
+ child.unref();
+ process.exit();
+ }
+};
+if (monitor.options.daemonize) {
+ Daemon.daemonize();
+}
+
+process.title = 'pm2-gui daemon';
+async.series([
+ Daemon.init,
+ Daemon.start
+], function (err) {
+ if (err) {
+ console.error(err.stack);
+ }
+});
\ No newline at end of file
diff --git a/lib/mon.js b/lib/monitor.js
similarity index 64%
rename from lib/mon.js
rename to lib/monitor.js
index 71c58fe..c87c311 100644
--- a/lib/mon.js
+++ b/lib/monitor.js
@@ -1,15 +1,15 @@
var fs = require('fs'),
path = require('path'),
- Debug = require('./util/debug'),
- stat = require('./stat'),
_ = require('lodash'),
chalk = require('chalk'),
ansiHTML = require('ansi-html'),
- pm = require('./pm'),
totalmem = require('os').totalmem(),
pidusage = require('pidusage'),
+ pm = require('./pm'),
+ stat = require('./stat'),
conf = require('./util/conf'),
layout = require('./blessed-widget/layout'),
+ Log = require('./util/log'),
defConf;
module.exports = Monitor;
@@ -29,6 +29,9 @@ function Monitor(options) {
this._init(options);
};
+Monitor.ACCEPT_KEYS = ['pm2', 'refresh', 'statsd', 'node', 'log', 'daemonize', 'max_restarts'];
+Monitor.DEF_CONF_FILE = 'pm2-gui.ini';
+
/**
* Resolve home path.
* @param {String} pm2Home
@@ -42,7 +45,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 initialize PM2 by executing `pm2 ls` or 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 environment variable vi `export PM2_HOME=[ROOT]`.');
}
}
return pm2Home;
@@ -55,11 +58,14 @@ Monitor.prototype._resolveHome = function (pm2Home) {
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);
+ defConf = conf.File(options.confFile || path.resolve(__dirname, '..', Monitor.DEF_CONF_FILE)).loadSync().valueOf();
+ defConf = _.pick.call(null, defConf, Monitor.ACCEPT_KEYS);
- options.pm2 = this._resolveHome(options.pm2.home);
+ options = _.pick.apply(options, Monitor.ACCEPT_KEYS).valueOf();
+ options = _.defaults(options, defConf);
+
+ options.pm2 = this._resolveHome(options.pm2);
+ Log(options.log);
// Load PM2 config.
var pm2ConfPath = path.join(options.pm2, 'conf.js');
@@ -69,21 +75,17 @@ Monitor.prototype._init = function (options) {
throw new Error(404);
}
} catch (err) {
- console.error('Can not load PM2 config, the file "' + pm2ConfPath + '" does not exist.');
+ var fbMsg = 'Can not load PM2 config, the file "' + pm2ConfPath + '" does not exist or empty, fallback to auto-load by pm2 home.';
+ console.warn(fbMsg);
options.pm2Conf = {
DAEMON_RPC_PORT: path.resolve(options.pm2, 'rpc.sock'),
DAEMON_PUB_PORT: path.resolve(options.pm2, 'pub.sock'),
PM2_LOG_FILE_PATH: path.resolve(options.pm2, 'pm2.log')
};
-
- if (!fs.existsSync(options.pm2Conf.DAEMON_RPC_PORT)) {
- throw new Error(fbMsg + ' But file ' + options.pm2Conf.DAEMON_RPC_PORT + ' can not found.');
- }
- if (!fs.existsSync(options.pm2Conf.DAEMON_PUB_PORT)) {
- throw new Error(fbMsg + ' But file ' + options.pm2Conf.DAEMON_PUB_PORT + ' can not found.');
- }
- if (!fs.existsSync(options.pm2Conf.PM2_LOG_FILE_PATH)) {
- throw new Error(fbMsg + ' But file ' + options.pm2Conf.PM2_LOG_FILE_PATH + ' can not found.');
+ for (var pm2ConfProp in options.pm2Conf) {
+ if (!fs.existsSync(options.pm2Conf[pm2ConfProp])) {
+ throw new Error(fbMsg + ' But file ' + options.pm2Conf[pm2ConfProp] + ' can not found.');
+ }
}
}
@@ -96,40 +98,6 @@ Monitor.prototype._init = function (options) {
// Bind to context.
this.options = options;
Object.freeze(this.options);
-
- // Initialize configurations.
- 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 debug = this.config('debug');
- this.config('debug', typeof debug == 'undefined' ? (debug = !!this.options.debug) : !!debug);
-
- // Logger.
- this._log = Debug({
- namespace: 'monitor',
- debug: debug
- });
-};
-
-/**
- * Operations of configuration.
- * @example:
- * set config : mon.config('key', 'value');
- * clear config : mon.config('key', null);
- * get config : mon.config('key');
- * @param {String} key
- * @param {Mixed} value
- * @returns {*}
- */
-Monitor.prototype.config = function (key, value) {
- var def;
- if (value == null) {
- def = defConf[key];
- }
- return this._config.val(key, value, def);
};
/**
@@ -145,36 +113,38 @@ Monitor.prototype.run = function () {
this._tails = {};
this._usages = {};
- // Watching PM2
- this._startWatching();
+ // Observe PM2
+ this._observePM2();
// Listen connection event.
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));
-}
+};
+
+/**
+ * Quit monitor.
+ * @return {[type]} [description]
+ */
+Monitor.prototype.quit = function () {
+ console.debug('Closing pm2 pub emitter socket.');
+ this.pm2Sock && this.pm2Sock.close();
+};
/**
* Monitor dashboard.
*/
Monitor.prototype.dashboard = function () {
- // No logger anymore.
- this._log = Debug({
- namespace: 'monitor',
- debug: false
- });
-
// Socket.io server.
- var port = this.config('port');
+ var port = this.options.port;
this._sockio = require('socket.io')();
this._sockio.listen(port);
this.run();
// Render screen.
- var lyt = layout({
+ layout({
port: port
- });
- lyt.render();
+ }).render();
};
/**
@@ -193,13 +163,13 @@ Monitor.prototype._connectSysSock = function (socket) {
// Trigger actions of process.
socket.on('action', function (action, id) {
- this._log.i('action', chalk.magenta(action), ' ', id);
+ console.info('[' + id + ']', action, 'sending to pm2 daemon...');
pm.action(this.options.pm2Conf.DAEMON_RPC_PORT, action, id, function (err, forceRefresh) {
if (err) {
- this._log.e(action, err.message);
+ console.error(action, err.message);
return socket.emit('action', id, err.message);
}
- this._log.i('action', chalk.magenta(action), 'finished', id);
+ console.info('[' + id + ']', action, 'completed!');
forceRefresh && this._throttleRefresh();
}.bind(this));
}.bind(this));
@@ -213,8 +183,8 @@ Monitor.prototype._connectSysSock = function (socket) {
this._sysStat && this._broadcast('system_stat', this._sysStat);
// Grep system states once and again.
- (this._status != 'R') && this._nextTick(this.config('refresh') || 5000);
-}
+ (this._status != 'R') && this._nextTick(this.options.refresh || 5000);
+};
/**
* Connection event of `log` namespace.
@@ -222,44 +192,33 @@ Monitor.prototype._connectSysSock = function (socket) {
* @private
*/
Monitor.prototype._connectLogSock = function (socket) {
- // Broadcast tailed logs.
- function broadcast(data) {
- var socks = this._sockio.of(conf.NSP.LOG).sockets;
- if (!socks || socks.length == 0) {
- return;
- }
- socks.forEach(function (sock) {
- if (sock._pm_id == data.pm_id) {
- sock.emit('log', data)
- }
- });
- }
+ var self = this;
// Emit error.
function emitError(err, pm_id, keepANSI) {
- broadcast.call(this, {
+ var data = {
pm_id: pm_id,
msg: keepANSI ? chalk.red(err.message) : 'Error: ' + err.message + ''
- });
+ };
+ self._broadcast.call(self, 'log', data, conf.NSP.LOG);
}
// Clean up `tail` after socket disconnected.
function killTail(pm_id) {
- this._tails[pm_id].forEach(function (tail) {
+ self._tails[pm_id].forEach(function (tail) {
try {
tail.kill('SIGTERM');
} catch (err) {}
});
- delete this._tails[pm_id];
- this._log.i('tail', chalk.magenta('destroy'), pm_id);
+ delete self._tails[pm_id];
+ console.info('[' + pm_id + ']', 'tail destroyed!');
}
function killTailProcess(pm_id) {
if (!isNaN(pm_id)) {
- killTail.call(this, pm_id);
- return;
+ return killTail(pm_id);
}
- var socks = this._sockio.of(conf.NSP.LOG).sockets,
+ var socks = self._sockio.of(conf.NSP.LOG).sockets,
canNotBeDeleted = {};
if (socks && socks.length > 0) {
socks.forEach(function (sock) {
@@ -267,32 +226,31 @@ Monitor.prototype._connectLogSock = function (socket) {
});
}
- for (var pm_id in this._tails) {
+ for (var pm_id in self._tails) {
if (!canNotBeDeleted[pm_id]) {
- killTail.call(this, pm_id);
+ killTail(pm_id);
}
}
}
- socket.on('disconnect', killTailProcess.bind(this));
- socket.on('tail_kill', killTailProcess.bind(this));
- socket.on('tail', function (pm_id, keepANSI) {
+
+ function startTailProcess(pm_id, keepANSI) {
socket._pm_id = pm_id;
- if (this._tails[pm_id]) {
+ if (self._tails[pm_id]) {
return;
}
// Tail logs.
pm.tail({
- sockPath: this.options.pm2Conf.DAEMON_RPC_PORT,
- logPath: this.options.pm2Conf.PM2_LOG_FILE_PATH,
+ sockPath: self.options.pm2Conf.DAEMON_RPC_PORT,
+ logPath: self.options.pm2Conf.PM2_LOG_FILE_PATH,
pm_id: pm_id
}, function (err, lines) {
if (err) {
- return emitError.call(this, err, pm_id, keepANSI);
+ return emitError(err, pm_id, keepANSI);
}
// Emit logs to clients.
- broadcast.call(this, {
+ var data = {
pm_id: pm_id,
msg: lines.map(function (line) {
if (!keepANSI) {
@@ -302,16 +260,21 @@ Monitor.prototype._connectLogSock = function (socket) {
return line;
}
}).join(keepANSI ? '\n' : '')
- });
- }.bind(this), function (err, tails) {
+ };
+ self._broadcast.call(self, 'log', data, conf.NSP.LOG);
+ }, function (err, tails) {
if (err) {
- return emitError.call(this, err, pm_id, keepANSI);
+ return emitError(err, pm_id, keepANSI);
}
- this._log.i('tail', chalk.magenta('start'), pm_id);
- this._tails[pm_id] = tails;
- }.bind(this));
- }.bind(this));
+ console.info('[' + pm_id + ']', 'tail starting...');
+ self._tails[pm_id] = tails;
+ });
+ }
+
+ socket.on('disconnect', killTailProcess);
+ socket.on('tail_kill', killTailProcess);
+ socket.on('tail', startTailProcess);
};
/**
@@ -320,30 +283,18 @@ Monitor.prototype._connectLogSock = function (socket) {
* @private
*/
Monitor.prototype._connectProcSock = function (socket) {
- // Broadcast memory/CPU usage of process.
- function broadcast(data) {
- var socks = this._sockio.of(conf.NSP.PROC).sockets;
- if (!socks || socks.length == 0) {
- return;
- }
- socks.forEach(function (sock) {
- if (sock._pid == data.pid) {
- sock.emit('proc', data)
- }
- });
- }
-
+ var self = this;
// Emit error.
function emitError(err, pid) {
- broadcast.call(this, {
+ var data = {
pid: pid,
msg: 'Error: ' + err.message + ''
- });
+ };
+ self._broadcast.call(self, 'proc', data, conf.NSP.PROC);
}
- // Clean up `proc` timer after socket disconnected.
- socket.on('disconnect', function () {
- var socks = this._sockio.of(conf.NSP.PROC).sockets,
+ function killObserver() {
+ var socks = self._sockio.of(conf.NSP.PROC).sockets,
canNotBeDeleted = {};
if (socks && socks.length > 0) {
socks.forEach(function (sock) {
@@ -356,20 +307,20 @@ Monitor.prototype._connectProcSock = function (socket) {
if (!canNotBeDeleted[pid] && (timer = this._usages[pid])) {
clearInterval(timer);
delete this._usages[pid];
- this._log.i('usage', chalk.magenta('destroy'), pid);
+ console.info('[' + pid + ']', 'cpu and memory observer destroyed!');
}
}
- }.bind(this));
+ }
- socket.on('proc', function (pid) {
+ function runObserver(pid) {
socket._pid = pid;
var pidStr = pid.toString();
- if (this._usages[pidStr]) {
+ if (self._usages[pidStr]) {
return;
}
- this._log.i('usage', chalk.magenta('start'), pidStr);
+ console.info('[' + pidStr + ']', 'cpu and memory observer is running...');
function runTimer(ctx) {
pidusage.stat(pid, function (err, stat) {
@@ -379,18 +330,22 @@ Monitor.prototype._connectProcSock = function (socket) {
return emitError.call(ctx, err, pid);
}
stat.memory = stat.memory * 100 / totalmem;
- // Emit memory/CPU usage to clients.
- broadcast.call(ctx, {
+
+ var data = {
pid: pid,
time: Date.now(),
usage: stat
- });
+ };
+ self._broadcast.call(ctx, 'proc', data, conf.NSP.PROC);
});
}
- this._usages[pidStr] = setInterval(runTimer, 3000, this);
+ self._usages[pidStr] = setInterval(runTimer, 3000, self);
runTimer(this);
- }.bind(this));
+ }
+
+ socket.on('disconnect', killObserver);
+ socket.on('proc', runObserver);
};
/**
@@ -405,7 +360,7 @@ Monitor.prototype._nextTick = function (tick, continuously) {
}
// Running
this._status = 'R';
- this._log.d(chalk.magenta('monitor'), tick);
+ console.debug('monitor heartbeat', tick);
// Grep system state
this._systemStat(function () {
// If there still has any client, grep again after `tick` ms.
@@ -414,9 +369,9 @@ Monitor.prototype._nextTick = function (tick, continuously) {
}
// Stop
delete this._status;
- this._log.d(chalk.magenta('monitor'), chalk.red('destroy'));
- }.bind(this));
-}
+ console.debug('monitor heartbeat destroyed!');
+ });
+};
/**
* Grep system states.
@@ -427,7 +382,7 @@ Monitor.prototype._systemStat = function (cb) {
stat.cpuUsage(function (err, cpu_usage) {
if (err) {
// Log only.
- this._log.e('sockio', 'Can not load system/cpu/memory information: ' + err.message);
+ console.error('Can not load system/cpu/memory information: ', err.message);
} else {
// System states.
this._sysStat = _.defaults(_(stat).pick('cpus', 'arch', 'hostname', 'platform', 'release', 'uptime', 'memory').clone(), {
@@ -435,20 +390,20 @@ Monitor.prototype._systemStat = function (cb) {
});
this._broadcast('system_stat', this._sysStat);
}
- cb();
- }.bind(this));
-}
+ cb.call(this);
+ }, this);
+};
/**
- * Watching PM2
+ * Observe PM2
* @private
*/
-Monitor.prototype._startWatching = function () {
- // Watching sub-emitter.
- pm.sub(this.options.pm2Conf.DAEMON_PUB_PORT, function (data) {
- this._log.i('sub-emitter', chalk.magenta(data.event), data.process.name + '-' + data.process.pm_id);
+Monitor.prototype._observePM2 = function () {
+ console.info('Connecting to pm2 daemon:', pm2Daemon);
+ this.pm2Sock = pm.sub(this.options.pm2Conf.DAEMON_PUB_PORT, function (data) {
+ console.info('sub-emitter', chalk.magenta(data.event), data.process.name + '-' + data.process.pm_id);
this._throttleRefresh();
- }.bind(this));
+ }, this);
// Enforce a refresh operation if RPC is not online.
this._throttleRefresh();
@@ -467,6 +422,7 @@ Monitor.prototype._throttleRefresh = function () {
ctx._refreshProcs();
}, 500, this);
};
+
/**
* Refresh processes
* @private
@@ -474,7 +430,7 @@ Monitor.prototype._throttleRefresh = function () {
Monitor.prototype._refreshProcs = function () {
pm.list(this.options.pm2Conf.DAEMON_RPC_PORT, function (err, procs) {
if (err) {
- return this._broadcast('info', 'Error: ' + err.message);
+ return this._broadcast('info', 'Can not connect to pm2 daemon, ' + err.message);
}
// Wrap processes and cache them.
this._procs = procs.map(function (proc) {
@@ -499,7 +455,7 @@ Monitor.prototype._refreshProcs = function () {
});
// Emit to client.
this._broadcast('procs', this._procs);
- }.bind(this))
+ }, this)
};
/**
@@ -521,4 +477,14 @@ Monitor.prototype._pm2Ver = function (socket) {
*/
Monitor.prototype._broadcast = function (event, data, nsp) {
this._sockio.of(nsp || conf.NSP.SYS).emit(event, data);
+ /*
+ var socks = this._sockio.of(nsp || conf.NSP.SYS).sockets;
+ if (!socks || socks.length == 0) {
+ return;
+ }
+ socks.forEach(function (sock) {
+ if (sock._pm_id == data.pm_id) {
+ sock.emit(event, data)
+ }
+ });*/
};
diff --git a/lib/pm.js b/lib/pm.js
index bf3292e..04ea1d0 100644
--- a/lib/pm.js
+++ b/lib/pm.js
@@ -1,12 +1,12 @@
var spawn = require('child_process').spawn,
- fs = require('fs'),
- path = require('path'),
- _ = require('lodash'),
- async = require('async'),
- chalk = require('chalk'),
- stat = require('./stat'),
- rpc = require('pm2-axon-rpc'),
- axon = require('pm2-axon');
+ fs = require('fs'),
+ path = require('path'),
+ _ = require('lodash'),
+ async = require('async'),
+ chalk = require('chalk'),
+ stat = require('./stat'),
+ rpc = require('pm2-axon-rpc'),
+ axon = require('pm2-axon');
/**
* Forever lib.
@@ -14,32 +14,33 @@ var spawn = require('child_process').spawn,
*/
var pm = module.exports = {};
-var re_blank = /^[\s\r\t]*$/,
- allowedEvents = ['start', 'restart', 'exit', 'online'];
+var re_blank = /^[\s\r\t]*$/,
+ allowedEvents = ['start', 'restart', 'exit', 'online'];
/**
* Subscribe event BUS.
* @param {String} sockPath
* @param {Function} cb
+ * @param {Object} context
*/
-pm.sub = function(sockPath, cb){
+pm.sub = function (sockPath, cb, context) {
var sub = axon.socket('sub-emitter');
- sub.connect(sockPath);
-
// Once awake from sleeping.
- sub.on('log:*', function(e, d){
+ sub.on('log:*', function (e, d) {
// Do not subscribe it.
sub.off('log:*');
d.event = 'awake';
- cb(d);
+ cb.call(context, d);
});
// Process events.
- sub.on('process:*', function(e, d){
+ sub.on('process:*', function (e, d) {
if (d && !!~allowedEvents.indexOf(d.event)) {
- cb(d);
+ cb.call(context, d);
}
});
+ sub.connect(sockPath);
+ return sub;
};
/**
@@ -47,10 +48,10 @@ pm.sub = function(sockPath, cb){
* @param {String} sockPath
* @param {Function} cb
*/
-pm.version = function(sockPath, cb){
+pm.version = function (sockPath, cb) {
pm._rpc({
sockPath: sockPath,
- events : [
+ events: [
['getVersion', {}, cb]
]
});
@@ -60,16 +61,18 @@ pm.version = function(sockPath, cb){
* List available processes.
* @param {String} sockPath
* @param {Function} cb
+ * @param {Object} context
*/
-pm.list = function(sockPath, cb){
+pm.list = function (sockPath, cb, context) {
if (!fs.existsSync(sockPath)) {
- return cb(null, []);
+ return cb.call(context, []);
}
pm._rpc({
sockPath: sockPath,
- events : [
+ events: [
['getMonitorData', {}, cb]
- ]
+ ],
+ context: context || this
});
};
@@ -77,42 +80,43 @@ pm.list = function(sockPath, cb){
* Execute remote RPC events.
* @param {Object} opts including:
* {String} sockPath
+ * {Object} context
* {Object} args
* {Object} events
* key: event name
* value: callback function
* @private
*/
-pm._rpc = function(opts){
+pm._rpc = function (opts) {
var req = axon.socket("req"),
- rpc_sock = req.connect(opts.sockPath),
- rpc_client = new rpc.Client(req);
+ rpcSock = req.connect(opts.sockPath),
+ rpcClient = new rpc.Client(req);
// Connect RPC server.
- rpc_sock.on('connect', function(){
+ rpcSock.on('connect', function () {
// Execute request.
- var waterfalls = opts.events.map(function(event){
- return function(next){
+ var waterfalls = opts.events.map(function (event) {
+ return function (next) {
var cb = typeof event[event.length - 1] == 'function' ? event.pop() : null;
if (cb) {
- event.push(function(){
+ event.push(function () {
// Wrap arguments, no [].slice (avoid leak)!!!
var args = new Array(arguments.length);
for (var i = 0; i < args; i++) {
args[i] = arguments[i];
}
- cb.apply(null, arguments);
+ cb.apply(opts.context, arguments);
next();
});
}
- rpc_client.call.apply(rpc_client, event);
+ rpcClient.call.apply(rpcClient, event);
if (!cb) {
next();
}
};
});
- async.waterfall(waterfalls, function(err, res){
- rpc_sock.close();
+ async.waterfall(waterfalls, function (err, res) {
+ rpcSock.close();
});
});
};
@@ -124,8 +128,8 @@ pm._rpc = function(opts){
* @param {Function} cb
* @private
*/
-pm._findById = function(sockPath, id, cb){
- pm.list(sockPath, function(err, procs){
+pm._findById = function (sockPath, id, cb) {
+ pm.list(sockPath, function (err, procs) {
if (err) {
return cb(err);
}
@@ -133,7 +137,7 @@ pm._findById = function(sockPath, id, cb){
return cb(new Error('No PM2 process running, the sockPath is "' + sockPath + '", please make sure it is existing!'));
}
- var proc = _.find(procs, function(p){
+ var proc = _.find(procs, function (p) {
return p && p.pm_id == id;
});
@@ -151,9 +155,9 @@ pm._findById = function(sockPath, id, cb){
* @param {String} id
* @param {Function} cb
*/
-pm.action = function(sockPath, action, id, cb){
+pm.action = function (sockPath, action, id, cb) {
if (id == 'all') {
- pm.list(sockPath, function(err, procs){
+ pm.list(sockPath, function (err, procs) {
if (err) {
return cb(err);
}
@@ -161,12 +165,12 @@ pm.action = function(sockPath, action, id, cb){
return cb(new Error('No PM2 process running, the sockPath is "' + sockPath + '", please make sure it is existing!'));
}
- async.map(procs, function(proc, next){
+ async.map(procs, function (proc, next) {
pm._actionByPMId(sockPath, proc, action, next.bind(null, null));
}, cb);
});
} else {
- pm._findById(sockPath, id, function(err, proc){
+ pm._findById(sockPath, id, function (err, proc) {
if (err) {
return cb(err);
}
@@ -183,30 +187,33 @@ pm.action = function(sockPath, action, id, cb){
* @param {Function} cb
* @private
*/
-pm._actionByPMId = function(sockPath, proc, action, cb){
+pm._actionByPMId = function (sockPath, proc, action, cb) {
var noBusEvent = action == 'delete' && proc.pm2_env.status != 'online',
- pm_id = proc.pm_id;
+ 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');
watchEvent.pop();
}
- var actionEvent = [action, pm_id, function(err, sock){
+ var actionEvent = [action, pm_id, function (err, sock) {
cb(err, noBusEvent);
}];
if (action == 'restartProcessId') {
- actionEvent.splice(1, 1, {id: pm_id});
+ actionEvent.splice(1, 1, {
+ id: pm_id
+ });
}
pm._rpc({
sockPath: sockPath,
- events : [
+ events: [
watchEvent,
actionEvent
]
@@ -220,9 +227,9 @@ pm._actionByPMId = function(sockPath, proc, action, cb){
* @param {Function} cb
* @returns {*}
*/
-pm.tail = function(opts, each, cb){
+pm.tail = function (opts, each, cb) {
// Fetch the proccess that we need.
- pm._findById(opts.sockPath, opts.pm_id, function(err, proc){
+ pm._findById(opts.sockPath, opts.pm_id, function (err, proc) {
if (err) {
return cb(err);
}
@@ -239,8 +246,10 @@ pm.tail = function(opts, each, cb){
* @returns {*}
* @private
*/
-pm._tailLogs = function(proc, cb){
- var logs = [['PM2', proc.pm2_log]];
+pm._tailLogs = function (proc, cb) {
+ var logs = [
+ ['PM2', proc.pm2_log]
+ ];
if (proc.pm_log_path) {
logs.push(['entire', proc.pm2_env.pm_log_path]);
} else {
@@ -252,7 +261,7 @@ pm._tailLogs = function(proc, cb){
paths.push(['err', proc.pm2_env.pm_err_log_path]);
}
- paths = paths.sort(function(a, b) {
+ paths = paths.sort(function (a, b) {
return (fs.existsSync(a[1]) ? fs.statSync(a[1]).mtime.valueOf() : 0) -
(fs.existsSync(b[1]) ? fs.statSync(b[1]).mtime.valueOf() : 0);
});
@@ -260,7 +269,7 @@ pm._tailLogs = function(proc, cb){
}
var tails = [];
- (function tailLog(ls){
+ (function tailLog(ls) {
var log = ls.shift();
if (!log) {
return;
@@ -270,22 +279,23 @@ pm._tailLogs = function(proc, cb){
return;
}
var key = log[0],
- prefix = chalk[key == 'err' ? 'red' : 'green'].bold('[' + key + ']');
+ prefix = chalk[key == 'err' ? 'red' : 'green'].bold('[' + key + ']');
var tail = spawn('tail', ['-f', '-n', 10, logPath], {
killSignal: 'SIGTERM',
- stdio : [null, 'pipe', 'pipe']
+ stdio: [null, 'pipe', 'pipe']
});
// Use utf8 encoding.
- tail.stdio.forEach(function(stdio){
+ tail.stdio.forEach(function (stdio) {
stdio.setEncoding('utf8');
});
// stdout.
- tail.stdout.on('data', function(data){
- var lines = [], _lines = data.split(/\n/);
- _lines.forEach(function(line){
+ tail.stdout.on('data', function (data) {
+ var lines = [],
+ _lines = data.split(/\n/);
+ _lines.forEach(function (line) {
if (!re_blank.test(line)) {
lines.push(prefix + ' ' + line);
}
@@ -296,7 +306,7 @@ pm._tailLogs = function(proc, cb){
});
// handle error.
- tail.stderr.on('data', function(data){
+ tail.stderr.on('data', function (data) {
tail.disconnect();
cb(new Error(data.toString().replace(/\n/, '')));
});
@@ -304,4 +314,4 @@ pm._tailLogs = function(proc, cb){
tailLog(ls);
})(logs);
return tails;
-};
\ No newline at end of file
+};
diff --git a/lib/stat.js b/lib/stat.js
index aef4453..3ff572a 100644
--- a/lib/stat.js
+++ b/lib/stat.js
@@ -1,4 +1,4 @@
-var os = require('os');
+var os = require('os');
/**
* System states
@@ -16,33 +16,33 @@ var stat = module.exports = {
/**
* Architecture, e.g.: 64, 32...
*/
- arch : os.arch(),
+ arch: os.arch(),
/**
* Ver number of system.
*/
- release : os.release(),
+ release: os.release(),
/**
* List all CPUs.
* @returns {*}
*/
- get cpus(){
+ get cpus() {
return os.cpus();
},
/**
* Uptime.
* @returns {*}
*/
- get uptime(){
+ get uptime() {
return os.uptime();
},
/**
* System memory usage.
* @returns {{free: *, total: *, percentage: number}}
*/
- get memory(){
+ get memory() {
return {
- free : os.freemem(),
- total : os.totalmem(),
+ free: os.freemem(),
+ total: os.totalmem(),
percentage: Math.round(100 * (1 - os.freemem() / os.totalmem()))
}
}
@@ -51,22 +51,24 @@ var stat = module.exports = {
/**
* System CPU usage percentage (total).
* @param fn
+ * @param context
*/
-stat.cpuUsage = function(fn){
- setTimeout(function(ctx, stat1){
+stat.cpuUsage = function (fn, context) {
+ setTimeout(function (ctx, stat1) {
var stat2 = ctx.cpuInfo(),
- perc = 100 * (1 - (stat2.idle - stat1.idle) / (stat2.total - stat1.total));
- fn(null, perc.toFixed(2));
+ perc = 100 * (1 - (stat2.idle - stat1.idle) / (stat2.total - stat1.total));
+ fn.call(context, perc.toFixed(2));
}, 1000, this, this.cpuInfo());
};
/**
* System CPU usage detail information.
- * @param fn
* @returns {{idle: number, total: number}}
*/
-stat.cpuInfo = function(fn){
- var cpus = this.cpus, idle = 0, total = 0;
+stat.cpuInfo = function () {
+ var cpus = this.cpus,
+ idle = 0,
+ total = 0;
for (var i in cpus) {
idle += cpus[i].times.idle;
for (var k in cpus[i].times) {
@@ -74,7 +76,7 @@ stat.cpuInfo = function(fn){
}
}
return {
- 'idle' : idle,
+ 'idle': idle,
'total': total
};
-};
\ No newline at end of file
+};
diff --git a/lib/util/conf.js b/lib/util/conf.js
index 091d7d4..b7c72c2 100644
--- a/lib/util/conf.js
+++ b/lib/util/conf.js
@@ -1,6 +1,20 @@
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'
+};
+
+/**
+ * Configurations
+ * @type {[type]}
+ */
exports.File = File;
/**
@@ -54,7 +68,6 @@ File.prototype.loadSync = function () {
if (re_comment.test(line)) {
return;
}
-
var ms;
// Sections.
if ((ms = line.match(re_setion)) && ms.length == 2) {
diff --git a/lib/util/debug.js b/lib/util/debug.js
deleted file mode 100644
index 7356505..0000000
--- a/lib/util/debug.js
+++ /dev/null
@@ -1,65 +0,0 @@
-var chalk = require('chalk'),
- _ = require('lodash');
-
-module.exports = Debug;
-
-/**
- * Simple debug tool.
- * @param {Object} options
- * @returns {Debug}
- * @constructor
- */
-function Debug(options) {
- if (!(this instanceof Debug)) {
- return new Debug(options);
- }
- if (typeof options == 'string') {
- options = {
- namespace: options
- };
- }
- this.options = _.defaults(options || {}, {
- namespace: 'pm2-gui',
- timestamp: true,
- debug: false
- });
-}
-Debug.prototype._l = function (level, args) {
- if(!this.options.debug){
- return;
- }
- args = _.values(args);
-
- var prints = [chalk.bgBlack.grey(this.options.namespace)];
- var prefix, color;
- switch (level) {
- case 'e':
- prefix = 'ERR!', color = 'red';
- break;
- case 'w':
- prefix = 'warn', color = 'yellow';
- break;
- case 'd':
- if(this.options.timestamp){
- prints.push(chalk.underline.dim((new Date()).toISOString()))
- }
- break;
- default :
- prefix = args.splice(0, 1), color = 'green';
- break;
- }
- if(prefix && color){
- prints.splice(2, 0, chalk.bgBlack[color](prefix));
- }
- prints.push(args.join(' '));
- console.log.apply(null, prints);
-};
-
-/**
- * Loggers: info, error, debug, log, warn.
- */
-['i', 'e', 'd', 'l', 'w'].forEach(function(s){
- Debug.prototype[s] = function(){
- this._l.call(this, s, arguments);
- };
-});
\ No newline at end of file
diff --git a/lib/util/log.js b/lib/util/log.js
new file mode 100644
index 0000000..1b00ec0
--- /dev/null
+++ b/lib/util/log.js
@@ -0,0 +1,39 @@
+var chalk = require('chalk');
+
+module.exports = function (options) {
+ if (console.__hacked) {
+ return;
+ }
+ options = options || {};
+ var hacks = ['debug', 'log', 'info', 'warn', 'error'],
+ colors = ['grey', '', 'green', 'yellow', 'red'],
+ consoled = {},
+ lev = options.level;
+
+ if ((typeof lev == 'string' && !(lev = hacks[lev])) || (isFinite(lev) && (lev < 0 || lev > hacks.length))) {
+ options.level = 0;
+ }
+
+ hacks.forEach(function (method) {
+ if (method == 'debug') {
+ consoled.debug = console.log;
+ return;
+ }
+ consoled[method] = console[method];
+ });
+
+ hacks.forEach(function (method, index) {
+ console[method] = function () {
+ if (index < options.level) {
+ return;
+ }
+ if (method != 'log' && arguments.length > 0) {
+ arguments[0] = (options.prefix ? chalk.bold[colors[index]]('[' + method.toUpperCase() + '] ') : '') +
+ (options.date ? (new Date()).toLocaleString() + ' ' : '') + arguments[0];
+ }
+ consoled[method].apply(console, arguments);
+ };
+ });
+
+ console.__hacked = true;
+};
diff --git a/package.json b/package.json
index 2769bf3..cd5093d 100644
--- a/package.json
+++ b/package.json
@@ -2,11 +2,12 @@
"name": "pm2-gui",
"version": "0.1.1",
"description": "An elegant web & terminal interface for Unitech/PM2.",
+ "main": "./pm2-gui.js",
"scripts": {
"test": "NODE_ENV=test bash test/index.sh"
},
"bin": {
- "pm2-gui": "bin/pm2-gui"
+ "pm2-gui": "./pm2-gui"
},
"repository": {
"type": "git",
@@ -34,24 +35,23 @@
"node >= 0.8.0"
],
"dependencies": {
- "async": "~0.9.0",
- "lodash": "~2.4.1",
- "commander": "~2.5.0",
- "chalk": "~1.0.0",
- "express": "~4.10.1",
+ "async": "~1.4.2",
+ "lodash": "~3.10.1",
+ "commander": "~2.9.0",
+ "chalk": "~1.1.0",
+ "express": "~4.13.3",
+ "express-session": "~1.12.1",
"swig": "~1.4.2",
- "windows-cpu": "~0.1.1",
- "socket.io": "~1.2.0",
+ "windows-cpu": "~0.1.4",
+ "socket.io": "~1.3.7",
"pm2-axon": "~2.0.7",
"pm2-axon-rpc": "~0.3.6",
- "ansi-html": "~0.0.4",
- "express-session": "~1.9.3",
- "pidusage": "~0.1.0",
- "blessed": "^0.1.60",
- "socket.io-client": "^1.3.5"
- },
- "devDependencies": {
+ "ansi-html": "~0.0.5",
+ "pidusage": "~1.0.0",
+ "blessed": "^0.1.81",
+ "socket.io-client": "^1.3.7"
},
+ "devDependencies": {},
"readmeFilename": "README.md",
"homepage": "https://github.com/Tjatse/pm2-gui"
}
diff --git a/pm2-gui b/pm2-gui
new file mode 100755
index 0000000..8612148
--- /dev/null
+++ b/pm2-gui
@@ -0,0 +1,123 @@
+#!/bin/bash
+node="$(which node)"
+pidfile="pm2-gui.pid"
+prefixINFO="\033[1;32m[INFO]\033[0m"
+prefixWARN="\033[1;33m[WARNING]\033[0m"
+
+function isRunning() {
+ if [ ! -f $pidfile ];
+ then
+ return 0;
+ fi
+ if [ ! -s $pidfile ];
+ then
+ rm $pidfile;
+ return 0;
+ fi
+ if test $(ps -p $(cat $pidfile) | wc -l) -gt 1;
+ then
+ return 1;
+ fi
+ rm $pidfile;
+ return 0;
+}
+
+function status() {
+ isRunning;
+ if [ 0 -eq $? ];
+ then
+ echo -e "$prefixWARN \033[31m✘ stopped\033[0m";
+ else
+ echo -e "$prefixINFO \033[32m✔ running\033[0m";
+ fi
+}
+
+function usage () {
+ cat <<-EOF
+
+ Usage: $0 [options]
+
+ Commands:
+ start [config_file] start the service
+ stop stop the running service
+ restart restart the service
+ mon run the curses-like dashboard in terminal
+ logs [log_directory] view the logs
+
+ Examples:
+ $0 start
+ $0 start /path/to/my-pm2-gui.ini
+ $0 mon
+ $0 logs
+ $0 logs /path/to/logs
+
+EOF
+}
+
+case "$1" in
+ start)
+ isRunning;
+ if [ 0 -eq $? ];
+ then
+ echo -e "$prefixINFO Starting..."
+ $node ./lib/daemon "$@"
+ sleep 1;
+ fi
+ status;
+ ;;
+
+ stop)
+ isRunning;
+ if [ 0 -eq $? ];
+ then
+ echo -e "$prefixWARN Already stopped.";
+ else
+ echo -e "$prefixINFO Stopping..."
+ kill $(cat $pidfile);
+ sleep 1;
+ status;
+ fi
+ ;;
+
+ restart)
+ isRunning;
+ if [ 0 -eq $? ];
+ then
+ echo -e "$prefixWARN PID not found, no need to stop anything.";
+ $node ./lib/daemon "$@"
+ else
+ kill -SIGHUP $(cat $pidfile);
+ fi
+ sleep 1;
+ status;
+ ;;
+
+ logs)
+ out="pm2-gui.out";
+ err="pm2-gui.err";
+ if [ ! -n "$2" ];
+ then
+ out="./logs/$out";
+ err="./logs/$err";
+ else
+ out="$2/$out";
+ err="$2/$err";
+ fi
+ if [ ! -f $out ] && [ ! -f $err ];
+ then
+ echo -e "$prefixWARN Logs can not be found in directory \033[1m$2\033[0m.";
+ else
+ echo -e "$prefixINFO Logs from \033[1m$out\033[0m and \033[1m$err\033[0m:";
+ tail -n 20 -F $out $err;
+ fi
+ ;;
+
+ status)
+ status;
+ ;;
+
+ *)
+ usage "$0";
+ exit 1
+ ;;
+esac
\ No newline at end of file
diff --git a/pm2-gui.ini b/pm2-gui.ini
index 642d6cd..1a372d8 100644
--- a/pm2-gui.ini
+++ b/pm2-gui.ini
@@ -1,4 +1,11 @@
-refresh = 5000
+node = LocalPM2
pm2 = ~/.pm2
-debug = true
-port = 8088
\ No newline at end of file
+refresh = 5000
+port = 8088
+daemonize = false
+
+[log]
+dir = ./logs
+prefix = true
+date = false
+level = debug
\ No newline at end of file
diff --git a/pm2-gui.js b/pm2-gui.js
new file mode 100644
index 0000000..7df1bbb
--- /dev/null
+++ b/pm2-gui.js
@@ -0,0 +1,96 @@
+var Monitor = require('./lib/monitor'),
+ chalk = require('chalk'),
+ path = require('path'),
+ fs = require('fs'),
+ _ = require('lodash'),
+ Log = require('./lib/util/log');
+
+exports.start = function (options) {
+ process.title = 'pm2-gui slave';
+ options = options || {};
+ var confFile = options.confFile;
+ if (!confFile) {
+ confFile = './pm2-gui.ini';
+ if (process.argv.length > 2) {
+ confFile = process.argv[2];
+ }
+ confFile = path.resolve(__dirname, confFile);
+
+ if (!fs.existsSync(confFile)) {
+ console.error(chalk.bold(confFile), 'does not exist!');
+ return process.exit(0);
+ }
+ }
+ var monitor = Monitor({
+ confFile: confFile
+ });
+
+ Log(monitor.options.log);
+
+ console.log(chalk.cyan(
+ '\n' +
+ '█▀▀█ █▀▄▀█ █▀█ ░░ █▀▀▀ █░░█ ░▀░\n' +
+ '█░░█ █░▀░█ ░▄▀ ▀▀ █░▀█ █░░█ ▀█▀\n' +
+ '█▀▀▀ ▀░░░▀ █▄▄ ░░ ▀▀▀▀ ░▀▀▀ ▀▀▀\n'));
+ monitor.run();
+
+ process.on('SIGTERM', shutdown);
+ process.on('SIGINT', shutdown);
+ process.on('SIGHUP', restart);
+ process.on('uncaughtException', caughtException);
+ process.on('exit', exports.exitGraceful)
+
+ function shutdown(code, signal) {
+ console.info('Shutting down....');
+ monitor.quit();
+ console.info('Both', chalk.bold('pm2-emitter'), 'and', chalk.bold('statsd dgram'), 'sockets are closed.');
+ console.info('Shutdown complete!');
+ exports.exitGraceful(code, '-f');
+ }
+
+ function restart() {
+ if (process.send) {
+ process.send({
+ action: 'restart'
+ });
+ } else {
+ console.error('No IPC found, could not restart monitor, shutting down.');
+ shutdown(1);
+ }
+ }
+
+ function caughtException(err) {
+ console.error(err.stack);
+ shutdown(1);
+ }
+};
+
+exports.exitGraceful = function exit(code, signal) {
+ code = code || 0;
+ if (signal != '-f') {
+ console.debug('Slave has exited, code: ' + code + ', signal: ' + (signal || 'NULL'));
+ }
+ var fds = 0;
+
+ function tryToExit() {
+ if ((fds & 1) && (fds & 2)) {
+ process.exit(code);
+ }
+ }
+
+ [process.stdout, process.stderr].forEach(function (std) {
+ var fd = std.fd;
+ if (!std.bufferSize) {
+ fds = fds | fd;
+ } else {
+ std.write && std.write('', function () {
+ fds = fds | fd;
+ tryToExit();
+ });
+ }
+ });
+ tryToExit();
+};
+if (path.basename(process.mainModule.filename, '.js') == 'pm2-ant') {
+ exports.start();
+}
diff --git a/test/fixtures/conf.js b/test/fixtures/conf.js
deleted file mode 100644
index 023ae62..0000000
--- a/test/fixtures/conf.js
+++ /dev/null
@@ -1,43 +0,0 @@
-/**
- * Overidde PM2 configuration
- */
-
-var p = require('path');
-
-module.exports = function(DEFAULT_HOME) {
-
- if (!DEFAULT_HOME)
- return false;
-
- var PM2_HOME = DEFAULT_HOME;
-
- var pm2_conf = {
- PM2_HOME : PM2_HOME,
-
- PM2_LOG_FILE_PATH : p.join(PM2_HOME, 'pm2.log'),
- PM2_PID_FILE_PATH : p.join(PM2_HOME, 'pm2.pid'),
-
- DEFAULT_PID_PATH : p.join(PM2_HOME, 'pids'),
- DEFAULT_LOG_PATH : p.join(PM2_HOME, 'logs'),
- DUMP_FILE_PATH : p.join(PM2_HOME, 'dump.pm2'),
-
- DAEMON_RPC_PORT : p.join(PM2_HOME, 'rpc.sock'),
- DAEMON_PUB_PORT : p.join(PM2_HOME, 'pub.sock'),
- INTERACTOR_RPC_PORT : p.join(PM2_HOME, 'interactor.sock'),
-
- GRACEFUL_TIMEOUT : parseInt(process.env.PM2_GRACEFUL_TIMEOUT) || 8000,
- GRACEFUL_LISTEN_TIMEOUT : parseInt(process.env.PM2_GRACEFUL_LISTEN_TIMEOUT) || 4000,
-
- DEBUG : process.env.PM2_DEBUG || false,
- WEB_INTERFACE : parseInt(process.env.PM2_API_PORT) || 9615,
- MODIFY_REQUIRE : process.env.PM2_MODIFY_REQUIRE || false,
-
- PM2_LOG_DATE_FORMAT : process.env.PM2_LOG_DATE_FORMAT !== undefined ? process.env.PM2_LOG_DATE_FORMAT : 'YYYY-MM-DD HH:mm:ss',
-
- INTERACTOR_LOG_FILE_PATH : p.join(PM2_HOME, 'agent.log'),
- INTERACTOR_PID_PATH : p.join(PM2_HOME, 'agent.pid'),
- INTERACTION_CONF : p.join(PM2_HOME, 'agent.json5')
- };
-
- return pm2_conf || null;
-};
diff --git a/test/fixtures/startup.json b/test/process.json
similarity index 100%
rename from test/fixtures/startup.json
rename to test/process.json
diff --git a/web/index.js b/web/index.js
index bfaf571..cbaa22b 100644
--- a/web/index.js
+++ b/web/index.js
@@ -2,7 +2,7 @@ var express = require('express'),
swig = require('swig'),
path = require('path'),
chalk = require('chalk'),
- Monitor = require('../lib/mon'),
+ Monitor = require('../lib/monitor'),
Debug = require('../lib/util/debug'),
session = require('express-session');