diff --git a/web/html/xui/form/client.html b/web/html/xui/form/client.html
index b170338c..7bac2185 100644
--- a/web/html/xui/form/client.html
+++ b/web/html/xui/form/client.html
@@ -75,7 +75,7 @@
-
+
diff --git a/web/html/xui/xray.html b/web/html/xui/xray.html
index 83553656..12b76fc4 100644
--- a/web/html/xui/xray.html
+++ b/web/html/xui/xray.html
@@ -75,7 +75,7 @@
-
+
{{ i18n "pages.xray.save" }}
{{ i18n "pages.xray.restart" }}
@@ -89,7 +89,7 @@
-
+
@@ -103,13 +103,10 @@
- { if(activeKey == 'tpl-advanced') this.changeCode(); }"
+ { this.changePage(activeKey); }"
:class="themeSwitcher.currentTheme">
-
-
- {{ i18n "pages.settings.resetDefaultConfig" }}
-
+
@@ -284,11 +281,14 @@
WARP
+
+
+ {{ i18n "pages.settings.resetDefaultConfig" }}
+
+
-
-
+
{{ i18n "pages.xray.rules.add" }}
-
+
{{ i18n
@@ -478,7 +478,7 @@
-
+
{{ i18n "pages.xray.outbound.addReverse" }}
-
-
+
{{ i18n "pages.xray.balancer.addBalancer"}}
[[ sel ]]
+
+ Observatory
+ Burst Observatory
+
+
-
+
@@ -696,6 +705,13 @@
{ 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 dnsColumns = [
{ title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
{ title: '{{ i18n "pages.xray.outbound.address"}}', align: 'center', width: 50, scopedSlots: { customRender: 'address' } },
@@ -708,13 +724,6 @@
{ title: '{{ i18n "pages.xray.fakedns.poolSize"}}', dataIndex: 'poolSize', 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({
delimiters: ['[[', ']]'],
el: '#app',
@@ -733,6 +742,7 @@
showAlert: false,
isMobile: window.innerWidth <= 768,
advSettings: 'xraySetting',
+ obsSettings: '',
cm: null,
cmOptions: {
lineNumbers: true,
@@ -827,6 +837,22 @@
],
"queryStrategy": "UseIP"
},
+ },
+ defaultObservatory: {
+ subjectSelector: [],
+ probeURL: "http://www.google.com/gen_204",
+ probeInterval: "10m",
+ enableConcurrency: true
+ },
+ defaultBurstObservatory: {
+ subjectSelector: [],
+ pingConfig: {
+ destination: "http://www.google.com/gen_204",
+ interval: "30m",
+ connectivity: "http://connectivitycheck.platform.hicloud.com/generate_204",
+ timeout: "10s",
+ sampling: 2
+ }
}
},
methods: {
@@ -927,6 +953,10 @@
this.saveBtnDisable = true;
}
},
+ changePage(pageKey) {
+ if(pageKey == 'tpl-advanced') this.changeCode();
+ if(pageKey == 'tpl-balancer') this.changeObsCode();
+ },
syncRulesWithOutbound(tag, setting) {
const newTemplateSettings = this.templateSettings;
const haveRules = newTemplateSettings.routing.rules.some((r) => r?.outboundTag === tag);
@@ -1009,6 +1039,23 @@
}
});
},
+ changeObsCode() {
+ if (this.obsSettings == ''){
+ return
+ }
+ if(this.cm != null) {
+ this.cm.toTextArea();
+ }
+ 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);
@@ -1087,123 +1134,6 @@
outbounds.splice(0, 0, outbounds.splice(index, 1)[0]);
this.outboundSettings = JSON.stringify(outbounds);
},
- async refreshOutboundTraffic() {
- if (!this.refreshing) {
- this.refreshing = true;
- await this.getOutboundsTraffic();
-
- data = []
- if (this.templateSettings != null) {
- this.templateSettings.outbounds.forEach((o, index) => {
- data.push({'key': index, ...o});
- });
- }
-
- this.outboundData = data;
- this.refreshing = false;
- }
- },
- async resetOutboundTraffic(index) {
- let tag = "-alltags-";
- if (index >= 0) {
- tag = this.outboundData[index].tag ? this.outboundData[index].tag : ""
- }
- const msg = await HttpUtil.post("/panel/xray/resetOutboundsTraffic", { tag: tag });
- if (msg.success) {
- await this.refreshOutboundTraffic();
- }
- },
- 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' || balancer.strategy === 'leastload' || balancer.strategy === 'leastping') {
- 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' || balancer.strategy === 'leastload' || balancer.strategy === 'leastping') {
- 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) {
- let 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);
-
- // Remove related routing rules
- newTemplateSettings.routing.rules.forEach((rule) => {
- if (rule.balancerTag === removedBalancer.tag) {
- delete rule.balancerTag;
- }
- });
-
- // 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;
- },
addReverse(){
reverseModal.show({
title: '{{ i18n "pages.xray.outbound.addReverse"}}',
@@ -1289,6 +1219,167 @@
this.templateSettings = newTemplateSettings;
},
+ async refreshOutboundTraffic() {
+ if (!this.refreshing) {
+ this.refreshing = true;
+ await this.getOutboundsTraffic();
+
+ data = []
+ if (this.templateSettings != null) {
+ this.templateSettings.outbounds.forEach((o, index) => {
+ data.push({'key': index, ...o});
+ });
+ }
+
+ this.outboundData = data;
+ this.refreshing = false;
+ }
+ },
+ async resetOutboundTraffic(index) {
+ let tag = "-alltags-";
+ if (index >= 0) {
+ tag = this.outboundData[index].tag ? this.outboundData[index].tag : ""
+ }
+ const msg = await HttpUtil.post("/panel/xray/resetOutboundsTraffic", { tag: tag });
+ if (msg.success) {
+ await this.refreshOutboundTraffic();
+ }
+ },
+ 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 && balancer.strategy != 'random') {
+ tmpBalancer.strategy = {
+ 'type': balancer.strategy
+ };
+ if (balancer.strategy == 'leastPing'){
+ if (!newTemplateSettings.observatory)
+ newTemplateSettings.observatory = this.defaultObservatory;
+ if (!newTemplateSettings.observatory.subjectSelector.includes(balancer.tag))
+ newTemplateSettings.observatory.subjectSelector.push(balancer.tag);
+ }
+ if (balancer.strategy == 'leastLoad'){
+ if (!newTemplateSettings.burstObservatory)
+ newTemplateSettings.burstObservatory = this.defaultBurstObservatory;
+ if (!newTemplateSettings.burstObservatory.subjectSelector.includes(balancer.tag))
+ newTemplateSettings.burstObservatory.subjectSelector.push(balancer.tag);
+ }
+ }
+ newTemplateSettings.routing.balancers.push(tmpBalancer);
+ this.templateSettings = newTemplateSettings;
+ 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
+ };
+
+ // 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
+ };
+ if (balancer.strategy == 'leastPing'){
+ if (!newTemplateSettings.observatory)
+ newTemplateSettings.observatory = this.defaultObservatory;
+ if (!newTemplateSettings.observatory.subjectSelector.includes(balancer.tag))
+ newTemplateSettings.observatory.subjectSelector.push(balancer.tag);
+ }
+ if (balancer.strategy == 'leastLoad'){
+ if (!newTemplateSettings.burstObservatory)
+ newTemplateSettings.burstObservatory = this.defaultBurstObservatory;
+ if (!newTemplateSettings.burstObservatory.subjectSelector.includes(balancer.tag))
+ newTemplateSettings.burstObservatory.subjectSelector.push(balancer.tag);
+ }
+ }
+
+ 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();
+ this.changeObsCode();
+ },
+ isEdit: true
+ });
+ },
+ deleteBalancer(index) {
+ let 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);
+
+ // Remove tag from observatory
+ if (newTemplateSettings.observatory){
+ newTemplateSettings.observatory.subjectSelector = newTemplateSettings.observatory.subjectSelector.filter(s => s != removedBalancer.tag);
+ }
+ if (newTemplateSettings.burstObservatory){
+ newTemplateSettings.burstObservatory.subjectSelector = newTemplateSettings.burstObservatory.subjectSelector.filter(s => s != removedBalancer.tag);
+ }
+
+ // Remove related routing rules
+ newTemplateSettings.routing.rules.forEach((rule) => {
+ if (rule.balancerTag === removedBalancer.tag) {
+ delete rule.balancerTag;
+ }
+ });
+
+ // 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.changeObsCode()
+ },
addDNSServer(){
dnsModal.show({
title: '{{ i18n "pages.xray.dns.add" }}',
@@ -1479,27 +1570,6 @@
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" || o.strategy.type == "leastload" || o.strategy.type == "leastping")) {
- strategy = o.strategy.type;
- }
-
- data.push({
- 'key': index,
- 'tag': o.tag ? o.tag : "",
- 'strategy': strategy,
- 'selector': o.selector ? o.selector : []
- });
- });
- }
- return data;
- }
- },
routingRuleSettings: {
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.routing.rules, null, 2) : null; },
set: function (newValue) {
@@ -1529,6 +1599,58 @@
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) => {
+ data.push({
+ 'key': index,
+ 'tag': o.tag ? o.tag : "",
+ 'strategy': o.strategy?.type ?? "random",
+ 'selector': o.selector ? o.selector : []
+ });
+ });
+ }
+ 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: {
+ get: function () { return this.templateSettings != null && this.templateSettings.observatory },
+ set: function (v) {
+ newTemplateSettings = this.templateSettings;
+ newTemplateSettings.observatory = v ? this.defaultObservatory : undefined;
+ this.templateSettings = newTemplateSettings;
+ }
+ },
+ burstObservatoryEnable: {
+ get: function () { return this.templateSettings != null && this.templateSettings.burstObservatory },
+ set: function (v) {
+ newTemplateSettings = this.templateSettings;
+ newTemplateSettings.burstObservatory = v ? this.defaultBurstObservatory : undefined;
+ this.templateSettings = newTemplateSettings;
+ }
+ },
freedomStrategy: {
get: function () {
if (!this.templateSettings) return "AsIs";