3x-ui/web/html/modals/inbound_modal.html
MHSanaei 04b4fb4384
finalmask
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2026-04-20 16:55:06 +02:00

368 lines
No EOL
15 KiB
HTML

{{define "modals/inboundModal"}}
<a-modal id="inbound-modal" v-model="inModal.visible" :title="inModal.title" :dialog-style="{ top: '20px' }"
@ok="inModal.ok" :confirm-loading="inModal.confirmLoading" :closable="true" :mask-closable="false"
:class="themeSwitcher.currentTheme" :ok-text="inModal.okText" cancel-text='{{ i18n "close" }}'>
{{template "form/inbound"}}
</a-modal>
<script>
// Make inModal globally available to ensure it works with any base path
const inModal = (window.inModal = {
title: "",
visible: false,
confirmLoading: false,
okText: '{{ i18n "sure" }}',
isEdit: false,
confirm: null,
inbound: new Inbound(),
dbInbound: new DBInbound(),
ok() {
ObjectUtil.execute(inModal.confirm, inModal.inbound, inModal.dbInbound);
},
show({
title = "",
okText = '{{ i18n "sure" }}',
inbound = null,
dbInbound = null,
confirm = (inbound, dbInbound) => { },
isEdit = false,
}) {
this.title = title;
this.okText = okText;
if (inbound) {
this.inbound = Inbound.fromJson(inbound.toJson());
} else {
this.inbound = new Inbound();
}
// Always ensure testseed is initialized for VLESS protocol (even if vision flow is not set yet)
// This ensures Vue reactivity works properly
if (this.inbound.protocol === Protocols.VLESS && this.inbound.settings) {
if (
!this.inbound.settings.testseed ||
!Array.isArray(this.inbound.settings.testseed) ||
this.inbound.settings.testseed.length < 4
) {
// Create a new array to ensure Vue reactivity
this.inbound.settings.testseed = [900, 500, 900, 256].slice();
}
}
if (dbInbound) {
this.dbInbound = new DBInbound(dbInbound);
} else {
this.dbInbound = new DBInbound();
}
this.confirm = confirm;
this.visible = true;
this.isEdit = isEdit;
},
close() {
inModal.visible = false;
inModal.loading(false);
},
loading(loading = true) {
inModal.confirmLoading = loading;
},
// Vision Seed methods - always available regardless of Vue context
updateTestseed(index, value) {
// Use inModal.inbound explicitly to ensure correct context
if (!inModal.inbound || !inModal.inbound.settings) return;
// Ensure testseed is initialized
if (
!inModal.inbound.settings.testseed ||
!Array.isArray(inModal.inbound.settings.testseed)
) {
inModal.inbound.settings.testseed = [900, 500, 900, 256];
}
// Ensure array has enough elements
while (inModal.inbound.settings.testseed.length <= index) {
inModal.inbound.settings.testseed.push(0);
}
// Update value
inModal.inbound.settings.testseed[index] = value;
},
setRandomTestseed() {
// Use inModal.inbound explicitly to ensure correct context
if (!inModal.inbound || !inModal.inbound.settings) return;
// Ensure testseed is initialized
if (
!inModal.inbound.settings.testseed ||
!Array.isArray(inModal.inbound.settings.testseed) ||
inModal.inbound.settings.testseed.length < 4
) {
inModal.inbound.settings.testseed = [900, 500, 900, 256].slice();
}
// Create new array with random values
inModal.inbound.settings.testseed = [
Math.floor(Math.random() * 1000),
Math.floor(Math.random() * 1000),
Math.floor(Math.random() * 1000),
Math.floor(Math.random() * 1000),
];
},
resetTestseed() {
// Use inModal.inbound explicitly to ensure correct context
if (!inModal.inbound || !inModal.inbound.settings) return;
// Reset testseed to default values
inModal.inbound.settings.testseed = [900, 500, 900, 256].slice();
},
});
// Store Vue instance globally to ensure methods are always accessible
let inboundModalVueInstance = null;
inboundModalVueInstance = new Vue({
delimiters: ["[[", "]]"],
el: "#inbound-modal",
data: {
inModal: inModal,
delayedStart: false,
get inbound() {
return inModal.inbound;
},
get dbInbound() {
return inModal.dbInbound;
},
get isEdit() {
return inModal.isEdit;
},
get client() {
return inModal.inbound &&
inModal.inbound.clients &&
inModal.inbound.clients.length > 0
? inModal.inbound.clients[0]
: null;
},
get datepicker() {
return app.datepicker;
},
get delayedExpireDays() {
return this.client && this.client.expiryTime < 0
? this.client.expiryTime / -86400000
: 0;
},
set delayedExpireDays(days) {
this.client.expiryTime = -86400000 * days;
},
get externalProxy() {
return this.inbound.stream.externalProxy.length > 0;
},
set externalProxy(value) {
if (value) {
inModal.inbound.stream.externalProxy = [
{
forceTls: "same",
dest: window.location.hostname,
port: inModal.inbound.port,
remark: "",
},
];
} else {
inModal.inbound.stream.externalProxy = [];
}
},
},
watch: {
"inModal.inbound.stream.security"(newVal, oldVal) {
// Clear flow when security changes from reality/tls to none
if (
inModal.inbound.protocol == Protocols.VLESS &&
!inModal.inbound.canEnableTlsFlow()
) {
inModal.inbound.settings.vlesses.forEach((client) => {
client.flow = "";
});
}
},
// Ensure testseed is always initialized when vision flow is enabled
"inModal.inbound.settings.vlesses": {
handler() {
if (
inModal.inbound.protocol === Protocols.VLESS &&
inModal.inbound.settings &&
inModal.inbound.settings.vlesses
) {
const hasVisionFlow = inModal.inbound.settings.vlesses.some(
(c) =>
c.flow === "xtls-rprx-vision" ||
c.flow === "xtls-rprx-vision-udp443",
);
if (
hasVisionFlow &&
(!inModal.inbound.settings.testseed ||
!Array.isArray(inModal.inbound.settings.testseed) ||
inModal.inbound.settings.testseed.length < 4)
) {
inModal.inbound.settings.testseed = [900, 500, 900, 256];
}
}
},
deep: true,
},
},
methods: {
streamNetworkChange() {
if (!inModal.inbound.canEnableTls()) {
this.inModal.inbound.stream.security = "none";
}
if (!inModal.inbound.canEnableReality()) {
this.inModal.inbound.reality = false;
}
if (
this.inModal.inbound.protocol == Protocols.VLESS &&
!inModal.inbound.canEnableTlsFlow()
) {
this.inModal.inbound.settings.vlesses.forEach((client) => {
client.flow = "";
});
}
if (inModal.inbound.stream.network != "kcp") {
inModal.inbound.stream.finalmask.udp = [];
}
},
SSMethodChange() {
this.inModal.inbound.settings.password =
RandomUtil.randomShadowsocksPassword(
this.inModal.inbound.settings.method,
);
if (this.inModal.inbound.isSSMultiUser) {
if (this.inModal.inbound.settings.shadowsockses.length == 0) {
this.inModal.inbound.settings.shadowsockses = [
new Inbound.ShadowsocksSettings.Shadowsocks(),
];
}
if (!this.inModal.inbound.isSS2022) {
this.inModal.inbound.settings.shadowsockses.forEach((client) => {
client.method = this.inModal.inbound.settings.method;
});
} else {
this.inModal.inbound.settings.shadowsockses.forEach((client) => {
client.method = "";
});
}
this.inModal.inbound.settings.shadowsockses.forEach((client) => {
client.password = RandomUtil.randomShadowsocksPassword(
this.inModal.inbound.settings.method,
);
});
} else {
if (this.inModal.inbound.settings.shadowsockses.length > 0) {
this.inModal.inbound.settings.shadowsockses = [];
}
}
},
setDefaultCertData(index) {
inModal.inbound.stream.tls.certs[index].certFile = app.defaultCert;
inModal.inbound.stream.tls.certs[index].keyFile = app.defaultKey;
},
async getNewX25519Cert() {
inModal.loading(true);
const msg = await HttpUtil.get("/panel/api/server/getNewX25519Cert");
inModal.loading(false);
if (!msg.success) {
return;
}
inModal.inbound.stream.reality.privateKey = msg.obj.privateKey;
inModal.inbound.stream.reality.settings.publicKey = msg.obj.publicKey;
},
clearX25519Cert() {
this.inbound.stream.reality.privateKey = "";
this.inbound.stream.reality.settings.publicKey = "";
},
async getNewmldsa65() {
inModal.loading(true);
const msg = await HttpUtil.get("/panel/api/server/getNewmldsa65");
inModal.loading(false);
if (!msg.success) {
return;
}
inModal.inbound.stream.reality.mldsa65Seed = msg.obj.seed;
inModal.inbound.stream.reality.settings.mldsa65Verify = msg.obj.verify;
},
clearMldsa65() {
this.inbound.stream.reality.mldsa65Seed = "";
this.inbound.stream.reality.settings.mldsa65Verify = "";
},
randomizeRealityTarget() {
if (typeof getRandomRealityTarget !== "undefined") {
const randomTarget = getRandomRealityTarget();
this.inbound.stream.reality.target = randomTarget.target;
this.inbound.stream.reality.serverNames = randomTarget.sni;
}
},
async getNewEchCert() {
inModal.loading(true);
const msg = await HttpUtil.post("/panel/api/server/getNewEchCert", {
sni: inModal.inbound.stream.tls.sni,
});
inModal.loading(false);
if (!msg.success) {
return;
}
inModal.inbound.stream.tls.echServerKeys = msg.obj.echServerKeys;
inModal.inbound.stream.tls.settings.echConfigList =
msg.obj.echConfigList;
},
clearEchCert() {
this.inbound.stream.tls.echServerKeys = "";
this.inbound.stream.tls.settings.echConfigList = "";
},
async getNewVlessEnc() {
inModal.loading(true);
const msg = await HttpUtil.get("/panel/api/server/getNewVlessEnc");
inModal.loading(false);
if (!msg.success) {
return;
}
const auths = msg.obj.auths || [];
const selected = inModal.inbound.settings.selectedAuth;
const block = auths.find((a) => a.label === selected);
if (!block) {
console.error("No auth block for", selected);
return;
}
inModal.inbound.settings.decryption = block.decryption;
inModal.inbound.settings.encryption = block.encryption;
},
clearVlessEnc() {
this.inbound.settings.decryption = "none";
this.inbound.settings.encryption = "none";
this.inbound.settings.selectedAuth = undefined;
},
// Vision Seed methods - must be in Vue methods for proper binding
updateTestseed(index, value) {
// Ensure testseed is initialized
if (
!this.inbound.settings.testseed ||
!Array.isArray(this.inbound.settings.testseed)
) {
this.$set(this.inbound.settings, "testseed", [900, 500, 900, 256]);
}
// Ensure array has enough elements
while (this.inbound.settings.testseed.length <= index) {
this.inbound.settings.testseed.push(0);
}
// Update value using Vue.set for reactivity
this.$set(this.inbound.settings.testseed, index, value);
},
setRandomTestseed() {
// Create new array with random values and use Vue.set for reactivity
const newSeed = [
Math.floor(Math.random() * 1000),
Math.floor(Math.random() * 1000),
Math.floor(Math.random() * 1000),
Math.floor(Math.random() * 1000),
];
this.$set(this.inbound.settings, "testseed", newSeed);
},
resetTestseed() {
// Reset testseed to default values using Vue.set for reactivity
this.$set(this.inbound.settings, "testseed", [900, 500, 900, 256]);
},
},
});
</script>
{{end}}