3x-ui/web/html/modals/warp_modal.html
2025-04-09 11:10:12 +00:00

279 lines
No EOL
14 KiB
HTML

{{define "modals/warpModal"}}
<a-modal id="warp-modal" v-model="warpModal.visible" title="Cloudflare WARP" :confirm-loading="warpModal.confirmLoading"
:closable="true" :mask-closable="true" :footer="null" :class="themeSwitcher.currentTheme">
<template v-if="ObjectUtil.isEmpty(warpModal.warpData)">
<a-empty :image-style="{ height: 'auto', marginTop: '24px', marginBottom: '24px' }">
<template #image>
<a-icon type="cloud" :style="{ width: '48px', height: '48px', fontSize: '48px' }"></a-icon>
</template>
<template #description>
<a-space direction="vertical">
<span :style="{ fontSize: '18px' }">{{ i18n "pages.xray.warp.warpModalTitle" }}</span>
<span>{{ i18n "pages.xray.warp.warpModalDescription" }}</span>
</a-space>
</template>
<a-button icon="api" @click="register" :loading="warpModal.confirmLoading"
:style="{ marginTop: '8px', marginBottom: '8px' }">{{ i18n "pages.xray.warp.warpCreateOutbound"
}}</a-button>
</a-empty>
</template>
<template v-else>
<a-collapse default-active-key="1">
<a-collapse-panel key='1' header='{{ i18n "pages.xray.warp.warpGeneralTitle" }}'>
<a-space direction="vertical" size="middle">
<a-radio-group v-if="!ObjectUtil.isEmpty(warpModal.warpConfig)" v-model="warpModal.informationType"
:style="{ width: '100%' }" button-style="solid">
<a-radio-button value="general">
{{ i18n "pages.xray.warp.warpGeneralTab" }}
</a-radio-button>
<a-radio-button value="account">
{{ i18n "pages.xray.warp.warpGeneralAccount" }}
</a-radio-button>
</a-radio-group>
<a-custom-descriptions v-if="warpModal.informationType === 'general'" layout="vertical" size="small"
:column="1">
<a-descriptions-item label='{{ i18n "pages.xray.warp.warpAccessToken" }}'>
[[ warpModal.warpData.access_token ]]
</a-descriptions-item>
<a-descriptions-item label='{{ i18n "pages.xray.warp.warpDeviceId" }}'>
[[ warpModal.warpData.device_id ]]
</a-descriptions-item>
<a-descriptions-item label='{{ i18n "pages.xray.warp.warpLicenseKey" }}'>
[[ warpModal.warpData.license_key ]]
</a-descriptions-item>
<a-descriptions-item label='{{ i18n "pages.xray.warp.warpPrivateKey" }}'>
[[ warpModal.warpData.private_key ]]
</a-descriptions-item>
</a-custom-descriptions>
<a-custom-descriptions
v-if="!ObjectUtil.isEmpty(warpModal.warpConfig) && warpModal.informationType === 'account'"
layout="vertical" size="small" :column="3">
<a-descriptions-item label='{{ i18n "pages.xray.warp.warpDeviceName" }}' :span="3">
[[ warpModal.warpConfig.name ]]
</a-descriptions-item>
<a-descriptions-item label='{{ i18n "pages.xray.warp.warpDeviceModel" }}' :span="3">
[[ warpModal.warpConfig.model ]]
</a-descriptions-item>
<a-descriptions-item label='{{ i18n "pages.xray.warp.warpDeviceEnabled" }}' :span="1">
[[ warpModal.warpConfig.enabled ]]
</a-descriptions-item>
<template v-if="!ObjectUtil.isEmpty(warpModal.warpConfig.account)">
<a-descriptions-item label='{{ i18n "pages.xray.warp.warpAccountType" }}' :span="1">
[[ warpModal.warpConfig.account.account_type ]]
</a-descriptions-item>
<a-descriptions-item label='{{ i18n "pages.xray.warp.warpAccountRole" }}' :span="1">
[[ warpModal.warpConfig.account.role ]]
</a-descriptions-item>
<a-descriptions-item label='{{ i18n "pages.xray.warp.warpPlusData" }}' :span="1">
<a-tag color="green">
[[ SizeFormatter.sizeFormat(warpModal.warpConfig.account.premium_data) ]]
</a-tag>
</a-descriptions-item>
<a-descriptions-item label='{{ i18n "pages.xray.warp.warpQuota" }}' :span="1">
<a-tag color="green">
[[ SizeFormatter.sizeFormat(warpModal.warpConfig.account.quota) ]]
</a-tag>
</a-descriptions-item>
<a-descriptions-item label='{{ i18n "pages.xray.warp.warpUsage" }}' :span="1">
<a-tag color="green">
[[ SizeFormatter.sizeFormat(warpModal.warpConfig.account.usage) ]]
</a-tag>
</a-descriptions-item>
</template>
</a-custom-descriptions>
<a-row type="flex" :style="{ gap: '8px' }">
<a-col :style="{ flex: '1' }">
<a-button icon="sync" @click="getConfig" :style="{ width: '100%' }"
:loading="warpModal.confirmLoading" type="primary">
<span>{{ i18n "info" }}</span>
</a-button>
</a-col>
<a-col :style="{ flex: '1' }">
<a-button icon="delete" @click="delConfig" :loading="warpModal.confirmLoading"
:style="{ width: '100%' }" type="danger">
<span>{{ i18n "delete" }}</span>
</a-button>
</a-col>
</a-row>
</a-space>
</a-collapse-panel>
<a-collapse-panel v-if="!ObjectUtil.isEmpty(warpModal.warpConfig)" key='2'
header='{{ i18n "pages.xray.outbound.outboundStatus" }}'>
<a-form :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<template v-if="warpOutboundIndex>=0">
<a-tag color="green" :style="{ lineHeight: '31px' }">{{ i18n "enabled" }}</a-tag>
<a-button @click="resetOutbound" :loading="warpModal.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" :loading="warpModal.confirmLoading" type="primary">{{ i18n
"pages.xray.outbound.addOutbound" }}</a-button>
</template>
</a-form-item>
</a-form>
</a-collapse-panel>
<a-collapse-panel key='3' header='{{ i18n "pages.xray.outbound.settings" }}'>
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "pages.xray.warp.warpPlusKey" }}'>
<a-input v-model="warpPlus"></a-input>
<a-button @click="updateLicense(warpPlus)" :disabled="warpPlus.length<26"
:loading="warpModal.confirmLoading">{{ i18n "pages.inbounds.update" }}</a-button>
</a-form-item>
</a-form>
</a-collapse-panel>
</a-collapse>
</template>
</a-modal>
<script>
const warpModal = {
visible: false,
confirmLoading: false,
warpData: null,
warpConfig: null,
warpOutbound: null,
informationType: "general",
show() {
this.visible = true;
this.warpConfig = null;
this.getData();
},
close() {
this.visible = false;
this.loading(false);
},
loading(loading = true) {
this.confirmLoading = loading;
},
async getData() {
this.loading(true);
const msg = await HttpUtil.post('/panel/xray/warp/data');
this.loading(false);
if (msg.success) {
this.warpData = msg.obj.length > 0 ? JSON.parse(msg.obj) : null;
}
},
};
new Vue({
delimiters: ['[[', ']]'],
el: '#warp-modal',
data: {
warpModal: warpModal,
warpPlus: '',
},
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,
}
});
}
},
getAddresses(addrs) {
let addresses = [];
if (addrs.v4) addresses.push(addrs.v4 + "/32");
if (addrs.v6) addresses.push(addrs.v6 + "/128");
return addresses;
},
getResolved(client_id) {
let reserved = [];
let decoded = atob(client_id);
let hexString = '';
for (let i = 0; i < decoded.length; i++) {
let hex = decoded.charCodeAt(i).toString(16);
hexString += (hex.length === 1 ? '0' : '') + hex;
}
for (let i = 0; i < hexString.length; i += 2) {
let hexByte = hexString.slice(i, i + 2);
let decValue = parseInt(hexByte, 16);
reserved.push(decValue);
}
return reserved;
},
async register() {
warpModal.loading(true);
const keys = Wireguard.generateKeypair();
const msg = await HttpUtil.post('/panel/xray/warp/reg', keys);
if (msg.success) {
const resp = JSON.parse(msg.obj);
warpModal.warpData = resp.data;
warpModal.warpConfig = resp.config;
this.collectConfig();
}
warpModal.loading(false);
},
async updateLicense(l) {
warpModal.loading(true);
const msg = await HttpUtil.post('/panel/xray/warp/license', { license: l });
if (msg.success) {
warpModal.warpData = JSON.parse(msg.obj);
warpModal.warpConfig = null;
this.warpPlus = '';
}
warpModal.loading(false);
},
async getConfig() {
warpModal.loading(true);
const msg = await HttpUtil.post('/panel/xray/warp/config');
warpModal.loading(false);
if (msg.success) {
warpModal.warpConfig = JSON.parse(msg.obj);
this.collectConfig();
}
},
async delConfig() {
warpModal.loading(true);
const msg = await HttpUtil.post('/panel/xray/warp/del');
warpModal.loading(false);
if (msg.success) {
warpModal.warpData = null;
warpModal.warpConfig = null;
this.delOutbound();
}
},
addOutbound() {
app.templateSettings.outbounds.push(warpModal.warpOutbound.toJson());
app.outboundSettings = JSON.stringify(app.templateSettings.outbounds);
warpModal.close();
},
resetOutbound() {
app.templateSettings.outbounds[this.warpOutboundIndex] = warpModal.warpOutbound.toJson();
app.outboundSettings = JSON.stringify(app.templateSettings.outbounds);
warpModal.close();
},
delOutbound() {
if (this.warpOutboundIndex != -1) {
app.templateSettings.outbounds.splice(this.warpOutboundIndex, 1);
app.outboundSettings = JSON.stringify(app.templateSettings.outbounds);
}
warpModal.close();
}
},
computed: {
warpOutboundIndex: {
get: function () {
return app.templateSettings ? app.templateSettings.outbounds.findIndex((o) => o.tag == 'warp') : -1;
}
}
}
});
</script>
{{end}}