Compare commits

...

44 Commits

Author SHA1 Message Date
刘祥超
39f9fa0e6b 访客IP设置中支持多个请求报头 2023-09-17 19:14:34 +08:00
刘祥超
0c8ae8960d 优化节点升级提示文字 2023-09-17 09:54:14 +08:00
刘祥超
ffd6a20829 优化界面文字和细节 2023-09-14 09:18:36 +08:00
刘祥超
2ee3e2782c 版本号修改为1.2.9 2023-09-14 09:01:40 +08:00
刘祥超
61324c28c6 在WAF规则集动作中优化已删除IP名单提示 2023-09-13 17:25:03 +08:00
刘祥超
e9f70ecb90 增加删除IP名单任务 2023-09-13 17:14:33 +08:00
刘祥超
cc3b802575 优化文字提示 2023-09-13 11:52:49 +08:00
刘祥超
9b780ff4a3 提交components.js 2023-09-12 15:02:00 +08:00
刘祥超
deb1a33e78 WebP增加图片像素限制提示 2023-09-11 15:48:02 +08:00
刘祥超
5021350aa0 WAF策略中验证码动作页面模板中使用<form></form>包裹${body}时提示警告 2023-09-10 18:05:38 +08:00
刘祥超
41b3dab135 删除不需要的文件 2023-09-08 19:36:48 +08:00
刘祥超
ecf94170c8 优化访客IP地址设置 2023-09-07 18:01:52 +08:00
刘祥超
56acdf3ce3 集群设置 -- 网站设置-- 增加“处理未绑定域名方式”选项 2023-09-07 15:15:35 +08:00
刘祥超
5d6d970d49 优化<digit-input>组件 2023-09-07 11:46:37 +08:00
刘祥超
b501cf3c88 重新实现套餐相关功能 2023-09-06 16:31:05 +08:00
刘祥超
deac2baa18 优化源站选择证书提示 2023-09-02 17:07:01 +08:00
刘祥超
b1e3f54055 优化mysql安装程序 2023-09-01 11:57:28 +08:00
刘祥超
8ebf79ffbe 优化文字提示 2023-08-27 21:37:49 +08:00
刘祥超
8d7307cddf WebP支持的默认格式中去除image/gif 2023-08-25 18:04:38 +08:00
刘祥超
c547837ae3 优化节点DNS线路选择组件 2023-08-25 17:30:21 +08:00
刘祥超
b408996e72 修复单个节点同属多个集群时DNS线路设置自动复制的问题 2023-08-25 17:28:07 +08:00
刘祥超
6b5866524d 有消息提示时页面标题增加点符号提示 2023-08-25 16:08:55 +08:00
刘祥超
4994dfb488 网站设置增加是否支持${serverAddr}选项 2023-08-25 15:31:08 +08:00
刘祥超
5d5040651e 优化几个内置的页面版本,增加连接信息,方便诊断 2023-08-25 15:03:31 +08:00
刘祥超
5252188a68 优化IP列表中区域显示 2023-08-24 12:33:06 +08:00
刘祥超
3f664882d5 使用查询本地IP库代替API查询IP信息 2023-08-24 12:22:16 +08:00
刘祥超
15f2a2d517 修复 安全设置 -- 允许访问的国家和地区 中不能使用中国特殊区域的问题 2023-08-24 11:50:21 +08:00
刘祥超
6637b0fb8f 删除不需要的文件 2023-08-24 11:01:30 +08:00
刘祥超
a06eeb9129 优化文字提示 2023-08-24 11:01:06 +08:00
刘祥超
a24fce2c22 反向代理增加是否重试50X选项,默认为启用 2023-08-20 15:49:09 +08:00
刘祥超
ddbdb64fc4 优化缓存条件界面文字 2023-08-20 11:16:58 +08:00
刘祥超
38c6d545ec 添加域名时移除多余的端口号 2023-08-20 10:27:55 +08:00
刘祥超
fe880abbb0 优化添加域名表单提示 2023-08-20 10:22:35 +08:00
刘祥超
081ed76c39 优化api_admin.yaml生成 2023-08-15 10:34:20 +08:00
刘祥超
613a00686f 修复Dockfile一处注释错误 2023-08-14 14:29:52 +08:00
刘祥超
11739ee671 修改Dockfile中的版本号为1.2.8 2023-08-14 12:21:53 +08:00
刘祥超
d06951e3b5 提交components.js 2023-08-14 12:21:42 +08:00
刘祥超
63bbacd4d8 版本号修改为1.2.8 2023-08-14 12:21:34 +08:00
刘祥超
6ba130b62c 自动生成新的配置文件(api_admin.yaml) 2023-08-14 12:21:00 +08:00
刘祥超
5529d644ac 优化缓存条件;默认缓存有效期从2个小时改为1天 2023-08-14 10:42:36 +08:00
刘祥超
7e5e6fb5b4 请求条件中的“URL前缀”改为“URL目录前缀” 2023-08-14 10:17:06 +08:00
刘祥超
f5fb1911f7 缓存条件中的“URL前缀”改为“URL目录前缀” 2023-08-14 10:02:01 +08:00
刘祥超
e26e129a63 缓存条件中的“条件类型”改为“缓存对象” 2023-08-14 09:59:55 +08:00
刘祥超
280cedad8d Dockerfile指定平台为amd64 2023-08-14 09:25:03 +08:00
78 changed files with 988 additions and 589 deletions

View File

@@ -1,11 +1,15 @@
FROM alpine:latest
FROM --platform=linux/amd64 alpine:latest
LABEL maintainer="goedge.cdn@gmail.com"
ENV TZ "Asia/Shanghai"
ENV VERSION 1.2.7
ENV VERSION 1.2.8
ENV ROOT_DIR /usr/local/goedge
ENV TAR_FILE edge-admin-linux-amd64-plus-v${VERSION}.zip
# remote official repository
ENV TAR_URL "https://dl.goedge.cn/edge/v${VERSION}/edge-admin-linux-amd64-plus-v${VERSION}.zip"
#ENV TAR_URL "http://192.168.2.61:8080/edge-admin-linux-amd64-plus-v${VERSION}.zip" # your local repository
# your local repository
#ENV TAR_URL "http://192.168.2.61:8080/edge-admin-linux-amd64-plus-v${VERSION}.zip"
RUN apk add --no-cache tzdata

View File

@@ -41,12 +41,17 @@ func LoadAPIConfig() (*APIConfig, error) {
var data []byte
var err error
var isFromOld = false
for _, path := range paths {
data, err = os.ReadFile(path)
if err == nil {
if path == realFile || path == oldRealFile {
isFromLocal = true
}
// 自动生成新的配置文件
isFromOld = path == oldRealFile
break
}
}
@@ -70,6 +75,12 @@ func LoadAPIConfig() (*APIConfig, error) {
_ = os.WriteFile(realFile, data, 0666)
}
// 自动生成新配置文件
if isFromOld {
config.OldRPC.Endpoints = nil
_ = config.WriteFile(Tea.ConfigFile(ConfigFileName))
}
return config, nil
}

View File

@@ -1,9 +1,9 @@
package teaconst
const (
Version = "1.2.7"
Version = "1.2.9"
APINodeVersion = "1.2.7"
APINodeVersion = "1.2.9"
ProductName = "Edge Admin"
ProcessName = "edge-admin"

View File

@@ -9,6 +9,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
)
@@ -35,6 +36,7 @@ func (this *IndexAction) RunGet(params struct {
clusters = append(clusters, node.SecondaryNodeClusters...)
var allDNSRouteMaps = map[int64][]maps.Map{} // domain id => routes
var routeMaps = map[int64][]maps.Map{} // domain id => routes
var domainIds = []int64{}
for _, cluster := range clusters {
dnsInfoResp, err := this.RPC().NodeRPC().FindEnabledNodeDNS(this.AdminContext(), &pb.FindEnabledNodeDNSRequest{
NodeId: params.NodeId,
@@ -49,6 +51,13 @@ func (this *IndexAction) RunGet(params struct {
continue
}
var domainId = dnsInfo.DnsDomainId
// remove same domain
if lists.ContainsInt64(domainIds, domainId) {
continue
}
domainIds = append(domainIds, domainId)
var domainName = dnsInfo.DnsDomainName
if len(dnsInfo.Routes) > 0 {
for _, route := range dnsInfo.Routes {
@@ -102,15 +111,23 @@ func (this *IndexAction) RunPost(params struct {
}) {
defer this.CreateLogInfo(codes.NodeDNS_LogUpdateNodeDNS, params.NodeId)
dnsRouteCodes := []string{}
var rawRouteCodes = []string{}
if len(params.DnsRoutesJSON) > 0 {
err := json.Unmarshal(params.DnsRoutesJSON, &dnsRouteCodes)
err := json.Unmarshal(params.DnsRoutesJSON, &rawRouteCodes)
if err != nil {
this.ErrorPage(err)
return
}
}
// remove duplications
var dnsRouteCodes = []string{}
for _, routeCode := range rawRouteCodes {
if !lists.ContainsString(dnsRouteCodes, routeCode) {
dnsRouteCodes = append(dnsRouteCodes, routeCode)
}
}
_, err := this.RPC().NodeRPC().UpdateNodeDNS(this.AdminContext(), &pb.UpdateNodeDNSRequest{
NodeId: params.NodeId,
IpAddr: "",

View File

@@ -43,13 +43,19 @@ func (this *IndexAction) RunGet(params struct {
}
this.Data["config"] = config
var httpAllDomainMismatchActionCode = serverconfigs.DomainMismatchActionPage
var httpAllDomainMismatchActionContentHTML string
var httpAllDomainMismatchActionStatusCode = "404"
if config.HTTPAll.DomainMismatchAction != nil && config.HTTPAll.DomainMismatchAction.Options != nil {
httpAllDomainMismatchActionContentHTML = config.HTTPAll.DomainMismatchAction.Options.GetString("contentHTML")
var statusCode = config.HTTPAll.DomainMismatchAction.Options.GetInt("statusCode")
if statusCode > 0 {
httpAllDomainMismatchActionStatusCode = types.String(statusCode)
if config.HTTPAll.DomainMismatchAction != nil {
httpAllDomainMismatchActionCode = config.HTTPAll.DomainMismatchAction.Code
if config.HTTPAll.DomainMismatchAction.Options != nil {
// 即使是非 page 处理动作,也读取这些内容,以便于在切换到 page 时,可以顺利读取到先前的设置
httpAllDomainMismatchActionContentHTML = config.HTTPAll.DomainMismatchAction.Options.GetString("contentHTML")
var statusCode = config.HTTPAll.DomainMismatchAction.Options.GetInt("statusCode")
if statusCode > 0 {
httpAllDomainMismatchActionStatusCode = types.String(statusCode)
}
}
} else {
httpAllDomainMismatchActionContentHTML = `<!DOCTYPE html>
@@ -73,6 +79,7 @@ p { color: grey; }
</html>`
}
this.Data["httpAllDomainMismatchActionCode"] = httpAllDomainMismatchActionCode
this.Data["httpAllDomainMismatchActionContentHTML"] = httpAllDomainMismatchActionContentHTML
this.Data["httpAllDomainMismatchActionStatusCode"] = httpAllDomainMismatchActionStatusCode
@@ -83,6 +90,7 @@ func (this *IndexAction) RunPost(params struct {
ClusterId int64
HttpAllMatchDomainStrictly bool
HttpAllDomainMismatchActionCode string
HttpAllDomainMismatchActionContentHTML string
HttpAllDomainMismatchActionStatusCode string
HttpAllAllowMismatchDomainsJSON []byte
@@ -90,11 +98,13 @@ func (this *IndexAction) RunPost(params struct {
HttpAllDefaultDomain string
HttpAllNodeIPPageHTML string
HttpAllNodeIPShowPage bool
HttpAllEnableServerAddrVariable bool
HttpAllServerName string
HttpAllSupportsLowVersionHTTP bool
HttpAllMatchCertFromAllServers bool
HttpAllForceLnRequest bool
HttpAllServerName string
HttpAllSupportsLowVersionHTTP bool
HttpAllMatchCertFromAllServers bool
HttpAllForceLnRequest bool
HttpAllLnRequestSchedulingMethod string
HttpAccessLogIsOn bool
HttpAccessLogEnableRequestHeaders bool
@@ -139,7 +149,7 @@ func (this *IndexAction) RunPost(params struct {
config.HTTPAll.MatchDomainStrictly = params.HttpAllMatchDomainStrictly
config.HTTPAll.DomainMismatchAction = &serverconfigs.DomainMismatchAction{
Code: serverconfigs.DomainMismatchActionPage,
Code: params.HttpAllDomainMismatchActionCode,
Options: maps.Map{
"statusCode": domainMisMatchStatusCode,
"contentHTML": params.HttpAllDomainMismatchActionContentHTML,
@@ -167,6 +177,8 @@ func (this *IndexAction) RunPost(params struct {
config.HTTPAll.SupportsLowVersionHTTP = params.HttpAllSupportsLowVersionHTTP
config.HTTPAll.MatchCertFromAllServers = params.HttpAllMatchCertFromAllServers
config.HTTPAll.ForceLnRequest = params.HttpAllForceLnRequest
config.HTTPAll.LnRequestSchedulingMethod = params.HttpAllLnRequestSchedulingMethod
config.HTTPAll.EnableServerAddrVariable = params.HttpAllEnableServerAddrVariable
// 访问日志
config.HTTPAccessLog.IsOn = params.HttpAccessLogIsOn

View File

@@ -3,11 +3,11 @@ package log
import (
"bytes"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
timeutil "github.com/iwind/TeaGo/utils/time"
"github.com/tealeg/xlsx/v3"
"strconv"
"strings"
)
type ExportExcelAction struct {
@@ -65,23 +65,11 @@ func (this *ExportExcelAction) RunGet(params struct {
for _, log := range logsResp.Logs {
var regionName = ""
var ispName = ""
regionResp, err := this.RPC().IPLibraryRPC().LookupIPRegion(this.AdminContext(), &pb.LookupIPRegionRequest{Ip: log.Ip})
if err != nil {
this.ErrorPage(err)
return
}
if regionResp.IpRegion != nil {
regionName = regionResp.IpRegion.Summary
// remove isp from regionName
var index = strings.LastIndex(regionName, "|")
if index > 0 {
regionName = regionName[:index]
}
if len(regionResp.IpRegion.Isp) > 0 {
ispName = regionResp.IpRegion.Isp
}
var ipRegion = iplibrary.LookupIP(log.Ip)
if ipRegion != nil && ipRegion.IsOk() {
regionName = ipRegion.RegionSummary()
ispName = ipRegion.ProviderName()
}
var row = sheet.AddRow()

View File

@@ -3,6 +3,7 @@ package log
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
@@ -84,14 +85,10 @@ func (this *IndexAction) RunGet(params struct {
}
var logMaps = []maps.Map{}
for _, log := range logsResp.Logs {
regionName := ""
regionResp, err := this.RPC().IPLibraryRPC().LookupIPRegion(this.AdminContext(), &pb.LookupIPRegionRequest{Ip: log.Ip})
if err != nil {
this.ErrorPage(err)
return
}
if regionResp.IpRegion != nil {
regionName = regionResp.IpRegion.Summary
var regionName = ""
var ipRegion = iplibrary.LookupIP(log.Ip)
if ipRegion != nil && ipRegion.IsOk() {
regionName = ipRegion.Summary()
}
logMaps = append(logMaps, maps.Map{

View File

@@ -4,6 +4,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"net"
"net/url"
"regexp"
"strings"
@@ -43,6 +44,14 @@ func (this *AddServerNamePopupAction) RunPost(params struct {
}
}
// 去除端口
if regexp.MustCompile(`:\d+$`).MatchString(serverName) {
host, _, err := net.SplitHostPort(serverName)
if err == nil && len(host) > 0 {
serverName = host
}
}
params.Must.
Field("serverName", serverName).
Require("请输入域名")
@@ -72,6 +81,14 @@ func (this *AddServerNamePopupAction) RunPost(params struct {
}
}
// 去除端口
if regexp.MustCompile(`:\d+$`).MatchString(serverName) {
host, _, err := net.SplitHostPort(serverName)
if err == nil && len(host) > 0 {
serverName = host
}
}
// 转成小写
serverName = strings.ToLower(serverName)

View File

@@ -3,6 +3,7 @@ package waf
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/iwind/TeaGo/lists"
@@ -141,20 +142,7 @@ func (this *LogAction) RunGet(params struct {
this.Data["groups"] = groupMaps
// 根据IP查询区域
regionMap := map[string]string{} // ip => region
if len(ipList) > 0 {
resp, err := this.RPC().IPLibraryRPC().LookupIPRegions(this.AdminContext(), &pb.LookupIPRegionsRequest{IpList: ipList})
if err != nil {
this.ErrorPage(err)
return
}
if resp.IpRegionMap != nil {
for ip, region := range resp.IpRegionMap {
regionMap[ip] = region.Summary
}
}
}
this.Data["regions"] = regionMap
this.Data["regions"] = iplibrary.LookupIPSummaries(ipList)
// WAF相关
var wafInfos = map[int64]maps.Map{} // set id => WAF Map

View File

@@ -564,9 +564,11 @@ func (this *CreateAction) RunPost(params struct {
var remoteAddrConfig = &serverconfigs.HTTPRemoteAddrConfig{
IsOn: true,
Value: "${rawRemoteAddr}",
Type: serverconfigs.HTTPRemoteAddrTypeDefault,
}
if params.RemoteAddrIsOn {
remoteAddrConfig.Value = "${remoteAddr}"
remoteAddrConfig.Type = serverconfigs.HTTPRemoteAddrTypeProxy
}
remoteAddrConfigJSON, err := json.Marshal(remoteAddrConfig)
if err != nil {

View File

@@ -42,7 +42,7 @@ func (this *IndexAction) RunGet(params struct {
}
this.Data["reverseProxyRef"] = reverseProxyRef
var reverseProxy = &serverconfigs.ReverseProxyConfig{}
var reverseProxy = serverconfigs.NewReverseProxyConfig()
err = json.Unmarshal(reverseProxyResp.ReverseProxyJSON, reverseProxy)
if err != nil {
this.ErrorPage(err)

View File

@@ -34,7 +34,7 @@ func (this *SchedulingAction) RunGet(params struct {
this.ErrorPage(err)
return
}
reverseProxy := &serverconfigs.ReverseProxyConfig{}
reverseProxy := serverconfigs.NewReverseProxyConfig()
err = json.Unmarshal(reverseProxyResp.ReverseProxyJSON, reverseProxy)
if err != nil {
this.ErrorPage(err)

View File

@@ -36,14 +36,14 @@ func (this *SettingAction) RunGet(params struct {
this.ErrorPage(err)
return
}
reverseProxyRef := &serverconfigs.ReverseProxyRef{}
var reverseProxyRef = &serverconfigs.ReverseProxyRef{}
err = json.Unmarshal(reverseProxyResp.ReverseProxyRefJSON, reverseProxyRef)
if err != nil {
this.ErrorPage(err)
return
}
reverseProxy := &serverconfigs.ReverseProxyConfig{}
var reverseProxy = serverconfigs.NewReverseProxyConfig()
err = json.Unmarshal(reverseProxyResp.ReverseProxyJSON, reverseProxy)
if err != nil {
this.ErrorPage(err)
@@ -67,7 +67,7 @@ func (this *SettingAction) RunPost(params struct {
// TODO 校验配置
var reverseProxyConfig = &serverconfigs.ReverseProxyConfig{}
var reverseProxyConfig = serverconfigs.NewReverseProxyConfig()
err := json.Unmarshal(params.ReverseProxyJSON, reverseProxyConfig)
if err != nil {
this.ErrorPage(err)
@@ -110,6 +110,7 @@ func (this *SettingAction) RunPost(params struct {
AddHeaders: reverseProxyConfig.AddHeaders,
FollowRedirects: reverseProxyConfig.FollowRedirects,
ProxyProtocolJSON: proxyProtocolJSON,
Retry50X: reverseProxyConfig.Retry50X,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -10,6 +10,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions"
"regexp"
"strings"
)
@@ -54,9 +55,29 @@ func (this *IndexAction) RunPost(params struct {
err := json.Unmarshal(params.RemoteAddrJSON, remoteAddrConfig)
if err != nil {
this.Fail("参数校验失败:" + err.Error())
return
}
remoteAddrConfig.Value = strings.TrimSpace(remoteAddrConfig.Value)
switch remoteAddrConfig.Type {
case serverconfigs.HTTPRemoteAddrTypeRequestHeader:
if len(remoteAddrConfig.RequestHeaderName) == 0 {
this.FailField("requestHeaderName", "请输入请求报头")
return
}
if !regexp.MustCompile(`^[\w-_,]+$`).MatchString(remoteAddrConfig.RequestHeaderName) {
this.FailField("requestHeaderName", "请求报头中只能含有数字、英文字母、下划线、中划线")
return
}
remoteAddrConfig.Value = "${header." + remoteAddrConfig.RequestHeaderName + "}"
case serverconfigs.HTTPRemoteAddrTypeVariable:
if len(remoteAddrConfig.Value) == 0 {
this.FailField("value", "请输入自定义变量")
return
}
}
err = remoteAddrConfig.Init()
if err != nil {
this.Fail("配置校验失败:" + err.Error())

View File

@@ -42,7 +42,7 @@ func (this *IndexAction) RunGet(params struct {
}
this.Data["reverseProxyRef"] = reverseProxyRef
var reverseProxy = &serverconfigs.ReverseProxyConfig{}
var reverseProxy = serverconfigs.NewReverseProxyConfig()
err = json.Unmarshal(reverseProxyResp.ReverseProxyJSON, reverseProxy)
if err != nil {
this.ErrorPage(err)

View File

@@ -34,7 +34,7 @@ func (this *SchedulingAction) RunGet(params struct {
this.ErrorPage(err)
return
}
reverseProxy := &serverconfigs.ReverseProxyConfig{}
var reverseProxy = serverconfigs.NewReverseProxyConfig()
err = json.Unmarshal(reverseProxyResp.ReverseProxyJSON, reverseProxy)
if err != nil {
this.ErrorPage(err)
@@ -42,8 +42,8 @@ func (this *SchedulingAction) RunGet(params struct {
}
this.Data["reverseProxyId"] = reverseProxy.Id
schedulingCode := reverseProxy.FindSchedulingConfig().Code
schedulingMap := schedulingconfigs.FindSchedulingType(schedulingCode)
var schedulingCode = reverseProxy.FindSchedulingConfig().Code
var schedulingMap = schedulingconfigs.FindSchedulingType(schedulingCode)
if schedulingMap == nil {
this.ErrorPage(errors.New("invalid scheduling code '" + schedulingCode + "'"))
return

View File

@@ -36,14 +36,14 @@ func (this *SettingAction) RunGet(params struct {
this.ErrorPage(err)
return
}
reverseProxyRef := &serverconfigs.ReverseProxyRef{}
var reverseProxyRef = &serverconfigs.ReverseProxyRef{}
err = json.Unmarshal(reverseProxyResp.ReverseProxyRefJSON, reverseProxyRef)
if err != nil {
this.ErrorPage(err)
return
}
reverseProxy := &serverconfigs.ReverseProxyConfig{}
var reverseProxy = serverconfigs.NewReverseProxyConfig()
err = json.Unmarshal(reverseProxyResp.ReverseProxyJSON, reverseProxy)
if err != nil {
this.ErrorPage(err)
@@ -67,7 +67,7 @@ func (this *SettingAction) RunPost(params struct {
// TODO 校验配置
var reverseProxyConfig = &serverconfigs.ReverseProxyConfig{}
var reverseProxyConfig = serverconfigs.NewReverseProxyConfig()
err := json.Unmarshal(params.ReverseProxyJSON, reverseProxyConfig)
if err != nil {
this.ErrorPage(err)
@@ -110,6 +110,7 @@ func (this *SettingAction) RunPost(params struct {
AddHeaders: reverseProxyConfig.AddHeaders,
FollowRedirects: reverseProxyConfig.FollowRedirects,
ProxyProtocolJSON: proxyProtocolJSON,
Retry50X: reverseProxyConfig.Retry50X,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -42,7 +42,7 @@ func (this *IndexAction) RunGet(params struct {
}
this.Data["reverseProxyRef"] = reverseProxyRef
var reverseProxy = &serverconfigs.ReverseProxyConfig{}
var reverseProxy = serverconfigs.NewReverseProxyConfig()
err = json.Unmarshal(reverseProxyResp.ReverseProxyJSON, reverseProxy)
if err != nil {
this.ErrorPage(err)

View File

@@ -34,7 +34,7 @@ func (this *SchedulingAction) RunGet(params struct {
this.ErrorPage(err)
return
}
reverseProxy := &serverconfigs.ReverseProxyConfig{}
var reverseProxy = serverconfigs.NewReverseProxyConfig()
err = json.Unmarshal(reverseProxyResp.ReverseProxyJSON, reverseProxy)
if err != nil {
this.ErrorPage(err)

View File

@@ -43,7 +43,7 @@ func (this *SettingAction) RunGet(params struct {
return
}
reverseProxy := &serverconfigs.ReverseProxyConfig{}
var reverseProxy = serverconfigs.NewReverseProxyConfig()
err = json.Unmarshal(reverseProxyResp.ReverseProxyJSON, reverseProxy)
if err != nil {
this.ErrorPage(err)
@@ -67,7 +67,7 @@ func (this *SettingAction) RunPost(params struct {
// TODO 校验配置
reverseProxyConfig := &serverconfigs.ReverseProxyConfig{}
var reverseProxyConfig = serverconfigs.NewReverseProxyConfig()
err := json.Unmarshal(params.ReverseProxyJSON, reverseProxyConfig)
if err != nil {
this.ErrorPage(err)

View File

@@ -4,10 +4,10 @@ package ipbox
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
"strings"
"time"
)
@@ -25,24 +25,15 @@ func (this *IndexAction) RunGet(params struct {
this.Data["ip"] = params.Ip
// IP信息
regionResp, err := this.RPC().IPLibraryRPC().LookupIPRegion(this.AdminContext(), &pb.LookupIPRegionRequest{Ip: params.Ip})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["regions"] = ""
this.Data["isp"] = ""
if regionResp.IpRegion != nil {
var regionName = regionResp.IpRegion.Summary
// remove isp from regionName
var index = strings.LastIndex(regionName, "|")
if index > 0 {
regionName = regionName[:index]
}
this.Data["regions"] = regionName
this.Data["isp"] = regionResp.IpRegion.Isp
var ipRegion = iplibrary.LookupIP(params.Ip)
if ipRegion != nil && ipRegion.IsOk() {
this.Data["regions"] = ipRegion.RegionSummary()
this.Data["isp"] = ipRegion.ProviderName()
}
// IP列表

View File

@@ -4,11 +4,11 @@ package iplists
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
"strings"
"time"
)
@@ -162,22 +162,10 @@ func (this *IndexAction) RunGet(params struct {
var region = ""
var isp = ""
if len(item.IpFrom) > 0 && len(item.IpTo) == 0 {
regionResp, err := this.RPC().IPLibraryRPC().LookupIPRegion(this.AdminContext(), &pb.LookupIPRegionRequest{Ip: item.IpFrom})
if err != nil {
this.ErrorPage(err)
return
}
var ipRegion = regionResp.IpRegion
if ipRegion != nil {
region = ipRegion.Summary
// remove isp from regionName
var index = strings.LastIndex(region, "|")
if index > 0 {
region = region[:index]
}
isp = ipRegion.Isp
var ipRegion = iplibrary.LookupIP(item.IpFrom)
if ipRegion != nil && ipRegion.IsOk() {
region = ipRegion.RegionSummary()
isp = ipRegion.ProviderName()
}
}

View File

@@ -4,11 +4,11 @@ package iplists
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
"strings"
"time"
)
@@ -109,22 +109,10 @@ func (this *ItemsAction) RunGet(params struct {
var region = ""
var isp = ""
if len(item.IpFrom) > 0 && len(item.IpTo) == 0 {
regionResp, err := this.RPC().IPLibraryRPC().LookupIPRegion(this.AdminContext(), &pb.LookupIPRegionRequest{Ip: item.IpFrom})
if err != nil {
this.ErrorPage(err)
return
}
var ipRegion = regionResp.IpRegion
if ipRegion != nil {
region = ipRegion.Summary
// remove isp from regionName
var index = strings.LastIndex(region, "|")
if index > 0 {
region = region[:index]
}
isp = ipRegion.Isp
var ipRegion = iplibrary.LookupIP(item.IpFrom)
if ipRegion != nil && ipRegion.IsOk() {
region = ipRegion.RegionSummary()
isp = ipRegion.ProviderName()
}
}

View File

@@ -5,6 +5,7 @@ package logs
import (
"fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
@@ -169,20 +170,7 @@ func (this *IndexAction) RunGet(params struct {
}
// 根据IP查询区域
var regionMap = map[string]string{} // ip => region
if len(ipList) > 0 {
resp, err := this.RPC().IPLibraryRPC().LookupIPRegions(this.AdminContext(), &pb.LookupIPRegionsRequest{IpList: ipList})
if err != nil {
this.ErrorPage(err)
return
}
if resp.IpRegionMap != nil {
for ip, region := range resp.IpRegionMap {
regionMap[ip] = region.Summary
}
}
}
this.Data["regions"] = regionMap
this.Data["regions"] = iplibrary.LookupIPSummaries(ipList)
// WAF相关
var wafInfos = map[int64]maps.Map{} // set id => WAF Map

View File

@@ -1,6 +1,7 @@
package log
import (
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
@@ -160,20 +161,7 @@ func (this *HistoryAction) RunGet(params struct {
}
// 根据IP查询区域
var regionMap = map[string]string{} // ip => region
if len(ipList) > 0 {
resp, err := this.RPC().IPLibraryRPC().LookupIPRegions(this.AdminContext(), &pb.LookupIPRegionsRequest{IpList: ipList})
if err != nil {
this.ErrorPage(err)
return
}
if resp.IpRegionMap != nil {
for ip, region := range resp.IpRegionMap {
regionMap[ip] = region.Summary
}
}
}
this.Data["regions"] = regionMap
this.Data["regions"] = iplibrary.LookupIPSummaries(ipList)
// WAF相关
var wafInfos = map[int64]maps.Map{} // set id => WAF Map

View File

@@ -1,6 +1,7 @@
package log
import (
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists"
@@ -129,20 +130,7 @@ func (this *IndexAction) RunPost(params struct {
this.Data["hasMore"] = accessLogsResp.HasMore
// 根据IP查询区域
var regionMap = map[string]string{} // ip => region
if len(ipList) > 0 {
resp, err := this.RPC().IPLibraryRPC().LookupIPRegions(this.AdminContext(), &pb.LookupIPRegionsRequest{IpList: ipList})
if err != nil {
this.ErrorPage(err)
return
}
if resp.IpRegionMap != nil {
for ip, region := range resp.IpRegionMap {
regionMap[ip] = region.Summary
}
}
}
this.Data["regions"] = regionMap
this.Data["regions"] = iplibrary.LookupIPSummaries(ipList)
// WAF相关
var wafInfos = map[int64]maps.Map{} // set id => WAF Map

View File

@@ -1,6 +1,7 @@
package log
import (
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
@@ -141,20 +142,7 @@ func (this *TodayAction) RunGet(params struct {
}
// 根据IP查询区域
var regionMap = map[string]string{} // ip => region
if len(ipList) > 0 {
resp, err := this.RPC().IPLibraryRPC().LookupIPRegions(this.AdminContext(), &pb.LookupIPRegionsRequest{IpList: ipList})
if err != nil {
this.ErrorPage(err)
return
}
if resp.IpRegionMap != nil {
for ip, region := range resp.IpRegionMap {
regionMap[ip] = region.Summary
}
}
}
this.Data["regions"] = regionMap
this.Data["regions"] = iplibrary.LookupIPSummaries(ipList)
// WAF相关
var wafInfos = map[int64]maps.Map{} // set id => WAF Map

View File

@@ -2,6 +2,7 @@ package log
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
"net/http"
@@ -86,24 +87,11 @@ func (this *ViewPopupAction) RunGet(params struct {
// 地域相关
var regionMap maps.Map = nil
regionResp, err := this.RPC().IPLibraryRPC().LookupIPRegion(this.AdminContext(), &pb.LookupIPRegionRequest{Ip: accessLog.RemoteAddr})
if err != nil {
this.ErrorPage(err)
return
}
region := regionResp.IpRegion
if region != nil {
var regionName = region.Summary
// remove isp from regionName
var index = strings.LastIndex(regionName, "|")
if index > 0 {
regionName = regionName[:index]
}
var ipRegion = iplibrary.LookupIP(accessLog.RemoteAddr)
if ipRegion != nil && ipRegion.IsOk() {
regionMap = maps.Map{
"full": regionName,
"isp": region.Isp,
"full": ipRegion.RegionSummary(),
"isp": ipRegion.ProviderName(),
}
}
this.Data["region"] = regionMap

View File

@@ -9,6 +9,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions"
"regexp"
"strings"
)
@@ -46,9 +47,29 @@ func (this *IndexAction) RunPost(params struct {
err := json.Unmarshal(params.RemoteAddrJSON, remoteAddrConfig)
if err != nil {
this.Fail("参数校验失败:" + err.Error())
return
}
remoteAddrConfig.Value = strings.TrimSpace(remoteAddrConfig.Value)
switch remoteAddrConfig.Type {
case serverconfigs.HTTPRemoteAddrTypeRequestHeader:
if len(remoteAddrConfig.RequestHeaderName) == 0 {
this.FailField("requestHeaderName", "请输入请求报头")
return
}
if !regexp.MustCompile(`^[\w-_,]+$`).MatchString(remoteAddrConfig.RequestHeaderName) {
this.FailField("requestHeaderName", "请求报头中只能含有数字、英文字母、下划线、中划线")
return
}
remoteAddrConfig.Value = "${header." + remoteAddrConfig.RequestHeaderName + "}"
case serverconfigs.HTTPRemoteAddrTypeVariable:
if len(remoteAddrConfig.Value) == 0 {
this.FailField("value", "请输入自定义变量")
return
}
}
err = remoteAddrConfig.Init()
if err != nil {
this.Fail("配置校验失败:" + err.Error())

View File

@@ -41,7 +41,7 @@ func (this *IndexAction) RunGet(params struct {
}
this.Data["reverseProxyRef"] = reverseProxyRef
var reverseProxy = &serverconfigs.ReverseProxyConfig{}
var reverseProxy = serverconfigs.NewReverseProxyConfig()
err = json.Unmarshal(reverseProxyResp.ReverseProxyJSON, reverseProxy)
if err != nil {
this.ErrorPage(err)

View File

@@ -18,7 +18,7 @@ func (this *SchedulingAction) Init() {
}
func (this *SchedulingAction) RunGet(params struct {
ServerId int64
ServerId int64
LocationId int64
}) {
reverseProxyResp, err := this.RPC().HTTPLocationRPC().FindAndInitHTTPLocationReverseProxyConfig(this.AdminContext(), &pb.FindAndInitHTTPLocationReverseProxyConfigRequest{LocationId: params.LocationId})
@@ -26,7 +26,7 @@ func (this *SchedulingAction) RunGet(params struct {
this.ErrorPage(err)
return
}
reverseProxy := &serverconfigs.ReverseProxyConfig{}
var reverseProxy = serverconfigs.NewReverseProxyConfig()
err = json.Unmarshal(reverseProxyResp.ReverseProxyJSON, reverseProxy)
if err != nil {
this.ErrorPage(err)
@@ -34,8 +34,8 @@ func (this *SchedulingAction) RunGet(params struct {
}
this.Data["reverseProxyId"] = reverseProxy.Id
schedulingCode := reverseProxy.FindSchedulingConfig().Code
schedulingMap := schedulingconfigs.FindSchedulingType(schedulingCode)
var schedulingCode = reverseProxy.FindSchedulingConfig().Code
var schedulingMap = schedulingconfigs.FindSchedulingType(schedulingCode)
if schedulingMap == nil {
this.ErrorPage(errors.New("invalid scheduling code '" + schedulingCode + "'"))
return

View File

@@ -34,7 +34,7 @@ func (this *SettingAction) RunGet(params struct {
return
}
reverseProxy := &serverconfigs.ReverseProxyConfig{}
var reverseProxy = serverconfigs.NewReverseProxyConfig()
err = json.Unmarshal(reverseProxyResp.ReverseProxyJSON, reverseProxy)
if err != nil {
this.ErrorPage(err)
@@ -58,7 +58,7 @@ func (this *SettingAction) RunPost(params struct {
// TODO 校验配置
var reverseProxyConfig = &serverconfigs.ReverseProxyConfig{}
var reverseProxyConfig = serverconfigs.NewReverseProxyConfig()
err := json.Unmarshal(params.ReverseProxyJSON, reverseProxyConfig)
if err != nil {
this.ErrorPage(err)
@@ -101,6 +101,7 @@ func (this *SettingAction) RunPost(params struct {
AddHeaders: reverseProxyConfig.AddHeaders,
FollowRedirects: reverseProxyConfig.FollowRedirects,
ProxyProtocolJSON: proxyProtocolJSON,
Retry50X: reverseProxyConfig.Retry50X,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -10,6 +10,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/types"
"regexp"
"strings"
)
@@ -63,6 +64,25 @@ func (this *IndexAction) RunPost(params struct {
}
remoteAddrConfig.Value = strings.TrimSpace(remoteAddrConfig.Value)
switch remoteAddrConfig.Type {
case serverconfigs.HTTPRemoteAddrTypeRequestHeader:
if len(remoteAddrConfig.RequestHeaderName) == 0 {
this.FailField("requestHeaderName", "请输入请求报头")
return
}
if !regexp.MustCompile(`^[\w-_,]+$`).MatchString(remoteAddrConfig.RequestHeaderName) {
this.FailField("requestHeaderName", "请求报头中只能含有数字、英文字母、下划线、中划线")
return
}
remoteAddrConfig.Value = "${header." + remoteAddrConfig.RequestHeaderName + "}"
case serverconfigs.HTTPRemoteAddrTypeVariable:
if len(remoteAddrConfig.Value) == 0 {
this.FailField("value", "请输入自定义变量")
return
}
}
err = remoteAddrConfig.Init()
if err != nil {
this.Fail("配置校验失败:" + err.Error())

View File

@@ -68,7 +68,7 @@ func (this *IndexAction) RunGet(params struct {
}
this.Data["reverseProxyRef"] = reverseProxyRef
var reverseProxy = &serverconfigs.ReverseProxyConfig{}
var reverseProxy = serverconfigs.NewReverseProxyConfig()
err = json.Unmarshal(reverseProxyResp.ReverseProxyJSON, reverseProxy)
if err != nil {
this.ErrorPage(err)

View File

@@ -25,7 +25,7 @@ func (this *SchedulingAction) RunGet(params struct {
this.ErrorPage(err)
return
}
reverseProxy := &serverconfigs.ReverseProxyConfig{}
var reverseProxy = serverconfigs.NewReverseProxyConfig()
err = json.Unmarshal(reverseProxyResp.ReverseProxyJSON, reverseProxy)
if err != nil {
this.ErrorPage(err)

View File

@@ -28,14 +28,14 @@ func (this *SettingAction) RunGet(params struct {
this.ErrorPage(err)
return
}
reverseProxyRef := &serverconfigs.ReverseProxyRef{}
var reverseProxyRef = &serverconfigs.ReverseProxyRef{}
err = json.Unmarshal(reverseProxyResp.ReverseProxyRefJSON, reverseProxyRef)
if err != nil {
this.ErrorPage(err)
return
}
reverseProxy := &serverconfigs.ReverseProxyConfig{}
var reverseProxy = serverconfigs.NewReverseProxyConfig()
err = json.Unmarshal(reverseProxyResp.ReverseProxyJSON, reverseProxy)
if err != nil {
this.ErrorPage(err)
@@ -57,7 +57,7 @@ func (this *SettingAction) RunPost(params struct {
}) {
defer this.CreateLogInfo(codes.ServerReverseProxy_LogUpdateServerReverseProxySettings, params.ServerId)
var reverseProxyConfig = &serverconfigs.ReverseProxyConfig{}
var reverseProxyConfig = serverconfigs.NewReverseProxyConfig()
err := json.Unmarshal(params.ReverseProxyJSON, reverseProxyConfig)
if err != nil {
this.ErrorPage(err)
@@ -133,6 +133,7 @@ func (this *SettingAction) RunPost(params struct {
ProxyProtocolJSON: proxyProtocolJSON,
FollowRedirects: reverseProxyConfig.FollowRedirects,
RequestHostExcludingPort: reverseProxyConfig.RequestHostExcludingPort,
Retry50X: reverseProxyConfig.Retry50X,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -43,7 +43,7 @@ func (this *UpdateSchedulingPopupAction) RunGet(params struct {
}
configData := reverseProxyResp.ReverseProxyJSON
reverseProxyConfig := &serverconfigs.ReverseProxyConfig{}
var reverseProxyConfig = serverconfigs.NewReverseProxyConfig()
err = json.Unmarshal(configData, reverseProxyConfig)
if err != nil {
this.ErrorPage(err)
@@ -126,8 +126,8 @@ func (this *UpdateSchedulingPopupAction) RunPost(params struct {
this.ErrorPage(err)
return
}
configData := reverseProxyResp.ReverseProxyJSON
reverseProxy := &serverconfigs.ReverseProxyConfig{}
var configData = reverseProxyResp.ReverseProxyJSON
var reverseProxy = serverconfigs.NewReverseProxyConfig()
err = json.Unmarshal(configData, reverseProxy)
if err != nil {
this.ErrorPage(err)

View File

@@ -41,6 +41,7 @@ func (this *CleanSettingAction) RunPost(params struct {
ServerAccessLogCleanDays int
ServerBandwidthStatCleanDays int
UserBandwidthStatCleanDays int
UserPlanBandwidthStatCleanDays int
ServerDailyStatCleanDays int
ServerDomainHourlyStatCleanDays int
TrafficDailyStatCleanDays int
@@ -85,6 +86,11 @@ func (this *CleanSettingAction) RunPost(params struct {
}
config.UserBandwidthStat.Clean.Days = params.UserBandwidthStatCleanDays
if params.UserPlanBandwidthStatCleanDays < 0 {
params.UserPlanBandwidthStatCleanDays = 0
}
config.UserPlanBandwidthStat.Clean.Days = params.UserPlanBandwidthStatCleanDays
if params.ServerDailyStatCleanDays < 0 {
params.ServerDailyStatCleanDays = 0
}

View File

@@ -342,7 +342,7 @@ func (this *MySQLInstaller) InstallFromFile(xzFilePath string, targetDir string)
}
// waiting for startup
for i := 0; i < 5; i++ {
for i := 0; i < 30; i++ {
_, err = net.Dial("tcp", "127.0.0.1:3306")
if err != nil {
time.Sleep(1 * time.Second)
@@ -362,7 +362,7 @@ func (this *MySQLInstaller) InstallFromFile(xzFilePath string, targetDir string)
this.log("changing mysql password ...")
var passwordSQL = "ALTER USER 'root'@'localhost' IDENTIFIED BY '" + newPassword + "';"
{
var cmd = utils.NewCmd("sh", "-c", baseDir+"/bin/mysql --user=root --password=\""+generatedPassword+"\" --execute=\""+passwordSQL+"\" --connect-expired-password")
var cmd = utils.NewCmd("sh", "-c", baseDir+"/bin/mysql --host=\"127.0.0.1\" --user=root --password=\""+generatedPassword+"\" --execute=\""+passwordSQL+"\" --connect-expired-password")
cmd.WithStderr()
err = cmd.Run()
if err != nil {

View File

@@ -4,6 +4,7 @@ import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/users/userutils"
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
@@ -60,13 +61,9 @@ func (this *UserAction) RunGet(params struct {
// IP地址
var registeredRegion = ""
if len(user.RegisteredIP) > 0 {
regionResp, err := this.RPC().IPLibraryRPC().LookupIPRegion(this.AdminContext(), &pb.LookupIPRegionRequest{Ip: user.RegisteredIP})
if err != nil {
this.ErrorPage(err)
return
}
if regionResp.IpRegion != nil {
registeredRegion = regionResp.IpRegion.Summary
var ipRegion = iplibrary.LookupIP(user.RegisteredIP)
if ipRegion != nil && ipRegion.IsOk() {
registeredRegion = ipRegion.Summary()
}
}

View File

@@ -2,10 +2,10 @@ package helpers
import (
"github.com/TeaOSLab/EdgeAdmin/internal/events"
nodes "github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/regionconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/logs"
@@ -77,23 +77,25 @@ func checkIPWithoutCache(config *systemconfigs.SecurityConfig, ipAddr string) bo
// 检查位置
if len(config.AllowCountryIds) > 0 || len(config.AllowProvinceIds) > 0 {
rpc, err := nodes.SharedRPC()
if err != nil {
logs.Println("[USER_MUST_AUTH][ERROR]" + err.Error())
var userRegion = iplibrary.Lookup(ip)
if userRegion == nil || !userRegion.IsOk() {
return false
}
resp, err := rpc.IPLibraryRPC().LookupIPRegion(rpc.Context(0), &pb.LookupIPRegionRequest{Ip: ipAddr})
if err != nil {
logs.Println("[USER_MUST_AUTH][ERROR]" + err.Error())
return false
if len(config.AllowCountryIds) > 0 {
// 检查大中华区
var found = false
for _, countryId := range config.AllowCountryIds {
if regionconfigs.MatchUserRegion(userRegion.CountryId(), userRegion.ProvinceId(), countryId) {
found = true
break
}
}
if !found {
return false
}
}
if resp.IpRegion == nil {
return true
}
if len(config.AllowCountryIds) > 0 && !lists.ContainsInt64(config.AllowCountryIds, resp.IpRegion.CountryId) {
return false
}
if len(config.AllowProvinceIds) > 0 && !lists.ContainsInt64(config.AllowProvinceIds, resp.IpRegion.ProvinceId) {
if len(config.AllowProvinceIds) > 0 && !lists.ContainsInt64(config.AllowProvinceIds, userRegion.ProvinceId()) {
return false
}
}

File diff suppressed because one or more lines are too long

View File

@@ -2840,6 +2840,42 @@ Vue.component("plan-user-selector", {
</div>`
})
// 显示流量限制说明
Vue.component("plan-limit-view", {
props: ["value", "v-single-mode"],
data: function () {
let config = this.value
let hasLimit = false
if (!this.vSingleMode) {
if (config.trafficLimit != null && config.trafficLimit.isOn && ((config.trafficLimit.dailySize != null && config.trafficLimit.dailySize.count > 0) || (config.trafficLimit.monthlySize != null && config.trafficLimit.monthlySize.count > 0))) {
hasLimit = true
} else if (config.dailyRequests > 0 || config.monthlyRequests > 0) {
hasLimit = true
}
}
return {
config: config,
hasLimit: hasLimit
}
},
methods: {
formatNumber: function (n) {
return teaweb.formatNumber(n)
}
},
template: `<div style="font-size: 0.8em; color: grey">
<div class="ui divider" v-if="hasLimit"></div>
<div v-if="config.trafficLimit != null && config.trafficLimit.isOn">
<span v-if="config.trafficLimit.dailySize != null && config.trafficLimit.dailySize.count > 0">日流量限制:{{config.trafficLimit.dailySize.count}}{{config.trafficLimit.dailySize.unit.toUpperCase()}}<br/></span>
<span v-if="config.trafficLimit.monthlySize != null && config.trafficLimit.monthlySize.count > 0">月流量限制:{{config.trafficLimit.monthlySize.count}}{{config.trafficLimit.monthlySize.unit.toUpperCase()}}<br/></span>
</div>
<div v-if="config.dailyRequests > 0">单日请求数限制:{{formatNumber(config.dailyRequests)}}</div>
<div v-if="config.monthlyRequests > 0">单月请求数限制:{{formatNumber(config.monthlyRequests)}}</div>
</div>`
})
Vue.component("plan-price-view", {
props: ["v-plan"],
data: function () {
@@ -3033,6 +3069,7 @@ Vue.component("plan-price-config-box", {
<input type="text" style="width: 7em" maxlength="10" v-model="monthlyPrice"/>
<span class="ui label">元</span>
</div>
<p class="comment">如果为0表示免费。</p>
</td>
</tr>
<tr>
@@ -3042,6 +3079,7 @@ Vue.component("plan-price-config-box", {
<input type="text" style="width: 7em" maxlength="10" v-model="seasonallyPrice"/>
<span class="ui label">元</span>
</div>
<p class="comment">如果为0表示免费。</p>
</td>
</tr>
<tr>
@@ -3051,6 +3089,7 @@ Vue.component("plan-price-config-box", {
<input type="text" style="width: 7em" maxlength="10" v-model="yearlyPrice"/>
<span class="ui label">元</span>
</div>
<p class="comment">如果为0表示免费。</p>
</td>
</tr>
</table>
@@ -4505,44 +4544,6 @@ Vue.component("http-firewall-actions-view", {
</div>`
})
Vue.component("http-request-scripts-config-box", {
props: ["vRequestScriptsConfig", "v-is-location"],
data: function () {
let config = this.vRequestScriptsConfig
if (config == null) {
config = {}
}
return {
config: config
}
},
methods: {
changeInitGroup: function (group) {
this.config.initGroup = group
this.$forceUpdate()
},
changeRequestGroup: function (group) {
this.config.requestGroup = group
this.$forceUpdate()
}
},
template: `<div>
<input type="hidden" name="requestScriptsJSON" :value="JSON.stringify(config)"/>
<div class="margin"></div>
<h4 style="margin-bottom: 0">请求初始化</h4>
<p class="comment">在请求刚初始化时调用此时自定义Header等尚未生效。</p>
<div>
<script-group-config-box :v-group="config.initGroup" @change="changeInitGroup" :v-is-location="vIsLocation"></script-group-config-box>
</div>
<h4 style="margin-bottom: 0">准备发送请求</h4>
<p class="comment">在准备执行请求或者转发请求之前调用此时自定义Header、源站等已准备好。</p>
<div>
<script-group-config-box :v-group="config.requestGroup" @change="changeRequestGroup" :v-is-location="vIsLocation"></script-group-config-box>
</div>
<div class="margin"></div>
</div>`
})
// 显示WAF规则的标签
Vue.component("http-firewall-rule-label", {
props: ["v-rule"],
@@ -5043,7 +5044,7 @@ Vue.component("http-cache-ref-box", {
isOn: true,
cachePolicyId: 0,
key: "${scheme}://${host}${requestPath}${isArgs}${args}",
life: {count: 2, unit: "hour"},
life: {count: 1, unit: "day"},
status: [200],
maxSize: {count: 128, unit: "mb"},
minSize: {count: 0, unit: "kb"},
@@ -5200,13 +5201,13 @@ Vue.component("http-cache-ref-box", {
},
template: `<tbody>
<tr v-if="condCategory == 'simple'">
<td class="title">条件类型 *</td>
<td class="title">缓存对象 *</td>
<td>
<select class="ui dropdown auto-width" name="condType" v-model="condType" @change="changeCondType(condType, false)">
<option value="url-extension">文件扩展名</option>
<option value="url-eq-index">首页</option>
<option value="url-all">全站</option>
<option value="url-prefix">URL前缀</option>
<option value="url-prefix">URL目录前缀</option>
<option value="url-eq">URL完整路径</option>
<option value="url-wildcard-match">URL通配符</option>
<option value="url-regexp">URL正则匹配</option>
@@ -5244,7 +5245,7 @@ Vue.component("http-cache-ref-box", {
<tr v-show="!vIsReverse">
<td>缓存有效期 *</td>
<td>
<time-duration-box :v-value="ref.life" @change="changeLife"></time-duration-box>
<time-duration-box :v-value="ref.life" @change="changeLife" :v-min-unit="'minute'" maxlength="4"></time-duration-box>
</td>
</tr>
<tr v-show="!vIsReverse">
@@ -5255,15 +5256,15 @@ Vue.component("http-cache-ref-box", {
</td>
</tr>
<tr v-show="!vIsReverse">
<td colspan="2"><more-options-indicator @change="changeOptionsVisible"></more-options-indicator></td>
</tr>
<tr v-show="moreOptionsVisible && !vIsReverse">
<td>缓存Key *</td>
<td>
<input type="text" v-model="ref.key" @input="changeKey(ref.key)"/>
<p class="comment">用来区分不同缓存内容的唯一Key。<request-variables-describer ref="variablesDescriber"></request-variables-describer>。</p>
</td>
</tr>
<tr v-show="!vIsReverse">
<td colspan="2"><more-options-indicator @change="changeOptionsVisible"></more-options-indicator></td>
</tr>
<tr v-show="moreOptionsVisible && !vIsReverse">
<td>请求方法限制</td>
<td>
@@ -5333,7 +5334,7 @@ Vue.component("http-cache-ref-box", {
<input type="checkbox" value="1" v-model="ref.skipSetCookie"/>
<label></label>
</div>
<p class="comment">选中后,当响应的Header中有Set-Cookie时不缓存响应内容。</p>
<p class="comment">选中后,当响应的报头中有Set-Cookie时不缓存响应内容,防止动态内容被缓存。</p>
</td>
</tr>
<tr v-show="moreOptionsVisible && !vIsReverse">
@@ -5343,7 +5344,7 @@ Vue.component("http-cache-ref-box", {
<input type="checkbox" name="enableRequestCachePragma" value="1" v-model="ref.enableRequestCachePragma"/>
<label></label>
</div>
<p class="comment">选中后,当请求的Header中含有Pragma: no-cache或Cache-Control: no-cache时会跳过缓存直接读取源内容。</p>
<p class="comment">选中后,当请求的报头中含有Pragma: no-cache或Cache-Control: no-cache时会跳过缓存直接读取源内容,一般仅用于调试。</p>
</td>
</tr>
<tr v-show="moreOptionsVisible && !vIsReverse">
@@ -5699,7 +5700,7 @@ Vue.component("http-firewall-config-box", {
</td>
</tr>
</tbody>
<more-options-tbody @change="changeOptionsVisible"></more-options-tbody>
<more-options-tbody @change="changeOptionsVisible" v-show="firewall.isOn"></more-options-tbody>
<tbody v-show="moreOptionsVisible">
<tr>
<td>启用系统全局规则</td>
@@ -8614,11 +8615,15 @@ Vue.component("http-firewall-actions-box", {
}
var defaultPageBody = `<!DOCTYPE html>
<html>
<html lang="en">
<title>403 Forbidden</title>
\t<style>
\t\taddress { line-height: 1.8; }
\t</style>
<body>
<h1>403 Forbidden</h1>
<address>Request ID: \${requestId}.</address>
<address>Connection: \${remoteAddr} (Client) -&gt; \${serverAddr} (Server)</address>
<address>Request ID: \${requestId}</address>
</body>
</html>`
@@ -9233,7 +9238,7 @@ Vue.component("http-firewall-actions-box", {
<span v-if="config.code == 'post_307' && config.options.life > 0">:有效期{{config.options.life}}秒</span>
<!-- record_ip -->
<span v-if="config.code == 'record_ip'">{{config.options.ipListName}}</span>
<span v-if="config.code == 'record_ip'"><span :class="{red: config.options.ipListIsDeleted}">{{config.options.ipListName}}</span></span>
<!-- tag -->
<span v-if="config.code == 'tag'">{{config.options.tags.join(", ")}}</span>
@@ -10178,17 +10183,21 @@ Vue.component("http-pages-and-shutdown-box", {
},
addShutdownHTMLTemplate: function () {
this.shutdownConfig.body = `<!DOCTYPE html>
<html>
<html lang="en">
<head>
\t<title>升级中</title>
\t<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
\t<style>
\t\taddress { line-height: 1.8; }
\t</style>
</head>
<body>
<h1>网站升级中</h1>
<p>为了给您提供更好的服务,我们正在升级网站,请稍后重新访问。</p>
<address>Request ID: \${requestId}.</address>
<address>Connection: \${remoteAddr} (Client) -&gt; \${serverAddr} (Server)</address>
<address>Request ID: \${requestId}</address>
</body>
</html>`
@@ -11560,23 +11569,6 @@ Vue.component("http-access-log-config-box", {
</div>`
})
// 显示流量限制说明
Vue.component("traffic-limit-view", {
props: ["v-traffic-limit"],
data: function () {
return {
config: this.vTrafficLimit
}
},
template: `<div>
<div v-if="config.isOn">
<span v-if="config.dailySize != null && config.dailySize.count > 0">日流量限制:{{config.dailySize.count}}{{config.dailySize.unit.toUpperCase()}}<br/></span>
<span v-if="config.monthlySize != null && config.monthlySize.count > 0">月流量限制:{{config.monthlySize.count}}{{config.monthlySize.unit.toUpperCase()}}<br/></span>
</div>
<span v-else class="disabled">没有限制。</span>
</div>`
})
// 基本认证用户配置
Vue.component("http-auth-basic-auth-user-box", {
props: ["v-users"],
@@ -12041,7 +12033,8 @@ Vue.component("reverse-proxy-box", {
idleTimeout: {count: 0, unit: "second"},
maxConns: 0,
maxIdleConns: 0,
followRedirects: false
followRedirects: false,
retry50X: true
}
}
if (reverseProxyConfig.addHeaders == null) {
@@ -12181,6 +12174,7 @@ Vue.component("reverse-proxy-box", {
<input type="checkbox" v-model="reverseProxyRef.isOn"/>
<label></label>
</div>
<p class="comment">选中后,所有源站设置才会生效。</p>
</td>
</tr>
<tr v-show="family == null || family == 'http'">
@@ -12192,7 +12186,7 @@ Vue.component("reverse-proxy-box", {
<div v-show="reverseProxyConfig.requestHostType == 2" style="margin-top: 0.8em">
<input type="text" placeholder="比如example.com" v-model="reverseProxyConfig.requestHost"/>
</div>
<p class="comment">请求源站时的Host用于修改源站接收到的域名
<p class="comment">请求源站时的主机名(Host,用于修改源站接收到的域名
<span v-if="reverseProxyConfig.requestHostType == 0">"跟随CDN域名"是指源站接收到的域名和当前CDN访问域名保持一致</span>
<span v-if="reverseProxyConfig.requestHostType == 1">"跟随源站"是指源站接收到的域名仍然是填写的源站地址中的信息,不随代理服务域名改变而改变</span>
<span v-if="reverseProxyConfig.requestHostType == 2">自定义Host内容中支持请求变量</span>。</p>
@@ -12215,7 +12209,7 @@ Vue.component("reverse-proxy-box", {
</td>
</tr>
<tr v-show="family == null || family == 'http'">
<td>自动添加的Header</td>
<td>自动添加报头</td>
<td>
<div>
<div style="width: 14em; float: left; margin-bottom: 1em" v-for="header in forwardHeaders" :key="header.name">
@@ -12223,7 +12217,7 @@ Vue.component("reverse-proxy-box", {
</div>
<div style="clear: both"></div>
</div>
<p class="comment">选中后,会自动向源站请求添加这些Header。</p>
<p class="comment">选中后,会自动向源站请求添加这些报头,以便于源站获取客户端信息。</p>
</td>
</tr>
<tr v-show="family == null || family == 'http'">
@@ -12314,6 +12308,13 @@ Vue.component("reverse-proxy-box", {
<p class="comment">源站保持等待的空闲超时时间0表示使用默认时间。</p>
</td>
</tr>
<tr v-show="family == null || family == 'http'">
<td>自动重试50X</td>
<td>
<checkbox v-model="reverseProxyConfig.retry50X"></checkbox>
<p class="comment">选中后表示当源站返回状态码为50X比如502、504自动重试。</p>
</td>
</tr>
<tr v-show="family != 'unix'">
<td>PROXY Protocol</td>
<td>
@@ -12428,13 +12429,35 @@ Vue.component("http-remote-addr-config-box", {
isPrior: false,
isOn: false,
value: "${rawRemoteAddr}",
isCustomized: false
type: "default",
requestHeaderName: ""
}
}
let optionValue = ""
if (!config.isCustomized && (config.value == "${remoteAddr}" || config.value == "${rawRemoteAddr}")) {
optionValue = config.value
// type
if (config.type == null || config.type.length == 0) {
config.type = "default"
switch (config.value) {
case "${rawRemoteAddr}":
config.type = "default"
break
case "${remoteAddrValue}":
config.type = "default"
break
case "${remoteAddr}":
config.type = "proxy"
break
default:
if (config.value != null && config.value.length > 0) {
config.type = "variable"
}
}
}
// value
if (config.value == null || config.value.length == 0) {
config.value = "${rawRemoteAddr}"
}
return {
@@ -12442,33 +12465,70 @@ Vue.component("http-remote-addr-config-box", {
options: [
{
name: "直接获取",
description: "用户直接访问边缘节点,即 \"用户 --> 边缘节点\" 模式,这时候可以直接连接中读取到真实的IP地址。",
value: "${rawRemoteAddr}"
description: "用户直接访问边缘节点,即 \"用户 --> 边缘节点\" 模式,这时候系统会试图从直接连接中读取到客户端IP地址。",
value: "${rawRemoteAddr}",
type: "default"
},
{
name: "从上级代理中获取",
description: "用户和边缘节点之间有别的代理服务转发,即 \"用户 --> [第三方代理服务] --> 边缘节点\"这时候只能从上级代理中获取传递的IP地址。",
value: "${remoteAddr}"
description: "用户和边缘节点之间有别的代理服务转发,即 \"用户 --> [第三方代理服务] --> 边缘节点\"这时候只能从上级代理中获取传递的IP地址;上级代理传递的请求报头中必须包含 X-Forwarded-For 或 X-Real-IP 信息。",
value: "${remoteAddr}",
type: "proxy"
},
{
name: "[自定义]",
name: "从请求报头中读取",
description: "从自定义请求报头读取客户端IP。",
value: "",
type: "requestHeader"
},
{
name: "[自定义变量]",
description: "通过自定义变量来获取客户端真实的IP地址。",
value: ""
value: "",
type: "variable"
}
],
optionValue: optionValue
]
}
},
watch: {
"config.requestHeaderName": function (value) {
if (this.config.type == "requestHeader"){
this.config.value = "${header." + value.trim() + "}"
}
}
},
methods: {
isOn: function () {
return ((!this.vIsLocation && !this.vIsGroup) || this.config.isPrior) && this.config.isOn
},
changeOptionValue: function () {
if (this.optionValue.length > 0) {
this.config.value = this.optionValue
this.config.isCustomized = false
} else {
this.config.isCustomized = true
changeOptionType: function () {
let that = this
switch(this.config.type) {
case "default":
this.config.value = "${rawRemoteAddr}"
break
case "proxy":
this.config.value = "${remoteAddr}"
break
case "requestHeader":
this.config.value = ""
if (this.requestHeaderName != null && this.requestHeaderName.length > 0) {
this.config.value = "${header." + this.requestHeaderName + "}"
}
setTimeout(function () {
that.$refs.requestHeaderInput.focus()
})
break
case "variable":
this.config.value = "${rawRemoteAddr}"
setTimeout(function () {
that.$refs.variableInput.focus()
})
break
}
}
},
@@ -12484,7 +12544,7 @@ Vue.component("http-remote-addr-config-box", {
<input type="checkbox" value="1" v-model="config.isOn"/>
<label></label>
</div>
<p class="comment">选中后表示使用自定义的请求变量获取客户端IP。</p>
<p class="comment">选中后表示使用自定义的请求变量获取客户端IP。</p>
</td>
</tr>
</tbody>
@@ -12492,20 +12552,28 @@ Vue.component("http-remote-addr-config-box", {
<tr>
<td>获取IP方式 *</td>
<td>
<select class="ui dropdown auto-width" v-model="optionValue" @change="changeOptionValue">
<option v-for="option in options" :value="option.value">{{option.name}}</option>
<select class="ui dropdown auto-width" v-model="config.type" @change="changeOptionType">
<option v-for="option in options" :value="option.type">{{option.name}}</option>
</select>
<p class="comment" v-for="option in options" v-if="option.value == optionValue && option.description.length > 0">{{option.description}}</p>
<p class="comment" v-for="option in options" v-if="option.type == config.type && option.description.length > 0">{{option.description}}</p>
</td>
</tr>
<tr v-show="optionValue.length == 0">
<!-- read from request header -->
<tr v-show="config.type == 'requestHeader'">
<td>请求报头 *</td>
<td>
<input type="text" name="requestHeaderName" v-model="config.requestHeaderName" maxlength="100" ref="requestHeaderInput"/>
<p class="comment">请输入包含有客户端IP的请求报头需要注意大小写常见的有<code-label>X-Forwarded-For</code-label>、<code-label>X-Real-IP</code-label>、<code-label>X-Client-IP</code-label>等。</p>
</td>
</tr>
<!-- read from variable -->
<tr v-show="config.type == 'variable'">
<td>读取IP变量值 *</td>
<td>
<input type="hidden" v-model="config.value" maxlength="100"/>
<div v-if="optionValue == ''" style="margin-top: 1em">
<input type="text" v-model="config.value" maxlength="100"/>
<p class="comment">通过此变量获取用户的IP地址。具体可用的请求变量列表可参考官方网站文档。</p>
</div>
<input type="text" name="value" v-model="config.value" maxlength="100" ref="variableInput"/>
<p class="comment">通过此变量获取用户的IP地址。具体可用的请求变量列表可参考官方网站文档比如通过报头传递IP的情形可以使用<code-label>\${header.你的自定义报头}</code-label>(类似于<code-label>\${header.X-Forwarded-For}</code-label>,需要注意大小写规范)。</p>
</td>
</tr>
</tbody>
@@ -12996,7 +13064,7 @@ Vue.component("http-webp-config-box", {
quality: 50,
minLength: {count: 0, "unit": "kb"},
maxLength: {count: 0, "unit": "kb"},
mimeTypes: ["image/png", "image/jpeg", "image/bmp", "image/x-ico", "image/gif"],
mimeTypes: ["image/png", "image/jpeg", "image/bmp", "image/x-ico"],
extensions: [".png", ".jpeg", ".jpg", ".bmp", ".ico"],
conds: null
}
@@ -13062,7 +13130,7 @@ Vue.component("http-webp-config-box", {
<input type="checkbox" value="1" v-model="config.isOn"/>
<label></label>
</div>
<p class="comment">选中后表示开启自动WebP压缩<span v-if="vRequireCache">;只有满足缓存条件的图片内容才会被转换</span>。</p>
<p class="comment">选中后表示开启自动WebP压缩图片的宽和高均不能超过16383像素<span v-if="vRequireCache">;只有满足缓存条件的图片内容才会被转换</span>。</p>
</td>
</tr>
</tbody>
@@ -13296,6 +13364,44 @@ Vue.component("http-oss-bucket-params", {
</tbody>`
})
Vue.component("http-request-scripts-config-box", {
props: ["vRequestScriptsConfig", "v-is-location"],
data: function () {
let config = this.vRequestScriptsConfig
if (config == null) {
config = {}
}
return {
config: config
}
},
methods: {
changeInitGroup: function (group) {
this.config.initGroup = group
this.$forceUpdate()
},
changeRequestGroup: function (group) {
this.config.requestGroup = group
this.$forceUpdate()
}
},
template: `<div>
<input type="hidden" name="requestScriptsJSON" :value="JSON.stringify(config)"/>
<div class="margin"></div>
<h4 style="margin-bottom: 0">请求初始化</h4>
<p class="comment">在请求刚初始化时调用,此时自定义报头等尚未生效。</p>
<div>
<script-group-config-box :v-group="config.initGroup" @change="changeInitGroup" :v-is-location="vIsLocation"></script-group-config-box>
</div>
<h4 style="margin-bottom: 0">准备发送请求</h4>
<p class="comment">在准备执行请求或者转发请求之前调用,此时自定义报头、源站等已准备好。</p>
<div>
<script-group-config-box :v-group="config.requestGroup" @change="changeRequestGroup" :v-is-location="vIsLocation"></script-group-config-box>
</div>
<div class="margin"></div>
</div>`
})
Vue.component("http-request-cond-view", {
props: ["v-cond"],
data: function () {
@@ -14923,7 +15029,8 @@ Vue.component("http-firewall-captcha-options", {
return {
options: options,
isEditing: false,
summary: ""
summary: "",
uiBodyWarning: ""
}
},
watch: {
@@ -14967,6 +15074,13 @@ Vue.component("http-firewall-captcha-options", {
},
"options.uiIsOn": function (v) {
this.updateSummary()
},
"options.uiBody": function (v) {
if (/<form(>|\s).+\$\{body}.*<\/form>/s.test(v)) {
this.uiBodyWarning = "页面模板中不能使用<form></form>标签包裹\${body}变量,否则将导致验证码表单无法提交。"
} else {
this.uiBodyWarning = ""
}
}
},
methods: {
@@ -15101,7 +15215,7 @@ Vue.component("http-firewall-captcha-options", {
<td class="color-border">页面模板</td>
<td>
<textarea spellcheck="false" rows="2" v-model="options.uiBody"></textarea>
<p class="comment"><span v-if="options.uiBody.length > 0 && options.uiBody.indexOf('\${body}') < 0 " class="red">模板中必须包含\${body}表示验证码表单!</span>整个页面的模板支持HTML其中必须使用<code-label>\${body}</code-label>变量代表验证码表单,否则将无法正常显示验证码。</p>
<p class="comment"><span v-if="uiBodyWarning.length > 0" class="red">警告:{{uiBodyWarning}}</span><span v-if="options.uiBody.length > 0 && options.uiBody.indexOf('\${body}') < 0 " class="red">模板中必须包含\${body}表示验证码表单!</span>整个页面的模板支持HTML其中必须使用<code-label>\${body}</code-label>变量代表验证码表单,否则将无法正常显示验证码。</p>
</td>
</tr>
</tbody>
@@ -15677,10 +15791,13 @@ Vue.component("ip-list-table", {
<keyword :v-word="keyword">{{item.ipFrom}}</keyword> <span> <span class="small red" v-if="item.isRead != null && !item.isRead">&nbsp;New&nbsp;</span>&nbsp;<a :href="'/servers/iplists?ip=' + item.ipFrom" v-if="vShowSearchButton" title="搜索此IP"><span><i class="icon search small" style="color: #ccc"></i></span></a></span>
<span v-if="item.ipTo.length > 0"> - <keyword :v-word="keyword">{{item.ipTo}}</keyword></span></span>
<span v-else class="disabled">*</span>
<div v-if="item.region != null && item.region.length > 0">
<span class="grey small">{{item.region}}</span>
<span v-if="item.isp != null && item.isp.length > 0 && item.isp != '内网IP'" class="grey small"><span class="disabled">|</span> {{item.isp}}</span>
</div>
<div v-else-if="item.isp != null && item.isp.length > 0 && item.isp != '内网IP'"><span class="grey small">{{item.isp}}</span></div>
<div v-if="item.createdTime != null">
<span class="small grey">添加于 {{item.createdTime}}
<span v-if="item.list != null && item.list.id > 0">
@@ -17799,7 +17916,7 @@ Vue.component("dot", {
})
Vue.component("time-duration-box", {
props: ["name", "v-name", "v-value", "v-count", "v-unit"],
props: ["v-name", "v-value", "v-count", "v-unit", "placeholder", "v-min-unit", "maxlength"],
mounted: function () {
this.change()
},
@@ -17815,17 +17932,52 @@ Vue.component("time-duration-box", {
v["count"] = -1
}
let realName = ""
if (typeof this.name == "string" && this.name.length > 0) {
realName = this.name
} else if (typeof this.vName == "string" && this.vName.length > 0) {
realName = this.vName
let minUnit = this.vMinUnit
let units = [
{
code: "ms",
name: "毫秒"
},
{
code: "second",
name: "秒"
},
{
code: "minute",
name: "分钟"
},
{
code: "hour",
name: "小时"
},
{
code: "day",
name: "天"
}
]
let minUnitIndex = -1
if (minUnit != null && typeof minUnit == "string" && minUnit.length > 0) {
for (let i = 0; i < units.length; i++) {
if (units[i].code == minUnit) {
minUnitIndex = i
break
}
}
}
if (minUnitIndex > -1) {
units = units.slice(minUnitIndex)
}
let maxLength = parseInt(this.maxlength)
if (typeof maxLength != "number") {
maxLength = 10
}
return {
duration: v,
countString: (v.count >= 0) ? v.count.toString() : "",
realName: realName
units: units,
realMaxLength: maxLength
}
},
watch: {
@@ -17848,23 +18000,41 @@ Vue.component("time-duration-box", {
}
},
template: `<div class="ui fields inline" style="padding-bottom: 0; margin-bottom: 0">
<input type="hidden" :name="realName" :value="JSON.stringify(duration)"/>
<input type="hidden" :name="vName" :value="JSON.stringify(duration)"/>
<div class="ui field">
<input type="text" v-model="countString" maxlength="11" size="11" @keypress.enter.prevent="1"/>
<input type="text" v-model="countString" :maxlength="realMaxLength" :size="realMaxLength" :placeholder="placeholder" @keypress.enter.prevent="1"/>
</div>
<div class="ui field">
<select class="ui dropdown" v-model="duration.unit" @change="change">
<option value="ms">毫秒</option>
<option value="second">秒</option>
<option value="minute">分钟</option>
<option value="hour">小时</option>
<option value="day">天</option>
<option value="week">周</option>
<option v-for="unit in units" :value="unit.code">{{unit.name}}</option>
</select>
</div>
</div>`
})
Vue.component("time-duration-text", {
props: ["v-value"],
methods: {
unitName: function (unit) {
switch (unit) {
case "ms":
return "毫秒"
case "second":
return "秒"
case "minute":
return "分钟"
case "hour":
return "小时"
case "day":
return "天"
}
}
},
template: `<span>
{{vValue.count}} {{unitName(vValue.unit)}}
</span>`
})
Vue.component("not-found-box", {
props: ["message"],
template: `<div style="text-align: center; margin-top: 5em;">
@@ -18156,7 +18326,7 @@ Vue.component("digit-input", {
}
}
},
template: `<input type="text" v-model="realValue" :maxlength="realMaxLength" :size="realSize" :class="{error: !this.isValid}" :placeholder="placeholder"/>`
template: `<input type="text" v-model="realValue" :maxlength="realMaxLength" :size="realSize" :class="{error: !this.isValid}" :placeholder="placeholder" autocomplete="off"/>`
})
Vue.component("keyword", {
@@ -20481,6 +20651,9 @@ Vue.component("dns-route-selector", {
this.routes.$removeIf(function (k, v) {
return v.code + "@" + v.domainId == route.code + "@" + route.domainId
})
},
clearKeyword: function () {
this.keyword = ""
}
},
watch: {
@@ -20514,16 +20687,22 @@ Vue.component("dns-route-selector", {
<tr>
<td class="title">所有线路</td>
<td>
<select class="ui dropdown auto-width" v-model="routeCode">
<option value="" v-if="keyword.length == 0">[请选择]</option>
<option v-for="route in searchingRoutes" :value="route.code + '@' + route.domainId">{{route.name}}{{route.code}}/{{route.domainName}}</option>
</select>
<span v-if="keyword.length > 0 && searchingRoutes.length == 0">没有和关键词“{{keyword}}”匹配的线路</span>
<span v-show="keyword.length == 0 || searchingRoutes.length > 0">
<select class="ui dropdown" v-model="routeCode">
<option value="" v-if="keyword.length == 0">[请选择]</option>
<option v-for="route in searchingRoutes" :value="route.code + '@' + route.domainId">{{route.name}}{{route.code}}/{{route.domainName}}</option>
</select>
</span>
</td>
</tr>
<tr>
<td>搜索</td>
<td>搜索线路</td>
<td>
<input type="text" placeholder="搜索..." size="10" style="width: 10em" v-model="keyword" ref="keywordRef" @keyup.enter="confirm" @keypress.enter.prevent="1"/>
<div class="ui input" :class="{'right labeled':keyword.length > 0}">
<input type="text" placeholder="线路名称或代号..." size="10" style="width: 10em" v-model="keyword" ref="keywordRef" @keyup.enter="confirm" @keypress.enter.prevent="1"/>
<a class="ui label" v-if="keyword.length > 0" @click.prevent="clearKeyword" href=""><i class="icon remove small blue"></i></a>
</div>
</td>
</tr>
</table>
@@ -20987,7 +21166,7 @@ Vue.component("grant-selector", {
</div>`
})
window.REQUEST_COND_COMPONENTS = [{"type":"url-extension","name":"文件扩展名","description":"根据URL中的文件路径扩展名进行过滤","component":"http-cond-url-extension","paramsTitle":"扩展名列表","isRequest":true,"caseInsensitive":false},{"type":"url-eq-index","name":"首页","description":"检查URL路径是为\"/\"","component":"http-cond-url-eq-index","paramsTitle":"URL完整路径","isRequest":true,"caseInsensitive":false},{"type":"url-all","name":"全站","description":"全站所有URL","component":"http-cond-url-all","paramsTitle":"URL完整路径","isRequest":true,"caseInsensitive":false},{"type":"url-prefix","name":"URL前缀","description":"根据URL中的文件路径前缀进行过滤","component":"http-cond-url-prefix","paramsTitle":"URL前缀","isRequest":true,"caseInsensitive":true},{"type":"url-eq","name":"URL完整路径","description":"检查URL中的文件路径是否一致","component":"http-cond-url-eq","paramsTitle":"URL完整路径","isRequest":true,"caseInsensitive":true},{"type":"url-regexp","name":"URL正则匹配","description":"使用正则表达式检查URL中的文件路径是否一致","component":"http-cond-url-regexp","paramsTitle":"正则表达式","isRequest":true,"caseInsensitive":true},{"type":"url-wildcard-match","name":"URL通配符","description":"使用通配符检查URL中的文件路径是否一致","component":"http-cond-url-wildcard-match","paramsTitle":"通配符","isRequest":true,"caseInsensitive":true},{"type":"user-agent-regexp","name":"User-Agent正则匹配","description":"使用正则表达式检查User-Agent中是否含有某些浏览器和系统标识","component":"http-cond-user-agent-regexp","paramsTitle":"正则表达式","isRequest":true,"caseInsensitive":true},{"type":"params","name":"参数匹配","description":"根据参数值进行匹配","component":"http-cond-params","paramsTitle":"参数配置","isRequest":true,"caseInsensitive":false},{"type":"url-not-extension","name":"排除URL扩展名","description":"根据URL中的文件路径扩展名进行过滤","component":"http-cond-url-not-extension","paramsTitle":"扩展名列表","isRequest":true,"caseInsensitive":false},{"type":"url-not-prefix","name":"排除URL前缀","description":"根据URL中的文件路径前缀进行过滤","component":"http-cond-url-not-prefix","paramsTitle":"URL前缀","isRequest":true,"caseInsensitive":true},{"type":"url-not-eq","name":"排除URL完整路径","description":"检查URL中的文件路径是否一致","component":"http-cond-url-not-eq","paramsTitle":"URL完整路径","isRequest":true,"caseInsensitive":true},{"type":"url-not-regexp","name":"排除URL正则匹配","description":"使用正则表达式检查URL中的文件路径是否一致如果一致则不匹配","component":"http-cond-url-not-regexp","paramsTitle":"正则表达式","isRequest":true,"caseInsensitive":true},{"type":"user-agent-not-regexp","name":"排除User-Agent正则匹配","description":"使用正则表达式检查User-Agent中是否含有某些浏览器和系统标识如果含有则不匹配","component":"http-cond-user-agent-not-regexp","paramsTitle":"正则表达式","isRequest":true,"caseInsensitive":true},{"type":"mime-type","name":"内容MimeType","description":"根据服务器返回的内容的MimeType进行过滤。注意当用于缓存条件时此条件需要结合别的请求条件使用。","component":"http-cond-mime-type","paramsTitle":"MimeType列表","isRequest":false,"caseInsensitive":false}]
window.REQUEST_COND_COMPONENTS = [{"type":"url-extension","name":"文件扩展名","description":"根据URL中的文件路径扩展名进行过滤","component":"http-cond-url-extension","paramsTitle":"扩展名列表","isRequest":true,"caseInsensitive":false},{"type":"url-eq-index","name":"首页","description":"检查URL路径是为\"/\"","component":"http-cond-url-eq-index","paramsTitle":"URL完整路径","isRequest":true,"caseInsensitive":false},{"type":"url-all","name":"全站","description":"全站所有URL","component":"http-cond-url-all","paramsTitle":"URL完整路径","isRequest":true,"caseInsensitive":false},{"type":"url-prefix","name":"URL目录前缀","description":"根据URL中的文件路径前缀进行过滤","component":"http-cond-url-prefix","paramsTitle":"URL目录前缀","isRequest":true,"caseInsensitive":true},{"type":"url-eq","name":"URL完整路径","description":"检查URL中的文件路径是否一致","component":"http-cond-url-eq","paramsTitle":"URL完整路径","isRequest":true,"caseInsensitive":true},{"type":"url-regexp","name":"URL正则匹配","description":"使用正则表达式检查URL中的文件路径是否一致","component":"http-cond-url-regexp","paramsTitle":"正则表达式","isRequest":true,"caseInsensitive":true},{"type":"url-wildcard-match","name":"URL通配符","description":"使用通配符检查URL中的文件路径是否一致","component":"http-cond-url-wildcard-match","paramsTitle":"通配符","isRequest":true,"caseInsensitive":true},{"type":"user-agent-regexp","name":"User-Agent正则匹配","description":"使用正则表达式检查User-Agent中是否含有某些浏览器和系统标识","component":"http-cond-user-agent-regexp","paramsTitle":"正则表达式","isRequest":true,"caseInsensitive":true},{"type":"params","name":"参数匹配","description":"根据参数值进行匹配","component":"http-cond-params","paramsTitle":"参数配置","isRequest":true,"caseInsensitive":false},{"type":"url-not-extension","name":"排除URL扩展名","description":"根据URL中的文件路径扩展名进行过滤","component":"http-cond-url-not-extension","paramsTitle":"扩展名列表","isRequest":true,"caseInsensitive":false},{"type":"url-not-prefix","name":"排除URL前缀","description":"根据URL中的文件路径前缀进行过滤","component":"http-cond-url-not-prefix","paramsTitle":"URL前缀","isRequest":true,"caseInsensitive":true},{"type":"url-not-eq","name":"排除URL完整路径","description":"检查URL中的文件路径是否一致","component":"http-cond-url-not-eq","paramsTitle":"URL完整路径","isRequest":true,"caseInsensitive":true},{"type":"url-not-regexp","name":"排除URL正则匹配","description":"使用正则表达式检查URL中的文件路径是否一致如果一致则不匹配","component":"http-cond-url-not-regexp","paramsTitle":"正则表达式","isRequest":true,"caseInsensitive":true},{"type":"user-agent-not-regexp","name":"排除User-Agent正则匹配","description":"使用正则表达式检查User-Agent中是否含有某些浏览器和系统标识如果含有则不匹配","component":"http-cond-user-agent-not-regexp","paramsTitle":"正则表达式","isRequest":true,"caseInsensitive":true},{"type":"mime-type","name":"内容MimeType","description":"根据服务器返回的内容的MimeType进行过滤。注意当用于缓存条件时此条件需要结合别的请求条件使用。","component":"http-cond-mime-type","paramsTitle":"MimeType列表","isRequest":false,"caseInsensitive":false}]
window.REQUEST_COND_OPERATORS = [{"description":"判断是否正则表达式匹配","name":"正则表达式匹配","op":"regexp"},{"description":"判断是否正则表达式不匹配","name":"正则表达式不匹配","op":"not regexp"},{"description":"判断是否和指定的通配符匹配","name":"通配符匹配","op":"wildcard match"},{"description":"判断是否和指定的通配符不匹配","name":"通配符不匹配","op":"wildcard not match"},{"description":"使用字符串对比参数值是否相等于某个值","name":"字符串等于","op":"eq"},{"description":"参数值包含某个前缀","name":"字符串前缀","op":"prefix"},{"description":"参数值包含某个后缀","name":"字符串后缀","op":"suffix"},{"description":"参数值包含另外一个字符串","name":"字符串包含","op":"contains"},{"description":"参数值不包含另外一个字符串","name":"字符串不包含","op":"not contains"},{"description":"使用字符串对比参数值是否不相等于某个值","name":"字符串不等于","op":"not"},{"description":"判断参数值在某个列表中","name":"在列表中","op":"in"},{"description":"判断参数值不在某个列表中","name":"不在列表中","op":"not in"},{"description":"判断小写的扩展名(不带点)在某个列表中","name":"扩展名","op":"file ext"},{"description":"判断MimeType在某个列表中支持类似于image/*的语法","name":"MimeType","op":"mime type"},{"description":"判断版本号在某个范围内格式为version1,version2","name":"版本号范围","op":"version range"},{"description":"将参数转换为整数数字后进行对比","name":"整数等于","op":"eq int"},{"description":"将参数转换为可以有小数的浮点数字进行对比","name":"浮点数等于","op":"eq float"},{"description":"将参数转换为数字进行对比","name":"数字大于","op":"gt"},{"description":"将参数转换为数字进行对比","name":"数字大于等于","op":"gte"},{"description":"将参数转换为数字进行对比","name":"数字小于","op":"lt"},{"description":"将参数转换为数字进行对比","name":"数字小于等于","op":"lte"},{"description":"对整数参数值取模除数为10对比值为余数","name":"整数取模10","op":"mod 10"},{"description":"对整数参数值取模除数为100对比值为余数","name":"整数取模100","op":"mod 100"},{"description":"对整数参数值取模,对比值格式为:除数,余数比如10,1","name":"整数取模","op":"mod"},{"description":"将参数转换为IP进行对比","name":"IP等于","op":"eq ip"},{"description":"将参数转换为IP进行对比","name":"IP大于","op":"gt ip"},{"description":"将参数转换为IP进行对比","name":"IP大于等于","op":"gte ip"},{"description":"将参数转换为IP进行对比","name":"IP小于","op":"lt ip"},{"description":"将参数转换为IP进行对比","name":"IP小于等于","op":"lte ip"},{"description":"IP在某个范围之内范围格式可以是英文逗号分隔的\u003ccode-label\u003e开始IP,结束IP\u003c/code-label\u003e比如\u003ccode-label\u003e192.168.1.100,192.168.2.200\u003c/code-label\u003e或者CIDR格式的ip/bits比如\u003ccode-label\u003e192.168.2.1/24\u003c/code-label\u003e","name":"IP范围","op":"ip range"},{"description":"对IP参数值取模除数为10对比值为余数","name":"IP取模10","op":"ip mod 10"},{"description":"对IP参数值取模除数为100对比值为余数","name":"IP取模100","op":"ip mod 100"},{"description":"对IP参数值取模对比值格式为除数,余数比如10,1","name":"IP取模","op":"ip mod"}]

View File

@@ -59,5 +59,5 @@ Vue.component("digit-input", {
}
}
},
template: `<input type="text" v-model="realValue" :maxlength="realMaxLength" :size="realSize" :class="{error: !this.isValid}" :placeholder="placeholder"/>`
template: `<input type="text" v-model="realValue" :maxlength="realMaxLength" :size="realSize" :class="{error: !this.isValid}" :placeholder="placeholder" autocomplete="off"/>`
})

View File

@@ -1,5 +1,5 @@
Vue.component("time-duration-box", {
props: ["name", "v-name", "v-value", "v-count", "v-unit"],
props: ["v-name", "v-value", "v-count", "v-unit", "placeholder", "v-min-unit", "maxlength"],
mounted: function () {
this.change()
},
@@ -15,17 +15,52 @@ Vue.component("time-duration-box", {
v["count"] = -1
}
let realName = ""
if (typeof this.name == "string" && this.name.length > 0) {
realName = this.name
} else if (typeof this.vName == "string" && this.vName.length > 0) {
realName = this.vName
let minUnit = this.vMinUnit
let units = [
{
code: "ms",
name: "毫秒"
},
{
code: "second",
name: "秒"
},
{
code: "minute",
name: "分钟"
},
{
code: "hour",
name: "小时"
},
{
code: "day",
name: "天"
}
]
let minUnitIndex = -1
if (minUnit != null && typeof minUnit == "string" && minUnit.length > 0) {
for (let i = 0; i < units.length; i++) {
if (units[i].code == minUnit) {
minUnitIndex = i
break
}
}
}
if (minUnitIndex > -1) {
units = units.slice(minUnitIndex)
}
let maxLength = parseInt(this.maxlength)
if (typeof maxLength != "number") {
maxLength = 10
}
return {
duration: v,
countString: (v.count >= 0) ? v.count.toString() : "",
realName: realName
units: units,
realMaxLength: maxLength
}
},
watch: {
@@ -48,19 +83,37 @@ Vue.component("time-duration-box", {
}
},
template: `<div class="ui fields inline" style="padding-bottom: 0; margin-bottom: 0">
<input type="hidden" :name="realName" :value="JSON.stringify(duration)"/>
<input type="hidden" :name="vName" :value="JSON.stringify(duration)"/>
<div class="ui field">
<input type="text" v-model="countString" maxlength="11" size="11" @keypress.enter.prevent="1"/>
<input type="text" v-model="countString" :maxlength="realMaxLength" :size="realMaxLength" :placeholder="placeholder" @keypress.enter.prevent="1"/>
</div>
<div class="ui field">
<select class="ui dropdown" v-model="duration.unit" @change="change">
<option value="ms">毫秒</option>
<option value="second">秒</option>
<option value="minute">分钟</option>
<option value="hour">小时</option>
<option value="day">天</option>
<option value="week">周</option>
<option v-for="unit in units" :value="unit.code">{{unit.name}}</option>
</select>
</div>
</div>`
})
Vue.component("time-duration-text", {
props: ["v-value"],
methods: {
unitName: function (unit) {
switch (unit) {
case "ms":
return "毫秒"
case "second":
return "秒"
case "minute":
return "分钟"
case "hour":
return "小时"
case "day":
return "天"
}
}
},
template: `<span>
{{vValue.count}} {{unitName(vValue.unit)}}
</span>`
})

View File

@@ -70,6 +70,9 @@ Vue.component("dns-route-selector", {
this.routes.$removeIf(function (k, v) {
return v.code + "@" + v.domainId == route.code + "@" + route.domainId
})
},
clearKeyword: function () {
this.keyword = ""
}
},
watch: {
@@ -103,16 +106,22 @@ Vue.component("dns-route-selector", {
<tr>
<td class="title">所有线路</td>
<td>
<select class="ui dropdown auto-width" v-model="routeCode">
<option value="" v-if="keyword.length == 0">[请选择]</option>
<option v-for="route in searchingRoutes" :value="route.code + '@' + route.domainId">{{route.name}}{{route.code}}/{{route.domainName}}</option>
</select>
<span v-if="keyword.length > 0 && searchingRoutes.length == 0">没有和关键词“{{keyword}}”匹配的线路</span>
<span v-show="keyword.length == 0 || searchingRoutes.length > 0">
<select class="ui dropdown" v-model="routeCode">
<option value="" v-if="keyword.length == 0">[请选择]</option>
<option v-for="route in searchingRoutes" :value="route.code + '@' + route.domainId">{{route.name}}{{route.code}}/{{route.domainName}}</option>
</select>
</span>
</td>
</tr>
<tr>
<td>搜索</td>
<td>搜索线路</td>
<td>
<input type="text" placeholder="搜索..." size="10" style="width: 10em" v-model="keyword" ref="keywordRef" @keyup.enter="confirm" @keypress.enter.prevent="1"/>
<div class="ui input" :class="{'right labeled':keyword.length > 0}">
<input type="text" placeholder="线路名称或代号..." size="10" style="width: 10em" v-model="keyword" ref="keywordRef" @keyup.enter="confirm" @keypress.enter.prevent="1"/>
<a class="ui label" v-if="keyword.length > 0" @click.prevent="clearKeyword" href=""><i class="icon remove small blue"></i></a>
</div>
</td>
</tr>
</table>

View File

@@ -118,10 +118,13 @@ Vue.component("ip-list-table", {
<keyword :v-word="keyword">{{item.ipFrom}}</keyword> <span> <span class="small red" v-if="item.isRead != null && !item.isRead">&nbsp;New&nbsp;</span>&nbsp;<a :href="'/servers/iplists?ip=' + item.ipFrom" v-if="vShowSearchButton" title="搜索此IP"><span><i class="icon search small" style="color: #ccc"></i></span></a></span>
<span v-if="item.ipTo.length > 0"> - <keyword :v-word="keyword">{{item.ipTo}}</keyword></span></span>
<span v-else class="disabled">*</span>
<div v-if="item.region != null && item.region.length > 0">
<span class="grey small">{{item.region}}</span>
<span v-if="item.isp != null && item.isp.length > 0 && item.isp != '内网IP'" class="grey small"><span class="disabled">|</span> {{item.isp}}</span>
</div>
<div v-else-if="item.isp != null && item.isp.length > 0 && item.isp != '内网IP'"><span class="grey small">{{item.isp}}</span></div>
<div v-if="item.createdTime != null">
<span class="small grey">添加于 {{item.createdTime}}
<span v-if="item.list != null && item.list.id > 0">

View File

@@ -19,7 +19,7 @@ Vue.component("http-cache-ref-box", {
isOn: true,
cachePolicyId: 0,
key: "${scheme}://${host}${requestPath}${isArgs}${args}",
life: {count: 2, unit: "hour"},
life: {count: 1, unit: "day"},
status: [200],
maxSize: {count: 128, unit: "mb"},
minSize: {count: 0, unit: "kb"},
@@ -176,13 +176,13 @@ Vue.component("http-cache-ref-box", {
},
template: `<tbody>
<tr v-if="condCategory == 'simple'">
<td class="title">条件类型 *</td>
<td class="title">缓存对象 *</td>
<td>
<select class="ui dropdown auto-width" name="condType" v-model="condType" @change="changeCondType(condType, false)">
<option value="url-extension">文件扩展名</option>
<option value="url-eq-index">首页</option>
<option value="url-all">全站</option>
<option value="url-prefix">URL前缀</option>
<option value="url-prefix">URL目录前缀</option>
<option value="url-eq">URL完整路径</option>
<option value="url-wildcard-match">URL通配符</option>
<option value="url-regexp">URL正则匹配</option>
@@ -220,7 +220,7 @@ Vue.component("http-cache-ref-box", {
<tr v-show="!vIsReverse">
<td>缓存有效期 *</td>
<td>
<time-duration-box :v-value="ref.life" @change="changeLife"></time-duration-box>
<time-duration-box :v-value="ref.life" @change="changeLife" :v-min-unit="'minute'" maxlength="4"></time-duration-box>
</td>
</tr>
<tr v-show="!vIsReverse">
@@ -231,15 +231,15 @@ Vue.component("http-cache-ref-box", {
</td>
</tr>
<tr v-show="!vIsReverse">
<td colspan="2"><more-options-indicator @change="changeOptionsVisible"></more-options-indicator></td>
</tr>
<tr v-show="moreOptionsVisible && !vIsReverse">
<td>缓存Key *</td>
<td>
<input type="text" v-model="ref.key" @input="changeKey(ref.key)"/>
<p class="comment">用来区分不同缓存内容的唯一Key。<request-variables-describer ref="variablesDescriber"></request-variables-describer>。</p>
</td>
</tr>
<tr v-show="!vIsReverse">
<td colspan="2"><more-options-indicator @change="changeOptionsVisible"></more-options-indicator></td>
</tr>
<tr v-show="moreOptionsVisible && !vIsReverse">
<td>请求方法限制</td>
<td>
@@ -309,7 +309,7 @@ Vue.component("http-cache-ref-box", {
<input type="checkbox" value="1" v-model="ref.skipSetCookie"/>
<label></label>
</div>
<p class="comment">选中后,当响应的Header中有Set-Cookie时不缓存响应内容。</p>
<p class="comment">选中后,当响应的报头中有Set-Cookie时不缓存响应内容,防止动态内容被缓存。</p>
</td>
</tr>
<tr v-show="moreOptionsVisible && !vIsReverse">
@@ -319,7 +319,7 @@ Vue.component("http-cache-ref-box", {
<input type="checkbox" name="enableRequestCachePragma" value="1" v-model="ref.enableRequestCachePragma"/>
<label></label>
</div>
<p class="comment">选中后,当请求的Header中含有Pragma: no-cache或Cache-Control: no-cache时会跳过缓存直接读取源内容。</p>
<p class="comment">选中后,当请求的报头中含有Pragma: no-cache或Cache-Control: no-cache时会跳过缓存直接读取源内容,一般仅用于调试。</p>
</td>
</tr>
<tr v-show="moreOptionsVisible && !vIsReverse">

View File

@@ -54,11 +54,15 @@ Vue.component("http-firewall-actions-box", {
}
var defaultPageBody = `<!DOCTYPE html>
<html>
<html lang="en">
<title>403 Forbidden</title>
\t<style>
\t\taddress { line-height: 1.8; }
\t</style>
<body>
<h1>403 Forbidden</h1>
<address>Request ID: \${requestId}.</address>
<address>Connection: \${remoteAddr} (Client) -&gt; \${serverAddr} (Server)</address>
<address>Request ID: \${requestId}</address>
</body>
</html>`
@@ -673,7 +677,7 @@ Vue.component("http-firewall-actions-box", {
<span v-if="config.code == 'post_307' && config.options.life > 0">:有效期{{config.options.life}}秒</span>
<!-- record_ip -->
<span v-if="config.code == 'record_ip'">{{config.options.ipListName}}</span>
<span v-if="config.code == 'record_ip'"><span :class="{red: config.options.ipListIsDeleted}">{{config.options.ipListName}}</span></span>
<!-- tag -->
<span v-if="config.code == 'tag'">{{config.options.tags.join(", ")}}</span>

View File

@@ -30,7 +30,8 @@ Vue.component("http-firewall-captcha-options", {
return {
options: options,
isEditing: false,
summary: ""
summary: "",
uiBodyWarning: ""
}
},
watch: {
@@ -74,6 +75,13 @@ Vue.component("http-firewall-captcha-options", {
},
"options.uiIsOn": function (v) {
this.updateSummary()
},
"options.uiBody": function (v) {
if (/<form(>|\s).+\$\{body}.*<\/form>/s.test(v)) {
this.uiBodyWarning = "页面模板中不能使用<form></form>标签包裹\${body}变量,否则将导致验证码表单无法提交。"
} else {
this.uiBodyWarning = ""
}
}
},
methods: {
@@ -208,7 +216,7 @@ Vue.component("http-firewall-captcha-options", {
<td class="color-border">页面模板</td>
<td>
<textarea spellcheck="false" rows="2" v-model="options.uiBody"></textarea>
<p class="comment"><span v-if="options.uiBody.length > 0 && options.uiBody.indexOf('\${body}') < 0 " class="red">模板中必须包含\${body}表示验证码表单!</span>整个页面的模板支持HTML其中必须使用<code-label>\${body}</code-label>变量代表验证码表单,否则将无法正常显示验证码。</p>
<p class="comment"><span v-if="uiBodyWarning.length > 0" class="red">警告:{{uiBodyWarning}}</span><span v-if="options.uiBody.length > 0 && options.uiBody.indexOf('\${body}') < 0 " class="red">模板中必须包含\${body}表示验证码表单!</span>整个页面的模板支持HTML其中必须使用<code-label>\${body}</code-label>变量代表验证码表单,否则将无法正常显示验证码。</p>
</td>
</tr>
</tbody>

View File

@@ -53,7 +53,7 @@ Vue.component("http-firewall-config-box", {
</td>
</tr>
</tbody>
<more-options-tbody @change="changeOptionsVisible"></more-options-tbody>
<more-options-tbody @change="changeOptionsVisible" v-show="firewall.isOn"></more-options-tbody>
<tbody v-show="moreOptionsVisible">
<tr>
<td>启用系统全局规则</td>

View File

@@ -71,17 +71,21 @@ Vue.component("http-pages-and-shutdown-box", {
},
addShutdownHTMLTemplate: function () {
this.shutdownConfig.body = `<!DOCTYPE html>
<html>
<html lang="en">
<head>
\t<title>升级中</title>
\t<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
\t<style>
\t\taddress { line-height: 1.8; }
\t</style>
</head>
<body>
<h1>网站升级中</h1>
<p>为了给您提供更好的服务,我们正在升级网站,请稍后重新访问。</p>
<address>Request ID: \${requestId}.</address>
<address>Connection: \${remoteAddr} (Client) -&gt; \${serverAddr} (Server)</address>
<address>Request ID: \${requestId}</address>
</body>
</html>`

View File

@@ -7,13 +7,35 @@ Vue.component("http-remote-addr-config-box", {
isPrior: false,
isOn: false,
value: "${rawRemoteAddr}",
isCustomized: false
type: "default",
requestHeaderName: ""
}
}
let optionValue = ""
if (!config.isCustomized && (config.value == "${remoteAddr}" || config.value == "${rawRemoteAddr}")) {
optionValue = config.value
// type
if (config.type == null || config.type.length == 0) {
config.type = "default"
switch (config.value) {
case "${rawRemoteAddr}":
config.type = "default"
break
case "${remoteAddrValue}":
config.type = "default"
break
case "${remoteAddr}":
config.type = "proxy"
break
default:
if (config.value != null && config.value.length > 0) {
config.type = "variable"
}
}
}
// value
if (config.value == null || config.value.length == 0) {
config.value = "${rawRemoteAddr}"
}
return {
@@ -21,33 +43,70 @@ Vue.component("http-remote-addr-config-box", {
options: [
{
name: "直接获取",
description: "用户直接访问边缘节点,即 \"用户 --> 边缘节点\" 模式,这时候可以直接连接中读取到真实的IP地址。",
value: "${rawRemoteAddr}"
description: "用户直接访问边缘节点,即 \"用户 --> 边缘节点\" 模式,这时候系统会试图从直接连接中读取到客户端IP地址。",
value: "${rawRemoteAddr}",
type: "default"
},
{
name: "从上级代理中获取",
description: "用户和边缘节点之间有别的代理服务转发,即 \"用户 --> [第三方代理服务] --> 边缘节点\"这时候只能从上级代理中获取传递的IP地址。",
value: "${remoteAddr}"
description: "用户和边缘节点之间有别的代理服务转发,即 \"用户 --> [第三方代理服务] --> 边缘节点\"这时候只能从上级代理中获取传递的IP地址;上级代理传递的请求报头中必须包含 X-Forwarded-For 或 X-Real-IP 信息。",
value: "${remoteAddr}",
type: "proxy"
},
{
name: "[自定义]",
name: "从请求报头中读取",
description: "从自定义请求报头读取客户端IP。",
value: "",
type: "requestHeader"
},
{
name: "[自定义变量]",
description: "通过自定义变量来获取客户端真实的IP地址。",
value: ""
value: "",
type: "variable"
}
],
optionValue: optionValue
]
}
},
watch: {
"config.requestHeaderName": function (value) {
if (this.config.type == "requestHeader"){
this.config.value = "${header." + value.trim() + "}"
}
}
},
methods: {
isOn: function () {
return ((!this.vIsLocation && !this.vIsGroup) || this.config.isPrior) && this.config.isOn
},
changeOptionValue: function () {
if (this.optionValue.length > 0) {
this.config.value = this.optionValue
this.config.isCustomized = false
} else {
this.config.isCustomized = true
changeOptionType: function () {
let that = this
switch(this.config.type) {
case "default":
this.config.value = "${rawRemoteAddr}"
break
case "proxy":
this.config.value = "${remoteAddr}"
break
case "requestHeader":
this.config.value = ""
if (this.requestHeaderName != null && this.requestHeaderName.length > 0) {
this.config.value = "${header." + this.requestHeaderName + "}"
}
setTimeout(function () {
that.$refs.requestHeaderInput.focus()
})
break
case "variable":
this.config.value = "${rawRemoteAddr}"
setTimeout(function () {
that.$refs.variableInput.focus()
})
break
}
}
},
@@ -63,7 +122,7 @@ Vue.component("http-remote-addr-config-box", {
<input type="checkbox" value="1" v-model="config.isOn"/>
<label></label>
</div>
<p class="comment">选中后表示使用自定义的请求变量获取客户端IP。</p>
<p class="comment">选中后表示使用自定义的请求变量获取客户端IP。</p>
</td>
</tr>
</tbody>
@@ -71,20 +130,28 @@ Vue.component("http-remote-addr-config-box", {
<tr>
<td>获取IP方式 *</td>
<td>
<select class="ui dropdown auto-width" v-model="optionValue" @change="changeOptionValue">
<option v-for="option in options" :value="option.value">{{option.name}}</option>
<select class="ui dropdown auto-width" v-model="config.type" @change="changeOptionType">
<option v-for="option in options" :value="option.type">{{option.name}}</option>
</select>
<p class="comment" v-for="option in options" v-if="option.value == optionValue && option.description.length > 0">{{option.description}}</p>
<p class="comment" v-for="option in options" v-if="option.type == config.type && option.description.length > 0">{{option.description}}</p>
</td>
</tr>
<tr v-show="optionValue.length == 0">
<!-- read from request header -->
<tr v-show="config.type == 'requestHeader'">
<td>请求报头 *</td>
<td>
<input type="text" name="requestHeaderName" v-model="config.requestHeaderName" maxlength="100" ref="requestHeaderInput"/>
<p class="comment">请输入包含有客户端IP的请求报头需要注意大小写常见的有<code-label>X-Forwarded-For</code-label>、<code-label>X-Real-IP</code-label>、<code-label>X-Client-IP</code-label>等。</p>
</td>
</tr>
<!-- read from variable -->
<tr v-show="config.type == 'variable'">
<td>读取IP变量值 *</td>
<td>
<input type="hidden" v-model="config.value" maxlength="100"/>
<div v-if="optionValue == ''" style="margin-top: 1em">
<input type="text" v-model="config.value" maxlength="100"/>
<p class="comment">通过此变量获取用户的IP地址。具体可用的请求变量列表可参考官方网站文档。</p>
</div>
<input type="text" name="value" v-model="config.value" maxlength="100" ref="variableInput"/>
<p class="comment">通过此变量获取用户的IP地址。具体可用的请求变量列表可参考官方网站文档比如通过报头传递IP的情形可以使用<code-label>\${header.你的自定义报头}</code-label>(类似于<code-label>\${header.X-Forwarded-For}</code-label>,需要注意大小写规范)。</p>
</td>
</tr>
</tbody>

View File

@@ -1,37 +0,0 @@
Vue.component("http-request-scripts-config-box", {
props: ["vRequestScriptsConfig", "v-is-location"],
data: function () {
let config = this.vRequestScriptsConfig
if (config == null) {
config = {}
}
return {
config: config
}
},
methods: {
changeInitGroup: function (group) {
this.config.initGroup = group
this.$forceUpdate()
},
changeRequestGroup: function (group) {
this.config.requestGroup = group
this.$forceUpdate()
}
},
template: `<div>
<input type="hidden" name="requestScriptsJSON" :value="JSON.stringify(config)"/>
<div class="margin"></div>
<h4 style="margin-bottom: 0">请求初始化</h4>
<p class="comment">在请求刚初始化时调用此时自定义Header等尚未生效。</p>
<div>
<script-group-config-box :v-group="config.initGroup" @change="changeInitGroup" :v-is-location="vIsLocation"></script-group-config-box>
</div>
<h4 style="margin-bottom: 0">准备发送请求</h4>
<p class="comment">在准备执行请求或者转发请求之前调用此时自定义Header、源站等已准备好。</p>
<div>
<script-group-config-box :v-group="config.requestGroup" @change="changeRequestGroup" :v-is-location="vIsLocation"></script-group-config-box>
</div>
<div class="margin"></div>
</div>`
})

View File

@@ -9,7 +9,7 @@ Vue.component("http-webp-config-box", {
quality: 50,
minLength: {count: 0, "unit": "kb"},
maxLength: {count: 0, "unit": "kb"},
mimeTypes: ["image/png", "image/jpeg", "image/bmp", "image/x-ico", "image/gif"],
mimeTypes: ["image/png", "image/jpeg", "image/bmp", "image/x-ico"],
extensions: [".png", ".jpeg", ".jpg", ".bmp", ".ico"],
conds: null
}
@@ -75,7 +75,7 @@ Vue.component("http-webp-config-box", {
<input type="checkbox" value="1" v-model="config.isOn"/>
<label></label>
</div>
<p class="comment">选中后表示开启自动WebP压缩<span v-if="vRequireCache">;只有满足缓存条件的图片内容才会被转换</span>。</p>
<p class="comment">选中后表示开启自动WebP压缩图片的宽和高均不能超过16383像素<span v-if="vRequireCache">;只有满足缓存条件的图片内容才会被转换</span>。</p>
</td>
</tr>
</tbody>

View File

@@ -25,7 +25,8 @@ Vue.component("reverse-proxy-box", {
idleTimeout: {count: 0, unit: "second"},
maxConns: 0,
maxIdleConns: 0,
followRedirects: false
followRedirects: false,
retry50X: true
}
}
if (reverseProxyConfig.addHeaders == null) {
@@ -165,6 +166,7 @@ Vue.component("reverse-proxy-box", {
<input type="checkbox" v-model="reverseProxyRef.isOn"/>
<label></label>
</div>
<p class="comment">选中后,所有源站设置才会生效。</p>
</td>
</tr>
<tr v-show="family == null || family == 'http'">
@@ -176,7 +178,7 @@ Vue.component("reverse-proxy-box", {
<div v-show="reverseProxyConfig.requestHostType == 2" style="margin-top: 0.8em">
<input type="text" placeholder="比如example.com" v-model="reverseProxyConfig.requestHost"/>
</div>
<p class="comment">请求源站时的Host用于修改源站接收到的域名
<p class="comment">请求源站时的主机名(Host,用于修改源站接收到的域名
<span v-if="reverseProxyConfig.requestHostType == 0">"跟随CDN域名"是指源站接收到的域名和当前CDN访问域名保持一致</span>
<span v-if="reverseProxyConfig.requestHostType == 1">"跟随源站"是指源站接收到的域名仍然是填写的源站地址中的信息,不随代理服务域名改变而改变</span>
<span v-if="reverseProxyConfig.requestHostType == 2">自定义Host内容中支持请求变量</span>。</p>
@@ -199,7 +201,7 @@ Vue.component("reverse-proxy-box", {
</td>
</tr>
<tr v-show="family == null || family == 'http'">
<td>自动添加的Header</td>
<td>自动添加报头</td>
<td>
<div>
<div style="width: 14em; float: left; margin-bottom: 1em" v-for="header in forwardHeaders" :key="header.name">
@@ -207,7 +209,7 @@ Vue.component("reverse-proxy-box", {
</div>
<div style="clear: both"></div>
</div>
<p class="comment">选中后,会自动向源站请求添加这些Header。</p>
<p class="comment">选中后,会自动向源站请求添加这些报头,以便于源站获取客户端信息。</p>
</td>
</tr>
<tr v-show="family == null || family == 'http'">
@@ -298,6 +300,13 @@ Vue.component("reverse-proxy-box", {
<p class="comment">源站保持等待的空闲超时时间0表示使用默认时间。</p>
</td>
</tr>
<tr v-show="family == null || family == 'http'">
<td>自动重试50X</td>
<td>
<checkbox v-model="reverseProxyConfig.retry50X"></checkbox>
<p class="comment">选中后表示当源站返回状态码为50X比如502、504自动重试。</p>
</td>
</tr>
<tr v-show="family != 'unix'">
<td>PROXY Protocol</td>
<td>

View File

@@ -1,16 +0,0 @@
// 显示流量限制说明
Vue.component("traffic-limit-view", {
props: ["v-traffic-limit"],
data: function () {
return {
config: this.vTrafficLimit
}
},
template: `<div>
<div v-if="config.isOn">
<span v-if="config.dailySize != null && config.dailySize.count > 0">日流量限制:{{config.dailySize.count}}{{config.dailySize.unit.toUpperCase()}}<br/></span>
<span v-if="config.monthlySize != null && config.monthlySize.count > 0">月流量限制:{{config.monthlySize.count}}{{config.monthlySize.unit.toUpperCase()}}<br/></span>
</div>
<span v-else class="disabled">没有限制。</span>
</div>`
})

View File

@@ -27,10 +27,10 @@
},
{
"type": "url-prefix",
"name": "URL前缀",
"name": "URL目录前缀",
"description": "根据URL中的文件路径前缀进行过滤",
"component": "http-cond-url-prefix",
"paramsTitle": "URL前缀",
"paramsTitle": "URL目录前缀",
"isRequest": true,
"caseInsensitive": true
},

View File

@@ -60,6 +60,18 @@ Tea.context(function () {
.params({})
.success(function (resp) {
this.globalMessageBadge = resp.data.count
// add dot to title
let dots = "••• "
if (typeof document.title == "string") {
if (resp.data.count > 0) {
if (!document.title.startsWith(dots)) {
document.title = dots + document.title
}
} else if (document.title.startsWith(dots)) {
document.title = document.title.substring(dots.length)
}
}
})
.done(function () {
let delay = 6000

View File

@@ -63,6 +63,7 @@ div.comment {
padding-top: 0.4em;
font-weight: normal;
word-break: break-all;
line-height: 1.8;
}
p.comment em,
div.comment em {

View File

@@ -1 +1 @@
{"version":3,"sources":["@layout_popup.less"],"names":[],"mappings":"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","file":"@layout_popup.css"}
{"version":3,"sources":["@layout_popup.less"],"names":[],"mappings":"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","file":"@layout_popup.css"}

View File

@@ -69,6 +69,7 @@ p.comment, div.comment {
padding-top: 0.4em;
font-weight: normal;
word-break: break-all;
line-height: 1.8;
}
p.comment em, div.comment em {

View File

@@ -11,7 +11,7 @@
<td class="title">缓存磁盘容量</td>
<td>
<size-capacity-box :v-value="node.maxCacheDiskCapacity" :v-name="'maxCacheDiskCapacityJSON'"></size-capacity-box>
<p class="comment">缓存所在磁盘的最大用量,超出此用量将会自动尝试清理旧数据0表示不限制。</p>
<p class="comment">缓存所在磁盘的最大用量,超出此用量或磁盘接近用尽时将会自动尝试清理旧数据0表示不限制。</p>
</td>
</tr>
<tr>

View File

@@ -14,23 +14,35 @@
<td class="title color-border">禁止未绑定域名访问</td>
<td>
<checkbox name="httpAllMatchDomainStrictly" v-model="config.httpAll.matchDomainStrictly"></checkbox>
<p class="comment">选中后表示禁止未绑定的域名和IP访问。</p>
<p class="comment">选中后,表示禁止未在网站绑定的域名和IP访问。</p>
</td>
</tr>
<tr v-show="config.httpAll.matchDomainStrictly">
<tr>
<td class="color-border">处理未绑定域名方式</td>
<td>
<radio name="httpAllDomainMismatchActionCode" :v-value="'page'" v-model="httpAllDomainMismatchActionCode">显示提示页面</radio> &nbsp;
&nbsp; <radio name="httpAllDomainMismatchActionCode" :v-value="'close'" v-model="httpAllDomainMismatchActionCode">关闭连接</radio>
<p class="comment" v-if="httpAllDomainMismatchActionCode == 'page'">显示提示内容。</p>
<p class="comment" v-if="httpAllDomainMismatchActionCode == 'close'">直接关闭网络连接,不提示任何内容。</p>
</td>
</tr>
<tr v-show="config.httpAll.matchDomainStrictly && httpAllDomainMismatchActionCode == 'page'">
<td class="color-border">提示页面状态码</td>
<td>
<input type="text" name="httpAllDomainMismatchActionStatusCode" v-model="httpAllDomainMismatchActionStatusCode" style="width: 4em" maxlength="3"/>
<p class="comment">默认404。</p>
<p class="comment">访问未绑定域名时的提示页面状态码,默认404。</p>
</td>
</tr>
<tr v-show="config.httpAll.matchDomainStrictly">
<td class="color-border">未绑定域名页面提示</td>
<tr v-show="config.httpAll.matchDomainStrictly && httpAllDomainMismatchActionCode == 'page'">
<td class="color-border">提示页面内容</td>
<td>
<textarea rows="3" name="httpAllDomainMismatchActionContentHTML" v-model="httpAllDomainMismatchActionContentHTML"></textarea>
<p class="comment">访问未绑定的域名时提示的文字可以使用HTML仅限于HTTP请求不适于用HTTPSHTTPS会提示证书错误</p>
<p class="comment">访问未绑定的域名时提示页面内容可以使用HTML仅限于HTTP请求不适于用HTTPSHTTPS会提示证书错误</p>
</td>
</tr>
<tr v-show="config.httpAll.matchDomainStrictly">
<td class="color-border">允许例外的域名</td>
<td>
@@ -181,6 +193,24 @@
<p class="comment">选中后所有请求优先发送到L2或者更高级别节点。默认不开启的情况下只有可以缓存的内容请求才会发送到L2或者更高级别节点。</p>
</td>
</tr>
<tr>
<td>Ln请求负载均衡方法</td>
<td>
<select class="ui dropdown auto-width" name="httpAllLnRequestSchedulingMethod" v-model="config.httpAll.lnRequestSchedulingMethod">
<option value="urlMapping">URL映射</option>
<option value="random">随机</option>
</select>
<p class="comment" v-if="config.httpAll.lnRequestSchedulingMethod == 'urlMapping'">当存在多个Ln节点时将请求根据URL自动映射到某个固定的Ln节点。</p>
<p class="comment" v-if="config.httpAll.lnRequestSchedulingMethod == 'random'">当存在多个Ln节点时将请求随机发送到某个Ln节点。</p>
</td>
</tr>
<tr>
<td>支持${serverAddr}变量</td>
<td>
<checkbox name="httpAllEnableServerAddrVariable" v-model="config.httpAll.enableServerAddrVariable"></checkbox>
<p class="comment">支持在自定义页面中使用<code-label>${serverAddr}</code-label>变量,表示访客当前正在访问的服务器地址。</p>
</td>
</tr>
</table>
<submit-btn></submit-btn>

View File

@@ -34,6 +34,7 @@
<span v-if="task.type == 'nodeLevelChanged'">同步L2节点</span>
<span v-if="task.type == 'ddosProtectionChanged'">DDoS配置</span>
<span v-if="task.type == 'userServersStateChanged'">用户网站状态</span>
<span v-if="task.type != null && task.type.startsWith('ipListDeleted')">删除IP名单</span>
</td>
<td>
<span v-if="task.isDone" class="red">{{task.error}}</span>

View File

@@ -26,7 +26,7 @@
<!-- 边缘节点升级提醒 -->
<div class="ui icon message error" v-if="!isLoading && nodeUpgradeInfo.count > 0">
<i class="icon warning circle"></i>
<a href="/clusters">升级提醒:有 {{nodeUpgradeInfo.count}} 个边缘节点需要升级到 v{{nodeUpgradeInfo.version}} 版本,系统正在尝试自动升级...</a><a href="" title="关闭" @click.prevent="closeMessage"><i class="ui icon remove small"></i></a>
<a href="/clusters">升级提醒:有 {{nodeUpgradeInfo.count}} 个边缘节点需要升级到 v{{nodeUpgradeInfo.version}} 版本,系统正在尝试自动升级,请耐心等待...</a><a href="" title="关闭" @click.prevent="closeMessage"><i class="ui icon remove small"></i></a>
</div>
<!-- API节点升级提醒 -->

View File

@@ -59,7 +59,7 @@
<tr v-if="protocol == 'https' || protocol == 'tls'">
<td>{{protocol.toUpperCase()}}证书</td>
<td>
<ssl-certs-box :v-single-mode="true" :v-view-size="'mini'" :v-description="'可以选择连接源站使用的证书。'"></ssl-certs-box>
<ssl-certs-box :v-single-mode="true" :v-view-size="'mini'" :v-description="'可以选择连接源站使用的证书;除非源站有特殊需求,否则不需要添加。'"></ssl-certs-box>
</td>
</tr>
<tr v-if="isHTTP">

View File

@@ -9,14 +9,14 @@
<td class="title">单个域名 *</td>
<td>
<input type="text" name="serverName" ref="focus" maxlength="1024" v-model="serverName.name"/>
<p class="comment">请输入单个域名,域名中<strong>不要</strong>包含<code-label>http://</code-label><code-label>https://</code-label>;也可以使用泛域名,比如<code-label>*.example.com</code-label></p>
<p class="comment">请输入单个域名,比如<code-label>example.com</code-label><code-label>www.example.com</code-label><strong>不要</strong>包含<code-label>http://</code-label><code-label>https://</code-label>或端口号;也可以使用泛域名,比如<code-label>*.example.com</code-label></p>
</td>
</tr>
<tr v-if="mode == 'multiple'">
<td class="title">多个域名 *</td>
<td>
<textarea name="serverNames" ref="serverNames" v-model="multipleServerNames"></textarea>
<p class="comment">每行一个域名,域名中<strong>不要</strong>包含<code-label>http://</code-label><code-label>https://</code-label>;也可以使用泛域名,比如<code-label>*.example.com</code-label></p>
<p class="comment">每行一个域名,比如<code-label>example.com</code-label><code-label>www.example.com</code-label><strong>不要</strong>包含<code-label>http://</code-label><code-label>https://</code-label>或端口号;也可以使用泛域名,比如<code-label>*.example.com</code-label></p>
</td>
</tr>
</table>

View File

@@ -42,7 +42,7 @@
<tr v-show="reason == REASON_ISSUE_REPORT">
<td>操作建议</td>
<td>
本产品提供的 <a href="/servers/components/cache/batch?keyType=prefix" target="_blank">[刷新预热]</a> 功能运行非常可靠如果你确认发现功能异常且能100%几率重现问题,请及时报告给软件开发者。
当前系统提供的 <a href="/servers/components/cache/batch?keyType=prefix" target="_blank">[刷新预热]</a> 功能运行非常可靠如果你确认发现功能异常且能100%几率重现问题,请及时报告给软件开发者。
</td>
</tr>
<tr v-show="reason == REASON_BATCH_DELETE">

View File

@@ -29,7 +29,7 @@
<td class="color-border">缓存磁盘最大用量 *</td>
<td>
<size-capacity-box :v-name="'capacityJSON'" :v-count="128" :v-unit="'gb'" :key="'capacityJSON1'"></size-capacity-box>
<p class="comment">缓存所在磁盘的最大用量,超出此用量将会自动尝试清理旧数据如果为0表示没有限制。</p>
<p class="comment">缓存所在磁盘的最大用量,超出此用量或磁盘接近用尽时将会自动尝试清理旧数据如果为0表示没有限制。</p>
</td>
</tr>
<tr>

View File

@@ -31,7 +31,7 @@
<td class="color-border">缓存磁盘最大用量</td>
<td>
<size-capacity-box :v-name="'capacityJSON'" :v-value="cachePolicy.capacity" :v-count="0" :v-unit="'gb'"></size-capacity-box>
<p class="comment">缓存所在磁盘的最大用量,超出此用量将会自动尝试清理旧数据如果为0表示没有限制。</p>
<p class="comment">缓存所在磁盘的最大用量,超出此用量或磁盘接近用尽时将会自动尝试清理旧数据如果为0表示没有限制。</p>
</td>
</tr>
<tr v-if="cachePolicy.options.memoryPolicy != null && cachePolicy.options.memoryPolicy.capacity != null">

View File

@@ -17,7 +17,7 @@
<td>代号</td>
<td>
<input type="text" name="code" maxlength="100"/>
<p class="comment">在导入时可以合并相同代号的分组。</p>
<p class="comment">可选项,在导入时可以合并相同代号的分组。</p>
</td>
</tr>
<tr>

View File

@@ -16,7 +16,7 @@
<td>代号</td>
<td>
<input type="text" name="code" maxlength="100" v-model="group.code"/>
<p class="comment">在导入时可以合并相同代号的分组。</p>
<p class="comment">可选项,在导入时可以合并相同代号的分组。</p>
</td>
</tr>
<tr>

View File

@@ -62,7 +62,7 @@
<tr v-if="protocol == 'https' || protocol == 'tls'">
<td>{{protocol.toUpperCase()}}证书</td>
<td>
<ssl-certs-box :v-single-mode="true" :v-view-size="'mini'" :v-description="'可以选择连接源站使用的证书。'"></ssl-certs-box>
<ssl-certs-box :v-single-mode="true" :v-view-size="'mini'" :v-description="'可以选择连接源站使用的证书;除非源站有特殊需求,否则不需要添加。'"></ssl-certs-box>
</td>
</tr>
<tr v-show="protocol != 'udp'">

View File

@@ -63,7 +63,7 @@
<tr v-if="origin.protocol == 'https' || origin.protocol == 'tls'">
<td>{{origin.protocol.toUpperCase()}}证书</td>
<td>
<ssl-certs-box :v-single-mode="true" :v-cert="origin.cert" :v-view-size="'mini'" :v-description="'可以选择连接源站使用的证书。'"></ssl-certs-box>
<ssl-certs-box :v-single-mode="true" :v-cert="origin.cert" :v-view-size="'mini'" :v-description="'可以选择连接源站使用的证书;除非源站有特殊需求,否则不需要添加。'"></ssl-certs-box>
</td>
</tr>
<tr v-show="origin.protocol != 'udp'">

View File

@@ -3,17 +3,21 @@ Tea.context(function () {
this.addHTMLTemplate = function () {
this.$refs.htmlBody.value = `<!DOCTYPE html>
<html>
<html lang="en">
<head>
\t<title>\${status} \${statusMessage}</title>
\t<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
\t<style>
\t\taddress { line-height: 1.8; }
\t</style>
</head>
<body>
<h1>\${status} \${statusMessage}</h1>
<p><!-- 内容 --></p>
<address>Request ID: \${requestId}.</address>
<address>Connection: \${remoteAddr} (Client) -&gt; \${serverAddr} (Server)</address>
<address>Request ID: \${requestId}</address>
</body>
</html>`

View File

@@ -10,17 +10,21 @@ Tea.context(function () {
this.addHTMLTemplate = function () {
this.$refs.htmlBody.value = `<!DOCTYPE html>
<html>
<html lang="en">
<head>
\t<title>\${status} \${statusMessage}</title>
\t<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
\t<style>
\t\taddress { line-height: 1.8; }
\t</style>
</head>
<body>
<h1>\${status} \${statusMessage}</h1>
<p><!-- 内容 --></p>
<address>Request ID: \${requestId}.</address>
<address>Connection: \${remoteAddr} (Client) -&gt; \${serverAddr} (Server)</address>
<address>Request ID: \${requestId}</address>
</body>
</html>`

View File

@@ -54,6 +54,20 @@
</tr>
</table>
<h4 style="margin-top: 0">用户套餐带宽统计</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">数据保存天数 *</td>
<td>
<div class="ui input right labeled">
<input type="text" name="userPlanBandwidthStatCleanDays" v-model="config.userPlanBandwidthStat.clean.days" style="width:6em" maxlength="6"/>
<span class="ui label"></span>
</div>
<p class="comment"><pro-warning-label></pro-warning-label></p>
</td>
</tr>
</table>
<h4 style="margin-top: 0">网站流量统计</h4>
<table class="ui table definition selectable">
<tr>