3x-ui/web/html/modals/nord_modal.html

256 lines
11 KiB
HTML
Raw Normal View History

{{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.server" }}' v-if="nordModal.servers.length > 0">
<a-select v-model="nordModal.serverId">
<a-select-option v-for="s in nordModal.servers" :key="s.id" :value="s.id">
[[ 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,
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.servers = [];
this.countryId = 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.serverId = 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);
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) },
},
computed: {
nordOutboundIndex: {
get: function () {
return app.templateSettings ? app.templateSettings.outbounds.findIndex((o) => o.tag.startsWith("nord-")) : -1;
}
}
}
});
</script>
{{end}}