diff --git a/.gitignore b/.gitignore index 2befc02c4..8c5e49720 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.pyc venv .elasticbeanstalk/ -static/snapshots/ \ No newline at end of file +static/snapshots/ +screenshots/screenshots/ diff --git a/initdb.py b/initdb.py index 10408d6fb..3356daacd 100644 --- a/initdb.py +++ b/initdb.py @@ -52,12 +52,16 @@ def create_subtree(repo, structure, parent): create_subtree(repo, subtree, new_image) -def __generate_repository(user, name, is_public, permissions, structure): +def __generate_repository(user, name, description, is_public, permissions, structure): repo = model.create_repository(user.username, name, user) if is_public: model.set_repository_visibility(repo, 'public') + if description: + repo.description = description + repo.save() + for delegate, role in permissions: model.set_user_repo_permission(delegate.username, user.username, name, role) @@ -81,16 +85,18 @@ if __name__ == '__main__': new_user_2.verified = True new_user_2.save() - __generate_repository(new_user_1, 'simple', False, [], (4, [], - ['latest', 'prod'])) + __generate_repository(new_user_1, 'simple', 'Simple repository.', False, + [], (4, [], ['latest', 'prod'])) - __generate_repository(new_user_1, 'complex', False, [], + __generate_repository(new_user_1, 'complex', + 'Complex repository with many branches and tags.', + False, [(new_user_2, 'read')], (2, [(3, [], 'v2.0'), (1, [(1, [(1, [], ['latest', 'prod'])], 'staging'), (1, [], None)], None)], None)) - __generate_repository(new_user_1, 'horrific', False, [], + __generate_repository(new_user_1, 'horrific', None, False, [], (2, [(3, [], 'v2.0'), (1, [(1, [(1, [], ['latest', 'prod'])], 'staging'), @@ -100,8 +106,10 @@ if __name__ == '__main__': (1, [(1, [], 'v5.0'), (1, [], 'v6.0')], None)], None)) - __generate_repository(new_user_2, 'publicrepo', True, [], - (10, [], 'latest')) + __generate_repository(new_user_2, 'publicrepo', + 'Public repository pullable by the world.', True, + [], (10, [], 'latest')) - __generate_repository(new_user_1, 'shared', False, + __generate_repository(new_user_1, 'shared', + 'Shared repository, another user can write.', False, [(new_user_2, 'write')], (5, [], 'latest')) diff --git a/screenshots/README.md b/screenshots/README.md new file mode 100644 index 000000000..2903bdd70 --- /dev/null +++ b/screenshots/README.md @@ -0,0 +1,11 @@ +run with: + +``` +casperjs screenshots.js +``` + +debug run (i.e. hit localhost): + +``` +casperjs screenshots.js --d +``` \ No newline at end of file diff --git a/screenshots/screenshots.js b/screenshots/screenshots.js new file mode 100644 index 000000000..5420388ef --- /dev/null +++ b/screenshots/screenshots.js @@ -0,0 +1,67 @@ +var casper = require('casper').create({ + viewportSize: { + width: 1070, + height: 768 + }, + verbose: true, + logLevel: "debug" +}); + +var options = casper.cli.options; +var isDebug = !!options['d']; + +var rootUrl = isDebug ? 'http://localhost:5001/' : 'https://quay.io/'; +var repo = isDebug ? 'complex' : 'r0'; + +var outputDir = "screenshots/"; + +casper.start(rootUrl); + +casper.on("remote.message", function(msg, trace) { + this.echo("Message: " + msg, "DEBUG"); +}); + +casper.on("page.error", function(msg, trace) { + this.echo("Page error: " + msg, "ERROR"); +}); + +casper.then(function() { + this.capture(outputDir + 'landing.png'); +}); + +casper.thenOpen(rootUrl + 'signin', function () { + this.fill('form', { + 'username': 'devtable', + 'password': isDebug ? 'password': 'C>K98%y"_=54x"<', + }, true); +}); + +casper.then(function() { + this.waitForText('Your Top Repositories'); +}); + +casper.then(function() { + this.capture(outputDir + 'user-home.png'); +}); + +casper.thenOpen(rootUrl + 'repository/devtable/' + repo, function() { + // Wait for the tree to initialize. + this.waitForSelector('.image-tree', function() { + // Wait for the tree's animation to finish. + this.wait(4000); + }); +}); + +casper.then(function() { + this.capture(outputDir + 'repo-view.png'); +}); + +casper.thenOpen(rootUrl + 'repository/devtable/' + repo + '/admin', function() { + this.waitForSelector('.repo-access-state'); +}); + +casper.then(function() { + this.capture(outputDir + 'repo-admin.png'); +}); + +casper.run(); diff --git a/static/css/quay.css b/static/css/quay.css index d8da48707..a5af37606 100644 --- a/static/css/quay.css +++ b/static/css/quay.css @@ -1,3 +1,6 @@ +* { + font-family: 'Droid Sans', sans-serif; +} .plans .callout { font-size: 2em; @@ -723,6 +726,7 @@ p.editable:hover i { #image-history-container { overflow: hidden; + min-height: 400px; } #image-history-container .node circle { diff --git a/static/img/repo-admin.png b/static/img/repo-admin.png new file mode 100644 index 000000000..c33335157 Binary files /dev/null and b/static/img/repo-admin.png differ diff --git a/static/img/repo-view.png b/static/img/repo-view.png new file mode 100644 index 000000000..3fe20240d Binary files /dev/null and b/static/img/repo-view.png differ diff --git a/static/img/user-home.png b/static/img/user-home.png new file mode 100644 index 000000000..507b17e39 Binary files /dev/null and b/static/img/user-home.png differ diff --git a/static/js/controllers.js b/static/js/controllers.js index fcd0824ca..36473ea5c 100644 --- a/static/js/controllers.js +++ b/static/js/controllers.js @@ -199,6 +199,8 @@ function LandingCtrl($scope, $timeout, Restangular, UserService, KeyService) { }; $scope.status = 'ready'; + + browserchrome.update(); } function RepoCtrl($scope, Restangular, $routeParams, $rootScope) { diff --git a/static/js/graphing.js b/static/js/graphing.js index f9fdcd644..f05943ae4 100644 --- a/static/js/graphing.js +++ b/static/js/graphing.js @@ -65,12 +65,7 @@ ImageHistoryTree.prototype.draw = function(container) { var boundingBox = document.getElementById(container).getBoundingClientRect(); document.getElementById(container).style.maxHeight = (viewportHeight - boundingBox.top) + 'px'; - var leftMargin = 20; - if (width > document.getElementById(container).clientWidth) { - leftMargin = 120; - } - - var margin = { top: 40, right: 20, bottom: 20, left: leftMargin }; + var margin = { top: 40, right: 20, bottom: 20, left: 40 }; var m = [margin.top, margin.right, margin.bottom, margin.left]; var w = width - m[1] - m[3]; var h = height - m[0] - m[2]; @@ -86,8 +81,9 @@ ImageHistoryTree.prototype.draw = function(container) { var vis = d3.select("#" + container).append("svg:svg") .attr("width", w + m[1] + m[3]) .attr("height", h + m[0] + m[2]) + .attr("class", "image-tree") .append("svg:g") - .attr("transform", "translate(" + m[3] + "," + m[0] + ")"); + .attr("transform", "translate(" + m[3] + "," + m[0] + ")"); var formatComment = this.formatComment_; var formatTime = this.formatTime_; @@ -97,13 +93,13 @@ ImageHistoryTree.prototype.draw = function(container) { .direction('e') .html(function(d) { var html = ''; - if (d.collapsed) { - for (var i = 1; i < d.encountered.length; ++i) { - html += '' + d.encountered[i].image.id.substr(0, 12) + ''; - html += '' + formatTime(d.encountered[i].image.created) + ''; - } - return html; - } + if (d.collapsed) { + for (var i = 1; i < d.encountered.length; ++i) { + html += '' + d.encountered[i].image.id.substr(0, 12) + ''; + html += '' + formatTime(d.encountered[i].image.created) + ''; + } + return html; + } if (d.image.comment) { html += '' + formatComment(d.image.comment) + ''; @@ -227,9 +223,9 @@ ImageHistoryTree.prototype.buildRoot_ = function() { var immediateParent = ancestors[ancestors.length - 1] * 1; var parent = imageByDBID[immediateParent]; if (parent) { - // Add a reference to the parent. This makes walking the tree later easier. - imageNode.parent = parent; - parent.children.push(imageNode); + // Add a reference to the parent. This makes walking the tree later easier. + imageNode.parent = parent; + parent.children.push(imageNode); } else { formatted = imageNode; } @@ -239,16 +235,16 @@ ImageHistoryTree.prototype.buildRoot_ = function() { // the width of the tree properly. var maxChildCount = 0; for (var i = 0; i < this.images_.length; ++i) { - var image = this.images_[i]; + var image = this.images_[i]; var imageNode = imageByDBID[image.dbid]; - maxChildCount = Math.max(maxChildCount, this.determineMaximumChildCount_(imageNode)); + maxChildCount = Math.max(maxChildCount, this.determineMaximumChildCount_(imageNode)); } // Compact the graph so that any single chain of three (or more) images becomes a collapsed // section. We only do this if the max width is > 1 (since for a single width tree, no long // chain will hide a branch). if (maxChildCount > 1) { - this.collapseNodes_(formatted); + this.collapseNodes_(formatted); } // Determine the maximum height of the tree. @@ -258,8 +254,8 @@ ImageHistoryTree.prototype.buildRoot_ = function() { this.root_ = formatted; return { - 'maxWidth': maxChildCount + 1, - 'maxHeight': maxHeight + 'maxWidth': maxChildCount + 1, + 'maxHeight': maxHeight }; }; @@ -283,35 +279,35 @@ ImageHistoryTree.prototype.determineMaximumHeight_ = function(node) { */ ImageHistoryTree.prototype.collapseNodes_ = function(node) { if (node.children.length == 1) { - // Keep searching downward until we find a node with more than a single child. - var current = node; - var previous = node; - var encountered = []; - while (current.children.length == 1) { - encountered.push(current); - previous = current; - current = current.children[0]; - } + // Keep searching downward until we find a node with more than a single child. + var current = node; + var previous = node; + var encountered = []; + while (current.children.length == 1) { + encountered.push(current); + previous = current; + current = current.children[0]; + } - if (encountered.length >= 3) { - // Collapse the node. - var collapsed = { - "name": '(' + encountered.length + ' images)', - "children": [current], - "collapsed": true, - "encountered": encountered - }; - node.children = [collapsed]; + if (encountered.length >= 3) { + // Collapse the node. + var collapsed = { + "name": '(' + encountered.length + ' images)', + "children": [current], + "collapsed": true, + "encountered": encountered + }; + node.children = [collapsed]; - // Update the parent relationships. - collapsed.parent = node; - current.parent = collapsed; - return; - } + // Update the parent relationships. + collapsed.parent = node; + current.parent = collapsed; + return; + } } for (var i = 0; i < node.children.length; ++i) { - this.collapseNodes_(node.children[i]); + this.collapseNodes_(node.children[i]); } }; @@ -325,7 +321,7 @@ ImageHistoryTree.prototype.determineMaximumChildCount_ = function(node) { var nestedCount = 0; for (var i = 0; i < children.length; ++i) { - nestedCount += children[i].children.length; + nestedCount += children[i].children.length; } return Math.max(myLevelCount, nestedCount); @@ -338,10 +334,10 @@ ImageHistoryTree.prototype.determineMaximumChildCount_ = function(node) { */ ImageHistoryTree.prototype.findImage_ = function(checker) { for (var i = 0; i < this.images_.length; ++i) { - var image = this.images_[i]; - if (checker(image)) { - return image; - } + var image = this.images_[i]; + if (checker(image)) { + return image; + } } return null; @@ -355,8 +351,8 @@ ImageHistoryTree.prototype.markPath_ = function(startingNode, isHighlighted) { var currentNode = startingNode; currentNode.current = isHighlighted; while (currentNode != null) { - currentNode.highlighted = isHighlighted; - currentNode = currentNode.parent; + currentNode.highlighted = isHighlighted; + currentNode = currentNode.parent; } }; @@ -383,25 +379,25 @@ ImageHistoryTree.prototype.setTag_ = function(tagName) { // Ensure that the children are in the correct order. for (var i = 0; i < this.images_.length; ++i) { - var image = this.images_[i]; - var imageNode = this.imageByDBID_[image.dbid]; - var ancestors = this.getAncestors_(image); + var image = this.images_[i]; + var imageNode = this.imageByDBID_[image.dbid]; + var ancestors = this.getAncestors_(image); var immediateParent = ancestors[ancestors.length - 1] * 1; var parent = imageByDBID[immediateParent]; - if (parent && imageNode.highlighted) { - var arr = parent.children; - if (parent._children) { - arr = parent._children; - } + if (parent && imageNode.highlighted) { + var arr = parent.children; + if (parent._children) { + arr = parent._children; + } - if (arr[0] != imageNode) { - var index = arr.indexOf(imageNode); - if (index > 0) { - arr.splice(index, 1); - arr.splice(0, 0, imageNode); - } - } - } + if (arr[0] != imageNode) { + var index = arr.indexOf(imageNode); + if (index > 0) { + arr.splice(index, 1); + arr.splice(0, 0, imageNode); + } + } + } } // Update the tree. @@ -415,11 +411,11 @@ ImageHistoryTree.prototype.setTag_ = function(tagName) { ImageHistoryTree.prototype.setImage_ = function(imageId) { // Find the new current image. var newImage = this.findImage_(function(image) { - return image.id == imageId; + return image.id == imageId; }); if (newImage == this.currentImage_) { - return; + return; } this.currentImage_ = newImage; @@ -509,7 +505,7 @@ ImageHistoryTree.prototype.update_ = function(source) { nodeUpdate.select("circle") .attr("r", 4.5) .attr("class", function(d) { - return (d._children ? "closed " : "open ") + (d.current ? "current " : "") + (d.highlighted ? "highlighted " : ""); + return (d._children ? "closed " : "open ") + (d.current ? "current " : "") + (d.highlighted ? "highlighted " : ""); }) .style("fill", function(d) { if (d.current) { @@ -521,10 +517,10 @@ ImageHistoryTree.prototype.update_ = function(source) { // Update the repo text. nodeUpdate.select("text") .attr("class", function(d) { - if (d.collapsed) { - return 'collapsed'; - } - return d.image.id == that.currentImage_.id ? 'current' : ''; + if (d.collapsed) { + return 'collapsed'; + } + return d.image.id == that.currentImage_.id ? 'current' : ''; }); // Ensure that the node is visible. @@ -534,9 +530,9 @@ ImageHistoryTree.prototype.update_ = function(source) { // Update the tags. node.select(".tags") .html(function(d) { - if (!d.tags) { - return ''; - } + if (!d.tags) { + return ''; + } var html = ''; for (var i = 0; i < d.tags.length; ++i) { @@ -553,10 +549,10 @@ ImageHistoryTree.prototype.update_ = function(source) { // Listen for click events on the labels. node.selectAll(".tag") .on("click", function(d, e) { - var tag = this.getAttribute('data-tag'); - if (tag) { - that.changeTag_(tag); - } + var tag = this.getAttribute('data-tag'); + if (tag) { + that.changeTag_(tag); + } }); // Ensure the tags are visible. @@ -567,7 +563,7 @@ ImageHistoryTree.prototype.update_ = function(source) { // we force a redraw by adjusting the height of the object ever so slightly. nodeUpdate.select(".fo") .attr('height', function(d) { - return DEPTH_HEIGHT - 20 + Math.random() / 10; + return DEPTH_HEIGHT - 20 + Math.random() / 10; }); // Transition exiting nodes to the parent's new position. diff --git a/static/lib/browser-chrome.css b/static/lib/browser-chrome.css new file mode 100644 index 000000000..4f0189d9d --- /dev/null +++ b/static/lib/browser-chrome.css @@ -0,0 +1,183 @@ +.browser-chrome-container * { + box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; +} + +.browser-chrome-container { + border: 2px solid #363532; + display: inline-block; + border-radius: 2px; + + max-width: 100%; + height: auto; +} + +.browser-chrome-header { + height: 40px; + padding: 2px; + overflow: hidden; + + background-color: rgb(54,53,50); + + background-image: linear-gradient(bottom, rgb(54,53,50) 0%, rgb(86,85,81) 100%); + background-image: -o-linear-gradient(bottom, rgb(54,53,50) 0%, rgb(86,85,81) 100%); + background-image: -moz-linear-gradient(bottom, rgb(54,53,50) 0%, rgb(86,85,81) 100%); + background-image: -webkit-linear-gradient(bottom, rgb(54,53,50) 0%, rgb(86,85,81) 100%); + background-image: -ms-linear-gradient(bottom, rgb(54,53,50) 0%, rgb(86,85,81) 100%); + + background-image: -webkit-gradient( + linear, + left bottom, + left top, + color-stop(0, rgb(54,53,50)), + color-stop(1, rgb(86,85,81)) + ); +} + +.browser-chrome-header .user-icon-container { + float: right; + margin-right: 5px; + margin-top: 10px; +} + +.browser-chrome-header .user-icon-container i { + color: #78bcf3; +} + +.browser-chrome-header i { + color: white; +} + +.browser-chrome-tab { + display: inline-block; + position: relative; + width: 200px; + height: 25px; + bottom: -13px; + left: 10px; + background-color: #f2f1f0; + vertical-align: middle; + + border-top-left-radius: 5px; + border-top-right-radius: 5px; +} + +.browser-chrome-tab-wrapper:before, +.browser-chrome-tab-wrapper:after { + content: ""; + position: absolute; + background-color: #f2f1f0; + width: 10px; + height: 10px; + bottom: 0px; +} + +.browser-chrome-tab-wrapper:before { + left: -10px; + +} + +.browser-chrome-tab-wrapper:after { + right: -10px; +} + +.browser-chrome-tab:before, +.browser-chrome-tab:after { + content: ""; + position: absolute; + width: 20px; + height: 20px; + border-radius: 10px; + bottom: 0px; + + background-color: rgb(54,53,50); + + background-image: linear-gradient(bottom, rgb(54,53,50) 0%, rgb(70,69,65) 100%); + background-image: -o-linear-gradient(bottom, rgb(54,53,50) 0%, rgb(70,69,65) 100%); + background-image: -moz-linear-gradient(bottom, rgb(54,53,50) 0%, rgb(70,69,65) 100%); + background-image: -webkit-linear-gradient(bottom, rgb(54,53,50) 0%, rgb(70,69,65) 100%); + background-image: -ms-linear-gradient(bottom, rgb(54,53,50) 0%, rgb(70,69,65) 100%); + + background-image: -webkit-gradient( + linear, + left bottom, + left top, + color-stop(0, rgb(54,53,50)), + color-stop(1, rgb(70,69,65)) + ); + + z-index: 1; +} + +.browser-chrome-tab:before { + left: -20px; +} + +.browser-chrome-tab:after { + right: -20px; +} + +.browser-chrome-tab-content { + overflow: hidden; + text-overflow: ellipsis; + font-size: 14px; + padding: 4px; + white-space: nowrap; +} + +.browser-chrome-tab-content i { + color: #363532; +} + +.browser-chrome-url-bar { + height: 35px; + background-color: #f2f1f0; + padding: 5px; +} + +.browser-chrome-url-bar i { + color: #363532; + margin-right: 5px; +} + +.browser-chrome-url-bar .left-controls { + width: 78px; + float: left; + padding-top: 2px; +} + +.browser-chrome-url-bar .right-controls { + width: 26px; + float: right; + padding-top: 2px; +} + +.browser-chrome-url-bar .right-controls i { + margin-left: 5px; + margin-right: 0; +} + +.browser-chrome-url { + color: black; + background-color: white; + border-radius: 2px; + border: 1px solid #ada9a5; + padding: 2px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.browser-chrome-url .protocol-https { + color: #079500; +} + +.browser-chrome-url .protocol-https i { + color: #079500; +} + +.browser-chrome-url i { + margin-right: 5px; + margin-left: 5px; +} diff --git a/static/lib/browser-chrome.js b/static/lib/browser-chrome.js new file mode 100644 index 000000000..208668a08 --- /dev/null +++ b/static/lib/browser-chrome.js @@ -0,0 +1,39 @@ +(function(browserchrome, $) { + var htmlTemplate = '
Tab Title
http://google.com/
' + + browserchrome.update = function() { + $('[data-screenshot-url]').each(function(index, element) { + var elem = $(element); + if (!elem.data('has-chrome')) { + // Create chrome + var createdHtml = $(htmlTemplate); + + // Add the new chrome to the page where the image was + elem.replaceWith(createdHtml); + + // Add the image to the new browser chrome html + createdHtml.append(elem); + + // Set the tab title + var tabTitle = elem.attr('title') || elem.data('tab-title'); + createdHtml.find('.tab-title').text(tabTitle); + + // Pick the protocol and set the url + var url = elem.data('screenshot-url'); + if (url.substring(0, 6) === 'https:') { + createdHtml.find('.protocol-http').hide(); + createdHtml.find('.protocol-https').show(); + url = url.substring(5); + } else { + createdHtml.find('.protocol-http').hide(); + createdHtml.find('.protocol-https').show(); + url = url.substring(4); + } + console.log('setting url to: ' + url); + createdHtml.find('.url-text').text(url); + + elem.data('has-chrome', 'true'); + } + }); + }; +}(window.browserchrome = window.browserchrome || {}, window.jQuery)); \ No newline at end of file diff --git a/static/partials/landing.html b/static/partials/landing.html index 2baf6baec..f95e2c331 100644 --- a/static/partials/landing.html +++ b/static/partials/landing.html @@ -102,7 +102,7 @@
-
+
Customized for you
@@ -113,7 +113,7 @@
-
+
Useful views of respositories
@@ -123,7 +123,7 @@
-
+
Share at your control
diff --git a/static/partials/view-repo.html b/static/partials/view-repo.html index aaadbb067..0aad8afaf 100644 --- a/static/partials/view-repo.html +++ b/static/partials/view-repo.html @@ -62,64 +62,64 @@
- -
+ +
-
-
- - - {{getTagCount(repo)}} - {{currentTag.name}} - - - Tags - -
- - -
-
-
+
+
+ + + {{getTagCount(repo)}} + {{currentTag.name}} + + + Tags + +
- -
-
-
- - - {{imageHistory.length}} - {{currentImage.id.substr(0, 12)}} - - - Image + +
+
+
-
- -
-
-
-
Created
-
-
+ +
+
+
+ + + {{imageHistory.length}} + {{currentImage.id.substr(0, 12)}} + + + Image -
- Description: -
-
-
-
-
-
-
+
+ +
+
+
+
Created
+
+
+ +
+ Description: +
+
+
+
+
+
+
diff --git a/templates/index.html b/templates/index.html index 226b24f68..e4df0cc43 100644 --- a/templates/index.html +++ b/templates/index.html @@ -12,6 +12,8 @@ + + @@ -39,6 +41,7 @@ + diff --git a/test.db b/test.db index 7872fe43e..a2213393b 100644 Binary files a/test.db and b/test.db differ