2 typeof define === "function" ? function (m) { define("kismet-ui-dot11-js", m); } :
3 typeof exports === "object" ? function (m) { module.exports = m(); } :
4 function(m){ this.kismet_ui_alerts = m(); }
11 var local_uri_prefix = "";
12 if (typeof(KISMET_URI_PREFIX) !== 'undefined')
13 local_uri_prefix = KISMET_URI_PREFIX;
15 exports.load_complete = 0;
17 function severity_to_string(sev) {
34 function severity_to_color(sev) {
54 var alert_status_element;
55 var AlertColumns = new Array();
57 exports.AddAlertColumn = function(id, options) {
60 sTitle: options.sTitle,
65 if ('field' in options) {
66 coldef.field = options.field;
69 if ('fields' in options) {
70 coldef.fields = options.fields;
73 if ('description' in options) {
74 coldef.description = options.description;
77 if ('name' in options) {
78 coldef.name = options.name;
81 if ('orderable' in options) {
82 coldef.bSortable = options.orderable;
85 if ('visible' in options) {
86 coldef.bVisible = options.visible;
88 coldef.bVisible = true;
91 if ('selectable' in options) {
92 coldef.user_selectable = options.selectable;
94 coldef.user_selectable = true;
97 if ('searchable' in options) {
98 coldef.bSearchable = options.searchable;
101 if ('width' in options) {
102 coldef.width = options.width;
106 if (typeof(coldef.field) === 'string') {
107 var fs = coldef.field.split('/');
108 f = fs[fs.length - 1];
109 } else if (Array.isArray(coldef.field)) {
113 coldef.mData = function(row, type, set) {
114 return kismet.ObjectByString(row, f);
117 if ('renderfunc' in options) {
118 coldef.mRender = options.renderfunc;
121 if ('drawfunc' in options) {
122 coldef.kismetdrawfunc = options.drawfunc;
125 AlertColumns.push(coldef);
128 exports.GetAlertColumns = function(showall = false) {
129 var ret = new Array();
131 var order = kismet.getStorage('kismet.alerttable.columns', []);
133 if (order.length == 0) {
134 // sort invisible columns to the end
135 for (var i in AlertColumns) {
136 if (!AlertColumns[i].bVisible)
138 ret.push(AlertColumns[i]);
141 for (var i in AlertColumns) {
142 if (AlertColumns[i].bVisible)
144 ret.push(AlertColumns[i]);
150 for (var oi in order) {
156 var sc = AlertColumns.find(function(e, i, a) {
157 if (e.kismetId === o.id)
162 if (sc != undefined && sc.user_selectable) {
168 // Fallback if no columns were selected somehow
169 if (ret.length == 0) {
170 // sort invisible columns to the end
171 for (var i in AlertColumns) {
172 if (!AlertColumns[i].bVisible)
174 ret.push(AlertColumns[i]);
177 for (var i in AlertColumns) {
178 if (AlertColumns[i].bVisible)
180 ret.push(AlertColumns[i]);
187 for (var sci in AlertColumns) {
188 var sc = AlertColumns[sci];
190 var rc = ret.find(function(e, i, a) {
191 if (e.kismetId === sc.kismetId)
196 if (rc == undefined) {
205 for (var sci in AlertColumns) {
206 if (!AlertColumns[sci].user_selectable) {
207 ret.push(AlertColumns[sci]);
214 exports.GetAlertColumnMap = function(columns) {
217 for (var ci in columns) {
218 var fields = new Array();
220 if ('field' in columns[ci])
221 fields.push(columns[ci]['field']);
223 if ('fields' in columns[ci])
224 fields.push.apply(fields, columns[ci]['fields']);
232 exports.GetAlertFields = function(selected) {
233 var rawret = new Array();
234 var cols = exports.GetAlertColumns();
236 for (var i in cols) {
237 if ('field' in cols[i])
238 rawret.push(cols[i]['field']);
240 if ('fields' in cols[i])
241 rawret.push.apply(rawret, cols[i]['fields']);
245 var ret = rawret.filter(function(item, pos, self) {
246 return self.indexOf(item) == pos;
253 function ScheduleAlertSummary() {
255 if (kismet_ui.window_visible && alert_element.is(":visible")) {
256 var dt = alert_element.DataTable();
258 // Save the state. We can't use proper state saving because it seems to break
259 // the table position
260 kismet.putStorage('kismet.base.alerttable.order', JSON.stringify(dt.order()));
261 kismet.putStorage('kismet.base.alertttable.search', JSON.stringify(dt.search()));
263 // Snapshot where we are, because the 'don't reset page' in ajax.reload
264 // DOES still reset the scroll position
266 'top': $(dt.settings()[0].nScrollBody).scrollTop(),
267 'left': $(dt.settings()[0].nScrollBody).scrollLeft()
269 dt.ajax.reload(function(d) {
270 // Restore our scroll position
271 $(dt.settings()[0].nScrollBody).scrollTop( prev_pos.top );
272 $(dt.settings()[0].nScrollBody).scrollLeft( prev_pos.left );
280 // Set our timer outside of the datatable callback so that we get called even
281 // if the ajax load fails
282 alertTid = setTimeout(ScheduleAlertSummary, 2000);
285 function InitializeAlertTable() {
286 var cols = exports.GetAlertColumns();
287 var colmap = exports.GetAlertColumnMap(cols);
288 var fields = exports.GetAlertFields();
296 if ($.fn.dataTable.isDataTable(alert_element)) {
297 alert_element.DataTable().destroy();
298 alert_element.empty();
302 .on('xhr.dt', function(e, settings, json, xhr) {
303 json = kismet.sanitizeObject(json);
306 if (json['recordsFiltered'] != json['recordsTotal'])
307 alert_status_element.html(`${json['recordsTotal']} alerts (${json['recordsFiltered']} shown after filter)`);
309 alert_status_element.html(`${json['recordsTotal']} alerts`);
324 loadingIndicator: true,
327 url: local_uri_prefix + "alerts/alerts_view.json",
329 json: JSON.stringify(json)
335 order: [ [ 0, "desc" ] ],
336 createdRow: function(row, data, index) {
337 row.id = data['kismet.alert.hash'];
339 drawCallback: function(settings) {
344 }).every(function(rowIdx, tableLoop, rowLoop) {
345 for (var c in AlertColumns) {
346 var col = AlertColumns[c];
348 if (!('kismetdrawfunc') in col)
352 col.kismetdrawfunc(col, dt, this);
358 $('td', this.node()).css('background-color', severity_to_color(this.data()['kismet.alert.severity']));
363 var alert_dt = alert_element.DataTable();
366 var saved_order = kismet.getStorage('kismet.base.alerttable.order', "");
367 if (saved_order !== "")
368 alert_dt.order(JSON.parse(saved_order));
370 // Restore the search
371 var saved_search = kismet.getStorage('kismet.base.alerttable.search', "");
372 if (saved_search !== "")
373 alert_dt.search(JSON.parse(saved_search));
375 // Set an onclick handler to spawn the device details dialog
376 $('tbody', alert_element).on('click', 'tr', function () {
377 exports.AlertDetailWindow(this.id);
380 $('tbody', alert_element)
381 .on( 'mouseenter', 'td', function () {
382 var alert_dt = alert_element.DataTable();
384 if (typeof(alert_dt.cell(this).index()) === 'undefined')
387 var colIdx = alert_dt.cell(this).index().column;
388 var rowIdx = alert_dt.cell(this).index().row;
390 // Remove from all cells
391 $(alert_dt.cells().nodes()).removeClass('kismet-highlight');
392 // Highlight the td in this row
393 $('td', alert_dt.row(rowIdx).nodes()).addClass('kismet-highlight');
399 kismet_ui_tabpane.AddTab({
402 createCallback: function(div) {
405 class: 'resize_wrapper',
410 class: 'stripe hover nowrap',
418 style: 'padding-bottom: 10px;',
422 alert_element = $('#alerts_dt', div);
423 alert_status_element = $('#alerts_status', div);
425 InitializeAlertTable();
426 ScheduleAlertSummary();
431 exports.AddAlertColumn('col_header', {
433 field: 'kismet.alert.header',
437 exports.AddAlertColumn('col_class', {
439 field: 'kismet.alert.class',
443 exports.AddAlertColumn('col_severity', {
445 field: 'kismet.alert.severity',
447 renderfunc: function(d, t, r, m) {
448 return severity_to_string(d);
452 exports.AddAlertColumn('col_time', {
454 field: 'kismet.alert.timestamp',
456 renderfunc: function(d, t, r, m) {
457 return kismet_ui_base.renderLastTime(d, t, r, m);
461 exports.AddAlertColumn('col_tx', {
462 sTitle: 'Transmitter',
463 field: 'kismet.alert.transmitter_mac',
464 name: 'Transmitter MAC',
465 renderfunc: function(d, t, r, m) {
466 if (d === "00:00:00:00:00:00")
468 return kismet.censorMAC(d);
472 exports.AddAlertColumn('col_sx', {
474 field: 'kismet.alert.source_mac',
476 renderfunc: function(d, t, r, m) {
477 if (d === "00:00:00:00:00:00")
479 return kismet.censorMAC(d);
483 exports.AddAlertColumn('col_dx', {
484 sTitle: 'Destination',
485 field: 'kismet.alert.dest_mac',
486 name: 'Destination MAC',
487 renderfunc: function(d, t, r, m) {
488 if (d === "00:00:00:00:00:00")
490 if (d === "FF:FF:FF:FF:FF:FF")
493 return kismet.censorMAC(d);
497 exports.AddAlertColumn('content', {
499 field: 'kismet.alert.text',
500 name: 'Alert content',
501 renderfunc: function(d, t, r, m) {
502 return kismet.censorMAC(d);
506 exports.AddAlertColumn('hash_hidden', {
508 field: 'kismet.alert.hash',
514 exports.AlertDetails = new Array();
516 exports.AddAlertDetail = function(id, title, pos, options) {
517 kismet_ui.AddDetail(exports.AlertDetails, id, title, pos, options);
520 exports.AlertDetailWindow = function(key) {
521 kismet_ui.DetailWindow(key, "Alert Details",
526 function(panel, options) {
527 var content = panel.content;
531 window['storage_detail_' + key] = {};
532 window['storage_detail_' + key]['foobar'] = 'bar';
534 panel.updater = function() {
535 if (kismet_ui.window_visible) {
536 $.get(local_uri_prefix + "alerts/by-id/" + key + "/alert.json")
537 .done(function(fulldata) {
538 fulldata = kismet.sanitizeObject(fulldata);
540 $('.loadoops', panel.content).hide();
542 panel.headerTitle(`Alert: ${fulldata['kismet.alert.header']}`);
544 var accordion = $('div#accordion', content);
546 if (accordion.length == 0) {
547 accordion = $('<div />', {
551 content.append(accordion);
554 var detailslist = exports.AlertDetails;
556 for (var dii in detailslist) {
557 var di = detailslist[dii];
560 if ('filter' in di.options &&
561 typeof(di.options.filter) === 'function') {
562 if (di.options.filter(fulldata) == false) {
567 var vheader = $('h3#header_' + di.id, accordion);
569 if (vheader.length == 0) {
570 vheader = $('<h3>', {
571 id: "header_" + di.id,
575 accordion.append(vheader);
578 var vcontent = $('div#' + di.id, accordion);
580 if (vcontent.length == 0) {
581 vcontent = $('<div>', {
584 accordion.append(vcontent);
587 // Do we have pre-rendered content?
588 if ('render' in di.options &&
589 typeof(di.options.render) === 'function') {
590 vcontent.html(di.options.render(fulldata));
593 if ('draw' in di.options && typeof(di.options.draw) === 'function') {
594 di.options.draw(fulldata, vcontent, options, 'storage_alert_' + key);
597 if ('finalize' in di.options &&
598 typeof(di.options.finalize) === 'function') {
599 di.options.finalize(fulldata, vcontent, options, 'storage_alert_' + key);
602 accordion.accordion({ heightStyle: 'fill' });
604 .fail(function(jqxhr, texterror) {
605 content.html("<div class=\"loadoops\" style=\"padding: 10px;\"><h1>Oops!</h1><p>An error occurred loading alert details for key <code>" + key +
606 "</code>: HTTP code <code>" + jqxhr.status + "</code>, " + texterror + "</div>");
609 panel.timerid = setTimeout(function() { panel.updater(); }, 1000);
612 panel.timerid = setTimeout(function() { panel.updater(); }, 1000);
620 function(panel, options) {
621 clearTimeout(panel.timerid);
622 panel.active = false;
623 window['storage_alert_' + key] = {};
627 exports.AddAlertDetail("alert", "Alert", 0, {
628 draw: function(data, target, options, storage) {
629 target.devicedata(data, {
633 field: 'kismet.alert.header',
636 help: 'Alert type / identifier; each alert has a unique type name.',
639 field: 'kismet.alert.class',
642 help: 'Each alert has a class, such as spoofing, denial of service, known exploit, etc.',
645 field: 'kismet.alert.severity',
648 draw: function(opts) {
649 return severity_to_string(opts['value']);
651 help: 'General severity of alert; in increasing severity, alerts are categorized as info, low, medium, high, and critical.',
654 field: 'kismet.alert.timestamp',
657 draw: function(opts) {
658 console.log(Math.floor(opts['value']));
659 return kismet_ui.RenderTrimmedTime({'value': Math.floor(opts['value'])});
663 field: 'kismet.alert.location/kismet.common.location.geopoint',
664 filter: function(opts) {
665 return opts['data']['kismet.alert.location']['kismet.common.location.fix'] >= 2;
668 draw: function(opts) {
670 if (opts['value'][1] == 0 || opts['value'][0] == 0)
671 return "<i>Unknown</i>";
673 return kismet.censorLocation(opts['value'][1]) + ", " + kismet.censorLocation(opts['value'][0]);
675 return "<i>Unknown</i>";
678 help: 'Location where alert occurred, either as the location of the Kismet server at the time of the alert or as the location of the packet, if per-packet location was available.',
681 field: 'kismet.alert.text',
682 title: 'Alert content',
683 draw: function(opts) {
684 return kismet.censorMAC(opts['value']);
686 help: 'Human-readable alert content',
689 groupTitle: 'Addresses',
691 filter: function(opts) {
692 return opts['data']['kismet.alert.transmitter_mac'] != '00:00:00:00:00:00' ||
693 opts['data']['kismet.alert.source_mac'] != '00:00:00:00:00:00' ||
694 opts['data']['kismet.alert.dest_mac'] != '00:00:00:00:00:00';
698 field: 'kismet.alert.source_mac',
700 filter: function(opts) {
701 return opts['value'] !== '00:00:00:00:00:00';
703 draw: function(opts) {
704 return kismet.censorMAC(opts['value']);
706 help: 'MAC address of the source of the packet triggering this alert.',
709 field: 'kismet.alert.transmitter_mac',
710 title: 'Transmitter',
711 filter: function(opts) {
712 return opts['value'] !== '00:00:00:00:00:00' &&
713 opts['data']['kismet.alert.source_mac'] !== opts['value'];
715 draw: function(opts) {
716 return kismet.censorMAC(opts['value']);
718 help: 'MAC address of the transmitter of the packet triggering this alert, if not the same as the source. On Wi-Fi this is typically the BSSID of the AP',
721 field: 'kismet.alert.dest_mac',
722 title: 'Destination',
723 filter: function(opts) {
724 return opts['value'] !== '00:00:00:00:00:00';
726 draw: function(opts) {
727 if (opts['value'] === 'FF:FF:FF:FF:FF:FF')
728 return '<i>All / Broadcast</i>'
729 return kismet.censorMAC(opts['value']);
731 help: 'MAC address of the destionation the packet triggering this alert.',
740 exports.AddAlertDetail("devel", "Dev/Debug Options", 10000, {
741 render: function(data) {
742 return 'Alert JSON: <a href="alerts/by-id/' + data['kismet.alert.hash'] + '/alert.prettyjson" target="_new">link</a><br />';
745 exports.load_complete = 1;