XHTTP Panel Fix

This commit is contained in:
2026-07-13 01:34:28 -03:00
parent 7d90568869
commit 047e4be207
6 changed files with 151 additions and 19 deletions
+4
View File
@@ -59,6 +59,8 @@ Na aba **Xray → Configuração Xray → Visual**, o cartão **Shared XHTTP end
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.
Para configurações XHTTP antigas, carregue a configuração visual e clique em **Ativar SSH /ssh** no cartão VLESS/VMess existente. O painel preserva o inbound original, seus clientes, path, host, modo, opções avançadas e TLS; cria apenas o inbound SSH correspondente na mesma porta e salva/reinicia o Xray. Não é necessário recriar clientes ou o endpoint.
### Requisitos
- Servidor Linux com `systemd`
@@ -581,6 +583,8 @@ In **Xray → Xray Config → Visual**, the **Shared XHTTP endpoint** card creat
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.
For older XHTTP configurations, load the visual configuration and click **Enable SSH /ssh** on the existing VLESS/VMess card. The panel preserves the original inbound, clients, path, host, mode, advanced options, and TLS; it only creates the matching SSH inbound on the same port and then saves/restarts Xray. There is no need to recreate clients or the endpoint.
### Requirements
- Linux server with `systemd`
+3 -1
View File
@@ -674,12 +674,14 @@ select:disabled {
.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}
.legacy-xhttp-migration{display:flex;align-items:center;gap:13px;margin:-3px 0 15px;padding:13px 15px;border:1px solid rgba(49,214,123,.19);border-radius:18px;background:linear-gradient(135deg,rgba(49,214,123,.075),rgba(34,211,238,.035));color:var(--text-2)}.legacy-xhttp-icon{display:grid;place-items:center;flex:0 0 auto;width:42px;height:42px;border:1px solid rgba(49,214,123,.25);border-radius:14px;background:rgba(49,214,123,.11);color:#72e6a4;font-size:.68rem;font-weight:950;letter-spacing:.035em}.legacy-xhttp-migration strong{display:block;color:var(--text);font-size:.8rem}.legacy-xhttp-migration p{margin-top:3px;color:var(--muted);font-size:.71rem;line-height:1.5}
.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)}
.legacy-ssh-btn{margin-right:auto;border-color:rgba(49,214,123,.3)!important;background:linear-gradient(135deg,rgba(49,214,123,.18),rgba(34,211,238,.09))!important;color:#8af0b5!important;box-shadow:inset 0 1px 0 rgba(255,255,255,.04)}.legacy-ssh-btn:hover:not(:disabled){border-color:rgba(49,214,123,.52)!important;transform:translateY(-1px)}.legacy-ssh-btn.is-enabled:disabled{opacity:1;border-color:rgba(49,214,123,.16)!important;background:rgba(49,214,123,.07)!important;color:#72b98e!important;cursor:default}
.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%}}
@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}.legacy-xhttp-migration{align-items:flex-start}.visual-config-toolbar{grid-template-columns:1fr;align-items:stretch}.visual-config-toolbar .btn{width:100%}.visual-inbound-actions{align-items:stretch;flex-wrap:wrap}.legacy-ssh-btn{flex:1 0 100%;margin-right:0}}
/* --- Bot sales workspace --- */
#tab-bot{--bot-accent:#7c5cff;--bot-line:rgba(160,174,192,.14);}
+4 -2
View File
@@ -121,7 +121,8 @@ Object.assign(I18N_TEXT["en-US"], {
"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"
"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",
"Old XHTTP configuration?":"Old XHTTP configuration?","Legacy XHTTP migration copy":"Use “Enable SSH /ssh” on the existing inbound card. The panel preserves the inbound, clients, and every current option; it only adds the SSH route and restarts Xray.","Enable SSH /ssh":"Enable SSH /ssh","SSH /ssh enabled":"SSH /ssh enabled","Legacy XHTTP migration":"Legacy XHTTP migration"
});
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.",
@@ -129,7 +130,8 @@ Object.assign(I18N_TEXT["pt-BR"], {
"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"
"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",
"Old XHTTP configuration?":"Configuração XHTTP antiga?","Legacy XHTTP migration copy":"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.","Enable SSH /ssh":"Ativar SSH /ssh","SSH /ssh enabled":"SSH /ssh ativado","Legacy XHTTP migration":"Migração de configuração XHTTP antiga"
});
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();
+118 -2
View File
@@ -152,6 +152,18 @@ function renderWzInbounds() {
const actions = document.createElement("div");
actions.className = "visual-inbound-actions";
const xhttp = visualXHTTPSettings(ib);
const proxyProtocol = String(ib.protocol || "").toLowerCase();
if (xhttp && ["vless", "vmess"].includes(proxyProtocol)) {
const sshRoute = findSSHRouteForInbound(ib);
const sshBtn = document.createElement("button");
sshBtn.className = sshRoute ? "btn btn-soft btn-sm legacy-ssh-btn is-enabled" : "btn btn-sm legacy-ssh-btn";
sshBtn.type = "button";
sshBtn.textContent = t(sshRoute ? "SSH /ssh enabled" : "Enable SSH /ssh");
sshBtn.disabled = !!sshRoute;
sshBtn.onclick = () => enableSSHForWzInbound(i);
actions.appendChild(sshBtn);
}
const duplicateBtn = document.createElement("button");
duplicateBtn.className = "btn btn-ghost btn-sm";
duplicateBtn.type = "button";
@@ -310,6 +322,108 @@ function visualXHTTPSettings(ib) {
return ss.xhttpSettings || ss.splithttpSettings || {};
}
function sameVisualEndpoint(a, b) {
return String(a?.listen || "0.0.0.0") === String(b?.listen || "0.0.0.0") && String(a?.port) === String(b?.port);
}
function findSSHRouteForInbound(source) {
return wzInbounds.find(candidate => {
const xh = visualXHTTPSettings(candidate);
return sameVisualEndpoint(source, candidate) && !!xh && String(candidate?.protocol || "").toLowerCase() === "ssh" && normalizeVisualPath(xh.path) === "/ssh";
}) || null;
}
function uniqueLegacySSHTag(source) {
const tags = new Set(wzInbounds.map(ib => String(ib?.tag || "")));
const sourceTag = String(source?.tag || source?.port || "xhttp").toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "xhttp";
let tag = `ssh-${sourceTag}`;
let suffix = 2;
while (tags.has(tag)) tag = `ssh-${sourceTag}-${suffix++}`;
return tag;
}
// Adds SSH to a legacy VLESS/VMess XHTTP listener without rebuilding or
// modifying the original inbound. The companion inherits its listen address,
// port, host, XHTTP mode, advanced transport options, TLS certificate, and key;
// only its protocol, empty SSH settings, tag, and /ssh path differ.
async function enableSSHForWzInbound(index, applyNow = true) {
const st = document.getElementById("wzStatus");
const source = wzInbounds[index];
const selectedID = selectedXrayServer() || "local";
if (!source || !visualXHTTPSettings(source) || !["vless", "vmess"].includes(String(source.protocol || "").toLowerCase())) {
if (st) st.textContent = "Selecione um inbound VLESS/VMess com transporte XHTTP.";
return false;
}
if (!wzLoadedConfigText || String(wzLoadedServerID || "") !== String(selectedID)) {
if (st) st.textContent = "Carregue a configuração do servidor selecionado antes de ativar SSH.";
return false;
}
if ((document.getElementById("xCoreMode")?.value || "native") !== "native") {
if (st) st.textContent = "SSH compartilhado requer o modo Xray nativo.";
return false;
}
const security = String(source.streamSettings?.security || "").toLowerCase();
if (security && security !== "none" && security !== "tls") {
if (st) st.textContent = `O inbound ${source.tag || "selecionado"} usa ${security}, que não é suportado pelo SSH XHTTP nativo. Use TLS ou sem TLS.`;
return false;
}
const existingSSH = findSSHRouteForInbound(source);
if (existingSSH) {
if (st) st.textContent = `SSH já está ativado em /ssh pelo inbound ${existingSSH.tag}.`;
return true;
}
const pathConflict = wzInbounds.find(candidate => {
const xh = visualXHTTPSettings(candidate);
return sameVisualEndpoint(source, candidate) && !!xh && normalizeVisualPath(xh.path) === "/ssh";
});
if (pathConflict) {
if (st) st.textContent = `O caminho /ssh já é usado pelo inbound ${pathConflict.tag || "sem tag"}. Edite esse caminho primeiro.`;
return false;
}
if (security === "tls") {
const certificate = source.streamSettings?.tlsSettings?.certificates?.[0] || {};
if (!certificate.certificateFile || !certificate.keyFile) {
if (st) st.textContent = "O inbound usa TLS, mas não possui arquivos de certificado e chave reutilizáveis.";
return false;
}
}
if (applyNow && !confirm(`Ativar SSH em /ssh no mesmo endpoint de ${source.tag || "este inbound"}?\n\nA configuração antiga e todos os clientes serão mantidos. O Xray será reiniciado para aplicar.`)) return false;
const streamSettings = cloneJsonSafe(source.streamSettings || {});
const settingsKey = streamSettings.xhttpSettings ? "xhttpSettings" : (streamSettings.splithttpSettings ? "splithttpSettings" : "xhttpSettings");
streamSettings[settingsKey] = Object.assign({}, streamSettings[settingsKey] || {}, { path:"/ssh" });
const sshInbound = {
tag: uniqueLegacySSHTag(source),
listen: source.listen || "0.0.0.0",
port: cloneJsonSafe(source.port),
protocol: "ssh",
settings: {},
streamSettings,
};
const before = cloneJsonSafe(source);
wzInbounds.push(sshInbound);
try {
validateVisualInbounds(wzInbounds);
} catch (error) {
wzInbounds.pop();
if (st) st.textContent = `Não foi possível ativar SSH: ${error.message}`;
return false;
}
if (JSON.stringify(source) !== JSON.stringify(before)) {
wzInbounds.pop();
if (st) st.textContent = "A migração foi cancelada porque alteraria o inbound antigo.";
return false;
}
wzDirty = true;
renderWzInbounds();
if (!applyNow) {
if (st) st.textContent = `SSH /ssh adicionado ao rascunho sem alterar ${source.tag || "o inbound antigo"}.`;
return true;
}
if (st) st.textContent = `Ativando SSH /ssh sem alterar ${source.tag || "o inbound antigo"}`;
return await applyWizardConfig();
}
function validateVisualInbounds(inbounds) {
const tags = new Set();
const binds = new Map();
@@ -752,7 +866,7 @@ async function applyWizardConfig() {
if (String(wzLoadedServerID || "") !== String(selectedID) || !wzLoadedConfigText) {
if (st) st.textContent = `Reloading config from ${target} before saving...`;
loadWizardFromConfig();
return;
return false;
}
let cfg;
@@ -760,7 +874,7 @@ async function applyWizardConfig() {
cfg = buildConfigFromVisualEditor();
} catch(e) {
if (st) st.textContent = `Invalid visual config: ${e.message}`;
return;
return false;
}
if (st) st.textContent = `Saving config to ${target}...`;
@@ -778,8 +892,10 @@ async function applyWizardConfig() {
? `Config saved on ${target} and Xray restarted.`
: `Config saved on ${target}, but Xray could not restart. Check Xray logs before editing again.`;
setTimeout(() => { loadXrayStatus(); loadInbounds({ force: true }); }, 700);
return !!restarted;
} catch (e) {
if (e.message==="auth") doAuthError();
else if (st) st.textContent = "Error: " + e.message;
return false;
}
}
+21 -13
View File
@@ -16,7 +16,7 @@
setTimeout(function(){document.documentElement.classList.remove("i18n-pending");},2500);
})();
</script>
<link rel="stylesheet" href="assets/app.css?v=20260713xhttp2"/>
<link rel="stylesheet" href="assets/app.css?v=20260713legacyssh3"/>
</head>
<body>
<div class="app">
@@ -428,6 +428,14 @@
<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>
@@ -1437,17 +1445,17 @@
<!-- 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=20260713xhttp2"></script>
<script defer src="assets/js/02-shell.js?v=20260713xhttp2"></script>
<script defer src="assets/js/03-ssh-users.js?v=20260713xhttp2"></script>
<script defer src="assets/js/04-xray.js?v=20260713xhttp2"></script>
<script defer src="assets/js/05-resellers.js?v=20260713xhttp2"></script>
<script defer src="assets/js/06-servers.js?v=20260713xhttp2"></script>
<script defer src="assets/js/07-stats-logs.js?v=20260713xhttp2"></script>
<script defer src="assets/js/08-server-config.js?v=20260713xhttp2"></script>
<script defer src="assets/js/09-xray-wizard.js?v=20260713xhttp2"></script>
<script defer src="assets/js/11-update-status.js?v=20260713xhttp2"></script>
<script defer src="assets/js/12-bot.js?v=20260713xhttp2"></script>
<script defer src="assets/js/10-boot.js?v=20260713xhttp2"></script>
<script defer src="assets/js/01-core.js?v=20260713legacyssh3"></script>
<script defer src="assets/js/02-shell.js?v=20260713legacyssh3"></script>
<script defer src="assets/js/03-ssh-users.js?v=20260713legacyssh3"></script>
<script defer src="assets/js/04-xray.js?v=20260713legacyssh3"></script>
<script defer src="assets/js/05-resellers.js?v=20260713legacyssh3"></script>
<script defer src="assets/js/06-servers.js?v=20260713legacyssh3"></script>
<script defer src="assets/js/07-stats-logs.js?v=20260713legacyssh3"></script>
<script defer src="assets/js/08-server-config.js?v=20260713legacyssh3"></script>
<script defer src="assets/js/09-xray-wizard.js?v=20260713legacyssh3"></script>
<script defer src="assets/js/11-update-status.js?v=20260713legacyssh3"></script>
<script defer src="assets/js/12-bot.js?v=20260713legacyssh3"></script>
<script defer src="assets/js/10-boot.js?v=20260713legacyssh3"></script>
</body>
</html>
+1 -1
View File
@@ -50,7 +50,7 @@ func TestSharedXHTTPListenerRejectsDuplicatePath(t *testing.T) {
func TestValidateNativeSharedXHTTPConfig(t *testing.T) {
valid := []byte(`{"inbounds":[
{"tag":"proxy","listen":"0.0.0.0","port":443,"protocol":"vless","streamSettings":{"network":"xhttp","xhttpSettings":{"path":"/"}}},
{"tag":"legacy-proxy","listen":"0.0.0.0","port":443,"protocol":"vless","streamSettings":{"network":"xhttp","xhttpSettings":{"path":"/legacy"}}},
{"tag":"ssh","listen":"0.0.0.0","port":443,"protocol":"ssh","streamSettings":{"network":"xhttp","xhttpSettings":{"path":"/ssh"}}}
]}`)
if err := validateNativeInboundBindings(valid); err != nil {