mirror of https://github.com/mthenw/frontail.git
parent
432716822d
commit
31e25acce6
238
.eslintrc.js
238
.eslintrc.js
|
@ -1,238 +0,0 @@
|
|||
module.exports = {
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"mocha": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"rules": {
|
||||
"accessor-pairs": "error",
|
||||
"array-bracket-spacing": "error",
|
||||
"array-callback-return": "error",
|
||||
"arrow-body-style": "error",
|
||||
"arrow-parens": "error",
|
||||
"arrow-spacing": "error",
|
||||
"block-scoped-var": "error",
|
||||
"block-spacing": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"brace-style": [
|
||||
"error",
|
||||
"1tbs",
|
||||
{
|
||||
"allowSingleLine": true
|
||||
}
|
||||
],
|
||||
"callback-return": "error",
|
||||
"camelcase": "error",
|
||||
"comma-spacing": [
|
||||
"error",
|
||||
{
|
||||
"after": true,
|
||||
"before": false
|
||||
}
|
||||
],
|
||||
"comma-style": [
|
||||
"error",
|
||||
"last"
|
||||
],
|
||||
"complexity": "error",
|
||||
"computed-property-spacing": [
|
||||
"error",
|
||||
"never"
|
||||
],
|
||||
"consistent-return": "error",
|
||||
"consistent-this": "off",
|
||||
"curly": "error",
|
||||
"default-case": "error",
|
||||
"dot-location": [
|
||||
"error",
|
||||
"property"
|
||||
],
|
||||
"dot-notation": [
|
||||
"error",
|
||||
{
|
||||
"allowKeywords": true
|
||||
}
|
||||
],
|
||||
"eol-last": "error",
|
||||
"eqeqeq": "error",
|
||||
"func-names": "off",
|
||||
"func-style": [
|
||||
"error",
|
||||
"expression"
|
||||
],
|
||||
"generator-star-spacing": "error",
|
||||
"global-require": "off",
|
||||
"guard-for-in": "error",
|
||||
"handle-callback-err": "off",
|
||||
"id-blacklist": "error",
|
||||
"id-length": "off",
|
||||
"id-match": "error",
|
||||
"indent": "error",
|
||||
"init-declarations": "off",
|
||||
"jsx-quotes": "error",
|
||||
"key-spacing": "error",
|
||||
"keyword-spacing": [
|
||||
"error",
|
||||
{
|
||||
"after": true,
|
||||
"before": true
|
||||
}
|
||||
],
|
||||
"linebreak-style": [
|
||||
"error",
|
||||
"unix"
|
||||
],
|
||||
"lines-around-comment": "off",
|
||||
"max-depth": "error",
|
||||
"max-len": "off",
|
||||
"max-nested-callbacks": "error",
|
||||
"max-params": "off",
|
||||
"max-statements": "off",
|
||||
"max-statements-per-line": "error",
|
||||
"new-cap": "error",
|
||||
"new-parens": "error",
|
||||
"newline-after-var": "off",
|
||||
"newline-before-return": "off",
|
||||
"newline-per-chained-call": "off",
|
||||
"no-alert": "error",
|
||||
"no-array-constructor": "error",
|
||||
"no-bitwise": "error",
|
||||
"no-caller": "error",
|
||||
"no-catch-shadow": "error",
|
||||
"no-confusing-arrow": "error",
|
||||
"no-continue": "error",
|
||||
"no-console": "off",
|
||||
"no-div-regex": "error",
|
||||
"no-duplicate-imports": "error",
|
||||
"no-else-return": "off",
|
||||
"no-empty-function": "off",
|
||||
"no-eq-null": "error",
|
||||
"no-eval": "error",
|
||||
"no-extend-native": "error",
|
||||
"no-extra-bind": "error",
|
||||
"no-extra-label": "error",
|
||||
"no-extra-parens": "error",
|
||||
"no-floating-decimal": "error",
|
||||
"no-implicit-globals": "off",
|
||||
"no-implied-eval": "error",
|
||||
"no-inline-comments": "error",
|
||||
"no-inner-declarations": [
|
||||
"error",
|
||||
"functions"
|
||||
],
|
||||
"no-invalid-this": "off",
|
||||
"no-iterator": "error",
|
||||
"no-label-var": "error",
|
||||
"no-labels": "error",
|
||||
"no-lone-blocks": "error",
|
||||
"no-lonely-if": "error",
|
||||
"no-loop-func": "error",
|
||||
"no-magic-numbers": "off",
|
||||
"no-mixed-requires": "error",
|
||||
"no-multi-spaces": "off",
|
||||
"no-multi-str": "error",
|
||||
"no-multiple-empty-lines": "error",
|
||||
"no-native-reassign": "error",
|
||||
"no-negated-condition": "error",
|
||||
"no-nested-ternary": "error",
|
||||
"no-new": "error",
|
||||
"no-new-func": "error",
|
||||
"no-new-object": "error",
|
||||
"no-new-require": "error",
|
||||
"no-new-wrappers": "error",
|
||||
"no-octal-escape": "error",
|
||||
"no-param-reassign": "off",
|
||||
"no-path-concat": "off",
|
||||
"no-plusplus": "error",
|
||||
"no-process-env": "error",
|
||||
"no-process-exit": "off",
|
||||
"no-proto": "error",
|
||||
"no-redeclare": [
|
||||
"error",
|
||||
{
|
||||
"builtinGlobals": false
|
||||
}
|
||||
],
|
||||
"no-restricted-globals": "error",
|
||||
"no-restricted-imports": "error",
|
||||
"no-restricted-modules": "error",
|
||||
"no-restricted-syntax": "error",
|
||||
"no-return-assign": "error",
|
||||
"no-script-url": "error",
|
||||
"no-self-compare": "error",
|
||||
"no-sequences": "error",
|
||||
"no-shadow": "error",
|
||||
"no-shadow-restricted-names": "error",
|
||||
"no-spaced-func": "error",
|
||||
"no-sync": "off",
|
||||
"no-ternary": "error",
|
||||
"no-throw-literal": "error",
|
||||
"no-trailing-spaces": "error",
|
||||
"no-undef-init": "error",
|
||||
"no-undefined": "error",
|
||||
"no-underscore-dangle": "off",
|
||||
"no-unmodified-loop-condition": "error",
|
||||
"no-unneeded-ternary": "error",
|
||||
"no-unsafe-finally": "error",
|
||||
"no-use-before-define": "off",
|
||||
"no-useless-call": "error",
|
||||
"no-useless-computed-key": "error",
|
||||
"no-useless-concat": "error",
|
||||
"no-useless-constructor": "error",
|
||||
"no-useless-escape": "error",
|
||||
"no-var": "off",
|
||||
"no-void": "error",
|
||||
"no-warning-comments": "error",
|
||||
"no-whitespace-before-property": "error",
|
||||
"no-with": "error",
|
||||
"object-curly-spacing": [
|
||||
"error",
|
||||
"never"
|
||||
],
|
||||
"object-shorthand": "off",
|
||||
"one-var": "off",
|
||||
"one-var-declaration-per-line": "error",
|
||||
"operator-assignment": "error",
|
||||
"operator-linebreak": "error",
|
||||
"padded-blocks": "off",
|
||||
"prefer-arrow-callback": "off",
|
||||
"prefer-const": "error",
|
||||
"prefer-reflect": "off",
|
||||
"prefer-rest-params": "error",
|
||||
"prefer-spread": "error",
|
||||
"prefer-template": "off",
|
||||
"quote-props": "off",
|
||||
"quotes": [
|
||||
"error",
|
||||
"single"
|
||||
],
|
||||
"radix": "error",
|
||||
"require-jsdoc": "off",
|
||||
"require-yield": "error",
|
||||
"semi": "error",
|
||||
"semi-spacing": "error",
|
||||
"sort-imports": "error",
|
||||
"sort-vars": "error",
|
||||
"space-before-blocks": "error",
|
||||
"space-before-function-paren": "error",
|
||||
"space-in-parens": [
|
||||
"error",
|
||||
"never"
|
||||
],
|
||||
"space-infix-ops": "error",
|
||||
"space-unary-ops": "error",
|
||||
"spaced-comment": "error",
|
||||
"template-curly-spacing": "error",
|
||||
"vars-on-top": "off",
|
||||
"wrap-iife": ["error", "inside"],
|
||||
"wrap-regex": "error",
|
||||
"yield-star-spacing": "error",
|
||||
"yoda": [
|
||||
"error",
|
||||
"never"
|
||||
]
|
||||
}
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"extends": "airbnb-base",
|
||||
"rules": {
|
||||
"no-console": "off",
|
||||
"strict": "off"
|
||||
},
|
||||
"env": {
|
||||
"node": true
|
||||
}
|
||||
}
|
200
index.js
200
index.js
|
@ -1,122 +1,134 @@
|
|||
'use strict';
|
||||
|
||||
var connect = require('connect');
|
||||
var cookieParser = require('cookie');
|
||||
var crypto = require('crypto');
|
||||
var path = require('path');
|
||||
var socketio = require('socket.io');
|
||||
var tail = require('./lib/tail');
|
||||
var connectBuilder = require('./lib/connect_builder');
|
||||
var program = require('./lib/options_parser');
|
||||
var serverBuilder = require('./lib/server_builder');
|
||||
var daemonize = require('./lib/daemonize');
|
||||
const connect = require('connect');
|
||||
const cookieParser = require('cookie');
|
||||
const crypto = require('crypto');
|
||||
const path = require('path');
|
||||
const socketio = require('socket.io');
|
||||
const tail = require('./lib/tail');
|
||||
const connectBuilder = require('./lib/connect_builder');
|
||||
const program = require('./lib/options_parser');
|
||||
const serverBuilder = require('./lib/server_builder');
|
||||
const daemonize = require('./lib/daemonize');
|
||||
|
||||
/**
|
||||
* Parse args
|
||||
*/
|
||||
program.parse(process.argv);
|
||||
if (program.args.length === 0) {
|
||||
console.error('Arguments needed, use --help');
|
||||
process.exit();
|
||||
console.error('Arguments needed, use --help');
|
||||
process.exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate params
|
||||
*/
|
||||
var doAuthorization = !!(program.user && program.password);
|
||||
var doSecure = !!(program.key && program.certificate);
|
||||
var sessionSecret = String(+new Date()) + Math.random();
|
||||
var sessionKey = 'sid';
|
||||
var files = program.args.join(' ');
|
||||
var filesNamespace = crypto.createHash('md5').update(files).digest('hex');
|
||||
const doAuthorization = !!(program.user && program.password);
|
||||
const doSecure = !!(program.key && program.certificate);
|
||||
const sessionSecret = String(+new Date()) + Math.random();
|
||||
const sessionKey = 'sid';
|
||||
const files = program.args.join(' ');
|
||||
const filesNamespace = crypto.createHash('md5').update(files).digest('hex');
|
||||
|
||||
if (program.daemonize) {
|
||||
daemonize(__filename, program, {
|
||||
doAuthorization: doAuthorization,
|
||||
doSecure: doSecure
|
||||
});
|
||||
daemonize(__filename, program, {
|
||||
doAuthorization,
|
||||
doSecure,
|
||||
});
|
||||
} else {
|
||||
/**
|
||||
* HTTP(s) server setup
|
||||
*/
|
||||
var appBuilder = connectBuilder();
|
||||
if (doAuthorization) {
|
||||
appBuilder.session(sessionSecret, sessionKey);
|
||||
appBuilder.authorize(program.user, program.password);
|
||||
}
|
||||
appBuilder
|
||||
.static(__dirname + '/lib/web/assets')
|
||||
.index(__dirname + '/lib/web/index.html', files, filesNamespace, program.theme);
|
||||
/**
|
||||
* HTTP(s) server setup
|
||||
*/
|
||||
const appBuilder = connectBuilder();
|
||||
if (doAuthorization) {
|
||||
appBuilder.session(sessionSecret, sessionKey);
|
||||
appBuilder.authorize(program.user, program.password);
|
||||
}
|
||||
appBuilder
|
||||
.static(path.join(__dirname, 'lib/web/assets'))
|
||||
.index(path.join(__dirname, 'lib/web/index.html'), files, filesNamespace, program.theme);
|
||||
|
||||
var builder = serverBuilder();
|
||||
if (doSecure) {
|
||||
builder.secure(program.key, program.certificate);
|
||||
}
|
||||
var server = builder
|
||||
.use(appBuilder.build())
|
||||
.port(program.port)
|
||||
.host(program.host)
|
||||
.build();
|
||||
const builder = serverBuilder();
|
||||
if (doSecure) {
|
||||
builder.secure(program.key, program.certificate);
|
||||
}
|
||||
const server = builder
|
||||
.use(appBuilder.build())
|
||||
.port(program.port)
|
||||
.host(program.host)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* socket.io setup
|
||||
*/
|
||||
var io = socketio.listen(server, {log: false});
|
||||
/**
|
||||
* socket.io setup
|
||||
*/
|
||||
const io = socketio.listen(server, {
|
||||
log: false,
|
||||
});
|
||||
|
||||
if (doAuthorization) {
|
||||
io.use(function (socket, next) {
|
||||
var handshakeData = socket.request;
|
||||
if (handshakeData.headers.cookie) {
|
||||
var cookie = cookieParser.parse(handshakeData.headers.cookie);
|
||||
var sessionId = connect.utils.parseSignedCookie(cookie[sessionKey], sessionSecret);
|
||||
if (sessionId) {
|
||||
return next(null);
|
||||
}
|
||||
return next(new Error('Invalid cookie'), false);
|
||||
} else {
|
||||
return next(new Error('No cookie in header'), false);
|
||||
}
|
||||
});
|
||||
if (doAuthorization) {
|
||||
io.use((socket, next) => {
|
||||
const handshakeData = socket.request;
|
||||
if (handshakeData.headers.cookie) {
|
||||
const cookie = cookieParser.parse(handshakeData.headers.cookie);
|
||||
const sessionId = connect.utils.parseSignedCookie(cookie[sessionKey], sessionSecret);
|
||||
if (sessionId) {
|
||||
return next(null);
|
||||
}
|
||||
return next(new Error('Invalid cookie'), false);
|
||||
}
|
||||
|
||||
return next(new Error('No cookie in header'), false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup UI highlights
|
||||
*/
|
||||
let highlightConfig;
|
||||
if (program.uiHighlight) {
|
||||
highlightConfig = require(path.resolve(__dirname, program.uiHighlightPreset)); // eslint-disable-line
|
||||
}
|
||||
|
||||
/**
|
||||
* When connected send starting data
|
||||
*/
|
||||
const tailer = tail(program.args, {
|
||||
buffer: program.number,
|
||||
});
|
||||
|
||||
const filesSocket = io.of(`/${filesNamespace}`).on('connection', (socket) => {
|
||||
socket.emit('options:lines', program.lines);
|
||||
|
||||
if (program.uiHideTopbar) {
|
||||
socket.emit('options:hide-topbar');
|
||||
}
|
||||
|
||||
if (!program.uiIndent) {
|
||||
socket.emit('options:no-indent');
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup UI highlights
|
||||
*/
|
||||
var highlightConfig;
|
||||
if (program.uiHighlight) {
|
||||
highlightConfig = require(path.resolve(__dirname, program.uiHighlightPreset));
|
||||
socket.emit('options:highlightConfig', highlightConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* When connected send starting data
|
||||
*/
|
||||
var tailer = tail(program.args, {buffer: program.number});
|
||||
|
||||
var filesSocket = io.of('/' + filesNamespace).on('connection', function (socket) {
|
||||
socket.emit('options:lines', program.lines);
|
||||
|
||||
program.uiHideTopbar && socket.emit('options:hide-topbar');
|
||||
!program.uiIndent && socket.emit('options:no-indent');
|
||||
program.uiHighlight && socket.emit('options:highlightConfig', highlightConfig);
|
||||
|
||||
tailer.getBuffer().forEach(function (line) {
|
||||
socket.emit('line', line);
|
||||
});
|
||||
tailer.getBuffer().forEach((line) => {
|
||||
socket.emit('line', line);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Send incoming data
|
||||
*/
|
||||
tailer.on('line', function (line) {
|
||||
filesSocket.emit('line', line);
|
||||
});
|
||||
/**
|
||||
* Send incoming data
|
||||
*/
|
||||
tailer.on('line', (line) => {
|
||||
filesSocket.emit('line', line);
|
||||
});
|
||||
|
||||
/**
|
||||
* Handle signals
|
||||
*/
|
||||
var cleanExit = function () {
|
||||
process.exit();
|
||||
};
|
||||
process.on('SIGINT', cleanExit);
|
||||
process.on('SIGTERM', cleanExit);
|
||||
/**
|
||||
* Handle signals
|
||||
*/
|
||||
const cleanExit = () => {
|
||||
process.exit();
|
||||
};
|
||||
process.on('SIGINT', cleanExit);
|
||||
process.on('SIGTERM', cleanExit);
|
||||
}
|
||||
|
|
|
@ -1,53 +1,56 @@
|
|||
'use strict';
|
||||
|
||||
var connect = require('connect');
|
||||
var fs = require('fs');
|
||||
const connect = require('connect');
|
||||
const fs = require('fs');
|
||||
|
||||
var ConnectBuilder = function () {
|
||||
this.app = connect();
|
||||
function ConnectBuilder() {
|
||||
this.app = connect();
|
||||
}
|
||||
|
||||
ConnectBuilder.prototype.authorize = function authorize(user, pass) {
|
||||
this.app.use(
|
||||
connect.basicAuth(
|
||||
(incomingUser, incomingPass) => user === incomingUser && pass === incomingPass));
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
ConnectBuilder.prototype.authorize = function (user, pass) {
|
||||
this.app.use(connect.basicAuth(function (incomingUser, incomingPass) {
|
||||
return user === incomingUser && pass === incomingPass;
|
||||
}));
|
||||
|
||||
return this;
|
||||
ConnectBuilder.prototype.build = function build() {
|
||||
return this.app;
|
||||
};
|
||||
|
||||
ConnectBuilder.prototype.build = function () {
|
||||
return this.app;
|
||||
};
|
||||
ConnectBuilder.prototype.index = function index(path, files, filesNamespace, themeOpt) {
|
||||
const theme = themeOpt || 'default';
|
||||
|
||||
ConnectBuilder.prototype.index = function (path, files, filesNamespace, theme) {
|
||||
theme = theme || 'default';
|
||||
|
||||
this.app.use(function (req, res) {
|
||||
fs.readFile(path, function (err, data) {
|
||||
res.writeHead(200, {'Content-Type': 'text/html'});
|
||||
res.end(data.toString('utf-8')
|
||||
.replace(/__TITLE__/g, files)
|
||||
.replace(/__THEME__/g, theme)
|
||||
.replace(/__NAMESPACE__/g, filesNamespace),
|
||||
'utf-8'
|
||||
);
|
||||
});
|
||||
this.app.use((req, res) => {
|
||||
fs.readFile(path, (err, data) => {
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'text/html',
|
||||
});
|
||||
res.end(data.toString('utf-8')
|
||||
.replace(/__TITLE__/g, files)
|
||||
.replace(/__THEME__/g, theme)
|
||||
.replace(/__NAMESPACE__/g, filesNamespace),
|
||||
'utf-8'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
return this;
|
||||
return this;
|
||||
};
|
||||
|
||||
ConnectBuilder.prototype.session = function (secret, key) {
|
||||
this.app.use(connect.cookieParser());
|
||||
this.app.use(connect.session({secret: secret, key: key}));
|
||||
return this;
|
||||
ConnectBuilder.prototype.session = function session(secret, key) {
|
||||
this.app.use(connect.cookieParser());
|
||||
this.app.use(connect.session({
|
||||
secret,
|
||||
key,
|
||||
}));
|
||||
return this;
|
||||
};
|
||||
|
||||
ConnectBuilder.prototype.static = function (path) {
|
||||
this.app.use(connect.static(path));
|
||||
return this;
|
||||
ConnectBuilder.prototype.static = function staticf(path) {
|
||||
this.app.use(connect.static(path));
|
||||
return this;
|
||||
};
|
||||
|
||||
module.exports = function () {
|
||||
return new ConnectBuilder();
|
||||
};
|
||||
module.exports = () => new ConnectBuilder();
|
||||
|
|
|
@ -1,58 +1,58 @@
|
|||
'use strict';
|
||||
|
||||
var daemon = require('daemon');
|
||||
var fs = require('fs');
|
||||
const daemon = require('daemon');
|
||||
const fs = require('fs');
|
||||
|
||||
var defaultOptions = {
|
||||
doAuthorization: false,
|
||||
doSecure: false
|
||||
const defaultOptions = {
|
||||
doAuthorization: false,
|
||||
doSecure: false,
|
||||
};
|
||||
|
||||
module.exports = function (script, params, options) {
|
||||
options = options || defaultOptions;
|
||||
module.exports = (script, params, opts) => {
|
||||
const options = opts || defaultOptions;
|
||||
|
||||
var logFile = fs.openSync(params.logPath, 'a');
|
||||
const logFile = fs.openSync(params.logPath, 'a');
|
||||
|
||||
var args = [
|
||||
'-h', params.host,
|
||||
'-p', params.port,
|
||||
'-n', params.number,
|
||||
'-l', params.lines,
|
||||
'-t', params.theme
|
||||
];
|
||||
let args = [
|
||||
'-h', params.host,
|
||||
'-p', params.port,
|
||||
'-n', params.number,
|
||||
'-l', params.lines,
|
||||
'-t', params.theme,
|
||||
];
|
||||
|
||||
if (options.doAuthorization) {
|
||||
args.push(
|
||||
'-U', params.user,
|
||||
'-P', params.password
|
||||
);
|
||||
}
|
||||
if (options.doAuthorization) {
|
||||
args.push(
|
||||
'-U', params.user,
|
||||
'-P', params.password
|
||||
);
|
||||
}
|
||||
|
||||
if (options.doSecure) {
|
||||
args.push(
|
||||
'-k', params.key,
|
||||
'-c', params.certificate
|
||||
);
|
||||
}
|
||||
if (options.doSecure) {
|
||||
args.push(
|
||||
'-k', params.key,
|
||||
'-c', params.certificate
|
||||
);
|
||||
}
|
||||
|
||||
if (params.uiHideTopbar) {
|
||||
args.push('--ui-hide-topbar');
|
||||
}
|
||||
if (params.uiHideTopbar) {
|
||||
args.push('--ui-hide-topbar');
|
||||
}
|
||||
|
||||
if (!params.uiIndent) {
|
||||
args.push('--ui-no-indent');
|
||||
}
|
||||
if (!params.uiIndent) {
|
||||
args.push('--ui-no-indent');
|
||||
}
|
||||
|
||||
if (params.uiHighlight) {
|
||||
args.push('--ui-highlight', '--ui-highlight-preset', params.uiHighlightPreset);
|
||||
}
|
||||
if (params.uiHighlight) {
|
||||
args.push('--ui-highlight', '--ui-highlight-preset', params.uiHighlightPreset);
|
||||
}
|
||||
|
||||
args = args.concat(params.args);
|
||||
args = args.concat(params.args);
|
||||
|
||||
var proc = daemon.daemon(script, args, {
|
||||
stdout: logFile,
|
||||
stderr: logFile
|
||||
});
|
||||
const proc = daemon.daemon(script, args, {
|
||||
stdout: logFile,
|
||||
stderr: logFile,
|
||||
});
|
||||
|
||||
fs.writeFileSync(params.pidPath, proc.pid);
|
||||
fs.writeFileSync(params.pidPath, proc.pid);
|
||||
};
|
||||
|
|
|
@ -1,32 +1,30 @@
|
|||
'use strict';
|
||||
|
||||
var program = require('commander');
|
||||
const program = require('commander');
|
||||
|
||||
program
|
||||
.version(require('../package.json').version)
|
||||
.usage('[options] [file ...]')
|
||||
.option('-h, --host <host>', 'listening host, default 0.0.0.0', String, '0.0.0.0')
|
||||
.option('-p, --port <port>', 'listening port, default 9001', Number, 9001)
|
||||
.option('-n, --number <number>', 'starting lines number, default 10', Number, 10)
|
||||
.option('-l, --lines <lines>', 'number on lines stored in browser, default 2000', Number, 2000)
|
||||
.option('-t, --theme <theme>', 'name of the theme (default, dark)', String, 'default')
|
||||
.option('-d, --daemonize', 'run as daemon')
|
||||
.option('-U, --user <username>', 'Basic Authentication username, option works only along with -P option',
|
||||
String, false)
|
||||
.option('-P, --password <password>', 'Basic Authentication password, option works only along with -U option',
|
||||
String, false)
|
||||
.option('-k, --key <key.pem>', 'Private Key for HTTPS, option works only along with -c option',
|
||||
String, false)
|
||||
.option('-c, --certificate <cert.pem>', 'Certificate for HTTPS, option works only along with -k option',
|
||||
String, false)
|
||||
.option('--pid-path <path>', 'if run as daemon file that will store the process id, default /var/run/frontail.pid',
|
||||
String, '/var/run/frontail.pid')
|
||||
.option('--log-path <path>', 'if run as daemon file that will be used as a log, default /dev/null',
|
||||
String, '/dev/null')
|
||||
.option('--ui-hide-topbar', 'hide topbar (log file name and search box)')
|
||||
.option('--ui-no-indent', 'don\'t indent log lines')
|
||||
.option('--ui-highlight', 'highlight words or lines if defined string found in logs, default preset')
|
||||
.option('--ui-highlight-preset <path>', 'custom preset for highlighting (see ./preset/default.json)', String,
|
||||
'./preset/default.json');
|
||||
.version(require('../package.json').version)
|
||||
.usage('[options] [file ...]')
|
||||
.option('-h, --host <host>', 'listening host, default 0.0.0.0', String, '0.0.0.0')
|
||||
.option('-p, --port <port>', 'listening port, default 9001', Number, 9001)
|
||||
.option('-n, --number <number>', 'starting lines number, default 10', Number, 10)
|
||||
.option('-l, --lines <lines>', 'number on lines stored in browser, default 2000', Number, 2000)
|
||||
.option('-t, --theme <theme>', 'name of the theme (default, dark)', String, 'default')
|
||||
.option('-d, --daemonize', 'run as daemon')
|
||||
.option('-U, --user <username>', 'Basic Authentication username, option works only along with -P option',
|
||||
String, false)
|
||||
.option('-P, --password <password>', 'Basic Authentication password, option works only along with -U option',
|
||||
String, false)
|
||||
.option('-k, --key <key.pem>', 'Private Key for HTTPS, option works only along with -c option',
|
||||
String, false)
|
||||
.option('-c, --certificate <cert.pem>', 'Certificate for HTTPS, option works only along with -k option',
|
||||
String, false)
|
||||
.option('--pid-path <path>', 'if run as daemon file that will store the process id, default /var/run/frontail.pid',
|
||||
String, '/var/run/frontail.pid')
|
||||
.option('--log-path <path>', 'if run as daemon file that will be used as a log, default /dev/null',
|
||||
String, '/dev/null')
|
||||
.option('--ui-hide-topbar', 'hide topbar (log file name and search box)')
|
||||
.option('--ui-no-indent', 'don\'t indent log lines')
|
||||
.option('--ui-highlight', 'highlight words or lines if defined string found in logs, default preset')
|
||||
.option('--ui-highlight-preset <path>', 'custom preset for highlighting (see ./preset/default.json)', String,
|
||||
'./preset/default.json');
|
||||
|
||||
module.exports = program;
|
||||
|
|
|
@ -1,52 +1,52 @@
|
|||
/* eslint no-underscore-dangle: off */
|
||||
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
var http = require('http');
|
||||
var https = require('https');
|
||||
const fs = require('fs');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
|
||||
var ServerBuilder = function () {
|
||||
this._host = null;
|
||||
this._port = 9001;
|
||||
function ServerBuilder() {
|
||||
this._host = null;
|
||||
this._port = 9001;
|
||||
}
|
||||
|
||||
ServerBuilder.prototype.build = function build() {
|
||||
if (this._key && this._cert) {
|
||||
const options = {
|
||||
key: this._key,
|
||||
cert: this._cert,
|
||||
};
|
||||
return https.createServer(options, this._callback).listen(this._port, this._host);
|
||||
}
|
||||
|
||||
return http.createServer(this._callback).listen(this._port, this._host);
|
||||
};
|
||||
|
||||
ServerBuilder.prototype.build = function () {
|
||||
if (this._key && this._cert) {
|
||||
var options = {
|
||||
key: this._key,
|
||||
cert: this._cert
|
||||
};
|
||||
return https.createServer(options, this._callback).listen(this._port, this._host);
|
||||
} else {
|
||||
return http.createServer(this._callback).listen(this._port, this._host);
|
||||
}
|
||||
ServerBuilder.prototype.host = function hostf(host) {
|
||||
this._host = host;
|
||||
return this;
|
||||
};
|
||||
|
||||
ServerBuilder.prototype.host = function (host) {
|
||||
this._host = host;
|
||||
return this;
|
||||
ServerBuilder.prototype.port = function portf(port) {
|
||||
this._port = port;
|
||||
return this;
|
||||
};
|
||||
|
||||
ServerBuilder.prototype.port = function (port) {
|
||||
this._port = port;
|
||||
return this;
|
||||
ServerBuilder.prototype.secure = function secure(keyPath, certPath) {
|
||||
try {
|
||||
this._key = fs.readFileSync(keyPath);
|
||||
this._cert = fs.readFileSync(certPath);
|
||||
} catch (e) {
|
||||
throw new Error('No key or certificate file found');
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
ServerBuilder.prototype.secure = function (keyPath, certPath) {
|
||||
try {
|
||||
this._key = fs.readFileSync(keyPath);
|
||||
this._cert = fs.readFileSync(certPath);
|
||||
} catch (e) {
|
||||
throw new Error('No key or certificate file found');
|
||||
}
|
||||
|
||||
return this;
|
||||
ServerBuilder.prototype.use = function use(callback) {
|
||||
this._callback = callback;
|
||||
return this;
|
||||
};
|
||||
|
||||
ServerBuilder.prototype.use = function (callback) {
|
||||
this._callback = callback;
|
||||
return this;
|
||||
};
|
||||
|
||||
module.exports = function () {
|
||||
return new ServerBuilder();
|
||||
};
|
||||
module.exports = () => new ServerBuilder();
|
||||
|
|
90
lib/tail.js
90
lib/tail.js
|
@ -1,58 +1,58 @@
|
|||
/* eslint no-underscore-dangle: off */
|
||||
|
||||
'use strict';
|
||||
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var childProcess = require('child_process');
|
||||
var util = require('util');
|
||||
var CBuffer = require('CBuffer');
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const childProcess = require('child_process');
|
||||
const util = require('util');
|
||||
const CBuffer = require('CBuffer');
|
||||
|
||||
var Tail = function (path, options) {
|
||||
EventEmitter.call(this);
|
||||
function Tail(path, opts) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
options = options || {buffer: 0};
|
||||
this._buffer = new CBuffer(options.buffer);
|
||||
const options = opts || {
|
||||
buffer: 0,
|
||||
};
|
||||
this._buffer = new CBuffer(options.buffer);
|
||||
|
||||
if (path[0] === '-') {
|
||||
process.stdin.setEncoding('utf8');
|
||||
if (path[0] === '-') {
|
||||
process.stdin.setEncoding('utf8');
|
||||
|
||||
var self = this;
|
||||
process.stdin.on('readable', function () {
|
||||
var line = process.stdin.read();
|
||||
process.stdin.on('readable', () => {
|
||||
const line = process.stdin.read();
|
||||
if (line !== null) {
|
||||
this.emit('line', line);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const tail = childProcess.spawn('tail', ['-n', options.buffer, '-F'].concat(path));
|
||||
|
||||
if (line !== null) {
|
||||
self.emit('line', line);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
var tail = childProcess.spawn('tail', ['-n', options.buffer, '-F'].concat(path));
|
||||
tail.stderr.on('data', (data) => {
|
||||
// If there is any important error then display it in the console. Tail will keep running.
|
||||
// File can be truncated over network.
|
||||
if (data.toString().indexOf('file truncated') === -1) {
|
||||
console.error(data.toString());
|
||||
}
|
||||
});
|
||||
|
||||
tail.stderr.on('data', function (data) {
|
||||
// If there is any important error then display it in the console. Tail will keep running.
|
||||
// File can be truncated over network.
|
||||
if (data.toString().indexOf('file truncated') === -1) {
|
||||
console.error(data.toString());
|
||||
}
|
||||
});
|
||||
tail.stdout.on('data', (data) => {
|
||||
const lines = data.toString('utf-8').split('\n');
|
||||
lines.pop();
|
||||
lines.forEach((line) => {
|
||||
this._buffer.push(line);
|
||||
this.emit('line', line);
|
||||
});
|
||||
});
|
||||
|
||||
tail.stdout.on('data', function (data) {
|
||||
var lines = data.toString('utf-8').split('\n');
|
||||
lines.pop();
|
||||
lines.forEach(function (line) {
|
||||
this._buffer.push(line);
|
||||
this.emit('line', line);
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
|
||||
process.on('exit', function () {
|
||||
tail.kill();
|
||||
});
|
||||
}
|
||||
};
|
||||
process.on('exit', () => {
|
||||
tail.kill();
|
||||
});
|
||||
}
|
||||
}
|
||||
util.inherits(Tail, EventEmitter);
|
||||
|
||||
Tail.prototype.getBuffer = function () {
|
||||
return this._buffer.toArray();
|
||||
Tail.prototype.getBuffer = function getBuffer() {
|
||||
return this._buffer.toArray();
|
||||
};
|
||||
|
||||
module.exports = function (path, options) {
|
||||
return new Tail(path, options);
|
||||
};
|
||||
module.exports = (path, options) => new Tail(path, options);
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"rules": {
|
||||
"no-var": "off",
|
||||
"no-underscore-dangle": "off",
|
||||
"prefer-arrow-callback": "off",
|
||||
"func-names": "off",
|
||||
"space-before-function-paren": "off",
|
||||
"prefer-template": "off"
|
||||
},
|
||||
"env": {
|
||||
"browser": true
|
||||
}
|
||||
}
|
|
@ -1,278 +1,279 @@
|
|||
/* global Tinycon:false, ansi_up:false */
|
||||
|
||||
window.App = (function (window, document) {
|
||||
'use strict';
|
||||
window.App = (function app(window, document) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
var _socket;
|
||||
/**
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
var _socket;
|
||||
|
||||
/**
|
||||
* @type {HTMLElement}
|
||||
* @private
|
||||
*/
|
||||
var _logContainer;
|
||||
/**
|
||||
* @type {HTMLElement}
|
||||
* @private
|
||||
*/
|
||||
var _logContainer;
|
||||
|
||||
/**
|
||||
* @type {HTMLElement}
|
||||
* @private
|
||||
*/
|
||||
var _filterInput;
|
||||
/**
|
||||
* @type {HTMLElement}
|
||||
* @private
|
||||
*/
|
||||
var _filterInput;
|
||||
|
||||
/**
|
||||
* @type {String}
|
||||
* @private
|
||||
*/
|
||||
var _filterValue = '';
|
||||
/**
|
||||
* @type {String}
|
||||
* @private
|
||||
*/
|
||||
var _filterValue = '';
|
||||
|
||||
/**
|
||||
* @type {HTMLElement}
|
||||
* @private
|
||||
*/
|
||||
var _topbar;
|
||||
/**
|
||||
* @type {HTMLElement}
|
||||
* @private
|
||||
*/
|
||||
var _topbar;
|
||||
|
||||
/**
|
||||
* @type {HTMLElement}
|
||||
* @private
|
||||
*/
|
||||
var _body;
|
||||
/**
|
||||
* @type {HTMLElement}
|
||||
* @private
|
||||
*/
|
||||
var _body;
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
var _linesLimit = Math.Infinity;
|
||||
/**
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
var _linesLimit = Math.Infinity;
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
var _newLinesCount = 0;
|
||||
/**
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
var _newLinesCount = 0;
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
var _isWindowFocused = true;
|
||||
/**
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
var _isWindowFocused = true;
|
||||
|
||||
/**
|
||||
* @type {object}
|
||||
* @private
|
||||
*/
|
||||
var _highlightConfig;
|
||||
/**
|
||||
* @type {object}
|
||||
* @private
|
||||
*/
|
||||
var _highlightConfig;
|
||||
|
||||
/**
|
||||
* Hide element if doesn't contain filter value
|
||||
*
|
||||
* @param {Object} element
|
||||
* @private
|
||||
*/
|
||||
var _filterElement = function(elem) {
|
||||
var pattern = new RegExp(_filterValue, 'i');
|
||||
var element = elem;
|
||||
if (pattern.test(element.textContent)) {
|
||||
element.style.display = '';
|
||||
} else {
|
||||
element.style.display = 'none';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Filter logs based on _filterValue
|
||||
*
|
||||
* @function
|
||||
* @private
|
||||
*/
|
||||
var _filterLogs = function() {
|
||||
var collection = _logContainer.childNodes;
|
||||
var i = collection.length;
|
||||
|
||||
if (i === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (i) {
|
||||
_filterElement(collection[i - 1]);
|
||||
i -= 1;
|
||||
}
|
||||
window.scrollTo(0, document.body.scrollHeight);
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {Boolean}
|
||||
* @private
|
||||
*/
|
||||
var _isScrolledBottom = function() {
|
||||
var currentScroll = document.documentElement.scrollTop || document.body.scrollTop;
|
||||
var totalHeight = document.body.offsetHeight;
|
||||
var clientHeight = document.documentElement.clientHeight;
|
||||
return totalHeight <= currentScroll + clientHeight;
|
||||
};
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @private
|
||||
*/
|
||||
var _faviconReset = function() {
|
||||
_newLinesCount = 0;
|
||||
Tinycon.setBubble(0);
|
||||
};
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @private
|
||||
*/
|
||||
var _updateFaviconCounter = function() {
|
||||
if (_isWindowFocused) {
|
||||
return;
|
||||
}
|
||||
|
||||
_newLinesCount += 1;
|
||||
|
||||
if (_newLinesCount > 99) {
|
||||
Tinycon.setBubble(99);
|
||||
} else {
|
||||
Tinycon.setBubble(_newLinesCount);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @return String
|
||||
* @private
|
||||
*/
|
||||
var _highlightWord = function(line) {
|
||||
if (_highlightConfig) {
|
||||
if (_highlightConfig.words) {
|
||||
for (var wordCheck in _highlightConfig.words) { // eslint-disable-line
|
||||
if (_highlightConfig.words.hasOwnProperty(wordCheck)) { // eslint-disable-line
|
||||
line = line.replace( // eslint-disable-line
|
||||
wordCheck,
|
||||
'<span style="' + _highlightConfig.words[wordCheck] + '">' + wordCheck + '</span>'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return line;
|
||||
};
|
||||
|
||||
/**
|
||||
* @return HTMLElement
|
||||
* @private
|
||||
*/
|
||||
var _highlightLine = function(line, container) {
|
||||
if (_highlightConfig) {
|
||||
if (_highlightConfig.lines) {
|
||||
for (var lineCheck in _highlightConfig.lines) { // eslint-disable-line
|
||||
if (line.indexOf(lineCheck) !== -1) { // eslint-disable-line
|
||||
container.setAttribute('style', _highlightConfig.lines[lineCheck]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return container;
|
||||
};
|
||||
|
||||
return {
|
||||
/**
|
||||
* Hide element if doesn't contain filter value
|
||||
* Init socket.io communication and log container
|
||||
*
|
||||
* @param {Object} element
|
||||
* @private
|
||||
* @param {Object} opts options
|
||||
*/
|
||||
var _filterElement = function (element) {
|
||||
var pattern = new RegExp(_filterValue, 'i');
|
||||
if (pattern.test(element.textContent)) {
|
||||
element.style.display = '';
|
||||
init: function init(opts) {
|
||||
var self = this;
|
||||
|
||||
// Elements
|
||||
_logContainer = opts.container;
|
||||
_filterInput = opts.filterInput;
|
||||
_filterInput.focus();
|
||||
_topbar = opts.topbar;
|
||||
_body = opts.body;
|
||||
|
||||
// Filter input bind
|
||||
_filterInput.addEventListener('keyup', function(e) {
|
||||
// ESC
|
||||
if (e.keyCode === 27) {
|
||||
this.value = '';
|
||||
_filterValue = '';
|
||||
} else {
|
||||
element.style.display = 'none';
|
||||
_filterValue = this.value;
|
||||
}
|
||||
};
|
||||
_filterLogs();
|
||||
});
|
||||
|
||||
// Favicon counter bind
|
||||
window.addEventListener('blur', function() {
|
||||
_isWindowFocused = false;
|
||||
}, true);
|
||||
window.addEventListener('focus', function() {
|
||||
_isWindowFocused = true;
|
||||
_faviconReset();
|
||||
}, true);
|
||||
|
||||
// socket.io init
|
||||
_socket = opts.socket;
|
||||
_socket
|
||||
.on('options:lines', function(limit) {
|
||||
_linesLimit = limit;
|
||||
})
|
||||
.on('options:hide-topbar', function() {
|
||||
_topbar.className += ' hide';
|
||||
_body.className = 'no-topbar';
|
||||
})
|
||||
.on('options:no-indent', function() {
|
||||
_logContainer.className += ' no-indent';
|
||||
})
|
||||
.on('options:highlightConfig', function(highlightConfig) {
|
||||
_highlightConfig = highlightConfig;
|
||||
})
|
||||
.on('line', function(line) {
|
||||
self.log(line);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter logs based on _filterValue
|
||||
* Log data
|
||||
*
|
||||
* @function
|
||||
* @private
|
||||
* @param {string} data data to log
|
||||
*/
|
||||
var _filterLogs = function () {
|
||||
var collection = _logContainer.childNodes;
|
||||
var i = collection.length;
|
||||
log: function log(data) {
|
||||
var wasScrolledBottom = _isScrolledBottom();
|
||||
var div = document.createElement('div');
|
||||
var p = document.createElement('p');
|
||||
p.className = 'inner-line';
|
||||
|
||||
if (i === 0) {
|
||||
return;
|
||||
}
|
||||
// convert ansi color codes to html && escape HTML tags
|
||||
data = ansi_up.escape_for_html(data); // eslint-disable-line
|
||||
data = ansi_up.ansi_to_html(data); // eslint-disable-line
|
||||
p.innerHTML = _highlightWord(data);
|
||||
|
||||
while (i) {
|
||||
_filterElement(collection[i - 1]);
|
||||
i -= 1;
|
||||
div.className = 'line';
|
||||
div = _highlightLine(data, div);
|
||||
div.addEventListener('click', function click() {
|
||||
if (this.className.indexOf('selected') === -1) {
|
||||
this.className = 'line-selected';
|
||||
} else {
|
||||
this.className = 'line';
|
||||
}
|
||||
});
|
||||
|
||||
div.appendChild(p);
|
||||
_filterElement(div);
|
||||
_logContainer.appendChild(div);
|
||||
|
||||
if (_logContainer.children.length > _linesLimit) {
|
||||
_logContainer.removeChild(_logContainer.children[0]);
|
||||
}
|
||||
|
||||
if (wasScrolledBottom) {
|
||||
window.scrollTo(0, document.body.scrollHeight);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Boolean}
|
||||
* @private
|
||||
*/
|
||||
var _isScrolledBottom = function () {
|
||||
var currentScroll = document.documentElement.scrollTop || document.body.scrollTop;
|
||||
var totalHeight = document.body.offsetHeight;
|
||||
var clientHeight = document.documentElement.clientHeight;
|
||||
return totalHeight <= currentScroll + clientHeight;
|
||||
};
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @private
|
||||
*/
|
||||
var _faviconReset = function () {
|
||||
_newLinesCount = 0;
|
||||
Tinycon.setBubble(0);
|
||||
};
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @private
|
||||
*/
|
||||
var _updateFaviconCounter = function () {
|
||||
if (_isWindowFocused) {
|
||||
return;
|
||||
}
|
||||
|
||||
_newLinesCount += 1;
|
||||
|
||||
if (_newLinesCount > 99) {
|
||||
Tinycon.setBubble(99);
|
||||
} else {
|
||||
Tinycon.setBubble(_newLinesCount);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @return String
|
||||
* @private
|
||||
*/
|
||||
var _highlightWord = function (line) {
|
||||
if (_highlightConfig) {
|
||||
if (_highlightConfig.words) {
|
||||
for (var wordCheck in _highlightConfig.words) {
|
||||
if (_highlightConfig.words.hasOwnProperty(wordCheck)) {
|
||||
line = line.replace(
|
||||
wordCheck,
|
||||
'<span style="' + _highlightConfig.words[wordCheck] + '">' + wordCheck + '</span>'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return line;
|
||||
};
|
||||
|
||||
/**
|
||||
* @return HTMLElement
|
||||
* @private
|
||||
*/
|
||||
var _highlightLine = function (line, container) {
|
||||
if (_highlightConfig) {
|
||||
if (_highlightConfig.lines) {
|
||||
for (var lineCheck in _highlightConfig.lines) {
|
||||
if (line.indexOf(lineCheck) !== -1) {
|
||||
container.setAttribute('style', _highlightConfig.lines[lineCheck]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return container;
|
||||
};
|
||||
|
||||
return {
|
||||
/**
|
||||
* Init socket.io communication and log container
|
||||
*
|
||||
* @param {Object} opts options
|
||||
*/
|
||||
init: function (opts) {
|
||||
var self = this;
|
||||
|
||||
// Elements
|
||||
_logContainer = opts.container;
|
||||
_filterInput = opts.filterInput;
|
||||
_filterInput.focus();
|
||||
_topbar = opts.topbar;
|
||||
_body = opts.body;
|
||||
|
||||
// Filter input bind
|
||||
_filterInput.addEventListener('keyup', function (e) {
|
||||
// ESC
|
||||
if (e.keyCode === 27) {
|
||||
this.value = '';
|
||||
_filterValue = '';
|
||||
} else {
|
||||
_filterValue = this.value;
|
||||
}
|
||||
_filterLogs();
|
||||
});
|
||||
|
||||
// Favicon counter bind
|
||||
window.addEventListener('blur', function () {
|
||||
_isWindowFocused = false;
|
||||
}, true);
|
||||
window.addEventListener('focus', function () {
|
||||
_isWindowFocused = true;
|
||||
_faviconReset();
|
||||
}, true);
|
||||
|
||||
// socket.io init
|
||||
_socket = opts.socket;
|
||||
_socket
|
||||
.on('options:lines', function (limit) {
|
||||
_linesLimit = limit;
|
||||
})
|
||||
.on('options:hide-topbar', function () {
|
||||
_topbar.className += ' hide';
|
||||
_body.className = 'no-topbar';
|
||||
})
|
||||
.on('options:no-indent', function () {
|
||||
_logContainer.className += ' no-indent';
|
||||
})
|
||||
.on('options:highlightConfig', function (highlightConfig) {
|
||||
_highlightConfig = highlightConfig;
|
||||
})
|
||||
.on('line', function (line) {
|
||||
self.log(line);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Log data
|
||||
*
|
||||
* @param {string} data data to log
|
||||
*/
|
||||
log: function (data) {
|
||||
var wasScrolledBottom = _isScrolledBottom();
|
||||
var div = document.createElement('div');
|
||||
var p = document.createElement('p');
|
||||
p.className = 'inner-line';
|
||||
|
||||
// convert ansi color codes to html && escape HTML tags
|
||||
data = ansi_up.escape_for_html(data); // eslint-disable-line
|
||||
data = ansi_up.ansi_to_html(data); // eslint-disable-line
|
||||
p.innerHTML = _highlightWord(data);
|
||||
|
||||
div.className = 'line';
|
||||
div = _highlightLine(data, div);
|
||||
div.addEventListener('click', function () {
|
||||
if (this.className.indexOf('selected') === -1) {
|
||||
this.className = 'line-selected';
|
||||
} else {
|
||||
this.className = 'line';
|
||||
}
|
||||
});
|
||||
|
||||
div.appendChild(p);
|
||||
_filterElement(div);
|
||||
_logContainer.appendChild(div);
|
||||
|
||||
if (_logContainer.children.length > _linesLimit) {
|
||||
_logContainer.removeChild(_logContainer.children[0]);
|
||||
}
|
||||
|
||||
if (wasScrolledBottom) {
|
||||
window.scrollTo(0, document.body.scrollHeight);
|
||||
}
|
||||
|
||||
_updateFaviconCounter();
|
||||
}
|
||||
};
|
||||
})(window, document);
|
||||
_updateFaviconCounter();
|
||||
},
|
||||
};
|
||||
}(window, document));
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "frontail",
|
||||
"version": "3.1.2",
|
||||
"version": "4.0.0",
|
||||
"description": "streaming logs to the browser",
|
||||
"homepage": "https://github.com/mthenw/frontail",
|
||||
"author": "Maciej Winnicki <maciej.winnicki@gmail.com>",
|
||||
|
@ -15,7 +15,9 @@
|
|||
"socket.io": "1.4.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^3.4.0",
|
||||
"eslint": "^3.6.0",
|
||||
"eslint-config-airbnb-base": "^8.0.0",
|
||||
"eslint-plugin-import": "^1.16.0",
|
||||
"jsdom": "^8.5.0",
|
||||
"mocha": "~2.3.2",
|
||||
"should": "~3.3.2",
|
||||
|
@ -25,7 +27,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"test": "mocha --reporter spec test/*.js"
|
||||
"test": "mocha -r should --reporter spec test/*.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"rules": {
|
||||
"import/no-extraneous-dependencies": "off",
|
||||
"no-unused-expressions": "off"
|
||||
},
|
||||
"env": {
|
||||
"mocha": true
|
||||
}
|
||||
}
|
223
test/app.js
223
test/app.js
|
@ -1,125 +1,136 @@
|
|||
'use strict';
|
||||
|
||||
require('should');
|
||||
var fs = require('fs');
|
||||
var jsdom = require('jsdom');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
const fs = require('fs');
|
||||
const jsdom = require('jsdom');
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
|
||||
describe('browser application', function () {
|
||||
var io, window;
|
||||
describe('browser application', () => {
|
||||
let io;
|
||||
let window;
|
||||
|
||||
beforeEach(function (done) {
|
||||
io = new EventEmitter();
|
||||
var html = '<title></title><body><div class="topbar"></div>' +
|
||||
'<div class="log"></div><input type="test" id="filter"/></body>';
|
||||
var ansiup = fs.readFileSync('./lib/web/assets/ansi_up.js', 'utf-8');
|
||||
var src = fs.readFileSync('./lib/web/assets/app.js', 'utf-8');
|
||||
|
||||
|
||||
jsdom.env({html: html, src: [ansiup, src], onload: function (domWindow) {
|
||||
window = domWindow;
|
||||
|
||||
initApp();
|
||||
done();
|
||||
}});
|
||||
function initApp() {
|
||||
window.App.init({
|
||||
socket: io,
|
||||
container: window.document.querySelector('.log'),
|
||||
filterInput: window.document.querySelector('#filter'),
|
||||
topbar: window.document.querySelector('.topbar'),
|
||||
body: window.document.querySelector('body'),
|
||||
});
|
||||
}
|
||||
|
||||
it('should show lines from socket.io', function () {
|
||||
io.emit('line', 'test');
|
||||
function clickOnElement(line) {
|
||||
const click = window.document.createEvent('MouseEvents');
|
||||
click.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
|
||||
line.dispatchEvent(click);
|
||||
}
|
||||
|
||||
var log = window.document.querySelector('.log');
|
||||
log.childNodes.length.should.be.equal(1);
|
||||
log.childNodes[0].textContent.should.be.equal('test');
|
||||
log.childNodes[0].className.should.be.equal('line');
|
||||
log.childNodes[0].tagName.should.be.equal('DIV');
|
||||
log.childNodes[0].innerHTML.should.be.equal('<p class="inner-line">test</p>');
|
||||
beforeEach((done) => {
|
||||
io = new EventEmitter();
|
||||
const html = '<title></title><body><div class="topbar"></div>' +
|
||||
'<div class="log"></div><input type="test" id="filter"/></body>';
|
||||
const ansiup = fs.readFileSync('./lib/web/assets/ansi_up.js', 'utf-8');
|
||||
const src = fs.readFileSync('./lib/web/assets/app.js', 'utf-8');
|
||||
|
||||
jsdom.env({
|
||||
html,
|
||||
src: [ansiup, src],
|
||||
onload: (domWindow) => {
|
||||
window = domWindow;
|
||||
|
||||
initApp();
|
||||
done();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should select line when clicked', function () {
|
||||
io.emit('line', 'test');
|
||||
it('should show lines from socket.io', () => {
|
||||
io.emit('line', 'test');
|
||||
|
||||
var line = window.document.querySelector('.line');
|
||||
clickOnElement(line);
|
||||
const log = window.document.querySelector('.log');
|
||||
log.childNodes.length.should.be.equal(1);
|
||||
log.childNodes[0].textContent.should.be.equal('test');
|
||||
log.childNodes[0].className.should.be.equal('line');
|
||||
log.childNodes[0].tagName.should.be.equal('DIV');
|
||||
log.childNodes[0].innerHTML.should.be.equal('<p class="inner-line">test</p>');
|
||||
});
|
||||
|
||||
line.className.should.be.equal('line-selected');
|
||||
it('should select line when clicked', () => {
|
||||
io.emit('line', 'test');
|
||||
|
||||
const line = window.document.querySelector('.line');
|
||||
clickOnElement(line);
|
||||
|
||||
line.className.should.be.equal('line-selected');
|
||||
});
|
||||
|
||||
it('should deselect line when selected line clicked', () => {
|
||||
io.emit('line', 'test');
|
||||
|
||||
const line = window.document.querySelector('.line');
|
||||
clickOnElement(line);
|
||||
clickOnElement(line);
|
||||
|
||||
line.className.should.be.equal('line');
|
||||
});
|
||||
|
||||
it('should limit number of lines in browser', () => {
|
||||
io.emit('options:lines', 2);
|
||||
io.emit('line', 'line1');
|
||||
io.emit('line', 'line2');
|
||||
io.emit('line', 'line3');
|
||||
|
||||
const log = window.document.querySelector('.log');
|
||||
log.childNodes.length.should.be.equal(2);
|
||||
log.childNodes[0].textContent.should.be.equal('line2');
|
||||
log.childNodes[1].textContent.should.be.equal('line3');
|
||||
});
|
||||
|
||||
it('should hide topbar', () => {
|
||||
io.emit('options:hide-topbar');
|
||||
|
||||
const topbar = window.document.querySelector('.topbar');
|
||||
topbar.className.should.match(/hide/);
|
||||
const body = window.document.querySelector('body');
|
||||
body.className.should.match(/no-topbar/);
|
||||
});
|
||||
|
||||
it('should not indent log lines', () => {
|
||||
io.emit('options:no-indent');
|
||||
|
||||
const log = window.document.querySelector('.log');
|
||||
log.className.should.match(/no-indent/);
|
||||
});
|
||||
|
||||
it('should highlight word', () => {
|
||||
io.emit('options:highlightConfig', {
|
||||
words: {
|
||||
line: 'background: black',
|
||||
},
|
||||
});
|
||||
io.emit('line', 'line1');
|
||||
|
||||
it('should deselect line when selected line clicked', function () {
|
||||
io.emit('line', 'test');
|
||||
const line = window.document.querySelector('.line');
|
||||
line.innerHTML.should.containEql('<span style="background: black">line</span>');
|
||||
});
|
||||
|
||||
var line = window.document.querySelector('.line');
|
||||
clickOnElement(line);
|
||||
clickOnElement(line);
|
||||
|
||||
line.className.should.be.equal('line');
|
||||
it('should highlight line', () => {
|
||||
io.emit('options:highlightConfig', {
|
||||
lines: {
|
||||
line: 'background: black',
|
||||
},
|
||||
});
|
||||
io.emit('line', 'line1');
|
||||
|
||||
it('should limit number of lines in browser', function () {
|
||||
io.emit('options:lines', 2);
|
||||
io.emit('line', 'line1');
|
||||
io.emit('line', 'line2');
|
||||
io.emit('line', 'line3');
|
||||
const line = window.document.querySelector('.line');
|
||||
line.parentNode.innerHTML.should.equal(
|
||||
'<div class="line" style="background: black"><p class="inner-line">line1</p></div>'
|
||||
);
|
||||
});
|
||||
|
||||
var log = window.document.querySelector('.log');
|
||||
log.childNodes.length.should.be.equal(2);
|
||||
log.childNodes[0].textContent.should.be.equal('line2');
|
||||
log.childNodes[1].textContent.should.be.equal('line3');
|
||||
});
|
||||
it('should escape HTML', () => {
|
||||
io.emit('line', '<a/>');
|
||||
|
||||
it('should hide topbar', function () {
|
||||
io.emit('options:hide-topbar');
|
||||
|
||||
var topbar = window.document.querySelector('.topbar');
|
||||
topbar.className.should.match(/hide/);
|
||||
var body = window.document.querySelector('body');
|
||||
body.className.should.match(/no-topbar/);
|
||||
});
|
||||
|
||||
it('should not indent log lines', function () {
|
||||
io.emit('options:no-indent');
|
||||
|
||||
var log = window.document.querySelector('.log');
|
||||
log.className.should.match(/no-indent/);
|
||||
});
|
||||
|
||||
it('should highlight word', function () {
|
||||
io.emit('options:highlightConfig', {words: {line: 'background: black'}});
|
||||
io.emit('line', 'line1');
|
||||
|
||||
var line = window.document.querySelector('.line');
|
||||
line.innerHTML.should.containEql('<span style="background: black">line</span>');
|
||||
});
|
||||
|
||||
it('should highlight line', function () {
|
||||
io.emit('options:highlightConfig', {lines: {line: 'background: black'}});
|
||||
io.emit('line', 'line1');
|
||||
|
||||
var line = window.document.querySelector('.line');
|
||||
line.parentNode.innerHTML.should.equal(
|
||||
'<div class="line" style="background: black"><p class="inner-line">line1</p></div>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should escape HTML', function () {
|
||||
io.emit('line', '<a/>');
|
||||
|
||||
var line = window.document.querySelector('.line');
|
||||
line.innerHTML.should.equal('<p class="inner-line"><a/></p>');
|
||||
});
|
||||
|
||||
var initApp = function () {
|
||||
window.App.init({
|
||||
socket: io,
|
||||
container: window.document.querySelector('.log'),
|
||||
filterInput: window.document.querySelector('#filter'),
|
||||
topbar: window.document.querySelector('.topbar'),
|
||||
body: window.document.querySelector('body')
|
||||
});
|
||||
};
|
||||
|
||||
var clickOnElement = function (line) {
|
||||
var click = window.document.createEvent('MouseEvents');
|
||||
click.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
|
||||
line.dispatchEvent(click);
|
||||
};
|
||||
const line = window.document.querySelector('.line');
|
||||
line.innerHTML.should.equal('<p class="inner-line"><a/></p>');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,107 +1,107 @@
|
|||
'use strict';
|
||||
|
||||
require('should');
|
||||
var connectBuilder = require('../lib/connect_builder');
|
||||
var request = require('supertest');
|
||||
const connectBuilder = require('../lib/connect_builder');
|
||||
const request = require('supertest');
|
||||
const path = require('path');
|
||||
|
||||
describe('connectBuilder', function () {
|
||||
it('should build connect app', function () {
|
||||
connectBuilder().build().should.have.property('use');
|
||||
connectBuilder().build().should.have.property('listen');
|
||||
describe('connectBuilder', () => {
|
||||
it('should build connect app', () => {
|
||||
connectBuilder().build().should.have.property('use');
|
||||
connectBuilder().build().should.have.property('listen');
|
||||
});
|
||||
|
||||
it('should build app requiring authorized user', (done) => {
|
||||
const app = connectBuilder().authorize('user', 'pass').build();
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('www-authenticate', 'Basic realm="Authorization Required"')
|
||||
.expect(401, done);
|
||||
});
|
||||
|
||||
it('should build app allowing user to login', (done) => {
|
||||
const app = connectBuilder().authorize('user', 'pass').build();
|
||||
app.use((req, res) => {
|
||||
res.end('secret!');
|
||||
});
|
||||
|
||||
it('should build app requiring authorized user', function (done) {
|
||||
var app = connectBuilder().authorize('user', 'pass').build();
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('Authorization', 'Basic dXNlcjpwYXNz')
|
||||
.expect(200, 'secret!', done);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('www-authenticate', 'Basic realm="Authorization Required"')
|
||||
.expect(401, done);
|
||||
it('should build app that setup session', (done) => {
|
||||
const app = connectBuilder().session('secret', 'sessionkey').build();
|
||||
app.use((req, res) => {
|
||||
res.end();
|
||||
});
|
||||
|
||||
it('should build app allowing user to login', function (done) {
|
||||
var app = connectBuilder().authorize('user', 'pass').build();
|
||||
app.use(function (req, res) {
|
||||
res.end('secret!');
|
||||
});
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('set-cookie', /^sessionkey/, done);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('Authorization', 'Basic dXNlcjpwYXNz')
|
||||
.expect(200, 'secret!', done);
|
||||
});
|
||||
it('should build app that serve static files', (done) => {
|
||||
const app = connectBuilder().static(path.join(__dirname, 'fixtures')).build();
|
||||
|
||||
it('should build app that setup session', function (done) {
|
||||
var app = connectBuilder().session('secret', 'sessionkey').build();
|
||||
app.use(function (req, res) {
|
||||
res.end();
|
||||
});
|
||||
request(app)
|
||||
.get('/foo')
|
||||
.expect('bar', done);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('set-cookie', /^sessionkey/, done);
|
||||
});
|
||||
it('should build app that serve index file', (done) => {
|
||||
const app = connectBuilder().index(path.join(__dirname, 'fixtures/index'), '/testfile').build();
|
||||
|
||||
it('should build app that serve static files', function (done) {
|
||||
var app = connectBuilder().static(__dirname + '/fixtures').build();
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(200)
|
||||
.expect('Content-Type', 'text/html', done);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/foo')
|
||||
.expect('bar', done);
|
||||
});
|
||||
it('should build app that replace index title', (done) => {
|
||||
const app = connectBuilder()
|
||||
.index(path.join(__dirname, 'fixtures/index_with_title'), '/testfile')
|
||||
.build();
|
||||
|
||||
it('should build app that serve index file', function (done) {
|
||||
var app = connectBuilder().index(__dirname + '/fixtures/index', '/testfile').build();
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('<head><title>/testfile</title></head>', done);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(200)
|
||||
.expect('Content-Type', 'text/html', done);
|
||||
});
|
||||
it('should build app that sets socket.io namespace based on files', (done) => {
|
||||
const app = connectBuilder()
|
||||
.index(path.join(__dirname, 'fixtures/index_with_ns'), '/testfile', 'ns', 'dark')
|
||||
.build();
|
||||
|
||||
it('should build app that replace index title', function (done) {
|
||||
var app = connectBuilder()
|
||||
.index(__dirname + '/fixtures/index_with_title', '/testfile')
|
||||
.build();
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('ns', done);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('<head><title>/testfile</title></head>', done);
|
||||
});
|
||||
it('should build app that sets theme', (done) => {
|
||||
const app = connectBuilder()
|
||||
.index(path.join(__dirname, '/fixtures/index_with_theme'), '/testfile', 'ns', 'dark')
|
||||
.build();
|
||||
|
||||
it('should build app that sets socket.io namespace based on files', function (done) {
|
||||
var app = connectBuilder()
|
||||
.index(__dirname + '/fixtures/index_with_ns', '/testfile', 'ns', 'dark')
|
||||
.build();
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(
|
||||
'<head><title>/testfile</title><link href="dark.css" rel="stylesheet" type="text/css"/></head>',
|
||||
done
|
||||
);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('ns', done);
|
||||
});
|
||||
it('should build app that sets default theme', (done) => {
|
||||
const app = connectBuilder()
|
||||
.index(path.join(__dirname, '/fixtures/index_with_theme'), '/testfile')
|
||||
.build();
|
||||
|
||||
it('should build app that sets theme', function (done) {
|
||||
var app = connectBuilder()
|
||||
.index(__dirname + '/fixtures/index_with_theme', '/testfile', 'ns', 'dark')
|
||||
.build();
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(
|
||||
'<head><title>/testfile</title><link href="dark.css" rel="stylesheet" type="text/css"/></head>',
|
||||
done
|
||||
);
|
||||
});
|
||||
|
||||
it('should build app that sets default theme', function (done) {
|
||||
var app = connectBuilder()
|
||||
.index(__dirname + '/fixtures/index_with_theme', '/testfile')
|
||||
.build();
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(
|
||||
'<head><title>/testfile</title><link href="default.css" rel="stylesheet" type="text/css"/></head>',
|
||||
done
|
||||
);
|
||||
});
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(
|
||||
'<head><title>/testfile</title><link href="default.css" rel="stylesheet" type="text/css"/></head>',
|
||||
done
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,159 +1,166 @@
|
|||
'use strict';
|
||||
|
||||
require('should');
|
||||
var daemon = require('daemon');
|
||||
var optionsParser = require('../lib/options_parser');
|
||||
var daemonize = require('../lib/daemonize');
|
||||
var sinon = require('sinon');
|
||||
var fs = require('fs');
|
||||
const daemon = require('daemon');
|
||||
const optionsParser = require('../lib/options_parser');
|
||||
const daemonize = require('../lib/daemonize');
|
||||
const sinon = require('sinon');
|
||||
const fs = require('fs');
|
||||
|
||||
describe('daemonize', function () {
|
||||
beforeEach(function () {
|
||||
sinon.stub(daemon, 'daemon');
|
||||
daemon.daemon.returns({pid: 1000});
|
||||
sinon.stub(fs, 'writeFileSync');
|
||||
sinon.stub(fs, 'openSync');
|
||||
describe('daemonize', () => {
|
||||
beforeEach(() => {
|
||||
sinon.stub(daemon, 'daemon');
|
||||
daemon.daemon.returns({
|
||||
pid: 1000,
|
||||
});
|
||||
sinon.stub(fs, 'writeFileSync');
|
||||
sinon.stub(fs, 'openSync');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
daemon.daemon.restore();
|
||||
fs.writeFileSync.restore();
|
||||
fs.openSync.restore();
|
||||
});
|
||||
|
||||
describe('should daemon', () => {
|
||||
it('current script', () => {
|
||||
daemonize('script', optionsParser);
|
||||
|
||||
daemon.daemon.lastCall.args[0].should.match('script');
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
daemon.daemon.restore();
|
||||
fs.writeFileSync.restore();
|
||||
fs.openSync.restore();
|
||||
it('with hostname', () => {
|
||||
optionsParser.parse(['node', '/path/to/frontail', '-h', '127.0.0.1']);
|
||||
|
||||
daemonize('script', optionsParser);
|
||||
|
||||
daemon.daemon.lastCall.args[1].should.containDeep(['-h', '127.0.0.1']);
|
||||
});
|
||||
|
||||
describe('should daemon', function () {
|
||||
it('current script', function () {
|
||||
daemonize('script', optionsParser);
|
||||
it('with port', () => {
|
||||
optionsParser.parse(['node', '/path/to/frontail', '-p', '80']);
|
||||
|
||||
daemon.daemon.lastCall.args[0].should.match('script');
|
||||
});
|
||||
daemonize('script', optionsParser);
|
||||
|
||||
it('with hostname', function () {
|
||||
optionsParser.parse(['node', '/path/to/frontail', '-h', '127.0.0.1']);
|
||||
|
||||
daemonize('script', optionsParser);
|
||||
|
||||
daemon.daemon.lastCall.args[1].should.containDeep(['-h', '127.0.0.1']);
|
||||
});
|
||||
|
||||
it('with port', function () {
|
||||
optionsParser.parse(['node', '/path/to/frontail', '-p', '80']);
|
||||
|
||||
daemonize('script', optionsParser);
|
||||
|
||||
daemon.daemon.lastCall.args[1].should.containDeep(['-p', 80]);
|
||||
});
|
||||
|
||||
it('with lines number', function () {
|
||||
optionsParser.parse(['node', '/path/to/frontail', '-n', '1']);
|
||||
|
||||
daemonize('script', optionsParser);
|
||||
|
||||
daemon.daemon.lastCall.args[1].should.containDeep(['-n', 1]);
|
||||
});
|
||||
|
||||
it('with lines stored in browser', function () {
|
||||
optionsParser.parse(['node', '/path/to/frontail', '-l', '1']);
|
||||
|
||||
daemonize('script', optionsParser);
|
||||
|
||||
daemon.daemon.lastCall.args[1].should.containDeep(['-l', 1]);
|
||||
});
|
||||
|
||||
it('with theme', function () {
|
||||
optionsParser.parse(['node', '/path/to/frontail', '-t', 'dark']);
|
||||
|
||||
daemonize('script', optionsParser);
|
||||
|
||||
daemon.daemon.lastCall.args[1].should.containDeep(['-t', 'dark']);
|
||||
});
|
||||
|
||||
it('with authorization', function () {
|
||||
optionsParser.parse(['node', '/path/to/frontail', '-U', 'user', '-P', 'passw0rd']);
|
||||
|
||||
daemonize('script', optionsParser, {doAuthorization: true});
|
||||
|
||||
daemon.daemon.lastCall.args[1].should.containDeep(['-U', 'user', '-P', 'passw0rd']);
|
||||
});
|
||||
|
||||
it('without authorization if option doAuthorization not passed', function () {
|
||||
optionsParser.parse(['node', '/path/to/frontail', '-U', 'user', '-P', 'passw0rd']);
|
||||
|
||||
daemonize('script', optionsParser);
|
||||
|
||||
daemon.daemon.lastCall.args[1].should.not.containDeep(['-U', 'user', '-P', 'passw0rd']);
|
||||
});
|
||||
|
||||
it('with secure connection', function () {
|
||||
optionsParser.parse(['node', '/path/to/frontail', '-k', 'key.file', '-c', 'cert.file']);
|
||||
|
||||
daemonize('script', optionsParser, {doSecure: true});
|
||||
|
||||
daemon.daemon.lastCall.args[1].should.containDeep(['-k', 'key.file', '-c', 'cert.file']);
|
||||
});
|
||||
|
||||
it('without secure connection if option doSecure not passed', function () {
|
||||
optionsParser.parse(['node', '/path/to/frontail', '-k', 'key.file', '-c', 'cert.file']);
|
||||
|
||||
daemonize('script', optionsParser, {doSecure: true});
|
||||
|
||||
daemon.daemon.lastCall.args[1].should.containDeep(['-k', 'key.file', '-c', 'cert.file']);
|
||||
});
|
||||
|
||||
it('with hide-topbar option', function () {
|
||||
optionsParser.parse(['node', '/path/to/frontail', '--ui-hide-topbar']);
|
||||
|
||||
daemonize('script', optionsParser);
|
||||
|
||||
daemon.daemon.lastCall.args[1].should.containDeep(['--ui-hide-topbar']);
|
||||
});
|
||||
|
||||
it('with no-indent option', function () {
|
||||
optionsParser.parse(['node', '/path/to/frontail', '--ui-no-indent']);
|
||||
|
||||
daemonize('script', optionsParser);
|
||||
|
||||
daemon.daemon.lastCall.args[1].should.containDeep(['--ui-no-indent']);
|
||||
});
|
||||
|
||||
it('with highlight option', function () {
|
||||
optionsParser.parse(['node', '/path/to/frontail', '--ui-highlight']);
|
||||
|
||||
daemonize('script', optionsParser);
|
||||
|
||||
daemon.daemon.lastCall.args[1].should.containDeep(['--ui-highlight']);
|
||||
daemon.daemon.lastCall.args[1].should.containDeep(['--ui-highlight-preset', './preset/default.json']);
|
||||
});
|
||||
|
||||
it('with file to tail', function () {
|
||||
optionsParser.parse(['node', '/path/to/frontail', '/path/to/file']);
|
||||
|
||||
daemonize('script', optionsParser);
|
||||
|
||||
daemon.daemon.lastCall.args[1].should.containDeep(['/path/to/file']);
|
||||
});
|
||||
daemon.daemon.lastCall.args[1].should.containDeep(['-p', 80]);
|
||||
});
|
||||
|
||||
it('should write pid to pidfile', function () {
|
||||
optionsParser.parse(['node', '/path/to/frontail', '--pid-path', '/path/to/pid']);
|
||||
it('with lines number', () => {
|
||||
optionsParser.parse(['node', '/path/to/frontail', '-n', '1']);
|
||||
|
||||
daemonize('script', optionsParser);
|
||||
daemonize('script', optionsParser);
|
||||
|
||||
fs.writeFileSync.lastCall.args[0].should.be.equal('/path/to/pid');
|
||||
fs.writeFileSync.lastCall.args[1].should.be.equal(1000);
|
||||
daemon.daemon.lastCall.args[1].should.containDeep(['-n', 1]);
|
||||
});
|
||||
|
||||
it('should log to file', function () {
|
||||
optionsParser.parse(['node', '/path/to/frontail', '--log-path', '/path/to/log']);
|
||||
fs.openSync.returns('file');
|
||||
it('with lines stored in browser', () => {
|
||||
optionsParser.parse(['node', '/path/to/frontail', '-l', '1']);
|
||||
|
||||
daemonize('script', optionsParser);
|
||||
daemonize('script', optionsParser);
|
||||
|
||||
fs.openSync.lastCall.args[0].should.equal('/path/to/log');
|
||||
fs.openSync.lastCall.args[1].should.equal('a');
|
||||
daemon.daemon.lastCall.args[2].should.eql({
|
||||
stdout: 'file',
|
||||
stderr: 'file'
|
||||
});
|
||||
daemon.daemon.lastCall.args[1].should.containDeep(['-l', 1]);
|
||||
});
|
||||
|
||||
it('with theme', () => {
|
||||
optionsParser.parse(['node', '/path/to/frontail', '-t', 'dark']);
|
||||
|
||||
daemonize('script', optionsParser);
|
||||
|
||||
daemon.daemon.lastCall.args[1].should.containDeep(['-t', 'dark']);
|
||||
});
|
||||
|
||||
it('with authorization', () => {
|
||||
optionsParser.parse(['node', '/path/to/frontail', '-U', 'user', '-P', 'passw0rd']);
|
||||
|
||||
daemonize('script', optionsParser, {
|
||||
doAuthorization: true,
|
||||
});
|
||||
|
||||
daemon.daemon.lastCall.args[1].should.containDeep(['-U', 'user', '-P', 'passw0rd']);
|
||||
});
|
||||
|
||||
it('without authorization if option doAuthorization not passed', () => {
|
||||
optionsParser.parse(['node', '/path/to/frontail', '-U', 'user', '-P', 'passw0rd']);
|
||||
|
||||
daemonize('script', optionsParser);
|
||||
|
||||
daemon.daemon.lastCall.args[1].should.not.containDeep(['-U', 'user', '-P', 'passw0rd']);
|
||||
});
|
||||
|
||||
it('with secure connection', () => {
|
||||
optionsParser.parse(['node', '/path/to/frontail', '-k', 'key.file', '-c', 'cert.file']);
|
||||
|
||||
daemonize('script', optionsParser, {
|
||||
doSecure: true,
|
||||
});
|
||||
|
||||
daemon.daemon.lastCall.args[1].should.containDeep(['-k', 'key.file', '-c', 'cert.file']);
|
||||
});
|
||||
|
||||
it('without secure connection if option doSecure not passed', () => {
|
||||
optionsParser.parse(['node', '/path/to/frontail', '-k', 'key.file', '-c', 'cert.file']);
|
||||
|
||||
daemonize('script', optionsParser, {
|
||||
doSecure: true,
|
||||
});
|
||||
|
||||
daemon.daemon.lastCall.args[1].should.containDeep(['-k', 'key.file', '-c', 'cert.file']);
|
||||
});
|
||||
|
||||
it('with hide-topbar option', () => {
|
||||
optionsParser.parse(['node', '/path/to/frontail', '--ui-hide-topbar']);
|
||||
|
||||
daemonize('script', optionsParser);
|
||||
|
||||
daemon.daemon.lastCall.args[1].should.containDeep(['--ui-hide-topbar']);
|
||||
});
|
||||
|
||||
it('with no-indent option', () => {
|
||||
optionsParser.parse(['node', '/path/to/frontail', '--ui-no-indent']);
|
||||
|
||||
daemonize('script', optionsParser);
|
||||
|
||||
daemon.daemon.lastCall.args[1].should.containDeep(['--ui-no-indent']);
|
||||
});
|
||||
|
||||
it('with highlight option', () => {
|
||||
optionsParser.parse(['node', '/path/to/frontail', '--ui-highlight']);
|
||||
|
||||
daemonize('script', optionsParser);
|
||||
|
||||
daemon.daemon.lastCall.args[1].should.containDeep(['--ui-highlight']);
|
||||
daemon.daemon.lastCall.args[1].should.containDeep(['--ui-highlight-preset', './preset/default.json']);
|
||||
});
|
||||
|
||||
it('with file to tail', () => {
|
||||
optionsParser.parse(['node', '/path/to/frontail', '/path/to/file']);
|
||||
|
||||
daemonize('script', optionsParser);
|
||||
|
||||
daemon.daemon.lastCall.args[1].should.containDeep(['/path/to/file']);
|
||||
});
|
||||
});
|
||||
|
||||
it('should write pid to pidfile', () => {
|
||||
optionsParser.parse(['node', '/path/to/frontail', '--pid-path', '/path/to/pid']);
|
||||
|
||||
daemonize('script', optionsParser);
|
||||
|
||||
fs.writeFileSync.lastCall.args[0].should.be.equal('/path/to/pid');
|
||||
fs.writeFileSync.lastCall.args[1].should.be.equal(1000);
|
||||
});
|
||||
|
||||
it('should log to file', () => {
|
||||
optionsParser.parse(['node', '/path/to/frontail', '--log-path', '/path/to/log']);
|
||||
fs.openSync.returns('file');
|
||||
|
||||
daemonize('script', optionsParser);
|
||||
|
||||
fs.openSync.lastCall.args[0].should.equal('/path/to/log');
|
||||
fs.openSync.lastCall.args[1].should.equal('a');
|
||||
daemon.daemon.lastCall.args[2].should.eql({
|
||||
stdout: 'file',
|
||||
stderr: 'file',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,114 +1,120 @@
|
|||
'use strict';
|
||||
|
||||
require('should');
|
||||
var fs = require('fs');
|
||||
var http = require('http');
|
||||
var https = require('https');
|
||||
var serverBuilder = require('../lib/server_builder');
|
||||
var sinon = require('sinon');
|
||||
const fs = require('fs');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const serverBuilder = require('../lib/server_builder');
|
||||
const sinon = require('sinon');
|
||||
|
||||
describe('serverBuilder', function () {
|
||||
describe('serverBuilder', () => {
|
||||
describe('http server', () => {
|
||||
let httpServer;
|
||||
let createServer;
|
||||
|
||||
describe('http server', function () {
|
||||
var httpServer;
|
||||
var createServer;
|
||||
|
||||
beforeEach(function () {
|
||||
httpServer = sinon.createStubInstance(http.Server);
|
||||
httpServer.listen.returns(httpServer);
|
||||
createServer = sinon.stub(http, 'createServer').returns(httpServer);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
createServer.restore();
|
||||
});
|
||||
|
||||
it('should build server', function () {
|
||||
var server = serverBuilder().build();
|
||||
|
||||
createServer.calledOnce.should.equal(true);
|
||||
server.should.be.an.instanceof(http.Server);
|
||||
});
|
||||
|
||||
it('should build server accepting requests', function () {
|
||||
var callback = function () {};
|
||||
|
||||
serverBuilder().use(callback).build();
|
||||
|
||||
createServer.calledWith(callback).should.equal(true);
|
||||
});
|
||||
|
||||
it('should build listening server', function () {
|
||||
serverBuilder().build();
|
||||
|
||||
httpServer.listen.calledOnce.should.equal(true);
|
||||
});
|
||||
|
||||
it('should build server listening on specified port', function () {
|
||||
serverBuilder().port(6666).build();
|
||||
|
||||
httpServer.listen.calledWith(6666).should.equal(true);
|
||||
});
|
||||
|
||||
it('should build server listening on default port', function () {
|
||||
serverBuilder().build();
|
||||
|
||||
httpServer.listen.calledWith(9001).should.equal(true);
|
||||
});
|
||||
|
||||
it('should build server listening on specified host', function () {
|
||||
serverBuilder().host('127.0.0.1').build();
|
||||
|
||||
httpServer.listen.calledWith(9001, '127.0.0.1').should.equal(true);
|
||||
});
|
||||
|
||||
it('should build server listening on default host', function () {
|
||||
serverBuilder().build();
|
||||
|
||||
httpServer.listen.calledWith(9001, null).should.equal(true);
|
||||
});
|
||||
beforeEach(() => {
|
||||
httpServer = sinon.createStubInstance(http.Server);
|
||||
httpServer.listen.returns(httpServer);
|
||||
createServer = sinon.stub(http, 'createServer').returns(httpServer);
|
||||
});
|
||||
|
||||
describe('https server', function () {
|
||||
var httpsServer;
|
||||
var createHttpsServer;
|
||||
var readFileSyncStub;
|
||||
|
||||
beforeEach(function () {
|
||||
httpsServer = sinon.createStubInstance(https.Server);
|
||||
httpsServer.listen.returns(httpsServer);
|
||||
createHttpsServer = sinon.stub(https, 'createServer').returns(httpsServer);
|
||||
readFileSyncStub = sinon.stub(fs, 'readFileSync');
|
||||
readFileSyncStub.withArgs('key.pem').returns('testkey');
|
||||
readFileSyncStub.withArgs('cert.pem').returns('testcert');
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
createHttpsServer.restore();
|
||||
readFileSyncStub.restore();
|
||||
});
|
||||
|
||||
it('should build server', function () {
|
||||
var server = serverBuilder().secure('key.pem', 'cert.pem').build();
|
||||
|
||||
server.should.be.an.instanceof(https.Server);
|
||||
createHttpsServer.calledWith({key: 'testkey', cert: 'testcert'}).should.equal(true);
|
||||
});
|
||||
|
||||
it('should build server accepting requests', function () {
|
||||
var callback = function () {};
|
||||
|
||||
serverBuilder().use(callback).secure('key.pem', 'cert.pem').build();
|
||||
|
||||
createHttpsServer.calledWith({key: 'testkey', cert: 'testcert'}, callback).should.equal(true);
|
||||
});
|
||||
|
||||
it('should throw error if key or cert not provided', function () {
|
||||
readFileSyncStub.restore();
|
||||
|
||||
(function () {
|
||||
serverBuilder().secure('nofile', 'nofile');
|
||||
}).should.throw('No key or certificate file found');
|
||||
});
|
||||
afterEach(() => {
|
||||
createServer.restore();
|
||||
});
|
||||
|
||||
it('should build server', () => {
|
||||
const server = serverBuilder().build();
|
||||
|
||||
createServer.calledOnce.should.equal(true);
|
||||
server.should.be.an.instanceof(http.Server);
|
||||
});
|
||||
|
||||
it('should build server accepting requests', () => {
|
||||
const callback = () => {
|
||||
};
|
||||
|
||||
serverBuilder().use(callback).build();
|
||||
|
||||
createServer.calledWith(callback).should.equal(true);
|
||||
});
|
||||
|
||||
it('should build listening server', () => {
|
||||
serverBuilder().build();
|
||||
|
||||
httpServer.listen.calledOnce.should.equal(true);
|
||||
});
|
||||
|
||||
it('should build server listening on specified port', () => {
|
||||
serverBuilder().port(6666).build();
|
||||
|
||||
httpServer.listen.calledWith(6666).should.equal(true);
|
||||
});
|
||||
|
||||
it('should build server listening on default port', () => {
|
||||
serverBuilder().build();
|
||||
|
||||
httpServer.listen.calledWith(9001).should.equal(true);
|
||||
});
|
||||
|
||||
it('should build server listening on specified host', () => {
|
||||
serverBuilder().host('127.0.0.1').build();
|
||||
|
||||
httpServer.listen.calledWith(9001, '127.0.0.1').should.equal(true);
|
||||
});
|
||||
|
||||
it('should build server listening on default host', () => {
|
||||
serverBuilder().build();
|
||||
|
||||
httpServer.listen.calledWith(9001, null).should.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('https server', () => {
|
||||
let httpsServer;
|
||||
let createHttpsServer;
|
||||
let readFileSyncStub;
|
||||
|
||||
beforeEach(() => {
|
||||
httpsServer = sinon.createStubInstance(https.Server);
|
||||
httpsServer.listen.returns(httpsServer);
|
||||
createHttpsServer = sinon.stub(https, 'createServer').returns(httpsServer);
|
||||
readFileSyncStub = sinon.stub(fs, 'readFileSync');
|
||||
readFileSyncStub.withArgs('key.pem').returns('testkey');
|
||||
readFileSyncStub.withArgs('cert.pem').returns('testcert');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
createHttpsServer.restore();
|
||||
readFileSyncStub.restore();
|
||||
});
|
||||
|
||||
it('should build server', () => {
|
||||
const server = serverBuilder().secure('key.pem', 'cert.pem').build();
|
||||
|
||||
server.should.be.an.instanceof(https.Server);
|
||||
createHttpsServer.calledWith({
|
||||
key: 'testkey',
|
||||
cert: 'testcert',
|
||||
}).should.equal(true);
|
||||
});
|
||||
|
||||
it('should build server accepting requests', () => {
|
||||
const callback = () => {
|
||||
};
|
||||
|
||||
serverBuilder().use(callback).secure('key.pem', 'cert.pem').build();
|
||||
|
||||
createHttpsServer.calledWith({
|
||||
key: 'testkey',
|
||||
cert: 'testcert',
|
||||
}, callback).should.equal(true);
|
||||
});
|
||||
|
||||
it('should throw error if key or cert not provided', () => {
|
||||
readFileSyncStub.restore();
|
||||
|
||||
(() => {
|
||||
serverBuilder().secure('nofile', 'nofile');
|
||||
}).should.throw('No key or certificate file found');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
84
test/tail.js
84
test/tail.js
|
@ -1,55 +1,57 @@
|
|||
'use strict';
|
||||
|
||||
require('should');
|
||||
var fs = require('fs');
|
||||
var tail = require('../lib/tail');
|
||||
var temp = require('temp');
|
||||
const fs = require('fs');
|
||||
const tail = require('../lib/tail');
|
||||
const temp = require('temp');
|
||||
|
||||
var TEMP_FILE_PROFIX = '';
|
||||
var SPAWN_DELAY = 10;
|
||||
const TEMP_FILE_PROFIX = '';
|
||||
const SPAWN_DELAY = 10;
|
||||
|
||||
describe('tail', function () {
|
||||
temp.track();
|
||||
function writeLines(fd, count) {
|
||||
for (let i = 0; i < count; i += 1) {
|
||||
fs.writeSync(fd, `line${i}
|
||||
`);
|
||||
}
|
||||
fs.closeSync(fd);
|
||||
}
|
||||
|
||||
it('calls event line if new line appear in file', function (done) {
|
||||
temp.open(TEMP_FILE_PROFIX, function (err, info) {
|
||||
tail(info.path).on('line', function (line) {
|
||||
line.should.equal('line0');
|
||||
done();
|
||||
});
|
||||
describe('tail', () => {
|
||||
temp.track();
|
||||
|
||||
setTimeout(writeLines, SPAWN_DELAY, info.fd, 1);
|
||||
});
|
||||
it('calls event line if new line appear in file', (done) => {
|
||||
temp.open(TEMP_FILE_PROFIX, (err, info) => {
|
||||
tail(info.path).on('line', (line) => {
|
||||
line.should.equal('line0');
|
||||
done();
|
||||
});
|
||||
|
||||
setTimeout(writeLines, SPAWN_DELAY, info.fd, 1);
|
||||
});
|
||||
});
|
||||
|
||||
it('buffers lines on start', function (done) {
|
||||
temp.open(TEMP_FILE_PROFIX, function (err, info) {
|
||||
writeLines(info.fd, 20);
|
||||
it('buffers lines on start', (done) => {
|
||||
temp.open(TEMP_FILE_PROFIX, (err, info) => {
|
||||
writeLines(info.fd, 20);
|
||||
|
||||
var tailer = tail(info.path, {buffer: 2});
|
||||
setTimeout(function () {
|
||||
tailer.getBuffer().should.be.eql(['line18', 'line19']);
|
||||
done();
|
||||
}, SPAWN_DELAY);
|
||||
});
|
||||
const tailer = tail(info.path, {
|
||||
buffer: 2,
|
||||
});
|
||||
setTimeout(() => {
|
||||
tailer.getBuffer().should.be.eql(['line18', 'line19']);
|
||||
done();
|
||||
}, SPAWN_DELAY);
|
||||
});
|
||||
});
|
||||
|
||||
it('buffers no lines on start by default', function (done) {
|
||||
temp.open(TEMP_FILE_PROFIX, function (err, info) {
|
||||
writeLines(info.fd, 3);
|
||||
it('buffers no lines on start by default', (done) => {
|
||||
temp.open(TEMP_FILE_PROFIX, (err, info) => {
|
||||
writeLines(info.fd, 3);
|
||||
|
||||
var tailer = tail(info.path);
|
||||
setTimeout(function () {
|
||||
tailer.getBuffer().should.be.empty;
|
||||
done();
|
||||
}, SPAWN_DELAY);
|
||||
});
|
||||
const tailer = tail(info.path);
|
||||
setTimeout(() => {
|
||||
tailer.getBuffer().should.be.empty;
|
||||
done();
|
||||
}, SPAWN_DELAY);
|
||||
});
|
||||
|
||||
var writeLines = function (fd, count) {
|
||||
for (var i = 0; i < count; i += 1) {
|
||||
fs.writeSync(fd, 'line' + i + '\n');
|
||||
}
|
||||
fs.closeSync(fd);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue