Fix Admin panel and xray count

This commit is contained in:
2026-05-10 18:05:24 -03:00
parent 03c43debf4
commit 77a722d4ed
4 changed files with 308 additions and 27 deletions

View File

@@ -298,3 +298,32 @@ pre.log-box{background:#121214;border-color:var(--border);border-radius:15px;}
@media(max-width:620px){.mini-summary{grid-template-columns:1fr}.mini-meter{max-width:none}}
.table-meter{height:6px;min-width:130px;max-width:220px;background:rgba(255,255,255,.07);border:1px solid var(--border);border-radius:999px;overflow:hidden;margin-top:6px;}
.table-meter span{display:block;height:100%;background:linear-gradient(90deg,#36d37a,#ffbe4c,#ff7070);border-radius:inherit;}
/* Final responsive hardening */
.card-hdr{align-items:flex-start;max-width:100%;}
.card-hdr .card-title{flex:1 1 220px;min-width:0;}
.card-actions{display:flex;align-items:center;justify-content:flex-end;gap:8px;flex-wrap:wrap;max-width:100%;min-width:0;}
.card-actions .btn{white-space:nowrap;}
.dashboard-meter{max-width:230px;}
.save-bar{margin-top:18px;padding:14px 16px;border:1px solid var(--border);border-radius:18px;background:linear-gradient(180deg,var(--card2),var(--card));box-shadow:var(--shadow);display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;}
.save-bar-actions{justify-content:flex-start;}
.topbar-actions{min-width:0;}
.welcome-actions .btn{white-space:nowrap;}
@media(max-width:820px){
.card-hdr .card-title{flex-basis:100%;}
.card-actions{width:100%;justify-content:flex-start;}
.card-actions .btn{flex:1 1 auto;justify-content:center;}
.save-bar{align-items:stretch;}
.save-bar-actions{width:100%;}
.save-bar-actions .btn{flex:1 1 130px;}
.welcome-actions{width:100%;justify-content:flex-start;}
.welcome-actions .btn{flex:1 1 150px;justify-content:center;}
}
@media(max-width:420px){
.topbar-left{min-width:0;}
.workspace-main{padding-left:10px;padding-right:10px;}
.dash-card{min-height:132px;padding:18px;}
.dash-card small{font-size:.78rem;}
.dash-icon{width:54px;height:54px;font-size:1.35rem;}
.btn{padding:9px 11px;}
}

View File

@@ -37,6 +37,15 @@ const dashServers = document.getElementById("dashServers");
const dashServerStatus = document.getElementById("dashServerStatus");
const dashXrayClients = document.getElementById("dashXrayClients");
const dashXrayStatus = document.getElementById("dashXrayStatus");
const dashCpuVal = document.getElementById("dashCpuVal");
const dashCpuText = document.getElementById("dashCpuText");
const dashCpuBar = document.getElementById("dashCpuBar");
const dashRamVal = document.getElementById("dashRamVal");
const dashRamText = document.getElementById("dashRamText");
const dashRamBar = document.getElementById("dashRamBar");
const dashNetVal = document.getElementById("dashNetVal");
const dashNetText = document.getElementById("dashNetText");
const dashNetTotal = document.getElementById("dashNetTotal");
const dashQuotaChip = document.getElementById("dashQuotaChip");
const dashQuotaBar = document.getElementById("dashQuotaBar");
const dashQuotaText = document.getElementById("dashQuotaText");
@@ -85,6 +94,7 @@ const xRunning = document.getElementById("xRunning");
const xPID = document.getElementById("xPID");
const xUptime = document.getElementById("xUptime");
const xStatus = document.getElementById("xStatus");
const xOnlineUsers = document.getElementById("xOnlineUsers");
const xCfgEditor = document.getElementById("xCfgEditor");
const xCfgStatus = document.getElementById("xCfgStatus");
const xLogsBox = document.getElementById("xLogsBox");
@@ -468,6 +478,7 @@ function refreshDashboard() {
loadUsersSilent();
loadInbounds();
loadXrayStatus();
if (currentRole === "superadmin") loadStats();
if (currentRole === "reseller") loadMe();
}
@@ -634,7 +645,7 @@ document.getElementById("xStartBtn").addEventListener("click", () => xrayCtrl("s
document.getElementById("xStopBtn").addEventListener("click", () => xrayCtrl("stop"));
document.getElementById("xRestartBtn").addEventListener("click", () => xrayCtrl("restart"));
document.getElementById("xRepairStatsBtn")?.addEventListener("click", repairXrayStats);
document.getElementById("xRefreshBtn").addEventListener("click", loadXrayStatus);
document.getElementById("xRefreshBtn").addEventListener("click", () => { loadXrayStatus(); loadInbounds(); });
document.getElementById("xLoadInboundsBtn").addEventListener("click", loadInbounds);
document.getElementById("xLoadCfgBtn").addEventListener("click", loadXrayCfg);
document.getElementById("xSaveCfgBtn").addEventListener("click", saveXrayCfg);
@@ -727,6 +738,24 @@ async function loadInbounds() {
}
}
async function copyText(text) {
try {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(text);
return true;
}
} catch {}
const ta = document.createElement("textarea");
ta.value = text;
ta.style.position = "fixed";
ta.style.left = "-9999px";
document.body.appendChild(ta);
ta.focus();
ta.select();
try { return document.execCommand("copy"); }
finally { document.body.removeChild(ta); }
}
function renderInbounds(inbounds) {
updateDashboardXray(inbounds);
if (!inbounds.length) {
@@ -815,7 +844,7 @@ function renderInbounds(inbounds) {
const copyBtn = document.createElement("button");
copyBtn.className = "btn btn-ghost btn-sm";
copyBtn.textContent = "Copy";
copyBtn.onclick = () => navigator.clipboard.writeText(c.id);
copyBtn.onclick = async () => { await copyText(c.id); xStatus.textContent = "Copied client ID."; };
const editBtn = document.createElement("button");
editBtn.className = "btn btn-warn btn-sm";
editBtn.style.marginLeft = "4px";
@@ -1043,10 +1072,37 @@ async function deleteReseller(username) {
// ─── Stats ────────────────────────────────────────────────────────────────────
document.querySelector("[data-tab='stats']")?.addEventListener("click", loadStats);
function updateDashboardStats(s) {
if (!s) return;
const cpu = Number(s.cpu_percent ?? 0);
const mem = s.mem_percent == null ? null : Number(s.mem_percent);
if (dashCpuVal) dashCpuVal.textContent = fmtPct(cpu);
if (dashCpuBar) dashCpuBar.style.width = Math.min(100, Math.max(0, cpu)) + "%";
if (dashCpuText) dashCpuText.textContent = cpu >= 85 ? "Carga alta" : cpu >= 60 ? "Carga moderada" : "Carga normal";
if (dashRamVal) dashRamVal.textContent = mem == null ? "--%" : fmtPct(mem);
if (dashRamBar) dashRamBar.style.width = mem == null ? "0%" : Math.min(100, Math.max(0, mem)) + "%";
if (dashRamText) {
const used = s.mem_used_bytes, total = s.mem_total_bytes;
dashRamText.textContent = used != null && total != null ? `${fmtBytes(used)} / ${fmtBytes(total)}` : "Memória usada";
}
const ifaces = Array.isArray(s.interfaces) ? s.interfaces : [];
let rx = 0, tx = 0, rxTotal = 0, txTotal = 0;
ifaces.forEach(it => {
rx += Number(it.rx_mbps || 0);
tx += Number(it.tx_mbps || 0);
rxTotal += Number(it.rx_bytes || 0);
txTotal += Number(it.tx_bytes || 0);
});
if (dashNetVal) dashNetVal.textContent = `${fmtMbps(rx + tx)} Mb/s`;
if (dashNetText) dashNetText.textContent = `RX ${fmtMbps(rx)} · TX ${fmtMbps(tx)} Mb/s`;
if (dashNetTotal) dashNetTotal.textContent = `Total ${fmtBytes(rxTotal + txTotal)}`;
}
async function loadStats() {
try {
const res = await api("/api/stats");
const s = await res.json();
updateDashboardStats(s);
const cpu = s?.cpu_percent ?? 0;
cpuVal.textContent = fmtPct(cpu);
cpuBar.style.width = Math.min(100, Math.max(0, cpu)) + "%";

View File

@@ -64,8 +64,6 @@
</div>
</div>
<div class="topbar-actions">
<button class="toolbar-pill" type="button" title="Idioma">🇧🇷 PT</button>
<button class="icon-btn" type="button" title="Notificações">🔔</button>
<button class="icon-btn" id="themeToggle" type="button" title="Alternar tema"></button>
<div class="user-pill">
<span id="roleChip"></span>
@@ -133,6 +131,33 @@
</div>
<div class="dash-icon"></div>
</div>
<div class="dash-card accent-blue superadmin-only hidden">
<div class="dash-card-main">
<span class="dash-label">CPU</span>
<strong id="dashCpuVal">--%</strong>
<small id="dashCpuText">Carga do processador</small>
<div class="mini-meter dashboard-meter"><span id="dashCpuBar"></span></div>
</div>
<div class="dash-icon"></div>
</div>
<div class="dash-card accent-green superadmin-only hidden">
<div class="dash-card-main">
<span class="dash-label">RAM</span>
<strong id="dashRamVal">--%</strong>
<small id="dashRamText">Memória usada</small>
<div class="mini-meter dashboard-meter"><span id="dashRamBar"></span></div>
</div>
<div class="dash-icon"></div>
</div>
<div class="dash-card accent-purple superadmin-only hidden">
<div class="dash-card-main">
<span class="dash-label">Rede</span>
<strong id="dashNetVal">--</strong>
<small id="dashNetText">RX -- · TX -- Mb/s</small>
<small id="dashNetTotal">Total --</small>
</div>
<div class="dash-icon"></div>
</div>
</div>
<div class="grid2 dashboard-lower">
@@ -273,7 +298,7 @@
<div class="card">
<div class="card-hdr">
<div class="card-title">Xray Core <span class="chip" id="xrayChip">--</span></div>
<div class="xray-admin-only" style="display:flex;gap:5px;flex-wrap:wrap;">
<div class="card-actions xray-admin-only">
<button class="btn btn-ghost btn-sm" id="xStartBtn">Start</button>
<button class="btn btn-danger btn-sm" id="xStopBtn">Stop</button>
<button class="btn btn-ghost btn-sm" id="xRestartBtn">Restart</button>
@@ -295,7 +320,7 @@
<div class="card" style="margin-top:12px;">
<div class="card-hdr">
<div class="card-title">Inbounds &amp; Clients</div>
<button class="btn btn-ghost btn-sm" id="xLoadInboundsBtn">Reload</button>
<div class="card-actions"><button class="btn btn-ghost btn-sm" id="xLoadInboundsBtn">Reload</button></div>
</div>
<div id="inboundsContainer">
<div class="hint" style="padding:8px 0;">Loading inbounds…</div>
@@ -306,7 +331,7 @@
<div class="card xray-admin-only" style="margin-top:12px;">
<div class="card-hdr">
<div class="card-title">Xray Config</div>
<div style="display:flex;gap:4px;">
<div class="card-actions">
<button class="btn btn-sm" id="xrayWizardTabBtn" onclick="setXrayCfgMode('wizard')">Visual</button>
<button class="btn btn-ghost btn-sm" id="xrayJsonTabBtn" onclick="setXrayCfgMode('json')">JSON</button>
</div>
@@ -480,7 +505,7 @@
<div class="card xray-admin-only" style="margin-top:12px;">
<div class="card-hdr">
<div class="card-title">Logs <span class="chip">last 200 lines</span></div>
<button class="btn btn-ghost btn-sm" id="xLoadLogsBtn">Refresh</button>
<div class="card-actions"><button class="btn btn-ghost btn-sm" id="xLoadLogsBtn">Refresh</button></div>
</div>
<pre class="log-box" id="xLogsBox"></pre>
</div>
@@ -834,9 +859,11 @@
</div><!-- /grid2 -->
<!-- Save bar -->
<div class="form-actions" style="margin-top:14px;padding-top:12px;border-top:1px solid var(--border);">
<button class="btn" onclick="saveServerConfig()">Save Config</button>
<button class="btn btn-ghost" onclick="loadServerConfig()">Reload</button>
<div class="save-bar">
<div class="card-actions save-bar-actions">
<button class="btn" onclick="saveServerConfig()">Save Config</button>
<button class="btn btn-ghost" onclick="loadServerConfig()">Reload</button>
</div>
<span id="srvCfgStatus" class="hint">All service changes apply live.</span>
</div>