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
167 borderColor: "hsl(" + color + ", 100%, 50%)",
168 backgroundColor: "hsl(" + color + ", 100%, 50%)",
171 // Add it to the dataset if we're not filtering
172 if (filter_string === fk ||
173 filter_string === '' ||
174 filter_string === 'any') {
179 state.devgraph_canvas.hide();
180 state.timegraph_canvas.show();
181 state.coming_soon.hide();
183 if (state.timegraph_chart == null) {
184 var device_options = {
188 maintainAspectRatio: false,
214 state.timegraph_chart = new Chart(state.timegraph_canvas,
217 state.timegraph_chart.data.datasets = datasets;
218 state.timegraph_chart.data.labels = pointtitles;
220 state.timegraph_chart.update();
223 // 'now', but default - if for some reason we didn't get a
224 // value from the selector, this falls through to the bar graph
225 // which is what we probably really want
226 for (var fk in data['kismet.channeltracker.frequency_map']) {
228 (data['kismet.channeltracker.frequency_map'][fk]['kismet.channelrec.device_rrd']['kismet.common.rrd.last_time']) % 60;
229 var dev_now = data['kismet.channeltracker.frequency_map'][fk]['kismet.channelrec.device_rrd']['kismet.common.rrd.minute_vec'][slot_now];
231 var cfk = kismet_ui.GetConvertedChannel(freqtrans, fk);
234 devtitles.push(kismet.HumanReadableFrequency(parseInt(fk)));
238 devnums.push(dev_now);
241 state.devgraph_canvas.show();
242 state.timegraph_canvas.hide();
244 if (state.devgraph_chart == null) {
245 state.coming_soon.hide();
247 var device_options = {
251 maintainAspectRatio: false,
264 label: "Devices per Channel",
265 backgroundColor: kismet_theme.graphBasicColor,
273 state.devgraph_chart = new Chart(state.devgraph_canvas,
277 state.devgraph_chart.data.datasets[0].data = devnums;
278 state.devgraph_chart.data.labels = devtitles;
280 state.devgraph_chart.update();
286 state.timerid = setTimeout(function() { channeldisplay_refresh(state); }, 5000);
290 var update_graphtype = function(state, gt = null) {
293 gt = $("input[name='graphtype']:checked", state.graphtype).val();
295 state.storage.set('jquery.kismet.channels.graphtype', gt);
298 $("select#historyrange", state.graphtype).hide();
299 $("select#gh_filter", state.graphtype).hide();
300 $("label#gh_filter_label", state.graphtype).hide();
302 $("select#historyrange", state.graphtype).show();
303 $("label#gh_filter_label", state.graphtype).show();
304 $("select#gh_filter", state.graphtype).show();
307 charttime = $('select#historyrange option:selected', state.element).val();
308 state.storage.set('jquery.kismet.channels.range', charttime);
311 var channels_resize = function(state) {
312 console.log('resize container ', state.devgraph_container.width(), state.devgraph_container.height());
314 if (state.devgraph_canvas != null)
315 state.devgraph_canvas
316 .prop('width', state.devgraph_container.width())
317 .prop('height', state.devgraph_container.height());
319 if (state.timegraph_canvas != null)
320 state.timegraph_canvas
321 .prop('width', state.devgraph_container.width())
322 .prop('height', state.devgraph_container.height());
324 if (state.devgraph_chart != null)
325 state.devgraph_chart.resize();
327 if (state.timegraph_chart != null)
328 state.timegraph_chart.resize();
330 channeldisplay_refresh(state);
333 $.fn.channels = function(inopt) {
336 options: base_options,
338 devgraph_container: null,
339 devgraph_chart: null,
340 timegraph_chart: null,
341 devgraph_canvas: null,
342 timegraph_canvas: null,
352 // Modeled on the datatables resize function
353 state.resizer = $('<iframe/>')
355 position: 'absolute',
363 .attr('frameBorder', '0')
364 .attr('src', 'about:blank');
366 state.resizer[0].onload = function() {
367 var body = this.contentDocument.body;
368 var height = body.offsetHeight;
369 var width = body.offsetWidth;
370 var contentDoc = this.contentDocument;
371 var defaultView = contentDoc.defaultView || contentDoc.parentWindow;
373 defaultView.onresize = function () {
374 var newHeight = body.clientHeight || body.offsetHeight;
375 var docClientHeight = contentDoc.documentElement.clientHeight;
377 var newWidth = body.clientWidth || body.offsetWidth;
378 var docClientWidth = contentDoc.documentElement.clientWidth;
380 if ( ! newHeight && docClientHeight ) {
381 newHeight = docClientHeight;
384 if ( ! newWidth && docClientWidth ) {
385 newWidth = docClientWidth;
388 if ( newHeight !== height || newWidth !== width ) {
391 console.log("triggered resizer", height, width);
392 channels_resize(state);
398 .appendTo(state.element)
399 .attr('data', 'about:blank');
401 state.element.on('resize', function() {
402 console.log("element resize");
403 channels_resize(state);
406 state.element.addClass(".channels");
408 state.storage = Storages.localStorage;
410 var stored_gtype = "now";
411 var stored_channel = "Frequency";
412 var stored_range = "min";
414 if (state.storage.isSet('jquery.kismet.channels.graphtype'))
415 stored_gtype = state.storage.get('jquery.kismet.channels.graphtype');
417 if (state.storage.isSet('jquery.kismet.channels.channeltype'))
418 stored_channel = state.storage.get('jquery.kismet.channels.channeltype');
420 if (state.storage.isSet('jquery.kismet.channels.range'))
421 stored_range = state.storage.get('jquery.kismet.channels.range');
424 if (!state.storage.isSet('jquery.kismet.channels.filter'))
425 state.storage.set('jquery.kismet.channels.filter', 'any');
427 state.visible = state.element.is(":visible");
429 if (typeof(inopt) === "string") {
432 state.options = $.extend(base_options, inopt);
435 var banner = $('div.k_cd_banner', state.element);
436 if (banner.length == 0) {
437 banner = $('<div>', {
442 state.element.append(banner);
445 if (state.graphtype == null) {
446 state.graphtype = $('<div>', {
448 "class": "k_cd_type",
455 .tooltipster({ content: 'Realtime devices-per-channel'})
457 .append($('<input>', {
469 .tooltipster({ content: 'Historical RRD device records'})
471 .append($('<input>', {
481 class: "k_cd_hr_list"
508 id: "gh_filter_label",
520 selected: "selected",
528 // Select time range from stored value
529 $('option#hr_' + stored_range, state.graphtype).attr('selected', 'selected');
531 // Select graph type from stored value
532 if (stored_gtype == 'now') {
533 $('input#gt_bar', state.graphtype).attr('checked', 'checked');
535 $('input#gt_line', state.graphtype).attr('checked', 'checked');
538 banner.append(state.graphtype);
540 update_graphtype(state, stored_gtype);
542 state.graphtype.on('change', function() {
543 update_graphtype(state);
544 channeldisplay_refresh(state);
547 $('select#gh_filter', state.graphtype).on('change', function() {
548 var filter_string = $('select#gh_filter option:selected', state.element).val();
549 state.storage.set('jquery.kismet.channels.filter', filter_string);
550 channeldisplay_refresh(state);
555 if (state.picker == null) {
556 state.picker = $('<div>', {
558 class: "k_cd_picker",
561 var sel = $('<select>', {
562 id: "k_cd_freq_selector",
566 state.picker.append(sel);
568 var chlist = new Array();
569 chlist.push("Frequency");
571 chlist = chlist.concat(kismet_ui.GetChannelListKeys());
573 for (var c in chlist) {
574 var e = $('<option>', {
578 if (chlist[c] === stored_channel)
579 e.attr("selected", "selected");
584 banner.append(state.picker);
586 state.picker.on('change', function() {
588 $('select#k_cd_freq_selector option:selected', state.element).val();
590 state.storage.set('jquery.kismet.channels.channeltype', freqtrans);
592 channeldisplay_refresh(state);
597 if (state.devgraph_container == null) {
598 state.devgraph_container =
600 class: "k_cd_container"
603 state.devgraph_canvas = $('<canvas>', {
607 state.devgraph_container.append(state.devgraph_canvas);
609 state.timegraph_canvas = $('<canvas>', {
613 state.timegraph_canvas.hide();
614 state.devgraph_container.append(state.timegraph_canvas);
616 state.element.append(state.devgraph_container);
619 // Add a 'coming soon' item
620 if (state.coming_soon == null) {
621 state.coming_soon = $('<i>', {
624 state.coming_soon.html("Channel data loading...");
625 state.element.append(state.coming_soon);
628 // Hook an observer to see when we become visible
629 var observer = new MutationObserver(function(mutations) {
630 if (state.element.is(":hidden") && state.timerid >= 0) {
631 state.visible = false;
632 clearTimeout(state.timerid);
633 } else if (state.element.is(":visible") && !state.visible) {
634 state.visible = true;
635 channeldisplay_refresh(state);
639 observer.observe(state.element[0], {
644 channeldisplay_refresh(state);