diff --git a/Duplicati/Server/webroot/ngax/index.html b/Duplicati/Server/webroot/ngax/index.html
index 2c671cf24..34b89e4f2 100644
--- a/Duplicati/Server/webroot/ngax/index.html
+++ b/Duplicati/Server/webroot/ngax/index.html
@@ -42,6 +42,7 @@
+
@@ -71,6 +72,7 @@
+
@@ -157,6 +159,8 @@
+
+
diff --git a/Duplicati/Server/webroot/ngax/scripts/directives/notificationArea.js b/Duplicati/Server/webroot/ngax/scripts/directives/notificationArea.js
new file mode 100644
index 000000000..ec35268bf
--- /dev/null
+++ b/Duplicati/Server/webroot/ngax/scripts/directives/notificationArea.js
@@ -0,0 +1,53 @@
+backupApp.directive('notificationArea', function() {
+ return {
+ restrict: 'E',
+ templateUrl: 'templates/notificationarea.html',
+ controller: function($scope, $location, $timeout, NotificationService, ServerStatus, AppService, AppUtils) {
+ $scope.Notifications = NotificationService.watch($scope);
+ $scope.state = ServerStatus.watch($scope);
+
+ $scope.doDismiss = function(id) {
+ AppService.delete('/notification/' + id);
+ };
+
+ $scope.doShowLog = function(backupid) {
+ AppService.get('/backup/' + backupid + '/isactive').then(
+ function() {
+ $location.path('/log/' + backupid);
+ },
+
+ function(resp) {
+
+ if (resp.status == 404) {
+ if ((parseInt(backupid) + '') != backupid)
+ alert('The backup was temporary and does not exist anymore, so the log data is lost');
+ else
+ alert('The backup is missing, has it been delete?');
+ } else {
+ AppUtils.connectionError('Failed to find backup: ', resp);
+ }
+
+ }
+ );
+ };
+
+ $scope.doInstallUpdate = function(id) {
+ AppService.post('/updates/install');
+ };
+
+ $scope.doActivateUpdate = function() {
+ AppService.post('/updates/activate').then(function() {}, AppUtils.connectionError('Activate failed: '));
+ };
+
+ $scope.doShowUpdate = function(id) {
+ $location.path('/updatechangelog');
+ };
+
+ $scope.doDownloadBugreport = function(item) {
+ var id = item.Action.substr('bug-report:created:'.length);
+ item.DownloadLink = AppService.get_bugreport_url(id);
+ document.getElementById('notification-download-iframe').src = item.DownloadLink;
+ };
+ }
+ }
+});
\ No newline at end of file
diff --git a/Duplicati/Server/webroot/ngax/scripts/services/NotificationService.js b/Duplicati/Server/webroot/ngax/scripts/services/NotificationService.js
new file mode 100644
index 000000000..7962e0554
--- /dev/null
+++ b/Duplicati/Server/webroot/ngax/scripts/services/NotificationService.js
@@ -0,0 +1,85 @@
+backupApp.service('NotificationService', function($rootScope, $timeout, AppService, ServerStatus) {
+
+ // Track repeated refresh requests
+ var isRefreshing = false;
+ var needsRefresh = false;
+
+ var notifications = [];
+
+ this.notifications = notifications;
+
+ function refresh_notifications() {
+
+ if (isRefreshing) {
+ needsRefresh = true;
+ return;
+ }
+
+ needsRefresh = false;
+ isRefreshing = true;
+
+ AppService.get('/notifications').then(
+ function(resp) {
+
+ var idmap = {};
+ for(var n in resp.data)
+ idmap[resp.data[n].ID] = resp.data[n];
+
+ // Sync map and list
+ for (var i = notifications.length - 1; i >= 0; i--)
+ if (!idmap[notifications[i].ID])
+ notifications.splice(i, 1);
+ else
+ delete idmap[notifications[i].ID];
+
+ // Then add all new items
+ for (var n in idmap)
+ notifications.push(idmap[n]);
+
+ notifications.sort(function(a, b) {
+ if (a.ID < b.ID)
+ return -1;
+ else if (a.ID > b.ID)
+ return 1;
+ else
+ return 0;
+ });
+
+ $rootScope.$broadcast('notificationschanged');
+
+ isRefreshing = false;
+ if (needsRefresh)
+ refresh_notifications();
+
+ },
+ function(resp) {
+ isRefreshing = false;
+ }
+ );
+ };
+
+ this.watch = function(scope, m) {
+ scope.$on('notificationschanged', function() {
+ $timeout(function() {
+ if (m) m();
+ scope.$digest();
+ });
+ });
+
+ if (m) $timeout(m);
+ return notifications;
+ }
+
+ var first = ServerStatus.state.lastNotificationUpdateId == -1;
+ $rootScope.$on('serverstatechanged.lastNotificationUpdateId', function() {
+ // We always refresh, so no need to use the initial event
+ if (first)
+ first = false;
+ else
+ refresh_notifications();
+ });
+
+ this.refresh_notifications = refresh_notifications;
+
+ refresh_notifications();
+});
\ No newline at end of file
diff --git a/Duplicati/Server/webroot/ngax/styles/styles2.css b/Duplicati/Server/webroot/ngax/styles/styles2.css
index de85586b7..dab44c66e 100644
--- a/Duplicati/Server/webroot/ngax/styles/styles2.css
+++ b/Duplicati/Server/webroot/ngax/styles/styles2.css
@@ -503,3 +503,69 @@ div.blocker {
.themelink {
margin-left: 20px;
}
+
+ul.notification {
+ position: absolute;
+ bottom: 0px;
+ left: 0px;
+ right: 0px;
+ margin: auto;
+ width: 300px;
+}
+
+.notification .title {
+ border: 1px solid #65b1dd;
+ background-color: #65b1dd;
+ border-top-left-radius: 5px;
+ border-top-right-radius: 5px;
+ border-bottom-left-radius: 0px;
+ border-bottom-right-radius: 0px;
+ padding: 2px;
+ padding-left: 5px;
+ padding-right: 5px;
+ font-weight: bold;
+ color: lightgray;
+ width: 100%;
+ text-align: center;
+ clear: both;
+}
+
+.notification .content {
+ background-color: white;
+ border: 1px solid #65b1dd;
+ border-top-left-radius: 0px;
+ border-top-right-radius: 0px;
+ border-bottom-left-radius: 5px;
+ border-bottom-right-radius: 5px;
+ padding: 2px;
+ padding-left: 5px;
+ padding-right: 5px;
+ padding-bottom: 30px;
+ width: 100%;
+}
+
+.notification .message {
+ width: 100%;
+ color: black;
+}
+
+.notification .button {
+ padding: 2px 10px;
+}
+
+
+.notification .error .title, .notification .error .content, .notification .error .button {
+ border-color: red;
+}
+
+.notification .error .title, .notification .error .button {
+ background-color: red;
+}
+
+.notification .warning .title, .notification .warning .button {
+ background-color: orange;
+}
+
+.notification .warning .title, .notification .warning .content, .notification .warning .button {
+ border-color: orange;
+}
diff --git a/Duplicati/Server/webroot/ngax/templates/notificationarea.html b/Duplicati/Server/webroot/ngax/templates/notificationarea.html
new file mode 100644
index 000000000..07f8ba3ae
--- /dev/null
+++ b/Duplicati/Server/webroot/ngax/templates/notificationarea.html
@@ -0,0 +1,58 @@
+
+
+
+ -
+
{{item.Title}}
+
+
{{item.Message}}
+
+
If the backup file was not downloaded automatically,
+
+
+
+
+
+
+
+
\ No newline at end of file