+
[[ loading ? '' : '{{ i18n "login" }}' ]]
diff --git a/web/html/xui/common_sider.html b/web/html/xui/common_sider.html
index f91b9fbb..13d5bd49 100644
--- a/web/html/xui/common_sider.html
+++ b/web/html/xui/common_sider.html
@@ -1,19 +1,19 @@
{{define "menuItems"}}
- {{ i18n "menu.dashboard"}}
+ {{ i18n "menu.dashboard"}}
- {{ i18n "menu.inbounds"}}
+ {{ i18n "menu.inbounds"}}
- {{ i18n "menu.settings"}}
+ {{ i18n "menu.settings"}}
- {{ i18n "menu.xray"}}
+ {{ i18n "menu.xray"}}
@@ -21,7 +21,7 @@
- {{ i18n "menu.logout"}}
+ {{ i18n "menu.logout"}}
{{end}}
diff --git a/web/html/xui/component/persianDatepicker.html b/web/html/xui/component/persianDatepicker.html
index 10ef8472..456d5ec6 100644
--- a/web/html/xui/component/persianDatepicker.html
+++ b/web/html/xui/component/persianDatepicker.html
@@ -5,7 +5,7 @@
@input="$emit('input', convertToGregorian($event.target.value)); jalaliDatepicker.hide();"
:placeholder="placeholder">
-
+
@@ -57,4 +57,4 @@
}
});
-{{end}}
\ No newline at end of file
+{{end}}
diff --git a/web/html/xui/form/inbound.html b/web/html/xui/form/inbound.html
index 048fc818..6f3705ff 100644
--- a/web/html/xui/form/inbound.html
+++ b/web/html/xui/form/inbound.html
@@ -54,7 +54,7 @@
-
{{template "form/sniffing"}}
-{{end}}
\ No newline at end of file
+{{end}}
diff --git a/web/html/xui/form/outbound.html b/web/html/xui/form/outbound.html
index 3f11907d..469c42b9 100644
--- a/web/html/xui/form/outbound.html
+++ b/web/html/xui/form/outbound.html
@@ -134,28 +134,10 @@
-
-
-
-
- {{ i18n "reset" }}
-
- {{ i18n "pages.xray.wireguard.publicKey" }}
-
-
-
+
-
-
-
-
- {{ i18n "reset" }}
-
- {{ i18n "pages.xray.wireguard.psk" }}
-
-
-
+
@@ -189,7 +171,6 @@
-
@@ -212,18 +193,23 @@
-
-
-
+
+
+
+
+
+
+
+
- [[ method_name ]]
+ [[ method_name ]]
-
+
@@ -363,13 +349,15 @@
-
+
None
[[ key ]]
-
[[ alpn ]]
@@ -381,11 +369,12 @@
-
+
-
+
[[ key ]]
diff --git a/web/html/xui/form/protocol/wireguard.html b/web/html/xui/form/protocol/wireguard.html
index ea2c3427..c618a770 100644
--- a/web/html/xui/form/protocol/wireguard.html
+++ b/web/html/xui/form/protocol/wireguard.html
@@ -38,10 +38,16 @@
{{ i18n "reset" }}
- {{ i18n "pages.xray.wireguard.publicKey" }}
-
+ {{ i18n "pages.xray.wireguard.secretKey" }}
+
+
+
+
+
+ {{ i18n "pages.xray.wireguard.publicKey" }}
+
@@ -51,7 +57,7 @@
{{ i18n "reset" }}
{{ i18n "pages.xray.wireguard.psk" }}
-
+
diff --git a/web/html/xui/form/stream/external_proxy.html b/web/html/xui/form/stream/external_proxy.html
index 2a072df9..9c3ed2e0 100644
--- a/web/html/xui/form/stream/external_proxy.html
+++ b/web/html/xui/form/stream/external_proxy.html
@@ -20,7 +20,7 @@
- -
+ -
{{end}}
diff --git a/web/html/xui/form/stream/stream_tcp.html b/web/html/xui/form/stream/stream_tcp.html
index 19a09ac3..8576df8c 100644
--- a/web/html/xui/form/stream/stream_tcp.html
+++ b/web/html/xui/form/stream/stream_tcp.html
@@ -33,7 +33,7 @@
- +
+ +
@@ -79,4 +79,4 @@
-{{end}}
\ No newline at end of file
+{{end}}
diff --git a/web/html/xui/form/stream/stream_ws.html b/web/html/xui/form/stream/stream_ws.html
index 00b64167..62380e94 100644
--- a/web/html/xui/form/stream/stream_ws.html
+++ b/web/html/xui/form/stream/stream_ws.html
@@ -7,7 +7,7 @@
- +
+ +
diff --git a/web/html/xui/inbound_info_modal.html b/web/html/xui/inbound_info_modal.html
index cdcabad9..23f8bd47 100644
--- a/web/html/xui/inbound_info_modal.html
+++ b/web/html/xui/inbound_info_modal.html
@@ -179,10 +179,10 @@
Telegram ID
- @[[ infoModal.clientSettings.tgId ]]
+ [[ infoModal.clientSettings.tgId ]]
-
@@ -283,24 +283,50 @@
- Peer [[ index + 1 ]] |
+ Peer [[ index + 1 ]] |
+ {{ i18n "pages.xray.wireguard.secretKey" }} |
+ [[ peer.privateKey ]] |
+
+
{{ i18n "pages.xray.wireguard.publicKey" }} |
[[ peer.publicKey ]] |
-
+
{{ i18n "pages.xray.wireguard.psk" }} |
[[ peer.psk ]] |
-
+
{{ i18n "pages.xray.wireguard.allowedIPs" }} |
[[ peer.allowedIPs.join(",") ]] |
-
+
Keep Alive |
[[ peer.keepAlive ]] |
+
+
+
+
+ Config
+
+
+
+
+
+
+
+
+
+
+ |
+
@@ -319,7 +345,6 @@
index: null,
isExpired: false,
subLink: '',
- tgLink: '',
show(dbInbound, index) {
this.index = index;
this.inbound = dbInbound.toInbound();
@@ -327,14 +352,15 @@
this.clientSettings = this.inbound.clients ? this.inbound.clients[index] : null;
this.isExpired = this.inbound.clients ? this.inbound.isExpiry(index): this.dbInbound.isExpiry;
this.clientStats = this.inbound.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : [];
- this.links = this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, this.clientSettings);
+ if (this.inbound.protocol == Protocols.WIREGUARD){
+ this.links = this.inbound.genInboundLinks(dbInbound.remark).split('\r\n')
+ } else {
+ this.links = this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, this.clientSettings);
+ }
if (this.clientSettings) {
if (this.clientSettings.subId) {
this.subLink = this.genSubLink(this.clientSettings.subId);
}
- if (this.clientSettings.tgId) {
- this.tgLink = "https://t.me/" + this.clientSettings.tgId;
- }
}
this.visible = true;
},
diff --git a/web/html/xui/inbounds.html b/web/html/xui/inbounds.html
index c986e3fd..93f25730 100644
--- a/web/html/xui/inbounds.html
+++ b/web/html/xui/inbounds.html
@@ -133,6 +133,10 @@
{{ i18n "pages.inbounds.export" }}
+
+
+ {{ i18n "pages.inbounds.export" }} - {{ i18n "pages.settings.subSettings" }}
+
{{ i18n "pages.inbounds.resetAllTraffic" }}
@@ -141,7 +145,7 @@
{{ i18n "pages.inbounds.resetAllClientTraffics" }}
-
+
{{ i18n "pages.inbounds.delDepletedClients" }}
@@ -196,7 +200,7 @@
{{ i18n "edit" }}
-
+
{{ i18n "qrCode" }}
@@ -217,7 +221,11 @@
{{ i18n "pages.inbounds.export"}}
-
+
+
+ {{ i18n "pages.inbounds.export"}} - {{ i18n "pages.settings.subSettings" }}
+
+
{{ i18n "pages.inbounds.delDepletedClients" }}
@@ -578,6 +586,7 @@
this.refreshing = false;
return;
}
+
await this.getOnlineUsers();
this.setInbounds(msg.obj);
setTimeout(() => {
@@ -642,8 +651,12 @@
clientCount = clients.length;
if (dbInbound.enable) {
clients.forEach(client => {
- client.enable ? active.push(client.email) : deactive.push(client.email);
- if(this.isClientOnline(client.email)) online.push(client.email);
+ if (client.enable && this.isClientOnline(client.email)) {
+ active.push(client.email);
+ online.push(client.email);
+ } else {
+ deactive.push(client.email);
+ }
});
clientStats.forEach(client => {
if (!client.enable) {
@@ -668,6 +681,7 @@
online: online,
};
},
+
searchInbounds(key) {
if (ObjectUtil.isEmpty(key)) {
this.searchedInbounds = this.dbInbounds.slice();
@@ -731,6 +745,9 @@
case "export":
this.exportAllLinks();
break;
+ case "subs":
+ this.exportAllSubs();
+ break;
case "resetInbounds":
this.resetAllTraffic();
break;
@@ -762,6 +779,9 @@
case "export":
this.inboundLinks(dbInbound.id);
break;
+ case "subs":
+ this.exportSubs(dbInbound.id);
+ break;
case "clipboard":
this.copyToClipboard(dbInbound.id);
break;
@@ -1186,6 +1206,22 @@
newDbInbound = this.checkFallback(dbInbound);
txtModal.show('{{ i18n "pages.inbounds.export"}}', newDbInbound.genInboundLinks(), newDbInbound.remark);
},
+ exportSubs(dbInboundId) {
+ const dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
+ const clients = this.getInboundClients(dbInbound);
+ let subLinks = []
+ if (clients != null){
+ clients.forEach(c => {
+ if (c.subId && c.subId.length>0){
+ subLinks.push(this.subSettings.subURI + c.subId + "?name=" + c.subId)
+ }
+ })
+ }
+ txtModal.show(
+ '{{ i18n "pages.inbounds.export"}} - {{ i18n "pages.settings.subSettings" }}',
+ [...new Set(subLinks)].join('\n'),
+ dbInbound.remark + "-Subs");
+ },
importInbound() {
promptModal.open({
title: '{{ i18n "pages.inbounds.importInbound" }}',
@@ -1198,6 +1234,23 @@
},
});
},
+ exportAllSubs() {
+ let subLinks = []
+ for (const dbInbound of this.dbInbounds) {
+ const clients = this.getInboundClients(dbInbound);
+ if (clients != null){
+ clients.forEach(c => {
+ if (c.subId && c.subId.length>0){
+ subLinks.push(this.subSettings.subURI + c.subId + "?name=" + c.subId)
+ }
+ })
+ }
+ }
+ txtModal.show(
+ '{{ i18n "pages.inbounds.export"}} - {{ i18n "pages.settings.subSettings" }}',
+ [...new Set(subLinks)].join('\r\n'),
+ 'All-Inbounds-Subs');
+ },
exportAllLinks() {
let copyText = [];
for (const dbInbound of this.dbInbounds) {
diff --git a/web/html/xui/index.html b/web/html/xui/index.html
index 258d4154..abd3b8d0 100644
--- a/web/html/xui/index.html
+++ b/web/html/xui/index.html
@@ -18,6 +18,14 @@
.ant-card-dark h2 {
color: hsla(0, 0%, 100%, .65);
}
+
+ .ant-tag-df {
+ color: rgb(0 0 0 / 80%);
+ }
+
+ .dark .ant-tag-df {
+ color: rgb(255 255 255 / 80%);
+ }
@@ -36,15 +44,15 @@
- CPU: [[ cpuCoreFormat(status.cpuCores) ]]
- Speed: [[ cpuSpeedFormat(status.cpuSpeedMhz) ]]
+ CPU: [[ cpuCoreFormat(status.cpuCores) ]]
+ Speed: [[ cpuSpeedFormat(status.cpuSpeedMhz) ]]
- {{ i18n "pages.index.memory"}}: [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]]
+ {{ i18n "pages.index.memory"}}: [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]]
@@ -56,7 +64,7 @@
:stroke-color="status.swap.color"
:percent="status.swap.percent">
- Swap: [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]]
+ Swap: [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]]
@@ -64,7 +72,7 @@
:stroke-color="status.disk.color"
:percent="status.disk.percent">
- {{ i18n "pages.index.hard"}}: [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]]
+ {{ i18n "pages.index.hard"}}: [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]]
@@ -75,25 +83,25 @@
-
+
- 3X-UI v{{ .cur_ver }}
- Xray v[[ status.xray.version ]]
- @panel3xui
+ 3X-UI:
+ v{{ .cur_ver }}
+ @Panel3xui
-
+
- {{ i18n "menu.link" }}:
- {{ i18n "pages.index.logs" }}
- {{ i18n "pages.index.config" }}
- {{ i18n "pages.index.backup" }}
+ {{ i18n "pages.index.operationHours" }}:
+ Xray [[ formatSecond(status.appStats.uptime) ]]
+ OS [[ formatSecond(status.uptime) ]]
-
+
- {{ i18n "pages.index.xrayStatus" }}:
- [[ status.xray.state ]]
+ {{ i18n "pages.index.xrayStatus" }}:
+ [[ status.xray.state ]]
+
An error occurred while running Xray
@@ -106,137 +114,143 @@
{{ i18n "pages.index.stopXray" }}
{{ i18n "pages.index.restartXray" }}
- {{ i18n "pages.index.xraySwitch" }}
+ v[[ status.xray.version ]]
-
+
- {{ i18n "pages.index.operationHours" }}:
- Xray
- [[ formatSecond(status.appStats.uptime) ]]
- OS
- [[ formatSecond(status.uptime) ]]
+ {{ i18n "menu.link" }}:
+ {{ i18n "pages.index.logs" }}
+ {{ i18n "pages.index.config" }}
+ {{ i18n "pages.index.backup" }}
-
+
- {{ i18n "pages.index.systemLoad" }}: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
+ {{ i18n "pages.index.systemLoad" }}:
+
+ [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
{{ i18n "pages.index.systemLoadDesc" }}
-
+
-
+
- {{ i18n "usage"}}:
- RAM [[ sizeFormat(status.appStats.mem) ]] -
- Threads [[ status.appStats.threads ]]
-
+ {{ i18n "usage"}}:
+
+ RAM [[ sizeFormat(status.appStats.mem) ]]
+
+
+ Threads [[ status.appStats.threads ]]
+
-
+
-
- IPv4:
+
+ IPv4
[[ status.publicIP.ipv4 ]]
-
-
-
-
- IPv6:
+
+
+
+
+ IPv6
[[ status.publicIP.ipv6 ]]
-
+
-
+
-
- TCP: [[ status.tcpCount ]]
+
+ TCP: [[ status.tcpCount ]]
{{ i18n "pages.index.connectionTcpCountDesc" }}
-
+
-
- UDP: [[ status.udpCount ]]
+
+ UDP: [[ status.udpCount ]]
{{ i18n "pages.index.connectionUdpCountDesc" }}
-
+
-
+
-
- [[ sizeFormat(status.netIO.up) ]]/s
+
+
+ Up: [[ sizeFormat(status.netIO.up) ]]/s
{{ i18n "pages.index.upSpeed" }}
-
+
-
- [[ sizeFormat(status.netIO.down) ]]/s
+
+
+ Down: [[ sizeFormat(status.netIO.down) ]]/s
{{ i18n "pages.index.downSpeed" }}
-
+
-
+
-
- [[ sizeFormat(status.netTraffic.sent) ]]
+
+
{{ i18n "pages.index.totalSent" }}
-
-
+ Out: [[ sizeFormat(status.netTraffic.sent) ]]
+
-
- [[ sizeFormat(status.netTraffic.recv) ]]
+
+
{{ i18n "pages.index.totalReceive" }}
-
-
+ In: [[ sizeFormat(status.netTraffic.recv) ]]
+
@@ -256,7 +270,7 @@
>
+ style="margin-right: 10px" @click="switchV2rayVersion(version)">
[[ version ]]
@@ -440,8 +454,8 @@
loading: false,
show(logs) {
this.visible = true;
- this.logs = logs;
- this.formattedLogs = logs.length > 0 ? this.formatLogs(logs) : "No Record...";
+ this.logs = logs || [];
+ this.formattedLogs = this.logs.length > 0 ? this.formatLogs(this.logs) : "No Record...";
},
formatLogs(logs) {
let formattedLogs = '';
diff --git a/web/html/xui/settings.html b/web/html/xui/settings.html
index 0acbbfec..533553c5 100644
--- a/web/html/xui/settings.html
+++ b/web/html/xui/settings.html
@@ -76,15 +76,15 @@
-
-
-
+
+
+
{{ i18n "pages.settings.save" }}
{{ i18n "pages.settings.restartPanel" }}
-
+
diff --git a/web/html/xui/xray.html b/web/html/xui/xray.html
index 267103cb..3e6cf851 100644
--- a/web/html/xui/xray.html
+++ b/web/html/xui/xray.html
@@ -114,15 +114,12 @@
-
+
-
[[ s ]]
@@ -132,33 +129,37 @@
-
+
-
[[ s ]]
+
+
+
+
+
+
+
+ {{ i18n "pages.xray.logConfigsDesc" }}
+
+
+
-
+
-
+
[[ s ]]
@@ -166,22 +167,31 @@
-
+
-
+
[[ s ]]
-
+
+
+
+
+
+
+
+ [[ s ]]
+
+
+
+
+
@@ -327,6 +337,14 @@
[[ rule.outboundTag ]]
+
+
+
+ Balancer Tag: [[ rule.balancerTag ]]
+
+ [[ rule.balancerTag ]]
+
+
- {{ i18n "pages.xray.outbound.addOutbound" }}
+ {{ i18n
+ "pages.xray.outbound.addOutbound" }}
WARP
-
+
+
e.preventDefault()" type="more" style="font-size: 16px; text-decoration: bold;">
+
+
+ {{ i18n "pages.xray.rules.first"}}
+
{{ i18n "edit" }}
+
+
+ {{ i18n "pages.inbounds.resetTraffic"}}
+
+
{{ i18n "delete"}}
@@ -452,6 +481,41 @@
+
+ {{ i18n "pages.xray.balancer.addBalancer"}}
+
+
+ [[ index+1 ]]
+
+ e.preventDefault()" type="more" style="font-size: 16px; text-decoration: bold;">
+
+
+
+ {{ i18n "edit" }}
+
+
+
+ {{ i18n "delete"}}
+
+
+
+
+
+
+ Random
+ Round Robin
+
+
+ [[ sel ]]
+
+
+
@@ -474,6 +538,7 @@
{{template "ruleModal"}}
{{template "outModal"}}
{{template "reverseModal"}}
+{{template "balancerModal"}}
{{template "warpModal"}}
+{{end}}
\ No newline at end of file
diff --git a/web/html/xui/xray_rule_modal.html b/web/html/xui/xray_rule_modal.html
index 9ed9e06a..07cc3217 100644
--- a/web/html/xui/xray_rule_modal.html
+++ b/web/html/xui/xray_rule_modal.html
@@ -107,6 +107,19 @@
[[ tag ]]
+
+
+
+
+ {{ i18n "pages.xray.balancer.balancerDesc" }}
+
+ Balancer Tag
+
+
+
+ [[ tag ]]
+
+
@@ -133,11 +146,12 @@
protocol: [],
attrs: [],
outboundTag: "",
+ balancerTag: "",
},
inboundTags: [],
outboundTags: [],
users: [],
- balancerTag: [],
+ balancerTags: [],
ok() {
newRule = ruleModal.getResult();
ObjectUtil.execute(ruleModal.confirm, newRule);
@@ -160,6 +174,7 @@
this.rule.protocol = rule.protocol;
this.rule.attrs = rule.attrs ? Object.entries(rule.attrs) : [];
this.rule.outboundTag = rule.outboundTag;
+ this.rule.balancerTag = rule.balancerTag ? rule.balancerTag : ""
} else {
this.rule = {
domainMatcher: "",
@@ -174,6 +189,7 @@
protocol: [],
attrs: [],
outboundTag: "",
+ balancerTag: "",
}
}
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.routing && app.templateSettings.routing.balancers) {
+ this.balancerTags = app.templateSettings.routing.balancers.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag)
+ }
},
close() {
ruleModal.visible = false;
@@ -211,6 +231,7 @@
rule.protocol = value.protocol;
rule.attrs = Object.fromEntries(value.attrs);
rule.outboundTag = value.outboundTag;
+ rule.balancerTag = value.balancerTag;
for (const [key, value] of Object.entries(rule)) {
if (
diff --git a/web/job/check_client_ip_job.go b/web/job/check_client_ip_job.go
index 905a8cc7..d48bc8eb 100644
--- a/web/job/check_client_ip_job.go
+++ b/web/job/check_client_ip_job.go
@@ -25,7 +25,6 @@ type CheckClientIpJob struct {
var job *CheckClientIpJob
var ipFiles = []string{
xray.GetIPLimitLogPath(),
- xray.GetIPLimitPrevLogPath(),
xray.GetIPLimitBannedLogPath(),
xray.GetIPLimitBannedPrevLogPath(),
xray.GetAccessPersistentLogPath(),
@@ -51,6 +50,37 @@ func (j *CheckClientIpJob) Run() {
j.checkFail2BanInstalled()
j.processLogFile()
}
+
+ if !j.hasLimitIp() && xray.GetAccessLogPath() == "./access.log" {
+ go j.clearLogTime()
+ }
+}
+
+func (j *CheckClientIpJob) clearLogTime() {
+ for {
+ time.Sleep(time.Hour)
+ j.clearAccessLog()
+ }
+}
+
+func (j *CheckClientIpJob) clearAccessLog() {
+ accessLogPath := xray.GetAccessLogPath()
+ logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
+ j.checkError(err)
+ defer logAccessP.Close()
+
+ // reopen the access log file for reading
+ file, err := os.Open(accessLogPath)
+ j.checkError(err)
+ defer file.Close()
+
+ // copy access log content to persistent file
+ _, err = io.Copy(logAccessP, file)
+ j.checkError(err)
+
+ // clean access log
+ err = os.Truncate(accessLogPath, 0)
+ j.checkError(err)
}
func (j *CheckClientIpJob) hasLimitIp() bool {
@@ -121,7 +151,7 @@ func (j *CheckClientIpJob) processLogFile() {
matches := ipRegx.FindStringSubmatch(line)
if len(matches) > 1 {
ip := matches[1]
- if ip == "127.0.0.1" || ip == "[::1]" {
+ if ip == "127.0.0.1" {
continue
}
@@ -160,24 +190,7 @@ func (j *CheckClientIpJob) processLogFile() {
time.Sleep(time.Second * 2)
if shouldCleanLog {
- // copy access log to persistent file
- logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
- j.checkError(err)
- defer logAccessP.Close()
-
- // reopen the access log file for reading
- file, err := os.Open(accessLogPath)
- j.checkError(err)
- defer file.Close()
-
- // copy access log content to persistent file
- _, err = io.Copy(logAccessP, file)
- j.checkError(err)
-
- // clean access log
- if err := os.Truncate(xray.GetAccessLogPath(), 0); err != nil {
- j.checkError(err)
- }
+ j.clearAccessLog()
}
}
diff --git a/web/job/clear_logs_job.go b/web/job/clear_logs_job.go
index 5ceb5a75..c6312006 100644
--- a/web/job/clear_logs_job.go
+++ b/web/job/clear_logs_job.go
@@ -15,8 +15,8 @@ func NewClearLogsJob() *ClearLogsJob {
// Here Run is an interface method of the Job interface
func (j *ClearLogsJob) Run() {
logFiles := []string{xray.GetIPLimitLogPath(), xray.GetIPLimitBannedLogPath(), xray.GetAccessPersistentLogPath()}
- logFilesPrev := []string{xray.GetIPLimitPrevLogPath(), xray.GetIPLimitBannedPrevLogPath(), xray.GetAccessPersistentPrevLogPath()}
-
+ logFilesPrev := []string{xray.GetIPLimitBannedPrevLogPath(), xray.GetAccessPersistentPrevLogPath()}
+
// clear old previous logs
for i := 0; i < len(logFilesPrev); i++ {
if err := os.Truncate(logFilesPrev[i], 0); err != nil {
@@ -26,25 +26,26 @@ func (j *ClearLogsJob) Run() {
// clear log files and copy to previous logs
for i := 0; i < len(logFiles); i++ {
-
- // copy to previous logs
- logFilePrev, err := os.OpenFile(logFilesPrev[i], os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
- if err != nil {
- logger.Warning("clear logs job err:", err)
+ if i > 0 {
+ // copy to previous logs
+ logFilePrev, err := os.OpenFile(logFilesPrev[i-1], os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
+ if err != nil {
+ logger.Warning("clear logs job err:", err)
+ }
+
+ logFile, err := os.ReadFile(logFiles[i])
+ if err != nil {
+ logger.Warning("clear logs job err:", err)
+ }
+
+ _, err = logFilePrev.Write(logFile)
+ if err != nil {
+ logger.Warning("clear logs job err:", err)
+ }
+ defer logFilePrev.Close()
}
- logFile, err := os.ReadFile(logFiles[i])
- if err != nil {
- logger.Warning("clear logs job err:", err)
- }
-
- _, err = logFilePrev.Write(logFile)
- if err != nil {
- logger.Warning("clear logs job err:", err)
- }
- defer logFilePrev.Close()
-
- err = os.Truncate(logFiles[i], 0)
+ err := os.Truncate(logFiles[i], 0)
if err != nil {
logger.Warning("clear logs job err:", err)
}
diff --git a/web/service/config.json b/web/service/config.json
index 6cf6c3a6..bd90e5c8 100644
--- a/web/service/config.json
+++ b/web/service/config.json
@@ -2,6 +2,7 @@
"log": {
"access": "none",
"dnsLog": false,
+ "error": "./error.log",
"loglevel": "warning"
},
"api": {
diff --git a/web/service/outbound.go b/web/service/outbound.go
index dc0e0742..05791f69 100644
--- a/web/service/outbound.go
+++ b/web/service/outbound.go
@@ -10,7 +10,6 @@ import (
)
type OutboundService struct {
- xrayApi xray.XrayAPI
}
func (s *OutboundService) AddTraffic(traffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) {
@@ -78,3 +77,25 @@ func (s *OutboundService) GetOutboundsTraffic() ([]*model.OutboundTraffics, erro
return traffics, nil
}
+
+func (s *OutboundService) ResetOutboundTraffic(tag string) error {
+ db := database.GetDB()
+
+ whereText := "tag "
+ if tag == "-alltags-" {
+ whereText += " <> ?"
+ } else {
+ whereText += " = ?"
+ }
+
+ result := db.Model(model.OutboundTraffics{}).
+ Where(whereText, tag).
+ Updates(map[string]interface{}{"up": 0, "down": 0, "total": 0})
+
+ err := result.Error
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/web/service/tgbot.go b/web/service/tgbot.go
index 7bd6a179..87445a9a 100644
--- a/web/service/tgbot.go
+++ b/web/service/tgbot.go
@@ -202,9 +202,13 @@ func (t *Tgbot) OnReceive() {
}, th.AnyCallbackQueryWithMessage())
botHandler.HandleMessage(func(_ *telego.Bot, message telego.Message) {
- if message.UserShared != nil {
+ if message.UsersShared != nil {
if checkAdmin(message.From.ID) {
- err := t.inboundService.SetClientTelegramUserID(message.UserShared.RequestID, strconv.FormatInt(message.UserShared.UserID, 10))
+ userIDsStr := ""
+ for _, userID := range message.UsersShared.UserIDs {
+ userIDsStr += strconv.FormatInt(userID, 10) + " "
+ }
+ err := t.inboundService.SetClientTelegramUserID(message.UsersShared.RequestID, userIDsStr)
output := ""
if err != nil {
output += t.I18nBot("tgbot.messages.selectUserFailed")
@@ -277,7 +281,7 @@ func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin boo
func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool) {
- chatId := callbackQuery.Message.Chat.ID
+ chatId := callbackQuery.Message.GetChat().ID
if isAdmin {
// get query from hash storage
@@ -296,22 +300,22 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
t.searchClient(chatId, email)
case "client_refresh":
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.clientRefreshSuccess", "Email=="+email))
- t.searchClient(chatId, email, callbackQuery.Message.MessageID)
+ t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
case "client_cancel":
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+email))
- t.searchClient(chatId, email, callbackQuery.Message.MessageID)
+ t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
case "ips_refresh":
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.IpRefreshSuccess", "Email=="+email))
- t.searchClientIps(chatId, email, callbackQuery.Message.MessageID)
+ t.searchClientIps(chatId, email, callbackQuery.Message.GetMessageID())
case "ips_cancel":
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+email))
- t.searchClientIps(chatId, email, callbackQuery.Message.MessageID)
+ t.searchClientIps(chatId, email, callbackQuery.Message.GetMessageID())
case "tgid_refresh":
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.TGIdRefreshSuccess", "Email=="+email))
- t.clientTelegramUserInfo(chatId, email, callbackQuery.Message.MessageID)
+ t.clientTelegramUserInfo(chatId, email, callbackQuery.Message.GetMessageID())
case "tgid_cancel":
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+email))
- t.clientTelegramUserInfo(chatId, email, callbackQuery.Message.MessageID)
+ t.clientTelegramUserInfo(chatId, email, callbackQuery.Message.GetMessageID())
case "reset_traffic":
inlineKeyboard := tu.InlineKeyboard(
tu.InlineKeyboardRow(
@@ -321,13 +325,13 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmResetTraffic")).WithCallbackData(t.encodeQuery("reset_traffic_c "+email)),
),
)
- t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard)
+ t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
case "reset_traffic_c":
err := t.inboundService.ResetClientTrafficByEmail(email)
if err == nil {
t.xrayService.SetToNeedRestart()
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.resetTrafficSuccess", "Email=="+email))
- t.searchClient(chatId, email, callbackQuery.Message.MessageID)
+ t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
} else {
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
}
@@ -361,7 +365,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
tu.InlineKeyboardButton("200 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 200")),
),
)
- t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard)
+ t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
case "limit_traffic_c":
if len(dataArray) == 3 {
limitTraffic, err := strconv.Atoi(dataArray[2])
@@ -370,13 +374,13 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
if err == nil {
t.xrayService.SetToNeedRestart()
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.setTrafficLimitSuccess", "Email=="+email))
- t.searchClient(chatId, email, callbackQuery.Message.MessageID)
+ t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
return
}
}
}
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
- t.searchClient(chatId, email, callbackQuery.Message.MessageID)
+ t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
case "limit_traffic_in":
if len(dataArray) >= 3 {
oldInputNumber, err := strconv.Atoi(dataArray[2])
@@ -432,12 +436,12 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
tu.InlineKeyboardButton("⬅️").WithCallbackData(t.encodeQuery("limit_traffic_in "+email+" "+strconv.Itoa(inputNumber)+" -1")),
),
)
- t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard)
+ t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
return
}
}
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
- t.searchClient(chatId, email, callbackQuery.Message.MessageID)
+ t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
case "reset_exp":
inlineKeyboard := tu.InlineKeyboard(
tu.InlineKeyboardRow(
@@ -464,7 +468,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 12 "+t.I18nBot("tgbot.months")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 365")),
),
)
- t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard)
+ t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
case "reset_exp_c":
if len(dataArray) == 3 {
days, err := strconv.Atoi(dataArray[2])
@@ -499,13 +503,13 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
if err == nil {
t.xrayService.SetToNeedRestart()
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.expireResetSuccess", "Email=="+email))
- t.searchClient(chatId, email, callbackQuery.Message.MessageID)
+ t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
return
}
}
}
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
- t.searchClient(chatId, email, callbackQuery.Message.MessageID)
+ t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
case "reset_exp_in":
if len(dataArray) >= 3 {
oldInputNumber, err := strconv.Atoi(dataArray[2])
@@ -561,12 +565,12 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
tu.InlineKeyboardButton("⬅️").WithCallbackData(t.encodeQuery("reset_exp_in "+email+" "+strconv.Itoa(inputNumber)+" -1")),
),
)
- t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard)
+ t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
return
}
}
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
- t.searchClient(chatId, email, callbackQuery.Message.MessageID)
+ t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
case "ip_limit":
inlineKeyboard := tu.InlineKeyboard(
tu.InlineKeyboardRow(
@@ -595,7 +599,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
tu.InlineKeyboardButton("10").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 10")),
),
)
- t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard)
+ t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
case "ip_limit_c":
if len(dataArray) == 3 {
count, err := strconv.Atoi(dataArray[2])
@@ -604,13 +608,13 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
if err == nil {
t.xrayService.SetToNeedRestart()
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.resetIpSuccess", "Email=="+email, "Count=="+strconv.Itoa(count)))
- t.searchClient(chatId, email, callbackQuery.Message.MessageID)
+ t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
return
}
}
}
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
- t.searchClient(chatId, email, callbackQuery.Message.MessageID)
+ t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
case "ip_limit_in":
if len(dataArray) >= 3 {
oldInputNumber, err := strconv.Atoi(dataArray[2])
@@ -666,12 +670,12 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
tu.InlineKeyboardButton("⬅️").WithCallbackData(t.encodeQuery("ip_limit_in "+email+" "+strconv.Itoa(inputNumber)+" -1")),
),
)
- t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard)
+ t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
return
}
}
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
- t.searchClient(chatId, email, callbackQuery.Message.MessageID)
+ t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
case "clear_ips":
inlineKeyboard := tu.InlineKeyboard(
tu.InlineKeyboardRow(
@@ -681,12 +685,12 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmClearIps")).WithCallbackData(t.encodeQuery("clear_ips_c "+email)),
),
)
- t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard)
+ t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
case "clear_ips_c":
err := t.inboundService.ClearClientIps(email)
if err == nil {
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.clearIpSuccess", "Email=="+email))
- t.searchClientIps(chatId, email, callbackQuery.Message.MessageID)
+ t.searchClientIps(chatId, email, callbackQuery.Message.GetMessageID())
} else {
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
}
@@ -705,7 +709,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmRemoveTGUser")).WithCallbackData(t.encodeQuery("tgid_remove_c "+email)),
),
)
- t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard)
+ t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
case "tgid_remove_c":
traffic, err := t.inboundService.GetClientTrafficByEmail(email)
if err != nil || traffic == nil {
@@ -715,7 +719,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
err = t.inboundService.SetClientTelegramUserID(traffic.Id, "")
if err == nil {
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.removedTGUserSuccess", "Email=="+email))
- t.clientTelegramUserInfo(chatId, email, callbackQuery.Message.MessageID)
+ t.clientTelegramUserInfo(chatId, email, callbackQuery.Message.GetMessageID())
} else {
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
}
@@ -728,7 +732,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmToggle")).WithCallbackData(t.encodeQuery("toggle_enable_c "+email)),
),
)
- t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard)
+ t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
case "toggle_enable_c":
enabled, err := t.inboundService.ToggleClientEnableByEmail(email)
if err == nil {
@@ -738,7 +742,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
} else {
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.disableSuccess", "Email=="+email))
}
- t.searchClient(chatId, email, callbackQuery.Message.MessageID)
+ t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
} else {
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
}
@@ -774,7 +778,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
t.onlineClients(chatId)
case "onlines_refresh":
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
- t.onlineClients(chatId, callbackQuery.Message.MessageID)
+ t.onlineClients(chatId, callbackQuery.Message.GetMessageID())
case "commands":
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.commands"))
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.commands.helpAdminCommands"))
@@ -1215,13 +1219,13 @@ func (t *Tgbot) clientTelegramUserInfo(chatId int64, email string, messageID ...
t.editMessageTgBot(chatId, messageID[0], output, inlineKeyboard)
} else {
t.SendMsgToTgbot(chatId, output, inlineKeyboard)
- requestUser := telego.KeyboardButtonRequestUser{
+ requestUser := telego.KeyboardButtonRequestUsers{
RequestID: int32(traffic.Id),
UserIsBot: new(bool),
}
keyboard := tu.Keyboard(
tu.KeyboardRow(
- tu.KeyboardButton(t.I18nBot("tgbot.buttons.selectTGUser")).WithRequestUser(&requestUser),
+ tu.KeyboardButton(t.I18nBot("tgbot.buttons.selectTGUser")).WithRequestUsers(&requestUser),
),
tu.KeyboardRow(
tu.KeyboardButton(t.I18nBot("tgbot.buttons.closeKeyboard")),
diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml
index 78c545f6..5e1afa9b 100644
--- a/web/translation/translate.en_US.toml
+++ b/web/translation/translate.en_US.toml
@@ -76,7 +76,7 @@
"title" = "Overview"
"memory" = "RAM"
"hard" = "Disk"
-"xrayStatus" = "Status"
+"xrayStatus" = "Xray"
"stopXray" = "Stop"
"restartXray" = "Restart"
"xraySwitch" = "Version"
@@ -311,6 +311,8 @@
"advancedTemplate" = "Advanced"
"generalConfigs" = "General"
"generalConfigsDesc" = "These options will determine general adjustments."
+"logConfigs" = "Log"
+"logConfigsDesc" = "Logs may affect your server's efficiency. It is recommended to enable it wisely only in case of your needs"
"blockConfigs" = "Protection Shield"
"blockConfigsDesc" = "These options will block traffic based on specific requested protocols and websites."
"blockCountryConfigs" = "Block Country"
@@ -388,6 +390,7 @@
"Inbounds" = "Inbounds"
"InboundsDesc" = "Accepting the specific clients."
"Outbounds" = "Outbounds"
+"Balancers" = "Balancers"
"OutboundsDesc" = "Set the outgoing traffic pathway."
"Routings" = "Routing Rules"
"RoutingsDesc" = "The priority of each rule is important!"
@@ -396,6 +399,8 @@
"logLevelDesc" = "The log level for error logs, indicating the information that needs to be recorded."
"accessLog" = "Access Log"
"accessLogDesc" = "The file path for the access log. The special value 'none' disabled access logs"
+"errorLog" = "Error Log"
+"errorLogDesc" = "The file path for the error log. The special value 'none' disabled error logs"
[pages.xray.rules]
"first" = "First"
@@ -406,6 +411,7 @@
"dest" = "Destination"
"inbound" = "Inbound"
"outbound" = "Outbound"
+"balancer" = "Balancer"
"info" = "Info"
"add" = "Add Rule"
"edit" = "Edit Rule"
@@ -426,6 +432,15 @@
"portal" = "Portal"
"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]
"secretKey" = "Secret Key"
"publicKey" = "Public Key"
diff --git a/web/translation/translate.es_ES.toml b/web/translation/translate.es_ES.toml
index b21fe252..80d162eb 100644
--- a/web/translation/translate.es_ES.toml
+++ b/web/translation/translate.es_ES.toml
@@ -20,7 +20,7 @@
"check" = "Verificar"
"indefinite" = "Indefinido"
"unlimited" = "Ilimitado"
-"none" = "Ninguno"
+"none" = "None"
"qrCode" = "Código QR"
"info" = "Más Información"
"edit" = "Editar"
@@ -76,7 +76,7 @@
"title" = "Estado del Sistema"
"memory" = "Memoria"
"hard" = "Disco Duro"
-"xrayStatus" = "Estado de"
+"xrayStatus" = "Xray"
"stopXray" = "Detener"
"restartXray" = "Reiniciar"
"xraySwitch" = "Versión"
@@ -311,6 +311,8 @@
"advancedTemplate" = "Plantilla Avanzada"
"generalConfigs" = "Configuraciones Generales"
"generalConfigsDesc" = "Estas opciones proporcionarán ajustes generales."
+"logConfigs" = "Registro"
+"logConfigsDesc" = "Los registros pueden afectar la eficiencia de su servidor. Se recomienda habilitarlo sabiamente solo en caso de sus necesidades."
"blockConfigs" = "Configuraciones de Bloqueo"
"blockConfigsDesc" = "Estas opciones evitarán que los usuarios se conecten a protocolos y sitios web específicos."
"blockCountryConfigs" = "Configuraciones de Bloqueo por País"
@@ -388,6 +390,7 @@
"Inbounds" = "Entrante"
"InboundsDesc" = "Cambia la plantilla de configuración para aceptar clientes específicos."
"Outbounds" = "Salidas"
+"Balancers" = "Equilibradores"
"OutboundsDesc" = "Cambia la plantilla de configuración para definir formas de salida para este servidor."
"Routings" = "Reglas de enrutamiento"
"RoutingsDesc" = "¡La prioridad de cada regla es importante!"
@@ -396,6 +399,8 @@
"logLevelDesc" = "El nivel de registro para registros de errores, que indica la información que debe registrarse."
"accessLog" = "Registro de acceso"
"accessLogDesc" = "La ruta del archivo para el registro de acceso. El valor especial 'ninguno' deshabilita los registros de acceso"
+"errorLog" = "Registro de errores"
+"errorLogDesc" = "La ruta del archivo para el registro de errores. El valor especial 'ninguno' deshabilitó los registros de errores"
[pages.xray.rules]
"first" = "Primero"
@@ -406,6 +411,7 @@
"dest" = "Destino"
"inbound" = "Entrante"
"outbound" = "saliente"
+"balancer" = "Balancín"
"info" = "Información"
"add" = "Agregar regla"
"edit" = "Editar regla"
@@ -426,6 +432,15 @@
"portal" = "portal"
"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]
"secretKey" = "Llave secreta"
"publicKey" = "Llave pública"
diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml
index 7abaf973..546712fd 100644
--- a/web/translation/translate.fa_IR.toml
+++ b/web/translation/translate.fa_IR.toml
@@ -76,7 +76,7 @@
"title" = "نمای کلی"
"memory" = "RAM"
"hard" = "Disk"
-"xrayStatus" = "وضعیتایکسری"
+"xrayStatus" = "ایکسری"
"stopXray" = "توقف"
"restartXray" = "شروعمجدد"
"xraySwitch" = "نسخه"
@@ -311,6 +311,8 @@
"advancedTemplate" = "پیشرفته"
"generalConfigs" = "استراتژی کلی"
"generalConfigsDesc" = "این گزینهها استراتژی کلی ترافیک را تعیین میکنند"
+"logConfigs" = "گزارش"
+"logConfigsDesc" = "گزارشها ممکن است بر کارایی سرور شما تأثیر بگذارد. توصیه می شود فقط در صورت نیاز آن را عاقلانه فعال کنید"
"blockConfigs" = "سپر محافظ"
"blockConfigsDesc" = "این گزینهها ترافیک را بر اساس پروتکلهای درخواستی خاص، و وب سایتها مسدود میکند"
"blockCountryConfigs" = "مسدودسازی کشور"
@@ -388,6 +390,7 @@
"Inbounds" = "ورودیها"
"InboundsDesc" = "پذیرش کلاینت خاص"
"Outbounds" = "خروجیها"
+"Balancers" = "بالانسرها"
"OutboundsDesc" = "مسیر ترافیک خروجی را تنظیم کنید"
"Routings" = "قوانین مسیریابی"
"RoutingsDesc" = "اولویت هر قانون مهم است"
@@ -396,6 +399,8 @@
"logLevelDesc" = "سطح گزارش برای گزارش های خطا، نشان دهنده اطلاعاتی است که باید ثبت شوند."
"accessLog" = "مسیر گزارش"
"accessLogDesc" = "مسیر فایل برای گزارش دسترسی. مقدار ویژه «هیچ» گزارشهای دسترسی را غیرفعال میکند."
+"errorLog" = "گزارش خطا"
+"errorLogDesc" = "مسیر فایل برای ورود به سیستم خطا. مقدار ویژه «هیچ» گزارش های خطا را غیرفعال میکند"
[pages.xray.rules]
"first" = "اولین"
@@ -406,6 +411,7 @@
"dest" = "مقصد"
"inbound" = "ورودی"
"outbound" = "خروجی"
+"balancer" = "بالانسر"
"info" = "اطلاعات"
"add" = "افزودن قانون"
"edit" = "ویرایش قانون"
@@ -426,6 +432,15 @@
"portal" = "پورتال"
"intercon" = "اتصال میانی"
+[pages.xray.balancer]
+"addBalancer" = "افزودن بالانسر"
+"editBalancer" = "ویرایش بالانسر"
+"balancerStrategy" = "استراتژی"
+"balancerSelectors" = "انتخابگرها"
+"tag" = "برچسب"
+"tagDesc" = "برچسب یگانه"
+"balancerDesc" = "امکان استفاده همزمان balancerTag و outboundTag باهم وجود ندارد. درصورت استفاده همزمان فقط outboundTag عمل خواهد کرد."
+
[pages.xray.wireguard]
"secretKey" = "کلید شخصی"
"publicKey" = "کلید عمومی"
diff --git a/web/translation/translate.id_ID.toml b/web/translation/translate.id_ID.toml
new file mode 100644
index 00000000..cfa0210d
--- /dev/null
+++ b/web/translation/translate.id_ID.toml
@@ -0,0 +1,595 @@
+"username" = "Nama Pengguna"
+"password" = "Kata Sandi"
+"login" = "Masuk"
+"confirm" = "Konfirmasi"
+"cancel" = "Batal"
+"close" = "Tutup"
+"copy" = "Salin"
+"copied" = "Tersalin"
+"download" = "Unduh"
+"remark" = "Catatan"
+"enable" = "Aktifkan"
+"protocol" = "Protokol"
+"search" = "Cari"
+"filter" = "Filter"
+"loading" = "Memuat..."
+"second" = "Detik"
+"minute" = "Menit"
+"hour" = "Jam"
+"day" = "Hari"
+"check" = "Centang"
+"indefinite" = "Tak Terbatas"
+"unlimited" = "Tanpa Batas"
+"none" = "None"
+"qrCode" = "Kode QR"
+"info" = "Informasi Lebih Lanjut"
+"edit" = "Edit"
+"delete" = "Hapus"
+"reset" = "Reset"
+"copySuccess" = "Berhasil Disalin"
+"sure" = "Yakin"
+"encryption" = "Enkripsi"
+"transmission" = "Transmisi"
+"host" = "Host"
+"path" = "Jalur"
+"camouflage" = "Obfuscation"
+"status" = "Status"
+"enabled" = "Aktif"
+"disabled" = "Nonaktif"
+"depleted" = "Habis"
+"depletingSoon" = "Akan Habis"
+"offline" = "Offline"
+"online" = "Online"
+"domainName" = "Nama Domain"
+"monitor" = "IP Pemantauan"
+"certificate" = "Sertifikat"
+"fail" = "Gagal"
+"success" = "Berhasil"
+"getVersion" = "Dapatkan Versi"
+"install" = "Instal"
+"clients" = "Klien"
+"usage" = "Penggunaan"
+"secretToken" = "Token Rahasia"
+"remained" = "Tersisa"
+"security" = "Keamanan"
+
+[menu]
+"dashboard" = "Ikhtisar"
+"inbounds" = "Masuk"
+"settings" = "Pengaturan Panel"
+"xray" = "Konfigurasi Xray"
+"logout" = "Keluar"
+"link" = "Kelola"
+
+[pages.login]
+"title" = "Selamat Datang"
+"loginAgain" = "Sesi Anda telah berakhir, harap masuk kembali"
+
+[pages.login.toasts]
+"invalidFormData" = "Format data input tidak valid."
+"emptyUsername" = "Nama Pengguna diperlukan"
+"emptyPassword" = "Kata Sandi diperlukan"
+"wrongUsernameOrPassword" = "Nama pengguna atau kata sandi tidak valid."
+"successLogin" = "Login berhasil"
+
+[pages.index]
+"title" = "Ikhtisar"
+"memory" = "RAM"
+"hard" = "Disk"
+"xrayStatus" = "Xray"
+"stopXray" = "Stop"
+"restartXray" = "Restart"
+"xraySwitch" = "Versi"
+"xraySwitchClick" = "Pilih versi yang ingin Anda pindah."
+"xraySwitchClickDesk" = "Pilih dengan hati-hati, karena versi yang lebih lama mungkin tidak kompatibel dengan konfigurasi saat ini."
+"operationHours" = "Waktu Aktif"
+"systemLoad" = "Beban Sistem"
+"systemLoadDesc" = "Rata-rata beban sistem selama 1, 5, dan 15 menit terakhir"
+"connectionTcpCountDesc" = "Total koneksi TCP di seluruh sistem"
+"connectionUdpCountDesc" = "Total koneksi UDP di seluruh sistem"
+"connectionCount" = "Statistik Koneksi"
+"upSpeed" = "Kecepatan unggah keseluruhan di seluruh sistem"
+"downSpeed" = "Kecepatan unduh keseluruhan di seluruh sistem"
+"totalSent" = "Total data terkirim di seluruh sistem sejak startup OS"
+"totalReceive" = "Total data diterima di seluruh sistem sejak startup OS"
+"xraySwitchVersionDialog" = "Ganti Versi Xray"
+"xraySwitchVersionDialogDesc" = "Apakah Anda yakin ingin mengubah versi Xray menjadi"
+"dontRefresh" = "Instalasi sedang berlangsung, harap jangan menyegarkan halaman ini"
+"logs" = "Log"
+"config" = "Konfigurasi"
+"backup" = "Cadangan & Pulihkan"
+"backupTitle" = "Cadangan & Pulihkan Database"
+"backupDescription" = "Disarankan untuk membuat cadangan sebelum memulihkan database."
+"exportDatabase" = "Cadangkan"
+"importDatabase" = "Pulihkan"
+
+[pages.inbounds]
+"title" = "Masuk"
+"totalDownUp" = "Total Terkirim/Diterima"
+"totalUsage" = "Penggunaan Total"
+"inboundCount" = "Total Masuk"
+"operate" = "Menu"
+"enable" = "Aktifkan"
+"remark" = "Catatan"
+"protocol" = "Protokol"
+"port" = "Port"
+"traffic" = "Traffic"
+"details" = "Rincian"
+"transportConfig" = "Transport"
+"expireDate" = "Durasi"
+"resetTraffic" = "Reset Traffic"
+"addInbound" = "Tambahkan Masuk"
+"generalActions" = "Tindakan Umum"
+"create" = "Buat"
+"update" = "Perbarui"
+"modifyInbound" = "Ubah Masuk"
+"deleteInbound" = "Hapus Masuk"
+"deleteInboundContent" = "Apakah Anda yakin ingin menghapus masuk?"
+"deleteClient" = "Hapus Klien"
+"deleteClientContent" = "Apakah Anda yakin ingin menghapus klien?"
+"resetTrafficContent" = "Apakah Anda yakin ingin mereset traffic?"
+"copyLink" = "Salin URL"
+"address" = "Alamat"
+"network" = "Jaringan"
+"destinationPort" = "Port Tujuan"
+"targetAddress" = "Alamat Target"
+"monitorDesc" = "Biarkan kosong untuk mendengarkan semua IP"
+"meansNoLimit" = " = Unlimited. (unit: GB)"
+"totalFlow" = "Total Aliran"
+"leaveBlankToNeverExpire" = "Biarkan kosong untuk tidak pernah kedaluwarsa"
+"noRecommendKeepDefault" = "Disarankan untuk tetap menggunakan pengaturan default"
+"certificatePath" = "Path Berkas"
+"certificateContent" = "Konten Berkas"
+"publicKeyPath" = "Path Kunci Publik"
+"publicKeyContent" = "Konten Kunci Publik"
+"keyPath" = "Path Kunci Privat"
+"keyContent" = "Konten Kunci Privat"
+"clickOnQRcode" = "Klik pada Kode QR untuk Menyalin"
+"client" = "Klien"
+"export" = "Ekspor Semua URL"
+"clone" = "Duplikat"
+"cloneInbound" = "Duplikat"
+"cloneInboundContent" = "Semua pengaturan masuk ini, kecuali Port, Listening IP, dan Klien, akan diterapkan pada duplikat."
+"cloneInboundOk" = "Duplikat"
+"resetAllTraffic" = "Reset Semua Traffic Masuk"
+"resetAllTrafficTitle" = "Reset Semua Traffic Masuk"
+"resetAllTrafficContent" = "Apakah Anda yakin ingin mereset traffic semua masuk?"
+"resetInboundClientTraffics" = "Reset Traffic Klien Masuk"
+"resetInboundClientTrafficTitle" = "Reset Traffic Klien Masuk"
+"resetInboundClientTrafficContent" = "Apakah Anda yakin ingin mereset traffic klien masuk ini?"
+"resetAllClientTraffics" = "Reset Traffic Semua Klien"
+"resetAllClientTrafficTitle" = "Reset Traffic Semua Klien"
+"resetAllClientTrafficContent" = "Apakah Anda yakin ingin mereset traffic semua klien?"
+"delDepletedClients" = "Hapus Klien Habis"
+"delDepletedClientsTitle" = "Hapus Klien Habis"
+"delDepletedClientsContent" = "Apakah Anda yakin ingin menghapus semua klien yang habis?"
+"email" = "Email"
+"emailDesc" = "Harap berikan alamat email yang unik."
+"IPLimit" = "Batas IP"
+"IPLimitDesc" = "Menonaktifkan masuk jika jumlah melebihi nilai yang ditetapkan. (0 = nonaktif)"
+"IPLimitlog" = "Log IP"
+"IPLimitlogDesc" = "Log histori IP. (untuk mengaktifkan masuk setelah menonaktifkan, hapus log)"
+"IPLimitlogclear" = "Hapus Log"
+"setDefaultCert" = "Atur Sertifikat dari Panel"
+"xtlsDesc" = "Xray harus versi 1.7.5"
+"realityDesc" = "Xray harus versi 1.8.0+"
+"telegramDesc" = "Harap berikan ID Telegram atau obrolan tanpa menggunakan '@'. (dapatkan di sini @userinfobot) atau (gunakan perintah '/id' di bot)"
+"subscriptionDesc" = "Untuk menemukan URL langganan Anda, buka 'Rincian'. Selain itu, Anda dapat menggunakan nama yang sama untuk beberapa klien."
+"info" = "Info"
+"same" = "Sama"
+"inboundData" = "Data Masuk"
+"exportInbound" = "Ekspor Masuk"
+"import" = "Impor"
+"importInbound" = "Impor Masuk"
+
+[pages.client]
+"add" = "Tambah Klien"
+"edit" = "Edit Klien"
+"submitAdd" = "Tambah Klien"
+"submitEdit" = "Simpan Perubahan"
+"clientCount" = "Jumlah Klien"
+"bulk" = "Tambahkan Massal"
+"method" = "Metode"
+"first" = "Pertama"
+"last" = "Terakhir"
+"prefix" = "Awalan"
+"postfix" = "Akhiran"
+"delayedStart" = "Mulai saat Penggunaan Awal"
+"expireDays" = "Durasi"
+"days" = "Hari"
+"renew" = "Perpanjang Otomatis"
+"renewDesc" = "Perpanjangan otomatis setelah kedaluwarsa. (0 = nonaktif)(unit: hari)"
+
+[pages.inbounds.toasts]
+"obtain" = "Dapatkan"
+
+[pages.inbounds.stream.general]
+"request" = "Permintaan"
+"response" = "Respons"
+"name" = "Nama"
+"value" = "Nilai"
+
+[pages.inbounds.stream.tcp]
+"version" = "Versi"
+"method" = "Metode"
+"path" = "Path"
+"status" = "Status"
+"statusDescription" = "Deskripsi Status"
+"requestHeader" = "Header Permintaan"
+"responseHeader" = "Header Respons"
+
+[pages.inbounds.stream.quic]
+"encryption" = "Enkripsi"
+
+[pages.settings]
+"title" = "Pengaturan Panel"
+"save" = "Simpan"
+"infoDesc" = "Setiap perubahan yang dibuat di sini perlu disimpan. Harap restart panel untuk menerapkan perubahan."
+"restartPanel" = "Restart Panel"
+"restartPanelDesc" = "Apakah Anda yakin ingin merestart panel? Jika Anda tidak dapat mengakses panel setelah merestart, lihat info log panel di server."
+"actions" = "Tindakan"
+"resetDefaultConfig" = "Reset ke Default"
+"panelSettings" = "Umum"
+"securitySettings" = "Otentikasi"
+"TGBotSettings" = "Bot Telegram"
+"panelListeningIP" = "IP Pendengar"
+"panelListeningIPDesc" = "Alamat IP untuk panel web. (biarkan kosong untuk mendengarkan semua IP)"
+"panelListeningDomain" = "Domain Pendengar"
+"panelListeningDomainDesc" = "Nama domain untuk panel web. (biarkan kosong untuk mendengarkan semua domain dan IP)"
+"panelPort" = "Port Pendengar"
+"panelPortDesc" = "Nomor port untuk panel web. (harus menjadi port yang tidak digunakan)"
+"publicKeyPath" = "Path Kunci Publik"
+"publicKeyPathDesc" = "Path berkas kunci publik untuk panel web. (dimulai dengan ‘/‘)"
+"privateKeyPath" = "Path Kunci Privat"
+"privateKeyPathDesc" = "Path berkas kunci privat untuk panel web. (dimulai dengan ‘/‘)"
+"panelUrlPath" = "URI Path"
+"panelUrlPathDesc" = "URI path untuk panel web. (dimulai dengan ‘/‘ dan diakhiri dengan ‘/‘)"
+"pageSize" = "Ukuran Halaman"
+"pageSizeDesc" = "Tentukan ukuran halaman untuk tabel masuk. (0 = nonaktif)"
+"remarkModel" = "Model Catatan & Karakter Pemisah"
+"datepicker" = "Jenis Kalender"
+"datepickerPlaceholder" = "Pilih tanggal"
+"datepickerDescription" = "Tugas terjadwal akan berjalan berdasarkan kalender ini."
+"sampleRemark" = "Contoh Catatan"
+"oldUsername" = "Username Saat Ini"
+"currentPassword" = "Kata Sandi Saat Ini"
+"newUsername" = "Username Baru"
+"newPassword" = "Kata Sandi Baru"
+"telegramBotEnable" = "Aktifkan Bot Telegram"
+"telegramBotEnableDesc" = "Mengaktifkan bot Telegram."
+"telegramToken" = "Token Telegram"
+"telegramTokenDesc" = "Token bot Telegram yang diperoleh dari '@BotFather'."
+"telegramProxy" = "Proxy SOCKS"
+"telegramProxyDesc" = "Mengaktifkan proxy SOCKS5 untuk terhubung ke Telegram. (sesuaikan pengaturan sesuai panduan)"
+"telegramChatId" = "ID Obrolan Admin"
+"telegramChatIdDesc" = "ID Obrolan Admin Telegram. (dipisahkan koma)(dapatkan di sini @userinfobot) atau (gunakan perintah '/id' di bot)"
+"telegramNotifyTime" = "Waktu Notifikasi"
+"telegramNotifyTimeDesc" = "Waktu notifikasi bot Telegram yang diatur untuk laporan berkala. (gunakan format waktu crontab)"
+"tgNotifyBackup" = "Cadangan Database"
+"tgNotifyBackupDesc" = "Kirim berkas cadangan database dengan laporan."
+"tgNotifyLogin" = "Notifikasi Login"
+"tgNotifyLoginDesc" = "Dapatkan notifikasi tentang username, alamat IP, dan waktu setiap kali seseorang mencoba masuk ke panel web Anda."
+"sessionMaxAge" = "Durasi Sesi"
+"sessionMaxAgeDesc" = "Durasi di mana Anda dapat tetap masuk. (unit: menit)"
+"expireTimeDiff" = "Notifikasi Tanggal Kedaluwarsa"
+"expireTimeDiffDesc" = "Dapatkan notifikasi tentang tanggal kedaluwarsa saat mencapai ambang batas ini. (unit: hari)"
+"trafficDiff" = "Notifikasi Batas Traffic"
+"trafficDiffDesc" = "Dapatkan notifikasi tentang batas traffic saat mencapai ambang batas ini. (unit: GB)"
+"tgNotifyCpu" = "Notifikasi Beban CPU"
+"tgNotifyCpuDesc" = "Dapatkan notifikasi jika beban CPU melebihi ambang batas ini. (unit: %)"
+"timeZone" = "Zone Waktu"
+"timeZoneDesc" = "Tugas terjadwal akan berjalan berdasarkan zona waktu ini."
+"subSettings" = "Langganan"
+"subEnable" = "Aktifkan Layanan Langganan"
+"subEnableDesc" = "Mengaktifkan layanan langganan."
+"subListen" = "IP Pendengar"
+"subListenDesc" = "Alamat IP untuk layanan langganan. (biarkan kosong untuk mendengarkan semua IP)"
+"subPort" = "Port Pendengar"
+"subPortDesc" = "Nomor port untuk layanan langganan. (harus menjadi port yang tidak digunakan)"
+"subCertPath" = "Path Kunci Publik"
+"subCertPathDesc" = "Path berkas kunci publik untuk layanan langganan. (dimulai dengan ‘/‘)"
+"subKeyPath" = "Path Kunci Privat"
+"subKeyPathDesc" = "Path berkas kunci privat untuk layanan langganan. (dimulai dengan ‘/‘)"
+"subPath" = "URI Path"
+"subPathDesc" = "URI path untuk layanan langganan. (dimulai dengan ‘/‘ dan diakhiri dengan ‘/‘)"
+"subDomain" = "Domain Pendengar"
+"subDomainDesc" = "Nama domain untuk layanan langganan. (biarkan kosong untuk mendengarkan semua domain dan IP)"
+"subUpdates" = "Interval Pembaruan"
+"subUpdatesDesc" = "Interval pembaruan URL langganan dalam aplikasi klien. (unit: jam)"
+"subEncrypt" = "Encode"
+"subEncryptDesc" = "Konten yang dikembalikan dari layanan langganan akan dienkripsi Base64."
+"subShowInfo" = "Tampilkan Info Penggunaan"
+"subShowInfoDesc" = "Sisa traffic dan tanggal akan ditampilkan di aplikasi klien."
+"subURI" = "URI Proxy Terbalik"
+"subURIDesc" = "URI path URL langganan untuk penggunaan di belakang proxy."
+
+[pages.xray]
+"title" = "Konfigurasi Xray"
+"save" = "Simpan"
+"restart" = "Restart Xray"
+"basicTemplate" = "Dasar"
+"advancedTemplate" = "Lanjutan"
+"generalConfigs" = "Strategi Umum"
+"generalConfigsDesc" = "Opsi ini akan menentukan penyesuaian strategi umum."
+"logConfigs" = "Catatan"
+"logConfigsDesc" = "Log dapat mempengaruhi efisiensi server Anda. Disarankan untuk mengaktifkannya dengan bijak hanya jika diperlukan"
+"blockConfigs" = "Pelindung"
+"blockConfigsDesc" = "Opsi ini akan memblokir lalu lintas berdasarkan protokol dan situs web yang diminta."
+"blockCountryConfigs" = "Blokir Negara"
+"blockCountryConfigsDesc" = "Opsi ini akan memblokir lalu lintas berdasarkan negara yang diminta."
+"directCountryConfigs" = "Langsung ke Negara"
+"directCountryConfigsDesc" = "Opsi ini akan langsung meneruskan lalu lintas berdasarkan negara yang diminta."
+"ipv4Configs" = "Pengalihan IPv4"
+"ipv4ConfigsDesc" = "Opsi ini akan mengalihkan lalu lintas berdasarkan tujuan tertentu melalui IPv4."
+"warpConfigs" = "Pengalihan WARP"
+"warpConfigsDesc" = "Opsi ini akan mengalihkan lalu lintas berdasarkan tujuan tertentu melalui WARP."
+"Template" = "Template Konfigurasi Xray Lanjutan"
+"TemplateDesc" = "File konfigurasi Xray akhir akan dibuat berdasarkan template ini."
+"FreedomStrategy" = "Strategi Protokol Freedom"
+"FreedomStrategyDesc" = "Atur strategi output untuk jaringan dalam Protokol Freedom."
+"RoutingStrategy" = "Strategi Pengalihan Keseluruhan"
+"RoutingStrategyDesc" = "Atur strategi pengalihan lalu lintas keseluruhan untuk menyelesaikan semua permintaan."
+"Torrent" = "Blokir Protokol BitTorrent"
+"TorrentDesc" = "Memblokir protokol BitTorrent."
+"PrivateIp" = "Blokir Koneksi ke IP Pribadi"
+"PrivateIpDesc" = "Memblokir pembentukan koneksi ke rentang IP pribadi."
+"Ads" = "Blokir Iklan"
+"AdsDesc" = "Memblokir situs web periklanan."
+"Family" = "Proteksi Keluarga"
+"FamilyDesc" = "Memblokir konten dewasa dan situs web berbahaya."
+"Security" = "Pelindung Keamanan"
+"SecurityDesc" = "Memblokir situs web malware, phishing, dan penambang kripto."
+"Speedtest" = "Blokir Speedtest"
+"SpeedtestDesc" = "Memblokir pembentukan koneksi ke situs web speedtest."
+"IRIp" = "Blokir Koneksi ke IP Iran"
+"IRIpDesc" = "Memblokir pembentukan koneksi ke rentang IP Iran."
+"IRDomain" = "Blokir Koneksi ke Domain Iran"
+"IRDomainDesc" = "Memblokir pembentukan koneksi ke domain Iran."
+"ChinaIp" = "Blokir Koneksi ke IP China"
+"ChinaIpDesc" = "Memblokir pembentukan koneksi ke rentang IP China."
+"ChinaDomain" = "Blokir Koneksi ke Domain China"
+"ChinaDomainDesc" = "Memblokir pembentukan koneksi ke domain China."
+"RussiaIp" = "Blokir Koneksi ke IP Rusia"
+"RussiaIpDesc" = "Memblokir pembentukan koneksi ke rentang IP Rusia."
+"RussiaDomain" = "Blokir Koneksi ke Domain Rusia"
+"RussiaDomainDesc" = "Memblokir pembentukan koneksi ke domain Rusia."
+"VNIp" = "Blokir Koneksi ke IP Vietnam"
+"VNIpDesc" = "Memblokir pembentukan koneksi ke rentang IP Vietnam."
+"VNDomain" = "Blokir Koneksi ke Domain Vietnam"
+"VNDomainDesc" = "Memblokir pembentukan koneksi ke domain Vietnam."
+"DirectIRIp" = "Koneksi Langsung ke IP Iran"
+"DirectIRIpDesc" = "Membentuk koneksi langsung ke rentang IP Iran."
+"DirectIRDomain" = "Koneksi Langsung ke Domain Iran"
+"DirectIRDomainDesc" = "Membentuk koneksi langsung ke domain Iran."
+"DirectChinaIp" = "Koneksi Langsung ke IP China"
+"DirectChinaIpDesc" = "Membentuk koneksi langsung ke rentang IP China."
+"DirectChinaDomain" = "Koneksi Langsung ke Domain China"
+"DirectChinaDomainDesc" = "Membentuk koneksi langsung ke domain China."
+"DirectRussiaIp" = "Koneksi Langsung ke IP Rusia"
+"DirectRussiaIpDesc" = "Membentuk koneksi langsung ke rentang IP Rusia."
+"DirectRussiaDomain" = "Koneksi Langsung ke Domain Rusia"
+"DirectRussiaDomainDesc" = "Membentuk koneksi langsung ke domain Rusia."
+"DirectVNIp" = "Koneksi Langsung ke IP Vietnam"
+"DirectVNIpDesc" = "Membentuk koneksi langsung ke rentang IP Vietnam."
+"DirectVNDomain" = "Koneksi Langsung ke Domain Vietnam"
+"DirectVNDomainDesc" = "Membentuk koneksi langsung ke domain Vietnam."
+"GoogleIPv4" = "Google"
+"GoogleIPv4Desc" = "Rute lalu lintas ke Google melalui IPv4."
+"NetflixIPv4" = "Netflix"
+"NetflixIPv4Desc" = "Rute lalu lintas ke Netflix melalui IPv4."
+"GoogleWARP" = "Google"
+"GoogleWARPDesc" = "Tambahkan pengalihan untuk Google melalui WARP."
+"OpenAIWARP" = "ChatGPT"
+"OpenAIWARPDesc" = "Rute lalu lintas ke ChatGPT melalui WARP."
+"NetflixWARP" = "Netflix"
+"NetflixWARPDesc" = "Rute lalu lintas ke Netflix melalui WARP."
+"SpotifyWARP" = "Spotify"
+"SpotifyWARPDesc" = "Rute lalu lintas ke Spotify melalui WARP."
+"IRWARP" = "Domain Iran"
+"IRWARPDesc" = "Rute lalu lintas ke domain Iran melalui WARP."
+"Inbounds" = "Masuk"
+"InboundsDesc" = "Menerima klien tertentu."
+"Outbounds" = "Keluar"
+"Balancers" = "Penyeimbang"
+"OutboundsDesc" = "Atur jalur lalu lintas keluar."
+"Routings" = "Aturan Pengalihan"
+"RoutingsDesc" = "Prioritas setiap aturan penting!"
+"completeTemplate" = "Semua"
+"logLevel" = "Tingkat Log"
+"logLevelDesc" = "Tingkat log untuk log kesalahan, menunjukkan informasi yang perlu dicatat."
+"accessLog" = "Log Akses"
+"accessLogDesc" = "Jalur file untuk log akses. Nilai khusus 'tidak ada' menonaktifkan log akses"
+"errorLog" = "Catatan eror"
+"errorLogDesc" = "Jalur file untuk log kesalahan. Nilai khusus 'tidak ada' menonaktifkan log kesalahan"
+
+[pages.xray.rules]
+"first" = "Pertama"
+"last" = "Terakhir"
+"up" = "Naik"
+"down" = "Turun"
+"source" = "Sumber"
+"dest" = "Tujuan"
+"inbound" = "Masuk"
+"outbound" = "Keluar"
+"balancer" = "Pengimbang"
+"info" = "Info"
+"add" = "Tambahkan Aturan"
+"edit" = "Edit Aturan"
+"useComma" = "Item yang dipisahkan koma"
+
+[pages.xray.outbound]
+"addOutbound" = "Tambahkan Keluar"
+"addReverse" = "Tambahkan Revers"
+"editOutbound" = "Edit Keluar"
+"editReverse" = "Edit Revers"
+"tag" = "Tag"
+"tagDesc" = "Tag Unik"
+"address" = "Alamat"
+"reverse" = "Revers"
+"domain" = "Domain"
+"type" = "Tipe"
+"bridge" = "Jembatan"
+"portal" = "Portal"
+"intercon" = "Interkoneksi"
+
+[pages.xray.balancer]
+"addBalancer" = "Tambahkan Penyeimbang"
+"editBalancer" = "Sunting Penyeimbang"
+"balancerStrategy" = "Strategi"
+"balancerSelectors" = "Penyeleksi"
+"tag" = "Menandai"
+"tagDesc" = "Label Unik"
+"balancerDesc" = "BalancerTag dan outboundTag tidak dapat digunakan secara bersamaan. Jika digunakan secara bersamaan, hanya outboundTag yang akan berfungsi."
+
+[pages.xray.wireguard]
+"secretKey" = "Kunci Rahasia"
+"publicKey" = "Kunci Publik"
+"allowedIPs" = "IP yang Diizinkan"
+"endpoint" = "Titik Akhir"
+"psk" = "Kunci Pra-Bagi"
+"domainStrategy" = "Strategi Domain"
+
+[pages.settings.security]
+"admin" = "Admin"
+"secret" = "Token Rahasia"
+"loginSecurity" = "Login Aman"
+"loginSecurityDesc" = "Menambahkan lapisan otentikasi tambahan untuk memberikan keamanan lebih."
+"secretToken" = "Token Rahasia"
+"secretTokenDesc" = "Simpan token ini dengan aman di tempat yang aman. Token ini diperlukan untuk login dan tidak dapat dipulihkan."
+
+[pages.settings.toasts]
+"modifySettings" = "Ubah Pengaturan"
+"getSettings" = "Dapatkan Pengaturan"
+"modifyUser" = "Ubah Admin"
+"originalUserPassIncorrect" = "Username atau password saat ini tidak valid"
+"userPassMustBeNotEmpty" = "Username dan password baru tidak boleh kosong"
+
+[tgbot]
+"keyboardClosed" = "❌ Papan ketik kustom ditutup!"
+"noResult" = "❗ Tidak ada hasil!"
+"noQuery" = "❌ Permintaan tidak ditemukan! Harap gunakan perintah lagi!"
+"wentWrong" = "❌ Ada yang salah!"
+"noIpRecord" = "❗ Tidak ada Catatan IP!"
+"noInbounds" = "❗ Tidak ada masuk ditemukan!"
+"unlimited" = "♾ Tak terbatas"
+"add" = "Tambah"
+"month" = "Bulan"
+"months" = "Bulan"
+"day" = "Hari"
+"days" = "Hari"
+"hours" = "Jam"
+"unknown" = "Tidak diketahui"
+"inbounds" = "Masuk"
+"clients" = "Klien"
+"offline" = "🔴 Offline"
+"online" = "🟢 Online"
+
+[tgbot.commands]
+"unknown" = "❗ Perintah tidak dikenal."
+"pleaseChoose" = "👇 Harap pilih:\r\n"
+"help" = "🤖 Selamat datang di bot ini! Ini dirancang untuk menyediakan data tertentu dari panel web dan memungkinkan Anda melakukan modifikasi sesuai kebutuhan.\r\n\r\n"
+"start" = "👋 Halo {{ .Firstname }}.\r\n"
+"welcome" = "🤖 Selamat datang di {{.Hostname }} bot managemen.\r\n"
+"status" = "✅ Bot dalam keadaan baik!"
+"usage" = "❗ Harap berikan teks untuk mencari!"
+"getID" = "🆔 ID Anda:{{.ID }}
"
+"helpAdminCommands" = "Untuk mencari email klien:\r\n/usage [Email]
\r\n\r\nUntuk mencari masuk (dengan statistik klien):\r\n/inbound [Remark]
"
+"helpClientCommands" = "Untuk mencari statistik, gunakan perintah berikut:\r\n\r\n/usage [Email]
"
+
+[tgbot.messages]
+"cpuThreshold" = "🔴 Beban CPU {{ .Percent }}% melebihi batas {{ .Threshold }}%"
+"selectUserFailed" = "❌ Kesalahan dalam pemilihan pengguna!"
+"userSaved" = "✅ Pengguna Telegram tersimpan."
+"loginSuccess" = "✅ Berhasil masuk ke panel.\r\n"
+"loginFailed" = "❗️ Gagal masuk ke panel.\r\n"
+"report" = "🕰 Laporan Terjadwal: {{ .RunTime }}\r\n"
+"datetime" = "⏰ Tanggal & Waktu: {{ .DateTime }}\r\n"
+"hostname" = "💻 Host: {{ .Hostname }}\r\n"
+"version" = "🚀 Versi 3X-UI: {{ .Version }}\r\n"
+"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
+"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
+"ip" = "🌐 IP: {{ .IP }}\r\n"
+"ips" = "🔢 IP:\r\n{{ .IPs }}\r\n"
+"serverUpTime" = "⏳ Waktu Aktif: {{ .UpTime }} {{ .Unit }}\r\n"
+"serverLoad" = "📈 Beban Sistem: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n"
+"serverMemory" = "📋 RAM: {{ .Current }}/{{ .Total }}\r\n"
+"tcpCount" = "🔹 TCP: {{ .Count }}\r\n"
+"udpCount" = "🔸 UDP: {{ .Count }}\r\n"
+"traffic" = "🚦 Lalu Lintas: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
+"xrayStatus" = "ℹ️ Status: {{ .State }}\r\n"
+"username" = "👤 Nama Pengguna: {{ .Username }}\r\n"
+"time" = "⏰ Waktu: {{ .Time }}\r\n"
+"inbound" = "📍 Inbound: {{ .Remark }}\r\n"
+"port" = "🔌 Port: {{ .Port }}\r\n"
+"expire" = "📅 Tanggal Kadaluarsa: {{ .Time }}\r\n"
+"expireIn" = "📅 Kadaluarsa Dalam: {{ .Time }}\r\n"
+"active" = "💡 Aktif: {{ .Enable }}\r\n"
+"enabled" = "🚨 Diaktifkan: {{ .Enable }}\r\n"
+"online" = "🌐 Status Koneksi: {{ .Status }}\r\n"
+"email" = "📧 Email: {{ .Email }}\r\n"
+"upload" = "🔼 Unggah: ↑{{ .Upload }}\r\n"
+"download" = "🔽 Unduh: ↓{{ .Download }}\r\n"
+"total" = "📊 Total: ↑↓{{ .UpDown }} / {{ .Total }}\r\n"
+"TGUser" = "👤 Pengguna Telegram: {{ .TelegramID }}\r\n"
+"exhaustedMsg" = "🚨 Habis {{ .Type }}:\r\n"
+"exhaustedCount" = "🚨 Jumlah Habis {{ .Type }}:\r\n"
+"onlinesCount" = "🌐 Klien Online: {{ .Count }}\r\n"
+"disabled" = "🛑 Dinonaktifkan: {{ .Disabled }}\r\n"
+"depleteSoon" = "🔜 Habis Sebentar: {{ .Deplete }}\r\n\r\n"
+"backupTime" = "🗄 Waktu Backup: {{ .Time }}\r\n"
+"refreshedOn" = "\r\n📋🔄 Diperbarui Pada: {{ .Time }}\r\n\r\n"
+"yes" = "✅ Ya"
+"no" = "❌ Tidak"
+
+[tgbot.buttons]
+"closeKeyboard" = "❌ Tutup Papan Ketik"
+"cancel" = "❌ Batal"
+"cancelReset" = "❌ Batal Reset"
+"cancelIpLimit" = "❌ Batal Batas IP"
+"confirmResetTraffic" = "✅ Konfirmasi Reset Lalu Lintas?"
+"confirmClearIps" = "✅ Konfirmasi Hapus IPs?"
+"confirmRemoveTGUser" = "✅ Konfirmasi Hapus Pengguna Telegram?"
+"confirmToggle" = "✅ Konfirmasi Aktifkan/Nonaktifkan Pengguna?"
+"dbBackup" = "Dapatkan Cadangan DB"
+"serverUsage" = "Penggunaan Server"
+"getInbounds" = "Dapatkan Inbounds"
+"depleteSoon" = "Habis Sebentar"
+"clientUsage" = "Dapatkan Penggunaan"
+"onlines" = "Klien Online"
+"commands" = "Perintah"
+"refresh" = "🔄 Perbarui"
+"clearIPs" = "❌ Hapus IPs"
+"removeTGUser" = "❌ Hapus Pengguna Telegram"
+"selectTGUser" = "👤 Pilih Pengguna Telegram"
+"selectOneTGUser" = "👤 Pilih Pengguna Telegram:"
+"resetTraffic" = "📈 Reset Lalu Lintas"
+"resetExpire" = "📅 Ubah Tanggal Kadaluarsa"
+"ipLog" = "🔢 Log IP"
+"ipLimit" = "🔢 Batas IP"
+"setTGUser" = "👤 Set Pengguna Telegram"
+"toggle" = "🔘 Aktifkan / Nonaktifkan"
+"custom" = "🔢 Kustom"
+"confirmNumber" = "✅ Konfirmasi: {{ .Num }}"
+"confirmNumberAdd" = "✅ Konfirmasi menambahkan: {{ .Num }}"
+"limitTraffic" = "🚧 Batas Lalu Lintas"
+"getBanLogs" = "Dapatkan Log Pemblokiran"
+
+[tgbot.answers]
+"successfulOperation" = "✅ Operasi berhasil!"
+"errorOperation" = "❗ Kesalahan dalam operasi."
+"getInboundsFailed" = "❌ Gagal mendapatkan inbounds."
+"canceled" = "❌ {{ .Email }}: Operasi dibatalkan."
+"clientRefreshSuccess" = "✅ {{ .Email }}: Klien diperbarui dengan berhasil."
+"IpRefreshSuccess" = "✅ {{ .Email }}: IP diperbarui dengan berhasil."
+"TGIdRefreshSuccess" = "✅ {{ .Email }}: Pengguna Telegram Klien diperbarui dengan berhasil."
+"resetTrafficSuccess" = "✅ {{ .Email }}: Lalu lintas direset dengan berhasil."
+"setTrafficLimitSuccess" = "✅ {{ .Email }}: Batas lalu lintas disimpan dengan berhasil."
+"expireResetSuccess" = "✅ {{ .Email }}: Hari kadaluarsa direset dengan berhasil."
+"resetIpSuccess" = "✅ {{ .Email }}: Batas IP {{ .Count }} disimpan dengan berhasil."
+"clearIpSuccess" = "✅ {{ .Email }}: IP dihapus dengan berhasil."
+"getIpLog" = "✅ {{ .Email }}: Dapatkan Log IP."
+"getUserInfo" = "✅ {{ .Email }}: Dapatkan Info Pengguna Telegram."
+"removedTGUserSuccess" = "✅ {{ .Email }}: Pengguna Telegram dihapus dengan berhasil."
+"enableSuccess" = "✅ {{ .Email }}: Diaktifkan dengan berhasil."
+"disableSuccess" = "✅ {{ .Email }}: Dinonaktifkan dengan berhasil."
+"askToAddUserId" = "Konfigurasi Anda tidak ditemukan!\r\nSilakan minta admin Anda untuk menggunakan ID Telegram Anda dalam konfigurasi Anda.\r\n\r\nID Pengguna Anda: {{ .TgUserID }}
"
diff --git a/web/translation/translate.ru_RU.toml b/web/translation/translate.ru_RU.toml
index cbd1d4f9..dfb75d4a 100644
--- a/web/translation/translate.ru_RU.toml
+++ b/web/translation/translate.ru_RU.toml
@@ -76,7 +76,7 @@
"title" = "Статус системы"
"memory" = "Память"
"hard" = "Жесткий диск"
-"xrayStatus" = "Статус"
+"xrayStatus" = "Xray"
"stopXray" = "Остановить"
"restartXray" = "Перезапустить"
"xraySwitch" = "Версия"
@@ -311,6 +311,8 @@
"advancedTemplate" = "Расширенный шаблон"
"generalConfigs" = "Основные настройки"
"generalConfigsDesc" = "Эти параметры описывают общие настройки"
+"logConfigs" = "Журнал"
+"logConfigsDesc" = "Журналы могут повлиять на эффективность вашего сервера. Рекомендуется включать их с умом только в случае ваших нужд!"
"blockConfigs" = "Блокировка конфигураций"
"blockConfigsDesc" = "Эти параметры не позволят пользователям подключаться к определенным протоколам и веб-сайтам"
"blockCountryConfigs" = "Конфигурации блокировки страны"
@@ -388,6 +390,7 @@
"Inbounds" = "Входящие"
"InboundsDesc" = "Изменение шаблона конфигурации для подключения определенных пользователей"
"Outbounds" = "Исходящие"
+"Balancers" = "Балансиры"
"OutboundsDesc" = "Изменение шаблона конфигурации, чтобы определить исходящие пути для этого сервера"
"Routings" = "Правила маршрутизации"
"RoutingsDesc" = "Важен приоритет каждого правила!"
@@ -396,6 +399,8 @@
"logLevelDesc" = "Уровень журнала для журналов ошибок, указывающий информацию, которую необходимо записать."
"accessLog" = "Журнал доступа"
"accessLogDesc" = "Путь к файлу журнала доступа. Специальное значение «none» отключило журналы доступа."
+"errorLog" = "Журнал ошибок"
+"errorLogDesc" = "Путь к файлу журнала ошибок. Специальное значение «none» отключает журналы ошибок."
[pages.xray.rules]
"first" = "Первый"
@@ -406,6 +411,7 @@
"dest" = "Пункт назначения"
"inbound" = "Входящий"
"outbound" = "Исходящий"
+"balancer" = "балансир"
"info" = "Информация"
"add" = "Добавить правило"
"edit" = "Редактировать правило"
@@ -426,6 +432,15 @@
"portal" = "Портал"
"intercon" = "Соединение"
+[pages.xray.balancer]
+"addBalancer" = "Добавить балансир"
+"editBalancer" = "Редактировать балансир"
+"balancerStrategy" = "Стратегия"
+"balancerSelectors" = "Селекторы"
+"tag" = "Тег"
+"tagDesc" = "уникальный тег"
+"balancerDesc" = "Невозможно одновременно использовать balancerTag и outboundTag. При одновременном использовании будет работать только outboundTag."
+
[pages.xray.wireguard]
"secretKey" = "Секретный ключ"
"publicKey" = "Открытый ключ"
diff --git a/web/translation/translate.vi_VN.toml b/web/translation/translate.vi_VN.toml
index f94b56b0..b6e7fd46 100644
--- a/web/translation/translate.vi_VN.toml
+++ b/web/translation/translate.vi_VN.toml
@@ -20,7 +20,7 @@
"check" = "Kiểm tra"
"indefinite" = "Không xác định"
"unlimited" = "Không giới hạn"
-"none" = "Không có"
+"none" = "None"
"qrCode" = "Mã QR"
"info" = "Thông tin thêm"
"edit" = "Chỉnh sửa"
@@ -76,7 +76,7 @@
"title" = "Trạng thái hệ thống"
"memory" = "Ram"
"hard" = "Dung lượng"
-"xrayStatus" = "Trạng thái Xray"
+"xrayStatus" = "Xray"
"stopXray" = "Dừng lại"
"restartXray" = "Khởi động lại"
"xraySwitch" = "Phiên bản"
@@ -311,6 +311,8 @@
"advancedTemplate" = "Mẫu Nâng cao"
"generalConfigs" = "Cấu hình Chung"
"generalConfigsDesc" = "Những tùy chọn này sẽ cung cấp điều chỉnh tổng quát."
+"logConfigs" = "Nhật ký"
+"logConfigsDesc" = "Nhật ký có thể ảnh hưởng đến hiệu suất máy chủ của bạn. Bạn chỉ nên kích hoạt nó một cách khôn ngoan trong trường hợp bạn cần"
"blockConfigs" = "Cấu hình Chặn"
"blockConfigsDesc" = "Những tùy chọn này sẽ ngăn người dùng kết nối đến các giao thức và trang web cụ thể."
"blockCountryConfigs" = "Cấu hình Chặn Quốc gia"
@@ -388,6 +390,7 @@
"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ể."
"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."
"Routings" = "Quy tắc định tuyến"
"RoutingsDesc" = "Mức độ ưu tiên của mỗi quy tắc đều quan trọng!"
@@ -396,6 +399,8 @@
"logLevelDesc" = "Cấp độ nhật ký cho nhật ký lỗi, cho biết thông tin cần được ghi lại."
"accessLog" = "Nhật ký truy cập"
"accessLogDesc" = "Đường dẫn tệp cho nhật ký truy cập. Nhật ký truy cập bị vô hiệu hóa có giá trị đặc biệt 'không'"
+"errorLog" = "Nhật ký lỗi"
+"errorLogDesc" = "Đường dẫn tệp cho nhật ký lỗi. Nhật ký lỗi bị vô hiệu hóa có giá trị đặc biệt 'không'"
[pages.xray.rules]
"first" = "Đầu tiên"
@@ -406,6 +411,7 @@
"dest" = "Đích"
"inbound" = "Vào"
"outbound" = "Ra"
+"balancer" = "Cân bằng"
"info" = "Thông tin"
"add" = "Thêm quy tắc"
"edit" = "Chỉnh sửa quy tắc"
@@ -426,6 +432,15 @@
"portal" = "Cổng thông tin"
"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]
"secretKey" = "Khoá bí mật"
"publicKey" = "Khóa công khai"
diff --git a/web/translation/translate.zh_Hans.toml b/web/translation/translate.zh_Hans.toml
index d64e1331..ba37ae92 100644
--- a/web/translation/translate.zh_Hans.toml
+++ b/web/translation/translate.zh_Hans.toml
@@ -76,7 +76,7 @@
"title" = "系统状态"
"memory" = "内存"
"hard" = "硬盘"
-"xrayStatus" = "状态"
+"xrayStatus" = "Xray"
"stopXray" = "停止"
"restartXray" = "重启"
"xraySwitch" = "版本"
@@ -311,6 +311,8 @@
"advancedTemplate" = "高级模板部件"
"generalConfigs" = "通用配置"
"generalConfigsDesc" = "这些选项将提供一般调整"
+"logConfigs"="日志"
+"logConfigsDesc" = "日志可能会影响您服务器的效率。建议仅在您需要时明智地启用它"
"blockConfigs" = "阻塞配置"
"blockConfigsDesc" = "这些选项将阻止用户连接到特定协议和网站"
"blockCountryConfigs" = "阻止国家配置"
@@ -388,6 +390,7 @@
"Inbounds" = "入站"
"InboundsDesc" = "更改配置模板接受特殊客户端"
"Outbounds" = "出站"
+"Balancers" = "平衡器"
"OutboundsDesc" = "更改配置模板定义此服务器的传出方式"
"Routings" = "路由规则"
"RoutingsDesc" = "每条规则的优先级都很重要"
@@ -396,6 +399,8 @@
"logLevelDesc" = "错误日志的日志级别,表示需要记录的信息。"
"accessLog" = "访问日志"
"accessLogDesc" = "访问日志的文件路径。 特殊值“none”禁用访问日志"
+"errorLog" = "错误日志"
+"errorLogDesc" = "错误日志的文件路径。 特殊值“none”禁用错误日志"
[pages.xray.rules]
"first" = "第一个"
@@ -406,6 +411,7 @@
"dest" = "目的地"
"inbound" = "入站"
"outbound" = "出站"
+"balancer" = "平衡器"
"info" = "信息"
"add" = "添加规则"
"edit" = "编辑规则"
@@ -426,6 +432,15 @@
"portal" = "门户"
"intercon" = "互连"
+[pages.xray.balancer]
+"addBalancer" = "添加平衡器"
+"editBalancer" = "编辑平衡器"
+"balancerStrategy" = "战略"
+"balancerSelectors" = "选择器"
+"tag" = "标签"
+"tagDesc" = "唯一标记"
+"balancerDesc" = "不能同时使用balancerTag和outboundTag。 如果同时使用,则只有outboundTag起作用。"
+
[pages.xray.wireguard]
"secretKey" = "密钥"
"publicKey" = "公钥"
diff --git a/x-ui.sh b/x-ui.sh
index ffb20f06..518b9232 100644
--- a/x-ui.sh
+++ b/x-ui.sh
@@ -150,6 +150,12 @@ custom_version() {
eval $install_command
}
+# Function to handle the deletion of the script file
+delete_script() {
+ rm "$0" # Remove the script file itself
+ exit 1
+}
+
uninstall() {
confirm "Are you sure you want to uninstall the panel? xray will also uninstalled!" "n"
if [[ $? != 0 ]]; then
@@ -167,12 +173,13 @@ uninstall() {
rm /usr/local/x-ui/ -rf
echo ""
- echo -e "Uninstalled Successfully, If you want to remove this script, then after exiting the script run ${green}rm /usr/bin/x-ui -f${plain} to delete it."
+ echo -e "Uninstalled Successfully.\n"
+ echo "If you need to install this panel again, you can use below command:"
+ echo -e "${green}bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)${plain}"
echo ""
-
- if [[ $# == 0 ]]; then
- before_show_menu
- fi
+ # Trap the SIGTERM signal
+ trap delete_script SIGTERM
+ delete_script
}
reset_user() {
@@ -483,6 +490,33 @@ show_xray_status() {
fi
}
+firewall_menu() {
+ echo -e "${green}\t1.${plain} Install Firewall & open ports"
+ echo -e "${green}\t2.${plain} Allowed List"
+ echo -e "${green}\t3.${plain} Delete Ports from List"
+ echo -e "${green}\t4.${plain} Disable Firewall"
+ echo -e "${green}\t0.${plain} Back to Main Menu"
+ read -p "Choose an option: " choice
+ case "$choice" in
+ 0)
+ show_menu
+ ;;
+ 1)
+ open_ports
+ ;;
+ 2)
+ sudo ufw status
+ ;;
+ 3)
+ delete_ports
+ ;;
+ 4)
+ sudo ufw disable
+ ;;
+ *) echo "Invalid choice" ;;
+ esac
+}
+
open_ports() {
if ! command -v ufw &>/dev/null; then
echo "ufw firewall is not installed. Installing now..."
@@ -535,6 +569,37 @@ open_ports() {
ufw status | grep $ports
}
+delete_ports() {
+ # Prompt the user to enter the ports they want to delete
+ read -p "Enter the ports you want to delete (e.g. 80,443,2053 or range 400-500): " ports
+
+ # Check if the input is valid
+ if ! [[ $ports =~ ^([0-9]+|[0-9]+-[0-9]+)(,([0-9]+|[0-9]+-[0-9]+))*$ ]]; then
+ echo "Error: Invalid input. Please enter a comma-separated list of ports or a range of ports (e.g. 80,443,2053 or 400-500)." >&2
+ exit 1
+ fi
+
+ # Delete the specified ports using ufw
+ IFS=',' read -ra PORT_LIST <<<"$ports"
+ for port in "${PORT_LIST[@]}"; do
+ if [[ $port == *-* ]]; then
+ # Split the range into start and end ports
+ start_port=$(echo $port | cut -d'-' -f1)
+ end_port=$(echo $port | cut -d'-' -f2)
+ # Loop through the range and delete each port
+ for ((i = start_port; i <= end_port; i++)); do
+ ufw delete allow $i
+ done
+ else
+ ufw delete allow "$port"
+ fi
+ done
+
+ # Confirm that the ports are deleted
+ echo "Deleted the specified ports:"
+ ufw status | grep $ports
+}
+
update_geo() {
local defaultBinFolder="/usr/local/x-ui/bin"
read -p "Please enter x-ui bin folder path. Leave blank for default. (Default: '${defaultBinFolder}')" binFolder
@@ -1124,10 +1189,10 @@ show_menu() {
${green}17.${plain} Cloudflare SSL Certificate
${green}18.${plain} IP Limit Management
${green}19.${plain} WARP Management
+ ${green}20.${plain} Firewall Management
————————————————
- ${green}20.${plain} Enable BBR
- ${green}21.${plain} Update Geo Files
- ${green}22.${plain} Active Firewall and open ports
+ ${green}21.${plain} Enable BBR
+ ${green}22.${plain} Update Geo Files
${green}23.${plain} Speedtest by Ookla
"
show_status
@@ -1195,13 +1260,13 @@ show_menu() {
warp_cloudflare
;;
20)
- enable_bbr
+ firewall_menu
;;
21)
- update_geo
+ enable_bbr
;;
22)
- open_ports
+ update_geo
;;
23)
run_speedtest
diff --git a/xray/log_writer.go b/xray/log_writer.go
index 53358ca2..2d208326 100644
--- a/xray/log_writer.go
+++ b/xray/log_writer.go
@@ -1,6 +1,7 @@
package xray
import (
+ "regexp"
"strings"
"x-ui/logger"
)
@@ -14,26 +15,18 @@ type LogWriter struct {
}
func (lw *LogWriter) Write(m []byte) (n int, err error) {
+ regex := regexp.MustCompile(`^(\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}) \[([^\]]+)\] (.+)$`)
// Convert the data to a string
message := strings.TrimSpace(string(m))
messages := strings.Split(message, "\n")
lw.lastLine = messages[len(messages)-1]
for _, msg := range messages {
- messageBody := msg
+ matches := regex.FindStringSubmatch(msg)
- // Remove timestamp
- splittedMsg := strings.SplitN(msg, " ", 3)
- if len(splittedMsg) > 2 {
- messageBody = strings.TrimSpace(strings.SplitN(msg, " ", 3)[2])
- }
-
- // Find level in []
- startIndex := strings.Index(messageBody, "[")
- endIndex := strings.Index(messageBody, "]")
- if startIndex != -1 && endIndex != -1 && startIndex < endIndex {
- level := strings.TrimSpace(messageBody[startIndex+1 : endIndex])
- msgBody := "XRAY: " + strings.TrimSpace(messageBody[endIndex+1:])
+ if len(matches) > 3 {
+ level := matches[2]
+ msgBody := matches[3]
// Map the level to the appropriate logger function
switch level {
diff --git a/xray/process.go b/xray/process.go
index 093cf69d..e37a0649 100644
--- a/xray/process.go
+++ b/xray/process.go
@@ -41,10 +41,6 @@ func GetIPLimitLogPath() string {
return config.GetLogFolder() + "/3xipl.log"
}
-func GetIPLimitPrevLogPath() string {
- return config.GetLogFolder() + "/3xipl.prev.log"
-}
-
func GetIPLimitBannedLogPath() string {
return config.GetLogFolder() + "/3xipl-banned.log"
}