Merge remote-tracking branch 'origin/master' into touchdown
Conflicts: static/js/app.js
This commit is contained in:
commit
73a0cc791b
48 changed files with 772 additions and 386 deletions
|
@ -235,6 +235,26 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
|
|||
};
|
||||
|
||||
dataFileService.tryAsTarGz_ = function(buf, success, failure) {
|
||||
var gunzip = new Zlib.Gunzip(buf);
|
||||
var plain = null;
|
||||
|
||||
try {
|
||||
plain = gunzip.decompress();
|
||||
} catch (e) {
|
||||
failure();
|
||||
return;
|
||||
}
|
||||
|
||||
dataFileService.arrayToString(plain, function(result) {
|
||||
if (result) {
|
||||
dataFileService.tryAsTarGzWithStringData_(result, success, failure);
|
||||
} else {
|
||||
failure();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
dataFileService.tryAsTarGzWithStringData_ = function(strData, success, failure) {
|
||||
var collapsePath = function(originalPath) {
|
||||
// Tar files can contain entries of the form './', so we need to collapse
|
||||
// those paths down.
|
||||
|
@ -248,12 +268,9 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
|
|||
return parts.join('/');
|
||||
};
|
||||
|
||||
var gunzip = new Zlib.Gunzip(buf);
|
||||
var plain = gunzip.decompress();
|
||||
|
||||
var handler = new MultiFile();
|
||||
handler.files = [];
|
||||
handler.processTarChunks(dataFileService.arrayToString(plain), 0);
|
||||
handler.processTarChunks(strData, 0);
|
||||
if (!handler.files.length) {
|
||||
failure();
|
||||
return;
|
||||
|
@ -288,8 +305,19 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
|
|||
reader.readAsText(blob);
|
||||
};
|
||||
|
||||
dataFileService.arrayToString = function(buf) {
|
||||
return String.fromCharCode.apply(null, new Uint16Array(buf));
|
||||
dataFileService.arrayToString = function(buf, callback) {
|
||||
var bb = new Blob([buf], {type: 'application/octet-binary'});
|
||||
var f = new FileReader();
|
||||
f.onload = function(e) {
|
||||
callback(e.target.result);
|
||||
};
|
||||
f.onerror = function(e) {
|
||||
callback(null);
|
||||
};
|
||||
f.onabort = function(e) {
|
||||
callback(null);
|
||||
};
|
||||
f.readAsText(bb);
|
||||
};
|
||||
|
||||
dataFileService.readDataArrayAsPossibleArchive = function(buf, success, failure) {
|
||||
|
@ -394,7 +422,7 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
|
|||
builderService.getDescription = function(name, config) {
|
||||
switch (name) {
|
||||
case 'github':
|
||||
var source = $sanitize(UtilService.textToSafeHtml(config['build_source']));
|
||||
var source = UtilService.textToSafeHtml(config['build_source']);
|
||||
var desc = '<i class="fa fa-github fa-lg" style="margin-left: 2px; margin-right: 2px"></i> Push to Github Repository ';
|
||||
desc += '<a href="https://github.com/' + source + '" target="_blank">' + source + '</a>';
|
||||
desc += '<br>Dockerfile folder: //' + UtilService.textToSafeHtml(config['subdir']);
|
||||
|
@ -778,7 +806,18 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
|
|||
olark('api.chat.updateVisitorStatus', {snippet: 'username: ' + userResponse.username});
|
||||
}
|
||||
|
||||
if (window.Raven !== undefined) {
|
||||
Raven.setUser({
|
||||
email: userResponse.email,
|
||||
id: userResponse.username
|
||||
});
|
||||
}
|
||||
|
||||
CookieService.putPermanent('quay.loggedin', 'true');
|
||||
} else {
|
||||
if (window.Raven !== undefined) {
|
||||
Raven.setUser();
|
||||
}
|
||||
}
|
||||
|
||||
if (opt_callback) {
|
||||
|
@ -1059,7 +1098,9 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
|
|||
planDict[data.plans[i].stripeId] = data.plans[i];
|
||||
}
|
||||
plans = data.plans;
|
||||
callback(plans);
|
||||
if (plans) {
|
||||
callback(plans);
|
||||
}
|
||||
}, function() { callback([]); });
|
||||
};
|
||||
|
||||
|
@ -1155,7 +1196,7 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
|
|||
|
||||
planService.getCardInfo(orgname, function(cardInfo) {
|
||||
if (plan.price > 0 && (previousSubscribeFailure || !cardInfo.last4)) {
|
||||
var title = cardInfo.last4 ? 'Subscribe' : 'Start Free trial<span style="display:none">{{amount}}</span>';
|
||||
var title = cardInfo.last4 ? 'Subscribe' : 'Start Trial ({{amount}} plan)';
|
||||
planService.showSubscribeDialog($scope, orgname, planId, callbacks, title);
|
||||
return;
|
||||
}
|
||||
|
@ -1374,6 +1415,17 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
|
|||
}]);
|
||||
}
|
||||
|
||||
if (window.__config && window.__config.SENTRY_PUBLIC_DSN) {
|
||||
quayApp.config(function($provide) {
|
||||
$provide.decorator("$exceptionHandler", function($delegate) {
|
||||
return function(ex, cause) {
|
||||
$delegate(ex, cause);
|
||||
Raven.captureException(ex, {extra: {cause: cause}});
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function buildConditionalLinker($animate, name, evaluator) {
|
||||
// Based off of a solution found here: http://stackoverflow.com/questions/20325480/angularjs-whats-the-best-practice-to-add-ngif-to-a-directive-programmatically
|
||||
|
@ -1552,7 +1604,7 @@ quayApp.directive('entityReference', function () {
|
|||
'entity': '=entity',
|
||||
'namespace': '=namespace'
|
||||
},
|
||||
controller: function($scope, $element, UserService, $sanitize) {
|
||||
controller: function($scope, $element, UserService, UtilService) {
|
||||
$scope.getIsAdmin = function(namespace) {
|
||||
return UserService.isNamespaceAdmin(namespace);
|
||||
};
|
||||
|
@ -1570,10 +1622,10 @@ quayApp.directive('entityReference', function () {
|
|||
var org = UserService.getOrganization(namespace);
|
||||
if (!org) {
|
||||
// This robot is owned by the user.
|
||||
return '/user/?tab=robots&showRobot=' + $sanitize(name);
|
||||
return '/user/?tab=robots&showRobot=' + UtilService.textToSafeHtml(name);
|
||||
}
|
||||
|
||||
return '/organization/' + org['name'] + '/admin?tab=robots&showRobot=' + $sanitize(name);
|
||||
return '/organization/' + org['name'] + '/admin?tab=robots&showRobot=' + UtilService.textToSafeHtml(name);
|
||||
};
|
||||
|
||||
$scope.getPrefix = function(name) {
|
||||
|
@ -4100,7 +4152,7 @@ quayApp.directive('dockerfileCommand', function () {
|
|||
scope: {
|
||||
'command': '=command'
|
||||
},
|
||||
controller: function($scope, $element, $sanitize, Config) {
|
||||
controller: function($scope, $element, UtilService, Config) {
|
||||
var registryHandlers = {
|
||||
'quay.io': function(pieces) {
|
||||
var rnamespace = pieces[pieces.length - 2];
|
||||
|
@ -4137,11 +4189,11 @@ quayApp.directive('dockerfileCommand', function () {
|
|||
$scope.getCommandTitleHtml = function(title) {
|
||||
var space = title.indexOf(' ');
|
||||
if (space <= 0) {
|
||||
return $sanitize(title);
|
||||
return UtilService.textToSafeHtml(title);
|
||||
}
|
||||
|
||||
var kind = $scope.getCommandKind(title);
|
||||
var sanitized = $sanitize(title.substring(space + 1));
|
||||
var sanitized = UtilService.textToSafeHtml(title.substring(space + 1));
|
||||
|
||||
var handler = kindHandlers[kind || ''];
|
||||
if (handler) {
|
||||
|
@ -4166,7 +4218,7 @@ quayApp.directive('dockerfileView', function () {
|
|||
scope: {
|
||||
'contents': '=contents'
|
||||
},
|
||||
controller: function($scope, $element, $sanitize) {
|
||||
controller: function($scope, $element, UtilService) {
|
||||
$scope.$watch('contents', function(contents) {
|
||||
$scope.lines = [];
|
||||
|
||||
|
@ -4181,7 +4233,7 @@ quayApp.directive('dockerfileView', function () {
|
|||
}
|
||||
|
||||
var lineInfo = {
|
||||
'text': $sanitize(line),
|
||||
'text': UtilService.textToSafeHtml(line),
|
||||
'kind': kind
|
||||
};
|
||||
$scope.lines.push(lineInfo);
|
||||
|
@ -4232,6 +4284,9 @@ quayApp.directive('buildMessage', function () {
|
|||
|
||||
case 'waiting':
|
||||
return 'Waiting for available build worker';
|
||||
|
||||
case 'pulling':
|
||||
return 'Pulling base image';
|
||||
|
||||
case 'building':
|
||||
return 'Building image from Dockerfile';
|
||||
|
@ -4265,10 +4320,14 @@ quayApp.directive('buildProgress', function () {
|
|||
controller: function($scope, $element) {
|
||||
$scope.getPercentage = function(buildInfo) {
|
||||
switch (buildInfo.phase) {
|
||||
case 'pulling':
|
||||
return buildInfo.status.pull_completion * 100;
|
||||
break;
|
||||
|
||||
case 'building':
|
||||
return (buildInfo.status.current_command / buildInfo.status.total_commands) * 100;
|
||||
break;
|
||||
|
||||
|
||||
case 'pushing':
|
||||
return buildInfo.status.push_completion * 100;
|
||||
break;
|
||||
|
@ -4810,6 +4869,9 @@ quayApp.run(['$location', '$rootScope', 'Restangular', 'UserService', 'PlanServi
|
|||
|
||||
$rootScope.$on('$routeChangeSuccess', function (event, current, previous) {
|
||||
$rootScope.pageClass = '';
|
||||
$rootScope.current = current.$$route;
|
||||
|
||||
if (!current.$$route) { return; }
|
||||
|
||||
if (current.$$route.title) {
|
||||
$rootScope.title = current.$$route.title;
|
||||
|
@ -4826,7 +4888,6 @@ quayApp.run(['$location', '$rootScope', 'Restangular', 'UserService', 'PlanServi
|
|||
}
|
||||
|
||||
$rootScope.fixFooter = !!current.$$route.fixFooter;
|
||||
$rootScope.current = current.$$route;
|
||||
});
|
||||
|
||||
$rootScope.$on('$viewContentLoaded', function(event, current) {
|
||||
|
|
|
@ -782,33 +782,34 @@ function RepoCtrl($scope, $sanitize, Restangular, ImageMetadataService, ApiServi
|
|||
}
|
||||
|
||||
// Create the new tree.
|
||||
$scope.tree = new ImageHistoryTree(namespace, name, resp.images,
|
||||
var tree = new ImageHistoryTree(namespace, name, resp.images,
|
||||
getFirstTextLine, $scope.getTimeSince, ImageMetadataService.getEscapedFormattedCommand);
|
||||
|
||||
$scope.tree.draw('image-history-container');
|
||||
$scope.tree = tree.draw('image-history-container');
|
||||
if ($scope.tree) {
|
||||
// If we already have a tag, use it
|
||||
if ($scope.currentTag) {
|
||||
$scope.tree.setTag($scope.currentTag.name);
|
||||
}
|
||||
|
||||
// If we already have a tag, use it
|
||||
if ($scope.currentTag) {
|
||||
$scope.tree.setTag($scope.currentTag.name);
|
||||
// Listen for changes to the selected tag and image in the tree.
|
||||
$($scope.tree).bind('tagChanged', function(e) {
|
||||
$scope.$apply(function() { $scope.setTag(e.tag, true); });
|
||||
});
|
||||
|
||||
$($scope.tree).bind('imageChanged', function(e) {
|
||||
$scope.$apply(function() { $scope.setImage(e.image.id, true); });
|
||||
});
|
||||
|
||||
$($scope.tree).bind('showTagMenu', function(e) {
|
||||
$scope.$apply(function() { $scope.showTagMenu(e.tag, e.clientX, e.clientY); });
|
||||
});
|
||||
|
||||
$($scope.tree).bind('hideTagMenu', function(e) {
|
||||
$scope.$apply(function() { $scope.hideTagMenu(); });
|
||||
});
|
||||
}
|
||||
|
||||
// Listen for changes to the selected tag and image in the tree.
|
||||
$($scope.tree).bind('tagChanged', function(e) {
|
||||
$scope.$apply(function() { $scope.setTag(e.tag, true); });
|
||||
});
|
||||
|
||||
$($scope.tree).bind('imageChanged', function(e) {
|
||||
$scope.$apply(function() { $scope.setImage(e.image.id, true); });
|
||||
});
|
||||
|
||||
$($scope.tree).bind('showTagMenu', function(e) {
|
||||
$scope.$apply(function() { $scope.showTagMenu(e.tag, e.clientX, e.clientY); });
|
||||
});
|
||||
|
||||
$($scope.tree).bind('hideTagMenu', function(e) {
|
||||
$scope.$apply(function() { $scope.hideTagMenu(); });
|
||||
});
|
||||
|
||||
if ($routeParams.image) {
|
||||
$scope.setImage($routeParams.image);
|
||||
}
|
||||
|
@ -892,7 +893,7 @@ function BuildPackageCtrl($scope, Restangular, ApiService, DataFileService, $rou
|
|||
if (dockerfile && dockerfile.canRead) {
|
||||
DataFileService.blobToString(dockerfile.toBlob(), function(result) {
|
||||
$scope.$apply(function() {
|
||||
$scope.dockerFilePath = dockerfilePath;
|
||||
$scope.dockerFilePath = dockerfilePath || 'Dockerfile';
|
||||
$scope.dockerFileContents = result;
|
||||
});
|
||||
});
|
||||
|
@ -902,8 +903,11 @@ function BuildPackageCtrl($scope, Restangular, ApiService, DataFileService, $rou
|
|||
};
|
||||
|
||||
var notarchive = function() {
|
||||
$scope.dockerFileContents = DataFileService.arrayToString(uint8array);
|
||||
$scope.loaded = true;
|
||||
DataFileService.arrayToString(uint8array, function(r) {
|
||||
$scope.dockerFilePath = 'Dockerfile';
|
||||
$scope.dockerFileContents = r;
|
||||
$scope.loaded = true;
|
||||
});
|
||||
};
|
||||
|
||||
DataFileService.readDataArrayAsPossibleArchive(uint8array, archiveread, notarchive);
|
||||
|
@ -2386,10 +2390,10 @@ function NewOrgCtrl($scope, $routeParams, $timeout, $location, UserService, Plan
|
|||
// Load the list of plans.
|
||||
PlanService.getPlans(function(plans) {
|
||||
$scope.plans = plans;
|
||||
$scope.currentPlan = null;
|
||||
$scope.holder.currentPlan = null;
|
||||
if (requested) {
|
||||
PlanService.getPlan(requested, function(plan) {
|
||||
$scope.currentPlan = plan;
|
||||
$scope.holder.currentPlan = plan;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -2410,7 +2414,7 @@ function NewOrgCtrl($scope, $routeParams, $timeout, $location, UserService, Plan
|
|||
};
|
||||
|
||||
$scope.setPlan = function(plan) {
|
||||
$scope.currentPlan = plan;
|
||||
$scope.holder.currentPlan = plan;
|
||||
};
|
||||
|
||||
$scope.createNewOrg = function() {
|
||||
|
@ -2438,7 +2442,7 @@ function NewOrgCtrl($scope, $routeParams, $timeout, $location, UserService, Plan
|
|||
};
|
||||
|
||||
// If the selected plan is free, simply move to the org page.
|
||||
if (!Features.BILLING || $scope.currentPlan.price == 0) {
|
||||
if (!Features.BILLING || $scope.holder.currentPlan.price == 0) {
|
||||
showOrg();
|
||||
return;
|
||||
}
|
||||
|
@ -2452,7 +2456,7 @@ function NewOrgCtrl($scope, $routeParams, $timeout, $location, UserService, Plan
|
|||
'failure': showOrg
|
||||
};
|
||||
|
||||
PlanService.changePlan($scope, org.name, $scope.currentPlan.stripeId, callbacks);
|
||||
PlanService.changePlan($scope, org.name, $scope.holder.currentPlan.stripeId, callbacks);
|
||||
}, function(result) {
|
||||
$scope.creating = false;
|
||||
$scope.createError = result.data.message || result.data;
|
||||
|
|
|
@ -186,6 +186,11 @@ ImageHistoryTree.prototype.draw = function(container) {
|
|||
// Save the container.
|
||||
this.container_ = container;
|
||||
|
||||
if (!$('#' + container)[0]) {
|
||||
this.container_ = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the tree and all its components.
|
||||
var tree = d3.layout.tree()
|
||||
.separation(function() { return 2; });
|
||||
|
@ -193,11 +198,10 @@ ImageHistoryTree.prototype.draw = function(container) {
|
|||
var diagonal = d3.svg.diagonal()
|
||||
.projection(function(d) { return [d.x, d.y]; });
|
||||
|
||||
var rootSvg = d3.select("#" + container).append("svg:svg")
|
||||
var rootSvg = d3.select("#" + container).append("svg:svg")
|
||||
.attr("class", "image-tree");
|
||||
|
||||
var vis = rootSvg.append("svg:g");
|
||||
|
||||
var formatComment = this.formatComment_;
|
||||
var formatTime = this.formatTime_;
|
||||
var formatCommand = this.formatCommand_;
|
||||
|
@ -262,6 +266,8 @@ ImageHistoryTree.prototype.draw = function(container) {
|
|||
|
||||
this.setTag_(this.currentTag_);
|
||||
this.setupOverscroll_();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
|
@ -1129,7 +1135,12 @@ FileTreeBase.prototype.update_ = function(source) {
|
|||
};
|
||||
|
||||
// Update the height of the container and the SVG.
|
||||
document.getElementById(this.container_).style.height = this.getContainerHeight_() + 'px';
|
||||
var containerElm = document.getElementById(this.container_);
|
||||
if (!containerElm) {
|
||||
return;
|
||||
}
|
||||
|
||||
containerElm.style.height = this.getContainerHeight_() + 'px';
|
||||
svg.attr('height', this.getContainerHeight_());
|
||||
|
||||
// Compute the flattened node list.
|
||||
|
@ -1691,7 +1702,12 @@ LogUsageChart.prototype.handleStateChange_ = function(e) {
|
|||
*/
|
||||
LogUsageChart.prototype.draw = function(container, logData, startDate, endDate) {
|
||||
// Reset the container's contents.
|
||||
document.getElementById(container).innerHTML = '<svg></svg>';
|
||||
var containerElm = document.getElementById(container);
|
||||
if (!containerElm) {
|
||||
return;
|
||||
}
|
||||
|
||||
containerElm.innerHTML = '<svg></svg>';
|
||||
|
||||
// Returns a date offset from the given date by "days" Days.
|
||||
var offsetDate = function(d, days) {
|
||||
|
|
Reference in a new issue