1 // Display the channel records system from Kismet
3 // Requires js-storage and jquery be loaded first
5 // dragorn@kismetwireless.net
6 // MIT/GPL License (pick one); the web ui code is licensed more
7 // freely than GPL to reflect the generally MIT-licensed nature
8 // of the web/jquery environment
13 var local_uri_prefix = "";
14 if (typeof(KISMET_URI_PREFIX) !== 'undefined')
15 local_uri_prefix = KISMET_URI_PREFIX;
21 var channeldisplay_refresh = function(state) {
22 clearTimeout(state.timerid);
24 if (!kismet_ui.window_visible || state.element.is(':hidden')) {
29 $.get(local_uri_prefix + state.options.url + "channels/channels.json")
30 .done(function(data) {
31 data = kismet.sanitizeObject(data);
33 var devtitles = new Array();
34 var devnums = new Array();
36 // Chart type from radio buttons
37 var charttype = $("input[name='graphtype']:checked", state.graphtype).val();
38 // Chart timeline from selector
39 var charttime = $('select#historyrange option:selected', state.element).val();
40 // Frequency translation from selector
41 var freqtrans = $('select#k_cd_freq_selector option:selected', state.element).val();
42 // Pull from the stored value instead of the live
43 var filter_string = state.storage.get('jquery.kismet.channels.filter');
45 // historical line chart
46 if (charttype === 'history') {
47 var pointtitles = new Array();
48 var datasets = new Array();
53 if (charttime === 'min') {
55 for (var x = 60; x > 0; x--) {
63 rrd_type = kismet.RRD_SECOND;
64 rrd_data = "kismet.channelrec.device_rrd/kismet.common.rrd.minute_vec";
65 } else if (charttime === 'hour') {
67 for (var x = 60; x > 0; x--) {
75 rrd_type = kismet.RRD_MINUTE;
76 rrd_data = "kismet.channelrec.device_rrd/kismet.common.rrd.hour_vec";
80 for (var x = 24; x > 0; x--) {
88 rrd_type = kismet.RRD_HOUR;
89 rrd_data = "kismet.channelrec.device.rrd/kismet.common.rrd.day_vec";
93 // Position in the color map
95 var nkeys = Object.keys(data['kismet.channeltracker.frequency_map']).length;
97 var filter = $('select#gh_filter', state.element);
100 if (filter_string === '' || filter_string === 'any') {
104 selected: "selected",
117 for (var fk in data['kismet.channeltracker.frequency_map']) {
119 kismet.RecalcRrdData(
120 data['kismet.channeltracker.frequency_map'][fk]['kismet.channelrec.device_rrd']['kismet.common.rrd.last_time'],
121 data['kismet.channeltracker.frequency_map'][fk]['kismet.channelrec.device_rrd']['kismet.common.rrd.last_time'],
123 kismet.ObjectByString(
124 data['kismet.channeltracker.frequency_map'][fk],
128 // Convert the freq name
129 var cfk = kismet_ui.GetConvertedChannel(freqtrans, fk);
134 label = kismet.HumanReadableFrequency(parseInt(fk));
138 // Make a filter option
139 if (filter_string === fk) {
143 selected: "selected",
156 // Rotate through the color wheel
157 var color = parseInt(255 * (colorpos / nkeys));
160 // Build the dataset record
166 borderColor: "hsl(" + color + ", 100%, 50%)",
167 backgroundColor: "hsl(" + color + ", 100%, 50%)",
170 // Add it to the dataset if we're not filtering
171 if (filter_string === fk ||
172 filter_string === '' ||
173 filter_string === 'any') {
178 state.devgraph_canvas.hide();
179 state.timegraph_canvas.show();
180 state.coming_soon.hide();
182 if (state.timegraph_chart == null) {
183 var device_options = {
187 maintainAspectRatio: false,
213 state.timegraph_chart = new Chart(state.timegraph_canvas,
216 state.timegraph_chart.data.datasets = datasets;
217 state.timegraph_chart.data.labels = pointtitles;
219 state.timegraph_chart.update(0);
222 // 'now', but default - if for some reason we didn't get a
223 // value from the selector, this falls through to the bar graph
224 // which is what we probably really want
225 for (var fk in data['kismet.channeltracker.frequency_map']) {
227 (data['kismet.channeltracker.frequency_map'][fk]['kismet.channelrec.device_rrd']['kismet.common.rrd.last_time']) % 60;
228 var dev_now = data['kismet.channeltracker.frequency_map'][fk]['kismet.channelrec.device_rrd']['kismet.common.rrd.minute_vec'][slot_now];
230 var cfk = kismet_ui.GetConvertedChannel(freqtrans, fk);
233 devtitles.push(kismet.HumanReadableFrequency(parseInt(fk)));
237 devnums.push(dev_now);
240 state.devgraph_canvas.show();
241 state.timegraph_canvas.hide();
243 if (state.devgraph_chart == null) {
244 state.coming_soon.hide();
246 var device_options = {
250 maintainAspectRatio: false,
263 label: "Devices per Channel",
264 backgroundColor: 'rgba(160, 160, 160, 1)',
272 state.devgraph_chart = new Chart(state.devgraph_canvas,
276 state.devgraph_chart.data.datasets[0].data = devnums;
277 state.devgraph_chart.data.labels = devtitles;
279 state.devgraph_chart.update();
285 state.timerid = setTimeout(function() { channeldisplay_refresh(state); }, 5000);
289 var update_graphtype = function(state, gt = null) {
292 gt = $("input[name='graphtype']:checked", state.graphtype).val();
294 state.storage.set('jquery.kismet.channels.graphtype', gt);
297 $("select#historyrange", state.graphtype).hide();
298 $("select#gh_filter", state.graphtype).hide();
299 $("label#gh_filter_label", state.graphtype).hide();
301 $("select#historyrange", state.graphtype).show();
302 $("label#gh_filter_label", state.graphtype).show();
303 $("select#gh_filter", state.graphtype).show();
306 charttime = $('select#historyrange option:selected', state.element).val();
307 state.storage.set('jquery.kismet.channels.range', charttime);
310 var channels_resize = function(state) {
311 console.log('resize container ', state.devgraph_container.width(), state.devgraph_container.height());
313 if (state.devgraph_canvas != null)
314 state.devgraph_canvas
315 .prop('width', state.devgraph_container.width())
316 .prop('height', state.devgraph_container.height());
318 if (state.timegraph_canvas != null)
319 state.timegraph_canvas
320 .prop('width', state.devgraph_container.width())
321 .prop('height', state.devgraph_container.height());
323 if (state.devgraph_chart != null)
324 state.devgraph_chart.resize();
326 if (state.timegraph_chart != null)
327 state.timegraph_chart.resize();
329 channeldisplay_refresh(state);
332 $.fn.channels = function(inopt) {
335 options: base_options,
337 devgraph_container: null,
338 devgraph_chart: null,
339 timegraph_chart: null,
340 devgraph_canvas: null,
341 timegraph_canvas: null,
351 // Modeled on the datatables resize function
352 state.resizer = $('<iframe/>')
354 position: 'absolute',
362 .attr('frameBorder', '0')
363 .attr('src', 'about:blank');
365 state.resizer[0].onload = function() {
366 var body = this.contentDocument.body;
367 var height = body.offsetHeight;
368 var width = body.offsetWidth;
369 var contentDoc = this.contentDocument;
370 var defaultView = contentDoc.defaultView || contentDoc.parentWindow;
372 defaultView.onresize = function () {
373 var newHeight = body.clientHeight || body.offsetHeight;
374 var docClientHeight = contentDoc.documentElement.clientHeight;
376 var newWidth = body.clientWidth || body.offsetWidth;
377 var docClientWidth = contentDoc.documentElement.clientWidth;
379 if ( ! newHeight && docClientHeight ) {
380 newHeight = docClientHeight;
383 if ( ! newWidth && docClientWidth ) {
384 newWidth = docClientWidth;
387 if ( newHeight !== height || newWidth !== width ) {
390 console.log("triggered resizer", height, width);
391 channels_resize(state);
397 .appendTo(state.element)
398 .attr('data', 'about:blank');
400 state.element.on('resize', function() {
401 console.log("element resize");
402 channels_resize(state);
405 state.element.addClass(".channels");
407 state.storage = Storages.localStorage;
409 var stored_gtype = "now";
410 var stored_channel = "Frequency";
411 var stored_range = "min";
413 if (state.storage.isSet('jquery.kismet.channels.graphtype'))
414 stored_gtype = state.storage.get('jquery.kismet.channels.graphtype');
416 if (state.storage.isSet('jquery.kismet.channels.channeltype'))
417 stored_channel = state.storage.get('jquery.kismet.channels.channeltype');
419 if (state.storage.isSet('jquery.kismet.channels.range'))
420 stored_range = state.storage.get('jquery.kismet.channels.range');
423 if (!state.storage.isSet('jquery.kismet.channels.filter'))
424 state.storage.set('jquery.kismet.channels.filter', 'any');
426 state.visible = state.element.is(":visible");
428 if (typeof(inopt) === "string") {
431 state.options = $.extend(base_options, inopt);
434 var banner = $('div.k_cd_banner', state.element);
435 if (banner.length == 0) {
436 banner = $('<div>', {
441 state.element.append(banner);
444 if (state.graphtype == null) {
445 state.graphtype = $('<div>', {
447 "class": "k_cd_type",
454 .tooltipster({ content: 'Realtime devices-per-channel'})
456 .append($('<input>', {
468 .tooltipster({ content: 'Historical RRD device records'})
470 .append($('<input>', {
480 class: "k_cd_hr_list"
507 id: "gh_filter_label",
519 selected: "selected",
527 // Select time range from stored value
528 $('option#hr_' + stored_range, state.graphtype).attr('selected', 'selected');
530 // Select graph type from stored value
531 if (stored_gtype == 'now') {
532 $('input#gt_bar', state.graphtype).attr('checked', 'checked');
534 $('input#gt_line', state.graphtype).attr('checked', 'checked');
537 banner.append(state.graphtype);
539 update_graphtype(state, stored_gtype);
541 state.graphtype.on('change', function() {
542 update_graphtype(state);
543 channeldisplay_refresh(state);
546 $('select#gh_filter', state.graphtype).on('change', function() {
547 var filter_string = $('select#gh_filter option:selected', state.element).val();
548 state.storage.set('jquery.kismet.channels.filter', filter_string);
549 channeldisplay_refresh(state);
554 if (state.picker == null) {
555 state.picker = $('<div>', {
557 class: "k_cd_picker",
560 var sel = $('<select>', {
561 id: "k_cd_freq_selector",
565 state.picker.append(sel);
567 var chlist = new Array();
568 chlist.push("Frequency");
570 chlist = chlist.concat(kismet_ui.GetChannelListKeys());
572 for (var c in chlist) {
573 var e = $('<option>', {
577 if (chlist[c] === stored_channel)
578 e.attr("selected", "selected");
583 banner.append(state.picker);
585 state.picker.on('change', function() {
587 $('select#k_cd_freq_selector option:selected', state.element).val();
589 state.storage.set('jquery.kismet.channels.channeltype', freqtrans);
591 channeldisplay_refresh(state);
596 if (state.devgraph_container == null) {
597 state.devgraph_container =
599 class: "k_cd_container"
602 state.devgraph_canvas = $('<canvas>', {
606 state.devgraph_container.append(state.devgraph_canvas);
608 state.timegraph_canvas = $('<canvas>', {
612 state.timegraph_canvas.hide();
613 state.devgraph_container.append(state.timegraph_canvas);
615 state.element.append(state.devgraph_container);
618 // Add a 'coming soon' item
619 if (state.coming_soon == null) {
620 state.coming_soon = $('<i>', {
623 state.coming_soon.html("Channel data loading...");
624 state.element.append(state.coming_soon);
627 // Hook an observer to see when we become visible
628 var observer = new MutationObserver(function(mutations) {
629 if (state.element.is(":hidden") && state.timerid >= 0) {
630 state.visible = false;
631 clearTimeout(state.timerid);
632 } else if (state.element.is(":visible") && !state.visible) {
633 state.visible = true;
634 channeldisplay_refresh(state);
638 observer.observe(state.element[0], {
643 channeldisplay_refresh(state);