165 lines
9.3 KiB
PHP
165 lines
9.3 KiB
PHP
<?php
|
|
/*
|
|
* Reflector Dashboard — web front-end for SvxReflector
|
|
* Copyright (C) 2026 SA7AUX (Björn)
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify it under
|
|
* the terms of the GNU Affero General Public License as published by the Free
|
|
* Software Foundation, either version 3 of the License, or (at your option) any
|
|
* later version. See the LICENSE file for details.
|
|
*/
|
|
declare(strict_types=1);
|
|
require __DIR__ . '/lib.php';
|
|
start_session();
|
|
$uuid = $_SESSION['uid'] ?? null;
|
|
if (!is_string($uuid) || $uuid === '') { header('Location: /'); exit; }
|
|
$me = acct_get($uuid);
|
|
$lvl = acct_admin_level($uuid);
|
|
if ($lvl < 2) { header('Location: /'); exit; }
|
|
|
|
$page_title = 'Admin · Accounts — Reflector Dashboard';
|
|
require __DIR__ . '/header.php';
|
|
?>
|
|
|
|
<div class="container py-4" style="max-width:800px;">
|
|
|
|
<!-- Account List -->
|
|
<div id="acctListPanel">
|
|
<div class="panel">
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<h5 class="mb-0">Accounts</h5>
|
|
<input type="text" class="form-control form-control-sm" id="acctSearch" placeholder="Search..." style="max-width:200px;">
|
|
</div>
|
|
<div style="max-height:70vh; overflow-y:auto; overflow-x:auto; -webkit-overflow-scrolling:touch;">
|
|
<table class="data-table" id="acctTable">
|
|
<thead><tr><th>Callsign</th><th>Name</th><th>Email</th><th>Level</th><th>Status</th><th>Last Login</th><th>Actions</th></tr></thead>
|
|
<tbody></tbody>
|
|
</table>
|
|
</div>
|
|
<div id="loadMsg" class="text-secondary small mt-2">Loading...</div>
|
|
<div id="paginationWrap" class="mt-2"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Edit Account -->
|
|
<div id="acctEditPanel" style="display:none;">
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<h5 class="mb-0">Edit: <span id="acctEditCs" class="mono"></span> <span id="acctApprovalBadge" class="badge ms-2"></span></h5>
|
|
<button type="button" class="btn btn-outline-secondary btn-sm" id="btnBack">Back to list</button>
|
|
</div>
|
|
|
|
<nav class="profile-tabs" role="tablist">
|
|
<button type="button" class="profile-tab active" data-ptab="account" role="tab">
|
|
<svg width="18" height="18" fill="currentColor" viewBox="0 0 16 16"><path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6m2-3a2 2 0 1 1-4 0 2 2 0 0 1 4 0m4 8c0 1-1 1-1 1H3s-1 0-1-1 1-4 6-4 6 3 6 4m-1-.004c-.001-.246-.154-.986-.832-1.664C11.516 10.68 10.289 10 8 10s-3.516.68-4.168 1.332c-.678.678-.83 1.418-.832 1.664z"/></svg>
|
|
<span>My Account</span>
|
|
</button>
|
|
<button type="button" class="profile-tab" data-ptab="nodes" role="tab">
|
|
<svg width="18" height="18" fill="currentColor" viewBox="0 0 16 16"><path d="M3.5 6.5A.5.5 0 0 1 4 7v1a4 4 0 0 0 8 0V7a.5.5 0 0 1 1 0v1a5 5 0 0 1-4.5 4.975V15h3a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1h3v-2.025A5 5 0 0 1 3 8V7a.5.5 0 0 1 .5-.5"/><path d="M10 8a2 2 0 1 1-4 0V3a2 2 0 1 1 4 0zM8 0a3 3 0 0 0-3 3v5a3 3 0 0 0 6 0V3a3 3 0 0 0-3-3"/></svg>
|
|
<span>My Nodes</span>
|
|
</button>
|
|
<button type="button" class="profile-tab" data-ptab="calls" role="tab">
|
|
<svg width="18" height="18" fill="currentColor" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M5 11.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m-3 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2m0 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2m0 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2"/></svg>
|
|
<span>Callsigns</span>
|
|
</button>
|
|
<button type="button" class="profile-tab" data-ptab="password" role="tab">
|
|
<svg width="18" height="18" fill="currentColor" viewBox="0 0 16 16"><path d="M0 8a4 4 0 0 1 7.465-2H14a.5.5 0 0 1 .354.146l1.5 1.5a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0L13 9.207l-.646.647a.5.5 0 0 1-.708 0L11 9.207l-.646.647a.5.5 0 0 1-.708 0L9 9.207l-.646.647A.5.5 0 0 1 8 10h-.535A4 4 0 0 1 0 8m4-3a3 3 0 1 0 2.959 3.5H8a.5.5 0 0 0 .354-.146l.853-.854 1.146 1.147a.5.5 0 0 0 .708 0l.646-.647.646.647a.5.5 0 0 0 .708 0l.646-.647.646.647a.5.5 0 0 0 .708 0L15 7.207l-1-1H7.465A3 3 0 0 0 4 5m-1 3a1 1 0 1 1-2 0 1 1 0 0 1 2 0"/></svg>
|
|
<span>Password</span>
|
|
</button>
|
|
</nav>
|
|
|
|
<div id="ptab-account" class="profile-tab-pane">
|
|
<div class="panel">
|
|
<div id="acctFields"></div>
|
|
<div class="small mb-2">Email status: <span id="acctVerifiedStatus"></span></div>
|
|
<div class="d-flex gap-2 mt-3 flex-wrap">
|
|
<button type="button" class="btn btn-primary btn-sm" id="btnSave">Save</button>
|
|
<button type="button" class="btn btn-danger btn-sm" id="btnBan">Ban</button>
|
|
<button type="button" class="btn btn-success btn-sm" id="btnUnban">Unban</button>
|
|
<button type="button" class="btn btn-warning btn-sm" id="btnLock">Lock</button>
|
|
<button type="button" class="btn btn-success btn-sm" id="btnUnlock">Unlock</button>
|
|
<button type="button" class="btn btn-success btn-sm" id="btnVerifyEmail">Mark email verified</button>
|
|
<button type="button" class="btn btn-outline-secondary btn-sm" id="btnUnverifyEmail">Unverify email</button>
|
|
<button type="button" class="btn btn-success btn-sm" id="btnApprove" title="Approve — sets reg+normal so the user can log in.">Approve</button>
|
|
<button type="button" class="btn btn-outline-warning btn-sm" id="btnUnapprove" title="Revoke approval — user can no longer log in.">Unapprove</button>
|
|
<button type="button" class="btn btn-outline-danger btn-sm" id="btnDelete">Delete</button>
|
|
</div>
|
|
<div class="d-flex align-items-center gap-2 mt-2" id="acctLevelRow">
|
|
<label class="form-label small mb-0">Admin Level:</label>
|
|
<span id="acctFlagsText" class="small mono"></span>
|
|
<button type="button" class="btn btn-outline-secondary btn-sm" id="btnAcctLevel">Admin</button>
|
|
</div>
|
|
<div id="acctMsg" class="small mt-2" style="display:none;"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="ptab-nodes" class="profile-tab-pane" style="display:none;">
|
|
<div class="panel">
|
|
<h5 class="mb-3">Reflector login & certificate</h5>
|
|
<div id="editNodeAuthList"><span class="small text-secondary">Loading…</span></div>
|
|
</div>
|
|
<div class="panel">
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<h5 class="mb-0">Nodes</h5>
|
|
<div class="d-flex gap-2 align-items-center">
|
|
<div id="editNodeSelWrap" class="d-flex gap-2 align-items-center" style="display:none;">
|
|
<label class="small text-secondary mb-0">Instance:</label>
|
|
<select class="form-select form-select-sm" id="editNodeSelect" style="max-width:180px;"></select>
|
|
</div>
|
|
<span id="editNodesStatus" class="small text-secondary">Loading...</span>
|
|
</div>
|
|
</div>
|
|
<div id="editNodesList"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="ptab-calls" class="profile-tab-pane" style="display:none;">
|
|
<div class="panel">
|
|
<h5 class="mb-3">Callsigns this account is admin for</h5>
|
|
<div class="small text-secondary mb-2">Club calls or additional callsigns this account administers (the primary callsign always counts). Nodes whose name contains any of these belong to the account. The same callsign may sit on several accounts. Only site admins can change this list.</div>
|
|
<div id="acctCallsEditor"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="ptab-password" class="profile-tab-pane" style="display:none;">
|
|
<div class="panel">
|
|
<h6 class="mb-3">Reset Password</h6>
|
|
<div class="d-flex gap-2 align-items-end">
|
|
<div>
|
|
<label class="form-label small">New password (min 6 chars)</label>
|
|
<input type="password" class="form-control form-control-sm" id="newPwd" style="width:200px;">
|
|
</div>
|
|
<button type="button" class="btn btn-warning btn-sm" id="btnResetPwd">Reset</button>
|
|
</div>
|
|
<div id="pwdMsg" class="small mt-2" style="display:none;"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Admin flags modal (same layout as the dmr site modals) -->
|
|
<div class="modal fade" id="acctFlagsModal" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered modal-sm">
|
|
<div class="modal-content border rounded" style="background: var(--bg);">
|
|
<div class="modal-header border-bottom">
|
|
<h5 class="modal-title">Admin: <span id="flagsModalCs" class="mono"></span></h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"
|
|
style="filter: invert(1);"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div id="flagsModalBtns" class="d-grid gap-2"></div>
|
|
<div id="flagsModalMsg" class="small mt-2" style="display:none;"></div>
|
|
</div>
|
|
<div class="modal-footer border-top">
|
|
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="button" class="btn btn-primary btn-sm" id="btnFlagsSave">Save</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<?php
|
|
$extra_scripts = '<script>var ADMIN_LEVEL = ' . $lvl . '; var ADMIN_FLAGS = ' . acct_flags($uuid) . ';</script><script src="/auth/admin.js?v=' . @filemtime(__DIR__ . '/admin.js') . '"></script>';
|
|
require __DIR__ . '/footer.php';
|
|
?>
|