From b4407c9f15863c37f224161f04564540ad25bcfb Mon Sep 17 00:00:00 2001 From: David Taylor Date: Mon, 18 Sep 2017 14:02:28 -0400 Subject: [PATCH] Quota Notifications --- app/scripts/constants.js | 7 +- app/scripts/controllers/overview.js | 13 +- .../notificationDrawerWrapper.js | 25 +- app/scripts/services/quota.js | 107 ++++- app/scripts/services/resourceAlerts.js | 40 +- .../notifications/notification-body.html | 6 +- dist/scripts/scripts.js | 405 ++++++++++-------- dist/scripts/templates.js | 7 +- 8 files changed, 380 insertions(+), 230 deletions(-) diff --git a/app/scripts/constants.js b/app/scripts/constants.js index 0b4d34d612..214ad92b90 100644 --- a/app/scripts/constants.js +++ b/app/scripts/constants.js @@ -555,5 +555,10 @@ angular.extend(window.OPENSHIFT_CONSTANTS, { // href: 'http://example.com/', // tooltip: 'Open Dashboard' // } - ] + ], + QUOTA_NOTIFICATION_MESSAGE: { + // Example quota messages to show in notification drawer + // "pods": "Upgrade to OpenShift Pro if you need additional resources.", + // "limits.memory": "Upgrade to OpenShift Online Pro if you need additional resources." + } }); diff --git a/app/scripts/controllers/overview.js b/app/scripts/controllers/overview.js index be4a9f6e28..dd19fbeab2 100644 --- a/app/scripts/controllers/overview.js +++ b/app/scripts/controllers/overview.js @@ -1041,11 +1041,10 @@ function OverviewController($scope, groupRecentBuildsByDeploymentConfig(); }; - var updateQuotaWarnings = function() { - ResourceAlertsService.setGenericQuotaWarning(state.quotas, - state.clusterQuotas, - $routeParams.project, - state.alerts); + var setQuotaNotifications = function() { + ResourceAlertsService.setQuotaNotifications(state.quotas, + state.clusterQuotas, + $routeParams.project); }; overview.clearFilter = function() { @@ -1305,12 +1304,12 @@ function OverviewController($scope, // Always poll quotas instead of watching, its not worth the overhead of maintaining websocket connections watches.push(DataService.watch('resourcequotas', context, function(quotaData) { state.quotas = quotaData.by("metadata.name"); - updateQuotaWarnings(); + setQuotaNotifications(); }, {poll: true, pollInterval: DEFAULT_POLL_INTERVAL})); watches.push(DataService.watch('appliedclusterresourcequotas', context, function(clusterQuotaData) { state.clusterQuotas = clusterQuotaData.by("metadata.name"); - updateQuotaWarnings(); + setQuotaNotifications(); }, {poll: true, pollInterval: DEFAULT_POLL_INTERVAL})); var canI = $filter('canI'); diff --git a/app/scripts/directives/notifications/notificationDrawerWrapper.js b/app/scripts/directives/notifications/notificationDrawerWrapper.js index 367b4ff7e9..cf6b387527 100644 --- a/app/scripts/directives/notifications/notificationDrawerWrapper.js +++ b/app/scripts/directives/notifications/notificationDrawerWrapper.js @@ -17,6 +17,7 @@ 'Constants', 'DataService', 'EventsService', + 'NotificationsService', NotificationDrawerWrapper ] }); @@ -94,6 +95,12 @@ }); }; + var removeNotificationFromGroup = function(notification) { + _.each(drawer.notificationGroups, function(group) { + _.remove(group.notifications, { uid: notification.uid, namespace: notification.namespace }); + }); + }; + var formatAPIEvents = function(apiEvents) { return _.map(apiEvents, function(event) { return { @@ -172,7 +179,7 @@ var id = notification.id || _.uniqueId('notification_') + Date.now(); notificationsMap[project] = notificationsMap[project] || {}; notificationsMap[project][id] = { - actions: null, + actions: notification.actions, unread: !EventsService.isRead(id), // using uid to match API events and have one filed to pass // to EventsService for read/cleared, etc @@ -183,6 +190,7 @@ // but we sort based on lastTimestamp first. lastTimestamp: notification.timestamp, message: notification.message, + isHTML: notification.isHTML, details: notification.details, namespace: project, links: notification.links @@ -261,7 +269,8 @@ onLinkClick: function(link) { link.onClick(); drawer.drawerHidden = true; - } + }, + countUnreadNotifications: countUnreadNotifications } }); @@ -286,6 +295,18 @@ rootScopeWatches.push($rootScope.$on('NotificationDrawerWrapper.toggle', function() { drawer.drawerHidden = !drawer.drawerHidden; })); + + // event to signal the drawer to close + rootScopeWatches.push($rootScope.$on('NotificationDrawerWrapper.hide', function() { + drawer.drawerHidden = true; + })); + + // event to signal the drawer to clear a notification + rootScopeWatches.push($rootScope.$on('NotificationDrawerWrapper.clear', function(event, notification) { + EventsService.markCleared(notification.uid); + removeNotificationFromGroup(notification); + drawer.countUnreadNotifications(); + })); }; drawer.$onInit = function() { diff --git a/app/scripts/services/quota.js b/app/scripts/services/quota.js index 0e24fbeca8..eb9f1e328f 100644 --- a/app/scripts/services/quota.js +++ b/app/scripts/services/quota.js @@ -3,12 +3,21 @@ angular.module("openshiftConsole") .factory("QuotaService", function(APIService, $filter, + $location, + $rootScope, + $routeParams, $q, + Constants, DataService, - Logger) { + EventsService, + Logger, + NotificationsService) { var isNil = $filter('isNil'); var usageValue = $filter('usageValue'); + var usageWithUnits = $filter('usageWithUnits'); + var percent = $filter('percent'); + var isBestEffortPod = function(pod) { // To be best effort a pod must not have any containers that have non-zero requests or limits // Break out as soon as we find any pod with a non-zero request or limit @@ -254,6 +263,99 @@ angular.module("openshiftConsole") }); }; + var COMPUTE_RESOURCE_QUOTAS = [ + "cpu", + "requests.cpu", + "memory", + "requests.memory", + "limits.cpu", + "limits.memory" + ]; + + var getNotificaitonMessage = function(used, usedValue, hard, hardValue, quotaKey) { + // Note: This function returns HTML markup, not plain text + + var msgPrefix = "Your project is " + (hardValue < usedValue ? 'over' : 'at') + " quota. "; + var msg; + if (_.includes(COMPUTE_RESOURCE_QUOTAS, quotaKey)) { + msg = msgPrefix + "It is using " + percent((usedValue/hardValue), 0) + " of " + usageWithUnits(hard, quotaKey) + " " + humanizeQuotaResource(quotaKey) + "."; + } else { + msg = msgPrefix + "It is using " + usedValue + " of " + hardValue + " " + humanizeQuotaResource(quotaKey) + "."; + } + + msg = _.escape(msg); + + if (Constants.QUOTA_NOTIFICATION_MESSAGE && Constants.QUOTA_NOTIFICATION_MESSAGE[quotaKey]) { + // QUOTA_NOTICIATION_MESSAGE can contain HTML and shouldn't be escaped. + msg += " " + Constants.QUOTA_NOTIFICATION_MESSAGE[quotaKey]; + } + + return msg; + }; + + // Return notifications if you are at quota or over any quota for any resource. Do *not* + // warn about quota for 'resourcequotas' or resources whose hard limit is + // 0, however. + var getQuotaNotifications = function(quotas, clusterQuotas, projectName) { + var notifications = []; + + var notificationsForQuota = function(quota) { + var q = quota.status.total || quota.status; + _.each(q.hard, function(hard, quotaKey) { + var hardValue = usageValue(hard); + var used = _.get(q, ['used', quotaKey]); + var usedValue = usageValue(used); + + // We always ignore quota warnings about being out of + // resourcequotas since end users cant do anything about it + if (quotaKey === 'resourcequotas' || !hardValue || !usedValue) { + return; + } + + if(hardValue <= usedValue) { + notifications.push({ + id: "quota-limit-reached-" + quotaKey, + namespace: projectName, + type: (hardValue < usedValue ? 'warning' : 'info'), + message: getNotificaitonMessage(used, usedValue, hard, hardValue, quotaKey), + isHTML: true, + skipToast: true, + showInDrawer: true, + actions: [ + { + name: 'View Quotas', + title: 'View project quotas', + onClick: function() { + $location.url("/project/" + $routeParams.project + "/quota"); + $rootScope.$emit('NotificationDrawerWrapper.hide'); + } + }, + { + name: "Don't Show Me Again", + title: 'Permenantly hide this notificaiton until quota limit changes', + onClick: function(notification) { + NotificationsService.permanentlyHideNotification(notification.uid, notification.namespace); + $rootScope.$emit('NotificationDrawerWrapper.clear', notification); + } + }, + { + name: "Clear", + title: 'Clear this notificaiton', + onClick: function(notification) { + $rootScope.$emit('NotificationDrawerWrapper.clear', notification); + } + } + ] + }); + } + }); + }; + _.each(quotas, notificationsForQuota); + _.each(clusterQuotas, notificationsForQuota); + + return notifications; + }; + // Warn if you are at quota or over any quota for any resource. Do *not* // warn about quota for 'resourcequotas' or resources whose hard limit is // 0, however. @@ -324,6 +426,7 @@ angular.module("openshiftConsole") getLatestQuotaAlerts: getLatestQuotaAlerts, isAnyQuotaExceeded: isAnyQuotaExceeded, isAnyStorageQuotaExceeded: isAnyStorageQuotaExceeded, - willRequestExceedQuota: willRequestExceedQuota + willRequestExceedQuota: willRequestExceedQuota, + getQuotaNotifications: getQuotaNotifications }; }); diff --git a/app/scripts/services/resourceAlerts.js b/app/scripts/services/resourceAlerts.js index 4f9313ac8c..5d95c8f1bc 100644 --- a/app/scripts/services/resourceAlerts.js +++ b/app/scripts/services/resourceAlerts.js @@ -6,6 +6,7 @@ angular.module("openshiftConsole") AlertMessageService, DeploymentsService, Navigate, + NotificationsService, QuotaService) { var annotation = $filter('annotation'); var humanizeKind = $filter('humanizeKind'); @@ -70,36 +71,13 @@ angular.module("openshiftConsole") return alerts; }; - var setGenericQuotaWarning = function(quotas, clusterQuotas, projectName, alerts) { - var isHidden = AlertMessageService.isAlertPermanentlyHidden("overview-quota-limit-reached", projectName); - if (!isHidden && QuotaService.isAnyQuotaExceeded(quotas, clusterQuotas)) { - if (alerts['quotaExceeded']) { - // Don't recreate the alert or it will reset the temporary hidden state - return; + var setQuotaNotifications = function(quotas, clusterQuotas, projectName) { + var notifications = QuotaService.getQuotaNotifications(quotas, clusterQuotas, projectName); + _.each(notifications, function(notification) { + if(!NotificationsService.isNotificationPermanentlyHidden(notification)) { + NotificationsService.addNotification(notification); } - - alerts['quotaExceeded'] = { - type: 'warning', - message: 'Quota limit has been reached.', - links: [{ - href: Navigate.quotaURL(projectName), - label: "View Quota" - },{ - href: "", - label: "Don't Show Me Again", - onClick: function() { - // Hide the alert on future page loads. - AlertMessageService.permanentlyHideAlert("overview-quota-limit-reached", projectName); - - // Return true close the existing alert. - return true; - } - }] - }; - } - else { - delete alerts['quotaExceeded']; - } + }); }; // deploymentConfig, k8s deployment @@ -207,9 +185,9 @@ angular.module("openshiftConsole") return { getPodAlerts: getPodAlerts, - setGenericQuotaWarning: setGenericQuotaWarning, getDeploymentStatusAlerts: getDeploymentStatusAlerts, getPausedDeploymentAlerts: getPausedDeploymentAlerts, - getServiceInstanceAlerts: getServiceInstanceAlerts + getServiceInstanceAlerts: getServiceInstanceAlerts, + setQuotaNotifications: setQuotaNotifications }; }); diff --git a/app/views/directives/notifications/notification-body.html b/app/views/directives/notifications/notification-body.html index c26bd8f609..8746e5b1dc 100644 --- a/app/views/directives/notifications/notification-body.html +++ b/app/views/directives/notifications/notification-body.html @@ -5,6 +5,7 @@ Clear notification @@ -35,7 +36,7 @@ href="" class="secondary-action" title="{{action.title}}" - ng-click="$ctrl.customScope.handleAction(notification, action)"> + ng-click="action.onClick(notification)"> {{action.name}} @@ -71,7 +72,8 @@ - {{notification.message}} + + {{notification.message}} 0 && a.push(k()), e > 0 && a.push(w()), n.all(a).then(b); } function b() { var e, n; -E(), "Template" === m.resourceKind && m.templateOptions.process && !m.errorOccurred ? m.isDialog ? m.$emit("fileImportedFromYAMLOrJSON", { +N(), "Template" === m.resourceKind && m.templateOptions.process && !m.errorOccurred ? m.isDialog ? m.$emit("fileImportedFromYAMLOrJSON", { project: m.input.selectedProject, template: m.resource }) : (n = m.templateOptions.add || m.updateResources.length > 0 ? m.input.selectedProject.metadata.name : "", e = s.createFromTemplateURL(m.resource, m.input.selectedProject.metadata.name, { @@ -9421,12 +9449,12 @@ cancelButtonText: "Cancel" } } }).result.then(y); -}, T = {}, E = function() { +}, T = {}, N = function() { c.hideNotification("from-file-error"), _.each(T, function(e) { !e.id || "error" !== e.type && "warning" !== e.type || c.hideNotification(e.id); }); -}, N = function(e) { -E(), T = u.getSecurityAlerts(m.createResources, m.input.selectedProject.metadata.name); +}, E = function(e) { +N(), T = u.getSecurityAlerts(m.createResources, m.input.selectedProject.metadata.name); var t = e.quotaAlerts || []; T = T.concat(t), _.filter(T, { type: "error" @@ -9452,7 +9480,7 @@ e.push(C(t)); m.input.selectedProject = t, n.all(e).then(function() { m.errorOccurred || (1 === m.createResources.length && "Template" === m.resourceList[0].kind ? h() : _.isEmpty(m.updateResources) ? l.getLatestQuotaAlerts(m.createResources, { namespace: m.input.selectedProject.metadata.name -}).then(N) : (m.updateTemplate = 1 === m.updateResources.length && "Template" === m.updateResources[0].kind, m.updateTemplate ? h() : v())); +}).then(E) : (m.updateTemplate = 1 === m.updateResources.length && "Template" === m.updateResources[0].kind, m.updateTemplate ? h() : v())); }); }, function(e) { c.addNotification({ @@ -9464,10 +9492,10 @@ details: R(e) }); } }, m.cancel = function() { -E(), s.toProjectOverview(m.input.selectedProject.metadata.name); +N(), s.toProjectOverview(m.input.selectedProject.metadata.name); }; var A = e("displayName"); -m.$on("importFileFromYAMLOrJSON", m.create), m.$on("$destroy", E); +m.$on("importFileFromYAMLOrJSON", m.create), m.$on("$destroy", N); } ] }; } ]), angular.module("openshiftConsole").directive("oscFileInput", [ "Logger", function(e) { @@ -11122,12 +11150,12 @@ if (!p.pod) return null; var t = p.options.selectedContainer; switch (e) { case "memory/usage": -var n = E(t); +var n = N(t); if (n) return s.bytesToMiB(d(n)); break; case "cpu/usage_rate": -var a = N(t); +var a = E(t); if (a) return d(a); } return null; @@ -11248,7 +11276,7 @@ p.loaded = !0; } } p.includedMetrics = p.includedMetrics || [ "cpu", "memory", "network" ]; -var R, I = {}, T = {}, E = n("resources.limits.memory"), N = n("resources.limits.cpu"), D = 30, A = !1; +var R, I = {}, T = {}, N = n("resources.limits.memory"), E = n("resources.limits.cpu"), D = 30, A = !1; p.uniqueID = c.uniqueID(), p.metrics = [], _.includes(p.includedMetrics, "memory") && p.metrics.push({ label: "Memory", units: "MiB", @@ -11412,9 +11440,9 @@ return e[0]; }), i); } function u(e) { -k || (N = 0, t.showAverage = _.size(t.pods) > 5 || w, _.each(t.metrics, function(n) { +k || (E = 0, t.showAverage = _.size(t.pods) > 5 || w, _.each(t.metrics, function(n) { var a, r = o(e, n), i = n.descriptor; -w && n.compactCombineWith && (i = n.compactCombineWith, n.lastValue && (E[i].lastValue = (E[i].lastValue || 0) + n.lastValue)), C[i] ? (C[i].load(r), t.showAverage ? C[i].legend.hide() : C[i].legend.show()) : ((a = D(n)).data = r, C[i] = c3.generate(a)); +w && n.compactCombineWith && (i = n.compactCombineWith, n.lastValue && (N[i].lastValue = (N[i].lastValue || 0) + n.lastValue)), C[i] ? (C[i].load(r), t.showAverage ? C[i].legend.hide() : C[i].legend.show()) : ((a = D(n)).data = r, C[i] = c3.generate(a)); })); } function d() { @@ -11438,10 +11466,10 @@ return w || (n.containerName = t.options.selectedContainer.name), n.start = j || } } function g(e) { -if (!k) if (N++, t.noData) t.metricsError = { +if (!k) if (E++, t.noData) t.metricsError = { status: _.get(e, "status", 0), details: _.get(e, "data.errorMsg") || _.get(e, "statusText") || "Status code " + _.get(e, "status", 0) -}; else if (!(N < 2) && t.alerts) { +}; else if (!(E < 2) && t.alerts) { var n = "metrics-failed-" + t.uniqueID; t.alerts[n] = { type: "error", @@ -11450,14 +11478,14 @@ links: [ { href: "", label: "Retry", onClick: function() { -delete t.alerts[n], N = 1, y(); +delete t.alerts[n], E = 1, y(); } } ] }; } } function h() { -return _.isEmpty(t.pods) ? (t.loaded = !0, !1) : !t.metricsError && N < 2; +return _.isEmpty(t.pods) ? (t.loaded = !0, !1) : !t.metricsError && E < 2; } function v(e, n, a) { t.noData = !1; @@ -11534,11 +11562,11 @@ compactDatasetLabel: "Received", compactType: "spline", chartID: "network-rx-" + t.uniqueID } ]; -var E = _.keyBy(t.metrics, "descriptor"); +var N = _.keyBy(t.metrics, "descriptor"); t.loaded = !1, t.noData = !0, t.showComputeUnitsHelp = function() { l.showComputeUnitsHelp(); }; -var N = 0; +var E = 0; c.getMetricsURL().then(function(e) { t.metricsURL = e; }), t.options = { @@ -11634,17 +11662,17 @@ n > 10 ? e() : (n++, j().is(":visible") && (P(), e())); P(!0), b(), S(); }, 100); p.on("resize", I); -var T, E = function() { +var T, N = function() { C = !0, d.scrollBottom(u); -}, N = document.createDocumentFragment(), D = _.debounce(function() { -l.appendChild(N), N = document.createDocumentFragment(), t.autoScrollActive && E(), t.showScrollLinks || b(); +}, E = document.createDocumentFragment(), D = _.debounce(function() { +l.appendChild(E), E = document.createDocumentFragment(), t.autoScrollActive && N(), t.showScrollLinks || b(); }, 100, { maxWait: 300 }), A = function(e) { var t = r.defer(); return T ? (T.onClose(function() { t.resolve(); -}), T.stop()) : t.resolve(), e || (D.cancel(), l && (l.innerHTML = ""), N = document.createDocumentFragment()), t.promise; +}), T.stop()) : t.resolve(), e || (D.cancel(), l && (l.innerHTML = ""), E = document.createDocumentFragment()), t.promise; }, B = function() { A().then(function() { t.$evalAsync(function() { @@ -11662,7 +11690,7 @@ follow: !0, tailLines: 5e3, limitBytes: 10485760 }, t.options), n = 0, a = function(e) { -n++, N.appendChild(f(n, e)), D(); +n++, E.appendChild(f(n, e)), D(); }; (T = c.createStream(h, v, t.context, e)).onMessage(function(r, o, i) { t.$evalAsync(function() { @@ -11724,7 +11752,7 @@ onScrollTop: function() { t.autoScrollActive = !1, d.scrollTop(u), $("#" + t.logViewerID + "-affixedFollow").affix("checkPosition"); }, toggleAutoScroll: function() { -t.autoScrollActive = !t.autoScrollActive, t.autoScrollActive && E(); +t.autoScrollActive = !t.autoScrollActive, t.autoScrollActive && N(); }, goChromeless: d.chromelessLink, restartLogs: B @@ -14240,7 +14268,7 @@ u(), d(); }(), function() { angular.module("openshiftConsole").component("notificationDrawerWrapper", { templateUrl: "views/directives/notifications/notification-drawer-wrapper.html", -controller: [ "$filter", "$interval", "$location", "$timeout", "$routeParams", "$rootScope", "Constants", "DataService", "EventsService", function(e, t, n, a, r, o, i, s, c) { +controller: [ "$filter", "$interval", "$location", "$timeout", "$routeParams", "$rootScope", "Constants", "DataService", "EventsService", "NotificationsService", function(e, t, n, a, r, o, i, s, c) { var l, u, d = _.get(i, "DISABLE_GLOBAL_EVENT_WATCH"), p = e("isIE")() || e("isEdge")(), m = this, f = [], g = {}, h = {}, v = {}, y = function(e) { e || (m.drawerHidden = !0); }, b = function(e, t) { @@ -14264,6 +14292,13 @@ _.each(m.notificationGroups, function(e) { e.totalUnread = w(e.notifications).length, e.hasUnread = !!e.totalUnread, o.$emit("NotificationDrawerWrapper.onUnreadNotifications", e.totalUnread); }); }, j = function(e) { +_.each(m.notificationGroups, function(t) { +_.remove(t.notifications, { +uid: e.uid, +namespace: e.namespace +}); +}); +}, P = function(e) { return _.map(e, function(e) { return { actions: null, @@ -14276,56 +14311,57 @@ firstTimestamp: e.firstTimestamp, event: e }; }); -}, P = function(e) { +}, R = function(e) { return _.reduce(e, function(e, t) { return c.isImportantAPIEvent(t) && !c.isCleared(t.metadata.uid) && (e[t.metadata.uid] = t), e; }, {}); -}, R = function(e, t) { +}, I = function(e, t) { var n = r.project; return _.assign({}, e[n], t[n]); -}, I = function(e) { +}, T = function(e) { return _.orderBy(e, [ "event.lastTimestamp", "event.firstTimestamp" ], [ "desc", "desc" ]); -}, T = function() { +}, N = function() { o.$evalAsync(function() { -m.notificationGroups = [ S(r.project, I(R(g, h))) ], k(); +m.notificationGroups = [ S(r.project, T(I(g, h))) ], k(); }); }, E = function() { _.each(f, function(e) { e(); }), f = []; -}, N = function() { -u && (s.unwatch(u), u = null); }, D = function() { +u && (s.unwatch(u), u = null); +}, A = function() { l && l(), l = null; -}, A = function(e) { -g[r.project] = j(P(e.by("metadata.name"))), T(); -}, $ = function(e, t) { +}, $ = function(e) { +g[r.project] = P(R(e.by("metadata.name"))), N(); +}, B = function(e, t) { if (t.showInDrawer) { var n = t.namespace || r.project, a = t.id || _.uniqueId("notification_") + Date.now(); h[n] = h[n] || {}, h[n][a] = { -actions: null, +actions: t.actions, unread: !c.isRead(a), trackByID: t.trackByID, uid: a, type: t.type, lastTimestamp: t.timestamp, message: t.message, +isHTML: t.isHTML, details: t.details, namespace: n, links: t.links -}, T(); +}, N(); } -}, B = function(e, t) { -N(), e && (u = s.watch("events", { +}, L = function(e, t) { +D(), e && (u = s.watch("events", { namespace: e }, _.debounce(t, 400), { skipDigest: !0 })); -}, L = _.once(function(e, t) { -D(), l = o.$on("NotificationsService.onNotificationAdded", t); -}), U = function() { +}, U = _.once(function(e, t) { +A(), l = o.$on("NotificationsService.onNotificationAdded", t); +}), O = function() { C(r.project).then(function() { -B(r.project, A), L(r.project, $), y(r.project), T(); +L(r.project, $), U(r.project, B), y(r.project), N(); }); }; angular.extend(m, { @@ -14342,12 +14378,12 @@ m.drawerHidden = !0; onMarkAllRead: function(e) { _.each(e.notifications, function(e) { e.unread = !1, c.markRead(e.uid); -}), T(), o.$emit("NotificationDrawerWrapper.onMarkAllRead"); +}), N(), o.$emit("NotificationDrawerWrapper.onMarkAllRead"); }, onClearAll: function(e) { _.each(e.notifications, function(e) { e.unread = !1, c.markRead(e.uid), c.markCleared(e.uid); -}), g[r.project] = {}, h[r.project] = {}, T(), o.$emit("NotificationDrawerWrapper.onMarkAllRead"); +}), g[r.project] = {}, h[r.project] = {}, N(), o.$emit("NotificationDrawerWrapper.onMarkAllRead"); }, notificationGroups: [], headingInclude: "views/directives/notifications/header.html", @@ -14364,20 +14400,25 @@ m.drawerHidden = !0; }, onLinkClick: function(e) { e.onClick(), m.drawerHidden = !0; -} +}, +countUnreadNotifications: k } }); -var O = function() { -r.project && U(), f.push(o.$on("$routeChangeSuccess", function(e, t, n) { -b(t, n) && (m.customScope.projectName = r.project, U()); +var F = function() { +r.project && O(), f.push(o.$on("$routeChangeSuccess", function(e, t, n) { +b(t, n) && (m.customScope.projectName = r.project, O()); })), f.push(o.$on("NotificationDrawerWrapper.toggle", function() { m.drawerHidden = !m.drawerHidden; +})), f.push(o.$on("NotificationDrawerWrapper.hide", function() { +m.drawerHidden = !0; +})), f.push(o.$on("NotificationDrawerWrapper.clear", function(e, t) { +c.markCleared(t.uid), j(t), m.countUnreadNotifications(); })); }; m.$onInit = function() { -d || p || O(); +d || p || F(); }, m.$onDestroy = function() { -D(), N(), E(); +A(), D(), E(); }; } ] }); diff --git a/dist/scripts/templates.js b/dist/scripts/templates.js index 4a7fc2ba29..fe33b3c0f8 100644 --- a/dist/scripts/templates.js +++ b/dist/scripts/templates.js @@ -7700,7 +7700,7 @@ angular.module('openshiftConsoleTemplates', []).run(['$templateCache', function( $templateCache.put('views/directives/notifications/notification-body.html', "
\n" + - "\n" + + "\n" + "Clear notification\n" + "\n" + "\n" + @@ -7710,7 +7710,7 @@ angular.module('openshiftConsoleTemplates', []).run(['$templateCache', function( "\n" + "
    \n" + "
  • \n" + - "\n" + + "\n" + "{{action.name}}\n" + "\n" + "
  • \n" + @@ -7731,7 +7731,8 @@ angular.module('openshiftConsoleTemplates', []).run(['$templateCache', function( "{{notification.event.involvedObject.name}}\n" + "\n" + "\n" + - "{{notification.message}}\n" + + "\n" + + "{{notification.message}}\n" + "\n" + "{{link.label}}\n" + "{{link.label}}\n" +