Merge branch 'dockerbuild' of ssh://bitbucket.org/yackob03/quay into dockerbuild
This commit is contained in:
commit
c276bce177
14 changed files with 444 additions and 35 deletions
|
@ -23,4 +23,4 @@ if application.config.get('INCLUDE_TEST_ENDPOINTS', False):
|
||||||
application.debug = True
|
application.debug = True
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
application.run(port=5000, debug=True, host='0.0.0.0')
|
application.run(port=5000, debug=True, threaded=True, host='0.0.0.0')
|
||||||
|
|
|
@ -4,6 +4,11 @@ import logging
|
||||||
|
|
||||||
from boto.s3.key import Key
|
from boto.s3.key import Key
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
import hmac
|
||||||
|
import time
|
||||||
|
import urllib
|
||||||
|
import base64
|
||||||
|
import sha
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -18,9 +23,25 @@ class UserRequestFiles(object):
|
||||||
self._s3_conn = boto.s3.connection.S3Connection(s3_access_key,
|
self._s3_conn = boto.s3.connection.S3Connection(s3_access_key,
|
||||||
s3_secret_key,
|
s3_secret_key,
|
||||||
is_secure=False)
|
is_secure=False)
|
||||||
|
self._bucket_name = bucket_name
|
||||||
self._bucket = self._s3_conn.get_bucket(bucket_name)
|
self._bucket = self._s3_conn.get_bucket(bucket_name)
|
||||||
|
self._access_key = s3_access_key
|
||||||
|
self._secret_key = s3_secret_key
|
||||||
self._prefix = 'userfiles'
|
self._prefix = 'userfiles'
|
||||||
|
|
||||||
|
def prepare_for_drop(self, mimeType):
|
||||||
|
""" Returns a signed URL to upload a file to our bucket. """
|
||||||
|
file_id = str(self._prefix + '/' + str(uuid4()))
|
||||||
|
|
||||||
|
expires = str(int(time.time() + 300))
|
||||||
|
signingString = "PUT\n\n" + mimeType + "\n" + expires + "\n/" + self._bucket_name + "/" + file_id;
|
||||||
|
|
||||||
|
hmac_signer = hmac.new(self._secret_key, signingString, sha)
|
||||||
|
signature = base64.b64encode(hmac_signer.digest())
|
||||||
|
|
||||||
|
url = "http://s3.amazonaws.com/" + self._bucket_name + "/" + file_id + "?AWSAccessKeyId=" + self._access_key + "&Expires=" + expires + "&Signature=" + urllib.quote(signature);
|
||||||
|
return (url, file_id)
|
||||||
|
|
||||||
def store_file(self, flask_file):
|
def store_file(self, flask_file):
|
||||||
file_id = str(uuid4())
|
file_id = str(uuid4())
|
||||||
full_key = os.path.join(self._prefix, file_id)
|
full_key = os.path.join(self._prefix, file_id)
|
||||||
|
|
|
@ -180,22 +180,22 @@ user_files = UserRequestFiles(app.config['AWS_ACCESS_KEY'],
|
||||||
app.config['REGISTRY_S3_BUCKET'])
|
app.config['REGISTRY_S3_BUCKET'])
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/repository/', methods=['POST'])
|
@app.route('/api/repository', methods=['POST'])
|
||||||
@api_login_required
|
@api_login_required
|
||||||
def create_repo_api():
|
def create_repo_api():
|
||||||
owner = current_user.db_user()
|
owner = current_user.db_user()
|
||||||
|
|
||||||
namespace_name = owner.username
|
namespace_name = owner.username
|
||||||
repository_name = request.values['repository']
|
repository_name = request.get_json()['repository']
|
||||||
visibility = request.values['visibility']
|
visibility = request.get_json()['visibility']
|
||||||
|
|
||||||
repo = model.create_repository(namespace_name, repository_name, owner,
|
repo = model.create_repository(namespace_name, repository_name, owner,
|
||||||
visibility)
|
visibility)
|
||||||
|
|
||||||
resp = make_response('Created', 201)
|
return jsonify({
|
||||||
resp.headers['Location'] = url_for('get_repo_api', namespace=namespace_name,
|
'namespace': namespace_name,
|
||||||
repository=repository_name)
|
'name': repository_name
|
||||||
return resp
|
})
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/find/repository', methods=['GET'])
|
@app.route('/api/find/repository', methods=['GET'])
|
||||||
|
@ -395,6 +395,17 @@ def get_repo_builds(namespace, repository):
|
||||||
abort(403) # Permissions denied
|
abort(403) # Permissions denied
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/filedrop/', methods=['POST'])
|
||||||
|
def get_filedrop_url():
|
||||||
|
mimeType = request.get_json()['mimeType']
|
||||||
|
(url, file_id) = user_files.prepare_for_drop(mimeType)
|
||||||
|
return jsonify({
|
||||||
|
'url': url,
|
||||||
|
'file_id': file_id
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/repository/<path:repository>/build/', methods=['POST'])
|
@app.route('/api/repository/<path:repository>/build/', methods=['POST'])
|
||||||
@api_login_required
|
@api_login_required
|
||||||
@parse_repository_name
|
@parse_repository_name
|
||||||
|
@ -402,8 +413,7 @@ def request_repo_build(namespace, repository):
|
||||||
permission = ModifyRepositoryPermission(namespace, repository)
|
permission = ModifyRepositoryPermission(namespace, repository)
|
||||||
if permission.can():
|
if permission.can():
|
||||||
logger.debug('User requested repository initialization.')
|
logger.debug('User requested repository initialization.')
|
||||||
dockerfile_source = request.files['initializedata']
|
dockerfile_id = request.get_json()['file_id']
|
||||||
dockerfile_id = user_files.store_file(dockerfile_source)
|
|
||||||
|
|
||||||
repo = model.get_repository(namespace, repository)
|
repo = model.get_repository(namespace, repository)
|
||||||
token = model.create_access_token(repo, 'write')
|
token = model.create_access_token(repo, 'write')
|
||||||
|
@ -414,7 +424,9 @@ def request_repo_build(namespace, repository):
|
||||||
tag)
|
tag)
|
||||||
dockerfile_build_queue.put(json.dumps({'request_id': build_request.id}))
|
dockerfile_build_queue.put(json.dumps({'request_id': build_request.id}))
|
||||||
|
|
||||||
return make_response('Created', 201)
|
return jsonify({
|
||||||
|
'started': True
|
||||||
|
})
|
||||||
|
|
||||||
abort(403) # Permissions denied
|
abort(403) # Permissions denied
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,30 @@
|
||||||
color: #428bca;
|
color: #428bca;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.build-statuses {
|
||||||
|
}
|
||||||
|
|
||||||
|
.build-status-container {
|
||||||
|
padding: 4px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.build-status-container .build-message {
|
||||||
|
display: block;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.build-status-container .progress {
|
||||||
|
height: 12px;
|
||||||
|
margin: 0px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.build-status-container:last-child {
|
||||||
|
margin-bottom: 0px;
|
||||||
|
border-bottom: 0px solid white;
|
||||||
|
}
|
||||||
|
|
||||||
.repo-circle {
|
.repo-circle {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -579,6 +603,7 @@ p.editable:hover i {
|
||||||
}
|
}
|
||||||
|
|
||||||
.repo .description {
|
.repo .description {
|
||||||
|
margin-top: 10px;
|
||||||
margin-bottom: 40px;
|
margin-bottom: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -608,22 +633,70 @@ p.editable:hover i {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.repo .status-boxes {
|
||||||
|
float: right;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo .status-boxes .status-box {
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo .status-boxes .status-box .title {
|
||||||
|
padding: 4px;
|
||||||
|
display: inline-block;
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo .status-boxes .status-box .title i {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo .status-boxes .status-box .count {
|
||||||
|
display: inline-block;
|
||||||
|
background-image: linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);
|
||||||
|
padding: 4px;
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
transform: scaleX(0);
|
||||||
|
-webkit-transform: scaleX(0);
|
||||||
|
-moz-transform: scaleX(0);
|
||||||
|
|
||||||
|
transition: transform 500ms ease-in-out;
|
||||||
|
-webkit-transition: -webkit-transform 500ms ease-in-out;
|
||||||
|
-moz-transition: -moz-transform 500ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo .status-boxes .status-box .count.visible {
|
||||||
|
transform: scaleX(1);
|
||||||
|
-webkit-transform: scaleX(1);
|
||||||
|
-moz-transform: scaleX(1);
|
||||||
|
}
|
||||||
|
|
||||||
.repo .pull-command {
|
.repo .pull-command {
|
||||||
float: right;
|
float: right;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: 1.2em;
|
font-size: 0.8em;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-right: 10px;
|
margin-top: 30px;
|
||||||
|
margin-right: 26px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.repo .pull-command .pull-container {
|
.repo .pull-container {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
|
margin-left: 10px;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.repo .pull-command input {
|
.repo .pull-container input {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
background: white;
|
background: white;
|
||||||
color: #666;
|
color: #666;
|
||||||
|
@ -788,8 +861,22 @@ p.editable:hover i {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.repo .description p {
|
|
||||||
margin-bottom: 6px;
|
.repo .build-info {
|
||||||
|
padding: 10px;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo .build-info .progress {
|
||||||
|
margin: 0px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo .section {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
}
|
}
|
||||||
|
|
||||||
.repo .description p:last-child {
|
.repo .description p:last-child {
|
||||||
|
@ -967,7 +1054,7 @@ p.editable:hover i {
|
||||||
background: rgb(253, 191, 191);
|
background: rgb(253, 191, 191);
|
||||||
}
|
}
|
||||||
|
|
||||||
.repo-admin .repo-access-state .state-icon i.fa-unlock-alt {
|
.repo-admin .repo-access-state .state-icon i.fa-unlock {
|
||||||
background: rgb(170, 236, 170);
|
background: rgb(170, 236, 170);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
8
static/directives/build-status.html
Normal file
8
static/directives/build-status.html
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<div class="build-status-container">
|
||||||
|
<span class="build-message">{{ getBuildMessage(build) }}</span>
|
||||||
|
<div class="progress" ng-class="getBuildProgress(build) < 100 ? 'active progress-striped' : ''" ng-show="getBuildProgress(build) >= 0">
|
||||||
|
<div class="progress-bar" role="progressbar" aria-valuenow="{{ getBuildProgress(build) }}" aria-valuemin="0" aria-valuemax="100" style="{{ 'width: ' + getBuildProgress(build) + '%' }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
|
@ -1,5 +1,5 @@
|
||||||
// Start the application code itself.
|
// Start the application code itself.
|
||||||
quayApp = angular.module('quay', ['restangular', 'angularMoment', 'angulartics', 'angulartics.mixpanel'], function($provide) {
|
quayApp = angular.module('quay', ['restangular', 'angularMoment', 'angulartics', 'angulartics.mixpanel', '$strap.directives'], function($provide) {
|
||||||
$provide.factory('UserService', ['Restangular', function(Restangular) {
|
$provide.factory('UserService', ['Restangular', function(Restangular) {
|
||||||
var userResponse = {
|
var userResponse = {
|
||||||
verified: false,
|
verified: false,
|
||||||
|
@ -177,7 +177,72 @@ quayApp.directive('repoCircle', function () {
|
||||||
'repo': '=repo'
|
'repo': '=repo'
|
||||||
},
|
},
|
||||||
controller: function($scope, $element) {
|
controller: function($scope, $element) {
|
||||||
window.console.log($scope);
|
}
|
||||||
|
};
|
||||||
|
return directiveDefinitionObject;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
quayApp.directive('buildStatus', function () {
|
||||||
|
var directiveDefinitionObject = {
|
||||||
|
priority: 0,
|
||||||
|
templateUrl: '/static/directives/build-status.html',
|
||||||
|
replace: false,
|
||||||
|
transclude: false,
|
||||||
|
restrict: 'C',
|
||||||
|
scope: {
|
||||||
|
'build': '=build'
|
||||||
|
},
|
||||||
|
controller: function($scope, $element) {
|
||||||
|
$scope.getBuildProgress = function(buildInfo) {
|
||||||
|
switch (buildInfo.status) {
|
||||||
|
case 'building':
|
||||||
|
return (buildInfo.current_command / buildInfo.total_commands) * 100;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'pushing':
|
||||||
|
return (buildInfo.current_image / buildInfo.total_images) * 100;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'complete':
|
||||||
|
return 100;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'initializing':
|
||||||
|
case 'starting':
|
||||||
|
case 'waiting':
|
||||||
|
return 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.getBuildMessage = function(buildInfo) {
|
||||||
|
switch (buildInfo.status) {
|
||||||
|
case 'initializing':
|
||||||
|
return 'Starting Dockerfile build';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'starting':
|
||||||
|
case 'waiting':
|
||||||
|
case 'building':
|
||||||
|
return 'Building image from Dockerfile';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'pushing':
|
||||||
|
return 'Pushing image built from Dockerfile';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'complete':
|
||||||
|
return 'Dockerfile build completed and pushed';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'error':
|
||||||
|
return 'Dockerfile build failed: ' + buildInfo.message;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return directiveDefinitionObject;
|
return directiveDefinitionObject;
|
||||||
|
|
|
@ -339,6 +339,19 @@ function RepoCtrl($scope, Restangular, $routeParams, $rootScope, $location, $tim
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var startBuildInfoTimer = function(repo) {
|
||||||
|
setInterval(function() {
|
||||||
|
$scope.$apply(function() { getBuildInfo(repo); });
|
||||||
|
}, 5000);
|
||||||
|
};
|
||||||
|
|
||||||
|
var getBuildInfo = function(repo) {
|
||||||
|
var buildInfo = Restangular.one('repository/' + repo.namespace + '/' + repo.name + '/build/');
|
||||||
|
buildInfo.get().then(function(resp) {
|
||||||
|
$scope.buildsInfo = resp.builds;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
var listImages = function() {
|
var listImages = function() {
|
||||||
if ($scope.imageHistory) { return; }
|
if ($scope.imageHistory) { return; }
|
||||||
|
|
||||||
|
@ -443,6 +456,10 @@ function RepoCtrl($scope, Restangular, $routeParams, $rootScope, $location, $tim
|
||||||
|
|
||||||
$('#copyClipboard').clipboardCopy();
|
$('#copyClipboard').clipboardCopy();
|
||||||
$scope.loading = false;
|
$scope.loading = false;
|
||||||
|
|
||||||
|
if (repo.is_building) {
|
||||||
|
getBuildInfo(repo);
|
||||||
|
}
|
||||||
}, function() {
|
}, function() {
|
||||||
$scope.repo = null;
|
$scope.repo = null;
|
||||||
$scope.loading = false;
|
$scope.loading = false;
|
||||||
|
@ -895,18 +912,83 @@ function V1Ctrl($scope, UserService) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function NewRepoCtrl($scope, UserService) {
|
function NewRepoCtrl($scope, $location, UserService, Restangular) {
|
||||||
$scope.repo = {
|
$scope.repo = {
|
||||||
'is_public': 1,
|
'is_public': 1,
|
||||||
'description': '',
|
'description': '',
|
||||||
'initialize': false
|
'initialize': false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var startBuild = function(repo, fileId) {
|
||||||
|
$scope.building = true;
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
'file_id': fileId
|
||||||
|
};
|
||||||
|
|
||||||
|
var startBuildCall = Restangular.one('repository/' + repo.namespace + '/' + repo.name + '/build/');
|
||||||
|
startBuildCall.customPOST(data).then(function(resp) {
|
||||||
|
$location.path('/repository/' + repo.namespace + '/' + repo.name);
|
||||||
|
}, function() {
|
||||||
|
$('#couldnotbuildModal').modal();
|
||||||
|
$location.path('/repository/' + repo.namespace + '/' + repo.name);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var conductUpload = function(repo, file, url, fileId, mimeType) {
|
||||||
|
var request = new XMLHttpRequest();
|
||||||
|
request.open('PUT', url, true);
|
||||||
|
request.overrideMimeType(mimeType);
|
||||||
|
request.onprogress = function(e) {
|
||||||
|
var percentLoaded;
|
||||||
|
if (e.lengthComputable) {
|
||||||
|
$scope.upload_progress = (e.loaded / e.total) * 100;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
request.onerror = function() {
|
||||||
|
$('#couldnotbuildModal').modal();
|
||||||
|
$location.path('/repository/' + repo.namespace + '/' + repo.name);
|
||||||
|
};
|
||||||
|
request.onreadystatechange = function() {
|
||||||
|
var state = request.readyState;
|
||||||
|
if (state == 4) {
|
||||||
|
$scope.$apply(function() {
|
||||||
|
$scope.uploading = false;
|
||||||
|
startBuild(repo, fileId);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
request.send(file);
|
||||||
|
};
|
||||||
|
|
||||||
|
var startFileUpload = function(repo) {
|
||||||
|
$scope.uploading = true;
|
||||||
|
$scope.uploading_progress = 0;
|
||||||
|
|
||||||
|
var uploader = $('#file-drop')[0];
|
||||||
|
var file = uploader.files[0];
|
||||||
|
$scope.upload_file = file.name;
|
||||||
|
|
||||||
|
var mimeType = file.type || 'application/octet-stream';
|
||||||
|
var data = {
|
||||||
|
'mimeType': mimeType
|
||||||
|
};
|
||||||
|
|
||||||
|
var getUploadUrl = Restangular.one('filedrop/');
|
||||||
|
getUploadUrl.customPOST(data).then(function(resp) {
|
||||||
|
conductUpload(repo, file, resp.url, resp.file_id, mimeType);
|
||||||
|
}, function() {
|
||||||
|
$('#couldnotbuildModal').modal();
|
||||||
|
$location.path('/repository/' + repo.namespace + '/' + repo.name);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
$scope.$watch( function () { return UserService.currentUser(); }, function (currentUser) {
|
$scope.$watch( function () { return UserService.currentUser(); }, function (currentUser) {
|
||||||
$scope.user = currentUser;
|
$scope.user = currentUser;
|
||||||
|
|
||||||
if ($scope.user.anonymous) {
|
if ($scope.user.anonymous) {
|
||||||
document.location = '/signin/';
|
$location.path('/signin/');
|
||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
|
@ -931,4 +1013,36 @@ function NewRepoCtrl($scope, UserService) {
|
||||||
$('#editModal').modal('hide');
|
$('#editModal').modal('hide');
|
||||||
$scope.repo.description = $('#wmd-input-description')[0].value;
|
$scope.repo.description = $('#wmd-input-description')[0].value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.createNewRepo = function() {
|
||||||
|
var uploader = $('#file-drop')[0];
|
||||||
|
if ($scope.repo.initialize && uploader.files.length < 1) {
|
||||||
|
$('#missingfileModal').modal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.creating = true;
|
||||||
|
var repo = $scope.repo;
|
||||||
|
var data = {
|
||||||
|
'repository': repo.name,
|
||||||
|
'visibility': repo.is_public == '1' ? 'public' : 'private'
|
||||||
|
};
|
||||||
|
|
||||||
|
var createPost = Restangular.one('repository');
|
||||||
|
createPost.customPOST(data).then(function(created) {
|
||||||
|
$scope.creating = false;
|
||||||
|
|
||||||
|
// Repository created. Start the upload process if applicable.
|
||||||
|
if ($scope.repo.initialize) {
|
||||||
|
startFileUpload(created);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, redirect to the repo page.
|
||||||
|
$location.path('/repository/' + created.namespace + '/' + created.name);
|
||||||
|
}, function() {
|
||||||
|
$('#cannotcreateModal').modal();
|
||||||
|
$scope.creating = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
8
static/lib/angular-strap.min.js
vendored
Normal file
8
static/lib/angular-strap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
5
static/partials/build-status-item.html
Normal file
5
static/partials/build-status-item.html
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<div class="build-statuses">
|
||||||
|
<div ng-repeat="build in buildsInfo">
|
||||||
|
<div class="build-status" build="build"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -1,4 +1,21 @@
|
||||||
<div class="container new-repo" ng-show="!user.anonymous">
|
<div class="container" ng-show="building">
|
||||||
|
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container" ng-show="creating">
|
||||||
|
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container" ng-show="uploading">
|
||||||
|
<span class="message">Uploading file {{ upload_file }}</span>
|
||||||
|
<div class="progress progress-striped active">
|
||||||
|
<div class="progress-bar" role="progressbar" aria-valuenow="{{ upload_progress }}" aria-valuemin="0" aria-valuemax="100" style="{{ 'width: ' + upload_progress + '%' }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container new-repo" ng-show="!user.anonymous && !creating && !uploading && !building">
|
||||||
<form method="post" name="newRepoForm" ng-submit="createNewRepo()">
|
<form method="post" name="newRepoForm" ng-submit="createNewRepo()">
|
||||||
|
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
|
@ -65,7 +82,7 @@
|
||||||
Upload a Dockerfile or a zip file containing a Dockerfile <b>in the root directory</b>
|
Upload a Dockerfile or a zip file containing a Dockerfile <b>in the root directory</b>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input class="file-drop" type="file">
|
<input id="file-drop" class="file-drop" type="file">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -74,7 +91,7 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-1"></div>
|
<div class="col-md-1"></div>
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<button class="btn btn-large btn-success" type="submit" ng-disabled="newRepoForm.$invalid">Create Repository</button>
|
<button class="btn btn-large btn-success" type="submit" ng-click="createNewRepo" ng-disabled="newRepoForm.$invalid">Create Repository</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -105,3 +122,60 @@
|
||||||
</div><!-- /.modal-dialog -->
|
</div><!-- /.modal-dialog -->
|
||||||
</div><!-- /.modal -->
|
</div><!-- /.modal -->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Modal message dialog -->
|
||||||
|
<div class="modal fade" id="missingfileModal">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
<h4 class="modal-title">File required</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
A file is required in order to initialize a repository.
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div><!-- /.modal-content -->
|
||||||
|
</div><!-- /.modal-dialog -->
|
||||||
|
</div><!-- /.modal -->
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Modal message dialog -->
|
||||||
|
<div class="modal fade" id="cannotcreateModal">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
<h4 class="modal-title">Cannot create repository</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
The repository could not be created.
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div><!-- /.modal-content -->
|
||||||
|
</div><!-- /.modal-dialog -->
|
||||||
|
</div><!-- /.modal -->
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Modal message dialog -->
|
||||||
|
<div class="modal fade" id="couldnotbuildModal">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
<h4 class="modal-title">Cannot initialize repository</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
The repository could not be initialized with the selected Dockerfile. Please try again later.
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div><!-- /.modal-content -->
|
||||||
|
</div><!-- /.modal-dialog -->
|
||||||
|
</div><!-- /.modal -->
|
||||||
|
|
|
@ -124,7 +124,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="repo-access-state" ng-show="repo.is_public">
|
<div class="repo-access-state" ng-show="repo.is_public">
|
||||||
<div class="state-icon"><i class="fa fa-unlock-alt"></i></div>
|
<div class="state-icon"><i class="fa fa-unlock"></i></div>
|
||||||
|
|
||||||
This repository is currently <b>public</b> and is visible to all users, and may be pulled by all users.
|
This repository is currently <b>public</b> and is visible to all users, and may be pulled by all users.
|
||||||
|
|
||||||
|
|
|
@ -22,9 +22,8 @@
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<!-- Pull command -->
|
<!-- Pull command -->
|
||||||
<div class="pull-command">
|
<div class="pull-command visible-md visible-lg" style="display: none;">
|
||||||
Get this repository:
|
<span class="pull-command-title">Pull repository:</span>
|
||||||
|
|
||||||
<div class="pull-container">
|
<div class="pull-container">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="pull-text" type="text" class="form-control" value="{{ 'docker pull quay.io/' + repo.namespace + '/' + repo.name }}" readonly>
|
<input id="pull-text" type="text" class="form-control" value="{{ 'docker pull quay.io/' + repo.namespace + '/' + repo.name }}" readonly>
|
||||||
|
@ -40,17 +39,32 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Status boxes -->
|
||||||
|
<div class="status-boxes">
|
||||||
|
<div id="buildInfoBox" class="status-box" ng-show="repo.is_building"
|
||||||
|
bs-popover="'static/partials/build-status-item.html'" data-placement="bottom">
|
||||||
|
<span class="title">
|
||||||
|
<i class="fa fa-spinner fa-spin"></i>
|
||||||
|
<b>Building Images</b>
|
||||||
|
</span>
|
||||||
|
<span class="count" ng-class="buildsInfo ? 'visible' : ''"><span>{{ buildsInfo ? buildsInfo.length : '-' }}</span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Description -->
|
<!-- Description -->
|
||||||
<p ng-class="'description lead ' + (repo.can_write ? 'editable' : 'noteditable')" ng-click="editDescription()">
|
<div class="description">
|
||||||
<span class="content" ng-bind-html-unsafe="getMarkedDown(repo.description)"></span>
|
<p ng-class="'lead ' + (repo.can_write ? 'editable' : 'noteditable')" ng-click="editDescription()">
|
||||||
<i class="fa fa-edit"></i>
|
<span class="content" ng-bind-html-unsafe="getMarkedDown(repo.description)"></span>
|
||||||
</p>
|
<i class="fa fa-edit"></i>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="repo-content" ng-show="!currentTag.image">
|
<!-- Empty message -->
|
||||||
|
<div class="repo-content" ng-show="!currentTag.image && !repo.is_building">
|
||||||
<div class="empty-message">(This repository is empty)</div>
|
<div class="empty-message">(This repository is empty)</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Content view -->
|
||||||
<div class="repo-content" ng-show="currentTag.image">
|
<div class="repo-content" ng-show="currentTag.image">
|
||||||
<!-- Image History -->
|
<!-- Image History -->
|
||||||
<div id="image-history">
|
<div id="image-history">
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
<script src="//cdn.jsdelivr.net/underscorejs/1.5.2/underscore-min.js"></script>
|
<script src="//cdn.jsdelivr.net/underscorejs/1.5.2/underscore-min.js"></script>
|
||||||
<script src="//cdn.jsdelivr.net/restangular/1.1.3/restangular.js"></script>
|
<script src="//cdn.jsdelivr.net/restangular/1.1.3/restangular.js"></script>
|
||||||
|
|
||||||
|
<script src="static/lib/angular-strap.min.js"></script>
|
||||||
<script src="static/lib/angulartics.js"></script>
|
<script src="static/lib/angulartics.js"></script>
|
||||||
<script src="static/lib/angulartics-mixpanel.js"></script>
|
<script src="static/lib/angulartics-mixpanel.js"></script>
|
||||||
|
|
||||||
|
|
Binary file not shown.
Reference in a new issue