mirror of https://github.com/mthenw/frontail.git
merger master
commit
2ae3eb9b6c
2
Makefile
2
Makefile
|
@ -2,6 +2,6 @@ lint:
|
|||
@./node_modules/.bin/jshint .
|
||||
|
||||
test:
|
||||
@./node_modules/.bin/mocha --reporter spec
|
||||
@./node_modules/.bin/mocha test/*.js
|
||||
|
||||
.PHONY: lint test
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
-p, --port <port> listening port, default 9001
|
||||
-n, --number <number> starting lines number, default 10
|
||||
-l, --lines <lines> number on lines stored in browser, default 2000
|
||||
-t, --theme <theme> name of the theme (default, dark), default "default"
|
||||
-d, --daemonize run as daemon
|
||||
-U, --user <username> Basic Authentication username, this option works only along with -P option
|
||||
-P, --password <password> Basic Authentication password, this option works only along with -U option
|
||||
|
|
53
index.js
53
index.js
|
@ -1,13 +1,14 @@
|
|||
var connect = require('connect');
|
||||
var cookieParser = require('cookie');
|
||||
var daemon = require('daemon');
|
||||
var fs = require('fs');
|
||||
var http = require('http');
|
||||
var https = require('https');
|
||||
var program = require('commander');
|
||||
var sanitizer = require('validator').sanitize;
|
||||
var socketio = require('socket.io');
|
||||
var tail = require('./lib/tail');
|
||||
var connect = require('connect');
|
||||
var cookieParser = require('cookie');
|
||||
var daemon = require('daemon');
|
||||
var fs = require('fs');
|
||||
var http = require('http');
|
||||
var https = require('https');
|
||||
var program = require('commander');
|
||||
var sanitizer = require('validator').sanitize;
|
||||
var socketio = require('socket.io');
|
||||
var tail = require('./lib/tail');
|
||||
var connectBuilder = require('./lib/connect_builder');
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
@ -56,7 +57,7 @@ var tail = require('./lib/tail');
|
|||
* Daemonize process
|
||||
*/
|
||||
var logFile = fs.openSync(program.logPath, 'a');
|
||||
var args = ['-p', program.port, '-n', program.number, '-l', program.lines];
|
||||
var args = ['-p', program.port, '-n', program.number, '-l', program.lines, '-t', program.theme];
|
||||
|
||||
if (doAuthorization) {
|
||||
args = args.concat(['-U', program.user, '-P', program.password]);
|
||||
|
@ -77,35 +78,17 @@ var tail = require('./lib/tail');
|
|||
/**
|
||||
* HTTP server setup
|
||||
*/
|
||||
var app = connect();
|
||||
var builder = connectBuilder();
|
||||
|
||||
if (doAuthorization) {
|
||||
app
|
||||
.use(connect.cookieParser())
|
||||
.use(connect.session({secret: sessionSecret, key: sessionKey}))
|
||||
.use(connect.basicAuth(function (user, password) {
|
||||
return program.user === user && program.password === password;
|
||||
}));
|
||||
builder.session(sessionSecret, sessionKey);
|
||||
builder.authorize(program.user, program.password);
|
||||
}
|
||||
|
||||
app
|
||||
.use(connect.static(__dirname + '/lib/web/assets'))
|
||||
.use(function (req, res) {
|
||||
fs.readFile(__dirname + '/lib/web/index.html', function (err, data) {
|
||||
if (err) {
|
||||
res.writeHead(500, {'Content-Type': 'text/plain'});
|
||||
res.end('Internal error');
|
||||
} else {
|
||||
|
||||
res.writeHead(200, {'Content-Type': 'text/html'});
|
||||
res.end(data.toString('utf-8').replace(
|
||||
/__TITLE__/g, 'tail -F ' + program.args.join(' ')).replace(
|
||||
/__STYLE__/g, program.theme), 'utf-8'
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
builder.static(__dirname + '/lib/web/assets');
|
||||
builder.index(__dirname + '/lib/web/index.html', program.args.join(' '), program.theme);
|
||||
|
||||
var app = builder.build();
|
||||
var server;
|
||||
|
||||
if (program.key && program.certificate) {
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
var connect = require('connect');
|
||||
var fs = require('fs');
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var ConnectBuilder = function () {
|
||||
this.app = connect();
|
||||
};
|
||||
|
||||
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 () {
|
||||
return this.app;
|
||||
};
|
||||
|
||||
ConnectBuilder.prototype.index = function (path, title, 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, title)
|
||||
.replace(/__THEME__/g, theme),
|
||||
'utf-8'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
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.static = function (path) {
|
||||
this.app.use(connect.static(path));
|
||||
return this;
|
||||
};
|
||||
|
||||
module.exports = function () {
|
||||
return new ConnectBuilder();
|
||||
};
|
||||
})();
|
|
@ -1,14 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>__TITLE__</title>
|
||||
<title>tail -F __TITLE__</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<link rel="stylesheet" type="text/css" href="/styles/__STYLE__.css">
|
||||
<link rel="stylesheet" type="text/css" href="/styles/__THEME__.css">
|
||||
<link rel="icon" href="/favicon.png">
|
||||
</head>
|
||||
<body>
|
||||
<div class="navbar">
|
||||
<span class="brand">__TITLE__</span>
|
||||
<span class="brand">tail -F __TITLE__</span>
|
||||
<input type="text" class="query" placeholder="Filter" tabindex="1">
|
||||
</div>
|
||||
<pre class="log"></pre>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"dependencies": {
|
||||
"commander": "1.3.2",
|
||||
"socket.io": "0.9.16",
|
||||
"connect": "2.8.5",
|
||||
"connect": "2.11.0",
|
||||
"validator": "1.5.0",
|
||||
"daemon": "1.1.0",
|
||||
"cookie": "0.1.0"
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
var connectBuilder = require('../lib/connect_builder');
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
describe('connectBuilder', function () {
|
||||
it('build connect app', function () {
|
||||
connectBuilder().build().should.have.property('use');
|
||||
connectBuilder().build().should.have.property('listen');
|
||||
});
|
||||
|
||||
it('returns app requiring authorized user', function (done) {
|
||||
var app = connectBuilder().authorize('user', 'pass').build();
|
||||
|
||||
app
|
||||
.request()
|
||||
.get('/')
|
||||
.end(function (res) {
|
||||
res.statusCode.should.equal(401);
|
||||
res.headers['www-authenticate'].should.equal('Basic realm="Authorization Required"');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns app allowing user to login', function (done) {
|
||||
var app = connectBuilder().authorize('user', 'pass').build();
|
||||
app.use(function (req, res) {
|
||||
res.end('secret!');
|
||||
});
|
||||
|
||||
app
|
||||
.request()
|
||||
.get('/')
|
||||
.set('Authorization', 'Basic dXNlcjpwYXNz')
|
||||
.end(function (res) {
|
||||
res.statusCode.should.equal(200);
|
||||
res.body.should.equal('secret!');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns app that setup session', function (done) {
|
||||
var app = connectBuilder().session('secret', 'sessionkey').build();
|
||||
app.use(function (req, res) {
|
||||
res.end();
|
||||
});
|
||||
|
||||
app
|
||||
.request()
|
||||
.get('/')
|
||||
.end(function (res) {
|
||||
res.headers['set-cookie'][0].should.startWith('sessionkey');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns app that serve static files', function (done) {
|
||||
var app = connectBuilder().static(__dirname + '/fixtures').build();
|
||||
|
||||
app
|
||||
.request()
|
||||
.get('/foo')
|
||||
.expect('bar', done);
|
||||
});
|
||||
|
||||
it('returns app that serve index file', function (done) {
|
||||
var app = connectBuilder().index(__dirname + '/fixtures/index').build();
|
||||
|
||||
app
|
||||
.request()
|
||||
.get('/')
|
||||
.end(function (res) {
|
||||
res.headers['content-type'].should.equal('text/html');
|
||||
res.statusCode.should.equal(200);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns app that replace index title', function (done) {
|
||||
var app = connectBuilder()
|
||||
.index(__dirname + '/fixtures/index_with_title', 'Test')
|
||||
.build();
|
||||
|
||||
app
|
||||
.request()
|
||||
.get('/')
|
||||
.expect('<head><title>Test</title></head>', done);
|
||||
});
|
||||
|
||||
it('returns app that sets theme', function (done) {
|
||||
var app = connectBuilder()
|
||||
.index(__dirname + '/fixtures/index_with_theme', 'Test', 'dark')
|
||||
.build();
|
||||
|
||||
app
|
||||
.request()
|
||||
.get('/')
|
||||
.expect(
|
||||
'<head><title>Test</title><link href="dark.css" rel="stylesheet" type="text/css"/></head>',
|
||||
done
|
||||
);
|
||||
});
|
||||
|
||||
it('returns app that sets default theme', function (done) {
|
||||
var app = connectBuilder()
|
||||
.index(__dirname + '/fixtures/index_with_theme', 'Test')
|
||||
.build();
|
||||
|
||||
app
|
||||
.request()
|
||||
.get('/')
|
||||
.expect(
|
||||
'<head><title>Test</title><link href="default.css" rel="stylesheet" type="text/css"/></head>',
|
||||
done
|
||||
);
|
||||
});
|
||||
});
|
||||
})();
|
|
@ -0,0 +1 @@
|
|||
bar
|
|
@ -0,0 +1 @@
|
|||
index
|
|
@ -0,0 +1 @@
|
|||
<head><title>__TITLE__</title><link href="__THEME__.css" rel="stylesheet" type="text/css"/></head>
|
|
@ -0,0 +1 @@
|
|||
<head><title>__TITLE__</title></head>
|
|
@ -0,0 +1,3 @@
|
|||
--reporter spec
|
||||
--require test/support/http
|
||||
--require should
|
|
@ -0,0 +1,119 @@
|
|||
var EventEmitter = require('events').EventEmitter;
|
||||
var methods = ['get', 'post', 'put', 'delete', 'head'];
|
||||
var connect = require('connect');
|
||||
var http = require('http');
|
||||
var util = require('util');
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function request(app) {
|
||||
return new Request(app);
|
||||
}
|
||||
|
||||
module.exports = request;
|
||||
|
||||
connect.proto.request = function () {
|
||||
return request(this);
|
||||
};
|
||||
|
||||
function Request(app) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
var self = this;
|
||||
this.data = [];
|
||||
this.header = {};
|
||||
this.app = app;
|
||||
if (!this.server) {
|
||||
this.server = http.Server(app);
|
||||
this.server.listen(0, '127.0.0.1', function () {
|
||||
self.addr = self.server.address();
|
||||
self.listening = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inherit from `EventEmitter.prototype`.
|
||||
*/
|
||||
|
||||
util.inherits(Request, EventEmitter);
|
||||
|
||||
methods.forEach(function (method) {
|
||||
Request.prototype[method] = function (path) {
|
||||
return this.request(method, path);
|
||||
};
|
||||
});
|
||||
|
||||
Request.prototype.set = function (field, val) {
|
||||
this.header[field] = val;
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.write = function (data) {
|
||||
this.data.push(data);
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.request = function (method, path) {
|
||||
this.method = method;
|
||||
this.path = path;
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.expect = function (body, fn) {
|
||||
var args = arguments;
|
||||
this.end(function (res) {
|
||||
switch (args.length) {
|
||||
case 3:
|
||||
res.headers.should.have.property(body.toLowerCase(), args[1]);
|
||||
args[2]();
|
||||
break;
|
||||
default:
|
||||
if ('number' === typeof body) {
|
||||
res.statusCode.should.equal(body);
|
||||
} else {
|
||||
res.body.should.equal(body);
|
||||
}
|
||||
fn();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Request.prototype.end = function (fn) {
|
||||
var self = this;
|
||||
|
||||
if (this.listening) {
|
||||
var req = http.request({
|
||||
method: this.method,
|
||||
port: this.addr.port,
|
||||
host: this.addr.address,
|
||||
path: this.path,
|
||||
headers: this.header
|
||||
});
|
||||
|
||||
this.data.forEach(function (chunk) {
|
||||
req.write(chunk);
|
||||
});
|
||||
|
||||
req.on('response', function (res) {
|
||||
var buf = '';
|
||||
res.setEncoding('utf8');
|
||||
res.on('data', function (chunk) { buf += chunk; });
|
||||
res.on('end', function () {
|
||||
res.body = buf;
|
||||
fn(res);
|
||||
});
|
||||
});
|
||||
|
||||
req.end();
|
||||
} else {
|
||||
this.server.on('listening', function () {
|
||||
self.end(fn);
|
||||
});
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
})();
|
10
test/tail.js
10
test/tail.js
|
@ -1,12 +1,10 @@
|
|||
var fs = require('fs');
|
||||
var tail = require('../lib/tail');
|
||||
var temp = require('temp');
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
require('should');
|
||||
|
||||
var fs = require('fs');
|
||||
var tail = require('../lib/tail');
|
||||
var temp = require('temp');
|
||||
|
||||
describe('tail', function () {
|
||||
temp.track();
|
||||
|
||||
|
|
Loading…
Reference in New Issue