x-ui/web/html/xui/xray.html
Alireza Ahmadi fff3af0d8e
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
DNS hosts mapping
2026-06-15 23:16:47 +02:00

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>