Merge branch 'casper'
Conflicts: static/partials/view-repo.html
This commit is contained in:
commit
0d2564e127
16 changed files with 464 additions and 150 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,4 +1,5 @@
|
||||||
*.pyc
|
*.pyc
|
||||||
venv
|
venv
|
||||||
.elasticbeanstalk/
|
.elasticbeanstalk/
|
||||||
static/snapshots/
|
static/snapshots/
|
||||||
|
screenshots/screenshots/
|
||||||
|
|
24
initdb.py
24
initdb.py
|
@ -52,12 +52,16 @@ def create_subtree(repo, structure, parent):
|
||||||
create_subtree(repo, subtree, new_image)
|
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)
|
repo = model.create_repository(user.username, name, user)
|
||||||
|
|
||||||
if is_public:
|
if is_public:
|
||||||
model.set_repository_visibility(repo, 'public')
|
model.set_repository_visibility(repo, 'public')
|
||||||
|
|
||||||
|
if description:
|
||||||
|
repo.description = description
|
||||||
|
repo.save()
|
||||||
|
|
||||||
for delegate, role in permissions:
|
for delegate, role in permissions:
|
||||||
model.set_user_repo_permission(delegate.username, user.username, name,
|
model.set_user_repo_permission(delegate.username, user.username, name,
|
||||||
role)
|
role)
|
||||||
|
@ -81,16 +85,18 @@ if __name__ == '__main__':
|
||||||
new_user_2.verified = True
|
new_user_2.verified = True
|
||||||
new_user_2.save()
|
new_user_2.save()
|
||||||
|
|
||||||
__generate_repository(new_user_1, 'simple', False, [], (4, [],
|
__generate_repository(new_user_1, 'simple', 'Simple repository.', False,
|
||||||
['latest', 'prod']))
|
[], (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'),
|
(2, [(3, [], 'v2.0'),
|
||||||
(1, [(1, [(1, [], ['latest', 'prod'])],
|
(1, [(1, [(1, [], ['latest', 'prod'])],
|
||||||
'staging'),
|
'staging'),
|
||||||
(1, [], None)], None)], None))
|
(1, [], None)], None)], None))
|
||||||
|
|
||||||
__generate_repository(new_user_1, 'horrific', False, [],
|
__generate_repository(new_user_1, 'horrific', None, False, [],
|
||||||
(2, [(3, [], 'v2.0'),
|
(2, [(3, [], 'v2.0'),
|
||||||
(1, [(1, [(1, [], ['latest', 'prod'])],
|
(1, [(1, [(1, [], ['latest', 'prod'])],
|
||||||
'staging'),
|
'staging'),
|
||||||
|
@ -100,8 +106,10 @@ if __name__ == '__main__':
|
||||||
(1, [(1, [], 'v5.0'), (1, [], 'v6.0')], None)],
|
(1, [(1, [], 'v5.0'), (1, [], 'v6.0')], None)],
|
||||||
None))
|
None))
|
||||||
|
|
||||||
__generate_repository(new_user_2, 'publicrepo', True, [],
|
__generate_repository(new_user_2, 'publicrepo',
|
||||||
(10, [], 'latest'))
|
'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'))
|
[(new_user_2, 'write')], (5, [], 'latest'))
|
||||||
|
|
11
screenshots/README.md
Normal file
11
screenshots/README.md
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
run with:
|
||||||
|
|
||||||
|
```
|
||||||
|
casperjs screenshots.js
|
||||||
|
```
|
||||||
|
|
||||||
|
debug run (i.e. hit localhost):
|
||||||
|
|
||||||
|
```
|
||||||
|
casperjs screenshots.js --d
|
||||||
|
```
|
67
screenshots/screenshots.js
Normal file
67
screenshots/screenshots.js
Normal file
|
@ -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();
|
|
@ -1,3 +1,6 @@
|
||||||
|
* {
|
||||||
|
font-family: 'Droid Sans', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
.plans .callout {
|
.plans .callout {
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
|
@ -723,6 +726,7 @@ p.editable:hover i {
|
||||||
|
|
||||||
#image-history-container {
|
#image-history-container {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
min-height: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#image-history-container .node circle {
|
#image-history-container .node circle {
|
||||||
|
|
BIN
static/img/repo-admin.png
Normal file
BIN
static/img/repo-admin.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 56 KiB |
BIN
static/img/repo-view.png
Normal file
BIN
static/img/repo-view.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 67 KiB |
BIN
static/img/user-home.png
Normal file
BIN
static/img/user-home.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 186 KiB |
|
@ -199,6 +199,8 @@ function LandingCtrl($scope, $timeout, Restangular, UserService, KeyService) {
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.status = 'ready';
|
$scope.status = 'ready';
|
||||||
|
|
||||||
|
browserchrome.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
function RepoCtrl($scope, Restangular, $routeParams, $rootScope) {
|
function RepoCtrl($scope, Restangular, $routeParams, $rootScope) {
|
||||||
|
|
|
@ -65,12 +65,7 @@ ImageHistoryTree.prototype.draw = function(container) {
|
||||||
var boundingBox = document.getElementById(container).getBoundingClientRect();
|
var boundingBox = document.getElementById(container).getBoundingClientRect();
|
||||||
document.getElementById(container).style.maxHeight = (viewportHeight - boundingBox.top) + 'px';
|
document.getElementById(container).style.maxHeight = (viewportHeight - boundingBox.top) + 'px';
|
||||||
|
|
||||||
var leftMargin = 20;
|
var margin = { top: 40, right: 20, bottom: 20, left: 40 };
|
||||||
if (width > document.getElementById(container).clientWidth) {
|
|
||||||
leftMargin = 120;
|
|
||||||
}
|
|
||||||
|
|
||||||
var margin = { top: 40, right: 20, bottom: 20, left: leftMargin };
|
|
||||||
var m = [margin.top, margin.right, margin.bottom, margin.left];
|
var m = [margin.top, margin.right, margin.bottom, margin.left];
|
||||||
var w = width - m[1] - m[3];
|
var w = width - m[1] - m[3];
|
||||||
var h = height - m[0] - m[2];
|
var h = height - m[0] - m[2];
|
||||||
|
@ -86,8 +81,9 @@ ImageHistoryTree.prototype.draw = function(container) {
|
||||||
var vis = d3.select("#" + container).append("svg:svg")
|
var vis = d3.select("#" + container).append("svg:svg")
|
||||||
.attr("width", w + m[1] + m[3])
|
.attr("width", w + m[1] + m[3])
|
||||||
.attr("height", h + m[0] + m[2])
|
.attr("height", h + m[0] + m[2])
|
||||||
|
.attr("class", "image-tree")
|
||||||
.append("svg:g")
|
.append("svg:g")
|
||||||
.attr("transform", "translate(" + m[3] + "," + m[0] + ")");
|
.attr("transform", "translate(" + m[3] + "," + m[0] + ")");
|
||||||
|
|
||||||
var formatComment = this.formatComment_;
|
var formatComment = this.formatComment_;
|
||||||
var formatTime = this.formatTime_;
|
var formatTime = this.formatTime_;
|
||||||
|
@ -97,13 +93,13 @@ ImageHistoryTree.prototype.draw = function(container) {
|
||||||
.direction('e')
|
.direction('e')
|
||||||
.html(function(d) {
|
.html(function(d) {
|
||||||
var html = '';
|
var html = '';
|
||||||
if (d.collapsed) {
|
if (d.collapsed) {
|
||||||
for (var i = 1; i < d.encountered.length; ++i) {
|
for (var i = 1; i < d.encountered.length; ++i) {
|
||||||
html += '<span>' + d.encountered[i].image.id.substr(0, 12) + '</span>';
|
html += '<span>' + d.encountered[i].image.id.substr(0, 12) + '</span>';
|
||||||
html += '<span class="created">' + formatTime(d.encountered[i].image.created) + '</span>';
|
html += '<span class="created">' + formatTime(d.encountered[i].image.created) + '</span>';
|
||||||
}
|
}
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (d.image.comment) {
|
if (d.image.comment) {
|
||||||
html += '<span class="comment">' + formatComment(d.image.comment) + '</span>';
|
html += '<span class="comment">' + formatComment(d.image.comment) + '</span>';
|
||||||
|
@ -227,9 +223,9 @@ ImageHistoryTree.prototype.buildRoot_ = function() {
|
||||||
var immediateParent = ancestors[ancestors.length - 1] * 1;
|
var immediateParent = ancestors[ancestors.length - 1] * 1;
|
||||||
var parent = imageByDBID[immediateParent];
|
var parent = imageByDBID[immediateParent];
|
||||||
if (parent) {
|
if (parent) {
|
||||||
// Add a reference to the parent. This makes walking the tree later easier.
|
// Add a reference to the parent. This makes walking the tree later easier.
|
||||||
imageNode.parent = parent;
|
imageNode.parent = parent;
|
||||||
parent.children.push(imageNode);
|
parent.children.push(imageNode);
|
||||||
} else {
|
} else {
|
||||||
formatted = imageNode;
|
formatted = imageNode;
|
||||||
}
|
}
|
||||||
|
@ -239,16 +235,16 @@ ImageHistoryTree.prototype.buildRoot_ = function() {
|
||||||
// the width of the tree properly.
|
// the width of the tree properly.
|
||||||
var maxChildCount = 0;
|
var maxChildCount = 0;
|
||||||
for (var i = 0; i < this.images_.length; ++i) {
|
for (var i = 0; i < this.images_.length; ++i) {
|
||||||
var image = this.images_[i];
|
var image = this.images_[i];
|
||||||
var imageNode = imageByDBID[image.dbid];
|
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
|
// 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
|
// section. We only do this if the max width is > 1 (since for a single width tree, no long
|
||||||
// chain will hide a branch).
|
// chain will hide a branch).
|
||||||
if (maxChildCount > 1) {
|
if (maxChildCount > 1) {
|
||||||
this.collapseNodes_(formatted);
|
this.collapseNodes_(formatted);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the maximum height of the tree.
|
// Determine the maximum height of the tree.
|
||||||
|
@ -258,8 +254,8 @@ ImageHistoryTree.prototype.buildRoot_ = function() {
|
||||||
this.root_ = formatted;
|
this.root_ = formatted;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'maxWidth': maxChildCount + 1,
|
'maxWidth': maxChildCount + 1,
|
||||||
'maxHeight': maxHeight
|
'maxHeight': maxHeight
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -283,35 +279,35 @@ ImageHistoryTree.prototype.determineMaximumHeight_ = function(node) {
|
||||||
*/
|
*/
|
||||||
ImageHistoryTree.prototype.collapseNodes_ = function(node) {
|
ImageHistoryTree.prototype.collapseNodes_ = function(node) {
|
||||||
if (node.children.length == 1) {
|
if (node.children.length == 1) {
|
||||||
// Keep searching downward until we find a node with more than a single child.
|
// Keep searching downward until we find a node with more than a single child.
|
||||||
var current = node;
|
var current = node;
|
||||||
var previous = node;
|
var previous = node;
|
||||||
var encountered = [];
|
var encountered = [];
|
||||||
while (current.children.length == 1) {
|
while (current.children.length == 1) {
|
||||||
encountered.push(current);
|
encountered.push(current);
|
||||||
previous = current;
|
previous = current;
|
||||||
current = current.children[0];
|
current = current.children[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (encountered.length >= 3) {
|
if (encountered.length >= 3) {
|
||||||
// Collapse the node.
|
// Collapse the node.
|
||||||
var collapsed = {
|
var collapsed = {
|
||||||
"name": '(' + encountered.length + ' images)',
|
"name": '(' + encountered.length + ' images)',
|
||||||
"children": [current],
|
"children": [current],
|
||||||
"collapsed": true,
|
"collapsed": true,
|
||||||
"encountered": encountered
|
"encountered": encountered
|
||||||
};
|
};
|
||||||
node.children = [collapsed];
|
node.children = [collapsed];
|
||||||
|
|
||||||
// Update the parent relationships.
|
// Update the parent relationships.
|
||||||
collapsed.parent = node;
|
collapsed.parent = node;
|
||||||
current.parent = collapsed;
|
current.parent = collapsed;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < node.children.length; ++i) {
|
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;
|
var nestedCount = 0;
|
||||||
|
|
||||||
for (var i = 0; i < children.length; ++i) {
|
for (var i = 0; i < children.length; ++i) {
|
||||||
nestedCount += children[i].children.length;
|
nestedCount += children[i].children.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Math.max(myLevelCount, nestedCount);
|
return Math.max(myLevelCount, nestedCount);
|
||||||
|
@ -338,10 +334,10 @@ ImageHistoryTree.prototype.determineMaximumChildCount_ = function(node) {
|
||||||
*/
|
*/
|
||||||
ImageHistoryTree.prototype.findImage_ = function(checker) {
|
ImageHistoryTree.prototype.findImage_ = function(checker) {
|
||||||
for (var i = 0; i < this.images_.length; ++i) {
|
for (var i = 0; i < this.images_.length; ++i) {
|
||||||
var image = this.images_[i];
|
var image = this.images_[i];
|
||||||
if (checker(image)) {
|
if (checker(image)) {
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -355,8 +351,8 @@ ImageHistoryTree.prototype.markPath_ = function(startingNode, isHighlighted) {
|
||||||
var currentNode = startingNode;
|
var currentNode = startingNode;
|
||||||
currentNode.current = isHighlighted;
|
currentNode.current = isHighlighted;
|
||||||
while (currentNode != null) {
|
while (currentNode != null) {
|
||||||
currentNode.highlighted = isHighlighted;
|
currentNode.highlighted = isHighlighted;
|
||||||
currentNode = currentNode.parent;
|
currentNode = currentNode.parent;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -383,25 +379,25 @@ ImageHistoryTree.prototype.setTag_ = function(tagName) {
|
||||||
|
|
||||||
// Ensure that the children are in the correct order.
|
// Ensure that the children are in the correct order.
|
||||||
for (var i = 0; i < this.images_.length; ++i) {
|
for (var i = 0; i < this.images_.length; ++i) {
|
||||||
var image = this.images_[i];
|
var image = this.images_[i];
|
||||||
var imageNode = this.imageByDBID_[image.dbid];
|
var imageNode = this.imageByDBID_[image.dbid];
|
||||||
var ancestors = this.getAncestors_(image);
|
var ancestors = this.getAncestors_(image);
|
||||||
var immediateParent = ancestors[ancestors.length - 1] * 1;
|
var immediateParent = ancestors[ancestors.length - 1] * 1;
|
||||||
var parent = imageByDBID[immediateParent];
|
var parent = imageByDBID[immediateParent];
|
||||||
if (parent && imageNode.highlighted) {
|
if (parent && imageNode.highlighted) {
|
||||||
var arr = parent.children;
|
var arr = parent.children;
|
||||||
if (parent._children) {
|
if (parent._children) {
|
||||||
arr = parent._children;
|
arr = parent._children;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arr[0] != imageNode) {
|
if (arr[0] != imageNode) {
|
||||||
var index = arr.indexOf(imageNode);
|
var index = arr.indexOf(imageNode);
|
||||||
if (index > 0) {
|
if (index > 0) {
|
||||||
arr.splice(index, 1);
|
arr.splice(index, 1);
|
||||||
arr.splice(0, 0, imageNode);
|
arr.splice(0, 0, imageNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the tree.
|
// Update the tree.
|
||||||
|
@ -415,11 +411,11 @@ ImageHistoryTree.prototype.setTag_ = function(tagName) {
|
||||||
ImageHistoryTree.prototype.setImage_ = function(imageId) {
|
ImageHistoryTree.prototype.setImage_ = function(imageId) {
|
||||||
// Find the new current image.
|
// Find the new current image.
|
||||||
var newImage = this.findImage_(function(image) {
|
var newImage = this.findImage_(function(image) {
|
||||||
return image.id == imageId;
|
return image.id == imageId;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (newImage == this.currentImage_) {
|
if (newImage == this.currentImage_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentImage_ = newImage;
|
this.currentImage_ = newImage;
|
||||||
|
@ -509,7 +505,7 @@ ImageHistoryTree.prototype.update_ = function(source) {
|
||||||
nodeUpdate.select("circle")
|
nodeUpdate.select("circle")
|
||||||
.attr("r", 4.5)
|
.attr("r", 4.5)
|
||||||
.attr("class", function(d) {
|
.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) {
|
.style("fill", function(d) {
|
||||||
if (d.current) {
|
if (d.current) {
|
||||||
|
@ -521,10 +517,10 @@ ImageHistoryTree.prototype.update_ = function(source) {
|
||||||
// Update the repo text.
|
// Update the repo text.
|
||||||
nodeUpdate.select("text")
|
nodeUpdate.select("text")
|
||||||
.attr("class", function(d) {
|
.attr("class", function(d) {
|
||||||
if (d.collapsed) {
|
if (d.collapsed) {
|
||||||
return 'collapsed';
|
return 'collapsed';
|
||||||
}
|
}
|
||||||
return d.image.id == that.currentImage_.id ? 'current' : '';
|
return d.image.id == that.currentImage_.id ? 'current' : '';
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ensure that the node is visible.
|
// Ensure that the node is visible.
|
||||||
|
@ -534,9 +530,9 @@ ImageHistoryTree.prototype.update_ = function(source) {
|
||||||
// Update the tags.
|
// Update the tags.
|
||||||
node.select(".tags")
|
node.select(".tags")
|
||||||
.html(function(d) {
|
.html(function(d) {
|
||||||
if (!d.tags) {
|
if (!d.tags) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
var html = '';
|
var html = '';
|
||||||
for (var i = 0; i < d.tags.length; ++i) {
|
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.
|
// Listen for click events on the labels.
|
||||||
node.selectAll(".tag")
|
node.selectAll(".tag")
|
||||||
.on("click", function(d, e) {
|
.on("click", function(d, e) {
|
||||||
var tag = this.getAttribute('data-tag');
|
var tag = this.getAttribute('data-tag');
|
||||||
if (tag) {
|
if (tag) {
|
||||||
that.changeTag_(tag);
|
that.changeTag_(tag);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ensure the tags are visible.
|
// 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.
|
// we force a redraw by adjusting the height of the object ever so slightly.
|
||||||
nodeUpdate.select(".fo")
|
nodeUpdate.select(".fo")
|
||||||
.attr('height', function(d) {
|
.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.
|
// Transition exiting nodes to the parent's new position.
|
||||||
|
|
183
static/lib/browser-chrome.css
Normal file
183
static/lib/browser-chrome.css
Normal file
|
@ -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;
|
||||||
|
}
|
39
static/lib/browser-chrome.js
Normal file
39
static/lib/browser-chrome.js
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
(function(browserchrome, $) {
|
||||||
|
var htmlTemplate = '<div class="browser-chrome-container"><div class="browser-chrome-header"><i class="icon-remove-sign"></i> <i class="icon-minus-sign"></i> <i class="icon-plus-sign"></i><div class="browser-chrome-tab"><div class="browser-chrome-tab-wrapper"><div class="browser-chrome-tab-content"><i class="icon-file-alt icon-large"></i> <span class="tab-title">Tab Title</span></div></div></div><div class="user-icon-container"><i class="icon-user icon-2x"></i></div></div><div class="browser-chrome-url-bar"><div class="left-controls"><i class="icon-arrow-left icon-large"></i> <i class="icon-arrow-right icon-large"></i> <i class="icon-rotate-right icon-large"></i> </div><div class="right-controls"> <i class="icon-reorder icon-large"></i></div><div class="browser-chrome-url"><span class="protocol-https" style="display: none"><i class="icon-lock"></i>https</span><span class="protocol-http"><i class="icon-file-alt"></i>http</span><span class="url-text">://google.com/</span></div></div></div>'
|
||||||
|
|
||||||
|
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));
|
|
@ -102,7 +102,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tour-section row">
|
<div class="tour-section row">
|
||||||
<div class="col-md-7"><img src="//1.bp.blogspot.com/-HieEyF9wvVQ/Uk3epFZc6QI/AAAAAAAAADU/-Ov_3v2JkHQ/s640/Screenshot+from+2013-10-03+17%253A12%253A21.png" class="img-responsive"></div>
|
<div class="col-md-7"><img src="/static/img/user-home.png" title="User Home - Quay" data-screenshot-url="https://quay.io/" class="img-responsive"></div>
|
||||||
<div class="col-md-5">
|
<div class="col-md-5">
|
||||||
<div class="tour-section-title">Customized for you</div>
|
<div class="tour-section-title">Customized for you</div>
|
||||||
<div class="tour-section-description">
|
<div class="tour-section-description">
|
||||||
|
@ -113,7 +113,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tour-section row">
|
<div class="tour-section row">
|
||||||
<div class="col-md-7 col-md-push-5"><img src="//4.bp.blogspot.com/-0uJZWmoTuaU/Uk3eo8BHAjI/AAAAAAAAADM/PhY-ZNNj0tw/s640/Screenshot+from+2013-10-03+17%253A12%253A41.png" class="img-responsive"></div>
|
<div class="col-md-7 col-md-push-5"><img src="/static/img/repo-view.png" title="Repository View - Quay" data-screenshot-url="https://quay.io/repository/devtable/complex" class="img-responsive"></div>
|
||||||
<div class="col-md-5 col-md-pull-7">
|
<div class="col-md-5 col-md-pull-7">
|
||||||
<div class="tour-section-title">Useful views of respositories</div>
|
<div class="tour-section-title">Useful views of respositories</div>
|
||||||
<div class="tour-section-description">
|
<div class="tour-section-description">
|
||||||
|
@ -123,7 +123,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tour-section row">
|
<div class="tour-section row">
|
||||||
<div class="col-md-7"><img src="//4.bp.blogspot.com/-aF-RMMxhKmw/Uk3eoys3wsI/AAAAAAAAADc/1uxE8BJ-QHY/s640/Screenshot+from+2013-10-03+17%253A13%253A07.png" class="img-responsive"></div>
|
<div class="col-md-7"><img src="/static/img/repo-admin.png" title="Repository Admin - Quay" data-screenshot-url="https://quay.io/repository/devtable/complex/admin" class="img-responsive"></div>
|
||||||
<div class="col-md-5">
|
<div class="col-md-5">
|
||||||
<div class="tour-section-title">Share at your control</div>
|
<div class="tour-section-title">Share at your control</div>
|
||||||
<div class="tour-section-description">
|
<div class="tour-section-description">
|
||||||
|
|
|
@ -62,64 +62,64 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<!-- Tree View container -->
|
<!-- Tree View container -->
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
|
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<!-- Tag dropdown -->
|
<!-- Tag dropdown -->
|
||||||
<span class="tag-dropdown dropdown" title="Tags">
|
<span class="tag-dropdown dropdown" title="Tags">
|
||||||
<i class="icon-tag"><span class="tag-count">{{getTagCount(repo)}}</span></i>
|
<i class="icon-tag"><span class="tag-count">{{getTagCount(repo)}}</span></i>
|
||||||
<a href="javascript:void(0)" class="dropdown-toggle" data-toggle="dropdown">{{currentTag.name}} <b class="caret"></b></a>
|
<a href="javascript:void(0)" class="dropdown-toggle" data-toggle="dropdown">{{currentTag.name}} <b class="caret"></b></a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li ng-repeat="tag in repo.tags">
|
<li ng-repeat="tag in repo.tags">
|
||||||
<a href="javascript:void(0)" ng-click="setTag(tag.name)">{{tag.name}}</a>
|
<a href="javascript:void(0)" ng-click="setTag(tag.name)">{{tag.name}}</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
<span class="right-title">Tags</span>
|
<span class="right-title">Tags</span>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tree View itself -->
|
|
||||||
<div id="image-history-container"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Side Panel -->
|
<!-- Tree View itself -->
|
||||||
<div class="col-md-4">
|
<div id="image-history-container"></div>
|
||||||
<div class="panel panel-default">
|
</div>
|
||||||
<div class="panel-heading">
|
</div>
|
||||||
<!-- Image dropdown -->
|
|
||||||
<span class="tag-dropdown dropdown" title="Images">
|
|
||||||
<i class="icon-archive"><span class="tag-count">{{imageHistory.length}}</span></i>
|
|
||||||
<a href="javascript:void(0)" class="dropdown-toggle" data-toggle="dropdown">{{currentImage.id.substr(0, 12)}} <b class="caret"></b></a>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
<li ng-repeat="image in imageHistory">
|
|
||||||
<a href="javascript:void(0)" ng-click="setImage(image)">{{image.id.substr(0, 12)}}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</span>
|
|
||||||
<span class="right-title">Image</span>
|
|
||||||
|
|
||||||
</div>
|
<!-- Side Panel -->
|
||||||
|
<div class="col-md-4">
|
||||||
<div class="panel-body">
|
<div class="panel panel-default">
|
||||||
<div id="current-image">
|
<div class="panel-heading">
|
||||||
<dl class="dl-horizontal">
|
<!-- Image dropdown -->
|
||||||
<dt>Created</dt>
|
<span class="tag-dropdown dropdown" title="Images">
|
||||||
<dd am-time-ago="parseDate(currentTag.image.created)"></dd>
|
<i class="icon-archive"><span class="tag-count">{{imageHistory.length}}</span></i>
|
||||||
</dl>
|
<a href="javascript:void(0)" class="dropdown-toggle" data-toggle="dropdown">{{currentImage.id.substr(0, 12)}} <b class="caret"></b></a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li ng-repeat="image in imageHistory">
|
||||||
|
<a href="javascript:void(0)" ng-click="setImage(image)">{{image.id.substr(0, 12)}}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</span>
|
||||||
|
<span class="right-title">Image</span>
|
||||||
|
|
||||||
<div ng-show="currentTag.image.comment">
|
</div>
|
||||||
<strong>Description:</strong>
|
|
||||||
<blockquote style="margin-top: 10px;" ng-bind-html-unsafe="getMarkedDown(currentTag.image.comment)">
|
<div class="panel-body">
|
||||||
</blockquote>
|
<div id="current-image">
|
||||||
</div>
|
<dl class="dl-horizontal">
|
||||||
</div>
|
<dt>Created</dt>
|
||||||
</div>
|
<dd am-time-ago="parseDate(currentTag.image.created)"></dd>
|
||||||
</div>
|
</dl>
|
||||||
</div>
|
|
||||||
|
<div ng-show="currentTag.image.comment">
|
||||||
|
<strong>Description:</strong>
|
||||||
|
<blockquote style="margin-top: 10px;" ng-bind-html-unsafe="getMarkedDown(currentTag.image.comment)">
|
||||||
|
</blockquote>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome.min.css">
|
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome.min.css">
|
||||||
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.no-icons.min.css">
|
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.no-icons.min.css">
|
||||||
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-theme.min.css">
|
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-theme.min.css">
|
||||||
|
<link href='http://fonts.googleapis.com/css?family=Droid+Sans:400,700' rel='stylesheet' type='text/css'>
|
||||||
|
<link rel="stylesheet" href="/static/lib/browser-chrome.css">
|
||||||
|
|
||||||
<link rel="stylesheet" href="/static/css/quay.css">
|
<link rel="stylesheet" href="/static/css/quay.css">
|
||||||
|
|
||||||
|
@ -39,6 +41,7 @@
|
||||||
<script src="static/lib/typeahead.min.js"></script>
|
<script src="static/lib/typeahead.min.js"></script>
|
||||||
|
|
||||||
<script src="static/lib/d3-tip.js" charset="utf-8"></script>
|
<script src="static/lib/d3-tip.js" charset="utf-8"></script>
|
||||||
|
<script src="static/lib/browser-chrome.js"></script>
|
||||||
|
|
||||||
<script src="static/js/app.js"></script>
|
<script src="static/js/app.js"></script>
|
||||||
<script src="static/js/controllers.js"></script>
|
<script src="static/js/controllers.js"></script>
|
||||||
|
|
BIN
test.db
BIN
test.db
Binary file not shown.
Reference in a new issue