diff --git a/README.md b/README.md
index 4538ac3..0d048e6 100644
--- a/README.md
+++ b/README.md
@@ -11,6 +11,8 @@ DragonCoreSSH V40 é um painel/servidor em Go para SSH com HTTP Injection, paine
- Banco de dados PostgreSQL
- Integração com Xray-core/V2Ray
- Configurador visual para VLESS e VMess
+- Editor visual completo de inbounds (criar, editar, duplicar e remover)
+- Endpoint XHTTP compartilhado no modo nativo: VLESS **ou** VMess em `/` e SSH em `/ssh`, usando o mesmo domínio/porta/TLS
- Contas de revendedor (reseller) com cota de usuários e escopo próprio
- Gerenciamento multi-servidor (master/slave) direto pelo painel
- API HTTP completa para bots/automações (ver **HTTP API Reference**)
@@ -48,6 +50,15 @@ gRPC
Observação: Reality deve ser usado apenas em protocolos compatíveis. No configurador visual, VMess não usa Reality.
+### Endpoint XHTTP compartilhado
+
+Na aba **Xray → Configuração Xray → Visual**, o cartão **Shared XHTTP endpoint** cria dois inbounds na mesma porta:
+
+- VLESS **ou** VMess em `/` (selecione um protocolo raiz);
+- SSH sobre XHTTP em `/ssh`, autenticado pelas contas SSH normais do painel.
+
+O listener HTTP interno escolhe o caminho mais específico, portanto `/ssh` é encaminhado ao servidor SSH antes do inbound raiz. Todos os inbounds na mesma porta devem usar a mesma opção TLS e o mesmo certificado. Esse recurso exige o modo **Internal native emulator**; o binário Xray externo não reconhece o protocolo `ssh` específico do DragonCore.
+
### Requisitos
- Servidor Linux com `systemd`
@@ -522,6 +533,8 @@ DragonCoreSSH V40 is a Go-based SSH HTTP Injection server with a web panel, Post
- PostgreSQL database
- Xray-core/V2Ray integration
- Visual configurator for VLESS and VMess
+- Full visual inbound editor (create, edit, duplicate, and remove)
+- Native shared XHTTP endpoint: VLESS **or** VMess on `/` and SSH on `/ssh`, using the same domain/port/TLS
- Reseller accounts with a user quota and self-scoped access
- Multi-server (master/slave) management directly from the panel
- Full HTTP API for bots/automations (see **HTTP API Reference**)
@@ -559,6 +572,15 @@ gRPC
Note: Reality should only be used with compatible protocols. In the visual configurator, VMess does not use Reality.
+### Shared XHTTP endpoint
+
+In **Xray → Xray Config → Visual**, the **Shared XHTTP endpoint** card creates two inbounds on the same port:
+
+- VLESS **or** VMess on `/` (select one root protocol);
+- SSH over XHTTP on `/ssh`, authenticated by the panel's regular SSH accounts.
+
+The internal HTTP listener selects the most specific path, so `/ssh` reaches the SSH server before the root inbound. Every inbound sharing a port must use the same TLS setting and certificate. This feature requires **Internal native emulator** mode; the external Xray binary does not recognize DragonCore's custom `ssh` protocol.
+
### Requirements
- Linux server with `systemd`
diff --git a/admin/assets/app.css b/admin/assets/app.css
index 651c41d..01d3d58 100644
--- a/admin/assets/app.css
+++ b/admin/assets/app.css
@@ -650,6 +650,37 @@ select:disabled {
.update-statusbar{align-items:center;gap:12px;flex-wrap:wrap;}
.btn-xs{padding:5px 8px!important;font-size:.67rem!important;border-radius:9px!important;}
+/* --- Shared visual language for every workspace tab --- */
+.page-hero{
+ --hero-accent:34,211,238;
+ position:relative;display:flex;align-items:flex-end;justify-content:space-between;gap:22px;
+ min-height:150px;margin-bottom:18px;padding:25px 27px;overflow:hidden;
+ border:1px solid rgba(var(--hero-accent),.22);border-radius:28px;
+ background:
+ radial-gradient(circle at 88% 8%,rgba(var(--hero-accent),.24),transparent 33%),
+ radial-gradient(circle at 8% 115%,rgba(139,92,246,.12),transparent 40%),
+ linear-gradient(135deg,rgba(17,22,35,.98),rgba(8,11,18,.98));
+ box-shadow:0 24px 70px rgba(0,0,0,.32),inset 0 1px 0 rgba(255,255,255,.04);
+}
+.page-hero::after{content:"";position:absolute;right:-65px;top:-100px;width:250px;height:250px;border:1px solid rgba(255,255,255,.055);border-radius:50%;box-shadow:0 0 0 34px rgba(255,255,255,.017),0 0 0 68px rgba(255,255,255,.011);pointer-events:none;}
+.page-hero[data-tone="green"]{--hero-accent:49,214,123}.page-hero[data-tone="purple"]{--hero-accent:139,92,246}.page-hero[data-tone="amber"]{--hero-accent:255,200,87}.page-hero[data-tone="blue"]{--hero-accent:80,145,255}
+.page-hero-copy,.page-hero-pills,.page-hero-mark{position:relative;z-index:1}.page-hero-copy{max-width:700px}.page-kicker{display:block;color:rgb(var(--hero-accent));font-size:.69rem;font-weight:900;letter-spacing:.17em;text-transform:uppercase}.page-hero h2{margin-top:7px;font-size:2rem;line-height:1.05;letter-spacing:-.045em}.page-hero p{max-width:650px;margin-top:9px;color:var(--muted);font-size:.82rem;line-height:1.55}.page-hero-pills{display:flex;align-items:center;justify-content:flex-end;gap:7px;flex-wrap:wrap;max-width:42%}.page-hero-pills span{padding:7px 10px;border:1px solid rgba(var(--hero-accent),.18);border-radius:999px;background:rgba(var(--hero-accent),.075);color:var(--text-2);font-size:.69rem;font-weight:850;letter-spacing:.04em}.page-hero-mark{display:grid;place-items:center;width:64px;height:64px;border:1px solid rgba(var(--hero-accent),.25);border-radius:22px;background:rgba(var(--hero-accent),.10);color:rgb(var(--hero-accent));font-size:1.05rem;font-weight:950;box-shadow:0 18px 44px rgba(0,0,0,.24)}
+
+/* Bring legacy screens up to the Bot workspace's information density. */
+.tab-pane:not(#tab-bot)>.grid2,.tab-pane:not(#tab-bot)>#serversListView>.grid2{gap:16px}.tab-pane:not(#tab-bot) .card-hdr{padding-bottom:12px;border-bottom:1px solid rgba(148,163,184,.09)}.tab-pane:not(#tab-bot) .card-title{font-size:.96rem}.tab-pane:not(#tab-bot) .statusbar{margin-top:13px;padding-top:11px;border-top:1px solid rgba(148,163,184,.08)}
+
+/* Xray visual configuration studio */
+.shared-endpoint-card{--hero-accent:139,92,246;position:relative;margin-bottom:18px;padding:20px;overflow:hidden;border:1px solid rgba(139,92,246,.22);border-radius:22px;background:radial-gradient(circle at 96% 0,rgba(139,92,246,.17),transparent 32%),rgba(8,12,20,.82)}
+.shared-endpoint-head{display:flex;align-items:flex-start;justify-content:space-between;gap:14px}.shared-endpoint-head h3,.visual-editor-heading h3{margin-top:5px;font-size:1.12rem;letter-spacing:-.02em}.shared-endpoint-head p{margin-top:5px;color:var(--muted);font-size:.75rem;line-height:1.45}.shared-endpoint-head code,.shared-route-preview code{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;color:#c8bbff}
+.shared-route-preview{display:grid;grid-template-columns:1fr 48px 1fr;align-items:center;gap:8px;margin:17px 0;padding:10px;border:1px solid rgba(148,163,184,.11);border-radius:17px;background:rgba(255,255,255,.025)}.shared-route-preview span{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:11px 12px;border:1px solid rgba(139,92,246,.16);border-radius:13px;background:rgba(139,92,246,.07)}.shared-route-preview strong{font-size:.78rem}.shared-route-preview code{font-size:.77rem;font-weight:900}.shared-route-preview i{height:1px;background:linear-gradient(90deg,rgba(139,92,246,.2),rgba(34,211,238,.7),rgba(139,92,246,.2));position:relative}.shared-route-preview i::after{content:"";position:absolute;right:0;top:-3px;width:7px;height:7px;border-radius:50%;background:var(--accent);box-shadow:0 0 12px var(--accent)}
+.shared-endpoint-grid{grid-template-columns:repeat(3,minmax(0,1fr));}.shared-endpoint-actions{display:flex;align-items:center;justify-content:space-between;gap:14px;margin-top:15px;padding-top:14px;border-top:1px solid rgba(148,163,184,.1)}.shared-endpoint-actions .hint{max-width:650px}
+.visual-config-toolbar{display:grid;grid-template-columns:180px minmax(0,1fr) auto;align-items:end;gap:14px;margin-bottom:13px;padding:13px 15px;border:1px solid rgba(148,163,184,.1);border-radius:18px;background:rgba(255,255,255,.025)}.visual-config-toolbar-copy{display:flex;flex-direction:column;gap:4px;padding-bottom:4px}.visual-config-toolbar-copy strong{font-size:.84rem}.visual-config-toolbar-copy span{color:var(--muted);font-size:.71rem}
+.visual-inbound-list{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;margin-bottom:14px}.visual-inbound-card{position:relative;display:flex;flex-direction:column;gap:13px;min-width:0;padding:15px;border:1px solid rgba(148,163,184,.11);border-radius:18px;background:rgba(255,255,255,.027);transition:.15s ease}.visual-inbound-card:hover{border-color:rgba(139,92,246,.3);background:rgba(139,92,246,.045);transform:translateY(-1px)}.visual-inbound-card-head,.visual-inbound-meta,.visual-inbound-actions{display:flex;align-items:center;gap:8px}.visual-inbound-card-head{justify-content:space-between}.visual-inbound-name{min-width:0}.visual-inbound-name strong{display:block;overflow:hidden;text-overflow:ellipsis;color:var(--text);font-size:.84rem;white-space:nowrap}.visual-inbound-name small{display:block;margin-top:4px;color:var(--muted);font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:.67rem}.visual-inbound-meta{flex-wrap:wrap}.visual-inbound-meta span{padding:4px 7px;border-radius:8px;background:rgba(148,163,184,.07);color:var(--muted);font-size:.67rem}.visual-inbound-actions{justify-content:flex-end;margin-top:auto;padding-top:11px;border-top:1px solid rgba(148,163,184,.08)}
+.visual-inbound-editor{margin:14px 0;padding:18px;border:1px solid rgba(34,211,238,.2);border-radius:22px;background:radial-gradient(circle at 100% 0,rgba(34,211,238,.09),transparent 28%),rgba(6,10,16,.86)}.visual-editor-heading{--hero-accent:34,211,238;display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:15px;padding-bottom:13px;border-bottom:1px solid rgba(148,163,184,.1)}.visual-save-bar{position:sticky;bottom:14px;z-index:8;display:flex;align-items:center;justify-content:space-between;gap:14px;margin-top:16px;padding:13px 15px;border:1px solid rgba(139,92,246,.2);border-radius:19px;background:rgba(8,12,20,.9);box-shadow:0 18px 48px rgba(0,0,0,.35);backdrop-filter:blur(16px)}
+
+@media(max-width:1100px){.shared-endpoint-grid{grid-template-columns:repeat(2,minmax(0,1fr));}.visual-inbound-list{grid-template-columns:1fr}}
+@media(max-width:760px){.page-hero{min-height:0;padding:20px;border-radius:22px;align-items:flex-start;flex-direction:column}.page-hero h2{font-size:1.55rem}.page-hero-pills{max-width:none;justify-content:flex-start}.page-hero-mark{width:50px;height:50px;border-radius:17px}.shared-endpoint-card{padding:15px}.shared-endpoint-head,.shared-endpoint-actions,.visual-save-bar{align-items:flex-start;flex-direction:column}.shared-endpoint-actions .btn,.visual-save-bar .btn{width:100%}.shared-route-preview{grid-template-columns:1fr}.shared-route-preview i{width:1px;height:22px;justify-self:center}.shared-route-preview i::after{right:-3px;top:auto;bottom:0}.shared-endpoint-grid{grid-template-columns:1fr!important}.visual-config-toolbar{grid-template-columns:1fr;align-items:stretch}.visual-config-toolbar .btn{width:100%}}
+
/* --- Bot sales workspace --- */
#tab-bot{--bot-accent:#7c5cff;--bot-line:rgba(160,174,192,.14);}
.bot-hero{
diff --git a/admin/assets/js/01-core.js b/admin/assets/js/01-core.js
index 96c5063..d406eb1 100644
--- a/admin/assets/js/01-core.js
+++ b/admin/assets/js/01-core.js
@@ -15,6 +15,7 @@ let wzLoadedFullConfig = null;
let wzLoadedConfigText = "";
let wzLoadedServerID = null;
let wzDirty = false;
+let wzEditingIndex = -1;
let dashboardCache = { sshUsers: [], xrayInbounds: [], me: null };
let currentTab = "dashboard";
let inboundsRefreshInFlight = false;
@@ -114,6 +115,22 @@ Object.assign(I18N_ALIASES, {
"Atualizações do painel":"Panel Updates","Verificando…":"Checking…","Abrir Git":"Open Git","Verificar agora":"Check now","Versão instalada":"Installed version","Última versão no Git":"Latest Git version","Última verificação":"Last checked",
"Comparando a versão instalada com o repositório Git.":"Comparing the installed version with the Git repository.","Para atualizar:":"To update:","Copiar comando":"Copy command","Atualizado":"Up to date","Atualização disponível":"Update available","Alterações locais":"Local changes","Desconhecido":"Unknown"
});
+Object.assign(I18N_TEXT["en-US"], {
+ "Command center":"Command center","Overview command copy":"Accounts, connections, and infrastructure at a glance.","Access workspace":"Access workspace","SSH accounts":"SSH accounts","SSH accounts copy":"Create, limit, and monitor SSH and SlowDNS access securely.",
+ "Proxy studio":"Proxy studio","Xray visual":"Visual Xray","Xray visual copy":"Manage clients, inbounds, and a shared XHTTP endpoint without editing JSON.","Partner operations":"Partner operations","Partner operations copy":"Control partner quotas, expiration, and access in one place.",
+ "Fleet control":"Fleet control","Fleet control copy":"Add nodes, test credentials, and configure remote infrastructure.","Live fleet":"Live fleet","Server status copy":"Health, load, and active sessions for every managed node.","Observability":"Observability","Monitoring copy":"Server resources, interfaces, and capacity in real time.",
+ "Traffic intelligence":"Traffic intelligence","Network traffic":"Network traffic","Network traffic copy":"Daily and monthly history for understanding infrastructure usage.","Diagnostics":"Diagnostics","System logs copy":"Investigate the panel, DNSTT, and Xray in a focused view.","System studio":"System studio","Settings copy":"Network, SSH, tunnels, and TLS organized visually and applied live.",
+ "One domain and port":"One domain and port","Shared endpoint copy":"The selected protocol uses /; SSH uses /ssh. Available in native Xray mode.","Protocol on /":"Protocol on /","Shared port":"Shared port","Listen IP":"Listen IP","HTTP host":"HTTP host","optional":"optional","XHTTP mode":"XHTTP mode","Security":"Security","No TLS":"No TLS","Certificate file":"Certificate file","Key file":"Key file","Create / update endpoint":"Create / update endpoint",
+ "Configured inbounds":"Configured inbounds","Visual inbound help":"Edit any card visually or use JSON for advanced fields.","New inbound":"New inbound","Visual editor":"Visual editor","Add inbound":"Add inbound","Save changes":"Save changes","Duplicate":"Duplicate","Remove":"Remove","Save config and restart":"Save config and restart"
+});
+Object.assign(I18N_TEXT["pt-BR"], {
+ "Command center":"Central de comando","Overview command copy":"Contas, conexões e infraestrutura em uma leitura rápida.","Access workspace":"Área de acessos","SSH accounts":"Contas SSH","SSH accounts copy":"Crie, limite e acompanhe acessos SSH e SlowDNS com segurança.",
+ "Proxy studio":"Estúdio de proxy","Xray visual":"Xray visual","Xray visual copy":"Gerencie clientes, inbounds e um endpoint XHTTP compartilhado sem editar JSON.","Partner operations":"Operação de parceiros","Partner operations copy":"Controle cotas, validade e acesso dos parceiros em um só lugar.",
+ "Fleet control":"Controle da frota","Fleet control copy":"Adicione nós, teste credenciais e configure a infraestrutura remota.","Live fleet":"Frota ao vivo","Server status copy":"Saúde, carga e sessões ativas de cada nó gerenciado.","Observability":"Observabilidade","Monitoring copy":"Recursos, interfaces e capacidade do servidor em tempo real.",
+ "Traffic intelligence":"Inteligência de tráfego","Network traffic":"Tráfego de rede","Network traffic copy":"Histórico diário e mensal para entender o consumo da infraestrutura.","Diagnostics":"Diagnóstico","System logs copy":"Investigue painel, DNSTT e Xray com uma visualização focada.","System studio":"Estúdio do sistema","Settings copy":"Rede, SSH, túneis e TLS organizados em blocos visuais e aplicados ao vivo.",
+ "One domain and port":"Um domínio e uma porta","Shared endpoint copy":"O protocolo selecionado usa /; SSH usa /ssh. Disponível no modo Xray nativo.","Protocol on /":"Protocolo em /","Shared port":"Porta compartilhada","Listen IP":"IP de listen","HTTP host":"Host HTTP","optional":"opcional","XHTTP mode":"Modo XHTTP","Security":"Segurança","No TLS":"Sem TLS","Certificate file":"Arquivo do certificado","Key file":"Arquivo da chave","Create / update endpoint":"Criar / atualizar endpoint",
+ "Configured inbounds":"Inbounds configurados","Visual inbound help":"Edite qualquer cartão visualmente ou use JSON para campos avançados.","New inbound":"Novo inbound","Visual editor":"Editor visual","Add inbound":"Adicionar inbound","Save changes":"Salvar alterações","Duplicate":"Duplicar","Remove":"Remover","Save config and restart":"Salvar configuração e reiniciar"
+});
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();
let i18nTranslating = false;
diff --git a/admin/assets/js/04-xray.js b/admin/assets/js/04-xray.js
index d1310b5..21f090a 100644
--- a/admin/assets/js/04-xray.js
+++ b/admin/assets/js/04-xray.js
@@ -98,9 +98,11 @@ async function xrayCtrl(action) {
xStatus.textContent = currentLang === "pt-BR" ? "Xray OK." : "Xray "+action+" OK.";
setTimeout(loadXrayStatus, 700);
setTimeout(() => loadInbounds({ force: true }), 1200);
+ return true;
} catch (e) {
if (e.message==="auth") doAuthError();
else xStatus.textContent = t("Error: {error}", {error: e.message});
+ return false;
}
}
diff --git a/admin/assets/js/09-xray-wizard.js b/admin/assets/js/09-xray-wizard.js
index 99931b4..1ee0f6c 100644
--- a/admin/assets/js/09-xray-wizard.js
+++ b/admin/assets/js/09-xray-wizard.js
@@ -82,7 +82,9 @@ function loadWizardFromConfig() {
wzLoadedFullConfig = cloneJsonSafe(cfg);
document.getElementById("wzLogLevel").value = cfg.log?.loglevel || "warning";
wzInbounds = cloneJsonSafe((cfg.inbounds || []).filter(ib => ib && ib.tag !== "api")) || [];
+ wzEditingIndex = -1;
renderWzInbounds();
+ loadSharedEndpointForm();
wzDirty = false;
if (st) st.textContent = `Config loaded from ${target}.`;
}).catch(e => {
@@ -90,7 +92,9 @@ function loadWizardFromConfig() {
wzLoadedConfigText = "";
wzLoadedFullConfig = null;
wzInbounds = [];
+ wzEditingIndex = -1;
renderWzInbounds();
+ loadSharedEndpointForm();
if (e.message === "auth") doAuthError();
else if (st) st.textContent = "Error: " + e.message;
});
@@ -99,49 +103,396 @@ function loadWizardFromConfig() {
function renderWzInbounds() {
const list = document.getElementById("wzInboundsList");
if (!list) return;
+ list.replaceChildren();
if (!wzInbounds.length) {
- list.innerHTML = '