- Add model functions for working with prototypes
- Add API calls for working with prototypes - Get UI for prototypes working (minus add)
This commit is contained in:
parent
330051f7d9
commit
e17c3590a7
9 changed files with 329 additions and 1 deletions
|
@ -135,6 +135,7 @@ class RepositoryPermission(BaseModel):
|
|||
|
||||
class PermissionPrototype(BaseModel):
|
||||
org = ForeignKeyField(User, index=True, related_name='orgpermissionproto')
|
||||
uuid = CharField()
|
||||
activating_user = ForeignKeyField(User, index=True, null=True,
|
||||
related_name='userpermissionproto')
|
||||
delegate_user = ForeignKeyField(User, related_name='receivingpermission',
|
||||
|
|
|
@ -4,6 +4,7 @@ import datetime
|
|||
import dateutil.parser
|
||||
import operator
|
||||
import json
|
||||
import uuid
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
|
@ -671,6 +672,52 @@ def get_all_user_permissions(user):
|
|||
(UserThroughTeam.id == user))
|
||||
|
||||
|
||||
def delete_prototype_permission(org, uid):
|
||||
found = get_prototype_permission(org, uid)
|
||||
if not found:
|
||||
return None
|
||||
|
||||
found.delete_instance()
|
||||
return found
|
||||
|
||||
|
||||
def get_prototype_permission(org, uid):
|
||||
found = None
|
||||
try:
|
||||
return PermissionPrototype.get(PermissionPrototype.org == org, PermissionPrototype.uuid == uid)
|
||||
except PermissionPrototype.DoesNotExist:
|
||||
return None
|
||||
|
||||
|
||||
def get_prototype_permissions(org):
|
||||
ActivatingUser = User.alias()
|
||||
DelegateUser = User.alias()
|
||||
where = PermissionPrototype.select().where(PermissionPrototype.org == org)
|
||||
join1 = where.join(ActivatingUser, JOIN_LEFT_OUTER, on=(ActivatingUser.id == PermissionPrototype.activating_user))
|
||||
join2 = join1.join(DelegateUser, JOIN_LEFT_OUTER, on=(DelegateUser.id == PermissionPrototype.delegate_user))
|
||||
join3 = join2.join(Team, JOIN_LEFT_OUTER, on=(Team.id == PermissionPrototype.delegate_team))
|
||||
join4 = join3.join(Role, JOIN_LEFT_OUTER, on=(Role.id == PermissionPrototype.role))
|
||||
return join4
|
||||
|
||||
|
||||
def update_prototype_permission(org, uid, role_name):
|
||||
found = get_prototype_permission(org, uid)
|
||||
if not found:
|
||||
return None
|
||||
|
||||
new_role = Role.get(Role.name == role_name)
|
||||
found.role = new_role
|
||||
found.save()
|
||||
return found
|
||||
|
||||
|
||||
def add_prototype_permission(org, role_name, activating_user, delegate_user=None, delegate_team=None):
|
||||
new_role = Role.get(Role.name == role_name)
|
||||
uid = uuid.uuid4()
|
||||
return PermissionPrototype.create(org=org, uuid=uid, role=new_role, activating_user=activating_user,
|
||||
delegate_user=delegate_user, delegate_team=delegate_team)
|
||||
|
||||
|
||||
def get_org_wide_permissions(user):
|
||||
Org = User.alias()
|
||||
team_with_role = Team.select(Team, Org, TeamRole).join(TeamRole)
|
||||
|
|
108
endpoints/api.py
108
endpoints/api.py
|
@ -541,6 +541,113 @@ def change_organization_details(orgname):
|
|||
|
||||
abort(403)
|
||||
|
||||
def prototype_view(p):
|
||||
def user_view(u):
|
||||
return {
|
||||
'name': u.username,
|
||||
'is_robot': u.robot,
|
||||
'kind': 'user'
|
||||
}
|
||||
|
||||
def team_view(t):
|
||||
return {
|
||||
'name': t.name,
|
||||
'kind': 'team'
|
||||
}
|
||||
|
||||
return {
|
||||
'activating_user': user_view(p.activating_user),
|
||||
'delegate': user_view(p.delegate_user) if p.delegate_user else team_view(p.delegate_team),
|
||||
'role': p.role.name,
|
||||
'id': p.uuid
|
||||
}
|
||||
|
||||
@app.route('/api/organization/<orgname>/prototypes', methods=['GET'])
|
||||
@api_login_required
|
||||
def get_organization_prototype_permissions(orgname):
|
||||
permission = AdministerOrganizationPermission(orgname)
|
||||
if permission.can():
|
||||
try:
|
||||
org = model.get_organization(orgname)
|
||||
except model.InvalidOrganizationException:
|
||||
abort(404)
|
||||
|
||||
permissions = model.get_prototype_permissions(org)
|
||||
return jsonify({'prototypes': [prototype_view(p) for p in permissions]})
|
||||
|
||||
abort(403)
|
||||
|
||||
|
||||
@app.route('/api/organization/<orgname>/prototypes', methods=['POST'])
|
||||
@api_login_required
|
||||
def create_organization_prototype_permission(orgname):
|
||||
permission = AdministerOrganizationPermission(orgname)
|
||||
if permission.can():
|
||||
try:
|
||||
org = model.get_organization(orgname)
|
||||
except model.InvalidOrganizationException:
|
||||
abort(404)
|
||||
|
||||
details = request.get_json()
|
||||
activating_user = details['activating_user']['name']
|
||||
|
||||
delegate = details['delegate']
|
||||
delegate_kind = delegate['kind']
|
||||
delegate_name = delegate['name']
|
||||
|
||||
delegate_user = delegate_name if delegate_kind == 'user' else None
|
||||
delegate_team = delegate_name if delegate_kind == 'team' else None
|
||||
|
||||
role_name = details['role']
|
||||
|
||||
if not delegate_user and not delegate_team:
|
||||
abort(400)
|
||||
|
||||
prototype = model.add_prototype_permission(org, role_name, activating_user, delegate_user, delegate_team)
|
||||
return jsonify(protoype_view(prototype))
|
||||
|
||||
abort(403)
|
||||
|
||||
|
||||
@app.route('/api/organization/<orgname>/prototypes/<prototypeid>', methods=['DELETE'])
|
||||
@api_login_required
|
||||
def delete_organization_prototype_permission(orgname, prototypeid):
|
||||
permission = AdministerOrganizationPermission(orgname)
|
||||
if permission.can():
|
||||
try:
|
||||
org = model.get_organization(orgname)
|
||||
except model.InvalidOrganizationException:
|
||||
abort(404)
|
||||
|
||||
prototype = model.delete_prototype_permission(org, prototypeid)
|
||||
if not prototype:
|
||||
abort(404)
|
||||
|
||||
return make_response('Deleted', 204)
|
||||
|
||||
abort(403)
|
||||
|
||||
|
||||
@app.route('/api/organization/<orgname>/prototypes/<prototypeid>', methods=['PUT'])
|
||||
@api_login_required
|
||||
def update_organization_prototype_permission(orgname, prototypeid):
|
||||
permission = AdministerOrganizationPermission(orgname)
|
||||
if permission.can():
|
||||
try:
|
||||
org = model.get_organization(orgname)
|
||||
except model.InvalidOrganizationException:
|
||||
abort(404)
|
||||
|
||||
details = request.get_json()
|
||||
role_name = details['role']
|
||||
prototype = model.update_prototype_permission(org, prototypeid, role_name)
|
||||
if not prototype:
|
||||
abort(404)
|
||||
|
||||
return jsonify(prototype_view(prototype))
|
||||
|
||||
abort(403)
|
||||
|
||||
|
||||
@app.route('/api/organization/<orgname>/members', methods=['GET'])
|
||||
@api_login_required
|
||||
|
@ -1140,7 +1247,6 @@ def get_filedrop_url():
|
|||
'file_id': file_id
|
||||
})
|
||||
|
||||
|
||||
def role_view(repo_perm_obj):
|
||||
return {
|
||||
'role': repo_perm_obj.role.name,
|
||||
|
|
|
@ -153,6 +153,10 @@ def initialize_database():
|
|||
LogEntryKind.create(name='org_remove_team_member')
|
||||
LogEntryKind.create(name='org_set_team_description')
|
||||
LogEntryKind.create(name='org_set_team_role')
|
||||
|
||||
LogEntryKind.create(name='org_create_prototype_permission')
|
||||
LogEntryKind.create(name='org_modify_prototype_permission')
|
||||
LogEntryKind.create(name='org_delete_prototype_permission')
|
||||
|
||||
|
||||
def wipe_database():
|
||||
|
@ -261,6 +265,10 @@ def populate_database():
|
|||
build.status_url = 'http://localhost:5000/test/build/status'
|
||||
build.save()
|
||||
|
||||
model.add_prototype_permission(org, 'read', activating_user=new_user_1, delegate_user=new_user_2)
|
||||
model.add_prototype_permission(org, 'read', activating_user=new_user_1, delegate_team=reader_team)
|
||||
model.add_prototype_permission(org, 'write', activating_user=new_user_2, delegate_user=new_user_1)
|
||||
|
||||
today = datetime.today()
|
||||
week_ago = today - timedelta(6)
|
||||
six_ago = today - timedelta(5)
|
||||
|
|
|
@ -2256,6 +2256,16 @@ p.editable:hover i {
|
|||
color: steelblue;
|
||||
}
|
||||
|
||||
.prototype-manager-element thead th {
|
||||
padding: 4px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.prototype-manager-element td {
|
||||
padding: 10px !important;
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
|
||||
.org-list h2 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
|
59
static/directives/prototype-manager.html
Normal file
59
static/directives/prototype-manager.html
Normal file
|
@ -0,0 +1,59 @@
|
|||
<div class="prototype-manager-element">
|
||||
<div class="quay-spinner" ng-show="loading"></div>
|
||||
|
||||
<div class="container" ng-show="!loading">
|
||||
<div class="alert alert-info">
|
||||
Default permissions provide a means of specifying <span class="context-tooltip" title="By default, all repositories have the creating user added as an 'Admin'" bs-tooltip="tooltip.title">additional</span> permissions that should be granted automatically to a repository based on the user or robot creating the repository.
|
||||
</div>
|
||||
|
||||
<div class="side-controls">
|
||||
<button class="btn btn-success">
|
||||
<i class="fa fa-plus"></i>
|
||||
New Default Permission
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<th>
|
||||
<span class="context-tooltip" title="The user that is creating the repository"
|
||||
bs-tooltip="tooltip.title" data-container="body">
|
||||
Creating User/Robot
|
||||
</span>
|
||||
</th>
|
||||
<th>
|
||||
<span class="context-tooltip" title="The user or team that is being granted the permission"
|
||||
bs-tooltip="tooltip.title" data-container="body">
|
||||
Delegated User/Team
|
||||
</span>
|
||||
</th>
|
||||
<th>Permission</th>
|
||||
<th style="width: 150px"></th>
|
||||
</thead>
|
||||
|
||||
<tr ng-repeat="prototype in prototypes">
|
||||
<td>
|
||||
<span class="entity-reference" orgname="organization.name" name="prototype.activating_user.name"
|
||||
isrobot="prototype.activating_user.is_robot"></span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="entity-reference" orgname="organization.name"
|
||||
name="prototype.delegate.kind == 'team' ? null :prototype.delegate.name"
|
||||
team="prototype.delegate.kind == 'team' ? prototype.delegate.name : null"
|
||||
isrobot="prototype.delegate.is_robot"></span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="role-group" current-role="prototype.role" role-changed="setRole(role, prototype)" roles="roles"></span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="delete-ui" tabindex="0">
|
||||
<span class="delete-ui-button" ng-click="deletePrototype(prototype)"><button class="btn btn-danger">Delete</button></span>
|
||||
<i class="fa fa-times" bs-tooltip="tooltip.title" data-placement="right" title="Delete Permission"></i>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
|
@ -1479,6 +1479,97 @@ quayApp.directive('robotsManager', function () {
|
|||
});
|
||||
|
||||
|
||||
quayApp.directive('prototypeManager', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/prototype-manager.html',
|
||||
replace: false,
|
||||
transclude: false,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'organization': '=organization'
|
||||
},
|
||||
controller: function($scope, $element, ApiService) {
|
||||
$scope.loading = false;
|
||||
|
||||
$scope.roles = [
|
||||
{ 'id': 'read', 'title': 'Read', 'kind': 'success' },
|
||||
{ 'id': 'write', 'title': 'Write', 'kind': 'success' },
|
||||
{ 'id': 'admin', 'title': 'Admin', 'kind': 'primary' }
|
||||
];
|
||||
|
||||
$scope.setRole = function(role, prototype) {
|
||||
var params = {
|
||||
'orgname': $scope.organization.name,
|
||||
'prototypeid': prototype.id
|
||||
};
|
||||
|
||||
var data = {
|
||||
'id': prototype.id,
|
||||
'role': role
|
||||
};
|
||||
|
||||
ApiService.updateOrganizationPrototypePermission(data, params).then(function(resp) {
|
||||
prototype.role = role;
|
||||
}, function(resp) {
|
||||
bootbox.dialog({
|
||||
"message": resp.data ? resp.data : 'The permission could not be modified',
|
||||
"title": "Cannot modify permission",
|
||||
"buttons": {
|
||||
"close": {
|
||||
"label": "Close",
|
||||
"className": "btn-primary"
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.deletePrototype = function(prototype) {
|
||||
$scope.loading = true;
|
||||
|
||||
var params = {
|
||||
'orgname': $scope.organization.name,
|
||||
'prototypeid': prototype.id
|
||||
};
|
||||
|
||||
ApiService.deleteOrganizationPrototypePermission(null, params).then(function(resp) {
|
||||
$scope.prototypes.splice($scope.prototypes.indexOf(prototype), 1);
|
||||
$scope.loading = false;
|
||||
}, function(resp) {
|
||||
bootbox.dialog({
|
||||
"message": resp.data ? resp.data : 'The permission could not be deleted',
|
||||
"title": "Cannot delete permission",
|
||||
"buttons": {
|
||||
"close": {
|
||||
"label": "Close",
|
||||
"className": "btn-primary"
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var update = function() {
|
||||
if (!$scope.organization) { return; }
|
||||
if ($scope.loading) { return; }
|
||||
|
||||
var params = {'orgname': $scope.organization.name};
|
||||
|
||||
$scope.loading = true;
|
||||
ApiService.getOrganizationPrototypePermissions(null, params).then(function(resp) {
|
||||
$scope.prototypes = resp.prototypes;
|
||||
$scope.loading = false;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.$watch('organization', update);
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
||||
|
||||
|
||||
quayApp.directive('popupInputButton', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#logs" ng-click="loadLogs()">Usage Logs</a></li>
|
||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#members" ng-click="loadMembers()">Members</a></li>
|
||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#robots">Robot Accounts</a></li>
|
||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#prototypes">Default Permissions</a></li>
|
||||
<li ng-show="hasPaidPlan"><a href="javascript:void(0)" data-toggle="tab" data-target="#billingoptions">Billing</a></li>
|
||||
<li ng-show="hasPaidPlan"><a href="javascript:void(0)" data-toggle="tab" data-target="#billing" ng-click="loadInvoices()">Billing History</a></li>
|
||||
</ul>
|
||||
|
@ -49,6 +50,11 @@
|
|||
<div class="robots-manager" organization="organization"></div>
|
||||
</div>
|
||||
|
||||
<!-- Prototypes tab -->
|
||||
<div id="prototypes" class="tab-pane">
|
||||
<div class="prototype-manager" organization="organization"></div>
|
||||
</div>
|
||||
|
||||
<!-- Logs tab -->
|
||||
<div id="logs" class="tab-pane">
|
||||
<div class="logs-view" organization="organization" visible="logsShown"></div>
|
||||
|
|
Binary file not shown.
Reference in a new issue