mirror of
https://github.com/alireza0/x-ui.git
synced 2026-08-04 14:46:25 +00:00
Some checks are pending
Release X-UI / build (386) (push) Waiting to run
Release X-UI / build (amd64) (push) Waiting to run
Release X-UI / build (arm64) (push) Waiting to run
Release X-UI / build (armv5) (push) Waiting to run
Release X-UI / build (armv6) (push) Waiting to run
Release X-UI / build (armv7) (push) Waiting to run
Release X-UI / build (s390x) (push) Waiting to run
Release X-UI / Build for Windows (push) Waiting to run
1342 lines
73 KiB
HTML
1342 lines
73 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
{{template "head" .}}
|
|
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/codemirror.min.css?{{ .cur_ver }}">
|
|
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/fold/foldgutter.css">
|
|
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/xq.min.css?{{ .cur_ver }}">
|
|
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/lint/lint.css">
|
|
|
|
<script src="{{ .base_path }}assets/base64/base64.min.js"></script>
|
|
<script src="{{ .base_path }}assets/js/model/outbound.js?{{ .cur_ver }}"></script>
|
|
<script src="{{ .base_path }}assets/codemirror/codemirror.js?{{ .cur_ver }}"></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 {
|
|
margin: 24px 16px;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.ant-tabs-nav .ant-tabs-tab {
|
|
margin: 0;
|
|
padding: 12px .5rem;
|
|
}
|
|
.ant-table-thead > tr > th,
|
|
.ant-table-tbody > tr > td {
|
|
padding: 10px 0px;
|
|
}
|
|
}
|
|
|
|
.ant-tabs-bar {
|
|
font-weight: bold;
|
|
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"}}'>
|
|
<transition name="list" appear>
|
|
<a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
|
|
message='{{ i18n "secAlertTitle" }}'
|
|
color="red"
|
|
description='{{ i18n "secAlertSsl" }}'
|
|
show-icon closable
|
|
>
|
|
</a-alert>
|
|
</transition>
|
|
<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.xray.save" }}</a-button>
|
|
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartXray">{{ i18n "pages.xray.restart" }}</a-button>
|
|
<a-popover v-if="restartResult"
|
|
:overlay-class-name="themeSwitcher.currentTheme">
|
|
<span slot="title" style="font-size: 12pt">Error in running xray-core</span>
|
|
<template slot="content">
|
|
<p style="max-width: 400px" v-for="line in restartResult.split('\n')">[[ line ]]</p>
|
|
</template>
|
|
<a-icon type="question-circle"></a-icon>
|
|
</a-popover>
|
|
</a-space>
|
|
</a-col>
|
|
<a-col :xs="24" :sm="16">
|
|
<template>
|
|
<div>
|
|
<a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200">
|
|
</a-back-top>
|
|
<a-alert type="warning" style="float: right; width: fit-content"
|
|
message='{{ i18n "pages.xray.infoDesc" }}'
|
|
show-icon
|
|
>
|
|
</div>
|
|
</template>
|
|
</a-col>
|
|
</a-row>
|
|
</a-card>
|
|
<a-tabs default-active-key="1" @change="(activeKey) => { this.changePage(activeKey); }">
|
|
<a-tab-pane key="tpl-basic" tab='{{ i18n "pages.xray.basicTemplate"}}' style="padding-top: 20px;">
|
|
<a-collapse>
|
|
<a-collapse-panel header='{{ i18n "pages.xray.generalConfigs"}}'>
|
|
<a-row :xs="24" :sm="24" :lg="12">
|
|
<a-alert type="warning" style="text-align: center;">
|
|
<template slot="message">
|
|
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
|
|
{{ i18n "pages.xray.generalConfigsDesc" }}
|
|
</template>
|
|
</a-alert>
|
|
</a-row>
|
|
<a-list-item>
|
|
<a-row style="padding: 20px">
|
|
<a-col :lg="24" :xl="12">
|
|
<a-list-item-meta
|
|
title='{{ i18n "pages.xray.FreedomStrategy" }}'
|
|
description='{{ i18n "pages.xray.FreedomStrategyDesc" }}'/>
|
|
</a-col>
|
|
<a-col :lg="24" :xl="12">
|
|
<template>
|
|
<a-select
|
|
v-model="freedomStrategy"
|
|
style="width: 100%" :dropdown-class-name="themeSwitcher.currentTheme">
|
|
<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-list-item>
|
|
<a-row style="padding: 20px">
|
|
<a-col :lg="24" :xl="12">
|
|
<a-list-item-meta
|
|
title='{{ i18n "pages.xray.RoutingStrategy" }}'
|
|
description='{{ i18n "pages.xray.RoutingStrategyDesc" }}'/>
|
|
</a-col>
|
|
<a-col :lg="24" :xl="12">
|
|
<a-select
|
|
v-model="routingStrategy"
|
|
style="width: 100%" :dropdown-class-name="themeSwitcher.currentTheme">
|
|
<a-select-option v-for="s in routingDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
|
</a-select>
|
|
</a-col>
|
|
</a-row>
|
|
</a-list-item>
|
|
</a-collapse-panel>
|
|
<a-collapse-panel header='{{ i18n "pages.xray.logConfigs" }}'>
|
|
<a-row :xs="24" :sm="24" :lg="12">
|
|
<a-alert type="warning" style="text-align: center;">
|
|
<template slot="message">
|
|
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
|
|
{{ i18n "pages.xray.logConfigsDesc" }}
|
|
</template>
|
|
</a-alert>
|
|
</a-row>
|
|
<a-list-item>
|
|
<a-row style="padding: 10px 20px">
|
|
<a-col :lg="24" :xl="12">
|
|
<a-list-item-meta title='{{ i18n "pages.xray.logLevel" }}'
|
|
description='{{ i18n "pages.xray.logLevelDesc" }}'>
|
|
</a-list-item-meta>
|
|
</a-col>
|
|
<a-col :lg="24" :xl="12">
|
|
<template>
|
|
<a-select v-model="logLevel" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%">
|
|
<a-select-option v-for="s in log.loglevel" :value="s">[[ s ]]</a-select-option>
|
|
</a-select>
|
|
</template>
|
|
</a-col>
|
|
</a-row>
|
|
<a-row style="padding: 10px 20px">
|
|
<a-col :lg="24" :xl="12">
|
|
<a-list-item-meta title='{{ i18n "pages.xray.accessLog" }}'
|
|
description='{{ i18n "pages.xray.accessLogDesc" }}'>
|
|
</a-list-item-meta>
|
|
</a-col>
|
|
<a-col :lg="24" :xl="12">
|
|
<template>
|
|
<a-select v-model="accessLog" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%">
|
|
<a-select-option value=''>Empty</a-select-option>
|
|
<a-select-option v-for="s in log.access" :value="s">[[ s ]]</a-select-option>
|
|
</a-select>
|
|
</template>
|
|
</a-col>
|
|
</a-row>
|
|
<a-row style="padding: 10px 20px">
|
|
<a-col :lg="24" :xl="12">
|
|
<a-list-item-meta title='{{ i18n "pages.xray.errorLog" }}'
|
|
description='{{ i18n "pages.xray.errorLogDesc" }}'>
|
|
</a-list-item-meta>
|
|
</a-col>
|
|
<a-col :lg="24" :xl="12">
|
|
<template>
|
|
<a-select v-model="errorLog" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%">
|
|
<a-select-option value=''>Empty</a-select-option>
|
|
<a-select-option v-for="s in log.error" :value="s">[[ s ]]</a-select-option>
|
|
</a-select>
|
|
</template>
|
|
</a-col>
|
|
</a-row>
|
|
<a-row style="padding: 10px 20px">
|
|
<a-col :lg="24" :xl="12">
|
|
<a-list-item-meta title='{{ i18n "pages.xray.maskAddress" }}'
|
|
description='{{ i18n "pages.xray.maskAddressDesc" }}'>
|
|
</a-list-item-meta>
|
|
</a-col>
|
|
<a-col :lg="24" :xl="12">
|
|
<template>
|
|
<a-select v-model="maskAddressLog" :dropdown-class-name="themeSwitcher.currentTheme"
|
|
style="width: 100%">
|
|
<a-select-option value=''>Empty</a-select-option>
|
|
<a-select-option v-for="s in log.maskAddress" :value="s">[[ s ]]</a-select-option>
|
|
</a-select>
|
|
</template>
|
|
</a-col>
|
|
</a-row>
|
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.dnsLog"}}' desc='{{ i18n "pages.xray.dnsLogDesc"}}'
|
|
v-model="dnslog"></setting-list-item>
|
|
</a-list-item>
|
|
</a-collapse-panel>
|
|
<a-collapse-panel header='{{ i18n "pages.xray.blockConfigs"}}'>
|
|
<a-row :xs="24" :sm="24" :lg="12">
|
|
<a-alert type="warning" style="text-align: center;">
|
|
<template slot="message">
|
|
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
|
|
{{ i18n "pages.xray.blockConfigsDesc" }}
|
|
</template>
|
|
</a-alert>
|
|
</a-row>
|
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.Family"}}' desc='{{ i18n "pages.xray.FamilyDesc"}}' v-model="familyProtectSettings"></setting-list-item>
|
|
</a-collapse-panel>
|
|
<a-collapse-panel header='{{ i18n "pages.xray.metrics.title"}}'>
|
|
<a-row :xs="24" :sm="24" :lg="12">
|
|
<a-alert type="warning" style="text-align: center;">
|
|
<template slot="message">
|
|
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
|
|
{{ i18n "pages.xray.metrics.desc" }}
|
|
</template>
|
|
</a-alert>
|
|
</a-row>
|
|
<setting-list-item type="switch" title='{{ i18n "enable"}}' desc='{{ i18n "pages.xray.metrics.enableDesc"}}' v-model="metricsEnable"></setting-list-item>
|
|
<template v-if="metricsEnable">
|
|
<setting-list-item type="text" title='{{ i18n "address"}}' desc='{{ i18n "pages.xray.metrics.listenDesc"}}' v-model="metricsListen"></setting-list-item>
|
|
<setting-list-item type="text" title='{{ i18n "pages.xray.metrics.tag"}}' desc='{{ i18n "pages.xray.metrics.tagDesc"}}' v-model="metricsTag"></setting-list-item>
|
|
</template>
|
|
</a-collapse-panel>
|
|
<a-collapse-panel header='{{ i18n "pages.settings.resetDefaultConfig"}}'>
|
|
<a-space direction="horizontal" style="padding: 0 20px">
|
|
<a-button type="danger" @click="resetXrayConfigToDefault">{{ i18n "pages.settings.resetDefaultConfig" }}</a-button>
|
|
</a-space>
|
|
</a-collapse-panel>
|
|
</a-collapse>
|
|
</a-tab-pane>
|
|
<a-tab-pane key="tpl-balancer" tab='{{ i18n "pages.xray.Balancers"}}' style="padding-top: 20px;" force-render="true">
|
|
<a-button type="primary" icon="plus" @click="addBalancer()" style="margin-bottom: 10px;">{{ i18n "pages.xray.balancer.addBalancer"}}</a-button>
|
|
<a-table :columns="balancerColumns" bordered v-if="balancersData.length>0"
|
|
:row-key="r => r.key"
|
|
:data-source="balancersData"
|
|
:scroll="isMobile ? {} : { x: 200 }"
|
|
:pagination="false"
|
|
:indent-size="0"
|
|
:style="isMobile ? 'padding: 5px 0' : 'margin-left: 1px;'">
|
|
<template slot="action" slot-scope="text, balancer, 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="editBalancer(index)">
|
|
<a-icon type="edit"></a-icon>
|
|
{{ i18n "edit" }}
|
|
</a-menu-item>
|
|
<a-menu-item @click="deleteBalancer(index)">
|
|
<span style="color: #FF4D4F">
|
|
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
|
</span>
|
|
</a-menu-item>
|
|
</a-menu>
|
|
</a-dropdown>
|
|
</template>
|
|
<template slot="strategy" slot-scope="text, balancer, index">
|
|
<a-tag style="margin:0;" v-if="balancer.strategy=='random'" color="blue">Random</a-tag>
|
|
<a-tag style="margin:0;" v-if="balancer.strategy=='roundRobin'" color="blue">Round Robin</a-tag>
|
|
<a-tag style="margin:0;" v-if="balancer.strategy=='leastPing'" color="green">Least Ping</a-tag>
|
|
<a-tag style="margin:0;" v-if="balancer.strategy=='leastLoad'" color="purple">Least Load</a-tag>
|
|
</template>
|
|
<template slot="selector" slot-scope="text, balancer, index">
|
|
<a-tag class="info-large-tag" style="margin:1;" v-for="sel in balancer.selector">[[ sel ]]</a-tag>
|
|
</template>
|
|
</a-table>
|
|
<a-radio-group
|
|
v-model="obsSettings"
|
|
v-if="observatoryEnable || burstObservatoryEnable"
|
|
@change="changeObsCode"
|
|
button-style="solid"
|
|
style="margin: 10px 0;"
|
|
:size="isMobile ? 'small' : ''">
|
|
<a-radio-button value="observatory" v-if="observatoryEnable">Observatory</a-radio-button>
|
|
<a-radio-button value="burstObservatory" v-if="burstObservatoryEnable">Burst Observatory</a-radio-button>
|
|
</a-radio-group>
|
|
<textarea style="position:absolute; left: -800px;" id="obsSetting"></textarea>
|
|
</a-tab-pane>
|
|
<a-tab-pane key="tpl-dns" tab='DNS' style="padding-top: 20px;" force-render="true">
|
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.dns.enable" }}' desc='{{ i18n "pages.xray.dns.enableDesc" }}' v-model="enableDNS"></setting-list-item>
|
|
<template v-if="enableDNS">
|
|
<setting-list-item type="text" title='{{ i18n "pages.xray.dns.tag" }}' desc='{{ i18n "pages.xray.dns.tagDesc" }}' v-model="dnsTag"></setting-list-item>
|
|
<a-list-item>
|
|
<a-row style="padding: 20px">
|
|
<a-col :lg="24" :xl="12">
|
|
<a-list-item-meta title='{{ i18n "pages.xray.dns.strategy" }}' description='{{ i18n "pages.xray.dns.strategyDesc" }}' />
|
|
</a-col>
|
|
<a-col :lg="24" :xl="12">
|
|
<a-select
|
|
v-model="dnsStrategy"
|
|
style="width: 100%"
|
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
|
<a-select-option :value="l" :label="l" v-for="l in ['UseIP', 'UseIPv4', 'UseIPv6']">
|
|
[[ l ]]
|
|
</a-select-option>
|
|
</a-select>
|
|
</a-col>
|
|
</a-row>
|
|
</a-list-item>
|
|
<a-divider>DNS</a-divider>
|
|
<a-button type="primary" icon="plus" @click="addDNSServer()" style="margin-bottom: 10px;">{{ i18n "pages.xray.dns.add" }}</a-button>
|
|
<a-table :columns="dnsColumns" bordered v-if="dnsServers.length>0"
|
|
:row-key="r => r.key"
|
|
:data-source="dnsServers"
|
|
:scroll="isMobile ? {} : { x: 200 }"
|
|
:pagination="false"
|
|
:indent-size="0"
|
|
:style="isMobile ? 'padding: 5px 0' : 'margin-left: 1px;'">
|
|
<template slot="action" slot-scope="text,dns,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="editDNSServer(index)">
|
|
<a-icon type="edit"></a-icon>
|
|
{{ i18n "edit" }}
|
|
</a-menu-item>
|
|
<a-menu-item @click="deleteDNSServer(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="dns,index">
|
|
<span v-if="typeof dns == 'object'">[[ dns.address ]]</span>
|
|
<span v-else>[[ dns ]]</span>
|
|
</template>
|
|
<template slot="domain" slot-scope="dns,index">
|
|
<span v-if="typeof dns == 'object'">[[ dns.domains.join(",") ]]</span>
|
|
</template>
|
|
<template slot="expectIPs" slot-scope="dns,index">
|
|
<span v-if="typeof dns == 'object'">[[ dns.expectIPs.join(",") ]]</span>
|
|
</template>
|
|
</a-table>
|
|
<a-divider>{{ i18n "pages.xray.dns.hosts" }}</a-divider>
|
|
<a-row :xs="24" :sm="24" :lg="12">
|
|
<a-alert type="info" style="text-align: center; margin-bottom: 10px;">
|
|
<template slot="message">{{ i18n "pages.xray.dns.hostsDesc" }}</template>
|
|
</a-alert>
|
|
</a-row>
|
|
<a-button type="primary" icon="plus" @click="addDnsHost()" style="margin-bottom: 10px;">{{ i18n "pages.xray.dns.addHost" }}</a-button>
|
|
<a-input-group compact v-for="(host, index) in dnsHostsArray" :key="index" style="margin-bottom: 8px; display: flex; max-width: 500px;">
|
|
<a-input style="width: 45%;" v-model.trim="host.domain" @change="applyDnsHosts" placeholder="domain:example.com"></a-input>
|
|
<a-input style="width: 45%; border-radius: 0;" v-model.trim="host.value" @change="applyDnsHosts" placeholder="1.1.1.1, 2.2.2.2"></a-input>
|
|
<a-button type="danger" icon="minus" @click="deleteDnsHost(index)" style="width: 10%;"></a-button>
|
|
</a-input-group>
|
|
<a-divider>Fake DNS</a-divider>
|
|
<a-button type="primary" icon="plus" @click="addFakedns()" style="margin-bottom: 10px;">{{ i18n "pages.xray.fakedns.add" }}</a-button>
|
|
<a-table :columns="fakednsColumns" bordered v-if="fakeDns && fakeDns.length>0"
|
|
:row-key="r => r.key"
|
|
:data-source="fakeDns"
|
|
:scroll="isMobile ? {} : { x: 200 }"
|
|
:pagination="false"
|
|
:indent-size="0"
|
|
:style="isMobile ? 'padding: 5px 0' : 'margin-left: 1px;'">
|
|
<template slot="action" slot-scope="text,fakedns,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="editFakedns(index)">
|
|
<a-icon type="edit"></a-icon>
|
|
{{ i18n "edit" }}
|
|
</a-menu-item>
|
|
<a-menu-item @click="deleteFakedns(index)">
|
|
<span style="color: #FF4D4F">
|
|
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
|
</span>
|
|
</a-menu-item>
|
|
</a-menu>
|
|
</a-dropdown>
|
|
</template>
|
|
</a-table>
|
|
</template>
|
|
</a-tab-pane>
|
|
<a-tab-pane key="tpl-geodata" tab='{{ i18n "pages.xray.geodata.title"}}' style="padding-top: 20px;" force-render="true">
|
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.geodata.enable" }}' desc='{{ i18n "pages.xray.geodata.enableDesc" }}' v-model="enableGeoData"></setting-list-item>
|
|
<template v-if="enableGeoData">
|
|
<setting-list-item type="text" title='{{ i18n "pages.xray.geodata.cron" }}' desc='{{ i18n "pages.xray.geodata.cronDesc" }}' v-model="geodataCron"></setting-list-item>
|
|
<a-list-item>
|
|
<a-row style="padding: 20px">
|
|
<a-col :lg="24" :xl="12">
|
|
<a-list-item-meta title='{{ i18n "pages.xray.geodata.outbound" }}' description='{{ i18n "pages.xray.geodata.outboundDesc" }}' />
|
|
</a-col>
|
|
<a-col :lg="24" :xl="12">
|
|
<a-select v-model="geodataOutbound" allow-clear style="width: 100%"
|
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
|
<a-select-option value="">Routing</a-select-option>
|
|
<a-select-option v-for="tag in geodataOutboundTags" :value="tag">[[ tag ]]</a-select-option>
|
|
</a-select>
|
|
</a-col>
|
|
</a-row>
|
|
</a-list-item>
|
|
<a-button type="primary" icon="plus" @click="addGeoDataAsset()" style="margin-bottom: 10px;">{{ i18n "pages.xray.geodata.addAsset" }}</a-button>
|
|
<a-table :columns="geodataAssetColumns" bordered v-if="geodataAssets.length>0"
|
|
:row-key="(r, index) => index"
|
|
:data-source="geodataAssets"
|
|
:scroll="isMobile ? {} : { x: 200 }"
|
|
:pagination="false"
|
|
:indent-size="0"
|
|
:style="isMobile ? 'padding: 5px 0' : 'margin-left: 1px;'">
|
|
<template slot="action" slot-scope="text, asset, 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="editGeoDataAsset(index)">
|
|
<a-icon type="edit"></a-icon>
|
|
{{ i18n "edit" }}
|
|
</a-menu-item>
|
|
<a-menu-item @click="deleteGeoDataAsset(index)">
|
|
<span style="color: #FF4D4F">
|
|
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
|
</span>
|
|
</a-menu-item>
|
|
</a-menu>
|
|
</a-dropdown>
|
|
</template>
|
|
</a-table>
|
|
</template>
|
|
</a-tab-pane>
|
|
<a-tab-pane key="tpl-advanced" 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>
|
|
<textarea style="position:absolute; left: -800px;" id="xraySetting"></textarea>
|
|
</a-tab-pane>
|
|
</a-tabs>
|
|
</a-space>
|
|
</a-spin>
|
|
</a-layout-content>
|
|
</a-layout>
|
|
</a-layout>
|
|
{{template "js" .}}
|
|
{{template "component/themeSwitcher" .}}
|
|
{{template "component/setting"}}
|
|
{{template "balancerModal"}}
|
|
{{template "dnsModal"}}
|
|
{{template "fakednsModal"}}
|
|
{{template "geodataAssetModal"}}
|
|
{{template "warpModal"}}
|
|
<script>
|
|
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 "address"}}', align: 'center', width: 50, scopedSlots: { customRender: 'address' } },
|
|
];
|
|
|
|
const balancerColumns = [
|
|
{ title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
|
|
{ title: '{{ i18n "pages.xray.balancer.tag"}}', dataIndex: 'tag', align: 'center', width: 50 },
|
|
{ title: '{{ i18n "pages.xray.balancer.balancerStrategy"}}', align: 'center', width: 50, scopedSlots: { customRender: 'strategy' }},
|
|
{ title: '{{ i18n "pages.xray.balancer.balancerSelectors"}}', align: 'center', width: 100, scopedSlots: { customRender: 'selector' }},
|
|
];
|
|
|
|
const dnsColumns = [
|
|
{ title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
|
|
{ title: '{{ i18n "address"}}', align: 'center', width: 50, scopedSlots: { customRender: 'address' } },
|
|
{ title: '{{ i18n "pages.xray.dns.domains"}}', align: 'center', width: 50, scopedSlots: { customRender: 'domain' } },
|
|
{ title: '{{ i18n "pages.xray.dns.expectIPs"}}', align: 'center', width: 50, scopedSlots: { customRender: 'expectIPs' } },
|
|
];
|
|
|
|
const fakednsColumns = [
|
|
{ title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
|
|
{ title: '{{ i18n "pages.xray.fakedns.ipPool"}}', dataIndex: 'ipPool', align: 'center', width: 50 },
|
|
{ title: '{{ i18n "pages.xray.fakedns.poolSize"}}', dataIndex: 'poolSize', align: 'center', width: 50 },
|
|
];
|
|
|
|
const geodataAssetColumns = [
|
|
{ title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
|
|
{ title: '{{ i18n "pages.xray.geodata.url"}}', dataIndex: 'url', align: 'center', width: 80, ellipsis: true },
|
|
{ title: '{{ i18n "pages.xray.geodata.file"}}', dataIndex: 'file', align: 'center', width: 40 },
|
|
];
|
|
|
|
const app = new Vue({
|
|
delimiters: ['[[', ']]'],
|
|
el: '#app',
|
|
data: {
|
|
siderDrawer,
|
|
themeSwitcher,
|
|
isDarkTheme: themeSwitcher.isDarkTheme,
|
|
spinning: false,
|
|
oldXraySetting: '',
|
|
xraySetting: '',
|
|
inboundTags: [],
|
|
clientReverseTags: [],
|
|
outboundTags: [],
|
|
outboundReverseTags: [],
|
|
loadedOutbounds: [],
|
|
dnsHostsArray: [],
|
|
saveBtnDisable: true,
|
|
restartResult: '',
|
|
showAlert: false,
|
|
isMobile: window.innerWidth <= 768,
|
|
obsSettings: '',
|
|
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",
|
|
settings: {
|
|
domainStrategy: "UseIPv4"
|
|
}
|
|
},
|
|
directSettings: {
|
|
tag: "direct",
|
|
protocol: "freedom"
|
|
},
|
|
routingDomainStrategies: ["AsIs", "IpIfNonMatch", "IpOnDemand"],
|
|
log: {
|
|
loglevel: ["none", "debug", "info", "warning", "error"],
|
|
access: ["none", "./access.log"],
|
|
error: ["none", "./error.log"],
|
|
dnsLog: false,
|
|
maskAddress: ["quarter", "half", "full"],
|
|
},
|
|
settingsData: {
|
|
protocols: {
|
|
bittorrent: ["bittorrent"],
|
|
},
|
|
IPsOptions: [
|
|
{ label: 'Private IP', value: 'geoip:private' },
|
|
{ label: '🇮🇷 Iran', value: 'ext:geoip_IR.dat:ir' },
|
|
{ label: '🇨🇳 China', value: 'geoip:cn' },
|
|
{ label: '🇷🇺 Russia', value: 'geoip:ru' },
|
|
],
|
|
DomainsOptions: [
|
|
{ label: 'Ads All', value: 'geosite:category-ads-all' },
|
|
{ label: 'Ads IR', value: 'ext:geosite_IR.dat:category-ads-all' },
|
|
{ label: '🇮🇷 Iran', value: 'ext:geosite_IR.dat:ir' },
|
|
{ label: '🇮🇷 .ir', value: 'regexp:.*\\.ir$' },
|
|
{ label: '🇮🇷 .ایران', value: 'regexp:.*\\.xn--mgba3a4f16a$' },
|
|
{ label: '🇨🇳 China', value: 'geosite:cn' },
|
|
{ label: '🇨🇳 .cn', value: 'regexp:.*\\.cn$' },
|
|
{ label: '🇷🇺 Russia', value: 'geosite:category-ru' },
|
|
{ label: '🇷🇺 .ru', value: 'regexp:.*\\.ru' },
|
|
],
|
|
ServicesOptions: [
|
|
{ label: 'Apple', value: 'geosite:apple' },
|
|
{ label: 'Meta', value: 'geosite:meta' },
|
|
{ label: 'Google', value: 'geosite:google' },
|
|
{ label: 'OpenAI', value: 'geosite:openai' },
|
|
{ label: 'Spotify', value: 'geosite:spotify' },
|
|
{ label: 'Netflix', value: 'geosite:netflix' },
|
|
],
|
|
familyProtectDNS: {
|
|
"servers": [
|
|
"1.1.1.3", // https://developers.cloudflare.com/1.1.1.1/setup/
|
|
"1.0.0.3",
|
|
"2606:4700:4700::1113",
|
|
"2606:4700:4700::1003"
|
|
],
|
|
"queryStrategy": "UseIP"
|
|
},
|
|
},
|
|
defaultObservatory: {
|
|
subjectSelector: [],
|
|
probeURL: "http://www.google.com/generate_204",
|
|
probeInterval: "10m",
|
|
enableConcurrency: true
|
|
},
|
|
defaultBurstObservatory: {
|
|
subjectSelector: [],
|
|
pingConfig: {
|
|
destination: "http://www.google.com/generate_204",
|
|
interval: "30m",
|
|
connectivity: "http://connectivitycheck.platform.hicloud.com/generate_204",
|
|
timeout: "10s",
|
|
sampling: 2
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
loading(spinning = true) {
|
|
this.spinning = spinning;
|
|
},
|
|
async getXraySetting() {
|
|
this.loading(true);
|
|
const msg = await HttpUtil.post("/xui/xray/");
|
|
this.loading(false);
|
|
if (msg.success) {
|
|
result = JSON.parse(msg.obj);
|
|
xs = JSON.stringify(result.xraySetting, null, 2);
|
|
this.oldXraySetting = xs;
|
|
this.xraySetting = xs;
|
|
this.dnsHostsArray = this.parseDnsHosts(result.xraySetting ? result.xraySetting.dns : null);
|
|
this.inboundTags = result.inboundTags;
|
|
this.clientReverseTags = result.clientReverseTags;
|
|
this.outboundTags = result.outboundTags || [];
|
|
this.outboundReverseTags = result.outboundReverseTags || [];
|
|
this.loadedOutbounds = result.outbounds || [];
|
|
this.saveBtnDisable = true;
|
|
}
|
|
},
|
|
async updateXraySetting() {
|
|
this.loading(true);
|
|
const msg = await HttpUtil.post("/xui/xray/update", {xraySetting : this.xraySetting});
|
|
this.loading(false);
|
|
if (msg.success) {
|
|
await this.getXraySetting();
|
|
}
|
|
},
|
|
async restartXray() {
|
|
this.loading(true);
|
|
const msg = await HttpUtil.post("server/restartXrayService");
|
|
if (msg.success) {
|
|
await PromiseUtil.sleep(500);
|
|
await this.getXrayResult();
|
|
}
|
|
this.loading(false);
|
|
},
|
|
async getXrayResult() {
|
|
const msg = await HttpUtil.get("/xui/xray/getXrayResult");
|
|
if(msg.success){
|
|
this.restartResult=msg.obj;
|
|
if(msg.obj.length > 1) Vue.prototype.$message.error(msg.obj);
|
|
}
|
|
},
|
|
async resetXrayConfigToDefault() {
|
|
this.loading(true);
|
|
const msg = await HttpUtil.get("/xui/xray/getDefaultJsonConfig");
|
|
this.loading(false);
|
|
if (msg.success) {
|
|
this.templateSettings = JSON.parse(JSON.stringify(msg.obj, null, 2));
|
|
this.saveBtnDisable = true;
|
|
}
|
|
},
|
|
changePage(pageKey) {
|
|
if(pageKey == 'tpl-advanced') this.changeCode();
|
|
if(pageKey == 'tpl-balancer') this.changeObsCode();
|
|
},
|
|
changeCode() {
|
|
if(this.cm != null) {
|
|
this.cm.toTextArea();
|
|
}
|
|
textAreaObj = document.getElementById('xraySetting');
|
|
textAreaObj.value = this.xraySetting;
|
|
this.cm = CodeMirror.fromTextArea(textAreaObj, this.cmOptions);
|
|
this.cm.on('change',editor => {
|
|
value = editor.getValue();
|
|
if(this.isJsonString(value)){
|
|
this.xraySetting = value;
|
|
}
|
|
});
|
|
},
|
|
changeObsCode() {
|
|
if(this.cm != null) {
|
|
this.cm.toTextArea();
|
|
}
|
|
if (this.obsSettings == ''){
|
|
this.cm = null;
|
|
return
|
|
}
|
|
textAreaObj = document.getElementById('obsSetting');
|
|
textAreaObj.value = this[this.obsSettings];
|
|
this.cm = CodeMirror.fromTextArea(textAreaObj, this.cmOptions);
|
|
this.cm.on('change',editor => {
|
|
value = editor.getValue();
|
|
if(this.isJsonString(value)){
|
|
this[this.obsSettings] = 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:
|
|
if (o.settings && o.settings.address && o.settings.port) {
|
|
return [o.settings.address + ':' + o.settings.port];
|
|
}
|
|
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];
|
|
case Protocols.Wireguard:
|
|
return o.settings.peers.map(peer => peer.endpoint);
|
|
default:
|
|
return null;
|
|
}
|
|
return serverObj ? serverObj.map(obj => obj.address + ':' + obj.port) : null;
|
|
},
|
|
addBalancer() {
|
|
balancerModal.show({
|
|
title: '{{ i18n "pages.xray.balancer.addBalancer"}}',
|
|
okText: '{{ i18n "pages.xray.balancer.addBalancer"}}',
|
|
balancerTags: this.balancersData.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag),
|
|
balancer: {
|
|
tag: '',
|
|
strategy: 'random',
|
|
selector: [],
|
|
fallbackTag: ''
|
|
},
|
|
confirm: (balancer) => {
|
|
balancerModal.loading();
|
|
newTemplateSettings = this.templateSettings;
|
|
if (newTemplateSettings.routing.balancers == undefined) {
|
|
newTemplateSettings.routing.balancers = [];
|
|
}
|
|
let tmpBalancer = {
|
|
'tag': balancer.tag,
|
|
'selector': balancer.selector,
|
|
'fallbackTag': balancer.fallbackTag
|
|
};
|
|
if (balancer.strategy && balancer.strategy != 'random') {
|
|
tmpBalancer.strategy = {
|
|
'type': balancer.strategy
|
|
};
|
|
}
|
|
newTemplateSettings.routing.balancers.push(tmpBalancer);
|
|
this.templateSettings = newTemplateSettings;
|
|
this.updateObservatorySelectors();
|
|
balancerModal.close();
|
|
this.changeObsCode();
|
|
},
|
|
isEdit: false
|
|
});
|
|
},
|
|
editBalancer(index) {
|
|
const oldTag = this.balancersData[index].tag;
|
|
balancerModal.show({
|
|
title: '{{ i18n "pages.xray.balancer.editBalancer"}}',
|
|
okText: '{{ i18n "sure" }}',
|
|
balancerTags: this.balancersData.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag),
|
|
balancer: this.balancersData[index],
|
|
confirm: (balancer) => {
|
|
balancerModal.loading();
|
|
newTemplateSettings = this.templateSettings;
|
|
|
|
let tmpBalancer = {
|
|
'tag': balancer.tag,
|
|
'selector': balancer.selector,
|
|
'fallbackTag': balancer.fallbackTag
|
|
};
|
|
|
|
// Remove old tag
|
|
if (newTemplateSettings.observatory){
|
|
newTemplateSettings.observatory.subjectSelector = newTemplateSettings.observatory.subjectSelector.filter(s => s != oldTag);
|
|
}
|
|
if (newTemplateSettings.burstObservatory){
|
|
newTemplateSettings.burstObservatory.subjectSelector = newTemplateSettings.burstObservatory.subjectSelector.filter(s => s != oldTag);
|
|
}
|
|
|
|
if (balancer.strategy && balancer.strategy != 'random') {
|
|
tmpBalancer.strategy = {
|
|
'type': balancer.strategy
|
|
};
|
|
}
|
|
|
|
newTemplateSettings.routing.balancers[index] = tmpBalancer;
|
|
if (oldTag != balancer.tag) {
|
|
HttpUtil.post('/xui/routing/replaceBalancerTag', { oldTag: oldTag, newTag: balancer.tag });
|
|
}
|
|
this.templateSettings = newTemplateSettings;
|
|
this.updateObservatorySelectors();
|
|
balancerModal.close();
|
|
this.changeObsCode();
|
|
},
|
|
isEdit: true
|
|
});
|
|
},
|
|
updateObservatorySelectors(){
|
|
newTemplateSettings = this.templateSettings;
|
|
const leastPings = this.balancersData.filter((b) => b.strategy == 'leastPing');
|
|
const leastLoads = this.balancersData.filter((b) =>
|
|
b.strategy === 'leastLoad' ||
|
|
b.strategy === 'roundRobin' ||
|
|
b.strategy === 'random'
|
|
);
|
|
if (leastPings.length>0){
|
|
if (!newTemplateSettings.observatory)
|
|
newTemplateSettings.observatory = this.defaultObservatory;
|
|
newTemplateSettings.observatory.subjectSelector = [];
|
|
leastPings.forEach((b) => {
|
|
b.selector.forEach((s) => {
|
|
if (!newTemplateSettings.observatory.subjectSelector.includes(s))
|
|
newTemplateSettings.observatory.subjectSelector.push(s);
|
|
});
|
|
});
|
|
} else {
|
|
delete newTemplateSettings.observatory
|
|
}
|
|
if (leastLoads.length>0){
|
|
if (!newTemplateSettings.burstObservatory)
|
|
newTemplateSettings.burstObservatory = this.defaultBurstObservatory;
|
|
newTemplateSettings.burstObservatory.subjectSelector = [];
|
|
leastLoads.forEach((b) => {
|
|
b.selector.forEach((s) => {
|
|
if (!newTemplateSettings.burstObservatory.subjectSelector.includes(s))
|
|
newTemplateSettings.burstObservatory.subjectSelector.push(s);
|
|
});
|
|
});
|
|
} else {
|
|
delete newTemplateSettings.burstObservatory
|
|
}
|
|
this.templateSettings = newTemplateSettings;
|
|
this.changeObsCode();
|
|
},
|
|
deleteBalancer(index) {
|
|
newTemplateSettings = this.templateSettings;
|
|
|
|
//remove from balancers
|
|
const removedBalancer = this.balancersData.splice(index, 1)[0];
|
|
|
|
// Remove from settings
|
|
let realIndex = newTemplateSettings.routing.balancers.findIndex((b) => b.tag === removedBalancer.tag);
|
|
newTemplateSettings.routing.balancers.splice(realIndex, 1);
|
|
|
|
// Update balancers property to an empty array if there are no more balancers
|
|
if (newTemplateSettings.routing.balancers.length === 0) {
|
|
delete newTemplateSettings.routing.balancers;
|
|
}
|
|
this.templateSettings = newTemplateSettings;
|
|
this.updateObservatorySelectors();
|
|
this.obsSettings = '';
|
|
this.changeObsCode()
|
|
},
|
|
addDNSServer(){
|
|
dnsModal.show({
|
|
title: '{{ i18n "pages.xray.dns.add" }}',
|
|
confirm: (dnsServer) => {
|
|
dnsServers = this.dnsServers;
|
|
dnsServers.push(dnsServer);
|
|
this.dnsServers = dnsServers;
|
|
dnsModal.close();
|
|
},
|
|
isEdit: false
|
|
});
|
|
},
|
|
editDNSServer(index){
|
|
dnsModal.show({
|
|
title: '{{ i18n "pages.xray.dns.edit" }} #' + (index+1),
|
|
dnsServer: this.dnsServers[index],
|
|
confirm: (dnsServer) => {
|
|
dnsServers = this.dnsServers;
|
|
dnsServers[index] = dnsServer;
|
|
this.dnsServers = dnsServers;
|
|
dnsModal.close();
|
|
},
|
|
isEdit: true
|
|
});
|
|
},
|
|
deleteDNSServer(index){
|
|
newDnsServers = this.dnsServers;
|
|
newDnsServers.splice(index,1);
|
|
this.dnsServers = newDnsServers;
|
|
},
|
|
parseDnsHosts(dns) {
|
|
if (!dns || !dns.hosts) return [];
|
|
return Object.entries(dns.hosts).map(([domain, value]) => ({
|
|
domain: domain,
|
|
value: Array.isArray(value) ? value.join(", ") : value,
|
|
}));
|
|
},
|
|
addDnsHost() {
|
|
this.dnsHostsArray.push({ domain: '', value: '' });
|
|
},
|
|
deleteDnsHost(index) {
|
|
this.dnsHostsArray.splice(index, 1);
|
|
this.applyDnsHosts();
|
|
},
|
|
applyDnsHosts() {
|
|
if (!this.enableDNS) return;
|
|
newTemplateSettings = this.templateSettings;
|
|
const hosts = {};
|
|
this.dnsHostsArray.forEach(h => {
|
|
const domain = (h.domain || '').trim();
|
|
if (!domain) return;
|
|
const values = (h.value || '').split(",").map(s => s.trim()).filter(s => s.length > 0);
|
|
if (values.length === 0) return;
|
|
hosts[domain] = values.length === 1 ? values[0] : values;
|
|
});
|
|
if (Object.keys(hosts).length > 0) {
|
|
newTemplateSettings.dns.hosts = hosts;
|
|
} else {
|
|
delete newTemplateSettings.dns.hosts;
|
|
}
|
|
this.templateSettings = newTemplateSettings;
|
|
},
|
|
addFakedns() {
|
|
fakednsModal.show({
|
|
title: '{{ i18n "pages.xray.fakedns.add" }}',
|
|
confirm: (item) => {
|
|
fakeDns = this.fakeDns?? [];
|
|
fakeDns.push(item);
|
|
this.fakeDns = fakeDns;
|
|
fakednsModal.close();
|
|
},
|
|
isEdit: false
|
|
});
|
|
},
|
|
editFakedns(index){
|
|
fakednsModal.show({
|
|
title: '{{ i18n "pages.xray.fakedns.edit" }} #' + (index+1),
|
|
fakeDns: this.fakeDns[index],
|
|
confirm: (item) => {
|
|
fakeDns = this.fakeDns;
|
|
fakeDns[index] = item;
|
|
this.fakeDns = fakeDns;
|
|
fakednsModal.close();
|
|
},
|
|
isEdit: true
|
|
});
|
|
},
|
|
deleteFakedns(index){
|
|
fakeDns = this.fakeDns;
|
|
fakeDns.splice(index,1);
|
|
this.fakeDns = fakeDns;
|
|
},
|
|
addGeoDataAsset() {
|
|
geodataAssetModal.show({
|
|
title: '{{ i18n "pages.xray.geodata.addAsset" }}',
|
|
confirm: (asset) => {
|
|
assets = this.geodataAssets;
|
|
assets.push(asset);
|
|
this.geodataAssets = assets;
|
|
geodataAssetModal.close();
|
|
},
|
|
isEdit: false
|
|
});
|
|
},
|
|
editGeoDataAsset(index) {
|
|
geodataAssetModal.show({
|
|
title: '{{ i18n "pages.xray.geodata.editAsset" }} #' + (index + 1),
|
|
asset: this.geodataAssets[index],
|
|
confirm: (asset) => {
|
|
assets = this.geodataAssets;
|
|
assets[index] = asset;
|
|
this.geodataAssets = assets;
|
|
geodataAssetModal.close();
|
|
},
|
|
isEdit: true
|
|
});
|
|
},
|
|
deleteGeoDataAsset(index) {
|
|
assets = this.geodataAssets;
|
|
assets.splice(index, 1);
|
|
this.geodataAssets = assets;
|
|
},
|
|
},
|
|
async mounted() {
|
|
if (window.location.protocol !== "https:") {
|
|
this.showAlert = true;
|
|
}
|
|
await this.getXraySetting();
|
|
await this.getXrayResult();
|
|
while (true) {
|
|
await PromiseUtil.sleep(1000);
|
|
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); },
|
|
},
|
|
logSettings: {
|
|
get: function () { return this.templateSettings ? this.templateSettings.log : {}; },
|
|
set: function (newValue) {
|
|
newTemplateSettings = this.templateSettings;
|
|
newTemplateSettings.log = newValue;
|
|
this.templateSettings = newTemplateSettings;
|
|
},
|
|
},
|
|
logLevel: {
|
|
get: function () {
|
|
if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.loglevel) return "warning";
|
|
return this.templateSettings.log.loglevel;
|
|
},
|
|
set: function (newValue) {
|
|
newTemplateSettings = this.templateSettings;
|
|
newTemplateSettings.log.loglevel = newValue;
|
|
this.templateSettings = newTemplateSettings;
|
|
}
|
|
},
|
|
accessLog: {
|
|
get: function () {
|
|
if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.access) return "";
|
|
return this.templateSettings.log.access;
|
|
},
|
|
set: function (newValue) {
|
|
newTemplateSettings = this.templateSettings;
|
|
newTemplateSettings.log.access = newValue;
|
|
this.templateSettings = newTemplateSettings;
|
|
}
|
|
},
|
|
errorLog: {
|
|
get: function () {
|
|
if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.error) return "";
|
|
return this.templateSettings.log.error;
|
|
},
|
|
set: function (newValue) {
|
|
newTemplateSettings = this.templateSettings;
|
|
newTemplateSettings.log.error = newValue;
|
|
this.templateSettings = newTemplateSettings;
|
|
}
|
|
},
|
|
dnslog: {
|
|
get: function () {
|
|
if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.dnsLog) return false;
|
|
return this.templateSettings.log.dnsLog;
|
|
},
|
|
set: function (newValue) {
|
|
newTemplateSettings = this.templateSettings;
|
|
newTemplateSettings.log.dnsLog = newValue;
|
|
this.templateSettings = newTemplateSettings;
|
|
}
|
|
},
|
|
maskAddressLog: {
|
|
get: function () {
|
|
if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.maskAddress) return "";
|
|
return this.templateSettings.log.maskAddress;
|
|
},
|
|
set: function (newValue) {
|
|
newTemplateSettings = this.templateSettings;
|
|
newTemplateSettings.log.maskAddress = newValue;
|
|
this.templateSettings = newTemplateSettings;
|
|
}
|
|
},
|
|
metricsEnable: {
|
|
get: function () { return this.templateSettings != null && this.templateSettings.metrics != undefined; },
|
|
set: function (newValue) {
|
|
newTemplateSettings = this.templateSettings;
|
|
if (newValue) {
|
|
if (!newTemplateSettings.metrics) newTemplateSettings.metrics = { tag: "metrics", listen: "127.0.0.1:11111" };
|
|
} else {
|
|
delete newTemplateSettings.metrics;
|
|
}
|
|
this.templateSettings = newTemplateSettings;
|
|
}
|
|
},
|
|
metricsListen: {
|
|
get: function () { return this.templateSettings && this.templateSettings.metrics ? this.templateSettings.metrics.listen : ""; },
|
|
set: function (newValue) {
|
|
newTemplateSettings = this.templateSettings;
|
|
if (newTemplateSettings.metrics) newTemplateSettings.metrics.listen = newValue;
|
|
this.templateSettings = newTemplateSettings;
|
|
}
|
|
},
|
|
metricsTag: {
|
|
get: function () { return this.templateSettings && this.templateSettings.metrics ? this.templateSettings.metrics.tag : ""; },
|
|
set: function (newValue) {
|
|
newTemplateSettings = this.templateSettings;
|
|
if (newTemplateSettings.metrics) newTemplateSettings.metrics.tag = newValue;
|
|
this.templateSettings = newTemplateSettings;
|
|
}
|
|
},
|
|
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;
|
|
},
|
|
},
|
|
balancersData: {
|
|
get: function () {
|
|
data = []
|
|
if (this.templateSettings != null && this.templateSettings.routing != null && this.templateSettings.routing.balancers != null) {
|
|
this.templateSettings.routing.balancers.forEach((o, index) => {
|
|
data.push({
|
|
'key': index,
|
|
'tag': o.tag ? o.tag : "",
|
|
'strategy': o.strategy?.type ?? "random",
|
|
'selector': o.selector ? o.selector : [],
|
|
'fallbackTag': o.fallbackTag?? '',
|
|
});
|
|
});
|
|
}
|
|
return data;
|
|
}
|
|
},
|
|
observatory: {
|
|
get: function () {
|
|
return this.templateSettings?.observatory ? JSON.stringify(this.templateSettings.observatory, null, 2) : null;
|
|
},
|
|
set: function (newValue) {
|
|
newTemplateSettings = this.templateSettings;
|
|
newTemplateSettings.observatory = JSON.parse(newValue);
|
|
this.templateSettings = newTemplateSettings;
|
|
},
|
|
},
|
|
burstObservatory: {
|
|
get: function () {
|
|
return this.templateSettings?.burstObservatory ? JSON.stringify(this.templateSettings.burstObservatory, null, 2) : null;
|
|
},
|
|
set: function (newValue) {
|
|
newTemplateSettings = this.templateSettings;
|
|
newTemplateSettings.burstObservatory = JSON.parse(newValue);
|
|
this.templateSettings = newTemplateSettings;
|
|
},
|
|
},
|
|
observatoryEnable: function () { return this.templateSettings != null && this.templateSettings.observatory != undefined },
|
|
burstObservatoryEnable: function () { return this.templateSettings != null && this.templateSettings.burstObservatory != undefined },
|
|
freedomStrategy: {
|
|
get: function () {
|
|
const freedomOutbound = this.loadedOutbounds.find((o) => o.protocol === "freedom" && o.tag == "direct");
|
|
if (!freedomOutbound || !freedomOutbound.settings || !freedomOutbound.settings.domainStrategy) return "AsIs";
|
|
return freedomOutbound.settings.domainStrategy;
|
|
},
|
|
set: function (newValue) {
|
|
const freedomOutbound = this.loadedOutbounds.find((o) => o.protocol === "freedom" && o.tag == "direct");
|
|
if (!freedomOutbound) return;
|
|
const settings = freedomOutbound.settings || {};
|
|
settings.domainStrategy = newValue;
|
|
const data = {
|
|
protocol: freedomOutbound.protocol,
|
|
tag: freedomOutbound.tag,
|
|
settings: JSON.stringify(settings, null, 2),
|
|
};
|
|
HttpUtil.post(`/xui/outbound/update/${freedomOutbound.id}`, data).then(() => this.getXraySetting());
|
|
}
|
|
},
|
|
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;
|
|
}
|
|
},
|
|
familyProtectSettings: {
|
|
get: function () {
|
|
if (!this.templateSettings || !this.templateSettings.dns || !this.templateSettings.dns.servers) return false;
|
|
return doAllItemsExist(this.settingsData.familyProtectDNS.servers, this.templateSettings.dns.servers);
|
|
},
|
|
set: function (newValue) {
|
|
newTemplateSettings = this.templateSettings;
|
|
if (newValue) {
|
|
newTemplateSettings.dns = this.settingsData.familyProtectDNS;
|
|
} else {
|
|
newTemplateSettings.dns.servers = newTemplateSettings.dns?.servers?.filter(data => !this.settingsData.familyProtectDNS.servers.includes(data))
|
|
}
|
|
this.templateSettings = newTemplateSettings;
|
|
},
|
|
},
|
|
WarpExist: {
|
|
get: function() {
|
|
return this.outboundTags ? this.outboundTags.includes("warp") : false;
|
|
},
|
|
},
|
|
enableDNS: {
|
|
get: function () {
|
|
return this.templateSettings ? this.templateSettings.dns != null : false;
|
|
},
|
|
set: function (newValue) {
|
|
newTemplateSettings = this.templateSettings;
|
|
if (newValue) {
|
|
newTemplateSettings.dns = { servers: [], queryStrategy: "UseIP", tag: "dns_inbound" };
|
|
newTemplateSettings.fakedns = null;
|
|
} else {
|
|
delete newTemplateSettings.dns;
|
|
delete newTemplateSettings.fakedns;
|
|
}
|
|
this.dnsHostsArray = [];
|
|
this.templateSettings = newTemplateSettings;
|
|
}
|
|
},
|
|
dnsTag: {
|
|
get: function () {
|
|
return this.enableDNS ? this.templateSettings.dns.tag : "";
|
|
},
|
|
set: function (newValue) {
|
|
newTemplateSettings = this.templateSettings;
|
|
newTemplateSettings.dns.tag = newValue;
|
|
this.templateSettings = newTemplateSettings;
|
|
}
|
|
},
|
|
dnsStrategy: {
|
|
get: function () {
|
|
return this.enableDNS ? this.templateSettings.dns.queryStrategy : null;
|
|
},
|
|
set: function (newValue) {
|
|
newTemplateSettings = this.templateSettings;
|
|
newTemplateSettings.dns.queryStrategy = newValue;
|
|
this.templateSettings = newTemplateSettings;
|
|
}
|
|
},
|
|
dnsServers: {
|
|
get: function () { return this.enableDNS ? this.templateSettings.dns.servers : []; },
|
|
set: function (newValue) {
|
|
newTemplateSettings = this.templateSettings;
|
|
newTemplateSettings.dns.servers = newValue;
|
|
this.templateSettings = newTemplateSettings;
|
|
}
|
|
},
|
|
fakeDns: {
|
|
get: function () { return this.templateSettings && this.templateSettings.fakedns ? this.templateSettings.fakedns : []; },
|
|
set: function (newValue) {
|
|
newTemplateSettings = this.templateSettings;
|
|
newTemplateSettings.fakedns = newValue.length >0 ? newValue : null;
|
|
this.templateSettings = newTemplateSettings;
|
|
}
|
|
},
|
|
geodataSettings: function () {
|
|
if (!this.templateSettings) return null;
|
|
return this.templateSettings.geodata ?? this.templateSettings.geoData ?? null;
|
|
},
|
|
enableGeoData: {
|
|
get: function () { return this.geodataSettings != null; },
|
|
set: function (newValue) {
|
|
newTemplateSettings = this.templateSettings;
|
|
if (newValue) {
|
|
if (!newTemplateSettings.geodata) {
|
|
newTemplateSettings.geodata = newTemplateSettings.geoData ?? { assets: [] };
|
|
delete newTemplateSettings.geoData;
|
|
}
|
|
} else {
|
|
delete newTemplateSettings.geodata;
|
|
delete newTemplateSettings.geoData;
|
|
}
|
|
this.templateSettings = newTemplateSettings;
|
|
}
|
|
},
|
|
geodataCron: {
|
|
get: function () {
|
|
const g = this.geodataSettings;
|
|
return g && g.cron ? g.cron : '';
|
|
},
|
|
set: function (newValue) {
|
|
newTemplateSettings = this.templateSettings;
|
|
if (!newTemplateSettings.geodata) {
|
|
newTemplateSettings.geodata = newTemplateSettings.geoData ?? { assets: [] };
|
|
delete newTemplateSettings.geoData;
|
|
}
|
|
if (newValue) {
|
|
newTemplateSettings.geodata.cron = newValue;
|
|
} else {
|
|
delete newTemplateSettings.geodata.cron;
|
|
}
|
|
this.templateSettings = newTemplateSettings;
|
|
}
|
|
},
|
|
geodataOutbound: {
|
|
get: function () {
|
|
const g = this.geodataSettings;
|
|
return g && g.outbound ? g.outbound : '';
|
|
},
|
|
set: function (newValue) {
|
|
newTemplateSettings = this.templateSettings;
|
|
if (!newTemplateSettings.geodata) {
|
|
newTemplateSettings.geodata = newTemplateSettings.geoData ?? { assets: [] };
|
|
delete newTemplateSettings.geoData;
|
|
}
|
|
if (newValue) {
|
|
newTemplateSettings.geodata.outbound = newValue;
|
|
} else {
|
|
delete newTemplateSettings.geodata.outbound;
|
|
}
|
|
this.templateSettings = newTemplateSettings;
|
|
}
|
|
},
|
|
geodataOutboundTags: function () {
|
|
return this.outboundTags || [];
|
|
},
|
|
geodataAssets: {
|
|
get: function () {
|
|
const g = this.geodataSettings;
|
|
return g && g.assets ? g.assets : [];
|
|
},
|
|
set: function (newValue) {
|
|
newTemplateSettings = this.templateSettings;
|
|
if (!newTemplateSettings.geodata) {
|
|
newTemplateSettings.geodata = newTemplateSettings.geoData ?? { assets: [] };
|
|
delete newTemplateSettings.geoData;
|
|
}
|
|
newTemplateSettings.geodata.assets = newValue;
|
|
this.templateSettings = newTemplateSettings;
|
|
}
|
|
}
|
|
},
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|