Files
server-manager/views/servers/system_users.php
2026-06-06 18:08:47 -04:00

167 lines
9.9 KiB
PHP

<?php
$server = $server ?? [];
$sysusers = $sysusers ?? [];
$users = $sysusers['users'] ?? [];
$totalUsers = $sysusers['total_users'] ?? 0;
$groups = $sysusers['groups'] ?? [];
$lastLogins = $sysusers['last_logins'] ?? '';
?>
<div class="page-header">
<div class="page-header-left">
<div class="back-link"><a href="/servers/<?= $server['id'] ?>"><i class="fas fa-arrow-left"></i> Back to Server</a></div>
<h1 class="page-title"><i class="fas fa-users-cog"></i> System Users</h1>
<p class="page-subtitle"><?= htmlspecialchars($server['name']) ?> &middot; <?= $totalUsers ?> users</p>
</div>
<div class="page-header-right">
<button class="btn btn-secondary" onclick="location.reload()"><i class="fas fa-sync-alt"></i></button>
</div>
</div>
<div style="display:flex;gap:1rem;margin-bottom:1rem;flex-wrap:wrap">
<div class="stat-card" style="flex:1;padding:1rem">
<div class="stat-icon" style="width:40px;height:40px;background:var(--accent-light);color:var(--accent)"><i class="fas fa-users"></i></div>
<div class="stat-info"><span class="stat-value" style="font-size:1.2rem"><?= $totalUsers ?></span><span class="stat-label">System Users</span></div>
</div>
<div class="stat-card" style="flex:1;padding:1rem;cursor:pointer" onclick="showAddUser()">
<div class="stat-icon" style="width:40px;height:40px;background:var(--success-bg);color:var(--success)"><i class="fas fa-user-plus"></i></div>
<div class="stat-info"><span class="stat-value" style="font-size:1rem;color:var(--success)">Add User</span><span class="stat-label">Create new system user</span></div>
</div>
<div class="stat-card" style="flex:1;padding:1rem;cursor:pointer" onclick="showAddGroup()">
<div class="stat-icon" style="width:40px;height:40px;background:var(--info-bg);color:var(--info)"><i class="fas fa-layer-group"></i></div>
<div class="stat-info"><span class="stat-value" style="font-size:1rem;color:var(--info)">Add Group</span><span class="stat-label">Create new group</span></div>
</div>
</div>
<div class="card">
<div class="card-header"><h2><i class="fas fa-list"></i> Users</h2><span class="badge badge-info"><?= count($users) ?></span></div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-sm">
<thead><tr><th>Username</th><th>UID</th><th>Home</th><th>Shell</th><th>Actions</th></tr></thead>
<tbody>
<?php foreach ($users as $u): ?>
<tr>
<td><strong><?= htmlspecialchars($u['username']) ?></strong></td>
<td class="font-mono"><?= $u['uid'] ?></td>
<td><code><?= htmlspecialchars($u['home']) ?></code></td>
<td><code><?= htmlspecialchars($u['shell']) ?></code></td>
<td class="actions-cell">
<button class="btn btn-xs btn-warning" onclick="lockUser('<?= htmlspecialchars($u['username']) ?>')" title="Lock"><i class="fas fa-lock"></i></button>
<button class="btn btn-xs btn-success" onclick="unlockUser('<?= htmlspecialchars($u['username']) ?>')" title="Unlock"><i class="fas fa-lock-open"></i></button>
<button class="btn btn-xs btn-danger" onclick="deleteUser('<?= htmlspecialchars($u['username']) ?>')" title="Delete"><i class="fas fa-trash"></i></button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<?php if (!empty($groups)): ?>
<div class="card">
<div class="card-header"><h2><i class="fas fa-layer-group"></i> Groups</h2><span class="badge badge-info"><?= count($groups) ?></span></div>
<div class="card-body">
<?php foreach (array_chunk($groups, 10) as $chunk): ?>
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;margin-bottom:0.25rem">
<?php foreach ($chunk as $g): ?>
<code style="font-size:0.8rem"><?= htmlspecialchars($g) ?></code>
<?php endforeach; ?>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<?php if (!empty($lastLogins)): ?>
<div class="card">
<div class="card-header"><h2><i class="fas fa-clock"></i> Recent Logins</h2></div>
<div class="card-body">
<div class="nginx-config-viewer" style="max-height:200px">
<pre><code><?= htmlspecialchars($lastLogins) ?></code></pre>
</div>
</div>
</div>
<?php endif; ?>
<div id="addUserModal" class="modal">
<div class="modal-dialog" style="max-width:500px">
<div class="modal-header"><h3><i class="fas fa-user-plus text-success"></i> Add System User</h3><button class="modal-close" onclick="closeModal('addUserModal')">&times;</button></div>
<form onsubmit="event.preventDefault();doAddUser()">
<div class="modal-body">
<div class="form-row">
<div class="form-group"><label>Username</label><input type="text" id="suUser" class="form-input" required pattern="[a-z_][a-z0-9_-]*"></div>
<div class="form-group"><label>Password</label><input type="password" id="suPass" class="form-input" required></div>
</div>
<div class="form-group"><label>Groups (comma separated)</label><input type="text" id="suGroups" class="form-input" placeholder="sudo,www-data"></div>
<div class="form-group"><label class="checkbox-label"><input type="checkbox" id="suHome" checked> <span>Create home directory</span></label></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="closeModal('addUserModal')">Cancel</button>
<button type="submit" class="btn btn-success"><i class="fas fa-save"></i> Create User</button>
</div>
</form>
</div>
</div>
<div id="addGroupModal" class="modal">
<div class="modal-dialog" style="max-width:400px">
<div class="modal-header"><h3><i class="fas fa-layer-group text-info"></i> Add Group</h3><button class="modal-close" onclick="closeModal('addGroupModal')">&times;</button></div>
<form onsubmit="event.preventDefault();doAddGroup()">
<div class="modal-body">
<div class="form-group"><label>Group name</label><input type="text" id="sgGroup" class="form-input" required></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="closeModal('addGroupModal')">Cancel</button>
<button type="submit" class="btn btn-info"><i class="fas fa-save"></i> Create Group</button>
</div>
</form>
</div>
</div>
<script>
function getCsrfToken() { return document.querySelector('meta[name="csrf-token"]')?.content || ''; }
function showAddUser() { document.getElementById('addUserModal').style.display = 'flex'; }
function showAddGroup() { document.getElementById('addGroupModal').style.display = 'flex'; }
function closeModal(id) { document.getElementById(id).style.display = 'none'; }
function doAddUser() {
const data = 'action=add&username=' + encodeURIComponent(document.getElementById('suUser').value)
+ '&password=' + encodeURIComponent(document.getElementById('suPass').value)
+ '&groups=' + encodeURIComponent(document.getElementById('suGroups').value)
+ '&create_home=' + (document.getElementById('suHome').checked ? '1' : '0')
+ '&_csrf_token=' + encodeURIComponent(getCsrfToken());
fetch('/servers/<?= $server['id'] ?>/users', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded','X-CSRF-Token':getCsrfToken()}, body:data})
.then(r=>r.json()).then(d=>{showToast(d.success?'success':'error',d.message);if(d.success)setTimeout(()=>location.reload(),800)});
closeModal('addUserModal');
}
function doAddGroup() {
const data = 'action=addgroup&group=' + encodeURIComponent(document.getElementById('sgGroup').value) + '&_csrf_token=' + encodeURIComponent(getCsrfToken());
fetch('/servers/<?= $server['id'] ?>/users', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded','X-CSRF-Token':getCsrfToken()}, body:data})
.then(r=>r.json()).then(d=>{showToast(d.success?'success':'error',d.message);if(d.success)setTimeout(()=>location.reload(),800)});
closeModal('addGroupModal');
}
function lockUser(u) {
if(!confirm('Lock user \''+u+'\'?'))return;
fetch('/servers/<?= $server['id'] ?>/users', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded','X-CSRF-Token':getCsrfToken()}, body:'action=lock&username='+encodeURIComponent(u)+'&_csrf_token='+encodeURIComponent(getCsrfToken())})
.then(r=>r.json()).then(d=>{showToast(d.success?'success':'error',d.message);if(d.success)setTimeout(()=>location.reload(),800)});
}
function unlockUser(u) {
fetch('/servers/<?= $server['id'] ?>/users', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded','X-CSRF-Token':getCsrfToken()}, body:'action=unlock&username='+encodeURIComponent(u)+'&_csrf_token='+encodeURIComponent(getCsrfToken())})
.then(r=>r.json()).then(d=>{showToast(d.success?'success':'error',d.message);if(d.success)setTimeout(()=>location.reload(),800)});
}
function deleteUser(u) {
if(!confirm('Delete user \''+u+'\'?\nThis cannot be undone.'))return;
fetch('/servers/<?= $server['id'] ?>/users', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded','X-CSRF-Token':getCsrfToken()}, body:'action=delete&username='+encodeURIComponent(u)+'&_csrf_token='+encodeURIComponent(getCsrfToken())})
.then(r=>r.json()).then(d=>{showToast(d.success?'success':'error',d.message);if(d.success)setTimeout(()=>location.reload(),800)});
}
document.addEventListener('keydown',function(e){if(e.key==='Escape'){closeModal('addUserModal');closeModal('addGroupModal')}});
</script>