Merge remote-tracking branch 'origin/master' into swaggerlikeus
Conflicts: data/database.py endpoints/api.py endpoints/common.py templates/base.html test/data/test.db test/specs.py
This commit is contained in:
commit
c93c62600d
59 changed files with 4636 additions and 216 deletions
364
static/js/app.js
364
static/js/app.js
|
@ -102,9 +102,88 @@ function getMarkedDown(string) {
|
|||
return Markdown.getSanitizingConverter().makeHtml(string || '');
|
||||
}
|
||||
|
||||
quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angular-tour', 'restangular', 'angularMoment', 'angulartics', /*'angulartics.google.analytics',*/ 'angulartics.mixpanel', '$strap.directives', 'ngCookies', 'ngSanitize', 'angular-md5', 'pasvaz.bindonce', 'ansiToHtml', 'ngAnimate'], function($provide, cfpLoadingBarProvider) {
|
||||
quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angular-tour', 'restangular', 'angularMoment', 'angulartics', /*'angulartics.google.analytics',*/ 'angulartics.mixpanel', 'mgcrea.ngStrap', 'ngCookies', 'ngSanitize', 'angular-md5', 'pasvaz.bindonce', 'ansiToHtml', 'ngAnimate'], function($provide, cfpLoadingBarProvider) {
|
||||
cfpLoadingBarProvider.includeSpinner = false;
|
||||
|
||||
/**
|
||||
* Specialized wrapper around array which provides a toggle() method for viewing the contents of the
|
||||
* array in a manner that is asynchronously filled in over a short time period. This prevents long
|
||||
* pauses in the UI for ngRepeat's when the array is significant in size.
|
||||
*/
|
||||
$provide.factory('AngularViewArray', ['$interval', function($interval) {
|
||||
var ADDTIONAL_COUNT = 50;
|
||||
|
||||
function _ViewArray() {
|
||||
this.isVisible = false;
|
||||
this.visibleEntries = null;
|
||||
this.hasEntries = false;
|
||||
this.entries = [];
|
||||
|
||||
this.timerRef_ = null;
|
||||
this.currentIndex_ = 0;
|
||||
}
|
||||
|
||||
_ViewArray.prototype.push = function(elem) {
|
||||
this.entries.push(elem);
|
||||
this.hasEntries = true;
|
||||
|
||||
if (this.isVisible) {
|
||||
this.setVisible(true);
|
||||
}
|
||||
};
|
||||
|
||||
_ViewArray.prototype.toggle = function() {
|
||||
this.setVisible(!this.isVisible);
|
||||
};
|
||||
|
||||
_ViewArray.prototype.setVisible = function(newState) {
|
||||
this.isVisible = newState;
|
||||
|
||||
this.visibleEntries = [];
|
||||
this.currentIndex_ = 0;
|
||||
|
||||
if (newState) {
|
||||
this.showAdditionalEntries_();
|
||||
this.startTimer_();
|
||||
} else {
|
||||
this.stopTimer_();
|
||||
}
|
||||
};
|
||||
|
||||
_ViewArray.prototype.showAdditionalEntries_ = function() {
|
||||
var i = 0;
|
||||
for (i = this.currentIndex_; i < (this.currentIndex_ + ADDTIONAL_COUNT) && i < this.entries.length; ++i) {
|
||||
this.visibleEntries.push(this.entries[i]);
|
||||
}
|
||||
|
||||
this.currentIndex_ = i;
|
||||
if (this.currentIndex_ >= this.entries.length) {
|
||||
this.stopTimer_();
|
||||
}
|
||||
};
|
||||
|
||||
_ViewArray.prototype.startTimer_ = function() {
|
||||
var that = this;
|
||||
this.timerRef_ = $interval(function() {
|
||||
that.showAdditionalEntries_();
|
||||
}, 10);
|
||||
};
|
||||
|
||||
_ViewArray.prototype.stopTimer_ = function() {
|
||||
if (this.timerRef_) {
|
||||
$interval.cancel(this.timerRef_);
|
||||
this.timerRef_ = null;
|
||||
}
|
||||
};
|
||||
|
||||
var service = {
|
||||
'create': function() {
|
||||
return new _ViewArray();
|
||||
}
|
||||
};
|
||||
|
||||
return service;
|
||||
}]);
|
||||
|
||||
$provide.factory('UtilService', ['$sanitize', function($sanitize) {
|
||||
var utilService = {};
|
||||
|
@ -143,6 +222,49 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu
|
|||
return builderService;
|
||||
}]);
|
||||
|
||||
$provide.factory('StringBuilderService', ['$sce', function($sce) {
|
||||
var stringBuilderService = {};
|
||||
|
||||
stringBuilderService.buildString = function(value_or_func, metadata) {
|
||||
var fieldIcons = {
|
||||
'username': 'user',
|
||||
'activating_username': 'user',
|
||||
'delegate_user': 'user',
|
||||
'delegate_team': 'group',
|
||||
'team': 'group',
|
||||
'token': 'key',
|
||||
'repo': 'hdd-o',
|
||||
'robot': 'wrench',
|
||||
'tag': 'tag',
|
||||
'role': 'th-large',
|
||||
'original_role': 'th-large'
|
||||
};
|
||||
|
||||
var description = value_or_func;
|
||||
if (typeof description != 'string') {
|
||||
description = description(metadata);
|
||||
}
|
||||
|
||||
for (var key in metadata) {
|
||||
if (metadata.hasOwnProperty(key)) {
|
||||
var value = metadata[key] != null ? metadata[key].toString() : '(Unknown)';
|
||||
var markedDown = getMarkedDown(value);
|
||||
markedDown = markedDown.substr('<p>'.length, markedDown.length - '<p></p>'.length);
|
||||
|
||||
var icon = fieldIcons[key];
|
||||
if (icon) {
|
||||
markedDown = '<i class="fa fa-' + icon + '"></i>' + markedDown;
|
||||
}
|
||||
|
||||
description = description.replace('{' + key + '}', '<code>' + markedDown + '</code>');
|
||||
}
|
||||
}
|
||||
return $sce.trustAsHtml(description.replace('\n', '<br>'));
|
||||
};
|
||||
|
||||
return stringBuilderService;
|
||||
}]);
|
||||
|
||||
|
||||
$provide.factory('ImageMetadataService', ['UtilService', function(UtilService) {
|
||||
var metadataService = {};
|
||||
|
@ -360,7 +482,6 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu
|
|||
anonymous: true,
|
||||
username: null,
|
||||
email: null,
|
||||
askForPassword: false,
|
||||
organizations: [],
|
||||
logins: []
|
||||
}
|
||||
|
@ -467,6 +588,101 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu
|
|||
return userService;
|
||||
}]);
|
||||
|
||||
$provide.factory('NotificationService', ['$rootScope', '$interval', 'UserService', 'ApiService', 'StringBuilderService', 'PlanService', 'UserService',
|
||||
function($rootScope, $interval, UserService, ApiService, StringBuilderService, PlanService, UserService) {
|
||||
var notificationService = {
|
||||
'user': null,
|
||||
'notifications': [],
|
||||
'notificationClasses': [],
|
||||
'notificationSummaries': []
|
||||
};
|
||||
|
||||
var pollTimerHandle = null;
|
||||
|
||||
var notificationKinds = {
|
||||
'test_notification': {
|
||||
'level': 'primary',
|
||||
'message': 'This notification is a long message for testing',
|
||||
'page': '/about/'
|
||||
},
|
||||
'password_required': {
|
||||
'level': 'error',
|
||||
'message': 'In order to begin pushing and pulling repositories to Quay.io, a password must be set for your account',
|
||||
'page': '/user?tab=password'
|
||||
},
|
||||
'over_private_usage': {
|
||||
'level': 'error',
|
||||
'message': 'Namespace {namespace} is over its allowed private repository count. ' +
|
||||
'<br><br>Please upgrade your plan to avoid disruptions in service.',
|
||||
'page': function(metadata) {
|
||||
var organization = UserService.getOrganization(metadata['namespace']);
|
||||
if (organization) {
|
||||
return '/organization/' + metadata['namespace'] + '/admin';
|
||||
} else {
|
||||
return '/user';
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
notificationService.getPage = function(notification) {
|
||||
var page = notificationKinds[notification['kind']]['page'];
|
||||
if (typeof page != 'string') {
|
||||
page = page(notification['metadata']);
|
||||
}
|
||||
return page;
|
||||
};
|
||||
|
||||
notificationService.getMessage = function(notification) {
|
||||
var kindInfo = notificationKinds[notification['kind']];
|
||||
return StringBuilderService.buildString(kindInfo['message'], notification['metadata']);
|
||||
};
|
||||
|
||||
notificationService.getClass = function(notification) {
|
||||
return 'notification-' + notificationKinds[notification['kind']]['level'];
|
||||
};
|
||||
|
||||
notificationService.getClasses = function(notifications) {
|
||||
var classes = [];
|
||||
for (var i = 0; i < notifications.length; ++i) {
|
||||
var notification = notifications[i];
|
||||
classes.push(notificationService.getClass(notification));
|
||||
}
|
||||
return classes.join(' ');
|
||||
};
|
||||
|
||||
notificationService.update = function() {
|
||||
var user = UserService.currentUser();
|
||||
if (!user || user.anonymous) {
|
||||
return;
|
||||
}
|
||||
|
||||
ApiService.listUserNotifications().then(function(resp) {
|
||||
notificationService.notifications = resp['notifications'];
|
||||
notificationService.notificationClasses = notificationService.getClasses(notificationService.notifications);
|
||||
});
|
||||
};
|
||||
|
||||
notificationService.reset = function() {
|
||||
$interval.cancel(pollTimerHandle);
|
||||
pollTimerHandle = $interval(notificationService.update, 5 * 60 * 1000 /* five minutes */);
|
||||
};
|
||||
|
||||
// Watch for plan changes and update.
|
||||
PlanService.registerListener(this, function(plan) {
|
||||
notificationService.reset();
|
||||
notificationService.update();
|
||||
});
|
||||
|
||||
// Watch for user changes and update.
|
||||
$rootScope.$watch(function() { return UserService.currentUser(); }, function(currentUser) {
|
||||
notificationService.reset();
|
||||
notificationService.update();
|
||||
});
|
||||
|
||||
return notificationService;
|
||||
}]);
|
||||
|
||||
$provide.factory('KeyService', ['$location', function($location) {
|
||||
var keyService = {}
|
||||
|
||||
|
@ -1405,7 +1621,7 @@ quayApp.directive('logsView', function () {
|
|||
'repository': '=repository',
|
||||
'performer': '=performer'
|
||||
},
|
||||
controller: function($scope, $element, $sce, Restangular, ApiService, TriggerDescriptionBuilder) {
|
||||
controller: function($scope, $element, $sce, Restangular, ApiService, TriggerDescriptionBuilder, StringBuilderService) {
|
||||
$scope.loading = true;
|
||||
$scope.logs = null;
|
||||
$scope.kindsAllowed = null;
|
||||
|
@ -1620,43 +1836,9 @@ quayApp.directive('logsView', function () {
|
|||
return $scope.chart.getColor(kind);
|
||||
};
|
||||
|
||||
$scope.getDescription = function(log) {
|
||||
var fieldIcons = {
|
||||
'username': 'user',
|
||||
'activating_username': 'user',
|
||||
'delegate_user': 'user',
|
||||
'delegate_team': 'group',
|
||||
'team': 'group',
|
||||
'token': 'key',
|
||||
'repo': 'hdd-o',
|
||||
'robot': 'wrench',
|
||||
'tag': 'tag',
|
||||
'role': 'th-large',
|
||||
'original_role': 'th-large'
|
||||
};
|
||||
|
||||
$scope.getDescription = function(log) {
|
||||
log.metadata['_ip'] = log.ip ? log.ip : null;
|
||||
|
||||
var description = logDescriptions[log.kind] || log.kind;
|
||||
if (typeof description != 'string') {
|
||||
description = description(log.metadata);
|
||||
}
|
||||
|
||||
for (var key in log.metadata) {
|
||||
if (log.metadata.hasOwnProperty(key)) {
|
||||
var value = log.metadata[key] != null ? log.metadata[key].toString() : '(Unknown)';
|
||||
var markedDown = getMarkedDown(value);
|
||||
markedDown = markedDown.substr('<p>'.length, markedDown.length - '<p></p>'.length);
|
||||
|
||||
var icon = fieldIcons[key];
|
||||
if (icon) {
|
||||
markedDown = '<i class="fa fa-' + icon + '"></i>' + markedDown;
|
||||
}
|
||||
|
||||
description = description.replace('{' + key + '}', '<code>' + markedDown + '</code>');
|
||||
}
|
||||
}
|
||||
return $sce.trustAsHtml(description.replace('\n', '<br>'));
|
||||
return StringBuilderService.buildString(logDescriptions[log.kind] || log.kind, log.metadata);
|
||||
};
|
||||
|
||||
$scope.$watch('organization', update);
|
||||
|
@ -1921,6 +2103,31 @@ quayApp.directive('prototypeManager', function () {
|
|||
});
|
||||
|
||||
|
||||
quayApp.directive('deleteUi', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/delete-ui.html',
|
||||
replace: false,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'deleteTitle': '=deleteTitle',
|
||||
'buttonTitle': '=buttonTitle',
|
||||
'performDelete': '&performDelete'
|
||||
},
|
||||
controller: function($scope, $element) {
|
||||
$scope.buttonTitleInternal = $scope.buttonTitle || 'Delete';
|
||||
|
||||
$element.children().attr('tabindex', 0);
|
||||
$scope.focus = function() {
|
||||
$element[0].firstChild.focus();
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
||||
|
||||
|
||||
quayApp.directive('popupInputButton', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
|
@ -1939,7 +2146,7 @@ quayApp.directive('popupInputButton', function () {
|
|||
var box = $('#input-box');
|
||||
box[0].value = '';
|
||||
box.focus();
|
||||
}, 10);
|
||||
}, 40);
|
||||
};
|
||||
|
||||
$scope.getRegexp = function(pattern) {
|
||||
|
@ -2153,26 +2360,12 @@ quayApp.directive('headerBar', function () {
|
|||
restrict: 'C',
|
||||
scope: {
|
||||
},
|
||||
controller: function($scope, $element, $location, UserService, PlanService, ApiService) {
|
||||
$scope.overPlan = false;
|
||||
|
||||
var checkOverPlan = function() {
|
||||
if ($scope.user.anonymous) {
|
||||
$scope.overPlan = false;
|
||||
return;
|
||||
}
|
||||
|
||||
ApiService.getUserPrivateAllowed().then(function(resp) {
|
||||
$scope.overPlan = !resp['privateAllowed'];
|
||||
});
|
||||
};
|
||||
|
||||
// Monitor any user changes and place the current user into the scope.
|
||||
UserService.updateUserIn($scope, checkOverPlan);
|
||||
controller: function($scope, $element, $location, UserService, PlanService, ApiService, NotificationService) {
|
||||
$scope.notificationService = NotificationService;
|
||||
|
||||
// Monitor any user changes and place the current user into the scope.
|
||||
UserService.updateUserIn($scope);
|
||||
|
||||
// Monitor any plan changes.
|
||||
PlanService.registerListener(this, checkOverPlan);
|
||||
|
||||
$scope.signout = function() {
|
||||
ApiService.logout().then(function() {
|
||||
UserService.load();
|
||||
|
@ -3314,6 +3507,54 @@ quayApp.directive('buildProgress', function () {
|
|||
});
|
||||
|
||||
|
||||
quayApp.directive('notificationView', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/notification-view.html',
|
||||
replace: false,
|
||||
transclude: false,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'notification': '=notification',
|
||||
'parent': '=parent'
|
||||
},
|
||||
controller: function($scope, $element, $location, UserService, NotificationService) {
|
||||
$scope.getMessage = function(notification) {
|
||||
return NotificationService.getMessage(notification);
|
||||
};
|
||||
|
||||
$scope.getGravatar = function(orgname) {
|
||||
var organization = UserService.getOrganization(orgname);
|
||||
return organization['gravatar'] || '';
|
||||
};
|
||||
|
||||
$scope.parseDate = function(dateString) {
|
||||
return Date.parse(dateString);
|
||||
};
|
||||
|
||||
$scope.showNotification = function() {
|
||||
var url = NotificationService.getPage($scope.notification);
|
||||
if (url) {
|
||||
var parts = url.split('?')
|
||||
$location.path(parts[0]);
|
||||
|
||||
if (parts.length > 1) {
|
||||
$location.search(parts[1]);
|
||||
}
|
||||
|
||||
$scope.parent.$hide();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.getClass = function(notification) {
|
||||
return NotificationService.getClass(notification);
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
||||
|
||||
|
||||
quayApp.directive('dockerfileBuildDialog', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
|
@ -3594,6 +3835,11 @@ quayApp.run(['$location', '$rootScope', 'Restangular', 'UserService', 'PlanServi
|
|||
}
|
||||
}
|
||||
|
||||
if (response.status == 500) {
|
||||
document.location = '/500';
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
|
|
Reference in a new issue