Realtime dhcp log watching via websockets
This commit is contained in:
parent
2a90e25c61
commit
ed96875322
180
app.js
180
app.js
|
@ -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
68
bin/www
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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})}
|
|
@ -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;
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
|
@ -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 + '">');
|
||||
|
|
Loading…
Reference in New Issue