Merge pull request #1373 from coreos-inc/orgconvert
Org conversion improvements
This commit is contained in:
commit
b0cc55276f
7 changed files with 146 additions and 77 deletions
|
@ -35,23 +35,30 @@ def get_organization(name):
|
|||
|
||||
|
||||
def convert_user_to_organization(user_obj, admin_user):
|
||||
# Change the user to an organization.
|
||||
user_obj.organization = True
|
||||
if user_obj.robot:
|
||||
raise DataModelException('Cannot convert a robot into an organization')
|
||||
|
||||
# disable this account for login.
|
||||
user_obj.password_hash = None
|
||||
user_obj.save()
|
||||
with db_transaction():
|
||||
# Change the user to an organization and disable this account for login.
|
||||
user_obj.organization = True
|
||||
user_obj.password_hash = None
|
||||
user_obj.save()
|
||||
|
||||
# Clear any federated auth pointing to this user
|
||||
FederatedLogin.delete().where(FederatedLogin.user == user_obj).execute()
|
||||
# Clear any federated auth pointing to this user.
|
||||
FederatedLogin.delete().where(FederatedLogin.user == user_obj).execute()
|
||||
|
||||
# Create a team for the owners
|
||||
owners_team = team.create_team('owners', user_obj, 'admin')
|
||||
# Delete any user-specific permissions on repositories.
|
||||
(RepositoryPermission.delete()
|
||||
.where(RepositoryPermission.user == user_obj)
|
||||
.execute())
|
||||
|
||||
# Add the user who will admin the org to the owners team
|
||||
team.add_user_to_team(admin_user, owners_team)
|
||||
# Create a team for the owners
|
||||
owners_team = team.create_team('owners', user_obj, 'admin')
|
||||
|
||||
return user_obj
|
||||
# Add the user who will admin the org to the owners team
|
||||
team.add_user_to_team(admin_user, owners_team)
|
||||
|
||||
return user_obj
|
||||
|
||||
|
||||
def get_user_organizations(username):
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
.convert-user-to-org-element {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.convert-user-to-org .convert-form h3 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.convert-user-to-org #convertForm {
|
||||
max-width: 700px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.convert-user-to-org #convertForm .form-group {
|
||||
|
@ -12,24 +17,53 @@
|
|||
|
||||
.convert-user-to-org #convertForm input {
|
||||
margin-bottom: 10px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.convert-user-to-org #convertForm .existing-data {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.convert-user-to-org #convertForm .existing-data .avatar {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.convert-user-to-org #convertForm .existing-data .username {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
|
||||
.convert-user-to-org #convertForm .description {
|
||||
margin-top: 10px;
|
||||
display: block;
|
||||
color: #888;
|
||||
font-size: 12px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.convert-user-to-org #convertForm .existing-data {
|
||||
display: block;
|
||||
padding-left: 20px;
|
||||
margin-top: 10px;
|
||||
.convert-user-to-org .org-list {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.convert-user-to-org .org-list li {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.convert-user-to-org .org-list li a {
|
||||
vertical-align: middle;
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.convert-user-to-org .fa-arrow-circle-right {
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.convert-user-to-org .form-group-content {
|
||||
padding-left: 10px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
|
||||
.convert-user-to-org .form-group-content .co-table {
|
||||
margin: 0px;
|
||||
}
|
30
static/css/directives/ui/plans-table.css
Normal file
30
static/css/directives/ui/plans-table.css
Normal file
|
@ -0,0 +1,30 @@
|
|||
.plans-table-element table {
|
||||
margin: 20px;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
|
||||
.plans-table-element thead td {
|
||||
padding-top: 10px !important;
|
||||
}
|
||||
|
||||
.plans-table-element td {
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
|
||||
.plans-table-element .plan-price {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.plans-table ul {
|
||||
margin-top: 10px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.plans-table ul li {
|
||||
padding: 4px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.plans-table ul li .plan-info {
|
||||
padding: 4px;
|
||||
}
|
|
@ -2819,34 +2819,6 @@ p.editable:hover i {
|
|||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.plans-table-element table {
|
||||
margin: 20px;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
|
||||
.plans-table-element td {
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
|
||||
.plans-table-element .plan-price {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.plans-table ul {
|
||||
margin-top: 10px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.plans-table ul li {
|
||||
padding: 4px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.plans-table ul li .plan-info {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
|
||||
.repo-breadcrumb-element .crumb {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
|
@ -1,51 +1,63 @@
|
|||
<div class="convert-user-to-org-element">
|
||||
<!-- Step 0 -->
|
||||
<div class="panel" ng-show="convertStep == 0">
|
||||
<div class="panel-body" ng-show="user.organizations.length > 0">
|
||||
<div class="co-alert co-alert-info">
|
||||
Cannot convert this account into an organization, as it is a member of {{user.organizations.length}} other
|
||||
organization{{user.organizations.length > 1 ? 's' : ''}}. Please leave
|
||||
{{user.organizations.length > 1 ? 'those organizations' : 'that organization'}} first.
|
||||
</div>
|
||||
<div ng-show="convertStep == 0">
|
||||
<div ng-show="user.organizations.length > 0">
|
||||
Cannot convert this account into an organization, as it is a member of {{user.organizations.length}} other
|
||||
organization{{user.organizations.length > 1 ? 's' : ''}}.
|
||||
<br><br>
|
||||
Please leave the following organizations first:
|
||||
<ul class="org-list">
|
||||
<li ng-repeat="org in user.organizations">
|
||||
<span class="avatar" size="avatarSize || 16" data="org.avatar"></span>
|
||||
<a href="/organization/{{ org.name }}">{{ org.name }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="panel-body" ng-show="user.organizations.length == 0">
|
||||
<div class="co-alert co-alert-warning">
|
||||
Note: Converting a user account into an organization <b>cannot be undone</b>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary" ng-click="showConvertForm()">Start conversion process</button>
|
||||
<div ng-show="user.organizations.length == 0">
|
||||
<button class="btn btn-primary" ng-click="showConvertForm()">Start conversion process <i class="fa fa-arrow-circle-right" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 1 -->
|
||||
<div class="convert-form" ng-show="convertStep == 1">
|
||||
Fill out the form below to convert your current user account into an organization. Your existing repositories will be maintained under the
|
||||
namespace. All <strong>direct</strong> permissions delegated to {{ user.username }} will be deleted.
|
||||
|
||||
<form method="post" name="convertForm" id="convertForm" ng-submit="convertToOrg()">
|
||||
<div class="form-group">
|
||||
<label for="orgName">Organization Name</label>
|
||||
<div class="existing-data">
|
||||
<span class="avatar" size="24" data="user.avatar"></span>
|
||||
{{ user.username }}</div>
|
||||
<span class="description">This will continue to be the namespace for your repositories</span>
|
||||
<div class="form-group-content">
|
||||
<div class="existing-data">
|
||||
<span class="avatar" size="24" data="user.avatar"></span>
|
||||
<span class="username">{{ user.username }}</span>
|
||||
</div>
|
||||
<span class="description">This will continue to be the namespace for your repositories</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="orgName">Admin User</label>
|
||||
<input id="adminUsername" name="adminUsername" type="text" class="form-control" placeholder="Admin Username"
|
||||
ng-model="org.adminUser" required autofocus>
|
||||
<input id="adminPassword" name="adminPassword" type="password" class="form-control" placeholder="Admin Password"
|
||||
ng-model="org.adminPassword" required>
|
||||
<span class="description">
|
||||
The username and password for the account that will become an administrator of the organization.
|
||||
Note that this account <b>must be a separate registered account</b> from the account that you are
|
||||
trying to convert, and <b>must already exist</b>.
|
||||
</span>
|
||||
<div class="form-group-content">
|
||||
<input id="adminUsername" name="adminUsername" type="text" class="form-control" placeholder="Admin Username"
|
||||
ng-model="org.adminUser" required autofocus>
|
||||
<input id="adminPassword" name="adminPassword" type="password" class="form-control" placeholder="Admin Password"
|
||||
ng-model="org.adminPassword" required>
|
||||
<span class="description">
|
||||
The username and password for the account that will become an administrator of the organization.
|
||||
Note that this account <b>must be a separate registered account</b> from the account that you are
|
||||
trying to convert, and <b>must already exist</b>.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Plans Table -->
|
||||
<div class="form-group plan-group" quay-require="['BILLING']">
|
||||
<label>Organization Plan</label>
|
||||
<div class="plans-table" plans="orgPlans" current-plan="org.plan"></div>
|
||||
<div class="form-group-content">
|
||||
<div class="plans-table" plans="orgPlans" current-plan="org.plan"></div>
|
||||
<span class="description">The billing plan for the new organization. If private repositories are unneeded, select "Open Source".</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="button-bar">
|
||||
|
@ -60,7 +72,7 @@
|
|||
|
||||
<!-- Modal message dialog -->
|
||||
<div class="modal fade" id="cannotconvertModal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-dialog co-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
|
@ -78,8 +90,8 @@
|
|||
|
||||
|
||||
<!-- Modal message dialog -->
|
||||
<div class="modal fade" id="reallyconvertModal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal co-modal fade" id="reallyconvertModal">
|
||||
<div class="modal-dialog co-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
|
|
|
@ -316,7 +316,7 @@ angular.module('quay').factory('ApiService', ['Restangular', '$q', 'UtilService'
|
|||
|
||||
bootbox.dialog({
|
||||
"message": message,
|
||||
"title": defaultMessage,
|
||||
"title": defaultMessage || 'Request Failure',
|
||||
"buttons": {
|
||||
"close": {
|
||||
"label": "Close",
|
||||
|
|
|
@ -417,6 +417,15 @@ class TestConvertToOrganization(ApiTestCase):
|
|||
|
||||
def test_convert(self):
|
||||
self.login(READ_ACCESS_USER)
|
||||
|
||||
# Add at least one permission for the read-user.
|
||||
read_user = model.user.get_user(READ_ACCESS_USER)
|
||||
simple_repo = model.repository.get_repository(ADMIN_ACCESS_USER, 'simple')
|
||||
read_role = database.Role.get(name='read')
|
||||
|
||||
database.RepositoryPermission.create(user=read_user, repository=simple_repo, role=read_role)
|
||||
|
||||
# Convert the read user into an organization.
|
||||
json = self.postJsonResponse(ConvertToOrganization,
|
||||
data={'adminUser': ADMIN_ACCESS_USER,
|
||||
'adminPassword': 'password',
|
||||
|
@ -436,6 +445,11 @@ class TestConvertToOrganization(ApiTestCase):
|
|||
self.assertEquals(READ_ACCESS_USER, json['name'])
|
||||
self.assertEquals(True, json['is_admin'])
|
||||
|
||||
# Verify the now-org has no permissions.
|
||||
count = (database.RepositoryPermission.select()
|
||||
.where(database.RepositoryPermission.user == organization)
|
||||
.count())
|
||||
self.assertEquals(0, count)
|
||||
|
||||
def test_convert_via_email(self):
|
||||
self.login(READ_ACCESS_USER)
|
||||
|
|
Reference in a new issue