From 7bf693615483b39e188e8ecc003ab1c9c785ae0d Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Fri, 14 Feb 2014 22:59:44 -0500 Subject: [PATCH] - New UI for the repo view, which shows the build status and admin button on the top bar, and allows for creation of new builds as well as linking to build history - Add a new build button to the build history page - --- static/css/quay.css | 58 ++-- .../directives/dockerfile-build-dialog.html | 27 ++ static/directives/dockerfile-build-form.html | 19 ++ static/js/app.js | 249 ++++++++++++++++++ static/js/controllers.js | 129 ++++----- static/partials/new-repo.html | 61 +---- static/partials/repo-build.html | 14 +- static/partials/view-repo.html | 86 +++--- 8 files changed, 439 insertions(+), 204 deletions(-) create mode 100644 static/directives/dockerfile-build-dialog.html create mode 100644 static/directives/dockerfile-build-form.html diff --git a/static/css/quay.css b/static/css/quay.css index 31e3b17da..fa9994afd 100644 --- a/static/css/quay.css +++ b/static/css/quay.css @@ -846,9 +846,19 @@ i.toggle-icon:hover { display: none; } +.visible-md-inline { + display: none; +} + .hidden-sm-inline { display: inline; -} +} + +@media (min-width: 991px) { + .visible-md-inline { + display: inline; + } +} @media (max-width: 991px) and (min-width: 768px) { .visible-sm-inline { @@ -1449,7 +1459,10 @@ p.editable:hover i { } .repo .header { - margin-bottom: 10px; + margin-bottom: 20px; + padding-bottom: 16px; + border-bottom: 1px solid #eee; + position: relative; } @@ -1499,36 +1512,12 @@ p.editable:hover i { display: inline-block; } -.repo .status-boxes { - float: right; - margin-bottom: 20px; -} - -.repo .status-boxes .status-box { - cursor: pointer; +.repo .repo-controls .count { display: inline-block; - border: 1px solid #eee; - border-radius: 4px; -} - -.repo .status-boxes .status-box .title { - padding: 4px; - display: inline-block; - padding-left: 10px; - padding-right: 10px; -} - -.repo .status-boxes .status-box .title i { - margin-right: 6px; -} - -.repo .status-boxes .status-box .count { - display: inline-block; - background-image: linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%); - padding: 4px; - padding-left: 10px; - padding-right: 10px; + padding-left: 4px; + padding-right: 4px; font-weight: bold; + color: #428bca; transform: scaleX(0); -webkit-transform: scaleX(0); @@ -1539,13 +1528,13 @@ p.editable:hover i { -moz-transition: -moz-transform 500ms ease-in-out; } -.repo .status-boxes .status-box .count.visible { +.repo .repo-controls .count.visible { transform: scaleX(1); -webkit-transform: scaleX(1); -moz-transform: scaleX(1); } -.repo .pull-command { +.repo .repo-controls { float: right; display: inline-block; font-size: 0.8em; @@ -3197,4 +3186,9 @@ pre.command:before { font-size: 14px; font-weight: bold; padding: 4px 8px; +} + +.file-drop { + padding: 10px; + margin: 10px; } \ No newline at end of file diff --git a/static/directives/dockerfile-build-dialog.html b/static/directives/dockerfile-build-dialog.html new file mode 100644 index 000000000..4848b341c --- /dev/null +++ b/static/directives/dockerfile-build-dialog.html @@ -0,0 +1,27 @@ +
+ + +
diff --git a/static/directives/dockerfile-build-form.html b/static/directives/dockerfile-build-form.html new file mode 100644 index 000000000..da808ec04 --- /dev/null +++ b/static/directives/dockerfile-build-form.html @@ -0,0 +1,19 @@ +
+
+
+
+
+ Uploading file {{ upload_file }} +
+
+
+
+
+ +
+
+ Upload a Dockerfile or a zip file containing a Dockerfile in the root directory +
+ +
+
diff --git a/static/js/app.js b/static/js/app.js index 93d05658d..e2e90d22e 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -2700,6 +2700,239 @@ quayApp.directive('buildProgress', function () { return directiveDefinitionObject; }); + +quayApp.directive('dockerfileBuildDialog', function () { + var directiveDefinitionObject = { + priority: 0, + templateUrl: '/static/directives/dockerfile-build-dialog.html', + replace: false, + transclude: false, + restrict: 'C', + scope: { + 'repository': '=repository', + 'showNow': '=showNow', + 'buildStarted': '&buildStarted' + }, + controller: function($scope, $element) { + $scope.building = false; + $scope.uploading = false; + $scope.startCounter = 0; + + $scope.handleBuildStarted = function(build) { + $('#dockerfilebuildModal').modal('hide'); + if ($scope.buildStarted) { + $scope.buildStarted({'build': build}); + } + }; + + $scope.handleBuildFailed = function(message) { + $scope.errorMessage = message; + }; + + $scope.startBuild = function() { + $scope.errorMessage = null; + $scope.startCounter++; + }; + + $scope.$watch('showNow', function(sn) { + if (sn && $scope.repository) { + $('#dockerfilebuildModal').modal({}); + } + }); + } + }; + return directiveDefinitionObject; +}); + + +quayApp.directive('dockerfileBuildForm', function () { + var directiveDefinitionObject = { + priority: 0, + templateUrl: '/static/directives/dockerfile-build-form.html', + replace: false, + transclude: false, + restrict: 'C', + scope: { + 'repository': '=repository', + 'startNow': '=startNow', + 'hasDockerfile': '=hasDockerfile', + 'uploadFailed': '&uploadFailed', + 'uploadStarted': '&uploadStarted', + 'buildStarted': '&buildStarted', + 'buildFailed': '&buildFailed', + 'missingFile': '&missingFile', + 'uploading': '=uploading', + 'building': '=building' + }, + controller: function($scope, $element, ApiService) { + $scope.internal = {'hasDockerfile': false}; + + var handleBuildFailed = function(message) { + message = message || 'Dockerfile build failed to start'; + + var result = false; + if ($scope.buildFailed) { + result = $scope.buildFailed({'message': message}); + } + + if (!result) { + bootbox.dialog({ + "message": message, + "title": "Cannot start Dockerfile build", + "buttons": { + "close": { + "label": "Close", + "className": "btn-primary" + } + } + }); + } + }; + + var handleUploadFailed = function(message) { + message = message || 'Error with file upload'; + + var result = false; + if ($scope.uploadFailed) { + result = $scope.uploadFailed({'message': message}); + } + + if (!result) { + bootbox.dialog({ + "message": message, + "title": "Cannot upload file for Dockerfile build", + "buttons": { + "close": { + "label": "Close", + "className": "btn-primary" + } + } + }); + } + }; + + var handleMissingFile = function() { + var result = false; + if ($scope.missingFile) { + result = $scope.missingFile({}); + } + + if (!result) { + bootbox.dialog({ + "message": 'A Dockerfile or an archive containing a Dockerfile is required', + "title": "Missing Dockerfile", + "buttons": { + "close": { + "label": "Close", + "className": "btn-primary" + } + } + }); + } + }; + + var startBuild = function(fileId) { + $scope.building = true; + + var repo = $scope.repository; + var data = { + 'file_id': fileId + }; + + var params = { + 'repository': repo.namespace + '/' + repo.name + }; + + ApiService.requestRepoBuild(data, params).then(function(resp) { + $scope.building = false; + $scope.uploading = false; + + if ($scope.buildStarted) { + $scope.buildStarted({'build': resp}); + } + }, function(resp) { + $scope.building = false; + $scope.uploading = false; + + handleBuildFailed(resp.message); + }); + }; + + var conductUpload = function(file, url, fileId, mimeType) { + if ($scope.uploadStarted) { + $scope.uploadStarted({}); + } + + var request = new XMLHttpRequest(); + request.open('PUT', url, true); + request.setRequestHeader('Content-Type', mimeType); + request.onprogress = function(e) { + $scope.$apply(function() { + var percentLoaded; + if (e.lengthComputable) { + $scope.upload_progress = (e.loaded / e.total) * 100; + } + }); + }; + request.onerror = function() { + $scope.$apply(function() { + handleUploadFailed(); + }); + }; + request.onreadystatechange = function() { + var state = request.readyState; + if (state == 4) { + $scope.$apply(function() { + startBuild(fileId); + $scope.uploading = false; + }); + return; + } + }; + request.send(file); + }; + + var startFileUpload = function(repo) { + $scope.uploading = true; + $scope.uploading_progress = 0; + + var uploader = $('#file-drop')[0]; + if (uploader.files.length == 0) { + handleMissingFile(); + $scope.uploading = false; + return; + } + + var file = uploader.files[0]; + $scope.upload_file = file.name; + + var mimeType = file.type || 'application/octet-stream'; + var data = { + 'mimeType': mimeType + }; + + var getUploadUrl = ApiService.getFiledropUrl(data).then(function(resp) { + conductUpload(file, resp.url, resp.file_id, mimeType); + }, function() { + handleUploadFailed('Could not retrieve upload URL'); + }); + }; + + $scope.$watch('internal.hasDockerfile', function(d) { + $scope.hasDockerfile = d; + }); + + $scope.$watch('startNow', function() { + if ($scope.startNow && $scope.repository && !$scope.uploading && !$scope.building) { + startFileUpload(); + } + }); + } + }; + return directiveDefinitionObject; +}); + + // Note: ngBlur is not yet in Angular stable, so we add it manaully here. quayApp.directive('ngBlur', function() { return function( scope, elem, attrs ) { @@ -2709,6 +2942,22 @@ quayApp.directive('ngBlur', function() { }; }); +quayApp.directive("filePresent", [function () { + return { + restrict: 'A', + scope: { + 'filePresent': "=" + }, + link: function (scope, element, attributes) { + element.bind("change", function (changeEvent) { + scope.$apply(function() { + scope.filePresent = changeEvent.target.files.length > 0; + }); + }); + } + } +}]); + quayApp.directive('ngVisible', function () { return function (scope, element, attr) { scope.$watch(attr.ngVisible, function (visible) { diff --git a/static/js/controllers.js b/static/js/controllers.js index 2b9cf5aed..d3ab397c1 100644 --- a/static/js/controllers.js +++ b/static/js/controllers.js @@ -341,8 +341,17 @@ function RepoCtrl($scope, $sanitize, Restangular, ImageMetadataService, ApiServi // Start scope methods ////////////////////////////////////////// + $scope.buildDialogShowCounter = 0; $scope.getFormattedCommand = ImageMetadataService.getFormattedCommand; + $scope.showNewBuildDialog = function() { + $scope.buildDialogShowCounter++; + }; + + $scope.handleBuildStarted = function(build) { + startBuildInfoTimer($scope.repo); + }; + $scope.showBuild = function(buildInfo) { $location.path('/repository/' + namespace + '/' + name + '/build'); $location.search('current', buildInfo.id); @@ -784,6 +793,17 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope $scope.builds = []; $scope.polling = false; + $scope.buildDialogShowCounter = 0; + + $scope.showNewBuildDialog = function() { + $scope.buildDialogShowCounter++; + }; + + $scope.handleBuildStarted = function(newBuild) { + $scope.builds.push(newBuild); + $scope.setCurrentBuild(newBuild['id'], true); + }; + $scope.adjustLogHeight = function() { $('.build-logs').height($(window).height() - 385); }; @@ -1548,12 +1568,6 @@ function NewRepoCtrl($scope, $location, $http, $timeout, UserService, ApiService 'initialize': false }; - $('#couldnotbuildModal').on('hidden.bs.modal', function() { - $scope.$apply(function() { - $location.path('/repository/' + $scope.created.namespace + '/' + $scope.created.name); - }); - }); - // Watch the namespace on the repo. If it changes, we update the plan and the public/private // accordingly. $scope.isUserNamespace = true; @@ -1600,15 +1614,36 @@ function NewRepoCtrl($scope, $location, $http, $timeout, UserService, ApiService $scope.repo.namespace = namespace; }; + $scope.handleBuildStarted = function() { + var repo = $scope.repo; + $location.path('/repository/' + repo.namespace + '/' + repo.name); + }; + + $scope.handleBuildFailed = function(message) { + var repo = $scope.repo; + + bootbox.dialog({ + "message": message, + "title": "Could not start Dockerfile build", + "buttons": { + "close": { + "label": "Close", + "className": "btn-primary", + "callback": function() { + $scope.$apply(function() { + $location.path('/repository/' + repo.namespace + '/' + repo.name); + }); + } + } + } + }); + + return true; + }; + $scope.createNewRepo = function() { $('#repoName').popover('hide'); - var uploader = $('#file-drop')[0]; - if ($scope.repo.initialize && uploader.files.length < 1) { - $('#missingfileModal').modal(); - return; - } - $scope.creating = true; var repo = $scope.repo; var data = { @@ -1624,7 +1659,7 @@ function NewRepoCtrl($scope, $location, $http, $timeout, UserService, ApiService // Repository created. Start the upload process if applicable. if ($scope.repo.initialize) { - startFileUpload(created); + $scope.createdForBuild = created; return; } @@ -1653,74 +1688,6 @@ function NewRepoCtrl($scope, $location, $http, $timeout, UserService, ApiService PlanService.changePlan($scope, null, $scope.planRequired.stripeId, callbacks); }; - - var startBuild = function(repo, fileId) { - $scope.building = true; - - var data = { - 'file_id': fileId - }; - - var params = { - 'repository': repo.namespace + '/' + repo.name - }; - - ApiService.requestRepoBuild(data, params).then(function(resp) { - $location.path('/repository/' + params.repository); - }, function() { - $('#couldnotbuildModal').modal(); - }); - }; - - var conductUpload = function(repo, file, url, fileId, mimeType) { - var request = new XMLHttpRequest(); - request.open('PUT', url, true); - request.setRequestHeader('Content-Type', mimeType); - request.onprogress = function(e) { - $scope.$apply(function() { - var percentLoaded; - if (e.lengthComputable) { - $scope.upload_progress = (e.loaded / e.total) * 100; - } - }); - }; - request.onerror = function() { - $scope.$apply(function() { - $('#couldnotbuildModal').modal(); - }); - }; - request.onreadystatechange = function() { - var state = request.readyState; - if (state == 4) { - $scope.$apply(function() { - $scope.uploading = false; - startBuild(repo, fileId); - }); - return; - } - }; - request.send(file); - }; - - var startFileUpload = function(repo) { - $scope.uploading = true; - $scope.uploading_progress = 0; - - var uploader = $('#file-drop')[0]; - var file = uploader.files[0]; - $scope.upload_file = file.name; - - var mimeType = file.type || 'application/octet-stream'; - var data = { - 'mimeType': mimeType - }; - - var getUploadUrl = ApiService.getFiledropUrl(data).then(function(resp) { - conductUpload(repo, file, resp.url, resp.file_id, mimeType); - }, function() { - $('#couldnotbuildModal').modal(); - }); - }; var subscribedToPlan = function(sub) { $scope.planChanging = false; diff --git a/static/partials/new-repo.html b/static/partials/new-repo.html index 30d686525..7eb73d86b 100644 --- a/static/partials/new-repo.html +++ b/static/partials/new-repo.html @@ -12,16 +12,7 @@
-
- Uploading file {{ upload_file }} -
-
-
-
- -
- -
+
@@ -109,11 +100,9 @@
-
- Upload a Dockerfile or a zip file containing a Dockerfile in the root directory -
- - +
@@ -123,7 +112,7 @@
@@ -157,26 +146,6 @@ - - - - - - - - - - +
+
+