diff --git a/data/model.py b/data/model.py index 16695ffae..66aa75008 100644 --- a/data/model.py +++ b/data/model.py @@ -770,6 +770,16 @@ def get_all_repo_users(namespace_name, repository_name): Repository.name == repository_name) +def get_repository_for_resource(resource_key): + joined = Repository.select().join(RepositoryBuild) + query = joined.where(RepositoryBuild.resource_key == resource_key).limit(1) + result = list(query) + if not result: + return None + + return result[0] + + def get_repository(namespace_name, repository_name): try: return Repository.get(Repository.name == repository_name, diff --git a/endpoints/api.py b/endpoints/api.py index d3a625e59..93b374dcf 100644 --- a/endpoints/api.py +++ b/endpoints/api.py @@ -1157,6 +1157,7 @@ def build_status_view(build_obj): 'started': build_obj.started, 'display_name': build_obj.display_name, 'status': status, + 'resource_key': build_obj.resource_key } @@ -1221,6 +1222,14 @@ def request_repo_build(namespace, repository): logger.debug('User requested repository initialization.') dockerfile_id = request.get_json()['file_id'] + # Check if the dockerfile resource has already been used. If so, then it can only be reused if the + # user has access to the repository for which it was used. + associated_repository = model.get_repository_for_resource(dockerfile_id) + if associated_repository: + if not ModifyRepositoryPermission(associated_repository.namespace, associated_repository.name): + abort(403) + + # Start the build. repo = model.get_repository(namespace, repository) token = model.create_access_token(repo, 'write') display_name = user_files.get_file_checksum(dockerfile_id) diff --git a/static/css/quay.css b/static/css/quay.css index 93ac21c85..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; @@ -1759,7 +1748,8 @@ p.editable:hover i { } .repo-build .build-pane .build-logs .command-title, -.repo-build .build-pane .build-logs .log-entry .message { +.repo-build .build-pane .build-logs .log-entry .message, +.repo-build .build-pane .build-logs .log-entry .message span { font-family: Consolas, "Lucida Console", Monaco, monospace; font-size: 13px; } @@ -3196,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 22e0bdfad..e2e90d22e 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -102,7 +102,7 @@ function getMarkedDown(string) { return Markdown.getSanitizingConverter().makeHtml(string || ''); } -quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angular-tour', 'restangular', 'angularMoment', 'angulartics', /*'angulartics.google.analytics',*/ 'angulartics.mixpanel', '$strap.directives', 'ngCookies', 'ngSanitize', 'angular-md5', 'pasvaz.bindonce'], function($provide, cfpLoadingBarProvider) { +quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angular-tour', 'restangular', 'angularMoment', 'angulartics', /*'angulartics.google.analytics',*/ 'angulartics.mixpanel', '$strap.directives', 'ngCookies', 'ngSanitize', 'angular-md5', 'pasvaz.bindonce', 'ansiToHtml'], function($provide, cfpLoadingBarProvider) { cfpLoadingBarProvider.includeSpinner = false; $provide.factory('UtilService', ['$sanitize', function($sanitize) { @@ -1143,6 +1143,13 @@ quayApp.directive('dockerAuthDialog', function () { }); +quayApp.filter('reverse', function() { + return function(items) { + return items.slice().reverse(); + }; +}); + + quayApp.filter('bytes', function() { return function(bytes, precision) { if (!bytes || isNaN(parseFloat(bytes)) || !isFinite(bytes)) return 'Unknown'; @@ -2693,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 ) { @@ -2702,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 9111a4566..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); @@ -765,7 +774,7 @@ function RepoCtrl($scope, $sanitize, Restangular, ImageMetadataService, ApiServi loadViewInfo(); } -function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $location, $interval, $sanitize) { +function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $location, $interval, $sanitize, ansi2html) { var namespace = $routeParams.namespace; var name = $routeParams.name; var pollTimerHandle = null; @@ -784,8 +793,40 @@ 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() - 365); + $('.build-logs').height($(window).height() - 385); + }; + + $scope.askRestartBuild = function(build) { + $('#confirmRestartBuildModal').modal({}); + }; + + $scope.restartBuild = function(build) { + $('#confirmRestartBuildModal').modal('hide'); + + var data = { + 'file_id': build['resource_key'] + }; + + var params = { + 'repository': namespace + '/' + name + }; + + ApiService.requestRepoBuild(data, params).then(function(newBuild) { + $scope.builds.push(newBuild); + $scope.setCurrentBuild(newBuild['id'], true); + }); }; $scope.hasLogs = function(container) { @@ -806,13 +847,23 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope // Find the build. for (var i = 0; i < $scope.builds.length; ++i) { if ($scope.builds[i].id == buildId) { - $scope.setCurrentBuildInternal($scope.builds[i], opt_updateURL); + $scope.setCurrentBuildInternal(i, $scope.builds[i], opt_updateURL); return; } } }; - $scope.setCurrentBuildInternal = function(build, opt_updateURL) { + $scope.processANSI = function(message, container) { + var filter = container.logs._filter = (container.logs._filter || ansi2html.create()); + + // Note: order is important here. + var setup = filter.getSetupHtml(); + var stream = filter.addInputToStream(message); + var teardown = filter.getTeardownHtml(); + return setup + stream + teardown; + }; + + $scope.setCurrentBuildInternal = function(index, build, opt_updateURL) { if (build == $scope.currentBuild) { return; } stopPollTimer(); @@ -822,6 +873,7 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope $scope.currentParentEntry = null; $scope.currentBuild = build; + $scope.currentBuildIndex = index; if (opt_updateURL) { if (build) { @@ -893,7 +945,6 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope var getBuildStatusAndLogs = function() { if (!$scope.currentBuild || $scope.polling) { return; } - $scope.polling = true; var params = { @@ -904,7 +955,7 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope ApiService.getRepoBuildStatus(null, params, true).then(function(resp) { // Note: We use extend here rather than replacing as Angular is depending on the // root build object to remain the same object. - $.extend(true, $scope.currentBuild, resp); + $.extend(true, $scope.builds[$scope.currentBuildIndex], resp); checkPollTimer(); // Load the updated logs for the build. @@ -913,9 +964,16 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope }; ApiService.getRepoBuildLogsAsResource(params, true).withOptions(options).get(function(resp) { + if ($scope.logStartIndex != null && resp['start'] != $scope.logStartIndex) { + $scope.polling = false; + return; + } + processLogs(resp['logs'], resp['start']); $scope.logStartIndex = resp['total']; $scope.polling = false; + }, function() { + $scope.polling = false; }); }); }; @@ -948,7 +1006,7 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope if ($location.search().current) { $scope.setCurrentBuild($location.search().current, false); } else if ($scope.builds.length > 0) { - $scope.setCurrentBuild($scope.builds[0].id, true); + $scope.setCurrentBuild($scope.builds[$scope.builds.length - 1].id, true); } }); }; @@ -1510,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; @@ -1562,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 = { @@ -1586,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; } @@ -1615,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/lib/ansi2html.js b/static/lib/ansi2html.js new file mode 100644 index 000000000..bd0c3b709 --- /dev/null +++ b/static/lib/ansi2html.js @@ -0,0 +1,335 @@ +/** + * Originally from: https://github.com/jorgeecardona/ansi-to-html + * Modified by jschorr: Add ability to repeat existing styles and not close them out automatically. + */ +angular.module('ansiToHtml', []).value('ansi2html', (function() { + // Define the styles supported from ANSI. + var STYLES = { + 'ef0': 'color:#000', + 'ef1': 'color:#A00', + 'ef2': 'color:#0A0', + 'ef3': 'color:#A50', + 'ef4': 'color:#00A', + 'ef5': 'color:#A0A', + 'ef6': 'color:#0AA', + 'ef7': 'color:#AAA', + 'ef8': 'color:#555', + 'ef9': 'color:#F55', + 'ef10': 'color:#5F5', + 'ef11': 'color:#FF5', + 'ef12': 'color:#55F', + 'ef13': 'color:#F5F', + 'ef14': 'color:#5FF', + 'ef15': 'color:#FFF', + 'eb0': 'background-color:#000', + 'eb1': 'background-color:#A00', + 'eb2': 'background-color:#0A0', + 'eb3': 'background-color:#A50', + 'eb4': 'background-color:#00A', + 'eb5': 'background-color:#A0A', + 'eb6': 'background-color:#0AA', + 'eb7': 'background-color:#AAA', + 'eb8': 'background-color:#555', + 'eb9': 'background-color:#F55', + 'eb10': 'background-color:#5F5', + 'eb11': 'background-color:#FF5', + 'eb12': 'background-color:#55F', + 'eb13': 'background-color:#F5F', + 'eb14': 'background-color:#5FF', + 'eb15': 'background-color:#FFF' + }; + + // Define the default styles. + var DEFAULTS = { + fg: '#FFF', + bg: '#000' + }; + + var __slice = [].slice; + + var toHexString = function(num) { + num = num.toString(16); + while (num.length < 2) { + num = "0" + num; + } + return num; + }; + + var escapeHtml = function (unsafe) { + return unsafe + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); + }; + + // Define the derived styles. + [0, 1, 2, 3, 4, 5].forEach( + function(red) { + return [0, 1, 2, 3, 4, 5].forEach( + function(green) { + return [0, 1, 2, 3, 4, 5].forEach( + function(blue) { + var b, c, g, n, r, rgb; + c = 16 + (red * 36) + (green * 6) + blue; + r = red > 0 ? red * 40 + 55 : 0; + g = green > 0 ? green * 40 + 55 : 0; + b = blue > 0 ? blue * 40 + 55 : 0; + rgb = ((function() { + var _i, _len, _ref, _results; + _ref = [r, g, b]; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + n = _ref[_i]; + _results.push(toHexString(n)); + } + return _results; + })()).join(''); + STYLES["ef" + c] = "color:#" + rgb; + return STYLES["eb" + c] = "background-color:#" + rgb; + }); + }); + }); + + (function() { + _results = []; + for (_i = 0; _i <= 23; _i++){ _results.push(_i); } + return _results; + }).apply(this).forEach( + function(gray) { + var c, l; + c = gray + 232; + l = toHexString(gray * 10 + 8); + STYLES["ef" + c] = "color:#" + l + l + l; + return STYLES["eb" + c] = "background-color:#" + l + l + l; + }); + + // Define the filter class which will track all the ANSI style state. + function Filter(options) { + this.opts = $.extend(true, DEFAULTS, options || {}); + this.input = []; + this.stack = []; + } + + Filter.create = function() { + return new Filter(); + }; + + Filter.prototype.toHtml = function(input) { + this.resetStyles(); + var html = this.addInputToStream(input) + this.getTeardownHtml(); + this.resetStyles(); + return html; + }; + + Filter.prototype.addInputToStream = function(input) { + var buf = []; + + this.input = typeof input === 'string' ? [input] : input; + this.forEach(function(chunk) { + return buf.push(chunk); + }); + + return buf.join(''); + }; + + Filter.prototype.getSetupHtml = function() { + return this.stack.map(function(data) { + return data['html']; + }).join(''); + }; + + Filter.prototype.getTeardownHtml = function() { + var stackCopy = this.stack.slice(); + return stackCopy.reverse().map(function(data) { + return ""; + }).join(''); + }; + + Filter.prototype.forEach = function(callback) { + var that = this; + var buf = ''; + var handleDisplay = function(code) { + code = parseInt(code, 10); + if (code === -1) { + callback('
'); + } + if (code === 0) { + callback(that.getTeardownHtml()); + that.resetStyles(); + } + if (code === 1) { + callback(that.pushTag('b')); + } + if (code === 2) { + + } + if ((2 < code && code < 5)) { + callback(that.pushTag('u')); + } + if ((4 < code && code < 7)) { + callback(that.pushTag('blink')); + } + if (code === 7) { + + } + if (code === 8) { + callback(that.pushStyle('display:none')); + } + if (code === 9) { + callback(that.pushTag('strike')); + } + if (code === 24) { + callback(that.closeTag('u')); + } + if ((29 < code && code < 38)) { + callback(that.pushStyle("ef" + (code - 30))); + } + if (code === 39) { + callback(that.pushStyle("color:" + that.opts.fg)); + } + if ((39 < code && code < 48)) { + callback(that.pushStyle("eb" + (code - 40))); + } + if (code === 49) { + callback(that.pushStyle("background-color:" + that.opts.bg)); + } + if ((89 < code && code < 98)) { + callback(that.pushStyle("ef" + (8 + (code - 90)))); + } + if ((99 < code && code < 108)) { + return callback(that.pushStyle("eb" + (8 + (code - 100)))); + } + }; + + this.input.forEach(function(chunk) { + buf += chunk; + return that.tokenize(buf, function(tok, data) { + switch (tok) { + case 'text': + return callback(escapeHtml(data)); + case 'display': + return handleDisplay(data); + case 'xterm256': + return callback(that.pushStyle("ef" + data)); + } + }); + }); + }; + + Filter.prototype.pushTag = function(tag, style) { + if (style == null) { + style = ''; + } + if (style.length && style.indexOf(':') === -1) { + style = STYLES[style]; + } + var html = ["<" + tag, (style ? " style=\"" + style + "\"" : void 0), ">"].join(''); + this.stack.push({'html': html, 'kind': tag}); + return html; + }; + + Filter.prototype.pushStyle = function(style) { + return this.pushTag("span", style); + }; + + Filter.prototype.closeTag = function(style) { + var last; + if (this.stack.slice(-1)[0]['kind'] === style) { + last = this.stack.pop(); + } + if (last != null) { + return ""; + } + }; + + Filter.prototype.resetStyles = function() { + this.stack = []; + }; + + Filter.prototype.tokenize = function(text, callback) { + var ansiHandler, ansiMatch, ansiMess, handler, i, length, newline, process, realText, remove, removeXterm256, tokens, _j, _len, _results1, + _this = this; + ansiMatch = false; + ansiHandler = 3; + remove = function(m) { + return ''; + }; + removeXterm256 = function(m, g1) { + callback('xterm256', g1); + return ''; + }; + newline = function(m) { + if (_this.opts.newline) { + callback('display', -1); + } else { + callback('text', m); + } + return ''; + }; + ansiMess = function(m, g1) { + var code, _j, _len; + ansiMatch = true; + if (g1.trim().length === 0) { + g1 = '0'; + } + g1 = g1.trimRight(';').split(';'); + for (_j = 0, _len = g1.length; _j < _len; _j++) { + code = g1[_j]; + callback('display', code); + } + return ''; + }; + realText = function(m) { + callback('text', m); + return ''; + }; + tokens = [ + { + pattern: /^\x08+/, + sub: remove + }, { + pattern: /^\x1b\[38;5;(\d+)m/, + sub: removeXterm256 + }, { + pattern: /^\n+/, + sub: newline + }, { + pattern: /^\x1b\[((?:\d{1,3};?)+|)m/, + sub: ansiMess + }, { + pattern: /^\x1b\[?[\d;]{0,3}/, + sub: remove + }, { + pattern: /^([^\x1b\x08\n]+)/, + sub: realText + } + ]; + process = function(handler, i) { + var matches; + if (i > ansiHandler && ansiMatch) { + return; + } else { + ansiMatch = false; + } + matches = text.match(handler.pattern); + text = text.replace(handler.pattern, handler.sub); + }; + _results1 = []; + while ((length = text.length) > 0) { + for (i = _j = 0, _len = tokens.length; _j < _len; i = ++_j) { + handler = tokens[i]; + process(handler, i); + } + if (text.length === length) { + break; + } else { + _results1.push(void 0); + } + } + return _results1; + }; + + return Filter; +})()); \ No newline at end of file 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 @@ - - - - - - - - - - + +
+
+ + + + diff --git a/static/partials/view-repo.html b/static/partials/view-repo.html index 9fdb632b7..49fd55b46 100644 --- a/static/partials/view-repo.html +++ b/static/partials/view-repo.html @@ -5,51 +5,65 @@
-
+

- - - - - - +

- - -
- - -
-
- @@ -72,7 +86,7 @@ sudo docker push quay.io/{{repo.namespace}}/{{repo.name}}
- Your build is currently processing, if this takes longer than an hour, please contact Quay.io support + A build is currently processing. If this takes longer than an hour, please contact us
@@ -235,6 +249,10 @@ sudo docker push quay.io/{{repo.namespace}}/{{repo.name}}
+
+
+