185 lines
8.3 KiB
Cheetah
185 lines
8.3 KiB
Cheetah
{{define "edit_rbac"}}
|
|
<div class="sg-container" style="justify-content: center;">
|
|
<div style="flex: 1; display: flex; align-items: flex-start; padding-top: 16px; justify-content: center;">
|
|
<div class="data-card" style="padding: 48px; max-width: 780px; width: 100%;">
|
|
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 32px;">
|
|
<div>
|
|
<h1 style="font-size: 2rem; font-weight: 700; margin-bottom: 8px;">Edit permissions</h1>
|
|
<p style="color: var(--text-muted); font-weight: 300;">{{.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}}">
|
|
|
|
<!-- Role Selector -->
|
|
<div style="margin-bottom: 24px;">
|
|
<label style="font-weight: 600; margin-bottom: 8px; display: block;">Role</label>
|
|
<select id="roleSelect" class="sg-select" 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}} — {{.Description}}</option>
|
|
{{end}}
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Category Permission Table -->
|
|
<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>
|
|
<thead>
|
|
<tr style="border-bottom: 2px solid var(--border-light);">
|
|
<th style="text-align: left; padding: 8px 12px; font-weight: 600;">Category</th>
|
|
<th style="text-align: center; padding: 8px 4px; font-weight: 600;">All</th>
|
|
<th style="text-align: center; padding: 8px 4px; font-weight: 600;"><label class="col-toggle" data-op="r" title="Toggle all Read"><input type="checkbox" class="col-cb" data-op="r"> Read</label></th>
|
|
<th style="text-align: center; padding: 8px 4px; font-weight: 600;"><label class="col-toggle" data-op="w" title="Toggle all Write"><input type="checkbox" class="col-cb" data-op="w"> Write</label></th>
|
|
<th style="text-align: center; padding: 8px 4px; font-weight: 600;"><label class="col-toggle" data-op="d" title="Toggle all Delete"><input type="checkbox" class="col-cb" data-op="d"> Delete</label></th>
|
|
<th style="text-align: center; padding: 8px 4px; font-weight: 600;"><label class="col-toggle" data-op="m" title="Toggle all Manage"><input type="checkbox" class="col-cb" data-op="m"> Manage</label></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{{range .CategoriesRBAC}}
|
|
<tr style="border-bottom: 1px solid var(--border-light);" 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}}
|
|
</tbody>
|
|
</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>
|
|
|
|
<!-- Revoke all access -->
|
|
<div style="border-top: 1px solid var(--border-light); 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" style="width: 100%;">Revoke all access</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.col-toggle { cursor: pointer; user-select: none; display: inline-flex; align-items: center; gap: 4px; }
|
|
</style>
|
|
|
|
<script>
|
|
(function() {
|
|
const roleSelect = document.getElementById('roleSelect');
|
|
const roleHidden = document.getElementById('roleHidden');
|
|
const allCbs = document.querySelectorAll('.perm-cb');
|
|
|
|
const catIDs = [];
|
|
document.querySelectorAll('tr[data-cat]').forEach(tr => {
|
|
catIDs.push(parseInt(tr.dataset.cat));
|
|
});
|
|
|
|
// --- Role selection ---
|
|
roleSelect.addEventListener('change', function() {
|
|
roleHidden.value = this.value;
|
|
if (this.value === 'Custom') return;
|
|
const opt = this.options[this.selectedIndex];
|
|
const grantsJSON = opt.dataset.grants;
|
|
if (!grantsJSON) return;
|
|
const grants = JSON.parse(grantsJSON);
|
|
|
|
allCbs.forEach(cb => cb.checked = false);
|
|
|
|
let rootOps = '';
|
|
grants.forEach(g => { if (g.Category === 0) rootOps = g.Ops || ''; });
|
|
if (rootOps) catIDs.forEach(id => setOps(id, rootOps));
|
|
grants.forEach(g => { if (g.Category > 0) setOps(g.Category, g.Ops || ''); });
|
|
syncAll();
|
|
});
|
|
|
|
function setOps(catID, ops) {
|
|
const row = document.querySelector('tr[data-cat="' + catID + '"]');
|
|
if (!row) return;
|
|
const 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');
|
|
}
|
|
|
|
// --- Manual perm change → Custom ---
|
|
allCbs.forEach(cb => {
|
|
cb.addEventListener('change', function() {
|
|
roleSelect.value = 'Custom';
|
|
roleHidden.value = 'Custom';
|
|
syncRowToggle(this.closest('tr'));
|
|
syncColToggles();
|
|
});
|
|
});
|
|
|
|
// --- Row toggle ---
|
|
document.querySelectorAll('.row-toggle').forEach(toggle => {
|
|
toggle.addEventListener('change', function() {
|
|
const row = this.closest('tr');
|
|
row.querySelectorAll('.perm-cb').forEach(cb => cb.checked = this.checked);
|
|
roleSelect.value = 'Custom';
|
|
roleHidden.value = 'Custom';
|
|
syncColToggles();
|
|
});
|
|
});
|
|
|
|
// --- Column toggle (checkbox in header) ---
|
|
document.querySelectorAll('.col-cb').forEach(cb => {
|
|
cb.addEventListener('change', function() {
|
|
const op = this.dataset.op;
|
|
document.querySelectorAll('.op-' + op).forEach(c => c.checked = this.checked);
|
|
roleSelect.value = 'Custom';
|
|
roleHidden.value = 'Custom';
|
|
syncRowToggles();
|
|
});
|
|
});
|
|
|
|
// --- Sync helpers ---
|
|
function syncRowToggle(row) {
|
|
const cbs = row.querySelectorAll('.perm-cb');
|
|
const toggle = row.querySelector('.row-toggle');
|
|
if (toggle) toggle.checked = cbs.length > 0 && Array.from(cbs).every(cb => cb.checked);
|
|
}
|
|
|
|
function syncRowToggles() {
|
|
document.querySelectorAll('tr[data-cat]').forEach(syncRowToggle);
|
|
}
|
|
|
|
function syncColToggles() {
|
|
document.querySelectorAll('.col-cb').forEach(cb => {
|
|
const colCbs = document.querySelectorAll('.op-' + cb.dataset.op);
|
|
cb.checked = colCbs.length > 0 && Array.from(colCbs).every(c => c.checked);
|
|
});
|
|
}
|
|
|
|
function syncAll() {
|
|
syncRowToggles();
|
|
syncColToggles();
|
|
}
|
|
|
|
// --- Init ---
|
|
syncAll();
|
|
})();
|
|
</script>
|
|
{{end}}
|