foist
[kismet-logviewer.git] / logviewer / static / js / jquery.kismet.alert.js
1 // Alert icon and window
2 //
3 // Requires js-storage and jquery be loaded prior
4 //
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
9 //
10
11 (function ($) {
12     var local_uri_prefix = "";
13     if (typeof(KISMET_URI_PREFIX) !== 'undefined')
14         local_uri_prefix = KISMET_URI_PREFIX;
15
16     var base_options = { 
17         use_color: true,
18         max_backlog: 50,
19     };
20
21     var options = base_options;
22
23     var timerid = -1;
24
25     var element = null;
26
27     var alerticon = null;
28     var alertbg = null;
29     var alertnum = null;
30
31     var alertclick = null;
32
33     // Last time from the server
34     var last_time = 0;
35
36     // Last time we closed the alert window
37     var last_closed_time = 0;
38
39     var dialog = null;
40
41     var alert_list = new Array();
42
43     var storage = null;
44
45     function update_tooltips() {
46         var num_new = 0;
47
48         var new_plural = 's';
49         var total_plural = 's';
50
51         for (var x = 0; x < alert_list.length; x++) {
52             if (alert_list[x]['kismet.alert.timestamp'] <= last_closed_time) {
53                 break;
54             }
55
56             num_new++;
57         }
58
59         var num_total = alert_list.length - num_new;
60
61         if (num_new <= 1)
62             new_plural = '';
63
64         if (num_total <= 1)
65             total_plural = '';
66
67         if (num_new == 0) {
68             if (num_total == 0) {
69                 element.tooltipster('content', 'No alerts');
70             } else {
71                 element.tooltipster('content', num_total + ' old alert' + total_plural);
72             }
73         } else {
74             if (num_total == 0) {
75                 element.tooltipster('content', num_new + ' alert' + new_plural);
76             } else {
77                 element.tooltipster('content', num_new + ' alert' + new_plural + ' ' + num_total + ' old alert' + total_plural);
78             }
79         }
80     }
81
82     // Close the alert panel if we click outside it
83     var close_dialog_outside = function(e) {
84         if (e == null ||
85             (e != null && $(e.target).closest('#alertdialog').length == 0)) {
86
87             // Remember the time
88             last_closed_time = last_time;
89
90             storage.set('jquery.kismet.alert.last_closed', last_closed_time);
91
92             if (dialog != null) {
93                 dialog.remove();
94                 dialog = null;
95             }
96
97             // Un-flag the alert button
98             alertbg.removeClass('ka-top-bg-alert');
99
100             // Remove the handler
101             $('body').off('click', close_dialog_outside);
102
103             update_tooltips();
104
105             // Don't pass the click on
106             e.stopImmediatePropagation();
107
108         }
109     }
110
111     var open_dialog = function(e) {
112         if (dialog != null) {
113             close_dialog_outside(e);
114
115             e.stopImmediatePropagation();
116             return;
117         }
118
119         var fullscreen = false;
120
121         var nominal_w = 400;
122         var nominal_h = ($(window).height() / 3) * 2;
123
124         var pos = { };
125
126         if ($(window).width() < 450) {
127             nominal_w = $(window).width() - 5;
128             nominal_h = $(window).height() - 5;
129         
130             pos = {
131                 "my": "left-top",
132                 "at": "left-top",
133                 "of": "window",
134                 "offsetX": 2,
135                 "offsetY": 2,
136                 "autoposition": "RIGHT"
137             };
138
139             fullscreen = true;
140         } else {
141             // Position under the element
142             var off_y = (nominal_h / 2) + (element.outerHeight() / 2) + 3;
143
144             // left-ish of the icon
145             var off_x = (nominal_w / 5) * 2;
146             off_x *= -1;
147
148             // Where the outer border lands
149             var outerborder = off_x + (nominal_w / 2);
150
151             pos = {
152                 of: element,
153                 offsetY: off_y,
154                 offsetX: off_x
155             };
156
157             fullscreen = false;
158         }
159
160
161         // Make the list of alerts
162         var listholder = $('<div>', {
163             class: "ka-alert-list",
164             id: "ka-alert-list"
165         });
166
167         for (var x = 0; x < options.max_backlog; x++) {
168             var d = $('<div>', {
169                 class: "ka-alert-line"
170             });
171             listholder.append(d);
172         }
173
174         var alert_popup_content = 
175             $('<div>', {
176                 class: "ka-dialog-content"
177             })
178             .append(
179                 $('<div>', {
180                     class: "ka-dialog-header"
181                 })
182                 .append(
183                     $('<i>', {
184                         class: "fa fa-bell ka-header-icon"
185                     })
186                 )
187                 .append(
188                     $('<b>', {
189                         class: "ka-header-text"
190                     }).text('Alerts')
191                 )
192                 .append(
193                     $('<a>', {
194                         href: "#"
195                     })
196                     .on('click', function() {
197                         close_dialog_outside(null);
198                     })
199                     .append(
200                         $('<span>', {
201                             class: "ka-header-close jsglyph jsglyph-close"
202                         })
203                         .hide()
204                     )
205                 )
206             )
207             .append(
208                 $('<div>', {
209                     class: "ka-dialog-main"
210                 })
211                 .append(
212                     $('<div>', {
213                         class: "ka-dialog-center",
214                         id: "ka-dialog-none"
215                     })
216                     .append(
217                         $('<span>', {
218                             class: "fa fa-bell-slash ka-big-icon"
219                         })
220                     )
221                     .append(
222                         $('<span>', {
223                             class: "ka-dialog-center ka-no-text"
224                         })
225                         .text("No alerts to show!")
226                     )
227                 )
228                 .append(listholder)
229             )
230             .append(
231                 $('<div>', {
232                     class: "ka-dialog-footer"
233                 })
234                 .append(
235                     $('<span>', {
236                         class: "ka-bottom-text"
237                     })
238                     .append(
239                         $('<a>', {
240                             href: '#',
241                             id: 'ka-alert-show-all'
242                         })
243                         .on('click', function() {
244                             populate_alert_content(alert_popup_content, true);
245                         })
246                         .text("No previous alerts")
247                     )
248                 )
249             );
250
251
252         if (fullscreen)
253             $('.ka-header-close', alert_popup_content).show();
254
255         populate_alert_content(alert_popup_content);
256
257         dialog = $.jsPanel({
258             id: "alertdialog",
259             headerRemove: true,
260             position: pos,
261             contentSize: {
262                 width: nominal_w,
263                 height: nominal_h
264             },
265             content: alert_popup_content,
266         });
267
268         $("body").on("click", close_dialog_outside);
269
270         e.stopImmediatePropagation();
271     }
272
273     var merge_alerts = function(alerts) {
274         alertbg.addClass('ka-top-bg-alert');
275
276         $.merge(alerts, alert_list);
277         alert_list = alerts.slice(0, options.max_backlog);
278
279         // Is the dialog showing?  Update it if it is
280         if (dialog != null) {
281             populate_alert_content(dialog.content);
282         }
283
284         update_tooltips();
285     }
286
287     var alert_refresh = function(fetchtime = last_time) {
288         $.get(local_uri_prefix + "alerts/wrapped/last-time/" + fetchtime + "/alerts.json")
289             .done(function(data) {
290                 data = kismet.sanitizeObject(data);
291
292                 // Reverse, combine in the data var, slice and assign to the alert list
293                 data['kismet.alert.list'].reverse();
294                 merge_alerts(data['kismet.alert.list']);
295             })
296     }
297
298     var populate_alert_content = function(c, showall = false) {
299         var divs = $('div.ka-alert-line', c);
300
301         if (showall) {
302             last_closed_time = 0;
303             storage.set('jquery.kismet.alert.last_closed', last_closed_time);
304         }
305
306         // If the top alert is older (or equal) to the last time we closed the
307         // alert popup, then we don't have any new alerts
308         if (alert_list.length == 0) {
309             $('div#ka-dialog-none', c).show();
310             $('div#ka-alert-list', c).hide();
311             $('a#ka-alert-show-all', c).text("No alerts...");
312             return;
313         } 
314        
315         // Are we showing all alerts, or do we have new ones?
316         if (alert_list.length > 0 &&
317                 alert_list[0]['kismet.alert.timestamp'] > last_closed_time) {
318             $('div#ka-dialog-none', c).hide();
319             $('div#ka-alert-list', c).show();
320
321             // Set the txt at the bottom to something sane
322             $('a#ka-alert-show-all', c).text("Showing all alerts...");
323
324             // Clear all the divs
325             divs.empty();
326             divs.hide();
327
328             for (var x = 0; x < alert_list.length; x++) {
329                 // Stop when we get to old ones
330                 if (alert_list[x]['kismet.alert.timestamp'] <= last_closed_time) {
331                     // Set the text to 'show all'
332                     $('a#ka-alert-show-all', c).text("Show all previous alerts...");
333                     break;
334                 }
335
336                 var d = divs.eq(x);
337
338                 var ds = (new Date(alert_list[x]['kismet.alert.timestamp'] * 1000).toString()).substring(4, 25);
339
340                 // Build the content of each alert line
341                 d.append(
342                     $('<div>', {
343                         class: "ka-alert-line-header"
344                     })
345                     .append(
346                         $('<i>', {
347                             class: "fa fa-bell ka-alert-line-icon"
348                         })
349                     )
350                     .append(
351                         $('<span>', {
352                             class: "ka-alert-line-date"
353                         })
354                         .html(ds)
355                     )
356                     .append(
357                         $('<span>', {
358                             class: "ka-alert-line-type"
359                         })
360                         .html(kismet.censorMAC(alert_list[x]['kismet.alert.header']))
361                     )
362                     .append(
363                         $('<div>', {
364                             class: "ka-alert-line-text"
365                         })
366                         .html(kismet.censorMAC(alert_list[x]['kismet.alert.text']))
367                     )
368                     .append(
369                         $('<div>', {
370                             class: "ka-alert-line-footer"
371                         })
372                         .append(
373                             $('<span>', {
374                                 class: "ka-alert-line-address"
375                             })
376                             .html(kismet_ui_base.renderMac(alert_list[x]['kismet.alert.source_mac']))
377                         )
378                         .append(
379                             $('<i>', {
380                                 class: "fa fa-arrow-right ka-alert-line-arrow"
381                             })
382                         )
383                         .append(
384                             $('<span>', {
385                                 class: "ka-alert-line-address"
386                             })
387                             .html(kismet_ui_base.renderMac(alert_list[x]['kismet.alert.dest_mac']))
388                         )
389                     )
390                 );
391
392                 d.show();
393
394             }
395
396         } else {
397             $('div#ka-dialog-none', c).show();
398             $('div#ka-alert-list', c).hide();
399             divs.empty();
400             $('a#ka-alert-show-all', c).text("Show all previous alerts...");
401         }
402     }
403
404     $.fn.alert = function(data, inopt) {
405         // Get the stored value if one exists
406         storage = Storages.localStorage;
407
408         if (storage.isSet('jquery.kismet.alert.last_closed'))
409             last_closed_time = storage.get('jquery.kismet.alert.last_closed');
410
411         element = $(this);
412
413         element.addClass('ka-top-icon');
414
415         options = $.extend(base_options, inopt);
416
417         alertbg = $('i.background', this);
418         if (alertbg.length == 0) {
419             alertbg = $('<i>', {
420                 class: "background fa fa-square fa-stack-2x ka-top-bg-normal"
421             });
422         }
423
424         alerticon = $('i.icon', this);
425         if (alerticon.length == 0) {
426             alerticon = $('<i>', {
427                 class: "icon fa fa-bell fa-inverse fa-stack-1x"
428             });
429         }
430
431         alertnum = $('span.number', this);
432         if (alertnum.length == 0) {
433             alertnum = $('<span>', {
434                 class: "number fa fa-stack-1x"
435             });
436         }
437
438         // Make the headerbar icon
439         var alertholder = $('span.fa-stack', this);
440
441         if (alertholder.length != 0) {
442             alertholder.empty();
443         } else {
444             alertholder = $('<span>', {
445                 class: "fa-stack"
446             });
447         }
448
449         alertholder.append(alertbg);
450         alertholder.append(alerticon);
451         alertholder.append(alertnum);
452
453         // Build the wrapper around the header button
454
455         alertclick = $('a.alertbutton', this);
456
457         if (alertclick.length != 0) {
458             alertclick.empty();
459         }
460
461         alertclick = $('<a>', {
462             href: "#",
463             class: "alertbutton"
464         })
465         .on('click', open_dialog);
466
467         alertclick.append(alertholder);
468         element.append(alertclick);
469
470         element.tooltipster({
471             maxWidth: 450
472         });
473
474         alert_refresh(0);
475
476         kismet_ui_base.SubscribeEventbus("ALERT", [], function(data) {
477             data = kismet.sanitizeObject(data);
478             merge_alerts([data]);
479         });
480     };
481
482 }(jQuery));