| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306 |
- {{define "modals/nordModal"}}
- <a-modal id="nord-modal" v-model="nordModal.visible" title="NordVPN NordLynx"
- :confirm-loading="nordModal.confirmLoading" :closable="true" :mask-closable="true"
- :footer="null" :class="themeSwitcher.currentTheme">
- <template v-if="nordModal.nordData == null">
- <a-tabs default-active-key="token" :class="themeSwitcher.currentTheme">
- <a-tab-pane key="token" tab='{{ i18n "pages.xray.outbound.accessToken" }}'>
- <a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:18} }" :style="{ marginTop: '20px' }">
- <a-form-item label='{{ i18n "pages.xray.outbound.accessToken" }}'>
- <a-input v-model="nordModal.token" placeholder='{{ i18n "pages.xray.outbound.accessToken" }}'></a-input>
- <div :style="{ marginTop: '10px' }">
- <a-button type="primary" icon="login" @click="login()" :loading="nordModal.confirmLoading">{{ i18n "login" }}</a-button>
- </div>
- </a-form-item>
- </a-form>
- </a-tab-pane>
- <a-tab-pane key="key" tab='{{ i18n "pages.xray.outbound.privateKey" }}'>
- <a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:18} }" :style="{ marginTop: '20px' }">
- <a-form-item label='{{ i18n "pages.xray.outbound.privateKey" }}'>
- <a-input v-model="nordModal.manualKey" placeholder='{{ i18n "pages.xray.outbound.privateKey" }}'></a-input>
- <div :style="{ marginTop: '10px' }">
- <a-button type="primary" icon="save" @click="saveKey()" :loading="nordModal.confirmLoading">{{ i18n "save" }}</a-button>
- </div>
- </a-form-item>
- </a-form>
- </a-tab-pane>
- </a-tabs>
- </template>
- <template v-else>
- <table :style="{ margin: '5px 0', width: '100%' }">
- <tr class="client-table-odd-row" v-if="nordModal.nordData.token">
- <td>{{ i18n "pages.xray.outbound.accessToken" }}</td>
- <td>[[ nordModal.nordData.token ]]</td>
- </tr>
- <tr>
- <td>{{ i18n "pages.xray.outbound.privateKey" }}</td>
- <td>[[ nordModal.nordData.private_key ]]</td>
- </tr>
- </table>
- <a-button @click="logout" :loading="nordModal.confirmLoading" type="danger">{{ i18n "logout" }}</a-button>
- <a-divider :style="{ margin: '0' }">{{ i18n "pages.xray.outbound.settings" }}</a-divider>
- <a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:18} }" :style="{ marginTop: '10px' }">
- <a-form-item label='{{ i18n "pages.xray.outbound.country" }}'>
- <a-select v-model="nordModal.countryId" @change="fetchServers" show-search option-filter-prop="label">
- <a-select-option v-for="c in nordModal.countries" :key="c.id" :value="c.id" :label="c.name">
- [[ c.name ]] ([[ c.code ]])
- </a-select-option>
- </a-select>
- </a-form-item>
- <a-form-item label='{{ i18n "pages.xray.outbound.city" }}' v-if="nordModal.cities.length > 0">
- <a-select v-model="nordModal.cityId" @change="onCityChange" show-search option-filter-prop="label">
- <a-select-option :key="0" :value="null" label='{{ i18n "pages.xray.outbound.allCities" }}'>
- {{ i18n "pages.xray.outbound.allCities" }}
- </a-select-option>
- <a-select-option v-for="c in nordModal.cities" :key="c.id" :value="c.id" :label="c.name">
- [[ c.name ]]
- </a-select-option>
- </a-select>
- </a-form-item>
- <a-form-item label='{{ i18n "pages.xray.outbound.server" }}' v-if="filteredServers.length > 0">
- <a-select v-model="nordModal.serverId">
- <a-select-option v-for="s in filteredServers" :key="s.id" :value="s.id">
- [[ s.cityName ]] - [[ s.name ]] ({{ i18n "pages.xray.outbound.load" }}: [[ s.load ]]%)
- </a-select-option>
- </a-select>
- </a-form-item>
- </a-form>
- <a-divider :style="{ margin: '10px 0' }">{{ i18n "pages.xray.outbound.outboundStatus" }}</a-divider>
- <a-form :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
- <template v-if="nordOutboundIndex>=0">
- <a-tag color="green" :style="{ lineHeight: '31px' }">{{ i18n "enabled" }}</a-tag>
- <a-button @click="resetOutbound" :loading="nordModal.confirmLoading" type="danger">{{ i18n "reset" }}</a-button>
- </template>
- <template v-else>
- <a-tag color="orange" :style="{ lineHeight: '31px' }">{{ i18n "disabled" }}</a-tag>
- <a-button @click="addOutbound" :disabled="!nordModal.serverId" :loading="nordModal.confirmLoading" type="primary">{{ i18n "pages.xray.outbound.addOutbound" }}</a-button>
- </template>
- </a-form>
- </template>
- </a-modal>
- <script>
- const nordModal = {
- visible: false,
- confirmLoading: false,
- nordData: null,
- token: '',
- manualKey: '',
- countries: [],
- countryId: null,
- cities: [],
- cityId: null,
- servers: [],
- serverId: null,
- show() {
- this.visible = true;
- this.getData();
- },
- close() {
- this.visible = false;
- },
- loading(loading = true) {
- this.confirmLoading = loading;
- },
- async getData() {
- this.loading(true);
- const msg = await HttpUtil.post('/panel/xray/nord/data');
- if (msg.success) {
- this.nordData = msg.obj ? JSON.parse(msg.obj) : null;
- if (this.nordData) {
- await this.fetchCountries();
- }
- }
- this.loading(false);
- },
- async login() {
- this.loading(true);
- const msg = await HttpUtil.post('/panel/xray/nord/reg', { token: this.token });
- if (msg.success) {
- this.nordData = JSON.parse(msg.obj);
- await this.fetchCountries();
- }
- this.loading(false);
- },
- async saveKey() {
- this.loading(true);
- const msg = await HttpUtil.post('/panel/xray/nord/setKey', { key: this.manualKey });
- if (msg.success) {
- this.nordData = JSON.parse(msg.obj);
- await this.fetchCountries();
- }
- this.loading(false);
- },
- async logout(index) {
- this.loading(true);
- const msg = await HttpUtil.post('/panel/xray/nord/del');
- if (msg.success) {
- this.delOutbound(index);
- this.delRouting();
- this.nordData = null;
- this.token = '';
- this.manualKey = '';
- this.countries = [];
- this.cities = [];
- this.servers = [];
- this.countryId = null;
- this.cityId = null;
- }
- this.loading(false);
- },
- async fetchCountries() {
- const msg = await HttpUtil.post('/panel/xray/nord/countries');
- if (msg.success) {
- this.countries = JSON.parse(msg.obj);
- }
- },
- 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);
- 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) {
- this.serverId = this.servers[0].id;
- }
- if (this.servers.length === 0) {
- app.$message.warning('No servers found for the selected country');
- }
- }
- this.loading(false);
- },
- addOutbound() {
- const server = this.servers.find(s => s.id === this.serverId);
- if (!server) return;
-
- const tech = server.technologies.find(t => t.id === 35);
- const publicKey = tech.metadata.find(m => m.name === 'public_key').value;
- const outbound = {
- tag: `nord-${server.hostname}`,
- protocol: 'wireguard',
- settings: {
- secretKey: this.nordData.private_key,
- address: ['10.5.0.2/32'],
- peers: [{
- publicKey: publicKey,
- endpoint: server.station + ':51820'
- }],
- noKernelTun: false
- }
- };
- app.templateSettings.outbounds.push(outbound);
- app.outboundSettings = JSON.stringify(app.templateSettings.outbounds);
- this.close();
- app.$message.success('NordVPN outbound added');
- },
- resetOutbound(index) {
- const server = this.servers.find(s => s.id === this.serverId);
- if (!server || index === -1) return;
-
- const tech = server.technologies.find(t => t.id === 35);
- const publicKey = tech.metadata.find(m => m.name === 'public_key').value;
- const oldTag = app.templateSettings.outbounds[index].tag;
- const newTag = `nord-${server.hostname}`;
- const outbound = {
- tag: newTag,
- protocol: 'wireguard',
- settings: {
- secretKey: this.nordData.private_key,
- address: ['10.5.0.2/32'],
- peers: [{
- publicKey: publicKey,
- endpoint: server.station + ':51820'
- }],
- noKernelTun: false
- }
- };
- app.templateSettings.outbounds[index] = outbound;
-
- // Sync routing rules
- app.templateSettings.routing.rules.forEach(r => {
- if (r.outboundTag === oldTag) {
- r.outboundTag = newTag;
- }
- });
- app.outboundSettings = JSON.stringify(app.templateSettings.outbounds);
- this.close();
- app.$message.success('NordVPN outbound updated');
- },
- delOutbound(index) {
- if (index !== -1) {
- app.templateSettings.outbounds.splice(index, 1);
- app.outboundSettings = JSON.stringify(app.templateSettings.outbounds);
- }
- },
- delRouting() {
- if (app.templateSettings && app.templateSettings.routing) {
- app.templateSettings.routing.rules = app.templateSettings.routing.rules.filter(r => !r.outboundTag.startsWith("nord-"));
- }
- }
- };
- new Vue({
- delimiters: ['[[', ']]'],
- el: '#nord-modal',
- data: {
- nordModal: nordModal,
- },
- methods: {
- login: () => nordModal.login(),
- saveKey: () => nordModal.saveKey(),
- logout() { nordModal.logout(this.nordOutboundIndex) },
- fetchServers: () => nordModal.fetchServers(),
- addOutbound: () => nordModal.addOutbound(),
- resetOutbound() { nordModal.resetOutbound(this.nordOutboundIndex) },
- onCityChange() {
- if (this.filteredServers.length > 0) {
- this.nordModal.serverId = this.filteredServers[0].id;
- } else {
- this.nordModal.serverId = null;
- }
- }
- },
- computed: {
- nordOutboundIndex: {
- 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);
- }
- }
- });
- </script>
- {{end}}
|