From c0821672c2a5a82fe7e715a222292cae4f6ad838 Mon Sep 17 00:00:00 2001 From: Peter_Liu Date: Sat, 21 Feb 2026 01:13:41 +0800 Subject: [PATCH] feat: add city selector to NordVPN modal --- web/html/modals/nord_modal.html | 48 +++++++++++++++++++++++++--- web/translation/translate.en_US.toml | 2 ++ web/translation/translate.zh_CN.toml | 2 ++ 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/web/html/modals/nord_modal.html b/web/html/modals/nord_modal.html index 814712db..5cd5fceb 100644 --- a/web/html/modals/nord_modal.html +++ b/web/html/modals/nord_modal.html @@ -47,10 +47,20 @@ - + + + + {{ i18n "pages.xray.outbound.allCities" }} + + + [[ c.name ]] + + + + - - [[ s.name ]] ({{ i18n "pages.xray.outbound.load" }}: [[ s.load ]]%) + + [[ s.cityName ]] - [[ s.name ]] ({{ i18n "pages.xray.outbound.load" }}: [[ s.load ]]%) @@ -78,6 +88,8 @@ manualKey: '', countries: [], countryId: null, + cities: [], + cityId: null, servers: [], serverId: null, show() { @@ -129,8 +141,10 @@ this.token = ''; this.manualKey = ''; this.countries = []; + this.cities = []; this.servers = []; this.countryId = null; + this.cityId = null; } this.loading(false); }, @@ -143,11 +157,31 @@ async fetchServers() { this.loading(true); this.servers = []; + this.cities = []; this.serverId = null; + this.cityId = null; const msg = await HttpUtil.post('/panel/xray/nord/servers', { countryId: this.countryId }); if (msg.success) { const data = JSON.parse(msg.obj); - this.servers = (data.servers || []).sort((a, b) => a.load - b.load); + const locations = data.locations || []; + const locToCity = {}; + const citiesMap = new Map(); + locations.forEach(loc => { + if (loc.country && loc.country.city) { + citiesMap.set(loc.country.city.id, loc.country.city); + locToCity[loc.id] = loc.country.city; + } + }); + this.cities = Array.from(citiesMap.values()).sort((a, b) => a.name.localeCompare(b.name)); + + this.servers = (data.servers || []).map(s => { + const firstLocId = (s.location_ids || [])[0]; + const city = locToCity[firstLocId]; + s.cityId = city ? city.id : null; + s.cityName = city ? city.name : 'Unknown'; + return s; + }).sort((a, b) => a.load - b.load); + if (this.servers.length === 0) { app.$message.warning('No servers found for the selected country'); } @@ -248,6 +282,12 @@ get: function () { return app.templateSettings ? app.templateSettings.outbounds.findIndex((o) => o.tag.startsWith("nord-")) : -1; } + }, + filteredServers: function() { + if (!this.nordModal.cityId) { + return this.nordModal.servers; + } + return this.nordModal.servers.filter(s => s.cityId === this.nordModal.cityId); } } }); diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml index b1a622e7..77e35729 100644 --- a/web/translation/translate.en_US.toml +++ b/web/translation/translate.en_US.toml @@ -539,6 +539,8 @@ "accessToken" = "Access Token" "country" = "Country" "server" = "Server" +"city" = "City" +"allCities" = "All Cities" "privateKey" = "Private Key" "load" = "Load" diff --git a/web/translation/translate.zh_CN.toml b/web/translation/translate.zh_CN.toml index 724732a2..57bf7434 100644 --- a/web/translation/translate.zh_CN.toml +++ b/web/translation/translate.zh_CN.toml @@ -536,6 +536,8 @@ "accessToken" = "访问令牌" "country" = "国家" "server" = "服务器" +"city" = "城市" +"allCities" = "所有城市" "privateKey" = "私钥" "load" = "负载"