Manage balancers in settings UI (#1759)

* add balancer config to ui

* manage balancer in rules table

* fix balancer translations

* fix edit button text
This commit is contained in:
Saeid 2024-02-06 11:40:49 +03:30 committed by GitHub
parent 222b9734ca
commit c53cee31f5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 363 additions and 3 deletions

View file

@ -327,6 +327,14 @@
[[ rule.outboundTag ]] [[ rule.outboundTag ]]
</a-popover> </a-popover>
</template> </template>
<template slot="balancer" slot-scope="text, rule, index">
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
<template slot="content">
<p v-if="rule.balancerTag">Balancer Tag: [[ rule.balancerTag ]]</p>
</template>
[[ rule.balancerTag ]]
</a-popover>
</template>
<template slot="info" slot-scope="text, rule, index"> <template slot="info" slot-scope="text, rule, index">
<a-popover placement="bottomRight" <a-popover placement="bottomRight"
v-if="(rule.source+rule.sourcePort+rule.network+rule.protocol+rule.attrs+rule.ip+rule.domain+rule.port).length>0" v-if="(rule.source+rule.sourcePort+rule.network+rule.protocol+rule.attrs+rule.ip+rule.domain+rule.port).length>0"
@ -452,6 +460,41 @@
</template> </template>
</a-table> </a-table>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="tpl-balancers" 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
: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="purple">Random</a-tag>
<a-tag style="margin:0;" v-if="balancer.strategy=='roundRobin'" color="green">Round Robin</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-tab-pane>
<a-tab-pane key="tpl-advanced" tab='{{ i18n "pages.xray.advancedTemplate"}}' style="padding-top: 20px;" force-render="true"> <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> <a-list-item-meta title='{{ i18n "pages.xray.Template"}}' description='{{ i18n "pages.xray.TemplateDesc"}}'></a-list-item-meta>
<a-radio-group v-model="advSettings" @change="changeCode" button-style="solid" style="margin: 10px 0;" :size="isMobile ? 'small' : ''"> <a-radio-group v-model="advSettings" @change="changeCode" button-style="solid" style="margin: 10px 0;" :size="isMobile ? 'small' : ''">
@ -474,6 +517,7 @@
{{template "ruleModal"}} {{template "ruleModal"}}
{{template "outModal"}} {{template "outModal"}}
{{template "reverseModal"}} {{template "reverseModal"}}
{{template "balancerModal"}}
{{template "warpModal"}} {{template "warpModal"}}
<script> <script>
const rulesColumns = [ const rulesColumns = [
@ -490,9 +534,10 @@
{ title: 'Domain', dataIndex: 'domain', align: 'center', width: 20, ellipsis: true }, { title: 'Domain', dataIndex: 'domain', align: 'center', width: 20, ellipsis: true },
{ title: 'Port', dataIndex: 'port', align: 'center', width: 10, ellipsis: true }]}, { title: 'Port', dataIndex: 'port', align: 'center', width: 10, ellipsis: true }]},
{ title: '{{ i18n "pages.xray.rules.inbound"}}', children: [ { title: '{{ i18n "pages.xray.rules.inbound"}}', children: [
{ title: 'Inbound Tag', dataIndex: 'inboundTag', align: 'center', width: 20, ellipsis: true }, { title: 'Inbound Tag', dataIndex: 'inboundTag', align: 'center', width: 15, ellipsis: true },
{ title: 'Client Email', dataIndex: 'user', align: 'center', width: 20, ellipsis: true }]}, { title: 'Client Email', dataIndex: 'user', align: 'center', width: 20, ellipsis: true }]},
{ title: '{{ i18n "pages.xray.rules.outbound"}}', dataIndex: 'outboundTag', align: 'center', width: 20 }, { title: '{{ i18n "pages.xray.rules.outbound"}}', dataIndex: 'outboundTag', align: 'center', width: 15 },
{ title: '{{ i18n "pages.xray.rules.balancer"}}', dataIndex: 'balancerTag', align: 'center', width: 15 },
]; ];
const rulesMobileColumns = [ const rulesMobileColumns = [
@ -517,6 +562,13 @@
{ title: '{{ i18n "pages.xray.outbound.domain"}}', dataIndex: 'domain', align: 'center', width: 50 }, { title: '{{ i18n "pages.xray.outbound.domain"}}', dataIndex: 'domain', align: 'center', width: 50 },
]; ];
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 app = new Vue({ const app = new Vue({
delimiters: ['[[', ']]'], delimiters: ['[[', ']]'],
el: '#app', el: '#app',
@ -895,6 +947,95 @@
this.refreshing = false; this.refreshing = false;
} }
}, },
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: []
},
confirm: (balancer) => {
balancerModal.loading();
newTemplateSettings = this.templateSettings;
if (newTemplateSettings.routing.balancers == undefined) {
newTemplateSettings.routing.balancers = [];
}
let tmpBalancer = {
'tag': balancer.tag,
'selector': balancer.selector
};
if (balancer.strategy == 'roundRobin') {
tmpBalancer.strategy = {
'type': balancer.strategy
};
}
newTemplateSettings.routing.balancers.push(tmpBalancer);
this.templateSettings = newTemplateSettings;
balancerModal.close();
},
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
};
if (balancer.strategy == 'roundRobin') {
tmpBalancer.strategy = {
'type': balancer.strategy
};
}
newTemplateSettings.routing.balancers[index] = tmpBalancer;
// change edited tag if used in rule section
if (oldTag != balancer.tag) {
newTemplateSettings.routing.rules.forEach((rule) => {
if (rule.balancerTag && rule.balancerTag == oldTag) {
rule.balancerTag = balancer.tag;
}
});
}
this.templateSettings = newTemplateSettings;
balancerModal.close();
},
isEdit: true
});
},
deleteBalancer(index) {
newTemplateSettings = this.templateSettings;
//remove from balancers
const oldTag = this.balancersData[index].tag;
this.balancersData.splice(index, 1);
// remove from settings
let realIndex = newTemplateSettings.routing.balancers.findIndex((b) => b.tag == oldTag);
newTemplateSettings.routing.balancers.splice(realIndex, 1);
// remove related routing rules
let rules = [];
newTemplateSettings.routing.rules.forEach((r) => {
if (!r.balancerTag || r.balancerTag != oldTag) {
rules.push(r);
}
});
newTemplateSettings.routing.rules = rules;
this.templateSettings = newTemplateSettings;
},
addReverse(){ addReverse(){
reverseModal.show({ reverseModal.show({
title: '{{ i18n "pages.xray.outbound.addReverse"}}', title: '{{ i18n "pages.xray.outbound.addReverse"}}',
@ -1084,6 +1225,27 @@
return data; return data;
}, },
}, },
balancersData: {
get: function () {
data = []
if (this.templateSettings != null && this.templateSettings.routing != null && this.templateSettings.routing.balancers != null) {
this.templateSettings.routing.balancers.forEach((o, index) => {
let strategy = "random"
if (o.strategy && o.strategy.type == "roundRobin") {
strategy = o.strategy.type
}
data.push({
'key': index,
'tag': o.tag ? o.tag : "",
'strategy': strategy,
'selector': o.selector ? o.selector : []
});
});
}
return data;
}
},
routingRuleSettings: { routingRuleSettings: {
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.routing.rules, null, 2) : null; }, get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.routing.rules, null, 2) : null; },
set: function (newValue) { set: function (newValue) {

View file

@ -0,0 +1,111 @@
{{define "balancerModal"}}
<a-modal
id="balancer-modal"
v-model="balancerModal.visible"
:title="balancerModal.title"
@ok="balancerModal.ok"
:confirm-loading="balancerModal.confirmLoading"
:ok-button-props="{ props: { disabled: !balancerModal.isValid } }"
:closable="true"
:mask-closable="false"
:ok-text="balancerModal.okText"
cancel-text='{{ i18n "close" }}'
:class="themeSwitcher.currentTheme">
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "pages.xray.balancer.tag" }}' has-feedback
:validate-status="balancerModal.duplicateTag? 'warning' : 'success'">
<a-input v-model.trim="balancerModal.balancer.tag" @change="balancerModal.check()"
placeholder='{{ i18n "balancerModal.balancer.tagDesc" }}'></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.balancer.balancerStrategy" }}'>
<a-select v-model="balancerModal.balancer.strategy" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="random">Random</a-select-option>
<a-select-option value="roundRobin">Round Robin</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.balancer.balancerSelectors" }}' has-feedback :validate-status="balancerModal.emptySelector? 'warning' : 'success'">
<a-select v-model="balancerModal.balancer.selector" mode="tags" @change="balancerModal.checkSelector()"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="tag in balancerModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
</a-select>
</a-form-item>
</table>
</a-form>
</a-modal>
<script>
const balancerModal = {
title: '',
visible: false,
confirmLoading: false,
okText: '{{ i18n "sure" }}',
isEdit: false,
confirm: null,
duplicateTag: false,
emptySelector: false,
balancer: {
tag: '',
strategy: 'random',
selector: []
},
outboundTags: [],
balancerTags:[],
ok() {
if (balancerModal.balancer.selector.length == 0) {
balancerModal.emptySelector = true;
return;
}
balancerModal.emptySelector = false;
ObjectUtil.execute(balancerModal.confirm, balancerModal.balancer);
},
show({ title = '', okText = '{{ i18n "sure" }}', balancerTags = [], balancer, confirm = (balancer) => { }, isEdit = false }) {
this.title = title;
this.okText = okText;
this.confirm = confirm;
this.visible = true;
if (isEdit) {
balancerModal.balancer = balancer;
} else {
balancerModal.balancer = {
tag: '',
strategy: 'random',
selector: []
};
}
this.balancerTags = balancerTags.filter((tag) => tag != balancer.tag);
this.outboundTags = app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag);
this.isEdit = isEdit;
this.check()
},
close() {
balancerModal.visible = false;
balancerModal.loading(false);
},
loading(loading) {
balancerModal.confirmLoading = loading;
},
check() {
if (balancerModal.balancer.tag == '' || balancerModal.balancerTags.includes(balancerModal.balancer.tag)) {
this.duplicateTag = true;
this.isValid = false;
} else {
this.duplicateTag = false;
this.isValid = true;
}
},
checkSelector() {
balancerModal.emptySelector = balancerModal.balancer.selector.length == 0;
}
};
new Vue({
delimiters: ['[[', ']]'],
el: '#balancer-modal',
data: {
balancerModal: balancerModal
},
methods: {
}
});
</script>
{{end}}

View file

@ -107,6 +107,19 @@
<a-select-option v-for="tag in ruleModal.outboundTags" :value="tag">[[ tag ]]</a-select-option> <a-select-option v-for="tag in ruleModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.xray.balancer.balancerDesc" }}</span>
</template>
Balancer Tag <a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-select v-model="ruleModal.rule.balancerTag" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="tag in ruleModal.balancerTags" :value="tag">[[ tag ]]</a-select-option>
</a-select>
</a-form-item>
</table> </table>
</a-form> </a-form>
</a-modal> </a-modal>
@ -133,11 +146,12 @@
protocol: [], protocol: [],
attrs: [], attrs: [],
outboundTag: "", outboundTag: "",
balancerTag: "",
}, },
inboundTags: [], inboundTags: [],
outboundTags: [], outboundTags: [],
users: [], users: [],
balancerTag: [], balancerTags: [],
ok() { ok() {
newRule = ruleModal.getResult(); newRule = ruleModal.getResult();
ObjectUtil.execute(ruleModal.confirm, newRule); ObjectUtil.execute(ruleModal.confirm, newRule);
@ -160,6 +174,7 @@
this.rule.protocol = rule.protocol; this.rule.protocol = rule.protocol;
this.rule.attrs = rule.attrs ? Object.entries(rule.attrs) : []; this.rule.attrs = rule.attrs ? Object.entries(rule.attrs) : [];
this.rule.outboundTag = rule.outboundTag; this.rule.outboundTag = rule.outboundTag;
this.rule.balancerTag = rule.balancerTag ? rule.balancerTag : ""
} else { } else {
this.rule = { this.rule = {
domainMatcher: "", domainMatcher: "",
@ -174,6 +189,7 @@
protocol: [], protocol: [],
attrs: [], attrs: [],
outboundTag: "", outboundTag: "",
balancerTag: "",
} }
} }
this.isEdit = isEdit; this.isEdit = isEdit;
@ -186,6 +202,10 @@
} }
if(app.templateSettings.reverse.portals) this.outboundTags.push(...app.templateSettings.reverse.portals.map(b => b.tag)); if(app.templateSettings.reverse.portals) this.outboundTags.push(...app.templateSettings.reverse.portals.map(b => b.tag));
} }
if (app.templateSettings.routing && app.templateSettings.routing.balancers) {
this.balancerTags = app.templateSettings.routing.balancers.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag)
}
}, },
close() { close() {
ruleModal.visible = false; ruleModal.visible = false;
@ -211,6 +231,7 @@
rule.protocol = value.protocol; rule.protocol = value.protocol;
rule.attrs = Object.fromEntries(value.attrs); rule.attrs = Object.fromEntries(value.attrs);
rule.outboundTag = value.outboundTag; rule.outboundTag = value.outboundTag;
rule.balancerTag = value.balancerTag;
for (const [key, value] of Object.entries(rule)) { for (const [key, value] of Object.entries(rule)) {
if ( if (

View file

@ -388,6 +388,7 @@
"Inbounds" = "Inbounds" "Inbounds" = "Inbounds"
"InboundsDesc" = "Accepting the specific clients." "InboundsDesc" = "Accepting the specific clients."
"Outbounds" = "Outbounds" "Outbounds" = "Outbounds"
"Balancers" = "Balancers"
"OutboundsDesc" = "Set the outgoing traffic pathway." "OutboundsDesc" = "Set the outgoing traffic pathway."
"Routings" = "Routing Rules" "Routings" = "Routing Rules"
"RoutingsDesc" = "The priority of each rule is important!" "RoutingsDesc" = "The priority of each rule is important!"
@ -406,6 +407,7 @@
"dest" = "Destination" "dest" = "Destination"
"inbound" = "Inbound" "inbound" = "Inbound"
"outbound" = "Outbound" "outbound" = "Outbound"
"balancer" = "Balancer"
"info" = "Info" "info" = "Info"
"add" = "Add Rule" "add" = "Add Rule"
"edit" = "Edit Rule" "edit" = "Edit Rule"
@ -426,6 +428,15 @@
"portal" = "Portal" "portal" = "Portal"
"intercon" = "Interconnection" "intercon" = "Interconnection"
[pages.xray.balancer]
"addBalancer" = "Add Balancer"
"editBalancer" = "Edit Balancer"
"balancerStrategy" = "Strategy"
"balancerSelectors" = "Selectors"
"tag" = "Tag"
"tagDesc" = "Unique Tag"
"balancerDesc" = "It is not possible to use balancerTag and outboundTag at the same time. If used at the same time, only outboundTag will work."
[pages.xray.wireguard] [pages.xray.wireguard]
"secretKey" = "Secret Key" "secretKey" = "Secret Key"
"publicKey" = "Public Key" "publicKey" = "Public Key"

View file

@ -388,6 +388,7 @@
"Inbounds" = "Entrante" "Inbounds" = "Entrante"
"InboundsDesc" = "Cambia la plantilla de configuración para aceptar clientes específicos." "InboundsDesc" = "Cambia la plantilla de configuración para aceptar clientes específicos."
"Outbounds" = "Salidas" "Outbounds" = "Salidas"
"Balancers" = "Equilibradores"
"OutboundsDesc" = "Cambia la plantilla de configuración para definir formas de salida para este servidor." "OutboundsDesc" = "Cambia la plantilla de configuración para definir formas de salida para este servidor."
"Routings" = "Reglas de enrutamiento" "Routings" = "Reglas de enrutamiento"
"RoutingsDesc" = "¡La prioridad de cada regla es importante!" "RoutingsDesc" = "¡La prioridad de cada regla es importante!"
@ -406,6 +407,7 @@
"dest" = "Destino" "dest" = "Destino"
"inbound" = "Entrante" "inbound" = "Entrante"
"outbound" = "saliente" "outbound" = "saliente"
"balancer" = "Balancín"
"info" = "Información" "info" = "Información"
"add" = "Agregar regla" "add" = "Agregar regla"
"edit" = "Editar regla" "edit" = "Editar regla"
@ -426,6 +428,15 @@
"portal" = "portal" "portal" = "portal"
"intercon" = "Interconexión" "intercon" = "Interconexión"
[pages.xray.balancer]
"addBalancer" = "Agregar equilibrador"
"editBalancer" = "Editar balanceador"
"balancerStrategy" = "Estrategia"
"balancerSelectors" = "Selectores"
"tag" = "Etiqueta"
"tagDesc" = "etiqueta única"
"balancerDesc" = "No es posible utilizar balancerTag y outboundTag al mismo tiempo. Si se utilizan al mismo tiempo, sólo funcionará outboundTag."
[pages.xray.wireguard] [pages.xray.wireguard]
"secretKey" = "Llave secreta" "secretKey" = "Llave secreta"
"publicKey" = "Llave pública" "publicKey" = "Llave pública"

View file

@ -388,6 +388,7 @@
"Inbounds" = "ورودی‌ها" "Inbounds" = "ورودی‌ها"
"InboundsDesc" = "پذیرش کلاینت خاص" "InboundsDesc" = "پذیرش کلاینت خاص"
"Outbounds" = "خروجی‌ها" "Outbounds" = "خروجی‌ها"
"Balancers" = "بالانسرها"
"OutboundsDesc" = "مسیر ترافیک خروجی را تنظیم کنید" "OutboundsDesc" = "مسیر ترافیک خروجی را تنظیم کنید"
"Routings" = "قوانین مسیریابی" "Routings" = "قوانین مسیریابی"
"RoutingsDesc" = "اولویت هر قانون مهم است" "RoutingsDesc" = "اولویت هر قانون مهم است"
@ -406,6 +407,7 @@
"dest" = "مقصد" "dest" = "مقصد"
"inbound" = "ورودی" "inbound" = "ورودی"
"outbound" = "خروجی" "outbound" = "خروجی"
"balancer" = "بالانسر"
"info" = "اطلاعات" "info" = "اطلاعات"
"add" = "افزودن قانون" "add" = "افزودن قانون"
"edit" = "ویرایش قانون" "edit" = "ویرایش قانون"
@ -426,6 +428,15 @@
"portal" = "پورتال" "portal" = "پورتال"
"intercon" = "اتصال میانی" "intercon" = "اتصال میانی"
[pages.xray.balancer]
"addBalancer" = "افزودن بالانسر"
"editBalancer" = "ویرایش بالانسر"
"balancerStrategy" = "استراتژی"
"balancerSelectors" = "انتخاب‌گرها"
"tag" = "برچسب"
"tagDesc" = "برچسب یگانه"
"balancerDesc" = "امکان استفاده همزمان balancerTag و outboundTag باهم وجود ندارد. درصورت استفاده همزمان فقط outboundTag عمل خواهد کرد."
[pages.xray.wireguard] [pages.xray.wireguard]
"secretKey" = "کلید شخصی" "secretKey" = "کلید شخصی"
"publicKey" = "کلید عمومی" "publicKey" = "کلید عمومی"

View file

@ -388,6 +388,7 @@
"Inbounds" = "Входящие" "Inbounds" = "Входящие"
"InboundsDesc" = "Изменение шаблона конфигурации для подключения определенных пользователей" "InboundsDesc" = "Изменение шаблона конфигурации для подключения определенных пользователей"
"Outbounds" = "Исходящие" "Outbounds" = "Исходящие"
"Balancers" = "Балансиры"
"OutboundsDesc" = "Изменение шаблона конфигурации, чтобы определить исходящие пути для этого сервера" "OutboundsDesc" = "Изменение шаблона конфигурации, чтобы определить исходящие пути для этого сервера"
"Routings" = "Правила маршрутизации" "Routings" = "Правила маршрутизации"
"RoutingsDesc" = "Важен приоритет каждого правила!" "RoutingsDesc" = "Важен приоритет каждого правила!"
@ -406,6 +407,7 @@
"dest" = "Пункт назначения" "dest" = "Пункт назначения"
"inbound" = "Входящий" "inbound" = "Входящий"
"outbound" = "Исходящий" "outbound" = "Исходящий"
"balancer" = "балансир"
"info" = "Информация" "info" = "Информация"
"add" = "Добавить правило" "add" = "Добавить правило"
"edit" = "Редактировать правило" "edit" = "Редактировать правило"
@ -426,6 +428,15 @@
"portal" = "Портал" "portal" = "Портал"
"intercon" = "Соединение" "intercon" = "Соединение"
[pages.xray.balancer]
"addBalancer" = "Добавить балансир"
"editBalancer" = "Редактировать балансир"
"balancerStrategy" = "Стратегия"
"balancerSelectors" = "Селекторы"
"tag" = "Тег"
"tagDesc" = "уникальный тег"
"balancerDesc" = "Невозможно одновременно использовать balancerTag и outboundTag. При одновременном использовании будет работать только outboundTag."
[pages.xray.wireguard] [pages.xray.wireguard]
"secretKey" = "Секретный ключ" "secretKey" = "Секретный ключ"
"publicKey" = "Открытый ключ" "publicKey" = "Открытый ключ"

View file

@ -388,6 +388,7 @@
"Inbounds" = "Đầu vào" "Inbounds" = "Đầu vào"
"InboundsDesc" = "Thay đổi mẫu cấu hình để chấp nhận các máy khách cụ thể." "InboundsDesc" = "Thay đổi mẫu cấu hình để chấp nhận các máy khách cụ thể."
"Outbounds" = "Đầu ra" "Outbounds" = "Đầu ra"
"Balancers" = "Cân bằng"
"OutboundsDesc" = "Thay đổi mẫu cấu hình để xác định các cách ra đi cho máy chủ này." "OutboundsDesc" = "Thay đổi mẫu cấu hình để xác định các cách ra đi cho máy chủ này."
"Routings" = "Quy tắc định tuyến" "Routings" = "Quy tắc định tuyến"
"RoutingsDesc" = "Mức độ ưu tiên của mỗi quy tắc đều quan trọng!" "RoutingsDesc" = "Mức độ ưu tiên của mỗi quy tắc đều quan trọng!"
@ -406,6 +407,7 @@
"dest" = "Đích" "dest" = "Đích"
"inbound" = "Vào" "inbound" = "Vào"
"outbound" = "Ra" "outbound" = "Ra"
"balancer" = "Cân bằng"
"info" = "Thông tin" "info" = "Thông tin"
"add" = "Thêm quy tắc" "add" = "Thêm quy tắc"
"edit" = "Chỉnh sửa quy tắc" "edit" = "Chỉnh sửa quy tắc"
@ -426,6 +428,15 @@
"portal" = "Cổng thông tin" "portal" = "Cổng thông tin"
"intercon" = "Kết nối" "intercon" = "Kết nối"
[pages.xray.balancer]
"addBalancer" = "Thêm cân bằng"
"editBalancer" = "Chỉnh sửa cân bằng"
"balancerStrategy" = "Chiến lược"
"balancerSelectors" = "Bộ chọn"
"tag" = "Thẻ"
"tagDesc" = "thẻ duy nhất"
"balancerDesc" = "Không thể sử dụng balancerTag và outboundTag cùng một lúc. Nếu sử dụng cùng lúc thì chỉ outboundTag mới hoạt động."
[pages.xray.wireguard] [pages.xray.wireguard]
"secretKey" = "Khoá bí mật" "secretKey" = "Khoá bí mật"
"publicKey" = "Khóa công khai" "publicKey" = "Khóa công khai"

View file

@ -388,6 +388,7 @@
"Inbounds" = "入站" "Inbounds" = "入站"
"InboundsDesc" = "更改配置模板接受特殊客户端" "InboundsDesc" = "更改配置模板接受特殊客户端"
"Outbounds" = "出站" "Outbounds" = "出站"
"Balancers" = "平衡器"
"OutboundsDesc" = "更改配置模板定义此服务器的传出方式" "OutboundsDesc" = "更改配置模板定义此服务器的传出方式"
"Routings" = "路由规则" "Routings" = "路由规则"
"RoutingsDesc" = "每条规则的优先级都很重要" "RoutingsDesc" = "每条规则的优先级都很重要"
@ -406,6 +407,7 @@
"dest" = "目的地" "dest" = "目的地"
"inbound" = "入站" "inbound" = "入站"
"outbound" = "出站" "outbound" = "出站"
"balancer" = "平衡器"
"info" = "信息" "info" = "信息"
"add" = "添加规则" "add" = "添加规则"
"edit" = "编辑规则" "edit" = "编辑规则"
@ -426,6 +428,15 @@
"portal" = "门户" "portal" = "门户"
"intercon" = "互连" "intercon" = "互连"
[pages.xray.balancer]
"addBalancer" = "添加平衡器"
"editBalancer" = "编辑平衡器"
"balancerStrategy" = "战略"
"balancerSelectors" = "选择器"
"tag" = "标签"
"tagDesc" = "唯一标记"
"balancerDesc" = "不能同时使用balancerTag和outboundTag。 如果同时使用则只有outboundTag起作用。"
[pages.xray.wireguard] [pages.xray.wireguard]
"secretKey" = "密钥" "secretKey" = "密钥"
"publicKey" = "公钥" "publicKey" = "公钥"