Files
DragonCoreSSH-NewWEB/admin/index.html
T

1575 lines
121 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en" class="i18n-pending">
<head>
<meta charset="UTF-8"/>
<title>DragonCore Command</title>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<style>html.i18n-pending body{visibility:hidden}</style>
<script>
(function(){
try{
var saved=localStorage.getItem("PANEL_LANG");
var langs=(navigator.languages&&navigator.languages.length?navigator.languages:[navigator.language||""]).join(" ").toLowerCase();
var lang=saved || (langs.indexOf("pt")!==-1 ? "pt-BR" : "en-US");
document.documentElement.lang=lang.toLowerCase();
}catch(e){}
setTimeout(function(){document.documentElement.classList.remove("i18n-pending");},2500);
})();
</script>
<link rel="stylesheet" href="assets/app.css?v=20260714pamfix1"/>
</head>
<body>
<div class="app">
<div class="shell">
<!-- Login overlay -->
<div class="overlay" id="loginOverlay">
<div class="overlay-inner">
<div class="ov-title">DragonCore Command</div>
<div class="ov-sub">Access the private control node with admin or reseller credentials.</div>
<input id="loginUser" class="ov-field" placeholder="Username" autocomplete="username"/>
<input id="loginPass" class="ov-field" type="password" placeholder="Password" autocomplete="current-password"/>
<button class="btn" id="loginBtn" style="width:100%;margin-top:6px;justify-content:center;">Sign in</button>
<div id="loginErr" style="margin-top:6px;font-size:.72rem;color:var(--danger);"></div>
</div>
</div>
<!-- Main app -->
<div id="mainApp" class="hidden">
<div class="panel-layout">
<aside class="sidebar" id="sidebar">
<div class="brand-block">
<div class="brand-mark">DC</div>
<div class="brand-copy">
<strong>DragonCore</strong>
<span>Command Node</span>
</div>
</div>
<nav id="mainNav" class="side-nav">
<button class="tab-btn active" data-tab="dashboard"><span class="nav-icon"></span><span>Painel</span></button>
<div class="nav-group-label">Contas</div>
<button class="tab-btn" data-tab="ssh"><span class="nav-icon">👥</span><span>SSH / SlowDNS</span></button>
<button class="tab-btn" data-tab="xray"><span class="nav-icon"></span><span>Xray Users</span></button>
<div class="nav-group-label superadmin-only hidden">Administração</div>
<button class="tab-btn superadmin-only hidden" data-tab="resellers"><span class="nav-icon">🏪</span><span>Revendedores</span></button>
<button class="tab-btn superadmin-only hidden" data-tab="servers"><span class="nav-icon"></span><span>Infraestrutura</span></button>
<button class="tab-btn superadmin-only hidden" data-tab="logs"><span class="nav-icon"></span><span>Logs</span></button>
<button class="tab-btn superadmin-only hidden" data-tab="bot"><span class="nav-icon">🤖</span><span>Bot / Vendas</span></button>
<button class="tab-btn superadmin-only hidden" data-tab="server"><span class="nav-icon"></span><span>Configurações</span></button>
</nav>
</aside>
<div class="drawer-backdrop" id="drawerBackdrop"></div>
<section class="workspace">
<header class="topbar">
<div class="topbar-left">
<button class="icon-btn" id="menuToggle" type="button" aria-label="Open menu"></button>
<div class="topbar-title">
<span id="pageEyebrow">Painel</span>
<strong id="pageTitle">Visão geral</strong>
</div>
</div>
<div class="topbar-actions">
<select id="languageSelect" class="language-select" title="Language" aria-label="Language">
<option value="pt-BR">PT-BR</option>
<option value="en-US">EN-US</option>
</select>
<div class="user-pill">
<span id="roleChip"></span>
<strong id="meUsername"></strong>
</div>
<button class="btn btn-ghost btn-sm" id="logoutBtn">Sair</button>
</div>
</header>
<main class="workspace-main">
<!-- ═══════════ Dashboard Tab ═══════════ -->
<div class="tab-pane active" id="tab-dashboard">
<div class="dash-grid">
<div class="dash-card accent-blue">
<div class="dash-card-main">
<span class="dash-label">Total de contas</span>
<strong id="dashTotalUsers">--</strong>
<small><span id="dashActiveUsers">--</span> ativas · <span id="dashExpiredUsers">--</span> expiradas</small>
<small id="dashAccountBreakdown">SSH -- · Xray --</small>
</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">Servidores</span>
<strong id="dashServers">--</strong>
<small id="dashServerStatus">monitoramento em tempo real</small>
</div>
<div class="dash-icon"></div>
</div>
<div class="dash-card accent-green reseller-only hidden" id="dashQuotaSummaryCard">
<div class="dash-card-main">
<span class="dash-label">Limite disponível</span>
<strong id="dashQuotaRemaining">--</strong>
<small id="dashQuotaSummaryText">Carregando cota…</small>
<div class="mini-meter"><span id="dashQuotaMiniBar"></span></div>
</div>
<div class="dash-icon"></div>
</div>
<div class="dash-card accent-purple">
<div class="dash-card-main">
<span class="dash-label">Conexões ativas</span>
<strong id="dashConnections">--</strong>
<small id="dashConnectionsText">SSH + Xray online agora</small>
</div>
<div class="dash-icon"></div>
</div>
<div class="dash-card accent-orange">
<div class="dash-card-main">
<span class="dash-label">Xray clients</span>
<strong id="dashXrayClients">--</strong>
<small id="dashXrayStatus">Pronto para revendedores</small>
</div>
<div class="dash-icon"></div>
</div>
<div class="dash-card dash-resource 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 dash-resource 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 dash-resource 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="card superadmin-only hidden" id="updateStatusCard" style="margin-top:18px;">
<div class="card-hdr">
<div class="card-title">Panel Updates <span class="chip" id="updateStateChip">Checking…</span></div>
<div class="card-actions">
<a class="btn btn-ghost btn-sm" id="updateRepoLink" href="https://git.dr2.site/penguinehis/DragonCoreSSH-NewWEB" target="_blank" rel="noopener noreferrer">Open Git</a>
<button class="btn btn-ghost btn-sm" type="button" id="checkUpdateBtn">Check now</button>
</div>
</div>
<div class="metrics">
<div class="metric"><div class="m-label">Installed version</div><div class="m-val update-commit" id="updateCurrentCommit">--</div></div>
<div class="metric"><div class="m-label">Latest Git version</div><div class="m-val update-commit" id="updateLatestCommit">--</div></div>
<div class="metric"><div class="m-label">Branch</div><div class="m-val update-commit" id="updateBranch">main</div></div>
<div class="metric"><div class="m-label">Last checked</div><div class="m-val update-check-time" id="updateCheckedAt">--</div></div>
</div>
<div class="statusbar update-statusbar">
<span id="updateStatusText">Comparing the installed version with the Git repository.</span>
<span class="hint hidden" id="updateCommandWrap">To update: <code>sudo bash /opt/sshpanel/update.sh</code> <button class="btn btn-ghost btn-xs" type="button" id="copyUpdateCommandBtn">Copy command</button></span>
</div>
</div>
<div class="grid2 dashboard-lower">
<div class="card hidden" id="dnsttDashboardCard">
<div class="card-hdr">
<div class="card-title">DNSTT Health <span class="chip">5s window</span></div>
<span class="hint" id="dnsttHealthUpdated">--</span>
</div>
<div class="metrics">
<div class="metric"><div class="m-label">Active Sessions</div><div class="m-val" id="dnsttActiveSessions">--</div></div>
<div class="metric"><div class="m-label">Active Streams</div><div class="m-val" id="dnsttActiveStreams">--</div></div>
<div class="metric"><div class="m-label">DNS RX / 5s</div><div class="m-val" id="dnsttDNSRx">--</div></div>
<div class="metric"><div class="m-label">Queue</div><div class="m-val" id="dnsttQueueLen">--</div></div>
</div>
<div class="tbl-wrap" style="margin-top:10px;">
<table>
<thead><tr><th>Counter</th><th>Value</th><th>Counter</th><th>Value</th></tr></thead>
<tbody id="dnsttHealthBody"></tbody>
</table>
</div>
<div class="statusbar"><span id="dnsttHealthSummary">DNSTT counters are loaded from /api/dnstt.</span><span class="hint">Reject/drop/panic counters should normally stay at 0.</span></div>
</div>
<div class="card" id="dashboardQuotaCard">
<div class="card-hdr"><div class="card-title">Minha cota</div><span class="chip" id="dashQuotaChip">--</span></div>
<div class="quota-meter"><span id="dashQuotaBar"></span></div>
<div class="statusbar"><span id="dashQuotaText">Carregando…</span><span id="dashQuotaBreakdown"></span></div>
</div>
</div>
</div><!-- /tab-dashboard -->
<!-- ═══════════ SSH Users Tab ═══════════ -->
<div class="tab-pane" id="tab-ssh">
<section class="page-hero status-hero" data-tone="green">
<div class="page-hero-copy"><span class="page-kicker">Access workspace</span><h2>Contas SSH</h2><p>Crie, limite e acompanhe acessos SSH e SlowDNS com segurança.</p></div>
<div class="workspace-hero-actions"><span class="workspace-live-status" id="sshLiveStatus">Aguardando dados</span><button class="btn btn-ghost btn-sm" id="sshHeroRefreshBtn" type="button">Atualizar</button></div>
<div class="workspace-overview-grid">
<article class="workspace-overview-card"><span class="workspace-overview-icon green"></span><div><small>Operação</small><strong id="sshMetricState">Carregando</strong></div></article>
<article class="workspace-overview-card"><span class="workspace-overview-icon purple"></span><div><small>Contas</small><strong id="sshMetricTotal">0</strong></div></article>
<article class="workspace-overview-card"><span class="workspace-overview-icon cyan"></span><div><small>Ativas</small><strong id="sshMetricActive">0</strong></div></article>
<article class="workspace-overview-card"><span class="workspace-overview-icon amber"></span><div><small>Online agora</small><strong id="sshMetricOnline">0</strong></div></article>
</div>
</section>
<div class="workspace-nav-shell" data-tone="green">
<nav class="workspace-section-nav" id="sshSectionNav" aria-label="Áreas SSH e SlowDNS" style="--workspace-nav-columns:2">
<button class="active" type="button" data-workspace="ssh" data-workspace-section="users"><span></span> Usuários</button>
<button type="button" data-workspace="ssh" data-workspace-section="create"><span></span> Criar usuário</button>
</nav>
<select id="sshSection" class="workspace-section-select" data-workspace-select="ssh" aria-label="Área SSH e SlowDNS">
<option value="users">Usuários</option><option value="create">Criar usuário</option>
</select>
</div>
<!-- Reseller info card (visible to resellers only) -->
<div id="resellerInfoCard" class="card hidden" style="margin-bottom:12px;">
<div class="card-hdr"><div class="card-title">My Account</div></div>
<div class="metrics">
<div class="metric">
<div class="m-label">Users (used / max)</div>
<div class="m-val" id="rUsedMax">-- / --</div>
</div>
<div class="metric">
<div class="m-label">Expires</div>
<div class="m-val" id="rExpiry">--</div>
</div>
<div class="metric">
<div class="m-label">Status</div>
<div class="m-val" id="rStatus">--</div>
</div>
</div>
</div>
<div id="sshServerPickerCard" class="card hidden" style="margin-bottom:12px;">
<div class="card-hdr">
<div class="card-title">Target server</div>
<span class="chip">master/slave</span>
</div>
<div class="form-grid" style="grid-template-columns:1fr auto;align-items:end;">
<div class="field"><label>Create/list SSH users on</label><select id="sshServerSelect"></select></div>
<button class="btn btn-ghost btn-sm" type="button" id="reloadServersBtn">Reload servers</button>
</div>
<div class="hint" id="sshServerHint">Servers with SSH enabled are available here.</div>
</div>
<section class="workspace-section active" data-workspace-panel="ssh" data-workspace-section-panel="users">
<div class="workspace-section-heading"><div><span>01 · Gestão</span><h3>Usuários SSH e SlowDNS</h3><p>Consulte conexões, limites, validade e ações de cada conta.</p></div></div>
<!-- Users list -->
<div class="card">
<div class="card-hdr">
<div class="card-title">Users <span class="chip" id="userCountChip">0</span></div>
<div style="display:flex;gap:5px;">
<button class="btn btn-ghost btn-sm" id="newUserBtn">+ New</button>
<button class="btn btn-ghost btn-sm" id="reloadUsersBtn">Reload</button>
</div>
</div>
<div class="tbl-wrap">
<table>
<thead><tr>
<th data-sort-key="username">User</th><th data-sort-key="status">Status</th><th data-sort-key="auth">Auth</th>
<th data-sort-key="conn">Conn</th><th data-sort-key="max">Max</th><th data-sort-key="up">Up</th><th data-sort-key="down">Dn</th><th data-sort-key="usage">Usage / Quota</th><th data-sort-key="expires">Expires</th>
<th id="ownerColHead" data-sort-key="owner" class="superadmin-only hidden">Owner</th>
<th>Actions</th>
</tr></thead>
<tbody id="usersBody"></tbody>
</table>
</div>
</div>
</section>
<section class="workspace-section" data-workspace-panel="ssh" data-workspace-section-panel="create">
<div class="workspace-section-heading"><div><span>02 · Cadastro</span><h3 id="userFormHeading">Criar usuário</h3><p>Defina autenticação, validade, conexões e velocidade em uma tela dedicada.</p></div></div>
<!-- Create / edit user form -->
<div class="card workspace-form-card">
<div class="card-hdr">
<div class="card-title" id="userFormTitle">Criar usuário SSH</div>
</div>
<div id="userFormWrap">
<form id="userForm">
<div class="form-grid">
<div class="field"><label>Username</label><input id="fUsername" required autocomplete="off"/></div>
<div class="field"><label>Password <span class="hint">(blank = keep)</span></label><input id="fPassword" type="password" autocomplete="new-password"/></div>
<div class="field"><label>TOTP Secret</label>
<div class="field-row">
<input id="fTotpSecret" placeholder="Leave blank to disable"/>
<button class="btn btn-ghost btn-sm" type="button" id="genTotpBtn">Gen</button>
<button class="btn btn-ghost btn-sm" type="button" id="clearTotpBtn">X</button>
</div>
</div>
<div class="field"><label>TOTP Period (s)</label><input id="fTotpPeriod" type="number" min="15" step="15" placeholder="60"/></div>
<div class="field"><label>TOTP Window</label><input id="fTotpWindow" type="number" min="0" placeholder="1"/></div>
<div class="field"><label>TOTP Digits</label><input id="fTotpDigits" type="number" min="6" max="8" placeholder="6"/></div>
<div class="field"><label>Allow static password too</label><input id="fAllowStatic" type="checkbox" style="width:16px;height:16px;margin-top:10px;"/></div>
<div class="field"><label>Max connections</label><input id="fMaxConn" type="number" min="0" placeholder="0 = unlimited"/></div>
<div class="field"><label>Expires at</label><input id="fExpires" type="datetime-local"/></div>
<div class="field"><label>Max Upload (Mb/s)</label><input id="fUp" type="number" min="0" placeholder="0 = default"/></div>
<div class="field"><label>Max Download (Mb/s)</label><input id="fDown" type="number" min="0" placeholder="0 = default"/></div>
<div class="field"><label>Data quota (GB) <span class="hint">0 = unlimited · 1024 = 1 TB</span></label><input id="fQuotaGB" type="number" min="0" step="0.01" placeholder="0"/></div>
<div class="field"><label>When quota is reached</label><select id="fQuotaAction"><option value="block">Block user</option><option value="throttle">Reduce speed</option></select></div>
<div class="field"><label>Post-quota speed (Mb/s)</label><input id="fQuotaThrottle" type="number" min="1" value="1"/></div>
<div class="field"><label>Current usage</label><input id="fUsageDisplay" readonly value="0 B"/></div>
<div class="field"><label>Reset traffic counter</label><input id="fResetUsage" type="checkbox" style="width:16px;height:16px;margin-top:10px;"/></div>
</div>
<div class="form-actions">
<button class="btn" type="submit" id="saveUserBtn">Save user</button>
<button class="btn btn-ghost" type="button" id="cancelUserBtn">Cancel</button>
</div>
</form>
</div>
</div>
</section>
<div class="statusbar workspace-section-status"><span id="userStatus">Ready.</span><span id="lastReload"></span></div>
</div><!-- /tab-ssh -->
<!-- ═══════════ Xray Tab (superadmin only) ═══════════ -->
<div class="tab-pane" id="tab-xray">
<section class="page-hero status-hero" data-tone="purple">
<div class="page-hero-copy"><span class="page-kicker">Proxy studio</span><h2>Xray visual</h2><p>Gerencie clientes, inbounds e um endpoint XHTTP compartilhado sem editar JSON.</p></div>
<div class="workspace-hero-actions"><span class="workspace-live-status" id="xrayChip">Aguardando dados</span><button class="btn btn-ghost btn-sm" id="xRefreshBtn" type="button">Atualizar</button></div>
<div class="workspace-overview-grid five">
<article class="workspace-overview-card"><span class="workspace-overview-icon green"></span><div><small>Status</small><strong id="xRunning">--</strong></div></article>
<article class="workspace-overview-card"><span class="workspace-overview-icon cyan"></span><div><small>Online</small><strong id="xOnlineUsers">--</strong></div></article>
<article class="workspace-overview-card"><span class="workspace-overview-icon purple">#</span><div><small>PID</small><strong id="xPID">--</strong></div></article>
<article class="workspace-overview-card"><span class="workspace-overview-icon amber"></span><div><small>Uptime</small><strong id="xUptime">--</strong></div></article>
<article class="workspace-overview-card"><span class="workspace-overview-icon blue">API</span><div><small>Contadores</small><strong id="xStatsConfig">--</strong></div></article>
</div>
<div class="workspace-hero-toolbar">
<span id="xStatus" class="workspace-toolbar-status">Pronto.</span>
<div class="workspace-toolbar-actions xray-admin-only">
<select id="xCoreMode" class="input-sm" title="Xray runtime mode"><option value="native">Internal native emulator</option><option value="external">External xray binary</option></select>
<button class="btn btn-ghost btn-sm" id="xSaveModeBtn">Salvar modo</button>
<button class="btn btn-ghost btn-sm" id="xStartBtn">Iniciar</button>
<button class="btn btn-danger btn-sm" id="xStopBtn">Parar</button>
<button class="btn btn-ghost btn-sm" id="xRestartBtn">Reiniciar</button>
<button class="btn btn-ghost btn-sm" id="xRepairStatsBtn">Reparar contadores</button>
</div>
</div>
</section>
<div class="workspace-nav-shell" data-tone="purple">
<nav class="workspace-section-nav" id="xraySectionNav" aria-label="Áreas do Xray" style="--workspace-nav-columns:4">
<button class="active" type="button" data-workspace="xray" data-workspace-section="users"><span></span> Usuários</button>
<button type="button" data-workspace="xray" data-workspace-section="create"><span></span> Criar usuário</button>
<button class="xray-admin-only" type="button" data-workspace="xray" data-workspace-section="config"><span></span> Configuração</button>
<button class="xray-admin-only" type="button" data-workspace="xray" data-workspace-section="logs"><span></span> Logs</button>
</nav>
<select id="xraySection" class="workspace-section-select" data-workspace-select="xray" aria-label="Área do Xray">
<option value="users">Usuários</option><option value="create">Criar usuário</option>
<option class="xray-admin-only" value="config">Configuração</option><option class="xray-admin-only" value="logs">Logs</option>
</select>
</div>
<section class="workspace-section active" data-workspace-panel="xray" data-workspace-section-panel="users">
<!-- Edit Client Panel (hidden by default) -->
<div id="editXrayClientPanel" class="card hidden" style="margin-bottom:12px;border-color:rgba(245,158,11,.4);">
<div class="card-hdr">
<div class="card-title">Edit Client <span id="editClientUUID" style="font-family:monospace;font-size:.65rem;color:var(--muted);"></span></div>
<button class="btn btn-ghost btn-sm" onclick="closeEditXrayClient()">Cancel</button>
</div>
<div class="form-grid">
<div class="field"><label>Display Name</label><input id="editXrayName" autocomplete="off"/></div>
<div class="field"><label>Email / Label</label><input id="editXrayEmail" autocomplete="off"/></div>
<div class="field"><label>Expiry Date</label><input type="datetime-local" id="editXrayExpiry" style="color-scheme:dark;"/></div>
<div class="field"><label>Max Connections <span class="hint">(0 = unlimited)</span></label><input type="number" min="0" id="editXrayMaxConns"/></div>
<div class="field"><label>Data quota (GB) <span class="hint">0 = unlimited · 1024 = 1 TB</span></label><input type="number" min="0" step="0.01" id="editXrayQuotaGB"/></div>
<div class="field"><label>When quota is reached</label><select id="editXrayQuotaAction"><option value="block">Block user</option><option value="throttle">Reduce speed</option></select></div>
<div class="field"><label>Post-quota speed (Mb/s)</label><input type="number" min="1" id="editXrayQuotaThrottle" value="1"/></div>
<div class="field"><label>Current usage</label><input id="editXrayUsage" readonly value="0 B"/></div>
<div class="field"><label>Reset traffic counter</label><input id="editXrayResetUsage" type="checkbox" style="width:16px;height:16px;margin-top:10px;"/></div>
</div>
<div class="form-actions" style="margin-top:8px;">
<button class="btn btn-sm" onclick="saveEditXrayClient()">Save Changes</button>
<button class="btn btn-ghost btn-sm" onclick="closeEditXrayClient()">Cancel</button>
</div>
<div id="editXrayClientStatus" class="hint" style="margin-top:4px;"></div>
</div>
</section>
<div class="card reseller-only hidden reseller-helper-card">
<div class="card-hdr"><div class="card-title">Área do revendedor</div><span class="chip green">cota única</span></div>
<p class="hint">Crie clientes Xray com a mesma experiência do painel principal. Cada cliente Xray desconta do mesmo limite usado pelas contas SSH.</p>
<div class="mini-summary">
<span><strong id="xrayResellerQuotaUsed">--</strong><small>usadas</small></span>
<span><strong id="xrayResellerQuotaRemaining">--</strong><small>disponíveis</small></span>
<span><strong id="xrayResellerQuotaMix">SSH -- · Xray --</strong><small>divisão</small></span>
</div>
</div>
<div id="xrayServerPickerCard" class="card hidden" style="margin-bottom:12px;">
<div class="card-hdr">
<div class="card-title">Target server</div>
<span class="chip">master/slave</span>
</div>
<div class="form-grid" style="grid-template-columns:1fr auto;align-items:end;">
<div class="field"><label>Manage Xray users and config on</label><select id="xrayServerSelect"></select></div>
<button class="btn btn-ghost btn-sm" type="button" id="reloadServersBtn2">Reload servers</button>
</div>
<div class="hint hidden" id="xrayServerHint"></div>
</div>
<section class="workspace-section active" data-workspace-panel="xray" data-workspace-section-panel="users">
<div class="workspace-section-heading"><div><span>01 · Gestão</span><h3>Usuários Xray</h3><p>Consulte clientes, conexões, validade e tráfego separados por inbound.</p></div><button class="btn btn-sm" id="xrayOpenCreateBtn" type="button">Criar usuário</button></div>
<!-- Inbounds & clients -->
<div class="card">
<div class="card-hdr">
<div class="card-title">Inbounds &amp; Clients</div>
<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>
</div>
</div>
</section>
<section class="workspace-section" data-workspace-panel="xray" data-workspace-section-panel="create">
<div class="workspace-section-heading"><div><span>02 · Cadastro</span><h3>Criar usuário Xray</h3><p>Escolha o inbound e cadastre o cliente sem abrir a tabela de usuários.</p></div></div>
<div class="card workspace-form-card">
<div class="card-hdr"><div class="card-title">Novo cliente Xray</div><span class="chip" id="xCreateProtocolChip">inbound</span></div>
<form id="xCreateClientForm">
<div class="form-grid">
<div class="field" style="grid-column:1/-1"><label>Inbound</label><select id="xCreateInbound" required><option value="">Carregando inbounds…</option></select><span class="hint" id="xCreateInboundHint">Selecione onde o cliente será adicionado.</span></div>
<div class="field"><label>UUID</label><div class="field-row"><input id="xCreateUUID" required autocomplete="off" placeholder="Gerado automaticamente"/><button class="btn btn-ghost btn-sm" id="xCreateUUIDBtn" type="button">Gerar</button></div></div>
<div class="field"><label>Nome de exibição</label><input id="xCreateName" autocomplete="off" placeholder="Ex.: Cliente 01"/></div>
<div class="field"><label>Email / identificação</label><input id="xCreateEmail" autocomplete="off" placeholder="cliente@example"/></div>
<div class="field"><label>Expira em</label><input id="xCreateExpiry" type="datetime-local"/></div>
<div class="field"><label>Máximo de conexões <span class="hint">0 = ilimitado</span></label><input id="xCreateMaxConns" type="number" min="0" value="0"/></div>
<div class="field"><label>Cota de dados (GB) <span class="hint">0 = ilimitado · 1024 = 1 TB</span></label><input id="xCreateQuotaGB" type="number" min="0" step="0.01" value="0"/></div>
<div class="field"><label>Ao atingir a cota</label><select id="xCreateQuotaAction"><option value="block">Bloquear usuário</option><option value="throttle">Reduzir velocidade</option></select></div>
<div class="field"><label>Velocidade após a cota (Mb/s)</label><input id="xCreateQuotaThrottle" type="number" min="1" value="1"/></div>
</div>
<div class="form-actions"><button class="btn" id="xCreateClientBtn" type="submit">Criar usuário</button><button class="btn btn-ghost" id="xCreateCancelBtn" type="button">Voltar aos usuários</button></div>
<div class="statusbar"><span id="xCreateClientStatus">Preencha os dados do novo cliente.</span></div>
</form>
</div>
</section>
<section class="workspace-section xray-admin-only" data-workspace-panel="xray" data-workspace-section-panel="config">
<div class="workspace-section-heading"><div><span>03 · Serviço</span><h3>Configuração Xray</h3><p>Edite endpoint, inbounds, TLS e opções avançadas visualmente ou em JSON.</p></div></div>
<!-- Config editor -->
<div class="card">
<div class="card-hdr">
<div class="card-title">Xray Config</div>
<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>
</div>
<!-- Wizard pane -->
<div id="xrayWizardPane">
<section class="shared-endpoint-card">
<div class="shared-endpoint-head">
<div><span class="page-kicker">Shared XHTTP endpoint</span><h3>Um domínio e uma porta</h3><p>O protocolo selecionado usa <code>/</code>; SSH usa <code>/ssh</code>. Disponível no modo Xray nativo.</p></div>
<span class="chip green">path routing</span>
</div>
<div class="shared-route-preview" aria-label="Shared endpoint route preview">
<span><strong id="sharedProxyRouteLabel">VLESS</strong><code>/</code></span>
<i></i>
<span><strong>SSH</strong><code>/ssh</code></span>
</div>
<div class="form-grid shared-endpoint-grid">
<div class="field"><label>Protocolo em /</label><select id="sharedXHTTPProtocol"><option value="vless">VLESS</option><option value="vmess">VMess</option></select></div>
<div class="field"><label>Porta compartilhada</label><input id="sharedXHTTPPort" type="number" min="1" max="65535" value="443"/></div>
<div class="field"><label>IP de listen</label><input id="sharedXHTTPListen" value="0.0.0.0" placeholder="0.0.0.0"/></div>
<div class="field"><label>Host HTTP <span class="hint">opcional</span></label><input id="sharedXHTTPHost" placeholder="vpn.seudominio.com"/></div>
<div class="field"><label>Modo XHTTP</label><select id="sharedXHTTPMode"><option value="auto">auto</option><option value="packet-up">packet-up</option><option value="stream-up">stream-up</option><option value="stream-down">stream-down</option><option value="stream-one">stream-one</option></select></div>
<div class="field"><label>Segurança</label><select id="sharedXHTTPSecurity"><option value="none">Sem TLS</option><option value="tls">TLS</option></select></div>
<div class="field shared-tls-field hidden"><label>Arquivo do certificado</label><input id="sharedXHTTPCert" placeholder="/opt/sshpanel/certs/domain/cert.pem"/></div>
<div class="field shared-tls-field hidden"><label>Arquivo da chave</label><input id="sharedXHTTPKey" placeholder="/opt/sshpanel/certs/domain/key.pem"/></div>
</div>
<div class="shared-endpoint-actions"><span id="sharedXHTTPStatus" class="hint">Carregue a configuração para detectar um endpoint existente.</span><button class="btn" id="sharedXHTTPApplyBtn" type="button">Criar / atualizar endpoint</button></div>
</section>
<aside class="legacy-xhttp-migration" aria-label="Migração de configuração XHTTP antiga">
<span class="legacy-xhttp-icon" aria-hidden="true">SSH+</span>
<div>
<strong>Configuração XHTTP antiga?</strong>
<p>Use “Ativar SSH /ssh” no cartão do inbound existente. O painel mantém o inbound, os clientes e todas as opções atuais; adiciona somente a rota SSH e reinicia o Xray.</p>
</div>
</aside>
<div class="visual-config-toolbar">
<div class="field">
<label>Log Level</label>
<select id="wzLogLevel">
<option value="none">none</option>
<option value="error">error</option>
<option value="warning" selected>warning</option>
<option value="info">info</option>
<option value="debug">debug</option>
</select>
</div>
<div class="visual-config-toolbar-copy"><strong>Inbounds configurados</strong><span>Edite qualquer cartão visualmente ou use JSON para campos avançados.</span></div>
<button class="btn btn-ghost btn-sm" type="button" onclick="wzToggleAddInbound()">+ Novo inbound</button>
</div>
<div id="wzInboundsList" class="visual-inbound-list"></div>
<!-- Add inbound form -->
<div id="wzAddInboundForm" class="visual-inbound-editor hidden">
<div class="visual-editor-heading"><div><span class="page-kicker">Visual editor</span><h3 id="wzInboundFormTitle">Novo inbound</h3></div><span id="wzEditingBadge" class="chip hidden">editing</span></div>
<div class="form-grid">
<div class="field">
<label>Protocol</label>
<select id="wzProtocol" onchange="onWzProtoChange(this.value)">
<option value="vless">VLESS</option>
<option value="vmess">VMess</option>
<option value="ssh">SSH Tunnel (over XHTTP)</option>
<option value="trojan">Trojan</option>
<option value="shadowsocks">Shadowsocks</option>
<option value="socks">SOCKS5 (local)</option>
</select>
</div>
<div class="field"><label>Port</label><input type="number" id="wzPort" min="1" max="65535" placeholder="10086"/></div>
<div class="field"><label>Listen IP</label><input type="text" id="wzListenIP" placeholder="0.0.0.0"/></div>
<div class="field"><label>Tag</label><input type="text" id="wzTag" placeholder="vless-in"/></div>
<!-- VLESS / VMess transport fields -->
<div id="wzVlessFields" style="grid-column:1/-1;display:grid;grid-template-columns:1fr 1fr;gap:8px;">
<div class="field">
<label>Network</label>
<select id="wzNetwork" onchange="onWzNetworkChange(this.value)">
<option value="tcp">TCP</option>
<option value="ws">WebSocket</option>
<option value="xhttp">XHTTP (SplitHTTP)</option>
<option value="httpupgrade">HTTPUpgrade</option>
<option value="h2">HTTP/2</option>
<option value="grpc">gRPC</option>
</select>
</div>
<!-- TCP: no extra fields -->
<!-- WebSocket -->
<div class="field" id="wzWSPathField" style="display:none;"><label>Path</label><input type="text" id="wzWSPath" placeholder="/ws"/></div>
<!-- XHTTP -->
<div class="field" id="wzXHTTPPathField" style="display:none;"><label>Path</label><input type="text" id="wzXHTTPPath" placeholder="/xhttp" value="/xhttp"/></div>
<div class="field" id="wzXHTTPHostField" style="display:none;"><label>HTTP Host <span class="hint">(separate from TLS SNI)</span></label><input type="text" id="wzXHTTPHost" placeholder="example.com"/></div>
<div class="field" id="wzXHTTPModeField" style="display:none;">
<label>Mode</label>
<select id="wzXHTTPMode">
<option value="auto">auto</option>
<option value="packet-up">packet-up</option>
<option value="stream-up">stream-up</option>
<option value="stream-down">stream-down</option>
<option value="stream-one">stream-one</option>
</select>
</div>
<!-- HTTPUpgrade -->
<div class="field" id="wzHUPathField" style="display:none;"><label>Path</label><input type="text" id="wzHUPath" placeholder="/upgrade"/></div>
<div class="field" id="wzHUHostField" style="display:none;"><label>Host <span class="hint">(SNI)</span></label><input type="text" id="wzHUHost" placeholder="example.com"/></div>
<!-- H2 -->
<div class="field" id="wzH2PathField" style="display:none;"><label>Path</label><input type="text" id="wzH2Path" placeholder="/h2"/></div>
<div class="field" id="wzH2HostField" style="display:none;"><label>Host</label><input type="text" id="wzH2Host" placeholder="example.com"/></div>
<!-- gRPC -->
<div class="field" id="wzGRPCServiceField" style="display:none;"><label>Service Name</label><input type="text" id="wzGRPCService" placeholder="grpc-service"/></div>
<div class="field" id="wzGRPCMultiField" style="display:none;">
<label style="display:flex;align-items:center;gap:5px;margin-top:20px;">
<input type="checkbox" id="wzGRPCMulti"/> Multi-mode
</label>
</div>
<!-- TLS (all transports) -->
<div class="field" style="grid-column:1/-1;">
<label>TLS</label>
<select id="wzTLS" onchange="onWzTLSChange(this.value)">
<option value="none">None</option>
<option value="tls">TLS</option>
<option value="reality">Reality</option>
</select>
</div>
<!-- TLS cert/key picker — shown when wzTLS=tls -->
<div id="wzTLSCertBlock" style="display:none;grid-column:1/-1;padding:8px;background:var(--card-bg);border:1px solid var(--border);border-radius:6px;">
<input type="hidden" id="wzTLSCert"/>
<input type="hidden" id="wzTLSKey"/>
<div style="margin-bottom:6px;display:flex;align-items:center;gap:6px;flex-wrap:wrap;">
<span style="font-size:.73rem;color:var(--muted);">Certificate source:</span>
<button class="btn btn-sm" id="wzCertSrcFileBtn" type="button" onclick="setWzCertSrc('file')">File Path</button>
<button class="btn btn-ghost btn-sm" id="wzCertSrcPasteBtn" type="button" onclick="setWzCertSrc('paste')">Paste PEM</button>
<button class="btn btn-ghost btn-sm" id="wzCertSrcGenBtn" type="button" onclick="setWzCertSrc('gen')">Self-Signed</button>
</div>
<!-- File Path sub-panel -->
<div id="wzCertSrcFile" style="display:grid;grid-template-columns:1fr 1fr;gap:8px;">
<div class="field" style="margin:0;"><label style="font-size:.72rem;">Cert File Path</label><input type="text" id="wzTLSCertPath" placeholder="/opt/sshpanel/certs/…/cert.pem" oninput="document.getElementById('wzTLSCert').value=this.value"/></div>
<div class="field" style="margin:0;"><label style="font-size:.72rem;">Key File Path</label><input type="text" id="wzTLSKeyPath" placeholder="/opt/sshpanel/certs/…/key.pem" oninput="document.getElementById('wzTLSKey').value=this.value"/></div>
</div>
<!-- Paste PEM sub-panel -->
<div id="wzCertSrcPaste" style="display:none;">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;">
<div class="field" style="margin:0;"><label style="font-size:.72rem;">Certificate PEM</label><textarea id="wzPastedCert" rows="5" placeholder="-----BEGIN CERTIFICATE-----&#10;…" style="font-family:monospace;font-size:.7rem;width:100%;box-sizing:border-box;resize:vertical;background:var(--input-bg);border:1px solid var(--border);border-radius:4px;color:inherit;padding:4px;"></textarea></div>
<div class="field" style="margin:0;"><label style="font-size:.72rem;">Private Key PEM</label><textarea id="wzPastedKey" rows="5" placeholder="-----BEGIN EC PRIVATE KEY-----&#10;…" style="font-family:monospace;font-size:.7rem;width:100%;box-sizing:border-box;resize:vertical;background:var(--input-bg);border:1px solid var(--border);border-radius:4px;color:inherit;padding:4px;"></textarea></div>
</div>
<div style="display:flex;gap:8px;align-items:flex-end;margin-top:6px;flex-wrap:wrap;">
<div class="field" style="flex:1;min-width:120px;margin:0;"><label style="font-size:.72rem;">Name <span class="hint">(storage folder)</span></label><input type="text" id="wzPastedName" placeholder="my-cert"/></div>
<button class="btn btn-sm" type="button" onclick="wzSavePastedCert()">Save PEM</button>
<span id="wzPasteCertStatus" class="hint"></span>
</div>
</div>
<!-- Self-Signed sub-panel -->
<div id="wzCertSrcGen" style="display:none;">
<div style="display:flex;gap:8px;align-items:flex-end;flex-wrap:wrap;">
<div class="field" style="flex:1;min-width:140px;margin:0;"><label style="font-size:.72rem;">Domain <span class="hint">(CN)</span></label><input type="text" id="wzGenDomain" placeholder="example.com"/></div>
<button class="btn btn-sm" type="button" onclick="wzGenerateCert()">Generate</button>
<span id="wzGenCertStatus" class="hint"></span>
</div>
</div>
</div>
<!-- Reality extra fields -->
<div class="field" id="wzRealityDestField" style="display:none;"><label>Dest <span class="hint">(e.g. example.com:443)</span></label><input type="text" id="wzRealityDest" placeholder="example.com:443"/></div>
<div class="field" id="wzRealitySNIField" style="display:none;"><label>Server Name</label><input type="text" id="wzRealitySNI" placeholder="example.com"/></div>
<div class="field" id="wzRealityPrivField" style="display:none;"><label>Private Key</label><input type="text" id="wzRealityPriv" placeholder="reality private key"/></div>
<div class="field" id="wzRealityShortIDField" style="display:none;"><label>Short ID</label><input type="text" id="wzRealityShortID" placeholder="short id (hex)"/></div>
</div>
<!-- Trojan fields -->
<div id="wzTrojanFields" class="hidden" style="grid-column:1/-1;">
<div class="field"><label>Password</label><input type="text" id="wzTrojanPass" placeholder="trojan-password"/></div>
</div>
<!-- Shadowsocks fields -->
<div id="wzSSFields" class="hidden" style="grid-column:1/-1;display:none;grid-template-columns:1fr 1fr;gap:8px;">
<div class="field"><label>Password</label><input type="text" id="wzSSPass" placeholder="password"/></div>
<div class="field"><label>Method</label>
<select id="wzSSMethod">
<option value="chacha20-ietf-poly1305">chacha20-ietf-poly1305</option>
<option value="aes-256-gcm">aes-256-gcm</option>
<option value="aes-128-gcm">aes-128-gcm</option>
</select>
</div>
</div>
</div>
<div class="form-actions" style="margin-top:8px;">
<button class="btn btn-sm" id="wzSaveInboundBtn" type="button" onclick="wzSaveInbound()">Adicionar inbound</button>
<button class="btn btn-ghost btn-sm" id="wzCancelInboundBtn" type="button" onclick="wzCancelInbound()">Cancelar</button>
</div>
</div>
<div class="visual-save-bar">
<span class="hint" id="wzStatus"></span>
<button class="btn" type="button" onclick="applyWizardConfig()">Salvar configuração e reiniciar</button>
</div>
</div>
<!-- JSON pane (hidden by default) -->
<div id="xrayCfgPaneJson" class="hidden">
<textarea id="xCfgEditor" class="code-area" rows="14" placeholder="Xray JSON config…"></textarea>
<div class="statusbar"><span id="xCfgStatus">Ready.</span></div>
<div class="form-actions" style="margin-top:8px;">
<button class="btn btn-ghost btn-sm" id="xLoadCfgBtn">Load JSON</button>
<button class="btn btn-sm" id="xSaveCfgBtn">Save &amp; Restart</button>
</div>
</div>
</div>
</section>
<section class="workspace-section xray-admin-only" data-workspace-panel="xray" data-workspace-section-panel="logs">
<div class="workspace-section-heading"><div><span>04 · Diagnóstico</span><h3>Logs do Xray</h3><p>Acompanhe as últimas mensagens do serviço em uma tela focada.</p></div></div>
<!-- Logs -->
<div class="card">
<div class="card-hdr">
<div class="card-title">Logs <span class="chip">last 200 lines</span></div>
<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>
</section>
</div><!-- /tab-xray -->
<!-- ═══════════ Resellers Tab (superadmin only) ═══════════ -->
<div class="tab-pane" id="tab-resellers">
<section class="page-hero" data-tone="amber"><div class="page-hero-copy"><span class="page-kicker">Partner operations</span><h2>Revendedores</h2><p>Controle cotas, validade e acesso dos parceiros em um só lugar.</p></div></section>
<div class="workspace-nav-shell" data-tone="amber">
<nav class="workspace-section-nav" id="resellerSectionNav" aria-label="Áreas de revendedores" style="--workspace-nav-columns:2">
<button class="active" type="button" data-workspace="resellers" data-workspace-section="users"><span></span> Revendedores</button>
<button type="button" data-workspace="resellers" data-workspace-section="create"><span></span> Criar revendedor</button>
</nav>
<select id="resellerSection" class="workspace-section-select" data-workspace-select="resellers" aria-label="Área de revendedores">
<option value="users">Revendedores</option><option value="create">Criar revendedor</option>
</select>
</div>
<section class="workspace-section active" data-workspace-panel="resellers" data-workspace-section-panel="users">
<div class="workspace-section-heading"><div><span>01 · Gestão</span><h3>Revendedores cadastrados</h3><p>Consulte cotas, consumo compartilhado, validade e situação de cada parceiro.</p></div></div>
<!-- Resellers list -->
<div class="card">
<div class="card-hdr">
<div class="card-title">Resellers <span class="chip" id="resellerCountChip">0</span></div>
<div style="display:flex;gap:5px;">
<button class="btn btn-ghost btn-sm" id="newResellerBtn">+ New</button>
<button class="btn btn-ghost btn-sm" id="reloadResellersBtn">Reload</button>
</div>
</div>
<div class="tbl-wrap">
<table>
<thead><tr>
<th>Username</th><th>Users (used/max)</th><th>Expires</th><th>Status</th><th>Actions</th>
</tr></thead>
<tbody id="resellersBody"></tbody>
</table>
</div>
</div>
</section>
<section class="workspace-section" data-workspace-panel="resellers" data-workspace-section-panel="create">
<div class="workspace-section-heading"><div><span>02 · Cadastro</span><h3 id="resellerFormHeading">Criar revendedor</h3><p>Defina login, limite compartilhado, validade e acesso em uma tela dedicada.</p></div></div>
<!-- Create / edit reseller form -->
<div class="card workspace-form-card">
<div class="card-hdr">
<div class="card-title" id="resellerFormTitle">Create Reseller</div>
<button class="btn btn-ghost btn-sm" id="cancelResellerBtn">Cancel</button>
</div>
<form id="resellerForm">
<div class="form-grid">
<div class="field"><label>Username</label><input id="rUsername" required autocomplete="off"/></div>
<div class="field"><label>Password <span class="hint">(blank = keep)</span></label><input id="rPassword" type="password" autocomplete="new-password"/></div>
<div class="field"><label>Max SSH users (0 = unlimited)</label><input id="rMaxUsers" type="number" min="0" placeholder="30"/></div>
<div class="field"><label>Expires at</label><input id="rExpires" type="datetime-local"/></div>
<div class="field"><label>Active</label><input id="rActive" type="checkbox" checked style="width:16px;height:16px;margin-top:10px;"/></div>
</div>
<div class="form-actions">
<button class="btn" type="submit" id="saveResellerBtn">Save reseller</button>
</div>
</form>
</div>
</section>
<div class="statusbar workspace-section-status"><span id="resellerStatus">Ready.</span></div>
</div><!-- /tab-resellers -->
<!-- ═══════════ Servers Tab (superadmin only) ═══════════ -->
<div class="tab-pane" id="tab-servers">
<section class="page-hero status-hero" data-tone="blue">
<div class="page-hero-copy"><span class="page-kicker">Fleet control</span><h2>Infraestrutura</h2><p>Gerencie nós, acompanhe a saúde do sistema e consulte tráfego em um único espaço.</p></div>
<div class="workspace-hero-actions"><span class="workspace-live-status" id="fleetLiveStatus">Aguardando dados</span><button class="btn btn-ghost btn-sm" id="refreshServersBtn" type="button">Atualizar</button></div>
<div class="workspace-overview-grid">
<article class="workspace-overview-card"><span class="workspace-overview-icon blue"></span><div><small>Nós</small><strong id="fleetMetricNodes">--</strong></div></article>
<article class="workspace-overview-card"><span class="workspace-overview-icon green"></span><div><small>Ativos</small><strong id="fleetMetricActive">--</strong></div></article>
<article class="workspace-overview-card"><span class="workspace-overview-icon cyan">SSH</span><div><small>SSH habilitado</small><strong id="fleetMetricSSH">--</strong></div></article>
<article class="workspace-overview-card"><span class="workspace-overview-icon purple"></span><div><small>Xray habilitado</small><strong id="fleetMetricXray">--</strong></div></article>
</div>
</section>
<div class="infra-nav-mount"></div>
<div id="serversListView">
<div class="grid2 servers-grid">
<div class="card">
<div class="card-hdr">
<div class="card-title">Managed servers <span class="chip" id="serversCountChip">0</span></div>
</div>
<div class="tbl-wrap">
<table>
<thead><tr><th>Name</th><th>URL</th><th>Options</th><th>Status</th><th>Actions</th></tr></thead>
<tbody id="serversBody"></tbody>
</table>
</div>
<div class="statusbar"><span id="serversStatus">Add slave nodes so the master can create SSH/Xray users remotely.</span></div>
</div>
<div class="card">
<div class="card-hdr"><div class="card-title" id="serverFormTitle">Add / edit server</div></div>
<form id="serverForm">
<input type="hidden" id="srvID"/>
<div class="form-grid server-form-grid">
<div class="field"><label>Name</label><input id="srvName" placeholder="Brazil Node 01"/></div>
<div class="field"><label>Base URL</label><input id="srvBaseURL" placeholder="https://node.example.com:8080"/></div>
<div class="field"><label>Admin username</label><input id="srvAdminUser" placeholder="admin"/></div>
<div class="field"><label>Admin key / password</label><input id="srvAdminKey" type="password" placeholder="leave blank to keep saved key"/></div>
<label class="toggle-field"><input type="checkbox" id="srvEnableSSH" checked/> SSH enabled</label>
<label class="toggle-field"><input type="checkbox" id="srvEnableXray" checked/> Xray enabled</label>
<label class="toggle-field"><input type="checkbox" id="srvIsActive" checked/> Active</label>
</div>
<div class="form-actions" style="margin-top:8px;">
<button class="btn" type="submit" id="saveServerBtn">Save server</button>
<button class="btn btn-ghost" type="button" id="testServerBtn">Test</button>
<button class="btn btn-ghost" type="button" id="clearServerFormBtn">New</button>
</div>
<div id="serverFormStatus" class="hint" style="margin-top:6px;"></div>
</form>
</div>
</div>
</div>
<div id="serverConfigSubpage" class="hidden">
<div class="card" style="margin-bottom:12px;">
<div class="card-hdr">
<div class="card-title">Configure server <span class="chip" id="cfgServerName">--</span></div>
<div class="card-actions">
<button class="btn btn-ghost btn-sm" id="backToServersBtn" type="button">Back</button>
<button class="btn btn-ghost btn-sm" id="loadManagedConfigBtn" type="button">Reload config</button>
<button class="btn btn-sm" id="saveManagedConfigBtn" type="button">Save config</button>
</div>
</div>
<div class="statusbar"><span id="managedConfigStatus">Select Configure on a server to edit that node config.</span><span class="hint">This visual editor writes the same config.json on the selected node.</span></div>
</div>
<div class="grid2">
<div>
<div class="card">
<div class="card-hdr"><div class="card-title">Network</div><span class="chip green">live</span></div>
<div class="field">
<label>Main Listen (SSH / HTTP)</label>
<input type="text" id="managedCfgListen" placeholder="0.0.0.0:80"/>
</div>
<div class="field" style="margin-top:8px;">
<label>Extra Listen Addresses <span class="hint">(one per line, e.g. 0.0.0.0:8080)</span></label>
<textarea id="managedCfgExtraListen" rows="4" style="width:100%;box-sizing:border-box;background:var(--input-bg);border:1px solid var(--border);border-radius:8px;color:inherit;padding:10px;resize:vertical;"></textarea>
</div>
<div class="form-grid" style="margin-top:8px;">
<div class="field"><label>Proxy Auto Restart Interval <span class="hint">0s/off disables</span></label><input type="text" id="managedCfgProxyAutoRestart" placeholder="24h"/></div>
<div class="field"><label>Proxy Restart Grace Delay</label><input type="text" id="managedCfgProxyRestartGrace" placeholder="2s"/></div>
<div class="hint" style="grid-column:1/-1;margin-top:-4px;">Hard restart: closes public proxy listeners and active SSH sessions, then starts the listeners again. Use duration values like 6h, 12h, or 24h.</div>
</div>
</div>
<div class="card" style="margin-top:12px;">
<div class="card-hdr"><div class="card-title">SSH & General</div></div>
<div class="form-grid">
<div class="field"><label>Default Upload Limit (Mbps)</label><input type="number" id="managedCfgLimitUp" min="0" placeholder="0"/></div>
<div class="field"><label>Default Download Limit (Mbps)</label><input type="number" id="managedCfgLimitDown" min="0" placeholder="0"/></div>
<div class="field"><label>SSH Idle Timeout <span class="hint">0s/off = disabled</span></label><input type="text" id="managedCfgSSHIdleTimeout" placeholder="0s" title="Keep disabled for VPN/XHTTP connections."/></div>
<label style="font-size:.73rem;display:flex;align-items:center;gap:5px;cursor:pointer;"><input type="checkbox" id="managedCfgQuiet"/> Quiet Logs</label>
<label style="font-size:.73rem;display:flex;align-items:center;gap:5px;cursor:pointer;"><input type="checkbox" id="managedCfgUserCount"/> User Count Display</label>
<label style="font-size:.73rem;display:flex;align-items:center;gap:5px;cursor:pointer;" title="Verify SSH logins against the Linux system password (/etc/shadow); regular accounts (UID ≥ 1000) are auto-imported."><input type="checkbox" id="managedCfgPamAuth"/> Linux PAM Login (auto-import)</label>
</div>
</div>
<div class="card" style="margin-top:12px;">
<div class="card-hdr"><div class="card-title">SSH Banner</div><span class="chip green">live</span></div>
<div class="field">
<label>Banner Text <span class="hint">(shown to connecting SSH clients)</span></label>
<textarea id="managedCfgBanner" rows="5" style="width:100%;box-sizing:border-box;background:var(--input-bg);border:1px solid var(--border);border-radius:8px;color:inherit;padding:10px;resize:vertical;"></textarea>
</div>
<div class="hint" style="margin-top:6px;">Banner file: /opt/sshpanel/banner.txt</div>
</div>
</div>
<div>
<div class="card">
<div class="card-hdr">
<div class="card-title">DNSTT Tunnel</div>
<span class="chip green">live</span>
<label style="font-size:.73rem;display:flex;align-items:center;gap:5px;cursor:pointer;margin-left:auto;">
<input type="checkbox" id="managedCfgDnsttEnabled" onchange="toggleManagedDnsttFields(this.checked)"/> Enabled
</label>
</div>
<div id="managedDnsttFields" class="form-grid" style="opacity:.4;pointer-events:none;">
<div class="field" style="grid-column:1/-1"><label>NS / Root Domains <span class="hint">one per line</span></label><textarea id="managedCfgDnsttDomains" rows="3" placeholder="t.example.com&#10;t.local.lan"></textarea></div>
<div class="field"><label>UDP Listen</label><input type="text" id="managedCfgDnsttUDP" placeholder="[::]:5300"/></div>
<label style="font-size:.73rem;display:flex;align-items:center;gap:5px;cursor:pointer;grid-column:1/-1"><input type="checkbox" id="managedCfgDnsttFakeEnabled"/> Built-in Local DNS / Fake DNS</label>
<div class="field"><label>Local DNS Listen <span class="hint">IPv6 ok</span></label><input type="text" id="managedCfgDnsttFakeListen" placeholder="[2001:db8::1234]:53"/></div>
<div class="field"><label>Local DNS Domain</label><input type="text" id="managedCfgDnsttFakeDomain" placeholder="t.local.lan"/></div>
<div class="field"><label>Local DNS Workers <span class="hint">0=auto</span></label><input type="number" id="managedCfgDnsttFakeWorkers" min="0" placeholder="4"/></div>
<div class="field"><label>Response Workers <span class="hint">0=safe</span></label><input type="number" id="managedCfgDnsttRespWorkers" min="0" placeholder="1"/></div>
<div class="field"><label>Auto Restart Interval <span class="hint">0s/off disables</span></label><input type="text" id="managedCfgDnsttAutoRestart" placeholder="6h"/></div>
<div class="field"><label>Restart Grace Delay</label><input type="text" id="managedCfgDnsttRestartGrace" placeholder="2s"/></div>
<div class="hint" style="grid-column:1/-1;margin-top:-4px;">Restarts only the DNSTT UDP listener, not the VPS or the full panel. Use duration values like 30m, 2h, or 6h.</div>
<div class="field"><label>Max Sessions <span class="hint">0=default, -1=unlimited</span></label><input type="number" id="managedCfgDnsttMaxSessions" placeholder="10000"/></div>
<div class="field"><label>Max Streams <span class="hint">0=default, -1=unlimited</span></label><input type="number" id="managedCfgDnsttMaxStreams" placeholder="15000"/></div>
<div class="field"><label>Pending DNS Responses</label><input type="number" id="managedCfgDnsttPendingResponses" placeholder="20000"/></div>
<div class="field"><label>Stream Buffer Bytes</label><input type="number" id="managedCfgDnsttStreamBuffer" placeholder="262144"/></div>
<div class="field"><label>UDP Read Buffer Bytes</label><input type="number" id="managedCfgDnsttUDPReadBuffer" placeholder="16777216"/></div>
<div class="field"><label>UDP Write Buffer Bytes</label><input type="number" id="managedCfgDnsttUDPWriteBuffer" placeholder="16777216"/></div>
<div class="hint" style="grid-column:1/-1;margin-top:-4px;">Scale guard: bounded sessions/streams stop RAM explosions under thousands of DNSTT users. Keep verbose connection logs disabled on busy servers.</div>
<div class="field" style="grid-column:1/-1">
<label>Private Key <span class="hint">auto-saved to /opt/sshpanel/dnstt.key</span></label>
<div class="field-row">
<input type="text" id="managedCfgDnsttKey" readonly placeholder="Generate a key first…"/>
<button class="btn btn-ghost btn-sm" type="button" onclick="generateManagedDnsttKey()">Generate</button>
<button class="btn btn-ghost btn-sm" type="button" onclick="loadManagedDnsttPubkey()">Public Key</button>
</div>
<div id="managedDnsttPubkeyWrap" class="hidden" style="margin-top:6px;padding:8px;border-radius:8px;background:rgba(15,23,42,.9);border:1px solid rgba(55,65,81,.9);">
<div style="font-size:.68rem;color:var(--muted);margin-bottom:4px;">Public Key — share with dnstt clients</div>
<div class="field-row">
<input type="text" id="managedDnsttPubkeyVal" readonly style="font-family:monospace;font-size:.65rem;"/>
<button class="btn btn-ghost btn-sm" type="button" id="managedDnsttCopyBtn" onclick="navigator.clipboard.writeText(document.getElementById('managedDnsttPubkeyVal').value);document.getElementById('managedDnsttCopyBtn').textContent='Copied!';setTimeout(()=>document.getElementById('managedDnsttCopyBtn').textContent='Copy',1500)">Copy</button>
</div>
</div>
<div id="managedDnsttKeyStatus" class="hint" style="margin-top:4px;"></div>
</div>
<label style="font-size:.73rem;display:flex;align-items:center;gap:5px;cursor:pointer;grid-column:1/-1"><input type="checkbox" id="managedCfgDnsttNoStats"/> Disable Stats Log</label>
<label style="font-size:.73rem;display:flex;align-items:center;gap:5px;cursor:pointer;grid-column:1/-1"><input type="checkbox" id="managedCfgDnsttNoConsole"/> Disable Console Log</label>
<label style="font-size:.73rem;display:flex;align-items:center;gap:5px;cursor:pointer;grid-column:1/-1"><input type="checkbox" id="managedCfgDnsttLogConnections"/> Verbose Connection Logs</label>
</div>
</div>
<div class="card" style="margin-top:12px">
<div class="card-hdr">
<div class="card-title">UDP Gateway</div>
<span class="chip green">live</span>
<label style="font-size:.73rem;display:flex;align-items:center;gap:5px;cursor:pointer;margin-left:auto;">
<input type="checkbox" id="managedCfgUdpgwEnabled" onchange="toggleManagedUdpgwFields(this.checked)"/> Enabled
</label>
</div>
<div id="managedUdpgwFields" class="form-grid" style="opacity:.4;pointer-events:none;">
<div class="field"><label>Listen</label><input type="text" id="managedCfgUdpgwListen" placeholder="0.0.0.0:7400"/></div>
<div class="field"><label>Max UDP Sessions Per Client <span class="hint">(not total server users)</span></label><input type="number" id="managedCfgUdpgwMaxConns" min="0" placeholder="10"/></div>
<div class="field"><label>Idle Timeout</label><input type="text" id="managedCfgUdpgwIdle" placeholder="2m"/></div>
<div class="field"><label>Map TTL</label><input type="text" id="managedCfgUdpgwMapTTL" placeholder="90s"/></div>
<div class="field"><label>Auto Restart Interval <span class="hint">0s/off disables</span></label><input type="text" id="managedCfgUdpgwAutoRestart" placeholder="24h"/></div>
<div class="field"><label>Restart Grace Delay</label><input type="text" id="managedCfgUdpgwRestartGrace" placeholder="2s"/></div>
<div class="hint" style="grid-column:1/-1;margin-top:-4px;">Hard restart: closes the UDPGW listener and every connected UDPGW client, then starts UDPGW again.</div>
<label style="font-size:.73rem;display:flex;align-items:center;gap:5px;cursor:pointer;grid-column:1/-1"><input type="checkbox" id="managedCfgUdpgwDebug"/> Debug Logging</label>
</div>
</div>
<div class="card" style="margin-top:12px">
<div class="card-hdr">
<div class="card-title">TLS Forwarders <span class="chip" id="managedTlsCountChip">0</span></div>
<span class="chip green">live</span>
<button class="btn btn-ghost btn-sm" type="button" onclick="toggleManagedAddTLSForm()">+ Add</button>
</div>
<div id="managedTlsForwardersList" style="margin-bottom:4px;"></div>
<div id="managedAddTLSPanel" class="hidden" style="border:1px solid var(--border);border-radius:8px;padding:10px;margin-top:6px;">
<div class="form-grid">
<div class="field" style="grid-column:1/-1"><label>Listen Address</label><input type="text" id="managedTlsListenAddr" placeholder="0.0.0.0:443"/></div>
<div class="field" style="grid-column:1/-1">
<label>Certificate</label>
<select id="managedTlsCertType" onchange="onManagedTLSTypeChange(this.value)">
<option value="selfsigned">Generate Self-Signed</option>
<option value="letsencrypt">Let's Encrypt (certbot)</option>
<option value="paste">Paste PEM text</option>
<option value="custom">Custom file paths</option>
</select>
</div>
<div id="managedTlsSSFields" style="grid-column:1/-1"><div class="field"><label>Domain Name <span class="hint">(CN for certificate)</span></label><input type="text" id="managedTlsSSLDomain" placeholder="example.com"/></div></div>
<div id="managedTlsLEFields" class="hidden" style="grid-column:1/-1;display:none;grid-template-columns:1fr 1fr;gap:8px;">
<div class="field"><label>Domain</label><input type="text" id="managedTlsLEDomain" placeholder="example.com"/></div>
<div class="field"><label>Email</label><input type="text" id="managedTlsLEEmail" placeholder="admin@example.com"/></div>
</div>
<div id="managedTlsCustomFields" class="hidden" style="grid-column:1/-1;display:none;grid-template-columns:1fr 1fr;gap:8px;">
<div class="field"><label>Cert File</label><input type="text" id="managedTlsCustomCert" placeholder="/path/to/fullchain.pem"/></div>
<div class="field"><label>Key File</label><input type="text" id="managedTlsCustomKey" placeholder="/path/to/privkey.pem"/></div>
</div>
<div id="managedTlsPasteFields" class="hidden" style="grid-column:1/-1;display:none;">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;">
<div class="field"><label>Certificate PEM</label><textarea id="managedTlsPasteCert" rows="5" placeholder="-----BEGIN CERTIFICATE-----&#10;…" style="font-family:monospace;font-size:.7rem;width:100%;box-sizing:border-box;resize:vertical;background:var(--input-bg);border:1px solid var(--border);border-radius:4px;color:inherit;padding:4px;"></textarea></div>
<div class="field"><label>Private Key PEM</label><textarea id="managedTlsPasteKey" rows="5" placeholder="-----BEGIN EC PRIVATE KEY-----&#10;…" style="font-family:monospace;font-size:.7rem;width:100%;box-sizing:border-box;resize:vertical;background:var(--input-bg);border:1px solid var(--border);border-radius:4px;color:inherit;padding:4px;"></textarea></div>
</div>
<div class="field" style="margin-top:6px;"><label>Name <span class="hint">(storage folder, e.g. my-cert)</span></label><input type="text" id="managedTlsPasteName" placeholder="my-cert"/></div>
</div>
</div>
<div class="form-actions" style="margin-top:8px;">
<button class="btn btn-sm" type="button" onclick="addManagedTLSForwarder()">Add Forwarder</button>
<button class="btn btn-ghost btn-sm" type="button" onclick="toggleManagedAddTLSForm()">Cancel</button>
</div>
<div id="managedTlsAddStatus" class="hint" style="margin-top:4px;"></div>
</div>
</div>
<div class="card" style="margin-top:12px">
<div class="card-hdr"><div class="card-title">Xray Core</div><span class="chip green">live</span></div>
<label style="font-size:.73rem;display:flex;align-items:center;gap:5px;cursor:pointer;margin-top:4px;"><input type="checkbox" id="managedCfgXrayEnabled"/> Enabled</label>
<div class="field" style="margin-top:8px;"><label>Runtime mode</label><select id="managedCfgXrayMode"><option value="native">Internal native emulator</option><option value="external">External xray binary</option></select></div>
<div class="hint" style="margin-top:6px;color:var(--muted);">Native runs inside DragonCoreSSH. External uses /opt/sshpanel/xray with DB-backed /opt/sshpanel/xray_config.json and Stats API on 127.0.0.1:10085.</div>
</div>
</div>
</div>
<div class="save-bar">
<div class="card-actions save-bar-actions">
<button class="btn" id="saveManagedConfigBottomBtn" type="button">Save Config</button>
<button class="btn btn-ghost" id="reloadManagedConfigBottomBtn" type="button">Reload</button>
</div>
<span class="hint">All service changes apply live on the selected node.</span>
</div>
</div>
</div><!-- /tab-servers -->
<!-- ═══════════ Servers Status Tab (superadmin only) ═══════════ -->
<div class="tab-pane" id="tab-servers-status">
<section class="page-hero status-hero" data-tone="green">
<div class="page-hero-copy"><span class="page-kicker">Live fleet</span><h2>Status dos servidores</h2><p>Saúde, carga e sessões ativas de cada nó gerenciado.</p></div>
<div class="workspace-hero-actions"><span class="workspace-live-status" id="serversStatusPageStatus">Aguardando dados</span><button class="btn btn-ghost btn-sm" id="refreshServersStatusBtn" type="button">Atualizar</button></div>
<div class="workspace-overview-grid">
<article class="workspace-overview-card"><span class="workspace-overview-icon blue"></span><div><small>Total</small><strong id="serversStatusCountChip">0</strong></div></article>
<article class="workspace-overview-card"><span class="workspace-overview-icon green"></span><div><small>Online</small><strong id="fleetStatusOnline">--</strong></div></article>
<article class="workspace-overview-card"><span class="workspace-overview-icon red"></span><div><small>Offline</small><strong id="fleetStatusOffline">--</strong></div></article>
<article class="workspace-overview-card"><span class="workspace-overview-icon amber"></span><div><small>Sessões</small><strong id="fleetStatusSessions">--</strong></div></article>
</div>
</section>
<div class="infra-nav-mount"></div>
<div class="servers-status-grid" id="serversStatusGrid"></div>
</div><!-- /tab-servers-status -->
<!-- ═══════════ Stats Tab (superadmin only) ═══════════ -->
<div class="tab-pane" id="tab-stats">
<section class="page-hero status-hero" data-tone="cyan">
<div class="page-hero-copy"><span class="page-kicker">Observability</span><h2>Monitoramento</h2><p>Recursos, interfaces e capacidade do servidor em tempo real.</p></div>
<div class="workspace-hero-actions"><span class="workspace-live-status is-ok" id="statsUpdated">Atualização ao vivo</span><button class="btn btn-ghost btn-sm" id="refreshStatsBtn" type="button">Atualizar</button></div>
<div class="workspace-overview-grid">
<article class="workspace-overview-card has-meter"><span class="workspace-overview-icon cyan">CPU</span><div><small>Processador</small><strong id="cpuVal">--%</strong><div class="workspace-mini-meter"><span id="cpuBar"></span></div></div></article>
<article class="workspace-overview-card has-meter"><span class="workspace-overview-icon purple">RAM</span><div><small>Memória</small><strong id="memVal">--%</strong><span class="workspace-card-note" id="memDetail">--</span><div class="workspace-mini-meter"><span id="memBar"></span></div></div></article>
<article class="workspace-overview-card"><span class="workspace-overview-icon green"></span><div><small>Rede agora</small><strong id="statsNetVal">--</strong></div></article>
<article class="workspace-overview-card"><span class="workspace-overview-icon amber">#</span><div><small>Interfaces</small><strong id="statsIfaceVal">--</strong></div></article>
</div>
</section>
<div class="infra-nav-mount"></div>
<div class="card">
<div class="card-hdr">
<div class="card-title">Interfaces <span class="chip">rx/tx Mbps</span><span class="chip warn">30-day rolling</span></div>
<button class="btn btn-danger btn-sm" id="resetIfaceStatsBtn" type="button">Clean usage</button>
</div>
<div class="tbl-wrap">
<table>
<thead><tr><th>Interface</th><th>Rx Mbps</th><th>Tx Mbps</th><th>Rx Total</th><th>Tx Total</th></tr></thead>
<tbody id="ifaceBody"></tbody>
</table>
</div>
<div class="statusbar"><span id="ifaceSummary"></span><span class="hint">Totals can be cleaned here and auto-clean every 30 days. VnStat history is separate.</span></div>
</div>
</div><!-- /tab-stats -->
<!-- ═══════════ VnStat Tab (superadmin only) ═══════════ -->
<div class="tab-pane" id="tab-vnstat">
<section class="page-hero status-hero" data-tone="purple">
<div class="page-hero-copy"><span class="page-kicker">Traffic intelligence</span><h2>Tráfego de rede</h2><p>Histórico diário e mensal para entender o consumo da infraestrutura.</p></div>
<div class="workspace-hero-actions"><span class="workspace-live-status" id="vnstatStatus">Aguardando dados</span><button class="btn btn-ghost btn-sm" id="reloadVnstatBtn" type="button">Atualizar</button><button class="btn btn-danger btn-sm" id="resetVnstatBtn" type="button">Limpar histórico</button></div>
<div class="workspace-overview-grid">
<article class="workspace-overview-card"><span class="workspace-overview-icon cyan">D</span><div><small>Hoje</small><strong id="vnTodayTotal">--</strong></div></article>
<article class="workspace-overview-card"><span class="workspace-overview-icon purple">M</span><div><small>Este mês</small><strong id="vnMonthTotal">--</strong></div></article>
<article class="workspace-overview-card"><span class="workspace-overview-icon green"></span><div><small>Interfaces</small><strong id="vnIfaceCount">--</strong></div></article>
<article class="workspace-overview-card"><span class="workspace-overview-icon amber"></span><div><small>Período recente</small><strong id="vnLatestPeriod">--</strong></div></article>
</div>
</section>
<div class="infra-nav-mount"></div>
<div class="grid2" style="margin-top:12px;">
<div class="card">
<div class="card-hdr"><div class="card-title">Daily usage <span class="chip">last 31 days</span></div></div>
<div class="tbl-wrap">
<table>
<thead><tr><th>Day</th><th>Interface</th><th>Rx</th><th>Tx</th><th>Total</th></tr></thead>
<tbody id="vnstatDailyBody"></tbody>
</table>
</div>
</div>
<div class="card">
<div class="card-hdr"><div class="card-title">Monthly usage <span class="chip">last 12 months</span></div></div>
<div class="tbl-wrap">
<table>
<thead><tr><th>Month</th><th>Interface</th><th>Rx</th><th>Tx</th><th>Total</th></tr></thead>
<tbody id="vnstatMonthlyBody"></tbody>
</table>
</div>
</div>
</div>
</div><!-- /tab-vnstat -->
<!-- ═══════════ Logs Tab (superadmin only) ═══════════ -->
<div class="tab-pane" id="tab-logs">
<section class="page-hero" data-tone="amber"><div class="page-hero-copy"><span class="page-kicker">Diagnostics</span><h2>Logs do sistema</h2><p>Investigue painel, DNSTT e Xray com uma visualização focada.</p></div><div class="page-hero-pills"><span>Panel</span><span>DNSTT</span><span>Xray</span></div></section>
<div class="card">
<div class="card-hdr">
<div class="card-title">System Logs</div>
<div class="form-actions" style="margin-top:0">
<select id="logSource" class="btn-ghost" style="border-radius:999px;padding:4px 8px;background:rgba(15,23,42,.9);color:var(--text);border:1px solid rgba(55,65,81,.9);font-size:.7rem;">
<option value="panel">Panel / system</option>
<option value="dnstt">DNSTT</option>
<option value="xray">Xray</option>
</select>
<button class="btn btn-sm" type="button" onclick="loadSystemLogs()">Refresh</button>
<button class="btn btn-danger btn-sm" type="button" id="clearPanelLogBtn">Clean panel log</button>
</div>
</div>
<pre class="log-box" id="systemLogBox" style="max-height:430px;min-height:260px;">Select a log source and click Refresh.</pre>
<div class="statusbar"><span id="systemLogStatus">Ready.</span></div>
</div>
</div><!-- /tab-logs -->
<!-- ═══════════ Bot / Vendas Tab (superadmin only) ═══════════ -->
<div class="tab-pane" id="tab-bot">
<section class="bot-hero">
<div class="bot-hero-copy">
<span class="bot-eyebrow">Central de automação</span>
<h2>Bot de vendas</h2>
<p>Telegram, PIX, planos e clientes em um único fluxo.</p>
</div>
<div class="bot-hero-actions">
<span id="botConfigStatus" class="bot-live-status">Aguardando dados</span>
<button class="btn btn-ghost btn-sm" id="botTestBtn" type="button">Testar integrações</button>
<button class="btn btn-ghost btn-sm" id="botConfigReloadBtn" type="button">Atualizar tudo</button>
</div>
<div class="bot-overview-grid">
<article class="bot-overview-card"><span class="bot-overview-icon"></span><div><small>Operação</small><strong id="botMetricState">Carregando</strong></div></article>
<article class="bot-overview-card"><span class="bot-overview-icon bot-purple"></span><div><small>Planos</small><strong id="botMetricPlans">0</strong></div></article>
<article class="bot-overview-card"><span class="bot-overview-icon bot-green"></span><div><small>Clientes</small><strong id="botMetricUsers">0</strong></div></article>
<article class="bot-overview-card"><span class="bot-overview-icon bot-amber">$</span><div><small>PIX pendentes</small><strong id="botMetricPending">0</strong></div></article>
</div>
</section>
<div class="bot-nav-shell">
<nav class="bot-section-nav" id="botSectionNav" aria-label="Áreas do bot">
<button class="active" type="button" data-bot-section="config"><span></span> Integrações</button>
<button type="button" data-bot-section="plans"><span></span> Planos</button>
<button type="button" data-bot-section="packages"><span></span> Créditos</button>
<button type="button" data-bot-section="messages"><span></span> Mensagens</button>
<button type="button" data-bot-section="users"><span></span> Clientes</button>
<button type="button" data-bot-section="transactions"><span>$</span> Pagamentos</button>
</nav>
<select id="botSection" class="bot-section-select" aria-label="Área do bot">
<option value="config">Integrações</option><option value="plans">Planos</option><option value="packages">Créditos</option>
<option value="messages">Mensagens</option><option value="users">Clientes</option><option value="transactions">Pagamentos</option>
</select>
</div>
<section class="bot-section active" id="botSec-config" data-bot-panel="config">
<div class="bot-section-heading"><div><span>01 · Integrações</span><h3>Conecte os serviços</h3><p>Tokens ficam criptografados. Campos vazios mantêm o valor atual.</p></div><button class="btn btn-sm" id="botConfigSaveBtn" type="button">Salvar e reiniciar bot</button></div>
<div class="bot-config-grid">
<article class="card bot-integration-card">
<div class="bot-card-heading"><div class="bot-service-icon telegram"></div><div><h4>Telegram</h4><p>Canal de atendimento e entrega</p></div><label class="bot-switch"><input id="botEnabled" type="checkbox"/><span></span></label></div>
<div class="field"><label>Token do @BotFather <span class="bot-secret-state" id="botHasTgToken"></span></label><input id="botTelegramToken" type="password" autocomplete="new-password" placeholder="Em branco para manter o token salvo"/></div>
<div class="field"><label>IDs dos administradores</label><input id="botAdminIDs" inputmode="numeric" placeholder="111111111, 222222222"/><span class="hint">Separe mais de um ID por vírgula.</span></div>
</article>
<article class="card bot-integration-card">
<div class="bot-card-heading"><div class="bot-service-icon mercado">MP</div><div><h4>Mercado Pago</h4><p>Recebimentos PIX automáticos</p></div></div>
<div class="field"><label>Access Token <span class="bot-secret-state" id="botHasMpToken"></span></label><input id="botMPToken" type="password" autocomplete="new-password" placeholder="APP_USR-… (vazio mantém o atual)"/></div>
<div class="form-grid">
<div class="field"><label>Confirmação</label><select id="botMPConfirmMode"><option value="polling">Polling — recomendado</option><option value="webhook">Webhook assinado</option></select></div>
<div class="field"><label>Intervalo</label><input id="botMPPollInterval" value="20s" placeholder="20s"/></div>
<div class="field"><label>Validade do PIX</label><div class="bot-input-suffix"><input id="botPixExp" type="number" min="5" max="1440" value="30"/><span>min</span></div></div>
</div>
<div id="botMPWebhookBox" class="bot-webhook-box hidden">
<div class="field"><label>Webhook Secret <span class="bot-secret-state" id="botHasMpSecret"></span></label><input id="botMPWebhookSecret" type="password" autocomplete="new-password" placeholder="Mínimo de 16 caracteres"/></div>
<div class="bot-copy-row"><code id="botMPWebhookURL">https://SEU_HOST/api/mp/webhook</code><button class="btn btn-ghost btn-sm" id="botCopyWebhookBtn" type="button">Copiar</button></div>
</div>
</article>
<article class="card bot-integration-card">
<div class="bot-card-heading"><div class="bot-service-icon trial"></div><div><h4>Teste grátis</h4><p>Entrada controlada para novos clientes</p></div><label class="bot-switch"><input id="botTrialEnabled" type="checkbox"/><span></span></label></div>
<div class="form-grid">
<div class="field"><label>Duração</label><div class="bot-input-suffix"><input id="botTrialHours" type="number" min="1" max="720" value="1"/><span>h</span></div></div>
<div class="field"><label>Conexões</label><input id="botTrialMaxConns" type="number" min="1" max="1000" value="1"/></div>
<div class="field"><label>Tipo</label><select id="botTrialKind"><option value="ssh">SSH</option><option value="xray">Xray</option></select></div>
<div class="field"><label>Inbound Xray</label><input id="botTrialInbound" list="botInboundList" placeholder="Somente para Xray"/></div>
</div>
</article>
<article class="card bot-integration-card">
<div class="bot-card-heading"><div class="bot-service-icon host"></div><div><h4>Entrega</h4><p>Endereços enviados após a compra</p></div></div>
<div class="field"><label>Host/IP para SSH</label><input id="botPublicHost" placeholder="vpn.seudominio.com ou IP"/></div>
<div class="field"><label>Host para links Xray</label><input id="botXrayPublicHost" placeholder="xray.seudominio.com"/></div>
<div class="bot-note">Esses endereços aparecem nas credenciais entregues ao cliente.</div>
</article>
</div>
</section>
<section class="bot-section" id="botSec-plans" data-bot-panel="plans">
<div class="bot-section-heading"><div><span>02 · Catálogo</span><h3>Planos de acesso</h3><p>Configure preço, validade, protocolo e limites.</p></div><div class="card-actions"><button class="btn btn-ghost btn-sm" id="botReloadPlansBtn" type="button">Atualizar</button><button class="btn btn-sm" id="botNewPlanBtn" type="button">Novo plano</button></div></div>
<div class="bot-master-detail">
<article class="card"><div class="bot-list-heading"><strong>Planos cadastrados</strong><span class="chip" id="botPlanCount">0</span></div><div class="tbl-wrap"><table class="bot-table"><thead><tr><th>Plano</th><th>Entrega</th><th>Validade</th><th>Preço</th><th>Status</th><th></th></tr></thead><tbody id="botPlansBody"></tbody></table></div><div class="statusbar"><span id="botPlansStatus">Aguardando.</span></div></article>
<article class="card bot-editor-card"><div class="card-hdr"><div><div class="card-title" id="botPlanFormTitle">Novo plano</div><p class="hint">Defina como a conta será criada.</p></div><button class="btn btn-ghost btn-sm" id="botCancelPlanBtn" type="button">Limpar</button></div>
<form id="botPlanForm"><input type="hidden" id="planId"/><div class="form-grid">
<div class="field bot-span-2"><label>Nome do plano</label><input id="planName" maxlength="120" required placeholder="Ex.: Premium 30 dias"/></div>
<div class="field"><label>Tipo</label><select id="planKind"><option value="ssh">SSH</option><option value="xray">Xray</option></select></div><div class="field"><label>Dias</label><input id="planDays" type="number" min="1" max="3650" value="30"/></div>
<div class="field"><label>Máx. conexões</label><input id="planMaxConns" type="number" min="0" max="10000" value="1"/></div><div class="field"><label>Preço (R$)</label><input id="planPrice" type="number" step="0.01" min="0" value="0"/></div>
<div class="field"><label>Upload Mbps</label><input id="planUpMbps" type="number" min="0" value="0"/></div><div class="field"><label>Download Mbps</label><input id="planDownMbps" type="number" min="0" value="0"/></div>
<div class="field"><label>Inbound Xray</label><input id="planInbound" list="botInboundList" placeholder="tag do inbound"/><datalist id="botInboundList"></datalist></div><div class="field"><label>Protocolo Xray</label><select id="planProtocol"><option value="">Automático</option><option value="vless">VLESS</option><option value="vmess">VMess</option><option value="trojan">Trojan</option></select></div>
<div class="field"><label>Custo em créditos</label><input id="planCreditCost" type="number" min="0" value="1"/></div><div class="field"><label>Servidor</label><input id="planServerId" placeholder="Vazio = local"/></div>
<div class="field"><label>Ordem</label><input id="planSort" type="number" value="0"/></div><label class="bot-check-field"><input id="planActive" type="checkbox" checked/><span>Plano visível no bot</span></label>
</div><div class="form-actions"><button class="btn" type="submit">Salvar plano</button></div></form>
</article>
</div>
</section>
<section class="bot-section" id="botSec-packages" data-bot-panel="packages">
<div class="bot-section-heading"><div><span>03 · Revendedores</span><h3>Pacotes de créditos</h3><p>Crie recargas para revendedores comprarem planos.</p></div><div class="card-actions"><button class="btn btn-ghost btn-sm" id="botReloadPkgsBtn" type="button">Atualizar</button><button class="btn btn-sm" id="botNewPkgBtn" type="button">Novo pacote</button></div></div>
<div class="bot-master-detail">
<article class="card"><div class="bot-list-heading"><strong>Pacotes cadastrados</strong><span class="chip" id="botPkgCount">0</span></div><div class="tbl-wrap"><table class="bot-table"><thead><tr><th>Pacote</th><th>Créditos</th><th>Preço</th><th>Status</th><th></th></tr></thead><tbody id="botPkgsBody"></tbody></table></div><div class="statusbar"><span id="botPkgStatus">Aguardando.</span></div></article>
<article class="card bot-editor-card"><div class="card-hdr"><div><div class="card-title">Editar pacote</div><p class="hint">Defina o saldo entregue e o preço PIX.</p></div></div><form id="botPkgForm"><input type="hidden" id="pkgId"/><div class="form-grid">
<div class="field bot-span-2"><label>Nome</label><input id="pkgName" maxlength="120" required placeholder="Ex.: Recarga 50 créditos"/></div><div class="field"><label>Créditos</label><input id="pkgCredits" type="number" min="1" value="10"/></div><div class="field"><label>Preço (R$)</label><input id="pkgPrice" type="number" step="0.01" min="0" value="0"/></div><div class="field"><label>Ordem</label><input id="pkgSort" type="number" value="0"/></div><label class="bot-check-field"><input id="pkgActive" type="checkbox" checked/><span>Pacote visível</span></label>
</div><div class="form-actions"><button class="btn" type="submit">Salvar pacote</button><button class="btn btn-ghost" id="botClearPkgBtn" type="button">Limpar</button></div></form></article>
</div>
</section>
<section class="bot-section" id="botSec-messages" data-bot-panel="messages">
<div class="bot-section-heading"><div><span>04 · Conteúdo</span><h3>Mensagens do bot</h3><p>Personalize os textos vistos pelo cliente.</p></div><button class="btn btn-ghost btn-sm" id="botReloadSettingsBtn" type="button">Restaurar salvo</button></div>
<article class="card bot-message-editor"><div class="bot-message-grid"><div class="field"><label>Mensagem de boas-vindas</label><textarea id="setWelcome" rows="7" maxlength="4096" placeholder="Olá, {name}! Escolha uma opção abaixo."></textarea><span class="hint">Use <code>{name}</code> para inserir o primeiro nome.</span></div><div class="field"><label>Contato e suporte</label><textarea id="setContact" rows="7" maxlength="4096" placeholder="Explique como falar com o suporte."></textarea></div><div class="field"><label>Texto para baixar o app</label><textarea id="setAppText" rows="5" maxlength="4096" placeholder="Baixe nosso aplicativo para conectar."></textarea></div><div class="field"><label>Link do aplicativo</label><input id="setAppUrl" type="url" maxlength="2048" placeholder="https://..."/><span class="hint">Somente links HTTP ou HTTPS.</span></div></div><div class="bot-save-row"><span id="botSettingsStatus" class="hint">Aguardando.</span><button class="btn" id="botSaveSettingsBtn" type="button">Salvar mensagens</button></div></article>
</section>
<section class="bot-section" id="botSec-users" data-bot-panel="users">
<div class="bot-section-heading"><div><span>05 · Relacionamento</span><h3>Clientes do Telegram</h3><p>Gerencie funções, vínculos e saldo sem sair da lista.</p></div><button class="btn btn-ghost btn-sm" id="botReloadUsersBtn" type="button">Atualizar clientes</button></div>
<article class="card"><div class="bot-list-heading"><div><strong>Base de clientes</strong><span class="hint"> Ações administrativas são registradas no livro de créditos.</span></div><span class="chip" id="botUserCount">0</span></div><div class="tbl-wrap"><table class="bot-table"><thead><tr><th>Cliente</th><th>Telegram ID</th><th>Função</th><th>Revenda vinculada</th><th>Saldo</th><th></th></tr></thead><tbody id="botUsersBody"></tbody></table></div><div class="statusbar"><span id="botUsersStatus">Aguardando.</span></div></article>
</section>
<section class="bot-section" id="botSec-transactions" data-bot-panel="transactions">
<div class="bot-section-heading"><div><span>06 · Financeiro</span><h3>Pagamentos PIX</h3><p>Acompanhe confirmações e reprocessamentos.</p></div><div class="card-actions"><select id="botTxnFilter" class="input-sm"><option value="">Todos os status</option><option value="pending">Pendentes</option><option value="approved">Aprovados</option><option value="expired">Expirados</option><option value="refunded">Marcados como estornados</option><option value="error">Com erro</option></select><button class="btn btn-ghost btn-sm" id="botReloadTxnsBtn" type="button">Atualizar</button></div></div>
<article class="card"><div class="bot-list-heading"><strong>Histórico recente</strong><span class="chip" id="botTxnCount">0</span></div><div class="tbl-wrap"><table class="bot-table"><thead><tr><th>Pagamento</th><th>Cliente</th><th>Tipo</th><th>Valor</th><th>Status</th><th>Entrega</th><th>Data</th><th></th></tr></thead><tbody id="botTxnsBody"></tbody></table></div><div class="statusbar"><span id="botTxnStatus">Aguardando.</span></div></article>
</section>
<div class="bot-modal hidden" id="botUserActionModal" role="dialog" aria-modal="true" aria-labelledby="botUserActionTitle">
<div class="bot-modal-backdrop" data-bot-modal-close></div>
<div class="bot-modal-card">
<div class="card-hdr"><div><div class="card-title" id="botUserActionTitle">Editar cliente</div><p class="hint" id="botUserActionSubtitle"></p></div><button class="btn btn-ghost btn-sm" type="button" data-bot-modal-close>Fechar</button></div>
<form id="botUserActionForm"><input type="hidden" id="botActionTelegramID"/><input type="hidden" id="botActionMode"/>
<div id="botRoleFields"><div class="field"><label>Função</label><select id="botActionRole"><option value="customer">Cliente</option><option value="reseller">Revendedor</option><option value="blocked">Bloqueado</option></select></div><div class="field" id="botActionLinkedField"><label>Usuário da revenda vinculada</label><input id="botActionLinked" placeholder="username em Revendedores"/></div></div>
<div id="botCreditFields" class="hidden"><div class="field"><label>Ajuste de créditos</label><input id="botActionCredits" type="number" placeholder="Ex.: 10 ou -5"/><span class="hint">Use número negativo para remover saldo.</span></div></div>
<div class="form-actions"><button class="btn" type="submit">Confirmar alteração</button></div>
</form>
</div>
</div>
</div><!-- /tab-bot -->
<!-- ═══════════ Server Config Tab (superadmin only) ═══════════ -->
<div class="tab-pane" id="tab-server">
<section class="page-hero" data-tone="blue"><div class="page-hero-copy"><span class="page-kicker">System studio</span><h2>Configurações</h2><p>Rede, SSH, túneis e TLS organizados em blocos visuais e aplicados ao vivo.</p></div></section>
<div class="workspace-nav-shell" data-tone="blue">
<nav class="workspace-section-nav" id="configSectionNav" aria-label="Áreas de configuração" style="--workspace-nav-columns:5">
<button class="active" type="button" data-workspace="config" data-workspace-section="general"><span></span> Rede e SSH</button>
<button type="button" data-workspace="config" data-workspace-section="slowdns"><span>DNS</span> SlowDNS</button>
<button type="button" data-workspace="config" data-workspace-section="udp"><span></span> UDP Gateway</button>
<button type="button" data-workspace="config" data-workspace-section="tls"><span></span> TLS</button>
<button type="button" data-workspace="config" data-workspace-section="xray"><span></span> Xray</button>
</nav>
<select id="configSection" class="workspace-section-select" data-workspace-select="config" aria-label="Área de configuração">
<option value="general">Rede e SSH</option><option value="slowdns">SlowDNS</option><option value="udp">UDP Gateway</option><option value="tls">TLS</option><option value="xray">Xray</option>
</select>
</div>
<div class="settings-workspace">
<section class="workspace-section active" data-workspace-panel="config" data-workspace-section-panel="general">
<div class="workspace-section-heading"><div><span>01 · Base</span><h3>Rede e SSH</h3><p>Configure listeners, limites padrão, tempo ocioso e o banner de conexão.</p></div></div>
<div class="settings-panel-grid">
<!-- Network -->
<div class="card">
<div class="card-hdr">
<div class="card-title">Network</div>
<span class="chip green">live</span>
</div>
<div class="form-grid">
<div class="field" style="grid-column:1/-1">
<label>Main Listen (SSH / HTTP)</label>
<input type="text" id="cfgListen" placeholder="0.0.0.0:80"/>
</div>
</div>
<div class="field" style="margin-top:6px">
<label>Extra Listen Addresses <span class="hint">(one per line, e.g. 0.0.0.0:8080)</span></label>
<textarea id="cfgExtraListen" rows="3" style="resize:vertical"></textarea>
</div>
<div class="form-grid" style="margin-top:8px;">
<div class="field">
<label>Proxy Auto Restart Interval <span class="hint">0s/off disables</span></label>
<input type="text" id="cfgProxyAutoRestart" placeholder="24h"/>
</div>
<div class="field">
<label>Proxy Restart Grace Delay</label>
<input type="text" id="cfgProxyRestartGrace" placeholder="2s"/>
</div>
<div class="hint" style="grid-column:1/-1;margin-top:-4px;">Hard restart: closes public proxy listeners and active SSH sessions, then starts the listeners again. Use duration values like 6h, 12h, or 24h.</div>
</div>
</div>
<!-- SSH & general -->
<div class="card" style="margin-top:12px">
<div class="card-hdr">
<div class="card-title">SSH &amp; General</div>
</div>
<div class="form-grid">
<div class="field">
<label>Default Upload Limit (Mbps)</label>
<input type="number" id="cfgLimitUp" min="0" placeholder="0 = unlimited"/>
</div>
<div class="field">
<label>Default Download Limit (Mbps)</label>
<input type="number" id="cfgLimitDown" min="0" placeholder="0 = unlimited"/>
</div>
<div class="field">
<label>Max Total SSH Connections <span class="hint">0 = default (10000) · -1 = unlimited</span></label>
<input type="number" id="cfgMaxTotalConns" min="-1" placeholder="10000" title="Global cap across all users. New connections past this are rejected before the SSH handshake, so a surge cannot exhaust memory/CPU. Raise it if the server has resources for more; set -1 to remove the cap entirely."/>
</div>
<div class="field">
<label>SSH Idle Timeout <span class="hint">0s/off = disabled</span></label>
<input type="text" id="cfgSSHIdleTimeout" placeholder="0s" title="Closes an authenticated SSH connection only after this period with no bytes in either direction. Keep disabled for VPN/XHTTP connections."/>
</div>
</div>
<div style="display:flex;gap:16px;margin-top:8px;flex-wrap:wrap;">
<label style="font-size:.73rem;display:flex;align-items:center;gap:5px;cursor:pointer;">
<input type="checkbox" id="cfgQuiet"/> Quiet Logs
</label>
<label style="font-size:.73rem;display:flex;align-items:center;gap:5px;cursor:pointer;">
<input type="checkbox" id="cfgUserCount"/> User Count Display
</label>
<label style="font-size:.73rem;display:flex;align-items:center;gap:5px;cursor:pointer;" title="Verify SSH logins against the Linux system password (/etc/shadow). Regular accounts (UID ≥ 1000) that log in successfully are auto-imported into the panel.">
<input type="checkbox" id="cfgPamAuth"/> Linux PAM Login (auto-import)
</label>
</div>
</div>
<!-- Banner -->
<div class="card settings-span-all" style="margin-top:12px">
<div class="card-hdr">
<div class="card-title">SSH Banner</div>
<span class="chip green">live</span>
</div>
<div class="field">
<label>Banner Text <span class="hint">(shown to connecting SSH clients)</span></label>
<textarea id="cfgBanner" rows="4" style="resize:vertical"></textarea>
</div>
<div class="hint" style="margin-top:6px;">Banner file: /opt/sshpanel/banner.txt</div>
</div>
</div>
</section>
<section class="workspace-section" data-workspace-panel="config" data-workspace-section-panel="slowdns">
<div class="workspace-section-heading"><div><span>02 · Túnel DNS</span><h3>SlowDNS / DNSTT</h3><p>Gerencie domínios, DNS local, capacidade, filas e reinício controlado.</p></div></div>
<!-- DNSTT -->
<div class="card">
<div class="card-hdr">
<div class="card-title">DNSTT Tunnel</div>
<span class="chip green">live</span>
<label style="font-size:.73rem;display:flex;align-items:center;gap:5px;cursor:pointer;margin-left:auto;">
<input type="checkbox" id="cfgDnsttEnabled" onchange="toggleDnsttFields(this.checked)"/> Enabled
</label>
</div>
<div id="dnsttFields" class="form-grid" style="opacity:.4;pointer-events:none;">
<div class="field" style="grid-column:1/-1">
<label>NS / Root Domains <span class="hint">one per line</span></label>
<textarea id="cfgDnsttDomains" rows="3" placeholder="t.example.com&#10;t.local.lan"></textarea>
</div>
<div class="field">
<label>UDP Listen</label>
<input type="text" id="cfgDnsttUDP" placeholder="[::]:5300"/>
</div>
<label style="font-size:.73rem;display:flex;align-items:center;gap:5px;cursor:pointer;grid-column:1/-1">
<input type="checkbox" id="cfgDnsttFakeEnabled"/> Built-in Local DNS / Fake DNS
</label>
<div class="field">
<label>Local DNS Listen <span class="hint">IPv6 ok</span></label>
<input type="text" id="cfgDnsttFakeListen" placeholder="[2001:db8::1234]:53"/>
</div>
<div class="field">
<label>Local DNS Domain</label>
<input type="text" id="cfgDnsttFakeDomain" placeholder="t.local.lan"/>
</div>
<div class="field">
<label>Local DNS Workers <span class="hint">0=auto</span></label>
<input type="number" id="cfgDnsttFakeWorkers" min="0" placeholder="4"/>
</div>
<div class="field">
<label>Response Workers <span class="hint">0=safe</span></label>
<input type="number" id="cfgDnsttRespWorkers" min="0" placeholder="1"/>
</div>
<div class="field">
<label>Auto Restart Interval <span class="hint">0s/off disables</span></label>
<input type="text" id="cfgDnsttAutoRestart" placeholder="6h"/>
</div>
<div class="field">
<label>Restart Grace Delay</label>
<input type="text" id="cfgDnsttRestartGrace" placeholder="2s"/>
</div>
<div class="hint" style="grid-column:1/-1;margin-top:-4px;">Restarts only the DNSTT UDP listener, not the VPS or the full panel. Use duration values like 30m, 2h, or 6h.</div>
<div class="field"><label>Max Sessions <span class="hint">0=default, -1=unlimited</span></label><input type="number" id="cfgDnsttMaxSessions" placeholder="10000"/></div>
<div class="field"><label>Max Streams <span class="hint">0=default, -1=unlimited</span></label><input type="number" id="cfgDnsttMaxStreams" placeholder="15000"/></div>
<div class="field"><label>Pending DNS Responses</label><input type="number" id="cfgDnsttPendingResponses" placeholder="20000"/></div>
<div class="field"><label>Stream Buffer Bytes</label><input type="number" id="cfgDnsttStreamBuffer" placeholder="262144"/></div>
<div class="field"><label>UDP Read Buffer Bytes</label><input type="number" id="cfgDnsttUDPReadBuffer" placeholder="16777216"/></div>
<div class="field"><label>UDP Write Buffer Bytes</label><input type="number" id="cfgDnsttUDPWriteBuffer" placeholder="16777216"/></div>
<div class="hint" style="grid-column:1/-1;margin-top:-4px;">Scale guard: bounded sessions/streams stop RAM explosions under thousands of DNSTT users. Keep verbose connection logs disabled on busy servers.</div>
<div class="field" style="grid-column:1/-1">
<label>Private Key <span class="hint">auto-saved to /opt/sshpanel/dnstt.key</span></label>
<div class="field-row">
<input type="text" id="cfgDnsttKey" readonly placeholder="Generate a key first…"/>
<button class="btn btn-ghost btn-sm" type="button" onclick="generateDnsttKey()">Generate</button>
<button class="btn btn-ghost btn-sm" type="button" onclick="loadDnsttPubkey()">Public Key</button>
</div>
<div id="dnsttPubkeyWrap" class="hidden" style="margin-top:6px;padding:8px;border-radius:8px;background:rgba(15,23,42,.9);border:1px solid rgba(55,65,81,.9);">
<div style="font-size:.68rem;color:var(--muted);margin-bottom:4px;">Public Key — share with dnstt clients</div>
<div class="field-row">
<input type="text" id="dnsttPubkeyVal" readonly style="font-family:monospace;font-size:.65rem;"/>
<button class="btn btn-ghost btn-sm" type="button" id="dnsttCopyBtn" onclick="navigator.clipboard.writeText(document.getElementById('dnsttPubkeyVal').value);document.getElementById('dnsttCopyBtn').textContent='Copied!';setTimeout(()=>document.getElementById('dnsttCopyBtn').textContent='Copy',1500)">Copy</button>
</div>
</div>
<div id="dnsttKeyStatus" class="hint" style="margin-top:4px;"></div>
</div>
<label style="font-size:.73rem;display:flex;align-items:center;gap:5px;cursor:pointer;grid-column:1/-1">
<input type="checkbox" id="cfgDnsttNoStats"/> Disable Stats Log
</label>
<label style="font-size:.73rem;display:flex;align-items:center;gap:5px;cursor:pointer;grid-column:1/-1">
<input type="checkbox" id="cfgDnsttNoConsole"/> Disable Console Log
</label>
<label style="font-size:.73rem;display:flex;align-items:center;gap:5px;cursor:pointer;grid-column:1/-1">
<input type="checkbox" id="cfgDnsttLogConnections"/> Verbose Connection Logs
</label>
</div>
</div>
</section>
<section class="workspace-section" data-workspace-panel="config" data-workspace-section-panel="udp">
<div class="workspace-section-heading"><div><span>03 · UDP</span><h3>UDP Gateway</h3><p>Defina listener, capacidade, expiração de mapa e reinício do serviço.</p></div></div>
<!-- UDPGW -->
<div class="card" style="margin-top:12px">
<div class="card-hdr">
<div class="card-title">UDP Gateway</div>
<span class="chip green">live</span>
<label style="font-size:.73rem;display:flex;align-items:center;gap:5px;cursor:pointer;margin-left:auto;">
<input type="checkbox" id="cfgUdpgwEnabled" onchange="toggleUdpgwFields(this.checked)"/> Enabled
</label>
</div>
<div id="udpgwFields" class="form-grid" style="opacity:.4;pointer-events:none;">
<div class="field">
<label>Listen</label>
<input type="text" id="cfgUdpgwListen" placeholder="0.0.0.0:7400"/>
</div>
<div class="field">
<label>Max UDP Sessions Per Client <span class="hint">(not total server users)</span></label>
<input type="number" id="cfgUdpgwMaxConns" min="0" placeholder="10" title="Per connected client. This is not the total number of UDPGW users allowed on the server."/>
</div>
<div class="field">
<label>Max Total Clients <span class="hint">0 = default (10000)</span></label>
<input type="number" id="cfgUdpgwMaxClients" min="0" placeholder="10000" title="Total concurrent UDPGW client connections allowed on the server. New clients past this are rejected. Raise it if the server has resources for more."/>
</div>
<div class="field">
<label>Idle Timeout</label>
<input type="text" id="cfgUdpgwIdle" placeholder="2m"/>
</div>
<div class="field">
<label>Map TTL</label>
<input type="text" id="cfgUdpgwMapTTL" placeholder="90s"/>
</div>
<div class="field">
<label>Auto Restart Interval <span class="hint">0s/off disables</span></label>
<input type="text" id="cfgUdpgwAutoRestart" placeholder="24h"/>
</div>
<div class="field">
<label>Restart Grace Delay</label>
<input type="text" id="cfgUdpgwRestartGrace" placeholder="2s"/>
</div>
<div class="hint" style="grid-column:1/-1;margin-top:-4px;">Hard restart: closes the UDPGW listener and every connected UDPGW client, then starts UDPGW again.</div>
<label style="font-size:.73rem;display:flex;align-items:center;gap:5px;cursor:pointer;grid-column:1/-1">
<input type="checkbox" id="cfgUdpgwDebug"/> Debug Logging
</label>
</div>
</div>
</section>
<section class="workspace-section" data-workspace-panel="config" data-workspace-section-panel="tls">
<div class="workspace-section-heading"><div><span>04 · Segurança</span><h3>Encaminhadores TLS</h3><p>Crie listeners TLS com certificado automático, colado ou armazenado em arquivo.</p></div></div>
<!-- TLS Forwarders -->
<div class="card" style="margin-top:12px">
<div class="card-hdr">
<div class="card-title">TLS Forwarders <span class="chip" id="tlsCountChip">0</span></div>
<span class="chip green">live</span>
<button class="btn btn-ghost btn-sm" type="button" onclick="toggleAddTLSForm()">+ Add</button>
</div>
<div id="tlsForwardersList" style="margin-bottom:4px;"></div>
<div id="addTLSPanel" class="hidden" style="border:1px solid var(--border);border-radius:8px;padding:10px;margin-top:6px;">
<div class="form-grid">
<div class="field" style="grid-column:1/-1">
<label>Listen Address</label>
<input type="text" id="tlsListenAddr" placeholder="0.0.0.0:443"/>
</div>
<div class="field" style="grid-column:1/-1">
<label>Certificate</label>
<select id="tlsCertType" onchange="onTLSTypeChange(this.value)">
<option value="selfsigned">Generate Self-Signed</option>
<option value="letsencrypt">Let's Encrypt (certbot)</option>
<option value="paste">Paste PEM text</option>
<option value="custom">Custom file paths</option>
</select>
</div>
<div id="tlsSSFields" style="grid-column:1/-1">
<div class="field"><label>Domain Name <span class="hint">(CN for certificate)</span></label><input type="text" id="tlsSSLDomain" placeholder="example.com"/></div>
</div>
<div id="tlsLEFields" class="hidden" style="grid-column:1/-1;display:none;grid-template-columns:1fr 1fr;gap:8px;">
<div class="field"><label>Domain</label><input type="text" id="tlsLEDomain" placeholder="example.com"/></div>
<div class="field"><label>Email</label><input type="text" id="tlsLEEmail" placeholder="admin@example.com"/></div>
</div>
<div id="tlsCustomFields" class="hidden" style="grid-column:1/-1;display:none;grid-template-columns:1fr 1fr;gap:8px;">
<div class="field"><label>Cert File</label><input type="text" id="tlsCustomCert" placeholder="/path/to/fullchain.pem"/></div>
<div class="field"><label>Key File</label><input type="text" id="tlsCustomKey" placeholder="/path/to/privkey.pem"/></div>
</div>
<div id="tlsPasteFields" class="hidden" style="grid-column:1/-1;display:none;">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;">
<div class="field"><label>Certificate PEM</label><textarea id="tlsPasteCert" rows="5" placeholder="-----BEGIN CERTIFICATE-----&#10;…" style="font-family:monospace;font-size:.7rem;width:100%;box-sizing:border-box;resize:vertical;background:var(--input-bg);border:1px solid var(--border);border-radius:4px;color:inherit;padding:4px;"></textarea></div>
<div class="field"><label>Private Key PEM</label><textarea id="tlsPasteKey" rows="5" placeholder="-----BEGIN EC PRIVATE KEY-----&#10;…" style="font-family:monospace;font-size:.7rem;width:100%;box-sizing:border-box;resize:vertical;background:var(--input-bg);border:1px solid var(--border);border-radius:4px;color:inherit;padding:4px;"></textarea></div>
</div>
<div class="field" style="margin-top:6px;"><label>Name <span class="hint">(storage folder, e.g. my-cert)</span></label><input type="text" id="tlsPasteName" placeholder="my-cert"/></div>
</div>
</div>
<div class="form-actions" style="margin-top:8px;">
<button class="btn btn-sm" type="button" onclick="addTLSForwarder()">Add Forwarder</button>
<button class="btn btn-ghost btn-sm" type="button" onclick="toggleAddTLSForm()">Cancel</button>
</div>
<div id="tlsAddStatus" class="hint" style="margin-top:4px;"></div>
</div>
</div>
</section>
<section class="workspace-section" data-workspace-panel="config" data-workspace-section-panel="xray">
<div class="workspace-section-heading"><div><span>05 · Core</span><h3>Xray</h3><p>Ative o core, escolha o runtime e aplique os ajustes nativos seguros.</p></div></div>
<!-- Xray -->
<div class="card" style="margin-top:12px">
<div class="card-hdr">
<div class="card-title">Xray Core</div>
<span class="chip green">live</span>
</div>
<label style="font-size:.73rem;display:flex;align-items:center;gap:5px;cursor:pointer;margin-top:4px;">
<input type="checkbox" id="cfgXrayEnabled"/> Enabled
</label>
<div class="field" style="margin-top:8px;"><label>Runtime mode</label><select id="cfgXrayMode"><option value="native">Internal native emulator</option><option value="external">External xray binary</option></select></div>
<div class="hint" style="margin-top:6px;color:var(--muted);">Native runs inside DragonCoreSSH. External uses /opt/sshpanel/xray with DB-backed /opt/sshpanel/xray_config.json and Stats API on 127.0.0.1:10085.</div>
<details style="margin-top:10px;" open>
<summary style="cursor:pointer;font-size:.76rem;font-weight:700;color:var(--text);">Native Xray scale tuning</summary>
<div class="grid2" style="margin-top:10px;gap:8px;">
<div class="field"><label>Go CPU threads (GOMAXPROCS)</label><input type="number" min="0" id="cfgXrayRuntimeGomaxprocs" placeholder="0 = all CPU cores"/></div>
<div class="field"><label>Global mux backend sessions</label><input type="number" min="1" id="cfgXrayMuxGlobalSessions" placeholder="8192"/></div>
<div class="field"><label>Global transport connections <span class="hint">0=4096, -1=unlimited</span></label><input type="number" min="-1" id="cfgXrayMaxConnections" placeholder="4096"/></div>
<div class="field"><label>Concurrent XHTTP requests <span class="hint">0=8192, -1=unlimited</span></label><input type="number" min="-1" id="cfgXrayMaxXHTTPRequests" placeholder="8192"/></div>
<div class="field"><label>Active XHTTP sessions <span class="hint">0=4096, -1=unlimited</span></label><input type="number" min="-1" id="cfgXrayMaxXHTTPSessions" placeholder="4096"/></div>
<label style="font-size:.73rem;display:flex;align-items:center;gap:5px;cursor:pointer;grid-column:1/-1"><input type="checkbox" id="cfgXrayTracePackets"/> Trace every XHTTP/mux packet <span class="hint">debug only, slows QUIC</span></label>
<div class="card-actions" style="grid-column:1/-1;">
<button class="btn btn-ghost btn-sm" type="button" onclick="setXrayNativeTuningDefaults('2k')">Apply 2K defaults</button>
<button class="btn btn-ghost btn-sm" type="button" onclick="setXrayNativeTuningDefaults('safe')">Apply safe defaults</button>
</div>
<div class="hint" style="grid-column:1/-1;margin-top:-4px;">The transport ceiling rejects sockets before native protocol/TLS work starts; the XHTTP ceilings bound concurrent handlers and session state. Keep the safe defaults unless load testing proves the VPS can sustain more; -1 disables an application ceiling and is not recommended on public listeners. HTTP/2 still keeps a 256-stream guard per connection. Transport buffers remain fixed to safe defaults. Saved in the panel config and applied live on restart/reload.</div>
</div>
</details>
</div>
</section>
</div><!-- /settings-workspace -->
<!-- Save bar -->
<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>
</div><!-- /tab-server -->
</main>
</section>
</div><!-- /panel-layout -->
</div><!-- /mainApp -->
</div><!-- /shell -->
</div><!-- /app -->
<div class="panel-dialog hidden" id="panelConfirmDialog" aria-hidden="true">
<div class="panel-dialog-backdrop" data-panel-dialog-close></div>
<section class="panel-dialog-card" role="dialog" aria-modal="true" aria-labelledby="panelDialogTitle" aria-describedby="panelDialogMessage">
<div class="panel-dialog-head"><span class="panel-dialog-icon" id="panelDialogIcon" aria-hidden="true">?</span><div><span class="page-kicker" id="panelDialogEyebrow">Confirmação</span><h3 id="panelDialogTitle">Confirmar ação</h3></div></div>
<p id="panelDialogMessage"></p>
<div class="panel-dialog-detail hidden" id="panelDialogDetail"></div>
<div class="panel-dialog-actions"><button class="btn btn-ghost" id="panelDialogCancelBtn" type="button">Cancelar</button><button class="btn" id="panelDialogConfirmBtn" type="button">Confirmar</button></div>
</section>
</div>
<div class="panel-toast-stack" id="panelToastStack" aria-live="polite" aria-atomic="false"></div>
<!-- app.js was split into ordered modules for maintainability. They are plain
classic scripts sharing one global scope; `defer` preserves execution order,
so behavior is identical to the old single file. Keep this load order. -->
<script defer src="assets/js/01-core.js?v=20260715quotareset1"></script>
<script defer src="assets/js/02-shell.js?v=20260714pamfix1"></script>
<script defer src="assets/js/03-ssh-users.js?v=20260715quotareset1"></script>
<script defer src="assets/js/04-xray.js?v=20260715quotareset1"></script>
<script defer src="assets/js/05-resellers.js?v=20260714pamfix1"></script>
<script defer src="assets/js/06-servers.js?v=20260714pamfix1"></script>
<script defer src="assets/js/07-stats-logs.js?v=20260714pamfix1"></script>
<script defer src="assets/js/08-server-config.js?v=20260715hardening1"></script>
<script defer src="assets/js/09-xray-wizard.js?v=20260714quota1"></script>
<script defer src="assets/js/11-update-status.js?v=20260714pamfix1"></script>
<script defer src="assets/js/12-bot.js?v=20260714pamfix1"></script>
<script defer src="assets/js/10-boot.js?v=20260714pamfix1"></script>
</body>
</html>