mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-05-08 14:36:13 +00:00
Audit panel xhttp config against xray-core's runtime paths and split fields per direction so each side carries only what it actually uses: - Bidirectional (must match): host, path, mode, all xPadding*, session*/seq*, uplinkData*/Key, scMaxEachPostBytes - Server-only (inbound): noSSEHeader, scMaxBufferedPosts, scStreamUpServerSecs, serverMaxHeaderBytes - Client-only (outbound): uplinkHTTPMethod, uplinkChunkSize, noGRPCHeader, scMinPostsIntervalMs, xmux The inbound previously held client-only fields and the outbound was missing every must-match field beyond host/path/mode — meaning a panel-built outbound couldn't connect to an inbound with a custom xPaddingKey/sessionKey/etc. Headers stay on the inbound for URL-share purposes only; xray's listener ignores them at runtime, but they travel through the share link's `extra` blob so the client picks them up. Renames the URL helpers (applyXhttpPadding* -> applyXhttpExtra*) since the blob now carries more than padding, and folds path/host/mode into the helper so each link generator's xhttp branch is one line. Adds two enforcement points for xray's "uplinkHTTPMethod=GET only in packet-up" rule: the GET option is disabled when mode != packet-up, and a watcher on the outbound modal auto-clears GET when the user switches modes. Hides the XMUX block behind an `enableXmux` switch on the outbound form (mirrors the QUIC Params toggle) so the section doesn't clutter the form by default; fromJson auto-flips it on for outbounds with saved xmux config. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
144 lines
4.9 KiB
HTML
144 lines
4.9 KiB
HTML
{{define "modals/outModal"}}
|
|
<a-modal id="out-modal" v-model="outModal.visible" :title="outModal.title" @ok="outModal.ok"
|
|
:confirm-loading="outModal.confirmLoading" :closable="true" :mask-closable="false"
|
|
:ok-button-props="{ props: { disabled: !outModal.isValid } }" :style="{ overflow: 'hidden' }"
|
|
:ok-text="outModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
|
{{template "form/outbound" .}}
|
|
</a-modal>
|
|
<script>
|
|
const outModal = {
|
|
title: '',
|
|
visible: false,
|
|
confirmLoading: false,
|
|
okText: '{{ i18n "sure" }}',
|
|
isEdit: false,
|
|
confirm: null,
|
|
outbound: new Outbound(),
|
|
jsonMode: false,
|
|
link: '',
|
|
cm: null,
|
|
duplicateTag: false,
|
|
isValid: true,
|
|
activeKey: '1',
|
|
tags: [],
|
|
ok() {
|
|
ObjectUtil.execute(outModal.confirm, outModal.outbound.toJson());
|
|
},
|
|
show({
|
|
title = '',
|
|
okText = '{{ i18n "sure" }}',
|
|
outbound,
|
|
confirm = (outbound) => {},
|
|
isEdit = false,
|
|
tags = []
|
|
}) {
|
|
this.title = title;
|
|
this.okText = okText;
|
|
this.confirm = confirm;
|
|
this.jsonMode = false;
|
|
this.link = '';
|
|
this.activeKey = '1';
|
|
this.visible = true;
|
|
this.outbound = isEdit ? Outbound.fromJson(outbound) : new Outbound();
|
|
this.isEdit = isEdit;
|
|
this.tags = tags;
|
|
this.check()
|
|
},
|
|
close() {
|
|
outModal.visible = false;
|
|
outModal.loading(false);
|
|
},
|
|
loading(loading = true) {
|
|
outModal.confirmLoading = loading;
|
|
},
|
|
check() {
|
|
if (outModal.outbound.tag == '' || outModal.tags.includes(outModal.outbound.tag)) {
|
|
this.duplicateTag = true;
|
|
this.isValid = false;
|
|
} else {
|
|
this.duplicateTag = false;
|
|
this.isValid = true;
|
|
}
|
|
},
|
|
toggleJson(jsonTab) {
|
|
textAreaObj = document.getElementById('outboundJson');
|
|
if (jsonTab) {
|
|
if (this.cm != null) {
|
|
this.cm.toTextArea();
|
|
this.cm = null;
|
|
}
|
|
textAreaObj.value = JSON.stringify(this.outbound.toJson(), null, 2);
|
|
this.cm = CodeMirror.fromTextArea(textAreaObj, app.cmOptions);
|
|
this.cm.on('change', editor => {
|
|
value = editor.getValue();
|
|
if (this.isJsonString(value)) {
|
|
this.outbound = Outbound.fromJson(JSON.parse(value));
|
|
this.check();
|
|
}
|
|
});
|
|
this.activeKey = '2';
|
|
} else {
|
|
if (this.cm != null) {
|
|
this.cm.toTextArea();
|
|
this.cm = null;
|
|
}
|
|
this.activeKey = '1';
|
|
}
|
|
},
|
|
isJsonString(str) {
|
|
try {
|
|
JSON.parse(str);
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
return true;
|
|
},
|
|
};
|
|
|
|
new Vue({
|
|
delimiters: ['[[', ']]'],
|
|
el: '#out-modal',
|
|
data: {
|
|
outModal: outModal,
|
|
get outbound() {
|
|
return outModal.outbound;
|
|
},
|
|
},
|
|
watch: {
|
|
// xray-core's SplitHTTPConfig.Build() rejects "GET" as
|
|
// uplinkHTTPMethod outside packet-up mode. Clear the field
|
|
// instead of carrying an invalid combination through.
|
|
"outModal.outbound.stream.xhttp.mode"(newMode) {
|
|
const xhttp = outModal.outbound.stream && outModal.outbound.stream.xhttp;
|
|
if (xhttp && xhttp.uplinkHTTPMethod === "GET" && newMode !== "packet-up") {
|
|
xhttp.uplinkHTTPMethod = "";
|
|
}
|
|
},
|
|
},
|
|
methods: {
|
|
streamNetworkChange() {
|
|
if (this.outModal.outbound.protocol == Protocols.VLESS && !outModal.outbound
|
|
.canEnableTlsFlow()) {
|
|
delete this.outModal.outbound.settings.flow;
|
|
}
|
|
},
|
|
canEnableTls() {
|
|
return this.outModal.outbound.canEnableTls();
|
|
},
|
|
convertLink() {
|
|
newOutbound = Outbound.fromLink(outModal.link);
|
|
if (newOutbound) {
|
|
this.outModal.outbound = newOutbound;
|
|
this.outModal.toggleJson(true);
|
|
this.outModal.check();
|
|
this.$message.success('Link imported successfully...');
|
|
outModal.link = '';
|
|
} else {
|
|
this.$message.error('Wrong Link!');
|
|
outModal.link = '';
|
|
}
|
|
},
|
|
},
|
|
});
|
|
</script>
|
|
{{end}} |