diff --git a/zeppelin-web/bower.json b/zeppelin-web/bower.json
index 4e5c3534066..70a08b48515 100644
--- a/zeppelin-web/bower.json
+++ b/zeppelin-web/bower.json
@@ -11,6 +11,7 @@
"angular-animate": "1.3.8",
"angular-touch": "1.3.8",
"angular-route": "1.3.8",
+ "angular-resource": "1.3.8",
"angular-bootstrap": "~0.13.0",
"angular-websocket": "~1.0.13",
"ace-builds": "1.1.9",
@@ -28,7 +29,8 @@
"angular-filter": "~0.5.4",
"ngtoast": "~1.5.5",
"ng-focus-if": "~1.0.2",
- "bootstrap3-dialog": "bootstrap-dialog#~1.34.7"
+ "bootstrap3-dialog": "bootstrap-dialog#~1.34.7",
+ "floatThead": "~1.3.2"
},
"devDependencies": {
"angular-mocks": "1.3.8"
@@ -61,4 +63,4 @@
"name": "highlightjs"
}
}
-}
+}
\ No newline at end of file
diff --git a/zeppelin-web/src/app/app.controller.js b/zeppelin-web/src/app/app.controller.js
index 2c302b542d9..f6af014bfe4 100644
--- a/zeppelin-web/src/app/app.controller.js
+++ b/zeppelin-web/src/app/app.controller.js
@@ -13,9 +13,11 @@
*/
'use strict';
-angular.module('zeppelinWebApp').controller('MainCtrl', function($scope, $rootScope, $window) {
+angular.module('zeppelinWebApp').controller('MainCtrl', function($scope, $rootScope, $window, webNotification) {
$rootScope.compiledScope = $scope.$new(true, $rootScope);
$scope.looknfeel = 'default';
+ $scope.windowFocus = true;
+ $rootScope.hasNewStatus = false;
var init = function() {
$scope.asIframe = (($window.location.href.indexOf('asIframe') > -1) ? true : false);
@@ -45,4 +47,41 @@ angular.module('zeppelinWebApp').controller('MainCtrl', function($scope, $rootSc
BootstrapDialog.defaultOptions.onshown = function() {
angular.element('#' + this.id).find('.btn:last').focus();
};
+
+ $rootScope.$on('hasNewStatus', function(event, data) {
+ if (!event.defaultPrevented && data && data === true && $rootScope.hasNewStatus === false) {
+ $rootScope.hasNewStatus = true;
+
+ // Send desktop notification
+ webNotification.showNotification('Zeppelin Notification', {
+ body: 'You have a job finished!',
+ icon: 'my-icon.ico',
+ onClick: function onNotificationClicked() {
+ console.log('Notification clicked.');
+ },
+ autoClose: 4000 //auto close the notification after 2 seconds (you can manually close it via hide function)
+ }, function onShow(error, hide) {
+ if (error) {
+ window.alert('Unable to show notification: ' + error.message);
+ } else {
+ setTimeout(function hideNotification() {
+ hide();
+ }, 5000);
+ }
+ });
+
+ event.preventDefault();
+ }
+ });
+
+ $window.onblur = function (){
+ $rootScope.windowFocus = false;
+ };
+
+ $window.onfocus = function (){
+ $rootScope.windowFocus = true;
+ if($rootScope.hasNewStatus === true) {
+ $rootScope.hasNewStatus = false;
+ }
+ };
});
diff --git a/zeppelin-web/src/app/app.js b/zeppelin-web/src/app/app.js
index 64d43bb3c59..d1c0a7940e0 100644
--- a/zeppelin-web/src/app/app.js
+++ b/zeppelin-web/src/app/app.js
@@ -32,7 +32,8 @@ angular.module('zeppelinWebApp', [
'puElasticInput',
'xeditable',
'ngToast',
- 'focus-if'
+ 'focus-if',
+ 'angular-web-notification'
])
.filter('breakFilter', function() {
return function (text) {
diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
index 8eafa6fb4dd..72bbb3c1a09 100644
--- a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
+++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
@@ -206,6 +206,11 @@ angular.module('zeppelinWebApp')
$scope.paragraph.result = data.paragraph.result;
$scope.paragraph.settings = data.paragraph.settings;
+ // when paragraph finishes, activate blinking tab notification if user is not focusing the the tab.
+ if (statusChanged && ($rootScope.windowFocus === false)) {
+ $rootScope.$emit('hasNewStatus', true);
+ }
+
if (!$scope.asIframe) {
$scope.paragraph.config = data.paragraph.config;
initializeDefault();
diff --git a/zeppelin-web/src/index.html b/zeppelin-web/src/index.html
index 4bddd9849d4..1954a870212 100644
--- a/zeppelin-web/src/index.html
+++ b/zeppelin-web/src/index.html
@@ -122,6 +122,9 @@
+
+
+