2015-02-19 21:21:54 +00:00
|
|
|
/**
|
|
|
|
* Service which defines the supported kinds of application notifications (those items that appear
|
|
|
|
* in the sidebar) and provides helper methods for working with them.
|
|
|
|
*/
|
|
|
|
angular.module('quay').factory('NotificationService',
|
2018-01-26 18:18:32 +00:00
|
|
|
['$rootScope', '$interval', 'UserService', 'ApiService', 'StringBuilderService', 'PlanService', 'UserService', 'Features', 'Config', '$location', 'VulnerabilityService',
|
2015-02-19 21:21:54 +00:00
|
|
|
|
2018-01-26 18:18:32 +00:00
|
|
|
function($rootScope, $interval, UserService, ApiService, StringBuilderService, PlanService, UserService, Features, Config, $location, VulnerabilityService) {
|
2015-02-19 21:21:54 +00:00
|
|
|
var notificationService = {
|
|
|
|
'user': null,
|
|
|
|
'notifications': [],
|
|
|
|
'notificationClasses': [],
|
|
|
|
'notificationSummaries': [],
|
2017-12-12 22:51:54 +00:00
|
|
|
'expiringAppTokens': [],
|
2015-02-19 21:21:54 +00:00
|
|
|
'additionalNotifications': false
|
|
|
|
};
|
|
|
|
|
|
|
|
var pollTimerHandle = null;
|
|
|
|
|
|
|
|
var notificationKinds = {
|
|
|
|
'test_notification': {
|
|
|
|
'level': 'primary',
|
|
|
|
'message': 'This notification is a long message for testing: {obj}',
|
|
|
|
'page': '/about/',
|
|
|
|
'dismissable': true
|
|
|
|
},
|
|
|
|
'org_team_invite': {
|
|
|
|
'level': 'primary',
|
|
|
|
'message': '{inviter} is inviting you to join team {team} under organization {org}',
|
|
|
|
'actions': [
|
|
|
|
{
|
|
|
|
'title': 'Join team',
|
|
|
|
'kind': 'primary',
|
|
|
|
'handler': function(notification) {
|
|
|
|
window.location = '/confirminvite?code=' + notification.metadata['code'];
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
'title': 'Decline',
|
|
|
|
'kind': 'default',
|
|
|
|
'handler': function(notification) {
|
|
|
|
ApiService.declineOrganizationTeamInvite(null, {'code': notification.metadata['code']}).then(function() {
|
|
|
|
notificationService.update();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
},
|
|
|
|
'password_required': {
|
|
|
|
'level': 'error',
|
|
|
|
'message': 'In order to begin pushing and pulling repositories, a password must be set for your account',
|
2016-11-28 20:25:13 +00:00
|
|
|
'page': function(metadata) {
|
|
|
|
return '/user/' + UserService.currentUser()['username'] + '?tab=settings';
|
|
|
|
}
|
2015-02-19 21:21:54 +00:00
|
|
|
},
|
|
|
|
'over_private_usage': {
|
|
|
|
'level': 'error',
|
|
|
|
'message': 'Namespace {namespace} is over its allowed private repository count. ' +
|
|
|
|
'<br><br>Please upgrade your plan to avoid disruptions in service.',
|
|
|
|
'page': function(metadata) {
|
|
|
|
var organization = UserService.getOrganization(metadata['namespace']);
|
|
|
|
if (organization) {
|
2015-06-29 09:33:00 +00:00
|
|
|
return '/organization/' + metadata['namespace'] + '?tab=billing';
|
2015-02-19 21:21:54 +00:00
|
|
|
} else {
|
2015-06-29 09:33:00 +00:00
|
|
|
return '/user/' + metadata['namespace'] + '?tab=billing';
|
2015-02-19 21:21:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
'expiring_license': {
|
|
|
|
'level': 'error',
|
|
|
|
'message': 'Your license will expire at: {expires_at} ' +
|
|
|
|
'<br><br>Please contact support to purchase a new license.',
|
|
|
|
'page': '/contact/'
|
|
|
|
},
|
|
|
|
'maintenance': {
|
|
|
|
'level': 'warning',
|
|
|
|
'message': 'We will be down for schedule maintenance from {from_date} to {to_date} ' +
|
|
|
|
'for {reason}. We are sorry about any inconvenience.',
|
|
|
|
'page': 'http://status.quay.io/'
|
|
|
|
},
|
|
|
|
'repo_push': {
|
|
|
|
'level': 'info',
|
|
|
|
'message': function(metadata) {
|
|
|
|
if (metadata.updated_tags && Object.getOwnPropertyNames(metadata.updated_tags).length) {
|
|
|
|
return 'Repository {repository} has been pushed with the following tags updated: {updated_tags}';
|
|
|
|
} else {
|
|
|
|
return 'Repository {repository} fhas been pushed';
|
|
|
|
}
|
|
|
|
},
|
|
|
|
'page': function(metadata) {
|
|
|
|
return '/repository/' + metadata.repository;
|
|
|
|
},
|
|
|
|
'dismissable': true
|
|
|
|
},
|
|
|
|
'build_queued': {
|
|
|
|
'level': 'info',
|
|
|
|
'message': 'A build has been queued for repository {repository}',
|
|
|
|
'page': function(metadata) {
|
2017-10-20 18:03:51 +00:00
|
|
|
return '/repository/' + metadata.repository + '/build/' + metadata.build_id;
|
2015-02-19 21:21:54 +00:00
|
|
|
},
|
|
|
|
'dismissable': true
|
|
|
|
},
|
|
|
|
'build_start': {
|
|
|
|
'level': 'info',
|
|
|
|
'message': 'A build has been started for repository {repository}',
|
|
|
|
'page': function(metadata) {
|
2017-10-20 18:03:51 +00:00
|
|
|
return '/repository/' + metadata.repository + '/build/' + metadata.build_id;
|
2015-02-19 21:21:54 +00:00
|
|
|
},
|
|
|
|
'dismissable': true
|
|
|
|
},
|
|
|
|
'build_success': {
|
|
|
|
'level': 'info',
|
|
|
|
'message': 'A build has succeeded for repository {repository}',
|
|
|
|
'page': function(metadata) {
|
2017-10-20 18:03:51 +00:00
|
|
|
return '/repository/' + metadata.repository + '/build/' + metadata.build_id;
|
2015-02-19 21:21:54 +00:00
|
|
|
},
|
|
|
|
'dismissable': true
|
|
|
|
},
|
|
|
|
'build_failure': {
|
|
|
|
'level': 'error',
|
|
|
|
'message': 'A build has failed for repository {repository}',
|
|
|
|
'page': function(metadata) {
|
2017-10-20 18:03:51 +00:00
|
|
|
return '/repository/' + metadata.repository + '/build/' + metadata.build_id;
|
2016-11-29 16:48:08 +00:00
|
|
|
},
|
|
|
|
'dismissable': true
|
|
|
|
},
|
|
|
|
'build_cancelled': {
|
|
|
|
'level': 'info',
|
|
|
|
'message': 'A build was cancelled for repository {repository}',
|
|
|
|
'page': function(metadata) {
|
2017-10-20 18:03:51 +00:00
|
|
|
return '/repository/' + metadata.repository + '/build/' + metadata.build_id;
|
2015-02-19 21:21:54 +00:00
|
|
|
},
|
|
|
|
'dismissable': true
|
2015-10-13 22:14:52 +00:00
|
|
|
},
|
|
|
|
'vulnerability_found': {
|
|
|
|
'level': function(metadata) {
|
|
|
|
var priority = metadata['vulnerability']['priority'];
|
|
|
|
return VulnerabilityService.LEVELS[priority].level;
|
|
|
|
},
|
|
|
|
'message': 'A {vulnerability.priority} vulnerability was detected in repository {repository}',
|
|
|
|
'page': function(metadata) {
|
|
|
|
return '/repository/' + metadata.repository + '?tab=tags';
|
2015-11-10 20:08:14 +00:00
|
|
|
},
|
|
|
|
'dismissable': true
|
2016-04-12 23:17:19 +00:00
|
|
|
},
|
|
|
|
'service_key_submitted': {
|
|
|
|
'level': 'primary',
|
|
|
|
'message': 'Service key {kid} for service {service} requests approval<br><br>Key was created on {created_date}',
|
|
|
|
'actions': [
|
|
|
|
{
|
|
|
|
'title': 'Approve Key',
|
|
|
|
'kind': 'primary',
|
|
|
|
'handler': function(notification) {
|
|
|
|
var params = {
|
|
|
|
'kid': notification.metadata.kid
|
|
|
|
};
|
|
|
|
|
|
|
|
ApiService.approveServiceKey({}, params).then(function(resp) {
|
|
|
|
notificationService.update();
|
|
|
|
window.location = '/superuser/?tab=servicekeys';
|
|
|
|
}, ApiService.errorDisplay('Could not approve service key'));
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
'title': 'Delete Key',
|
|
|
|
'kind': 'default',
|
|
|
|
'handler': function(notification) {
|
|
|
|
var params = {
|
|
|
|
'kid': notification.metadata.kid
|
|
|
|
};
|
|
|
|
|
|
|
|
ApiService.deleteServiceKey(null, params).then(function(resp) {
|
|
|
|
notificationService.update();
|
|
|
|
}, ApiService.errorDisplay('Could not delete service key'));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
],
|
|
|
|
'page': function(metadata) {
|
|
|
|
return '/superuser/?tab=servicekeys';
|
|
|
|
},
|
2015-02-19 21:21:54 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
notificationService.dismissNotification = function(notification) {
|
|
|
|
notification.dismissed = true;
|
|
|
|
var params = {
|
|
|
|
'uuid': notification.id
|
|
|
|
};
|
|
|
|
|
2015-12-22 12:38:57 +00:00
|
|
|
ApiService.updateUserNotification(notification, params).then(function(resp) {
|
|
|
|
var index = $.inArray(notification, notificationService.notifications);
|
|
|
|
if (index >= 0) {
|
|
|
|
notificationService.notifications.splice(index, 1);
|
|
|
|
}
|
|
|
|
|
2015-02-19 21:21:54 +00:00
|
|
|
notificationService.update();
|
|
|
|
}, ApiService.errorDisplay('Could not update notification'));
|
|
|
|
};
|
|
|
|
|
|
|
|
notificationService.getActions = function(notification) {
|
|
|
|
var kindInfo = notificationKinds[notification['kind']];
|
|
|
|
if (!kindInfo) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
return kindInfo['actions'] || [];
|
|
|
|
};
|
|
|
|
|
|
|
|
notificationService.canDismiss = function(notification) {
|
|
|
|
var kindInfo = notificationKinds[notification['kind']];
|
|
|
|
if (!kindInfo) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return !!kindInfo['dismissable'];
|
|
|
|
};
|
|
|
|
|
|
|
|
notificationService.getPage = function(notification) {
|
|
|
|
var kindInfo = notificationKinds[notification['kind']];
|
|
|
|
if (!kindInfo) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
var page = kindInfo['page'];
|
|
|
|
if (page != null && typeof page != 'string') {
|
|
|
|
page = page(notification['metadata']);
|
|
|
|
}
|
|
|
|
return page || '';
|
|
|
|
};
|
|
|
|
|
|
|
|
notificationService.getMessage = function(notification) {
|
|
|
|
var kindInfo = notificationKinds[notification['kind']];
|
|
|
|
if (!kindInfo) {
|
|
|
|
return '(Unknown notification kind: ' + notification['kind'] + ')';
|
|
|
|
}
|
2015-07-31 17:38:02 +00:00
|
|
|
return StringBuilderService.buildTrustedString(kindInfo['message'], notification['metadata']);
|
2015-02-19 21:21:54 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
notificationService.getClass = function(notification) {
|
|
|
|
var kindInfo = notificationKinds[notification['kind']];
|
|
|
|
if (!kindInfo) {
|
|
|
|
return 'notification-info';
|
|
|
|
}
|
2015-10-13 22:14:52 +00:00
|
|
|
|
|
|
|
var level = kindInfo['level'];
|
|
|
|
if (level != null && typeof level != 'string') {
|
|
|
|
level = level(notification['metadata']);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 'notification-' + level;
|
2015-02-19 21:21:54 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
notificationService.getClasses = function(notifications) {
|
2015-05-26 17:46:41 +00:00
|
|
|
if (!notifications.length) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
2015-02-19 21:21:54 +00:00
|
|
|
var classes = [];
|
|
|
|
for (var i = 0; i < notifications.length; ++i) {
|
|
|
|
var notification = notifications[i];
|
|
|
|
classes.push(notificationService.getClass(notification));
|
|
|
|
}
|
|
|
|
return classes.join(' ');
|
|
|
|
};
|
|
|
|
|
|
|
|
notificationService.update = function() {
|
|
|
|
var user = UserService.currentUser();
|
|
|
|
if (!user || user.anonymous) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ApiService.listUserNotifications().then(function(resp) {
|
|
|
|
notificationService.notifications = resp['notifications'];
|
|
|
|
notificationService.additionalNotifications = resp['additional'];
|
|
|
|
notificationService.notificationClasses = notificationService.getClasses(notificationService.notifications);
|
|
|
|
});
|
2017-12-12 22:51:54 +00:00
|
|
|
|
2018-01-26 18:18:32 +00:00
|
|
|
if (Features.APP_SPECIFIC_TOKENS) {
|
|
|
|
var params = {
|
|
|
|
'expiring': true
|
|
|
|
};
|
|
|
|
ApiService.listAppTokens(null, params).then(function(resp) {
|
|
|
|
notificationService.expiringAppTokens = resp['tokens'];
|
|
|
|
});
|
|
|
|
}
|
2015-02-19 21:21:54 +00:00
|
|
|
};
|
|
|
|
notificationService.reset = function() {
|
|
|
|
$interval.cancel(pollTimerHandle);
|
|
|
|
pollTimerHandle = $interval(notificationService.update, 5 * 60 * 1000 /* five minutes */);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Watch for plan changes and update.
|
|
|
|
PlanService.registerListener(this, function(plan) {
|
|
|
|
notificationService.reset();
|
|
|
|
notificationService.update();
|
|
|
|
});
|
|
|
|
|
|
|
|
// Watch for user changes and update.
|
|
|
|
$rootScope.$watch(function() { return UserService.currentUser(); }, function(currentUser) {
|
|
|
|
notificationService.reset();
|
|
|
|
notificationService.update();
|
|
|
|
});
|
|
|
|
|
|
|
|
return notificationService;
|
|
|
|
}]);
|