xray setting enhancements #1286
This commit is contained in:
@@ -2,18 +2,12 @@
|
||||
<script src="{{ .base_path }}assets/vue@2.6.12/vue.min.js"></script>
|
||||
<script src="{{ .base_path }}assets/moment/moment.min.js"></script>
|
||||
<script src="{{ .base_path }}assets/ant-design-vue@1.7.8/antd.min.js"></script>
|
||||
<script src="{{ .base_path }}assets/base64/base64.min.js"></script>
|
||||
<script src="{{ .base_path }}assets/axios/axios.min.js"></script>
|
||||
<script src="{{ .base_path }}assets/qs/qs.min.js"></script>
|
||||
<script src="{{ .base_path }}assets/qrcode/qrious.min.js"></script>
|
||||
<script src="{{ .base_path }}assets/clipboard/clipboard.min.js"></script>
|
||||
<script src="{{ .base_path }}assets/uri/URI.min.js"></script>
|
||||
<script src="{{ .base_path }}assets/js/axios-init.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/js/util/common.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/js/util/date-util.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/js/util/utils.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/js/model/xray.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/js/model/models.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/js/langs.js"></script>
|
||||
<script>
|
||||
const basePath = '{{ .base_path }}';
|
||||
|
||||
+6
-1
@@ -176,7 +176,12 @@
|
||||
{{template "component/themeSwitcher" .}}
|
||||
{{template "component/password" .}}
|
||||
<script>
|
||||
|
||||
class User {
|
||||
constructor() {
|
||||
this.username = "";
|
||||
this.password = "";
|
||||
}
|
||||
}
|
||||
const app = new Vue({
|
||||
delimiters: ['[[', ']]'],
|
||||
el: '#app',
|
||||
|
||||
@@ -0,0 +1,306 @@
|
||||
{{define "form/outbound"}}
|
||||
<!-- base -->
|
||||
<a-tabs :active-key="outModal.activeKey" style="padding: 0; background-color: transparent;" @change="(activeKey) => {outModal.toggleJson(activeKey == '2'); }">
|
||||
<a-tab-pane key="1" tab="Form">
|
||||
<a-form layout="inline">
|
||||
<a-form-item label='{{ i18n "protocol" }}'>
|
||||
<a-select v-model="outbound.protocol" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="x,y in Protocols" :value="x">[[ y ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.xray.outbound.tag" }}'>
|
||||
<a-input v-model.trim="outbound.tag" style="width: 250px" @change="outModal.check()" :style="outModal.duplicateTag? 'border-color: red;' : ''"></a-input>
|
||||
</a-form-item>
|
||||
<!-- freedom settings-->
|
||||
<template v-if="outbound.protocol === Protocols.Freedom">
|
||||
<a-form-item label="Strategy">
|
||||
<a-select
|
||||
v-model="outbound.settings.domainStrategy"
|
||||
style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="s in outboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="Fragment">
|
||||
<a-switch
|
||||
:checked="Object.keys(outbound.settings.fragment).length >0"
|
||||
@change="checked => outbound.settings.fragment = checked ? new Outbound.FreedomSettings.Fragment() : {}">
|
||||
</a-switch>
|
||||
</a-form-item>
|
||||
<template v-if="Object.keys(outbound.settings.fragment).length >0">
|
||||
<a-form-item label="Packets">
|
||||
<a-select
|
||||
v-model="outbound.settings.fragment.packets"
|
||||
style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="s in ['1-3','tlshello']" :value="s">[[ s ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="Length">
|
||||
<a-input v-model.trim="outbound.settings.fragment.length" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Interval">
|
||||
<a-input v-model.trim="outbound.settings.fragment.interval" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- blackhole settings -->
|
||||
<template v-if="outbound.protocol === Protocols.Blackhole">
|
||||
<a-form-item label="Response Type">
|
||||
<a-select
|
||||
v-model="outbound.settings.type"
|
||||
style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="s in ['', 'none','http']" :value="s">[[ s ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- dns settings -->
|
||||
<template v-if="outbound.protocol === Protocols.DNS">
|
||||
<a-form-item label='{{ i18n "pages.inbounds.network" }}'>
|
||||
<a-select
|
||||
v-model="outbound.settings.network"
|
||||
style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="s in ['udp','tcp']" :value="s">[[ s ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- Address + Port -->
|
||||
<template v-if="outbound.hasAddressPort()">
|
||||
<a-form-item label='{{ i18n "pages.inbounds.address" }}'>
|
||||
<a-input v-model.trim="outbound.settings.address" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.port" }}'>
|
||||
<a-input-number v-model.number="outbound.settings.port"></a-input-number>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- Vnext (vless/vmess) settings -->
|
||||
<template v-if="[Protocols.VMess, Protocols.VLESS].includes(outbound.protocol)">
|
||||
<a-form-item label="ID">
|
||||
<a-input v-model.trim="outbound.settings.id" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
<!-- vless settings -->
|
||||
<template v-if="outbound.canEnableTlsFlow()">
|
||||
<a-form-item label="Flow">
|
||||
<a-select v-model="outbound.settings.flow" style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<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>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- Servers (trojan/shadowsocks/socks/http) settings -->
|
||||
<template v-if="outbound.hasServers()">
|
||||
<template v-if="outbound.hasUsername()">
|
||||
<a-form-item label='{{ i18n "username" }}'>
|
||||
<a-input v-model.trim="outbound.settings.user" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "password" }}'>
|
||||
<a-input v-model.trim="outbound.settings.password" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<!-- shadowsocks -->
|
||||
<template v-if="outbound.protocol === Protocols.Shadowsocks">
|
||||
<a-form-item label='{{ i18n "encryption" }}'>
|
||||
<a-select v-model="outbound.settings.method" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="UDP over TCP">
|
||||
<a-switch v-model="outbound.settings.uot"></a-switch>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- stream settings -->
|
||||
<template v-if="outbound.canEnableStream()">
|
||||
<a-form-item label='{{ i18n "transmission" }}'>
|
||||
<a-select v-model="outbound.stream.network" @change="streamNetworkChange"
|
||||
style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="tcp">TCP</a-select-option>
|
||||
<a-select-option value="kcp">KCP</a-select-option>
|
||||
<a-select-option value="ws">WebSocket</a-select-option>
|
||||
<a-select-option value="http">HTTP2</a-select-option>
|
||||
<a-select-option value="quic">QUIC</a-select-option>
|
||||
<a-select-option value="grpc">gRPC</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<template v-if="outbound.stream.network === 'tcp'">
|
||||
<a-form-item label='http {{ i18n "camouflage" }}'>
|
||||
<a-switch
|
||||
:checked="outbound.stream.tcp.type === 'http'"
|
||||
@change="checked => outbound.stream.tcp.type = checked ? 'http' : 'none'">
|
||||
</a-switch>
|
||||
</a-form-item>
|
||||
<template v-if="outbound.stream.tcp.type == 'http'">
|
||||
<a-form-item label='{{ i18n "host" }}'>
|
||||
<a-input style="width: 250px;" v-model.trim="outbound.stream.tcp.host"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "path" }}'>
|
||||
<a-input style="width: 250px;" v-model.trim="outbound.stream.tcp.path"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- kcp -->
|
||||
<template v-if="outbound.stream.network === 'kcp'">
|
||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||
<a-select v-model="outbound.stream.kcp.type" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="none">none (not camouflage)</a-select-option>
|
||||
<a-select-option value="srtp">srtp (video call)</a-select-option>
|
||||
<a-select-option value="utp">utp (BT download)</a-select-option>
|
||||
<a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
|
||||
<a-select-option value="dtls">dtls (DTLS 1.2 packages)</a-select-option>
|
||||
<a-select-option value="wireguard">wireguard (wireguard packages)</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "password" }}'>
|
||||
<a-input v-model="outbound.stream.kcp.seed" style="width: 250px;"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="mtu">
|
||||
<a-input-number v-model.number="outbound.stream.kcp.mtu"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="tti (ms)">
|
||||
<a-input-number v-model.number="outbound.stream.kcp.tti"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="uplink capacity (MB/S)">
|
||||
<a-input-number v-model.number="outbound.stream.kcp.upCap"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="downlink capacity (MB/S)">
|
||||
<a-input-number v-model.number="outbound.stream.kcp.downCap"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="congestion">
|
||||
<a-switch v-model="outbound.stream.kcp.congestion"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="read buffer size (MB)">
|
||||
<a-input-number v-model.number="outbound.stream.kcp.readBuffer"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="write buffer size (MB)">
|
||||
<a-input-number v-model.number="outbound.stream.kcp.writeBuffer"></a-input-number>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- ws -->
|
||||
<template v-if="outbound.stream.network === 'ws'">
|
||||
<a-form-item label='{{ i18n "host" }}'>
|
||||
<a-input style="width: 250px" v-model="outbound.stream.ws.host"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "path" }}'>
|
||||
<a-input style="width: 250px;" v-model.trim="outbound.stream.ws.path"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- http -->
|
||||
<template v-if="outbound.stream.network === 'http'">
|
||||
<a-form-item label='{{ i18n "host" }}'>
|
||||
<a-input v-model.trim="outbound.stream.http.host" style="width: 250px;"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "path" }}'>
|
||||
<a-input v-model.trim="outbound.stream.http.path" style="width: 250px;"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- quic -->
|
||||
<template v-if="outbound.stream.network === 'quic'">
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
|
||||
<a-select v-model="outbound.stream.quic.security" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<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="chacha20-poly1305">chacha20-poly1305</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "password" }}'>
|
||||
<a-input v-model.trim="outbound.stream.quic.key" style="width: 250px;"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||
<a-select v-model="outbound.stream.quic.type" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="none">none (not camouflage)</a-select-option>
|
||||
<a-select-option value="srtp">srtp (video call)</a-select-option>
|
||||
<a-select-option value="utp">utp (BT download)</a-select-option>
|
||||
<a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
|
||||
<a-select-option value="dtls">dtls (DTLS 1.2 packages)</a-select-option>
|
||||
<a-select-option value="wireguard">wireguard (wireguard packages)</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- grpc -->
|
||||
<template v-if="outbound.stream.network === 'grpc'">
|
||||
<a-form-item label="serviceName">
|
||||
<a-input v-model.trim="outbound.stream.grpc.serviceName" style="width: 250px;"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="MultiMode">
|
||||
<a-switch v-model="outbound.stream.grpc.multiMode"></a-switch>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- tls settings -->
|
||||
<template v-if="outbound.canEnableTls()">
|
||||
<a-form-item label='{{ i18n "security" }}'>
|
||||
<a-radio-group v-model="outbound.stream.security" button-style="solid">
|
||||
<a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
|
||||
<a-radio-button value="tls">TLS</a-radio-button>
|
||||
<a-radio-button v-if="outbound.canEnableReality()" value="reality">Reality</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item><br />
|
||||
<template v-if="outbound.stream.isTls">
|
||||
<a-form-item label="SNI" placeholder="Server Name Indication">
|
||||
<a-input v-model.trim="outbound.stream.tls.serverName" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="uTLS">
|
||||
<a-select v-model="outbound.stream.tls.fingerprint"
|
||||
style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<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>
|
||||
</a-form-item>
|
||||
<a-form-item label="ALPN">
|
||||
<a-select
|
||||
mode="multiple"
|
||||
style="width: 250px"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||
v-model="outbound.stream.tls.alpn">
|
||||
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="Allow insecure">
|
||||
<a-switch v-model="outbound.stream.tls.allowInsecure"></a-switch>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- reality settings -->
|
||||
<template v-if="outbound.stream.isReality">
|
||||
<a-form-item label='{{ i18n "domainName" }}'>
|
||||
<a-input v-model.trim="outbound.stream.reality.serverName" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="uTLS">
|
||||
<a-select v-model="outbound.stream.reality.fingerprint"
|
||||
style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="Short Id">
|
||||
<a-input v-model.trim="outbound.stream.reality.shortId" style="width:250px"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="SpiderX">
|
||||
<a-input v-model.trim="outbound.stream.reality.spiderX" style="width:250px"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Public Key">
|
||||
<a-input v-model.trim="outbound.stream.reality.publicKey" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" tab="JSON" force-render="true">
|
||||
<a-form-item style="margin: 10px 0">
|
||||
Link: <a-input v-model.trim="outModal.link" style="width: 300px; margin-right: 5px;" placeholder="vmess:// vless:// trojan:// ss://"></a-input>
|
||||
<a-button @click="convertLink" type="primary"><a-icon type="form"></a-icon></a-button>
|
||||
</a-form-item>
|
||||
<textarea style="position:absolute; left: -800px;" id="outboundJson"></textarea>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
{{end}}
|
||||
@@ -429,6 +429,12 @@
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
{{template "js" .}}
|
||||
<script src="{{ .base_path }}assets/base64/base64.min.js"></script>
|
||||
<script src="{{ .base_path }}assets/qrcode/qrious.min.js"></script>
|
||||
<script src="{{ .base_path }}assets/clipboard/clipboard.min.js"></script>
|
||||
<script src="{{ .base_path }}assets/uri/URI.min.js"></script>
|
||||
<script src="{{ .base_path }}assets/js/model/xray.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/js/model/dbinbound.js?{{ .cur_ver }}"></script>
|
||||
{{template "component/themeSwitcher" .}}
|
||||
<script>
|
||||
const columns = [{
|
||||
|
||||
@@ -317,6 +317,7 @@
|
||||
|
||||
</a-layout>
|
||||
{{template "js" .}}
|
||||
<script src="{{ .base_path }}assets/clipboard/clipboard.min.js"></script>
|
||||
{{template "component/themeSwitcher" .}}
|
||||
{{template "textModal"}}
|
||||
<script>
|
||||
|
||||
@@ -253,6 +253,7 @@
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
{{template "js" .}}
|
||||
<script src="{{ .base_path }}assets/js/model/setting.js?{{ .cur_ver }}"></script>
|
||||
{{template "component/themeSwitcher" .}}
|
||||
{{template "component/password" .}}
|
||||
{{template "component/setting"}}
|
||||
@@ -267,7 +268,7 @@
|
||||
oldAllSetting: new AllSetting(),
|
||||
allSetting: new AllSetting(),
|
||||
saveBtnDisable: true,
|
||||
user: new User(),
|
||||
user: {},
|
||||
lang: getLang(),
|
||||
},
|
||||
methods: {
|
||||
|
||||
+515
-45
@@ -1,6 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
{{template "head" .}}
|
||||
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/codemirror.css">
|
||||
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/fold/foldgutter.css">
|
||||
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/xq.css">
|
||||
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/lint/lint.css">
|
||||
|
||||
<script src="{{ .base_path }}assets/js/model/outbound.js"></script>
|
||||
<script src="{{ .base_path }}assets/codemirror/codemirror.js"></script>
|
||||
<script src="{{ .base_path }}assets/codemirror/javascript.js"></script>
|
||||
<script src="{{ .base_path }}assets/codemirror/jshint.js"></script>
|
||||
<script src="{{ .base_path }}assets/codemirror/jsonlint.js"></script>
|
||||
<script src="{{ .base_path }}assets/codemirror/lint/lint.js"></script>
|
||||
<script src="{{ .base_path }}assets/codemirror/lint/javascript-lint.js"></script>
|
||||
<script src="{{ .base_path }}assets/codemirror/hint/javascript-hint.js"></script>
|
||||
<script src="{{ .base_path }}assets/codemirror/fold/foldcode.js"></script>
|
||||
<script src="{{ .base_path }}assets/codemirror/fold/foldgutter.js"></script>
|
||||
<script src="{{ .base_path }}assets/codemirror/fold/brace-fold.js"></script>
|
||||
<style>
|
||||
@media (min-width: 769px) {
|
||||
.ant-layout-content {
|
||||
@@ -59,8 +75,10 @@
|
||||
</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.xray.basicTemplate"}}' style="padding-top: 20px;">
|
||||
<a-tabs class="ant-card-dark-box-nohover" default-active-key="tpl-1"
|
||||
@change="(activeKey) => { if(activeKey == 'tpl-4') this.changeCode(); }"
|
||||
:class="themeSwitcher.currentTheme">
|
||||
<a-tab-pane key="tpl-1" tab='{{ i18n "pages.xray.basicTemplate"}}'>
|
||||
<a-space direction="horizontal" style="padding: 20px 20px">
|
||||
<a-button type="primary" @click="resetXrayConfigToDefault">{{ i18n "pages.settings.resetDefaultConfig" }}</a-button>
|
||||
</a-space>
|
||||
@@ -175,49 +193,196 @@
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="tpl-2" tab='{{ i18n "pages.xray.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.xray.manualListsDesc" }}
|
||||
</h2>
|
||||
<a-tab-pane key="tpl-2" tab='{{ i18n "pages.xray.Routings"}}' style="padding-top: 20px;">
|
||||
<a-alert type="warning" style="margin-bottom: 10px; width: fit-content"
|
||||
message='{{ i18n "pages.xray.RoutingsDesc"}}' show-icon></a-alert>
|
||||
<a-button type="primary" icon="plus" @click="addRule">{{ i18n "pages.xray.rules.add" }}</a-button>
|
||||
<a-table :columns="isMobile ? rulesMobileColumns : rulesColumns" bordered
|
||||
:row-key="r => r.key"
|
||||
:data-source="routingRuleData"
|
||||
:scroll="isMobile ? {} : { x: 1000 }"
|
||||
:pagination="false"
|
||||
:indent-size="0"
|
||||
:style="isMobile ? 'padding: 5px 0' : 'margin-top: 10px;'">
|
||||
<template slot="action" slot-scope="text, rule, index">
|
||||
[[ index+1 ]]
|
||||
<a-dropdown :trigger="['click']">
|
||||
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 16px; text-decoration: bold;"></a-icon>
|
||||
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
||||
<a-menu-item v-if="index>0" @click="replaceRule(index,0)">
|
||||
<a-icon type="vertical-align-top"></a-icon>
|
||||
{{ i18n "pages.xray.rules.first"}}
|
||||
</a-menu-item>
|
||||
<a-menu-item v-if="index>0" @click="replaceRule(index,index-1)">
|
||||
<a-icon type="arrow-up"></a-icon>
|
||||
{{ i18n "pages.xray.rules.up"}}
|
||||
</a-menu-item>
|
||||
<a-menu-item v-if="index<routingRuleData.length-1" @click="replaceRule(index,index+1)">
|
||||
<a-icon type="arrow-down"></a-icon>
|
||||
{{ i18n "pages.xray.rules.down"}}
|
||||
</a-menu-item>
|
||||
<a-menu-item v-if="index<routingRuleData.length-1" @click="replaceRule(index,routingRuleData.length-1)">
|
||||
<a-icon type="vertical-align-bottom"></a-icon>
|
||||
{{ i18n "pages.xray.rules.last"}}
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="editRule(index)">
|
||||
<a-icon type="edit"></a-icon>
|
||||
{{ i18n "edit" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="deleteRule(index)">
|
||||
<span style="color: #FF4D4F">
|
||||
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
||||
</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
<template slot="inbound" slot-scope="text, rule, index">
|
||||
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<p v-if="rule.inboundTag">Inbound Tag: [[ rule.inboundTag ]]</p>
|
||||
<p v-if="rule.user">User email: [[ rule.user ]]</p>
|
||||
</template>
|
||||
[[ [rule.inboundTag,rule.user].join('\n') ]]
|
||||
</a-popover>
|
||||
</template>
|
||||
<template slot="outbound" slot-scope="text, rule, index">
|
||||
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<p v-if="rule.outboundTag">Outbound Tag: [[ rule.outboundTag ]]</p>
|
||||
</template>
|
||||
[[ rule.outboundTag ]]
|
||||
</a-popover>
|
||||
</template>
|
||||
<template slot="info" slot-scope="text, rule, index">
|
||||
<a-popover placement="bottomRight"
|
||||
v-if="(rule.source+rule.sourcePort+rule.network+rule.protocol+rule.attrs+rule.ip+rule.domain+rule.port).length>0"
|
||||
:overlay-class-name="themeSwitcher.currentTheme" trigger="click">
|
||||
<template slot="content">
|
||||
<table cellpadding="2" style="max-width: 300px;">
|
||||
<tr v-if="rule.source">
|
||||
<td>Source</td>
|
||||
<td><a-tag color="blue" v-for="r in rule.source.split(',')">[[ r ]]</a-tag></td>
|
||||
</tr>
|
||||
<tr v-if="rule.sourcePort">
|
||||
<td>Source Port</td>
|
||||
<td><a-tag color="green" v-for="r in rule.sourcePort.split(',')">[[ r ]]</a-tag></td>
|
||||
</tr>
|
||||
<tr v-if="rule.network">
|
||||
<td>Network</td>
|
||||
<td><a-tag color="blue" v-for="r in rule.network.split(',')">[[ r ]]</a-tag></td>
|
||||
</tr>
|
||||
<tr v-if="rule.protocol">
|
||||
<td>Protocol</td>
|
||||
<td><a-tag color="green" v-for="r in rule.protocol.split(',')">[[ r ]]</a-tag></td>
|
||||
</tr>
|
||||
<tr v-if="rule.attrs">
|
||||
<td>Attrs</td>
|
||||
<td><a-tag color="blue" v-for="r in rule.attrs.split(',')">[[ r ]]</a-tag></td>
|
||||
</tr>
|
||||
<tr v-if="rule.ip">
|
||||
<td>IP</td>
|
||||
<td><a-tag color="green" v-for="r in rule.ip.split(',')">[[ r ]]</a-tag></td>
|
||||
</tr>
|
||||
<tr v-if="rule.domain">
|
||||
<td>Domain</td>
|
||||
<td><a-tag color="blue" v-for="r in rule.domain.split(',')">[[ r ]]</a-tag></td>
|
||||
</tr>
|
||||
<tr v-if="rule.port">
|
||||
<td>Port</td>
|
||||
<td><a-tag color="green" v-for="r in rule.port.split(',')">[[ r ]]</a-tag></td>
|
||||
</tr>
|
||||
</table>
|
||||
</template>
|
||||
<a-button shape="round" size="small" style="font-size: 14px; padding: 0 10px;">
|
||||
<a-icon type="info"></a-icon>
|
||||
</a-button>
|
||||
</a-popover>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="tpl-3" tab='{{ i18n "pages.xray.Outbounds"}}' style="padding-top: 20px;" force-render="true">
|
||||
<a-button type="primary" icon="plus" @click="addOutbound()">{{ i18n "pages.xray.outbound.addOutbound" }}</a-button>
|
||||
<a-button type="primary" icon="plus" @click="addReverse()">{{ i18n "pages.xray.outbound.addReverse" }}</a-button>
|
||||
<a-row>
|
||||
<a-col :sm="24" :md="12">
|
||||
<p style="margin: 10px;">{{ i18n "pages.xray.Outbounds"}}</p>
|
||||
<a-table :columns="outboundColumns" bordered
|
||||
:row-key="r => r.key"
|
||||
:data-source="outboundData"
|
||||
:scroll="isMobile ? {} : { x: 200 }"
|
||||
:pagination="false"
|
||||
:indent-size="0"
|
||||
:style="isMobile ? 'padding: 5px 5px' : 'margin-right: 1px;'">
|
||||
<template slot="action" slot-scope="text, outbound, index">
|
||||
[[ index+1 ]]
|
||||
<a-dropdown :trigger="['click']">
|
||||
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 16px; text-decoration: bold;"></a-icon>
|
||||
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
||||
<a-menu-item @click="editOutbound(index)">
|
||||
<a-icon type="edit"></a-icon>
|
||||
{{ i18n "edit" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="deleteOutbound(index)">
|
||||
<span style="color: #FF4D4F">
|
||||
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
||||
</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
<template slot="address" slot-scope="text, outbound, index">
|
||||
<p style="margin: 0 5px;" v-for="addr in findOutboundAddress(outbound)">[[ addr ]]</p>
|
||||
</template>
|
||||
<template slot="protocol" slot-scope="text, outbound, index">
|
||||
<a-tag style="margin:0;" color="purple">[[ outbound.protocol ]]</a-tag>
|
||||
<template v-if="[Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(outbound.protocol)">
|
||||
<a-tag style="margin:0;" color="blue">[[ outbound.streamSettings.network ]]</a-tag>
|
||||
<a-tag style="margin:0;" v-if="outbound.streamSettings.security=='tls'" color="green">tls</a-tag>
|
||||
<a-tag style="margin:0;" v-if="outbound.streamSettings.security=='reality'" color="green">reality</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12" v-if="reverseData.length>0">
|
||||
<p style="margin: 10px;">{{ i18n "pages.xray.outbound.reverse"}}</p>
|
||||
<a-table :columns="reverseColumns" bordered
|
||||
:row-key="r => r.key"
|
||||
:data-source="reverseData"
|
||||
:scroll="isMobile ? {} : { x: 200 }"
|
||||
:pagination="false"
|
||||
:indent-size="0"
|
||||
:style="isMobile ? 'padding: 5px 0' : 'margin-left: 1px;'">
|
||||
<template slot="action" slot-scope="text, reverse, index">
|
||||
[[ index+1 ]]
|
||||
<a-dropdown :trigger="['click']">
|
||||
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 16px; text-decoration: bold;"></a-icon>
|
||||
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
||||
<a-menu-item @click="editReverse(index)">
|
||||
<a-icon type="edit"></a-icon>
|
||||
{{ i18n "edit" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="deleteReverse(index)">
|
||||
<span style="color: #FF4D4F">
|
||||
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
||||
</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-collapse>
|
||||
<a-collapse-panel header='{{ i18n "pages.xray.manualBlockedIPs"}}'>
|
||||
<setting-list-item type="textarea" v-model="manualBlockedIPs"></setting-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.xray.manualBlockedDomains"}}'>
|
||||
<setting-list-item type="textarea" v-model="manualBlockedDomains"></setting-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.xray.manualDirectIPs"}}'>
|
||||
<setting-list-item type="textarea" v-model="manualDirectIPs"></setting-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.xray.manualDirectDomains"}}'>
|
||||
<setting-list-item type="textarea" v-model="manualDirectDomains"></setting-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.xray.manualIPv4Domains"}}'>
|
||||
<setting-list-item type="textarea" v-model="manualIPv4Domains"></setting-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.xray.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.xray.advancedTemplate"}}' style="padding-top: 20px;">
|
||||
<a-collapse>
|
||||
<a-collapse-panel header='{{ i18n "pages.xray.xrayConfigInbounds"}}'>
|
||||
<setting-list-item type="textarea" title='{{ i18n "pages.xray.xrayConfigInbounds"}}' desc='{{ i18n "pages.xray.xrayConfigInboundsDesc"}}' v-model="inboundSettings"></setting-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.xray.xrayConfigOutbounds"}}'>
|
||||
<setting-list-item type="textarea" title='{{ i18n "pages.xray.xrayConfigOutbounds"}}' desc='{{ i18n "pages.xray.xrayConfigOutboundsDesc"}}' v-model="outboundSettings"></setting-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.xray.xrayConfigRoutings"}}'>
|
||||
<setting-list-item type="textarea" title='{{ i18n "pages.xray.xrayConfigRoutings"}}' desc='{{ i18n "pages.xray.xrayConfigRoutingsDesc"}}' v-model="routingRuleSettings"></setting-list-item>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="tpl-4" tab='{{ i18n "pages.xray.completeTemplate"}}' style="padding-top: 20px;">
|
||||
<setting-list-item type="textarea" title='{{ i18n "pages.xray.xrayConfigTemplate"}}' desc='{{ i18n "pages.xray.xrayConfigTemplateDesc"}}' v-model="this.xraySetting"></setting-list-item>
|
||||
<a-tab-pane key="tpl-4" tab='{{ i18n "pages.xray.advancedTemplate"}}' style="padding-top: 20px;" force-render="true">
|
||||
<a-list-item-meta title='{{ i18n "pages.xray.Template"}}' description='{{ i18n "pages.xray.TemplateDesc"}}'></a-list-item-meta>
|
||||
<a-radio-group v-model="advSettings" @change="changeCode" button-style="solid" style="margin: 10px 0;" :size="isMobile ? 'small' : ''">
|
||||
<a-radio-button value="xraySetting">{{ i18n "pages.xray.completeTemplate"}}</a-radio-button>
|
||||
<a-radio-button value="inboundSettings">{{ i18n "pages.xray.Inbounds" }}</a-radio-button>
|
||||
<a-radio-button value="outboundSettings">{{ i18n "pages.xray.Outbounds" }}</a-radio-button>
|
||||
<a-radio-button value="routingRuleSettings">{{ i18n "pages.xray.Routings" }}</a-radio-button>
|
||||
</a-radio-group>
|
||||
<textarea style="position:absolute; left: -800px;" id="xraySetting"></textarea>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-space>
|
||||
@@ -228,17 +393,85 @@
|
||||
{{template "js" .}}
|
||||
{{template "component/themeSwitcher" .}}
|
||||
{{template "component/setting"}}
|
||||
{{template "ruleModal"}}
|
||||
{{template "outModal"}}
|
||||
{{template "reverseModal"}}
|
||||
<script>
|
||||
const rulesColumns = [
|
||||
{ title: "#", align: 'center', width: 15, scopedSlots: { customRender: 'action' } },
|
||||
{ title: '{{ i18n "pages.xray.rules.source"}}', children: [
|
||||
{ title: 'IP', dataIndex: "source", align: 'center', width: 20, ellipsis: true },
|
||||
{ title: 'port', dataIndex: 'sourcePort', align: 'center', width: 10, ellipsis: true } ]},
|
||||
{ title: '{{ i18n "pages.inbounds.network"}}', children: [
|
||||
{ title: 'L4', dataIndex: 'network', align: 'center', width: 10 },
|
||||
{ title: 'Protocol', dataIndex: 'protocol', align: 'center', width: 10, ellipsis: true },
|
||||
{ title: 'Attrs', dataIndex: 'attrs', align: 'center', width: 20, ellipsis: true } ]},
|
||||
{ title: '{{ i18n "pages.xray.rules.dest"}}', children: [
|
||||
{ title: 'IP', dataIndex: 'ip', align: 'center', width: 20, ellipsis: true },
|
||||
{ title: 'Domain', dataIndex: 'domain', align: 'center', width: 20, ellipsis: true },
|
||||
{ title: 'Port', dataIndex: 'port', align: 'center', width: 10, ellipsis: true }]},
|
||||
{ title: '{{ i18n "pages.xray.rules.inbound"}}', children: [
|
||||
{ title: 'Inbound Tag', dataIndex: 'inboundTag', align: 'center', width: 20, ellipsis: true },
|
||||
{ title: 'User email', dataIndex: 'user', align: 'center', width: 20, ellipsis: true }]},
|
||||
{ title: '{{ i18n "pages.xray.rules.outbound"}}', dataIndex: 'outboundTag', align: 'center', width: 20 },
|
||||
];
|
||||
|
||||
const rulesMobileColumns = [
|
||||
{ title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
|
||||
{ title: '{{ i18n "pages.xray.rules.inbound"}}', align: 'center', width: 50, ellipsis: true, scopedSlots: { customRender: 'inbound' } },
|
||||
{ title: '{{ i18n "pages.xray.rules.outbound"}}', align: 'center', width: 50, ellipsis: true, scopedSlots: { customRender: 'outbound' } },
|
||||
{ title: '{{ i18n "pages.xray.rules.info"}}', align: 'center', width: 50, ellipsis: true, scopedSlots: { customRender: 'info' } },
|
||||
];
|
||||
|
||||
const outboundColumns = [
|
||||
{ title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
|
||||
{ title: '{{ i18n "pages.xray.outbound.tag"}}', dataIndex: 'tag', align: 'center', width: 50 },
|
||||
{ title: '{{ i18n "protocol"}}', align: 'center', width: 50, scopedSlots: { customRender: 'protocol' } },
|
||||
{ title: '{{ i18n "pages.xray.outbound.address"}}', align: 'center', width: 50, scopedSlots: { customRender: 'address' } },
|
||||
];
|
||||
|
||||
const reverseColumns = [
|
||||
{ title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
|
||||
{ title: '{{ i18n "pages.xray.outbound.type"}}', dataIndex: 'type', align: 'center', width: 50 },
|
||||
{ title: '{{ i18n "pages.xray.outbound.tag"}}', dataIndex: 'tag', align: 'center', width: 50 },
|
||||
{ title: '{{ i18n "pages.xray.outbound.domain"}}', dataIndex: 'domain', align: 'center', width: 50 },
|
||||
];
|
||||
const app = new Vue({
|
||||
delimiters: ['[[', ']]'],
|
||||
el: '#app',
|
||||
data: {
|
||||
siderDrawer,
|
||||
themeSwitcher,
|
||||
isDarkTheme: themeSwitcher.isDarkTheme,
|
||||
spinning: false,
|
||||
oldXraySetting: '',
|
||||
xraySetting: '',
|
||||
inboundTags: [],
|
||||
saveBtnDisable: true,
|
||||
isMobile: window.innerWidth <= 768,
|
||||
advSettings: 'xraySetting',
|
||||
cm: null,
|
||||
cmOptions: {
|
||||
lineNumbers: true,
|
||||
mode: "application/json",
|
||||
lint: true,
|
||||
styleActiveLine: true,
|
||||
matchBrackets: true,
|
||||
theme: "xq",
|
||||
autoCloseTags: true,
|
||||
lineWrapping: true,
|
||||
indentUnit: 2,
|
||||
indentWithTabs: true,
|
||||
smartIndent: true,
|
||||
tabSize: 2,
|
||||
lineWiseCopyCut: false,
|
||||
foldGutter: true,
|
||||
gutters: [
|
||||
"CodeMirror-lint-markers",
|
||||
"CodeMirror-linenumbers",
|
||||
"CodeMirror-foldgutter",
|
||||
],
|
||||
},
|
||||
ipv4Settings: {
|
||||
tag: "IPv4",
|
||||
protocol: "freedom",
|
||||
@@ -318,8 +551,11 @@
|
||||
const msg = await HttpUtil.post("/panel/xray/");
|
||||
this.loading(false);
|
||||
if (msg.success) {
|
||||
this.oldXraySetting = msg.obj;
|
||||
this.xraySetting = msg.obj;
|
||||
result = JSON.parse(msg.obj);
|
||||
xs = JSON.stringify(result.xraySetting, null, 2);
|
||||
this.oldXraySetting = xs;
|
||||
this.xraySetting = xs;
|
||||
this.inboundTags = result.inboundTags;
|
||||
this.saveBtnDisable = true;
|
||||
}
|
||||
},
|
||||
@@ -456,6 +692,190 @@
|
||||
newTemplateSettings.routing.rules = newRules;
|
||||
}
|
||||
this.templateSettings = newTemplateSettings;
|
||||
},
|
||||
changeCode() {
|
||||
if(this.cm != null) {
|
||||
this.cm.toTextArea();
|
||||
}
|
||||
textAreaObj = document.getElementById('xraySetting');
|
||||
textAreaObj.value = this[this.advSettings];
|
||||
this.cm = CodeMirror.fromTextArea(textAreaObj, this.cmOptions);
|
||||
this.cm.on('change',editor => {
|
||||
value = editor.getValue();
|
||||
if(this.isJsonString(value)){
|
||||
this[this.advSettings] = value;
|
||||
}
|
||||
});
|
||||
},
|
||||
isJsonString(str) {
|
||||
try {
|
||||
JSON.parse(str);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
findOutboundAddress(o) {
|
||||
serverObj = null;
|
||||
switch(o.protocol){
|
||||
case Protocols.VMess:
|
||||
case Protocols.VLESS:
|
||||
serverObj = o.settings.vnext;
|
||||
break;
|
||||
case Protocols.HTTP:
|
||||
case Protocols.Socks:
|
||||
case Protocols.Shadowsocks:
|
||||
case Protocols.Trojan:
|
||||
serverObj = o.settings.servers;
|
||||
break;
|
||||
case Protocols.DNS:
|
||||
return [o.settings.address + ':' + o.settings.port];
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
return serverObj ? serverObj.map(obj => obj.address + ':' + obj.port) : null;
|
||||
},
|
||||
addOutbound(){
|
||||
outModal.show({
|
||||
title: '{{ i18n "pages.xray.outbound.addOutbound"}}',
|
||||
okText: '{{ i18n "pages.xray.outbound.addOutbound" }}',
|
||||
confirm: (outbound) => {
|
||||
outModal.loading();
|
||||
if(outbound.tag.length > 0){
|
||||
this.templateSettings.outbounds.push(outbound);
|
||||
this.outboundSettings = JSON.stringify(this.templateSettings.outbounds);
|
||||
}
|
||||
outModal.close();
|
||||
},
|
||||
isEdit: false
|
||||
});
|
||||
},
|
||||
editOutbound(index){
|
||||
outModal.show({
|
||||
title: '{{ i18n "pages.xray.outbound.editOutbound"}} ' + (index+1),
|
||||
outbound: app.templateSettings.outbounds[index],
|
||||
confirm: (outbound) => {
|
||||
outModal.loading();
|
||||
this.templateSettings.outbounds[index] = outbound;
|
||||
this.outboundSettings = JSON.stringify(this.templateSettings.outbounds);
|
||||
outModal.close();
|
||||
},
|
||||
isEdit: true
|
||||
});
|
||||
},
|
||||
deleteOutbound(index){
|
||||
outbounds = this.templateSettings.outbounds;
|
||||
outbounds.splice(index,1);
|
||||
this.outboundSettings = JSON.stringify(outbounds);
|
||||
},
|
||||
addReverse(){
|
||||
reverseModal.show({
|
||||
title: '{{ i18n "pages.xray.outbound.addReverse"}}',
|
||||
okText: '{{ i18n "pages.xray.outbound.addReverse" }}',
|
||||
confirm: (reverse, rules) => {
|
||||
reverseModal.loading();
|
||||
if(reverse.tag.length > 0){
|
||||
newTemplateSettings = this.templateSettings;
|
||||
if(newTemplateSettings.reverse == undefined) newTemplateSettings.reverse = {};
|
||||
if(newTemplateSettings.reverse[reverse.type+'s'] == undefined) newTemplateSettings.reverse[reverse.type+'s'] = [];
|
||||
newTemplateSettings.reverse[reverse.type+'s'].push({ tag: reverse.tag, domain: reverse.domain });
|
||||
this.templateSettings = newTemplateSettings;
|
||||
|
||||
// Add related rules
|
||||
this.templateSettings.routing.rules.push(...rules);
|
||||
this.routingRuleSettings = JSON.stringify(this.templateSettings.routing.rules);
|
||||
}
|
||||
reverseModal.close();
|
||||
},
|
||||
isEdit: false
|
||||
});
|
||||
},
|
||||
editReverse(index){
|
||||
if(this.reverseData[index].type == "bridge") {
|
||||
oldRules = this.templateSettings.routing.rules.filter(r => r.inboundTag && r.inboundTag[0] == this.reverseData[index].tag);
|
||||
} else {
|
||||
oldRules = this.templateSettings.routing.rules.filter(r => r.outboundTag && r.outboundTag == this.reverseData[index].tag);
|
||||
}
|
||||
reverseModal.show({
|
||||
title: '{{ i18n "pages.xray.outbound.editReverse"}} ' + (index+1),
|
||||
reverse: this.reverseData[index],
|
||||
rules: oldRules,
|
||||
confirm: (reverse, rules) => {
|
||||
reverseModal.loading();
|
||||
if(reverse.tag.length > 0){
|
||||
oldtag = this.reverseData[index].tag;
|
||||
this.deleteReverse(index);
|
||||
newTemplateSettings = this.templateSettings;
|
||||
if(newTemplateSettings.reverse == undefined) newTemplateSettings.reverse = {};
|
||||
if(newTemplateSettings.reverse[reverse.type+'s'] == undefined) newTemplateSettings.reverse[reverse.type+'s'] = [];
|
||||
newTemplateSettings.reverse[reverse.type+'s'].push({ tag: reverse.tag, domain: reverse.domain });
|
||||
this.templateSettings = newTemplateSettings;
|
||||
|
||||
// Adjust Rules
|
||||
newRules = this.templateSettings.routing.rules.filter(r => r.outboundTag != oldtag && (r.inboundTag && !r.inboundTag.includes(oldtag)));
|
||||
newRules.push(...rules)
|
||||
this.routingRuleSettings = JSON.stringify(newRules);
|
||||
}
|
||||
reverseModal.close();
|
||||
},
|
||||
isEdit: true
|
||||
});
|
||||
},
|
||||
deleteReverse(index){
|
||||
oldData = this.reverseData[index];
|
||||
newTemplateSettings = this.templateSettings;
|
||||
reverseTypeObj = newTemplateSettings.reverse[oldData.type+'s'];
|
||||
realIndex = reverseTypeObj.findIndex(r => r.tag==oldData.tag && r.domain==oldData.domain);
|
||||
newTemplateSettings.reverse[oldData.type+'s'].splice(realIndex,1);
|
||||
|
||||
if(reverseTypeObj.length == 0) Reflect.deleteProperty(newTemplateSettings.reverse, oldData.type+'s');
|
||||
if(Object.keys(newTemplateSettings.reverse).length === 0) Reflect.deleteProperty(newTemplateSettings, 'reverse');
|
||||
|
||||
newRules = newTemplateSettings.routing.rules.filter(r => r.outboundTag != oldData.tag && (r.inboundTag && !r.inboundTag.includes(oldData.tag)));
|
||||
newTemplateSettings.routing.rules = newRules;
|
||||
|
||||
this.templateSettings = newTemplateSettings;
|
||||
},
|
||||
addRule(){
|
||||
ruleModal.show({
|
||||
title: '{{ i18n "pages.xray.rules.add"}}',
|
||||
okText: '{{ i18n "pages.xray.rules.add" }}',
|
||||
confirm: (rule) => {
|
||||
ruleModal.loading();
|
||||
if(JSON.stringify(rule).length > 3){
|
||||
this.templateSettings.routing.rules.push(rule);
|
||||
this.routingRuleSettings = JSON.stringify(this.templateSettings.routing.rules);
|
||||
}
|
||||
ruleModal.close();
|
||||
},
|
||||
isEdit: false
|
||||
});
|
||||
},
|
||||
editRule(index){
|
||||
ruleModal.show({
|
||||
title: '{{ i18n "pages.xray.rules.edit"}} ' + (index+1),
|
||||
rule: app.templateSettings.routing.rules[index],
|
||||
confirm: (rule) => {
|
||||
ruleModal.loading();
|
||||
if(JSON.stringify(rule).length > 3){
|
||||
this.templateSettings.routing.rules[index] = rule;
|
||||
this.routingRuleSettings = JSON.stringify(this.templateSettings.routing.rules);
|
||||
}
|
||||
ruleModal.close();
|
||||
},
|
||||
isEdit: true
|
||||
});
|
||||
},
|
||||
replaceRule(old_index,new_index){
|
||||
rules = this.templateSettings.routing.rules;
|
||||
if (new_index >= rules.length) rules.push(undefined);
|
||||
rules.splice(new_index, 0, rules.splice(old_index, 1)[0]);
|
||||
this.routingRuleSettings = JSON.stringify(rules);
|
||||
},
|
||||
deleteRule(index){
|
||||
rules = this.templateSettings.routing.rules;
|
||||
rules.splice(index,1);
|
||||
this.routingRuleSettings = JSON.stringify(rules);
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
@@ -486,6 +906,35 @@
|
||||
this.templateSettings = newTemplateSettings;
|
||||
},
|
||||
},
|
||||
outboundData: {
|
||||
get: function () {
|
||||
data = []
|
||||
if (this.templateSettings != null) {
|
||||
this.templateSettings.outbounds.forEach((o, index) => {
|
||||
data.push({'key': index, ...o});
|
||||
});
|
||||
}
|
||||
return data;
|
||||
},
|
||||
},
|
||||
reverseData: {
|
||||
get: function () {
|
||||
data = []
|
||||
if (this.templateSettings != null && this.templateSettings.reverse != null) {
|
||||
if(this.templateSettings.reverse.bridges) {
|
||||
this.templateSettings.reverse.bridges.forEach((o, index) => {
|
||||
data.push({'key': index, 'type':'bridge', ...o});
|
||||
});
|
||||
}
|
||||
if(this.templateSettings.reverse.portals){
|
||||
this.templateSettings.reverse.portals.forEach((o, index) => {
|
||||
data.push({'key': index, 'type':'portal', ...o});
|
||||
});
|
||||
}
|
||||
}
|
||||
return data;
|
||||
},
|
||||
},
|
||||
routingRuleSettings: {
|
||||
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.routing.rules, null, 2) : null; },
|
||||
set: function (newValue) {
|
||||
@@ -494,6 +943,27 @@
|
||||
this.templateSettings = newTemplateSettings;
|
||||
},
|
||||
},
|
||||
routingRuleData: {
|
||||
get: function () {
|
||||
data = [];
|
||||
if (this.templateSettings != null) {
|
||||
this.templateSettings.routing.rules.forEach((r, index) => {
|
||||
data.push({'key': index, ...r});
|
||||
});
|
||||
// Make rules readable
|
||||
data.forEach(r => {
|
||||
if(r.domain) r.domain = r.domain.join(',')
|
||||
if(r.ip) r.ip = r.ip.join(',')
|
||||
if(r.source) r.source = r.source.join(',');
|
||||
if(r.user) r.user = r.user.join(',')
|
||||
if(r.inboundTag) r.inboundTag = r.inboundTag.join(',')
|
||||
if(r.protocol) r.protocol = r.protocol.join(',')
|
||||
if(r.attrs) r.attrs = JSON.stringify(r.attrs, null, 2)
|
||||
});
|
||||
}
|
||||
return data;
|
||||
}
|
||||
},
|
||||
freedomStrategy: {
|
||||
get: function () {
|
||||
if (!this.templateSettings) return "AsIs";
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
{{define "outModal"}}
|
||||
<a-modal id="out-modal" v-model="outModal.visible" :title="outModal.title" @ok="outModal.ok"
|
||||
:confirm-loading="outModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||
:ok-button-props="{ props: { disabled: !outModal.isValid } }" style="overflow: hidden;"
|
||||
:ok-text="outModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||
{{template "form/outbound"}}
|
||||
</a-modal>
|
||||
<script>
|
||||
|
||||
const outModal = {
|
||||
title: '',
|
||||
visible: false,
|
||||
confirmLoading: false,
|
||||
okText: '{{ i18n "sure" }}',
|
||||
isEdit: false,
|
||||
confirm: null,
|
||||
outbound: new Outbound(),
|
||||
jsonMode: false,
|
||||
link: '',
|
||||
cm: null,
|
||||
duplicateTag: false,
|
||||
isValid: true,
|
||||
activeKey: '1',
|
||||
ok() {
|
||||
ObjectUtil.execute(outModal.confirm, outModal.outbound.toJson());
|
||||
},
|
||||
show({ title='', okText='{{ i18n "sure" }}', outbound, confirm=(outbound)=>{}, isEdit=false }) {
|
||||
this.title = title;
|
||||
this.okText = okText;
|
||||
this.confirm = confirm;
|
||||
this.jsonMode = false;
|
||||
this.link = '';
|
||||
this.activeKey = '1';
|
||||
this.visible = true;
|
||||
this.outbound = isEdit ? Outbound.fromJson(outbound) : new Outbound();
|
||||
this.isEdit = isEdit;
|
||||
this.check()
|
||||
},
|
||||
close() {
|
||||
outModal.visible = false;
|
||||
outModal.loading(false);
|
||||
},
|
||||
loading(loading) {
|
||||
outModal.confirmLoading = loading;
|
||||
},
|
||||
check(){
|
||||
if(outModal.outbound.tag == ''){
|
||||
this.duplicateTag = true;
|
||||
this.isValid = false;
|
||||
} else {
|
||||
this.duplicateTag = false;
|
||||
this.isValid = true;
|
||||
}
|
||||
},
|
||||
toggleJson(jsonTab) {
|
||||
textAreaObj = document.getElementById('outboundJson');
|
||||
if(jsonTab){
|
||||
if(this.cm != null) {
|
||||
this.cm.toTextArea();
|
||||
this.cm=null;
|
||||
}
|
||||
textAreaObj.value = JSON.stringify(this.outbound.toJson(), null, 2);
|
||||
this.cm = CodeMirror.fromTextArea(textAreaObj, app.cmOptions);
|
||||
this.cm.on('change',editor => {
|
||||
value = editor.getValue();
|
||||
if(this.isJsonString(value)){
|
||||
this.outbound = Outbound.fromJson(JSON.parse(value));
|
||||
this.check();
|
||||
}
|
||||
});
|
||||
this.activeKey = '2';
|
||||
} else {
|
||||
if(this.cm != null) {
|
||||
this.cm.toTextArea();
|
||||
this.cm=null;
|
||||
}
|
||||
this.activeKey = '1';
|
||||
}
|
||||
},
|
||||
isJsonString(str) {
|
||||
try {
|
||||
JSON.parse(str);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
new Vue({
|
||||
delimiters: ['[[', ']]'],
|
||||
el: '#out-modal',
|
||||
data: {
|
||||
outModal: outModal,
|
||||
get outbound() {
|
||||
return outModal.outbound;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
streamNetworkChange() {
|
||||
if (this.outModal.outbound.protocol == Protocols.VLESS && !outModal.outbound.canEnableTlsFlow()) {
|
||||
delete this.outModal.outbound.settings.flow;
|
||||
}
|
||||
},
|
||||
canEnableTls() {
|
||||
return this.outModal.outbound.canEnableTls();
|
||||
},
|
||||
convertLink(){
|
||||
newOutbound = Outbound.fromLink(outModal.link);
|
||||
if(newOutbound){
|
||||
this.outModal.outbound = newOutbound;
|
||||
this.outModal.toggleJson(true);
|
||||
this.outModal.check();
|
||||
this.$message.success('Link imported successfully...');
|
||||
outModal.link = '';
|
||||
} else {
|
||||
this.$message.error('Wrong Link!');
|
||||
outModal.link = '';
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
</script>
|
||||
{{end}}
|
||||
@@ -0,0 +1,139 @@
|
||||
{{define "reverseModal"}}
|
||||
<a-modal id="reverse-modal" v-model="reverseModal.visible" :title="reverseModal.title" @ok="reverseModal.ok"
|
||||
:confirm-loading="reverseModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||
:ok-text="reverseModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||
<a-form layout="inline">
|
||||
<a-form-item label='{{ i18n "pages.xray.outbound.type" }}'>
|
||||
<a-select v-model="reverseModal.reverse.type" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="x,y in reverseTypes" :value="y">[[ x ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.xray.outbound.tag" }}'>
|
||||
<a-input v-model.trim="reverseModal.reverse.tag" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.xray.outbound.domain" }}'>
|
||||
<a-input v-model.trim="reverseModal.reverse.domain" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
<template v-if="reverseModal.reverse.type=='bridge'">
|
||||
<a-form-item label='{{ i18n "pages.xray.outbound.intercon" }}'>
|
||||
<a-select v-model="reverseModal.rules[0].outboundTag" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="x in reverseModal.outboundTags" :value="x">[[ x ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.xray.rules.outbound" }}'>
|
||||
<a-select v-model="reverseModal.rules[1].outboundTag" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="x in reverseModal.outboundTags" :value="x">[[ x ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-form-item label='{{ i18n "pages.xray.outbound.intercon" }}'>
|
||||
<a-checkbox-group
|
||||
v-model="reverseModal.rules[0].inboundTag"
|
||||
:options="reverseModal.inboundTags"></a-checkbox-group>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.xray.rules.inbound" }}'>
|
||||
<a-checkbox-group
|
||||
v-model="reverseModal.rules[1].inboundTag"
|
||||
:options="reverseModal.inboundTags"></a-checkbox-group>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
<script>
|
||||
const reverseModal = {
|
||||
title: '',
|
||||
visible: false,
|
||||
confirmLoading: false,
|
||||
okText: '{{ i18n "sure" }}',
|
||||
isEdit: false,
|
||||
confirm: null,
|
||||
reverse: {
|
||||
tag: "",
|
||||
type: "",
|
||||
domain: ""
|
||||
},
|
||||
rules: [
|
||||
{ outboundTag: '', inboundTag: []},
|
||||
{ outboundTag: '', inboundTag: []}
|
||||
],
|
||||
inboundTags: [],
|
||||
outboundTags: [],
|
||||
ok() {
|
||||
reverseModal.rules[0].domain = ["full:" + reverseModal.reverse.domain];
|
||||
reverseModal.rules[0].type = 'field';
|
||||
reverseModal.rules[1].type = 'field';
|
||||
|
||||
if(reverseModal.reverse.type == 'bridge'){
|
||||
reverseModal.rules[0].inboundTag = [reverseModal.reverse.tag];
|
||||
reverseModal.rules[1].inboundTag = [reverseModal.reverse.tag];
|
||||
} else {
|
||||
reverseModal.rules[0].outboundTag = reverseModal.reverse.tag;
|
||||
reverseModal.rules[1].outboundTag = reverseModal.reverse.tag;
|
||||
}
|
||||
ObjectUtil.execute(reverseModal.confirm, reverseModal.reverse, reverseModal.rules);
|
||||
},
|
||||
show({ title='', okText='{{ i18n "sure" }}', reverse, rules, confirm=(reverse, rules)=>{}, isEdit=false }) {
|
||||
this.title = title;
|
||||
this.okText = okText;
|
||||
this.confirm = confirm;
|
||||
this.visible = true;
|
||||
if(isEdit) {
|
||||
this.reverse = {
|
||||
tag: reverse.tag,
|
||||
type: reverse.type,
|
||||
domain: reverse.domain,
|
||||
};
|
||||
reverse;
|
||||
rules0 = rules.filter(r => r.domain != null);
|
||||
if(rules0.length == 0) rules0 = [{ outboundTag: '', domain: ["full:" + this.reverse.domain], inboundTag: []}];
|
||||
rules1 = rules.filter(r => r.domain == null);
|
||||
if(rules1.length == 0) rules1 = [{ outboundTag: '', inboundTag: []}];
|
||||
this.rules = [];
|
||||
this.rules.push({
|
||||
domain: rules0[0].domain,
|
||||
outboundTag: rules0[0].outboundTag,
|
||||
inboundTag: rules0.map(r => r.inboundTag).flat()
|
||||
});
|
||||
this.rules.push({
|
||||
outboundTag: rules1[0].outboundTag,
|
||||
inboundTag: rules1.map(r => r.inboundTag).flat()
|
||||
});
|
||||
} else {
|
||||
this.reverse = {
|
||||
tag: "reverse-" + app.reverseData.length,
|
||||
type: "bridge",
|
||||
domain: "reverse.xui"
|
||||
}
|
||||
this.rules = [
|
||||
{ outboundTag: '', inboundTag: []},
|
||||
{ outboundTag: '', inboundTag: []}
|
||||
]
|
||||
}
|
||||
this.isEdit = isEdit;
|
||||
this.inboundTags = app.templateSettings.inbounds.map(obj => obj.tag);
|
||||
this.inboundTags.push(...app.inboundTags);
|
||||
this.outboundTags = app.templateSettings.outbounds.map(obj => obj.tag);
|
||||
},
|
||||
close() {
|
||||
reverseModal.visible = false;
|
||||
reverseModal.loading(false);
|
||||
},
|
||||
loading(loading) {
|
||||
reverseModal.confirmLoading = loading;
|
||||
},
|
||||
};
|
||||
|
||||
new Vue({
|
||||
delimiters: ['[[', ']]'],
|
||||
el: '#reverse-modal',
|
||||
data: {
|
||||
reverseModal: reverseModal,
|
||||
reverseTypes: { bridge: '{{ i18n "pages.xray.outbound.bridge" }}', portal:'{{ i18n "pages.xray.outbound.portal" }}'},
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
{{end}}
|
||||
@@ -0,0 +1,213 @@
|
||||
{{define "ruleModal"}}
|
||||
<a-modal id="rule-modal" v-model="ruleModal.visible" :title="ruleModal.title" @ok="ruleModal.ok"
|
||||
:confirm-loading="ruleModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||
:ok-text="ruleModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||
<a-form layout="inline">
|
||||
<a-form-item label='Domain Matcher'>
|
||||
<a-select v-model="ruleModal.rule.domainMatcher" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="dm in ['','hybrid','linear']" :value="dm">[[ dm ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='Source IPs'>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
<a-input v-model.trim="ruleModal.rule.source" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Source Port'>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
<a-input v-model.trim="ruleModal.rule.sourcePort" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Network'>
|
||||
<a-select v-model="ruleModal.rule.network" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="x in ['','tcp','tdp','tcp,udp']" :value="x">[[ x ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='Protocol'>
|
||||
<a-select v-model="ruleModal.rule.protocol" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="x in ['','http','tls','bittorrent']" :value="x">[[ x ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='Attributes'>
|
||||
<a-button size="small" style="margin-left: 10px" @click="ruleModal.rule.attrs.push(['', ''])">+</a-button>
|
||||
<a-input-group compact v-for="(attr,index) in ruleModal.rule.attrs">
|
||||
<a-input style="width: 50%" v-model="attr[0]" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
|
||||
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||
</a-input>
|
||||
<a-input style="width: 50%" v-model="attr[1]" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||
<a-button slot="addonAfter" size="small" @click="ruleModal.rule.attrs.splice(index,1)">-</a-button>
|
||||
</a-input>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
<a-form-item label='IP'>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
<a-input v-model.trim="ruleModal.rule.ip" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Domain'>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
<a-input v-model.trim="ruleModal.rule.domain" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Port'>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
<a-input v-model.trim="ruleModal.rule.port" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Inbound Tags'>
|
||||
<a-select v-model="ruleModal.rule.inboundTag" mode="multiple" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="tag in ruleModal.inboundTags" :value="tag">[[ tag ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='Outbound Tag'>
|
||||
<a-select v-model="ruleModal.rule.outboundTag" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="tag in ruleModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
<script>
|
||||
|
||||
const ruleModal = {
|
||||
title: '',
|
||||
visible: false,
|
||||
confirmLoading: false,
|
||||
okText: '{{ i18n "sure" }}',
|
||||
isEdit: false,
|
||||
confirm: null,
|
||||
rule: {
|
||||
domainMatcher: "",
|
||||
domain: "",
|
||||
ip: "",
|
||||
port: "",
|
||||
sourcePort: "",
|
||||
network: "",
|
||||
source: "",
|
||||
user: "",
|
||||
inboundTag: [],
|
||||
protocol: [],
|
||||
attrs: [],
|
||||
outboundTag: "",
|
||||
},
|
||||
inboundTags: [],
|
||||
outboundTags: [],
|
||||
users: [],
|
||||
balancerTag: [],
|
||||
ok() {
|
||||
newRule = ruleModal.getResult();
|
||||
ObjectUtil.execute(ruleModal.confirm, newRule);
|
||||
},
|
||||
show({ title='', okText='{{ i18n "sure" }}', rule, confirm=(rule)=>{}, isEdit=false }) {
|
||||
this.title = title;
|
||||
this.okText = okText;
|
||||
this.confirm = confirm;
|
||||
this.visible = true;
|
||||
if(isEdit) {
|
||||
this.rule.domainMatcher = rule.domainMatcher;
|
||||
this.rule.domain = rule.domain ? rule.domain.join(',') : [];
|
||||
this.rule.ip = rule.ip ? rule.ip.join(',') : [];
|
||||
this.rule.port = rule.port;
|
||||
this.rule.sourcePort = rule.sourcePort;
|
||||
this.rule.network = rule.network;
|
||||
this.rule.source = rule.source ? rule.source.join(',') : [];
|
||||
this.rule.user = rule.user ? rule.user.join(',') : [];
|
||||
this.rule.inboundTag = rule.inboundTag;
|
||||
this.rule.protocol = rule.protocol;
|
||||
this.rule.attrs = rule.attrs ? Object.entries(rule.attrs) : [];
|
||||
this.rule.outboundTag = rule.outboundTag;
|
||||
} else {
|
||||
this.rule = {
|
||||
domainMatcher: "",
|
||||
domain: "",
|
||||
ip: "",
|
||||
port: "",
|
||||
sourcePort: "",
|
||||
network: "",
|
||||
source: "",
|
||||
user: "",
|
||||
inboundTag: [],
|
||||
protocol: [],
|
||||
attrs: [],
|
||||
outboundTag: "",
|
||||
}
|
||||
}
|
||||
this.isEdit = isEdit;
|
||||
this.inboundTags = app.templateSettings.inbounds.map(obj => obj.tag);
|
||||
this.inboundTags.push(...app.inboundTags);
|
||||
this.outboundTags = app.templateSettings.outbounds.map(obj => obj.tag);
|
||||
if(app.templateSettings.reverse){
|
||||
if(app.templateSettings.reverse.bridges) {
|
||||
this.inboundTags.push(...app.templateSettings.reverse.bridges.map(b => b.tag));
|
||||
}
|
||||
if(app.templateSettings.reverse.portals) this.outboundTags.push(...app.templateSettings.reverse.portals.map(b => b.tag));
|
||||
}
|
||||
},
|
||||
close() {
|
||||
ruleModal.visible = false;
|
||||
ruleModal.loading(false);
|
||||
},
|
||||
loading(loading) {
|
||||
ruleModal.confirmLoading = loading;
|
||||
},
|
||||
getResult() {
|
||||
value = ruleModal.rule;
|
||||
rule = {};
|
||||
newRule = {};
|
||||
rule.domainMatcher = value.domainMatcher;
|
||||
rule.domain = value.domain.length>0 ? value.domain.split(',') : [];
|
||||
rule.ip = value.ip.length>0 ? value.ip.split(',') : [];
|
||||
rule.port = value.port;
|
||||
rule.sourcePort = value.sourcePort;
|
||||
rule.network = value.network;
|
||||
rule.source = value.source.length>0 ? value.source.split(',') : [];
|
||||
rule.user = value.user.length>0 ? value.user.split(',') : [];
|
||||
rule.inboundTag = value.inboundTag;
|
||||
rule.protocol = value.protocol;
|
||||
rule.attrs = Object.fromEntries(value.attrs);
|
||||
rule.outboundTag = value.outboundTag;
|
||||
|
||||
for (const [key, value] of Object.entries(rule)) {
|
||||
if (
|
||||
value !== null &&
|
||||
value !== undefined &&
|
||||
!(Array.isArray(value) && value.length === 0) &&
|
||||
!(typeof value === 'object' && Object.keys(value).length === 0) &&
|
||||
value !== ''
|
||||
) {
|
||||
newRule[key] = value;
|
||||
}
|
||||
}
|
||||
return newRule;
|
||||
}
|
||||
};
|
||||
|
||||
new Vue({
|
||||
delimiters: ['[[', ']]'],
|
||||
el: '#rule-modal',
|
||||
data: {
|
||||
ruleModal: ruleModal,
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
{{end}}
|
||||
Reference in New Issue
Block a user