Realtime dhcp log watching via websockets

This commit is contained in:
Akkadius 2017-09-02 04:44:39 -05:00
parent 2a90e25c61
commit ed96875322
10 changed files with 364 additions and 55 deletions

180
app.js
View File

@ -9,7 +9,7 @@ var app = express();
/* Read Config */
var json_file = require('jsonfile');
glass_config = json_file.readFileSync('config/glass_config.json');
var glass_config = json_file.readFileSync('config/glass_config.json');
// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
@ -25,6 +25,7 @@ app.use('/users', require('./routes/users'));
app.use('/get_dashboard', require('./routes/dashboard'));
app.use('/get_stats', require('./routes/get_stats'));
app.use('/dhcp_leases', require('./routes/dhcp_leases'));
app.use('/dhcp_log', require('./routes/dhcp_log'));
app.use('/api_examples', require('./routes/api_examples'));
app.use('/glass_settings', require('./routes/glass_settings'));
app.use('/glass_settings_save', require('./routes/glass_settings_save'));
@ -37,33 +38,43 @@ app.set('view engine', 'html');
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
var err = new Error('Not Found');
err.status = 404;
next(err);
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.send(err.message);
// render the error page
res.status(err.status || 500);
res.send(err.message);
});
module.exports = app;
/* Tail leases file */
/**
* Global Variables
*/
cpu_utilization = 0;
total_leases = 0;
current_time = 0;
leases_per_second = 0;
current_leases_per_second = 0;
leases_last_update_time = 0;
listening_to_log_file = 0;
options = {};
options.interval = 1000;
/**
* Ingest Current Lease File
*/
var lease_parser = require('./lib/lease_parser.js');
dhcp_lease_data = {};
lease_read_buffer = "";
@ -73,11 +84,14 @@ fs.readFile(glass_config.leases_file, 'utf8', function (err,data) {
if (err) {
return console.log(err);
}
lease_parser.parse(data);
// console.log(JSON.stringify(dhcp_lease_data, null, 2));
else {
lease_parser.parse(data);
}
});
/**
* Leases File Listener
*/
var tail_module = require('always-tail');
tail = new tail_module(
glass_config.leases_file,
@ -109,9 +123,15 @@ tail.on("line", function(data) {
}
});
/* Globals */
cpu_utilization = 0;
total_leases = 0;
/**
* Watch DHCP Log File
*/
var json_file = require('jsonfile');
var glass_config = json_file.readFileSync('config/glass_config.json');
var options = {};
options.interval = 1000;
dashboard_timer = setInterval(function(){
// console.log("Checking timers...");
@ -126,4 +146,128 @@ dashboard_timer = setInterval(function(){
lease_clean_timer = setInterval(function(){
lease_parser.clean();
}, (60 * 1000));
}, (60 * 1000));
function get_socket_clients_connected_count() {
wss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
socket_clients++;
}
});
return socket_clients;
}
/**
* Websocker Server
*/
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
options.interval = 100;
var tail_dhcp_log = new tail_module(
glass_config.log_file,
"\n",
options
);
tail_dhcp_log.on("line", function(data) {
if(listening_to_log_file) {
// console.log(data);
wss.broadcast_event(data, 'dhcp_log_subscription');
}
});
wss.on('connection', function connection(ws) {
socket_clients++;
console.log("[WS] CLIENT_CONNECT: Socket clients (" + socket_clients + ")");
if (!listening_to_log_file) {
/* Watch log file for new information */
var tail_module = require('always-tail');
listening_to_log_file = 1;
}
});
wss.on('close', function close() {
socket_clients--;
console.log("[WS] CLIENT_DISCONNECT: Socket clients (" + socket_clients + ")");
});
function heartbeat() {
this.isAlive = true;
}
function isJson(str) {
try {
JSON.parse(str);
} catch (e) {
return false;
}
return true;
}
wss.on('connection', function connection(ws) {
ws.isAlive = true;
ws.on('pong', heartbeat);
ws.event_subscription = [];
ws.on('message', function incoming(data) {
if(data != "" && isJson(data)) {
var json = JSON.parse(data);
if(typeof json["event_subscription"] !== "undefined"){
console.log("[WS] Incoming Subscription '%s'", json['event_subscription']);
ws.event_subscription[json["event_subscription"]] = 1;
}
if(typeof json["event_unsubscribe"] !== "undefined"){
console.log("[WS] event_unsubscribe '%s'", json['event_unsubscribe']);
delete ws.event_subscription[json["event_unsubscribe"]];
}
}
});
stale_connections_audit();
});
wss.broadcast = function broadcast(data) {
wss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
client.send(data);
}
});
};
wss.broadcast_event = function broadcast(data, event) {
wss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
if(client.event_subscription[event])
client.send(data);
}
});
};
function stale_connections_audit() {
socket_clients = 0;
wss.clients.forEach(function each(ws) {
if (ws.isAlive === false) return ws.terminate();
ws.isAlive = false;
ws.ping('', false, true);
socket_clients++;
});
console.log("[WS] STATUS: Socket clients (" + socket_clients + ")");
}
/* Keepalive - kill stale connections (30s poll) */
const interval = setInterval(function ping() {
stale_connections_audit();
}, 30000);
var socket_clients = 0;

68
bin/www
View File

@ -34,19 +34,19 @@ server.on('listening', onListening);
*/
function normalizePort(val) {
var port = parseInt(val, 10);
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
if (port >= 0) {
// port number
return port;
}
return false;
return false;
}
/**
@ -54,27 +54,27 @@ function normalizePort(val) {
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
@ -82,9 +82,9 @@ function onError(error) {
*/
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}

View File

@ -107,7 +107,7 @@ module.exports = {
end_unix_time = dhcp_lease_data[key].end;
if((now_unix_time >= end_unix_time)) {
console.log("element " + key + " has expired - clearing");
console.log("[DHCP Lease Data] Lease " + key + " has expired - clearing");
delete dhcp_lease_data[key];
}
}

View File

@ -13,7 +13,7 @@ module.exports = {
core = fs.readFileSync('./public/templates/index.html', 'utf8');
core = core.replace(/\[application_name\]/, 'Glass - isc dhcp server utility');
core = core.replace(/\[body_content\]/, body_content);
core = core.replace(/\[(.*?)\]/, "");
// core = core.replace(/\[(.*?)\]/, "");
return core;
}
},

View File

@ -0,0 +1,2 @@
/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
var saveAs=saveAs||function(e){"use strict";if(typeof e==="undefined"||typeof navigator!=="undefined"&&/MSIE [1-9]\./.test(navigator.userAgent)){return}var t=e.document,n=function(){return e.URL||e.webkitURL||e},r=t.createElementNS("http://www.w3.org/1999/xhtml","a"),o="download"in r,a=function(e){var t=new MouseEvent("click");e.dispatchEvent(t)},i=/constructor/i.test(e.HTMLElement)||e.safari,f=/CriOS\/[\d]+/.test(navigator.userAgent),u=function(t){(e.setImmediate||e.setTimeout)(function(){throw t},0)},s="application/octet-stream",d=1e3*40,c=function(e){var t=function(){if(typeof e==="string"){n().revokeObjectURL(e)}else{e.remove()}};setTimeout(t,d)},l=function(e,t,n){t=[].concat(t);var r=t.length;while(r--){var o=e["on"+t[r]];if(typeof o==="function"){try{o.call(e,n||e)}catch(a){u(a)}}}},p=function(e){if(/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)){return new Blob([String.fromCharCode(65279),e],{type:e.type})}return e},v=function(t,u,d){if(!d){t=p(t)}var v=this,w=t.type,m=w===s,y,h=function(){l(v,"writestart progress write writeend".split(" "))},S=function(){if((f||m&&i)&&e.FileReader){var r=new FileReader;r.onloadend=function(){var t=f?r.result:r.result.replace(/^data:[^;]*;/,"data:attachment/file;");var n=e.open(t,"_blank");if(!n)e.location.href=t;t=undefined;v.readyState=v.DONE;h()};r.readAsDataURL(t);v.readyState=v.INIT;return}if(!y){y=n().createObjectURL(t)}if(m){e.location.href=y}else{var o=e.open(y,"_blank");if(!o){e.location.href=y}}v.readyState=v.DONE;h();c(y)};v.readyState=v.INIT;if(o){y=n().createObjectURL(t);setTimeout(function(){r.href=y;r.download=u;a(r);h();c(y);v.readyState=v.DONE});return}S()},w=v.prototype,m=function(e,t,n){return new v(e,t||e.name||"download",n)};if(typeof navigator!=="undefined"&&navigator.msSaveOrOpenBlob){return function(e,t,n){t=t||e.name||"download";if(!n){e=p(e)}return navigator.msSaveOrOpenBlob(e,t)}}w.abort=function(){};w.readyState=w.INIT=0;w.WRITING=1;w.DONE=2;w.error=w.onwritestart=w.onprogress=w.onwrite=w.onabort=w.onerror=w.onwriteend=null;return m}(typeof self!=="undefined"&&self||typeof window!=="undefined"&&window||this.content);if(typeof module!=="undefined"&&module.exports){module.exports.saveAs=saveAs}else if(typeof define!=="undefined"&&define!==null&&define.amd!==null){define("FileSaver.js",function(){return saveAs})}

View File

@ -0,0 +1,82 @@
function log_action (action){
switch(action) {
case "stop":
socket.send(JSON.stringify({"event_unsubscribe": "dhcp_log_subscription"}));
break;
case "start":
socket.send(JSON.stringify({"event_subscription": "dhcp_log_subscription"}));
break;
case "clear":
editor.setValue("");
break;
case "download_logs":
var d = new Date();
var am_pm = format_am_pm(d);
var df = d.getMonth() + '-' + d.getDate() + '-' + d.getFullYear() + '_' + (d.getHours()) + '-' + d.getMinutes() + ' ' + am_pm;
var filename = "dhcp_logs_" + df;
var text = editor.getValue();
var blob = new Blob([text], {type: "text/plain;charset=utf-8"});
saveAs(blob, filename + ".txt");
break;
default:
break;
}
}
function format_am_pm(date) {
var hours = date.getHours();
var minutes = date.getMinutes();
var am_pm = hours >= 12 ? 'PM' : 'AM';
return am_pm;
}
var killed_connection = 0;
if(typeof socket === "undefined") {
var socket = new WebSocket("ws://" + window.location.hostname + ":8080");
socket.onopen = function (event) {
console.log("socket is opened");
console.log("[Subscription] subscribing to dhcp log listen ");
};
socket.onmessage = function (event) {
if(killed_connection)
return false;
if(!document.getElementById("dhcp_log")){
console.log("[WS] DHCP Log unsubscribed");
socket.send(JSON.stringify({"event_unsubscribe": "dhcp_log_subscription"}));
killed_connection = 1;
return false;
}
if(document.getElementById("grep_fitler").value){
var matcher = new RegExp(document.getElementById("grep_fitler").value, "i");
var found = matcher.test(event.data);
if(!found){
return false;
}
}
var session = editor.session;
session.insert({
row: session.getLength(),
column: 0
}, "\n" + event.data);
if(session.getLength() >= 50000){
/* If we get over 500,000 lines lets clear the editor */
editor.setValue("");
}
var row = editor.session.getLength() - 1
var column = editor.session.getLine(row).length // or simply Infinity
editor.gotoLine(row + 1, column);
};
}
var editor = ace.edit("dhcp_log");
editor.setTheme("ace/theme/terminal");
editor.$blockScrolling = Infinity;

View File

@ -0,0 +1,39 @@
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<div class="card">
<div class="header">
<h2>
[title]
</h2>
</div>
<div class="body">
[log_content]
<form>
<label>Actions</label>
<div class="form-group">
<div >
<button type="button" onclick="log_action('start')" class="btn btn-default waves-effect">Start Watch</button>
<button type="button" onclick="log_action('stop')" class="btn btn-default waves-effect">Stop Watch</button>
<button type="button" onclick="log_action('clear')" class="btn btn-default waves-effect">Clear</button>
<button type="button" onclick="log_action('download_logs')" class="btn btn-default waves-effect">Save to File</button>
</div>
</div>
<label>Grep Filter</label>
<div class="form-group">
<div class="form-line">
<input type="text" id="grep_fitler" class="form-control" placeholder="Add text/regex you would like to filter the logs with...">
</div>
</div>
<div id="dhcp_log" style="width:100%; height:800px; color: #95cd24"></div>
</form>
</div>
</div>
</div>
</div>
<script src="assets/js/ace_editor/ace.js" type="text/javascript" charset="utf-8"></script>
<script src="assets/js/ace_editor/theme-terminal.js" type="text/javascript" charset="utf-8"></script>
<script src="assets/js/page-dhcp-logs.js" type="text/javascript" charset="utf-8"></script>

View File

@ -85,7 +85,7 @@
</li>
<li>
<a href="/dhcp_logs" pjax="1">
<a href="/dhcp_log" pjax="1">
<i class="material-icons">view_stream</i>
<span>Logs</span>
</a>
@ -99,6 +99,14 @@
</a>
</li>
<li class="header">Admin</li>
<li>
<a href="/dhcp_config" pjax="1">
<i class="material-icons">mode_edit</i>
<span>DHCP Config</span>
</a>
</li>
<li class="header">Glass API</li>
<li>
<a href="/api_examples" pjax="1">
@ -166,6 +174,8 @@
<script src="assets/js/api-examples.js"></script>
<script src="assets/js/glass-core.js"></script>
<script src="assets/js/file-saver.js"></script>
<script src="assets/plugins/jquery-datatable/jquery.dataTables.js"></script>
<script src="assets/plugins/jquery-datatable/skin/bootstrap/js/dataTables.bootstrap.js"></script>
<script src="assets/plugins/jquery-datatable/extensions/export/dataTables.buttons.min.js"></script>

29
routes/dhcp_log.js Normal file
View File

@ -0,0 +1,29 @@
var express = require('express');
var router = express.Router();
var fs = require('fs');
var template_render = require('../lib/render_template.js');
function human_time (time){
var time = new Date(time);
var year = time.getFullYear();
var month = time.getMonth()+1;
var date1 = time.getDate();
var hour = time.getHours();
var minutes = time.getMinutes();
var seconds = time.getSeconds();
return year + "-" + month+"-"+date1+" "+hour+":"+minutes+":"+seconds;
}
router.get('/', function(req, res, next) {
var content = "";
content = template_render.get_template("dhcp_log");
content = template_render.set_template_variable(content, "title", "DHCP Log");
content = template_render.set_template_variable(content, "log_content", "");
res.send(template_render.get_index_template(content, req.url));
});
module.exports = router;

View File

@ -22,6 +22,9 @@ router.get('/', function(req, res, next) {
/* Config File */
input = input + template_render.form_input('Config File', '<input type="input" class="form-control" id="config_file" placeholder="/etc/dhcp/dhcpd.conf" value="' + glass_config.config_file + '">');
/* Log File */
input = input + template_render.form_input('Log File', '<input type="input" class="form-control" id="log_file" placeholder="/var/log/dhcp.log" value="' + glass_config.log_file + '">');
/* Admin User */
input = input + template_render.form_input('Admin User', '<input type="input" class="form-control" id="admin_user" placeholder="Username" value="' + glass_config.admin_user + '">');
input = input + template_render.form_input('Admin Password', '<input type="input" class="form-control" id="admin_password" placeholder="Password" value="' + glass_config.admin_password + '">');