From 6c2fc33fff33f806572ee25a5e47e84a319bef1a Mon Sep 17 00:00:00 2001 From: penguinehis Date: Fri, 24 Jul 2026 16:04:20 -0300 Subject: [PATCH] Fix xray panel --- admin/assets/app.css | 13 +- admin/assets/js/08-server-config.js | 4 +- admin/assets/js/09-xray-wizard.js | 276 +++++++++++++++------------- admin/index.html | 45 ++--- 4 files changed, 172 insertions(+), 166 deletions(-) diff --git a/admin/assets/app.css b/admin/assets/app.css index 3d1c65c..c4e7d68 100644 --- a/admin/assets/app.css +++ b/admin/assets/app.css @@ -686,19 +686,18 @@ select:disabled { .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} +.xray-inbound-launcher{--hero-accent:139,92,246;position:relative;display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:17px;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)} +.xray-inbound-launcher-copy,.xray-inbound-launcher-actions,.azion-preset-summary,.xray-inbound-launcher>.hint{position:relative;z-index:1}.xray-inbound-launcher-copy h3,.visual-editor-heading h3{margin-top:5px;font-size:1.12rem;letter-spacing:-.02em}.xray-inbound-launcher-copy p{max-width:760px;margin-top:5px;color:var(--muted);font-size:.75rem;line-height:1.5}.xray-inbound-launcher-copy p strong{color:var(--text-2)}.xray-inbound-launcher-actions{display:flex;align-items:center;justify-content:flex-end;gap:8px;flex-wrap:wrap}.xray-inbound-launcher-actions .btn-soft{border-color:rgba(49,214,123,.28);background:linear-gradient(135deg,rgba(49,214,123,.16),rgba(34,211,238,.08));color:#8af0b5} +.azion-preset-summary{grid-column:1/-1;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;padding-top:15px;border-top:1px solid rgba(148,163,184,.1)}.azion-preset-summary span{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:10px 11px;border:1px solid rgba(139,92,246,.14);border-radius:13px;background:rgba(139,92,246,.055)}.azion-preset-summary small{color:var(--muted);font-size:.65rem}.azion-preset-summary strong{color:var(--text-2);font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:.7rem}.xray-inbound-launcher>.hint{grid-column:1/-1} .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-config-toolbar{display:grid;grid-template-columns:180px minmax(0,1fr);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:1180px){.workspace-overview-grid.five{grid-template-columns:repeat(3,minmax(0,1fr))}.infra-nav-shell,.workspace-nav-shell{top:78px}} -@media(max-width:1100px){.workspace-overview-grid,.workspace-overview-grid.five{grid-template-columns:repeat(2,minmax(0,1fr))}#configSectionNav{grid-template-columns:repeat(3,minmax(0,1fr))}#xraySectionNav{grid-template-columns:repeat(2,minmax(0,1fr))}.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.status-hero{display:grid;grid-template-columns:1fr}.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}.workspace-hero-actions{justify-content:flex-start;max-width:none;margin-top:14px}.workspace-live-status{max-width:100%}.workspace-overview-grid,.workspace-overview-grid.five{grid-template-columns:repeat(2,minmax(0,1fr));margin-top:18px}.workspace-hero-toolbar{align-items:stretch;flex-direction:column}.workspace-toolbar-actions{justify-content:flex-start}.workspace-toolbar-actions .input-sm{width:100%;max-width:none}.infra-section-nav,.workspace-section-nav{display:none}.infra-section-select,.workspace-section-select{display:block}.infra-nav-shell,.workspace-nav-shell{top:76px}.workspace-section-heading{align-items:flex-start;flex-direction:column}.workspace-section-heading>.btn,.workspace-section-heading>.card-actions{width:100%}.workspace-section-heading>.btn{justify-content:center}.settings-panel-grid{grid-template-columns:1fr}.settings-panel-grid>.settings-span-all{grid-column:auto}.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}.panel-dialog{padding:14px}.panel-dialog-card{padding:19px}.panel-dialog-actions .btn{flex:1}.panel-toast-stack{right:14px;bottom:14px;width:calc(100vw - 28px)}} +@media(max-width:1100px){.workspace-overview-grid,.workspace-overview-grid.five{grid-template-columns:repeat(2,minmax(0,1fr))}#configSectionNav{grid-template-columns:repeat(3,minmax(0,1fr))}#xraySectionNav{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.status-hero{display:grid;grid-template-columns:1fr}.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}.workspace-hero-actions{justify-content:flex-start;max-width:none;margin-top:14px}.workspace-live-status{max-width:100%}.workspace-overview-grid,.workspace-overview-grid.five{grid-template-columns:repeat(2,minmax(0,1fr));margin-top:18px}.workspace-hero-toolbar{align-items:stretch;flex-direction:column}.workspace-toolbar-actions{justify-content:flex-start}.workspace-toolbar-actions .input-sm{width:100%;max-width:none}.infra-section-nav,.workspace-section-nav{display:none}.infra-section-select,.workspace-section-select{display:block}.infra-nav-shell,.workspace-nav-shell{top:76px}.workspace-section-heading{align-items:flex-start;flex-direction:column}.workspace-section-heading>.btn,.workspace-section-heading>.card-actions{width:100%}.workspace-section-heading>.btn{justify-content:center}.settings-panel-grid{grid-template-columns:1fr}.settings-panel-grid>.settings-span-all{grid-column:auto}.xray-inbound-launcher{grid-template-columns:1fr;padding:15px}.xray-inbound-launcher-actions,.visual-save-bar{align-items:stretch;flex-direction:column}.xray-inbound-launcher-actions{justify-content:flex-start}.xray-inbound-launcher-actions .btn,.visual-save-bar .btn{width:100%}.azion-preset-summary{grid-column:auto;grid-template-columns:repeat(2,minmax(0,1fr))}.xray-inbound-launcher>.hint{grid-column:auto}.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}.panel-dialog{padding:14px}.panel-dialog-card{padding:19px}.panel-dialog-actions .btn{flex:1}.panel-toast-stack{right:14px;bottom:14px;width:calc(100vw - 28px)}} @media(max-width:460px){.workspace-overview-grid,.workspace-overview-grid.five{grid-template-columns:1fr}.workspace-hero-actions .btn{flex:1}.workspace-toolbar-actions .btn{flex:1}.workspace-overview-card{padding:11px 12px}} /* --- Bot sales workspace --- */ diff --git a/admin/assets/js/08-server-config.js b/admin/assets/js/08-server-config.js index 83422dd..8aa6255 100644 --- a/admin/assets/js/08-server-config.js +++ b/admin/assets/js/08-server-config.js @@ -397,7 +397,7 @@ async function wzSavePastedCert() { if (!name || !cert || !key) { st.textContent = "Name, cert, and key required."; return; } st.textContent = "Saving…"; try { - const res = await api("/api/tls/upload-pem", { method:"POST", body: JSON.stringify({ name, cert, key }) }); + const res = await api(withServerParam("/api/tls/upload-pem", selectedXrayServer()), { method:"POST", body: JSON.stringify({ name, cert, key }) }); if (!res.ok) throw new Error(await res.text()); const data = await res.json(); document.getElementById("wzTLSCert").value = data.cert_file; @@ -415,7 +415,7 @@ async function wzGenerateCert() { if (!domain) { st.textContent = "Domain required."; return; } st.textContent = "Generating…"; try { - const res = await api("/api/tls/generate-selfsigned", { method:"POST", body: JSON.stringify({ domain }) }); + const res = await api(withServerParam("/api/tls/generate-selfsigned", selectedXrayServer()), { method:"POST", body: JSON.stringify({ domain }) }); if (!res.ok) throw new Error(await res.text()); const data = await res.json(); document.getElementById("wzTLSCert").value = data.cert_file; diff --git a/admin/assets/js/09-xray-wizard.js b/admin/assets/js/09-xray-wizard.js index af04428..2026153 100644 --- a/admin/assets/js/09-xray-wizard.js +++ b/admin/assets/js/09-xray-wizard.js @@ -92,9 +92,11 @@ function loadWizardFromConfig() { document.getElementById("wzLogLevel").value = cfg.log?.loglevel || "warning"; wzInbounds = cloneJsonSafe((cfg.inbounds || []).filter(ib => ib && ib.tag !== "api")) || []; wzEditingIndex = -1; + wzCancelInbound(); renderWzInbounds(); - loadSharedEndpointForm(); wzDirty = false; + const presetStatus = document.getElementById("wzAzionDefaultStatus"); + if (presetStatus) presetStatus.textContent = "O padrão cria o certificado autoassinado, habilita TLS e salva/reinicia o Xray automaticamente."; if (st) st.textContent = `Config loaded from ${target}.`; }).catch(e => { wzLoadedServerID = null; @@ -102,8 +104,8 @@ function loadWizardFromConfig() { wzLoadedFullConfig = null; wzInbounds = []; wzEditingIndex = -1; + wzCancelInbound(); renderWzInbounds(); - loadSharedEndpointForm(); if (e.message === "auth") doAuthError(); else if (st) st.textContent = "Error: " + e.message; }); @@ -116,7 +118,7 @@ function renderWzInbounds() { if (!wzInbounds.length) { const empty = document.createElement("div"); empty.className = "hint visual-empty-state"; - empty.textContent = "Nenhum inbound configurado. Crie um endpoint compartilhado ou adicione um inbound."; + empty.textContent = "Nenhum inbound configurado. Use “Adicionar inbound” ou “Criar padrão Azion XHTTP”."; list.appendChild(empty); return; } @@ -201,7 +203,6 @@ function renderWzInbounds() { else if (wzEditingIndex > i) wzEditingIndex--; wzDirty = true; renderWzInbounds(); - loadSharedEndpointForm(); }; actions.append(duplicateBtn, editBtn, delBtn); row.appendChild(actions); @@ -242,7 +243,8 @@ function resetWzInboundForm() { setWzValue("wzTLS", "none"); ["wzTLSCert", "wzTLSKey", "wzTLSCertPath", "wzTLSKeyPath", "wzRealityDest", "wzRealitySNI", "wzRealityPriv", "wzRealityShortID", "wzTrojanPass", "wzSSPass"].forEach(id => setWzValue(id, "")); setWzValue("wzSSMethod", "chacha20-ietf-poly1305"); - document.getElementById("wzInboundFormTitle").textContent = "Novo inbound"; + document.getElementById("wzInboundFormKicker").textContent = "Novo inbound"; + document.getElementById("wzInboundFormTitle").textContent = "Adicionar inbound"; document.getElementById("wzSaveInboundBtn").textContent = "Adicionar inbound"; document.getElementById("wzEditingBadge").classList.add("hidden"); onWzProtoChange("vless"); @@ -301,6 +303,7 @@ function editWzInbound(index) { setWzValue("wzSSMethod", ib.settings?.method || "chacha20-ietf-poly1305"); document.getElementById("wzInboundFormTitle").textContent = `Editar ${ib.tag || "inbound"}`; + document.getElementById("wzInboundFormKicker").textContent = "Editar inbound existente"; document.getElementById("wzSaveInboundBtn").textContent = "Salvar alterações"; document.getElementById("wzEditingBadge").classList.remove("hidden"); const form = document.getElementById("wzAddInboundForm"); @@ -525,149 +528,159 @@ function validateVisualInbounds(inbounds) { } } -function findSharedEndpointPair() { - const roots = wzInbounds.filter(ib => { - const xh = visualXHTTPSettings(ib); - return !!xh && ["vless", "vmess"].includes(String(ib?.protocol || "").toLowerCase()) && normalizeVisualPath(xh.path) === "/"; +const azionPresetProxyTags = new Set(["azion-vless-xhttp", "shared-proxy-xhttp"]); +const azionPresetSSHTags = new Set(["azion-ssh-xhttp", "shared-ssh-xhttp"]); + +function findAzionPresetInbound(tags) { + return wzInbounds.find(ib => tags.has(String(ib?.tag || ""))) || null; +} + +function azionVLESSClients(existingProxy) { + const clients = Array.isArray(existingProxy?.settings?.clients) ? cloneJsonSafe(existingProxy.settings.clients) : []; + if (String(existingProxy?.protocol || "").toLowerCase() === "vless") return clients; + return clients.map(client => { + const converted = { id:client?.id }; + if (client?.email) converted.email = client.email; + if (client?.flow) converted.flow = client.flow; + return converted; + }).filter(client => client.id); +} + +function buildAzionPresetInbounds(certFile, keyFile) { + const existingProxy = findAzionPresetInbound(azionPresetProxyTags); + const existingSSH = findAzionPresetInbound(azionPresetSSHTags); + const managed = new Set([existingProxy, existingSSH].filter(Boolean)); + const others = wzInbounds.filter(ib => !managed.has(ib)); + const portConflict = others.find(ib => Number(ib?.port) === 443); + if (portConflict) { + throw new Error(`A porta 443 já é usada pelo inbound ${portConflict.tag || "sem tag"}. Edite ou remova esse inbound antes de criar o padrão Azion.`); + } + const stream = path => ({ + network:"xhttp", + security:"tls", + xhttpSettings:{ path, mode:"auto" }, + tlsSettings:{ certificates:[{ certificateFile:certFile, keyFile }] }, }); - for (const proxy of roots) { - const ssh = wzInbounds.find(ib => String(ib?.protocol || "").toLowerCase() === "ssh" && - String(ib.listen || "0.0.0.0") === String(proxy.listen || "0.0.0.0") && String(ib.port) === String(proxy.port) && - normalizeVisualPath(visualXHTTPSettings(ib)?.path) === "/ssh"); - if (ssh) return { proxy, ssh }; - } - const proxy = wzInbounds.find(ib => ib?.tag === "shared-proxy-xhttp") || null; - const ssh = wzInbounds.find(ib => ib?.tag === "shared-ssh-xhttp") || null; - return proxy && ssh ? { proxy, ssh } : null; + const proxyInbound = { + tag:"azion-vless-xhttp", + listen:"0.0.0.0", + port:443, + protocol:"vless", + settings:{ clients:azionVLESSClients(existingProxy), decryption:"none" }, + streamSettings:stream("/"), + }; + const sshInbound = { + tag:"azion-ssh-xhttp", + listen:"0.0.0.0", + port:443, + protocol:"ssh", + settings:{}, + streamSettings:stream("/ssh"), + }; + return [...others, proxyInbound, sshInbound]; } -function loadSharedEndpointForm() { - const status = document.getElementById("sharedXHTTPStatus"); - if (!status) return; - const pair = findSharedEndpointPair(); - if (!pair) { - status.textContent = wzLoadedConfigText ? "Nenhum endpoint compartilhado detectado. Preencha os campos para criar um." : "Carregue a configuração para detectar um endpoint existente."; - return; - } - const xh = visualXHTTPSettings(pair.proxy) || {}; - const ss = pair.proxy.streamSettings || {}; - const cert = ss.tlsSettings?.certificates?.[0] || {}; - setWzValue("sharedXHTTPProtocol", pair.proxy.protocol || "vless"); - setWzValue("sharedXHTTPPort", pair.proxy.port || 443); - setWzValue("sharedXHTTPListen", pair.proxy.listen || "0.0.0.0"); - setWzValue("sharedXHTTPHost", xh.host || ""); - setWzValue("sharedXHTTPMode", xh.mode || "auto"); - setWzValue("sharedXHTTPSecurity", ss.security === "tls" ? "tls" : "none"); - setWzValue("sharedXHTTPCert", cert.certificateFile || ""); - setWzValue("sharedXHTTPKey", cert.keyFile || ""); - updateSharedEndpointControls(); - status.textContent = `Endpoint detectado em ${pair.proxy.listen || "0.0.0.0"}:${pair.proxy.port} — ${String(pair.proxy.protocol).toUpperCase()} / e SSH /ssh.`; -} - -function updateSharedEndpointControls() { - const protocol = document.getElementById("sharedXHTTPProtocol")?.value || "vless"; - const security = document.getElementById("sharedXHTTPSecurity")?.value || "none"; - document.getElementById("sharedProxyRouteLabel").textContent = protocol.toUpperCase(); - document.querySelectorAll(".shared-tls-field").forEach(el => el.classList.toggle("hidden", security !== "tls")); -} - -function applySharedXHTTPEndpoint() { - const status = document.getElementById("sharedXHTTPStatus"); +async function createAzionDefaultXHTTP() { + const status = document.getElementById("wzAzionDefaultStatus"); + const button = document.getElementById("wzAzionDefaultBtn"); const selectedID = selectedXrayServer() || "local"; + const target = selectedXrayServerLabel(); if (!wzLoadedConfigText || String(wzLoadedServerID || "") !== String(selectedID)) { - status.textContent = "Carregue a configuração do servidor selecionado antes de editar."; + if (status) status.textContent = `Carregue a configuração de ${target} antes de criar o padrão Azion.`; return; } if ((document.getElementById("xCoreMode")?.value || "native") !== "native") { - status.textContent = "O endpoint compartilhado requer o modo Xray nativo."; - return; - } - const protocol = document.getElementById("sharedXHTTPProtocol").value; - const port = Number(document.getElementById("sharedXHTTPPort").value || 0); - const listen = document.getElementById("sharedXHTTPListen").value.trim() || "0.0.0.0"; - const host = document.getElementById("sharedXHTTPHost").value.trim(); - const mode = document.getElementById("sharedXHTTPMode").value || "auto"; - const security = document.getElementById("sharedXHTTPSecurity").value; - const cert = document.getElementById("sharedXHTTPCert").value.trim(); - const key = document.getElementById("sharedXHTTPKey").value.trim(); - if (!["vless", "vmess"].includes(protocol) || !Number.isInteger(port) || port < 1 || port > 65535) { - status.textContent = "Escolha VLESS/VMess e uma porta válida."; - return; - } - if (/[\u0000-\u001f\u007f]/.test(`${listen}${host}${cert}${key}`)) { - status.textContent = "Os campos contêm caracteres de controle inválidos."; - return; - } - if (security === "tls" && (!cert || !key)) { - status.textContent = "Informe os arquivos do certificado e da chave para usar TLS."; + if (status) status.textContent = "O padrão Azion com SSH requer o modo Xray nativo."; return; } - const pair = findSharedEndpointPair(); - const sameEndpoint = ib => String(ib?.listen || "0.0.0.0") === listen && String(ib?.port) === String(port); - const existingProxy = pair?.proxy || wzInbounds.find(ib => ib?.tag === "shared-proxy-xhttp") || wzInbounds.find(ib => { - const xh = visualXHTTPSettings(ib); - return sameEndpoint(ib) && !!xh && ["vless", "vmess"].includes(String(ib?.protocol || "").toLowerCase()) && normalizeVisualPath(xh.path) === "/"; - }) || null; - const existingSSH = pair?.ssh || wzInbounds.find(ib => ib?.tag === "shared-ssh-xhttp") || wzInbounds.find(ib => { - const xh = visualXHTTPSettings(ib); - return sameEndpoint(ib) && !!xh && String(ib?.protocol || "").toLowerCase() === "ssh" && normalizeVisualPath(xh.path) === "/ssh"; - }) || null; - const removeSet = new Set([existingProxy, existingSSH].filter(Boolean)); - const others = wzInbounds.filter(ib => !removeSet.has(ib)); - const blocking = others.find(ib => String(ib.listen || "0.0.0.0") === listen && String(ib.port) === String(port) && !visualXHTTPSettings(ib)); - if (blocking) { - status.textContent = `A porta já é usada pelo inbound não-XHTTP ${blocking.tag || "sem tag"}. Escolha outra porta.`; + let nextInbounds; + try { + nextInbounds = buildAzionPresetInbounds( + "/opt/sshpanel/certs/example.com/cert.pem", + "/opt/sshpanel/certs/example.com/key.pem", + ); + validateVisualInbounds(nextInbounds); + } catch (error) { + if (status) status.textContent = error.message; return; } - const buildSharedStream = (existing, path) => { - const stream = cloneJsonSafe(existing?.streamSettings || {}); - stream.network = "xhttp"; - stream.xhttpSettings = Object.assign({}, stream.xhttpSettings || stream.splithttpSettings || {}, { path, mode }); - delete stream.splithttpSettings; - if (host) stream.xhttpSettings.host = host; - else delete stream.xhttpSettings.host; - if (security === "tls") { - stream.security = "tls"; - stream.tlsSettings = Object.assign({}, stream.tlsSettings || {}, { certificates:[{ certificateFile:cert, keyFile:key }] }); - } else { - delete stream.security; - delete stream.tlsSettings; + const existingProxy = findAzionPresetInbound(azionPresetProxyTags); + const accepted = await panelConfirm({ + tone:"success", + icon:"AZ", + eyebrow:"Azion XHTTP", + title:existingProxy ? "Atualizar padrão Azion" : "Criar padrão Azion", + message:existingProxy + ? "Atualizar o endpoint padrão e manter os clientes VLESS existentes?" + : "Criar o endpoint padrão completo neste servidor?", + detail:[ + `Servidor: ${target}`, + "Listen: 0.0.0.0:443", + "TLS autoassinado: example.com", + "VLESS XHTTP: /", + "SSH XHTTP: /ssh", + "O Xray será salvo e reiniciado automaticamente.", + ].join("\n"), + confirmLabel:existingProxy ? "Atualizar padrão" : "Criar padrão", + }); + if (!accepted) return; + + const previousInbounds = cloneJsonSafe(wzInbounds); + const previousDirty = wzDirty; + let presetApplied = false; + let configSaved = false; + if (button) { + button.disabled = true; + button.textContent = "Criando padrão…"; + } + if (status) status.textContent = `Gerando certificado example.com em ${target}…`; + try { + const certResponse = await api(withServerParam("/api/tls/generate-selfsigned", selectedID), { + method:"POST", + body:JSON.stringify({ domain:"example.com" }), + }); + if (!certResponse.ok) throw new Error(await certResponse.text()); + const cert = await certResponse.json(); + if (!cert?.cert_file || !cert?.key_file) throw new Error("o servidor não retornou os caminhos do certificado"); + + wzInbounds = buildAzionPresetInbounds(cert.cert_file, cert.key_file); + validateVisualInbounds(wzInbounds); + presetApplied = true; + wzDirty = true; + wzCancelInbound(); + renderWzInbounds(); + if (status) status.textContent = "Certificado criado. Salvando configuração e reiniciando o Xray…"; + const result = await applyWizardConfig(); + if (!result?.saved) throw new Error(result?.error || "não foi possível salvar a configuração"); + configSaved = true; + if (status) status.textContent = result.restarted + ? "Padrão Azion ativo: TLS example.com, VLESS / e SSH /ssh em 0.0.0.0:443." + : "O padrão foi salvo, mas o Xray não reiniciou. Verifique os logs e use Reiniciar."; + if (typeof showPanelToast === "function") { + showPanelToast( + result.restarted ? "Padrão Azion XHTTP criado e ativo." : "Padrão Azion salvo; reinício pendente.", + result.restarted ? "success" : "warning", + "Azion XHTTP", + ); } - delete stream.realitySettings; - return stream; - }; - const previousClients = Array.isArray(existingProxy?.settings?.clients) ? cloneJsonSafe(existingProxy.settings.clients) : []; - const proxyInbound = cloneJsonSafe(existingProxy || {}); - proxyInbound.tag = existingProxy?.tag || "shared-proxy-xhttp"; - proxyInbound.listen = listen; - proxyInbound.port = port; - proxyInbound.protocol = protocol; - proxyInbound.settings = existingProxy?.protocol === protocol ? cloneJsonSafe(existingProxy.settings || {}) : {}; - proxyInbound.settings.clients = previousClients; - if (protocol === "vless") proxyInbound.settings.decryption = "none"; - else delete proxyInbound.settings.decryption; - proxyInbound.streamSettings = buildSharedStream(existingProxy, "/"); - const sshInbound = cloneJsonSafe(existingSSH || {}); - sshInbound.tag = existingSSH?.tag || "shared-ssh-xhttp"; - sshInbound.listen = listen; - sshInbound.port = port; - sshInbound.protocol = "ssh"; - sshInbound.settings = {}; - sshInbound.streamSettings = buildSharedStream(existingSSH, "/ssh"); - wzInbounds = [...others, proxyInbound, sshInbound]; - wzDirty = true; - renderWzInbounds(); - loadSharedEndpointForm(); - status.textContent = "Endpoint atualizado no rascunho. Clique em Salvar configuração e reiniciar para aplicar."; + } catch (error) { + if (presetApplied && !configSaved) { + wzInbounds = previousInbounds; + wzDirty = previousDirty; + renderWzInbounds(); + } + if (error.message === "auth") doAuthError(); + else if (status) status.textContent = "Erro ao criar padrão Azion: " + error.message; + } finally { + if (button) { + button.disabled = false; + button.textContent = "Criar padrão Azion XHTTP"; + } + } } -document.getElementById("sharedXHTTPProtocol")?.addEventListener("change", updateSharedEndpointControls); -document.getElementById("sharedXHTTPSecurity")?.addEventListener("change", updateSharedEndpointControls); -document.getElementById("sharedXHTTPApplyBtn")?.addEventListener("click", applySharedXHTTPEndpoint); -updateSharedEndpointControls(); - function onWzProtoChange(val) { const isSSH = val === "ssh"; // SSH tunnels reuse the VLESS/VMess transport block to expose the XHTTP @@ -876,7 +889,6 @@ function wzSaveInbound() { else wzInbounds.push(ib); wzDirty = true; renderWzInbounds(); - loadSharedEndpointForm(); st.textContent = original ? `Inbound ${tag} atualizado no rascunho.` : `Inbound ${tag} adicionado ao rascunho.`; wzCancelInbound(); } diff --git a/admin/index.html b/admin/index.html index 9ccd7f7..448d346 100644 --- a/admin/index.html +++ b/admin/index.html @@ -16,7 +16,7 @@ setTimeout(function(){document.documentElement.classList.remove("i18n-pending");},2500); })(); - +
@@ -483,27 +483,23 @@
-
-
-
Shared XHTTP endpoint

Um domínio e uma porta

O protocolo selecionado usa /; SSH usa /ssh. Disponível no modo Xray nativo.

- path routing +
+
+ Gerenciar inbounds +

Adicionar um novo inbound

+

Crie um inbound em branco ou instale automaticamente o padrão usado com Azion XHTTP. Para alterar um inbound existente, use somente o botão Editar no cartão dele.

-
- VLESS/ - - SSH/ssh +
+ +
-
-
-
-
-
-
-
- - +
+ Listen0.0.0.0:443 + TLSexample.com + VLESS/ + SSH/ssh
-
Carregue a configuração para detectar um endpoint existente.
+ O padrão cria o certificado autoassinado, habilita TLS e salva/reinicia o Xray automaticamente.
-
Inbounds configuradosEdite qualquer cartão visualmente ou use JSON para campos avançados.
- +
Inbounds configuradosO editor permanece fechado. Clique em Editar somente no inbound que deseja alterar.
- +