feat: edit permission entries (v1.35.0)
The Permissions page only offered Delete, so changing a role or scope meant removing the grant and re-adding it from memory. A permission's id is derived from (subjectType, subject, scope, domain), so changing any of those is a different record rather than an update. The new PUT creates the new grant and removes the superseded one in that order, so an edit can never leave the old grant behind still conferring access. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,11 +6,20 @@
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
// Kept alongside the rendered scope so the Edit modal can pre-fill from the
|
||||
// record without a second round-trip ($.scope exposes push/empty/remove,
|
||||
// not a lookup by key).
|
||||
var permissionsById = {};
|
||||
|
||||
function loadPermissions() {
|
||||
app.permission.list(function(error, data){
|
||||
if(error) return app.messages.action(error, $('#permissions-list'), 'danger');
|
||||
permissionsById = {};
|
||||
$.scope.Permission.empty();
|
||||
for(let p of (data.results || [])) $.scope.Permission.push(p);
|
||||
for(let p of (data.results || [])){
|
||||
permissionsById[p.id] = p;
|
||||
$.scope.Permission.push(p);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -82,6 +91,76 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Edit an existing grant. A permission's id is derived from
|
||||
// (subjectType, subject, scope, domain), so changing any of those replaces
|
||||
// the record rather than updating it -- the API handles that and removes the
|
||||
// superseded grant, which is why this is a single Edit rather than making
|
||||
// the operator delete and re-add (and risk leaving the old grant in place).
|
||||
function permissionEditOpen(id){
|
||||
var p = permissionsById[id] || {};
|
||||
function sel(v, want){ return v === want ? ' selected' : ''; }
|
||||
app.modal.open({title: 'Edit Permission', bodyHtml:
|
||||
'<div class="mb-3">'
|
||||
+ '<label class="form-label fw-bold">Subject type</label>'
|
||||
+ '<select class="form-select" id="perm-edit-subjectType">'
|
||||
+ '<option value="user"' + sel(p.subjectType, 'user') + '>User</option>'
|
||||
+ '<option value="group"' + sel(p.subjectType, 'group') + '>Group</option>'
|
||||
+ '</select>'
|
||||
+ '</div>'
|
||||
+ '<div class="mb-3">'
|
||||
+ '<label class="form-label fw-bold">Subject (username or group)</label>'
|
||||
+ '<input type="text" class="form-control" id="perm-edit-subject" list="subjectUsers" autocomplete="off" value="' + app.util.escapeHtml(p.subject || '') + '" />'
|
||||
+ '</div>'
|
||||
+ '<div class="mb-3">'
|
||||
+ '<label class="form-label fw-bold">Scope</label>'
|
||||
+ '<select class="form-select" id="perm-edit-scope">'
|
||||
+ '<option value="domain"' + sel(p.scope, 'domain') + '>Domain</option>'
|
||||
+ '<option value="global"' + sel(p.scope, 'global') + '>Global</option>'
|
||||
+ '</select>'
|
||||
+ '</div>'
|
||||
+ '<div class="mb-3">'
|
||||
+ '<label class="form-label fw-bold">Domain (for domain scope)</label>'
|
||||
+ '<input type="text" class="form-control" id="perm-edit-domain" autocomplete="off" value="' + app.util.escapeHtml(p.domain || '') + '" />'
|
||||
+ '<div class="form-text text-muted">'
|
||||
+ 'Wildcards: <code>*.example.com</code> matches one label, '
|
||||
+ '<code>**.example.com</code> matches any depth (incl. the apex), '
|
||||
+ '<code>**</code> matches every domain.'
|
||||
+ '</div>'
|
||||
+ '</div>'
|
||||
+ '<div class="mb-3">'
|
||||
+ '<label class="form-label fw-bold">Role</label>'
|
||||
+ '<select class="form-select" id="perm-edit-role">'
|
||||
+ '<option value="viewer"' + sel(p.role, 'viewer') + '>Viewer (read)</option>'
|
||||
+ '<option value="manager"' + sel(p.role, 'manager') + '>Manager (full over domain)</option>'
|
||||
+ '<option value="admin"' + sel(p.role, 'admin') + '>Admin (global only)</option>'
|
||||
+ '</select>'
|
||||
+ '</div>'
|
||||
+ '<hr />'
|
||||
+ '<div class="d-flex justify-content-end gap-2">'
|
||||
+ '<button type="button" class="btn btn-secondary" onclick="app.modal.close()">Cancel</button>'
|
||||
+ '<button type="button" class="btn btn-primary" onclick="permissionEditSave(\'' + id + '\')">Save changes</button>'
|
||||
+ '</div>',
|
||||
});
|
||||
}
|
||||
|
||||
function permissionEditSave(id){
|
||||
var payload = {
|
||||
subjectType: $('#perm-edit-subjectType').val(),
|
||||
subject: ($('#perm-edit-subject').val() || '').trim(),
|
||||
scope: $('#perm-edit-scope').val(),
|
||||
domain: ($('#perm-edit-domain').val() || '').trim(),
|
||||
role: $('#perm-edit-role').val(),
|
||||
};
|
||||
if(!payload.subject){
|
||||
return app.messages.toast('Subject is required', 'warning');
|
||||
}
|
||||
app.permission.update(id, payload, function(error, data){
|
||||
if(error) return app.messages.action((data && data.message) || error, $('#permissions-list'), 'danger');
|
||||
app.modal.close();
|
||||
loadPermissions();
|
||||
});
|
||||
}
|
||||
|
||||
function removePermission(id){
|
||||
app.permission.remove(id, function(error, data){
|
||||
if(error) return app.messages.action(error, $('#permissions-list'), 'danger');
|
||||
@@ -148,7 +227,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="d-flex gap-2">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="permissionEditOpen('{{id}}')">
|
||||
<i class="fa-solid fa-pen me-1"></i> Edit
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removePermission('{{id}}')">
|
||||
<i class="fa-solid fa-trash me-1"></i> Delete
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user