inou/portal/templates/edit_rbac.tmpl

150 lines
7.3 KiB
Cheetah

{{define "edit_rbac"}}
<style>
.col-toggle { cursor: pointer; user-select: none; display: inline-flex; align-items: center; gap: 4px; }
</style>
<div class="page-container">
<div class="page-card" style="max-width: 780px; margin: 0 auto;">
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 32px;">
<div>
<h1>Edit permissions</h1>
<p class="intro">{{.GranteeName}}'s access to {{.TargetDossier.Name}}</p>
</div>
<a href="/dossier/{{.TargetDossier.DossierID}}" class="btn btn-secondary btn-small">Back</a>
</div>
{{if .Error}}<div class="error">{{.Error}}</div>{{end}}
{{if .Success}}<div class="success">{{.Success}}</div>{{end}}
<form action="/dossier/{{.TargetDossier.DossierID}}/rbac/{{.GranteeID}}" method="POST">
<input type="hidden" name="action" value="update">
<input type="hidden" name="role" id="roleHidden" value="{{.SelectedRole}}">
<div style="margin-bottom: 24px;">
<label style="font-weight: 600; margin-bottom: 8px; display: block;">Role</label>
<select id="roleSelect" style="width: 100%;">
<option value="Custom"{{if eq .SelectedRole "Custom"}} selected{{end}}>Custom</option>
{{range .Roles}}
<option value="{{.Name}}" data-grants='{{.GrantsJSON}}'{{if eq $.SelectedRole .Name}} selected{{end}}>{{.Name}} &mdash; {{.Description}}</option>
{{end}}
</select>
</div>
<table style="width: 100%; border-collapse: collapse; font-size: 0.9rem; table-layout: fixed;">
<colgroup>
<col style="width: auto;">
<col style="width: 72px;">
<col style="width: 72px;">
<col style="width: 72px;">
<col style="width: 72px;">
<col style="width: 72px;">
</colgroup>
<tr style="border-bottom: 2px solid var(--border);">
<th style="text-align: left; padding: 8px 12px;">Category</th>
<th style="text-align: center; padding: 8px 4px;">All</th>
<th style="text-align: center; padding: 8px 4px;"><label class="col-toggle" data-op="r"><input type="checkbox" class="col-cb" data-op="r"> Read</label></th>
<th style="text-align: center; padding: 8px 4px;"><label class="col-toggle" data-op="w"><input type="checkbox" class="col-cb" data-op="w"> Write</label></th>
<th style="text-align: center; padding: 8px 4px;"><label class="col-toggle" data-op="d"><input type="checkbox" class="col-cb" data-op="d"> Delete</label></th>
<th style="text-align: center; padding: 8px 4px;"><label class="col-toggle" data-op="m"><input type="checkbox" class="col-cb" data-op="m"> Manage</label></th>
</tr>
{{range .CategoriesRBAC}}
<tr style="border-bottom: 1px solid var(--border);" data-cat="{{.ID}}">
<td style="padding: 6px 12px; text-transform: capitalize;">{{.Name}}</td>
<td style="text-align: center; padding: 6px 4px;"><input type="checkbox" class="row-toggle"></td>
<td style="text-align: center; padding: 6px 4px;"><input type="checkbox" name="cat_{{.ID}}_r" value="1" class="perm-cb op-r" {{if .CanRead}}checked{{end}}></td>
<td style="text-align: center; padding: 6px 4px;"><input type="checkbox" name="cat_{{.ID}}_w" value="1" class="perm-cb op-w" {{if .CanWrite}}checked{{end}}></td>
<td style="text-align: center; padding: 6px 4px;"><input type="checkbox" name="cat_{{.ID}}_d" value="1" class="perm-cb op-d" {{if .CanDelete}}checked{{end}}></td>
<td style="text-align: center; padding: 6px 4px;"><input type="checkbox" name="cat_{{.ID}}_m" value="1" class="perm-cb op-m" {{if .CanManage}}checked{{end}}></td>
</tr>
{{end}}
</table>
<div style="display: flex; gap: 12px; margin-top: 32px;">
<a href="/dossier/{{.TargetDossier.DossierID}}" class="btn btn-secondary" style="flex: 1; text-align: center;">Cancel</a>
<button type="submit" class="btn btn-primary" style="flex: 1;">Save changes</button>
</div>
</form>
<div style="border-top: 1px solid var(--border); padding-top: 32px; margin-top: 32px;">
<form action="/dossier/{{.TargetDossier.DossierID}}/rbac/{{.GranteeID}}" method="POST" onsubmit="return confirm('Revoke all access for {{.GranteeName}}?');">
<input type="hidden" name="action" value="revoke">
<button type="submit" class="btn btn-danger btn-full">Revoke all access</button>
</form>
</div>
</div>
</div>
<script>
(function() {
var roleSelect = document.getElementById('roleSelect');
var roleHidden = document.getElementById('roleHidden');
var allCbs = document.querySelectorAll('.perm-cb');
var catIDs = [];
document.querySelectorAll('tr[data-cat]').forEach(function(tr) { catIDs.push(parseInt(tr.dataset.cat)); });
roleSelect.addEventListener('change', function() {
roleHidden.value = this.value;
if (this.value === 'Custom') return;
var opt = this.options[this.selectedIndex];
var grantsJSON = opt.dataset.grants;
if (!grantsJSON) return;
var grants = JSON.parse(grantsJSON);
allCbs.forEach(function(cb) { cb.checked = false; });
var rootOps = '';
grants.forEach(function(g) { if (g.Category === 0) rootOps = g.Ops || ''; });
if (rootOps) catIDs.forEach(function(id) { setOps(id, rootOps); });
grants.forEach(function(g) { if (g.Category > 0) setOps(g.Category, g.Ops || ''); });
syncAll();
});
function setOps(catID, ops) {
var row = document.querySelector('tr[data-cat="' + catID + '"]');
if (!row) return;
var cbs = row.querySelectorAll('.perm-cb');
if (cbs[0]) cbs[0].checked = ops.includes('r');
if (cbs[1]) cbs[1].checked = ops.includes('w');
if (cbs[2]) cbs[2].checked = ops.includes('d');
if (cbs[3]) cbs[3].checked = ops.includes('m');
}
allCbs.forEach(function(cb) {
cb.addEventListener('change', function() {
roleSelect.value = 'Custom'; roleHidden.value = 'Custom';
syncRowToggle(this.closest('tr')); syncColToggles();
});
});
document.querySelectorAll('.row-toggle').forEach(function(toggle) {
toggle.addEventListener('change', function() {
var row = this.closest('tr');
row.querySelectorAll('.perm-cb').forEach(function(cb) { cb.checked = toggle.checked; });
roleSelect.value = 'Custom'; roleHidden.value = 'Custom';
syncColToggles();
});
});
document.querySelectorAll('.col-cb').forEach(function(cb) {
cb.addEventListener('change', function() {
document.querySelectorAll('.op-' + this.dataset.op).forEach(function(c) { c.checked = cb.checked; });
roleSelect.value = 'Custom'; roleHidden.value = 'Custom';
syncRowToggles();
});
});
function syncRowToggle(row) {
var cbs = row.querySelectorAll('.perm-cb');
var toggle = row.querySelector('.row-toggle');
if (toggle) toggle.checked = cbs.length > 0 && Array.from(cbs).every(function(cb) { return cb.checked; });
}
function syncRowToggles() { document.querySelectorAll('tr[data-cat]').forEach(syncRowToggle); }
function syncColToggles() {
document.querySelectorAll('.col-cb').forEach(function(cb) {
var colCbs = document.querySelectorAll('.op-' + cb.dataset.op);
cb.checked = colCbs.length > 0 && Array.from(colCbs).every(function(c) { return c.checked; });
});
}
function syncAll() { syncRowToggles(); syncColToggles(); }
syncAll();
})();
</script>
{{end}}