Add the members tab to the org admin
This commit is contained in:
parent
fb1732d069
commit
56f777448a
5 changed files with 112 additions and 3 deletions
|
@ -348,6 +348,12 @@ def get_organization_team(orgname, teamname):
|
||||||
return result[0]
|
return result[0]
|
||||||
|
|
||||||
|
|
||||||
|
def get_organization_members_with_teams(organization):
|
||||||
|
joined = TeamMember.select().annotate(Team).annotate(User)
|
||||||
|
query = joined.where(Team.organization == organization)
|
||||||
|
return query
|
||||||
|
|
||||||
|
|
||||||
def get_organization_team_members(teamid):
|
def get_organization_team_members(teamid):
|
||||||
joined = User.select().join(TeamMember).join(Team)
|
joined = User.select().join(TeamMember).join(Team)
|
||||||
query = joined.where(Team.id == teamid)
|
query = joined.where(Team.id == teamid)
|
||||||
|
|
|
@ -307,6 +307,31 @@ def get_organization(orgname):
|
||||||
|
|
||||||
abort(403)
|
abort(403)
|
||||||
|
|
||||||
|
@app.route('/api/organization/<orgname>/members', methods=['GET'])
|
||||||
|
@api_login_required
|
||||||
|
def get_organization_members(orgname):
|
||||||
|
permission = AdministerOrganizationPermission(orgname)
|
||||||
|
if permission.can():
|
||||||
|
try:
|
||||||
|
org = model.get_organization(orgname)
|
||||||
|
except model.InvalidOrganizationException:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
# Loop to create the members dictionary. Note that the members collection
|
||||||
|
# will return an entry for *every team* a member is on, so we will have
|
||||||
|
# duplicate keys (which is why we pre-build the dictionary).
|
||||||
|
members_dict = {}
|
||||||
|
members = model.get_organization_members_with_teams(org)
|
||||||
|
for member in members:
|
||||||
|
if not member.user.username in members_dict:
|
||||||
|
members_dict[member.user.username] = {'username': member.user.username, 'teams': []}
|
||||||
|
|
||||||
|
members_dict[member.user.username]['teams'].append(member.team.name)
|
||||||
|
|
||||||
|
return jsonify({'members': members_dict})
|
||||||
|
|
||||||
|
abort(403)
|
||||||
|
|
||||||
@app.route('/api/organization/<orgname>/private', methods=['GET'])
|
@app.route('/api/organization/<orgname>/private', methods=['GET'])
|
||||||
@api_login_required
|
@api_login_required
|
||||||
def get_organization_private_allowed(orgname):
|
def get_organization_private_allowed(orgname):
|
||||||
|
|
|
@ -1514,7 +1514,6 @@ p.editable:hover i {
|
||||||
100% { background-color: rgba(92, 184, 92, 0.36); }
|
100% { background-color: rgba(92, 184, 92, 0.36); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.org-view .team-title {
|
.org-view .team-title {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
|
@ -1533,6 +1532,33 @@ p.editable:hover i {
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.org-admin .team-link {
|
||||||
|
display: inline-block;
|
||||||
|
text-transform: capitalize;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.org-admin #members table td {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.org-admin #members table i {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.org-admin #members .side-controls {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.org-admin #members .result-count {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.org-admin #members .filter-input {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
.plan-manager-element .plans-table thead td {
|
.plan-manager-element .plans-table thead td {
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
|
@ -1109,7 +1109,28 @@ function OrgAdminCtrl($rootScope, $scope, Restangular, $routeParams, UserService
|
||||||
});
|
});
|
||||||
|
|
||||||
var orgname = $routeParams.orgname;
|
var orgname = $routeParams.orgname;
|
||||||
|
|
||||||
$scope.orgname = orgname;
|
$scope.orgname = orgname;
|
||||||
|
$scope.membersLoading = true;
|
||||||
|
$scope.membersFound = null;
|
||||||
|
|
||||||
|
$scope.loadMembers = function() {
|
||||||
|
if ($scope.membersFound) { return; }
|
||||||
|
$scope.membersLoading = true;
|
||||||
|
|
||||||
|
var getMembers = Restangular.one(getRestUrl('organization', orgname, 'members'));
|
||||||
|
getMembers.get().then(function(resp) {
|
||||||
|
var membersArray = [];
|
||||||
|
for (var key in resp.members) {
|
||||||
|
if (resp.members.hasOwnProperty(key)) {
|
||||||
|
membersArray.push(resp.members[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.membersFound = membersArray;
|
||||||
|
$scope.membersLoading = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
var loadOrganization = function() {
|
var loadOrganization = function() {
|
||||||
var getOrganization = Restangular.one(getRestUrl('organization', orgname));
|
var getOrganization = Restangular.one(getRestUrl('organization', orgname));
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
<ul class="nav nav-pills nav-stacked">
|
<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 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="#members">Members</a></li>
|
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#members" ng-click="loadMembers()">Members</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -28,7 +28,38 @@
|
||||||
|
|
||||||
<!-- Members tab -->
|
<!-- Members tab -->
|
||||||
<div id="members" class="tab-pane">
|
<div id="members" class="tab-pane">
|
||||||
members
|
<i class="fa fa-spinner fa-spin fa-3x" ng-show="membersLoading"></i>
|
||||||
|
|
||||||
|
<div ng-show="!membersLoading">
|
||||||
|
<div class="side-controls">
|
||||||
|
<div class="result-count">
|
||||||
|
Showing {{(membersFound | filter:search | limitTo:50).length}} of {{(membersFound | filter:search).length}} matching members
|
||||||
|
</div>
|
||||||
|
<div class="filter-input">
|
||||||
|
<input id="member-filter" class="form-control" placeholder="Filter Members" type="text" ng-model="search.$">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<th>User</th>
|
||||||
|
<th>Teams</th>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tr ng-repeat="memberInfo in (membersFound | filter:search | limitTo:50)">
|
||||||
|
<td>
|
||||||
|
<i class="fa fa-user"></i>
|
||||||
|
{{ memberInfo.username }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="team-link" ng-repeat="team in memberInfo.teams">
|
||||||
|
<i class="fa fa-group"></i>
|
||||||
|
<a href="/organization/{{ organization.name }}/teams/{{ team }}">{{ team }}</a>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Reference in a new issue