Fix xray panel

This commit is contained in:
2026-07-24 16:04:20 -03:00
parent 44f5b2b09c
commit 6c2fc33fff
4 changed files with 172 additions and 166 deletions
+6 -7
View File
@@ -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 --- */
+2 -2
View File
@@ -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;
+142 -130
View File
@@ -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…";
}
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];
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();
loadSharedEndpointForm();
status.textContent = "Endpoint atualizado no rascunho. Clique em Salvar configuração e reiniciar para aplicar.";
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",
);
}
} 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();
}
+20 -25
View File
@@ -16,7 +16,7 @@
setTimeout(function(){document.documentElement.classList.remove("i18n-pending");},2500);
})();
</script>
<link rel="stylesheet" href="assets/app.css?v=20260720sshfilters1"/>
<link rel="stylesheet" href="assets/app.css?v=20260724xrayinboundsr3"/>
</head>
<body>
<div class="app">
@@ -483,27 +483,23 @@
</div>
<!-- Wizard pane -->
<div id="xrayWizardPane">
<section class="shared-endpoint-card">
<div class="shared-endpoint-head">
<div><span class="page-kicker">Shared XHTTP endpoint</span><h3>Um domínio e uma porta</h3><p>O protocolo selecionado usa <code>/</code>; SSH usa <code>/ssh</code>. Disponível no modo Xray nativo.</p></div>
<span class="chip green">path routing</span>
<section class="xray-inbound-launcher">
<div class="xray-inbound-launcher-copy">
<span class="page-kicker">Gerenciar inbounds</span>
<h3>Adicionar um novo inbound</h3>
<p>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 <strong>Editar</strong> no cartão dele.</p>
</div>
<div class="shared-route-preview" aria-label="Shared endpoint route preview">
<span><strong id="sharedProxyRouteLabel">VLESS</strong><code>/</code></span>
<i></i>
<span><strong>SSH</strong><code>/ssh</code></span>
<div class="xray-inbound-launcher-actions">
<button class="btn" id="wzAddInboundBtn" type="button" onclick="wzToggleAddInbound()">+ Adicionar inbound</button>
<button class="btn btn-soft" id="wzAzionDefaultBtn" type="button" onclick="createAzionDefaultXHTTP()">Criar padrão Azion XHTTP</button>
</div>
<div class="form-grid shared-endpoint-grid">
<div class="field"><label>Protocolo em /</label><select id="sharedXHTTPProtocol"><option value="vless">VLESS</option><option value="vmess">VMess</option></select></div>
<div class="field"><label>Porta compartilhada</label><input id="sharedXHTTPPort" type="number" min="1" max="65535" value="443"/></div>
<div class="field"><label>IP de listen</label><input id="sharedXHTTPListen" value="0.0.0.0" placeholder="0.0.0.0"/></div>
<div class="field"><label>Host HTTP <span class="hint">opcional</span></label><input id="sharedXHTTPHost" placeholder="vpn.seudominio.com"/></div>
<div class="field"><label>Modo XHTTP</label><select id="sharedXHTTPMode"><option value="auto">auto</option><option value="packet-up">packet-up</option><option value="stream-up">stream-up</option><option value="stream-down">stream-down</option><option value="stream-one">stream-one</option></select></div>
<div class="field"><label>Segurança</label><select id="sharedXHTTPSecurity"><option value="none">Sem TLS</option><option value="tls">TLS</option></select></div>
<div class="field shared-tls-field hidden"><label>Arquivo do certificado</label><input id="sharedXHTTPCert" placeholder="/opt/sshpanel/certs/domain/cert.pem"/></div>
<div class="field shared-tls-field hidden"><label>Arquivo da chave</label><input id="sharedXHTTPKey" placeholder="/opt/sshpanel/certs/domain/key.pem"/></div>
<div class="azion-preset-summary" aria-label="Configuração criada pelo padrão Azion XHTTP">
<span><small>Listen</small><strong>0.0.0.0:443</strong></span>
<span><small>TLS</small><strong>example.com</strong></span>
<span><small>VLESS</small><strong>/</strong></span>
<span><small>SSH</small><strong>/ssh</strong></span>
</div>
<div class="shared-endpoint-actions"><span id="sharedXHTTPStatus" class="hint">Carregue a configuração para detectar um endpoint existente.</span><button class="btn" id="sharedXHTTPApplyBtn" type="button">Criar / atualizar endpoint</button></div>
<span id="wzAzionDefaultStatus" class="hint">O padrão cria o certificado autoassinado, habilita TLS e salva/reinicia o Xray automaticamente.</span>
</section>
<aside class="legacy-xhttp-migration" aria-label="Migração de configuração XHTTP antiga">
@@ -525,13 +521,12 @@
<option value="debug">debug</option>
</select>
</div>
<div class="visual-config-toolbar-copy"><strong>Inbounds configurados</strong><span>Edite qualquer cartão visualmente ou use JSON para campos avançados.</span></div>
<button class="btn btn-ghost btn-sm" type="button" onclick="wzToggleAddInbound()">+ Novo inbound</button>
<div class="visual-config-toolbar-copy"><strong>Inbounds configurados</strong><span>O editor permanece fechado. Clique em Editar somente no inbound que deseja alterar.</span></div>
</div>
<div id="wzInboundsList" class="visual-inbound-list"></div>
<!-- Add inbound form -->
<!-- Shared add/edit form: hidden until the user explicitly adds or edits. -->
<div id="wzAddInboundForm" class="visual-inbound-editor hidden">
<div class="visual-editor-heading"><div><span class="page-kicker">Visual editor</span><h3 id="wzInboundFormTitle">Novo inbound</h3></div><span id="wzEditingBadge" class="chip hidden">editing</span></div>
<div class="visual-editor-heading"><div><span class="page-kicker" id="wzInboundFormKicker">Novo inbound</span><h3 id="wzInboundFormTitle">Adicionar inbound</h3></div><span id="wzEditingBadge" class="chip hidden">Editando</span></div>
<div class="form-grid">
<div class="field">
<label>Protocol</label>
@@ -1585,8 +1580,8 @@
<script defer src="assets/js/05-resellers.js?v=20260714pamfix1"></script>
<script defer src="assets/js/06-servers.js?v=20260714pamfix1"></script>
<script defer src="assets/js/07-stats-logs.js?v=20260714pamfix1"></script>
<script defer src="assets/js/08-server-config.js?v=20260722xhttpunlimited2"></script>
<script defer src="assets/js/09-xray-wizard.js?v=20260714quota1"></script>
<script defer src="assets/js/08-server-config.js?v=20260724xrayinboundsr3"></script>
<script defer src="assets/js/09-xray-wizard.js?v=20260724xrayinboundsr3"></script>
<script defer src="assets/js/11-update-status.js?v=20260714pamfix1"></script>
<script defer src="assets/js/12-bot.js?v=20260714pamfix1"></script>
<script defer src="assets/js/10-boot.js?v=20260714pamfix1"></script>