add --url-path option. Closes #108 (#120)

* Added configuration to fix #108

* Conflicting option

* Updated README

* Fix for web sockets behind nginx

* make url path working without nginx

* update readme
pull/121/head
Maciej Winnicki 2018-04-07 14:16:20 +02:00 committed by GitHub
parent 9be6486c92
commit 35f7e0e80a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 117 additions and 45 deletions

View File

@ -1,6 +1,6 @@
# frontail streaming logs to the browser
```frontail``` is a Node.js application for streaming logs to the browser. It's a `tail -F` with UI.
`frontail` is a Node.js application for streaming logs to the browser. It's a `tail -F` with UI.
![frontial](https://user-images.githubusercontent.com/455261/29570317-660c8122-8756-11e7-9d2f-8fea19e05211.gif)
@ -9,9 +9,9 @@
## Quick start
- `npm i frontail -g` or download a binary file from [Releases](https://github.com/mthenw/frontail/releases) page
- `frontail /var/log/syslog`
- visit [http://127.0.0.1:9001](http://127.0.0.1:9001)
* `npm i frontail -g` or download a binary file from [Releases](https://github.com/mthenw/frontail/releases) page
* `frontail /var/log/syslog`
* visit [http://127.0.0.1:9001](http://127.0.0.1:9001)
## Features
@ -21,7 +21,7 @@
* number of unread logs in favicon
* themes (default, dark)
* [highlighting](#highlighting)
* search (```Tab``` to focus, ```Esc``` to clear)
* search (`Tab` to focus, `Esc` to clear)
* tailing [multiple files](#tailing-multiple-files) and [stdin](#stdin)
* basic authentication
@ -51,10 +51,12 @@
-c, --certificate <cert.pem> Certificate for HTTPS, option works only along with -k option
--pid-path <path> if run as daemon file that will store the process id, default /var/run/frontail.pid
--log-path <path> if run as daemon file that will be used as a log, default /dev/null
--url-path <path> URL path for the browser application, default /
--ui-hide-topbar hide topbar (log file name and search box)
--ui-no-indent don't indent log lines
--ui-highlight highlight words or lines if defined string found in logs, default preset
--ui-highlight-preset <path> custom preset for highlighting (see ./preset/default.json)
--path <path> prefix path for the running application, default /
Web interface runs on **http://127.0.0.1:[port]**.
@ -70,7 +72,7 @@ Use `-` for streaming stdin:
### Highlighting
```--ui-highlight``` option turns on highlighting in UI. By default preset from ```./preset/default.json``` is used:
`--ui-highlight` option turns on highlighting in UI. By default preset from `./preset/default.json` is used:
```
{
@ -85,4 +87,31 @@ Use `-` for streaming stdin:
which means that every "err" string will be in red and every line containing "err" will be bolded.
*New presets are very welcome. If you don't like default or you would like to share yours, please create PR with json file.*
_New presets are very welcome. If you don't like default or you would like to share yours, please create PR with json file._
### Running behind nginx
Using the `--url-path` option `frontail` can run behind nginx with the example configuration
Using `frontail` with `--url-path /frontail`
```
events {
worker_connections 1024;
}
http {
server {
listen 8080;
server_name localhost;
location /frontail {
proxy_pass http://127.0.0.1:9001/frontail;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
}
```

View File

@ -4,7 +4,7 @@ const connect = require('connect');
const cookieParser = require('cookie');
const crypto = require('crypto');
const path = require('path');
const socketio = require('socket.io');
const SocketIO = require('socket.io');
const tail = require('./lib/tail');
const connectBuilder = require('./lib/connect_builder');
const program = require('./lib/options_parser');
@ -30,7 +30,11 @@ 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');
const filesNamespace = crypto
.createHash('md5')
.update(files)
.digest('hex');
const urlPath = program.urlPath.replace(/\/$/, ''); // remove trailing slash
if (program.daemonize) {
daemonize(__filename, program, {
@ -41,7 +45,7 @@ if (program.daemonize) {
/**
* HTTP(s) server setup
*/
const appBuilder = connectBuilder();
const appBuilder = connectBuilder(urlPath);
if (doAuthorization) {
appBuilder.session(sessionSecret, sessionKey);
appBuilder.authorize(program.user, program.password);
@ -63,9 +67,8 @@ if (program.daemonize) {
/**
* socket.io setup
*/
const io = socketio.listen(server, {
log: false,
});
const io = new SocketIO({ path: path.join(urlPath, '/socket.io') });
io.attach(server);
if (doAuthorization) {
io.use((socket, next) => {

View File

@ -3,14 +3,18 @@
const connect = require('connect');
const fs = require('fs');
function ConnectBuilder() {
function ConnectBuilder(urlPath) {
this.app = connect();
this.urlPath = urlPath;
}
ConnectBuilder.prototype.authorize = function authorize(user, pass) {
this.app.use(
this.urlPath,
connect.basicAuth(
(incomingUser, incomingPass) => user === incomingUser && pass === incomingPass));
(incomingUser, incomingPass) => user === incomingUser && pass === incomingPass
)
);
return this;
};
@ -22,7 +26,7 @@ ConnectBuilder.prototype.build = function build() {
ConnectBuilder.prototype.index = function index(path, files, filesNamespace, themeOpt) {
const theme = themeOpt || 'default';
this.app.use((req, res) => {
this.app.use(this.urlPath, (req, res) => {
fs.readFile(path, (err, data) => {
res.writeHead(200, {
'Content-Type': 'text/html',
@ -32,7 +36,8 @@ ConnectBuilder.prototype.index = function index(path, files, filesNamespace, the
.toString('utf-8')
.replace(/__TITLE__/g, files)
.replace(/__THEME__/g, theme)
.replace(/__NAMESPACE__/g, filesNamespace),
.replace(/__NAMESPACE__/g, filesNamespace)
.replace(/__PATH__/g, this.urlPath),
'utf-8'
);
});
@ -42,8 +47,9 @@ ConnectBuilder.prototype.index = function index(path, files, filesNamespace, the
};
ConnectBuilder.prototype.session = function session(secret, key) {
this.app.use(connect.cookieParser());
this.app.use(this.urlPath, connect.cookieParser());
this.app.use(
this.urlPath,
connect.session({
secret,
key,
@ -53,8 +59,8 @@ ConnectBuilder.prototype.session = function session(secret, key) {
};
ConnectBuilder.prototype.static = function staticf(path) {
this.app.use(connect.static(path));
this.app.use(this.urlPath, connect.static(path));
return this;
};
module.exports = () => new ConnectBuilder();
module.exports = urlPath => new ConnectBuilder(urlPath);

View File

@ -14,11 +14,16 @@ module.exports = (script, params, opts) => {
const logFile = fs.openSync(params.logPath, 'a');
let args = [
'-h', params.host,
'-p', params.port,
'-n', params.number,
'-l', params.lines,
'-t', params.theme,
'-h',
params.host,
'-p',
params.port,
'-n',
params.number,
'-l',
params.lines,
'-t',
params.theme,
];
if (options.doAuthorization) {
@ -33,6 +38,10 @@ module.exports = (script, params, opts) => {
args.push('--ui-hide-topbar');
}
if (params.urlPath) {
args.push('--url-path', params.urlPath);
}
if (!params.uiIndent) {
args.push('--ui-no-indent');
}

View File

@ -45,6 +45,7 @@ program
String,
'/dev/null'
)
.option('--url-path <path>', 'URL path for the browser application, default /', String, '/')
.option('--ui-hide-topbar', 'hide topbar (log file name and search box)')
.option('--ui-no-indent', "don't indent log lines")
.option(

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "frontail",
"version": "4.1.0",
"version": "4.1.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@ -6,7 +6,7 @@ const path = require('path');
describe('connectBuilder', () => {
it('should build connect app', () => {
connectBuilder()
connectBuilder('/')
.build()
.should.have.property('use');
connectBuilder()
@ -15,7 +15,7 @@ describe('connectBuilder', () => {
});
it('should build app requiring authorized user', (done) => {
const app = connectBuilder()
const app = connectBuilder('/')
.authorize('user', 'pass')
.build();
@ -26,7 +26,7 @@ describe('connectBuilder', () => {
});
it('should build app allowing user to login', (done) => {
const app = connectBuilder()
const app = connectBuilder('/')
.authorize('user', 'pass')
.build();
app.use((req, res) => {
@ -40,7 +40,7 @@ describe('connectBuilder', () => {
});
it('should build app that setup session', (done) => {
const app = connectBuilder()
const app = connectBuilder('/')
.session('secret', 'sessionkey')
.build();
app.use((req, res) => {
@ -53,7 +53,7 @@ describe('connectBuilder', () => {
});
it('should build app that serve static files', (done) => {
const app = connectBuilder()
const app = connectBuilder('/')
.static(path.join(__dirname, 'fixtures'))
.build();
@ -63,7 +63,7 @@ describe('connectBuilder', () => {
});
it('should build app that serve index file', (done) => {
const app = connectBuilder()
const app = connectBuilder('/')
.index(path.join(__dirname, 'fixtures/index'), '/testfile')
.build();
@ -73,8 +73,19 @@ describe('connectBuilder', () => {
.expect('Content-Type', 'text/html', done);
});
it('should build app that serve index file on specified path', (done) => {
const app = connectBuilder('/test')
.index(path.join(__dirname, 'fixtures/index'), '/testfile')
.build();
request(app)
.get('/test')
.expect(200)
.expect('Content-Type', 'text/html', done);
});
it('should build app that replace index title', (done) => {
const app = connectBuilder()
const app = connectBuilder('/')
.index(path.join(__dirname, 'fixtures/index_with_title'), '/testfile')
.build();
@ -84,7 +95,7 @@ describe('connectBuilder', () => {
});
it('should build app that sets socket.io namespace based on files', (done) => {
const app = connectBuilder()
const app = connectBuilder('/')
.index(path.join(__dirname, 'fixtures/index_with_ns'), '/testfile', 'ns', 'dark')
.build();
@ -94,7 +105,7 @@ describe('connectBuilder', () => {
});
it('should build app that sets theme', (done) => {
const app = connectBuilder()
const app = connectBuilder('/')
.index(path.join(__dirname, '/fixtures/index_with_theme'), '/testfile', 'ns', 'dark')
.build();
@ -107,7 +118,7 @@ describe('connectBuilder', () => {
});
it('should build app that sets default theme', (done) => {
const app = connectBuilder()
const app = connectBuilder('/')
.index(path.join(__dirname, '/fixtures/index_with_theme'), '/testfile')
.build();

View File

@ -107,6 +107,14 @@ describe('daemonize', () => {
daemon.daemon.lastCall.args[1].should.containDeep(['-k', 'key.file', '-c', 'cert.file']);
});
it('with url-path option', () => {
optionsParser.parse(['node', '/path/to/frontail', '--url-path', '/test']);
daemonize('script', optionsParser);
daemon.daemon.lastCall.args[1].should.containDeep(['--url-path', '/test']);
});
it('with hide-topbar option', () => {
optionsParser.parse(['node', '/path/to/frontail', '--ui-hide-topbar']);

View File

@ -1,11 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<title>tail -F __TITLE__</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" type="text/css" href="/styles/__THEME__.css">
<link rel="icon" href="/favicon.ico">
<link rel="stylesheet" type="text/css" href="__PATH__/styles/__THEME__.css">
<link rel="icon" href="__PATH__/favicon.ico">
</head>
<body>
<nav class="topbar navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container-fluid">
@ -20,12 +22,14 @@
<pre class="log"></pre>
<script src="/socket.io/socket.io.js"></script>
<script src="/tinycon.min.js"></script>
<script src="/ansi_up.js"></script>
<script src="/app.js"></script>
<script src="__PATH__/socket.io/socket.io.js"></script>
<script src="__PATH__/tinycon.min.js"></script>
<script src="__PATH__/ansi_up.js"></script>
<script src="__PATH__/app.js"></script>
<script type="text/javascript">
var socket = new io.connect('/' + '__NAMESPACE__');
var socket = new io.connect('/__NAMESPACE__', {
path: '__PATH__/socket.io'
});
window.load = App.init({
socket: socket,
@ -34,6 +38,7 @@
topbar: document.getElementsByClassName('topbar')[0],
body: document.getElementsByTagName('body')[0]
});
</script>
</script>
</body>
</html>
</html>