Start conversion of the user admin/view
This commit is contained in:
parent
bcd8a48159
commit
f67eeee8c8
20 changed files with 804 additions and 14 deletions
|
@ -2,7 +2,7 @@ import logging
|
|||
import json
|
||||
|
||||
from random import SystemRandom
|
||||
from flask import request
|
||||
from flask import request, abort
|
||||
from flask.ext.login import logout_user
|
||||
from flask.ext.principal import identity_changed, AnonymousIdentity
|
||||
from peewee import IntegrityError
|
||||
|
@ -59,7 +59,6 @@ def user_view(user):
|
|||
logins = model.list_federated_logins(user)
|
||||
|
||||
user_response = {
|
||||
'verified': user.verified,
|
||||
'anonymous': False,
|
||||
'username': user.username,
|
||||
'avatar': avatar.get_data_for_user(user)
|
||||
|
@ -68,6 +67,8 @@ def user_view(user):
|
|||
user_admin = UserAdminPermission(user.username)
|
||||
if user_admin.can():
|
||||
user_response.update({
|
||||
'is_me': True,
|
||||
'verified': user.verified,
|
||||
'email': user.email,
|
||||
'organizations': [org_view(o) for o in organizations],
|
||||
'logins': [login_view(login) for login in logins],
|
||||
|
@ -77,7 +78,7 @@ def user_view(user):
|
|||
'tag_expiration': user.removed_tag_expiration_s,
|
||||
})
|
||||
|
||||
if features.SUPER_USERS:
|
||||
if features.SUPER_USERS and SuperUserPermission().can():
|
||||
user_response.update({
|
||||
'super_user': user and user == get_authenticated_user() and SuperUserPermission().can()
|
||||
})
|
||||
|
@ -788,6 +789,7 @@ class StarredRepositoryList(ApiResource):
|
|||
'repository': repository,
|
||||
}, 201
|
||||
|
||||
|
||||
@resource('/v1/user/starred/<repopath:repository>')
|
||||
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
|
||||
class StarredRepository(RepositoryParamResource):
|
||||
|
@ -802,3 +804,17 @@ class StarredRepository(RepositoryParamResource):
|
|||
if repo:
|
||||
model.unstar_repository(user, repo)
|
||||
return 'Deleted', 204
|
||||
|
||||
|
||||
@resource('/v1/users/<username>')
|
||||
class Users(ApiResource):
|
||||
""" Operations related to retrieving information about other users. """
|
||||
@nickname('getUserInformation')
|
||||
def get(self, username):
|
||||
""" Get user information for the specified user. """
|
||||
user = model.get_user(username)
|
||||
if user is None or user.organization or user.robot:
|
||||
abort(404)
|
||||
|
||||
return user_view(user)
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@ STATUS_TAGS = app.config['STATUS_TAGS']
|
|||
|
||||
@web.route('/', methods=['GET'], defaults={'path': ''})
|
||||
@web.route('/organization/<path:path>', methods=['GET'])
|
||||
@web.route('/user/<path:path>', methods=['GET'])
|
||||
@no_cache
|
||||
def index(path, **kwargs):
|
||||
return render_page_template('index.html', **kwargs)
|
||||
|
|
3
static/css/directives/ui/authorized-apps-manager.css
Normal file
3
static/css/directives/ui/authorized-apps-manager.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
.authorized-apps-manager .avatar {
|
||||
margin-right: 4px;
|
||||
}
|
3
static/css/directives/ui/external-login-button.css
Normal file
3
static/css/directives/ui/external-login-button.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
.external-login-button i.fa {
|
||||
margin-right: 4px;
|
||||
}
|
11
static/css/directives/ui/external-logins-manager.css
Normal file
11
static/css/directives/ui/external-logins-manager.css
Normal file
|
@ -0,0 +1,11 @@
|
|||
.external-logins-manager .empty {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.external-logins-manager .external-auth-provider td:first-child {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.external-logins-manager .external-auth-provider td:first-child i.fa {
|
||||
margin-right: 6px;
|
||||
}
|
50
static/css/pages/user-view.css
Normal file
50
static/css/pages/user-view.css
Normal file
|
@ -0,0 +1,50 @@
|
|||
.user-view .user-name {
|
||||
vertical-align: middle;
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.user-view h3 {
|
||||
margin-bottom: 20px;
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.user-view .section-description-header {
|
||||
padding-left: 40px;
|
||||
position: relative;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.user-view .section-description-header:before {
|
||||
font-family: FontAwesome;
|
||||
content: "\f05a";
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 6px;
|
||||
font-size: 27px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.user-view .user-settings-form .row {
|
||||
padding: 10px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.user-view .co-panel {
|
||||
position: relative;
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
.user-view .co-panel .panel-body {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.user-view .co-panel .row {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.user-view .co-panel .row .panel {
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 0px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
56
static/directives/authorized-apps-manager.html
Normal file
56
static/directives/authorized-apps-manager.html
Normal file
|
@ -0,0 +1,56 @@
|
|||
<div class="authorized-apps-manager-element">
|
||||
<div class="manager-header">
|
||||
<h3>Authorized Applications</h3>
|
||||
</div>
|
||||
|
||||
<div class="manager-header section-description-header">
|
||||
The authorized applications panel lists applications you have authorized to view information and perform actions on your behalf. You can revoke any of your authorizations here by clicking the gear icon and clicking "Revoke Authorization".
|
||||
</div>
|
||||
|
||||
<div class="resource-view" resource="authorizedAppsResource"
|
||||
error-message="'Cannot load authorized applications'"></div>
|
||||
|
||||
<div class="empty" ng-if="!authorizedApps.length">
|
||||
<div class="empty-primary-msg">You have not authorized any external applications.</div>
|
||||
</div>
|
||||
|
||||
<table class="co-table" ng-if="authorizedApps.length">
|
||||
<thead>
|
||||
<td>Application Name</td>
|
||||
<td>Authorized Permissions</td>
|
||||
<td class="options-col"></td>
|
||||
</thead>
|
||||
|
||||
<tr class="auth-info" ng-repeat="authInfo in authorizedApps">
|
||||
<td>
|
||||
<span class="avatar" size="24" data="authInfo.application.avatar"></span>
|
||||
<a href="{{ authInfo.application.url }}" ng-if="authInfo.application.url" target="_blank"
|
||||
data-title="{{ authInfo.application.description || authInfo.application.name }}" bs-tooltip>
|
||||
{{ authInfo.application.name }}
|
||||
</a>
|
||||
<span ng-if="!authInfo.application.url"
|
||||
data-title="{{ authInfo.application.description || authInfo.application.name }}"
|
||||
bs-tooltip>
|
||||
{{ authInfo.application.name }}
|
||||
</span>
|
||||
<span class="by">{{ authInfo.application.organization.name }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="label label-default scope"
|
||||
ng-class="{'repo:admin': 'label-primary', 'repo:write': 'label-success', 'repo:create': 'label-success'}[scopeInfo.scope]"
|
||||
ng-repeat="scopeInfo in authInfo.scopes" data-title="{{ scopeInfo.description }}"
|
||||
bs-tooltip>
|
||||
{{ scopeInfo.scope }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="options-col">
|
||||
<span class="cor-options-menu">
|
||||
<span class="cor-option" option-click="deleteAccess(authInfo)">
|
||||
<i class="fa fa-times"></i> Revoke Authorization
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
102
static/directives/convert-user-to-org.html
Normal file
102
static/directives/convert-user-to-org.html
Normal file
|
@ -0,0 +1,102 @@
|
|||
<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="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-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>
|
||||
</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">
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<div class="button-bar">
|
||||
<button class="btn btn-large btn-danger" type="submit"
|
||||
ng-disabled="convertForm.$invalid || (Features.BILLING && !org.plan)"
|
||||
analytics-on analytics-event="convert_to_organization">
|
||||
Convert To Organization
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</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="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 -->
|
||||
|
||||
</div>
|
|
@ -1,6 +1,6 @@
|
|||
<span class="external-login-button-element">
|
||||
<span ng-if="provider == 'github'">
|
||||
<a href="javascript:void(0)" class="btn btn-primary btn-block" quay-require="['GITHUB_LOGIN']" ng-click="startSignin('github')" style="margin-bottom: 10px" ng-disabled="signingIn">
|
||||
<a href="javascript:void(0)" ng-class="isLink ? '' : 'btn btn-primary btn-block'" quay-require="['GITHUB_LOGIN']" ng-click="startSignin('github')" style="margin-bottom: 10px" ng-disabled="signingIn">
|
||||
<i class="fa fa-github fa-lg"></i>
|
||||
<span ng-if="action != 'attach'">
|
||||
Sign In with GitHub
|
||||
|
@ -15,7 +15,7 @@
|
|||
</span>
|
||||
|
||||
<span ng-if="provider == 'google'">
|
||||
<a href="javascript:void(0)" class="btn btn-primary btn-block" quay-require="['GOOGLE_LOGIN']" ng-click="startSignin('google')" ng-disabled="signingIn">
|
||||
<a href="javascript:void(0)" ng-class="isLink ? '' : 'btn btn-primary btn-block'" quay-require="['GOOGLE_LOGIN']" ng-click="startSignin('google')" ng-disabled="signingIn">
|
||||
<i class="fa fa-google fa-lg"></i>
|
||||
<span ng-if="action != 'attach'">Sign In with Google</span>
|
||||
<span ng-if="action == 'attach'">Attach to Google Account</span>
|
||||
|
|
63
static/directives/external-logins-manager.html
Normal file
63
static/directives/external-logins-manager.html
Normal file
|
@ -0,0 +1,63 @@
|
|||
<div class="external-logins-manager-element">
|
||||
<div class="manager-header">
|
||||
<h3>External Logins</h3>
|
||||
</div>
|
||||
|
||||
<div class="manager-header section-description-header">
|
||||
The external logins panel lists all supported external login providers, which can be used for one-click OAuth-based login to <span class="registry-name"></span>. Accounts can be attached or detached by clicking the associated button below.
|
||||
</div>
|
||||
|
||||
<table class="co-table">
|
||||
<thead>
|
||||
<td>Provider</td>
|
||||
<td>Account Status</td>
|
||||
<td>Attach/Detach</td>
|
||||
</thead>
|
||||
|
||||
<!-- GitHub Login -->
|
||||
<tr class="external-auth-provider" ng-show="Features.GITHUB_LOGIN">
|
||||
<td>
|
||||
<i class="fa fa-github"></i> GitHub <span ng-if="KeyService.isEnterprise('github')">Enterprise</span>
|
||||
</td>
|
||||
<td>
|
||||
<span ng-if="hasGithubLogin">
|
||||
Attached to GitHub <span ng-if="KeyService.isEnterprise('github')">Enterprise</span> account <b><a href="{{githubEndpoint}}{{githubLogin}}" target="_blank">{{githubLogin}}</a></b>
|
||||
</span>
|
||||
|
||||
<span class="empty" ng-if="!hasGithubLogin">
|
||||
(Not attached to GitHub<span ng-if="KeyService.isEnterprise('github')"> Enterprise</span>)
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<span class="external-login-button" provider="github" action="attach" is-link="true"
|
||||
ng-if="!hasGithubLogin"></span>
|
||||
<a href="javascript:void(0)" ng-if="hasGithubLogin"
|
||||
ng-click="detachExternalLogin('github')">Detach Account</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Google Login -->
|
||||
<tr class="external-auth-provider" ng-show="Features.GOOGLE_LOGIN">
|
||||
<td>
|
||||
<i class="fa fa-google"></i> Google Account
|
||||
</td>
|
||||
<td>
|
||||
<span ng-if="hasGoogleLogin">
|
||||
Attached to Google account <b>{{ googleLogin }}</b>
|
||||
</span>
|
||||
|
||||
<span class="empty" ng-if="!hasGoogleLogin">
|
||||
(Not attached to a Google account)
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<span class="external-login-button" provider="google" action="attach" is-link="true"
|
||||
ng-if="!hasGoogleLogin"></span>
|
||||
<a href="javascript:void(0)" ng-if="hasGoogleLogin"
|
||||
ng-click="detachExternalLogin('google')">Detach Account</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
|
@ -28,7 +28,7 @@
|
|||
|
||||
<table class="co-table" ng-if="robots.length">
|
||||
<thead>
|
||||
<td class="caret-col" ng-if="organization.is_admin && Config.isNewLayout()"></td>
|
||||
<td class="caret-col" ng-if="(user || organization.is_admin) && Config.isNewLayout()"></td>
|
||||
<td>Robot Account Name</td>
|
||||
<td ng-if="organization && Config.isNewLayout()">Teams</td>
|
||||
<td ng-if="Config.isNewLayout()">Repository Permissions</td>
|
||||
|
@ -37,7 +37,7 @@
|
|||
|
||||
<tbody ng-repeat="robotInfo in robots">
|
||||
<tr ng-class="robotInfo.showing_permissions ? 'open' : 'closed'">
|
||||
<td class="caret-col" ng-if="organization.is_admin && Config.isNewLayout()">
|
||||
<td class="caret-col" ng-if="(user || organization.is_admin) && Config.isNewLayout()">
|
||||
<span ng-if="robotInfo.repositories.length > 0" ng-click="showPermissions(robotInfo)">
|
||||
<i class="fa"
|
||||
ng-class="robotInfo.showing_permissions ? 'fa-caret-down' : 'fa-caret-right'"
|
||||
|
|
|
@ -126,7 +126,10 @@ quayApp.config(['$routeProvider', '$locationProvider', 'pages', function($routeP
|
|||
// Organization View Application
|
||||
.route('/organization/:orgname/application/:clientid', 'manage-application')
|
||||
|
||||
// User Admin
|
||||
// View User
|
||||
.route('/user/:username', 'user-view')
|
||||
|
||||
// DEPRECATED: User Admin
|
||||
.route('/user/', 'user-admin')
|
||||
|
||||
// Sign In
|
||||
|
|
41
static/js/directives/ui/authorized-apps-manager.js
Normal file
41
static/js/directives/ui/authorized-apps-manager.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* Element for managing the applications authorized by a user.
|
||||
*/
|
||||
angular.module('quay').directive('authorizedAppsManager', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/authorized-apps-manager.html',
|
||||
replace: false,
|
||||
transclude: false,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'user': '=user',
|
||||
'isEnabled': '=isEnabled'
|
||||
},
|
||||
controller: function($scope, $element, ApiService) {
|
||||
$scope.$watch('isEnabled', function(enabled) {
|
||||
if (!enabled) { return; }
|
||||
loadAuthedApps();
|
||||
});
|
||||
|
||||
var loadAuthedApps = function() {
|
||||
if ($scope.authorizedAppsResource) { return; }
|
||||
|
||||
$scope.authorizedAppsResource = ApiService.listUserAuthorizationsAsResource().get(function(resp) {
|
||||
$scope.authorizedApps = resp['authorizations'];
|
||||
});
|
||||
};
|
||||
|
||||
$scope.deleteAccess = function(accessTokenInfo) {
|
||||
var params = {
|
||||
'access_token_uuid': accessTokenInfo['uuid']
|
||||
};
|
||||
|
||||
ApiService.deleteUserAuthorization(null, params).then(function(resp) {
|
||||
$scope.authorizedApps.splice($scope.authorizedApps.indexOf(accessTokenInfo), 1);
|
||||
}, ApiService.errorDisplay('Could not revoke authorization'));
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
62
static/js/directives/ui/convert-user-to-org.js
Normal file
62
static/js/directives/ui/convert-user-to-org.js
Normal file
|
@ -0,0 +1,62 @@
|
|||
/**
|
||||
* Displays a panel for converting the current user to an organization.
|
||||
*/
|
||||
angular.module('quay').directive('convertUserToOrg', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/convert-user-to-org.html',
|
||||
replace: false,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'user': '=user'
|
||||
},
|
||||
controller: function($scope, $element, Features, PlanService, Config) {
|
||||
$scope.convertStep = 0;
|
||||
|
||||
$scope.showConvertForm = function() {
|
||||
if (Features.BILLING) {
|
||||
PlanService.getMatchingBusinessPlan(function(plan) {
|
||||
$scope.org.plan = plan;
|
||||
});
|
||||
|
||||
PlanService.getPlans(function(plans) {
|
||||
$scope.orgPlans = plans;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.convertStep = 1;
|
||||
};
|
||||
|
||||
$scope.convertToOrg = function() {
|
||||
$('#reallyconvertModal').modal({});
|
||||
};
|
||||
|
||||
$scope.reallyConvert = function() {
|
||||
if (Config.AUTHENTICATION_TYPE != 'Database') { return; }
|
||||
|
||||
$scope.loading = true;
|
||||
|
||||
var data = {
|
||||
'adminUser': $scope.org.adminUser,
|
||||
'adminPassword': $scope.org.adminPassword,
|
||||
'plan': $scope.org.plan ? $scope.org.plan.stripeId : ''
|
||||
};
|
||||
|
||||
ApiService.convertUserToOrganization(data).then(function(resp) {
|
||||
CookieService.putPermanent('quay.namespace', $scope.cuser.username);
|
||||
UserService.load();
|
||||
$location.path('/');
|
||||
}, function(resp) {
|
||||
$scope.loading = false;
|
||||
if (resp.data.reason == 'invaliduser') {
|
||||
$('#invalidadminModal').modal({});
|
||||
} else {
|
||||
$('#cannotconvertModal').modal({});
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
|
@ -11,6 +11,7 @@ angular.module('quay').directive('externalLoginButton', function () {
|
|||
scope: {
|
||||
'signInStarted': '&signInStarted',
|
||||
'redirectUrl': '=redirectUrl',
|
||||
'isLink': '=isLink',
|
||||
'provider': '@provider',
|
||||
'action': '@action'
|
||||
},
|
||||
|
|
55
static/js/directives/ui/external-logins-manager.js
Normal file
55
static/js/directives/ui/external-logins-manager.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* Element for managing the applications authorized by a user.
|
||||
*/
|
||||
angular.module('quay').directive('externalLoginsManager', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/external-logins-manager.html',
|
||||
replace: false,
|
||||
transclude: false,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'user': '=user',
|
||||
},
|
||||
controller: function($scope, $element, ApiService, UserService, Features, Config, KeyService) {
|
||||
$scope.Features = Features;
|
||||
$scope.Config = Config;
|
||||
$scope.KeyService = KeyService;
|
||||
|
||||
UserService.updateUserIn($scope, function(user) {
|
||||
$scope.cuser = jQuery.extend({}, user);
|
||||
|
||||
if ($scope.cuser.logins) {
|
||||
for (var i = 0; i < $scope.cuser.logins.length; i++) {
|
||||
var login = $scope.cuser.logins[i];
|
||||
login.metadata = login.metadata || {};
|
||||
|
||||
if (login.service == 'github') {
|
||||
$scope.hasGithubLogin = true;
|
||||
$scope.githubLogin = login.metadata['service_username'];
|
||||
$scope.githubEndpoint = KeyService['githubEndpoint'];
|
||||
}
|
||||
|
||||
if (login.service == 'google') {
|
||||
$scope.hasGoogleLogin = true;
|
||||
$scope.googleLogin = login.metadata['service_username'];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$scope.detachExternalLogin = function(kind) {
|
||||
var params = {
|
||||
'servicename': kind
|
||||
};
|
||||
|
||||
ApiService.detachExternalLogin(null, params).then(function() {
|
||||
$scope.hasGithubLogin = false;
|
||||
$scope.hasGoogleLogin = false;
|
||||
UserService.load();
|
||||
}, ApiService.errorDisplay('Count not detach service'));
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
(function() {
|
||||
/**
|
||||
* User admin/settings page.
|
||||
* DEPRECATED: User admin/settings page.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('user-admin', 'user-admin.html', UserAdminCtrl, {
|
||||
|
@ -160,11 +160,7 @@
|
|||
$scope.updatingUser = false;
|
||||
$scope.changeEmailSent = true;
|
||||
$scope.sentEmail = $scope.cuser.email;
|
||||
|
||||
// Reset the form.
|
||||
delete $scope.cuser['email'];
|
||||
|
||||
$scope.changeEmailForm.$setPristine();
|
||||
}, function(result) {
|
||||
$scope.updatingUser = false;
|
||||
UIService.showFormError('#changeEmailForm', result);
|
||||
|
|
113
static/js/pages/user-view.js
Normal file
113
static/js/pages/user-view.js
Normal file
|
@ -0,0 +1,113 @@
|
|||
(function() {
|
||||
/**
|
||||
* Page that displays details about an user.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('user-view', 'user-view.html', UserViewCtrl, {
|
||||
'newLayout': true,
|
||||
'title': 'User {{ user.username }}',
|
||||
'description': 'User {{ user.username }}'
|
||||
}, ['layout'])
|
||||
}]);
|
||||
|
||||
function UserViewCtrl($scope, $routeParams, $timeout, ApiService, UserService, UIService, AvatarService) {
|
||||
var username = $routeParams.username;
|
||||
|
||||
$scope.showInvoicesCounter = 0;
|
||||
$scope.showAppsCounter = 0;
|
||||
$scope.changeEmailInfo = {};
|
||||
$scope.changePasswordInfo = {};
|
||||
|
||||
UserService.updateUserIn($scope);
|
||||
|
||||
var loadRepositories = function() {
|
||||
var options = {
|
||||
'public': false,
|
||||
'private': true,
|
||||
'sort': true,
|
||||
'namespace': username,
|
||||
};
|
||||
|
||||
$scope.repositoriesResource = ApiService.listReposAsResource().withOptions(options).get(function(resp) {
|
||||
return resp.repositories;
|
||||
});
|
||||
};
|
||||
|
||||
var loadUser = function() {
|
||||
$scope.userResource = ApiService.getUserInformationAsResource({'username': username}).get(function(user) {
|
||||
$scope.user = user;
|
||||
|
||||
// Load the repositories.
|
||||
$timeout(function() {
|
||||
loadRepositories();
|
||||
}, 10);
|
||||
});
|
||||
};
|
||||
|
||||
// Load the user.
|
||||
loadUser();
|
||||
|
||||
$scope.showInvoices = function() {
|
||||
$scope.showInvoicesCounter++;
|
||||
};
|
||||
|
||||
$scope.showApplications = function() {
|
||||
$scope.showAppsCounter++;
|
||||
};
|
||||
|
||||
$scope.changePassword = function() {
|
||||
UIService.hidePopover('#changePasswordForm');
|
||||
$scope.changePasswordInfo.state = 'changing';
|
||||
|
||||
var data = {
|
||||
'password': $scope.changePasswordInfo.password
|
||||
};
|
||||
|
||||
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);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.generateClientToken = function() {
|
||||
var generateToken = function(password) {
|
||||
var data = {
|
||||
'password': password
|
||||
};
|
||||
|
||||
ApiService.generateUserClientKey(data).then(function(resp) {
|
||||
$scope.generatedClientToken = resp['key'];
|
||||
$('#clientTokenModal').modal({});
|
||||
}, ApiService.errorDisplay('Could not generate token'));
|
||||
};
|
||||
|
||||
UIService.showPasswordDialog('Enter your password to generated an encrypted version:', generateToken);
|
||||
};
|
||||
|
||||
$scope.changeEmail = function() {
|
||||
UIService.hidePopover('#changeEmailForm');
|
||||
|
||||
var details = {
|
||||
'email': $scope.changeEmailInfo.email
|
||||
};
|
||||
|
||||
$scope.changeEmailInfo.state = 'sending';
|
||||
ApiService.changeUserDetails(details).then(function() {
|
||||
$scope.changeEmailInfo.state = 'sent';
|
||||
delete $scope.changeEmailInfo['email'];
|
||||
}, function(result) {
|
||||
$scope.changeEmailInfo.state = 'send-error';
|
||||
UIService.showFormError('#changeEmailForm', result);
|
||||
});
|
||||
};
|
||||
}
|
||||
})();
|
|
@ -376,7 +376,6 @@
|
|||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
|
||||
|
||||
<!-- Modal message dialog -->
|
||||
<div class="modal fade" id="invalidadminModal">
|
||||
<div class="modal-dialog">
|
||||
|
|
215
static/partials/user-view.html
Normal file
215
static/partials/user-view.html
Normal file
|
@ -0,0 +1,215 @@
|
|||
<div class="resource-view user-view"
|
||||
resource="userResource"
|
||||
error-message="'User not found'">
|
||||
<div class="page-content">
|
||||
<div class="cor-title">
|
||||
<span class="cor-title-link"></span>
|
||||
<span class="cor-title-content">
|
||||
<span class="avatar" size="32" data="user.avatar"></span>
|
||||
<span class="user-name">{{ user.username }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="cor-tab-panel">
|
||||
<div class="cor-tabs" quay-show="user.is_me">
|
||||
<span class="cor-tab" tab-active="true" tab-title="Repositories" tab-target="#repos">
|
||||
<i class="fa fa-hdd-o"></i>
|
||||
</span>
|
||||
<span class="cor-tab" tab-title="Robot Accounts" tab-target="#robots">
|
||||
<i class="fa fa-wrench"></i>
|
||||
</span>
|
||||
<span class="cor-tab" tab-title="User Settings" tab-target="#settings">
|
||||
<i class="fa fa-gears"></i>
|
||||
</span>
|
||||
<span class="cor-tab" tab-title="Billing" tab-target="#usage"
|
||||
quay-show="Features.BILLING">
|
||||
<i class="fa fa-credit-card"></i>
|
||||
</span>
|
||||
<span class="cor-tab" tab-title="Billing Invoices" tab-target="#invoices"
|
||||
tab-init="showInvoices()" quay-show="Features.BILLING">
|
||||
<i class="fa ci-invoice"></i>
|
||||
</span>
|
||||
<span class="cor-tab" tab-title="External Logins" tab-target="#external">
|
||||
<i class="fa fa-external-link-square"></i>
|
||||
</span>
|
||||
<span class="cor-tab" tab-title="Authorized Applications" tab-target="#applications"
|
||||
tab-init="showApplications()">
|
||||
<i class="fa ci-application"></i>
|
||||
</span>
|
||||
</div> <!-- /cor-tabs -->
|
||||
|
||||
<div class="cor-tab-content">
|
||||
<!-- Repositories -->
|
||||
<div id="repos" class="tab-pane active">
|
||||
<h3>Repositories</h3>
|
||||
<div class="repo-list-grid"
|
||||
repositories-resource="repositoriesResource"
|
||||
starred="false"
|
||||
namespace="namespace"
|
||||
hide-title="true">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Robot Accounts -->
|
||||
<div id="robots" class="tab-pane">
|
||||
<div class="robots-manager" user="user"></div>
|
||||
</div>
|
||||
|
||||
<!-- External Logins -->
|
||||
<div id="external" class="tab-pane">
|
||||
<div class="external-logins-manager" user="user"></div>
|
||||
</div>
|
||||
|
||||
<!-- Applications -->
|
||||
<div id="applications" class="tab-pane">
|
||||
<div class="authorized-apps-manager" user="user" is-enabled="showAppsCounter"></div>
|
||||
</div>
|
||||
|
||||
<!-- Plan and Usage -->
|
||||
<div id="usage" class="tab-pane" quay-require="['BILLING']">
|
||||
<h3>Plan Usage and Billing</h3>
|
||||
<div class="plan-manager" user="user"></div>
|
||||
</div>
|
||||
|
||||
<!-- Billing Invoices -->
|
||||
<div id="invoices" class="tab-pane" quay-require="['BILLING']">
|
||||
<h3>Billing Invoices</h3>
|
||||
<div class="billing-invoices" user="user"
|
||||
makevisible="showInvoicesCounter"></div>
|
||||
</div>
|
||||
|
||||
<!-- Settings -->
|
||||
<div id="settings" class="tab-pane">
|
||||
<h3>User Settings</h3>
|
||||
|
||||
<!-- E-mail address -->
|
||||
<div class="co-panel" quay-show="Features.MAILING">
|
||||
<div class="co-panel-heading"><i class="fa fa-envelope-o"></i> E-mail Address</div>
|
||||
<div class="panel-body" style="padding-top: 5px;">
|
||||
|
||||
<div class="alert alert-success" ng-show="changeEmailInfo.state == 'sent'">
|
||||
An e-mail has been sent to {{ sentEmail }} to verify the change.
|
||||
</div>
|
||||
|
||||
<div class="cor-loader" ng-show="changeEmailInfo.state == 'sending'"></div>
|
||||
<div ng-show="changeEmailInfo.state != 'sending'">
|
||||
<form class="form-change user-settings-form"
|
||||
id="changeEmailForm" name="changeEmailForm"
|
||||
ng-submit="changeEmail(); changeEmailForm.$setPristine()"
|
||||
ng-show="!awaitingConfirmation && !registering">
|
||||
|
||||
<div class="row">
|
||||
<table class="col-md-6">
|
||||
<tr>
|
||||
<td>Current E-mail Address:</td>
|
||||
<td>{{ user.email }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>New E-mail Address:</td>
|
||||
<td>
|
||||
<input type="email" class="form-control"
|
||||
placeholder="Your new e-mail address"
|
||||
ng-model="changeEmailInfo.email" required>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary"
|
||||
ng-disabled="changeEmailForm.$invalid || changeEmail.email == user.email"
|
||||
type="submit">
|
||||
Change E-mail Address
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- /E-mail -->
|
||||
|
||||
<!-- Password -->
|
||||
<div class="co-panel" style="margin-bottom: 0px">
|
||||
<div class="co-panel-heading"><i class="fa fa-lock"></i> Password</div>
|
||||
<div class="panel-body" style="padding-top: 5px;">
|
||||
<div class="cor-loader" ng-show="changePasswordInfo.state == 'changing'"></div>
|
||||
|
||||
<!-- Encrypted Password -->
|
||||
<div class="row" ng-show="changePasswordInfo.state !='changing'">
|
||||
<div class="panel">
|
||||
<div class="panel-title">Generate Encrypted Password</div>
|
||||
|
||||
<div class="panel-body">
|
||||
<div class="alert alert-info" ng-if="!Features.REQUIRE_ENCRYPTED_BASIC_AUTH">
|
||||
Due to Docker storing passwords entered on the command line in <strong>plaintext</strong>, it is highly recommended to use the button below to generate an an encrypted version of your password.
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning" ng-if="Features.REQUIRE_ENCRYPTED_BASIC_AUTH">
|
||||
This installation is set to <strong>require</strong> encrypted passwords when
|
||||
using the Docker command line interface. To generate an encrypted password, click the button below.
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary" ng-click="generateClientToken()">
|
||||
<i class="fa fa-key" style="margin-right: 6px;"></i>Generate Encrypted Password
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Change Password -->
|
||||
<div class="row" ng-show="changePasswordInfo.state !='changing'">
|
||||
<div class="panel">
|
||||
<div class="panel-title">Change Password</div>
|
||||
|
||||
<span class="help-block" ng-show="changePasswordInfo.state == 'changed'">
|
||||
Password changed successfully
|
||||
</span>
|
||||
|
||||
<div class="panel-body">
|
||||
<div class="alert alert-warning">Note: Changing your password will also invalidate any generated encrypted passwords.</div>
|
||||
|
||||
|
||||
<form class="form-change col-md-6" id="changePasswordForm" name="changePasswordForm" ng-submit="changePassword(); changePasswordForm.$setPristine()"
|
||||
ng-show="!awaitingConfirmation && !registering">
|
||||
<input type="password" class="form-control" placeholder="Your new password" ng-model="changePasswordInfo.password" required
|
||||
ng-pattern="/^.{8,}$/">
|
||||
<input type="password" class="form-control" placeholder="Verify your new password" ng-model="changePasswordInfo.repeatPassword"
|
||||
match="changePasswordInfo.password" required ng-pattern="/^.{8,}$/">
|
||||
<button class="btn btn-danger" ng-disabled="changePasswordForm.$invalid" type="submit"
|
||||
analytics-on analytics-event="change_pass">Change Password</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- /Password -->
|
||||
|
||||
<!-- Convert -->
|
||||
<div class="co-panel" quay-show="Config.AUTHENTICATION_TYPE == 'Database'">
|
||||
<div class="co-panel-heading"><i class="fa fa-group"></i> Convert to organization</div>
|
||||
<div class="panel-body" style="padding-top: 5px;">
|
||||
<div class="convert-user-to-org" user="user"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- /Convert -->
|
||||
|
||||
</div> <!-- /cor-tab-content -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal message dialog -->
|
||||
<div class="modal fade" id="clientTokenModal">
|
||||
<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">Encrypted Password</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div style="margin-bottom: 10px;">Your generated encrypted password:</div>
|
||||
<div class="copy-box" value="generatedClientToken"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Dismiss</button>
|
||||
</div>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
</div>
|
Reference in a new issue