Native Xray

This commit is contained in:
2026-07-04 17:26:01 -03:00
parent 6cd9626db9
commit 0eaa48ffd0
23 changed files with 7058 additions and 312 deletions
+11
View File
@@ -627,3 +627,14 @@ select:disabled {
color:#94a3b8 !important;
background:#070b12 !important;
}
/* Xray runtime mode selector */
.input-sm{
min-height:30px;
padding:4px 8px;
border-radius:10px;
border:1px solid rgba(34,211,238,.26);
background:#070b12;
color:#f3f7ff;
font-size:.78rem;
}
+260 -18
View File
@@ -42,7 +42,7 @@ const I18N_TEXT = {
"Server Load":"Server Load","Interfaces":"Interfaces","Interface":"Interface","Rx Mbps":"Rx Mbps","Tx Mbps":"Tx Mbps","Rx Total":"Rx Total","Tx Total":"Tx Total","Updated: {time}":"Updated: {time}","Error loading stats.":"Error loading stats.","Normal load":"Normal load","Moderate load":"Moderate load","High load":"High load","Cleaning interface totals…":"Cleaning interface totals…","Interface totals cleaned. Auto-clean remains every 30 days.":"Interface totals cleaned. Auto-clean remains every 30 days.","Error cleaning totals: {error}":"Error cleaning totals: {error}",
"VnStat Usage":"VnStat Usage","Today total":"Today total","This month total":"This month total","Interfaces tracked":"Interfaces tracked","daily / monthly":"daily / monthly","Daily usage":"Daily usage","Monthly usage":"Monthly usage","Day":"Day","Month":"Month","Clean usage":"Clean usage","Clean VnStat history":"Clean VnStat history","VnStat history does not auto-clean. Use the button when you want to reset it.":"VnStat history does not auto-clean. Use the button when you want to reset it.","Totals can be cleaned here and auto-clean every 30 days. VnStat history is separate.":"Totals can be cleaned here and auto-clean every 30 days. VnStat history is separate.","Loading VnStat usage…":"Loading VnStat usage…","VnStat history cleaned.":"VnStat history cleaned.","Error loading VnStat usage: {error}":"Error loading VnStat usage: {error}","Error cleaning VnStat history: {error}":"Error cleaning VnStat history: {error}",
"Panel / system":"Panel / system","Select a log source and click Refresh.":"Select a log source and click Refresh.","Clean panel log":"Clean panel log","No log lines yet.":"No log lines yet.","Panel log cleaned · {path} · max {max}":"Panel log cleaned · {path} · max {max}","Cleaning panel log…":"Cleaning panel log…",
"Network":"Network","Main Listen (SSH / HTTP)":"Main Listen (SSH / HTTP)","Extra Listen Addresses":"Extra Listen Addresses","(one per line, e.g. 0.0.0.0:8080)":"(one per line, e.g. 0.0.0.0:8080)","SSH & General":"SSH & General","Default Upload Limit (Mbps)":"Default Upload Limit (Mbps)","Default Download Limit (Mbps)":"Default Download Limit (Mbps)","Quiet Logs":"Quiet Logs","User Count Display":"User Count Display","SSH Banner":"SSH Banner","Banner Text":"Banner Text","(shown to connecting SSH clients)":"(shown to connecting SSH clients)","DNSTT Tunnel":"DNSTT Tunnel","Domain":"Domain","UDP Listen":"UDP Listen","Private Key":"Private Key","Public Key":"Public Key","Disable Stats Log":"Disable Stats Log","Disable Console Log":"Disable Console Log","UDP Gateway":"UDP Gateway","Listen":"Listen","Idle Timeout":"Idle Timeout","Map TTL":"Map TTL","Debug Logging":"Debug Logging","TLS Forwarders":"TLS Forwarders","Listen Address":"Listen Address","Certificate":"Certificate","Generate Self-Signed":"Generate Self-Signed","Let's Encrypt (certbot)":"Let's Encrypt (certbot)","Paste PEM text":"Paste PEM text","Custom file paths":"Custom file paths","Cert File":"Cert File","Key File":"Key File","Certificate PEM":"Certificate PEM","Private Key PEM":"Private Key PEM","Add Forwarder":"Add Forwarder","Save Config":"Save Config","All service changes apply live.":"All service changes apply live.","Saved and applied live.":"Saved and applied live.","Saved live with warnings: {warnings}":"Saved live with warnings: {warnings}","Processing…":"Processing…","Listen address required.":"Listen address required.","Domain required.":"Domain required.","Domain and email required.":"Domain and email required.","Cert and key paths required.":"Cert and key paths required.","Added. Save config to apply.":"Added. Save config to apply.","Generating…":"Generating…","Generated ✓ paths set.":"Generated ✓ paths set.","Generating key…":"Generating key…","Key generated. Save config to apply.":"Key generated. Save config to apply.","Loading public key…":"Loading public key…","Self-signed cert generated.":"Self-signed cert generated.","Let's Encrypt cert issued.":"Let's Encrypt cert issued.","PEM saved.":"PEM saved.","Saved ✓ paths set.":"Saved ✓ paths set.","Name, cert PEM, and key PEM required.":"Name, cert PEM, and key PEM required.","Name, cert, and key required.":"Name, cert, and key required.","Name, cert PEM, and key PEM required.":"Name, cert PEM, and key PEM required.","Save Changes":"Save Changes"
"Network":"Network","Main Listen (SSH / HTTP)":"Main Listen (SSH / HTTP)","Extra Listen Addresses":"Extra Listen Addresses","(one per line, e.g. 0.0.0.0:8080)":"(one per line, e.g. 0.0.0.0:8080)","SSH & General":"SSH & General","Default Upload Limit (Mbps)":"Default Upload Limit (Mbps)","Default Download Limit (Mbps)":"Default Download Limit (Mbps)","Quiet Logs":"Quiet Logs","User Count Display":"User Count Display","SSH Banner":"SSH Banner","Banner Text":"Banner Text","(shown to connecting SSH clients)":"(shown to connecting SSH clients)","DNSTT Tunnel":"DNSTT Tunnel","Domain":"Domain","UDP Listen":"UDP Listen","Auto Restart Interval":"Auto Restart Interval","Restart Grace Delay":"Restart Grace Delay","0s/off disables":"0s/off disables","Private Key":"Private Key","Public Key":"Public Key","Disable Stats Log":"Disable Stats Log","Disable Console Log":"Disable Console Log","UDP Gateway":"UDP Gateway","Listen":"Listen","Idle Timeout":"Idle Timeout","Map TTL":"Map TTL","Debug Logging":"Debug Logging","TLS Forwarders":"TLS Forwarders","Listen Address":"Listen Address","Certificate":"Certificate","Generate Self-Signed":"Generate Self-Signed","Let's Encrypt (certbot)":"Let's Encrypt (certbot)","Paste PEM text":"Paste PEM text","Custom file paths":"Custom file paths","Cert File":"Cert File","Key File":"Key File","Certificate PEM":"Certificate PEM","Private Key PEM":"Private Key PEM","Add Forwarder":"Add Forwarder","Save Config":"Save Config","All service changes apply live.":"All service changes apply live.","Saved and applied live.":"Saved and applied live.","Saved live with warnings: {warnings}":"Saved live with warnings: {warnings}","Processing…":"Processing…","Listen address required.":"Listen address required.","Domain required.":"Domain required.","Domain and email required.":"Domain and email required.","Cert and key paths required.":"Cert and key paths required.","Added. Save config to apply.":"Added. Save config to apply.","Generating…":"Generating…","Generated ✓ paths set.":"Generated ✓ paths set.","Generating key…":"Generating key…","Key generated. Save config to apply.":"Key generated. Save config to apply.","Loading public key…":"Loading public key…","Self-signed cert generated.":"Self-signed cert generated.","Let's Encrypt cert issued.":"Let's Encrypt cert issued.","PEM saved.":"PEM saved.","Saved ✓ paths set.":"Saved ✓ paths set.","Name, cert PEM, and key PEM required.":"Name, cert PEM, and key PEM required.","Name, cert, and key required.":"Name, cert, and key required.","Name, cert PEM, and key PEM required.":"Name, cert PEM, and key PEM required.","Save Changes":"Save Changes"
},
"pt-BR": {
"Dashboard":"Painel","Overview":"Visão geral","Accounts":"Contas","Administration":"Administração","Server":"Servidor","System":"Sistema","Settings":"Configurações","Traffic":"Tráfego","Monitoring":"Monitoramento",
@@ -59,7 +59,7 @@ const I18N_TEXT = {
"Server Load":"Carga do servidor","Interfaces":"Interfaces","Interface":"Interface","Rx Mbps":"Rx Mbps","Tx Mbps":"Tx Mbps","Rx Total":"Rx Total","Tx Total":"Tx Total","Updated: {time}":"Atualizado: {time}","Error loading stats.":"Erro ao carregar stats.","Normal load":"Carga normal","Moderate load":"Carga moderada","High load":"Carga alta","Cleaning interface totals…":"Limpando totais das interfaces…","Interface totals cleaned. Auto-clean remains every 30 days.":"Totais das interfaces limpos. A limpeza automática continua a cada 30 dias.","Error cleaning totals: {error}":"Erro ao limpar totais: {error}",
"VnStat Usage":"Uso do VnStat","Today total":"Total hoje","This month total":"Total este mês","Interfaces tracked":"Interfaces monitoradas","daily / monthly":"diário / mensal","Daily usage":"Uso diário","Monthly usage":"Uso mensal","Day":"Dia","Month":"Mês","Clean usage":"Limpar uso","Clean VnStat history":"Limpar histórico VnStat","VnStat history does not auto-clean. Use the button when you want to reset it.":"O histórico VnStat não é limpo automaticamente. Use o botão quando quiser resetar.","Totals can be cleaned here and auto-clean every 30 days. VnStat history is separate.":"Os totais podem ser limpos aqui e têm limpeza automática a cada 30 dias. O histórico VnStat é separado.","Loading VnStat usage…":"Carregando uso do VnStat…","VnStat history cleaned.":"Histórico VnStat limpo.","Error loading VnStat usage: {error}":"Erro ao carregar uso do VnStat: {error}","Error cleaning VnStat history: {error}":"Erro ao limpar histórico VnStat: {error}",
"Panel / system":"Painel / sistema","Select a log source and click Refresh.":"Selecione uma fonte de log e clique em Atualizar.","Clean panel log":"Limpar log do painel","No log lines yet.":"Ainda não há linhas de log.","Panel log cleaned · {path} · max {max}":"Log do painel limpo · {path} · máx {max}","Cleaning panel log…":"Limpando log do painel…",
"Network":"Rede","Main Listen (SSH / HTTP)":"Listen principal (SSH / HTTP)","Extra Listen Addresses":"Endereços extras de listen","(one per line, e.g. 0.0.0.0:8080)":"(um por linha, ex. 0.0.0.0:8080)","SSH & General":"SSH e geral","Default Upload Limit (Mbps)":"Limite padrão de upload (Mbps)","Default Download Limit (Mbps)":"Limite padrão de download (Mbps)","Quiet Logs":"Logs silenciosos","User Count Display":"Exibir contagem de usuários","SSH Banner":"Banner SSH","Banner Text":"Texto do banner","(shown to connecting SSH clients)":"(mostrado aos clientes SSH ao conectar)","DNSTT Tunnel":"Túnel DNSTT","Domain":"Domínio","UDP Listen":"Listen UDP","Private Key":"Chave privada","Public Key":"Chave pública","Disable Stats Log":"Desativar log de stats","Disable Console Log":"Desativar log do console","UDP Gateway":"Gateway UDP","Listen":"Listen","Idle Timeout":"Timeout ocioso","Map TTL":"TTL do mapa","Debug Logging":"Log de debug","TLS Forwarders":"Encaminhadores TLS","Listen Address":"Endereço de listen","Certificate":"Certificado","Generate Self-Signed":"Gerar autoassinado","Let's Encrypt (certbot)":"Let's Encrypt (certbot)","Paste PEM text":"Colar texto PEM","Custom file paths":"Caminhos personalizados","Cert File":"Arquivo cert","Key File":"Arquivo key","Certificate PEM":"Certificado PEM","Private Key PEM":"Chave privada PEM","Add Forwarder":"Adicionar forwarder","Save Config":"Salvar config","All service changes apply live.":"Todas as mudanças de serviço aplicam ao vivo.","Saved and applied live.":"Salvo e aplicado ao vivo.","Saved live with warnings: {warnings}":"Salvo ao vivo com avisos: {warnings}","Processing…":"Processando…","Listen address required.":"Endereço de listen obrigatório.","Domain required.":"Domínio obrigatório.","Domain and email required.":"Domínio e email obrigatórios.","Cert and key paths required.":"Caminhos do certificado e da chave obrigatórios.","Added. Save config to apply.":"Adicionado. Salve a config para aplicar.","Generating…":"Gerando…","Generated ✓ paths set.":"Gerado ✓ caminhos definidos.","Generating key…":"Gerando chave…","Key generated. Save config to apply.":"Chave gerada. Salve a config para aplicar.","Loading public key…":"Carregando chave pública…","Self-signed cert generated.":"Certificado autoassinado gerado.","Let's Encrypt cert issued.":"Certificado Let's Encrypt emitido.","PEM saved.":"PEM salvo.","Saved ✓ paths set.":"Salvo ✓ caminhos definidos.","Name, cert PEM, and key PEM required.":"Nome, cert PEM e chave PEM obrigatórios.","Name, cert, and key required.":"Nome, cert e chave obrigatórios.","Save Changes":"Salvar alterações"
"Network":"Rede","Main Listen (SSH / HTTP)":"Listen principal (SSH / HTTP)","Extra Listen Addresses":"Endereços extras de listen","Proxy Auto Restart":"Reinício automático do proxy","Proxy Auto Restart Interval":"Intervalo de reinício automático do proxy","Proxy Restart Grace Delay":"Atraso para reiniciar proxy","(one per line, e.g. 0.0.0.0:8080)":"(um por linha, ex. 0.0.0.0:8080)","SSH & General":"SSH e geral","Default Upload Limit (Mbps)":"Limite padrão de upload (Mbps)","Default Download Limit (Mbps)":"Limite padrão de download (Mbps)","Quiet Logs":"Logs silenciosos","User Count Display":"Exibir contagem de usuários","SSH Banner":"Banner SSH","Banner Text":"Texto do banner","(shown to connecting SSH clients)":"(mostrado aos clientes SSH ao conectar)","DNSTT Tunnel":"Túnel DNSTT","Domain":"Domínio","UDP Listen":"Listen UDP","Auto Restart Interval":"Intervalo de reinício automático","Restart Grace Delay":"Atraso para reiniciar","0s/off disables":"0s/off desativa","Private Key":"Chave privada","Public Key":"Chave pública","Disable Stats Log":"Desativar log de stats","Disable Console Log":"Desativar log do console","UDP Gateway":"Gateway UDP","Listen":"Listen","Idle Timeout":"Timeout ocioso","Map TTL":"TTL do mapa","Debug Logging":"Log de debug","TLS Forwarders":"Encaminhadores TLS","Listen Address":"Endereço de listen","Certificate":"Certificado","Generate Self-Signed":"Gerar autoassinado","Let's Encrypt (certbot)":"Let's Encrypt (certbot)","Paste PEM text":"Colar texto PEM","Custom file paths":"Caminhos personalizados","Cert File":"Arquivo cert","Key File":"Arquivo key","Certificate PEM":"Certificado PEM","Private Key PEM":"Chave privada PEM","Add Forwarder":"Adicionar forwarder","Save Config":"Salvar config","All service changes apply live.":"Todas as mudanças de serviço aplicam ao vivo.","Saved and applied live.":"Salvo e aplicado ao vivo.","Saved live with warnings: {warnings}":"Salvo ao vivo com avisos: {warnings}","Processing…":"Processando…","Listen address required.":"Endereço de listen obrigatório.","Domain required.":"Domínio obrigatório.","Domain and email required.":"Domínio e email obrigatórios.","Cert and key paths required.":"Caminhos do certificado e da chave obrigatórios.","Added. Save config to apply.":"Adicionado. Salve a config para aplicar.","Generating…":"Gerando…","Generated ✓ paths set.":"Gerado ✓ caminhos definidos.","Generating key…":"Gerando chave…","Key generated. Save config to apply.":"Chave gerada. Salve a config para aplicar.","Loading public key…":"Carregando chave pública…","Self-signed cert generated.":"Certificado autoassinado gerado.","Let's Encrypt cert issued.":"Certificado Let's Encrypt emitido.","PEM saved.":"PEM salvo.","Saved ✓ paths set.":"Salvo ✓ caminhos definidos.","Name, cert PEM, and key PEM required.":"Nome, cert PEM e chave PEM obrigatórios.","Name, cert, and key required.":"Nome, cert e chave obrigatórios.","Save Changes":"Salvar alterações"
}
};
const I18N_ALIASES = {
@@ -69,14 +69,14 @@ const I18N_ALIASES = {
"Minha conta":"My Account","Usuários":"Users","Usuário":"User","Autenticação":"Auth","Conexões":"Conn","Máximo":"Max","Dono":"Owner","Ações":"Actions","Criar / atualizar usuário":"Create / update user","Mostrar formulário":"Show form","Ocultar formulário":"Hide form","Salvar usuário":"Save user","Cancelar":"Cancel","Gerar":"Gen","Copiar":"Copy","Editar":"Edit","Excluir":"Del","Recarregar":"Reload","Atualizar":"Refresh",
"Rodando":"Running","Parado":"Stopped","rodando":"running","parado":"stopped","desativado":"disabled","API de contadores":"Counters API","Reparar contadores":"Repair counters","Iniciar":"Start","Parar":"Stop","Reiniciar":"Restart","Inbounds e clientes":"Inbounds & Clients","Configuração Xray":"Xray Config","Editor de configuração":"Config editor","Carregar JSON":"Load JSON","Salvar e reiniciar":"Save & Restart","Logs do sistema":"System Logs","últimas 200 linhas":"last 200 lines","Clientes Xray":"Xray clients","Núcleo Xray":"Xray Core","Ativado":"Enabled","Tempo ativo":"Uptime","Precisa de reparo":"Needs repair",
"Nome":"Name","Nome de exibição":"Display Name","Data de vencimento":"Expiry Date","Máx. conexões":"Max Connections","Ilimitado":"Unlimited","Ativo":"Active","Suspenso":"Suspended","Expirado":"Expired","Sem vencimento":"No expiration","ocioso":"idle","Nenhum cliente.":"No clients.","Adicionar cliente":"Add Client","Novo usuário.":"New user.","Carregado.":"Loaded.","Salvo.":"Saved.","Salvando…":"Saving…","Erro ao carregar usuários.":"Error loading users.","Erro ao excluir.":"Error deleting.","Credenciais inválidas.":"Invalid credentials.","Conta suspensa ou expirada.":"Account suspended or expired.","Falha no login.":"Login failed.","Erro de rede.":"Network error.","Sessão expirada — faça login novamente.":"Session expired — please sign in again.",
"Rede":"Network","Listen principal (SSH / HTTP)":"Main Listen (SSH / HTTP)","Endereços extras de listen":"Extra Listen Addresses","SSH e geral":"SSH & General","Limite padrão de upload (Mbps)":"Default Upload Limit (Mbps)","Limite padrão de download (Mbps)":"Default Download Limit (Mbps)","Logs silenciosos":"Quiet Logs","Exibir contagem de usuários":"User Count Display","Banner SSH":"SSH Banner","Texto do banner":"Banner Text","Túnel DNSTT":"DNSTT Tunnel","Domínio":"Domain","Chave privada":"Private Key","Chave pública":"Public Key","Gateway UDP":"UDP Gateway","Endereço de listen":"Listen Address","Certificado":"Certificate","Gerar autoassinado":"Generate Self-Signed","Colar texto PEM":"Paste PEM text","Caminhos personalizados":"Custom file paths","Arquivo cert":"Cert File","Arquivo key":"Key File","Adicionar forwarder":"Add Forwarder","Salvar config":"Save Config","Todas as mudanças de serviço aplicam ao vivo.":"All service changes apply live."
"Rede":"Network","Listen principal (SSH / HTTP)":"Main Listen (SSH / HTTP)","Endereços extras de listen":"Extra Listen Addresses","Reinício automático do proxy":"Proxy Auto Restart","Intervalo de reinício automático do proxy":"Proxy Auto Restart Interval","Atraso para reiniciar proxy":"Proxy Restart Grace Delay","SSH e geral":"SSH & General","Limite padrão de upload (Mbps)":"Default Upload Limit (Mbps)","Limite padrão de download (Mbps)":"Default Download Limit (Mbps)","Logs silenciosos":"Quiet Logs","Exibir contagem de usuários":"User Count Display","Banner SSH":"SSH Banner","Texto do banner":"Banner Text","Túnel DNSTT":"DNSTT Tunnel","Domínio":"Domain","Chave privada":"Private Key","Intervalo de reinício automático":"Auto Restart Interval","Atraso para reiniciar":"Restart Grace Delay","0s/off desativa":"0s/off disables","Chave pública":"Public Key","Gateway UDP":"UDP Gateway","Endereço de listen":"Listen Address","Certificado":"Certificate","Gerar autoassinado":"Generate Self-Signed","Colar texto PEM":"Paste PEM text","Caminhos personalizados":"Custom file paths","Arquivo cert":"Cert File","Arquivo key":"Key File","Adicionar forwarder":"Add Forwarder","Salvar config":"Save Config","Todas as mudanças de serviço aplicam ao vivo.":"All service changes apply live."
};
Object.assign(I18N_TEXT["en-US"], {
"Servers":"Servers","Reseller area":"Reseller area","shared quota":"shared quota","available":"available","used":"used","breakdown":"breakdown",
"Create Xray clients with the same experience as the main panel. Each Xray client uses the same limit shared with SSH accounts.":"Create Xray clients with the same experience as the main panel. Each Xray client uses the same limit shared with SSH accounts.",
"Loading inbounds…":"Loading inbounds…","SSH -- · Xray --":"SSH -- · Xray --","active ·":"active ·","expired":"expired",
"Binary: /opt/sshpanel/xray · Config: /opt/sshpanel/xray_config.json · Online counters use Xray Stats API on 127.0.0.1:10085":"Binary: /opt/sshpanel/xray · Config: /opt/sshpanel/xray_config.json · Online counters use Xray Stats API on 127.0.0.1:10085",
"Binary: /opt/sshpanel/xray · Config: DB-backed /opt/sshpanel/xray_config.json · Online counters use Xray Stats API on 127.0.0.1:10085":"Binary: /opt/sshpanel/xray · Config: DB-backed /opt/sshpanel/xray_config.json · Online counters use Xray Stats API on 127.0.0.1:10085",
"Public Key — share with dnstt clients":"Public Key — share with DNSTT clients","auto-saved to /opt/sshpanel/dnstt.key":"auto-saved to /opt/sshpanel/dnstt.key",
"Max UDP Sessions Per Client":"Max UDP Sessions Per Client","(not total server users)":"(not total server users)","Service Name":"Service Name","Mode":"Mode","Protocol":"Protocol","Port":"Port","Tag":"Tag","Listen IP":"Listen IP","Method":"Method","Host":"Host","Path":"Path","Dest":"Dest","Short ID":"Short ID","Server Name":"Server Name","Cert File Path":"Cert File Path","Key File Path":"Key File Path","Certificate source:":"Certificate source:","Self-Signed":"Self-Signed","Paste PEM":"Paste PEM","File Path":"File Path","Save PEM":"Save PEM","Generate":"Generate","Public Key":"Public Key","Debug Logging":"Debug Logging","Name":"Name","Private Key PEM":"Private Key PEM","Certificate PEM":"Certificate PEM","Domain Name":"Domain Name"
});
@@ -84,7 +84,7 @@ Object.assign(I18N_TEXT["pt-BR"], {
"Servers":"Servidores","Reseller area":"Área do revendedor","shared quota":"cota única","available":"disponíveis","used":"usadas","breakdown":"divisão",
"Create Xray clients with the same experience as the main panel. Each Xray client uses the same limit shared with SSH accounts.":"Crie clientes Xray com a mesma experiência do painel principal. Cada cliente Xray desconta do mesmo limite usado pelas contas SSH.",
"Loading inbounds…":"Carregando inbounds…","SSH -- · Xray --":"SSH -- · Xray --","active ·":"ativas ·","expired":"expiradas",
"Binary: /opt/sshpanel/xray · Config: /opt/sshpanel/xray_config.json · Online counters use Xray Stats API on 127.0.0.1:10085":"Binário: /opt/sshpanel/xray · Config: /opt/sshpanel/xray_config.json · Contadores online usam a Xray Stats API em 127.0.0.1:10085",
"Binary: /opt/sshpanel/xray · Config: DB-backed /opt/sshpanel/xray_config.json · Online counters use Xray Stats API on 127.0.0.1:10085":"Binário: /opt/sshpanel/xray · Config: DB-backed /opt/sshpanel/xray_config.json · Contadores online usam a Xray Stats API em 127.0.0.1:10085",
"Public Key — share with dnstt clients":"Chave pública — compartilhe com clientes DNSTT","auto-saved to /opt/sshpanel/dnstt.key":"salva automaticamente em /opt/sshpanel/dnstt.key",
"Max UDP Sessions Per Client":"Máx. sessões UDP por cliente","(not total server users)":"(não é o total de usuários do servidor)","Service Name":"Nome do serviço","Mode":"Modo","Protocol":"Protocolo","Port":"Porta","Tag":"Tag","Listen IP":"IP de listen","Method":"Método","Host":"Host","Path":"Caminho","Dest":"Destino","Short ID":"ID curto","Server Name":"Nome do servidor","Cert File Path":"Caminho do arquivo cert","Key File Path":"Caminho do arquivo key","Certificate source:":"Fonte do certificado:","Self-Signed":"Autoassinado","Paste PEM":"Colar PEM","File Path":"Caminho do arquivo","Save PEM":"Salvar PEM","Generate":"Gerar","Public Key":"Chave pública","Debug Logging":"Log de debug","Name":"Nome","Private Key PEM":"Chave privada PEM","Certificate PEM":"Certificado PEM","Domain Name":"Nome do domínio"
});
@@ -92,9 +92,9 @@ Object.assign(I18N_ALIASES, {
"Servidores":"Servers","Área do revendedor":"Reseller area","cota única":"shared quota","disponíveis":"available","usadas":"used","divisão":"breakdown",
"Crie clientes Xray com a mesma experiência do painel principal. Cada cliente Xray desconta do mesmo limite usado pelas contas SSH.":"Create Xray clients with the same experience as the main panel. Each Xray client uses the same limit shared with SSH accounts.",
"Carregando inbounds…":"Loading inbounds…","Loading inbounds…":"Loading inbounds…","ativas ·":"active ·","expiradas":"expired",
"Binário: /opt/sshpanel/xray · Config: /opt/sshpanel/xray_config.json · Contadores online usam a Xray Stats API em 127.0.0.1:10085":"Binary: /opt/sshpanel/xray · Config: /opt/sshpanel/xray_config.json · Online counters use Xray Stats API on 127.0.0.1:10085",
"Binário: /opt/sshpanel/xray · Config: DB-backed /opt/sshpanel/xray_config.json · Contadores online usam a Xray Stats API em 127.0.0.1:10085":"Binary: /opt/sshpanel/xray · Config: DB-backed /opt/sshpanel/xray_config.json · Online counters use Xray Stats API on 127.0.0.1:10085",
"Public Key — share with dnstt clients":"Public Key — share with dnstt clients","Chave pública — compartilhe com clientes DNSTT":"Public Key — share with dnstt clients",
"Máx. sessões UDP por cliente":"Max UDP Sessions Per Client","(não é o total de usuários do servidor)":"(not total server users)","Nome do serviço":"Service Name","Modo":"Mode","Protocolo":"Protocol","Porta":"Port","IP de listen":"Listen IP","Método":"Method","Caminho":"Path","Destino":"Dest","ID curto":"Short ID","Nome do servidor":"Server Name","Caminho do arquivo cert":"Cert File Path","Caminho do arquivo key":"Key File Path","Fonte do certificado:":"Certificate source:","Autoassinado":"Self-Signed","Colar PEM":"Paste PEM","Caminho do arquivo":"File Path","Salvar PEM":"Save PEM","Chave pública":"Public Key","Nome do domínio":"Domain Name"
"Máx. sessões UDP por cliente":"Max UDP Sessions Per Client","(não é o total de usuários do servidor)":"(not total server users)","Nome do serviço":"Service Name","Modo":"Mode","Protocolo":"Protocol","Porta":"Port","IP de listen":"Listen IP","Método":"Method","Caminho":"Path","Destino":"Dest","ID curto":"Short ID","Nome do servidor":"Server Name","Caminho do arquivo cert":"Cert File Path","Caminho do arquivo key":"Key File Path","Fonte do certificado:":"Certificate source:","Autoassinado":"Self-Signed","Colar PEM":"Paste PEM","Caminho do arquivo":"File Path","Salvar PEM":"Save PEM","Intervalo de reinício automático":"Auto Restart Interval","Atraso para reiniciar":"Restart Grace Delay","0s/off desativa":"0s/off disables","Chave pública":"Public Key","Nome do domínio":"Domain Name"
});
const I18N_REVERSE = Object.fromEntries(SUPPORTED_LANGS.map(lang => [lang, Object.fromEntries(Object.entries(I18N_TEXT[lang] || {}).map(([k, v]) => [v, k]))]));
let currentLang = detectInitialLanguage();
@@ -269,6 +269,8 @@ const xPID = document.getElementById("xPID");
const xUptime = document.getElementById("xUptime");
const xStatus = document.getElementById("xStatus");
const xOnlineUsers = document.getElementById("xOnlineUsers");
const xCoreMode = document.getElementById("xCoreMode");
const xSaveModeBtn = document.getElementById("xSaveModeBtn");
const xCfgEditor = document.getElementById("xCfgEditor");
const xCfgStatus = document.getElementById("xCfgStatus");
const xLogsBox = document.getElementById("xLogsBox");
@@ -326,6 +328,14 @@ const ifaceBody = document.getElementById("ifaceBody");
const ifaceSummary = document.getElementById("ifaceSummary");
const statsUpdated = document.getElementById("statsUpdated");
const resetIfaceStatsBtn = document.getElementById("resetIfaceStatsBtn");
const dnsttDashboardCard = document.getElementById("dnsttDashboardCard");
const dnsttHealthUpdated = document.getElementById("dnsttHealthUpdated");
const dnsttActiveSessions = document.getElementById("dnsttActiveSessions");
const dnsttActiveStreams = document.getElementById("dnsttActiveStreams");
const dnsttDNSRx = document.getElementById("dnsttDNSRx");
const dnsttQueueLen = document.getElementById("dnsttQueueLen");
const dnsttHealthBody = document.getElementById("dnsttHealthBody");
const dnsttHealthSummary = document.getElementById("dnsttHealthSummary");
// VnStat
const vnstatDailyBody = document.getElementById("vnstatDailyBody");
@@ -362,6 +372,25 @@ function selectedXrayServerLabel() {
if (srv) return srv.name || srv.base_url || id;
return id === "local" ? "Master node" : id;
}
function xrayModeFromConfig(x) {
const mode = String(x?.mode || "").toLowerCase();
return mode === "external" ? "external" : "native";
}
function applyXrayModeToConfig(cfg, mode) {
mode = mode === "external" ? "external" : "native";
cfg.xray = cfg.xray && typeof cfg.xray === "object" ? cfg.xray : {};
cfg.xray.mode = mode;
cfg.xray.native = mode === "native";
cfg.xray.bin_path = cfg.xray.bin_path || "/opt/sshpanel/xray";
cfg.xray.config_file = cfg.xray.config_file || "/opt/sshpanel/xray_config.json";
cfg.xray.native_config_file = cfg.xray.native_config_file || "/opt/sshpanel/xray_native_config.json";
cfg.xray.api_server = cfg.xray.api_server || "127.0.0.1:10085";
cfg.xray.online_window_seconds = cfg.xray.online_window_seconds || 90;
cfg.xray.stats_poll_seconds = cfg.xray.stats_poll_seconds || 15;
return cfg;
}
function reloadXrayConfigForSelectedServer() {
const wizPane = document.getElementById("xrayWizardPane");
const jsonPane = document.getElementById("xrayCfgPaneJson");
@@ -379,6 +408,16 @@ function fmtBytes(n) {
const m=k/1024; if(m<1024) return m.toFixed(1)+" MiB";
return (m/1024).toFixed(1)+" GiB";
}
function fmtInt(n) {
const v = Number(n);
return Number.isFinite(v) ? v.toLocaleString() : "--";
}
function fmtDnsttTimestamp(ts) {
if (!ts) return "Waiting for DNSTT stats…";
const d = new Date(ts);
if (!Number.isFinite(d.getTime()) || d.getFullYear() < 2020) return "Waiting for DNSTT stats…";
return "Updated: " + d.toLocaleTimeString();
}
function localDateKey(d = new Date()) {
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, "0");
@@ -461,6 +500,13 @@ function clientOnlineHTML(c) {
return `${c.online ? `<span class="badge-on">${t("online")}</span>` : `<span class="badge-off">${t("offline")}</span>`}<div class="hint">${escapeHTML(formatLastActive(c.last_active))}</div>`;
}
function clientTrafficHTML(c) {
const up = Number(c.uplink_bytes || 0);
const down = Number(c.downlink_bytes || 0);
const total = Number(c.total_bytes || (up + down) || 0);
return `${escapeHTML(formatBytes(total))}<div class="hint">↑ ${escapeHTML(formatBytes(up))} · ↓ ${escapeHTML(formatBytes(down))}</div>`;
}
function updateCell(row, name, html) {
const cell = row?.querySelector?.(`[data-cell="${name}"]`);
if (cell && cell.innerHTML !== html) cell.innerHTML = html;
@@ -496,7 +542,7 @@ function patchRenderedInbounds(inbounds) {
updateCell(row, "expiry", escapeHTML(clientExpiryLabel(c)));
updateCell(row, "status", clientStatusHTML(c));
updateCell(row, "online", clientOnlineHTML(c));
updateCell(row, "traffic", escapeHTML(formatBytes(c.total_bytes)));
updateCell(row, "traffic", clientTrafficHTML(c));
updateCell(row, "max", escapeHTML(c.max_conns || "∞"));
}
}
@@ -550,7 +596,6 @@ document.querySelectorAll(".tab-btn").forEach(btn => btn.addEventListener("click
menuToggle?.addEventListener("click", () => document.body.classList.add("sidebar-open"));
drawerBackdrop?.addEventListener("click", () => document.body.classList.remove("sidebar-open"));
languageSelect?.addEventListener("change", () => { applyLanguage(languageSelect.value); renderDashboardCounters(); });
document.querySelectorAll(".quick-action[data-jump]").forEach(btn => btn.addEventListener("click", () => selectTab(btn.dataset.jump)));
applyLanguage(currentLang, { persist: false });
startI18nObserver();
@@ -956,6 +1001,7 @@ document.getElementById("xStartBtn").addEventListener("click", () => xrayCtrl("s
document.getElementById("xStopBtn").addEventListener("click", () => xrayCtrl("stop"));
document.getElementById("xRestartBtn").addEventListener("click", () => xrayCtrl("restart"));
document.getElementById("xRepairStatsBtn")?.addEventListener("click", repairXrayStats);
xSaveModeBtn?.addEventListener("click", saveXrayCoreMode);
document.getElementById("xRefreshBtn").addEventListener("click", () => { loadXrayStatus(); loadInbounds({ force: true }); });
document.getElementById("xLoadInboundsBtn").addEventListener("click", () => loadInbounds({ force: true }));
document.getElementById("xLoadCfgBtn").addEventListener("click", loadXrayCfg);
@@ -972,8 +1018,9 @@ async function loadXrayStatus() {
xrayChip.className = "chip " + (run ? "green" : "red");
xRunning.textContent = run ? t("Running") : t("Stopped");
xRunning.style.color = run ? "var(--success)" : "var(--danger)";
xPID.textContent = s.pid || "--";
xPID.textContent = s.pid || (s.native ? "internal" : "--");
xUptime.textContent = s.uptime || "--";
if (xCoreMode) xCoreMode.value = String(s.mode || (s.native ? "native" : "external")).toLowerCase() === "external" ? "external" : "native";
const statsCfgEl = document.getElementById("xStatsConfig");
const repairBtn = document.getElementById("xRepairStatsBtn");
if (statsCfgEl) {
@@ -997,6 +1044,29 @@ async function loadXrayStatus() {
} catch (e) { if (e.message==="auth") doAuthError(); }
}
async function saveXrayCoreMode() {
const mode = xCoreMode?.value === "external" ? "external" : "native";
const target = selectedXrayServerLabel();
const selectedID = selectedXrayServer() || "local";
if (xStatus) xStatus.textContent = `Saving Xray mode on ${target}...`;
try {
const getRes = await api(withServerParam("/api/servers/config", selectedID));
if (!getRes.ok) throw new Error(await getRes.text());
const cfg = await getRes.json();
applyXrayModeToConfig(cfg, mode);
const postRes = await api(withServerParam("/api/servers/config", selectedID), { method:"POST", body: JSON.stringify(cfg) });
if (!postRes.ok) throw new Error(await postRes.text());
if (xStatus) xStatus.textContent = mode === "native"
? `Saved on ${target}: using internal native emulator.`
: `Saved on ${target}: using external Xray binary.`;
setTimeout(loadXrayStatus, 700);
setTimeout(() => loadInbounds({ force: true }), 1200);
} catch (e) {
if (e.message === "auth") doAuthError();
else if (xStatus) xStatus.textContent = t("Error: {error}", {error: e.message});
}
}
async function repairXrayStats() {
const btn = document.getElementById("xRepairStatsBtn");
if (btn) btn.disabled = true;
@@ -1155,7 +1225,7 @@ function renderInbounds(inbounds, options = {}) {
<td data-cell="expiry" style="font-size:.7rem;">${escapeHTML(clientExpiryLabel(c))}</td>
<td data-cell="status">${clientStatusHTML(c)}</td>
<td data-cell="online">${clientOnlineHTML(c)}</td>
<td data-cell="traffic" style="font-size:.7rem;">${escapeHTML(formatBytes(c.total_bytes))}</td>
<td data-cell="traffic" style="font-size:.7rem;">${clientTrafficHTML(c)}</td>
<td data-cell="max" style="font-size:.7rem;">${escapeHTML(c.max_conns || "∞")}</td>`;
const actTd = document.createElement("td");
actTd.style.whiteSpace = "nowrap";
@@ -1215,7 +1285,7 @@ async function addClient(tag) {
body: JSON.stringify({ inbound_tag: tag, uuid, email, name, expires_at: expiresAt, max_connections: maxConns, server_id: selectedXrayServer() }),
});
if (!res.ok) throw new Error(await res.text());
xStatus.textContent = t("Client {id}… added. Restarting Xray…", {id: uuid.slice(0,8)});
xStatus.textContent = t("Client {id}… added. Native mode hot-reloads without restart.", {id: uuid.slice(0,8)});
setTimeout(() => { loadInbounds({ force: true }); if (currentRole === "reseller") loadMe(); }, 1500);
} catch (e) {
if (e.message==="auth") doAuthError();
@@ -1228,7 +1298,7 @@ async function removeClient(tag, uuid) {
try {
const res = await api(withServerParam(`/api/xray/clients/remove?inbound_tag=${encodeURIComponent(tag)}&uuid=${encodeURIComponent(uuid)}`, selectedXrayServer()), { method:"DELETE" });
if (!res.ok && res.status !== 204) throw new Error(await res.text());
xStatus.textContent = t("Client removed. Restarting Xray…");
xStatus.textContent = t("Client removed. Native mode hot-reloads without restart.");
setTimeout(() => { loadInbounds({ force: true }); if (currentRole === "reseller") loadMe(); }, 1500);
} catch (e) {
if (e.message==="auth") doAuthError();
@@ -1764,6 +1834,8 @@ async function loadManagedServerConfig(id) {
document.getElementById("managedCfgListen").value = c.listen || "";
document.getElementById("managedCfgExtraListen").value = (c.extra_listen || []).join("\n");
document.getElementById("managedCfgProxyAutoRestart").value = c.proxy_auto_restart_interval || "";
document.getElementById("managedCfgProxyRestartGrace").value = c.proxy_auto_restart_grace || "";
document.getElementById("managedCfgLimitUp").value = c.default_limit_mbps_up || 0;
document.getElementById("managedCfgLimitDown").value = c.default_limit_mbps_down || 0;
@@ -1775,11 +1847,25 @@ async function loadManagedServerConfig(id) {
document.getElementById("managedCfgDnsttEnabled").checked = hasDnstt;
toggleManagedDnsttFields(hasDnstt);
const d = c.dnstt || {};
document.getElementById("managedCfgDnsttDomain").value = d.domain || "";
document.getElementById("managedCfgDnsttDomains").value = dnsttDomainsText(d);
document.getElementById("managedCfgDnsttUDP").value = d.udp_listen || "";
document.getElementById("managedCfgDnsttFakeEnabled").checked = !!d.fake_dns_enabled;
document.getElementById("managedCfgDnsttFakeListen").value = d.fake_dns_listen || "";
document.getElementById("managedCfgDnsttFakeDomain").value = d.fake_dns_domain || "t.local.lan";
document.getElementById("managedCfgDnsttFakeWorkers").value = d.fake_dns_workers || 0;
document.getElementById("managedCfgDnsttRespWorkers").value = d.dns_response_workers || 0;
document.getElementById("managedCfgDnsttAutoRestart").value = d.auto_restart_interval || "";
document.getElementById("managedCfgDnsttRestartGrace").value = d.auto_restart_grace || "";
document.getElementById("managedCfgDnsttMaxSessions").value = d.max_sessions || 0;
document.getElementById("managedCfgDnsttMaxStreams").value = d.max_streams || 0;
document.getElementById("managedCfgDnsttPendingResponses").value = d.pending_responses || 0;
document.getElementById("managedCfgDnsttStreamBuffer").value = d.stream_buffer || 0;
document.getElementById("managedCfgDnsttUDPReadBuffer").value = d.udp_read_buffer || 0;
document.getElementById("managedCfgDnsttUDPWriteBuffer").value = d.udp_write_buffer || 0;
document.getElementById("managedCfgDnsttKey").value = d.privkey_file || "/opt/sshpanel/dnstt.key";
document.getElementById("managedCfgDnsttNoStats").checked = !!d.disable_stats_log;
document.getElementById("managedCfgDnsttNoConsole").checked = !!d.disable_console_log;
document.getElementById("managedCfgDnsttLogConnections").checked = !!d.log_connections;
const hasUdpgw = !!c.udpgw;
document.getElementById("managedCfgUdpgwEnabled").checked = hasUdpgw;
@@ -1789,6 +1875,8 @@ async function loadManagedServerConfig(id) {
document.getElementById("managedCfgUdpgwMaxConns").value = u.max_client_conns || 0;
document.getElementById("managedCfgUdpgwIdle").value = u.idle_timeout || "";
document.getElementById("managedCfgUdpgwMapTTL").value = u.map_ttl || "";
document.getElementById("managedCfgUdpgwAutoRestart").value = u.auto_restart_interval || "";
document.getElementById("managedCfgUdpgwRestartGrace").value = u.auto_restart_grace || "";
document.getElementById("managedCfgUdpgwDebug").checked = !!u.debug;
managedTlsForwardersState = c.tls_forwarders || [];
@@ -1796,6 +1884,7 @@ async function loadManagedServerConfig(id) {
const x = c.xray || {};
document.getElementById("managedCfgXrayEnabled").checked = !!x.enabled;
document.getElementById("managedCfgXrayMode").value = xrayModeFromConfig(x);
document.getElementById("managedDnsttPubkeyWrap")?.classList.add("hidden");
if (st) st.textContent = "Config loaded.";
@@ -1808,9 +1897,12 @@ async function loadManagedServerConfig(id) {
function managedConfigFromForm() {
const extraLines = document.getElementById("managedCfgExtraListen").value
.split("\n").map(s => s.trim()).filter(Boolean);
const dnsttDomains = readDnsttDomains("managedCfgDnsttDomains");
return {
listen: document.getElementById("managedCfgListen").value.trim(),
extra_listen: extraLines,
proxy_auto_restart_interval: document.getElementById("managedCfgProxyAutoRestart").value.trim(),
proxy_auto_restart_grace: document.getElementById("managedCfgProxyRestartGrace").value.trim(),
host_key_file: "/opt/sshpanel/ssh_host_rsa_key",
admin_dir: "/opt/sshpanel/admin",
default_limit_mbps_up: parseInt(document.getElementById("managedCfgLimitUp").value || "0", 10),
@@ -1820,24 +1912,44 @@ function managedConfigFromForm() {
banner: document.getElementById("managedCfgBanner").value,
banner_file: "/opt/sshpanel/banner.txt",
dnstt: document.getElementById("managedCfgDnsttEnabled").checked ? {
domain: document.getElementById("managedCfgDnsttDomain").value.trim(),
domain: dnsttDomains[0] || "",
domains: dnsttDomains,
udp_listen: document.getElementById("managedCfgDnsttUDP").value.trim(),
fake_dns_enabled: document.getElementById("managedCfgDnsttFakeEnabled").checked,
fake_dns_listen: document.getElementById("managedCfgDnsttFakeListen").value.trim(),
fake_dns_domain: document.getElementById("managedCfgDnsttFakeDomain").value.trim(),
fake_dns_workers: parseInt(document.getElementById("managedCfgDnsttFakeWorkers").value || "0", 10),
dns_response_workers: parseInt(document.getElementById("managedCfgDnsttRespWorkers").value || "0", 10),
auto_restart_interval: document.getElementById("managedCfgDnsttAutoRestart").value.trim(),
auto_restart_grace: document.getElementById("managedCfgDnsttRestartGrace").value.trim(),
max_sessions: parseInt(document.getElementById("managedCfgDnsttMaxSessions").value || "0", 10),
max_streams: parseInt(document.getElementById("managedCfgDnsttMaxStreams").value || "0", 10),
pending_responses: parseInt(document.getElementById("managedCfgDnsttPendingResponses").value || "0", 10),
stream_buffer: parseInt(document.getElementById("managedCfgDnsttStreamBuffer").value || "0", 10),
udp_read_buffer: parseInt(document.getElementById("managedCfgDnsttUDPReadBuffer").value || "0", 10),
udp_write_buffer: parseInt(document.getElementById("managedCfgDnsttUDPWriteBuffer").value || "0", 10),
privkey_file: document.getElementById("managedCfgDnsttKey").value.trim(),
disable_stats_log: document.getElementById("managedCfgDnsttNoStats").checked,
disable_console_log: document.getElementById("managedCfgDnsttNoConsole").checked,
log_connections: document.getElementById("managedCfgDnsttLogConnections").checked,
} : null,
udpgw: document.getElementById("managedCfgUdpgwEnabled").checked ? {
listen: document.getElementById("managedCfgUdpgwListen").value.trim(),
max_client_conns: parseInt(document.getElementById("managedCfgUdpgwMaxConns").value || "0", 10),
idle_timeout: document.getElementById("managedCfgUdpgwIdle").value.trim(),
map_ttl: document.getElementById("managedCfgUdpgwMapTTL").value.trim(),
auto_restart_interval: document.getElementById("managedCfgUdpgwAutoRestart").value.trim(),
auto_restart_grace: document.getElementById("managedCfgUdpgwRestartGrace").value.trim(),
debug: document.getElementById("managedCfgUdpgwDebug").checked,
} : null,
tls_forwarders: managedTlsForwardersState,
xray: {
enabled: document.getElementById("managedCfgXrayEnabled").checked,
mode: document.getElementById("managedCfgXrayMode").value === "external" ? "external" : "native",
native: document.getElementById("managedCfgXrayMode").value !== "external",
bin_path: "/opt/sshpanel/xray",
config_file: "/opt/sshpanel/xray_config.json",
native_config_file: "/opt/sshpanel/xray_native_config.json",
api_server: "127.0.0.1:10085",
online_window_seconds: 90,
stats_poll_seconds: 15,
@@ -2025,6 +2137,7 @@ async function loadDashboardStats() {
if (!res.ok) throw new Error(await res.text());
const s = await res.json();
updateDashboardStats(s);
await loadDnsttHealth();
} catch (e) {
if (e.message === "auth") doAuthError();
else {
@@ -2061,6 +2174,76 @@ function updateDashboardStats(s) {
if (dashNetTotal) dashNetTotal.textContent = `Total ${fmtBytes(rxTotal + txTotal)}`;
}
async function loadDnsttHealth() {
if (!dnsttDashboardCard && !dnsttHealthBody && !dnsttActiveSessions) return;
if (currentRole !== "superadmin") {
dnsttDashboardCard?.classList.add("hidden");
return;
}
try {
const res = await api("/api/dnstt");
if (!res.ok) throw new Error(await res.text());
const d = await res.json();
const enabled = d.enabled !== false;
if (dnsttDashboardCard) dnsttDashboardCard.classList.toggle("hidden", !enabled);
if (!enabled) return;
if (dnsttActiveSessions) dnsttActiveSessions.textContent = fmtInt(d.active_sessions);
if (dnsttActiveStreams) dnsttActiveStreams.textContent = fmtInt(d.active_streams);
if (dnsttDNSRx) dnsttDNSRx.textContent = fmtInt(d.dns_rx);
if (dnsttQueueLen) dnsttQueueLen.textContent = fmtInt(d.ch_len);
if (dnsttHealthUpdated) dnsttHealthUpdated.textContent = d.running === false ? "DNSTT stopped" : fmtDnsttTimestamp(d.timestamp);
const rows = [
["Session rejected", d.sess_rejected],
["Stream rejected", d.stream_rejected],
["DNS parse errors", d.parse_err],
["No EDNS", d.no_edns],
["EDNS limit 512", d.limit512],
["Local DNS workers", d.fake_dns_workers],
["Response workers", d.dns_response_workers],
["Responses queued", d.rec_queued],
["Responses dropped", d.rec_dropped],
["Responses sent", d.resp_sent],
["Response bytes", d.resp_bytes],
["Empty responses", d.resp_empty],
["Data responses", d.resp_data],
["Oversize responses", d.resp_oversize],
["KCP sessions new", d.kcp_new],
["KCP sessions ended", d.kcp_end],
["SMUX streams new", d.smux_new],
["SMUX streams ended", d.smux_end],
["Panic recovered", d.panic_recovered],
];
if (dnsttHealthBody) {
dnsttHealthBody.innerHTML = "";
for (let i = 0; i < rows.length; i += 2) {
const a = rows[i];
const b = rows[i + 1] || ["", ""];
const tr = document.createElement("tr");
tr.innerHTML = `<td>${a[0]}</td><td>${fmtInt(a[1])}</td><td>${b[0]}</td><td>${b[0] ? fmtInt(b[1]) : ""}</td>`;
dnsttHealthBody.appendChild(tr);
}
}
const bad = Number(d.sess_rejected || 0) + Number(d.stream_rejected || 0) + Number(d.rec_dropped || 0) + Number(d.panic_recovered || 0);
if (dnsttHealthSummary) {
if (d.running === false) {
dnsttHealthSummary.textContent = "DNSTT is enabled but not running. Check key/domain/listen config or recent logs.";
} else {
dnsttHealthSummary.textContent = bad > 0
? `Attention: ${fmtInt(bad)} overload/recovery events in the last DNSTT stats window.`
: "DNSTT health OK: no rejects, drops, or recovered panics in the last stats window.";
}
}
} catch (e) {
if (e.message === "auth") throw e;
dnsttDashboardCard?.classList.add("hidden");
if (dnsttHealthUpdated) dnsttHealthUpdated.textContent = "Error loading DNSTT stats.";
if (dnsttHealthSummary) dnsttHealthSummary.textContent = e.message || "DNSTT stats unavailable.";
}
}
async function loadStats() {
try {
const res = await api("/api/stats");
@@ -2087,6 +2270,7 @@ async function loadStats() {
});
if (ifaceSummary) ifaceSummary.textContent = `Total: ${fmtBytes(totRx)} rx / ${fmtBytes(totTx)} tx`;
if (statsUpdated) statsUpdated.textContent = "Updated: " + new Date().toLocaleTimeString();
await loadDnsttHealth();
} catch (e) {
if (e.message==="auth") doAuthError();
else if (statsUpdated) statsUpdated.textContent = "Erro ao carregar stats.";
@@ -2223,6 +2407,22 @@ async function clearPanelLog() {
// ─── Server Config ────────────────────────────────────────────────────────────
document.querySelector("[data-tab='server']")?.addEventListener("click", loadServerConfig);
function dnsttDomainsText(d) {
const domains = Array.isArray(d?.domains) && d.domains.length ? d.domains : (d?.domain ? [d.domain] : []);
return domains.join("\n");
}
function readDnsttDomains(id) {
const el = document.getElementById(id);
if (!el) return [];
const seen = new Set();
return el.value.split(/\r?\n|,/).map(s => s.trim()).filter(Boolean).map(s => s.replace(/\.$/, "").toLowerCase()).filter(s => {
if (seen.has(s)) return false;
seen.add(s);
return true;
});
}
function toggleDnsttFields(on) {
const el = document.getElementById("dnsttFields");
el.style.opacity = on ? "1" : ".4";
@@ -2245,6 +2445,8 @@ async function loadServerConfig() {
// Network
document.getElementById("cfgListen").value = c.listen || "";
document.getElementById("cfgExtraListen").value = (c.extra_listen || []).join("\n");
document.getElementById("cfgProxyAutoRestart").value = c.proxy_auto_restart_interval || "";
document.getElementById("cfgProxyRestartGrace").value = c.proxy_auto_restart_grace || "";
// SSH / general
document.getElementById("cfgLimitUp").value = c.default_limit_mbps_up || 0;
@@ -2260,11 +2462,25 @@ async function loadServerConfig() {
document.getElementById("cfgDnsttEnabled").checked = hasDnstt;
toggleDnsttFields(hasDnstt);
const d = c.dnstt || {};
document.getElementById("cfgDnsttDomain").value = d.domain || "";
document.getElementById("cfgDnsttDomains").value = dnsttDomainsText(d);
document.getElementById("cfgDnsttUDP").value = d.udp_listen || "";
document.getElementById("cfgDnsttFakeEnabled").checked = !!d.fake_dns_enabled;
document.getElementById("cfgDnsttFakeListen").value = d.fake_dns_listen || "";
document.getElementById("cfgDnsttFakeDomain").value = d.fake_dns_domain || "t.local.lan";
document.getElementById("cfgDnsttFakeWorkers").value = d.fake_dns_workers || 0;
document.getElementById("cfgDnsttRespWorkers").value = d.dns_response_workers || 0;
document.getElementById("cfgDnsttAutoRestart").value = d.auto_restart_interval || "";
document.getElementById("cfgDnsttRestartGrace").value = d.auto_restart_grace || "";
document.getElementById("cfgDnsttMaxSessions").value = d.max_sessions || 0;
document.getElementById("cfgDnsttMaxStreams").value = d.max_streams || 0;
document.getElementById("cfgDnsttPendingResponses").value = d.pending_responses || 0;
document.getElementById("cfgDnsttStreamBuffer").value = d.stream_buffer || 0;
document.getElementById("cfgDnsttUDPReadBuffer").value = d.udp_read_buffer || 0;
document.getElementById("cfgDnsttUDPWriteBuffer").value = d.udp_write_buffer || 0;
document.getElementById("cfgDnsttKey").value = d.privkey_file || "/opt/sshpanel/dnstt.key";
document.getElementById("cfgDnsttNoStats").checked = !!d.disable_stats_log;
document.getElementById("cfgDnsttNoConsole").checked = !!d.disable_console_log;
document.getElementById("cfgDnsttLogConnections").checked = !!d.log_connections;
// UDPGW
const hasUdpgw = !!c.udpgw;
@@ -2275,6 +2491,8 @@ async function loadServerConfig() {
document.getElementById("cfgUdpgwMaxConns").value = u.max_client_conns || 0;
document.getElementById("cfgUdpgwIdle").value = u.idle_timeout || "";
document.getElementById("cfgUdpgwMapTTL").value = u.map_ttl || "";
document.getElementById("cfgUdpgwAutoRestart").value = u.auto_restart_interval || "";
document.getElementById("cfgUdpgwRestartGrace").value = u.auto_restart_grace || "";
document.getElementById("cfgUdpgwDebug").checked = !!u.debug;
// TLS forwarders
@@ -2284,6 +2502,7 @@ async function loadServerConfig() {
// Xray
const x = c.xray || {};
document.getElementById("cfgXrayEnabled").checked = !!x.enabled;
document.getElementById("cfgXrayMode").value = xrayModeFromConfig(x);
st.textContent = "Config loaded.";
} catch (e) {
@@ -2300,10 +2519,13 @@ async function saveServerConfig() {
const extraLines = document.getElementById("cfgExtraListen").value
.split("\n").map(s => s.trim()).filter(Boolean);
const dnsttDomains = readDnsttDomains("cfgDnsttDomains");
const cfg = {
listen: document.getElementById("cfgListen").value.trim(),
extra_listen: extraLines,
proxy_auto_restart_interval: document.getElementById("cfgProxyAutoRestart").value.trim(),
proxy_auto_restart_grace: document.getElementById("cfgProxyRestartGrace").value.trim(),
host_key_file: "/opt/sshpanel/ssh_host_rsa_key",
admin_dir: "/opt/sshpanel/admin",
default_limit_mbps_up: parseInt(document.getElementById("cfgLimitUp").value || "0", 10),
@@ -2313,24 +2535,44 @@ async function saveServerConfig() {
banner: document.getElementById("cfgBanner").value,
banner_file: "/opt/sshpanel/banner.txt",
dnstt: document.getElementById("cfgDnsttEnabled").checked ? {
domain: document.getElementById("cfgDnsttDomain").value.trim(),
domain: dnsttDomains[0] || "",
domains: dnsttDomains,
udp_listen: document.getElementById("cfgDnsttUDP").value.trim(),
fake_dns_enabled: document.getElementById("cfgDnsttFakeEnabled").checked,
fake_dns_listen: document.getElementById("cfgDnsttFakeListen").value.trim(),
fake_dns_domain: document.getElementById("cfgDnsttFakeDomain").value.trim(),
fake_dns_workers: parseInt(document.getElementById("cfgDnsttFakeWorkers").value || "0", 10),
dns_response_workers: parseInt(document.getElementById("cfgDnsttRespWorkers").value || "0", 10),
auto_restart_interval: document.getElementById("cfgDnsttAutoRestart").value.trim(),
auto_restart_grace: document.getElementById("cfgDnsttRestartGrace").value.trim(),
max_sessions: parseInt(document.getElementById("cfgDnsttMaxSessions").value || "0", 10),
max_streams: parseInt(document.getElementById("cfgDnsttMaxStreams").value || "0", 10),
pending_responses: parseInt(document.getElementById("cfgDnsttPendingResponses").value || "0", 10),
stream_buffer: parseInt(document.getElementById("cfgDnsttStreamBuffer").value || "0", 10),
udp_read_buffer: parseInt(document.getElementById("cfgDnsttUDPReadBuffer").value || "0", 10),
udp_write_buffer: parseInt(document.getElementById("cfgDnsttUDPWriteBuffer").value || "0", 10),
privkey_file: document.getElementById("cfgDnsttKey").value.trim(),
disable_stats_log: document.getElementById("cfgDnsttNoStats").checked,
disable_console_log: document.getElementById("cfgDnsttNoConsole").checked,
log_connections: document.getElementById("cfgDnsttLogConnections").checked,
} : null,
udpgw: document.getElementById("cfgUdpgwEnabled").checked ? {
listen: document.getElementById("cfgUdpgwListen").value.trim(),
max_client_conns: parseInt(document.getElementById("cfgUdpgwMaxConns").value || "0", 10),
idle_timeout: document.getElementById("cfgUdpgwIdle").value.trim(),
map_ttl: document.getElementById("cfgUdpgwMapTTL").value.trim(),
auto_restart_interval: document.getElementById("cfgUdpgwAutoRestart").value.trim(),
auto_restart_grace: document.getElementById("cfgUdpgwRestartGrace").value.trim(),
debug: document.getElementById("cfgUdpgwDebug").checked,
} : null,
tls_forwarders: tlsArr,
xray: {
enabled: document.getElementById("cfgXrayEnabled").checked,
mode: document.getElementById("cfgXrayMode").value === "external" ? "external" : "native",
native: document.getElementById("cfgXrayMode").value !== "external",
bin_path: "/opt/sshpanel/xray",
config_file: "/opt/sshpanel/xray_config.json",
native_config_file: "/opt/sshpanel/xray_native_config.json",
api_server: "127.0.0.1:10085",
online_window_seconds: 90,
stats_poll_seconds: 15,
@@ -2779,7 +3021,7 @@ function wzSaveInbound() {
break;
case "xhttp":
ib.streamSettings.xhttpSettings = {
path: document.getElementById("wzXHTTPPath").value.trim() || "/",
path: document.getElementById("wzXHTTPPath").value.trim() || "/xhttp",
host: document.getElementById("wzXHTTPHost").value.trim() || undefined,
mode: document.getElementById("wzXHTTPMode").value,
};
+112 -16
View File
@@ -159,17 +159,24 @@
</div>
<div class="grid2 dashboard-lower">
<div class="card">
<div class="card hidden" id="dnsttDashboardCard">
<div class="card-hdr">
<div class="card-title">Ações rápidas</div>
<span class="chip green">simples</span>
<div class="card-title">DNSTT Health <span class="chip">5s window</span></div>
<span class="hint" id="dnsttHealthUpdated">--</span>
</div>
<div class="quick-actions">
<button class="quick-action" type="button" data-jump="ssh"><strong>Criar SSH</strong><span>Usuário, senha, validade e limite.</span></button>
<button class="quick-action" type="button" data-jump="xray"><strong>Criar Xray</strong><span>UUID, label, validade e conexões.</span></button>
<button class="quick-action superadmin-only hidden" type="button" data-jump="resellers"><strong>Novo revendedor</strong><span>Plano, validade e limite de contas.</span></button>
<button class="quick-action superadmin-only hidden" type="button" data-jump="server"><strong>Configurar serviços</strong><span>Portas, DNSTT, UDPGW e TLS.</span></button>
<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>
@@ -321,6 +328,11 @@
<div class="card-hdr">
<div class="card-title">Xray Core <span class="chip" id="xrayChip">--</span></div>
<div class="card-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">Save mode</button>
<button class="btn btn-ghost btn-sm" id="xStartBtn">Start</button>
<button class="btn btn-danger btn-sm" id="xStopBtn">Stop</button>
<button class="btn btn-ghost btn-sm" id="xRestartBtn">Restart</button>
@@ -410,7 +422,7 @@
<!-- 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"/></div>
<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>Host <span class="hint">(SNI)</span></label><input type="text" id="wzXHTTPHost" placeholder="example.com"/></div>
<div class="field" id="wzXHTTPModeField" style="display:none;">
<label>Mode</label>
@@ -645,6 +657,11 @@
<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;">
@@ -677,8 +694,23 @@
</label>
</div>
<div id="managedDnsttFields" class="form-grid" style="opacity:.4;pointer-events:none;">
<div class="field"><label>Domain</label><input type="text" id="managedCfgDnsttDomain" placeholder="t.example.com"/></div>
<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">
@@ -697,6 +729,7 @@
</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>
@@ -713,6 +746,9 @@
<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>
@@ -764,7 +800,8 @@
<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="hint" style="margin-top:6px;color:var(--muted);">Binary: /opt/sshpanel/xray &nbsp;·&nbsp; Config: /opt/sshpanel/xray_config.json &nbsp;·&nbsp; Online counters use Xray Stats API on 127.0.0.1:10085</div>
<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>
@@ -912,6 +949,17 @@
<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 -->
@@ -967,14 +1015,49 @@
</label>
</div>
<div id="dnsttFields" class="form-grid" style="opacity:.4;pointer-events:none;">
<div class="field">
<label>Domain</label>
<input type="text" id="cfgDnsttDomain" placeholder="t.example.com"/>
<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">
@@ -997,6 +1080,9 @@
<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>
@@ -1026,6 +1112,15 @@
<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>
@@ -1091,7 +1186,8 @@
<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="hint" style="margin-top:6px;color:var(--muted);">Binary: /opt/sshpanel/xray &nbsp;·&nbsp; Config: /opt/sshpanel/xray_config.json &nbsp;·&nbsp; Online counters use Xray Stats API on 127.0.0.1:10085</div>
<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>
</div>
</div><!-- /right -->
@@ -1115,6 +1211,6 @@
</div><!-- /shell -->
</div><!-- /app -->
<script defer src="assets/app.js?v=20260511visualsafesave1"></script>
<script defer src="assets/app.js?v=20260622dnsttfakednsfast"></script>
</body>
</html>