Start on new org view

This commit is contained in:
Joseph Schorr 2015-03-25 15:31:05 -04:00
parent a7a8571396
commit 2459b6b467
11 changed files with 442 additions and 103 deletions

View file

@ -24,16 +24,20 @@ logger = logging.getLogger(__name__)
def org_view(o, teams):
admin_org = AdministerOrganizationPermission(o.username)
is_admin = admin_org.can()
is_admin = AdministerOrganizationPermission(o.username).can()
is_member = OrganizationMemberPermission(o.username).can()
view = {
'name': o.username,
'email': o.email if is_admin else '',
'avatar': avatar.compute_hash(o.email, name=o.username),
'teams': {t.name : team_view(o.username, t) for t in teams},
'is_admin': is_admin
'is_admin': is_admin,
'is_member': is_member
}
if teams is not None:
view['teams'] = {t.name : team_view(o.username, t) for t in teams}
if is_admin:
view['invoice_email'] = o.invoice_email
@ -129,17 +133,17 @@ class Organization(ApiResource):
@nickname('getOrganization')
def get(self, orgname):
""" Get the details for the specified organization """
permission = OrganizationMemberPermission(orgname)
if permission.can():
try:
org = model.get_organization(orgname)
except model.InvalidOrganizationException:
raise NotFound()
teams = None
if OrganizationMemberPermission(orgname).can():
teams = model.get_teams_within_org(org)
return org_view(org, teams)
raise Unauthorized()
@require_scope(scopes.ORG_ADMIN)
@nickname('changeOrganizationDetails')

View file

@ -0,0 +1,9 @@
.org-view .organization-name {
vertical-align: middle;
margin-left: 6px;
}
.org-view h3 {
margin-bottom: 20px;
margin-top: 0px;
}

View file

@ -2,6 +2,7 @@
<div class="new-repo-listing">
<!-- Titles -->
<div ng-if="!hideTitle">
<div ng-if="starred" class="repo-list-title">
<i class="fa fa-star starred"></i>
Starred
@ -12,6 +13,7 @@
<a ng-if="isOrganization(namespace.name)"
href="/organization/{{ namespace.name }}">{{ namespace.name }}</a>
</div>
</div>
<!-- Repositories -->
<div class="resource-view" resource="repositoriesResource">

View file

@ -14,7 +14,7 @@
error-message="'Could not load repository events'">
<div class="empty" ng-if="!notifications.length">
<div class="empty-primary-msg">No notification have been setup for this repository.</div>
<div class="empty-primary-msg">No notifications have been setup for this repository.</div>
<div class="empty-secondary-msg" ng-if="repository.can_write">
Click the "Create Notification" button above to add a new notification for a repository event.
</div>

View file

@ -0,0 +1,30 @@
<div class="teams-manager-element">
<span class="popup-input-button" pattern="TEAM_PATTERN" placeholder="'Team Name'"
submitted="createTeam(value)">
<i class="fa fa-group"></i> Create Team
</span>
<div class="team-listing" ng-repeat="(name, team) in organization.teams">
<div id="team-{{name}}" class="row">
<div class="col-sm-7 col-md-8">
<div class="team-title">
<i class="fa fa-group"></i>
<span ng-show="team.can_view">
<a href="/organization/{{ organization.name }}/teams/{{ team.name }}">{{ team.name }}</a>
</span>
<span ng-show="!team.can_view">
{{ team.name }}
</span>
</div>
<div class="team-description markdown-view" content="team.description" first-line-only="true"></div>
</div>
<div class="col-sm-5 col-md-4 control-col" ng-show="organization.is_admin">
<span class="role-group" current-role="team.role" role-changed="setRole(role, team.name)" roles="teamRoles"></span>
<button class="btn btn-sm btn-danger" ng-click="askDeleteTeam(team.name)">Delete</button>
</div>
</div>
</div>
</div>
</div>

View file

@ -11,9 +11,9 @@ angular.module('quay').directive('repoListGrid', function () {
scope: {
repositoriesResource: '=repositoriesResource',
starred: '=starred',
user: "=user",
namespace: '=namespace',
starToggled: '&starToggled'
starToggled: '&starToggled',
hideTitle: '=hideTitle'
},
controller: function($scope, $element, UserService) {
$scope.isOrganization = function(namespace) {

View file

@ -0,0 +1,82 @@
/**
* Element for managing the teams of an organization.
*/
angular.module('quay').directive('teamsManager', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/teams-manager.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'organization': '=organization'
},
controller: function($scope, $element, ApiService, CreateService) {
$scope.TEAM_PATTERN = TEAM_PATTERN;
$scope.teamRoles = [
{ 'id': 'member', 'title': 'Member', 'kind': 'default' },
{ 'id': 'creator', 'title': 'Creator', 'kind': 'success' },
{ 'id': 'admin', 'title': 'Admin', 'kind': 'primary' }
];
$scope.setRole = function(role, teamname) {
var previousRole = $scope.organization.teams[teamname].role;
$scope.organization.teams[teamname].role = role;
var params = {
'orgname': $scope.organization.name,
'teamname': teamname
};
var data = $scope.organization.teams[teamname];
var errorHandler = ApiService.errorDisplay('Cannot update team', function(resp) {
$scope.organization.teams[teamname].role = previousRole;
});
ApiService.updateOrganizationTeam(data, params).then(function(resp) {
}, errorHandler);
};
$scope.createTeam = function(teamname) {
if (!teamname) {
return;
}
if ($scope.organization.teams[teamname]) {
$('#team-' + teamname).removeClass('highlight');
setTimeout(function() {
$('#team-' + teamname).addClass('highlight');
}, 10);
return;
}
var orgname = $scope.organization.name;
CreateService.createOrganizationTeam(ApiService, orgname, teamname, function(created) {
$scope.organization.teams[teamname] = created;
});
};
$scope.askDeleteTeam = function(teamname) {
bootbox.confirm('Are you sure you want to delete team ' + teamname + '?', function(resp) {
if (resp) {
$scope.deleteTeam(teamname);
}
});
};
$scope.deleteTeam = function(teamname) {
var params = {
'orgname': $scope.organization.name,
'teamname': teamname
};
ApiService.deleteOrganizationTeam(null, params).then(function() {
delete $scope.organization.teams[teamname];
}, ApiService.errorDisplay('Cannot delete team'));
};
}
};
return directiveDefinitionObject;
});

View file

@ -1,6 +1,6 @@
(function() {
/**
* Organization admin/settings page.
* DEPRECATED: Organization admin/settings page.
*/
angular.module('quayPages').config(['pages', function(pages) {
pages.create('org-admin', 'org-admin.html', OrgAdminCtrl);

View file

@ -3,10 +3,94 @@
* Page that displays details about an organization, such as its teams.
*/
angular.module('quayPages').config(['pages', function(pages) {
pages.create('org-view', 'org-view.html', OrgViewCtrl);
pages.create('org-view', 'org-view.html', OrgViewCtrl, {
'newLayout': true,
'title': 'Organization {{ organization.name }}',
'description': 'Organization {{ organization.name }}'
}, ['layout'])
pages.create('org-view', 'old-org-view.html', OldOrgViewCtrl, {
}, ['old-layout']);
}]);
function OrgViewCtrl($rootScope, $scope, ApiService, $routeParams, CreateService) {
function OrgViewCtrl($scope, $routeParams, $timeout, ApiService, UIService) {
var orgname = $routeParams.orgname;
$scope.showLogsCounter = 0;
$scope.showApplicationsCounter = 0;
$scope.showInvoicesCounter = 0;
$scope.changingOrganization = false;
$scope.$watch('organizationEmail', function(e) {
UIService.hidePopover('#changeEmailForm');
});
var loadRepositories = function() {
var options = {
'public': false,
'private': true,
'sort': true,
'namespace': orgname,
};
$scope.repositoriesResource = ApiService.listReposAsResource().withOptions(options).get(function(resp) {
return resp.repositories;
});
};
var loadOrganization = function() {
$scope.orgResource = ApiService.getOrganizationAsResource({'orgname': orgname}).get(function(org) {
$scope.organization = org;
$scope.organizationEmail = org.email;
$scope.isAdmin = org.is_admin;
$scope.isMember = org.is_member;
// Load the repositories.
$timeout(function() {
loadRepositories();
}, 10);
});
};
// Load the organization.
loadOrganization();
$scope.showInvoices = function() {
$scope.showInvoicesCounter++;
};
$scope.showApplications = function() {
$scope.showApplicationsCounter++;
};
$scope.showLogs = function() {
$scope.showLogsCounter++;
};
$scope.changeEmail = function() {
UIService.hidePopover('#changeEmailForm');
$scope.changingOrganization = true;
var params = {
'orgname': orgname
};
var data = {
'email': $scope.organizationEmail
};
ApiService.changeOrganizationDetails(data, params).then(function(org) {
$scope.changingOrganization = false;
$scope.changeEmailForm.$setPristine();
$scope.organization = org;
}, function(result) {
$scope.changingOrganization = false;
UIService.showFormError('#changeEmailForm', result);
});
};
}
function OldOrgViewCtrl($rootScope, $scope, ApiService, $routeParams, CreateService) {
var orgname = $routeParams.orgname;
$scope.TEAM_PATTERN = TEAM_PATTERN;

View file

@ -0,0 +1,85 @@
<div class="resource-view" resource="orgResource" error-message="'No matching organization found'">
<div class="org-view cor-container">
<div class="organization-header" organization="organization">
<div class="header-buttons" ng-show="organization.is_admin">
<span class="popup-input-button" pattern="TEAM_PATTERN" placeholder="'Team Name'"
submitted="createTeam(value)">
<i class="fa fa-group"></i> Create Team
</span>
<a class="btn btn-default" href="/organization/{{ organization.name }}/admin"><i class="fa fa-gear"></i> Settings</a>
</div>
</div>
<div class="row hidden-xs">
<div class="col-md-4 col-md-offset-8 col-sm-5 col-sm-offset-7 header-col" ng-show="organization.is_admin">
Team Permissions
<i class="info-icon fa fa-info-circle" data-placement="bottom" data-original-title="" data-title=""
data-content="Global permissions for the team and its members<br><br><dl><dt>Member</dt><dd>Permissions are assigned on a per repository basis</dd><dt>Creator</dt><dd>A team can create its own repositories</dd><dt>Admin</dt><dd>A team has full control of the organization</dd></dl>"></i>
</div>
</div>
<div class="team-listing" ng-repeat="(name, team) in organization.teams">
<div id="team-{{name}}" class="row">
<div class="col-sm-7 col-md-8">
<div class="team-title">
<i class="fa fa-group"></i>
<span ng-show="team.can_view">
<a href="/organization/{{ organization.name }}/teams/{{ team.name }}">{{ team.name }}</a>
</span>
<span ng-show="!team.can_view">
{{ team.name }}
</span>
</div>
<div class="team-description markdown-view" content="team.description" first-line-only="true"></div>
</div>
<div class="col-sm-5 col-md-4 control-col" ng-show="organization.is_admin">
<span class="role-group" current-role="team.role" role-changed="setRole(role, team.name)" roles="teamRoles"></span>
<button class="btn btn-sm btn-danger" ng-click="askDeleteTeam(team.name)">Delete</button>
</div>
</div>
</div>
</div>
</div>
<!-- Modal message dialog -->
<div class="modal fade" id="cannotChangeTeamModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Cannot change team</h4>
</div>
<div class="modal-body">
<span ng-show="!roleError">You do not have permission to change properties on teams.</span>
<span ng-show="roleError">{{ roleError }}</span>
</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="confirmdeleteModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Delete Team?</h4>
</div>
<div class="modal-body">
Are you sure you would like to delete this team? This <b>cannot be undone</b>.
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" ng-click="deleteTeam()">Delete Team</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->

View file

@ -1,85 +1,128 @@
<div class="resource-view" resource="orgResource" error-message="'No matching organization found'">
<div class="org-view cor-container">
<div class="organization-header" organization="organization">
<div class="header-buttons" ng-show="organization.is_admin">
<span class="popup-input-button" pattern="TEAM_PATTERN" placeholder="'Team Name'"
submitted="createTeam(value)">
<i class="fa fa-group"></i> Create Team
</span>
<a class="btn btn-default" href="/organization/{{ organization.name }}/admin"><i class="fa fa-gear"></i> Settings</a>
</div>
</div>
<div class="row hidden-xs">
<div class="col-md-4 col-md-offset-8 col-sm-5 col-sm-offset-7 header-col" ng-show="organization.is_admin">
Team Permissions
<i class="info-icon fa fa-info-circle" data-placement="bottom" data-original-title="" data-title=""
data-content="Global permissions for the team and its members<br><br><dl><dt>Member</dt><dd>Permissions are assigned on a per repository basis</dd><dt>Creator</dt><dd>A team can create its own repositories</dd><dt>Admin</dt><dd>A team has full control of the organization</dd></dl>"></i>
</div>
</div>
<div class="team-listing" ng-repeat="(name, team) in organization.teams">
<div id="team-{{name}}" class="row">
<div class="col-sm-7 col-md-8">
<div class="team-title">
<i class="fa fa-group"></i>
<span ng-show="team.can_view">
<a href="/organization/{{ organization.name }}/teams/{{ team.name }}">{{ team.name }}</a>
</span>
<span ng-show="!team.can_view">
{{ team.name }}
<div class="resource-view org-view"
resource="orgResource"
error-message="'Organization 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" hash="organization.avatar"></span>
<span class="organization-name">{{ organization.name }}</span>
</span>
</div>
<div class="team-description markdown-view" content="team.description" first-line-only="true"></div>
<div class="cor-tab-panel">
<div class="cor-tabs" quay-show="isMember">
<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="Teams" tab-target="#teams">
<i class="fa fa-users"></i>
</span>
<span class="cor-tab" tab-title="Robot Accounts" tab-target="#robots" ng-if="isAdmin">
<i class="fa fa-wrench"></i>
</span>
<span class="cor-tab" tab-title="Default Permissions" tab-target="#default" ng-if="isAdmin">
<i class="fa ci-stamp"></i>
</span>
<span class="cor-tab" tab-title="Billing" tab-target="#usage"
quay-show="isAdmin && 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="isAdmin && Features.BILLING">
<i class="fa ci-invoice"></i>
</span>
<span class="cor-tab" tab-title="Usage Logs" tab-target="#logs"
tab-init="showLogs()" ng-if="isAdmin">
<i class="fa fa-bar-chart"></i>
</span>
<span class="cor-tab" tab-title="Applications" tab-target="#applications"
tab-init="showApplications()"ng-if="isAdmin">
<i class="fa ci-application"></i>
</span>
<span class="cor-tab" tab-title="Organization Settings" tab-target="#settings"
ng-if="isAdmin">
<i class="fa fa-gears"></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>
<div class="col-sm-5 col-md-4 control-col" ng-show="organization.is_admin">
<span class="role-group" current-role="team.role" role-changed="setRole(role, team.name)" roles="teamRoles"></span>
<button class="btn btn-sm btn-danger" ng-click="askDeleteTeam(team.name)">Delete</button>
<!-- Teams -->
<div id="teams" class="tab-pane">
<h3>Teams</h3>
<div class="teams-manager" organization="organization"></div>
</div>
<!-- Robot Accounts -->
<div id="robots" class="tab-pane">
<h3>Robot Accounts</h3>
<div class="robots-manager" organization="organization"></div>
</div>
<!-- Default Permissions -->
<div id="default" class="tab-pane">
<h3>Default Permissions</h3>
<div class="prototype-manager" organization="organization"></div>
</div>
<!-- Usage Logs -->
<div id="logs" class="tab-pane">
<div class="logs-view" organization="organization" makevisible="showLogsCounter"></div>
</div>
<!-- Applications -->
<div id="applications" class="tab-pane">
<h3>Applications</h3>
<div class="application-manager" organization="organization"
makevisible="showApplicationsCounter"></div>
</div>
<!-- Plan and Usage -->
<div id="usage" class="tab-pane" quay-require="['BILLING']">
<h3>Plan Usage and Billing</h3>
<div class="plan-manager" organization="organization.name"></div>
</div>
<!-- Billing Invoices -->
<div id="invoices" class="tab-pane" quay-require="['BILLING']">
<h3>Billing Invoices</h3>
<div class="billing-invoices" organization="organization"
makevisible="showInvoicesCounter"></div>
</div>
<!-- Settings -->
<div id="settings" class="tab-pane">
<h3>Organization Settings</h3>
<div class="panel" ng-show="!changingOrganization">
<div class="panel-title">Organization's e-mail address</div>
<div class="panel-content" style="padding-left: 20px; margin-top: 10px;">
<form class="form-change" id="changeEmailForm" name="changeEmailForm" ng-submit="changeEmail()" data-trigger="manual"
data-content="{{ changeEmailError }}" data-placement="bottom" ng-show="!updatingOrganization">
<span class="avatar" size="24" email="organizationEmail" name="orgname"></span>
<input type="email" class="form-control" ng-model="organizationEmail"
style="margin-left: 10px; margin-right: 10px; width: 400px; display: inline-block;" required>
<button class="btn btn-primary" type="submit" ng-disabled="changeEmailForm.$invalid || organizationEmail == organization.email">
Save
</button>
</form>
</div>
</div>
</div>
</div> <!-- /cor-tab-content -->
</div>
</div>
</div>
<!-- Modal message dialog -->
<div class="modal fade" id="cannotChangeTeamModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Cannot change team</h4>
</div>
<div class="modal-body">
<span ng-show="!roleError">You do not have permission to change properties on teams.</span>
<span ng-show="roleError">{{ roleError }}</span>
</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="confirmdeleteModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Delete Team?</h4>
</div>
<div class="modal-body">
Are you sure you would like to delete this team? This <b>cannot be undone</b>.
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" ng-click="deleteTeam()">Delete Team</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->