Feature: filter device visibility by allowed groups for web user
This commit is contained in:
@@ -997,10 +997,14 @@ inline std::string GetWebPageHTML() {
|
||||
<h4>Create New User</h4>
|
||||
<input type="text" id="new-username" placeholder="Username" autocomplete="off">
|
||||
<input type="password" id="new-password" placeholder="Password" autocomplete="new-password">
|
||||
<select id="new-role">
|
||||
<select id="new-role" onchange="onRoleChange()">
|
||||
<option value="viewer">Viewer (read-only)</option>
|
||||
<option value="admin">Admin (full access)</option>
|
||||
</select>
|
||||
<div class="groups-section" id="groups-section">
|
||||
<label style="font-size:13px;color:#aaa;display:block;margin:8px 0 4px;">Allowed Groups:</label>
|
||||
<div id="groups-checkboxes" style="max-height:120px;overflow-y:auto;background:rgba(0,0,0,0.2);border-radius:6px;padding:6px 8px;"></div>
|
||||
</div>
|
||||
<button onclick="createUser()">Create User</button>
|
||||
</div>
|
||||
<div class="user-list">
|
||||
@@ -1286,6 +1290,11 @@ inline std::string GetWebPageHTML() {
|
||||
renderUsersList(msg.users);
|
||||
}
|
||||
break;
|
||||
case 'groups':
|
||||
if (msg.ok) {
|
||||
renderGroupsCheckboxes(msg.groups);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
)HTML";
|
||||
@@ -1661,7 +1670,35 @@ inline std::string GetWebPageHTML() {
|
||||
function openUsersModal() {
|
||||
document.getElementById('users-modal').classList.add('active');
|
||||
document.getElementById('user-msg').innerHTML = '';
|
||||
document.getElementById('new-role').value = 'viewer'; // Reset to default
|
||||
onRoleChange(); // Update groups section visibility
|
||||
listUsers();
|
||||
getGroups();
|
||||
}
|
||||
|
||||
function getGroups() {
|
||||
if (ws && ws.readyState === WebSocket.OPEN && token) {
|
||||
ws.send(JSON.stringify({ cmd: 'get_groups', token }));
|
||||
}
|
||||
}
|
||||
|
||||
function renderGroupsCheckboxes(groups) {
|
||||
const container = document.getElementById('groups-checkboxes');
|
||||
if (!groups || groups.length === 0) {
|
||||
container.innerHTML = '<span style="color:#666;font-size:12px;">No groups available</span>';
|
||||
return;
|
||||
}
|
||||
container.innerHTML = groups.map(g =>
|
||||
'<label style="display:flex;align-items:center;padding:3px 0;cursor:pointer;white-space:nowrap;">' +
|
||||
'<input type="checkbox" value="' + escapeHtml(g) + '" style="margin:0 6px 0 0;flex-shrink:0;width:14px;height:14px;">' +
|
||||
escapeHtml(g) + '</label>'
|
||||
).join('');
|
||||
}
|
||||
|
||||
function onRoleChange() {
|
||||
const role = document.getElementById('new-role').value;
|
||||
const groupsSection = document.getElementById('groups-section');
|
||||
groupsSection.style.display = (role === 'admin') ? 'none' : 'block';
|
||||
}
|
||||
|
||||
function closeUsersModal() {
|
||||
@@ -1685,8 +1722,12 @@ inline std::string GetWebPageHTML() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Collect selected groups
|
||||
const checkboxes = document.querySelectorAll('#groups-checkboxes input[type="checkbox"]:checked');
|
||||
const allowed_groups = Array.from(checkboxes).map(cb => cb.value);
|
||||
|
||||
if (ws && ws.readyState === WebSocket.OPEN && token) {
|
||||
ws.send(JSON.stringify({ cmd: 'create_user', token, username, password, role }));
|
||||
ws.send(JSON.stringify({ cmd: 'create_user', token, username, password, role, allowed_groups }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1712,10 +1753,14 @@ inline std::string GetWebPageHTML() {
|
||||
container.innerHTML = users.map(u => {
|
||||
const isAdmin = u.role === 'admin';
|
||||
const canDelete = u.username !== 'admin'; // Cannot delete built-in admin
|
||||
const groups = u.allowed_groups || [];
|
||||
const groupsText = u.username === 'admin' ? '(all)' :
|
||||
(groups.length > 0 ? groups.join(', ') : '(none)');
|
||||
return '<div class="user-item">' +
|
||||
'<div class="user-info">' +
|
||||
'<div class="username">' + escapeHtml(u.username) + '</div>' +
|
||||
'<div class="role ' + (isAdmin ? 'admin' : '') + '">' + u.role + '</div>' +
|
||||
'<div class="groups" style="font-size:11px;color:#888;margin-top:2px;">Groups: ' + escapeHtml(groupsText) + '</div>' +
|
||||
'</div>' +
|
||||
(canDelete ? '<button class="delete-btn" onclick="deleteUser(\'' + escapeHtml(u.username) + '\')">Delete</button>' : '') +
|
||||
'</div>';
|
||||
|
||||
Reference in New Issue
Block a user