From 333e0acd6d45703314abe42e5ac36953837f2f98 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Fri, 13 Mar 2015 15:34:28 -0700 Subject: [PATCH] Add the builds tab --- data/model/legacy.py | 5 +- endpoints/api/build.py | 9 +- endpoints/callbacks.py | 10 + endpoints/common.py | 10 +- static/css/core-ui.css | 20 +- .../repo-view/repo-panel-builds.css | 29 +++ .../css/directives/ui/build-mini-status.css | 20 -- static/css/directives/ui/build-state-icon.css | 19 ++ static/css/directives/ui/filter-control.css | 15 ++ static/css/directives/ui/repo-list-grid.css | 13 - .../ui/triggered-build-description.css | 8 + static/css/pages/repo-view.css | 12 +- static/directives/build-mini-status.html | 12 +- static/directives/build-state-icon.html | 8 + static/directives/filter-control.html | 3 + .../repo-view/repo-panel-builds.html | 185 ++++++++++++++ .../directives/repo-view/repo-panel-info.html | 2 +- .../directives/repo-view/repo-panel-tags.html | 4 +- .../triggered-build-description.html | 11 +- .../directives/repo-view/repo-panel-builds.js | 232 ++++++++++++++++++ static/js/directives/ui/build-state-icon.js | 22 ++ static/js/directives/ui/filter-control.js | 22 ++ static/js/services/trigger-service.js | 47 +++- static/lib/angular-moment.min.js | 3 +- static/partials/repo-view.html | 7 +- 25 files changed, 668 insertions(+), 60 deletions(-) create mode 100644 static/css/directives/repo-view/repo-panel-builds.css create mode 100644 static/css/directives/ui/build-state-icon.css create mode 100644 static/css/directives/ui/filter-control.css create mode 100644 static/directives/build-state-icon.html create mode 100644 static/directives/filter-control.html create mode 100644 static/directives/repo-view/repo-panel-builds.html create mode 100644 static/js/directives/repo-view/repo-panel-builds.js create mode 100644 static/js/directives/ui/build-state-icon.js create mode 100644 static/js/directives/ui/filter-control.js diff --git a/data/model/legacy.py b/data/model/legacy.py index 058d65248..b84b262eb 100644 --- a/data/model/legacy.py +++ b/data/model/legacy.py @@ -2084,11 +2084,14 @@ def get_repository_build(build_uuid): def list_repository_builds(namespace_name, repository_name, limit, - include_inactive=True): + include_inactive=True, since=None): query = (_get_build_base_query() .where(Repository.name == repository_name, Namespace.username == namespace_name) .limit(limit)) + if since is not None: + query = query.where(RepositoryBuild.started >= since) + if not include_inactive: query = query.where(RepositoryBuild.phase != 'error', RepositoryBuild.phase != 'complete') diff --git a/endpoints/api/build.py b/endpoints/api/build.py index e52962416..2add3adc4 100644 --- a/endpoints/api/build.py +++ b/endpoints/api/build.py @@ -148,12 +148,17 @@ class RepositoryBuildList(RepositoryParamResource): @require_repo_read @parse_args @query_param('limit', 'The maximum number of builds to return', type=int, default=5) + @query_param('since', 'Returns all builds since the given unix timecode', type=int, default=None) @nickname('getRepoBuilds') def get(self, args, namespace, repository): """ Get the list of repository builds. """ - limit = args['limit'] - builds = list(model.list_repository_builds(namespace, repository, limit)) + limit = args.get('limit', 5) + since = args.get('since', None) + if since is not None: + since = datetime.datetime.utcfromtimestamp(since) + + builds = model.list_repository_builds(namespace, repository, limit, since=since) can_write = ModifyRepositoryPermission(namespace, repository).can() return { 'builds': [build_status_view(build, can_write) for build in builds] diff --git a/endpoints/callbacks.py b/endpoints/callbacks.py index a8bb05dbe..f6e9bd20c 100644 --- a/endpoints/callbacks.py +++ b/endpoints/callbacks.py @@ -247,6 +247,7 @@ def github_oauth_attach(): @callback.route('/github/callback/trigger/', methods=['GET']) +@callback.route('/github/callback/trigger//__new', methods=['GET']) @route_show_if(features.GITHUB_BUILD) @require_session_login @parse_repository_name @@ -260,9 +261,18 @@ def attach_github_build_trigger(namespace, repository): abort(404, message=msg) trigger = model.create_build_trigger(repo, 'github', token, current_user.db_user()) + + # TODO(jschorr): Remove once the new layout is in place. admin_path = '%s/%s/%s' % (namespace, repository, 'admin') full_url = '%s%s%s' % (url_for('web.repository', path=admin_path), '?tab=trigger&new_trigger=', trigger.uuid) + + if '__new' in request.url: + repo_path = '%s/%s' % (namespace, repository) + full_url = '%s%s%s' % (url_for('web.repository', path=repo_path), '?tab=builds&newtrigger=', + trigger.uuid) + + logger.debug('Redirecting to full url: %s' % full_url) return redirect(full_url) diff --git a/endpoints/common.py b/endpoints/common.py index e8f49b087..5f40e48ed 100644 --- a/endpoints/common.py +++ b/endpoints/common.py @@ -19,6 +19,7 @@ from app import app, oauth_apps, dockerfile_build_queue, LoginWrappedDBUser from auth.permissions import QuayDeferredPermissionUser from auth import scopes +from auth.auth_context import get_authenticated_user from endpoints.api.discovery import swagger_route_data from werkzeug.routing import BaseConverter from functools import wraps @@ -225,7 +226,8 @@ def start_build(repository, dockerfile_id, tags, build_name, subdir, manual, 'registry': host, 'build_subdir': subdir, 'trigger_metadata': trigger_metadata or {}, - 'is_manual': manual + 'is_manual': manual, + 'manual_user': get_authenticated_user().username if get_authenticated_user() else None } with app.config['DB_TRANSACTION_FACTORY'](db): @@ -250,7 +252,8 @@ def start_build(repository, dockerfile_id, tags, build_name, subdir, manual, 'repo': repository.name, 'namespace': repository.namespace_user.username, 'fileid': dockerfile_id, - 'manual': manual, + 'is_manual': manual, + 'manual_user': get_authenticated_user().username if get_authenticated_user() else None } if trigger: @@ -267,7 +270,8 @@ def start_build(repository, dockerfile_id, tags, build_name, subdir, manual, 'build_id': build_request.uuid, 'build_name': build_name, 'docker_tags': tags, - 'is_manual': manual + 'is_manual': manual, + 'manual_user': get_authenticated_user().username if get_authenticated_user() else None } if trigger: diff --git a/static/css/core-ui.css b/static/css/core-ui.css index 4aea13371..165a25dc6 100644 --- a/static/css/core-ui.css +++ b/static/css/core-ui.css @@ -190,7 +190,7 @@ width: 24px; } -.co-panel .co-panel-heading i.fa { +.co-panel .co-panel-heading > i.fa { margin-right: 6px; width: 24px; text-align: center; @@ -887,3 +887,21 @@ .co-check-bar .co-filter-box input { width: 300px; } + +.empty { + border-bottom: none !important; +} + +.empty-primary-msg { + font-size: 18px; + margin-bottom: 10px; + text-align: center; +} + +.empty-secondary-msg { + font-size: 14px; + color: #999; + text-align: center; + margin-bottom: 10px; +} + diff --git a/static/css/directives/repo-view/repo-panel-builds.css b/static/css/directives/repo-view/repo-panel-builds.css new file mode 100644 index 000000000..03c3a6a57 --- /dev/null +++ b/static/css/directives/repo-view/repo-panel-builds.css @@ -0,0 +1,29 @@ +.repo-panel-builds .status-col { + width: 48px; +} + +.repo-panel-builds .building-tag { + margin-right: 10px; +} + +.repo-panel-builds .building-tag .fa { + margin-right: 6px; + vertical-align: middle; +} + +.repo-panel-builds .heading-title { + font-size: 20px; +} + +.repo-panel-builds .heading-controls { + font-size: 14px; + float: right; +} + +.repo-panel-builds .heading-controls .btn { + margin-top: -10px; +} + +.repo-panel-builds .heading-controls .btn .fa { + margin-right: 6px; +} diff --git a/static/css/directives/ui/build-mini-status.css b/static/css/directives/ui/build-mini-status.css index f4e82a977..0913e9b53 100644 --- a/static/css/directives/ui/build-mini-status.css +++ b/static/css/directives/ui/build-mini-status.css @@ -10,14 +10,6 @@ text-decoration: none !important; } -.build-mini-status .build-status-icon { - width: 42px; - padding: 4px; - text-align: center; - display: inline-block; - font-size: 18px; -} - .build-mini-status .timing { display: inline-block; margin-left: 30px; @@ -38,16 +30,4 @@ line-height: 33px; overflow: hidden; text-overflow: ellipsis; -} - -.build-mini-status .build-status-icon.error { - color: red; -} - -.build-mini-status .build-status-icon.internalerror { - color: #DFFF00; -} - -.build-mini-status .build-status-icon.complete { - color: #2fcc66; } \ No newline at end of file diff --git a/static/css/directives/ui/build-state-icon.css b/static/css/directives/ui/build-state-icon.css new file mode 100644 index 000000000..31b4adb8a --- /dev/null +++ b/static/css/directives/ui/build-state-icon.css @@ -0,0 +1,19 @@ +.build-state-icon { + width: 42px; + padding: 4px; + text-align: center; + display: inline-block; + font-size: 18px; +} + +.build-state-icon .error { + color: red; +} + +.build-state-icon .internalerror { + color: #DFFF00; +} + +.build-state-icon .complete { + color: #2fcc66; +} \ No newline at end of file diff --git a/static/css/directives/ui/filter-control.css b/static/css/directives/ui/filter-control.css new file mode 100644 index 000000000..5c8c09506 --- /dev/null +++ b/static/css/directives/ui/filter-control.css @@ -0,0 +1,15 @@ +.filter-control { + padding: 4px; +} + +.filter-control a { + text-decoration: none !important; +} + +.filter-control .not-selected a { + color: #aaa; +} + +.filter-control .selected { + font-weight: bold; +} diff --git a/static/css/directives/ui/repo-list-grid.css b/static/css/directives/ui/repo-list-grid.css index 8e253edd2..d9418ae95 100644 --- a/static/css/directives/ui/repo-list-grid.css +++ b/static/css/directives/ui/repo-list-grid.css @@ -30,19 +30,6 @@ right: 2px; } -.empty-primary-msg { - font-size: 18px; - margin-bottom: 30px; - text-align: center; -} - -.empty-secondary-msg { - font-size: 14px; - color: #999; - text-align: center; - margin-bottom: 10px; -} - .repo-list-title { margin-bottom: 30px; margin-top: 10px; diff --git a/static/css/directives/ui/triggered-build-description.css b/static/css/directives/ui/triggered-build-description.css index 2ef4914e3..a3b0bda1a 100644 --- a/static/css/directives/ui/triggered-build-description.css +++ b/static/css/directives/ui/triggered-build-description.css @@ -41,4 +41,12 @@ .triggered-build-description-element .commit-who:before { content: "by "; +} + +.triggered-build-description-element .manual { + color: #ccc; +} + +.triggered-build-description-element .fa-user { + margin-right: 4px; } \ No newline at end of file diff --git a/static/css/pages/repo-view.css b/static/css/pages/repo-view.css index e713ace27..63fd8d0ed 100644 --- a/static/css/pages/repo-view.css +++ b/static/css/pages/repo-view.css @@ -12,5 +12,13 @@ .repository-view .tab-header { margin-top: 0px; - margin-bottom: 20px; -} \ No newline at end of file + margin-bottom: 30px; +} + +.repository-view .tab-header-controls { + float: right; +} + +.repository-view .tab-header-controls .btn .fa { + margin-right: 6px; +} diff --git a/static/directives/build-mini-status.html b/static/directives/build-mini-status.html index f06c2785e..e16086195 100644 --- a/static/directives/build-mini-status.html +++ b/static/directives/build-mini-status.html @@ -1,18 +1,10 @@
- - - - - - - - + -
-
Manually Started Build
+
\ No newline at end of file diff --git a/static/directives/build-state-icon.html b/static/directives/build-state-icon.html new file mode 100644 index 000000000..3d027ec69 --- /dev/null +++ b/static/directives/build-state-icon.html @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/static/directives/filter-control.html b/static/directives/filter-control.html new file mode 100644 index 000000000..9f0a5e140 --- /dev/null +++ b/static/directives/filter-control.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/static/directives/repo-view/repo-panel-builds.html b/static/directives/repo-view/repo-panel-builds.html new file mode 100644 index 000000000..fee480204 --- /dev/null +++ b/static/directives/repo-view/repo-panel-builds.html @@ -0,0 +1,185 @@ +
+
+ +
+

Repository Builds

+ + +
+ +
+ +
+ + Build History +
+
+ + +
+
+ + +
+
No matching builds found
+
+ Please change the filter above to search for more builds. +
+
+ + + + + + + + + + + + + + + + + + + +
+ Build ID + + Tags + + Triggered By + + Date Started +
+ {{ build.id.substr(0, 8) }} + + + {{ tag }} + + +
+
{{ build.started | amCalendar }}
+
+
+
+ + +
+ +
+ + Build Triggers + + +
+ + +
+
+ +
+
No build triggers defined
+
+ Build triggers invoke builds whenever the triggered condition is met (source control push, webhook, etc) +
+
+ + + + + + + + + + + + + + + + + + + + + + +
Trigger NameDockerfile LocationBranches/TagsPull Robot
+ + Trigger Setup in progress (Cancel) +
{{ trigger.subdir || '(Root Directory)' }}{{ trigger.config.branchtag_regex || '(All)' }} + + + + + Run Trigger Now + + + Delete Trigger + + +
+ + +
+
+
+ + +
+ Are you sure you want to delete this build trigger? No further builds will be automatically + started. +
+ + +
+
+ + +
+ + +
+ +
\ No newline at end of file diff --git a/static/directives/repo-view/repo-panel-info.html b/static/directives/repo-view/repo-panel-info.html index 06f45acf4..ca5f53496 100644 --- a/static/directives/repo-view/repo-panel-info.html +++ b/static/directives/repo-view/repo-panel-info.html @@ -41,7 +41,7 @@
No builds have been run for this repository.
-
+
Click on the Builds tab to start a new build.
diff --git a/static/directives/repo-view/repo-panel-tags.html b/static/directives/repo-view/repo-panel-tags.html index 43d1f725e..10e6b9f3b 100644 --- a/static/directives/repo-view/repo-panel-tags.html +++ b/static/directives/repo-view/repo-panel-tags.html @@ -24,7 +24,9 @@ Visualize - diff --git a/static/directives/triggered-build-description.html b/static/directives/triggered-build-description.html index 1aa33820f..9fb907701 100644 --- a/static/directives/triggered-build-description.html +++ b/static/directives/triggered-build-description.html @@ -1,5 +1,14 @@
- + + + (Manually Triggered Build) + + + + {{ build.job_config.manual_user }} + + + diff --git a/static/js/directives/repo-view/repo-panel-builds.js b/static/js/directives/repo-view/repo-panel-builds.js new file mode 100644 index 000000000..13f441a52 --- /dev/null +++ b/static/js/directives/repo-view/repo-panel-builds.js @@ -0,0 +1,232 @@ +/** + * An element which displays the builds panel for a repository view. + */ +angular.module('quay').directive('repoPanelBuilds', function () { + var directiveDefinitionObject = { + priority: 0, + templateUrl: '/static/directives/repo-view/repo-panel-builds.html', + replace: false, + transclude: false, + restrict: 'C', + scope: { + 'repository': '=repository', + 'builds': '=builds' + }, + controller: function($scope, $element, $filter, $routeParams, ApiService, TriggerService) { + var orderBy = $filter('orderBy'); + + $scope.TriggerService = TriggerService; + + $scope.options = { + 'filter': 'recent', + 'reverse': false, + 'predicate': 'started_datetime' + }; + + $scope.currentFilter = null; + + $scope.currentStartTrigger = null; + $scope.currentSetupTrigger = null; + + $scope.showBuildDialogCounter = 0; + $scope.showTriggerStartDialogCounter = 0; + $scope.showTriggerSetupCounter = 0; + + var updateBuilds = function() { + if (!$scope.allBuilds) { return; } + + var unordered = $scope.allBuilds.map(function(build_info) { + var commit_sha = null; + + if (build_info.job_config.trigger_metadata) { + commit_sha = build_info.job_config.trigger_metadata.commit_sha; + } + + return $.extend(build_info, { + 'started_datetime': (new Date(build_info.started)).valueOf() * (-1), + 'building_tags': build_info.job_config.docker_tags, + 'commit_sha': commit_sha + }); + }); + + $scope.fullBuilds = orderBy(unordered, $scope.options.predicate, $scope.options.reverse); + }; + + var loadBuilds = function() { + if (!$scope.builds || !$scope.repository || !$scope.options.filter) { + return; + } + + // Note: We only refresh if the filter has changed. + var filter = $scope.options.filter; + if ($scope.buildsResource && filter == $scope.currentFilter) { return; } + + var since = null; + + if ($scope.options.filter == '48hours') { + since = Math.floor(moment().subtract(2, 'days').valueOf() / 1000); + } else if ($scope.options.filter == '30days') { + since = Math.floor(moment().subtract(30, 'days').valueOf() / 1000); + } else { + since = null; + } + + var params = { + 'repository': $scope.repository.namespace + '/' + $scope.repository.name, + 'limit': 100, + 'since': since + }; + + $scope.buildsResource = ApiService.getRepoBuildsAsResource(params).get(function(resp) { + $scope.allBuilds = resp.builds; + $scope.currentFilter = filter; + updateBuilds(); + }); + }; + + var buildsChanged = function() { + if (!$scope.allBuilds) { + loadBuilds(); + return; + } + + if (!$scope.builds || !$scope.repository) { + return; + } + + // Replace any build records with updated records from the server. + $scope.builds.map(function(build) { + for (var i = 0; i < $scope.allBuilds.length; ++i) { + var current = $scope.allBuilds[i]; + if (current.id == build.id && current.phase != build.phase) { + $scope.allBuilds[i] = build; + break + } + } + }); + + updateBuilds(); + }; + + var loadBuildTriggers = function() { + if (!$scope.repository || !$scope.repository.can_admin) { return; } + + var params = { + 'repository': $scope.repository.namespace + '/' + $scope.repository.name + }; + + $scope.triggersResource = ApiService.listBuildTriggersAsResource(params).get(function(resp) { + $scope.triggers = resp.triggers; + + // Check to see if we need to setup any trigger. + var newTriggerId = $routeParams.newtrigger; + if (newTriggerId) { + $scope.triggers.map(function(trigger) { + if (trigger['id'] == newTriggerId && !trigger['is_active']) { + $scope.setupTrigger(trigger); + } + }); + } + }); + }; + + $scope.$watch('repository', loadBuildTriggers); + $scope.$watch('repository', loadBuilds); + + $scope.$watch('builds', buildsChanged); + + $scope.$watch('options.filter', loadBuilds); + $scope.$watch('options.predicate', updateBuilds); + $scope.$watch('options.reverse', updateBuilds); + + $scope.tablePredicateClass = function(name, predicate, reverse) { + if (name != predicate) { + return ''; + } + + return 'current ' + (reverse ? 'reversed' : ''); + }; + + $scope.orderBy = function(predicate) { + if (predicate == $scope.options.predicate) { + $scope.options.reverse = !$scope.options.reverse; + return; + } + + $scope.options.reverse = false; + $scope.options.predicate = predicate; + }; + + $scope.askDeleteTrigger = function(trigger) { + $scope.deleteTriggerInfo = { + 'trigger': trigger + }; + }; + + $scope.askRunTrigger = function(trigger) { + $scope.currentStartTrigger = trigger; + $scope.showTriggerStartDialogCounter++; + }; + + $scope.cancelSetupTrigger = function(trigger) { + if ($scope.currentSetupTrigger != trigger) { return; } + + $scope.currentSetupTrigger = null; + $scope.deleteTrigger(trigger); + }; + + $scope.setupTrigger = function(trigger) { + $scope.currentSetupTrigger = trigger; + $scope.showTriggerSetupCounter++; + }; + + $scope.startTrigger = function(trigger, opt_custom) { + var parameters = TriggerService.getRunParameters(trigger.service); + if (parameters.length && !opt_custom) { + $scope.currentStartTrigger = trigger; + $scope.showTriggerStartDialogCounter++; + return; + } + + var params = { + 'repository': $scope.repository.namespace + '/' + $scope.repository.name, + 'trigger_uuid': trigger.id + }; + + ApiService.manuallyStartBuildTrigger(opt_custom || {}, params).then(function(resp) { + $scope.allBuilds.push(resp); + updateBuilds(); + }, ApiService.errorDisplay('Could not start build')); + }; + + $scope.deleteTrigger = function(trigger, opt_callback) { + if (!trigger) { return; } + + var params = { + 'repository': $scope.repository.namespace + '/' + $scope.repository.name, + 'trigger_uuid': trigger.id + }; + + var errorHandler = ApiService.errorDisplay('Could not delete build trigger', function() { + opt_callback && opt_callback(false); + }); + + ApiService.deleteBuildTrigger(null, params).then(function(resp) { + $scope.triggers.splice($scope.triggers.indexOf(trigger), 1); + opt_callback && opt_callback(true); + }, errorHandler); + }; + + $scope.showNewBuildDialog = function() { + $scope.showBuildDialogCounter++; + }; + + $scope.handleBuildStarted = function(build) { + $scope.allBuilds.push(build); + updateBuilds(); + }; + } + }; + return directiveDefinitionObject; +}); + diff --git a/static/js/directives/ui/build-state-icon.js b/static/js/directives/ui/build-state-icon.js new file mode 100644 index 000000000..7b3df6ca7 --- /dev/null +++ b/static/js/directives/ui/build-state-icon.js @@ -0,0 +1,22 @@ +/** + * An element which displays an icon representing the state of the build. + */ +angular.module('quay').directive('buildStateIcon', function () { + var directiveDefinitionObject = { + priority: 0, + templateUrl: '/static/directives/build-state-icon.html', + replace: false, + transclude: false, + restrict: 'C', + scope: { + 'build': '=build' + }, + controller: function($scope, $element) { + $scope.isBuilding = function(build) { + if (!build) { return true; } + return build.phase != 'complete' && build.phase != 'error'; + }; + } + }; + return directiveDefinitionObject; +}); \ No newline at end of file diff --git a/static/js/directives/ui/filter-control.js b/static/js/directives/ui/filter-control.js new file mode 100644 index 000000000..0656c2cb6 --- /dev/null +++ b/static/js/directives/ui/filter-control.js @@ -0,0 +1,22 @@ +/** + * An element which displays a link to change a lookup filter, and shows whether it is selected. + */ +angular.module('quay').directive('filterControl', function () { + var directiveDefinitionObject = { + priority: 0, + templateUrl: '/static/directives/filter-control.html', + replace: false, + transclude: true, + restrict: 'C', + scope: { + 'filter': '=filter', + 'value': '@value' + }, + controller: function($scope, $element) { + $scope.setFilter = function() { + $scope.filter = $scope.value; + }; + } + }; + return directiveDefinitionObject; +}); \ No newline at end of file diff --git a/static/js/services/trigger-service.js b/static/js/services/trigger-service.js index 4a060866c..4ddf02539 100644 --- a/static/js/services/trigger-service.js +++ b/static/js/services/trigger-service.js @@ -2,8 +2,8 @@ * Helper service for defining the various kinds of build triggers and retrieving information * about them. */ -angular.module('quay').factory('TriggerService', ['UtilService', '$sanitize', 'KeyService', - function(UtilService, $sanitize, KeyService) { +angular.module('quay').factory('TriggerService', ['UtilService', '$sanitize', 'KeyService', 'Features', 'CookieService', + function(UtilService, $sanitize, KeyService, Features, CookieService) { var triggerService = {}; var triggerTypes = { @@ -28,15 +28,46 @@ angular.module('quay').factory('TriggerService', ['UtilService', '$sanitize', 'K var redirect_uri = KeyService['githubRedirectUri'] + '/trigger/' + namespace + '/' + repository; + // TODO(jschorr): Remove once the new layout is in place. + if (CookieService.get('quay.exp-new-layout') == 'true') { + redirect_uri += '/__new'; + } + var authorize_url = KeyService['githubTriggerAuthorizeUrl']; var client_id = KeyService['githubTriggerClientId']; return authorize_url + 'client_id=' + client_id + '&scope=repo,user:email&redirect_uri=' + redirect_uri; + }, + + 'is_enabled': function() { + return Features.GITHUB_BUILD; + }, + + 'icon': 'fa-github', + + 'title': function() { + var isEnterprise = KeyService.isEnterprise('github-trigger'); + if (isEnterprise) { + return 'GitHub Enterprise Repository Push'; + } + + return 'GitHub Repository Push'; } } } + triggerService.getTypes = function() { + var types = []; + for (var key in triggerTypes) { + if (!triggerTypes.hasOwnProperty(key)) { + continue; + } + types.push(key); + } + return types; + }; + triggerService.getRedirectUrl = function(name, namespace, repository) { var type = triggerTypes[name]; if (!type) { @@ -45,6 +76,14 @@ angular.module('quay').factory('TriggerService', ['UtilService', '$sanitize', 'K return type['get_redirect_url'](namespace, repository); }; + triggerService.getTitle = function(name) { + var type = triggerTypes[name]; + if (!type) { + return 'Unknown'; + } + return type['title'](); + }; + triggerService.getDescription = function(name, config) { var type = triggerTypes[name]; if (!type) { @@ -53,6 +92,10 @@ angular.module('quay').factory('TriggerService', ['UtilService', '$sanitize', 'K return type['description'](config); }; + triggerService.getMetadata = function(name) { + return triggerTypes[name]; + }; + triggerService.getRunParameters = function(name, config) { var type = triggerTypes[name]; if (!type) { diff --git a/static/lib/angular-moment.min.js b/static/lib/angular-moment.min.js index 25702c027..e74234d7a 100644 --- a/static/lib/angular-moment.min.js +++ b/static/lib/angular-moment.min.js @@ -1 +1,2 @@ -angular.module("angularMoment",[]).directive("amTimeAgo",["$window","$timeout",function(a,b){"use strict";return function(c,d,e){function f(){k&&(b.cancel(k),k=null)}function g(c){d.text(c.fromNow());var e=a.moment().diff(c,"minute"),f=3600;1>e?f=1:60>e?f=30:180>e&&(f=300),k=b(function(){g(c)},1e3*f,!1)}function h(){f(),g(a.moment(i,j))}var i,j,k=null;c.$watch(e.amTimeAgo,function(a){"undefined"!=typeof a&&null!==a&&(angular.isNumber(a)&&(a=new Date(a)),i=a,h())}),e.$observe("amFormat",function(a){j=a,h()}),c.$on("$destroy",function(){f()})}}]).filter("amDateFormat",["$window",function(a){"use strict";return function(b,c){return"undefined"==typeof b||null===b?"":(angular.isNumber(b)&&(b=new Date(b)),a.moment(b).format(c))}}]); \ No newline at end of file +"format global";"deps angular";"deps moment";!function(){"use strict";function a(a,b){return a.module("angularMoment",[]).constant("angularMomentConfig",{preprocess:null,timezone:"",format:null,statefulFilters:!0}).constant("moment",b).constant("amTimeAgoConfig",{withoutSuffix:!1,serverTime:null,titleFormat:null}).directive("amTimeAgo",["$window","moment","amMoment","amTimeAgoConfig","angularMomentConfig",function(b,c,d,e,f){return function(g,h,i){function j(){var a;if(e.serverTime){var b=(new Date).getTime(),d=b-u+e.serverTime;a=c(d)}else a=c();return a}function k(){q&&(b.clearTimeout(q),q=null)}function l(a){if(h.text(a.from(j(),s)),t&&!h.attr("title")&&h.attr("title",a.local().format(t)),!x){var c=Math.abs(j().diff(a,"minute")),d=3600;1>c?d=1:60>c?d=30:180>c&&(d=300),q=b.setTimeout(function(){l(a)},1e3*d)}}function m(a){y&&h.attr("datetime",a)}function n(){if(k(),o){var a=d.preprocessDate(o,v,r);l(a),m(a.toISOString())}}var o,p,q=null,r=f.format,s=e.withoutSuffix,t=e.titleFormat,u=(new Date).getTime(),v=f.preprocess,w=i.amTimeAgo.replace(/^::/,""),x=0===i.amTimeAgo.indexOf("::"),y="TIME"===h[0].nodeName.toUpperCase();p=g.$watch(w,function(a){return"undefined"==typeof a||null===a||""===a?(k(),void(o&&(h.text(""),m(""),o=null))):(o=a,n(),void(void 0!==a&&x&&p()))}),a.isDefined(i.amWithoutSuffix)&&g.$watch(i.amWithoutSuffix,function(a){"boolean"==typeof a?(s=a,n()):s=e.withoutSuffix}),i.$observe("amFormat",function(a){"undefined"!=typeof a&&(r=a,n())}),i.$observe("amPreprocess",function(a){v=a,n()}),g.$on("$destroy",function(){k()}),g.$on("amMoment:localeChanged",function(){n()})}}]).service("amMoment",["moment","$rootScope","$log","angularMomentConfig",function(b,c,d,e){this.preprocessors={utc:b.utc,unix:b.unix},this.changeLocale=function(d,e){var f=b.locale(d,e);return a.isDefined(d)&&c.$broadcast("amMoment:localeChanged"),f},this.changeTimezone=function(a){e.timezone=a,c.$broadcast("amMoment:timezoneChanged")},this.preprocessDate=function(c,f,g){return a.isUndefined(f)&&(f=e.preprocess),this.preprocessors[f]?this.preprocessors[f](c,g):(f&&d.warn("angular-moment: Ignoring unsupported value for preprocess: "+f),!isNaN(parseFloat(c))&&isFinite(c)?b(parseInt(c,10)):b(c,g))},this.applyTimezone=function(a){var b=e.timezone;return a&&b&&(a.tz?a=a.tz(b):d.warn("angular-moment: timezone specified but moment.tz() is undefined. Did you forget to include moment-timezone.js?")),a}}]).filter("amCalendar",["moment","amMoment","angularMomentConfig",function(a,b,c){function d(c,d){if("undefined"==typeof c||null===c)return"";c=b.preprocessDate(c,d);var e=a(c);return e.isValid()?b.applyTimezone(e).calendar():""}return d.$stateful=c.statefulFilters,d}]).filter("amDateFormat",["moment","amMoment","angularMomentConfig",function(a,b,c){function d(c,d,e){if("undefined"==typeof c||null===c)return"";c=b.preprocessDate(c,e);var f=a(c);return f.isValid()?b.applyTimezone(f).format(d):""}return d.$stateful=c.statefulFilters,d}]).filter("amDurationFormat",["moment","angularMomentConfig",function(a,b){function c(b,c,d){return"undefined"==typeof b||null===b?"":a.duration(b,c).humanize(d)}return c.$stateful=b.statefulFilters,c}]).filter("amTimeAgo",["moment","amMoment","angularMomentConfig",function(a,b,c){function d(c,d,e){if("undefined"==typeof c||null===c)return"";c=b.preprocessDate(c,d);var f=a(c);return f.isValid()?b.applyTimezone(f).fromNow(e):""}return d.$stateful=c.statefulFilters,d}])}"function"==typeof define&&define.amd?define(["angular","moment"],a):"undefined"!=typeof module&&module&&module.exports?a(angular,require("moment")):a(angular,window.moment)}(); +//# sourceMappingURL=angular-moment.min.js.map \ No newline at end of file diff --git a/static/partials/repo-view.html b/static/partials/repo-view.html index 5d00dc98c..113da0e78 100644 --- a/static/partials/repo-view.html +++ b/static/partials/repo-view.html @@ -21,7 +21,8 @@ - + @@ -59,7 +60,9 @@
- builds +