diff --git a/initdb.py b/initdb.py index 88468c022..2cd57708f 100644 --- a/initdb.py +++ b/initdb.py @@ -16,11 +16,14 @@ store = app.config['STORAGE'] SAMPLE_DIFFS = ['test/data/sample/diffs/diffs%s.json' % i for i in range(1, 10)] -SAMPLE_CMDS = [['/bin/bash'], +SAMPLE_CMDS = [["/bin/bash"], ["/bin/sh", "-c", "echo \"PasswordAuthentication no\" >> /etc/ssh/sshd_config"], ["/bin/sh", "-c", "sed -i 's/#\\(force_color_prompt\\)/\\1/' /etc/skel/.bashrc"], + ["/bin/sh", "-c", "#(nop) EXPOSE [8080]"], + ["/bin/sh", "-c", + "#(nop) MAINTAINER Jake Moshenko "], None] REFERENCE_DATE = datetime(2013, 6, 23) diff --git a/nginx-staging.conf b/nginx-staging.conf index c6927609e..b29bda2c5 100644 --- a/nginx-staging.conf +++ b/nginx-staging.conf @@ -1,4 +1,4 @@ -worker_processes 8; +worker_processes 2; user root nogroup; pid /mnt/nginx/nginx.pid; diff --git a/static/css/quay.css b/static/css/quay.css index b226e6593..b7c394d0e 100644 --- a/static/css/quay.css +++ b/static/css/quay.css @@ -9,6 +9,16 @@ } } +.codetooltipcontainer .tooltip-inner { + white-space:pre; + max-width:none; +} + +.codetooltip { + font-family: Consolas, "Lucida Console", Monaco, monospace; + display: block; +} + .resource-view-element { position: relative; } @@ -1655,6 +1665,18 @@ p.editable:hover i { padding-top: 4px; } +.repo .formatted-command { + margin-top: 4px; + padding: 4px; + font-size: 12px; +} + +.repo .formatted-command.trimmed { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + .repo .changes-count-container { text-align: center; } @@ -2503,16 +2525,37 @@ p.editable:hover i { display: block; } -.d3-tip .created { +.d3-tip .command { font-size: 12px; color: white; display: block; - margin-bottom: 6px; + font-family: Consolas, "Lucida Console", Monaco, monospace; +} + +.d3-tip .info-line { + display: block; + margin-top: 6px; + padding-top: 6px; +} + +.d3-tip .info-line i { + margin-right: 10px; + font-size: 14px; + color: #888; } .d3-tip .comment { display: block; font-size: 14px; + padding-bottom: 4px; + margin-bottom: 10px; + border-bottom: 1px dotted #ccc; +} + +.d3-tip .created { + font-size: 12px; + color: white; + display: block; margin-bottom: 6px; } diff --git a/static/js/app.js b/static/js/app.js index c85d5d304..ef2317d31 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -103,9 +103,51 @@ function getMarkedDown(string) { } // Start the application code itself. -quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'restangular', 'angularMoment', 'angulartics', /*'angulartics.google.analytics',*/ 'angulartics.mixpanel', '$strap.directives', 'ngCookies'], function($provide, cfpLoadingBarProvider) { +quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'restangular', 'angularMoment', 'angulartics', /*'angulartics.google.analytics',*/ 'angulartics.mixpanel', '$strap.directives', 'ngCookies', 'ngSanitize'], function($provide, cfpLoadingBarProvider) { cfpLoadingBarProvider.includeSpinner = false; + $provide.factory('UtilService', ['$sanitize', function($sanitize) { + var utilService = {}; + + utilService.textToSafeHtml = function(text) { + var adjusted = text.replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); + + return $sanitize(adjusted); + }; + + return utilService; + }]); + + $provide.factory('ImageMetadataService', ['UtilService', function(UtilService) { + var metadataService = {}; + metadataService.getFormattedCommand = function(image) { + if (!image || !image.command || !image.command.length) { + return ''; + } + + var getCommandStr = function(command) { + // Handle /bin/sh commands specially. + if (command.length > 2 && command[0] == '/bin/sh' && command[1] == '-c') { + return command[2]; + } + + return command.join(' '); + }; + + return getCommandStr(image.command); + }; + + metadataService.getEscapedFormattedCommand = function(image) { + return UtilService.textToSafeHtml(metadataService.getFormattedCommand(image)); + }; + + return metadataService; + }]); + $provide.factory('ApiService', ['Restangular', function(Restangular) { var apiService = {}; diff --git a/static/js/controllers.js b/static/js/controllers.js index 8768e7734..e824565ef 100644 --- a/static/js/controllers.js +++ b/static/js/controllers.js @@ -48,7 +48,7 @@ function GuideCtrl($scope) { function SecurityCtrl($scope) { } -function RepoListCtrl($scope, Restangular, UserService, ApiService) { +function RepoListCtrl($scope, $sanitize, Restangular, UserService, ApiService) { $scope.namespace = null; $scope.page = 1; $scope.publicPageCount = null; @@ -157,7 +157,7 @@ function LandingCtrl($scope, UserService, ApiService) { browserchrome.update(); } -function RepoCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $location, $timeout) { +function RepoCtrl($scope, $sanitize, Restangular, ImageMetadataService, ApiService, $routeParams, $rootScope, $location, $timeout) { var namespace = $routeParams.namespace; var name = $routeParams.name; @@ -192,6 +192,13 @@ function RepoCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $lo // Start scope methods ////////////////////////////////////////// + $scope.getFormattedCommand = ImageMetadataService.getFormattedCommand; + + $scope.getTooltipCommand = function(image) { + var sanitized = ImageMetadataService.getEscapedFormattedCommand(image); + return '' + sanitized + ''; + }; + $scope.updateForDescription = function(content) { $scope.repo.description = content; $scope.repo.put(); @@ -563,7 +570,7 @@ function RepoCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $lo // Create the new tree. $scope.tree = new ImageHistoryTree(namespace, name, resp.images, - getFirstTextLine, $scope.getTimeSince); + getFirstTextLine, $scope.getTimeSince, ImageMetadataService.getEscapedFormattedCommand); $scope.tree.draw('image-history-container'); @@ -983,11 +990,13 @@ function UserAdminCtrl($scope, $timeout, $location, ApiService, PlanService, Use }; } -function ImageViewCtrl($scope, $routeParams, $rootScope, $timeout, ApiService) { +function ImageViewCtrl($scope, $routeParams, $rootScope, $timeout, ApiService, ImageMetadataService) { var namespace = $routeParams.namespace; var name = $routeParams.name; var imageid = $routeParams.image; + $scope.getFormattedCommand = ImageMetadataService.getFormattedCommand; + $scope.parseDate = function(dateString) { return Date.parse(dateString); }; diff --git a/static/js/graphing.js b/static/js/graphing.js index bfbb7a099..396228ab0 100644 --- a/static/js/graphing.js +++ b/static/js/graphing.js @@ -31,7 +31,7 @@ var DEPTH_WIDTH = 132; /** * Based off of http://mbostock.github.io/d3/talk/20111018/tree.html by Mike Bostock (@mbostock) */ -function ImageHistoryTree(namespace, name, images, formatComment, formatTime) { +function ImageHistoryTree(namespace, name, images, formatComment, formatTime, formatCommand) { /** * The namespace of the repo. */ @@ -57,6 +57,11 @@ function ImageHistoryTree(namespace, name, images, formatComment, formatTime) { */ this.formatTime_ = formatTime; + /** + * Method to invoke to format the command for an image. + */ + this.formatCommand_ = formatCommand; + /** * The current tag (if any). */ @@ -187,6 +192,8 @@ ImageHistoryTree.prototype.draw = function(container) { var formatComment = this.formatComment_; var formatTime = this.formatTime_; + var formatCommand = this.formatCommand_; + var tip = d3.tip() .attr('class', 'd3-tip') .offset([-1, 24]) @@ -212,8 +219,10 @@ ImageHistoryTree.prototype.draw = function(container) { if (d.image.comment) { html += '' + formatComment(d.image.comment) + ''; } - html += '' + formatTime(d.image.created) + ''; - html += '' + d.image.id + ''; + if (d.image.command && d.image.command.length) { + html += '' + formatCommand(d.image) + ''; + } + html += '' + formatTime(d.image.created) + ''; return html; }) diff --git a/static/partials/image-view.html b/static/partials/image-view.html index 30ecaf5a7..45c9c7a74 100644 --- a/static/partials/image-view.html +++ b/static/partials/image-view.html @@ -43,6 +43,11 @@ title="The amount of data sent between Docker and Quay.io when pushing/pulling" bs-tooltip="tooltip.title" data-container="body">{{ image.value.size | bytes }} + +
Command
+
+
{{ getFormattedCommand(image.value) }}
+
diff --git a/static/partials/view-repo.html b/static/partials/view-repo.html index 053bd2952..89091258e 100644 --- a/static/partials/view-repo.html +++ b/static/partials/view-repo.html @@ -167,6 +167,12 @@ sudo docker push quay.io/{{repo.namespace}}/{{repo.name}} title="The amount of data sent between Docker and Quay.io when pushing/pulling" bs-tooltip="tooltip.title" data-container="body">{{ currentImage.size | bytes }} +
Command
+
+
{{ getFormattedCommand(currentImage) }}
+
diff --git a/templates/base.html b/templates/base.html index 0f8851206..5729c40bd 100644 --- a/templates/base.html +++ b/templates/base.html @@ -42,6 +42,7 @@ + diff --git a/test/data/test.db b/test/data/test.db index e8d38d652..bc58279da 100644 Binary files a/test/data/test.db and b/test/data/test.db differ