Merge remote-tracking branch 'origin/master' into rustedbuilds
Conflicts: test/data/test.db
This commit is contained in:
commit
ed38bcdafc
17 changed files with 1116 additions and 694 deletions
|
@ -1117,7 +1117,7 @@ def get_repo(namespace, repository):
|
||||||
abort(403) # Permission denied
|
abort(403) # Permission denied
|
||||||
|
|
||||||
|
|
||||||
def build_status_view(build_obj):
|
def build_status_view(build_obj, can_write=False):
|
||||||
status = build_logs.get_status(build_obj.uuid)
|
status = build_logs.get_status(build_obj.uuid)
|
||||||
return {
|
return {
|
||||||
'id': build_obj.uuid,
|
'id': build_obj.uuid,
|
||||||
|
@ -1125,7 +1125,8 @@ def build_status_view(build_obj):
|
||||||
'started': build_obj.started,
|
'started': build_obj.started,
|
||||||
'display_name': build_obj.display_name,
|
'display_name': build_obj.display_name,
|
||||||
'status': status,
|
'status': status,
|
||||||
'resource_key': build_obj.resource_key
|
'resource_key': build_obj.resource_key if can_write else None,
|
||||||
|
'is_writer': can_write
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1135,9 +1136,10 @@ def get_repo_builds(namespace, repository):
|
||||||
permission = ReadRepositoryPermission(namespace, repository)
|
permission = ReadRepositoryPermission(namespace, repository)
|
||||||
is_public = model.repository_is_public(namespace, repository)
|
is_public = model.repository_is_public(namespace, repository)
|
||||||
if permission.can() or is_public:
|
if permission.can() or is_public:
|
||||||
|
can_write = ModifyRepositoryPermission(namespace, repository).can()
|
||||||
builds = model.list_repository_builds(namespace, repository)
|
builds = model.list_repository_builds(namespace, repository)
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'builds': [build_status_view(build) for build in builds]
|
'builds': [build_status_view(build, can_write) for build in builds]
|
||||||
})
|
})
|
||||||
|
|
||||||
abort(403) # Permission denied
|
abort(403) # Permission denied
|
||||||
|
@ -1151,7 +1153,29 @@ def get_repo_build_status(namespace, repository, build_uuid):
|
||||||
is_public = model.repository_is_public(namespace, repository)
|
is_public = model.repository_is_public(namespace, repository)
|
||||||
if permission.can() or is_public:
|
if permission.can() or is_public:
|
||||||
build = model.get_repository_build(namespace, repository, build_uuid)
|
build = model.get_repository_build(namespace, repository, build_uuid)
|
||||||
return jsonify(build_status_view(build))
|
if not build:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
can_write = ModifyRepositoryPermission(namespace, repository).can()
|
||||||
|
return jsonify(build_status_view(build, can_write))
|
||||||
|
|
||||||
|
abort(403) # Permission denied
|
||||||
|
|
||||||
|
|
||||||
|
@api.route('/repository/<path:repository>/build/<build_uuid>/archiveurl',
|
||||||
|
methods=['GET'])
|
||||||
|
@parse_repository_name
|
||||||
|
def get_repo_build_archive_url(namespace, repository, build_uuid):
|
||||||
|
permission = ModifyRepositoryPermission(namespace, repository)
|
||||||
|
if permission.can():
|
||||||
|
build = model.get_repository_build(namespace, repository, build_uuid)
|
||||||
|
if not build:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
url = user_files.get_file_url(build.resource_key)
|
||||||
|
return jsonify({
|
||||||
|
'url': url
|
||||||
|
})
|
||||||
|
|
||||||
abort(403) # Permission denied
|
abort(403) # Permission denied
|
||||||
|
|
||||||
|
@ -1217,7 +1241,7 @@ def request_repo_build(namespace, repository):
|
||||||
{'repo': repository, 'namespace': namespace,
|
{'repo': repository, 'namespace': namespace,
|
||||||
'fileid': dockerfile_id}, repo=repo)
|
'fileid': dockerfile_id}, repo=repo)
|
||||||
|
|
||||||
resp = jsonify(build_status_view(build_request))
|
resp = jsonify(build_status_view(build_request, True))
|
||||||
repo_string = '%s/%s' % (namespace, repository)
|
repo_string = '%s/%s' % (namespace, repository)
|
||||||
resp.headers['Location'] = url_for('api.get_repo_build_status',
|
resp.headers['Location'] = url_for('api.get_repo_build_status',
|
||||||
repository=repo_string,
|
repository=repo_string,
|
||||||
|
|
|
@ -281,8 +281,8 @@ def populate_database():
|
||||||
|
|
||||||
token = model.create_access_token(building, 'write')
|
token = model.create_access_token(building, 'write')
|
||||||
tag = 'ci.devtable.com:5000/%s/%s' % (building.namespace, building.name)
|
tag = 'ci.devtable.com:5000/%s/%s' % (building.namespace, building.name)
|
||||||
build = model.create_repository_build(building, token, '123-45-6789', tag,
|
build = model.create_repository_build(building, token, '701dcc3724fb4f2ea6c31400528343cd',
|
||||||
'build-name')
|
tag, 'build-name')
|
||||||
build.uuid = 'deadbeef-dead-beef-dead-beefdeadbeef'
|
build.uuid = 'deadbeef-dead-beef-dead-beefdeadbeef'
|
||||||
build.save()
|
build.save()
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,47 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dockerfile-view {
|
||||||
|
margin: 20px;
|
||||||
|
padding: 20px;
|
||||||
|
background: #F7F6F6;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dockerfile-view .entry {
|
||||||
|
font-family: Consolas, "Lucida Console", Monaco, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dockerfile-view .entry.comment {
|
||||||
|
color: rgb(82, 172, 82);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dockerfile-command {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dockerfile-command .command-title {
|
||||||
|
font-family: Consolas, "Lucida Console", Monaco, monospace;
|
||||||
|
padding-left: 90px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dockerfile-command .label {
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
padding-top: 4px;
|
||||||
|
text-align: right;
|
||||||
|
margin-right: 4px;
|
||||||
|
width: 86px;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
border-right: 4px solid #aaa;
|
||||||
|
background-color: #333;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
left: -4px;
|
||||||
|
}
|
||||||
|
|
||||||
.codetooltipcontainer .tooltip-inner {
|
.codetooltipcontainer .tooltip-inner {
|
||||||
white-space:pre;
|
white-space:pre;
|
||||||
max-width:none;
|
max-width:none;
|
||||||
|
@ -264,7 +305,7 @@ i.toggle-icon:hover {
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer-container, .push {
|
.footer-container, .push {
|
||||||
height: 74px;
|
height: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer-container.fixed {
|
.footer-container.fixed {
|
||||||
|
@ -1819,6 +1860,14 @@ p.editable:hover i {
|
||||||
left: 24px;
|
left: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.repo-build .build-pane .build-logs .dockerfile-command {
|
||||||
|
position: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-build .build-pane .build-logs .dockerfile-command .command-title {
|
||||||
|
padding-left: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
.repo-build .build-pane .build-logs .container-header .container-content {
|
.repo-build .build-pane .build-logs .container-header .container-content {
|
||||||
display: block;
|
display: block;
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
|
@ -1828,26 +1877,6 @@ p.editable:hover i {
|
||||||
padding-left: 120px;
|
padding-left: 120px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label.FROM {
|
|
||||||
border-color: #5bc0de !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label.CMD, .label.EXPOSE, .label.ENTRYPOINT {
|
|
||||||
border-color: #428bca !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label.RUN, .label.ADD {
|
|
||||||
border-color: #5cb85c !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label.ENV, .label.VOLUME, .label.USER, .label.WORKDIR {
|
|
||||||
border-color: #f0ad4e !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label.MAINTAINER {
|
|
||||||
border-color: #aaa !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.repo-build .build-pane .build-logs .log-entry {
|
.repo-build .build-pane .build-logs .log-entry {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
@ -2263,23 +2292,10 @@ p.editable:hover i {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
#changes-tree-container .node rect {
|
|
||||||
cursor: pointer;
|
|
||||||
fill: #fff;
|
|
||||||
fill-opacity: 1;
|
|
||||||
stroke: #fff;
|
|
||||||
stroke-width: 1.5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changes-tree-container .node .change-icon {
|
#changes-tree-container .node .change-icon {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#changes-tree-container .node text {
|
|
||||||
font: 12px sans-serif;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changes-tree-container .node.changed text {
|
#changes-tree-container .node.changed text {
|
||||||
fill: rgb(73, 100, 209);
|
fill: rgb(73, 100, 209);
|
||||||
}
|
}
|
||||||
|
@ -2293,7 +2309,20 @@ p.editable:hover i {
|
||||||
fill: rgb(209, 73, 73);
|
fill: rgb(209, 73, 73);
|
||||||
}
|
}
|
||||||
|
|
||||||
#changes-tree-container path.link {
|
.file-tree-base .node rect {
|
||||||
|
cursor: pointer;
|
||||||
|
fill: #fff;
|
||||||
|
fill-opacity: 1;
|
||||||
|
stroke: #fff;
|
||||||
|
stroke-width: 1.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-tree-base .node text {
|
||||||
|
font: 12px sans-serif;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-tree-base path.link {
|
||||||
fill: none;
|
fill: none;
|
||||||
stroke: #9ecae1;
|
stroke: #9ecae1;
|
||||||
stroke-width: 1.5px;
|
stroke-width: 1.5px;
|
||||||
|
@ -3191,4 +3220,24 @@ pre.command:before {
|
||||||
.file-drop {
|
.file-drop {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label.FROM {
|
||||||
|
border-color: #5bc0de !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label.CMD, .label.EXPOSE, .label.ENTRYPOINT {
|
||||||
|
border-color: #428bca !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label.RUN, .label.ADD {
|
||||||
|
border-color: #5cb85c !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label.ENV, .label.VOLUME, .label.USER, .label.WORKDIR {
|
||||||
|
border-color: #f0ad4e !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label.MAINTAINER {
|
||||||
|
border-color: #aaa !important;
|
||||||
}
|
}
|
|
@ -1,6 +1 @@
|
||||||
<span class="command" bindonce>
|
<span class="dockerfile-command command" command="getWithoutStep(command.message)"></span>
|
||||||
<span class="label" bo-class="getCommandKind(command.message)" bo-show="getCommandKind(command.message)"
|
|
||||||
bo-text="getCommandKind(command.message)">
|
|
||||||
</span>
|
|
||||||
<span class="command-title" bo-html="getCommandTitleHtml(command.message)"></span>
|
|
||||||
</span>
|
|
||||||
|
|
6
static/directives/dockerfile-command.html
Normal file
6
static/directives/dockerfile-command.html
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<span class="dockerfile-command-element" bindonce>
|
||||||
|
<span class="label" bo-class="getCommandKind(command)" bo-show="getCommandKind(command)"
|
||||||
|
bo-text="getCommandKind(command)">
|
||||||
|
</span>
|
||||||
|
<span class="command-title" bo-html="getCommandTitleHtml(command)"></span>
|
||||||
|
</span>
|
15
static/directives/dockerfile-view.html
Normal file
15
static/directives/dockerfile-view.html
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<div class="dockerfile-view-element">
|
||||||
|
<div class="dockerfile-line" ng-repeat="line in lines">
|
||||||
|
<div ng-switch on="line.kind">
|
||||||
|
<div class="command entry" ng-switch-when="command">
|
||||||
|
<div class="dockerfile-command" command="line.text"></div>
|
||||||
|
</div>
|
||||||
|
<div class="comment entry" ng-switch-when="comment">
|
||||||
|
{{ line.text || ' ' }}
|
||||||
|
</div>
|
||||||
|
<div class="text entry" ng-switch-default>
|
||||||
|
{{ line.text || ' ' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -791,6 +791,7 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu
|
||||||
when('/repository/:namespace/:name/image/:image', {templateUrl: '/static/partials/image-view.html', controller: ImageViewCtrl, reloadOnSearch: false}).
|
when('/repository/:namespace/:name/image/:image', {templateUrl: '/static/partials/image-view.html', controller: ImageViewCtrl, reloadOnSearch: false}).
|
||||||
when('/repository/:namespace/:name/admin', {templateUrl: '/static/partials/repo-admin.html', controller:RepoAdminCtrl, reloadOnSearch: false}).
|
when('/repository/:namespace/:name/admin', {templateUrl: '/static/partials/repo-admin.html', controller:RepoAdminCtrl, reloadOnSearch: false}).
|
||||||
when('/repository/:namespace/:name/build', {templateUrl: '/static/partials/repo-build.html', controller:RepoBuildCtrl, reloadOnSearch: false}).
|
when('/repository/:namespace/:name/build', {templateUrl: '/static/partials/repo-build.html', controller:RepoBuildCtrl, reloadOnSearch: false}).
|
||||||
|
when('/repository/:namespace/:name/build/:buildid/buildpack', {templateUrl: '/static/partials/build-package.html', controller:BuildPackageCtrl, reloadOnSearch: false}).
|
||||||
when('/repository/', {title: 'Repositories', description: 'Public and private docker repositories list',
|
when('/repository/', {title: 'Repositories', description: 'Public and private docker repositories list',
|
||||||
templateUrl: '/static/partials/repo-list.html', controller: RepoListCtrl}).
|
templateUrl: '/static/partials/repo-list.html', controller: RepoListCtrl}).
|
||||||
when('/user/', {title: 'Account Settings', description:'Account settings for Quay.io', templateUrl: '/static/partials/user-admin.html',
|
when('/user/', {title: 'Account Settings', description:'Account settings for Quay.io', templateUrl: '/static/partials/user-admin.html',
|
||||||
|
@ -2536,17 +2537,42 @@ quayApp.directive('buildLogCommand', function () {
|
||||||
scope: {
|
scope: {
|
||||||
'command': '=command'
|
'command': '=command'
|
||||||
},
|
},
|
||||||
|
controller: function($scope, $element) {
|
||||||
|
$scope.getWithoutStep = function(fullTitle) {
|
||||||
|
var colon = fullTitle.indexOf(':');
|
||||||
|
if (colon <= 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $.trim(fullTitle.substring(colon + 1));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return directiveDefinitionObject;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
quayApp.directive('dockerfileCommand', function () {
|
||||||
|
var directiveDefinitionObject = {
|
||||||
|
priority: 0,
|
||||||
|
templateUrl: '/static/directives/dockerfile-command.html',
|
||||||
|
replace: false,
|
||||||
|
transclude: false,
|
||||||
|
restrict: 'C',
|
||||||
|
scope: {
|
||||||
|
'command': '=command'
|
||||||
|
},
|
||||||
controller: function($scope, $element, $sanitize) {
|
controller: function($scope, $element, $sanitize) {
|
||||||
var registryHandlers = {
|
var registryHandlers = {
|
||||||
'quay.io': function(pieces) {
|
'quay.io': function(pieces) {
|
||||||
var rnamespace = pieces[pieces.length - 2];
|
var rnamespace = pieces[pieces.length - 2];
|
||||||
var rname = pieces[pieces.length - 1];
|
var rname = pieces[pieces.length - 1].split(':')[0];
|
||||||
return '/repository/' + rnamespace + '/' + rname + '/';
|
return '/repository/' + rnamespace + '/' + rname + '/';
|
||||||
},
|
},
|
||||||
|
|
||||||
'': function(pieces) {
|
'': function(pieces) {
|
||||||
var rnamespace = pieces.length == 1 ? '_' : pieces[0];
|
var rnamespace = pieces.length == 1 ? '_' : pieces[0];
|
||||||
var rname = pieces[pieces.length - 1];
|
var rname = pieces[pieces.length - 1].split(':')[0];
|
||||||
return 'https://index.docker.io/u/' + rnamespace + '/' + rname + '/';
|
return 'https://index.docker.io/u/' + rnamespace + '/' + rname + '/';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -2563,25 +2589,18 @@ quayApp.directive('buildLogCommand', function () {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.getCommandKind = function(fullTitle) {
|
$scope.getCommandKind = function(title) {
|
||||||
var colon = fullTitle.indexOf(':');
|
|
||||||
var title = getTitleWithoutStep(fullTitle);
|
|
||||||
if (!title) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var space = title.indexOf(' ');
|
var space = title.indexOf(' ');
|
||||||
return title.substring(0, space);
|
return title.substring(0, space);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.getCommandTitleHtml = function(fullTitle) {
|
$scope.getCommandTitleHtml = function(title) {
|
||||||
var title = getTitleWithoutStep(fullTitle) || fullTitle;
|
|
||||||
var space = title.indexOf(' ');
|
var space = title.indexOf(' ');
|
||||||
if (space <= 0) {
|
if (space <= 0) {
|
||||||
return $sanitize(title);
|
return $sanitize(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
var kind = $scope.getCommandKind(fullTitle);
|
var kind = $scope.getCommandKind(title);
|
||||||
var sanitized = $sanitize(title.substring(space + 1));
|
var sanitized = $sanitize(title.substring(space + 1));
|
||||||
|
|
||||||
var handler = kindHandlers[kind || ''];
|
var handler = kindHandlers[kind || ''];
|
||||||
|
@ -2590,21 +2609,50 @@ quayApp.directive('buildLogCommand', function () {
|
||||||
} else {
|
} else {
|
||||||
return sanitized;
|
return sanitized;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var getTitleWithoutStep = function(fullTitle) {
|
|
||||||
var colon = fullTitle.indexOf(':');
|
|
||||||
if (colon <= 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $.trim(fullTitle.substring(colon + 1));
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return directiveDefinitionObject;
|
return directiveDefinitionObject;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
quayApp.directive('dockerfileView', function () {
|
||||||
|
var directiveDefinitionObject = {
|
||||||
|
priority: 0,
|
||||||
|
templateUrl: '/static/directives/dockerfile-view.html',
|
||||||
|
replace: false,
|
||||||
|
transclude: false,
|
||||||
|
restrict: 'C',
|
||||||
|
scope: {
|
||||||
|
'contents': '=contents'
|
||||||
|
},
|
||||||
|
controller: function($scope, $element, $sanitize) {
|
||||||
|
$scope.$watch('contents', function(contents) {
|
||||||
|
$scope.lines = [];
|
||||||
|
|
||||||
|
var lines = contents ? contents.split('\n') : [];
|
||||||
|
for (var i = 0; i < lines.length; ++i) {
|
||||||
|
var line = $.trim(lines[i]);
|
||||||
|
var kind = 'text';
|
||||||
|
if (line && line[0] == '#') {
|
||||||
|
kind = 'comment';
|
||||||
|
} else if (line.match(/^([A-Z]+\s)/)) {
|
||||||
|
kind = 'command';
|
||||||
|
}
|
||||||
|
|
||||||
|
var lineInfo = {
|
||||||
|
'text': $sanitize(line),
|
||||||
|
'kind': kind
|
||||||
|
};
|
||||||
|
$scope.lines.push(lineInfo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return directiveDefinitionObject;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
quayApp.directive('buildStatus', function () {
|
quayApp.directive('buildStatus', function () {
|
||||||
var directiveDefinitionObject = {
|
var directiveDefinitionObject = {
|
||||||
priority: 0,
|
priority: 0,
|
||||||
|
|
|
@ -774,6 +774,137 @@ function RepoCtrl($scope, $sanitize, Restangular, ImageMetadataService, ApiServi
|
||||||
loadViewInfo();
|
loadViewInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function BuildPackageCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $location, $timeout) {
|
||||||
|
var namespace = $routeParams.namespace;
|
||||||
|
var name = $routeParams.name;
|
||||||
|
var buildid = $routeParams.buildid;
|
||||||
|
|
||||||
|
var params = {
|
||||||
|
'repository': namespace + '/' + name,
|
||||||
|
'build_uuid': buildid
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.initializeTree = function() {
|
||||||
|
if ($scope.drawn) {
|
||||||
|
$scope.tree.notifyResized();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.drawn = true;
|
||||||
|
$timeout(function() {
|
||||||
|
$scope.tree.draw('file-tree-container');
|
||||||
|
}, 10);
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.downloadForUser = function() {
|
||||||
|
var blob = $scope.zip.generate({type:"blob"});
|
||||||
|
saveAs(blob, $scope.repobuild['display_name'] + '.zip');
|
||||||
|
};
|
||||||
|
|
||||||
|
var processBuildPack = function(response) {
|
||||||
|
// Try to load as a zip file.
|
||||||
|
var zipFiles = null;
|
||||||
|
var zip = null;
|
||||||
|
try {
|
||||||
|
var zip = new JSZip(response);
|
||||||
|
zipFiles = zip.files;
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the Dockerfile in the zip file. If there isn't any zip file, then the response
|
||||||
|
// itself (should) be the Dockerfile.
|
||||||
|
if (zipFiles && Object.keys(zipFiles).length) {
|
||||||
|
// Load the dockerfile contents.
|
||||||
|
var dockerfile = zip.file('Dockerfile');
|
||||||
|
if (dockerfile) {
|
||||||
|
$scope.dockerFileContents = dockerfile.asText();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the zip file tree.
|
||||||
|
$scope.zip = zip;
|
||||||
|
$scope.tree = new FileTree(Object.keys(zipFiles));
|
||||||
|
$($scope.tree).bind('fileClicked', function(e) {
|
||||||
|
var file = zip.file(e.path);
|
||||||
|
if (file) {
|
||||||
|
var blob = new Blob([file.asArrayBuffer()]);
|
||||||
|
saveAs(blob, file.name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$scope.dockerFileContents = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.loaded = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
var downloadBuildPack = function() {
|
||||||
|
$scope.downloadProgress = 0;
|
||||||
|
$scope.downloading = true;
|
||||||
|
|
||||||
|
ApiService.getRepoBuildArchiveUrl(null, params).then(function(resp) {
|
||||||
|
startDownload(resp['url']);
|
||||||
|
}, function() {
|
||||||
|
$scope.downloading = false;
|
||||||
|
$scope.downloadError = true;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var startDownload = function(url) {
|
||||||
|
var request = new XMLHttpRequest();
|
||||||
|
request.open('GET', url, true);
|
||||||
|
if (request.overrideMimeType) {
|
||||||
|
request.overrideMimeType('text/plain; charset=x-user-defined');
|
||||||
|
}
|
||||||
|
request.onprogress = function(e) {
|
||||||
|
$scope.$apply(function() {
|
||||||
|
var percentLoaded;
|
||||||
|
if (e.lengthComputable) {
|
||||||
|
$scope.downloadProgress = (e.loaded / e.total) * 100;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
request.onerror = function() {
|
||||||
|
$scope.$apply(function() {
|
||||||
|
$scope.downloading = false;
|
||||||
|
$scope.downloadError = true;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
request.onreadystatechange = function() {
|
||||||
|
var state = request.readyState;
|
||||||
|
if (state == 4) {
|
||||||
|
$scope.$apply(function() {
|
||||||
|
$scope.downloading = false;
|
||||||
|
processBuildPack(request.responseText);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
request.send();
|
||||||
|
};
|
||||||
|
|
||||||
|
var getBuildInfo = function() {
|
||||||
|
$scope.repository_build = ApiService.getRepoBuildStatus(null, params, true).then(function(resp) {
|
||||||
|
if (!resp['is_writer']) {
|
||||||
|
$rootScope.title = 'Unknown build';
|
||||||
|
$scope.accessDenied = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$rootScope.title = 'Repository Build Pack - ' + resp['display_name'];
|
||||||
|
$scope.repobuild = resp;
|
||||||
|
$scope.repo = {
|
||||||
|
'namespace': namespace,
|
||||||
|
'name': name
|
||||||
|
};
|
||||||
|
|
||||||
|
downloadBuildPack();
|
||||||
|
return resp;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
getBuildInfo();
|
||||||
|
}
|
||||||
|
|
||||||
function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $location, $interval, $sanitize, ansi2html) {
|
function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $location, $interval, $sanitize, ansi2html) {
|
||||||
var namespace = $routeParams.namespace;
|
var namespace = $routeParams.namespace;
|
||||||
var name = $routeParams.name;
|
var name = $routeParams.name;
|
||||||
|
@ -805,7 +936,7 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.adjustLogHeight = function() {
|
$scope.adjustLogHeight = function() {
|
||||||
$('.build-logs').height($(window).height() - 385);
|
$('.build-logs').height($(window).height() - 415);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.askRestartBuild = function(build) {
|
$scope.askRestartBuild = function(build) {
|
||||||
|
|
File diff suppressed because it is too large
Load diff
14
static/lib/jszip.min.js
vendored
Executable file
14
static/lib/jszip.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
50
static/partials/build-package.html
Normal file
50
static/partials/build-package.html
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
<div class="resource-view" resource="repository_build" error-message="'No matching repository build found'"></div>
|
||||||
|
<div class="container repo repo-build" ng-show="accessDenied">
|
||||||
|
You do not have permission to view this page
|
||||||
|
</div>
|
||||||
|
<div class="container repo repo-build repo-build-pack" ng-show="repobuild">
|
||||||
|
<div class="header row">
|
||||||
|
<a href="{{ '/repository/' + repo.namespace + '/' + repo.name + '/build' }}" class="back"><i class="fa fa-chevron-left"></i></a>
|
||||||
|
<h3>
|
||||||
|
<span class="repo-circle no-background" repo="repo"></span>
|
||||||
|
<span class="repo-breadcrumb" repo="repo" subsection-icon="'fa-tasks'" subsection="repobuild.display_name"></span>
|
||||||
|
</h3>
|
||||||
|
<div class="repo-controls">
|
||||||
|
<a href="javascript:void(0)" ng-show="zip" ng-click="downloadForUser()">
|
||||||
|
<i class="fa fa-download toggle-icon" title="Download Build Package" bs-tooltip="tooltip.title" data-container="body"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" ng-show="downloading">
|
||||||
|
Downloading build pack:
|
||||||
|
<div class="progress" class="active progress-striped">
|
||||||
|
<div class="progress-bar" role="progressbar" aria-valuenow="{{ downloadProgress }}" aria-valuemin="0" aria-valuemax="100" style="{{ 'width: ' + downloadProgress + '%' }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" ng-show="downloadError">
|
||||||
|
Error: Could not download the build pack
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" ng-show="loaded">
|
||||||
|
<ul class="nav nav-tabs">
|
||||||
|
<li class="active"><a href="javascript:void(0)" data-toggle="tab" data-target="#dockerfile">Dockerfile</a></li>
|
||||||
|
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#tree" ng-click="initializeTree()" ng-show="tree">All Files</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="tab-content">
|
||||||
|
<!-- Dockerfile view -->
|
||||||
|
<div class="tab-pane active" id="dockerfile">
|
||||||
|
<div class="dockerfile-view" contents="dockerFileContents"></div>
|
||||||
|
<span ng-show="!dockerFileContents">No Dockerfile found in the build pack</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- File tree -->
|
||||||
|
<div class="tab-pane" id="tree">
|
||||||
|
<div id="file-tree-container" class="tree-container" onresize="tree && drawn && tree.notifyResized()"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -43,6 +43,12 @@
|
||||||
<div class="timing">
|
<div class="timing">
|
||||||
<i class="fa fa-clock-o"></i>
|
<i class="fa fa-clock-o"></i>
|
||||||
Started: <span am-time-ago="build.started || 0"></span>
|
Started: <span am-time-ago="build.started || 0"></span>
|
||||||
|
<span style="display: inline-block; margin-left: 20px" ng-show="currentBuild.resource_key">
|
||||||
|
<i class="fa fa-archive"></i>
|
||||||
|
<a href="/repository/{{ repo.namespace }}/{{ repo.name }}/build/{{ currentBuild.id }}/buildpack"
|
||||||
|
style="display: inline-block; margin-left: 6px" bs-tooltip="tooltip.title"
|
||||||
|
title="View the uploaded build package for this build">Build Package</a>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="phase-icon" ng-class="build.phase"></span>
|
<span class="phase-icon" ng-class="build.phase"></span>
|
||||||
<span class="build-message" phase="build.phase"></span>
|
<span class="build-message" phase="build.phase"></span>
|
||||||
|
|
|
@ -62,7 +62,7 @@
|
||||||
|
|
||||||
<div class="panel" ng-show="!updatingUser">
|
<div class="panel" ng-show="!updatingUser">
|
||||||
<div class="panel-title">Account e-mail address</div>
|
<div class="panel-title">Account e-mail address</div>
|
||||||
<div class="panel-setting-content">
|
<div class="panel-setting-content panel-body">
|
||||||
{{ user.email }}
|
{{ user.email }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -113,14 +113,11 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="row" ng-show="cuser">
|
<div class="row" ng-show="cuser">
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
<div class="panel-title">GitHub Login</div>
|
<div class="panel-title">GitHub Login:</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<div ng-show="githubLogin" class="lead col-md-8">
|
<div ng-show="githubLogin" class="lead col-md-8">
|
||||||
<span class="fa-stack">
|
<i class="fa fa-github fa-lg" style="margin-right: 6px;" title="GitHub" bs-tooltip="tooltip.title"></i>
|
||||||
<i class="fa fa-circle fa-stack-2x check-green"></i>
|
<b><a href="https://github.com/{{githubLogin}}" target="_blank">{{githubLogin}}</a></b>
|
||||||
<i class="fa fa-check fa-stack-1x fa-inverse"></i>
|
|
||||||
</span>
|
|
||||||
This account is connected with GitHub account: <b>{{githubLogin}}</b>
|
|
||||||
</div>
|
</div>
|
||||||
<div ng-show="!githubLogin" class="col-md-8">
|
<div ng-show="!githubLogin" class="col-md-8">
|
||||||
<a href="https://github.com/login/oauth/authorize?client_id={{ githubClientId }}&scope=user:email{{ github_state_clause }}&redirect_uri={{ githubRedirectUri }}/attach" class="btn btn-primary"><i class="fa fa-github fa-lg"></i> Connect with GitHub</a>
|
<a href="https://github.com/login/oauth/authorize?client_id={{ githubClientId }}&scope=user:email{{ github_state_clause }}&redirect_uri={{ githubRedirectUri }}/attach" class="btn btn-primary"><i class="fa fa-github fa-lg"></i> Connect with GitHub</a>
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<!-- Repo Header -->
|
<!-- Repo Header -->
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<h3>
|
<h3>
|
||||||
<span class="repo-circle" repo="repo"></span>
|
<span class="repo-circle no-background" repo="repo"></span>
|
||||||
<span class="repo-breadcrumb" repo="repo"></span>
|
<span class="repo-breadcrumb" repo="repo"></span>
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
|
|
|
@ -23,8 +23,8 @@
|
||||||
|
|
||||||
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.2.1/moment.min.js"></script>
|
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.2.1/moment.min.js"></script>
|
||||||
<script src="//cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.2.0/js/bootstrap-datepicker.min.js"></script>
|
<script src="//cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.2.0/js/bootstrap-datepicker.min.js"></script>
|
||||||
|
|
||||||
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.3.3/d3.min.js"></script>
|
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.3.3/d3.min.js"></script>
|
||||||
|
|
||||||
<script src="static/lib/nv.d3.min.js"></script>
|
<script src="static/lib/nv.d3.min.js"></script>
|
||||||
|
|
||||||
<script src="static/lib/ZeroClipboard.min.js"></script>
|
<script src="static/lib/ZeroClipboard.min.js"></script>
|
||||||
|
@ -32,6 +32,7 @@
|
||||||
<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/lib/browser-chrome.js"></script>
|
||||||
|
|
||||||
|
<script src="static/lib/jszip.min.js"></script>
|
||||||
<script src="static/lib/Blob.js"></script>
|
<script src="static/lib/Blob.js"></script>
|
||||||
<script src="static/lib/FileSaver.js"></script>
|
<script src="static/lib/FileSaver.js"></script>
|
||||||
<script src="static/lib/jquery.base64.min.js"></script>
|
<script src="static/lib/jquery.base64.min.js"></script>
|
||||||
|
|
Binary file not shown.
|
@ -250,7 +250,7 @@ class TestGetMatchingEntities(ApiTestCase):
|
||||||
|
|
||||||
json = self.getJsonResponse('api.get_matching_entities',
|
json = self.getJsonResponse('api.get_matching_entities',
|
||||||
params=dict(prefix='o', namespace=ORGANIZATION,
|
params=dict(prefix='o', namespace=ORGANIZATION,
|
||||||
includeTeams=True))
|
includeTeams='true'))
|
||||||
|
|
||||||
names = set([r['name'] for r in json['results']])
|
names = set([r['name'] for r in json['results']])
|
||||||
assert 'outsideorg' in names
|
assert 'outsideorg' in names
|
||||||
|
@ -261,7 +261,7 @@ class TestGetMatchingEntities(ApiTestCase):
|
||||||
|
|
||||||
json = self.getJsonResponse('api.get_matching_entities',
|
json = self.getJsonResponse('api.get_matching_entities',
|
||||||
params=dict(prefix='o', namespace=ORGANIZATION,
|
params=dict(prefix='o', namespace=ORGANIZATION,
|
||||||
includeTeams=True))
|
includeTeams='true'))
|
||||||
|
|
||||||
names = set([r['name'] for r in json['results']])
|
names = set([r['name'] for r in json['results']])
|
||||||
assert 'outsideorg' in names
|
assert 'outsideorg' in names
|
||||||
|
@ -684,12 +684,12 @@ class TestFindRepos(ApiTestCase):
|
||||||
|
|
||||||
class TestListRepos(ApiTestCase):
|
class TestListRepos(ApiTestCase):
|
||||||
def test_listrepos_asguest(self):
|
def test_listrepos_asguest(self):
|
||||||
json = self.getJsonResponse('api.list_repos', params=dict(public=True))
|
json = self.getJsonResponse('api.list_repos', params=dict(public='true'))
|
||||||
assert len(json['repositories']) == 0
|
assert len(json['repositories']) > 1
|
||||||
|
|
||||||
def test_listrepos_orgmember(self):
|
def test_listrepos_orgmember(self):
|
||||||
self.login(READ_ACCESS_USER)
|
self.login(READ_ACCESS_USER)
|
||||||
json = self.getJsonResponse('api.list_repos', params=dict(public=True))
|
json = self.getJsonResponse('api.list_repos', params=dict(public='true'))
|
||||||
assert len(json['repositories']) > 1
|
assert len(json['repositories']) > 1
|
||||||
|
|
||||||
def test_listrepos_filter(self):
|
def test_listrepos_filter(self):
|
||||||
|
|
Reference in a new issue