Merge branch 'orgs' of ssh://bitbucket.org/yackob03/quay into orgs
This commit is contained in:
commit
71f7320532
11 changed files with 633 additions and 67 deletions
|
@ -99,6 +99,22 @@ def create_organization(name, email, creating_user):
|
|||
raise InvalidOrganizationException('Invalid organization name: %s' % name)
|
||||
|
||||
|
||||
def convert_user_to_organization(user, admin_user):
|
||||
# Change the user to an organization.
|
||||
user.organization = True
|
||||
|
||||
# TODO: disable this account for login.
|
||||
user.password = ''
|
||||
user.save()
|
||||
|
||||
# Create a team for the owners
|
||||
owners_team = create_team('owners', user, 'admin')
|
||||
|
||||
# Add the user who will admin the org to the owners team
|
||||
add_user_to_team(admin_user, owners_team)
|
||||
|
||||
return user
|
||||
|
||||
def create_team(name, org, team_role_name, description=''):
|
||||
if not validate_username(name):
|
||||
raise InvalidTeamException('Invalid team name: %s' % name)
|
||||
|
|
|
@ -96,6 +96,38 @@ def get_logged_in_user():
|
|||
})
|
||||
|
||||
|
||||
@app.route('/api/user/convert', methods=['POST'])
|
||||
@api_login_required
|
||||
def convert_user_to_organization():
|
||||
user = current_user.db_user()
|
||||
convert_data = request.get_json()
|
||||
|
||||
# Ensure that the new admin user is the not user being converted.
|
||||
admin_username = convert_data['adminUser']
|
||||
if admin_username == user.username:
|
||||
error_resp = jsonify({
|
||||
'reason': 'invaliduser'
|
||||
})
|
||||
error_resp.status_code = 400
|
||||
return error_resp
|
||||
|
||||
# Ensure that the sign in credentials work.
|
||||
admin_password = convert_data['adminPassword']
|
||||
if not model.verify_user(admin_username, admin_password):
|
||||
error_resp = jsonify({
|
||||
'reason': 'invaliduser'
|
||||
})
|
||||
error_resp.status_code = 400
|
||||
return error_resp
|
||||
|
||||
# Convert the user to an organization.
|
||||
model.convert_user_to_organization(user, model.get_user(admin_username))
|
||||
|
||||
# And finally login with the admin credentials.
|
||||
return conduct_signin(admin_username, admin_password)
|
||||
|
||||
|
||||
|
||||
@app.route('/api/user/', methods=['PUT'])
|
||||
@api_login_required
|
||||
def change_user_details():
|
||||
|
@ -157,6 +189,10 @@ def signin_api():
|
|||
username = signin_data['username']
|
||||
password = signin_data['password']
|
||||
|
||||
return conduct_signin(username, password)
|
||||
|
||||
|
||||
def conduct_signin(username, password):
|
||||
#TODO Allow email login
|
||||
needs_email_verification = False
|
||||
invalid_credentials = False
|
||||
|
@ -264,6 +300,36 @@ def team_view(orgname, t):
|
|||
}
|
||||
|
||||
|
||||
@app.route('/api/organization/', methods=['POST'])
|
||||
@api_login_required
|
||||
def create_organization_api():
|
||||
org_data = request.get_json()
|
||||
existing = None
|
||||
|
||||
try:
|
||||
existing = model.get_organization(org_data['name']) or model.get_user(org_data['name'])
|
||||
except:
|
||||
pass
|
||||
|
||||
if existing:
|
||||
error_resp = jsonify({
|
||||
'message': 'A user or organization with this name already exists'
|
||||
})
|
||||
error_resp.status_code = 400
|
||||
return error_resp
|
||||
|
||||
try:
|
||||
organization = model.create_organization(org_data['name'], org_data['email'],
|
||||
current_user.db_user())
|
||||
return make_response('Created', 201)
|
||||
except model.DataModelException as ex:
|
||||
error_resp = jsonify({
|
||||
'message': ex.message,
|
||||
})
|
||||
error_resp.status_code = 400
|
||||
return error_resp
|
||||
|
||||
|
||||
@app.route('/api/organization/<orgname>', methods=['GET'])
|
||||
@api_login_required
|
||||
def get_organization(orgname):
|
||||
|
|
|
@ -1276,6 +1276,11 @@ p.editable:hover i {
|
|||
border: inherit;
|
||||
}
|
||||
|
||||
.user-admin #migrate .panel {
|
||||
max-width: 600px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.user-admin .panel-plan {
|
||||
text-align: center;
|
||||
}
|
||||
|
@ -1295,6 +1300,41 @@ p.editable:hover i {
|
|||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.user-admin .convert-form h3 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.user-admin #convertForm {
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.user-admin #convertForm .form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.user-admin #convertForm input {
|
||||
margin-bottom: 10px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.user-admin #convertForm .existing-data {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.user-admin #convertForm .description {
|
||||
margin-top: 10px;
|
||||
display: block;
|
||||
color: #888;
|
||||
font-size: 12px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.user-admin #convertForm .existing-data {
|
||||
display: block;
|
||||
padding-left: 20px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#image-history-container {
|
||||
overflow: hidden;
|
||||
|
@ -1611,6 +1651,103 @@ p.editable:hover i {
|
|||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.create-org .steps-container {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.create-org .steps {
|
||||
|
||||
background: #222;
|
||||
|
||||
display: inline-block;
|
||||
margin-top: 16px;
|
||||
margin-left: 0px;
|
||||
border-radius: 4px;
|
||||
padding: 0px;
|
||||
list-style: none;
|
||||
height: 46px;
|
||||
width: 675px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
|
||||
.create-org .steps .step {
|
||||
width: 225px;
|
||||
float: left;
|
||||
padding: 10px;
|
||||
border-right: 1px solid #222;
|
||||
margin: 0px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: #aaa;
|
||||
border-left: 4px solid transparent;
|
||||
}
|
||||
|
||||
.create-org .steps .step i {
|
||||
font-size: 26px;
|
||||
margin-right: 6px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.create-org .steps .step.active {
|
||||
color: white;
|
||||
border-left: 4px solid steelblue;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.create-org .steps .step:last-child {
|
||||
border-right: 0px;
|
||||
}
|
||||
|
||||
.create-org .steps .step b {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.create-org .button-bar {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.create-org .form-group {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.create-org .plan-group {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.create-org .plan-group table {
|
||||
margin: 20px;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
|
||||
.create-org .plan-group strong {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.create-org .plan-group td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.create-org .plan-group .plan-price {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.create-org .step-container .description {
|
||||
margin-top: 10px;
|
||||
display: block;
|
||||
color: #888;
|
||||
font-size: 12px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.create-org .form-group input {
|
||||
margin-top: 10px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.create-org h3 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.plan-manager-element .plans-table thead td {
|
||||
color: #aaa;
|
||||
font-weight: bold;
|
||||
|
|
25
static/directives/signin-form.html
Normal file
25
static/directives/signin-form.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
<div class="signin-form-element">
|
||||
<form class="form-signin" ng-submit="signin();">
|
||||
<input type="text" class="form-control input-lg" name="username"
|
||||
placeholder="Username" ng-model="user.username" autofocus>
|
||||
<input type="password" class="form-control input-lg" name="password"
|
||||
placeholder="Password" ng-model="user.password">
|
||||
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign In</button>
|
||||
|
||||
<span class="social-alternate">
|
||||
<i class="fa fa-circle"></i>
|
||||
<span class="inner-text">OR</span>
|
||||
</span>
|
||||
|
||||
<a id="github-signin-link"
|
||||
href="https://github.com/login/oauth/authorize?client_id={{ githubClientId }}&scope=user:email{{ mixpanelDistinctIdClause }}"
|
||||
class="btn btn-primary btn-lg btn-block">
|
||||
<i class="fa fa-github fa-lg"></i> Sign In with GitHub
|
||||
</a>
|
||||
</form>
|
||||
|
||||
<div class="alert alert-danger" ng-show="invalidCredentials">Invalid username or password.</div>
|
||||
<div class="alert alert-danger" ng-show="needsEmailVerification">
|
||||
You must verify your email address before you can sign in.
|
||||
</div>
|
||||
</div>
|
|
@ -343,6 +343,50 @@ quayApp.directive('repoCircle', function () {
|
|||
});
|
||||
|
||||
|
||||
quayApp.directive('signinForm', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/signin-form.html',
|
||||
replace: false,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'redirectUrl': '=redirectUrl'
|
||||
},
|
||||
controller: function($scope, $location, $timeout, Restangular, KeyService, UserService) {
|
||||
$scope.githubClientId = KeyService.githubClientId;
|
||||
|
||||
var appendMixpanelId = function() {
|
||||
if (mixpanel.get_distinct_id !== undefined) {
|
||||
$scope.mixpanelDistinctIdClause = "&state=" + mixpanel.get_distinct_id();
|
||||
} else {
|
||||
// Mixpanel not yet loaded, try again later
|
||||
$timeout(appendMixpanelId, 200);
|
||||
}
|
||||
};
|
||||
|
||||
appendMixpanelId();
|
||||
|
||||
$scope.signin = function() {
|
||||
var signinPost = Restangular.one('signin');
|
||||
signinPost.customPOST($scope.user).then(function() {
|
||||
$scope.needsEmailVerification = false;
|
||||
$scope.invalidCredentials = false;
|
||||
|
||||
// Redirect to the specified page or the landing page
|
||||
UserService.load();
|
||||
$location.path($scope.redirectUrl ? $scope.redirectUrl : '/');
|
||||
}, function(result) {
|
||||
$scope.needsEmailVerification = result.data.needsEmailVerification;
|
||||
$scope.invalidCredentials = result.data.invalidCredentials;
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
||||
|
||||
|
||||
quayApp.directive('organizationHeader', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
|
@ -604,16 +648,17 @@ quayApp.directive('planManager', function () {
|
|||
};
|
||||
|
||||
var loadPlans = function() {
|
||||
if ($scope.plans) { return; }
|
||||
if ($scope.plans || $scope.loadingPlans) { return; }
|
||||
if (!$scope.user && !$scope.organization) { return; }
|
||||
|
||||
$scope.loadingPlans = true;
|
||||
PlanService.getPlans(function(plans) {
|
||||
$scope.plans = plans[$scope.organization ? 'business' : 'user'];
|
||||
update();
|
||||
|
||||
if ($scope.readyForPlan) {
|
||||
var planRequested = $scope.readyForPlan();
|
||||
if (planRequested) {
|
||||
if (planRequested && planRequested != getFreePlan()) {
|
||||
$scope.changeSubscription(planRequested);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,35 +75,6 @@ function HeaderCtrl($scope, $location, UserService, Restangular) {
|
|||
}
|
||||
|
||||
function SigninCtrl($scope, $location, $timeout, Restangular, KeyService, UserService) {
|
||||
$scope.githubClientId = KeyService.githubClientId;
|
||||
|
||||
var appendMixpanelId = function() {
|
||||
if (mixpanel.get_distinct_id !== undefined) {
|
||||
$scope.mixpanelDistinctIdClause = "&state=" + mixpanel.get_distinct_id();
|
||||
} else {
|
||||
// Mixpanel not yet loaded, try again later
|
||||
$timeout(appendMixpanelId, 200);
|
||||
}
|
||||
};
|
||||
|
||||
appendMixpanelId();
|
||||
|
||||
$scope.signin = function() {
|
||||
var signinPost = Restangular.one('signin');
|
||||
signinPost.customPOST($scope.user).then(function() {
|
||||
$scope.needsEmailVerification = false;
|
||||
$scope.invalidCredentials = false;
|
||||
|
||||
// Redirect to the landing page
|
||||
UserService.load();
|
||||
$location.path('/');
|
||||
}, function(result) {
|
||||
$scope.needsEmailVerification = result.data.needsEmailVerification;
|
||||
$scope.invalidCredentials = result.data.invalidCredentials;
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
$scope.sendRecovery = function() {
|
||||
var signinPost = Restangular.one('recovery');
|
||||
signinPost.customPOST($scope.recovery).then(function() {
|
||||
|
@ -118,7 +89,7 @@ function SigninCtrl($scope, $location, $timeout, Restangular, KeyService, UserSe
|
|||
$scope.status = 'ready';
|
||||
};
|
||||
|
||||
function PlansCtrl($scope, UserService, PlanService) {
|
||||
function PlansCtrl($scope, $location, UserService, PlanService) {
|
||||
// Load the list of plans.
|
||||
PlanService.getPlans(function(plans) {
|
||||
$scope.plans = plans;
|
||||
|
@ -136,6 +107,14 @@ function PlansCtrl($scope, UserService, PlanService) {
|
|||
$('#signinModal').modal({});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.createOrg = function(plan) {
|
||||
if ($scope.user && !$scope.user.anonymous) {
|
||||
document.location = '/organizations/new/?plan=' + plan;
|
||||
} else {
|
||||
$('#signinModal').modal({});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function GuideCtrl($scope) {
|
||||
|
@ -711,7 +690,7 @@ function RepoAdminCtrl($scope, Restangular, $routeParams, $rootScope) {
|
|||
|
||||
}
|
||||
|
||||
function UserAdminCtrl($scope, $timeout, Restangular, PlanService, UserService, KeyService, $routeParams) {
|
||||
function UserAdminCtrl($scope, $timeout, $location, Restangular, PlanService, UserService, KeyService, $routeParams) {
|
||||
$scope.$watch(function () { return UserService.currentUser(); }, function (currentUser) {
|
||||
$scope.askForPassword = currentUser.askForPassword;
|
||||
if (!currentUser.anonymous) {
|
||||
|
@ -725,12 +704,48 @@ function UserAdminCtrl($scope, $timeout, Restangular, PlanService, UserService,
|
|||
return $routeParams['plan'];
|
||||
};
|
||||
|
||||
if ($routeParams['migrate']) {
|
||||
$('#migrateTab').tab('show')
|
||||
}
|
||||
|
||||
$scope.loading = true;
|
||||
$scope.updatingUser = false;
|
||||
$scope.changePasswordSuccess = false;
|
||||
$scope.convertStep = 0;
|
||||
|
||||
|
||||
$('.form-change-pw').popover();
|
||||
|
||||
$scope.showConvertForm = function() {
|
||||
$scope.convertStep = 1;
|
||||
};
|
||||
|
||||
$scope.convertToOrg = function() {
|
||||
$('#reallyconvertModal').modal({});
|
||||
};
|
||||
|
||||
$scope.reallyConvert = function() {
|
||||
$scope.loading = true;
|
||||
|
||||
var data = {
|
||||
'adminUser': $scope.org.adminUser,
|
||||
'adminPassword': $scope.org.adminPassword
|
||||
};
|
||||
|
||||
var convertAccount = Restangular.one('user/convert');
|
||||
convertAccount.customPOST(data).then(function(resp) {
|
||||
UserService.load();
|
||||
$location.path('/');
|
||||
}, function(resp) {
|
||||
$scope.loading = false;
|
||||
if (resp.data.reason == 'invaliduser') {
|
||||
$('#invalidadminModal').modal({});
|
||||
} else {
|
||||
$('#cannotconvertModal').modal({});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.changePassword = function() {
|
||||
$('.form-change-pw').popover('hide');
|
||||
$scope.updatingUser = true;
|
||||
|
@ -1282,6 +1297,73 @@ function OrgsCtrl($scope, UserService) {
|
|||
browserchrome.update();
|
||||
}
|
||||
|
||||
function NewOrgCtrl($scope, UserService) {
|
||||
function NewOrgCtrl($scope, $routeParams, $timeout, $location, UserService, PlanService, Restangular) {
|
||||
$scope.loading = true;
|
||||
|
||||
$scope.$watch( function () { return UserService.currentUser(); }, function (currentUser) {
|
||||
$scope.user = currentUser;
|
||||
$scope.loading = false;
|
||||
}, true);
|
||||
|
||||
requested = $routeParams['plan'];
|
||||
|
||||
// Load the list of plans.
|
||||
PlanService.getPlans(function(plans) {
|
||||
$scope.plans = plans.business;
|
||||
$scope.currentPlan = null;
|
||||
if (requested) {
|
||||
PlanService.getPlan(requested, function(plan) {
|
||||
$scope.currentPlan = plan;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$scope.setPlan = function(plan) {
|
||||
$scope.currentPlan = plan;
|
||||
};
|
||||
|
||||
$scope.createNewOrg = function() {
|
||||
$('#orgName').popover('hide');
|
||||
|
||||
$scope.creating = true;
|
||||
var org = $scope.org;
|
||||
var data = {
|
||||
'name': org.name,
|
||||
'email': org.email
|
||||
};
|
||||
|
||||
var createPost = Restangular.one('organization/');
|
||||
createPost.customPOST(data).then(function(created) {
|
||||
$scope.creating = false;
|
||||
$scope.created = created;
|
||||
|
||||
// Reset the organizations list.
|
||||
UserService.load();
|
||||
|
||||
// If the selected plan is free, simply move to the org page.
|
||||
if ($scope.currentPlan.price == 0) {
|
||||
$location.path('/organization/' + org.name + '/');
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, show the subscribe for the plan.
|
||||
PlanService.changePlan($scope, org.name, $scope.currentPlan.stripeId, false, function() {
|
||||
// Started.
|
||||
$scope.creating = true;
|
||||
}, function(sub) {
|
||||
// Success.
|
||||
$location.path('/organization/' + org.name + '/');
|
||||
}, function() {
|
||||
// Failure.
|
||||
$location.path('/organization/' + org.name + '/');
|
||||
});
|
||||
|
||||
}, function(result) {
|
||||
$scope.creating = false;
|
||||
$scope.createError = result.data.message || result.data;
|
||||
$timeout(function() {
|
||||
$('#orgName').popover('show');
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
|
@ -1173,7 +1173,7 @@ RepositoryUsageChart.prototype.drawInternal_ = function() {
|
|||
var count = this.count_;
|
||||
var total = this.total_;
|
||||
|
||||
var data = [count, Math.max(0, total - count)];
|
||||
var data = [Math.max(count, 1), Math.max(0, total - count)];
|
||||
|
||||
var arcTween = function(a) {
|
||||
var i = d3.interpolate(this._current, a);
|
||||
|
|
|
@ -1 +1,113 @@
|
|||
new org
|
||||
<div class="loading" ng-show="loading || creating">
|
||||
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||
</div>
|
||||
|
||||
<div class="container create-org" ng-show="!loading && !creating">
|
||||
|
||||
<div class="row header-row">
|
||||
<div class="col-md-1"></div>
|
||||
<div class="col-md-8">
|
||||
<h2>Create Organization</h2>
|
||||
|
||||
<div class="steps-container" ng-show="false">
|
||||
<ul class="steps">
|
||||
<li class="step" ng-class="!user || user.anonymous ? 'active' : ''">
|
||||
<i class="fa fa-sign-in"></i>
|
||||
<span class="title">Login with an account</span>
|
||||
</li>
|
||||
<li class="step" ng-class="!user.anonymous && !created ? 'active' : ''">
|
||||
<i class="fa fa-gear"></i>
|
||||
<span class="title">Setup your organization</span>
|
||||
</li>
|
||||
<li class="step" ng-class="!user.anonymous && created ? 'active' : ''">
|
||||
<i class="fa fa-group"></i>
|
||||
<span class="title">Create teams</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 1 -->
|
||||
<div class="row" ng-show="!user || user.anonymous">
|
||||
<div class="col-sm-6 col-sm-offset-3">
|
||||
<div class="step-container" >
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body">
|
||||
<div class="signin-form" redirect-url="'/organizations/new'"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 2 -->
|
||||
<div class="row" ng-show="user && !user.anonymous && !created">
|
||||
<div class="col-md-1"></div>
|
||||
<div class="col-md-8">
|
||||
<div class="step-container">
|
||||
<h3>Setup the new organization</h3>
|
||||
|
||||
<form method="post" name="newOrgForm" id="newOrgForm" ng-submit="createNewOrg()">
|
||||
<div class="form-group">
|
||||
<label for="orgName">Organization Name</label>
|
||||
<input id="orgName" name="orgName" type="text" class="form-control" placeholder="Organization Name"
|
||||
ng-model="org.name" required autofocus data-trigger="manual" data-content="{{ createError }}"
|
||||
data-placement="right">
|
||||
<span class="description">This will also be the namespace for your repositories</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="orgName">Organization Email</label>
|
||||
<input id="orgEmail" name="orgEmail" type="email" class="form-control" placeholder="Organization Email"
|
||||
ng-model="org.email" required>
|
||||
<span class="description">This address must be different from your account's email</span>
|
||||
</div>
|
||||
|
||||
<!-- Plans Table -->
|
||||
<div class="form-group plan-group">
|
||||
<strong>Choose your organization's plan</strong>
|
||||
<table class="table table-hover plans-table" ng-show="plans">
|
||||
<thead>
|
||||
<th>Plan</th>
|
||||
<th>Private Repositories</th>
|
||||
<th style="min-width: 64px">Price</th>
|
||||
<th></th>
|
||||
</thead>
|
||||
|
||||
<tr ng-repeat="plan in plans" ng-class="currentPlan == plan ? 'active' : ''">
|
||||
<td>{{ plan.title }}</td>
|
||||
<td>{{ plan.privateRepos }}</td>
|
||||
<td><div class="plan-price">${{ plan.price / 100 }}</div></td>
|
||||
<td class="controls">
|
||||
<a class="btn" href="javascript:void(0)" ng-class="currentPlan == plan ? 'btn-primary' : 'btn-default'"
|
||||
ng-click="setPlan(plan)">
|
||||
{{ currentPlan == plan ? 'Selected' : 'Choose' }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="button-bar">
|
||||
<button class="btn btn-large btn-success" type="submit" ng-disabled="newOrgForm.$invalid || !currentPlan">
|
||||
Create Organization
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 3 -->
|
||||
<div class="row" ng-show="user && !user.anonymous && created">
|
||||
<div class="col-md-1"></div>
|
||||
<div class="col-md-8">
|
||||
<div class="step-container">
|
||||
<h3>Organization Created</h3>
|
||||
<h4><a href="/organization/{{ org.name }}">Manage Teams Now</a></h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
<div class="count"><b>{{ plan.privateRepos }}</b> private repositories</div>
|
||||
<div class="description">{{ plan.audience }}</div>
|
||||
<div class="smaller">SSL secured connections</div>
|
||||
<button class="btn btn-success btn-block" ng-click="buyNow(plan.stripeId)">Sign Up Now</button>
|
||||
<button class="btn btn-success btn-block" ng-click="createOrg(plan.stripeId)">Sign Up Now</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -12,22 +12,7 @@
|
|||
</div>
|
||||
<div id="collapseSignin" class="panel-collapse collapse in">
|
||||
<div class="panel-body">
|
||||
<form class="form-signin" ng-submit="signin();">
|
||||
<input type="text" class="form-control input-lg" name="username" placeholder="Username" ng-model="user.username" autofocus>
|
||||
<input type="password" class="form-control input-lg" name="password" placeholder="Password" ng-model="user.password">
|
||||
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign In</button>
|
||||
|
||||
<span class="social-alternate">
|
||||
<i class="fa fa-circle"></i>
|
||||
<span class="inner-text">OR</span>
|
||||
</span>
|
||||
|
||||
<a id='github-signin-link' href="https://github.com/login/oauth/authorize?client_id={{ githubClientId }}&scope=user:email{{ mixpanelDistinctIdClause }}" class="btn btn-primary btn-lg btn-block"><i class="fa fa-github fa-lg"></i> Sign In with GitHub</a>
|
||||
</form>
|
||||
|
||||
<div class="alert alert-danger" ng-show="invalidCredentials">Invalid username or password.</div>
|
||||
|
||||
<div class="alert alert-danger" ng-show="needsEmailVerification">You must verify your email address before you can sign in.</div>
|
||||
<div class="signin-form"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -56,17 +41,3 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <script type="text/javascript">
|
||||
function appendMixpanelId() {
|
||||
if (mixpanel.get_distinct_id !== undefined) {
|
||||
var signinLink = document.getElementById("github-signin-link");
|
||||
signinLink.href += ("&state=" + mixpanel.get_distinct_id());
|
||||
} else {
|
||||
// Mixpanel not yet loaded, try again later
|
||||
window.setTimeout(appendMixpanelId, 200);
|
||||
}
|
||||
};
|
||||
|
||||
appendMixpanelId();
|
||||
</script> -->
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
<ul class="nav nav-pills nav-stacked">
|
||||
<li class="active"><a href="javascript:void(0)" data-toggle="tab" data-target="#plan">Plan and Usage</a></li>
|
||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#password">Set Password</a></li>
|
||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#migrate" id="migrateTab">Convert to Organization</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
@ -55,8 +56,119 @@
|
|||
<span class="help-block" ng-show="changePasswordSuccess">Password changed successfully</span>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Convert to organization tab -->
|
||||
<div id="migrate" class="tab-pane">
|
||||
<!-- Step 0 -->
|
||||
<div class="panel" ng-show="convertStep == 0">
|
||||
<div class="panel-body" ng-show="user.organizations.length > 0">
|
||||
<div class="alert 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>
|
||||
|
||||
<div class="panel-body" ng-show="user.organizations.length == 0">
|
||||
<div class="alert alert-danger">
|
||||
Converting a user account into an organization <b>cannot be undone</b>.<br> Here be many fire-breathing dragons!
|
||||
</div>
|
||||
|
||||
<button class="btn btn-danger" ng-click="showConvertForm()">Start conversion process</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 1 -->
|
||||
<div class="convert-form" ng-show="convertStep == 1">
|
||||
<h3>Convert to organization</h3>
|
||||
|
||||
<form method="post" name="convertForm" id="convertForm" ng-submit="convertToOrg()">
|
||||
<div class="form-group">
|
||||
<label for="orgName">Organization Name</label>
|
||||
<div class="existing-data">
|
||||
<img src="//www.gravatar.com/avatar/{{ user.gravatar }}?s=24&d=identicon">
|
||||
{{ user.username }}</div>
|
||||
<span class="description">This will continue to be the namespace for your repositories</span>
|
||||
</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 an <b>existing account</b> that will become administrator of the organization</span>
|
||||
</div>
|
||||
|
||||
<div class="button-bar">
|
||||
<button class="btn btn-large btn-danger" type="submit" ng-disabled="convertForm.$invalid">
|
||||
Convert To Organization
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Modal message dialog -->
|
||||
<div class="modal fade" id="cannotconvertModal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title">Cannot convert account</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
Your account could not be converted. Please try again in a moment.
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
|
||||
|
||||
<!-- Modal message dialog -->
|
||||
<div class="modal fade" id="invalidadminModal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title">Username or password invalid</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
The username or password specified for the admin account is not valid.
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
|
||||
|
||||
<!-- Modal message dialog -->
|
||||
<div class="modal fade" id="reallyconvertModal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title">Convert to organization?</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-danger">You will not be able to login to this account once converted</div>
|
||||
<div>Are you <b>absolutely sure</b> you would like to convert this account to an organization? Once done, there is no going back.</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-danger" data-dismiss="modal" ng-click="reallyConvert()">Absolutely: Convert Now</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
|
|
Reference in a new issue