fix frontend loading and client modal bugs

This commit is contained in:
Sora39831 2026-04-06 21:00:02 +08:00
parent 39ba517d9f
commit 3e1b6ed76f
9 changed files with 183 additions and 120 deletions

View file

@ -845,9 +845,9 @@
},
getClientCounts(dbInbound, inbound) {
let clientCount = 0, active = [], deactive = [], depleted = [], expiring = [], online = [], comments = new Map();
clients = inbound.clients;
clientStats = dbInbound.clientStats
now = new Date().getTime()
const clients = inbound.clients;
const clientStats = dbInbound.clientStats;
const now = new Date().getTime();
if (clients) {
clientCount = clients.length;
if (dbInbound.enable) {
@ -862,17 +862,19 @@
deactive.push(client.email);
}
});
clientStats.forEach(stats => {
const exhausted = stats.total > 0 && (stats.up + stats.down) >= stats.total;
const expired = stats.expiryTime > 0 && stats.expiryTime <= now;
if (expired || exhausted) {
depleted.push(stats.email);
} else {
const expiringSoon = (stats.expiryTime > 0 && (stats.expiryTime - now < this.expireDiff)) ||
(stats.total > 0 && (stats.total - (stats.up + stats.down) < this.trafficDiff));
if (expiringSoon) expiring.push(stats.email);
}
});
if (Array.isArray(clientStats)) {
clientStats.forEach(stats => {
const exhausted = stats.total > 0 && (stats.up + stats.down) >= stats.total;
const expired = stats.expiryTime > 0 && stats.expiryTime <= now;
if (expired || exhausted) {
depleted.push(stats.email);
} else {
const expiringSoon = (stats.expiryTime > 0 && (stats.expiryTime - now < this.expireDiff)) ||
(stats.total > 0 && (stats.total - (stats.up + stats.down) < this.trafficDiff));
if (expiringSoon) expiring.push(stats.email);
}
});
}
} else {
clients.forEach(client => {
deactive.push(client.email);
@ -1060,7 +1062,8 @@
});
},
openEditInbound(dbInboundId) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
const dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
if (!dbInbound) return;
const inbound = dbInbound.toInbound();
inModal.show({
title: '{{ i18n "pages.inbounds.modifyInbound"}}',
@ -1127,7 +1130,8 @@
await this.submit(`/panel/api/inbounds/update/${dbInbound.id}`, data, inModal);
},
openAddClient(dbInboundId) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
const dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
if (!dbInbound) return;
clientModal.show({
title: '{{ i18n "pages.client.add"}}',
okText: '{{ i18n "pages.client.submitAdd"}}',
@ -1139,7 +1143,8 @@
});
},
openAddBulkClient(dbInboundId) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
const dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
if (!dbInbound) return;
clientsBulkModal.show({
title: '{{ i18n "pages.client.bulk"}} ' + dbInbound.remark,
okText: '{{ i18n "pages.client.bulk"}}',
@ -1150,11 +1155,11 @@
});
},
openEditClient(dbInboundId, client) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
const dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
if (!dbInbound) return;
clients = this.getInboundClients(dbInbound);
const clients = this.getInboundClients(dbInbound);
if (!clients || !Array.isArray(clients)) return;
index = this.findIndexOfClient(dbInbound.protocol, clients, client);
const index = this.findIndexOfClient(dbInbound.protocol, clients, client);
if (index < 0) return;
clientModal.show({
title: '{{ i18n "pages.client.edit"}}',
@ -1195,7 +1200,8 @@
await this.submit(`/panel/api/inbounds/updateClient/${clientId}`, data, clientModal);
},
resetTraffic(dbInboundId) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
const dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
if (!dbInbound) return;
this.$confirm({
title: '{{ i18n "pages.inbounds.resetTraffic"}}' + ' #' + dbInboundId,
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
@ -1211,6 +1217,8 @@
});
},
delInbound(dbInboundId) {
const dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
if (!dbInbound) return;
this.$confirm({
title: '{{ i18n "pages.inbounds.deleteInbound"}}' + ' #' + dbInboundId,
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
@ -1221,8 +1229,9 @@
});
},
delClient(dbInboundId, client, confirmation = true) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
clientId = this.getClientId(dbInbound.protocol, client);
const dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
if (!dbInbound) return;
const clientId = this.getClientId(dbInbound.protocol, client);
if (confirmation) {
this.$confirm({
title: '{{ i18n "pages.inbounds.deleteClient"}}' + ' ' + client.email,
@ -1274,9 +1283,9 @@
}
},
checkFallback(dbInbound) {
newDbInbound = new DBInbound(dbInbound);
const newDbInbound = new DBInbound(dbInbound);
if (dbInbound.listen.startsWith("@")) {
rootInbound = this.inbounds.find((i) =>
const rootInbound = this.inbounds.find((i) =>
i.isTcp &&
['trojan', 'vless'].includes(i.protocol) &&
i.settings.fallbacks.find(f => f.dest === dbInbound.listen)
@ -1294,43 +1303,48 @@
return newDbInbound;
},
showQrcode(dbInboundId, client) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
newDbInbound = this.checkFallback(dbInbound);
const dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
if (!dbInbound) return;
const newDbInbound = this.checkFallback(dbInbound);
qrModal.show('{{ i18n "qrCode"}}', newDbInbound, client);
},
showInfo(dbInboundId, client) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
const dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
if (!dbInbound) return;
index = 0;
let index = 0;
if (dbInbound.isMultiUser()) {
inbound = dbInbound.toInbound();
clients = inbound && inbound.clients ? inbound.clients : null;
const inbound = dbInbound.toInbound();
const clients = inbound && inbound.clients ? inbound.clients : null;
if (clients && Array.isArray(clients)) {
index = this.findIndexOfClient(dbInbound.protocol, clients, client);
if (index < 0) index = 0;
}
}
newDbInbound = this.checkFallback(dbInbound);
const newDbInbound = this.checkFallback(dbInbound);
infoModal.show(newDbInbound, index);
},
switchEnable(dbInboundId, state) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
const dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
if (!dbInbound) return;
dbInbound.enable = state;
this.submit(`/panel/api/inbounds/update/${dbInboundId}`, dbInbound);
},
async switchEnableClient(dbInboundId, client) {
this.loading()
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
if (!dbInbound) return;
inbound = dbInbound.toInbound();
clients = inbound && inbound.clients ? inbound.clients : null;
if (!clients || !Array.isArray(clients)) return;
index = this.findIndexOfClient(dbInbound.protocol, clients, client);
if (index < 0 || !clients[index]) return;
clients[index].enable = !clients[index].enable;
clientId = this.getClientId(dbInbound.protocol, clients[index]);
await this.updateClient(clients[index], dbInboundId, clientId);
this.loading(false);
this.loading();
try {
const dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
if (!dbInbound) return;
const inbound = dbInbound.toInbound();
const clients = inbound && inbound.clients ? inbound.clients : null;
if (!clients || !Array.isArray(clients)) return;
const index = this.findIndexOfClient(dbInbound.protocol, clients, client);
if (index < 0 || !clients[index]) return;
clients[index].enable = !clients[index].enable;
const clientId = this.getClientId(dbInbound.protocol, clients[index]);
await this.updateClient(clients[index], dbInboundId, clientId);
} finally {
this.loading(false);
}
},
async submit(url, data, modal) {
const msg = await HttpUtil.postWithModal(url, data, modal);
@ -1489,15 +1503,18 @@
return new Date(ts).toLocaleString()
},
isRemovable(dbInboundId) {
return this.getInboundClients(this.dbInbounds.find(row => row.id === dbInboundId)).length > 1;
const clients = this.getInboundClients(this.dbInbounds.find(row => row.id === dbInboundId));
return Array.isArray(clients) && clients.length > 1;
},
inboundLinks(dbInboundId) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
newDbInbound = this.checkFallback(dbInbound);
const dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
if (!dbInbound) return;
const newDbInbound = this.checkFallback(dbInbound);
txtModal.show('{{ i18n "pages.inbounds.export"}}', newDbInbound.genInboundLinks(this.remarkModel), newDbInbound.remark);
},
exportSubs(dbInboundId) {
const dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
if (!dbInbound) return;
const clients = this.getInboundClients(dbInbound);
let subLinks = []
if (clients != null) {
@ -1548,7 +1565,8 @@
txtModal.show('{{ i18n "pages.inbounds.export"}}', copyText.join('\r\n'), 'All-Inbounds');
},
copy(dbInboundId) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
const dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
if (!dbInbound) return;
txtModal.show('{{ i18n "pages.inbounds.inboundData" }}', JSON.stringify(dbInbound, null, 2));
},
async startDataRefreshLoop() {
@ -1613,9 +1631,13 @@
this.getClientEmailOptions();
// Initial data fetch
this.getDBInbounds().then(() => {
this.loading(false);
});
this.getDBInbounds()
.catch((e) => {
console.error(e);
})
.finally(() => {
this.loading(false);
});
// Setup WebSocket for real-time updates
if (window.wsClient) {

View file

@ -1018,23 +1018,29 @@
},
async openLogs() {
logModal.loading = true;
const msg = await HttpUtil.post('/panel/api/server/logs/' + logModal.rows, { level: logModal.level, syslog: logModal.syslog });
if (!msg.success) {
return;
try {
const msg = await HttpUtil.post('/panel/api/server/logs/' + logModal.rows, { level: logModal.level, syslog: logModal.syslog });
if (!msg.success) {
return;
}
logModal.show(msg.obj);
await PromiseUtil.sleep(500);
} finally {
logModal.loading = false;
}
logModal.show(msg.obj);
await PromiseUtil.sleep(500);
logModal.loading = false;
},
async openXrayLogs() {
xraylogModal.loading = true;
const msg = await HttpUtil.post('/panel/api/server/xraylogs/' + xraylogModal.rows, { filter: xraylogModal.filter, showDirect: xraylogModal.showDirect, showBlocked: xraylogModal.showBlocked, showProxy: xraylogModal.showProxy });
if (!msg.success) {
return;
try {
const msg = await HttpUtil.post('/panel/api/server/xraylogs/' + xraylogModal.rows, { filter: xraylogModal.filter, showDirect: xraylogModal.showDirect, showBlocked: xraylogModal.showBlocked, showProxy: xraylogModal.showProxy });
if (!msg.success) {
return;
}
xraylogModal.show(msg.obj);
await PromiseUtil.sleep(500);
} finally {
xraylogModal.loading = false;
}
xraylogModal.show(msg.obj);
await PromiseUtil.sleep(500);
xraylogModal.loading = false;
},
downloadXrayLogs() {
if (!Array.isArray(this.xraylogModal.logs) || this.xraylogModal.logs.length === 0) {

View file

@ -167,10 +167,20 @@
},
async mounted() {
this.lang = LanguageManager.getLanguage();
this.twoFactorEnable = await this.getTwoFactorEnable();
this.turnstileSiteKey = await this.getTurnstileSiteKey();
if (this.turnstileSiteKey) {
this.$nextTick(() => this.ensureTurnstileRendered());
try {
this.twoFactorEnable = await this.getTwoFactorEnable();
this.turnstileSiteKey = await this.getTurnstileSiteKey();
} finally {
this.loadingStates.fetched = true;
this.$nextTick(() => {
if (!this.animationStarted) {
this.animationStarted = true;
this.initHeadline();
}
if (this.turnstileSiteKey) {
this.ensureTurnstileRendered();
}
});
}
},
computed: {
@ -248,15 +258,9 @@
const msg = await HttpUtil.post('/getTwoFactorEnable');
if (msg.success) {
this.twoFactorEnable = msg.obj;
this.loadingStates.fetched = true;
this.$nextTick(() => {
if (!this.animationStarted) {
this.animationStarted = true;
this.initHeadline();
}
});
return msg.obj;
}
return false;
},
initHeadline() {
const animationDelay = 2000;

View file

@ -150,8 +150,13 @@
delayedStart: false,
reset: 0,
ok() {
clients = [];
method = clientsBulkModal.emailMethod;
const clients = [];
const method = clientsBulkModal.emailMethod;
let start;
let end;
let prefix;
let useNum;
let postfix;
if (method > 1) {
start = clientsBulkModal.firstNum;
end = clientsBulkModal.lastNum + 1;
@ -163,7 +168,7 @@
useNum = (method > 1);
postfix = (method > 2 && clientsBulkModal.emailPostfix.length > 0) ? clientsBulkModal.emailPostfix : "";
for (let i = start; i < end; i++) {
newClient = clientsBulkModal.newClient(clientsBulkModal.dbInbound.protocol);
const newClient = clientsBulkModal.newClient(clientsBulkModal.dbInbound.protocol);
if (method == 4) newClient.email = "";
newClient.email += useNum ? prefix + i.toString() + postfix : prefix + postfix;
if (clientsBulkModal.subId.length > 0) newClient.subId = clientsBulkModal.subId;
@ -186,6 +191,9 @@
dbInbound = null,
confirm = (inbound, dbInbound) => { }
}) {
if (!dbInbound) {
return;
}
this.visible = true;
this.title = title;
this.okText = okText;
@ -213,7 +221,10 @@
case Protocols.VMESS: return new Inbound.VmessSettings.VMESS();
case Protocols.VLESS: return new Inbound.VLESSSettings.VLESS();
case Protocols.TROJAN: return new Inbound.TrojanSettings.Trojan();
case Protocols.SHADOWSOCKS: return new Inbound.ShadowsocksSettings.Shadowsocks(clientsBulkModal.inbound.settings.shadowsockses[0].method);
case Protocols.SHADOWSOCKS: {
const method = clientsBulkModal.inbound?.settings?.method || '';
return new Inbound.ShadowsocksSettings.Shadowsocks(method, RandomUtil.randomShadowsocksPassword(method));
}
default: return null;
}
},
@ -247,4 +258,4 @@
});
</script>
{{end}}
{{end}}

View file

@ -32,6 +32,9 @@
}
},
show({ title = '', okText = '{{ i18n "sure" }}', index = null, dbInbound = null, confirm = () => { }, isEdit = false }) {
if (!dbInbound) {
return;
}
this.visible = true;
this.title = title;
this.okText = okText;
@ -42,14 +45,22 @@
this.index = index === null ? this.clients.length : index;
this.delayedStart = false;
if (isEdit) {
if (this.clients[index].expiryTime < 0) {
const currentClient = this.clients[index];
if (!currentClient) {
this.visible = false;
return;
}
if (currentClient.expiryTime < 0) {
this.delayedStart = true;
}
this.oldClientId = this.getClientId(dbInbound.protocol, clients[index]);
this.oldClientId = this.getClientId(dbInbound.protocol, currentClient);
} else {
this.addClient(this.inbound, this.clients);
}
this.clientStats = this.dbInbound.clientStats.find(row => row.email === this.clients[this.index].email);
const activeClient = this.clients[this.index];
this.clientStats = Array.isArray(this.dbInbound.clientStats)
? this.dbInbound.clientStats.find(row => row.email === activeClient?.email)
: null;
this.confirm = confirm;
},
getClientId(protocol, client) {
@ -64,7 +75,7 @@
case Protocols.VMESS: return clients.push(new Inbound.VmessSettings.VMESS());
case Protocols.VLESS: return clients.push(new Inbound.VLESSSettings.VLESS());
case Protocols.TROJAN: return clients.push(new Inbound.TrojanSettings.Trojan());
case Protocols.SHADOWSOCKS: return clients.push(new Inbound.ShadowsocksSettings.Shadowsocks(clients[0].method, RandomUtil.randomShadowsocksPassword(inbound.settings.method)));
case Protocols.SHADOWSOCKS: return clients.push(new Inbound.ShadowsocksSettings.Shadowsocks(inbound.settings.method, RandomUtil.randomShadowsocksPassword(inbound.settings.method)));
default: return null;
}
},

View file

@ -131,26 +131,27 @@
},
methods: {
collectConfig() {
config = warpModal.warpConfig.config;
peer = config.peers[0];
if (config) {
warpModal.warpOutbound = Outbound.fromJson({
tag: 'warp',
protocol: Protocols.Wireguard,
settings: {
mtu: 1420,
secretKey: warpModal.warpData.private_key,
address: this.getAddresses(config.interface.addresses),
reserved: this.getResolved(config.client_id),
domainStrategy: 'ForceIP',
peers: [{
publicKey: peer.public_key,
endpoint: peer.endpoint.host,
}],
noKernelTun: false,
}
});
const config = warpModal.warpConfig?.config;
if (!config || !Array.isArray(config.peers) || config.peers.length === 0) {
return;
}
const peer = config.peers[0];
warpModal.warpOutbound = Outbound.fromJson({
tag: 'warp',
protocol: Protocols.Wireguard,
settings: {
mtu: 1420,
secretKey: warpModal.warpData.private_key,
address: this.getAddresses(config.interface.addresses),
reserved: this.getResolved(config.client_id),
domainStrategy: 'ForceIP',
peers: [{
publicKey: peer.public_key,
endpoint: peer.endpoint.host,
}],
noKernelTun: false,
}
});
},
getAddresses(addrs) {
let addresses = [];
@ -243,4 +244,4 @@
});
</script>
{{end}}
{{end}}

View file

@ -55,7 +55,7 @@
}
},
toggleJson(jsonTab) {
textAreaObj = document.getElementById('outboundJson');
const textAreaObj = document.getElementById('outboundJson');
if(jsonTab){
if(this.cm != null) {
this.cm.toTextArea();
@ -64,7 +64,7 @@
textAreaObj.value = JSON.stringify(this.outbound.toJson(), null, 2);
this.cm = CodeMirror.fromTextArea(textAreaObj, app.cmOptions);
this.cm.on('change',editor => {
value = editor.getValue();
const value = editor.getValue();
if(this.isJsonString(value)){
this.outbound = Outbound.fromJson(JSON.parse(value));
this.check();
@ -107,11 +107,11 @@
canEnableTls() {
return this.outModal.outbound.canEnableTls();
},
convertLink(){
newOutbound = Outbound.fromLink(outModal.link);
if(newOutbound){
this.outModal.outbound = newOutbound;
this.outModal.toggleJson(true);
convertLink(){
const 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 = '';

View file

@ -626,8 +626,12 @@
this.entryPort = window.location.port;
this.entryProtocol = window.location.protocol;
this.entryIsIP = this._isIp(this.entryHost);
await this.getAllSetting();
await this.loadInboundTags();
try {
await this.getAllSetting();
await this.loadInboundTags();
} finally {
this.loadingStates.fetched = true;
}
while (true) {
await PromiseUtil.sleep(1000);
this.saveBtnDisable = this.oldAllSetting.equals(this.allSetting);
@ -635,4 +639,4 @@
}
});
</script>
{{ template "page/body_end" .}}
{{ template "page/body_end" .}}

View file

@ -398,8 +398,8 @@
this.loadingStates.fetched = true
}
result = JSON.parse(msg.obj);
xs = JSON.stringify(result.xraySetting, null, 2);
const result = JSON.parse(msg.obj);
const xs = JSON.stringify(result.xraySetting, null, 2);
this.oldXraySetting = xs;
this.xraySetting = xs;
this.inboundTags = result.inboundTags;
@ -1063,9 +1063,13 @@
if (window.location.protocol !== "https:") {
this.showAlert = true;
}
await this.getXraySetting();
await this.getXrayResult();
await this.getOutboundsTraffic();
try {
await this.getXraySetting();
await this.getXrayResult();
await this.getOutboundsTraffic();
} finally {
this.loadingStates.fetched = true;
}
if (window.wsClient) {
window.wsClient.connect();
@ -1562,4 +1566,4 @@
},
});
</script>
{{ template "page/body_end" .}}
{{ template "page/body_end" .}}