mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-04-20 05:52:24 +00:00
new frontend and mobile view #1286
This commit is contained in:
parent
f734c821d6
commit
729d8549e2
29 changed files with 1698 additions and 1423 deletions
|
@ -141,6 +141,19 @@ class DBInbound {
|
||||||
return Inbound.fromJson(config);
|
return Inbound.fromJson(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isMultiUser() {
|
||||||
|
switch (this.protocol) {
|
||||||
|
case Protocols.VMESS:
|
||||||
|
case Protocols.VLESS:
|
||||||
|
case Protocols.TROJAN:
|
||||||
|
return true;
|
||||||
|
case Protocols.SHADOWSOCKS:
|
||||||
|
return this.toInbound().isSSMultiUser;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
hasLink() {
|
hasLink() {
|
||||||
switch (this.protocol) {
|
switch (this.protocol) {
|
||||||
case Protocols.VMESS:
|
case Protocols.VMESS:
|
||||||
|
|
|
@ -52,13 +52,15 @@ function safeBase64(str) {
|
||||||
|
|
||||||
function formatSecond(second) {
|
function formatSecond(second) {
|
||||||
if (second < 60) {
|
if (second < 60) {
|
||||||
return second.toFixed(0) + ' s';
|
return second.toFixed(0) + 's';
|
||||||
} else if (second < 3600) {
|
} else if (second < 3600) {
|
||||||
return (second / 60).toFixed(0) + ' m';
|
return (second / 60).toFixed(0) + 'm';
|
||||||
} else if (second < 3600 * 24) {
|
} else if (second < 3600 * 24) {
|
||||||
return (second / 3600).toFixed(0) + ' h';
|
return (second / 3600).toFixed(0) + 'h';
|
||||||
} else {
|
} else {
|
||||||
return (second / 3600 / 24).toFixed(0) + ' d';
|
day = Math.floor(second / 3600 / 24);
|
||||||
|
remain = ((second/3600) - (day*24)).toFixed(0);
|
||||||
|
return day + 'd' + (remain > 0 ? ' ' + remain + 'h' : '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,7 +74,7 @@ function addZero(num) {
|
||||||
|
|
||||||
function toFixed(num, n) {
|
function toFixed(num, n) {
|
||||||
n = Math.pow(10, n);
|
n = Math.pow(10, n);
|
||||||
return Math.round(num * n) / n;
|
return Math.floor(num * n) / n;
|
||||||
}
|
}
|
||||||
|
|
||||||
function debounce(fn, delay) {
|
function debounce(fn, delay) {
|
||||||
|
@ -115,15 +117,39 @@ function setCookie(cname, cvalue, exdays) {
|
||||||
function usageColor(data, threshold, total) {
|
function usageColor(data, threshold, total) {
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case data === null:
|
case data === null:
|
||||||
return 'blue';
|
return "green";
|
||||||
case total <= 0:
|
case total < 0:
|
||||||
return 'blue';
|
return "blue";
|
||||||
|
case total == 0:
|
||||||
|
return "purple";
|
||||||
case data < total - threshold:
|
case data < total - threshold:
|
||||||
return 'cyan';
|
return "blue";
|
||||||
case data < total:
|
case data < total:
|
||||||
return 'orange';
|
return "orange";
|
||||||
default:
|
default:
|
||||||
return 'red';
|
return "red";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function userExpiryColor(threshold, client, isDark = false) {
|
||||||
|
if (!client.enable) {
|
||||||
|
return isDark ? '#2c3950' : '#bcbcbc';
|
||||||
|
}
|
||||||
|
now = new Date().getTime(),
|
||||||
|
expiry = client.expiryTime;
|
||||||
|
switch (true) {
|
||||||
|
case expiry === null:
|
||||||
|
return "#389e0d";
|
||||||
|
case expiry < 0:
|
||||||
|
return "#0e49b5";
|
||||||
|
case expiry == 0:
|
||||||
|
return "#7a316f";
|
||||||
|
case now < expiry - threshold:
|
||||||
|
return "#0e49b5";
|
||||||
|
case now < expiry:
|
||||||
|
return "#ffa031";
|
||||||
|
default:
|
||||||
|
return "#e04141";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<a-modal id="prompt-modal" v-model="promptModal.visible" :title="promptModal.title"
|
<a-modal id="prompt-modal" v-model="promptModal.visible" :title="promptModal.title"
|
||||||
:closable="true" @ok="promptModal.ok" :mask-closable="false"
|
:closable="true" @ok="promptModal.ok" :mask-closable="false"
|
||||||
:class="themeSwitcher.darkCardClass"
|
:class="themeSwitcher.darkCardClass"
|
||||||
:ok-text="promptModal.okText" cancel-text='{{ i18n "cancel" }}'>
|
:ok-text="promptModal.okText" cancel-text='{{ i18n "cancel" }}' :class="themeSwitcher.currentTheme">
|
||||||
<a-input id="prompt-modal-input" :type="promptModal.type"
|
<a-input id="prompt-modal-input" :type="promptModal.type"
|
||||||
v-model="promptModal.value"
|
v-model="promptModal.value"
|
||||||
:autosize="{minRows: 10, maxRows: 20}"
|
:autosize="{minRows: 10, maxRows: 20}"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{{define "qrcodeModal"}}
|
{{define "qrcodeModal"}}
|
||||||
<a-modal id="qrcode-modal" v-model="qrModal.visible" :title="qrModal.title"
|
<a-modal id="qrcode-modal" v-model="qrModal.visible" :title="qrModal.title"
|
||||||
:closable="true"
|
:closable="true"
|
||||||
:class="themeSwitcher.darkCardClass"
|
:class="themeSwitcher.currentTheme"
|
||||||
:footer="null"
|
:footer="null"
|
||||||
width="300px">
|
width="300px">
|
||||||
<a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;">
|
<a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;">
|
||||||
|
@ -13,7 +13,7 @@
|
||||||
</template>
|
</template>
|
||||||
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
|
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
|
||||||
<template v-for="(row, index) in qrModal.qrcodes">
|
<template v-for="(row, index) in qrModal.qrcodes">
|
||||||
<a-tag color="orange" style="margin-top: 10px;display: block;text-align: center;">[[ row.remark ]]</a-tag>
|
<a-tag color="green" style="margin: 10px 0; display: block; text-align: center;">[[ row.remark ]]</a-tag>
|
||||||
<canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" style="width: 100%; height: 100%;"></canvas>
|
<canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" style="width: 100%; height: 100%;"></canvas>
|
||||||
</template>
|
</template>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{{define "textModal"}}
|
{{define "textModal"}}
|
||||||
<a-modal id="text-modal" v-model="txtModal.visible" :title="txtModal.title"
|
<a-modal id="text-modal" v-model="txtModal.visible" :title="txtModal.title"
|
||||||
:closable="true" ok-text='{{ i18n "copy" }}' cancel-text='{{ i18n "close" }}'
|
:closable="true" ok-text='{{ i18n "copy" }}' cancel-text='{{ i18n "close" }}'
|
||||||
:class="themeSwitcher.darkCardClass"
|
:class="themeSwitcher.currentTheme"
|
||||||
:ok-button-props="{attrs:{id:'txt-modal-ok-btn'}}">
|
:ok-button-props="{attrs:{id:'txt-modal-ok-btn'}}">
|
||||||
<a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" type="primary" style="margin-bottom: 10px;"
|
<a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" type="primary" style="margin-bottom: 10px;"
|
||||||
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)"
|
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)"
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
{{define "clientsBulkModal"}}
|
{{define "clientsBulkModal"}}
|
||||||
<a-modal id="client-bulk-modal" v-model="clientsBulkModal.visible" :title="clientsBulkModal.title" @ok="clientsBulkModal.ok"
|
<a-modal id="client-bulk-modal" v-model="clientsBulkModal.visible" :title="clientsBulkModal.title" @ok="clientsBulkModal.ok"
|
||||||
:confirm-loading="clientsBulkModal.confirmLoading" :closable="true" :mask-closable="false"
|
:confirm-loading="clientsBulkModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
:class="themeSwitcher.darkCardClass"
|
:class="themeSwitcher.currentTheme"
|
||||||
:ok-text="clientsBulkModal.okText" cancel-text='{{ i18n "close" }}'>
|
:ok-text="clientsBulkModal.okText" cancel-text='{{ i18n "close" }}'>
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "pages.client.method" }}'>
|
<a-form-item label='{{ i18n "pages.client.method" }}'>
|
||||||
<a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid" style="width: 350px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid" style="width: 350px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option :value="0">Random</a-select-option>
|
<a-select-option :value="0">Random</a-select-option>
|
||||||
<a-select-option :value="1">Random+Prefix</a-select-option>
|
<a-select-option :value="1">Random+Prefix</a-select-option>
|
||||||
<a-select-option :value="2">Random+Prefix+Num</a-select-option>
|
<a-select-option :value="2">Random+Prefix+Num</a-select-option>
|
||||||
|
@ -72,13 +72,13 @@
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
<br>
|
||||||
<a-form-item v-if="clientsBulkModal.inbound.xtls" label="Flow">
|
<a-form-item v-if="clientsBulkModal.inbound.xtls" label="Flow">
|
||||||
<a-select v-model="clientsBulkModal.flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="clientsBulkModal.flow" style="width: 200px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="">{{ i18n "none" }}</a-select-option>
|
<a-select-option value="">{{ i18n "none" }}</a-select-option>
|
||||||
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="clientsBulkModal.inbound.canEnableTlsFlow()" label="Flow" layout="inline">
|
<a-form-item v-if="clientsBulkModal.inbound.canEnableTlsFlow()" label="Flow" layout="inline">
|
||||||
<a-select v-model="clientsBulkModal.flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="clientsBulkModal.flow" style="width: 200px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
{{define "clientsModal"}}
|
{{define "clientsModal"}}
|
||||||
<a-modal id="client-modal" v-model="clientModal.visible" :title="clientModal.title" @ok="clientModal.ok"
|
<a-modal id="client-modal" v-model="clientModal.visible" :title="clientModal.title" @ok="clientModal.ok"
|
||||||
:confirm-loading="clientModal.confirmLoading" :closable="true" :mask-closable="false"
|
:confirm-loading="clientModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
:class="themeSwitcher.darkCardClass"
|
:class="themeSwitcher.currentTheme"
|
||||||
:ok-text="clientModal.okText" cancel-text='{{ i18n "close" }}'>
|
:ok-text="clientModal.okText" cancel-text='{{ i18n "close" }}'>
|
||||||
|
<template v-if="isEdit">
|
||||||
|
<a-tag v-if="isExpiry || isTrafficExhausted" color="red" style="margin-bottom: 10px;display: block;text-align: center;">Account is (Expired|Traffic Ended) And Disabled</a-tag>
|
||||||
|
</template>
|
||||||
{{template "form/client"}}
|
{{template "form/client"}}
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<script>
|
<script>
|
||||||
|
@ -151,7 +154,7 @@
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
||||||
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
||||||
class: themeSwitcher.darkCardClass,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
|
|
|
@ -11,6 +11,10 @@
|
||||||
<a-icon type="setting"></a-icon>
|
<a-icon type="setting"></a-icon>
|
||||||
<span>{{ i18n "menu.settings"}}</span>
|
<span>{{ i18n "menu.settings"}}</span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
|
<a-menu-item key="{{ .base_path }}xui/xray">
|
||||||
|
<a-icon type="tool"></a-icon>
|
||||||
|
<span>{{ i18n "menu.xray"}}</span>
|
||||||
|
</a-menu-item>
|
||||||
<!--<a-menu-item key="{{ .base_path }}panel/clients">-->
|
<!--<a-menu-item key="{{ .base_path }}panel/clients">-->
|
||||||
<!-- <a-icon type="laptop"></a-icon>-->
|
<!-- <a-icon type="laptop"></a-icon>-->
|
||||||
<!-- <span>Client</span>-->
|
<!-- <span>Client</span>-->
|
||||||
|
@ -26,7 +30,7 @@
|
||||||
<a-layout-sider :theme="themeSwitcher.currentTheme" id="sider" collapsible breakpoint="md" collapsed-width="0">
|
<a-layout-sider :theme="themeSwitcher.currentTheme" id="sider" collapsible breakpoint="md" collapsed-width="0">
|
||||||
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
|
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
|
||||||
<a-menu-item mode="inline">
|
<a-menu-item mode="inline">
|
||||||
<a-icon type="bg-colors"></a-icon>
|
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
||||||
<theme-switch />
|
<theme-switch />
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
</a-menu>
|
</a-menu>
|
||||||
|
@ -38,14 +42,14 @@
|
||||||
<a-drawer id="sider-drawer" placement="left" :closable="false"
|
<a-drawer id="sider-drawer" placement="left" :closable="false"
|
||||||
@close="siderDrawer.close()"
|
@close="siderDrawer.close()"
|
||||||
:visible="siderDrawer.visible"
|
:visible="siderDrawer.visible"
|
||||||
:wrap-class-name="themeSwitcher.darkDrawerClass"
|
:wrap-class-name="themeSwitcher.currentTheme"
|
||||||
:wrap-style="{ padding: 0 }">
|
:wrap-style="{ padding: 0 }">
|
||||||
<div class="drawer-handle" @click="siderDrawer.change()" slot="handle">
|
<div class="drawer-handle" @click="siderDrawer.change()" slot="handle">
|
||||||
<a-icon :type="siderDrawer.visible ? 'close' : 'menu-fold'"></a-icon>
|
<a-icon :type="siderDrawer.visible ? 'close' : 'menu-fold'"></a-icon>
|
||||||
</div>
|
</div>
|
||||||
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
|
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
|
||||||
<a-menu-item mode="inline">
|
<a-menu-item mode="inline">
|
||||||
<a-icon type="bg-colors"></a-icon>
|
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
||||||
<theme-switch />
|
<theme-switch />
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
</a-menu>
|
</a-menu>
|
||||||
|
|
|
@ -4,12 +4,12 @@
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
@input="$emit('input', $event.target.value)">
|
@input="$emit('input', $event.target.value)">
|
||||||
<template v-if="icon" #prefix>
|
<template v-if="icon" #prefix>
|
||||||
<a-icon :type="icon" :style="'font-size: 16px;' + themeSwitcher.textStyle" />
|
<a-icon :type="icon" style="font-size: 16px;" />
|
||||||
</template>
|
</template>
|
||||||
<template #addonAfter>
|
<template #addonAfter>
|
||||||
<a-icon :type="showPassword ? 'eye-invisible' : 'eye'"
|
<a-icon :type="showPassword ? 'eye-invisible' : 'eye'"
|
||||||
@click="toggleShowPassword"
|
@click="toggleShowPassword"
|
||||||
:style="'font-size: 16px;' + themeSwitcher.textStyle" />
|
style="font-size: 16px;" />
|
||||||
</template>
|
</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
{{define "component/themeSwitchTemplate"}}
|
{{define "component/themeSwitchTemplate"}}
|
||||||
<template>
|
<template>
|
||||||
<a-switch :default-checked="themeSwitcher.isDarkTheme"
|
<a-switch size="small" :default-checked="themeSwitcher.isDarkTheme"
|
||||||
checked-children="☀"
|
|
||||||
un-checked-children="🌙"
|
|
||||||
@change="themeSwitcher.toggleTheme()">
|
@change="themeSwitcher.toggleTheme()">
|
||||||
</a-switch>
|
</a-switch>
|
||||||
</template>
|
</template>
|
||||||
|
@ -10,39 +8,17 @@
|
||||||
|
|
||||||
{{define "component/themeSwitcher"}}
|
{{define "component/themeSwitcher"}}
|
||||||
<script>
|
<script>
|
||||||
const colors = {
|
|
||||||
dark: {
|
|
||||||
bg: "#242c3a",
|
|
||||||
text: "hsla(0,0%,100%,.65)"
|
|
||||||
},
|
|
||||||
light: {
|
|
||||||
bg: '#f0f2f5',
|
|
||||||
text: "rgba(0, 0, 0, 0.7)",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createThemeSwitcher() {
|
function createThemeSwitcher() {
|
||||||
const isDarkTheme = localStorage.getItem('dark-mode') === 'true';
|
const isDarkTheme = localStorage.getItem('dark-mode') === 'true';
|
||||||
const theme = isDarkTheme ? 'dark' : 'light';
|
const theme = isDarkTheme ? 'dark' : 'light';
|
||||||
return {
|
return {
|
||||||
isDarkTheme,
|
isDarkTheme,
|
||||||
bgStyle: `background: ${colors[theme].bg};`,
|
|
||||||
textStyle: `color: ${colors[theme].text};`,
|
|
||||||
darkClass: isDarkTheme ? 'ant-dark' : '',
|
|
||||||
darkCardClass: isDarkTheme ? 'ant-card-dark' : '',
|
|
||||||
darkDrawerClass: isDarkTheme ? 'ant-drawer-dark' : '',
|
|
||||||
get currentTheme() {
|
get currentTheme() {
|
||||||
return this.isDarkTheme ? 'dark' : 'light';
|
return this.isDarkTheme ? 'dark' : 'light';
|
||||||
},
|
},
|
||||||
toggleTheme() {
|
toggleTheme() {
|
||||||
this.isDarkTheme = !this.isDarkTheme;
|
this.isDarkTheme = !this.isDarkTheme;
|
||||||
this.theme = this.isDarkTheme ? 'dark' : 'light';
|
|
||||||
localStorage.setItem('dark-mode', this.isDarkTheme);
|
localStorage.setItem('dark-mode', this.isDarkTheme);
|
||||||
this.bgStyle = `background: ${colors[this.theme].bg};`;
|
|
||||||
this.textStyle = `color: ${colors[this.theme].text};`;
|
|
||||||
this.darkClass = this.isDarkTheme ? 'ant-dark' : '';
|
|
||||||
this.darkCardClass = this.isDarkTheme ? 'ant-card-dark' : '';
|
|
||||||
this.darkDrawerClass = this.isDarkTheme ? 'ant-drawer-dark' : '';
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
{{define "form/client"}}
|
{{define "form/client"}}
|
||||||
<a-form layout="inline" v-if="client">
|
<a-form layout="inline" v-if="client">
|
||||||
<template v-if="isEdit">
|
|
||||||
<a-tag v-if="isExpiry || isTrafficExhausted" color="red" style="margin-bottom: 10px;display: block;text-align: center;">
|
|
||||||
Account is (Expired|Traffic Ended) And Disabled
|
|
||||||
</a-tag>
|
|
||||||
</template>
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.enable" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.enable" }}'>
|
||||||
<a-switch v-model="client.enable"></a-switch>
|
<a-switch v-model="client.enable"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
@ -97,13 +92,13 @@
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
<br>
|
||||||
<a-form-item v-if="inbound.xtls" label="Flow">
|
<a-form-item v-if="inbound.xtls" label="Flow">
|
||||||
<a-select v-model="client.flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="client.flow" style="width: 200px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="">{{ i18n "none" }}</a-select-option>
|
<a-select-option value="">{{ i18n "none" }}</a-select-option>
|
||||||
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-else-if="inbound.canEnableTlsFlow()" label="Flow">
|
<a-form-item v-else-if="inbound.canEnableTlsFlow()" label="Flow">
|
||||||
<a-select v-model="client.flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="client.flow" style="width: 200px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
|
@ -136,10 +131,10 @@
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
<br>
|
||||||
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
||||||
<a-switch v-model="clientModal.delayedStart" @click="client._expiryTime=0"></a-switch>
|
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
<br>
|
||||||
<a-form-item label='{{ i18n "pages.client.expireDays" }}' v-if="clientModal.delayedStart">
|
<a-form-item label='{{ i18n "pages.client.expireDays" }}' v-if="delayedStart">
|
||||||
<a-input-number v-model="delayedExpireDays" :min="0"></a-input-number>
|
<a-input-number v-model="delayedExpireDays" :min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-else>
|
<a-form-item v-else>
|
||||||
|
@ -153,9 +148,21 @@
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
|
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
|
||||||
<a-tag color="red" v-if="isExpiry">Expired</a-tag>
|
<a-tag color="red" v-if="isEdit && isExpiry">Expired</a-tag>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-if="client.expiryTime != 0">
|
||||||
|
<span slot="label">
|
||||||
|
<span>{{ i18n "pages.client.renew" }}</span>
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.client.renewDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
<a-input-number v-model.number="client.reset" :min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
|
@ -9,7 +9,7 @@
|
||||||
<a-input v-model.trim="dbInbound.remark"></a-input>
|
<a-input v-model.trim="dbInbound.remark"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "protocol" }}'>
|
<a-form-item label='{{ i18n "protocol" }}'>
|
||||||
<a-select v-model="inbound.protocol" style="width: 160px;" :disabled="isEdit" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="inbound.protocol" style="width: 160px;" :disabled="isEdit" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option>
|
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
@ -53,7 +53,7 @@
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
v-model="dbInbound._expiryTime" style="width: 250px;"></a-date-picker>
|
v-model="dbInbound._expiryTime" style="width: 250px;"></a-date-picker>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
<br>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.network"}}'>
|
<a-form-item label='{{ i18n "pages.inbounds.network"}}'>
|
||||||
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="tcp,udp">TCP+UDP</a-select-option>
|
<a-select-option value="tcp,udp">TCP+UDP</a-select-option>
|
||||||
<a-select-option value="tcp">TCP</a-select-option>
|
<a-select-option value="tcp">TCP</a-select-option>
|
||||||
<a-select-option value="udp">UDP</a-select-option>
|
<a-select-option value="udp">UDP</a-select-option>
|
||||||
|
|
|
@ -3,94 +3,7 @@
|
||||||
<template v-if="inbound.isSSMultiUser">
|
<template v-if="inbound.isSSMultiUser">
|
||||||
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.shadowsockses.slice(0,1)" v-if="!isEdit">
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.shadowsockses.slice(0,1)" v-if="!isEdit">
|
||||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
<a-form-item>
|
{{template "form/client"}}
|
||||||
<span slot="label">
|
|
||||||
<span>{{ i18n "pages.inbounds.email" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(8)" type="sync"> </a-icon>
|
|
||||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="Password">
|
|
||||||
<a-icon @click="client.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
|
|
||||||
<a-input v-model.trim="client.password" style="width: 250px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-if="client.email && app.subSettings.enable">
|
|
||||||
<span slot="label">
|
|
||||||
Subscription
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"> </a-icon>
|
|
||||||
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-if="client.email && app.tgBotEnable">
|
|
||||||
<span slot="label">
|
|
||||||
Telegram ID
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-input v-model.trim="client.tgId"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item>
|
|
||||||
<span slot="label">
|
|
||||||
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.IPLimitDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-input-number v-model="client.limitIp" min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
<br>
|
|
||||||
<a-form-item>
|
|
||||||
<span slot="label">
|
|
||||||
<span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
<br>
|
|
||||||
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
|
||||||
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
|
||||||
</a-form-item>
|
|
||||||
<br>
|
|
||||||
<a-form-item v-if="delayedStart" label='{{ i18n "pages.client.expireDays" }}'>
|
|
||||||
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-else>
|
|
||||||
<span slot="label">
|
|
||||||
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
|
||||||
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
|
|
||||||
</a-form-item>
|
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
<a-collapse v-else>
|
<a-collapse v-else>
|
||||||
|
@ -111,7 +24,7 @@
|
||||||
</a-form>
|
</a-form>
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "encryption" }}'>
|
<a-form-item label='{{ i18n "encryption" }}'>
|
||||||
<a-select v-model="inbound.settings.method" style="width: 250px;" :dropdown-class-name="themeSwitcher.darkCardClass" @change="SSMethodChange">
|
<a-select v-model="inbound.settings.method" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme" @change="SSMethodChange">
|
||||||
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
|
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
@ -120,7 +33,7 @@
|
||||||
<a-input v-model.trim="inbound.settings.password" style="width: 250px;"></a-input>
|
<a-input v-model.trim="inbound.settings.password" style="width: 250px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.network" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.network" }}'>
|
||||||
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="tcp,udp">TCP+UDP</a-select-option>
|
<a-select-option value="tcp,udp">TCP+UDP</a-select-option>
|
||||||
<a-select-option value="tcp">TCP</a-select-option>
|
<a-select-option value="tcp">TCP</a-select-option>
|
||||||
<a-select-option value="udp">UDP</a-select-option>
|
<a-select-option value="udp">UDP</a-select-option>
|
||||||
|
|
|
@ -2,100 +2,7 @@
|
||||||
<a-form layout="inline" style="padding: 10px 0px;">
|
<a-form layout="inline" style="padding: 10px 0px;">
|
||||||
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.trojans.slice(0,1)" v-if="!isEdit">
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.trojans.slice(0,1)" v-if="!isEdit">
|
||||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
<a-form-item>
|
{{template "form/client"}}
|
||||||
<span slot="label">
|
|
||||||
<span>{{ i18n "pages.inbounds.email" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(8)" type="sync"> </a-icon>
|
|
||||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="Password">
|
|
||||||
<a-icon @click="client.password = RandomUtil.randomSeq(10)" type="sync"> </a-icon>
|
|
||||||
<a-input v-model.trim="client.password" style="width: 150px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-if="client.email && app.subSettings.enable">
|
|
||||||
<span slot="label">
|
|
||||||
Subscription
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"> </a-icon>
|
|
||||||
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-if="client.email && app.tgBotEnable">
|
|
||||||
<span slot="label">
|
|
||||||
Telegram ID
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-input v-model.trim="client.tgId"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item>
|
|
||||||
<span slot="label">
|
|
||||||
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.IPLimitDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-input-number v-model="client.limitIp" min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
<br>
|
|
||||||
<a-form-item v-if="inbound.xtls" label="Flow">
|
|
||||||
<a-select v-model="client.flow" style="width: 150px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
|
||||||
<a-select-option value="">{{ i18n "none" }}</a-select-option>
|
|
||||||
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item>
|
|
||||||
<span slot="label">
|
|
||||||
<span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
<br>
|
|
||||||
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
|
||||||
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
|
||||||
</a-form-item>
|
|
||||||
<br>
|
|
||||||
<a-form-item v-if="delayedStart" label='{{ i18n "pages.client.expireDays" }}'>
|
|
||||||
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-else>
|
|
||||||
<span slot="label">
|
|
||||||
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
|
||||||
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
|
|
||||||
</a-form-item>
|
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
<a-collapse v-else>
|
<a-collapse v-else>
|
||||||
|
@ -126,7 +33,7 @@
|
||||||
|
|
||||||
<!-- trojan fallbacks -->
|
<!-- trojan fallbacks -->
|
||||||
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" layout="inline">
|
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" layout="inline">
|
||||||
<a-divider>
|
<a-divider style="margin:0;">
|
||||||
fallback[[ index + 1 ]]
|
fallback[[ index + 1 ]]
|
||||||
<a-icon type="delete" @click="() => inbound.settings.delTrojanFallback(index)"
|
<a-icon type="delete" @click="() => inbound.settings.delTrojanFallback(index)"
|
||||||
style="color: rgb(255, 77, 79);cursor: pointer;"/>
|
style="color: rgb(255, 77, 79);cursor: pointer;"/>
|
||||||
|
@ -146,7 +53,7 @@
|
||||||
<a-form-item label="xVer">
|
<a-form-item label="xVer">
|
||||||
<a-input-number v-model="fallback.xver"></a-input-number>
|
<a-input-number v-model="fallback.xver"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-divider v-if="inbound.settings.fallbacks.length - 1 === index"/>
|
|
||||||
</a-form>
|
</a-form>
|
||||||
|
<a-divider style="margin:0;"></a-divider>
|
||||||
</template>
|
</template>
|
||||||
{{end}}
|
{{end}}
|
|
@ -2,106 +2,7 @@
|
||||||
<a-form layout="inline" style="padding: 10px 0px;">
|
<a-form layout="inline" style="padding: 10px 0px;">
|
||||||
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vlesses.slice(0,1)" v-if="!isEdit">
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vlesses.slice(0,1)" v-if="!isEdit">
|
||||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
<a-form-item>
|
{{template "form/client"}}
|
||||||
<span slot="label">
|
|
||||||
<span>{{ i18n "pages.inbounds.email" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(8)" type="sync"> </a-icon>
|
|
||||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="ID">
|
|
||||||
<a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"> </a-icon>
|
|
||||||
<a-input v-model.trim="client.id" style="width: 300px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-if="client.email && app.subSettings.enable">
|
|
||||||
<span slot="label">
|
|
||||||
Subscription
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"> </a-icon>
|
|
||||||
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-if="client.email && app.tgBotEnable">
|
|
||||||
<span slot="label">
|
|
||||||
Telegram ID
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-input v-model.trim="client.tgId"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item>
|
|
||||||
<span slot="label">
|
|
||||||
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.IPLimitDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-input-number v-model="client.limitIp" min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
<br>
|
|
||||||
<a-form-item v-if="inbound.xtls" label="Flow">
|
|
||||||
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
|
||||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
|
||||||
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-else-if="inbound.canEnableTlsFlow()" label="Flow">
|
|
||||||
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
|
||||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
|
||||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item>
|
|
||||||
<span slot="label">
|
|
||||||
<span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
<br>
|
|
||||||
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
|
||||||
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
|
||||||
</a-form-item>
|
|
||||||
<br>
|
|
||||||
<a-form-item v-if="delayedStart" label='{{ i18n "pages.client.expireDays" }}'>
|
|
||||||
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-else>
|
|
||||||
<span slot="label">
|
|
||||||
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
|
||||||
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
|
|
||||||
</a-form-item>
|
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
<a-collapse v-else>
|
<a-collapse v-else>
|
||||||
|
@ -134,7 +35,7 @@
|
||||||
|
|
||||||
<!-- vless fallbacks -->
|
<!-- vless fallbacks -->
|
||||||
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" layout="inline">
|
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" layout="inline">
|
||||||
<a-divider>
|
<a-divider style="margin:0;">
|
||||||
fallback[[ index + 1 ]]
|
fallback[[ index + 1 ]]
|
||||||
<a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
|
<a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
|
||||||
style="color: rgb(255, 77, 79);cursor: pointer;"/>
|
style="color: rgb(255, 77, 79);cursor: pointer;"/>
|
||||||
|
@ -154,7 +55,6 @@
|
||||||
<a-form-item label="xVer">
|
<a-form-item label="xVer">
|
||||||
<a-input-number v-model="fallback.xver"></a-input-number>
|
<a-input-number v-model="fallback.xver"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-divider v-if="inbound.settings.fallbacks.length - 1 === index"/>
|
|
||||||
</a-form>
|
</a-form>
|
||||||
</template>
|
</template>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -2,95 +2,7 @@
|
||||||
<a-form layout="inline" style="padding: 10px 0px;">
|
<a-form layout="inline" style="padding: 10px 0px;">
|
||||||
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vmesses.slice(0,1)" v-if="!isEdit">
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vmesses.slice(0,1)" v-if="!isEdit">
|
||||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
<a-form-item>
|
{{template "form/client"}}
|
||||||
<span slot="label">
|
|
||||||
<span>{{ i18n "pages.inbounds.email" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(8)" type="sync"> </a-icon>
|
|
||||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<br>
|
|
||||||
<a-form-item label="ID">
|
|
||||||
<a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"> </a-icon>
|
|
||||||
<a-input v-model.trim="client.id" style="width: 300px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-if="client.email && app.subSettings.enable">
|
|
||||||
<span slot="label">
|
|
||||||
Subscription
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"> </a-icon>
|
|
||||||
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-if="client.email && app.tgBotEnable">
|
|
||||||
<span slot="label">
|
|
||||||
Telegram ID
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-input v-model.trim="client.tgId"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item>
|
|
||||||
<span slot="label">
|
|
||||||
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.IPLimitDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-input-number v-model="client.limitIp" min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
<br>
|
|
||||||
<a-form-item>
|
|
||||||
<span slot="label">
|
|
||||||
<span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
<br>
|
|
||||||
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
|
||||||
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
|
||||||
</a-form-item>
|
|
||||||
<br>
|
|
||||||
<a-form-item v-if="delayedStart" label='{{ i18n "pages.client.expireDays" }}'>
|
|
||||||
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-else>
|
|
||||||
<span slot="label">
|
|
||||||
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
|
||||||
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
|
|
||||||
</a-form-item>
|
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
<a-collapse v-else>
|
<a-collapse v-else>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{{define "form/streamKCP"}}
|
{{define "form/streamKCP"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||||
<a-select v-model="inbound.stream.kcp.type" style="width: 280px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="inbound.stream.kcp.type" style="width: 280px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="none">None (Not Camouflage)</a-select-option>
|
<a-select-option value="none">None (Not Camouflage)</a-select-option>
|
||||||
<a-select-option value="srtp">SRTP (Camouflage Video Call)</a-select-option>
|
<a-select-option value="srtp">SRTP (Camouflage Video Call)</a-select-option>
|
||||||
<a-select-option value="utp">UTP (Camouflage BT Download)</a-select-option>
|
<a-select-option value="utp">UTP (Camouflage BT Download)</a-select-option>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{{define "form/streamQUIC"}}
|
{{define "form/streamQUIC"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
|
||||||
<a-select v-model="inbound.stream.quic.security" style="width: 165px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="inbound.stream.quic.security" style="width: 165px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="none">none</a-select-option>
|
<a-select-option value="none">none</a-select-option>
|
||||||
<a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option>
|
<a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option>
|
||||||
<a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option>
|
<a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option>
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
<a-input v-model.trim="inbound.stream.quic.key" style="width: 150px;"></a-input>
|
<a-input v-model.trim="inbound.stream.quic.key" style="width: 150px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||||
<a-select v-model="inbound.stream.quic.type" style="width: 280px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="inbound.stream.quic.type" style="width: 280px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="none">none (not camouflage)</a-select-option>
|
<a-select-option value="none">none (not camouflage)</a-select-option>
|
||||||
<a-select-option value="srtp">srtp (camouflage video call)</a-select-option>
|
<a-select-option value="srtp">srtp (camouflage video call)</a-select-option>
|
||||||
<a-select-option value="utp">utp (camouflage BT download)</a-select-option>
|
<a-select-option value="utp">utp (camouflage BT download)</a-select-option>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<!-- select stream network -->
|
<!-- select stream network -->
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "transmission" }}'>
|
<a-form-item label='{{ i18n "transmission" }}'>
|
||||||
<a-select v-model="inbound.stream.network" @change="streamNetworkChange" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="inbound.stream.network" @change="streamNetworkChange" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="tcp">TCP</a-select-option>
|
<a-select-option value="tcp">TCP</a-select-option>
|
||||||
<a-select-option value="kcp">KCP</a-select-option>
|
<a-select-option value="kcp">KCP</a-select-option>
|
||||||
<a-select-option value="ws">WS</a-select-option>
|
<a-select-option value="ws">WS</a-select-option>
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-select v-model="inbound.stream.sockopt.tproxy" style="width: 250px;"
|
<a-select v-model="inbound.stream.sockopt.tproxy" style="width: 250px;"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="off">OFF</a-select-option>
|
<a-select-option value="off">OFF</a-select-option>
|
||||||
<a-select-option value="redirect">Redirect</a-select-option>
|
<a-select-option value="redirect">Redirect</a-select-option>
|
||||||
<a-select-option value="tproxy">T-Proxy</a-select-option>
|
<a-select-option value="tproxy">T-Proxy</a-select-option>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{{define "form/tlsSettings"}}
|
{{define "form/tlsSettings"}}
|
||||||
<!-- tls enable -->
|
<!-- tls enable -->
|
||||||
<a-form layout="inline" v-if="inbound.canSetTls()">
|
<a-form layout="inline" v-if="inbound.canSetTls()">
|
||||||
|
<a-divider style="margin:0;"></a-divider>
|
||||||
<a-form-item v-if="inbound.canEnableTls()" label="TLS">
|
<a-form-item v-if="inbound.canEnableTls()" label="TLS">
|
||||||
<a-switch v-model="inbound.tls">
|
<a-switch v-model="inbound.tls">
|
||||||
</a-switch>
|
</a-switch>
|
||||||
|
@ -54,27 +55,27 @@
|
||||||
<a-input v-model.trim="inbound.stream.tls.server" style="width: 250px"></a-input>
|
<a-input v-model.trim="inbound.stream.tls.server" style="width: 250px"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="CipherSuites">
|
<a-form-item label="CipherSuites">
|
||||||
<a-select v-model="inbound.stream.tls.cipherSuites" style="width: 300px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="inbound.stream.tls.cipherSuites" style="width: 300px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="">auto</a-select-option>
|
<a-select-option value="">auto</a-select-option>
|
||||||
<a-select-option v-for="key in TLS_CIPHER_OPTION" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key,value in TLS_CIPHER_OPTION" :value="key">[[ value ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="MinVersion">
|
<a-form-item label="Min/Max Version">
|
||||||
<a-select v-model="inbound.stream.tls.minVersion" style="width: 60px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-input-group compact>
|
||||||
|
<a-select style="width: 60px" v-model="inbound.stream.tls.minVersion" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
<a-select style="width: 60px" v-model="inbound.stream.tls.maxVersion" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-form-item label="MaxVersion">
|
|
||||||
<a-select v-model="inbound.stream.tls.maxVersion" style="width: 60px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
|
||||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
|
</a-input-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="SNI" placeholder="Server Name Indication">
|
<a-form-item label="SNI" placeholder="Server Name Indication">
|
||||||
<a-input v-model.trim="inbound.stream.tls.settings.serverName" style="width: 250px"></a-input>
|
<a-input v-model.trim="inbound.stream.tls.settings.serverName" style="width: 250px"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="uTLS">
|
<a-form-item label="uTLS">
|
||||||
<a-select v-model="inbound.stream.tls.settings.fingerprint"
|
<a-select v-model="inbound.stream.tls.settings.fingerprint"
|
||||||
style="width: 170px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
style="width: 170px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value=''>None</a-select-option>
|
<a-select-option value=''>None</a-select-option>
|
||||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
|
@ -83,7 +84,7 @@
|
||||||
<a-select
|
<a-select
|
||||||
mode="multiple"
|
mode="multiple"
|
||||||
style="width: 250px"
|
style="width: 250px"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
v-model="inbound.stream.tls.alpn">
|
v-model="inbound.stream.tls.alpn">
|
||||||
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
|
@ -185,7 +186,7 @@
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="uTLS">
|
<a-form-item label="uTLS">
|
||||||
<a-select v-model="inbound.stream.reality.settings.fingerprint"
|
<a-select v-model="inbound.stream.reality.settings.fingerprint"
|
||||||
style="width: 135px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
style="width: 135px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
|
@ -2,34 +2,66 @@
|
||||||
<template slot="actions" slot-scope="text, client, index">
|
<template slot="actions" slot-scope="text, client, index">
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">{{ i18n "qrCode" }}</template>
|
<template slot="title">{{ i18n "qrCode" }}</template>
|
||||||
<a-icon style="font-size: 24px;" type="qrcode" v-if="record.hasLink()" @click="showQrcode(record,index);"></a-icon>
|
<a-icon style="font-size: 24px;" class="normal-icon" type="qrcode" v-if="record.hasLink()" @click="showQrcode(record,index);"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">{{ i18n "pages.client.edit" }}</template>
|
<template slot="title">{{ i18n "pages.client.edit" }}</template>
|
||||||
<a-icon style="font-size: 24px;" type="edit" @click="openEditClient(record.id,client);"></a-icon>
|
<a-icon style="font-size: 24px;" class="normal-icon" type="edit" @click="openEditClient(record.id,client);"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">{{ i18n "info" }}</template>
|
<template slot="title">{{ i18n "info" }}</template>
|
||||||
<a-icon style="font-size: 24px;" type="info-circle" @click="showInfo(record,index);"></a-icon>
|
<a-icon style="font-size: 24px;" class="normal-icon" type="info-circle" @click="showInfo(record,index);"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
|
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
|
||||||
<a-icon style="font-size: 24px;" type="retweet" @click="resetClientTraffic(client,record.id)" v-if="client.email.length > 0"></a-icon>
|
<a-popconfirm @confirm="resetClientTraffic(client,record.id,false)"
|
||||||
|
title='{{ i18n "pages.inbounds.resetTrafficContent"}}'
|
||||||
|
:overlay-class-name="themeSwitcher.currentTheme"
|
||||||
|
ok-text='{{ i18n "reset"}}'
|
||||||
|
cancel-text='{{ i18n "cancel"}}'>
|
||||||
|
<a-icon slot="icon" type="question-circle-o" style="color: blue"></a-icon>
|
||||||
|
<a-icon style="font-size: 24px;" class="normal-icon" type="retweet" v-if="client.email.length > 0"></a-icon>
|
||||||
|
</a-popconfirm>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title"><span style="color: #FF4D4F"> {{ i18n "delete"}}</span></template>
|
<template slot="title"><span style="color: #FF4D4F"> {{ i18n "delete"}}</span></template>
|
||||||
<a-icon style="font-size: 24px;" type="delete" v-if="isRemovable(record.id)" @click="delClient(record.id,client)"></a-icon>
|
<a-popconfirm @confirm="delClient(record.id,client,false)"
|
||||||
|
title='{{ i18n "pages.inbounds.deleteClientContent"}}'
|
||||||
|
:overlay-class-name="themeSwitcher.currentTheme"
|
||||||
|
ok-text='{{ i18n "delete"}}'
|
||||||
|
ok-type="danger"
|
||||||
|
cancel-text='{{ i18n "cancel"}}'>
|
||||||
|
<a-icon slot="icon" type="question-circle-o" style="color: #e04141"></a-icon>
|
||||||
|
<a-icon style="font-size: 24px" class="delete-icon" type="delete" v-if="isRemovable(record.id)"></a-icon>
|
||||||
|
</a-popconfirm>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<template slot="enable" slot-scope="text, client, index">
|
<template slot="enable" slot-scope="text, client, index">
|
||||||
<a-switch v-model="client.enable" @change="switchEnableClient(record.id,client)"></a-switch>
|
<a-switch v-model="client.enable" @change="switchEnableClient(record.id,client)"></a-switch>
|
||||||
</template>
|
</template>
|
||||||
|
<template slot="online" slot-scope="text, client, index">
|
||||||
|
<template v-if="isClientOnline(client.email)">
|
||||||
|
<a-tag color="green">{{ i18n "online" }}</a-tag>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-tag>{{ i18n "offline" }}</a-tag>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
<template slot="client" slot-scope="text, client">
|
<template slot="client" slot-scope="text, client">
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<template v-if="!isClientEnabled(record, client.email)">{{ i18n "depleted" }}</template>
|
||||||
|
<template v-else-if="!client.enable">{{ i18n "disabled" }}</template>
|
||||||
|
<template v-else-if="isClientOnline(client.email)">{{ i18n "online" }}</template>
|
||||||
|
</template>
|
||||||
|
<a-badge :class="isClientOnline(client.email)? 'online-animation' : ''" :color="client.enable ? statsExpColor(record, client.email) : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'">
|
||||||
|
</a-badge>
|
||||||
|
</a-tooltip>
|
||||||
[[ client.email ]]
|
[[ client.email ]]
|
||||||
<a-tag v-if="!isClientEnabled(record, client.email)" color="red">{{ i18n "depleted" }}</a-tag>
|
<a-tag v-if="!isClientEnabled(record, client.email)" color="red">{{ i18n "depleted" }}</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<template slot="traffic" slot-scope="text, client">
|
<template slot="traffic" slot-scope="text, client">
|
||||||
<a-popover :overlay-class-name="themeSwitcher.darkClass">
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content" v-if="client.email">
|
<template slot="content" v-if="client.email">
|
||||||
<table cellpadding="2" width="100%">
|
<table cellpadding="2" width="100%">
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -38,28 +70,200 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="client.totalGB > 0">
|
<tr v-if="client.totalGB > 0">
|
||||||
<td>{{ i18n "remained" }}</td>
|
<td>{{ i18n "remained" }}</td>
|
||||||
<td>[[ sizeFormat(client.totalGB - getUpStats(record, client.email) - getDownStats(record, client.email)) ]]</td>
|
<td>[[ sizeFormat(getRemStats(record, client.email)) ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</template>
|
</template>
|
||||||
<a-tag :color="statsColor(record, client.email)">
|
<table>
|
||||||
[[ sizeFormat(getUpStats(record, client.email) + getDownStats(record, client.email)) ]] /
|
<tr>
|
||||||
<template v-if="client.totalGB > 0">[[client._totalGB]]GB</template>
|
<td width="80px" style="margin:0; text-align: right;font-size: 1em;">
|
||||||
<template v-else>
|
[[ sizeFormat(getSumStats(record, client.email)) ]]
|
||||||
<svg style="fill: currentColor; height: 10px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z"/></svg>
|
</td>
|
||||||
</template>
|
<td width="120px" v-if="!client.enable">
|
||||||
</a-tag>
|
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? 'rgb(72 84 105)' : '#bcbcbc'"
|
||||||
|
:show-info="false"
|
||||||
|
:percent="statsProgress(record, client.email)"/>
|
||||||
|
</td>
|
||||||
|
<td width="120px" v-else-if="client.totalGB > 0">
|
||||||
|
<a-progress :stroke-color="statsColor(record, client.email)"
|
||||||
|
:show-info="false"
|
||||||
|
:status="isClientOnline(client.email)? 'active' : isClientEnabled(record, client.email)? 'exception' : ''"
|
||||||
|
:percent="statsProgress(record, client.email)"/>
|
||||||
|
</td>
|
||||||
|
<td width="120px" v-else class="infinite-bar">
|
||||||
|
<a-progress
|
||||||
|
:show-info="false"
|
||||||
|
:status="isClientOnline(client.email)? 'active' : ''"
|
||||||
|
:percent="100"></a-progress>
|
||||||
|
</td>
|
||||||
|
<td width="60px">
|
||||||
|
<template v-if="client.totalGB > 0">[[ client._totalGB + "GB" ]]</template>
|
||||||
|
<span v-else style="font-weight: 100;font-size: 14pt;">∞</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
</template>
|
</template>
|
||||||
<template slot="expiryTime" slot-scope="text, client, index">
|
<template slot="expiryTime" slot-scope="text, client, index">
|
||||||
<template v-if="client.expiryTime > 0">
|
<template v-if="client.expiryTime !=0 && client.reset >0">
|
||||||
<a-tag :color="usageColor(new Date().getTime(), app.expireDiff, client.expiryTime)">
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
[[ DateUtil.formatMillis(client._expiryTime) ]]
|
<template slot="content">
|
||||||
</a-tag>
|
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}</span>
|
||||||
|
<span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span>
|
||||||
</template>
|
</template>
|
||||||
<a-tag v-else-if="client.expiryTime < 0" color="cyan">
|
<table>
|
||||||
[[ client._expiryTime ]] {{ i18n "pages.client.days" }}
|
<tr>
|
||||||
|
<td width="80px" style="margin:0; text-align: right;font-size: 1em;">
|
||||||
|
[[ remainedDays(client.expiryTime) ]]
|
||||||
|
</td>
|
||||||
|
<td width="120px" class="infinite-bar">
|
||||||
|
<a-progress :show-info="false"
|
||||||
|
:status="isClientOnline(client.email)? 'active' : isClientEnabled(record, client.email)? 'exception' : ''"
|
||||||
|
:percent="expireProgress(client.expiryTime, client.reset)"/>
|
||||||
|
</td>
|
||||||
|
<td width="60px">[[ client.reset + "d" ]]</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</a-popover>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-popover v-if="client.expiryTime != 0" :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}</span>
|
||||||
|
<span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span>
|
||||||
|
</template>
|
||||||
|
<a-tag style="min-width: 50px; border: none;" :color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)">
|
||||||
|
[[ remainedDays(client.expiryTime) ]]
|
||||||
</a-tag>
|
</a-tag>
|
||||||
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
</a-popover>
|
||||||
|
<a-tag v-else :color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)" style="border: 0;" class="infinite-tag">∞</a-tag>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template slot="actionMenu" slot-scope="text, client, index">
|
||||||
|
<a-dropdown :trigger="['click']">
|
||||||
|
<a-icon @click="e => e.preventDefault()" type="ellipsis" style="font-size: 20px;"></a-icon>
|
||||||
|
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
||||||
|
<a-menu-item v-if="record.hasLink()" @click="showQrcode(record.id,client);">
|
||||||
|
<a-icon style="font-size: 14px;" type="qrcode"></a-icon>
|
||||||
|
{{ i18n "qrCode" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item @click="openEditClient(record.id,client);">
|
||||||
|
<a-icon style="font-size: 14px;" type="edit"></a-icon>
|
||||||
|
{{ i18n "pages.client.edit" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item @click="showInfo(record.id,client);">
|
||||||
|
<a-icon style="font-size: 14px;" type="info-circle"></a-icon>
|
||||||
|
{{ i18n "info" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item @click="resetClientTraffic(client,record.id)" v-if="client.email.length > 0">
|
||||||
|
<a-icon style="font-size: 14px;" type="retweet"></a-icon>
|
||||||
|
{{ i18n "pages.inbounds.resetTraffic" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item v-if="isRemovable(record.id)" @click="delClient(record.id,client)">
|
||||||
|
<a-icon style="font-size: 14px;" type="delete"></a-icon>
|
||||||
|
<span style="color: #FF4D4F"> {{ i18n "delete"}}</span>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item>
|
||||||
|
<a-switch v-model="client.enable" size="small" @change="switchEnableClient(record.id,client)">
|
||||||
|
</a-switch>
|
||||||
|
{{ i18n "enable"}}
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</a-dropdown>
|
||||||
|
</template>
|
||||||
|
<template slot="info" slot-scope="text, client, index">
|
||||||
|
<a-popover placement="bottomRight" :overlay-class-name="themeSwitcher.currentTheme" trigger="click">
|
||||||
|
<template slot="content">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td colspan="3" style="text-align: center;">{{ i18n "pages.inbounds.traffic" }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="80px" style="margin:0; text-align: right;font-size: 1em;">
|
||||||
|
[[ sizeFormat(getUpStats(record, client.email) + getDownStats(record, client.email)) ]]
|
||||||
|
</td>
|
||||||
|
<td width="120px" v-if="!client.enable">
|
||||||
|
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? 'rgb(72 84 105)' : '#bcbcbc'"
|
||||||
|
:show-info="false"
|
||||||
|
:percent="statsProgress(record, client.email)"/>
|
||||||
|
</td>
|
||||||
|
<td width="120px" v-else-if="client.totalGB > 0">
|
||||||
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content" v-if="client.email">
|
||||||
|
<table cellpadding="2" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td>↑[[ sizeFormat(getUpStats(record, client.email)) ]]</td>
|
||||||
|
<td>↓[[ sizeFormat(getDownStats(record, client.email)) ]]</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "remained" }}</td>
|
||||||
|
<td>[[ sizeFormat(getRemStats(record, client.email)) ]]</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
<a-progress :stroke-color="statsColor(record, client.email)"
|
||||||
|
:show-info="false"
|
||||||
|
:status="isClientOnline(client.email)? 'active' : isClientEnabled(record, client.email)? 'exception' : ''"
|
||||||
|
:percent="statsProgress(record, client.email)"/>
|
||||||
|
</a-popover>
|
||||||
|
</td>
|
||||||
|
<td width="120px" v-else class="infinite-bar">
|
||||||
|
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? '#2c1e32':'#F2EAF1'"
|
||||||
|
:show-info="false"
|
||||||
|
:status="isClientOnline(client.email)? 'active' : ''"
|
||||||
|
:percent="100"></a-progress>
|
||||||
|
</td>
|
||||||
|
<td width="80px">
|
||||||
|
<template v-if="client.totalGB > 0">[[ client._totalGB + "GB" ]]</template>
|
||||||
|
<span v-else style="font-weight: 100;font-size: 14pt;">∞</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="3" style="text-align: center;">
|
||||||
|
<a-divider style="margin: 0; border-collapse: separate;"></a-divider>
|
||||||
|
{{ i18n "pages.inbounds.expireDate" }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<template v-if="client.expiryTime !=0 && client.reset >0">
|
||||||
|
<td width="80px" style="margin:0; text-align: right;font-size: 1em;">
|
||||||
|
[[ remainedDays(client.expiryTime) ]]
|
||||||
|
</td>
|
||||||
|
<td width="120px" class="infinite-bar">
|
||||||
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}</span>
|
||||||
|
<span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span>
|
||||||
|
</template>
|
||||||
|
<a-progress :show-info="false"
|
||||||
|
:status="isClientOnline(client.email)? 'active' : isClientEnabled(record, client.email)? 'exception' : ''"
|
||||||
|
:percent="expireProgress(client.expiryTime, client.reset)"/>
|
||||||
|
</a-popover>
|
||||||
|
</td>
|
||||||
|
<td width="60px">[[ client.reset + "d" ]]</td>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<td colspan="3" style="text-align: center;">
|
||||||
|
<a-popover v-if="client.expiryTime != 0" :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}</span>
|
||||||
|
<span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span>
|
||||||
|
</template>
|
||||||
|
<a-tag style="min-width: 50px; border: none;"
|
||||||
|
:color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)">
|
||||||
|
[[ remainedDays(client.expiryTime) ]]
|
||||||
|
</a-tag>
|
||||||
|
</a-popover>
|
||||||
|
<a-tag v-else :color="client.enable ? 'purple' : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'" class="infinite-tag">∞</a-tag>
|
||||||
|
</template>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
<a-badge>
|
||||||
|
<a-icon v-if="!client.enable" slot="count" type="pause-circle" :style="'color: ' + themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'"></a-icon>
|
||||||
|
<a-button shape="round" size="small" style="font-size: 14px; padding: 0 10px;"><a-icon type="solution"></a-icon></a-button>
|
||||||
|
</a-badge>
|
||||||
|
</a-popover>
|
||||||
</template>
|
</template>
|
||||||
{{end}}
|
{{end}}
|
|
@ -3,7 +3,7 @@
|
||||||
v-model="infoModal.visible" title='{{ i18n "pages.inbounds.details"}}'
|
v-model="infoModal.visible" title='{{ i18n "pages.inbounds.details"}}'
|
||||||
:closable="true"
|
:closable="true"
|
||||||
:mask-closable="true"
|
:mask-closable="true"
|
||||||
:class="themeSwitcher.darkCardClass"
|
:class="themeSwitcher.currentTheme"
|
||||||
:footer="null"
|
:footer="null"
|
||||||
width="600px"
|
width="600px"
|
||||||
>
|
>
|
||||||
|
@ -315,7 +315,7 @@
|
||||||
if (infoModal.clientStats) {
|
if (infoModal.clientStats) {
|
||||||
return infoModal.clientStats.enable;
|
return infoModal.clientStats.enable;
|
||||||
}
|
}
|
||||||
return infoModal.dbInbound.isEnable;
|
return true;
|
||||||
},
|
},
|
||||||
get isEnable() {
|
get isEnable() {
|
||||||
if (infoModal.clientSettings) {
|
if (infoModal.clientSettings) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{{define "inboundModal"}}
|
{{define "inboundModal"}}
|
||||||
<a-modal id="inbound-modal" v-model="inModal.visible" :title="inModal.title" @ok="inModal.ok"
|
<a-modal id="inbound-modal" v-model="inModal.visible" :title="inModal.title" @ok="inModal.ok"
|
||||||
:confirm-loading="inModal.confirmLoading" :closable="true" :mask-closable="false"
|
:confirm-loading="inModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
:class="themeSwitcher.darkCardClass"
|
:class="themeSwitcher.currentTheme"
|
||||||
:ok-text="inModal.okText" cancel-text='{{ i18n "close" }}'>
|
:ok-text="inModal.okText" cancel-text='{{ i18n "close" }}'>
|
||||||
{{template "form/inbound"}}
|
{{template "form/inbound"}}
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
|
@ -8,27 +8,56 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.ant-card-body {
|
||||||
|
padding: .5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.ant-col-sm-24 {
|
.ant-col-sm-24 {
|
||||||
margin-top: 10px;
|
margin: 0.5rem -2rem 0.5rem 2rem;
|
||||||
}
|
}
|
||||||
tr.hideExpandIcon .ant-table-row-expand-icon {
|
tr.hideExpandIcon .ant-table-row-expand-icon {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
.infinite-tag {
|
||||||
|
padding: 0 5px;
|
||||||
|
border-radius: 2rem;
|
||||||
|
min-width: 50px;
|
||||||
|
}
|
||||||
|
.infinite-bar .ant-progress-inner .ant-progress-bg {
|
||||||
|
background-color: #F2EAF1;
|
||||||
|
border: #D5BED2 solid 1px;
|
||||||
|
}
|
||||||
|
.dark .infinite-bar .ant-progress-inner .ant-progress-bg {
|
||||||
|
background-color: #3c1536;
|
||||||
|
border: #7a316f solid 1px;
|
||||||
|
}
|
||||||
|
.ant-collapse {
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
.online-animation .ant-badge-status-dot {
|
||||||
|
animation: 1.2s ease infinite normal none running onlineAnimation;
|
||||||
|
}
|
||||||
|
@keyframes onlineAnimation {
|
||||||
|
0%, 50%, 100% { transform: scale(1); opacity: 1; }
|
||||||
|
10% { transform: scale(1.5); opacity: .2; }
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<a-layout id="app" v-cloak>
|
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
|
||||||
{{ template "commonSider" . }}
|
{{ template "commonSider" . }}
|
||||||
<a-layout id="content-layout" :style="themeSwitcher.bgStyle">
|
<a-layout id="content-layout">
|
||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="spinning" :delay="500" tip="loading">
|
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-tag v-if="false" color="red" style="margin-bottom: 10px">
|
<a-tag v-if="false" color="red" style="margin-bottom: 10px">
|
||||||
Please go to the panel settings as soon as possible to modify the username and password, otherwise there may be a risk of leaking account information
|
Please go to the panel settings as soon as possible to modify the username and password, otherwise there may be a risk of leaking account information
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</transition>
|
</transition>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-card hoverable style="margin-bottom: 20px;" :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :xs="24" :sm="24" :lg="12">
|
<a-col :xs="24" :sm="24" :lg="12">
|
||||||
{{ i18n "pages.inbounds.totalDownUp" }}:
|
{{ i18n "pages.inbounds.totalDownUp" }}:
|
||||||
|
@ -45,36 +74,46 @@
|
||||||
<a-col :xs="24" :sm="24" :lg="12">
|
<a-col :xs="24" :sm="24" :lg="12">
|
||||||
{{ i18n "clients" }}:
|
{{ i18n "clients" }}:
|
||||||
<a-tag color="green">[[ total.clients ]]</a-tag>
|
<a-tag color="green">[[ total.clients ]]</a-tag>
|
||||||
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.darkClass">
|
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in total.deactive">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in total.deactive">[[ clientEmail ]]</p>
|
||||||
</template>
|
</template>
|
||||||
<a-tag v-if="total.deactive.length">[[ total.deactive.length ]]</a-tag>
|
<a-tag v-if="total.deactive.length">[[ total.deactive.length ]]</a-tag>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.darkClass">
|
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in total.depleted">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in total.depleted">[[ clientEmail ]]</p>
|
||||||
</template>
|
</template>
|
||||||
<a-tag color="red" v-if="total.depleted.length">[[ total.depleted.length ]]</a-tag>
|
<a-tag color="red" v-if="total.depleted.length">[[ total.depleted.length ]]</a-tag>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.darkClass">
|
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in total.expiring">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in total.expiring">[[ clientEmail ]]</p>
|
||||||
</template>
|
</template>
|
||||||
<a-tag color="orange" v-if="total.expiring.length">[[ total.expiring.length ]]</a-tag>
|
<a-tag color="orange" v-if="total.expiring.length">[[ total.expiring.length ]]</a-tag>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
|
<a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<p v-for="clientEmail in onlineClients">[[ clientEmail ]]</p>
|
||||||
|
</template>
|
||||||
|
<a-tag color="blue" v-if="onlineClients.length">[[ onlineClients.length ]]</a-tag>
|
||||||
|
</a-popover>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
</transition>
|
</transition>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
<div slot="title">
|
<div slot="title">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :xs="24" :sm="24" :lg="12">
|
<a-col :xs="12" :sm="12" :lg="12">
|
||||||
<a-button type="primary" icon="plus" @click="openAddInbound">{{ i18n "pages.inbounds.addInbound" }}</a-button>
|
<a-button type="primary" icon="plus" @click="openAddInbound">
|
||||||
|
<template v-if="!isMobile">{{ i18n "pages.inbounds.addInbound" }}</template>
|
||||||
|
</a-button>
|
||||||
<a-dropdown :trigger="['click']">
|
<a-dropdown :trigger="['click']">
|
||||||
<a-button type="primary" icon="menu">{{ i18n "pages.inbounds.generalActions" }}</a-button>
|
<a-button type="primary" icon="menu">
|
||||||
|
<template v-if="!isMobile">{{ i18n "pages.inbounds.generalActions" }}</template>
|
||||||
|
</a-button>
|
||||||
<a-menu slot="overlay" @click="a => generalActions(a)" :theme="themeSwitcher.currentTheme">
|
<a-menu slot="overlay" @click="a => generalActions(a)" :theme="themeSwitcher.currentTheme">
|
||||||
<a-menu-item key="export">
|
<a-menu-item key="export">
|
||||||
<a-icon type="export"></a-icon>
|
<a-icon type="export"></a-icon>
|
||||||
|
@ -95,12 +134,12 @@
|
||||||
</a-menu>
|
</a-menu>
|
||||||
</a-dropdown>
|
</a-dropdown>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :xs="24" :sm="24" :lg="12" style="text-align: right;">
|
<a-col :xs="12" :sm="12" :lg="12" style="text-align: right;">
|
||||||
<a-select v-model="refreshInterval"
|
<a-select v-model="refreshInterval"
|
||||||
style="width: 65px;"
|
style="width: 65px;"
|
||||||
v-if="isRefreshEnabled"
|
v-if="isRefreshEnabled"
|
||||||
@change="changeRefreshInterval"
|
@change="changeRefreshInterval"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="key in [5,10,30,60]" :value="key*1000">[[ key ]]s</a-select-option>
|
<a-select-option v-for="key in [5,10,30,60]" :value="key*1000">[[ key ]]s</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
<a-icon type="sync" :spin="refreshing" @click="manualRefresh" style="margin: 0 5px;"></a-icon>
|
<a-icon type="sync" :spin="refreshing" @click="manualRefresh" style="margin: 0 5px;"></a-icon>
|
||||||
|
@ -108,26 +147,32 @@
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</div>
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; justify-content: flex-start;">
|
||||||
<a-switch v-model="enableFilter"
|
<a-switch v-model="enableFilter"
|
||||||
checked-children='{{ i18n "search" }}' un-checked-children='{{ i18n "filter" }}'
|
style="margin-right: .5rem;"
|
||||||
@change="toggleFilter" style="margin-right: 10px;">
|
@change="toggleFilter">
|
||||||
|
<a-icon slot="checkedChildren" type="search"></a-icon>
|
||||||
|
<a-icon slot="unCheckedChildren" type="filter"></a-icon>
|
||||||
</a-switch>
|
</a-switch>
|
||||||
<a-input v-if="!enableFilter" v-model.lazy="searchKey" placeholder='{{ i18n "search" }}' autofocus style="max-width: 300px"></a-input>
|
<a-input v-if="!enableFilter" v-model.lazy="searchKey" placeholder='{{ i18n "search" }}' autofocus style="max-width: 300px" :size="isMobile ? 'small' : ''"></a-input>
|
||||||
<a-radio-group v-if="enableFilter" v-model="filterBy" @change="filterInbounds" button-style="solid">
|
<a-radio-group v-if="enableFilter" v-model="filterBy" @change="filterInbounds" button-style="solid" :size="isMobile ? 'small' : ''">
|
||||||
<a-radio-button value="">{{ i18n "none" }}</a-radio-button>
|
<a-radio-button value="">{{ i18n "none" }}</a-radio-button>
|
||||||
<a-radio-button value="deactive">{{ i18n "disabled" }}</a-radio-button>
|
<a-radio-button value="deactive">{{ i18n "disabled" }}</a-radio-button>
|
||||||
<a-radio-button value="depleted">{{ i18n "depleted" }}</a-radio-button>
|
<a-radio-button value="depleted">{{ i18n "depleted" }}</a-radio-button>
|
||||||
<a-radio-button value="expiring">{{ i18n "depletingSoon" }}</a-radio-button>
|
<a-radio-button value="expiring">{{ i18n "depletingSoon" }}</a-radio-button>
|
||||||
|
<a-radio-button value="online">{{ i18n "online" }}</a-radio-button>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
<a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
|
</div>
|
||||||
|
<a-table :columns="isMobile ? mobileColums : columns" :row-key="dbInbound => dbInbound.id"
|
||||||
:data-source="searchedInbounds"
|
:data-source="searchedInbounds"
|
||||||
:loading="spinning" :scroll="{ x: 1200 }"
|
:scroll="isMobile ? {} : { x: 1000 }"
|
||||||
:pagination="false"
|
:pagination="false"
|
||||||
:expand-icon-as-cell="false"
|
:expand-icon-as-cell="false"
|
||||||
:expand-row-by-click="false"
|
:expand-row-by-click="false"
|
||||||
:expand-icon-column-index="0"
|
:expand-icon-column-index="0"
|
||||||
:row-class-name="dbInbound => (dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess || (dbInbound.isSS && dbInbound.toInbound().isSSMultiUser) ? '' : 'hideExpandIcon')"
|
:indent-size="0"
|
||||||
style="margin-top: 20px"
|
:row-class-name="dbInbound => (dbInbound.isMultiUser() ? '' : 'hideExpandIcon')"
|
||||||
|
style="margin-top: 10px"
|
||||||
@change="() => getDBInbounds()">
|
@change="() => getDBInbounds()">
|
||||||
<template slot="action" slot-scope="text, dbInbound">
|
<template slot="action" slot-scope="text, dbInbound">
|
||||||
<a-icon type="edit" style="font-size: 22px" @click="openEditInbound(dbInbound.id);"></a-icon>
|
<a-icon type="edit" style="font-size: 22px" @click="openEditInbound(dbInbound.id);"></a-icon>
|
||||||
|
@ -142,7 +187,7 @@
|
||||||
<a-icon type="qrcode"></a-icon>
|
<a-icon type="qrcode"></a-icon>
|
||||||
{{ i18n "qrCode" }}
|
{{ i18n "qrCode" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<template v-if="dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess || (dbInbound.isSS && dbInbound.toInbound().isSSMultiUser)">
|
<template v-if="dbInbound.isMultiUser()">
|
||||||
<a-menu-item key="addClient">
|
<a-menu-item key="addClient">
|
||||||
<a-icon type="user-add"></a-icon>
|
<a-icon type="user-add"></a-icon>
|
||||||
{{ i18n "pages.client.add"}}
|
{{ i18n "pages.client.add"}}
|
||||||
|
@ -181,43 +226,53 @@
|
||||||
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
||||||
</span>
|
</span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
|
<a-menu-item v-if="isMobile">
|
||||||
|
<a-switch size="small" v-model="dbInbound.enable" @change="switchEnable(dbInbound.id)"></a-switch>
|
||||||
|
{{ i18n "pages.inbounds.enable" }}
|
||||||
|
</a-menu-item>
|
||||||
</a-menu>
|
</a-menu>
|
||||||
</a-dropdown>
|
</a-dropdown>
|
||||||
</template>
|
</template>
|
||||||
<template slot="protocol" slot-scope="text, dbInbound">
|
<template slot="protocol" slot-scope="text, dbInbound">
|
||||||
<a-tag style="margin:0;" color="blue">[[ dbInbound.protocol ]]</a-tag>
|
<a-tag style="margin:0;" color="purple">[[ dbInbound.protocol ]]</a-tag>
|
||||||
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
||||||
<a-tag style="margin:0;" color="green">[[ dbInbound.toInbound().stream.network ]]</a-tag>
|
<a-tag style="margin:0;" color="green">[[ dbInbound.toInbound().stream.network ]]</a-tag>
|
||||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="cyan">TLS</a-tag>
|
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="blue">TLS</a-tag>
|
||||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isXtls" color="cyan">XTLS</a-tag>
|
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isXtls" color="blue">XTLS</a-tag>
|
||||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="cyan">Reality</a-tag>
|
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="blue">Reality</a-tag>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template slot="clients" slot-scope="text, dbInbound">
|
<template slot="clients" slot-scope="text, dbInbound">
|
||||||
<template v-if="clientCount[dbInbound.id]">
|
<template v-if="clientCount[dbInbound.id]">
|
||||||
<a-tag style="margin:0;" color="green">[[ clientCount[dbInbound.id].clients ]]</a-tag>
|
<a-tag style="margin:0;" color="green">[[ clientCount[dbInbound.id].clients ]]</a-tag>
|
||||||
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.darkClass">
|
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p>
|
||||||
</template>
|
</template>
|
||||||
<a-tag style="margin:0; padding: 0 2px;" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag>
|
<a-tag style="margin:0; padding: 0 2px;" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.darkClass">
|
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p>
|
||||||
</template>
|
</template>
|
||||||
<a-tag style="margin:0; padding: 0 2px;" color="red" v-if="clientCount[dbInbound.id].depleted.length">[[ clientCount[dbInbound.id].depleted.length ]]</a-tag>
|
<a-tag style="margin:0; padding: 0 2px;" color="red" v-if="clientCount[dbInbound.id].depleted.length">[[ clientCount[dbInbound.id].depleted.length ]]</a-tag>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.darkClass">
|
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p>
|
||||||
</template>
|
</template>
|
||||||
<a-tag style="margin:0; padding: 0 2px;" color="orange" v-if="clientCount[dbInbound.id].expiring.length">[[ clientCount[dbInbound.id].expiring.length ]]</a-tag>
|
<a-tag style="margin:0; padding: 0 2px;" color="orange" v-if="clientCount[dbInbound.id].expiring.length">[[ clientCount[dbInbound.id].expiring.length ]]</a-tag>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
|
<a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<p v-for="clientEmail in clientCount[dbInbound.id].online">[[ clientEmail ]]</p>
|
||||||
|
</template>
|
||||||
|
<a-tag style="margin:0; padding: 0 2px;" color="blue" v-if="clientCount[dbInbound.id].online.length">[[ clientCount[dbInbound.id].online.length ]]</a-tag>
|
||||||
|
</a-popover>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template slot="traffic" slot-scope="text, dbInbound">
|
<template slot="traffic" slot-scope="text, dbInbound">
|
||||||
<a-popover :overlay-class-name="themeSwitcher.darkClass">
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<table cellpadding="2" width="100%">
|
<table cellpadding="2" width="100%">
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -230,7 +285,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</template>
|
</template>
|
||||||
<a-tag :color="dbInbound.total == 0 ? 'green' : dbInbound.up + dbInbound.down < dbInbound.total ? 'cyan' : 'red'">
|
<a-tag :color="usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
|
||||||
[[ sizeFormat(dbInbound.up + dbInbound.down) ]] /
|
[[ sizeFormat(dbInbound.up + dbInbound.down) ]] /
|
||||||
<template v-if="dbInbound.total > 0">
|
<template v-if="dbInbound.total > 0">
|
||||||
[[ sizeFormat(dbInbound.total) ]]
|
[[ sizeFormat(dbInbound.total) ]]
|
||||||
|
@ -245,35 +300,117 @@
|
||||||
<a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound.id)"></a-switch>
|
<a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound.id)"></a-switch>
|
||||||
</template>
|
</template>
|
||||||
<template slot="expiryTime" slot-scope="text, dbInbound">
|
<template slot="expiryTime" slot-scope="text, dbInbound">
|
||||||
<template v-if="dbInbound.expiryTime > 0">
|
<a-popover v-if="dbInbound.expiryTime > 0" :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<a-tag v-if="dbInbound.isExpiry" color="red">
|
<template slot="content">
|
||||||
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
|
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
|
||||||
</a-tag>
|
|
||||||
<a-tag v-else color="blue">
|
|
||||||
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
|
|
||||||
</a-tag>
|
|
||||||
</template>
|
</template>
|
||||||
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
<a-tag style="min-width: 50px;" :color="usageColor(new Date().getTime(), app.expireDiff, dbInbound._expiryTime)">
|
||||||
|
[[ remainedDays(dbInbound._expiryTime) ]]
|
||||||
|
</a-tag>
|
||||||
|
</a-popover>
|
||||||
|
<a-tag v-else color="purple" class="infinite-tag">∞</a-tag>
|
||||||
|
</template>
|
||||||
|
<template slot="info" slot-scope="text, dbInbound">
|
||||||
|
<a-popover placement="bottomRight" :overlay-class-name="themeSwitcher.currentTheme" trigger="click">
|
||||||
|
<template slot="content">
|
||||||
|
<table cellpadding="2">
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.inbounds.protocol" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-tag style="margin:0;" color="purple">[[ dbInbound.protocol ]]</a-tag>
|
||||||
|
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
||||||
|
<a-tag style="margin:0;" color="blue">[[ dbInbound.toInbound().stream.network ]]</a-tag>
|
||||||
|
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="green">tls</a-tag>
|
||||||
|
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="green">reality</a-tag>
|
||||||
|
</template>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.inbounds.port" }}</td>
|
||||||
|
<td><a-tag>[[ dbInbound.port ]]</a-tag></td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="clientCount[dbInbound.id]">
|
||||||
|
<td>{{ i18n "clients" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-tag style="margin:0;" color="blue">[[ clientCount[dbInbound.id].clients ]]</a-tag>
|
||||||
|
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p>
|
||||||
|
</template>
|
||||||
|
<a-tag style="margin:0; padding: 0 2px;" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag>
|
||||||
|
</a-popover>
|
||||||
|
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p>
|
||||||
|
</template>
|
||||||
|
<a-tag style="margin:0; padding: 0 2px;" color="red" v-if="clientCount[dbInbound.id].depleted.length">[[ clientCount[dbInbound.id].depleted.length ]]</a-tag>
|
||||||
|
</a-popover>
|
||||||
|
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p>
|
||||||
|
</template>
|
||||||
|
<a-tag style="margin:0; padding: 0 2px;" color="orange" v-if="clientCount[dbInbound.id].expiring.length">[[ clientCount[dbInbound.id].expiring.length ]]</a-tag>
|
||||||
|
</a-popover>
|
||||||
|
<a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<p v-for="clientEmail in clientCount[dbInbound.id].online">[[ clientEmail ]]</p>
|
||||||
|
</template>
|
||||||
|
<a-tag style="margin:0; padding: 0 2px;" color="green" v-if="clientCount[dbInbound.id].online.length">[[ clientCount[dbInbound.id].online.length ]]</a-tag>
|
||||||
|
</a-popover>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.inbounds.traffic" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<table cellpadding="2" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td>↑[[ sizeFormat(dbInbound.up) ]]</td>
|
||||||
|
<td>↓[[ sizeFormat(dbInbound.down) ]]</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="dbInbound.total > 0 && dbInbound.up + dbInbound.down < dbInbound.total">
|
||||||
|
<td>{{ i18n "remained" }}</td>
|
||||||
|
<td>[[ sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
<a-tag :color="usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
|
||||||
|
[[ sizeFormat(dbInbound.up + dbInbound.down) ]] /
|
||||||
|
<template v-if="dbInbound.total > 0">
|
||||||
|
[[ sizeFormat(dbInbound.total) ]]
|
||||||
|
</template>
|
||||||
|
<template v-else>∞</template>
|
||||||
|
</a-tag>
|
||||||
|
</a-popover>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.inbounds.expireDate" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-tag style="min-width: 50px; text-align: center;" v-if="dbInbound.expiryTime > 0" :color="dbInbound.isExpiry? 'red': 'blue'">
|
||||||
|
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
|
||||||
|
</a-tag>
|
||||||
|
<a-tag v-else style="text-align: center;" color="purple" class="infinite-tag">∞</a-tag>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
<a-badge>
|
||||||
|
<a-icon v-if="!dbInbound.enable" slot="count" type="pause-circle" :style="'color: ' + themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'"></a-icon>
|
||||||
|
<a-button shape="round" size="small" style="font-size: 14px; padding: 0 10px;">
|
||||||
|
<a-icon type="info"></a-icon>
|
||||||
|
</a-button>
|
||||||
|
</a-badge>
|
||||||
|
</a-popover>
|
||||||
</template>
|
</template>
|
||||||
<template slot="expandedRowRender" slot-scope="record">
|
<template slot="expandedRowRender" slot-scope="record">
|
||||||
<a-table
|
<a-table
|
||||||
v-if="(record.protocol === Protocols.VLESS) || (record.protocol === Protocols.VMESS)"
|
|
||||||
:row-key="client => client.id"
|
:row-key="client => client.id"
|
||||||
:columns="innerColumns"
|
:columns="isMobile ? innerMobileColumns : innerColumns"
|
||||||
:data-source="getInboundClients(record)"
|
:data-source="getInboundClients(record)"
|
||||||
:pagination="false"
|
:pagination="false"
|
||||||
style="margin-left: 20px;"
|
:style="isMobile ? 'margin: -16px -5px -17px;' : 'margin-left: 10px;'">
|
||||||
>
|
|
||||||
{{template "client_table"}}
|
|
||||||
</a-table>
|
|
||||||
<a-table
|
|
||||||
v-else-if="record.protocol === Protocols.TROJAN || record.toInbound().isSSMultiUser"
|
|
||||||
:row-key="client => client.id"
|
|
||||||
:columns="innerTrojanColumns"
|
|
||||||
:data-source="getInboundClients(record)"
|
|
||||||
:pagination="false"
|
|
||||||
style="margin-left: 20px;"
|
|
||||||
>
|
|
||||||
{{template "client_table"}}
|
{{template "client_table"}}
|
||||||
</a-table>
|
</a-table>
|
||||||
</template>
|
</template>
|
||||||
|
@ -292,6 +429,7 @@
|
||||||
align: 'right',
|
align: 'right',
|
||||||
dataIndex: "id",
|
dataIndex: "id",
|
||||||
width: 30,
|
width: 30,
|
||||||
|
responsive: ["xs"],
|
||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "pages.inbounds.operate" }}',
|
title: '{{ i18n "pages.inbounds.operate" }}',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
|
@ -320,7 +458,7 @@
|
||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "clients" }}',
|
title: '{{ i18n "clients" }}',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
width: 40,
|
width: 50,
|
||||||
scopedSlots: { customRender: 'clients' },
|
scopedSlots: { customRender: 'clients' },
|
||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "pages.inbounds.traffic" }}',
|
title: '{{ i18n "pages.inbounds.traffic" }}',
|
||||||
|
@ -330,26 +468,46 @@
|
||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "pages.inbounds.expireDate" }}',
|
title: '{{ i18n "pages.inbounds.expireDate" }}',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: 80,
|
width: 40,
|
||||||
scopedSlots: { customRender: 'expiryTime' },
|
scopedSlots: { customRender: 'expiryTime' },
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
const mobileColums = [{
|
||||||
|
title: "ID",
|
||||||
|
align: 'right',
|
||||||
|
dataIndex: "id",
|
||||||
|
width: 10,
|
||||||
|
responsive: ["s"],
|
||||||
|
}, {
|
||||||
|
title: '{{ i18n "pages.inbounds.operate" }}',
|
||||||
|
align: 'center',
|
||||||
|
width: 25,
|
||||||
|
scopedSlots: { customRender: 'action' },
|
||||||
|
}, {
|
||||||
|
title: '{{ i18n "pages.inbounds.remark" }}',
|
||||||
|
align: 'left',
|
||||||
|
width: 70,
|
||||||
|
dataIndex: "remark",
|
||||||
|
}, {
|
||||||
|
title: '{{ i18n "pages.inbounds.info" }}',
|
||||||
|
align: 'center',
|
||||||
|
width: 10,
|
||||||
|
scopedSlots: { customRender: 'info' },
|
||||||
|
}];
|
||||||
|
|
||||||
const innerColumns = [
|
const innerColumns = [
|
||||||
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
|
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 50, scopedSlots: { customRender: 'actions' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 40, scopedSlots: { customRender: 'enable' } },
|
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 20, scopedSlots: { customRender: 'enable' } },
|
||||||
|
{ title: '{{ i18n "online" }}', width: 20, scopedSlots: { customRender: 'online' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.traffic" }}', width: 50, scopedSlots: { customRender: 'traffic' } },
|
{ title: '{{ i18n "pages.inbounds.traffic" }}', width: 80, align: 'center', scopedSlots: { customRender: 'traffic' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 50, scopedSlots: { customRender: 'expiryTime' } },
|
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 80, align: 'center', scopedSlots: { customRender: 'expiryTime' } },
|
||||||
{ title: 'UUID', width: 120, dataIndex: "id" },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const innerTrojanColumns = [
|
const innerMobileColumns = [
|
||||||
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
|
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 10, align: 'center', scopedSlots: { customRender: 'actionMenu' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 40, scopedSlots: { customRender: 'enable' } },
|
{ title: '{{ i18n "pages.inbounds.client" }}', width: 90, align: 'left', scopedSlots: { customRender: 'client' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
{ title: '{{ i18n "pages.inbounds.info" }}', width: 10, align: 'center', scopedSlots: { customRender: 'info' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.traffic" }}', width: 50, scopedSlots: { customRender: 'traffic' } },
|
|
||||||
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 50, scopedSlots: { customRender: 'expiryTime' } },
|
|
||||||
{ title: '{{ i18n "password" }}', width: 170, dataIndex: "password" },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
|
@ -370,6 +528,7 @@
|
||||||
defaultCert: '',
|
defaultCert: '',
|
||||||
defaultKey: '',
|
defaultKey: '',
|
||||||
clientCount: [],
|
clientCount: [],
|
||||||
|
onlineClients: [],
|
||||||
isRefreshEnabled: localStorage.getItem("isRefreshEnabled") === "true" ? true : false,
|
isRefreshEnabled: localStorage.getItem("isRefreshEnabled") === "true" ? true : false,
|
||||||
refreshing: false,
|
refreshing: false,
|
||||||
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
|
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
|
||||||
|
@ -380,7 +539,8 @@
|
||||||
domain: '',
|
domain: '',
|
||||||
tls: false
|
tls: false
|
||||||
},
|
},
|
||||||
tgBotEnable: false
|
tgBotEnable: false,
|
||||||
|
isMobile: window.innerWidth <= 768,
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
loading(spinning = true) {
|
loading(spinning = true) {
|
||||||
|
@ -393,11 +553,19 @@
|
||||||
this.refreshing = false;
|
this.refreshing = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
await this.getOnlineUsers();
|
||||||
this.setInbounds(msg.obj);
|
this.setInbounds(msg.obj);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.refreshing = false;
|
this.refreshing = false;
|
||||||
}, 500);
|
}, 500);
|
||||||
},
|
},
|
||||||
|
async getOnlineUsers() {
|
||||||
|
const msg = await HttpUtil.post('/panel/inbound/onlines');
|
||||||
|
if (!msg.success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.onlineClients = msg.obj != null ? msg.obj : [];
|
||||||
|
},
|
||||||
async getDefaultSettings() {
|
async getDefaultSettings() {
|
||||||
const msg = await HttpUtil.post('/panel/setting/defaultSettings');
|
const msg = await HttpUtil.post('/panel/setting/defaultSettings');
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
|
@ -441,7 +609,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getClientCounts(dbInbound, inbound) {
|
getClientCounts(dbInbound, inbound) {
|
||||||
let clientCount = 0, active = [], deactive = [], depleted = [], expiring = [];
|
let clientCount = 0, active = [], deactive = [], depleted = [], expiring = [], online = [];
|
||||||
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
||||||
clientStats = dbInbound.clientStats
|
clientStats = dbInbound.clientStats
|
||||||
now = new Date().getTime()
|
now = new Date().getTime()
|
||||||
|
@ -450,6 +618,7 @@
|
||||||
if (dbInbound.enable) {
|
if (dbInbound.enable) {
|
||||||
clients.forEach(client => {
|
clients.forEach(client => {
|
||||||
client.enable ? active.push(client.email) : deactive.push(client.email);
|
client.enable ? active.push(client.email) : deactive.push(client.email);
|
||||||
|
if(this.isClientOnline(client.email)) online.push(client.email);
|
||||||
});
|
});
|
||||||
clientStats.forEach(client => {
|
clientStats.forEach(client => {
|
||||||
if (!client.enable) {
|
if (!client.enable) {
|
||||||
|
@ -471,6 +640,7 @@
|
||||||
deactive: deactive,
|
deactive: deactive,
|
||||||
depleted: depleted,
|
depleted: depleted,
|
||||||
expiring: expiring,
|
expiring: expiring,
|
||||||
|
online: online,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
searchInbounds(key) {
|
searchInbounds(key) {
|
||||||
|
@ -547,10 +717,10 @@
|
||||||
clickAction(action, dbInbound) {
|
clickAction(action, dbInbound) {
|
||||||
switch (action.key) {
|
switch (action.key) {
|
||||||
case "qrcode":
|
case "qrcode":
|
||||||
this.showQrcode(dbInbound);
|
this.showQrcode(dbInbound.id);
|
||||||
break;
|
break;
|
||||||
case "showInfo":
|
case "showInfo":
|
||||||
this.showInfo(dbInbound);
|
this.showInfo(dbInbound.id);
|
||||||
break;
|
break;
|
||||||
case "edit":
|
case "edit":
|
||||||
this.openEditInbound(dbInbound.id);
|
this.openEditInbound(dbInbound.id);
|
||||||
|
@ -586,6 +756,7 @@
|
||||||
title: '{{ i18n "pages.inbounds.cloneInbound"}} \"' + dbInbound.remark + '\"',
|
title: '{{ i18n "pages.inbounds.cloneInbound"}} \"' + dbInbound.remark + '\"',
|
||||||
content: '{{ i18n "pages.inbounds.cloneInboundContent"}}',
|
content: '{{ i18n "pages.inbounds.cloneInboundContent"}}',
|
||||||
okText: '{{ i18n "pages.inbounds.cloneInboundOk"}}',
|
okText: '{{ i18n "pages.inbounds.cloneInboundOk"}}',
|
||||||
|
class: themeSwitcher.currentTheme,
|
||||||
cancelText: '{{ i18n "cancel" }}',
|
cancelText: '{{ i18n "cancel" }}',
|
||||||
onOk: () => {
|
onOk: () => {
|
||||||
const baseInbound = dbInbound.toInbound();
|
const baseInbound = dbInbound.toInbound();
|
||||||
|
@ -752,7 +923,7 @@
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
||||||
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
||||||
class: themeSwitcher.darkCardClass,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => {
|
onOk: () => {
|
||||||
|
@ -767,23 +938,27 @@
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
|
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
|
||||||
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
|
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
|
||||||
class: themeSwitcher.darkCardClass,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "delete"}}',
|
okText: '{{ i18n "delete"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/panel/inbound/del/' + dbInboundId),
|
onOk: () => this.submit('/panel/inbound/del/' + dbInboundId),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
delClient(dbInboundId, client) {
|
delClient(dbInboundId, client,confirmation = true) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
clientId = this.getClientId(dbInbound.protocol, client);
|
clientId = this.getClientId(dbInbound.protocol, client);
|
||||||
|
if (confirmation){
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
|
title: '{{ i18n "pages.inbounds.deleteClient"}}',
|
||||||
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
|
content: '{{ i18n "pages.inbounds.deleteClientContent"}}',
|
||||||
class: themeSwitcher.darkCardClass,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "delete"}}',
|
okText: '{{ i18n "delete"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit(`/panel/inbound/${dbInboundId}/delClient/${clientId}`),
|
onOk: () => this.submit(`/xui/inbound/${dbInboundId}/delClient/${clientId}`),
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
this.submit(`/xui/inbound/${dbInboundId}/delClient/${clientId}`);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
getClients(protocol, clientSettings) {
|
getClients(protocol, clientSettings) {
|
||||||
switch (protocol) {
|
switch (protocol) {
|
||||||
|
@ -860,21 +1035,25 @@
|
||||||
return dbInbound.toInbound().settings.shadowsockses;
|
return dbInbound.toInbound().settings.shadowsockses;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
resetClientTraffic(client, dbInboundId) {
|
resetClientTraffic(client, dbInboundId, confirmation = true) {
|
||||||
|
if (confirmation){
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
||||||
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
||||||
class: themeSwitcher.darkCardClass,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email),
|
onOk: () => this.submit('/xui/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email),
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
this.submit('/xui/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
resetAllTraffic() {
|
resetAllTraffic() {
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.resetAllTrafficTitle"}}',
|
title: '{{ i18n "pages.inbounds.resetAllTrafficTitle"}}',
|
||||||
content: '{{ i18n "pages.inbounds.resetAllTrafficContent"}}',
|
content: '{{ i18n "pages.inbounds.resetAllTrafficContent"}}',
|
||||||
class: themeSwitcher.darkCardClass,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/panel/inbound/resetAllTraffics'),
|
onOk: () => this.submit('/panel/inbound/resetAllTraffics'),
|
||||||
|
@ -884,7 +1063,7 @@
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: dbInboundId > 0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficTitle"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficTitle"}}',
|
title: dbInboundId > 0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficTitle"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficTitle"}}',
|
||||||
content: dbInboundId > 0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficContent"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficContent"}}',
|
content: dbInboundId > 0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficContent"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficContent"}}',
|
||||||
class: themeSwitcher.darkCardClass,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/panel/inbound/resetAllClientTraffics/' + dbInboundId),
|
onOk: () => this.submit('/panel/inbound/resetAllClientTraffics/' + dbInboundId),
|
||||||
|
@ -894,36 +1073,98 @@
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.delDepletedClientsTitle"}}',
|
title: '{{ i18n "pages.inbounds.delDepletedClientsTitle"}}',
|
||||||
content: '{{ i18n "pages.inbounds.delDepletedClientsContent"}}',
|
content: '{{ i18n "pages.inbounds.delDepletedClientsContent"}}',
|
||||||
class: themeSwitcher.darkCardClass,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/panel/inbound/delDepletedClients/' + dbInboundId),
|
onOk: () => this.submit('/panel/inbound/delDepletedClients/' + dbInboundId),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
isExpiry(dbInbound, index) {
|
isExpiry(dbInbound, index) {
|
||||||
return dbInbound.toInbound().isExpiry(index)
|
return dbInbound.toInbound().isExpiry(index);
|
||||||
},
|
},
|
||||||
getUpStats(dbInbound, email) {
|
getUpStats(dbInbound, email) {
|
||||||
if (email.length == 0) return 0
|
if (email.length == 0) return 0;
|
||||||
clientStats = dbInbound.clientStats.find(stats => stats.email === email)
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||||
return clientStats ? clientStats.up : 0
|
return clientStats ? clientStats.up : 0;
|
||||||
},
|
},
|
||||||
getDownStats(dbInbound, email) {
|
getDownStats(dbInbound, email) {
|
||||||
if (email.length == 0) return 0
|
if (email.length == 0) return 0;
|
||||||
clientStats = dbInbound.clientStats.find(stats => stats.email === email)
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||||
return clientStats ? clientStats.down : 0
|
return clientStats ? clientStats.down : 0;
|
||||||
|
},
|
||||||
|
getSumStats(dbInbound, email) {
|
||||||
|
if (email.length == 0) return 0;
|
||||||
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||||
|
return clientStats ? clientStats.up + clientStats.down : 0;
|
||||||
|
},
|
||||||
|
getRemStats(dbInbound, email) {
|
||||||
|
if (email.length == 0) return 0;
|
||||||
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||||
|
if (!clientStats) return 0;
|
||||||
|
remained = clientStats.totalGB - (clientStats.up + clientStats.down);
|
||||||
|
return remained>0 ? remained : 0;
|
||||||
},
|
},
|
||||||
statsColor(dbInbound, email) {
|
statsColor(dbInbound, email) {
|
||||||
if(email.length == 0) return 'blue';
|
if (email.length == 0) return '#0e49b5';
|
||||||
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||||
return usageColor(clientStats.down + clientStats.up, this.trafficDiff, clientStats.total);
|
switch (true) {
|
||||||
|
case !clientStats:
|
||||||
|
return "#0e49b5";
|
||||||
|
case clientStats.up + clientStats.down < clientStats.total - app.trafficDiff:
|
||||||
|
return "#0e49b5";
|
||||||
|
case clientStats.up + clientStats.down < clientStats.total:
|
||||||
|
return "#FFA031";
|
||||||
|
default:
|
||||||
|
return "#E04141";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
statsProgress(dbInbound, email) {
|
||||||
|
if (email.length == 0) return 100;
|
||||||
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||||
|
if (!clientStats) return 0;
|
||||||
|
if (clientStats.total == 0) return 100;
|
||||||
|
return 100*(clientStats.down + clientStats.up)/clientStats.total;
|
||||||
|
},
|
||||||
|
expireProgress(expTime, reset) {
|
||||||
|
now = new Date().getTime();
|
||||||
|
remainedSeconds = expTime < 0 ? -expTime/1000 : (expTime-now)/1000;
|
||||||
|
resetSeconds = reset * 86400;
|
||||||
|
if (remainedSeconds >= resetSeconds) return 0;
|
||||||
|
return 100*(1-(remainedSeconds/resetSeconds));
|
||||||
|
},
|
||||||
|
remainedDays(expTime){
|
||||||
|
if (expTime == 0) return null;
|
||||||
|
if (expTime < 0) return formatSecond(expTime/-1000);
|
||||||
|
now = new Date().getTime();
|
||||||
|
if (expTime < now) return '{{ i18n "depleted" }}';
|
||||||
|
return formatSecond((expTime-now)/1000);
|
||||||
|
},
|
||||||
|
statsExpColor(dbInbound, email){
|
||||||
|
if (email.length == 0) return '#7a316f';
|
||||||
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||||
|
if (!clientStats) return '#7a316f';
|
||||||
|
statsColor = usageColor(clientStats.down + clientStats.up, this.trafficDiff, clientStats.total);
|
||||||
|
expColor = usageColor(new Date().getTime(), this.expireDiff, clientStats.expiryTime);
|
||||||
|
switch (true) {
|
||||||
|
case statsColor == "red" || expColor == "red":
|
||||||
|
return "#E04141";
|
||||||
|
case statsColor == "orange" || expColor == "orange":
|
||||||
|
return "#FFA031";
|
||||||
|
case statsColor == "blue" || expColor == "blue":
|
||||||
|
return "#0e49b5";
|
||||||
|
default:
|
||||||
|
return "#7a316f";
|
||||||
|
}
|
||||||
},
|
},
|
||||||
isClientEnabled(dbInbound, email) {
|
isClientEnabled(dbInbound, email) {
|
||||||
clientStats = dbInbound.clientStats ? dbInbound.clientStats.find(stats => stats.email === email) : null
|
clientStats = dbInbound.clientStats ? dbInbound.clientStats.find(stats => stats.email === email) : null;
|
||||||
return clientStats ? clientStats['enable'] : true
|
return clientStats ? clientStats['enable'] : true;
|
||||||
|
},
|
||||||
|
isClientOnline(email) {
|
||||||
|
return this.onlineClients.includes(email);
|
||||||
},
|
},
|
||||||
isRemovable(dbInbound_id) {
|
isRemovable(dbInbound_id) {
|
||||||
return this.getInboundClients(this.dbInbounds.find(row => row.id === dbInbound_id)).length > 1
|
return this.getInboundClients(this.dbInbounds.find(row => row.id === dbInbound_id)).length > 1;
|
||||||
},
|
},
|
||||||
inboundLinks(dbInboundId) {
|
inboundLinks(dbInboundId) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
|
@ -933,7 +1174,7 @@
|
||||||
exportAllLinks() {
|
exportAllLinks() {
|
||||||
let copyText = '';
|
let copyText = '';
|
||||||
for (const dbInbound of this.dbInbounds) {
|
for (const dbInbound of this.dbInbounds) {
|
||||||
copyText += dbInbound.genInboundLinks
|
copyText += dbInbound.genInboundLinks;
|
||||||
}
|
}
|
||||||
txtModal.show('{{ i18n "pages.inbounds.export"}}', copyText, 'All-Inbounds');
|
txtModal.show('{{ i18n "pages.inbounds.export"}}', copyText, 'All-Inbounds');
|
||||||
},
|
},
|
||||||
|
@ -963,6 +1204,9 @@
|
||||||
this.spinning = false;
|
this.spinning = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onResize() {
|
||||||
|
this.isMobile = window.innerWidth <= 768;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
searchKey: debounce(function (newVal) {
|
searchKey: debounce(function (newVal) {
|
||||||
|
@ -970,6 +1214,8 @@
|
||||||
}, 500)
|
}, 500)
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
window.addEventListener('resize', this.onResize);
|
||||||
|
this.onResize();
|
||||||
this.loading();
|
this.loading();
|
||||||
this.getDefaultSettings();
|
this.getDefaultSettings();
|
||||||
if (this.isRefreshEnabled) {
|
if (this.isRefreshEnabled) {
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
.ant-layout-content {
|
.ant-layout-content {
|
||||||
margin: 24px 16px;
|
margin: 24px 16px;
|
||||||
}
|
}
|
||||||
|
.ant-card-hoverable {
|
||||||
|
margin-inline: 0.3rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-col-sm-24 {
|
.ant-col-sm-24 {
|
||||||
|
@ -18,21 +21,20 @@
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<a-layout id="app" v-cloak>
|
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
|
||||||
{{ template "commonSider" . }}
|
{{ template "commonSider" . }}
|
||||||
<a-layout id="content-layout" :style="themeSwitcher.bgStyle">
|
<a-layout id="content-layout">
|
||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="spinning" :delay="200" :tip="loadingTip"/>
|
<a-spin :spinning="spinning" :delay="200" :tip="loadingTip"/>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12" style="text-align: center">
|
<a-col :span="12" style="text-align: center">
|
||||||
<a-progress type="dashboard" status="normal"
|
<a-progress type="dashboard" status="normal"
|
||||||
:stroke-color="status.cpu.color"
|
:stroke-color="status.cpu.color"
|
||||||
:class="themeSwitcher.darkCardClass"
|
|
||||||
:percent="status.cpu.percent"></a-progress>
|
:percent="status.cpu.percent"></a-progress>
|
||||||
<div>CPU: [[ cpuCoreFormat(status.cpuCores) ]]</div>
|
<div>CPU: [[ cpuCoreFormat(status.cpuCores) ]]</div>
|
||||||
<div>Speed: [[ cpuSpeedFormat(status.cpuSpeedMhz) ]]</div>
|
<div>Speed: [[ cpuSpeedFormat(status.cpuSpeedMhz) ]]</div>
|
||||||
|
@ -40,7 +42,6 @@
|
||||||
<a-col :span="12" style="text-align: center">
|
<a-col :span="12" style="text-align: center">
|
||||||
<a-progress type="dashboard" status="normal"
|
<a-progress type="dashboard" status="normal"
|
||||||
:stroke-color="status.mem.color"
|
:stroke-color="status.mem.color"
|
||||||
:class="themeSwitcher.darkCardClass"
|
|
||||||
:percent="status.mem.percent"></a-progress>
|
:percent="status.mem.percent"></a-progress>
|
||||||
<div>
|
<div>
|
||||||
{{ i18n "pages.index.memory"}}: [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]]
|
{{ i18n "pages.index.memory"}}: [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]]
|
||||||
|
@ -53,7 +54,6 @@
|
||||||
<a-col :span="12" style="text-align: center">
|
<a-col :span="12" style="text-align: center">
|
||||||
<a-progress type="dashboard" status="normal"
|
<a-progress type="dashboard" status="normal"
|
||||||
:stroke-color="status.swap.color"
|
:stroke-color="status.swap.color"
|
||||||
:class="themeSwitcher.darkCardClass"
|
|
||||||
:percent="status.swap.percent"></a-progress>
|
:percent="status.swap.percent"></a-progress>
|
||||||
<div>
|
<div>
|
||||||
Swap: [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]]
|
Swap: [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]]
|
||||||
|
@ -62,7 +62,6 @@
|
||||||
<a-col :span="12" style="text-align: center">
|
<a-col :span="12" style="text-align: center">
|
||||||
<a-progress type="dashboard" status="normal"
|
<a-progress type="dashboard" status="normal"
|
||||||
:stroke-color="status.disk.color"
|
:stroke-color="status.disk.color"
|
||||||
:class="themeSwitcher.darkCardClass"
|
|
||||||
:percent="status.disk.percent"></a-progress>
|
:percent="status.disk.percent"></a-progress>
|
||||||
<div>
|
<div>
|
||||||
{{ i18n "pages.index.hard"}}: [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]]
|
{{ i18n "pages.index.hard"}}: [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]]
|
||||||
|
@ -77,22 +76,22 @@
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
3X: <a href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a>
|
3X: <a href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a>
|
||||||
Xray: <a-tag color="green" style="cursor: pointer;" @click="openSelectV2rayVersion">v[[ status.xray.version ]]</a-tag>
|
Xray: <a-tag color="green" style="cursor: pointer;" @click="openSelectV2rayVersion">v[[ status.xray.version ]]</a-tag>
|
||||||
<a href="https://t.me/panel3xui" target="_blank"><a-tag color="green">@panel3xui</a-tag></a>
|
<a href="https://t.me/panel3xui" target="_blank"><a-tag color="green">@panel3xui</a-tag></a>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
{{ i18n "menu.link" }}:
|
{{ i18n "menu.link" }}:
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
|
<a-tag color="purple" style="cursor: pointer;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
|
<a-tag color="purple" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
|
<a-tag color="purple" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
{{ i18n "pages.index.xrayStatus" }}:
|
{{ i18n "pages.index.xrayStatus" }}:
|
||||||
<a-tag :color="status.xray.color">[[ status.xray.state ]]</a-tag>
|
<a-tag :color="status.xray.color">[[ status.xray.state ]]</a-tag>
|
||||||
<a-tooltip v-if="status.xray.state === State.Error">
|
<a-tooltip v-if="status.xray.state === State.Error">
|
||||||
|
@ -101,13 +100,13 @@
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="stopXrayService">{{ i18n "pages.index.stopXray" }}</a-tag>
|
<a-tag color="purple" style="cursor: pointer;" @click="stopXrayService">{{ i18n "pages.index.stopXray" }}</a-tag>
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="restartXrayService">{{ i18n "pages.index.restartXray" }}</a-tag>
|
<a-tag color="purple" style="cursor: pointer;" @click="restartXrayService">{{ i18n "pages.index.restartXray" }}</a-tag>
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="openSelectV2rayVersion">{{ i18n "pages.index.xraySwitch" }}</a-tag>
|
<a-tag color="purple" style="cursor: pointer;" @click="openSelectV2rayVersion">{{ i18n "pages.index.xraySwitch" }}</a-tag>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
{{ i18n "pages.index.operationHours" }}:
|
{{ i18n "pages.index.operationHours" }}:
|
||||||
Xray:
|
Xray:
|
||||||
<a-tag color="green">[[ formatSecond(status.appStats.uptime) ]]</a-tag>
|
<a-tag color="green">[[ formatSecond(status.appStats.uptime) ]]</a-tag>
|
||||||
|
@ -116,7 +115,7 @@
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
{{ i18n "pages.index.systemLoad" }}: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
{{ i18n "pages.index.systemLoad" }}: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
|
@ -127,7 +126,7 @@
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
{{ i18n "usage"}}:
|
{{ i18n "usage"}}:
|
||||||
Memory [[ sizeFormat(status.appStats.mem) ]] -
|
Memory [[ sizeFormat(status.appStats.mem) ]] -
|
||||||
Threads [[ status.appStats.threads ]]
|
Threads [[ status.appStats.threads ]]
|
||||||
|
@ -135,7 +134,7 @@
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
IPv4:
|
IPv4:
|
||||||
|
@ -159,7 +158,7 @@
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
TCP: [[ status.tcpCount ]]
|
TCP: [[ status.tcpCount ]]
|
||||||
|
@ -183,7 +182,7 @@
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-icon type="arrow-up"></a-icon>
|
<a-icon type="arrow-up"></a-icon>
|
||||||
|
@ -209,7 +208,7 @@
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-icon type="cloud-upload"></a-icon>
|
<a-icon type="cloud-upload"></a-icon>
|
||||||
|
@ -241,12 +240,12 @@
|
||||||
|
|
||||||
<a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}'
|
<a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}'
|
||||||
:closable="true" @ok="() => versionModal.visible = false"
|
:closable="true" @ok="() => versionModal.visible = false"
|
||||||
:class="themeSwitcher.darkCardClass"
|
:class="themeSwitcher.currentTheme"
|
||||||
footer="">
|
footer="">
|
||||||
<h2>{{ i18n "pages.index.xraySwitchClick"}}</h2>
|
<h2>{{ i18n "pages.index.xraySwitchClick"}}</h2>
|
||||||
<h2>{{ i18n "pages.index.xraySwitchClickDesk"}}</h2>
|
<h2>{{ i18n "pages.index.xraySwitchClickDesk"}}</h2>
|
||||||
<template v-for="version, index in versionModal.versions">
|
<template v-for="version, index in versionModal.versions">
|
||||||
<a-tag :color="index % 2 == 0 ? 'blue' : 'green'"
|
<a-tag :color="index % 2 == 0 ? 'purple' : 'green'"
|
||||||
style="margin: 10px" @click="switchV2rayVersion(version)">
|
style="margin: 10px" @click="switchV2rayVersion(version)">
|
||||||
[[ version ]]
|
[[ version ]]
|
||||||
</a-tag>
|
</a-tag>
|
||||||
|
@ -255,7 +254,7 @@
|
||||||
|
|
||||||
<a-modal id="log-modal" v-model="logModal.visible" title="X-UI logs"
|
<a-modal id="log-modal" v-model="logModal.visible" title="X-UI logs"
|
||||||
:closable="true" @ok="() => logModal.visible = false" @cancel="() => logModal.visible = false"
|
:closable="true" @ok="() => logModal.visible = false" @cancel="() => logModal.visible = false"
|
||||||
:class="themeSwitcher.darkCardClass"
|
:class="themeSwitcher.currentTheme"
|
||||||
width="800px"
|
width="800px"
|
||||||
footer="">
|
footer="">
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
|
@ -263,7 +262,7 @@
|
||||||
<a-select v-model="logModal.rows"
|
<a-select v-model="logModal.rows"
|
||||||
style="width: 80px"
|
style="width: 80px"
|
||||||
@change="openLogs()"
|
@change="openLogs()"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="10">10</a-select-option>
|
<a-select-option value="10">10</a-select-option>
|
||||||
<a-select-option value="20">20</a-select-option>
|
<a-select-option value="20">20</a-select-option>
|
||||||
<a-select-option value="50">50</a-select-option>
|
<a-select-option value="50">50</a-select-option>
|
||||||
|
@ -274,7 +273,7 @@
|
||||||
<a-select v-model="logModal.level"
|
<a-select v-model="logModal.level"
|
||||||
style="width: 120px"
|
style="width: 120px"
|
||||||
@change="openLogs()"
|
@change="openLogs()"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="debug">Debug</a-select-option>
|
<a-select-option value="debug">Debug</a-select-option>
|
||||||
<a-select-option value="info">Info</a-select-option>
|
<a-select-option value="info">Info</a-select-option>
|
||||||
<a-select-option value="notice">Notice</a-select-option>
|
<a-select-option value="notice">Notice</a-select-option>
|
||||||
|
@ -300,12 +299,12 @@
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<a-modal id="backup-modal" v-model="backupModal.visible" :title="backupModal.title"
|
<a-modal id="backup-modal" v-model="backupModal.visible" :title="backupModal.title"
|
||||||
:closable="true" :class="themeSwitcher.darkCardClass"
|
:closable="true" :class="themeSwitcher.currentTheme"
|
||||||
@ok="() => backupModal.hide()" @cancel="() => backupModal.hide()">
|
@ok="() => backupModal.hide()" @cancel="() => backupModal.hide()">
|
||||||
<p style="color: inherit; font-size: 16px; padding: 4px 2px;">
|
<a-alert type="warning" style="margin-bottom: 10px; width: fit-content"
|
||||||
<a-icon type="warning" style="color: inherit; font-size: 20px;"></a-icon>
|
:message="backupModal.description"
|
||||||
[[ backupModal.description ]]
|
show-icon
|
||||||
</p>
|
></a-alert>
|
||||||
<a-space direction="horizontal" style="text-align: center" style="margin-bottom: 10px;">
|
<a-space direction="horizontal" style="text-align: center" style="margin-bottom: 10px;">
|
||||||
<a-button type="primary" @click="exportDatabase()">
|
<a-button type="primary" @click="exportDatabase()">
|
||||||
[[ backupModal.exportText ]]
|
[[ backupModal.exportText ]]
|
||||||
|
@ -346,11 +345,11 @@
|
||||||
get color() {
|
get color() {
|
||||||
const percent = this.percent;
|
const percent = this.percent;
|
||||||
if (percent < 80) {
|
if (percent < 80) {
|
||||||
return '#67C23A';
|
return '#0a7557';
|
||||||
} else if (percent < 90) {
|
} else if (percent < 90) {
|
||||||
return '#E6A23C';
|
return '#ffa031';
|
||||||
} else {
|
} else {
|
||||||
return '#F56C6C';
|
return '#e04141';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -504,7 +503,7 @@
|
||||||
title: '{{ i18n "pages.index.xraySwitchVersionDialog"}}',
|
title: '{{ i18n "pages.index.xraySwitchVersionDialog"}}',
|
||||||
content: '{{ i18n "pages.index.xraySwitchVersionDialogDesc"}}' + ` ${version}?`,
|
content: '{{ i18n "pages.index.xraySwitchVersionDialogDesc"}}' + ` ${version}?`,
|
||||||
okText: '{{ i18n "confirm"}}',
|
okText: '{{ i18n "confirm"}}',
|
||||||
class: themeSwitcher.darkCardClass,
|
class: themeSwitcher.currentTheme,
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
versionModal.hide();
|
versionModal.hide();
|
||||||
|
|
|
@ -8,8 +8,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-col-sm-24 {
|
@media (max-width: 768px) {
|
||||||
margin-top: 10px;
|
.ant-tabs-nav .ant-tabs-tab {
|
||||||
|
margin: 0;
|
||||||
|
padding: 12px .5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-tabs-bar {
|
.ant-tabs-bar {
|
||||||
|
@ -20,10 +23,6 @@
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
:not(.ant-card-dark)>.ant-tabs-top-bar {
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-msg {
|
.alert-msg {
|
||||||
color: rgb(194, 117, 18);
|
color: rgb(194, 117, 18);
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
|
@ -71,25 +70,31 @@
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<body>
|
<body>
|
||||||
<a-layout id="app" v-cloak>
|
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
|
||||||
{{ template "commonSider" . }}
|
{{ template "commonSider" . }}
|
||||||
<a-layout id="content-layout" :style="themeSwitcher.bgStyle">
|
<a-layout id="content-layout">
|
||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="spinning" :delay="500" tip="loading">
|
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
||||||
<a-space direction="vertical">
|
<a-space direction="vertical">
|
||||||
|
<a-card hoverable style="margin-bottom: .5rem;">
|
||||||
|
<a-row>
|
||||||
|
<a-col :xs="24" :sm="8" style="padding: 4px;">
|
||||||
<a-space direction="horizontal">
|
<a-space direction="horizontal">
|
||||||
<a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.settings.save" }}</a-button>
|
<a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.settings.save" }}</a-button>
|
||||||
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.settings.restartPanel" }}</a-button>
|
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.settings.restartPanel" }}</a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
<a-tabs style="margin:1rem 0.5rem;" default-active-key="1" :class="themeSwitcher.darkCardClass">
|
</a-col>
|
||||||
<a-tab-pane key="1" tab='{{ i18n "pages.settings.panelSettings"}}'>
|
<a-col :xs="24" :sm="16">
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
<a-alert type="warning" style="float: right; width: fit-content"
|
||||||
<h2 class="alert-msg">
|
message='{{ i18n "pages.settings.infoDesc" }}'
|
||||||
<a-icon type="warning"></a-icon>
|
show-icon
|
||||||
{{ i18n "pages.settings.infoDesc" }}
|
>
|
||||||
</h2>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-list item-layout="horizontal" :style="themeSwitcher.textStyle">
|
</a-card>
|
||||||
|
<a-tabs default-active-key="1">
|
||||||
|
<a-tab-pane key="1" tab='{{ i18n "pages.settings.panelSettings"}}'>
|
||||||
|
<a-list item-layout="horizontal">
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.panelListeningIP"}}' desc='{{ i18n "pages.settings.panelListeningIPDesc"}}' v-model="allSetting.webListen"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.panelListeningIP"}}' desc='{{ i18n "pages.settings.panelListeningIPDesc"}}' v-model="allSetting.webListen"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.panelListeningDomain"}}' desc='{{ i18n "pages.settings.panelListeningDomainDesc"}}' v-model="allSetting.webDomain"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.panelListeningDomain"}}' desc='{{ i18n "pages.settings.panelListeningDomainDesc"}}' v-model="allSetting.webDomain"></setting-list-item>
|
||||||
<setting-list-item type="number" title='{{ i18n "pages.settings.panelPort"}}' desc='{{ i18n "pages.settings.panelPortDesc"}}' v-model="allSetting.webPort" :min="0"></setting-list-item>
|
<setting-list-item type="number" title='{{ i18n "pages.settings.panelPort"}}' desc='{{ i18n "pages.settings.panelPortDesc"}}' v-model="allSetting.webPort" :min="0"></setting-list-item>
|
||||||
|
@ -112,7 +117,7 @@
|
||||||
ref="selectLang"
|
ref="selectLang"
|
||||||
v-model="lang"
|
v-model="lang"
|
||||||
@change="setLang(lang)"
|
@change="setLang(lang)"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
>
|
>
|
||||||
<a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs">
|
<a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs">
|
||||||
|
@ -127,9 +132,9 @@
|
||||||
</a-list>
|
</a-list>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="2" tab='{{ i18n "pages.settings.securitySettings"}}' style="padding: 20px;">
|
<a-tab-pane key="2" tab='{{ i18n "pages.settings.securitySettings"}}' style="padding: 20px;">
|
||||||
<a-tabs class="ant-card-dark-securitybox-nohover" default-active-key="sec-1" :class="themeSwitcher.darkCardClass">
|
<a-tabs class="ant-card-dark-securitybox-nohover" default-active-key="sec-1" :class="themeSwitcher.currentTheme">
|
||||||
<a-tab-pane key="sec-1" tab='{{ i18n "pages.settings.security.admin"}}'>
|
<a-tab-pane key="sec-1" tab='{{ i18n "pages.settings.security.admin"}}'>
|
||||||
<a-form :style="'padding: 20px;' + themeSwitcher.textStyle">
|
<a-form style="padding: 20px;">
|
||||||
<a-form-item label='{{ i18n "pages.settings.oldUsername"}}'>
|
<a-form-item label='{{ i18n "pages.settings.oldUsername"}}'>
|
||||||
<a-input v-model="user.oldUsername" style="max-width: 300px"></a-input>
|
<a-input v-model="user.oldUsername" style="max-width: 300px"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
@ -148,7 +153,7 @@
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="sec-2" tab='{{ i18n "pages.settings.security.secret"}}'>
|
<a-tab-pane key="sec-2" tab='{{ i18n "pages.settings.security.secret"}}'>
|
||||||
<a-form :style="'padding: 20px;' + themeSwitcher.textStyle">
|
<a-form style="padding: 20px;">
|
||||||
<a-list-item style="padding: 20px">
|
<a-list-item style="padding: 20px">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
|
@ -183,188 +188,8 @@
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
|
|
||||||
<a-tab-pane key="3" tab='{{ i18n "pages.settings.xrayConfiguration"}}'>
|
<a-tab-pane key="3" tab='{{ i18n "pages.settings.TGBotSettings"}}'>
|
||||||
<a-list item-layout="horizontal" :style="themeSwitcher.textStyle">
|
<a-list item-layout="horizontal">
|
||||||
<a-divider style="padding: 20px;">{{ i18n "pages.settings.actions"}}</a-divider>
|
|
||||||
<a-space direction="horizontal" style="padding: 0px 20px">
|
|
||||||
<a-button type="primary" @click="resetXrayConfigToDefault">{{ i18n "pages.settings.resetDefaultConfig" }}</a-button>
|
|
||||||
</a-space>
|
|
||||||
<a-divider style="padding: 20px;">{{ i18n "pages.settings.templates.title"}} </a-divider>
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<h2 class="alert-msg">
|
|
||||||
<a-icon type="warning"></a-icon>
|
|
||||||
{{ i18n "pages.settings.infoDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<a-tabs class="ant-card-dark-box-nohover" default-active-key="tpl-1" :class="themeSwitcher.darkCardClass" style="padding: 20px 20px;">
|
|
||||||
<a-tab-pane key="tpl-1" tab='{{ i18n "pages.settings.templates.basicTemplate"}}' style="padding-top: 20px;">
|
|
||||||
<a-collapse>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.generalConfigs"}}'>
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<h2 class="collapse-title">
|
|
||||||
<a-icon type="warning"></a-icon>
|
|
||||||
{{ i18n "pages.settings.templates.generalConfigsDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<a-list-item>
|
|
||||||
<a-row style="padding: 20px">
|
|
||||||
<a-col :lg="24" :xl="12">
|
|
||||||
<a-list-item-meta
|
|
||||||
title='{{ i18n "pages.settings.templates.xrayConfigFreedomStrategy" }}'
|
|
||||||
description='{{ i18n "pages.settings.templates.xrayConfigFreedomStrategyDesc" }}'/>
|
|
||||||
</a-col>
|
|
||||||
<a-col :lg="24" :xl="12">
|
|
||||||
<template>
|
|
||||||
<a-select
|
|
||||||
v-model="freedomStrategy"
|
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
|
||||||
style="width: 100%">
|
|
||||||
<a-select-option v-for="s in outboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</template>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</a-list-item>
|
|
||||||
<a-row style="padding: 20px">
|
|
||||||
<a-col :lg="24" :xl="12">
|
|
||||||
<a-list-item-meta
|
|
||||||
title='{{ i18n "pages.settings.templates.xrayConfigRoutingStrategy" }}'
|
|
||||||
description='{{ i18n "pages.settings.templates.xrayConfigRoutingStrategyDesc" }}'/>
|
|
||||||
</a-col>
|
|
||||||
<a-col :lg="24" :xl="12">
|
|
||||||
<template>
|
|
||||||
<a-select
|
|
||||||
v-model="routingStrategy"
|
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
|
||||||
style="width: 100%">
|
|
||||||
<a-select-option v-for="s in routingDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</template>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</a-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.blockConfigs"}}'>
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<h2 class="collapse-title">
|
|
||||||
<a-icon type="warning"></a-icon>
|
|
||||||
{{ i18n "pages.settings.templates.blockConfigsDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigTorrent"}}' desc='{{ i18n "pages.settings.templates.xrayConfigTorrentDesc"}}' v-model="torrentSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigPrivateIp"}}' desc='{{ i18n "pages.settings.templates.xrayConfigPrivateIpDesc"}}' v-model="privateIpSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigAds"}}' desc='{{ i18n "pages.settings.templates.xrayConfigAdsDesc"}}' v-model="AdsSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigFamily"}}' desc='{{ i18n "pages.settings.templates.xrayConfigFamilyDesc"}}' v-model="familyProtectSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigSpeedtest"}}' desc='{{ i18n "pages.settings.templates.xrayConfigSpeedtestDesc"}}' v-model="SpeedTestSettings"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.blockCountryConfigs"}}'>
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<h2 class="collapse-title">
|
|
||||||
<a-icon type="warning"></a-icon>
|
|
||||||
{{ i18n "pages.settings.templates.blockCountryConfigsDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigIRIp"}}' desc='{{ i18n "pages.settings.templates.xrayConfigIRIpDesc"}}' v-model="IRIpSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigIRDomain"}}' desc='{{ i18n "pages.settings.templates.xrayConfigIRDomainDesc"}}' v-model="IRDomainSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigChinaIp"}}' desc='{{ i18n "pages.settings.templates.xrayConfigChinaIpDesc"}}' v-model="ChinaIpSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigChinaDomain"}}' desc='{{ i18n "pages.settings.templates.xrayConfigChinaDomainDesc"}}' v-model="ChinaDomainSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigRussiaIp"}}' desc='{{ i18n "pages.settings.templates.xrayConfigRussiaIpDesc"}}' v-model="RussiaIpSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigRussiaDomain"}}' desc='{{ i18n "pages.settings.templates.xrayConfigRussiaDomainDesc"}}' v-model="RussiaDomainSettings"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.directCountryConfigs"}}'>
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<h2 class="collapse-title">
|
|
||||||
<a-icon type="warning"></a-icon>
|
|
||||||
{{ i18n "pages.settings.templates.directCountryConfigsDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigDirectIRIp"}}' desc='{{ i18n "pages.settings.templates.xrayConfigDirectIRIpDesc"}}' v-model="IRIpDirectSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigDirectIRDomain"}}' desc='{{ i18n "pages.settings.templates.xrayConfigDirectIRDomainDesc"}}' v-model="IRDomainDirectSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigDirectChinaIp"}}' desc='{{ i18n "pages.settings.templates.xrayConfigDirectChinaIpDesc"}}' v-model="ChinaIpDirectSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigDirectChinaDomain"}}' desc='{{ i18n "pages.settings.templates.xrayConfigDirectChinaDomainDesc"}}' v-model="ChinaDomainDirectSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigDirectRussiaIp"}}' desc='{{ i18n "pages.settings.templates.xrayConfigDirectRussiaIpDesc"}}' v-model="RussiaIpDirectSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigDirectRussiaDomain"}}' desc='{{ i18n "pages.settings.templates.xrayConfigDirectRussiaDomainDesc"}}' v-model="RussiaDomainDirectSettings"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.ipv4Configs"}}'>
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<h2 class="collapse-title">
|
|
||||||
<a-icon type="warning"></a-icon>
|
|
||||||
{{ i18n "pages.settings.templates.ipv4ConfigsDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigGoogleIPv4"}}' desc='{{ i18n "pages.settings.templates.xrayConfigGoogleIPv4Desc"}}' v-model="GoogleIPv4Settings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigNetflixIPv4"}}' desc='{{ i18n "pages.settings.templates.xrayConfigNetflixIPv4Desc"}}' v-model="NetflixIPv4Settings"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.warpConfigs"}}'>
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<h2 class="collapse-title">
|
|
||||||
<a-icon type="warning"></a-icon>
|
|
||||||
{{ i18n "pages.settings.templates.warpConfigsDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigGoogleWARP"}}' desc='{{ i18n "pages.settings.templates.xrayConfigGoogleWARPDesc"}}' v-model="GoogleWARPSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigOpenAIWARP"}}' desc='{{ i18n "pages.settings.templates.xrayConfigOpenAIWARPDesc"}}' v-model="OpenAIWARPSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigNetflixWARP"}}' desc='{{ i18n "pages.settings.templates.xrayConfigNetflixWARPDesc"}}' v-model="NetflixWARPSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigSpotifyWARP"}}' desc='{{ i18n "pages.settings.templates.xrayConfigSpotifyWARPDesc"}}' v-model="SpotifyWARPSettings"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
</a-collapse>
|
|
||||||
</a-tab-pane>
|
|
||||||
<a-tab-pane key="tpl-2" tab='{{ i18n "pages.settings.templates.manualLists"}}' style="padding-top: 20px;">
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<h2 class="collapse-title">
|
|
||||||
<a-icon type="warning"></a-icon>
|
|
||||||
{{ i18n "pages.settings.templates.manualListsDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<a-collapse>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.manualBlockedIPs"}}'>
|
|
||||||
<setting-list-item type="textarea" v-model="manualBlockedIPs"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.manualBlockedDomains"}}'>
|
|
||||||
<setting-list-item type="textarea" v-model="manualBlockedDomains"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.manualDirectIPs"}}'>
|
|
||||||
<setting-list-item type="textarea" v-model="manualDirectIPs"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.manualDirectDomains"}}'>
|
|
||||||
<setting-list-item type="textarea" v-model="manualDirectDomains"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.manualIPv4Domains"}}'>
|
|
||||||
<setting-list-item type="textarea" v-model="manualIPv4Domains"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.manualWARPDomains"}}'>
|
|
||||||
<setting-list-item type="textarea" v-model="manualWARPDomains"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
</a-collapse>
|
|
||||||
</a-tab-pane>
|
|
||||||
<a-tab-pane key="tpl-3" tab='{{ i18n "pages.settings.templates.advancedTemplate"}}' style="padding-top: 20px;">
|
|
||||||
<a-collapse>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.xrayConfigInbounds"}}'>
|
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.xrayConfigInbounds"}}' desc='{{ i18n "pages.settings.templates.xrayConfigInboundsDesc"}}' v-model="inboundSettings"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.xrayConfigOutbounds"}}'>
|
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.xrayConfigOutbounds"}}' desc='{{ i18n "pages.settings.templates.xrayConfigOutboundsDesc"}}' v-model="outboundSettings"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.xrayConfigRoutings"}}'>
|
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.xrayConfigRoutings"}}' desc='{{ i18n "pages.settings.templates.xrayConfigRoutingsDesc"}}' v-model="routingRuleSettings"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
</a-collapse>
|
|
||||||
</a-tab-pane>
|
|
||||||
<a-tab-pane key="tpl-4" tab='{{ i18n "pages.settings.templates.completeTemplate"}}' style="padding-top: 20px;">
|
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.xrayConfigTemplate"}}' desc='{{ i18n "pages.settings.templates.xrayConfigTemplateDesc"}}' v-model="allSetting.xrayTemplateConfig"></setting-list-item>
|
|
||||||
</a-tab-pane>
|
|
||||||
</a-tabs>
|
|
||||||
</a-list>
|
|
||||||
</a-tab-pane>
|
|
||||||
|
|
||||||
<a-tab-pane key="4" tab='{{ i18n "pages.settings.TGBotSettings"}}'>
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<h2 class="alert-msg">
|
|
||||||
<a-icon type="warning"></a-icon>
|
|
||||||
{{ i18n "pages.settings.infoDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<a-list item-layout="horizontal" :style="themeSwitcher.textStyle">
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.telegramBotEnable" }}' desc='{{ i18n "pages.settings.telegramBotEnableDesc" }}' v-model="allSetting.tgBotEnable"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.telegramBotEnable" }}' desc='{{ i18n "pages.settings.telegramBotEnableDesc" }}' v-model="allSetting.tgBotEnable"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramToken"}}' desc='{{ i18n "pages.settings.telegramTokenDesc"}}' v-model="allSetting.tgBotToken"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramToken"}}' desc='{{ i18n "pages.settings.telegramTokenDesc"}}' v-model="allSetting.tgBotToken"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramChatId"}}' desc='{{ i18n "pages.settings.telegramChatIdDesc"}}' v-model="allSetting.tgBotChatId"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramChatId"}}' desc='{{ i18n "pages.settings.telegramChatIdDesc"}}' v-model="allSetting.tgBotChatId"></setting-list-item>
|
||||||
|
@ -383,7 +208,7 @@
|
||||||
<a-select
|
<a-select
|
||||||
ref="selectBotLang"
|
ref="selectBotLang"
|
||||||
v-model="allSetting.tgLang"
|
v-model="allSetting.tgLang"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
>
|
>
|
||||||
<a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs">
|
<a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs">
|
||||||
|
@ -397,14 +222,8 @@
|
||||||
</a-list-item>
|
</a-list-item>
|
||||||
</a-list>
|
</a-list>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="5" tab='{{ i18n "pages.settings.subSettings" }}'>
|
<a-tab-pane key="4" tab='{{ i18n "pages.settings.subSettings" }}'>
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
<a-list item-layout="horizontal">
|
||||||
<h2 class="alert-msg">
|
|
||||||
<a-icon type="warning"></a-icon>
|
|
||||||
{{ i18n "pages.settings.infoDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<a-list item-layout="horizontal" :style="themeSwitcher.textStyle">
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.subEnable"}}' desc='{{ i18n "pages.settings.subEnableDesc"}}' v-model="allSetting.subEnable"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.subEnable"}}' desc='{{ i18n "pages.settings.subEnableDesc"}}' v-model="allSetting.subEnable"></setting-list-item>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.subEncrypt"}}' desc='{{ i18n "pages.settings.subEncryptDesc"}}' v-model="allSetting.subEncrypt"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.subEncrypt"}}' desc='{{ i18n "pages.settings.subEncryptDesc"}}' v-model="allSetting.subEncrypt"></setting-list-item>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.subShowInfo"}}' desc='{{ i18n "pages.settings.subShowInfoDesc"}}' v-model="allSetting.subShowInfo"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.subShowInfo"}}' desc='{{ i18n "pages.settings.subShowInfoDesc"}}' v-model="allSetting.subShowInfo"></setting-list-item>
|
||||||
|
@ -440,75 +259,6 @@
|
||||||
saveBtnDisable: true,
|
saveBtnDisable: true,
|
||||||
user: new User(),
|
user: new User(),
|
||||||
lang: getLang(),
|
lang: getLang(),
|
||||||
ipv4Settings: {
|
|
||||||
tag: "IPv4",
|
|
||||||
protocol: "freedom",
|
|
||||||
settings: {
|
|
||||||
domainStrategy: "UseIPv4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
warpSettings: {
|
|
||||||
tag: "WARP",
|
|
||||||
protocol: "socks",
|
|
||||||
settings: {
|
|
||||||
servers: [
|
|
||||||
{
|
|
||||||
address: "127.0.0.1",
|
|
||||||
port: 40000
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
directSettings: {
|
|
||||||
tag: "direct",
|
|
||||||
protocol: "freedom"
|
|
||||||
},
|
|
||||||
outboundDomainStrategies: ["AsIs", "UseIP", "UseIPv4", "UseIPv6"],
|
|
||||||
routingDomainStrategies: ["AsIs", "IPIfNonMatch", "IPOnDemand"],
|
|
||||||
settingsData: {
|
|
||||||
protocols: {
|
|
||||||
bittorrent: ["bittorrent"],
|
|
||||||
},
|
|
||||||
ips: {
|
|
||||||
local: ["geoip:private"],
|
|
||||||
cn: ["geoip:cn"],
|
|
||||||
ir: ["ext:geoip_IR.dat:ir","ext:geoip_IR.dat:arvancloud","ext:geoip_IR.dat:derakcloud","ext:geoip_IR.dat:iranserver"],
|
|
||||||
ru: ["geoip:ru"],
|
|
||||||
},
|
|
||||||
domains: {
|
|
||||||
ads: [
|
|
||||||
"geosite:category-ads-all",
|
|
||||||
"ext:geosite_IR.dat:category-ads-all"
|
|
||||||
],
|
|
||||||
speedtest: ["geosite:speedtest"],
|
|
||||||
openai: ["geosite:openai"],
|
|
||||||
google: ["geosite:google"],
|
|
||||||
spotify: ["geosite:spotify"],
|
|
||||||
netflix: ["geosite:netflix"],
|
|
||||||
cn: [
|
|
||||||
"geosite:cn",
|
|
||||||
"regexp:.*\\.cn$"
|
|
||||||
],
|
|
||||||
ru: [
|
|
||||||
"geosite:category-gov-ru",
|
|
||||||
"regexp:.*\\.ru$"
|
|
||||||
],
|
|
||||||
ir: [
|
|
||||||
"regexp:.*\\.ir$",
|
|
||||||
"regexp:.*\\.xn--mgba3a4f16a$", // .ایران
|
|
||||||
"ext:geosite_IR.dat:ir" // have rules to bypass all .ir domains.
|
|
||||||
]
|
|
||||||
},
|
|
||||||
familyProtectDNS: {
|
|
||||||
"servers": [
|
|
||||||
"1.1.1.3", // https://developers.cloudflare.com/1.1.1.1/setup/
|
|
||||||
"1.0.0.3",
|
|
||||||
"94.140.14.15", // https://adguard-dns.io/kb/general/dns-providers/
|
|
||||||
"94.140.15.16"
|
|
||||||
],
|
|
||||||
"queryStrategy": "UseIPv4"
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
loading(spinning = true) {
|
loading(spinning = true) {
|
||||||
|
@ -547,6 +297,7 @@
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.settings.restartPanel" }}',
|
title: '{{ i18n "pages.settings.restartPanel" }}',
|
||||||
content: '{{ i18n "pages.settings.restartPanelDesc" }}',
|
content: '{{ i18n "pages.settings.restartPanelDesc" }}',
|
||||||
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "sure" }}',
|
okText: '{{ i18n "sure" }}',
|
||||||
cancelText: '{{ i18n "cancel" }}',
|
cancelText: '{{ i18n "cancel" }}',
|
||||||
onOk: () => resolve(),
|
onOk: () => resolve(),
|
||||||
|
@ -558,7 +309,9 @@
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
await PromiseUtil.sleep(5000);
|
await PromiseUtil.sleep(5000);
|
||||||
const { webCertFile, webKeyFile, webDomain: host, webPort: port, webBasePath: base } = this.allSetting;
|
var { webCertFile, webKeyFile, webDomain: host, webPort: port, webBasePath: base } = this.allSetting;
|
||||||
|
if (host == this.oldAllSetting.webDomain) host = null;
|
||||||
|
if (port == this.oldAllSetting.webPort) port = null;
|
||||||
const isTLS = webCertFile !== "" || webKeyFile !== "";
|
const isTLS = webCertFile !== "" || webKeyFile !== "";
|
||||||
const url = buildURL({ host, port, isTLS, base, path: "panel/settings" });
|
const url = buildURL({ host, port, isTLS, base, path: "panel/settings" });
|
||||||
window.location.replace(url);
|
window.location.replace(url);
|
||||||
|
@ -605,83 +358,6 @@
|
||||||
this.user.loginSecret = "";
|
this.user.loginSecret = "";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async resetXrayConfigToDefault() {
|
|
||||||
this.loading(true);
|
|
||||||
const msg = await HttpUtil.get("/panel/setting/getDefaultJsonConfig");
|
|
||||||
this.loading(false);
|
|
||||||
if (msg.success) {
|
|
||||||
this.templateSettings = JSON.parse(JSON.stringify(msg.obj, null, 2));
|
|
||||||
this.saveBtnDisable = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
syncRulesWithOutbound(tag, setting) {
|
|
||||||
const newTemplateSettings = {...this.templateSettings};
|
|
||||||
const haveRules = newTemplateSettings.routing.rules.some((r) => r?.outboundTag === tag);
|
|
||||||
const outboundIndex = newTemplateSettings.outbounds.findIndex((o) => o.tag === tag);
|
|
||||||
if (!haveRules && outboundIndex >= 0) {
|
|
||||||
newTemplateSettings.outbounds.splice(outboundIndex, 1);
|
|
||||||
}
|
|
||||||
if (haveRules && outboundIndex === -1) {
|
|
||||||
newTemplateSettings.outbounds.push(setting);
|
|
||||||
}
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
},
|
|
||||||
templateRuleGetter(routeSettings) {
|
|
||||||
const { property, outboundTag } = routeSettings;
|
|
||||||
let result = [];
|
|
||||||
if (this.templateSettings != null) {
|
|
||||||
this.templateSettings.routing.rules.forEach(
|
|
||||||
(routingRule) => {
|
|
||||||
if (
|
|
||||||
routingRule.hasOwnProperty(property) &&
|
|
||||||
routingRule.hasOwnProperty("outboundTag") &&
|
|
||||||
routingRule.outboundTag === outboundTag
|
|
||||||
) {
|
|
||||||
result.push(...routingRule[property]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
templateRuleSetter(routeSettings) {
|
|
||||||
const { data, property, outboundTag } = routeSettings;
|
|
||||||
const oldTemplateSettings = this.templateSettings;
|
|
||||||
const newTemplateSettings = oldTemplateSettings;
|
|
||||||
currentProperty = this.templateRuleGetter({ outboundTag, property })
|
|
||||||
if (currentProperty.length == 0) {
|
|
||||||
const propertyRule = {
|
|
||||||
type: "field",
|
|
||||||
outboundTag,
|
|
||||||
[property]: data
|
|
||||||
};
|
|
||||||
newTemplateSettings.routing.rules.push(propertyRule);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const newRules = [];
|
|
||||||
insertedOnce = false;
|
|
||||||
newTemplateSettings.routing.rules.forEach(
|
|
||||||
(routingRule) => {
|
|
||||||
if (
|
|
||||||
routingRule.hasOwnProperty(property) &&
|
|
||||||
routingRule.hasOwnProperty("outboundTag") &&
|
|
||||||
routingRule.outboundTag === outboundTag
|
|
||||||
) {
|
|
||||||
if (!insertedOnce && data.length > 0) {
|
|
||||||
insertedOnce = true;
|
|
||||||
routingRule[property] = data;
|
|
||||||
newRules.push(routingRule);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
newRules.push(routingRule);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
newTemplateSettings.routing.rules = newRules;
|
|
||||||
}
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
await this.getAllSetting();
|
await this.getAllSetting();
|
||||||
|
@ -690,429 +366,6 @@
|
||||||
this.saveBtnDisable = this.oldAllSetting.equals(this.allSetting);
|
this.saveBtnDisable = this.oldAllSetting.equals(this.allSetting);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
|
||||||
templateSettings: {
|
|
||||||
get: function () { return this.allSetting.xrayTemplateConfig ? JSON.parse(this.allSetting.xrayTemplateConfig) : null; },
|
|
||||||
set: function (newValue) { this.allSetting.xrayTemplateConfig = JSON.stringify(newValue, null, 2); },
|
|
||||||
},
|
|
||||||
inboundSettings: {
|
|
||||||
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.inbounds, null, 2) : null; },
|
|
||||||
set: function (newValue) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
newTemplateSettings.inbounds = JSON.parse(newValue);
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
outboundSettings: {
|
|
||||||
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.outbounds, null, 2) : null; },
|
|
||||||
set: function (newValue) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
newTemplateSettings.outbounds = JSON.parse(newValue);
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
routingRuleSettings: {
|
|
||||||
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.routing.rules, null, 2) : null; },
|
|
||||||
set: function (newValue) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
newTemplateSettings.routing.rules = JSON.parse(newValue);
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
freedomStrategy: {
|
|
||||||
get: function () {
|
|
||||||
if (!this.templateSettings) return "AsIs";
|
|
||||||
freedomOutbound = this.templateSettings.outbounds.find((o) => o.protocol === "freedom" && !o.tag);
|
|
||||||
if (!freedomOutbound) return "AsIs";
|
|
||||||
if (!freedomOutbound.settings || !freedomOutbound.settings.domainStrategy) return "AsIs";
|
|
||||||
return freedomOutbound.settings.domainStrategy;
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
freedomOutboundIndex = newTemplateSettings.outbounds.findIndex((o) => o.protocol === "freedom" && !o.tag);
|
|
||||||
if (!newTemplateSettings.outbounds[freedomOutboundIndex].settings) {
|
|
||||||
newTemplateSettings.outbounds[freedomOutboundIndex].settings = {"domainStrategy": newValue};
|
|
||||||
} else {
|
|
||||||
newTemplateSettings.outbounds[freedomOutboundIndex].settings.domainStrategy = newValue;
|
|
||||||
}
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
routingStrategy: {
|
|
||||||
get: function () {
|
|
||||||
if (!this.templateSettings || !this.templateSettings.routing || !this.templateSettings.routing.domainStrategy) return "AsIs";
|
|
||||||
return this.templateSettings.routing.domainStrategy;
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
newTemplateSettings.routing.domainStrategy = newValue;
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
blockedIPs: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({ outboundTag: "blocked", property: "ip" });
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({ outboundTag: "blocked", property: "ip", data: newValue });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
blockedDomains: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({ outboundTag: "blocked", property: "domain" });
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({ outboundTag: "blocked", property: "domain", data: newValue });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
blockedProtocols: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({ outboundTag: "blocked", property: "protocol" });
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({ outboundTag: "blocked", property: "protocol", data: newValue });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
directIPs: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({ outboundTag: "direct", property: "ip" });
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({ outboundTag: "direct", property: "ip", data: newValue });
|
|
||||||
this.syncRulesWithOutbound("direct", this.directSettings);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
directDomains: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({ outboundTag: "direct", property: "domain" });
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({ outboundTag: "direct", property: "domain", data: newValue });
|
|
||||||
this.syncRulesWithOutbound("direct", this.directSettings);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ipv4Domains: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({ outboundTag: "IPv4", property: "domain" });
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({ outboundTag: "IPv4", property: "domain", data: newValue });
|
|
||||||
this.syncRulesWithOutbound("IPv4", this.ipv4Settings);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
warpDomains: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({ outboundTag: "WARP", property: "domain" });
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({ outboundTag: "WARP", property: "domain", data: newValue });
|
|
||||||
this.syncRulesWithOutbound("WARP", this.warpSettings);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
manualBlockedIPs: {
|
|
||||||
get: function () { return JSON.stringify(this.blockedIPs, null, 2); },
|
|
||||||
set: debounce(function (value) { this.blockedIPs = JSON.parse(value); }, 1000)
|
|
||||||
},
|
|
||||||
manualBlockedDomains: {
|
|
||||||
get: function () { return JSON.stringify(this.blockedDomains, null, 2); },
|
|
||||||
set: debounce(function (value) { this.blockedDomains = JSON.parse(value); }, 1000)
|
|
||||||
},
|
|
||||||
manualDirectIPs: {
|
|
||||||
get: function () { return JSON.stringify(this.directIPs, null, 2); },
|
|
||||||
set: debounce(function (value) { this.directIPs = JSON.parse(value); }, 1000)
|
|
||||||
},
|
|
||||||
manualDirectDomains: {
|
|
||||||
get: function () { return JSON.stringify(this.directDomains, null, 2); },
|
|
||||||
set: debounce(function (value) { this.directDomains = JSON.parse(value); }, 1000)
|
|
||||||
},
|
|
||||||
manualIPv4Domains: {
|
|
||||||
get: function () { return JSON.stringify(this.ipv4Domains, null, 2); },
|
|
||||||
set: debounce(function (value) { this.ipv4Domains = JSON.parse(value); }, 1000)
|
|
||||||
},
|
|
||||||
manualWARPDomains: {
|
|
||||||
get: function () { return JSON.stringify(this.warpDomains, null, 2); },
|
|
||||||
set: debounce(function (value) { this.warpDomains = JSON.parse(value); }, 1000)
|
|
||||||
},
|
|
||||||
torrentSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.protocols.bittorrent, this.blockedProtocols);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.blockedProtocols = [...this.blockedProtocols, ...this.settingsData.protocols.bittorrent];
|
|
||||||
} else {
|
|
||||||
this.blockedProtocols = this.blockedProtocols.filter(data => !this.settingsData.protocols.bittorrent.includes(data));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
privateIpSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.ips.local, this.blockedIPs);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.local];
|
|
||||||
} else {
|
|
||||||
this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.local.includes(data));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
AdsSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.ads, this.blockedDomains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.ads];
|
|
||||||
} else {
|
|
||||||
this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.ads.includes(data));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
SpeedTestSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.speedtest, this.blockedDomains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.speedtest];
|
|
||||||
} else {
|
|
||||||
this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.speedtest.includes(data));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
familyProtectSettings: {
|
|
||||||
get: function () {
|
|
||||||
if (!this.templateSettings || !this.templateSettings.dns || !this.templateSettings.dns.servers) return false;
|
|
||||||
return doAllItemsExist(this.templateSettings.dns.servers, this.settingsData.familyProtectDNS.servers);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
if (newValue) {
|
|
||||||
newTemplateSettings.dns = this.settingsData.familyProtectDNS;
|
|
||||||
} else {
|
|
||||||
delete newTemplateSettings.dns;
|
|
||||||
}
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
GoogleIPv4Settings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.google, this.ipv4Domains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.ipv4Domains = [...this.ipv4Domains, ...this.settingsData.domains.google];
|
|
||||||
} else {
|
|
||||||
this.ipv4Domains = this.ipv4Domains.filter(data => !this.settingsData.domains.google.includes(data));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
NetflixIPv4Settings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.netflix, this.ipv4Domains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.ipv4Domains = [...this.ipv4Domains, ...this.settingsData.domains.netflix];
|
|
||||||
} else {
|
|
||||||
this.ipv4Domains = this.ipv4Domains.filter(data => !this.settingsData.domains.netflix.includes(data));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
IRIpSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.ips.ir, this.blockedIPs);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.ir];
|
|
||||||
} else {
|
|
||||||
this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.ir.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
IRDomainSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.ir, this.blockedDomains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.ir];
|
|
||||||
} else {
|
|
||||||
this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.ir.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ChinaIpSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.ips.cn, this.blockedIPs);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.cn];
|
|
||||||
} else {
|
|
||||||
this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.cn.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ChinaDomainSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.cn, this.blockedDomains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.cn];
|
|
||||||
} else {
|
|
||||||
this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.cn.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
RussiaIpSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.ips.ru, this.blockedIPs);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.ru];
|
|
||||||
} else {
|
|
||||||
this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.ru.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
RussiaDomainSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.ru, this.blockedDomains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.ru];
|
|
||||||
} else {
|
|
||||||
this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.ru.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
IRIpDirectSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.ips.ir, this.directIPs);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.directIPs = [...this.directIPs, ...this.settingsData.ips.ir];
|
|
||||||
} else {
|
|
||||||
this.directIPs = this.directIPs.filter(data => !this.settingsData.ips.ir.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
IRDomainDirectSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.ir, this.directDomains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.directDomains = [...this.directDomains, ...this.settingsData.domains.ir];
|
|
||||||
} else {
|
|
||||||
this.directDomains = this.directDomains.filter(data => !this.settingsData.domains.ir.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ChinaIpDirectSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.ips.cn, this.directIPs);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.directIPs = [...this.directIPs, ...this.settingsData.ips.cn];
|
|
||||||
} else {
|
|
||||||
this.directIPs = this.directIPs.filter(data => !this.settingsData.ips.cn.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ChinaDomainDirectSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.cn, this.directDomains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.directDomains = [...this.directDomains, ...this.settingsData.domains.cn];
|
|
||||||
} else {
|
|
||||||
this.directDomains = this.directDomains.filter(data => !this.settingsData.domains.cn.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
RussiaIpDirectSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.ips.ru, this.directIPs);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.directIPs = [...this.directIPs, ...this.settingsData.ips.ru];
|
|
||||||
} else {
|
|
||||||
this.directIPs = this.directIPs.filter(data => !this.settingsData.ips.ru.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
RussiaDomainDirectSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.ru, this.directDomains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.directDomains = [...this.directDomains, ...this.settingsData.domains.ru];
|
|
||||||
} else {
|
|
||||||
this.directDomains = this.directDomains.filter(data => !this.settingsData.domains.ru.includes(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
GoogleWARPSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.google, this.warpDomains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.google];
|
|
||||||
} else {
|
|
||||||
this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.google.includes(data));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
OpenAIWARPSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.openai, this.warpDomains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.openai];
|
|
||||||
} else {
|
|
||||||
this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.openai.includes(data));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
NetflixWARPSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.netflix, this.warpDomains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.netflix];
|
|
||||||
} else {
|
|
||||||
this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.netflix.includes(data));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
SpotifyWARPSettings: {
|
|
||||||
get: function () {
|
|
||||||
return doAllItemsExist(this.settingsData.domains.spotify, this.warpDomains);
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.spotify];
|
|
||||||
} else {
|
|
||||||
this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.spotify.includes(data));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
911
web/html/xui/xray.html
Normal file
911
web/html/xui/xray.html
Normal file
|
@ -0,0 +1,911 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
{{template "head" .}}
|
||||||
|
<style>
|
||||||
|
@media (min-width: 769px) {
|
||||||
|
.ant-layout-content {
|
||||||
|
margin: 24px 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.ant-tabs-nav .ant-tabs-tab {
|
||||||
|
margin: 0;
|
||||||
|
padding: 12px .5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tabs-bar {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-list-item {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-title {
|
||||||
|
color: inherit;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 18px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-bottom: 2px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-title > i {
|
||||||
|
color: inherit;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<body>
|
||||||
|
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
|
||||||
|
{{ template "commonSider" . }}
|
||||||
|
<a-layout id="content-layout">
|
||||||
|
<a-layout-content>
|
||||||
|
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
||||||
|
<a-space direction="vertical">
|
||||||
|
<a-card hoverable style="margin-bottom: .5rem;">
|
||||||
|
<a-row>
|
||||||
|
<a-col :xs="24" :sm="8" style="padding: 4px;">
|
||||||
|
<a-space direction="horizontal">
|
||||||
|
<a-button type="primary" :disabled="saveBtnDisable" @click="updateXraySetting">{{ i18n "pages.settings.save" }}</a-button>
|
||||||
|
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.settings.restartPanel" }}</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
<a-col :xs="24" :sm="16">
|
||||||
|
<a-alert type="warning" style="float: right; width: fit-content"
|
||||||
|
message='{{ i18n "pages.settings.infoDesc" }}'
|
||||||
|
show-icon
|
||||||
|
>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-card>
|
||||||
|
<a-tabs class="ant-card-dark-box-nohover" default-active-key="tpl-1" :class="themeSwitcher.currentTheme" style="padding: 20px 20px;">
|
||||||
|
<a-tab-pane key="tpl-1" tab='{{ i18n "pages.settings.templates.basicTemplate"}}' style="padding-top: 20px;">
|
||||||
|
<a-space direction="horizontal" style="padding: 20px 20px">
|
||||||
|
<a-button type="primary" @click="resetXrayConfigToDefault">{{ i18n "pages.settings.resetDefaultConfig" }}</a-button>
|
||||||
|
</a-space>
|
||||||
|
<a-collapse>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.generalConfigs"}}'>
|
||||||
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
|
<h2 class="collapse-title">
|
||||||
|
<a-icon type="warning"></a-icon>
|
||||||
|
{{ i18n "pages.settings.templates.generalConfigsDesc" }}
|
||||||
|
</h2>
|
||||||
|
</a-row>
|
||||||
|
<a-list-item>
|
||||||
|
<a-row style="padding: 20px">
|
||||||
|
<a-col :lg="24" :xl="12">
|
||||||
|
<a-list-item-meta
|
||||||
|
title='{{ i18n "pages.settings.templates.xrayConfigFreedomStrategy" }}'
|
||||||
|
description='{{ i18n "pages.settings.templates.xrayConfigFreedomStrategyDesc" }}'/>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="24" :xl="12">
|
||||||
|
<template>
|
||||||
|
<a-select
|
||||||
|
v-model="freedomStrategy"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
|
style="width: 100%">
|
||||||
|
<a-select-option v-for="s in outboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-list-item>
|
||||||
|
<a-row style="padding: 20px">
|
||||||
|
<a-col :lg="24" :xl="12">
|
||||||
|
<a-list-item-meta
|
||||||
|
title='{{ i18n "pages.settings.templates.xrayConfigRoutingStrategy" }}'
|
||||||
|
description='{{ i18n "pages.settings.templates.xrayConfigRoutingStrategyDesc" }}'/>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="24" :xl="12">
|
||||||
|
<template>
|
||||||
|
<a-select
|
||||||
|
v-model="routingStrategy"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
|
style="width: 100%">
|
||||||
|
<a-select-option v-for="s in routingDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.blockConfigs"}}'>
|
||||||
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
|
<h2 class="collapse-title">
|
||||||
|
<a-icon type="warning"></a-icon>
|
||||||
|
{{ i18n "pages.settings.templates.blockConfigsDesc" }}
|
||||||
|
</h2>
|
||||||
|
</a-row>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigTorrent"}}' desc='{{ i18n "pages.settings.templates.xrayConfigTorrentDesc"}}' v-model="torrentSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigPrivateIp"}}' desc='{{ i18n "pages.settings.templates.xrayConfigPrivateIpDesc"}}' v-model="privateIpSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigAds"}}' desc='{{ i18n "pages.settings.templates.xrayConfigAdsDesc"}}' v-model="AdsSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigFamily"}}' desc='{{ i18n "pages.settings.templates.xrayConfigFamilyDesc"}}' v-model="familyProtectSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigSpeedtest"}}' desc='{{ i18n "pages.settings.templates.xrayConfigSpeedtestDesc"}}' v-model="SpeedTestSettings"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.blockCountryConfigs"}}'>
|
||||||
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
|
<h2 class="collapse-title">
|
||||||
|
<a-icon type="warning"></a-icon>
|
||||||
|
{{ i18n "pages.settings.templates.blockCountryConfigsDesc" }}
|
||||||
|
</h2>
|
||||||
|
</a-row>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigIRIp"}}' desc='{{ i18n "pages.settings.templates.xrayConfigIRIpDesc"}}' v-model="IRIpSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigIRDomain"}}' desc='{{ i18n "pages.settings.templates.xrayConfigIRDomainDesc"}}' v-model="IRDomainSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigChinaIp"}}' desc='{{ i18n "pages.settings.templates.xrayConfigChinaIpDesc"}}' v-model="ChinaIpSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigChinaDomain"}}' desc='{{ i18n "pages.settings.templates.xrayConfigChinaDomainDesc"}}' v-model="ChinaDomainSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigRussiaIp"}}' desc='{{ i18n "pages.settings.templates.xrayConfigRussiaIpDesc"}}' v-model="RussiaIpSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigRussiaDomain"}}' desc='{{ i18n "pages.settings.templates.xrayConfigRussiaDomainDesc"}}' v-model="RussiaDomainSettings"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.directCountryConfigs"}}'>
|
||||||
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
|
<h2 class="collapse-title">
|
||||||
|
<a-icon type="warning"></a-icon>
|
||||||
|
{{ i18n "pages.settings.templates.directCountryConfigsDesc" }}
|
||||||
|
</h2>
|
||||||
|
</a-row>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigDirectIRIp"}}' desc='{{ i18n "pages.settings.templates.xrayConfigDirectIRIpDesc"}}' v-model="IRIpDirectSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigDirectIRDomain"}}' desc='{{ i18n "pages.settings.templates.xrayConfigDirectIRDomainDesc"}}' v-model="IRDomainDirectSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigDirectChinaIp"}}' desc='{{ i18n "pages.settings.templates.xrayConfigDirectChinaIpDesc"}}' v-model="ChinaIpDirectSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigDirectChinaDomain"}}' desc='{{ i18n "pages.settings.templates.xrayConfigDirectChinaDomainDesc"}}' v-model="ChinaDomainDirectSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigDirectRussiaIp"}}' desc='{{ i18n "pages.settings.templates.xrayConfigDirectRussiaIpDesc"}}' v-model="RussiaIpDirectSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigDirectRussiaDomain"}}' desc='{{ i18n "pages.settings.templates.xrayConfigDirectRussiaDomainDesc"}}' v-model="RussiaDomainDirectSettings"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.ipv4Configs"}}'>
|
||||||
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
|
<h2 class="collapse-title">
|
||||||
|
<a-icon type="warning"></a-icon>
|
||||||
|
{{ i18n "pages.settings.templates.ipv4ConfigsDesc" }}
|
||||||
|
</h2>
|
||||||
|
</a-row>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigGoogleIPv4"}}' desc='{{ i18n "pages.settings.templates.xrayConfigGoogleIPv4Desc"}}' v-model="GoogleIPv4Settings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigNetflixIPv4"}}' desc='{{ i18n "pages.settings.templates.xrayConfigNetflixIPv4Desc"}}' v-model="NetflixIPv4Settings"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.warpConfigs"}}'>
|
||||||
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
|
<h2 class="collapse-title">
|
||||||
|
<a-icon type="warning"></a-icon>
|
||||||
|
{{ i18n "pages.settings.templates.warpConfigsDesc" }}
|
||||||
|
</h2>
|
||||||
|
</a-row>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigGoogleWARP"}}' desc='{{ i18n "pages.settings.templates.xrayConfigGoogleWARPDesc"}}' v-model="GoogleWARPSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigOpenAIWARP"}}' desc='{{ i18n "pages.settings.templates.xrayConfigOpenAIWARPDesc"}}' v-model="OpenAIWARPSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigNetflixWARP"}}' desc='{{ i18n "pages.settings.templates.xrayConfigNetflixWARPDesc"}}' v-model="NetflixWARPSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigSpotifyWARP"}}' desc='{{ i18n "pages.settings.templates.xrayConfigSpotifyWARPDesc"}}' v-model="SpotifyWARPSettings"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="tpl-2" tab='{{ i18n "pages.settings.templates.manualLists"}}' style="padding-top: 20px;">
|
||||||
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
|
<h2 class="collapse-title">
|
||||||
|
<a-icon type="warning"></a-icon>
|
||||||
|
{{ i18n "pages.settings.templates.manualListsDesc" }}
|
||||||
|
</h2>
|
||||||
|
</a-row>
|
||||||
|
<a-collapse>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.manualBlockedIPs"}}'>
|
||||||
|
<setting-list-item type="textarea" v-model="manualBlockedIPs"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.manualBlockedDomains"}}'>
|
||||||
|
<setting-list-item type="textarea" v-model="manualBlockedDomains"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.manualDirectIPs"}}'>
|
||||||
|
<setting-list-item type="textarea" v-model="manualDirectIPs"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.manualDirectDomains"}}'>
|
||||||
|
<setting-list-item type="textarea" v-model="manualDirectDomains"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.manualIPv4Domains"}}'>
|
||||||
|
<setting-list-item type="textarea" v-model="manualIPv4Domains"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.manualWARPDomains"}}'>
|
||||||
|
<setting-list-item type="textarea" v-model="manualWARPDomains"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="tpl-3" tab='{{ i18n "pages.settings.templates.advancedTemplate"}}' style="padding-top: 20px;">
|
||||||
|
<a-collapse>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.xrayConfigInbounds"}}'>
|
||||||
|
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.xrayConfigInbounds"}}' desc='{{ i18n "pages.settings.templates.xrayConfigInboundsDesc"}}' v-model="inboundSettings"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.xrayConfigOutbounds"}}'>
|
||||||
|
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.xrayConfigOutbounds"}}' desc='{{ i18n "pages.settings.templates.xrayConfigOutboundsDesc"}}' v-model="outboundSettings"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.xrayConfigRoutings"}}'>
|
||||||
|
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.xrayConfigRoutings"}}' desc='{{ i18n "pages.settings.templates.xrayConfigRoutingsDesc"}}' v-model="routingRuleSettings"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="tpl-4" tab='{{ i18n "pages.settings.templates.completeTemplate"}}' style="padding-top: 20px;">
|
||||||
|
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.xrayConfigTemplate"}}' desc='{{ i18n "pages.settings.templates.xrayConfigTemplateDesc"}}' v-model="this.xraySetting"></setting-list-item>
|
||||||
|
</a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
|
</a-space>
|
||||||
|
</a-spin>
|
||||||
|
</a-layout-content>
|
||||||
|
</a-layout>
|
||||||
|
</a-layout>
|
||||||
|
{{template "js" .}}
|
||||||
|
{{template "component/themeSwitcher" .}}
|
||||||
|
{{template "component/setting"}}
|
||||||
|
<script>
|
||||||
|
const app = new Vue({
|
||||||
|
delimiters: ['[[', ']]'],
|
||||||
|
el: '#app',
|
||||||
|
data: {
|
||||||
|
siderDrawer,
|
||||||
|
themeSwitcher,
|
||||||
|
spinning: false,
|
||||||
|
oldXraySetting: '',
|
||||||
|
xraySetting: '',
|
||||||
|
saveBtnDisable: true,
|
||||||
|
ipv4Settings: {
|
||||||
|
tag: "IPv4",
|
||||||
|
protocol: "freedom",
|
||||||
|
settings: {
|
||||||
|
domainStrategy: "UseIPv4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
warpSettings: {
|
||||||
|
tag: "WARP",
|
||||||
|
protocol: "socks",
|
||||||
|
settings: {
|
||||||
|
servers: [
|
||||||
|
{
|
||||||
|
address: "127.0.0.1",
|
||||||
|
port: 40000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
directSettings: {
|
||||||
|
tag: "direct",
|
||||||
|
protocol: "freedom"
|
||||||
|
},
|
||||||
|
outboundDomainStrategies: ["AsIs", "UseIP", "UseIPv4", "UseIPv6"],
|
||||||
|
routingDomainStrategies: ["AsIs", "IPIfNonMatch", "IPOnDemand"],
|
||||||
|
settingsData: {
|
||||||
|
protocols: {
|
||||||
|
bittorrent: ["bittorrent"],
|
||||||
|
},
|
||||||
|
ips: {
|
||||||
|
local: ["geoip:private"],
|
||||||
|
cn: ["geoip:cn"],
|
||||||
|
ir: ["ext:geoip_IR.dat:ir","ext:geoip_IR.dat:arvancloud","ext:geoip_IR.dat:derakcloud","ext:geoip_IR.dat:iranserver"],
|
||||||
|
ru: ["geoip:ru"],
|
||||||
|
},
|
||||||
|
domains: {
|
||||||
|
ads: [
|
||||||
|
"geosite:category-ads-all",
|
||||||
|
"ext:geosite_IR.dat:category-ads-all"
|
||||||
|
],
|
||||||
|
speedtest: ["geosite:speedtest"],
|
||||||
|
openai: ["geosite:openai"],
|
||||||
|
google: ["geosite:google"],
|
||||||
|
spotify: ["geosite:spotify"],
|
||||||
|
netflix: ["geosite:netflix"],
|
||||||
|
cn: [
|
||||||
|
"geosite:cn",
|
||||||
|
"regexp:.*\\.cn$"
|
||||||
|
],
|
||||||
|
ru: [
|
||||||
|
"geosite:category-gov-ru",
|
||||||
|
"regexp:.*\\.ru$"
|
||||||
|
],
|
||||||
|
ir: [
|
||||||
|
"regexp:.*\\.ir$",
|
||||||
|
"regexp:.*\\.xn--mgba3a4f16a$", // .ایران
|
||||||
|
"ext:geosite_IR.dat:ir" // have rules to bypass all .ir domains.
|
||||||
|
]
|
||||||
|
},
|
||||||
|
familyProtectDNS: {
|
||||||
|
"servers": [
|
||||||
|
"1.1.1.3", // https://developers.cloudflare.com/1.1.1.1/setup/
|
||||||
|
"1.0.0.3",
|
||||||
|
"94.140.14.15", // https://adguard-dns.io/kb/general/dns-providers/
|
||||||
|
"94.140.15.16"
|
||||||
|
],
|
||||||
|
"queryStrategy": "UseIPv4"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
loading(spinning = true) {
|
||||||
|
this.spinning = spinning;
|
||||||
|
},
|
||||||
|
async getXraySetting() {
|
||||||
|
this.loading(true);
|
||||||
|
const msg = await HttpUtil.post("/panel/xray/");
|
||||||
|
this.loading(false);
|
||||||
|
if (msg.success) {
|
||||||
|
this.oldXraySetting = msg.obj;
|
||||||
|
this.xraySetting = msg.obj;
|
||||||
|
this.saveBtnDisable = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async updateXraySetting() {
|
||||||
|
this.loading(true);
|
||||||
|
const msg = await HttpUtil.post("/panel/xray/update", {xraySetting : this.xraySetting});
|
||||||
|
this.loading(false);
|
||||||
|
if (msg.success) {
|
||||||
|
await this.getXraySetting();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async restartPanel() {
|
||||||
|
await new Promise(resolve => {
|
||||||
|
this.$confirm({
|
||||||
|
title: '{{ i18n "pages.settings.restartPanel" }}',
|
||||||
|
content: '{{ i18n "pages.settings.restartPanelDesc" }}',
|
||||||
|
class: themeSwitcher.currentTheme,
|
||||||
|
okText: '{{ i18n "sure" }}',
|
||||||
|
cancelText: '{{ i18n "cancel" }}',
|
||||||
|
onOk: () => resolve(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.loading(true);
|
||||||
|
const msg = await HttpUtil.post("/panel/setting/restartPanel");
|
||||||
|
this.loading(false);
|
||||||
|
if (msg.success) {
|
||||||
|
this.loading(true);
|
||||||
|
await PromiseUtil.sleep(5000);
|
||||||
|
var { webCertFile, webKeyFile, webDomain: host, webPort: port, webBasePath: base } = this.xraySetting;
|
||||||
|
if (host == this.oldXraySetting.webDomain) host = null;
|
||||||
|
if (port == this.oldXraySetting.webPort) port = null;
|
||||||
|
const isTLS = webCertFile !== "" || webKeyFile !== "";
|
||||||
|
const url = buildURL({ host, port, isTLS, base, path: "panel/settings" });
|
||||||
|
window.location.replace(url);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async fetchUserSecret() {
|
||||||
|
this.loading(true);
|
||||||
|
const userMessage = await HttpUtil.post("/panel/setting/getUserSecret", this.user);
|
||||||
|
if (userMessage.success) {
|
||||||
|
this.user = userMessage.obj;
|
||||||
|
}
|
||||||
|
this.loading(false);
|
||||||
|
},
|
||||||
|
async updateSecret() {
|
||||||
|
this.loading(true);
|
||||||
|
const msg = await HttpUtil.post("/panel/setting/updateUserSecret", this.user);
|
||||||
|
if (msg.success) {
|
||||||
|
this.user = msg.obj;
|
||||||
|
window.location.replace(basePath + "logout");
|
||||||
|
}
|
||||||
|
this.loading(false);
|
||||||
|
await this.updateXraySetting();
|
||||||
|
},
|
||||||
|
generateRandomString(length) {
|
||||||
|
var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
|
||||||
|
let randomString = "";
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
randomString += chars[Math.floor(Math.random() * chars.length)];
|
||||||
|
}
|
||||||
|
return randomString;
|
||||||
|
},
|
||||||
|
async getNewSecret() {
|
||||||
|
this.loading(true);
|
||||||
|
await PromiseUtil.sleep(600);
|
||||||
|
const newSecret = this.generateRandomString(64);
|
||||||
|
this.user.loginSecret = newSecret;
|
||||||
|
document.getElementById("token").textContent = newSecret;
|
||||||
|
this.loading(false);
|
||||||
|
},
|
||||||
|
async toggleToken(value) {
|
||||||
|
if (value) {
|
||||||
|
await this.getNewSecret();
|
||||||
|
} else {
|
||||||
|
this.user.loginSecret = "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async resetXrayConfigToDefault() {
|
||||||
|
this.loading(true);
|
||||||
|
const msg = await HttpUtil.get("/panel/setting/getDefaultJsonConfig");
|
||||||
|
this.loading(false);
|
||||||
|
if (msg.success) {
|
||||||
|
this.templateSettings = JSON.parse(JSON.stringify(msg.obj, null, 2));
|
||||||
|
this.saveBtnDisable = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
syncRulesWithOutbound(tag, setting) {
|
||||||
|
const newTemplateSettings = {...this.templateSettings};
|
||||||
|
const haveRules = newTemplateSettings.routing.rules.some((r) => r?.outboundTag === tag);
|
||||||
|
const outboundIndex = newTemplateSettings.outbounds.findIndex((o) => o.tag === tag);
|
||||||
|
if (!haveRules && outboundIndex >= 0) {
|
||||||
|
newTemplateSettings.outbounds.splice(outboundIndex, 1);
|
||||||
|
}
|
||||||
|
if (haveRules && outboundIndex === -1) {
|
||||||
|
newTemplateSettings.outbounds.push(setting);
|
||||||
|
}
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
},
|
||||||
|
templateRuleGetter(routeSettings) {
|
||||||
|
const { property, outboundTag } = routeSettings;
|
||||||
|
let result = [];
|
||||||
|
if (this.templateSettings != null) {
|
||||||
|
this.templateSettings.routing.rules.forEach(
|
||||||
|
(routingRule) => {
|
||||||
|
if (
|
||||||
|
routingRule.hasOwnProperty(property) &&
|
||||||
|
routingRule.hasOwnProperty("outboundTag") &&
|
||||||
|
routingRule.outboundTag === outboundTag
|
||||||
|
) {
|
||||||
|
result.push(...routingRule[property]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
templateRuleSetter(routeSettings) {
|
||||||
|
const { data, property, outboundTag } = routeSettings;
|
||||||
|
const oldTemplateSettings = this.templateSettings;
|
||||||
|
const newTemplateSettings = oldTemplateSettings;
|
||||||
|
currentProperty = this.templateRuleGetter({ outboundTag, property })
|
||||||
|
if (currentProperty.length == 0) {
|
||||||
|
const propertyRule = {
|
||||||
|
type: "field",
|
||||||
|
outboundTag,
|
||||||
|
[property]: data
|
||||||
|
};
|
||||||
|
newTemplateSettings.routing.rules.push(propertyRule);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const newRules = [];
|
||||||
|
insertedOnce = false;
|
||||||
|
newTemplateSettings.routing.rules.forEach(
|
||||||
|
(routingRule) => {
|
||||||
|
if (
|
||||||
|
routingRule.hasOwnProperty(property) &&
|
||||||
|
routingRule.hasOwnProperty("outboundTag") &&
|
||||||
|
routingRule.outboundTag === outboundTag
|
||||||
|
) {
|
||||||
|
if (!insertedOnce && data.length > 0) {
|
||||||
|
insertedOnce = true;
|
||||||
|
routingRule[property] = data;
|
||||||
|
newRules.push(routingRule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
newRules.push(routingRule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
newTemplateSettings.routing.rules = newRules;
|
||||||
|
}
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
await this.getXraySetting();
|
||||||
|
while (true) {
|
||||||
|
await PromiseUtil.sleep(600);
|
||||||
|
this.saveBtnDisable = this.oldXraySetting === this.xraySetting;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
templateSettings: {
|
||||||
|
get: function () { return this.xraySetting ? JSON.parse(this.xraySetting) : null; },
|
||||||
|
set: function (newValue) { this.xraySetting = JSON.stringify(newValue, null, 2); },
|
||||||
|
},
|
||||||
|
inboundSettings: {
|
||||||
|
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.inbounds, null, 2) : null; },
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
newTemplateSettings.inbounds = JSON.parse(newValue);
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
outboundSettings: {
|
||||||
|
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.outbounds, null, 2) : null; },
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
newTemplateSettings.outbounds = JSON.parse(newValue);
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
routingRuleSettings: {
|
||||||
|
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.routing.rules, null, 2) : null; },
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
newTemplateSettings.routing.rules = JSON.parse(newValue);
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
freedomStrategy: {
|
||||||
|
get: function () {
|
||||||
|
if (!this.templateSettings) return "AsIs";
|
||||||
|
freedomOutbound = this.templateSettings.outbounds.find((o) => o.protocol === "freedom" && !o.tag);
|
||||||
|
if (!freedomOutbound) return "AsIs";
|
||||||
|
if (!freedomOutbound.settings || !freedomOutbound.settings.domainStrategy) return "AsIs";
|
||||||
|
return freedomOutbound.settings.domainStrategy;
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
freedomOutboundIndex = newTemplateSettings.outbounds.findIndex((o) => o.protocol === "freedom" && !o.tag);
|
||||||
|
if (!newTemplateSettings.outbounds[freedomOutboundIndex].settings) {
|
||||||
|
newTemplateSettings.outbounds[freedomOutboundIndex].settings = {"domainStrategy": newValue};
|
||||||
|
} else {
|
||||||
|
newTemplateSettings.outbounds[freedomOutboundIndex].settings.domainStrategy = newValue;
|
||||||
|
}
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
routingStrategy: {
|
||||||
|
get: function () {
|
||||||
|
if (!this.templateSettings || !this.templateSettings.routing || !this.templateSettings.routing.domainStrategy) return "AsIs";
|
||||||
|
return this.templateSettings.routing.domainStrategy;
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
newTemplateSettings.routing.domainStrategy = newValue;
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
blockedIPs: {
|
||||||
|
get: function () {
|
||||||
|
return this.templateRuleGetter({ outboundTag: "blocked", property: "ip" });
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
this.templateRuleSetter({ outboundTag: "blocked", property: "ip", data: newValue });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
blockedDomains: {
|
||||||
|
get: function () {
|
||||||
|
return this.templateRuleGetter({ outboundTag: "blocked", property: "domain" });
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
this.templateRuleSetter({ outboundTag: "blocked", property: "domain", data: newValue });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
blockedProtocols: {
|
||||||
|
get: function () {
|
||||||
|
return this.templateRuleGetter({ outboundTag: "blocked", property: "protocol" });
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
this.templateRuleSetter({ outboundTag: "blocked", property: "protocol", data: newValue });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
directIPs: {
|
||||||
|
get: function () {
|
||||||
|
return this.templateRuleGetter({ outboundTag: "direct", property: "ip" });
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
this.templateRuleSetter({ outboundTag: "direct", property: "ip", data: newValue });
|
||||||
|
this.syncRulesWithOutbound("direct", this.directSettings);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
directDomains: {
|
||||||
|
get: function () {
|
||||||
|
return this.templateRuleGetter({ outboundTag: "direct", property: "domain" });
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
this.templateRuleSetter({ outboundTag: "direct", property: "domain", data: newValue });
|
||||||
|
this.syncRulesWithOutbound("direct", this.directSettings);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ipv4Domains: {
|
||||||
|
get: function () {
|
||||||
|
return this.templateRuleGetter({ outboundTag: "IPv4", property: "domain" });
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
this.templateRuleSetter({ outboundTag: "IPv4", property: "domain", data: newValue });
|
||||||
|
this.syncRulesWithOutbound("IPv4", this.ipv4Settings);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
warpDomains: {
|
||||||
|
get: function () {
|
||||||
|
return this.templateRuleGetter({ outboundTag: "WARP", property: "domain" });
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
this.templateRuleSetter({ outboundTag: "WARP", property: "domain", data: newValue });
|
||||||
|
this.syncRulesWithOutbound("WARP", this.warpSettings);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
manualBlockedIPs: {
|
||||||
|
get: function () { return JSON.stringify(this.blockedIPs, null, 2); },
|
||||||
|
set: debounce(function (value) { this.blockedIPs = JSON.parse(value); }, 1000)
|
||||||
|
},
|
||||||
|
manualBlockedDomains: {
|
||||||
|
get: function () { return JSON.stringify(this.blockedDomains, null, 2); },
|
||||||
|
set: debounce(function (value) { this.blockedDomains = JSON.parse(value); }, 1000)
|
||||||
|
},
|
||||||
|
manualDirectIPs: {
|
||||||
|
get: function () { return JSON.stringify(this.directIPs, null, 2); },
|
||||||
|
set: debounce(function (value) { this.directIPs = JSON.parse(value); }, 1000)
|
||||||
|
},
|
||||||
|
manualDirectDomains: {
|
||||||
|
get: function () { return JSON.stringify(this.directDomains, null, 2); },
|
||||||
|
set: debounce(function (value) { this.directDomains = JSON.parse(value); }, 1000)
|
||||||
|
},
|
||||||
|
manualIPv4Domains: {
|
||||||
|
get: function () { return JSON.stringify(this.ipv4Domains, null, 2); },
|
||||||
|
set: debounce(function (value) { this.ipv4Domains = JSON.parse(value); }, 1000)
|
||||||
|
},
|
||||||
|
manualWARPDomains: {
|
||||||
|
get: function () { return JSON.stringify(this.warpDomains, null, 2); },
|
||||||
|
set: debounce(function (value) { this.warpDomains = JSON.parse(value); }, 1000)
|
||||||
|
},
|
||||||
|
torrentSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.protocols.bittorrent, this.blockedProtocols);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.blockedProtocols = [...this.blockedProtocols, ...this.settingsData.protocols.bittorrent];
|
||||||
|
} else {
|
||||||
|
this.blockedProtocols = this.blockedProtocols.filter(data => !this.settingsData.protocols.bittorrent.includes(data));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
privateIpSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.ips.local, this.blockedIPs);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.local];
|
||||||
|
} else {
|
||||||
|
this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.local.includes(data));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AdsSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.domains.ads, this.blockedDomains);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.ads];
|
||||||
|
} else {
|
||||||
|
this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.ads.includes(data));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SpeedTestSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.domains.speedtest, this.blockedDomains);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.speedtest];
|
||||||
|
} else {
|
||||||
|
this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.speedtest.includes(data));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
familyProtectSettings: {
|
||||||
|
get: function () {
|
||||||
|
if (!this.templateSettings || !this.templateSettings.dns || !this.templateSettings.dns.servers) return false;
|
||||||
|
return doAllItemsExist(this.templateSettings.dns.servers, this.settingsData.familyProtectDNS.servers);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
if (newValue) {
|
||||||
|
newTemplateSettings.dns = this.settingsData.familyProtectDNS;
|
||||||
|
} else {
|
||||||
|
delete newTemplateSettings.dns;
|
||||||
|
}
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
GoogleIPv4Settings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.domains.google, this.ipv4Domains);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.ipv4Domains = [...this.ipv4Domains, ...this.settingsData.domains.google];
|
||||||
|
} else {
|
||||||
|
this.ipv4Domains = this.ipv4Domains.filter(data => !this.settingsData.domains.google.includes(data));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NetflixIPv4Settings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.domains.netflix, this.ipv4Domains);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.ipv4Domains = [...this.ipv4Domains, ...this.settingsData.domains.netflix];
|
||||||
|
} else {
|
||||||
|
this.ipv4Domains = this.ipv4Domains.filter(data => !this.settingsData.domains.netflix.includes(data));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
IRIpSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.ips.ir, this.blockedIPs);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.ir];
|
||||||
|
} else {
|
||||||
|
this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.ir.includes(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
IRDomainSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.domains.ir, this.blockedDomains);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.ir];
|
||||||
|
} else {
|
||||||
|
this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.ir.includes(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ChinaIpSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.ips.cn, this.blockedIPs);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.cn];
|
||||||
|
} else {
|
||||||
|
this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.cn.includes(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ChinaDomainSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.domains.cn, this.blockedDomains);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.cn];
|
||||||
|
} else {
|
||||||
|
this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.cn.includes(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
RussiaIpSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.ips.ru, this.blockedIPs);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.ru];
|
||||||
|
} else {
|
||||||
|
this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.ru.includes(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
RussiaDomainSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.domains.ru, this.blockedDomains);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.ru];
|
||||||
|
} else {
|
||||||
|
this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.ru.includes(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
IRIpDirectSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.ips.ir, this.directIPs);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.directIPs = [...this.directIPs, ...this.settingsData.ips.ir];
|
||||||
|
} else {
|
||||||
|
this.directIPs = this.directIPs.filter(data => !this.settingsData.ips.ir.includes(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
IRDomainDirectSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.domains.ir, this.directDomains);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.directDomains = [...this.directDomains, ...this.settingsData.domains.ir];
|
||||||
|
} else {
|
||||||
|
this.directDomains = this.directDomains.filter(data => !this.settingsData.domains.ir.includes(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ChinaIpDirectSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.ips.cn, this.directIPs);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.directIPs = [...this.directIPs, ...this.settingsData.ips.cn];
|
||||||
|
} else {
|
||||||
|
this.directIPs = this.directIPs.filter(data => !this.settingsData.ips.cn.includes(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ChinaDomainDirectSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.domains.cn, this.directDomains);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.directDomains = [...this.directDomains, ...this.settingsData.domains.cn];
|
||||||
|
} else {
|
||||||
|
this.directDomains = this.directDomains.filter(data => !this.settingsData.domains.cn.includes(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
RussiaIpDirectSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.ips.ru, this.directIPs);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.directIPs = [...this.directIPs, ...this.settingsData.ips.ru];
|
||||||
|
} else {
|
||||||
|
this.directIPs = this.directIPs.filter(data => !this.settingsData.ips.ru.includes(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
RussiaDomainDirectSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.domains.ru, this.directDomains);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.directDomains = [...this.directDomains, ...this.settingsData.domains.ru];
|
||||||
|
} else {
|
||||||
|
this.directDomains = this.directDomains.filter(data => !this.settingsData.domains.ru.includes(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
GoogleWARPSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.domains.google, this.warpDomains);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.google];
|
||||||
|
} else {
|
||||||
|
this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.google.includes(data));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
OpenAIWARPSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.domains.openai, this.warpDomains);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.openai];
|
||||||
|
} else {
|
||||||
|
this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.openai.includes(data));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NetflixWARPSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.domains.netflix, this.warpDomains);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.netflix];
|
||||||
|
} else {
|
||||||
|
this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.netflix.includes(data));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SpotifyWARPSettings: {
|
||||||
|
get: function () {
|
||||||
|
return doAllItemsExist(this.settingsData.domains.spotify, this.warpDomains);
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.spotify];
|
||||||
|
} else {
|
||||||
|
this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.spotify.includes(data));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in a new issue