Merge pull request #1698 from coreos-inc/delete-namespace
Add support for deleting namespaces (users, organizations)
This commit is contained in:
commit
edc2bc8b93
23 changed files with 407 additions and 33 deletions
|
@ -1650,4 +1650,13 @@ a:focus {
|
|||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
max-height: 400px;
|
||||
}
|
||||
|
||||
.cor-confirm-dialog-element .modal-body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.cor-confirm-dialog-element .progress-message {
|
||||
margin-bottom: 10px;
|
||||
font-size: 16px;
|
||||
}
|
3
static/css/directives/ui/delete-namespace-view.css
Normal file
3
static/css/directives/ui/delete-namespace-view.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
.delete-namespace-view-element .yellow {
|
||||
color: #FCA657;
|
||||
}
|
|
@ -8,13 +8,19 @@
|
|||
<h4 class="modal-title">{{ dialogTitle }}</h4>
|
||||
</div>
|
||||
<div class="modal-body" ng-show="working">
|
||||
<div class="cor-loader"></div>
|
||||
<div class="cor-loader" ng-if="!dialogContext.progress"></div>
|
||||
<div class="progress-message" ng-if="dialogContext.progressMessage">
|
||||
{{ dialogContext.progressMessage }}
|
||||
</div>
|
||||
<div class="cor-progress-bar" ng-if="dialogContext.progress" progress="dialogContext.progress">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body" ng-show="!working">
|
||||
<span ng-transclude/>
|
||||
</div>
|
||||
<div class="modal-footer" ng-show="!working">
|
||||
<button type="button" class="btn btn-primary" ng-click="performAction()" ng-disabled="dialogForm && dialogForm.$invalid">
|
||||
<button type="button" class="btn btn-primary" ng-class="dialogButtonClass || 'btn-primary'"
|
||||
ng-click="performAction()" ng-disabled="dialogForm && dialogForm.$invalid">
|
||||
{{ dialogActionTitle }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
|
|
4
static/directives/cor-progress-bar.html
Normal file
4
static/directives/cor-progress-bar.html
Normal file
|
@ -0,0 +1,4 @@
|
|||
<div class="cor-progress-bar-element progress">
|
||||
<div class="progress-bar" ng-style="{'width': (progress * 100) + '%'}"
|
||||
aria-valuenow="{{ (progress * 100) }}" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
35
static/directives/delete-namespace-view.html
Normal file
35
static/directives/delete-namespace-view.html
Normal file
|
@ -0,0 +1,35 @@
|
|||
<div class="delete-namespace-view-element" quay-show="!Features.BILLING || subscriptionStatus != 'loading'">
|
||||
<table class="co-list-table">
|
||||
<tr>
|
||||
<td>Delete Account:</td>
|
||||
<td quay-show="!Features.BILLING || subscriptionStatus == 'none'">
|
||||
<a class="co-modify-link" ng-click="showDeleteNamespace()">Begin deletion</a>
|
||||
</td>
|
||||
<td quay-show="Features.BILLING && subscriptionStatus == 'valid'">
|
||||
<i class="fa fa-exclamation-triangle yellow"></i> You must cancel your billing subscription before this account can be deleted.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Delete account dialog -->
|
||||
<div class="cor-confirm-dialog"
|
||||
dialog-context="deleteNamespaceInfo"
|
||||
dialog-action="deleteNamespace(info, callback)"
|
||||
dialog-title="Delete Account"
|
||||
dialog-action-title="Delete Account"
|
||||
dialog-form="context.deleteform"
|
||||
dialog-button-class="btn-danger">
|
||||
<form name="context.deleteform" class="co-single-field-dialog">
|
||||
<div class="co-alert co-alert-danger">
|
||||
Deleting an account is <strong>non-reversable</strong> and will delete
|
||||
<strong>all of the account's data</strong> including repositories, created build triggers,
|
||||
and notifications.
|
||||
</div>
|
||||
|
||||
You must type <code>{{ deleteNamespaceInfo.namespace }}</code> below to confirm deletion is requested:
|
||||
<input type="text" class="form-control" placeholder="Enter namespace here"
|
||||
ng-model="deleteNamespaceInfo.verification" ng-pattern="deleteNamespaceInfo.namespace"
|
||||
required>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
|
@ -173,6 +173,7 @@ angular.module("core-ui", [])
|
|||
'dialogTitle': '@dialogTitle',
|
||||
'dialogActionTitle': '@dialogActionTitle',
|
||||
'dialogForm': '=dialogForm',
|
||||
'dialogButtonClass': '@dialogButtonClass',
|
||||
|
||||
'dialogContext': '=dialogContext',
|
||||
'dialogAction': '&dialogAction'
|
||||
|
@ -614,6 +615,22 @@ angular.module("core-ui", [])
|
|||
return directiveDefinitionObject;
|
||||
})
|
||||
|
||||
.directive('corProgressBar', function() {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 4,
|
||||
templateUrl: '/static/directives/cor-progress-bar.html',
|
||||
replace: true,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'progress': '=progress'
|
||||
},
|
||||
controller: function($rootScope, $scope, $element) {
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
})
|
||||
|
||||
.directive('corStepBar', function() {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 4,
|
||||
|
|
|
@ -11,7 +11,8 @@ angular.module('quay').directive('billingManagementPanel', function () {
|
|||
scope: {
|
||||
'user': '=user',
|
||||
'organization': '=organization',
|
||||
'isEnabled': '=isEnabled'
|
||||
'isEnabled': '=isEnabled',
|
||||
'subscriptionStatus': '=subscriptionStatus'
|
||||
},
|
||||
controller: function($scope, $element, PlanService, ApiService, Features) {
|
||||
$scope.currentCard = null;
|
||||
|
@ -19,6 +20,7 @@ angular.module('quay').directive('billingManagementPanel', function () {
|
|||
$scope.updating = true;
|
||||
$scope.changeReceiptsInfo = null;
|
||||
$scope.context = {};
|
||||
$scope.subscriptionStatus = 'loading';
|
||||
|
||||
var setSubscription = function(sub) {
|
||||
$scope.subscription = sub;
|
||||
|
@ -29,12 +31,14 @@ angular.module('quay').directive('billingManagementPanel', function () {
|
|||
|
||||
if (!sub.hasSubscription) {
|
||||
$scope.updating = false;
|
||||
$scope.subscriptionStatus = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
// Load credit card information.
|
||||
PlanService.getCardInfo($scope.organization ? $scope.organization.name : null, function(card) {
|
||||
$scope.currentCard = card;
|
||||
$scope.subscriptionStatus = 'valid';
|
||||
$scope.updating = false;
|
||||
});
|
||||
});
|
||||
|
|
34
static/js/directives/ui/delete-namespace-view.js
Normal file
34
static/js/directives/ui/delete-namespace-view.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* An element which displays a settings table row for deleting a namespace (user or organization).
|
||||
*/
|
||||
angular.module('quay').directive('deleteNamespaceView', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/delete-namespace-view.html',
|
||||
replace: false,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'user': '=user',
|
||||
'organization': '=organization',
|
||||
'subscriptionStatus': '=subscriptionStatus'
|
||||
},
|
||||
controller: function($scope, $element, UserService) {
|
||||
$scope.context = {};
|
||||
|
||||
$scope.showDeleteNamespace = function() {
|
||||
$scope.deleteNamespaceInfo = {
|
||||
'user': $scope.user,
|
||||
'organization': $scope.organization,
|
||||
'namespace': $scope.user ? $scope.user.username : $scope.organization.name,
|
||||
'verification': ''
|
||||
};
|
||||
};
|
||||
|
||||
$scope.deleteNamespace = function(info, callback) {
|
||||
UserService.deleteNamespace(info, callback);
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
|
@ -20,6 +20,7 @@
|
|||
$scope.showRobotsCounter = 0;
|
||||
$scope.showTeamsCounter = 0;
|
||||
$scope.changeEmailInfo = null;
|
||||
$scope.context = {};
|
||||
|
||||
$scope.orgScope = {
|
||||
'changingOrganization': false,
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
* about the user.
|
||||
*/
|
||||
angular.module('quay')
|
||||
.factory('UserService', ['ApiService', 'CookieService', '$rootScope', 'Config',
|
||||
.factory('UserService', ['ApiService', 'CookieService', '$rootScope', 'Config', '$location',
|
||||
|
||||
function(ApiService, CookieService, $rootScope, Config) {
|
||||
function(ApiService, CookieService, $rootScope, Config, $location) {
|
||||
var userResponse = {
|
||||
verified: false,
|
||||
anonymous: true,
|
||||
|
@ -169,6 +169,69 @@ function(ApiService, CookieService, $rootScope, Config) {
|
|||
return externalUsername || userResponse.username;
|
||||
};
|
||||
|
||||
userService.deleteNamespace = function(info, callback) {
|
||||
var namespace = info.user ? info.user.username : info.organization.name;
|
||||
var deleteNamespaceItself = function() {
|
||||
info.progress = 1;
|
||||
info.progressMessage = 'Deleting namespace...';
|
||||
|
||||
if (info.user) {
|
||||
ApiService.deleteCurrentUser().then(function(resp) {
|
||||
// Reload the user.
|
||||
userService.load();
|
||||
callback(true);
|
||||
$location.path('/');
|
||||
}, errorDisplay);
|
||||
} else {
|
||||
var delParams = {
|
||||
'name': info.organization.name
|
||||
};
|
||||
|
||||
ApiService.deleteOrganization(null, delParams).then(function(resp) {
|
||||
// Reload the user.
|
||||
userService.load();
|
||||
callback(true);
|
||||
$location.path('/');
|
||||
}, errorDisplay);
|
||||
}
|
||||
};
|
||||
|
||||
var repoIndex = 0;
|
||||
var repositories = null;
|
||||
var deleteAllRepos = function() {
|
||||
if (repoIndex >= repositories.length) {
|
||||
deleteNamespaceItself();
|
||||
return;
|
||||
}
|
||||
|
||||
var repoParams = {
|
||||
'repository': namespace + '/' + repositories[repoIndex]['name']
|
||||
};
|
||||
|
||||
info.progress = repoIndex / repositories.length;
|
||||
info.progressMessage = 'Deleting repository ' + repoParams['repository'] + '...';
|
||||
|
||||
ApiService.deleteRepository(null, repoParams).then(function() {
|
||||
repoIndex++;
|
||||
deleteAllRepos();
|
||||
}, errorDisplay);
|
||||
};
|
||||
|
||||
// First delete each repo for the namespace, updating the info so it can show a progress bar.
|
||||
// This is not strictly necessary (as the namespace delete call will do it as well), but it is
|
||||
// a better user experience.
|
||||
var params = {
|
||||
'namespace': namespace,
|
||||
'public': false
|
||||
};
|
||||
|
||||
var errorDisplay = ApiService.errorDisplay('Could not delete namespace', callback);
|
||||
ApiService.listRepos(null, params).then(function(resp) {
|
||||
repositories = resp['repositories'];
|
||||
deleteAllRepos();
|
||||
}, errorDisplay);
|
||||
};
|
||||
|
||||
userService.currentUser = function() {
|
||||
return userResponse;
|
||||
};
|
||||
|
|
|
@ -116,12 +116,14 @@
|
|||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="delete-namespace-view" subscription-status="subscriptionStatus" organization="organization"></div>
|
||||
</div>
|
||||
|
||||
<!-- Billing Information -->
|
||||
<div class="settings-section" quay-show="Features.BILLING">
|
||||
<h3>Billing Information</h3>
|
||||
<div class="billing-management-panel" organization="organization" is-enabled="showBillingCounter"></div>
|
||||
<div class="billing-management-panel" organization="organization" is-enabled="showBillingCounter" subscription-status="subscriptionStatus"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -129,7 +131,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Change email dialog -->
|
||||
<div class="cor-confirm-dialog"
|
||||
dialog-context="changeEmailInfo"
|
||||
|
|
|
@ -133,12 +133,15 @@
|
|||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="delete-namespace-view" subscription-status="subscriptionStatus" user="context.viewuser"
|
||||
quay-show="Config.AUTHENTICATION_TYPE == 'Database'"></div>
|
||||
</div>
|
||||
|
||||
<!-- Billing Information -->
|
||||
<div class="settings-section" quay-show="Features.BILLING">
|
||||
<h3>Billing Information</h3>
|
||||
<div class="billing-management-panel" user="context.viewuser" is-enabled="showBillingCounter"></div>
|
||||
<div class="billing-management-panel" user="context.viewuser" is-enabled="showBillingCounter" subscription-status="subscriptionStatus"></div>
|
||||
</div>
|
||||
|
||||
</div> <!-- /cor-tab-content -->
|
||||
|
|
Reference in a new issue