Commit f641ba59 authored by Michal Hrusecky's avatar Michal Hrusecky 🦎

Experimental version using WebWorkers and WordCloud

Displaying and processing all data using WebWorker, support basics like
pagination, sorting, aggregation. All data are used to create a WordCloud that
can be used to provide some kind of overview.

What is currently missing is error handling and some more styling.
parent f88b8747
......@@ -41,10 +41,6 @@ class PakonPluginPage(ConfigPageMixin, PakonPluginConfigHandler):
args['PLUGIN_STYLES'] = PakonPlugin.PLUGIN_STYLES
args['PLUGIN_STATIC_SCRIPTS'] = PakonPlugin.PLUGIN_STATIC_SCRIPTS
args['PLUGIN_DYNAMIC_SCRIPTS'] = PakonPlugin.PLUGIN_DYNAMIC_SCRIPTS
args['query'] = json.dumps({})
data = current_state.backend.perform(
"pakon", "perform_query", {"query_data": args['query']})
args['results'] = self._prepare_data(data)
def render(self, **kwargs):
self._prepare_render_args(kwargs)
......@@ -55,20 +51,14 @@ class PakonPluginPage(ConfigPageMixin, PakonPluginConfigHandler):
def call_ajax_action(self, action):
if action == "perform_query":
if bottle.request.method != 'POST':
raise bottle.HTTPError(404, "Wrong http method (only POST is allowed.")
if bottle.request.method != 'GET':
raise bottle.HTTPError(404, "Wrong http method (only GET is allowed.")
query_data = bottle.request.POST.get('query', {})
query_data = bottle.request.GET.get('query', {})
data = current_state.backend.perform(
"pakon", "perform_query", {"query_data": query_data})
if "text/html" in bottle.request.headers["Accept"]:
return bottle.template(
"pakon/_results",
results=self._prepare_data(data),
)
else:
bottle.response.content_type = "application/json"
return data["response_data"]
bottle.response.content_type = "application/json"
return data["response_data"]
raise ValueError("Unknown AJAX action.")
......@@ -81,6 +71,7 @@ class PakonPlugin(ForisPlugin):
"css/pakon.css"
]
PLUGIN_STATIC_SCRIPTS = [
"js/jQWCloudv3.1.js"
]
PLUGIN_DYNAMIC_SCRIPTS = [
"pakon.js"
......
@keyframes spinner {
from {transform:rotate(0deg);}
to {transform: rotate(360deg);}
}
.spinner:after {
content: '';
box-sizing: border-box;
position: fixed;
top: 50%;
left: 50%;
width: 150px;
height: 150px;
margin-top: -75px;
margin-left: -75px;
border-radius: 50%;
border: 5px solid #ccc;
border-top-color: #00a2e2;
animation: spinner .6s linear infinite;
}
i {
padding-left: 5px;
padding-right: 5px;
}
#pakon-pager-page li {
display: inline;
margin: 5px;
text-decoration: underline;
}
#pakon-pager-page li.current-page {
text-decoration: none;
font-weight: bold;
}
.extra_row {
display: none;
font-style: italic;
}
#date-from, #date-to, #time-from, #time-to, #apply-changes {
float: right;
margin-left: 10px;
margin-right: 0px;
}
#page-pakon-plugin {
display: inline-block;
}
#filter-form {
display: inline-block;
clear: both;
}
#client-filter, #hostname-filter {
width: 100%;
}
label {
padding-top: 10px;
display: block;
width: 100%;
}
#no-data {
text-align: center;
}
#pakon-results {
white-space: nowrap;
}
#pakon-results td, th {
padding: 5px;
padding-left: 10px;
padding-right: 10px;
}
#pakon-results th {
font-weight: bold;
}
tr.odd, tr.even, tr.extra_row.even, tr.extra_row.odd {
border-style: none none dashed none;
border-width: 2px;
border-color: rgba(255,255,255,0);
}
tr.odd:hover, tr.even:hover, tr.extra_row.even:hover, tr.extra_row.odd:hover {
border-color: rgba(0,0,0,1);
}
tr.odd {
background: #EEE;
}
tr.even {
background: #FFF;
}
tr.extra_row.even {
background: #DDD;
}
tr.extra_row.odd {
background: #EEE;
}
td:nth-child(7), td:nth-child(8), th:nth-child(7), th:nth-child(8) {
text-align: right;
}
#pakon-pager {
margin-top: 10px;
text-align: right;
}
#pakon-pager-page {
margin: 10px;
display: inline-block;
}
#pakon-pager-pagesize {
display: inline-block;
width: 10em;
}
This diff is collapsed.
This diff is collapsed.
#pakon-results
td, th
padding: 3px
th
text-align: center
font-weight: bold
Foris.update_pakon_data = function(data_type) {
$.ajax({
type: 'post',
url:'{{ url("config_ajax", page_name="pakon") }}',
data: {
action: "perform_query",
query: $("#field-query").val(),
csrf_token: $("#csrf-token").val()
},
dataType: data_type,
}).done(function(response, status, xhr) {
if (xhr.status == 200) {
$("#pakon-results").replaceWith(response); // replace the table
}
}).fail(function(xhr) {
if (xhr.responseJSON && xhr.responseJSON.loggedOut && xhr.responseJSON.loginUrl) {
window.location.replace(xhr.responseJSON.loginUrl);
return;
}
});
/*
$.post(
'{{ url("config_ajax", page_name="pakon") }}', {
action: "perform_query",
query: $("#field-query").val(),
csrf_token: $("#csrf-token").val()
}).done(function(response, status, xhr) {
if (xhr.status == 200) {
$("#pakon-results").replaceWith(response); // replace the table
}
})
.fail(function(xhr) {
if (xhr.responseJSON && xhr.responseJSON.loggedOut && xhr.responseJSON.loginUrl) {
window.location.replace(xhr.responseJSON.loginUrl);
if (typeof(Worker) == "undefined") {
alert("{{ trans("Your browser doesn't support web workers, get a browser from this milenia") }}")
} else {
var wrk = new Worker("{{ static("plugins/pakon/js/data_manager.js") }}");
var sort_by = [0, 1];
var hosts = [];
var from;
var to;
var page_size = 25;
var clients = [];
var filter_changed = true;
var date_changed = true;
function toogle_lines(line) {
if($('#line_' + line + ' td i').attr('class') == 'fas fa-plus') {
$('.extra_' + line).show();
$('#line_' + line + ' td i').removeClass('fa-plus').addClass('fa-minus');
} else {
$('.extra_' + line).hide();
$('#line_' + line + ' td i').addClass('fa-plus').removeClass('fa-minus');
}
}
function toogle_sort(by) {
dnd();
if(by === sort_by[0]) {
sort_by[1] = sort_by[1] > 0 ? -1 : 1;
} else {
sort_by[0] = by;
if(by > 1 && by < 5) {
sort_by[1] = 1;
} else {
sort_by[1] = -1;
}
}
wrk.postMessage({
'command': 'sort',
'sort_by': sort_by
});
}
function goto_page(i) {
dnd();
wrk.postMessage({
'command': 'page',
'page_size': page_size,
'page': i
});
}
function filter() {
if(!filter_changed)
return;
}
hosts = $('#hostname-filter').val().split(",").map(function (e) {
console.log('Filtering hosts with ' + e.trim());
return new RegExp(e.trim())
});
*/
};
Foris.WS["pakon"] = function(msg) {
// No notifications for pakon right now
};
$(document).ready(function() {
$("#pakon-query-trigger").click(function(e) {
e.preventDefault();
Foris.update_pakon_data("html")
});
clients = $('#client-filter').val().split(",").map(function (e) {
console.log('Filtering clients with ' + e.trim());
return new RegExp(e.trim())
});
}
function get_time_vars() {
let time_from = $('#time-from').val();
if(time_from == "") {
$('#time-from').val('00:00:00.00');
time_from = "00:00:00";
}
from = Date.parse($('#date-from').val() + ' ' + time_from);
let time_to = $('#time-to').val();
if(time_to == "") {
$('#time-to').val('00:00:00.00');
time_to = "00:00:00";
}
to = Date.parse($('#date-to').val() + ' ' + time_to);
}
function apply() {
dnd();
filter();
get_time_vars();
let command = {
'command': 'change'
};
command.ajax_url = '{{ url("config_ajax", page_name="pakon") }}';
command.aggregate = $('#aggregate').val();
if(filter_changed) {
command.filter_hosts = hosts;
command.filter_clients = clients;
}
if(date_changed) {
command.date_from = Math.floor(from / 1000);
command.date_to = Math.floor(to / 1000);
}
console.log(command);
wrk.postMessage(command);
date_changed = false;
filter_changed = false;
}
function dnd() {
$("#spinner").addClass('spinner');
$("#tagcloud").css("height", '0px');
$("#apply-changes").prop("disabled", true);
$("#pakon-table-data").html('');
}
function available() {
$("#apply-changes").prop("disabled", false);
$("#spinner").removeClass('spinner');
}
function zp(data) {
if(data < 9) {
return '0' + data;
} else {
return '' + data;
}
}
$(document).ready(function () {
dnd();
$(".sortable i").addClass("fa fa-sort");
for(let i = 0; i < 7; i++) {
$(".sortable").eq(i).on("click", {
value: i
}, function (event) {
dnd();
toogle_sort(event.data.value);
});
}
var dt_now = new Date(Date.now());
$('#date-to').val('' + dt_now.getFullYear() + '-' + zp(dt_now.getMonth() + 1) + '-' + zp(dt_now.getDate()));
$('#time-to').val('' + zp(dt_now.getHours()) + ':' + zp(dt_now.getMinutes()));
dt_now.setTime(dt_now.getTime() - 24 * 3600 * 1000);
$('#date-from').val('' + dt_now.getFullYear() + '-' + zp(dt_now.getMonth() + 1) + '-' + zp(dt_now.getDate()))
$('#time-from').val('' + zp(dt_now.getHours()) + ':' + zp(dt_now.getMinutes()));
apply();
wrk.onmessage = function (e) {
console.log('Message received from worker');
console.log(e.data);
if(e.data.pager) {
$("#pakon-pager-page").html(e.data.pager);
}
if(e.data.page_size) {
$("pakon-pager-pagesize").val(e.data.page_size);
page_size = e.data.page_size;
}
if(e.data.word_list && e.data.word_list[0]) {
$("#tagcloud").css("height", '750px');
$("#tagcloud").css("width", '750px');
$("#tagcloud").css("margin-left", '-375px');
$("#tagcloud").css("left", '50%');
$('#tagcloud').jQWCloud({
words: e.data.word_list,
word_mouseOver: function () {
$(this).css("text-decoration", "underline");
},
word_mouseOut: function () {
$(this).css("text-decoration", "none");
},
word_click: function () {
let filter = $(this).text();
filter_changed = true;
if($("#hostname-filter").val() == "") {
$("#hostname-filter").val(filter);
} else {
$("#hostname-filter").val($("#hostname-filter").val() + ', ' + filter);
}
}
});
} else {
$("#tagcloud").css("height", '0px');
}
if(e.data.sort_by) {
available();
sort_by = e.data.sort_by;
$(".sortable i").removeClass("fa-sort-up").removeClass("fa-sort-down").addClass("fa-sort");
$("th.sortable:eq(" + sort_by[0] + ") i").removeClass("fa-sort").addClass(sort_by[1] < 0 ? "fa-sort-down" : "fa-sort-up");
}
if(e.data.table) {
$("#pakon-table-data").html(e.data.table);
available();
}
};
});
}
<div id="pakon-results">
%if results:
<table id="pakon-results-table">
<tbody>
%for record in results:
<tr>
%for item in record:
<td>{{ item }}</td>
%end
</tr>
%end
</tbody>
</table>
%else:
<p>{{ trans("No results found.") }}</p>
%end
</div>
......@@ -4,16 +4,68 @@
<div id="page-pakon-plugin" class="config-page">
%include("_messages.tpl")
<form action="{{ url("config_action", page_name="pakon", action="perform_query") }}" class="config-form" method="post">
<div class="row">
<label for="field-lan_ipaddr">Router IP address</label>
<input name="query" value="{{ query }}" type="text" id="field-query"/>
<input type="hidden" name="csrf_token" value="{{ get_csrf_token() }}" id="csrf-token">
</div>
<div class="row">
<button type="submit" name="action" id="pakon-query-trigger" value="query-trigger" class="button">{{ trans("Query") }}</button>
<div id="filter-form">
<div id="spinner"></div>
<fieldset>
<legend>
<h3>{{ trans("Filtering:") }}</h3>
</legend>
<label for="date-from" title="Date From">
<span>{{ trans("From:") }}</span>
<input name="time-from" id="time-from" type="time" onChange="date_changed=true;">
<input name="date-from" id="date-from" type="date" onChange="date_changed=true;">
</label>
<label for="date-to" title="Date To">
<span>{{ trans("To:") }}</span>
<input name="time-to" id="time-to" type="time" onChange="date_changed=true;">
<input name="date-to" id="date-to" type="date" onChange="date_changed=true;">
</label>
<label for="aggregate" tabindex="0" class="checkbox">
{{ trans("Aggregation by hostname:") }}
<input name="aggregate" id="aggregate" tabindex="-1" checked="" style="pointer-events: none;" type="checkbox">
</label>
<label for="hostname-filter">
{{ trans("Only following hostnames:") }}
<br>
<textarea name="hostname-filter" id="hostname-filter" placeholder="All hostnames if empty" onChange="filter_changed=true;"></textarea>
</label>
<label for="srcMAC-filter">
{{ trans("Only following clients:") }}
<br>
<textarea name="client-filter" id="client-filter" placeholder="All clients if empty" onChange="filter_changed=true;"></textarea>
</label>
<label for="apply-changes">
<input id="apply-changes" class="button" onClick="apply()" value="Apply changes" type="submit">
</label>
</fieldset>
</div>
<h3>{{ trans("Overview") }}</h3>
<div id="tagcloud"></div>
<h3>{{ trans("Results") }}</h3>
<div id="pakon-results">
<table id="pakon-results-table">
<thead><tr>
<th></th>
<th class="sortable"><i></i>Date</th>
<th class="sortable"><i></i>Duration</th>
<th class="sortable"><i></i>Client</th>
<th class="sortable"><i></i>Hostname</th>
<th class="sortable"><i></i>Port</th>
<th class="sortable"><i></i>Sent</th>
<th class="sortable"><i></i>Received</th>
</tr></thead><tbody id="pakon-table-data">
</table>
<div id="pakon-pager">
<p id="pakon-pager-page"></p>
<select id="pakon-pager-pagesize" onChange="dnd(); page_size = this.value; wrk.postMessage({'command': 'page', 'page_size': this.value, 'page': 0});">
<option value="50">50 per page</option>
<option value="100">100 per page</option>
<option value="200">200 per page</option>
<option value="500">500 per page</option>
<option value="0">everything</option>
</select>
</div>
</form>
<h3>{{ trans("Results") }}</h3>
%include("pakon/_results.tpl", results=results)
</div>
</div>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment