Merge branch 'main' into X-Panel

This commit is contained in:
心隨緣動
2025-09-05 02:19:58 +08:00
committed by GitHub
103 changed files with 5680 additions and 1743 deletions
+4 -1
View File
@@ -10,6 +10,9 @@ class DBInbound {
this.remark = "";
this.enable = true;
this.expiryTime = 0;
// 新增:入站级设备限制(0 表示不限制)
this.deviceLimit = 0;
this.listen = "";
this.port = 0;
@@ -146,4 +149,4 @@ class DBInbound {
const inbound = this.toInbound();
return inbound.genInboundLinks(this.remark, remarkModel);
}
}
}
+2 -2
View File
@@ -731,8 +731,8 @@ class RealityStreamSettings extends XrayCommonClass {
constructor(
show = false,
xver = 0,
dest = 'google.com:443',
serverNames = 'google.com,www.google.com',
dest = 'tesla.com:443',
serverNames = 'tesla.com,www.tesla.com',
privateKey = '',
minClientVer = '',
maxClientVer = '',
+2 -2
View File
@@ -22,7 +22,7 @@ class AllSetting {
this.tgBotBackup = false;
this.tgBotLoginNotify = true;
this.tgCpu = 80;
this.tgLang = "en-US";
this.tgLang = "zh-CN";
this.twoFactorEnable = false;
this.twoFactorToken = "";
this.xrayTemplateConfig = "";
@@ -58,4 +58,4 @@ class AllSetting {
equals(other) {
return ObjectUtil.equals(this, other);
}
}
}
+4 -4
View File
@@ -817,11 +817,11 @@ class LanguageManager {
if (LanguageManager.isSupportLanguage(lang)) {
CookieManager.setCookie("lang", lang, 150);
} else {
CookieManager.setCookie("lang", "en-US", 150);
CookieManager.setCookie("lang", "zh-CN", 150);
window.location.reload();
}
} else {
CookieManager.setCookie("lang", "en-US", 150);
CookieManager.setCookie("lang", "zh-CN", 150);
window.location.reload();
}
}
@@ -831,7 +831,7 @@ class LanguageManager {
static setLanguage(language) {
if (!LanguageManager.isSupportLanguage(language)) {
language = "en-US";
language = "zh-CN";
}
CookieManager.setCookie("lang", language, 150);
@@ -884,4 +884,4 @@ class FileManager {
link.remove();
}
}
}
+5
View File
@@ -26,6 +26,7 @@ func (a *XUIController) initRouter(g *gin.RouterGroup) {
g.GET("/inbounds", a.inbounds)
g.GET("/settings", a.settings)
g.GET("/xray", a.xraySettings)
g.GET("/navigation", a.navigation)
a.inboundController = NewInboundController(g)
a.settingController = NewSettingController(g)
@@ -47,3 +48,7 @@ func (a *XUIController) settings(c *gin.Context) {
func (a *XUIController) xraySettings(c *gin.Context) {
html(c, "xray.html", "pages.xray.title", nil)
}
func (a *XUIController) navigation(c *gin.Context) {
html(c, "navigation.html", "pages.navigation.title", nil)
}
+6 -1
View File
@@ -64,6 +64,11 @@
icon: 'tool',
title: '{{ i18n "menu.xray"}}'
},
{
key: '{{ .base_path }}panel/navigation',
icon: 'link',
title: '{{ i18n "menu.navigation"}}'
},
{
key: '{{ .base_path }}logout/',
icon: 'logout',
@@ -100,4 +105,4 @@
template: `{{template "component/sidebar/content"}}`,
});
</script>
{{end}}
{{end}}
+48 -6
View File
@@ -1,19 +1,24 @@
{{define "form/inbound"}}
<!-- base -->
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<!-- 开关:启用/禁用 -->
<a-form-item label='{{ i18n "enable" }}'>
<a-switch v-model="dbInbound.enable"></a-switch>
</a-form-item>
<!-- 备注 -->
<a-form-item label='{{ i18n "remark" }}'>
<a-input v-model.trim="dbInbound.remark"></a-input>
</a-form-item>
<!-- 协议选择 -->
<a-form-item label='{{ i18n "protocol" }}'>
<a-select v-model="inbound.protocol" :disabled="isEdit" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option>
</a-select>
</a-form-item>
<!-- 监听地址 -->
<a-form-item>
<template slot="label">
<a-tooltip>
@@ -27,23 +32,60 @@
<a-input v-model.trim="inbound.listen"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.port" }}'>
<a-input-number v-model.number="inbound.port" :min="1" :max="65531"></a-input-number>
</a-form-item>
<!-- 端口 + 总流量 在同一行 -->
<a-row :gutter="16">
<!-- 左侧:端口 -->
<a-col :span="12">
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
端口范围:1 - 65531(请尽量使用高位端口)
</template>
{{ i18n "pages.inbounds.port" }}
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input-number v-model.number="inbound.port" :min="1" :max="65531" style="width: 100%" />
</a-form-item>
</a-col>
<!-- 右侧:总流量 -->
<a-col :span="12">
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
</template>
{{ i18n "pages.inbounds.totalFlow" }}
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input-number v-model.number="dbInbound.totalGB" :min="0" style="width: 100%" />
</a-form-item>
</a-col>
</a-row>
<!-- 设备限制 单独占一行 -->
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
0 表示不限制(留空也表示不限制)
</template>
{{ i18n "pages.inbounds.totalFlow" }}
设备限制
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input-number v-model.number="dbInbound.totalGB" :min="0"></a-input-number>
<a-input-number
v-model.number="dbInbound.deviceLimit"
:min="0"
style="width: 100%"
placeholder="0 = 不限制" />
</a-form-item>
<!-- 到期时间 -->
<a-form-item>
<template slot="label">
<a-tooltip>
+3 -3
View File
@@ -48,7 +48,7 @@
<a-textarea v-model="inbound.stream.reality.privateKey"></a-textarea>
</a-form-item>
<a-form-item label=" ">
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Cert</a-button>
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Cert(随机获取新证书)</a-button>
</a-form-item>
<a-form-item label="mldsa65 Seed">
<a-textarea v-model="inbound.stream.reality.mldsa65Seed"></a-textarea>
@@ -57,7 +57,7 @@
<a-textarea v-model="inbound.stream.reality.settings.mldsa65Verify"></a-textarea>
</a-form-item>
<a-form-item label=" ">
<a-button type="primary" icon="import" @click="getNewmldsa65">Get New Seed</a-button>
<a-button type="primary" icon="import" @click="getNewmldsa65">Get New Seed(获取Mldsa65数值)</a-button>
</a-form-item>
</template>
{{end}}
{{end}}
+17 -12
View File
@@ -694,9 +694,9 @@
<script>
const columns = [{
title: "ID",
align: 'right',
align: 'center',
dataIndex: "id",
width: 30,
width: 40,
responsive: ["xs"],
}, {
title: '{{ i18n "pages.inbounds.operate" }}',
@@ -711,7 +711,7 @@
}, {
title: '{{ i18n "pages.inbounds.remark" }}',
align: 'center',
width: 60,
width: 50,
dataIndex: "remark",
}, {
title: '{{ i18n "pages.inbounds.port" }}',
@@ -720,24 +720,25 @@
width: 40,
}, {
title: '{{ i18n "pages.inbounds.protocol" }}',
align: 'left',
align: 'center',
width: 70,
scopedSlots: { customRender: 'protocol' },
}, {
title: '{{ i18n "clients" }}',
align: 'left',
width: 50,
width: 40,
scopedSlots: { customRender: 'clients' },
}, {
title: '{{ i18n "pages.inbounds.deviceLimit" }}',
dataIndex: "deviceLimit",
align: "center",
width: 40,
customRender: (text) => (text > 0 ? text : i18n("pages.inbounds.unlimited")),
}, {
title: '{{ i18n "pages.inbounds.traffic" }}',
align: 'center',
width: 90,
width: 50,
scopedSlots: { customRender: 'traffic' },
}, {
title: '{{ i18n "pages.inbounds.allTimeTraffic" }}',
align: 'center',
width: 70,
scopedSlots: { customRender: 'allTimeInbound' },
}, {
title: '{{ i18n "pages.inbounds.expireDate" }}',
align: 'center',
@@ -1135,6 +1136,8 @@
remark: dbInbound.remark,
enable: dbInbound.enable,
expiryTime: dbInbound.expiryTime,
// 新增这一行
deviceLimit: dbInbound.deviceLimit,
listen: inbound.listen,
port: inbound.port,
@@ -1158,6 +1161,8 @@
remark: dbInbound.remark,
enable: dbInbound.enable,
expiryTime: dbInbound.expiryTime,
// 新增这一行
deviceLimit: dbInbound.deviceLimit,
listen: inbound.listen,
port: inbound.port,
@@ -1669,4 +1674,4 @@
},
});
</script>
{{ template "page/body_end" .}}
{{ template "page/body_end" .}}
+9 -7
View File
@@ -219,20 +219,20 @@
</a-card>
</a-col>
<a-col :sm="24" :lg="12">
<a-card title='3X-UI' hoverable>
<a rel="noopener" href="https://github.com/MHSanaei/3x-ui/releases" target="_blank">
<a-card title='X-Panel面板〕' hoverable>
<a rel="noopener" href="https://github.com/xeefei/x-panel/releases" target="_blank">
<a-tag color="green">
<span>v{{ .cur_ver }}</span>
</a-tag>
</a>
<a rel="noopener" href="https://t.me/XrayUI" target="_blank">
<a rel="noopener" href="https://t.me/is_Chat_Bot" target="_blank">
<a-tag color="green">
<span>@XrayUI</span>
<span>TG私聊交流</span>
</a-tag>
</a>
<a rel="noopener" href="https://github.com/MHSanaei/3x-ui/wiki" target="_blank">
<a rel="noopener" href="https://t.me/XUI_CN" target="_blank">
<a-tag color="purple">
<span>{{ i18n "pages.index.documentation" }}</span>
<span>X-Panel面板〕交流群</span>
</a-tag>
</a>
</a-card>
@@ -253,6 +253,8 @@
</template>
</a-tooltip>
</a-tag>
<a rel="noopener" href="https://ping.pe" target="_blank"><a-tag color="green">端口检测</a-tag></a>
<a rel="noopener" href="https://www.speedtest.net" target="_blank"><a-tag color="green">网络测速</a-tag></a>
</a-card>
</a-col>
<a-col :sm="24" :lg="12">
@@ -911,4 +913,4 @@ ${dateTime}
},
});
</script>
{{ template "page/body_end" .}}
{{ template "page/body_end" .}}
+11 -11
View File
@@ -6,7 +6,7 @@
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template> Source IPs <a-icon type="question-circle"></a-icon>
</template> {{ i18n "pages.xray.rules.SourceIPs" }} <a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="ruleModal.rule.source"></a-input>
@@ -16,22 +16,22 @@
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template> Source Port <a-icon type="question-circle"></a-icon>
</template> {{ i18n "pages.xray.rules.SourcePort" }} <a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="ruleModal.rule.sourcePort"></a-input>
</a-form-item>
<a-form-item label='Network'>
<a-form-item label='{{ i18n "pages.xray.rules.Network" }}'>
<a-select v-model="ruleModal.rule.network" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="x in ['','TCP','UDP','TCP,UDP']" :value="x">[[ x ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Protocol'>
<a-form-item label='{{ i18n "pages.xray.rules.Protocol" }}'>
<a-select v-model="ruleModal.rule.protocol" mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="x in ['http','tls','bittorrent','quic']" :value="x">[[ x ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Attributes'>
<a-form-item label='{{ i18n "pages.xray.rules.Attributes" }}'>
<a-button icon="plus" size="small" :style="{ marginLeft: '10px' }" @click="ruleModal.rule.attrs.push(['', ''])"></a-button>
</a-form-item>
<a-form-item :wrapper-col="{span: 24}">
@@ -59,7 +59,7 @@
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template> Domain <a-icon type="question-circle"></a-icon>
</template> {{ i18n "pages.xray.rules.Domain" }} <a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="ruleModal.rule.domain"></a-input>
@@ -69,7 +69,7 @@
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template> User <a-icon type="question-circle"></a-icon>
</template> {{ i18n "pages.xray.rules.User" }} <a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="ruleModal.rule.user"></a-input>
@@ -79,17 +79,17 @@
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template> Port <a-icon type="question-circle"></a-icon>
</template> {{ i18n "pages.xray.rules.Port" }} <a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="ruleModal.rule.port"></a-input>
</a-form-item>
<a-form-item label='Inbound Tags'>
<a-form-item label='{{ i18n "pages.xray.rules.InboundTag" }}'>
<a-select v-model="ruleModal.rule.inboundTag" mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="tag in ruleModal.inboundTags" :value="tag">[[ tag ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Outbound Tag'>
<a-form-item label='{{ i18n "pages.xray.rules.OutboundTag" }}'>
<a-select v-model="ruleModal.rule.outboundTag" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="tag in ruleModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
</a-select>
@@ -99,7 +99,7 @@
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.xray.balancer.balancerDesc" }}</span>
</template> Balancer Tag <a-icon type="question-circle"></a-icon>
</template> {{ i18n "pages.xray.rules.BalancerTag" }} <a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-select v-model="ruleModal.rule.balancerTag" :dropdown-class-name="themeSwitcher.currentTheme">
+167
View File
@@ -0,0 +1,167 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>实用导航&技巧</title>
<style>
body {
background-color: #151F31;
color: white;
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
display: flex;
}
.sidebar {
width: 150px;
background-color: #151F31;
padding: 20px;
}
.sidebar h2 {
color: #fff;
margin-top: 0;
}
.sidebar button {
background-color: #2C3E56;
color: #008000; /* 绿色字体 */
border: none;
padding: 10px 20px;
margin-bottom: 10px;
cursor: pointer;
display: block;
width: 100%;
border-radius: 15px; /* 增加圆角幅度 */
font-weight: bold; /* 加粗字体 */
}
.sidebar button:hover {
background-color: #4A90E2;
}
.sidebar .friend-links {
margin-top: auto;
text-align: center;
margin-bottom: 12px; /* 调整友情链接下边距 */
}
.sidebar .friend-links a {
color: #0080FF; /* 蓝色字体 */
text-decoration: none;
display: block;
margin-bottom: 10px;
}
.separator {
width: 15px;
background-color: #34495e;
}
.content {
flex: 1;
padding: 20px;
padding-top: 0; /* 移除顶部空白 */
}
</style>
</head>
<body>
<div class="sidebar">
<button onclick="history.back()">返回上一步</button>
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
<div class="friend-links">
<a href="https://chat.openai.com/">ChatGPT</a>
<a href="https://github.com/xeefei/x-panel">项目地址</a>
<a href="https://t.me/XUI_CN">X-Panel面板〕交流群</a>
<a href="https://www.youtube.com/results?search_query=4k%E6%B5%8B%E9%80%9F">油管4K测速</a>
<a href="https://whatismyipaddress.com/">我的IP查询</a>
<a href="https://translate.google.com/?hl=zh-CN">Google翻译</a>
<a href="https://xtls.github.io/">Project X</a>
<a href="https://t.me/is_Haotian_Bot">预留&占位</a>
<a href="https://t.me/is_Chat_Bot">联系作者</a>
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
<button onclick="window.scrollTo(0, 0)">回滚至顶部</button>
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
<button onclick="history.back()">返回到面板</button>
</div>
</div>
<div class="separator"></div>
<div class="content">
<h3>一、〔X-Panel面板〕交流群:<a href="https://t.me/XUI_CN">https://t.me/XUI_CN</a></h3>
<h3> 〔X-Panel面板〕详细安装流程步骤:<a href="https://xeefei.blogspot.com/2025/09/x-panel.html">https://xeefei.blogspot.com/2025/09/x-panel.html</a></h3>
<h3>二、判断VPS服务器的IP是否【送中】?</h3>
<p>***点击打开:<a href="https://music.youtube.com/">https://music.youtube.com/</a>,能正常打开访问,就代表【没送中】,反之就是送中了。</p>
<p>***如果送中了如何解决去【拉回来】?</p>
<p>1:关闭/取消登录了谷歌账户的APP定位权限/授权;2:将常用的一个谷歌账号的位置记录功能打开;3:在电脑上打开Chrome/谷歌浏览器,登录开了位置记录功能的谷歌账号,安装Location Guard拓展插件<a href="https://chrome.google.com/webstore/detail/location-guard/cfohepagpmnodfdmjliccbbigdkfcgia">https://chrome.google.com/webstore/detail/location-guard/cfohepagpmnodfdmjliccbbigdkfcgia</a>(也可在其他支持此插件的浏览器使用);4:打开Location Guard插件,选择Fixed Location,并在给出的地图上单击,即可标记上你想要IP所处的国家/地区
Google IP定位错误,使用Location Guard修改;5:转换到Options选项,Default level默认设置为Use fixed location6:打开谷歌地图google.com/maps,点击右下角定位授权图标,使google maps获取当前“我的GPS位置”
Google IP定位错误,使用Location Guard修改GPS位置地址;7:谷歌搜索my ip,即可看到谷歌IP定位到了刚才地图上标记的位置;8:在此网页向谷歌报告IP问题:<a href="https://support.google.com/websearch/workflow/9308722">https://support.google.com/websearch/workflow/9308722</a></p>
<h3>三、在自己的VPS服务器部署【订阅转换】功能</h3>
<p>如何把vless/vmess等协议转换成Clash/Surge等软件支持的格式?
1、进入脚本输入x-ui命令调取面板,选择第【25】选项安装订阅转换模块,
2、等待安装【订阅转换】成功之后,访问地址:https://你的域名:15268
3、因为在转换过程中需要调取后端API,所以请确保端口 8000 和 15268 是打开放行的,
4、直接复制脚本中提供的【登录地址】,进入后台,点击【节点列表】----->>>【添加节点】,
5、接下来点击左边侧边栏的【订阅列表】去【添加订阅】,
6、最后一步,点击【客户端】,即可导入Clash等软件中使用。
7、此功能集成到〔X-Panel面板〕中,是为了保证安全,不会造成链接泄露。</p>
<h3>四、如何保护自己的IP不被墙被封?</h3>
<p>1、使用的代理协议要安全,加密是必备,推荐使用vless+reality+vision协议组合,
2、因为有时节点会共享,在不同的地区,多个省份之间不要共同连接同一个IP,
3、连接同一个IP就算了,不要同一个端口,不要同IP+同端口到处漫游,要分开,
4、同一台VPS,不要在一天内一直大流量去下载东西使用,不要流量过高要切换,
5、创建【入站协议】的时候,尽量用【高位端口】,比如40000--65000之间的端口号。
提醒:为什么在特殊时期,比如:两会,春节等被封得最严重最惨?
尼玛同一个IP+同一个端口号,多个省份去漫游,跟开飞机场一样!不封你,封谁的IP和端口?
总结:不要多终端/多省份/多个朋友/共同使用同一个IP和端口号!使用〔X-Panel面板〕多创建几个【入站】,
多做几条备用,各用各的!各行其道才比较安全!GFW的思维模式是干掉机场,机场的特征个人用户不要去沾染,自然IP就保护好了。</p>
<h3>五、检测IP纯净度的方法:</h3>
<p>网址:<a href="https://scamalytics.com/">https://scamalytics.com/</a>,输入IP进行检测,看【欺诈分数】,分数越高IP越脏。</p>
<h3>六、常见的翻墙软件/工具:</h3>
<ol>
<p>1、Windows系统v2rayN<a href="https://github.com/2dust/v2rayN">https://github.com/2dust/v2rayN</a></p>
<p>2、安卓手机版【v2rayNG】:<a href="https://github.com/2dust/v2rayNG">https://github.com/2dust/v2rayNG</a></p>
<p>3、苹果手机IOS【小火箭】:<a href="https://apple02.com/">https://apple02.com/</a></p>
<p>4、苹果MacOS电脑【Clash Verge】:<a href="https://github.com/clash-verge-rev/clash-verge-rev/releases">https://github.com/clash-verge-rev/clash-verge-rev/releases</a>或v2rayN<a href="https://github.com/2dust/v2rayN">https://github.com/2dust/v2rayN</a></p>
</ol>
<h3>七、查看节点【指定端口】的网络连接数/命令:</h3>
<p>netstat -ntu | grep :节点端口 | grep ESTABLISHED | awk '{print $5}'</p>
<h3>八、用〔X-Panel面板〕如何实现【自己偷自己】?</h3>
<p>其实很简单,只要你为面板设置了证书,
开启了HTTPS登录,就可以将〔X-Panel面板〕自身作为web server
无需Nginx等,这里给一个示例:
其中目标网站(Dest)请填写面板监听端口,
可选域名(SNI)填写面板登录域名,
如果您使用其他web server(如nginx)等,
将目标网站改为对应监听端口也可。
需要说明的是,如果您处于白名单地区,自己“偷”自己并不适合你;其次,可选域名一项实际上可以填写任意SNI,只要客户端保持一致即可,不过并不推荐这样做。</p>
<h3>九、【接码】网站:</h3>
<p>网址:<a href="https://sms-activate.org/cn">https://sms-activate.org/cn</a>,直接注册账号购买。</p>
<h3>十、一些MJJ经常逛的网站和群组:</h3>
<ol>
<p>1、NodeSeek论坛:<a href="https://www.nodeseek.com/">https://www.nodeseek.com/</a></p>
<p>2、V2EX论坛:<a href="https://www.v2ex.com/">https://www.v2ex.com/</a></p>
<p>3、搬瓦工TG群:<a href="https://t.me/BWHOfficial">https://t.me/BWHOfficial</a></p>
<p>4、Xray的官方群:<a href="https://t.me/projectXray">https://t.me/projectXray</a></p>
<p>5、Dmit交流群:<a href="https://t.me/DmitChat">https://t.me/DmitChat</a></p>
<p>6、白丝云用户群:<a href="https://t.me/+VHZLKELTQyzPNgOV">https://t.me/+VHZLKELTQyzPNgOV</a></p>
<p>7、NameSilo域名注册:<a href="https://www.namesilo.com/">https://www.namesilo.com/</a></p>
</ol>
<h3>十一、若此项目对你有帮助,你正想购买VPS的话,可以走一下我的AFF:</h3>
<ol>
<p>1、搬瓦工GIA线路:<a href="https://bandwagonhost.com/aff.php?aff=75015">https://bandwagonhost.com/aff.php?aff=75015</a></p>
<p>2、Dmit高端GIA<a href="https://www.dmit.io/aff.php?aff=9326">https://www.dmit.io/aff.php?aff=9326</a></p>
<p>3、白丝云【4837】:<a href="https://cloudsilk.io/aff.php?aff=706">https://cloudsilk.io/aff.php?aff=706</a></p>
</ol>
<h3>十二、项目〔声明和注意〕</h3>
<ol>
<p>1、声明: 此项目仅供个人学习、交流使用,请遵守当地法律法规,勿用于非法用途;请勿用于生产环境;</a></p>
<p>2、注意: 在使用此项目和〔教程〕过程中,若因违反以上声明使用规则而产生的一切后果由使用者自负。</a></p>
</ol>
</div>
</body>
</html>
+9 -3
View File
@@ -1,4 +1,10 @@
{{define "settings/panel/general"}}
<style>
.red-placeholder input::-webkit-input-placeholder { color: red !important; }
.red-placeholder input::-moz-placeholder { color: red !important; }
.red-placeholder input:-ms-input-placeholder { color: red !important; }
.red-placeholder input::placeholder { color: red !important; }
</style>
<a-collapse default-active-key="1">
<a-collapse-panel key="1" header='{{ i18n "pages.xray.generalConfigs"}}'>
<a-setting-list-item paddings="small">
@@ -97,14 +103,14 @@
<template #title>{{ i18n "pages.settings.publicKeyPath"}}</template>
<template #description>{{ i18n "pages.settings.publicKeyPathDesc"}}</template>
<template #control>
<a-input type="text" v-model="allSetting.webCertFile"></a-input>
<a-input type="text" v-model="allSetting.webCertFile" placeholder="/root/cert/域名/fullchain.pem" class="red-placeholder"></a-input>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.privateKeyPath"}}</template>
<template #description>{{ i18n "pages.settings.privateKeyPathDesc"}}</template>
<template #control>
<a-input type="text" v-model="allSetting.webKeyFile"></a-input>
<a-input type="text" v-model="allSetting.webKeyFile" placeholder="/root/cert/域名/privkey.pem" class="red-placeholder"></a-input>
</template>
</a-setting-list-item>
</a-collapse-panel>
@@ -146,4 +152,4 @@
</a-setting-list-item>
</a-collapse-panel>
</a-collapse>
{{end}}
{{end}}
+317
View File
@@ -10,13 +10,330 @@ import (
"regexp"
"sort"
"time"
"sync"
"crypto/rand"
"encoding/hex"
"x-ui/database"
"x-ui/database/model"
"x-ui/logger"
"x-ui/xray"
"x-ui/web/service"
)
// =================================================================
// 中文注释: 以下是用于实现设备限制功能的核心代码
// =================================================================
// ActiveClientIPs 中文注释: 用于在内存中跟踪每个用户的活跃IP (TTL机制)
// 结构: map[用户email] -> map[IP地址] -> 最后活跃时间
var ActiveClientIPs = make(map[string]map[string]time.Time)
var activeClientsLock sync.RWMutex
// ClientStatus 中文注释: 用于跟踪每个用户的状态(是否因为设备超限而被禁用)
// 结构: map[用户email] -> 是否被禁用(true/false)
var ClientStatus = make(map[string]bool)
var clientStatusLock sync.RWMutex
// CheckDeviceLimitJob 中文注释: 这是我们的设备限制任务的结构体
type CheckDeviceLimitJob struct {
inboundService service.InboundService
xrayService *service.XrayService
// 中文注释: 新增 xrayApi 字段,用于持有 Xray API 客户端实例
xrayApi xray.XrayAPI
// lastPosition 中文注释: 用于记录上次读取 access.log 的位置,避免重复读取
lastPosition int64
}
// RandomUUID 中文注释: 新增一个辅助函数,用于生成一个随机的 UUID
func RandomUUID() string {
uuid := make([]byte, 16)
rand.Read(uuid)
uuid[6] = (uuid[6] & 0x0f) | 0x40
uuid[8] = (uuid[8] & 0x3f) | 0x80
return hex.EncodeToString(uuid[0:4]) + "-" + hex.EncodeToString(uuid[4:6]) + "-" + hex.EncodeToString(uuid[6:8]) + "-" + hex.EncodeToString(uuid[8:10]) + "-" + hex.EncodeToString(uuid[10:16])
}
// NewCheckDeviceLimitJob 中文注释: 创建一个新的任务实例
func NewCheckDeviceLimitJob(xrayService *service.XrayService) *CheckDeviceLimitJob {
return &CheckDeviceLimitJob{
xrayService: xrayService,
// 中文注释: 初始化 xrayApi 字段
xrayApi: xray.XrayAPI{},
}
}
// Run 中文注释: 定时任务的主函数,每次定时器触发时执行
func (j *CheckDeviceLimitJob) Run() {
// 中文注释: 检查 xray 是否正在运行,如果xray没运行,则无需执行此任务
if !j.xrayService.IsXrayRunning() {
return
}
// 1. 清理过期的IP
j.cleanupExpiredIPs()
// 2. 解析新的日志并更新IP列表
j.parseAccessLog()
// 3. 检查所有用户的设备限制状态
j.checkAllClientsLimit()
}
// cleanupExpiredIPs 中文注释: 清理长时间不活跃的IP
func (j *CheckDeviceLimitJob) cleanupExpiredIPs() {
activeClientsLock.Lock()
defer activeClientsLock.Unlock()
now := time.Now()
// 中文注释: 活跃判断窗口(TTL): 近3分钟内出现过就算“活跃”
const activeTTL = 3 * time.Minute
for email, ips := range ActiveClientIPs {
for ip, lastSeen := range ips {
// 中文注释: 如果一个IP超过3分钟没有新的连接日志,我们就认为它已经下线
if now.Sub(lastSeen) > activeTTL {
delete(ActiveClientIPs[email], ip)
}
}
// 中文注释: 如果一个用户的所有IP都下线了,就从大Map中移除这个用户,节省内存
if len(ActiveClientIPs[email]) == 0 {
delete(ActiveClientIPs, email)
}
}
}
// parseAccessLog 中文注释: 解析 xray access log 来获取最新的用户IP信息
func (j *CheckDeviceLimitJob) parseAccessLog() {
logPath, err := xray.GetAccessLogPath()
if err != nil || logPath == "none" || logPath == "" {
return
}
file, err := os.Open(logPath)
if err != nil {
return
}
defer file.Close()
// 中文注释: 移动到上次读取结束的位置,实现增量读取
file.Seek(j.lastPosition, 0)
scanner := bufio.NewScanner(file)
// 中文注释: 使用正则表达式从日志行中提取 email 和 IP
emailRegex := regexp.MustCompile(`email: ([^ ]+)`)
ipRegex := regexp.MustCompile(`from (?:tcp:|udp:)?\[?([0-9a-fA-F\.:]+)\]?:\d+ accepted`)
activeClientsLock.Lock()
defer activeClientsLock.Unlock()
now := time.Now()
for scanner.Scan() {
line := scanner.Text()
emailMatch := emailRegex.FindStringSubmatch(line)
ipMatch := ipRegex.FindStringSubmatch(line)
if len(emailMatch) > 1 && len(ipMatch) > 1 {
email := emailMatch[1]
ip := ipMatch[1]
if ip == "127.0.0.1" || ip == "::1" {
continue
}
if _, ok := ActiveClientIPs[email]; !ok {
ActiveClientIPs[email] = make(map[string]time.Time)
}
ActiveClientIPs[email][ip] = now
}
}
currentPosition, err := file.Seek(0, os.SEEK_END)
if err == nil {
if currentPosition < j.lastPosition {
j.lastPosition = 0
} else {
j.lastPosition = currentPosition
}
}
}
// checkAllClientsLimit 中文注释: 核心功能,检查所有用户,对超限的执行封禁,对恢复的执行解封
func (j *CheckDeviceLimitJob) checkAllClientsLimit() {
db := database.GetDB()
var inbounds []*model.Inbound
// 中文注释: 这里仅查询启用了设备限制(device_limit > 0)并且自身是开启状态的入站规则
db.Where("device_limit > 0 AND enable = ?", true).Find(&inbounds)
if len(inbounds) == 0 {
return
}
// 中文注释: 获取 API 端口。如果端口为0 (说明Xray未完全启动或有问题),则直接返回
apiPort := j.xrayService.GetApiPort()
if apiPort == 0 {
return
}
// 中文注释: 使用获取到的端口号初始化 API 客户端
j.xrayApi.Init(apiPort)
defer j.xrayApi.Close()
// 中文注释: 优化 - 在一次循环中同时获取 tag 和 protocol
inboundInfoMap := make(map[int]struct {
Limit int
Tag string
Protocol model.Protocol
})
for _, inbound := range inbounds {
inboundInfoMap[inbound.Id] = struct {
Limit int
Tag string
Protocol model.Protocol
}{Limit: inbound.DeviceLimit, Tag: inbound.Tag, Protocol: inbound.Protocol}
}
activeClientsLock.RLock()
clientStatusLock.Lock()
defer activeClientsLock.RUnlock()
defer clientStatusLock.Unlock()
// 第一步: 处理当前在线的用户
for email, ips := range ActiveClientIPs {
traffic, err := j.inboundService.GetClientTrafficByEmail(email)
if err != nil || traffic == nil {
continue
}
info, ok := inboundInfoMap[traffic.InboundId]
if !ok || info.Limit <= 0 {
continue
}
isBanned := ClientStatus[email]
activeIPCount := len(ips)
// 调用封禁函数
if activeIPCount > info.Limit && !isBanned {
// 中文注释: 调用封禁函数时,传入当前的IP数用于记录日志
j.banUser(email, activeIPCount, &info)
}
// 调用解封函数
if activeIPCount <= info.Limit && isBanned {
// 中文注释: 调用解封函数时,传入当前的IP数用于记录日志
j.unbanUser(email, activeIPCount, &info)
}
}
// 第二步: 专门处理那些“已被封禁”但“已不在线”的用户,为他们解封
for email, isBanned := range ClientStatus {
if !isBanned {
continue
}
if _, online := ActiveClientIPs[email]; !online {
traffic, err := j.inboundService.GetClientTrafficByEmail(email)
if err != nil || traffic == nil {
continue
}
info, ok := inboundInfoMap[traffic.InboundId]
if !ok {
continue
}
logger.Infof("已封禁用户 %s 已完全下线,执行解封操作。", email)
// 调用解封函数,这种情况下:活跃IP数为0,我们直接传入0用于记录日志
j.unbanUser(email, 0, &info)
}
}
}
// banUser 中文注释: 封装的封禁用户函数;IP数量超限,且用户当前未被封禁 -> 执行封禁 (UUID 替换)
func (j *CheckDeviceLimitJob) banUser(email string, activeIPCount int, info *struct {
Limit int
Tag string
Protocol model.Protocol
}) {
// =================================================================
// 这一行代码是整个解封逻辑的灵魂!
// GetClientByEmail 函数会去查询您的数据库 (x-ui.db),
// 找到 `inbounds` 表,解析其中的 `settings` 字段,并从中去,
// 读取出您最初设置的、最原始、最正确的用户信息(包括最原始的UUID),
// 然后把它赋值给 `client` 这个变量;此时,`client` 变量就持有了那个“老链接”的正确原始 UUID。
// =================================================================
_, client, err := j.inboundService.GetClientByEmail(email)
if err != nil || client == nil {
return
}
logger.Infof("〔设备限制〕超限:用户 %s. 限制: %d, 当前活跃: %d. 执行封禁掐网。", email, info.Limit, activeIPCount)
// 中文注释: 步骤一:先从 Xray-Core 中删除该用户。
j.xrayApi.RemoveUser(info.Tag, email)
// =================================================================
// 中文注释: 增加 5000 毫秒延时,解决竞态条件问题
time.Sleep(5000 * time.Millisecond)
// =================================================================
// 中文注释: 创建一个带有随机UUID/Password的临时客户端配置用于“封禁”
tempClient := *client
// 适用于 VMess/VLESS
if tempClient.ID != "" { tempClient.ID = RandomUUID() }
// 适用于 Trojan/Shadowsocks/Socks
if tempClient.Password != "" { tempClient.Password = RandomUUID() }
var clientMap map[string]interface{}
clientJson, _ := json.Marshal(tempClient)
json.Unmarshal(clientJson, &clientMap)
// 中文注释: 步骤二:将这个带有错误UUID/Password的临时用户添加回去。
// 客户端持有的还是旧的UUID,自然就无法通过验证,从而达到了“封禁”的效果。
err = j.xrayApi.AddUser(string(info.Protocol), info.Tag, clientMap)
if err != nil {
logger.Warningf("通过API封禁用户 %s 失败: %v", email, err)
} else {
// 中文注释: 封禁成功后,在内存中标记该用户为“已封禁”状态。
ClientStatus[email] = true
}
}
// unbanUser 中文注释: 封装的解封用户函数;IP数量已恢复正常,但用户处于封禁状态 -> 执行解封 (恢复原始 UUID)
func (j *CheckDeviceLimitJob) unbanUser(email string, activeIPCount int, info *struct {
Limit int
Tag string
Protocol model.Protocol
}) {
_, client, err := j.inboundService.GetClientByEmail(email)
if err != nil || client == nil {
return
}
logger.Infof("〔设备数量〕已恢复:用户 %s. 限制: %d, 当前活跃: %d. 执行解封/恢复用户。", email, info.Limit, activeIPCount)
// 中文注释: 步骤一:先从 Xray-Core 中删除用于“封禁”的那个临时用户。
j.xrayApi.RemoveUser(info.Tag, email)
// =================================================================
// 中文注释: 同样增加 5000 毫秒延时,确保解封操作的稳定性
time.Sleep(5000 * time.Millisecond)
// =================================================================
var clientMap map[string]interface{}
clientJson, _ := json.Marshal(client)
json.Unmarshal(clientJson, &clientMap)
// 中文注释: 步骤二:将数据库中原始的、正确的用户信息重新添加回 Xray-Core,从而实现“解封”。
err = j.xrayApi.AddUser(string(info.Protocol), info.Tag, clientMap)
if err != nil {
logger.Warningf("通过API恢复用户 %s 失败: %v", email, err)
} else {
// 中文注释: 解封成功后,从内存中移除该用户的“已封禁”状态标记。
delete(ClientStatus, email)
}
}
type CheckClientIpJob struct {
lastClear int64
disAllowedIps []string
+4 -4
View File
@@ -1,9 +1,9 @@
{
"log": {
"access": "none",
"dnsLog": false,
"error": "",
"loglevel": "warning",
"access": "./access.log",
"dnsLog": true,
"error": "./error.log",
"loglevel": "debug",
"maskAddress": ""
},
"api": {
+2
View File
@@ -409,6 +409,8 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
oldInbound.Remark = inbound.Remark
oldInbound.Enable = inbound.Enable
oldInbound.ExpiryTime = inbound.ExpiryTime
// 中文注释:确保在更新数据时,将前端传来的 deviceLimit 值赋给从数据库中读出的旧对象。
oldInbound.DeviceLimit = inbound.DeviceLimit
oldInbound.Listen = inbound.Listen
oldInbound.Port = inbound.Port
oldInbound.Protocol = inbound.Protocol
+1 -1
View File
@@ -47,7 +47,7 @@ var defaultValueMap = map[string]string{
"tgBotBackup": "false",
"tgBotLoginNotify": "true",
"tgCpu": "80",
"tgLang": "en-US",
"tgLang": "zh-CN",
"twoFactorEnable": "false",
"twoFactorToken": "",
"subEnable": "false",
+14
View File
@@ -26,10 +26,24 @@ type XrayService struct {
xrayAPI xray.XrayAPI
}
// IsXrayRunning 检查 Xray 是否正在运行
func (s *XrayService) IsXrayRunning() bool {
return p != nil && p.IsRunning()
}
// 中文注释:
// 新增 GetApiPort 函数。
// 这个函数的作用是安全地返回当前 Xray 进程正在监听的 API 端口号。
// 如果 Xray 没有运行 (p == nil),则返回 0。
// 我们的后台任务将调用这个函数来获取端口号。
func (s *XrayService) GetApiPort() int {
if p == nil {
return 0
}
return p.GetAPIPort()
}
func (s *XrayService) GetXrayErr() error {
if p == nil {
return nil
+1 -1
View File
@@ -615,7 +615,7 @@
"report" = "🕰 التقارير المجدولة: {{ .RunTime }}\r\n"
"datetime" = "⏰ التاريخ والوقت: {{ .DateTime }}\r\n"
"hostname" = "💻 السيرفر: {{ .Hostname }}\r\n"
"version" = "🚀 نسخة 3X-UI: {{ .Version }}\r\n"
"version" = "🚀 X-Panel: {{ .Version }}\r\n"
"xrayVersion" = "📡 نسخة Xray: {{ .XrayVersion }}\r\n"
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
+5 -1
View File
@@ -82,6 +82,7 @@
"xray" = "Xray Configs"
"logout" = "Log Out"
"link" = "Manage"
"navigation" = "navigation"
[pages.login]
"hello" = "Hello"
@@ -454,6 +455,9 @@
"statsOutboundDownlink" = "Outbound Download Statistics"
"statsOutboundDownlinkDesc" = "Enables the statistics collection for downstream traffic of all outbound proxies."
[pages.navigation]
"title" = "navigation"
[pages.xray.rules]
"first" = "First"
"last" = "Last"
@@ -615,7 +619,7 @@
"report" = "🕰 Scheduled Reports: {{ .RunTime }}\r\n"
"datetime" = "⏰ Date&Time: {{ .DateTime }}\r\n"
"hostname" = "💻 Host: {{ .Hostname }}\r\n"
"version" = "🚀 3X-UI Version: {{ .Version }}\r\n"
"version" = "🚀 X-Panel Version: {{ .Version }}\r\n"
"xrayVersion" = "📡 Xray Version: {{ .XrayVersion }}\r\n"
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
+2 -1
View File
@@ -615,7 +615,7 @@
"report" = "🕰 Informes programados: {{ .RunTime }}\r\n"
"datetime" = "⏰ Fecha y Hora: {{ .DateTime }}\r\n"
"hostname" = "💻 Nombre del Host: {{ .Hostname }}\r\n"
"version" = "🚀 Versión de X-UI: {{ .Version }}\r\n"
"version" = "🚀 Versión de X-Panel: {{ .Version }}\r\n"
"xrayVersion" = "📡 Versión de Xray: {{ .XrayVersion }}\r\n"
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
@@ -737,3 +737,4 @@
"askToAddUserId" = "¡No se encuentra su configuración!\r\nPor favor, pídale a su administrador que use su ChatID de usuario de Telegram en su(s) configuración(es).\r\n\r\nSu ChatID de usuario: <code>{{ .TgUserID }}</code>"
"chooseClient" = "Elige un Cliente para Inbound {{ .Inbound }}"
"chooseInbound" = "Elige un Inbound"
+1 -1
View File
@@ -615,7 +615,7 @@
"report" = "🕰 Laporan Terjadwal: {{ .RunTime }}\r\n"
"datetime" = "⏰ Tanggal & Waktu: {{ .DateTime }}\r\n"
"hostname" = "💻 Host: {{ .Hostname }}\r\n"
"version" = "🚀 Versi 3X-UI: {{ .Version }}\r\n"
"version" = "🚀 Versi X-Panel: {{ .Version }}\r\n"
"xrayVersion" = "📡 Versi Xray: {{ .XrayVersion }}\r\n"
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
+1 -1
View File
@@ -615,7 +615,7 @@
"report" = "🕰 定期報告:{{ .RunTime }}\r\n"
"datetime" = "⏰ 日時:{{ .DateTime }}\r\n"
"hostname" = "💻 ホスト名:{{ .Hostname }}\r\n"
"version" = "🚀 X-UI バージョン:{{ .Version }}\r\n"
"version" = "🚀 X-Panel バージョン:{{ .Version }}\r\n"
"xrayVersion" = "📡 Xray バージョン: {{ .XrayVersion }}\r\n"
"ipv6" = "🌐 IPv6{{ .IPv6 }}\r\n"
"ipv4" = "🌐 IPv4{{ .IPv4 }}\r\n"
+1 -1
View File
@@ -615,7 +615,7 @@
"report" = "🕰 Relatórios agendados: {{ .RunTime }}\r\n"
"datetime" = "⏰ Data&Hora: {{ .DateTime }}\r\n"
"hostname" = "💻 Host: {{ .Hostname }}\r\n"
"version" = "🚀 Versão 3X-UI: {{ .Version }}\r\n"
"version" = "🚀 Versão X-Panel: {{ .Version }}\r\n"
"xrayVersion" = "📡 Versão Xray: {{ .XrayVersion }}\r\n"
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
+1 -1
View File
@@ -615,7 +615,7 @@
"report" = "🕰 Запланированные отчеты: {{ .RunTime }}\r\n"
"datetime" = "⏰ Дата и время: {{ .DateTime }}\r\n"
"hostname" = "💻 Имя хоста: {{ .Hostname }}\r\n"
"version" = "🚀 Версия X-UI: {{ .Version }}\r\n"
"version" = "🚀 Версия X-Panel: {{ .Version }}\r\n"
"xrayVersion" = "📡 Версия Xray: {{ .XrayVersion }}\r\n"
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
+1 -1
View File
@@ -615,7 +615,7 @@
"report" = "🕰 Planlanmış Raporlar: {{ .RunTime }}\r\n"
"datetime" = "⏰ Tarih&Zaman: {{ .DateTime }}\r\n"
"hostname" = "💻 Sunucu: {{ .Hostname }}\r\n"
"version" = "🚀 3X-UI Sürümü: {{ .Version }}\r\n"
"version" = "🚀 X-Panel Sürümü: {{ .Version }}\r\n"
"xrayVersion" = "📡 Xray Sürümü: {{ .XrayVersion }}\r\n"
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
+1 -1
View File
@@ -615,7 +615,7 @@
"report" = "🕰 Заплановані звіти: {{ .RunTime }}\r\n"
"datetime" = "⏰ Дата й час: {{ .DateTime }}\r\n"
"hostname" = "💻 Хост: {{ .Hostname }}\r\n"
"version" = "🚀 3X-UI Версія: {{ .Version }}\r\n"
"version" = "🚀 X-Panel Версія: {{ .Version }}\r\n"
"xrayVersion" = "📡 Xray Версія: {{ .XrayVersion }}\r\n"
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
+2 -1
View File
@@ -615,7 +615,7 @@
"report" = "🕰 Báo cáo định kỳ: {{ .RunTime }}\r\n"
"datetime" = "⏰ Ngày-Giờ: {{ .DateTime }}\r\n"
"hostname" = "💻 Tên máy chủ: {{ .Hostname }}\r\n"
"version" = "🚀 Phiên bản X-UI: {{ .Version }}\r\n"
"version" = "🚀 Phiên bản X-Panel: {{ .Version }}\r\n"
"xrayVersion" = "📡 Phiên bản Xray: {{ .XrayVersion }}\r\n"
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
@@ -737,3 +737,4 @@
"askToAddUserId" = "Cấu hình của bạn không được tìm thấy!\r\nVui lòng yêu cầu Quản trị viên sử dụng ID người dùng telegram của bạn trong cấu hình của bạn.\r\n\r\nID người dùng của bạn: <code>{{ .TgUserID }}</code>"
"chooseClient" = "Chọn một Khách hàng cho Inbound {{ .Inbound }}"
"chooseInbound" = "Chọn một Inbound"
+61 -34
View File
@@ -28,7 +28,7 @@
"edit" = "编辑"
"delete" = "删除"
"reset" = "重置"
"noData" = "无数据"
"noData" = "无数据"
"copySuccess" = "复制成功"
"sure" = "确定"
"encryption" = "加密"
@@ -55,17 +55,18 @@
"install" = "安装"
"clients" = "客户端"
"usage" = "使用情况"
"secretToken" = "安全密钥"
"twoFactorCode" = "代码"
"remained" = "剩余"
"security" = "安全"
"secAlertTitle" = "安全警报"
"secAlertSsl" = "此连接不安全在激活 TLS 进行数据保护之前,请勿输入敏感信息。"
"secAlertConf" = "某些设置易受攻击建议加强安全协议以防止潜在漏洞。"
"secAlertSSL" = "面板缺少安全连接请安装 TLS 证书以保护数据安全。"
"secAlertPanelPort" = "面板默认端口存在安全风险请配置随机端口或特定端口。"
"secAlertPanelURI" = "面板默认 URI 路径不安全请配置复杂的 URI 路径。"
"secAlertSubURI" = "订阅默认 URI 路径不安全请配置复杂的 URI 路径。"
"secAlertSubJsonURI" = "订阅 JSON 默认 URI 路径不安全请配置复杂的 URI 路径。"
"secAlertSsl" = "此连接不安全在激活 TLS 进行数据保护之前,请勿输入敏感信息。"
"secAlertConf" = "某些设置易受攻击建议加强安全协议以防止潜在漏洞。"
"secAlertSSL" = "面板缺少安全连接请安装 TLS 证书以保护数据安全。"
"secAlertPanelPort" = "面板默认端口存在安全风险请配置随机端口或特定端口。"
"secAlertPanelURI" = "面板默认 URI 路径不安全请配置复杂的 URI 路径。"
"secAlertSubURI" = "订阅默认 URI 路径不安全请配置复杂的 URI 路径。"
"secAlertSubJsonURI" = "订阅 JSON 默认 URI 路径不安全请配置复杂的 URI 路径。"
"emptyDnsDesc" = "未添加DNS服务器。"
"emptyFakeDnsDesc" = "未添加Fake DNS服务器。"
"emptyBalancersDesc" = "未添加负载均衡器。"
@@ -75,13 +76,14 @@
[menu]
"theme" = "主题"
"dark" = "暗色"
"ultraDark" = "超暗色"
"ultraDark" = "色"
"dashboard" = "系统状态"
"inbounds" = "入站列表"
"settings" = "面板设置"
"xray" = "Xray 设置"
"xray" = "Xray设置"
"logout" = "退出登录"
"link" = "管理"
"navigation" = "实用导航"
[pages.login]
"hello" = "你好"
@@ -103,6 +105,7 @@
"swap" = "交换分区"
"storage" = "存储"
"memory" = "内存"
"hard" = "磁盘"
"threads" = "线程"
"xrayStatus" = "Xray"
"stopXray" = "停止"
@@ -139,7 +142,7 @@
"dontRefresh" = "安装中,请勿刷新此页面"
"logs" = "日志"
"config" = "配置"
"backup" = "备份"
"backup" = "备份和恢复"
"backupTitle" = "备份和恢复数据库"
"exportDatabase" = "备份"
"exportDatabaseDesc" = "点击下载包含当前数据库备份的 .db 文件到您的设备。"
@@ -175,6 +178,8 @@
"generalActions" = "通用操作"
"autoRefresh" = "自动刷新"
"autoRefreshInterval" = "间隔"
"create" = "添加"
"update" = "修改"
"modifyInbound" = "修改入站"
"deleteInbound" = "删除入站"
"deleteInboundContent" = "确定要删除入站吗?"
@@ -196,7 +201,7 @@
"publicKey" = "公钥"
"privatekey" = "私钥"
"clickOnQRcode" = "点击二维码复制"
"client" = "客户"
"client" = "客户/用户"
"export" = "导出链接"
"clone" = "克隆"
"cloneInbound" = "克隆"
@@ -215,14 +220,14 @@
"delDepletedClientsTitle" = "删除流量耗尽的客户端"
"delDepletedClientsContent" = "确定要删除所有流量耗尽的客户端吗?"
"email" = "电子邮件"
"emailDesc" = "电子邮件必须完全唯一"
"emailDesc" = "电子邮件必须确保唯一"
"IPLimit" = "IP 限制"
"IPLimitDesc" = "如果数量超过设置值,则禁用入站流量。(0 = 禁用)"
"IPLimitlog" = "IP 日志"
"IPLimitlogDesc" = "IP 历史日志(要启用被禁用的入站流量,请清除日志)"
"IPLimitlogclear" = "清除日志"
"setDefaultCert" = "从面板设置证书"
"telegramDesc" = "请提供Telegram聊天ID。(在机器人中使用'/id'命令)或(@userinfobot"
"telegramDesc" = "请提供Telegram聊天ID。(在机器人中使用'/id'命令或跟@userinfobot机器人对话获取)"
"subscriptionDesc" = "要找到你的订阅 URL,请导航到“详细信息”。此外,你可以为多个客户端使用相同的名称。"
"info" = "信息"
"same" = "相同"
@@ -230,6 +235,12 @@
"exportInbound" = "导出入站规则"
"import"="导入"
"importInbound" = "导入入站规则"
"reviewTitle"="请确认以下配置信息"
"neverExpire"="永不过期"
"reviewHint"="如需修改请返回上一步"
"unlimited"="无限制"
"deviceLimit"="设备限制"
[pages.client]
"add" = "添加客户端"
@@ -244,7 +255,7 @@
"prefix" = "前缀"
"postfix" = "后缀"
"delayedStart" = "首次使用后开始"
"expireDays" = "期间"
"expireDays" = "到期天数/期间"
"days" = "天"
"renew" = "自动续订"
"renewDesc" = "到期后自动续订。(0 = 禁用)(单位: 天)"
@@ -294,7 +305,7 @@
"resetDefaultConfig" = "重置为默认配置"
"panelSettings" = "常规"
"securitySettings" = "安全设定"
"TGBotSettings" = "Telegram 机器人配置"
"TGBotSettings" = "Telegram机器人配置"
"panelListeningIP" = "面板监听 IP"
"panelListeningIPDesc" = "默认留空监听所有 IP"
"panelListeningDomain" = "面板监听域名"
@@ -302,10 +313,12 @@
"panelPort" = "面板监听端口"
"panelPortDesc" = "重启面板生效"
"publicKeyPath" = "面板证书公钥文件路径"
"publicKeyPathDesc" = "填写一个 '/' 开头的绝对路径"
"DefaultpublicKeyPath" = "/root/.acme.sh/域名_ecc/域名.cer"
"publicKeyPathDesc" = "填写一个 '/' 开头的绝对路径,〔acme方式〕请自行在填入时修改域名"
"privateKeyPath" = "面板证书密钥文件路径"
"privateKeyPathDesc" = "填写一个 '/' 开头的绝对路径"
"panelUrlPath" = "面板 url 根路径"
"DefaultprivateKeyPath" = "/root/.acme.sh/域名_ecc/域名.key"
"privateKeyPathDesc" = "填写一个 '/' 开头的绝对路径,〔acme方式〕请自行在填入时修改域名"
"panelUrlPath" = "面板登录访问路径"
"panelUrlPathDesc" = "必须以 '/' 开头,以 '/' 结尾"
"pageSize" = "分页大小"
"pageSizeDesc" = "定义入站表的页面大小。设置 0 表示禁用"
@@ -321,7 +334,7 @@
"telegramBotEnable" = "启用 Telegram 机器人"
"telegramBotEnableDesc" = "启用 Telegram 机器人功能"
"telegramToken" = "Telegram 机器人令牌(token"
"telegramTokenDesc" = " '@BotFather' 获取的 Telegram 机器人令牌"
"telegramTokenDesc" = " '@BotFather' 对话获取的 Telegram 机器人令牌"
"telegramProxy" = "SOCKS5 Proxy"
"telegramProxyDesc" = "启用 SOCKS5 代理连接到 Telegram(根据指南调整设置)"
"telegramAPIServer" = "Telegram API Server"
@@ -454,13 +467,16 @@
"statsOutboundDownlink" = "出站下载统计"
"statsOutboundDownlinkDesc" = "启用所有出站代理的下行流量统计收集。"
[pages.navigation]
"title" = "实用导航"
[pages.xray.rules]
"first" = "置顶"
"last" = "置底"
"up" = "向上"
"down" = "向下"
"source" = "来源"
"dest" = "目地址"
"dest" = "目地址"
"inbound" = "入站"
"outbound" = "出站"
"balancer" = "负载均衡"
@@ -468,6 +484,18 @@
"add" = "添加规则"
"edit" = "编辑规则"
"useComma" = "逗号分隔的项目"
"DomainMatcher" = "域匹配类型"
"SourceIPs" = "源IP"
"SourcePort" = "源端口"
"Network" = "网络类型"
"Protocol" = "传输协议"
"Attributes" = "属性"
"Domain" = "域地址"
"User" = "用户"
"Port" = "端口"
"InboundTag" = "入站 Tag"
"OutboundTag" = "出站 Tag"
"BalancerTag" = "负载均衡 Tag"
[pages.xray.outbound]
"addOutbound" = "添加出站"
@@ -568,27 +596,26 @@
[tgbot]
"keyboardClosed" = "❌ 自定义键盘已关闭!"
"noResult" = "❗ 没有结果!"
"noQuery" = "❌ 未找到查询!请再次使用命令!"
"noQuery" = "❌ 未找到查询!请重新使用命令!"
"wentWrong" = "❌ 出了点问题!"
"noIpRecord" = "❗ 没有IP记录!"
"noInbounds" = "❗ 找到入站!"
"unlimited" = "♾ 无限(重置)"
"noIpRecord" = "❗ 没有 IP 记录!"
"noInbounds" = "❗ 没有找到入站连接"
"unlimited" = "♾ 无限"
"add" = "添加"
"month" = "月"
"months" = "月"
"day" = "天"
"days" = "天"
"hours" = "小时"
"minutes" = "分钟"
"unknown" = "未知"
"inbounds" = "入站"
"inbounds" = "入站连接"
"clients" = "客户端"
"offline" = "🔴 离线"
"online" = "🟢 在线"
[tgbot.commands]
"unknown" = "❗ 未知命令"
"pleaseChoose" = "👇 请选择\r\n"
"pleaseChoose" = "👇请〔按照需求〕选择下方按钮 \r\n"
"help" = "🤖 欢迎使用本机器人!它旨在为您提供来自服务器的特定数据,并允许您进行必要的修改。\r\n\r\n"
"start" = "👋 你好,<i>{{ .Firstname }}</i>。\r\n"
"welcome" = "🤖 欢迎来到 <b>{{ .Hostname }}</b> 管理机器人。\r\n"
@@ -615,8 +642,8 @@
"report" = "🕰 定时报告:{{ .RunTime }}\r\n"
"datetime" = "⏰ 日期时间:{{ .DateTime }}\r\n"
"hostname" = "💻 主机名:{{ .Hostname }}\r\n"
"version" = "🚀 X-UI 版本:{{ .Version }}\r\n"
"xrayVersion" = "📡 Xray 版本: {{ .XrayVersion }}\r\n"
"version" = "🚀 X-Panel 版本:v{{ .Version }}\r\n"
"xrayVersion" = "📡 Xray 版本v{{ .XrayVersion }}\r\n"
"ipv6" = "🌐 IPv6{{ .IPv6 }}\r\n"
"ipv4" = "🌐 IPv4{{ .IPv4 }}\r\n"
"ip" = "🌐 IP{{ .IP }}\r\n"
@@ -638,7 +665,7 @@
"active" = "💡 激活:{{ .Enable }}\r\n"
"enabled" = "🚨 已启用:{{ .Enable }}\r\n"
"online" = "🌐 连接状态:{{ .Status }}\r\n"
"email" = "📧 邮箱:{{ .Email }}\r\n"
"email" = "📧 邮箱(用户){{ .Email }}\r\n"
"upload" = "🔼 上传↑:{{ .Upload }}\r\n"
"download" = "🔽 下载↓:{{ .Download }}\r\n"
"total" = "📊 总计:{{ .UpDown }} / {{ .Total }}\r\n"
@@ -681,12 +708,12 @@
"confirmRemoveTGUser" = "✅ 确认移除 Telegram 用户?"
"confirmToggle" = "✅ 确认启用/禁用用户?"
"dbBackup" = "获取数据库备份"
"serverUsage" = "服务器使用情况"
"serverUsage" = "服务器状态"
"getInbounds" = "获取入站信息"
"depleteSoon" = "即将耗尽"
"clientUsage" = "获取使用情况"
"onlines" = "在线客户端"
"commands" = "命令"
"commands" = "常用命令"
"refresh" = "🔄 刷新"
"clearIPs" = "❌ 清除 IP"
"removeTGUser" = "❌ 移除 Telegram 用户"
@@ -698,7 +725,7 @@
"ipLimit" = "🔢 IP 限制"
"setTGUser" = "👤 设置 Telegram 用户"
"toggle" = "🔘 启用/禁用"
"custom" = "🔢 风俗"
"custom" = "🔢 自定义输入"
"confirmNumber" = "✅ 确认: {{ .Num }}"
"confirmNumberAdd" = "✅ 确认添加:{{ .Num }}"
"limitTraffic" = "🚧 流量限制"
+285 -259
View File
@@ -1,10 +1,10 @@
"username" = "使用者名稱"
"username" = "用戶名"
"password" = "密碼"
"login" = "登入"
"confirm" = "確定"
"cancel" = "取消"
"close" = "關閉"
"create" = "建"
"create" = "建"
"update" = "更新"
"copy" = "複製"
"copied" = "已複製"
@@ -27,8 +27,8 @@
"info" = "更多資訊"
"edit" = "編輯"
"delete" = "刪除"
"reset" = "重"
"noData" = "無數據。"
"reset" = "重"
"noData" = "無資料"
"copySuccess" = "複製成功"
"sure" = "確定"
"encryption" = "加密"
@@ -43,10 +43,10 @@
"depleted" = "耗盡"
"depletingSoon" = "即將耗盡"
"offline" = "離線"
"online" = "線"
"domainName" = "域名"
"online" = "線"
"domainName" = "域名"
"monitor" = "監聽"
"certificate" = "憑證"
"certificate" = "數位憑證"
"fail" = "失敗"
"comment" = "評論"
"success" = "成功"
@@ -55,33 +55,35 @@
"install" = "安裝"
"clients" = "客戶端"
"usage" = "使用情況"
"secretToken" = "安全金鑰"
"twoFactorCode" = "代碼"
"remained" = "剩餘"
"security" = "安全"
"secAlertTitle" = "安全警報"
"secAlertSsl" = "此連線不安全在啟用 TLS 進行資料保護之前,請勿輸入敏感資訊。"
"secAlertConf" = "某些設定易受攻擊建議加強安全協議以防止潛在漏洞。"
"secAlertSSL" = "面板缺少安全連線請安裝 TLS 證以保護資料安全。"
"secAlertPanelPort" = "面板預設埠存在安全風險。請配置隨機埠或特定埠。"
"secAlertPanelURI" = "面板預設 URI 路徑不安全。請配置複雜的 URI 路徑。"
"secAlertSubURI" = "訂閱預設 URI 路徑不安全。請配置複雜的 URI 路徑。"
"secAlertSubJsonURI" = "訂閱 JSON 預設 URI 路徑不安全。請配置複雜的 URI 路徑。"
"emptyDnsDesc" = "未添加DNS伺服器。"
"emptyFakeDnsDesc" = "未添加Fake DNS伺服器。"
"emptyBalancersDesc" = "未添加負載平衡器。"
"emptyReverseDesc" = "未添加反向代理。"
"somethingWentWrong" = "發生錯誤"
"secAlertSsl" = "此連線不安全在啟用 TLS 進行資料保護之前,請勿輸入敏感資訊。"
"secAlertConf" = "某些設定易受攻擊建議加強安全協議以防止潛在漏洞。"
"secAlertSSL" = "面板缺少安全連線請安裝 TLS 證以保護資料安全。"
"secAlertPanelPort" = "面板預設連接埠存在安全風險!請設定隨機連接埠或特定連接埠。"
"secAlertPanelURI" = "面板預設 URI 路徑不安全!請設定複雜的 URI 路徑。"
"secAlertSubURI" = "訂閱預設 URI 路徑不安全!請設定複雜的 URI 路徑。"
"secAlertSubJsonURI" = "訂閱 JSON 預設 URI 路徑不安全!請設定複雜的 URI 路徑。"
"emptyDnsDesc" = "未新增 DNS 伺服器。"
"emptyFakeDnsDesc" = "未新增 Fake DNS 伺服器。"
"emptyBalancersDesc" = "未新增負載平衡器。"
"emptyReverseDesc" = "未新增反向代理。"
"somethingWentWrong" = "出了一點問題"
[menu]
"theme" = "主題"
"dark" = "色"
"ultraDark" = "超深色"
"dark" = "色"
"ultraDark" = "色"
"dashboard" = "系統狀態"
"inbounds" = "入站列表"
"settings" = "面板設定"
"xray" = "Xray 設定"
"logout" = "退出登入"
"logout" = "登出"
"link" = "管理"
"navigation" = "實用導覽"
[pages.login]
"hello" = "你好"
@@ -90,9 +92,9 @@
[pages.login.toasts]
"invalidFormData" = "資料格式錯誤"
"emptyUsername" = "請輸入使用者名稱"
"emptyUsername" = "請輸入用戶名"
"emptyPassword" = "請輸入密碼"
"wrongUsernameOrPassword" = "用戶名、密碼或雙重驗證碼無效。"
"wrongUsernameOrPassword" = "用戶名、密碼或雙重驗證碼無效。"
"successLogin" = "您已成功登入您的帳戶。"
[pages.index]
@@ -100,56 +102,57 @@
"cpu" = "CPU"
"logicalProcessors" = "邏輯處理器"
"frequency" = "頻率"
"swap" = "交換空間"
"swap" = "交換分區"
"storage" = "儲存"
"memory" = "記憶體"
"hard" = "磁碟"
"threads" = "執行緒"
"xrayStatus" = "Xray"
"stopXray" = "停止"
"restartXray" = "重啟"
"xraySwitch" = "版本"
"xraySwitchClick" = "選擇要切換到的版本"
"xraySwitchClickDesk" = "請謹慎選擇,因為較舊版本可能與當前配置不相容"
"xraySwitchClick" = "選擇要切換到的版本"
"xraySwitchClickDesk" = "請謹慎選擇,因為較舊版本可能與目前設定不相容"
"xrayStatusUnknown" = "未知"
"xrayStatusRunning" = "運行中"
"xrayStatusStop" = "停止"
"xrayStatusError" = "錯誤"
"xrayErrorPopoverTitle" = "執行Xray時發生錯誤"
"xrayErrorPopoverTitle" = "執行 Xray 時發生錯誤"
"operationHours" = "系統正常執行時間"
"systemLoad" = "系統負載"
"systemLoadDesc" = "過去 1、5 和 15 分鐘的系統平均負載"
"connectionCount" = "連線數"
"ipAddresses" = "IP址"
"toggleIpVisibility" = "切換IP可見性"
"ipAddresses" = "IP址"
"toggleIpVisibility" = "切換 IP 可見性"
"overallSpeed" = "整體速度"
"upload" = "上傳"
"download" = "下載"
"totalData" = "總數據"
"sent" = "已送"
"sent" = "已送"
"received" = "已接收"
"documentation" = "文件"
"xraySwitchVersionDialog" = "您確定要變更Xray版本嗎?"
"xraySwitchVersionDialogDesc" = "這將把Xray版本變更為#version#。"
"xraySwitchVersionDialog" = "您確定要變更 Xray 版本嗎?"
"xraySwitchVersionDialogDesc" = "這將把 Xray 版本變更為 #version#。"
"xraySwitchVersionPopover" = "Xray 更新成功"
"geofileUpdateDialog" = "您確定要更新地理檔案嗎?"
"geofileUpdateDialogDesc" = "這將更新 #filename# 檔案。"
"geofilesUpdateDialogDesc" = "這將更新所有文件。"
"geofilesUpdateDialogDesc" = "這將更新所有檔案。"
"geofilesUpdateAll" = "全部更新"
"geofileUpdatePopover" = "地理檔案更新成功"
"dontRefresh" = "安裝中,請勿重新整理此頁面"
"logs" = "日誌"
"config" = "配置"
"backup" = "備份和恢復"
"backupTitle" = "備份和恢復資料庫"
"config" = "設定"
"backup" = "備份與還原"
"backupTitle" = "備份與還原資料庫"
"exportDatabase" = "備份"
"exportDatabaseDesc" = "點擊下載包含前資料庫備份的 .db 文件到您的設備。"
"importDatabase" = "恢復"
"importDatabaseDesc" = "點擊選擇並上傳設備中的 .db 文件以從備份恢復資料庫。"
"exportDatabaseDesc" = "點擊下載包含前資料庫備份的 .db 檔案到您的裝置。"
"importDatabase" = "還原"
"importDatabaseDesc" = "點擊選擇並上傳裝置中的 .db 檔案以從備份還原資料庫。"
"importDatabaseSuccess" = "資料庫匯入成功"
"importDatabaseError" = "匯入資料庫時發生錯誤"
"readDatabaseError" = "讀取資料庫時發生錯誤"
"getDatabaseError" = "檢索資料庫時發生錯誤"
"getConfigError" = "檢索設定檔時發生錯誤"
"getDatabaseError" = "擷取資料庫時發生錯誤"
"getConfigError" = "擷取設定檔時發生錯誤"
[pages.inbounds]
"allTimeTraffic" = "累計總流量"
@@ -162,30 +165,32 @@
"enable" = "啟用"
"remark" = "備註"
"protocol" = "協議"
"port" = "埠"
"portMap" = "埠映射"
"port" = "連接埠"
"portMap" = "連接埠映射"
"traffic" = "流量"
"details" = "詳細資訊"
"transportConfig" = "傳輸配置"
"transportConfig" = "傳輸設定"
"expireDate" = "到期時間"
"createdAt" = "建立時間"
"updatedAt" = "更新時間"
"resetTraffic" = "重置流量"
"addInbound" = "新增入站"
"generalActions" = "通用操作"
"autoRefresh" = "自動刷新"
"autoRefresh" = "自動重新整理"
"autoRefreshInterval" = "間隔"
"create" = "新增"
"update" = "修改"
"modifyInbound" = "修改入站"
"deleteInbound" = "刪除入站"
"deleteInboundContent" = "確定要刪除入站嗎?"
"deleteClient" = "刪除客戶端"
"deleteClientContent" = "確定要刪除客戶端嗎?"
"resetTrafficContent" = "確定要重流量嗎?"
"resetTrafficContent" = "確定要重流量嗎?"
"copyLink" = "複製連結"
"address" = "址"
"address" = "址"
"network" = "網路"
"destinationPort" = "目標埠"
"targetAddress" = "目標址"
"destinationPort" = "目標連接埠"
"targetAddress" = "目標址"
"monitorDesc" = "留空表示監聽所有 IP"
"meansNoLimit" = "= 無限制(單位:GB)"
"totalFlow" = "總流量"
@@ -195,41 +200,46 @@
"certificateContent" = "檔案內容"
"publicKey" = "公鑰"
"privatekey" = "私鑰"
"clickOnQRcode" = "點二維碼複製"
"client" = "客戶"
"clickOnQRcode" = "點二維碼複製"
"client" = "客戶/用戶"
"export" = "匯出連結"
"clone" = "複製"
"cloneInbound" = "複製"
"cloneInboundContent" = "此入站規則除埠(Port)、監聽 IP(Listening IP)和客戶端(Clients)以外的所有配置都將應用於克隆"
"cloneInboundOk" = "建克隆"
"resetAllTraffic" = "重所有入站流量"
"resetAllTrafficTitle" = "重所有入站流量"
"resetAllTrafficContent" = "確定要重所有入站流量嗎?"
"resetInboundClientTraffics" = "重客戶端流量"
"resetInboundClientTrafficTitle" = "重所有客戶端流量"
"resetInboundClientTrafficContent" = "確定要重此入站客戶端的所有流量嗎?"
"resetAllClientTraffics" = "重所有客戶端流量"
"resetAllClientTrafficTitle" = "重所有客戶端流量"
"resetAllClientTrafficContent" = "確定要重所有客戶端的所有流量嗎?"
"clone" = "克隆"
"cloneInbound" = "克隆"
"cloneInboundContent" = "此入站規則除連接埠(Port)、監聽 IPListening IP)和客戶端(Clients)以外的所有設定都將應用於克隆"
"cloneInboundOk" = "建克隆"
"resetAllTraffic" = "重所有入站流量"
"resetAllTrafficTitle" = "重所有入站流量"
"resetAllTrafficContent" = "確定要重所有入站流量嗎?"
"resetInboundClientTraffics" = "重客戶端流量"
"resetInboundClientTrafficTitle" = "重所有客戶端流量"
"resetInboundClientTrafficContent" = "確定要重此入站客戶端的所有流量嗎?"
"resetAllClientTraffics" = "重所有客戶端流量"
"resetAllClientTrafficTitle" = "重所有客戶端流量"
"resetAllClientTrafficContent" = "確定要重所有客戶端的所有流量嗎?"
"delDepletedClients" = "刪除流量耗盡的客戶端"
"delDepletedClientsTitle" = "刪除流量耗盡的客戶端"
"delDepletedClientsContent" = "確定要刪除所有流量耗盡的客戶端嗎?"
"email" = "電子郵件"
"emailDesc" = "電子郵件必須完全唯一"
"emailDesc" = "電子郵件必須確保唯一"
"IPLimit" = "IP 限制"
"IPLimitDesc" = "如果數量超過設定值,則用入站流量。(0 = 用)"
"IPLimitDesc" = "如果數量超過設定值,則用入站流量。(0 = 用)"
"IPLimitlog" = "IP 日誌"
"IPLimitlogDesc" = "IP 歷史日誌(要啟用被用的入站流量,請清除日誌)"
"IPLimitlogDesc" = "IP 歷史日誌(要啟用被用的入站流量,請清除日誌)"
"IPLimitlogclear" = "清除日誌"
"setDefaultCert" = "從面板設定證"
"telegramDesc" = "請提供Telegram聊天ID。(在機器人中使用'/id'命令)或(@userinfobot"
"subscriptionDesc" = "要找到的訂閱 URL,請導航到“詳細資訊。此外,可以為多個客戶端使用相同的名稱。"
"setDefaultCert" = "從面板設定證"
"telegramDesc" = "請提供 Telegram 聊天 ID。(在機器人中使用 '/id' 指令或跟 @userinfobot 機器人對話獲取)"
"subscriptionDesc" = "要找到的訂閱 URL,請導覽至「詳細資訊。此外,可以為多個客戶端使用相同的名稱。"
"info" = "資訊"
"same" = "相同"
"inboundData" = "入站資料"
"exportInbound" = "匯出入站規則"
"import"="匯入"
"import" = "匯入"
"importInbound" = "匯入入站規則"
"reviewTitle" = "請確認以下設定資訊"
"neverExpire" = "永不過期"
"reviewHint" = "如需修改請返回上一步"
"unlimited" = "無限制"
"deviceLimit" = "裝置限制"
[pages.client]
"add" = "新增客戶端"
@@ -237,40 +247,40 @@
"submitAdd" = "新增客戶端"
"submitEdit" = "儲存修改"
"clientCount" = "客戶端數量"
"bulk" = "批量建立"
"bulk" = "批次創建"
"method" = "方法"
"first" = "置頂"
"last" = "置底"
"prefix" = "字首"
"postfix" = "字尾"
"prefix" = "前綴"
"postfix" = "後綴"
"delayedStart" = "首次使用後開始"
"expireDays" = "期間"
"expireDays" = "到期天數/期間"
"days" = "天"
"renew" = "自動續"
"renewDesc" = "到期後自動續。(0 = 用)(單位: 天)"
"renew" = "自動續"
"renewDesc" = "到期後自動續。(0 = 用)(單位: 天)"
[pages.inbounds.toasts]
"obtain" = "獲取"
"updateSuccess" = "更新成功"
"logCleanSuccess" = "日誌已清除"
"inboundsUpdateSuccess" = "入站連已成功更新"
"inboundUpdateSuccess" = "入站連已成功更新"
"inboundCreateSuccess" = "入站連已成功建"
"inboundDeleteSuccess" = "入站連已成功刪除"
"inboundsUpdateSuccess" = "入站連已成功更新"
"inboundUpdateSuccess" = "入站連已成功更新"
"inboundCreateSuccess" = "入站連已成功建"
"inboundDeleteSuccess" = "入站連已成功刪除"
"inboundClientAddSuccess" = "已新增入站客戶端"
"inboundClientDeleteSuccess" = "入站客戶端已刪除"
"inboundClientUpdateSuccess" = "入站客戶端已更新"
"delDepletedClientsSuccess" = "所有耗盡客戶端已刪除"
"resetAllClientTrafficSuccess" = "客戶端所有流量已重"
"resetAllTrafficSuccess" = "所有流量已重"
"resetInboundClientTrafficSuccess" = "流量已重"
"trafficGetError" = "取流量資料時發生錯誤"
"getNewX25519CertError" = "取得X25519憑證時發生錯誤。"
"getNewmldsa65Error" = "取得mldsa65憑證時發生錯誤。"
"resetAllClientTrafficSuccess" = "客戶端所有流量已重"
"resetAllTrafficSuccess" = "所有流量已重"
"resetInboundClientTrafficSuccess" = "流量已重"
"trafficGetError" = "取流量資料時發生錯誤"
"getNewX25519CertError" = "獲取 X25519 憑證時發生錯誤。"
"getNewmldsa65Error" = "獲取 mldsa65 憑證時發生錯誤。"
[pages.inbounds.stream.general]
"request" = "請求"
"response" = "應"
"response" = "應"
"name" = "名稱"
"value" = "值"
@@ -280,60 +290,62 @@
"path" = "路徑"
"status" = "狀態"
"statusDescription" = "狀態說明"
"requestHeader" = "請求頭"
"responseHeader" = "響應頭"
"requestHeader" = "請求頭"
"responseHeader" = "回應標頭"
[pages.settings]
"title" = "面板設定"
"save" = "儲存"
"infoDesc" = "此處的所有更都需要儲存並重啟面板才能生效"
"infoDesc" = "此處的所有更都需要儲存並重啟面板才能生效"
"restartPanel" = "重啟面板"
"restartPanelDesc" = "確定要重啟面板嗎?若重啟後無法訪問面板,請前往伺服器檢視面板日誌資訊"
"restartPanelSuccess" = "面板已成功重新啟動"
"restartPanelDesc" = "確定要重啟面板嗎?若重啟後無法存取面板,請前往伺服器查看面板日誌資訊"
"restartPanelSuccess" = "面板已成功重"
"actions" = "操作"
"resetDefaultConfig" = "重為預設配置"
"panelSettings" = "常規"
"resetDefaultConfig" = "重為預設設定"
"panelSettings" = "一般"
"securitySettings" = "安全設定"
"TGBotSettings" = "Telegram 機器人配置"
"TGBotSettings" = "Telegram 機器人設定"
"panelListeningIP" = "面板監聽 IP"
"panelListeningIPDesc" = "預設留空監聽所有 IP"
"panelListeningDomain" = "面板監聽域"
"panelListeningDomainDesc" = "預設情況下留空以監視所有域和 IP 址"
"panelPort" = "面板監聽埠"
"panelListeningDomain" = "面板監聽域"
"panelListeningDomainDesc" = "預設情況下留空以監視所有域和 IP 址"
"panelPort" = "面板監聽連接埠"
"panelPortDesc" = "重啟面板生效"
"publicKeyPath" = "面板證公鑰檔案路徑"
"publicKeyPathDesc" = "填寫一個 '/' 開頭的絕對路徑"
"privateKeyPath" = "面板證書金鑰檔案路徑"
"privateKeyPathDesc" = "填寫一個 '/' 開頭的絕對路徑"
"panelUrlPath" = "面板 url 根路徑"
"publicKeyPath" = "面板證公鑰檔案路徑"
"DefaultpublicKeyPath" = "/root/.acme.sh/網域_ecc/網域.cer"
"publicKeyPathDesc" = "填寫一個 '/' 開頭的絕對路徑,〔acme 方式〕請自行在填入時修改網域"
"privateKeyPath" = "面板憑證金鑰檔案路徑"
"DefaultprivateKeyPath" = "/root/.acme.sh/網域_ecc/網域.key"
"privateKeyPathDesc" = "填寫一個 '/' 開頭的絕對路徑,〔acme 方式〕請自行在填入時修改網域"
"panelUrlPath" = "面板登入存取路徑"
"panelUrlPathDesc" = "必須以 '/' 開頭,以 '/' 結尾"
"pageSize" = "分頁大小"
"pageSizeDesc" = "定義入站表的頁面大小。設定 0 表示用"
"remarkModel" = "備註模型分隔符"
"pageSizeDesc" = "定義入站表的頁面大小。設定 0 表示用"
"remarkModel" = "備註模型分隔符"
"datepicker" = "日期選擇器"
"datepickerPlaceholder" = "選擇日期"
"datepickerDescription" = "選擇器日曆類型指定到期日期"
"sampleRemark" = "備註例"
"oldUsername" = "原使用者名稱"
"sampleRemark" = "備註例"
"oldUsername" = "原用戶名"
"currentPassword" = "原密碼"
"newUsername" = "新使用者名稱"
"newUsername" = "新用戶名"
"newPassword" = "新密碼"
"telegramBotEnable" = "啟用 Telegram 機器人"
"telegramBotEnableDesc" = "啟用 Telegram 機器人功能"
"telegramToken" = "Telegram 機器人令牌token"
"telegramTokenDesc" = " '@BotFather' 獲取的 Telegram 機器人令牌"
"telegramToken" = "Telegram 機器人權杖token"
"telegramTokenDesc" = " '@BotFather' 對話獲取的 Telegram 機器人權杖"
"telegramProxy" = "SOCKS5 Proxy"
"telegramProxyDesc" = "啟用 SOCKS5 代理連線到 Telegram(根據指南調整設定)"
"telegramAPIServer" = "Telegram API Server"
"telegramAPIServerDesc" = "要使用的 Telegram API 伺服器。留空以使用預設伺服器。"
"telegramChatId" = "管理員聊天 ID"
"telegramChatIdDesc" = "Telegram 管理員聊天 ID (多個以逗號分隔)(可過 @userinfobot 獲取,或在機器人中使用 '/id' 令獲取)"
"telegramChatIdDesc" = "Telegram 管理員聊天 ID (多個以逗號分隔)(可過 @userinfobot 獲取,或在機器人中使用 '/id' 令獲取)"
"telegramNotifyTime" = "通知時間"
"telegramNotifyTimeDesc" = "設定週期性的 Telegram 機器人通知時間(使用 crontab 時間格式)"
"tgNotifyBackup" = "資料庫備份"
"tgNotifyBackupDesc" = "傳送帶有報告的資料庫備份檔案"
"tgNotifyLogin" = "登入通知"
"tgNotifyLoginDesc" = "當有人試圖登入的面板時顯示使用者名稱、IP 址和時間"
"tgNotifyLoginDesc" = "當有人試圖登入的面板時顯示用戶名、IP 址和時間"
"sessionMaxAge" = "會話時長"
"sessionMaxAgeDesc" = "保持登入狀態的時長(單位:分鐘)"
"expireTimeDiff" = "到期通知閾值"
@@ -348,19 +360,19 @@
"subEnable" = "啟用訂閱服務"
"subEnableDesc" = "啟用訂閱服務功能"
"subTitle" = "訂閱標題"
"subTitleDesc" = "在VPN客戶端中顯示的標題"
"subTitleDesc" = "在 VPN 客戶端中顯示的標題"
"subListen" = "監聽 IP"
"subListenDesc" = "訂閱服務監聽的 IP 址(留空表示監聽所有 IP"
"subPort" = "監聽埠"
"subPortDesc" = "訂閱服務監聽的埠號(必須是未使用的埠)"
"subListenDesc" = "訂閱服務監聽的 IP 址(留空表示監聽所有 IP"
"subPort" = "監聽連接埠"
"subPortDesc" = "訂閱服務監聽的連接埠號(必須是未使用的連接埠)"
"subCertPath" = "公鑰路徑"
"subCertPathDesc" = "訂閱服務使用的公鑰檔案路徑(以 '/' 開頭)"
"subKeyPath" = "私鑰路徑"
"subKeyPathDesc" = "訂閱服務使用的私鑰檔案路徑(以 '/' 開頭)"
"subPath" = "URI 路徑"
"subPathDesc" = "訂閱服務使用的 URI 路徑(以 '/' 開頭,以 '/' 結尾)"
"subDomain" = "監聽域"
"subDomainDesc" = "訂閱服務監聽的域(留空表示監聽所有域和 IP"
"subDomain" = "監聽域"
"subDomainDesc" = "訂閱服務監聽的域(留空表示監聽所有域和 IP"
"subUpdates" = "更新間隔"
"subUpdatesDesc" = "客戶端應用中訂閱 URL 的更新間隔(單位:小時)"
"subEncrypt" = "編碼"
@@ -369,12 +381,12 @@
"subShowInfoDesc" = "客戶端應用中將顯示剩餘流量和日期資訊"
"subURI" = "反向代理 URI"
"subURIDesc" = "用於代理後面的訂閱 URL 的 URI 路徑"
"externalTrafficInformEnable" = "外部交通通知"
"externalTrafficInformEnable" = "外部流量通知"
"externalTrafficInformEnableDesc" = "每次流量更新時通知外部 API"
"externalTrafficInformURI" = "外部流量通知 URI"
"externalTrafficInformURIDesc" = "流量更新將傳送到此 URI"
"externalTrafficInformURIDesc" = "流量更新將傳送到此 URI"
"fragment" = "分片"
"fragmentDesc" = "啟用 TLS hello 資料包分片"
"fragmentDesc" = "啟用 TLS hello 包分片"
"fragmentSett" = "設定"
"noisesDesc" = "啟用 Noises."
"noisesSett" = "Noises 設定"
@@ -382,68 +394,68 @@
"muxDesc" = "在已建立的資料流內傳輸多個獨立的資料流"
"muxSett" = "複用器設定"
"direct" = "直接連線"
"directDesc" = "直接與特定國家的域或IP範圍建立連線"
"directDesc" = "直接與特定國家的域或 IP 範圍建立連線"
"notifications" = "通知"
"certs" = "證"
"certs" = "證"
"externalTraffic" = "外部流量"
"dateAndTime" = "日期時間"
"proxyAndServer" = "代理伺服器"
"dateAndTime" = "日期時間"
"proxyAndServer" = "代理伺服器"
"intervals" = "間隔"
"information" = "資訊"
"language" = "語言"
"telegramBotLanguage" = "Telegram 機器人語言"
[pages.xray]
"title" = "Xray 配置"
"title" = "Xray 設定"
"save" = "儲存"
"restart" = "重新啟動 Xray"
"restartSuccess" = "Xray 已成功重新啟動"
"stopSuccess" = "Xray 已成功停止"
"restartError" = "重新啟動Xray時發生錯誤。"
"stopError" = "停止Xray時發生錯誤。"
"basicTemplate" = "基礎配置"
"advancedTemplate" = "高階配置"
"generalConfigs" = "常規配置"
"generalConfigsDesc" = "這些選項將決定常規配置"
"restartError" = "重Xray 時發生錯誤。"
"stopError" = "停止 Xray 時發生錯誤。"
"basicTemplate" = "基礎設定"
"advancedTemplate" = "進階設定"
"generalConfigs" = "一般設定"
"generalConfigsDesc" = "這些選項將決定一般設定"
"logConfigs" = "日誌"
"logConfigsDesc" = "日誌可能會影響伺服器的效能,建議僅在需要時啟用"
"blockConfigsDesc" = "這些選項將阻止使用者連線到特定協議和網站"
"basicRouting" = "基本路由"
"blockConnectionsConfigsDesc" = "這些選項將根據特定的請求國家阻止流量。"
"directConnectionsConfigsDesc" = "直接連線確保特定的流量不會過其他伺服器路由。"
"blockips" = "阻止IP"
"blockdomains" = "阻止域"
"directips" = "直接IP"
"directdomains" = "直接域"
"directConnectionsConfigsDesc" = "直接連線確保特定的流量不會過其他伺服器路由。"
"blockips" = "阻止 IP"
"blockdomains" = "阻止域"
"directips" = "直接 IP"
"directdomains" = "直接域"
"ipv4Routing" = "IPv4 路由"
"ipv4RoutingDesc" = "此選項將僅過 IPv4 路由到目標域"
"ipv4RoutingDesc" = "此選項將僅過 IPv4 路由到目標域"
"warpRouting" = "WARP 路由"
"warpRoutingDesc" = "注意:在使用這些選項之前,請按照面板 GitHub 上的步驟在的伺服器上以 socks5 代理模式安裝 WARP。WARP 將過 Cloudflare 伺服器將流量路由到網站。"
"Template" = "階 Xray 配置模板"
"TemplateDesc" = "最終的 Xray 配置檔案將基於此模板生"
"warpRoutingDesc" = "注意:在使用這些選項之前,請按照面板 GitHub 上的步驟在的伺服器上以 socks5 代理模式安裝 WARP。WARP 將過 Cloudflare 伺服器將流量路由到網站。"
"Template" = "階 Xray 設定模板"
"TemplateDesc" = "最終的 Xray 設定檔將基於此模板生"
"FreedomStrategy" = "Freedom 協議策略"
"FreedomStrategyDesc" = "設定 Freedom 協議中網路的輸出策略"
"RoutingStrategy" = "配置路由域策略"
"RoutingStrategy" = "設定路由域策略"
"RoutingStrategyDesc" = "設定 DNS 解析的整體路由策略"
"Torrent" = "遮蔽 BitTorrent 協議"
"Torrent" = "封鎖 BitTorrent 協議"
"Inbounds" = "入站規則"
"InboundsDesc" = "接受來自特定客戶端的流量"
"Outbounds" = "出站規則"
"Balancers" = "負載衡"
"Balancers" = "負載衡"
"OutboundsDesc" = "設定出站流量傳出方式"
"Routings" = "路由規則"
"RoutingsDesc" = "每條規則的優先順序都很重要"
"RoutingsDesc" = "每條規則的優先都很重要"
"completeTemplate" = "全部"
"logLevel" = "日誌級別"
"logLevelDesc" = "錯誤日誌的日誌級別,用於指示需要記錄的資訊"
"accessLog" = "訪問日誌"
"accessLogDesc" = "訪問日誌的檔案路徑。特殊值 'none' 禁用訪問日誌"
"accessLog" = "存取日誌"
"accessLogDesc" = "存取日誌的檔案路徑。特殊值 'none' 停用存取日誌"
"errorLog" = "錯誤日誌"
"errorLogDesc" = "錯誤日誌的檔案路徑。特殊值 'none' 用錯誤日誌"
"errorLogDesc" = "錯誤日誌的檔案路徑。特殊值 'none' 用錯誤日誌"
"dnsLog" = "DNS 日誌"
"dnsLogDesc" = "是否啟用 DNS 查詢日誌"
"maskAddress" = "隱藏址"
"maskAddressDesc" = "IP 地址掩碼,啟用時會自動替換日誌中出現的 IP 址。"
"maskAddress" = "隱藏址"
"maskAddressDesc" = "IP 位址遮罩,啟用時會自動替換日誌中出現的 IP 址。"
"statistics" = "統計"
"statsInboundUplink" = "入站上傳統計"
"statsInboundUplinkDesc" = "啟用所有入站代理的上行流量統計收集。"
@@ -454,20 +466,35 @@
"statsOutboundDownlink" = "出站下載統計"
"statsOutboundDownlinkDesc" = "啟用所有出站代理的下行流量統計收集。"
[pages.navigation]
"title" = "實用導覽"
[pages.xray.rules]
"first" = "置頂"
"last" = "置底"
"up" = "向上"
"down" = "向下"
"source" = "來源"
"dest" = "目的地址"
"dest" = "目標位址"
"inbound" = "入站"
"outbound" = "出站"
"balancer" = "負載衡"
"balancer" = "負載衡"
"info" = "資訊"
"add" = "新增規則"
"edit" = "編輯規則"
"useComma" = "逗號分隔的項目"
"DomainMatcher" = "網域匹配類型"
"SourceIPs" = "來源 IP"
"SourcePort" = "來源連接埠"
"Network" = "網路類型"
"Protocol" = "傳輸協議"
"Attributes" = "屬性"
"Domain" = "網域"
"User" = "使用者"
"Port" = "連接埠"
"InboundTag" = "入站 Tag"
"OutboundTag" = "出站 Tag"
"BalancerTag" = "負載平衡 Tag"
[pages.xray.outbound]
"addOutbound" = "新增出站"
@@ -476,9 +503,9 @@
"editReverse" = "編輯反向"
"tag" = "標籤"
"tagDesc" = "唯一標籤"
"address" = "址"
"address" = "址"
"reverse" = "反向"
"domain" = "域"
"domain" = "域"
"type" = "類型"
"bridge" = "Bridge"
"portal" = "Portal"
@@ -490,8 +517,8 @@
"sendThrough" = "傳送通過"
[pages.xray.balancer]
"addBalancer" = "新增負載衡"
"editBalancer" = "編輯負載衡"
"addBalancer" = "新增負載衡"
"editBalancer" = "編輯負載衡"
"balancerStrategy" = "策略"
"balancerSelectors" = "選擇器"
"tag" = "標籤"
@@ -504,103 +531,102 @@
"allowedIPs" = "允許的 IP"
"endpoint" = "端點"
"psk" = "共享金鑰"
"domainStrategy" = "域策略"
"domainStrategy" = "域策略"
[pages.xray.dns]
"enable" = "啟用 DNS"
"enableDesc" = "啟用內建 DNS 伺服器"
"tag" = "DNS 入站標籤"
"tagDesc" = "此標籤將在路由規則中可用作入站標籤"
"clientIp" = "客戶端IP"
"clientIpDesc" = "用於在DNS查詢期間通知伺服器指定的IP位置"
"disableCache" = "用快取"
"disableCacheDesc" = "禁用DNS快取"
"disableFallback" = "用回退"
"disableFallbackDesc" = "用回退DNS查詢"
"disableFallbackIfMatch" = "匹配時用回退"
"disableFallbackIfMatchDesc" = "當DNS伺服器的匹配域列表命中時,用回退DNS查詢"
"clientIp" = "客戶端 IP"
"clientIpDesc" = "用於在 DNS 查詢期間通知伺服器指定的 IP 位置"
"disableCache" = "用快取"
"disableCacheDesc" = "停用 DNS 快取"
"disableFallback" = "用回退"
"disableFallbackDesc" = "用回退 DNS 查詢"
"disableFallbackIfMatch" = "匹配時用回退"
"disableFallbackIfMatchDesc" = "當 DNS 伺服器的匹配域列表命中時,用回退 DNS 查詢"
"strategy" = "查詢策略"
"strategyDesc" = "解析域的總體策略"
"strategyDesc" = "解析域的總體策略"
"add" = "新增伺服器"
"edit" = "編輯伺服器"
"domains" = "域"
"domains" = "域"
"expectIPs" = "預期 IP"
"unexpectIPs" = "意外IP"
"useSystemHosts" = "使用系統Hosts"
"useSystemHostsDesc" = "使用已安裝系統的hosts檔案"
"usePreset" = "使用範本"
"dnsPresetTitle" = "DNS範本"
"unexpectIPs" = "意外 IP"
"useSystemHosts" = "使用系統 Hosts"
"useSystemHostsDesc" = "使用已安裝系統的 hosts 檔案"
"usePreset" = "使用模板"
"dnsPresetTitle" = "DNS 模板"
"dnsPresetFamily" = "家庭"
[pages.xray.fakedns]
"add" = "新增假 DNS"
"edit" = "編輯假 DNS"
"ipPool" = "IP 池子網"
"ipPool" = "IP 池子網"
"poolSize" = "池大小"
[pages.settings.security]
"admin" = "管理員憑證"
"twoFactor" = "雙重驗證"
"twoFactorEnable" = "啟用2FA"
"twoFactorEnableDesc" = "增加額外的驗證層以提高安全性。"
"twoFactor" = "雙重驗證"
"twoFactorEnable" = "啟用 2FA"
"twoFactorEnableDesc" = "增加額外的驗證層以提高安全性。"
"twoFactorModalSetTitle" = "啟用雙重認證"
"twoFactorModalDeleteTitle" = "停用雙重認證"
"twoFactorModalSteps" = "要設定雙重認證,請執行以下步驟:"
"twoFactorModalFirstStep" = "1. 在認證應用程式中掃描此QR碼,或複製QR碼附近的令牌並貼到應用程式中"
"twoFactorModalFirstStep" = "1. 在認證應用程式中掃描此 QR 碼,或複製 QR 碼附近的權杖並貼到應用程式中"
"twoFactorModalSecondStep" = "2. 輸入應用程式中的驗證碼"
"twoFactorModalRemoveStep" = "輸入應用程式中的驗證碼以移除雙重認證。"
"twoFactorModalChangeCredentialsTitle" = "更憑證"
"twoFactorModalChangeCredentialsStep" = "輸入應用程式中的代碼以更管理員憑證。"
"twoFactorModalSetSuccess" = "雙重身份驗證已成功建立"
"twoFactorModalDeleteSuccess" = "雙重身份驗證已成功刪除"
"twoFactorModalChangeCredentialsTitle" = "更憑證"
"twoFactorModalChangeCredentialsStep" = "輸入應用程式中的代碼以更管理員憑證。"
"twoFactorModalSetSuccess" = "雙因素認證已成功建立"
"twoFactorModalDeleteSuccess" = "雙因素認證已成功刪除"
"twoFactorModalError" = "驗證碼錯誤"
[pages.settings.toasts]
"modifySettings" = "參數已更。"
"getSettings" = "取參數時發生錯誤"
"modifySettings" = "參數已更。"
"getSettings" = "取參數時發生錯誤"
"modifyUserError" = "變更管理員憑證時發生錯誤。"
"modifyUser" = "您已成功變更管理員憑證。"
"originalUserPassIncorrect" = "原使用者名稱或原密碼錯誤"
"userPassMustBeNotEmpty" = "新使用者名稱和新密碼不能為空"
"getOutboundTrafficError" = "取出站流量錯誤"
"originalUserPassIncorrect" = "原用戶名或原密碼錯誤"
"userPassMustBeNotEmpty" = "新用戶名和新密碼不能為空"
"getOutboundTrafficError" = "取出站流量錯誤"
"resetOutboundTrafficError" = "重設出站流量錯誤"
[tgbot]
"keyboardClosed" = "❌ 自定義鍵盤已關閉!"
"keyboardClosed" = "❌ 自鍵盤已關閉!"
"noResult" = "❗ 沒有結果!"
"noQuery" = "❌ 找到查詢!請再次使用該命令!"
"wentWrong" = "❌ 出了點問題!"
"noIpRecord" = "❗ 沒有IP記錄!"
"noInbounds" = "❗ 找到入站!"
"unlimited" = "♾ 無限(重置)"
"add" = "添加"
"noQuery" = "❌ 找到查詢!請重新使用指令!"
"wentWrong" = "❌ 出了點問題!"
"noIpRecord" = "❗ 沒有 IP 記錄!"
"noInbounds" = "❗ 找到入站連線"
"unlimited" = "♾ 無限"
"add" = "新增"
"month" = "月"
"months" = "月"
"day" = "天"
"days" = "天"
"hours" = "小時"
"minutes" = "分鐘"
"unknown" = "未知"
"inbounds" = "入站"
"inbounds" = "入站連線"
"clients" = "客戶端"
"offline" = "🔴 離線"
"online" = "🟢 在線"
[tgbot.commands]
"unknown" = "❗ 未知令"
"pleaseChoose" = "👇 請選擇\r\n"
"unknown" = "❗ 未知令"
"pleaseChoose" = "👇請〔按照需求〕選擇下方按鈕 \r\n"
"help" = "🤖 歡迎使用本機器人!它旨在為您提供來自伺服器的特定資料,並允許您進行必要的修改。\r\n\r\n"
"start" = "👋 你好,<i>{{ .Firstname }}</i>。\r\n"
"welcome" = "🤖 歡迎來到 <b>{{ .Hostname }}</b> 管理機器人。\r\n"
"status" = "✅ 機器人正常執行"
"status" = "✅ 機器人正常運作"
"usage" = "❗ 請輸入要搜尋的文字!"
"getID" = "🆔 您的 ID 為:<code>{{ .ID }}</code>"
"helpAdminCommands" = "要重新啟動 Xray Core\r\n<code>/restart</code>\r\n\r\n要搜尋客戶電子郵件:\r\n<code>/usage [電子郵件]</code>\r\n\r\n要搜尋入站(帶有客戶統計資料):\r\n<code>/inbound [備註]</code>\r\n\r\nTelegram聊天ID\r\n<code>/id</code>"
"helpClientCommands" = "要搜尋統計資料,請使用以下令:\r\n<code>/usage [電子郵件]</code>\r\n\r\nTelegram聊天ID\r\n<code>/id</code>"
"helpAdminCommands" = "要重新啟動 Xray Core\r\n<code>/restart</code>\r\n\r\n要搜尋客戶電子郵件:\r\n<code>/usage [電子郵件]</code>\r\n\r\n要搜尋入站(帶有客戶統計資料):\r\n<code>/inbound [備註]</code>\r\n\r\nTelegram 聊天 ID\r\n<code>/id</code>"
"helpClientCommands" = "要搜尋統計資料,請使用以下令:\r\n<code>/usage [電子郵件]</code>\r\n\r\nTelegram 聊天 ID\r\n<code>/id</code>"
"restartUsage" = "\r\n\r\n<code>/restart</code>"
"restartSuccess" = "✅ 操作成功!"
"restartSuccess" = "✅ 操作成功"
"restartFailed" = "❗ 操作錯誤。\r\n\r\n<code>錯誤: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core 未運。"
"xrayNotRunning" = "❗ Xray Core 未運。"
"startDesc" = "顯示主選單"
"helpDesc" = "機器人幫助"
"statusDesc" = "檢查機器人狀態"
@@ -609,44 +635,44 @@
[tgbot.messages]
"cpuThreshold" = "🔴 CPU 使用率為 {{ .Percent }}%,超過閾值 {{ .Threshold }}%"
"selectUserFailed" = "❌ 使用者選擇錯誤!"
"userSaved" = "✅ 電報使用者已儲存。"
"userSaved" = "✅ Telegram 用戶已儲存。"
"loginSuccess" = "✅ 成功登入到面板。\r\n"
"loginFailed" = "❗️ 面板登入失敗。\r\n"
"report" = "🕰 定時報告:{{ .RunTime }}\r\n"
"datetime" = "⏰ 日期時間:{{ .DateTime }}\r\n"
"hostname" = "💻 主機名:{{ .Hostname }}\r\n"
"version" = "🚀 X-UI 版本:{{ .Version }}\r\n"
"xrayVersion" = "📡 Xray 版本: {{ .XrayVersion }}\r\n"
"hostname" = "💻 主機名{{ .Hostname }}\r\n"
"version" = "🚀 X-Panel 版本:v{{ .Version }}\r\n"
"xrayVersion" = "📡 Xray 版本v{{ .XrayVersion }}\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" = "⏳ 伺服器執行時間:{{ .UpTime }} {{ .Unit }}\r\n"
"ips" = "🔢 IP 址:\r\n{{ .IPs }}\r\n"
"serverUpTime" = "⏳ 伺服器運作時間:{{ .UpTime }} {{ .Unit }}\r\n"
"serverLoad" = "📈 伺服器負載:{{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n"
"serverMemory" = "📋 伺服器記憶體:{{ .Current }}/{{ .Total }}\r\n"
"tcpCount" = "🔹 TCP 連線數:{{ .Count }}\r\n"
"udpCount" = "🔸 UDP 連線數:{{ .Count }}\r\n"
"traffic" = "🚦 流量:{{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
"xrayStatus" = "️ Xray 狀態:{{ .State }}\r\n"
"username" = "👤 使用者名稱{{ .Username }}\r\n"
"password" = "👤 密碼: {{ .Password }}\r\n"
"username" = "👤 用戶名{{ .Username }}\r\n"
"password" = "👤 密碼{{ .Password }}\r\n"
"time" = "⏰ 時間:{{ .Time }}\r\n"
"inbound" = "📍 入站:{{ .Remark }}\r\n"
"port" = "🔌 埠:{{ .Port }}\r\n"
"port" = "🔌 連接埠:{{ .Port }}\r\n"
"expire" = "📅 過期日期:{{ .Time }}\r\n"
"expireIn" = "📅 剩餘時間:{{ .Time }}\r\n"
"active" = "💡 啟用:{{ .Enable }}\r\n"
"enabled" = "🚨 已啟用:{{ .Enable }}\r\n"
"online" = "🌐 連線狀態:{{ .Status }}\r\n"
"email" = "📧 郵箱{{ .Email }}\r\n"
"email" = "📧 電子郵件(用戶){{ .Email }}\r\n"
"upload" = "🔼 上傳↑:{{ .Upload }}\r\n"
"download" = "🔽 下載↓:{{ .Download }}\r\n"
"total" = "📊 總計:{{ .UpDown }} / {{ .Total }}\r\n"
"TGUser" = "👤 電報使用者{{ .TelegramID }}\r\n"
"TGUser" = "👤 Telegram 用戶{{ .TelegramID }}\r\n"
"exhaustedMsg" = "🚨 耗盡的 {{ .Type }}\r\n"
"exhaustedCount" = "🚨 耗盡的 {{ .Type }} 數量:\r\n"
"onlinesCount" = "🌐 線客戶:{{ .Count }}\r\n"
"disabled" = "🛑 用:{{ .Disabled }}\r\n"
"onlinesCount" = "🌐 線客戶:{{ .Count }}\r\n"
"disabled" = "🛑 用:{{ .Disabled }}\r\n"
"depleteSoon" = "🔜 即將耗盡:{{ .Deplete }}\r\n\r\n"
"backupTime" = "🗄 備份時間:{{ .Time }}\r\n"
"refreshedOn" = "\r\n📋🔄 重新整理時間:{{ .Time }}\r\n\r\n"
@@ -656,64 +682,64 @@
"received_password" = "🔑📥 密碼已更新。"
"received_email" = "📧📥 電子郵件已更新。"
"received_comment" = "💬📥 評論已更新。"
"id_prompt" = "🔑 預設 ID: {{ .ClientId }}\n\n請輸入您的 ID。"
"pass_prompt" = "🔑 預設密碼: {{ .ClientPassword }}\n\n請輸入您的密碼。"
"email_prompt" = "📧 預設電子郵件: {{ .ClientEmail }}\n\n請輸入您的電子郵件。"
"comment_prompt" = "💬 預設評論: {{ .ClientComment }}\n\n請輸入您的評論。"
"inbound_client_data_id" = "🔄 入站: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 電子郵件: {{ .ClientEmail }}\n📊 流量: {{ .ClientTraffic }}\n📅 到期日: {{ .ClientExp }}\n🌐 IP 限制: {{ .IpLimit }}\n💬 備註: {{ .ClientComment }}\n\n現在可以將客戶加入入站了!"
"inbound_client_data_pass" = "🔄 入站: {{ .InboundRemark }}\n\n🔑 密碼: {{ .ClientPass }}\n📧 電子郵件: {{ .ClientEmail }}\n📊 流量: {{ .ClientTraffic }}\n📅 到期日: {{ .ClientExp }}\n🌐 IP 限制: {{ .IpLimit }}\n💬 備註: {{ .ClientComment }}\n\n現在可以將客戶加入入站了!"
"cancel" = "❌ 程已取消!\n\n您可以隨時使用 /start 重新開始。 🔄"
"error_add_client" = "⚠️ 錯誤:\n\n {{ .error }}"
"id_prompt" = "🔑 預設 ID{{ .ClientId }}\n\n請輸入您的 ID。"
"pass_prompt" = "🔑 預設密碼{{ .ClientPassword }}\n\n請輸入您的密碼。"
"email_prompt" = "📧 預設電子郵件{{ .ClientEmail }}\n\n請輸入您的電子郵件。"
"comment_prompt" = "💬 預設評論{{ .ClientComment }}\n\n請輸入您的評論。"
"inbound_client_data_id" = "🔄 入站{{ .InboundRemark }}\n\n🔑 ID{{ .ClientId }}\n📧 電子郵件{{ .ClientEmail }}\n📊 流量{{ .ClientTraffic }}\n📅 到期日期:{{ .ClientExp }}\n🌐 IP 限制{{ .IpLimit }}\n💬 備註{{ .ClientComment }}\n\n現在可以將客戶新增到入站了!"
"inbound_client_data_pass" = "🔄 入站{{ .InboundRemark }}\n\n🔑 密碼{{ .ClientPass }}\n📧 電子郵件{{ .ClientEmail }}\n📊 流量{{ .ClientTraffic }}\n📅 到期日期:{{ .ClientExp }}\n🌐 IP 限制{{ .IpLimit }}\n💬 備註{{ .ClientComment }}\n\n現在可以將客戶新增到入站了!"
"cancel" = "❌ 程已取消!\n\n您可以隨時使用 /start 重新開始。 🔄"
"error_add_client" = "⚠️ 錯誤\n\n {{ .error }}"
"using_default_value" = "好的,我會使用預設值。 😊"
"incorrect_input" ="您的輸入無效。\n語應連續輸入,不能有空格。\n正確示例: aaaaaa\n錯誤示例: aaa aaa 🚫"
"AreYouSure" = "確定嗎?🤔"
"SuccessResetTraffic" = "📧 電子郵件: {{ .ClientEmail }}\n🏁 結果: ✅ 成功"
"FailedResetTraffic" = "📧 電子郵件: {{ .ClientEmail }}\n🏁 結果: ❌ 失敗 \n\n🛠️ 錯誤: [ {{ .ErrorMessage }} ]"
"FinishProcess" = "🔚 所有客戶的流量重已完成。"
"incorrect_input" ="您的輸入無效。\n語應連續輸入,不能有空格。\n正確範例:aaaaaa\n錯誤範例:aaa aaa 🚫"
"AreYouSure" = "確定嗎?🤔"
"SuccessResetTraffic" = "📧 電子郵件{{ .ClientEmail }}\n🏁 結果✅ 成功"
"FailedResetTraffic" = "📧 電子郵件{{ .ClientEmail }}\n🏁 結果❌ 失敗 \n\n🛠️ 錯誤[ {{ .ErrorMessage }} ]"
"FinishProcess" = "🔚 所有客戶的流量重已完成。"
[tgbot.buttons]
"closeKeyboard" = "❌ 關閉鍵盤"
"cancel" = "❌ 取消"
"cancelReset" = "❌ 取消重"
"cancelReset" = "❌ 取消重"
"cancelIpLimit" = "❌ 取消 IP 限制"
"confirmResetTraffic" = "✅ 確認重流量?"
"confirmResetTraffic" = "✅ 確認重流量?"
"confirmClearIps" = "✅ 確認清除 IP"
"confirmRemoveTGUser" = "✅ 確認移除 Telegram 使用者"
"confirmToggle" = "✅ 確認啟用/禁用使用者"
"confirmRemoveTGUser" = "✅ 確認移除 Telegram 用戶"
"confirmToggle" = "✅ 確認啟用/停用用戶"
"dbBackup" = "獲取資料庫備份"
"serverUsage" = "伺服器使用情況"
"serverUsage" = "伺服器狀態"
"getInbounds" = "獲取入站資訊"
"depleteSoon" = "即將耗盡"
"clientUsage" = "獲取使用情況"
"onlines" = "線客戶端"
"commands" = "令"
"onlines" = "線客戶端"
"commands" = "常用指令"
"refresh" = "🔄 重新整理"
"clearIPs" = "❌ 清除 IP"
"removeTGUser" = "❌ 移除 Telegram 使用者"
"selectTGUser" = "👤 選擇 Telegram 使用者"
"selectOneTGUser" = "👤 選擇一個 Telegram 使用者"
"resetTraffic" = "📈 重流量"
"resetExpire" = "📅 更到期日期"
"removeTGUser" = "❌ 移除 Telegram 用戶"
"selectTGUser" = "👤 選擇 Telegram 用戶"
"selectOneTGUser" = "👤 選擇一個 Telegram 用戶"
"resetTraffic" = "📈 重流量"
"resetExpire" = "📅 更到期日期"
"ipLog" = "🔢 IP 日誌"
"ipLimit" = "🔢 IP 限制"
"setTGUser" = "👤 設定 Telegram 使用者"
"toggle" = "🔘 啟用/用"
"custom" = "🔢 風俗"
"confirmNumber" = "✅ 確認: {{ .Num }}"
"setTGUser" = "👤 設定 Telegram 用戶"
"toggle" = "🔘 啟用/用"
"custom" = "🔢 自訂輸入"
"confirmNumber" = "✅ 確認{{ .Num }}"
"confirmNumberAdd" = "✅ 確認新增:{{ .Num }}"
"limitTraffic" = "🚧 流量限制"
"getBanLogs" = "禁止日誌"
"getBanLogs" = "封鎖日誌"
"allClients" = "所有客戶"
"addClient" = "新增客戶"
"submitDisable" = "以停用方式送出 ☑️"
"submitEnable" = "以啟用方式送出 ✅"
"use_default" = "🏷️ 使用預設"
"submitDisable" = "提交為停用 ☑️"
"submitEnable" = "提交為啟用 ✅"
"use_default" = "🏷️ 使用預設"
"change_id" = "⚙️🔑 ID"
"change_password" = "⚙️🔑 密碼"
"change_email" = "⚙️📧 電子郵件"
"change_comment" = "⚙️💬 評論"
"ResetAllTraffics" = "重設所有流量"
"SortedTrafficUsageReport" = "排序的流量使用報告"
"SortedTrafficUsageReport" = "排序的流量使用報告"
[tgbot.answers]
"successfulOperation" = "✅ 成功!"
@@ -723,17 +749,17 @@
"canceled" = "❌ {{ .Email }}:操作已取消。"
"clientRefreshSuccess" = "✅ {{ .Email }}:客戶端重新整理成功。"
"IpRefreshSuccess" = "✅ {{ .Email }}IP 重新整理成功。"
"TGIdRefreshSuccess" = "✅ {{ .Email }}:客戶端的 Telegram 使用者重新整理成功。"
"resetTrafficSuccess" = "✅ {{ .Email }}:流量已重成功。"
"setTrafficLimitSuccess" = "✅ {{ .Email }}: 流量限制儲存成功。"
"expireResetSuccess" = "✅ {{ .Email }}:過期天數已重成功。"
"TGIdRefreshSuccess" = "✅ {{ .Email }}:客戶端的 Telegram 用戶重新整理成功。"
"resetTrafficSuccess" = "✅ {{ .Email }}:流量已重成功。"
"setTrafficLimitSuccess" = "✅ {{ .Email }}流量限制儲存成功。"
"expireResetSuccess" = "✅ {{ .Email }}:過期天數已重成功。"
"resetIpSuccess" = "✅ {{ .Email }}:成功儲存 IP 限制數量為 {{ .Count }}。"
"clearIpSuccess" = "✅ {{ .Email }}IP 已成功清除。"
"getIpLog" = "✅ {{ .Email }}:獲取 IP 日誌。"
"getUserInfo" = "✅ {{ .Email }}:獲取 Telegram 使用者資訊。"
"removedTGUserSuccess" = "✅ {{ .Email }}Telegram 使用者已成功移除。"
"getUserInfo" = "✅ {{ .Email }}:獲取 Telegram 用戶資訊。"
"removedTGUserSuccess" = "✅ {{ .Email }}Telegram 用戶已成功移除。"
"enableSuccess" = "✅ {{ .Email }}:已成功啟用。"
"disableSuccess" = "✅ {{ .Email }}:已成功用。"
"askToAddUserId" = "找到您的配置\r\n請向管理員詢問,在您的配置中使用您的 Telegram 使用者 ChatID。\r\n\r\n您的使用者 ChatID<code>{{ .TgUserID }}</code>"
"disableSuccess" = "✅ {{ .Email }}:已成功用。"
"askToAddUserId" = "找到您的設定\r\n請向管理員詢問,在您的設定中使用您的 Telegram 用戶 ChatID。\r\n\r\n您的用戶 ChatID<code>{{ .TgUserID }}</code>"
"chooseClient" = "為入站 {{ .Inbound }} 選擇一個客戶"
"chooseInbound" = "選擇一個入站"
+124 -32
View File
@@ -42,6 +42,9 @@ var i18nFS embed.FS
var startTime = time.Now()
// 预定义 IPv4 私网和回环网段
var privateIPv4Nets []*net.IPNet
type wrapAssetsFS struct {
embed.FS
}
@@ -125,28 +128,39 @@ func (s *Server) getHtmlFiles() ([]string, error) {
}
func (s *Server) getHtmlTemplate(funcMap template.FuncMap) (*template.Template, error) {
t := template.New("").Funcs(funcMap)
err := fs.WalkDir(htmlFS, "html", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
// 这里用 htmlFS//go:embed html/*)而不是“templates”
t := template.New("").Funcs(funcMap)
if d.IsDir() {
newT, err := t.ParseFS(htmlFS, path+"/*.html")
if err != nil {
// ignore
return nil
}
t = newT
}
return nil
})
if err != nil {
return nil, err
}
return t, nil
// 递归遍历 embed 的 html 目录,解析所有 .html 模板
err := fs.WalkDir(htmlFS, "html", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
if !strings.HasSuffix(path, ".html") {
return nil
}
// 读出模板内容
b, err := htmlFS.ReadFile(path)
if err != nil {
return err
}
// 去掉前缀“html/”,让 {{template "form/inbound"}} 这种名字能被正确找到
name := strings.TrimPrefix(path, "html/")
_, err = t.New(name).Parse(string(b))
return err
})
if err != nil {
return nil, err
}
return t, nil
}
func (s *Server) initRouter() (*gin.Engine, error) {
if config.IsDebug() {
gin.SetMode(gin.DebugMode)
@@ -331,24 +345,42 @@ func (s *Server) Start() (err error) {
if err != nil {
return err
}
listenAddr := net.JoinHostPort(listen, strconv.Itoa(port))
var listenAddr string
if certFile != "" && keyFile != "" {
// 方式一:配置了证书,启用 HTTPS
// 检查证书是否有效,如果无效则直接报错退出,不允许回退到 HTTP
_, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
logger.Errorf("Error loading certificates, please check the file path and content: %v", err)
return err
}
// 监听用户配置的地址
listenAddr = net.JoinHostPort(listen, strconv.Itoa(port))
} else {
// 方式二:未配置证书,强制监听在本地回环地址,仅供 SSH 转发使用
logger.Info("No certificate configured. Forcing listen address to localhost for security.")
logger.Info("Access is only possible via SSH tunnel (e.g., http://127.0.0.1).")
// 无论用户在 listen 中填写什么,都强制使用回环地址
listen = fallbackToLocalhost(listen)
listenAddr = net.JoinHostPort(listen, strconv.Itoa(port))
}
listener, err := net.Listen("tcp", listenAddr)
if err != nil {
return err
}
if certFile != "" || keyFile != "" {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err == nil {
c := &tls.Config{
Certificates: []tls.Certificate{cert},
}
listener = network.NewAutoHttpsListener(listener)
listener = tls.NewListener(listener, c)
logger.Info("Web server running HTTPS on", listener.Addr())
} else {
logger.Error("Error loading certificates:", err)
logger.Info("Web server running HTTP on", listener.Addr())
// 再次检查证书,配置 TLS Listener
if certFile != "" && keyFile != "" {
cert, _ := tls.LoadX509KeyPair(certFile, keyFile) // 这里我们忽略错误,因为上面已经检查过了
c := &tls.Config{
Certificates: []tls.Certificate{cert},
}
listener = network.NewAutoHttpsListener(listener)
listener = tls.NewListener(listener, c)
logger.Info("Web server running HTTPS on", listener.Addr())
} else {
logger.Info("Web server running HTTP on", listener.Addr())
}
@@ -400,3 +432,63 @@ func (s *Server) GetCtx() context.Context {
func (s *Server) GetCron() *cron.Cron {
return s.cron
}
// isInternalIP 判断是否为私网或回环IP(支持IPv4和IPv6)
func isInternalIP(ipStr string) bool {
ip := net.ParseIP(ipStr)
if ip == nil {
return false
}
if ip4 := ip.To4(); ip4 != nil {
// IPv4 判断是否在私网/回环网段内
for _, privateNet := range privateIPv4Nets {
if privateNet.Contains(ip4) {
return true
}
}
return false
}
// IPv6 判断回环或链路本地地址
if ip.IsLoopback() || ip.IsLinkLocalUnicast() {
return true
}
// 判断 IPv6 fc00::/7 私网地址段
if ip[0]&0xfe == 0xfc {
return true
}
return false
}
// fallbackToLocalhost 根据传入地址返回对应的本地回环地址
func fallbackToLocalhost(listen string) string {
ip := net.ParseIP(listen)
if ip == nil {
// 无法解析则默认回退 IPv4 回环
return "127.0.0.1"
}
if ip.To4() != nil {
// IPv4 回退 IPv4 回环
return "127.0.0.1"
}
// IPv6 回退 IPv6 回环
return "::1"
}
func init() {
for _, cidr := range []string{
"10.0.0.0/8", // A类私网
"172.16.0.0/12", // B类私网
"192.168.0.0/16", // C类私网
"100.64.0.0/10", // CGNAT地址段
"127.0.0.0/8", // 回环
} {
_, netw, err := net.ParseCIDR(cidr)
if err == nil {
privateIPv4Nets = append(privateIPv4Nets, netw)
}
}
}