enable browser notifications for alarms; fixes #846; fixes #317; contributes to #318, #809
Costa Tsaousis (ktsaou) committed
Sep 4, 2016 at 23:48 UTC
49aef8624fe1b50538d53f28722abadf7c149190
18 files changed
+229
-35
src/health.c
+5
-3
@@ -1583,7 +1583,7 @@ static inline void health_alarm_entry2json_nolock(BUFFER *wb, ALARM_ENTRY *ae) {
1583
buffer_strcat(wb, "\t}");
1584
}
1585
1586
-void health_alarm_log2json(RRDHOST *host, BUFFER *wb) {
1586
+void health_alarm_log2json(RRDHOST *host, BUFFER *wb, uint32_t after) {
1587
pthread_rwlock_rdlock(&host->health_log.alarm_log_rwlock);
1588
1589
buffer_strcat(wb, "[");
@@ -1592,8 +1592,10 @@ void health_alarm_log2json(RRDHOST *host, BUFFER *wb) {
1592
unsigned int count = 0;
1593
ALARM_ENTRY *ae;
1594
for(ae = host->health_log.alarms; ae && count < max ; count++, ae = ae->next) {
1595
- if(likely(count)) buffer_strcat(wb, ",");
1596
- health_alarm_entry2json_nolock(wb, ae);
1595
+ if(ae->unique_id > after) {
1596
+ if(likely(count)) buffer_strcat(wb, ",");
1597
+ health_alarm_entry2json_nolock(wb, ae);
1598
+ }
1599
}
1600
1601
buffer_strcat(wb, "\n]\n");
src/health.h
+1
-1
@@ -287,6 +287,6 @@ extern void health_reload(void);
287
288
extern int health_variable_lookup(const char *variable, uint32_t hash, RRDCALC *rc, calculated_number *result);
289
extern void health_alarms2json(RRDHOST *host, BUFFER *wb, int all);
290
-extern void health_alarm_log2json(RRDHOST *host, BUFFER *wb);
290
+extern void health_alarm_log2json(RRDHOST *host, BUFFER *wb, uint32_t after);
291
292
#endif //NETDATA_HEALTH_H
src/web_client.c
+15
-4
@@ -680,17 +680,28 @@ int web_client_api_request_v1_alarms(struct web_client *w, char *url)
680
681
int web_client_api_request_v1_alarm_log(struct web_client *w, char *url)
682
{
683
- (void)url;
683
+ uint32_t after = 0;
684
+
685
+ while(url) {
686
+ char *value = mystrsep(&url, "?&[]");
687
+ if (!value || !*value) continue;
688
+
689
+ char *name = mystrsep(&value, "=");
690
+ if(!name || !*name) continue;
691
+ if(!value || !*value) continue;
692
+
693
+ if(!strcmp(name, "after")) after = strtoul(value, NULL, 0);
694
+ }
695
696
buffer_flush(w->response.data);
697
w->response.data->contenttype = CT_APPLICATION_JSON;
687
- health_alarm_log2json(&localhost, w->response.data);
698
+ health_alarm_log2json(&localhost, w->response.data, after);
699
return 200;
700
}
701
702
int web_client_api_request_v1_charts(struct web_client *w, char *url)
703
{
693
- if(url) { ; }
704
+ (void)url;
705
706
buffer_flush(w->response.data);
707
w->response.data->contenttype = CT_APPLICATION_JSON;
@@ -1336,7 +1347,7 @@ int web_client_api_request_v1_registry(struct web_client *w, char *url)
1347
1348
if(unlikely(cookie && person_guid[0] && !strcmp(person_guid, REGISTRY_VERIFY_COOKIES_GUID)))
1349
person_guid[0] = '\0';
1339
-
1350
+
1351
return registry_request_access_json(w, person_guid, machine_guid, machine_url, url_name, time(NULL));
1352
1353
case 'D':
web/Makefile.am
+6
@@ -78,7 +78,13 @@ dist_webfonts_DATA = \
78
79
webimagesdir=$(webdir)/images
80
dist_webimages_DATA = \
81
+ images/alert-128-orange.png \
82
+ images/alert-128-red.png \
83
+ images/alert-multi-size-orange.ico \
84
+ images/alert-multi-size-red.ico \
85
images/animated.gif \
86
+ images/check-mark-2-128-green.png \
87
+ images/check-mark-2-multi-size-green.ico \
88
images/post.png \
89
images/seo-performance-16.png \
90
images/seo-performance-24.png \
web/dashboard.html
+1
-1
@@ -655,4 +655,4 @@ So, to avoid flashing the charts, we destroy and re-create the charts on each up
655
<!-- <script> netdataServer = "http://box:19999"; </script> -->
656
657
<!-- load the dashboard manager - it will do the rest -->
658
- <script type="text/javascript" src="dashboard.js?v42"></script>
658
+ <script type="text/javascript" src="dashboard.js?v44"></script>
web/dashboard.js
+191
-19
@@ -159,7 +159,7 @@
159
netdataShowHelp = true;
160
161
if(typeof netdataShowAlarms === 'undefined')
162
- netdataShowAlarms = true;
162
+ netdataShowAlarms = false;
163
164
NETDATA.colors = NETDATA.themes.current.colors;
165
@@ -3448,8 +3448,7 @@
3448
NETDATA.parseDom(NETDATA.chartRefresher);
3449
3450
// Alarms initialization
3451
- if(netdataShowAlarms === true)
3452
- setTimeout(NETDATA.alarms.init, 1000);
3451
+ setTimeout(NETDATA.alarms.init, 1000);
3452
3453
// Registry initialization
3454
setTimeout(NETDATA.registry.init, 1500);
@@ -5576,9 +5575,179 @@
5575
// Registry of netdata hosts
5576
5577
NETDATA.alarms = {
5579
- server: null,
5580
- current: null,
5581
- callback: null,
5578
+ notifications: false, // when true, the browser supports notifications (may not be granted though)
5579
+ last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
5580
+ notifications_shown: new Array(),
5581
+
5582
+ server: null, // the server to connect to for fetching alarms
5583
+ current: null, // the list of raised alarms
5584
+ notified: { alarms: {} }, // the list of raised alarms for which we have shown notifications
5585
+
5586
+ callback: null, // a callback function to call every time the list of raised alarms is refreshed
5587
+
5588
+ notify: function(entry) {
5589
+ // console.log('alarm ' + entry.unique_id);
5590
+
5591
+ if(entry.updated === true) {
5592
+ // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
5593
+ return;
5594
+ }
5595
+
5596
+ var name = entry.name.replace(/_/g, ' ');
5597
+ var title = name + ' = ' + ((entry.value === null)?'NaN':Math.floor(entry.value)).toString() + ' ' + entry.units;
5598
+ var body = entry.info + ' of ' + entry.chart + ' (' + entry.family + ')';
5599
+ var tag = entry.alarm_id;
5600
+ var icon = 'images/seo-performance-128.png';
5601
+ var interaction = false;
5602
+ var data = entry;
5603
+
5604
+ switch(entry.status) {
5605
+ case 'UNINITIALIZED':
5606
+ // console.log('alarm ' + entry.unique_id + ' is UNINITIALIZED');
5607
+ return;
5608
+
5609
+ case 'UNDEFINED':
5610
+ // console.log('alarm ' + entry.unique_id + ' is UNDEFINED');
5611
+ return;
5612
+
5613
+ case 'CLEAR':
5614
+ if(NETDATA.alarms.last_notification_id === 0) {
5615
+ // console.log('alarm ' + entry.unique_id + ' is not current');
5616
+ return;
5617
+ }
5618
+ if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
5619
+ // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
5620
+ return;
5621
+ }
5622
+ title = entry.name + ' back to normal';
5623
+ icon = 'images/check-mark-2-128-green.png'
5624
+ interaction = false;
5625
+ break;
5626
+
5627
+ case 'WARNING':
5628
+ icon = 'images/alert-128-orange.png';
5629
+ interaction = false;
5630
+ break;
5631
+
5632
+ case 'CRITICAL':
5633
+ icon = 'images/alert-128-red.png'
5634
+ interaction = true;
5635
+ break;
5636
+
5637
+ default:
5638
+ console.log('invalid alarm status ' + entry.status);
5639
+ return;
5640
+ }
5641
+
5642
+ // cleanup old notifications with the same alarm_id as this one
5643
+ var len = NETDATA.alarms.notifications_shown.length;
5644
+ while(len--) {
5645
+ var n = NETDATA.alarms.notifications_shown[len];
5646
+ if(n.data.alarm_id === entry.alarm_id) {
5647
+ // console.log('removing old alarm ' + n.data.unique_id);
5648
+
5649
+ // close the notification
5650
+ n.close.bind(n);
5651
+
5652
+ // remove it from the array
5653
+ NETDATA.alarms.notifications_shown.splice(len, 1);
5654
+ len = NETDATA.alarms.notifications_shown.length;
5655
+ }
5656
+ }
5657
+
5658
+ // show this notification
5659
+ // console.log('new notification: ' + title);
5660
+ var n = new Notification(title, {
5661
+ body: body,
5662
+ tag: tag,
5663
+ requireInteraction: interaction,
5664
+ icon: NETDATA.serverDefault + icon,
5665
+ data: data
5666
+ });
5667
+
5668
+ // console.log(n);
5669
+ NETDATA.alarms.notifications_shown.push(n);
5670
+ // console.log(entry);
5671
+ },
5672
+
5673
+ notifyAll: function() {
5674
+ NETDATA.alarms.notified = NETDATA.alarms.current;
5675
+
5676
+ // console.log('FETCHING ALARM LOG');
5677
+ NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
5678
+ // console.log('ALARM LOG FETCHED');
5679
+
5680
+ if(data === null || typeof data !== 'object') {
5681
+ console.log('invalid alarms log response');
5682
+ return;
5683
+ }
5684
+
5685
+ if(data.length === 0) {
5686
+ console.log('received empty alarm log');
5687
+ return;
5688
+ }
5689
+
5690
+ data.sort(function(a, b) {
5691
+ if(a.unique_id > b.unique_id) return -1;
5692
+ if(a.unique_id < b.unique_id) return 1;
5693
+ return 0;
5694
+ });
5695
+
5696
+ var len = data.length;
5697
+ while(len--) {
5698
+ if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
5699
+ NETDATA.alarms.notify(data[len]);
5700
+ }
5701
+ }
5702
+
5703
+ NETDATA.alarms.last_notification_id = data[0].unique_id;
5704
+ })
5705
+ },
5706
+
5707
+ check_notifications: function() {
5708
+ // returns true if we should fire 1+ notifications
5709
+
5710
+ if(NETDATA.alarms.notifications !== true) {
5711
+ // console.log('notifications not available');
5712
+ return false;
5713
+ }
5714
+
5715
+ if(Notification.permission !== 'granted') {
5716
+ // console.log('notifications not granted');
5717
+ return false;
5718
+ }
5719
+
5720
+ if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
5721
+ // console.log('can do alarms');
5722
+
5723
+ var current = NETDATA.alarms.current.alarms;
5724
+ var old = NETDATA.alarms.notified.alarms;
5725
+ var x;
5726
+
5727
+ if(Object.keys(current).length !== Object.keys(old).length) {
5728
+ // console.log('alarm count differs');
5729
+ return true;
5730
+ }
5731
+ else {
5732
+ for(x in current) {
5733
+ if(!(x in old)) {
5734
+ // console.log('new alarm detected: ' + x);
5735
+ return true;
5736
+ }
5737
+
5738
+ if(current[x].status !== old[x].status) {
5739
+ // console.log('alarm changed state: ' + x);
5740
+ return true;
5741
+ }
5742
+ }
5743
+ }
5744
+
5745
+ //console.log('alarms did not change');
5746
+ }
5747
+ // else console.log('cannot process alarms');
5748
+
5749
+ return false;
5750
+ },
5751
5752
get: function(what, callback) {
5753
$.ajax({
@@ -5602,17 +5771,13 @@
5771
update_forever: function() {
5772
NETDATA.alarms.get('active', function(data) {
5773
if(data !== null) {
5605
- if('Notification' in window && NETDATA.alarms.current != null) {
5606
- if(Object.keys(NETDATA.alarms.current.alarms).length < Object.keys(data.alarms).length) {
5607
- if (Notification.permission === 'granted') {
5608
- new Notification('Netdata Alarm!', {body: 'Your Server needs attention!',
5609
- icon: 'images/seo-performance-128.png'});
5610
- }
5611
- }
5612
- }
5774
5775
NETDATA.alarms.current = data;
5776
5777
+ if(NETDATA.alarms.check_notifications() === true) {
5778
+ NETDATA.alarms.notifyAll();
5779
+ }
5780
+
5781
if (typeof NETDATA.alarms.callback === 'function') {
5782
NETDATA.alarms.callback(data);
5783
}
@@ -5622,9 +5787,9 @@
5787
});
5788
},
5789
5625
- get_log: function(callback) {
5790
+ get_log: function(last_id, callback) {
5791
$.ajax({
5627
- url: NETDATA.alarms.server + '/api/v1/alarm_log',
5792
+ url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
5793
async: true,
5794
cache: false,
5795
xhrFields: { withCredentials: true } // required for the cookie
@@ -5647,9 +5812,16 @@
5812
host = host.substring(0, host.length - 1);
5813
NETDATA.alarms.server = host;
5814
5650
- NETDATA.alarms.update_forever();
5651
- if ('Notification' in window && Notification.permission === 'default') {
5652
- Notification.requestPermission();
5815
+ if(netdataShowAlarms === true) {
5816
+ NETDATA.alarms.update_forever();
5817
+
5818
+ if('Notification' in window) {
5819
+ // console.log('notifications available');
5820
+ NETDATA.alarms.notifications = true;
5821
+
5822
+ if(Notification.permission === 'default')
5823
+ Notification.requestPermission();
5824
+ }
5825
}
5826
}
5827
};
web/demo.html
+1
-1
@@ -20,7 +20,7 @@
20
<meta property="og:title" content="netdata - real-time performance monitoring, done right!"/>
21
<meta property="og:description" content="Stunning real-time dashboards, blazingly fast and extremely interactive. Zero configuration, zero dependencies, zero maintenance." />
22
23
- <script type="text/javascript" src="dashboard.js?v42"></script>
23
+ <script type="text/javascript" src="dashboard.js?v44"></script>
24
</head>
25
<body>
26
web/demo2.html
+1
-1
@@ -21,7 +21,7 @@
21
<meta property="og:description" content="Stunning real-time dashboards, blazingly fast and extremely interactive. Zero configuration, zero dependencies, zero maintenance." />
22
23
<script>var netdataTheme = 'slate';</script>
24
- <script type="text/javascript" src="http://my-netdata.io/dashboard.js?v42"></script>
24
+ <script type="text/javascript" src="http://my-netdata.io/dashboard.js?v44"></script>
25
</head>
26
<body>
27
web/demosites.html
+1
-1
@@ -51,7 +51,7 @@
51
and that you have chown it to be owned by netdata:netdata
52
-->
53
<!-- <script type="text/javascript" src="http://my.server:19999/dashboard.js"></script> -->
54
- <script type="text/javascript" src="dashboard.js?v42"></script>
54
+ <script type="text/javascript" src="dashboard.js?v44"></script>
55
56
<script>
57
// --- OPTIONS FOR THE CHARTS --
web/images/alert-128-orange.png
Binary files /dev/null and b/web/images/alert-128-orange.png differ
web/images/alert-128-red.png
Binary files /dev/null and b/web/images/alert-128-red.png differ
web/images/alert-multi-size-orange.ico
Binary files /dev/null and b/web/images/alert-multi-size-orange.ico differ
web/images/alert-multi-size-red.ico
Binary files /dev/null and b/web/images/alert-multi-size-red.ico differ
web/images/check-mark-2-128-green.png
Binary files /dev/null and b/web/images/check-mark-2-128-green.png differ
web/images/check-mark-2-multi-size-green.ico
Binary files /dev/null and b/web/images/check-mark-2-multi-size-green.ico differ
web/index.html
+5
-2
@@ -344,6 +344,9 @@
344
345
<!-- check which theme to use -->
346
<script type="text/javascript">
347
+ var netdataShowNotifications = true;
348
+ var netdataShowAlarms = true;
349
+
350
// --------------------------------------------------------------------
351
// urlOptions
352
@@ -539,7 +542,7 @@
542
</script>
543
544
<!-- load the dashboard manager - it will do the rest -->
542
- <script type="text/javascript" src="dashboard.js?v43"></script>
545
+ <script type="text/javascript" src="dashboard.js?v44"></script>
546
</head>
547
<body data-spy="scroll" data-target="#sidebar">
548
<div id="loadOverlay" class="loadOverlay" style="background-color: #888; color: #888;">
@@ -2718,7 +2721,7 @@ function alarmsUpdateModal() {
2721
$('#alarm_all_' + id.toString()).html('');
2722
});
2723
2721
- NETDATA.alarms.get_log(function(data) {
2724
+ NETDATA.alarms.get_log(0, function(data) {
2725
if(data === null) {
2726
document.getElementById('alarms_log').innerHTML =
2727
'failed to load alarm data!';
web/registry.html
+1
-1
@@ -134,7 +134,7 @@
134
and that you have chown it to be owned by netdata:netdata
135
-->
136
<!-- <script type="text/javascript" src="http://my.server:19999/dashboard.js"></script> -->
137
- <script type="text/javascript" src="dashboard.js?v42"></script>
137
+ <script type="text/javascript" src="dashboard.js?v44"></script>
138
139
<script>
140
// Set options for TV operation
web/tv.html
+1
-1
@@ -49,7 +49,7 @@
49
and that you have chown it to be owned by netdata:netdata
50
-->
51
<!-- <script type="text/javascript" src="http://my.server:19999/dashboard.js"></script> -->
52
- <script type="text/javascript" src="dashboard.js?v42"></script>
52
+ <script type="text/javascript" src="dashboard.js?v44"></script>
53
54
<script>
55
// Set options for TV operation