parent
d63ec8c6b0
commit
fe735b8048
25 changed files with 784 additions and 614 deletions
|
@ -121,9 +121,21 @@ quayApp.config(['$routeProvider', '$locationProvider', 'pages', function($routeP
|
|||
// Organization View Application
|
||||
.route('/organization/:orgname/application/:clientid', 'manage-application')
|
||||
|
||||
// View Organization Billing
|
||||
.route('/organization/:orgname/billing', 'billing')
|
||||
|
||||
// View Organization Billing Invoices
|
||||
.route('/organization/:orgname/billing/invoices', 'invoices')
|
||||
|
||||
// View User
|
||||
.route('/user/:username', 'user-view')
|
||||
|
||||
// View User Billing
|
||||
.route('/user/:username/billing', 'billing')
|
||||
|
||||
// View User Billing Invoices
|
||||
.route('/user/:username/billing/invoices', 'invoices')
|
||||
|
||||
// Sign In
|
||||
.route('/signin/', 'signin')
|
||||
|
||||
|
|
|
@ -172,10 +172,12 @@ angular.module("core-ui", [])
|
|||
scope: {
|
||||
'dialogTitle': '@dialogTitle',
|
||||
'dialogActionTitle': '@dialogActionTitle',
|
||||
'dialogForm': '=dialogForm',
|
||||
|
||||
'dialogContext': '=dialogContext',
|
||||
'dialogAction': '&dialogAction'
|
||||
},
|
||||
|
||||
controller: function($rootScope, $scope, $element) {
|
||||
$scope.working = false;
|
||||
|
||||
|
@ -195,6 +197,10 @@ angular.module("core-ui", [])
|
|||
};
|
||||
|
||||
$scope.show = function() {
|
||||
if ($scope.dialogForm) {
|
||||
$scope.dialogForm.$setPristine();
|
||||
}
|
||||
|
||||
$scope.working = false;
|
||||
$element.find('.modal').modal({});
|
||||
};
|
||||
|
|
140
static/js/directives/ui/billing-management-panel.js
Normal file
140
static/js/directives/ui/billing-management-panel.js
Normal file
|
@ -0,0 +1,140 @@
|
|||
/**
|
||||
* An element which displays the billing options for a user or an organization.
|
||||
*/
|
||||
angular.module('quay').directive('billingManagementPanel', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/billing-management-panel.html',
|
||||
replace: false,
|
||||
transclude: false,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'user': '=user',
|
||||
'organization': '=organization',
|
||||
'isEnabled': '=isEnabled'
|
||||
},
|
||||
controller: function($scope, $element, PlanService, ApiService) {
|
||||
$scope.currentCard = null;
|
||||
$scope.subscription = null;
|
||||
$scope.updating = true;
|
||||
$scope.changeReceiptsInfo = null;
|
||||
$scope.context = {};
|
||||
|
||||
var setSubscription = function(sub) {
|
||||
$scope.subscription = sub;
|
||||
|
||||
// Load the plan info.
|
||||
PlanService.getPlan(sub['plan'], function(plan) {
|
||||
$scope.currentPlan = plan;
|
||||
|
||||
if (!sub.hasSubscription) {
|
||||
$scope.updating = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Load credit card information.
|
||||
PlanService.getCardInfo($scope.organization ? $scope.organization.name : null, function(card) {
|
||||
$scope.currentCard = card;
|
||||
$scope.updating = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var update = function() {
|
||||
if (!$scope.isEnabled || !($scope.user || $scope.organization)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.entity = $scope.user ? $scope.user : $scope.organization;
|
||||
$scope.invoice_email = $scope.entity.invoice_email;
|
||||
$scope.invoice_email_address = $scope.entity.invoice_email_address || $scope.entity.email;
|
||||
|
||||
$scope.updating = true;
|
||||
|
||||
// Load plan information.
|
||||
PlanService.getSubscription($scope.organization, setSubscription, function() {
|
||||
setSubscription({ 'plan': PlanService.getFreePlan() });
|
||||
});
|
||||
};
|
||||
|
||||
// Listen to plan changes.
|
||||
PlanService.registerListener(this, function(plan) {
|
||||
if (plan && plan.price > 0) {
|
||||
update();
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$on('$destroy', function() {
|
||||
PlanService.unregisterListener(this);
|
||||
});
|
||||
|
||||
$scope.$watch('isEnabled', update);
|
||||
$scope.$watch('organization', update);
|
||||
$scope.$watch('user', update);
|
||||
|
||||
$scope.getEntityPrefix = function() {
|
||||
if ($scope.organization) {
|
||||
return '/organization/' + $scope.organization.name;
|
||||
} else {
|
||||
return '/user/' + $scope.user.username;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.changeCreditCard = function() {
|
||||
var callbacks = {
|
||||
'opened': function() { },
|
||||
'closed': function() { },
|
||||
'started': function() { },
|
||||
'success': function(resp) {
|
||||
$scope.currentCard = resp.card;
|
||||
update();
|
||||
},
|
||||
'failure': function(resp) {
|
||||
if (!PlanService.isCardError(resp)) {
|
||||
bootbox.alert('Could not change credit card. Please try again later.');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
PlanService.changeCreditCard($scope, $scope.organization ? $scope.organization.name : null, callbacks);
|
||||
};
|
||||
|
||||
$scope.getCreditImage = function(creditInfo) {
|
||||
if (!creditInfo || !creditInfo.type) { return 'credit.png'; }
|
||||
|
||||
var kind = creditInfo.type.toLowerCase() || 'credit';
|
||||
var supported = {
|
||||
'american express': 'amex',
|
||||
'credit': 'credit',
|
||||
'diners club': 'diners',
|
||||
'discover': 'discover',
|
||||
'jcb': 'jcb',
|
||||
'mastercard': 'mastercard',
|
||||
'visa': 'visa'
|
||||
};
|
||||
|
||||
kind = supported[kind] || 'credit';
|
||||
return kind + '.png';
|
||||
};
|
||||
|
||||
$scope.changeReceipts = function(info, callback) {
|
||||
$scope.entity['invoice_email'] = info['sendOption'] || false;
|
||||
$scope.entity['invoice_email_address'] = info['address'] || $scope.entity.email;
|
||||
|
||||
var errorHandler = ApiService.errorDisplay('Could not change billing options', callback);
|
||||
ApiService.changeDetails($scope.organization, $scope.entity).then(function(resp) {
|
||||
callback(true);
|
||||
update();
|
||||
}, errorHandler);
|
||||
};
|
||||
|
||||
$scope.showChangeReceipts = function() {
|
||||
$scope.changeReceiptsInfo = {
|
||||
'sendOption': $scope.invoice_email,
|
||||
'address': $scope.invoice_email_address
|
||||
};
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
|
@ -1,130 +0,0 @@
|
|||
/**
|
||||
* An element which displays the billing options for a user or an organization.
|
||||
*/
|
||||
angular.module('quay').directive('billingOptions', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/billing-options.html',
|
||||
replace: false,
|
||||
transclude: false,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'user': '=user',
|
||||
'organization': '=organization'
|
||||
},
|
||||
controller: function($scope, $element, PlanService, ApiService) {
|
||||
$scope.invoice_email = false;
|
||||
$scope.currentCard = null;
|
||||
|
||||
// Listen to plan changes.
|
||||
PlanService.registerListener(this, function(plan) {
|
||||
if (plan && plan.price > 0) {
|
||||
update();
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$on('$destroy', function() {
|
||||
PlanService.unregisterListener(this);
|
||||
});
|
||||
|
||||
$scope.isExpiringSoon = function(cardInfo) {
|
||||
var current = new Date();
|
||||
var expires = new Date(cardInfo.exp_year, cardInfo.exp_month, 1);
|
||||
var difference = expires - current;
|
||||
return difference < (60 * 60 * 24 * 60 * 1000 /* 60 days */);
|
||||
};
|
||||
|
||||
$scope.changeInvoiceEmailAddress = function() {
|
||||
bootbox.prompt('Enter the email address for receiving receipts', function(email) {
|
||||
// Copied from Angular.
|
||||
var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-z0-9-]+(\.[a-z0-9-]+)*$/i;
|
||||
if (!email || !EMAIL_REGEXP.test(email)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.obj['invoice_email_address'] = email;
|
||||
|
||||
var errorHandler = ApiService.errorDisplay('Could not change user details');
|
||||
ApiService.changeDetails($scope.organization, $scope.obj).then(function(resp) {
|
||||
$scope.working = false;
|
||||
}, errorHandler);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.changeCard = function() {
|
||||
var previousCard = $scope.currentCard;
|
||||
$scope.changingCard = true;
|
||||
var callbacks = {
|
||||
'opened': function() { $scope.changingCard = true; },
|
||||
'closed': function() { $scope.changingCard = false; },
|
||||
'started': function() { $scope.currentCard = null; },
|
||||
'success': function(resp) {
|
||||
$scope.currentCard = resp.card;
|
||||
$scope.changingCard = false;
|
||||
},
|
||||
'failure': function(resp) {
|
||||
$scope.changingCard = false;
|
||||
$scope.currentCard = previousCard;
|
||||
|
||||
if (!PlanService.isCardError(resp)) {
|
||||
$('#cannotchangecardModal').modal({});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
PlanService.changeCreditCard($scope, $scope.organization ? $scope.organization.name : null, callbacks);
|
||||
};
|
||||
|
||||
$scope.getCreditImage = function(creditInfo) {
|
||||
if (!creditInfo || !creditInfo.type) { return 'credit.png'; }
|
||||
|
||||
var kind = creditInfo.type.toLowerCase() || 'credit';
|
||||
var supported = {
|
||||
'american express': 'amex',
|
||||
'credit': 'credit',
|
||||
'diners club': 'diners',
|
||||
'discover': 'discover',
|
||||
'jcb': 'jcb',
|
||||
'mastercard': 'mastercard',
|
||||
'visa': 'visa'
|
||||
};
|
||||
|
||||
kind = supported[kind] || 'credit';
|
||||
return kind + '.png';
|
||||
};
|
||||
|
||||
var update = function() {
|
||||
if (!$scope.user && !$scope.organization) { return; }
|
||||
$scope.obj = $scope.user ? $scope.user : $scope.organization;
|
||||
$scope.invoice_email = $scope.obj.invoice_email;
|
||||
|
||||
// Load the credit card information.
|
||||
PlanService.getCardInfo($scope.organization ? $scope.organization.name : null, function(card) {
|
||||
$scope.currentCard = card;
|
||||
});
|
||||
};
|
||||
|
||||
var save = function() {
|
||||
$scope.working = true;
|
||||
|
||||
var errorHandler = ApiService.errorDisplay('Could not change user details');
|
||||
ApiService.changeDetails($scope.organization, $scope.obj).then(function(resp) {
|
||||
$scope.working = false;
|
||||
}, errorHandler);
|
||||
};
|
||||
|
||||
var checkSave = function() {
|
||||
if (!$scope.obj) { return; }
|
||||
if ($scope.obj.invoice_email != $scope.invoice_email) {
|
||||
$scope.obj.invoice_email = $scope.invoice_email;
|
||||
save();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.$watch('invoice_email', checkSave);
|
||||
$scope.$watch('organization', update);
|
||||
$scope.$watch('user', update);
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
|
@ -9,14 +9,29 @@ angular.module('quay').directive('convertUserToOrg', function () {
|
|||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'user': '=user'
|
||||
'info': '=info'
|
||||
},
|
||||
controller: function($scope, $element, $location, Features, PlanService, Config, ApiService, CookieService, UserService) {
|
||||
$scope.convertStep = 0;
|
||||
$scope.org = {};
|
||||
$scope.loading = false;
|
||||
$scope.user = null;
|
||||
$scope.Features = Features;
|
||||
|
||||
$scope.$watch('info', function(info) {
|
||||
if (info && info.user) {
|
||||
$scope.user = info.user;
|
||||
$scope.accountType = 'user';
|
||||
$scope.convertStep = 0;
|
||||
$('#convertAccountModal').modal({});
|
||||
}
|
||||
});
|
||||
|
||||
$scope.showConvertForm = function() {
|
||||
$scope.convertStep = 1;
|
||||
};
|
||||
|
||||
$scope.nextStep = function() {
|
||||
if (Features.BILLING) {
|
||||
PlanService.getMatchingBusinessPlan(function(plan) {
|
||||
$scope.org.plan = plan;
|
||||
|
@ -25,22 +40,19 @@ angular.module('quay').directive('convertUserToOrg', function () {
|
|||
PlanService.getPlans(function(plans) {
|
||||
$scope.orgPlans = plans;
|
||||
});
|
||||
|
||||
$scope.convertStep = 2;
|
||||
} else {
|
||||
$scope.performConversion();
|
||||
}
|
||||
|
||||
$scope.convertStep = 1;
|
||||
};
|
||||
|
||||
$scope.convertToOrg = function() {
|
||||
$('#reallyconvertModal').modal({});
|
||||
};
|
||||
|
||||
$scope.reallyConvert = function() {
|
||||
$scope.performConversion = function() {
|
||||
if (Config.AUTHENTICATION_TYPE != 'Database') { return; }
|
||||
|
||||
$scope.loading = true;
|
||||
$scope.convertStep = 3;
|
||||
|
||||
var errorHandler = ApiService.errorDisplay(function() {
|
||||
$scope.loading = false;
|
||||
$('#convertAccountModal').modal('hide');
|
||||
});
|
||||
|
||||
var data = {
|
||||
|
@ -52,6 +64,7 @@ angular.module('quay').directive('convertUserToOrg', function () {
|
|||
ApiService.convertUserToOrganization(data).then(function(resp) {
|
||||
CookieService.putPermanent('quay.namespace', $scope.user.username);
|
||||
UserService.load();
|
||||
$('#convertAccountModal').modal('hide');
|
||||
$location.path('/');
|
||||
}, errorHandler);
|
||||
};
|
||||
|
|
44
static/js/pages/billing.js
Normal file
44
static/js/pages/billing.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
(function() {
|
||||
/**
|
||||
* Billing plans page.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('billing', 'billing.html', BillingCtrl, {
|
||||
'title': 'Billing',
|
||||
'description': 'Billing',
|
||||
'newLayout': true
|
||||
});
|
||||
}]);
|
||||
|
||||
/**
|
||||
* Billing invoices page.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('invoices', 'invoices.html', BillingCtrl, {
|
||||
'title': 'Billing Invoices',
|
||||
'description': 'Billing Invoices',
|
||||
'newLayout': true
|
||||
});
|
||||
}]);
|
||||
|
||||
|
||||
function BillingCtrl($scope, ApiService, $routeParams) {
|
||||
$scope.orgname = $routeParams['orgname'];
|
||||
$scope.username = $routeParams['username'];
|
||||
|
||||
var loadEntity = function() {
|
||||
if ($scope.orgname) {
|
||||
$scope.entityResource = ApiService.getOrganizationAsResource({'orgname': $scope.orgname}).get(function(org) {
|
||||
$scope.organization = org;
|
||||
});
|
||||
} else {
|
||||
$scope.entityResource = ApiService.getUserInformationAsResource({'username': $scope.username}).get(function(user) {
|
||||
$scope.viewuser = user;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Load the user or organization.
|
||||
loadEntity();
|
||||
}
|
||||
}());
|
|
@ -16,9 +16,10 @@
|
|||
$scope.namespace = orgname;
|
||||
$scope.showLogsCounter = 0;
|
||||
$scope.showApplicationsCounter = 0;
|
||||
$scope.showInvoicesCounter = 0;
|
||||
$scope.showBillingCounter = 0;
|
||||
$scope.showRobotsCounter = 0;
|
||||
$scope.showTeamsCounter = 0;
|
||||
$scope.changeEmailInfo = null;
|
||||
|
||||
$scope.orgScope = {
|
||||
'changingOrganization': false,
|
||||
|
@ -67,8 +68,8 @@
|
|||
$scope.showTeamsCounter++;
|
||||
};
|
||||
|
||||
$scope.showInvoices = function() {
|
||||
$scope.showInvoicesCounter++;
|
||||
$scope.showBilling = function() {
|
||||
$scope.showBillingCounter = true;
|
||||
};
|
||||
|
||||
$scope.showApplications = function() {
|
||||
|
@ -79,25 +80,27 @@
|
|||
$scope.showLogsCounter++;
|
||||
};
|
||||
|
||||
$scope.changeEmail = function() {
|
||||
UIService.hidePopover('#changeEmailForm input');
|
||||
$scope.showChangeEmail = function() {
|
||||
$scope.changeEmailInfo = {
|
||||
'email': $scope.organization.email
|
||||
};
|
||||
};
|
||||
|
||||
$scope.orgScope.changingOrganization = true;
|
||||
$scope.changeEmail = function(info, callback) {
|
||||
var params = {
|
||||
'orgname': orgname
|
||||
};
|
||||
|
||||
var data = {
|
||||
'email': $scope.orgScope.organizationEmail
|
||||
var details = {
|
||||
'email': $scope.changeEmailInfo.email
|
||||
};
|
||||
|
||||
ApiService.changeOrganizationDetails(data, params).then(function(org) {
|
||||
$scope.orgScope.changingOrganization = false;
|
||||
$scope.organization = org;
|
||||
}, function(result) {
|
||||
$scope.orgScope.changingOrganization = false;
|
||||
UIService.showFormError('#changeEmailForm input', result, 'right');
|
||||
});
|
||||
var errorDisplay = ApiService.errorDisplay('Could not change email address', callback);
|
||||
|
||||
ApiService.changeOrganizationDetails(details, params).then(function() {
|
||||
$scope.organization.email = $scope.changeEmailInfo.email;
|
||||
callback(true);
|
||||
}, errorDisplay);
|
||||
};
|
||||
}
|
||||
})();
|
|
@ -13,11 +13,13 @@
|
|||
function UserViewCtrl($scope, $routeParams, $timeout, ApiService, UserService, UIService, AvatarService, Config, ExternalLoginService) {
|
||||
var username = $routeParams.username;
|
||||
|
||||
$scope.showInvoicesCounter = 0;
|
||||
$scope.showAppsCounter = 0;
|
||||
$scope.showRobotsCounter = 0;
|
||||
$scope.changeEmailInfo = {};
|
||||
$scope.changePasswordInfo = {};
|
||||
$scope.showBillingCounter = 0;
|
||||
$scope.showLogsCounter = 0;
|
||||
|
||||
$scope.changeEmailInfo = null;
|
||||
$scope.changePasswordInfo = null;
|
||||
$scope.hasSingleSignin = ExternalLoginService.hasSingleSignin();
|
||||
$scope.context = {};
|
||||
|
||||
|
@ -54,37 +56,32 @@
|
|||
$scope.showRobotsCounter++;
|
||||
};
|
||||
|
||||
$scope.showInvoices = function() {
|
||||
$scope.showInvoicesCounter++;
|
||||
$scope.showLogs = function() {
|
||||
$scope.showLogsCounter++;
|
||||
};
|
||||
|
||||
$scope.showApplications = function() {
|
||||
$scope.showAppsCounter++;
|
||||
};
|
||||
|
||||
$scope.changePassword = function() {
|
||||
if (Config.AUTHENTICATION_TYPE != 'Database') { return; }
|
||||
$scope.showChangePassword = function() {
|
||||
$scope.changePasswordInfo = {};
|
||||
};
|
||||
|
||||
UIService.hidePopover('#changePasswordForm');
|
||||
$scope.changePasswordInfo.state = 'changing';
|
||||
$scope.changePassword = function(info, callback) {
|
||||
if (Config.AUTHENTICATION_TYPE != 'Database') { return; }
|
||||
|
||||
var data = {
|
||||
'password': $scope.changePasswordInfo.password
|
||||
};
|
||||
|
||||
var errorDisplay = ApiService.errorDisplay('Could not change password', callback);
|
||||
|
||||
ApiService.changeUserDetails(data).then(function(resp) {
|
||||
$scope.changePasswordInfo.state = 'changed';
|
||||
|
||||
// Reset the form
|
||||
delete $scope.changePasswordInfo['password']
|
||||
delete $scope.changePasswordInfo['repeatPassword']
|
||||
|
||||
// Reload the user.
|
||||
UserService.load();
|
||||
}, function(result) {
|
||||
$scope.changePasswordInfo.state = 'change-error';
|
||||
UIService.showFormError('#changePasswordForm', result);
|
||||
});
|
||||
callback(true);
|
||||
}, errorDisplay);
|
||||
};
|
||||
|
||||
$scope.generateClientToken = function() {
|
||||
|
@ -102,21 +99,32 @@
|
|||
UIService.showPasswordDialog('Enter your password to generate an encrypted version:', generateToken);
|
||||
};
|
||||
|
||||
$scope.changeEmail = function() {
|
||||
UIService.hidePopover('#changeEmailForm');
|
||||
$scope.showChangeEmail = function() {
|
||||
$scope.changeEmailInfo = {
|
||||
'email': $scope.context.viewuser.email
|
||||
};
|
||||
};
|
||||
|
||||
$scope.changeEmail = function(info, callback) {
|
||||
var details = {
|
||||
'email': $scope.changeEmailInfo.email
|
||||
};
|
||||
|
||||
$scope.changeEmailInfo.state = 'sending';
|
||||
var errorDisplay = ApiService.errorDisplay('Could not change email address', callback);
|
||||
|
||||
ApiService.changeUserDetails(details).then(function() {
|
||||
$scope.changeEmailInfo.state = 'sent';
|
||||
delete $scope.changeEmailInfo['email'];
|
||||
}, function(result) {
|
||||
$scope.changeEmailInfo.state = 'send-error';
|
||||
UIService.showFormError('#changeEmailForm', result);
|
||||
});
|
||||
callback(true);
|
||||
}, errorDisplay);
|
||||
};
|
||||
|
||||
$scope.showChangeAccount = function() {
|
||||
$scope.convertAccountInfo = {
|
||||
'user': $scope.context.viewuser
|
||||
};
|
||||
};
|
||||
|
||||
$scope.showBilling = function() {
|
||||
$scope.showBillingCounter++;
|
||||
};
|
||||
}
|
||||
})();
|
|
@ -313,7 +313,6 @@ angular.module('quay').factory('ApiService', ['Restangular', '$q', 'UtilService'
|
|||
}
|
||||
|
||||
message = UtilService.stringToHTML(message);
|
||||
|
||||
bootbox.dialog({
|
||||
"message": message,
|
||||
"title": defaultMessage || 'Request Failure',
|
||||
|
|
Reference in a new issue