diff --git a/data/model/legacy.py b/data/model/legacy.py index 271ab3106..860b0af87 100644 --- a/data/model/legacy.py +++ b/data/model/legacy.py @@ -1537,7 +1537,7 @@ def create_repo_notification(repo, event_name, method_name, config): method = ExternalNotificationMethod.get(ExternalNotificationMethod.name == method_name) return RepositoryNotification.create(repository=repo, event=event, method=method, - confing_json=json.dumps(config)) + config_json=json.dumps(config)) def get_repo_notification(namespace_name, repository_name, uuid): diff --git a/endpoints/api/repositorynotification.py b/endpoints/api/repositorynotification.py index 5cb6de72c..5315d5282 100644 --- a/endpoints/api/repositorynotification.py +++ b/endpoints/api/repositorynotification.py @@ -16,8 +16,8 @@ def notification_view(notification): return { 'uuid': notification.uuid, - 'kind': notification.kind, - 'method': notification.method, + 'event': notification.event.name, + 'method': notification.method.name, 'config': config } diff --git a/external_libraries.py b/external_libraries.py index d731d5ce1..8f3de73a1 100644 --- a/external_libraries.py +++ b/external_libraries.py @@ -18,7 +18,7 @@ EXTERNAL_JS = [ ] EXTERNAL_CSS = [ - 'netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css', + 'netdna.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.css', 'netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.no-icons.min.css', 'fonts.googleapis.com/css?family=Droid+Sans:400,700', ] diff --git a/static/css/quay.css b/static/css/quay.css index 27a87b2e6..feb71edc6 100644 --- a/static/css/quay.css +++ b/static/css/quay.css @@ -3889,6 +3889,11 @@ pre.command:before { display: none; } +.dropdown-select input.form-control[readonly] { + cursor: pointer; + background-color: #fff; +} + .dropdown-select .lookahead-input { padding-left: 32px; } @@ -4429,3 +4434,42 @@ have a fixed width and height (but it's not required). float: right; font-size: 22px; } + +i.quay-icon { + background-image: url(/static/img/favicon.ico); + background-size: 16px; + width: 16px; + height: 16px; +} + +.external-notification-view-element { + margin: 10px; + padding: 6px; + border: 1px solid #eee; + border-radius: 6px; +} + +.external-notification-view-element .view-row { + margin-bottom: 10px; +} + +.external-notification-view-element .view-row:last-child { + margin-bottom: 0px; +} + +.external-notification-view-element .flow-text { + display: inline-block; + color: #aaa; + text-transform: lowercase; + font-variant: small-caps; + width: 50px; +} + +.external-notification-view-element .side-controls { + opacity: 0; + transition: opacity 300ms ease-in-out; +} + +.external-notification-view-element:hover .side-controls { + opacity: 1; +} \ No newline at end of file diff --git a/static/directives/create-external-notification-dialog.html b/static/directives/create-external-notification-dialog.html new file mode 100644 index 000000000..065138207 --- /dev/null +++ b/static/directives/create-external-notification-dialog.html @@ -0,0 +1,91 @@ + + diff --git a/static/directives/dropdown-select.html b/static/directives/dropdown-select.html index d24cd531e..c1157e3d0 100644 --- a/static/directives/dropdown-select.html +++ b/static/directives/dropdown-select.html @@ -1,7 +1,8 @@ + +
+ On A + + + {{ eventInfo.title }} + +
+ +
+ Issue A + + + {{ methodInfo.title }} + +
+ +
+ + + To + {{ config.email }} + + + + To + {{ config.url }} + + +
+ diff --git a/static/js/app.js b/static/js/app.js index fe1719449..9f1cc4456 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -969,6 +969,94 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading return userService; }]); + $provide.factory('ExternalNotificationData', [function() { + var externalNotificationData = {}; + + var events = [ + { + 'id': 'repo_push', + 'title': 'Push to Repository', + 'icon': 'fa-upload' + }, + { + 'id': 'build_start', + 'title': 'Dockerfile Build Started', + 'icon': 'fa-tasks' + }, + { + 'id': 'build_success', + 'title': 'Dockerfile Build Successfully Completed', + 'icon': 'fa-check-circle-o' + }, + { + 'id': 'build_failure', + 'title': 'Dockerfile Build Failed', + 'icon': 'fa-times-circle-o' + } + ]; + + var methods = [ + { + 'id': 'quay_notification', + 'title': 'Quay.io notification', + 'icon': 'quay-icon' + }, + { + 'id': 'email', + 'title': 'E-mail notification', + 'icon': 'fa-envelope', + 'fields': [ + { + 'name': 'email', + 'type': 'email', + 'title': 'E-mail address' + } + ] + }, + { + 'id': 'webhook', + 'title': 'Webhook invoke', + 'icon': 'fa-link', + 'fields': [ + { + 'name': 'url', + 'type': 'url', + 'title': 'Webhook URL' + } + ] + } + ]; + + var methodMap = {}; + var eventMap = {}; + + for (var i = 0; i < methods.length; ++i) { + methodMap[methods[i].id] = methods[i]; + } + + for (var i = 0; i < events.length; ++i) { + eventMap[events[i].id] = events[i]; + } + + externalNotificationData.getSupportedEvents = function() { + return events; + }; + + externalNotificationData.getSupportedMethods = function() { + return methods; + }; + + externalNotificationData.getEventInfo = function(event) { + return eventMap[event]; + }; + + externalNotificationData.getMethodInfo = function(method) { + return methodMap[method]; + }; + + return externalNotificationData; + }]); + $provide.factory('NotificationService', ['$rootScope', '$interval', 'UserService', 'ApiService', 'StringBuilderService', 'PlanService', 'UserService', 'Config', function($rootScope, $interval, UserService, ApiService, StringBuilderService, PlanService, UserService, Config) { var notificationService = { @@ -4462,6 +4550,126 @@ quayApp.directive('buildProgress', function () { }); +quayApp.directive('externalNotificationView', function () { + var directiveDefinitionObject = { + priority: 0, + templateUrl: '/static/directives/external-notification-view.html', + replace: false, + transclude: false, + restrict: 'C', + scope: { + 'repository': '=repository', + 'notification': '=notification', + 'notificationDeleted': '¬ificationDeleted' + }, + controller: function($scope, $element, ExternalNotificationData, ApiService) { + $scope.deleteNotification = function() { + var params = { + 'repository': $scope.repository.namespace + '/' + $scope.repository.name, + 'uuid': $scope.notification.uuid + }; + + ApiService.deleteRepoNotification(null, params).then(function() { + $scope.notificationDeleted({'notification': $scope.notification}); + }); + }; + + $scope.testNotification = function() { + var params = { + 'repository': $scope.repository.namespace + '/' + $scope.repository.name, + 'uuid': $scope.notification.uuid + }; + + ApiService.testRepoNotification(null, params).then(function() { + bootbox.dialog({ + "message": "Test Notification Sent", + "buttons": { + "close": { + "label": "Close", + "className": "btn-primary" + } + } + }); + }); + }; + + $scope.$watch('notification', function(notification) { + if (notification) { + $scope.eventInfo = ExternalNotificationData.getEventInfo(notification.event); + $scope.methodInfo = ExternalNotificationData.getMethodInfo(notification.method); + $scope.config = notification.config; + } + }); + } + }; + return directiveDefinitionObject; +}); + + +quayApp.directive('createExternalNotificationDialog', function () { + var directiveDefinitionObject = { + priority: 0, + templateUrl: '/static/directives/create-external-notification-dialog.html', + replace: false, + transclude: false, + restrict: 'C', + scope: { + 'repository': '=repository', + 'counter': '=counter', + 'notificationCreated': '¬ificationCreated' + }, + controller: function($scope, $element, ExternalNotificationData, ApiService) { + $scope.currentEvent = null; + $scope.currentMethod = null; + $scope.creating = false; + $scope.currentConfig = {}; + + $scope.events = ExternalNotificationData.getSupportedEvents(); + $scope.methods = ExternalNotificationData.getSupportedMethods(); + + $scope.setEvent = function(event) { + $scope.currentEvent = event; + }; + + $scope.setMethod = function(method) { + $scope.currentConfig = {}; + $scope.currentMethod = method; + }; + + $scope.createNotification = function() { + $scope.creating = true; + var params = { + 'repository': $scope.repository.namespace + '/' + $scope.repository.name + }; + + var data = { + 'event': $scope.currentEvent.id, + 'method': $scope.currentMethod.id, + 'config': $scope.currentConfig + }; + + ApiService.createRepoNotification(data, params).then(function(resp) { + $scope.creating = false; + $scope.notificationCreated({'notification': resp}); + $('#createNotificationModal').modal('hide'); + }); + }; + + $scope.$watch('counter', function(counter) { + if (counter) { + $scope.creating = false; + $scope.currentEvent = null; + $scope.currentMethod = null; + $('#createNotificationModal').modal({}); + } + }); + } + }; + return directiveDefinitionObject; +}); + + + quayApp.directive('twitterView', function () { var directiveDefinitionObject = { priority: 0, diff --git a/static/js/controllers.js b/static/js/controllers.js index 0b19eba2e..a331bf69a 100644 --- a/static/js/controllers.js +++ b/static/js/controllers.js @@ -181,7 +181,7 @@ function TutorialCtrl($scope, AngularTour, AngularTourSignals, UserService, Conf }, { 'title': 'Repository Admin', - 'content': "The repository admin panel allows for modification of a repository's permissions, webhooks, visibility and other settings", + 'content': "The repository admin panel allows for modification of a repository's permissions, notifications, visibility and other settings", 'overlayable': true, 'mixpanelEvent': 'tutorial_view_admin' }, @@ -1246,7 +1246,7 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope fetchRepository(); } -function RepoAdminCtrl($scope, Restangular, ApiService, KeyService, $routeParams, $rootScope, $location, UserService, Config, Features) { +function RepoAdminCtrl($scope, Restangular, ApiService, KeyService, $routeParams, $rootScope, $location, UserService, Config, Features, ExternalNotificationData) { var namespace = $routeParams.namespace; var name = $routeParams.name; @@ -1467,43 +1467,32 @@ function RepoAdminCtrl($scope, Restangular, ApiService, KeyService, $routeParams }); }; - $scope.loadWebhooks = function() { + $scope.showNewNotificationCounter = 0; + + $scope.showNewNotificationDialog = function() { + $scope.showNewNotificationCounter++; + }; + + $scope.handleNotificationCreated = function(notification) { + $scope.notifications.push(notification); + }; + + $scope.handleNotificationDeleted = function(notification) { + var index = $.inArray(notification, $scope.notifications); + if (index < 0) { return; } + $scope.notifications.splice(index, 1); + }; + + $scope.loadNotifications = function() { var params = { 'repository': namespace + '/' + name }; - $scope.newWebhook = {}; - $scope.webhooksResource = ApiService.listWebhooksAsResource(params).get(function(resp) { - $scope.webhooks = resp.webhooks; - return $scope.webhooks; - }); - }; - - $scope.createWebhook = function() { - if (!$scope.newWebhook.url) { - return; - } - - var params = { - 'repository': namespace + '/' + name - }; - - ApiService.createWebhook($scope.newWebhook, params).then(function(resp) { - $scope.webhooks.push(resp); - $scope.newWebhook.url = ''; - $scope.createWebhookForm.$setPristine(); - }); - }; - - $scope.deleteWebhook = function(webhook) { - var params = { - 'repository': namespace + '/' + name, - 'public_id': webhook.public_id - }; - - ApiService.deleteWebhook(null, params).then(function(resp) { - $scope.webhooks.splice($scope.webhooks.indexOf(webhook), 1); - }); + $scope.notificationsResource = ApiService.listRepoNotificationsAsResource(params).get( + function(resp) { + $scope.notifications = resp.notifications; + return $scope.notifications; + }); }; $scope.showBuild = function(buildInfo) { @@ -1635,7 +1624,7 @@ function RepoAdminCtrl($scope, Restangular, ApiService, KeyService, $routeParams $scope.repo = repo; $rootScope.title = 'Settings - ' + namespace + '/' + name; $rootScope.description = 'Administrator settings for ' + namespace + '/' + name + - ': Permissions, webhooks and other settings'; + ': Permissions, notifications and other settings'; // Fetch all the permissions and token info for the repository. fetchPermissions('user'); diff --git a/static/partials/repo-admin.html b/static/partials/repo-admin.html index 441c146bb..ef88e687f 100644 --- a/static/partials/repo-admin.html +++ b/static/partials/repo-admin.html @@ -20,7 +20,7 @@
  • Permissions
  • Build Triggers
  • Status Badge
  • -
  • Webhooks
  • +
  • Notifications
  • Public/Private
  • Delete
  • Usage Logs
  • @@ -192,51 +192,32 @@ - -
    + +
    -
    Push Webhooks - +
    Repository Notifications +
    -
    - - - - - - - - - - - - - - -
    Webhook URL
    {{ webhook.parameters.url }} - -
    + +
    +
    + There are no notifications defined for this repository +
    +
    +
    +
    -
    - - - - - - - -
    - - - -
    -
    - -
    - Quay will POST to these webhooks whenever a push occurs. See the User Guide for more information. + +
    +
    @@ -390,6 +371,11 @@ canceled="cancelSetupTrigger(trigger)" counter="showTriggerSetupCounter">
    + +
    +