clean house & refactor

This commit is contained in:
Tjatse 2015-12-25 16:01:50 +08:00
parent 59e000249d
commit 31e65020b5
19 changed files with 660 additions and 603 deletions

4
.gitignore vendored
View File

@ -1 +1,3 @@
node_modules
node_modules
logs
*.pid

View File

@ -1,3 +1,5 @@
node_modules
.gitignore
screenshots
screenshots
logs
*.pid

View File

@ -5,3 +5,4 @@ branches:
node_js:
- 0.10
- 0.11
- 0.12

View File

@ -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
```
<a name="daemonic" />
## Daemonic
<a name="daemonize" />
## 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 <key> <value>
```
Example
```bash
$ pm2-gui set refresh 2000
```
Above command will set `refresh` to 2 seconds.
### Remove
Usage
```bash
$ pm2-gui rm <key>
```
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.
<a name="auth" />
# 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
```
<a name="ui" />
# UI/UX
- Amazing and smooth animations.

View File

@ -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 <key> <value>')
.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 <key>')
.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);
}

144
lib/daemon.js Normal file
View File

@ -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);
}
});

View File

@ -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) : '<span style="color: #ff0000">Error: ' + err.message + '</span>'
});
};
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: '<span style="color: #ff0000">Error: ' + err.message + '</span>'
});
};
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)
}
});*/
};

134
lib/pm.js
View File

@ -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;
};
};

View File

@ -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
};
};
};

View File

@ -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) {

View File

@ -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);
};
});

39
lib/util/log.js Normal file
View File

@ -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;
};

View File

@ -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"
}

123
pm2-gui Executable file
View File

@ -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 <cmd> [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

View File

@ -1,4 +1,11 @@
refresh = 5000
node = LocalPM2
pm2 = ~/.pm2
debug = true
port = 8088
refresh = 5000
port = 8088
daemonize = false
[log]
dir = ./logs
prefix = true
date = false
level = debug

96
pm2-gui.js Normal file
View File

@ -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();
}

43
test/fixtures/conf.js vendored
View File

@ -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;
};

View File

@ -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');