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" LABEL maintainer="goedge.cdn@gmail.com"
ENV TZ "Asia/Shanghai" ENV TZ "Asia/Shanghai"
ENV VERSION 1.2.7 ENV VERSION 1.2.8
ENV ROOT_DIR /usr/local/goedge ENV ROOT_DIR /usr/local/goedge
ENV TAR_FILE edge-admin-linux-amd64-plus-v${VERSION}.zip 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 "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 RUN apk add --no-cache tzdata

View File

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

View File

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

View File

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

View File

@@ -43,13 +43,19 @@ func (this *IndexAction) RunGet(params struct {
} }
this.Data["config"] = config this.Data["config"] = config
var httpAllDomainMismatchActionCode = serverconfigs.DomainMismatchActionPage
var httpAllDomainMismatchActionContentHTML string var httpAllDomainMismatchActionContentHTML string
var httpAllDomainMismatchActionStatusCode = "404" var httpAllDomainMismatchActionStatusCode = "404"
if config.HTTPAll.DomainMismatchAction != nil && config.HTTPAll.DomainMismatchAction.Options != nil { if config.HTTPAll.DomainMismatchAction != nil {
httpAllDomainMismatchActionContentHTML = config.HTTPAll.DomainMismatchAction.Options.GetString("contentHTML") httpAllDomainMismatchActionCode = config.HTTPAll.DomainMismatchAction.Code
var statusCode = config.HTTPAll.DomainMismatchAction.Options.GetInt("statusCode")
if statusCode > 0 { if config.HTTPAll.DomainMismatchAction.Options != nil {
httpAllDomainMismatchActionStatusCode = types.String(statusCode) // 即使是非 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 { } else {
httpAllDomainMismatchActionContentHTML = `<!DOCTYPE html> httpAllDomainMismatchActionContentHTML = `<!DOCTYPE html>
@@ -73,6 +79,7 @@ p { color: grey; }
</html>` </html>`
} }
this.Data["httpAllDomainMismatchActionCode"] = httpAllDomainMismatchActionCode
this.Data["httpAllDomainMismatchActionContentHTML"] = httpAllDomainMismatchActionContentHTML this.Data["httpAllDomainMismatchActionContentHTML"] = httpAllDomainMismatchActionContentHTML
this.Data["httpAllDomainMismatchActionStatusCode"] = httpAllDomainMismatchActionStatusCode this.Data["httpAllDomainMismatchActionStatusCode"] = httpAllDomainMismatchActionStatusCode
@@ -83,6 +90,7 @@ func (this *IndexAction) RunPost(params struct {
ClusterId int64 ClusterId int64
HttpAllMatchDomainStrictly bool HttpAllMatchDomainStrictly bool
HttpAllDomainMismatchActionCode string
HttpAllDomainMismatchActionContentHTML string HttpAllDomainMismatchActionContentHTML string
HttpAllDomainMismatchActionStatusCode string HttpAllDomainMismatchActionStatusCode string
HttpAllAllowMismatchDomainsJSON []byte HttpAllAllowMismatchDomainsJSON []byte
@@ -90,11 +98,13 @@ func (this *IndexAction) RunPost(params struct {
HttpAllDefaultDomain string HttpAllDefaultDomain string
HttpAllNodeIPPageHTML string HttpAllNodeIPPageHTML string
HttpAllNodeIPShowPage bool HttpAllNodeIPShowPage bool
HttpAllEnableServerAddrVariable bool
HttpAllServerName string HttpAllServerName string
HttpAllSupportsLowVersionHTTP bool HttpAllSupportsLowVersionHTTP bool
HttpAllMatchCertFromAllServers bool HttpAllMatchCertFromAllServers bool
HttpAllForceLnRequest bool HttpAllForceLnRequest bool
HttpAllLnRequestSchedulingMethod string
HttpAccessLogIsOn bool HttpAccessLogIsOn bool
HttpAccessLogEnableRequestHeaders bool HttpAccessLogEnableRequestHeaders bool
@@ -139,7 +149,7 @@ func (this *IndexAction) RunPost(params struct {
config.HTTPAll.MatchDomainStrictly = params.HttpAllMatchDomainStrictly config.HTTPAll.MatchDomainStrictly = params.HttpAllMatchDomainStrictly
config.HTTPAll.DomainMismatchAction = &serverconfigs.DomainMismatchAction{ config.HTTPAll.DomainMismatchAction = &serverconfigs.DomainMismatchAction{
Code: serverconfigs.DomainMismatchActionPage, Code: params.HttpAllDomainMismatchActionCode,
Options: maps.Map{ Options: maps.Map{
"statusCode": domainMisMatchStatusCode, "statusCode": domainMisMatchStatusCode,
"contentHTML": params.HttpAllDomainMismatchActionContentHTML, "contentHTML": params.HttpAllDomainMismatchActionContentHTML,
@@ -167,6 +177,8 @@ func (this *IndexAction) RunPost(params struct {
config.HTTPAll.SupportsLowVersionHTTP = params.HttpAllSupportsLowVersionHTTP config.HTTPAll.SupportsLowVersionHTTP = params.HttpAllSupportsLowVersionHTTP
config.HTTPAll.MatchCertFromAllServers = params.HttpAllMatchCertFromAllServers config.HTTPAll.MatchCertFromAllServers = params.HttpAllMatchCertFromAllServers
config.HTTPAll.ForceLnRequest = params.HttpAllForceLnRequest config.HTTPAll.ForceLnRequest = params.HttpAllForceLnRequest
config.HTTPAll.LnRequestSchedulingMethod = params.HttpAllLnRequestSchedulingMethod
config.HTTPAll.EnableServerAddrVariable = params.HttpAllEnableServerAddrVariable
// 访问日志 // 访问日志
config.HTTPAccessLog.IsOn = params.HttpAccessLogIsOn config.HTTPAccessLog.IsOn = params.HttpAccessLogIsOn

View File

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

View File

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

View File

@@ -4,6 +4,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
"net"
"net/url" "net/url"
"regexp" "regexp"
"strings" "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. params.Must.
Field("serverName", serverName). Field("serverName", serverName).
Require("请输入域名") 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) serverName = strings.ToLower(serverName)

View File

@@ -3,6 +3,7 @@ package waf
import ( import (
"encoding/json" "encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "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/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/iwind/TeaGo/lists" "github.com/iwind/TeaGo/lists"
@@ -141,20 +142,7 @@ func (this *LogAction) RunGet(params struct {
this.Data["groups"] = groupMaps this.Data["groups"] = groupMaps
// 根据IP查询区域 // 根据IP查询区域
regionMap := map[string]string{} // ip => region this.Data["regions"] = iplibrary.LookupIPSummaries(ipList)
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
// WAF相关 // WAF相关
var wafInfos = map[int64]maps.Map{} // set id => WAF Map 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{ var remoteAddrConfig = &serverconfigs.HTTPRemoteAddrConfig{
IsOn: true, IsOn: true,
Value: "${rawRemoteAddr}", Value: "${rawRemoteAddr}",
Type: serverconfigs.HTTPRemoteAddrTypeDefault,
} }
if params.RemoteAddrIsOn { if params.RemoteAddrIsOn {
remoteAddrConfig.Value = "${remoteAddr}" remoteAddrConfig.Value = "${remoteAddr}"
remoteAddrConfig.Type = serverconfigs.HTTPRemoteAddrTypeProxy
} }
remoteAddrConfigJSON, err := json.Marshal(remoteAddrConfig) remoteAddrConfigJSON, err := json.Marshal(remoteAddrConfig)
if err != nil { if err != nil {

View File

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

View File

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

View File

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

View File

@@ -10,6 +10,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
"regexp"
"strings" "strings"
) )
@@ -54,9 +55,29 @@ func (this *IndexAction) RunPost(params struct {
err := json.Unmarshal(params.RemoteAddrJSON, remoteAddrConfig) err := json.Unmarshal(params.RemoteAddrJSON, remoteAddrConfig)
if err != nil { if err != nil {
this.Fail("参数校验失败:" + err.Error()) this.Fail("参数校验失败:" + err.Error())
return
} }
remoteAddrConfig.Value = strings.TrimSpace(remoteAddrConfig.Value) 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() err = remoteAddrConfig.Init()
if err != nil { if err != nil {
this.Fail("配置校验失败:" + err.Error()) this.Fail("配置校验失败:" + err.Error())

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,6 +5,7 @@ package logs
import ( import (
"fmt" "fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "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/rpc/pb"
"github.com/iwind/TeaGo/lists" "github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
@@ -169,20 +170,7 @@ func (this *IndexAction) RunGet(params struct {
} }
// 根据IP查询区域 // 根据IP查询区域
var regionMap = map[string]string{} // ip => region this.Data["regions"] = iplibrary.LookupIPSummaries(ipList)
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
// WAF相关 // WAF相关
var wafInfos = map[int64]maps.Map{} // set id => WAF Map var wafInfos = map[int64]maps.Map{} // set id => WAF Map

View File

@@ -1,6 +1,7 @@
package log package log
import ( import (
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/lists" "github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
@@ -160,20 +161,7 @@ func (this *HistoryAction) RunGet(params struct {
} }
// 根据IP查询区域 // 根据IP查询区域
var regionMap = map[string]string{} // ip => region this.Data["regions"] = iplibrary.LookupIPSummaries(ipList)
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
// WAF相关 // WAF相关
var wafInfos = map[int64]maps.Map{} // set id => WAF Map var wafInfos = map[int64]maps.Map{} // set id => WAF Map

View File

@@ -1,6 +1,7 @@
package log package log
import ( import (
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists" "github.com/iwind/TeaGo/lists"
@@ -129,20 +130,7 @@ func (this *IndexAction) RunPost(params struct {
this.Data["hasMore"] = accessLogsResp.HasMore this.Data["hasMore"] = accessLogsResp.HasMore
// 根据IP查询区域 // 根据IP查询区域
var regionMap = map[string]string{} // ip => region this.Data["regions"] = iplibrary.LookupIPSummaries(ipList)
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
// WAF相关 // WAF相关
var wafInfos = map[int64]maps.Map{} // set id => WAF Map var wafInfos = map[int64]maps.Map{} // set id => WAF Map

View File

@@ -1,6 +1,7 @@
package log package log
import ( import (
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/lists" "github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
@@ -141,20 +142,7 @@ func (this *TodayAction) RunGet(params struct {
} }
// 根据IP查询区域 // 根据IP查询区域
var regionMap = map[string]string{} // ip => region this.Data["regions"] = iplibrary.LookupIPSummaries(ipList)
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
// WAF相关 // WAF相关
var wafInfos = map[int64]maps.Map{} // set id => WAF Map var wafInfos = map[int64]maps.Map{} // set id => WAF Map

View File

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

View File

@@ -9,6 +9,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
"regexp"
"strings" "strings"
) )
@@ -46,9 +47,29 @@ func (this *IndexAction) RunPost(params struct {
err := json.Unmarshal(params.RemoteAddrJSON, remoteAddrConfig) err := json.Unmarshal(params.RemoteAddrJSON, remoteAddrConfig)
if err != nil { if err != nil {
this.Fail("参数校验失败:" + err.Error()) this.Fail("参数校验失败:" + err.Error())
return
} }
remoteAddrConfig.Value = strings.TrimSpace(remoteAddrConfig.Value) 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() err = remoteAddrConfig.Init()
if err != nil { if err != nil {
this.Fail("配置校验失败:" + err.Error()) this.Fail("配置校验失败:" + err.Error())

View File

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

View File

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

View File

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

View File

@@ -10,6 +10,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"regexp"
"strings" "strings"
) )
@@ -63,6 +64,25 @@ func (this *IndexAction) RunPost(params struct {
} }
remoteAddrConfig.Value = strings.TrimSpace(remoteAddrConfig.Value) 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() err = remoteAddrConfig.Init()
if err != nil { if err != nil {
this.Fail("配置校验失败:" + err.Error()) this.Fail("配置校验失败:" + err.Error())

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -342,7 +342,7 @@ func (this *MySQLInstaller) InstallFromFile(xzFilePath string, targetDir string)
} }
// waiting for startup // waiting for startup
for i := 0; i < 5; i++ { for i := 0; i < 30; i++ {
_, err = net.Dial("tcp", "127.0.0.1:3306") _, err = net.Dial("tcp", "127.0.0.1:3306")
if err != nil { if err != nil {
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
@@ -362,7 +362,7 @@ func (this *MySQLInstaller) InstallFromFile(xzFilePath string, targetDir string)
this.log("changing mysql password ...") this.log("changing mysql password ...")
var passwordSQL = "ALTER USER 'root'@'localhost' IDENTIFIED BY '" + newPassword + "';" 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() cmd.WithStderr()
err = cmd.Run() err = cmd.Run()
if err != nil { if err != nil {

View File

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

View File

@@ -2,10 +2,10 @@ package helpers
import ( import (
"github.com/TeaOSLab/EdgeAdmin/internal/events" "github.com/TeaOSLab/EdgeAdmin/internal/events"
nodes "github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/utils" "github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils" "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/TeaOSLab/EdgeCommon/pkg/systemconfigs"
"github.com/iwind/TeaGo/lists" "github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/logs" "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 { if len(config.AllowCountryIds) > 0 || len(config.AllowProvinceIds) > 0 {
rpc, err := nodes.SharedRPC() var userRegion = iplibrary.Lookup(ip)
if err != nil { if userRegion == nil || !userRegion.IsOk() {
logs.Println("[USER_MUST_AUTH][ERROR]" + err.Error())
return false return false
} }
resp, err := rpc.IPLibraryRPC().LookupIPRegion(rpc.Context(0), &pb.LookupIPRegionRequest{Ip: ipAddr}) if len(config.AllowCountryIds) > 0 {
if err != nil { // 检查大中华区
logs.Println("[USER_MUST_AUTH][ERROR]" + err.Error()) var found = false
return 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 { if len(config.AllowProvinceIds) > 0 && !lists.ContainsInt64(config.AllowProvinceIds, userRegion.ProvinceId()) {
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) {
return false 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>` </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", { Vue.component("plan-price-view", {
props: ["v-plan"], props: ["v-plan"],
data: function () { data: function () {
@@ -3033,6 +3069,7 @@ Vue.component("plan-price-config-box", {
<input type="text" style="width: 7em" maxlength="10" v-model="monthlyPrice"/> <input type="text" style="width: 7em" maxlength="10" v-model="monthlyPrice"/>
<span class="ui label">元</span> <span class="ui label">元</span>
</div> </div>
<p class="comment">如果为0表示免费。</p>
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -3042,6 +3079,7 @@ Vue.component("plan-price-config-box", {
<input type="text" style="width: 7em" maxlength="10" v-model="seasonallyPrice"/> <input type="text" style="width: 7em" maxlength="10" v-model="seasonallyPrice"/>
<span class="ui label">元</span> <span class="ui label">元</span>
</div> </div>
<p class="comment">如果为0表示免费。</p>
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -3051,6 +3089,7 @@ Vue.component("plan-price-config-box", {
<input type="text" style="width: 7em" maxlength="10" v-model="yearlyPrice"/> <input type="text" style="width: 7em" maxlength="10" v-model="yearlyPrice"/>
<span class="ui label">元</span> <span class="ui label">元</span>
</div> </div>
<p class="comment">如果为0表示免费。</p>
</td> </td>
</tr> </tr>
</table> </table>
@@ -4505,44 +4544,6 @@ Vue.component("http-firewall-actions-view", {
</div>` </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规则的标签 // 显示WAF规则的标签
Vue.component("http-firewall-rule-label", { Vue.component("http-firewall-rule-label", {
props: ["v-rule"], props: ["v-rule"],
@@ -5043,7 +5044,7 @@ Vue.component("http-cache-ref-box", {
isOn: true, isOn: true,
cachePolicyId: 0, cachePolicyId: 0,
key: "${scheme}://${host}${requestPath}${isArgs}${args}", key: "${scheme}://${host}${requestPath}${isArgs}${args}",
life: {count: 2, unit: "hour"}, life: {count: 1, unit: "day"},
status: [200], status: [200],
maxSize: {count: 128, unit: "mb"}, maxSize: {count: 128, unit: "mb"},
minSize: {count: 0, unit: "kb"}, minSize: {count: 0, unit: "kb"},
@@ -5200,13 +5201,13 @@ Vue.component("http-cache-ref-box", {
}, },
template: `<tbody> template: `<tbody>
<tr v-if="condCategory == 'simple'"> <tr v-if="condCategory == 'simple'">
<td class="title">条件类型 *</td> <td class="title">缓存对象 *</td>
<td> <td>
<select class="ui dropdown auto-width" name="condType" v-model="condType" @change="changeCondType(condType, false)"> <select class="ui dropdown auto-width" name="condType" v-model="condType" @change="changeCondType(condType, false)">
<option value="url-extension">文件扩展名</option> <option value="url-extension">文件扩展名</option>
<option value="url-eq-index">首页</option> <option value="url-eq-index">首页</option>
<option value="url-all">全站</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-eq">URL完整路径</option>
<option value="url-wildcard-match">URL通配符</option> <option value="url-wildcard-match">URL通配符</option>
<option value="url-regexp">URL正则匹配</option> <option value="url-regexp">URL正则匹配</option>
@@ -5244,7 +5245,7 @@ Vue.component("http-cache-ref-box", {
<tr v-show="!vIsReverse"> <tr v-show="!vIsReverse">
<td>缓存有效期 *</td> <td>缓存有效期 *</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> </td>
</tr> </tr>
<tr v-show="!vIsReverse"> <tr v-show="!vIsReverse">
@@ -5255,15 +5256,15 @@ Vue.component("http-cache-ref-box", {
</td> </td>
</tr> </tr>
<tr v-show="!vIsReverse"> <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>缓存Key *</td>
<td> <td>
<input type="text" v-model="ref.key" @input="changeKey(ref.key)"/> <input type="text" v-model="ref.key" @input="changeKey(ref.key)"/>
<p class="comment">用来区分不同缓存内容的唯一Key。<request-variables-describer ref="variablesDescriber"></request-variables-describer>。</p> <p class="comment">用来区分不同缓存内容的唯一Key。<request-variables-describer ref="variablesDescriber"></request-variables-describer>。</p>
</td> </td>
</tr> </tr>
<tr v-show="!vIsReverse">
<td colspan="2"><more-options-indicator @change="changeOptionsVisible"></more-options-indicator></td>
</tr>
<tr v-show="moreOptionsVisible && !vIsReverse"> <tr v-show="moreOptionsVisible && !vIsReverse">
<td>请求方法限制</td> <td>请求方法限制</td>
<td> <td>
@@ -5333,7 +5334,7 @@ Vue.component("http-cache-ref-box", {
<input type="checkbox" value="1" v-model="ref.skipSetCookie"/> <input type="checkbox" value="1" v-model="ref.skipSetCookie"/>
<label></label> <label></label>
</div> </div>
<p class="comment">选中后,当响应的Header中有Set-Cookie时不缓存响应内容。</p> <p class="comment">选中后,当响应的报头中有Set-Cookie时不缓存响应内容,防止动态内容被缓存。</p>
</td> </td>
</tr> </tr>
<tr v-show="moreOptionsVisible && !vIsReverse"> <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"/> <input type="checkbox" name="enableRequestCachePragma" value="1" v-model="ref.enableRequestCachePragma"/>
<label></label> <label></label>
</div> </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> </td>
</tr> </tr>
<tr v-show="moreOptionsVisible && !vIsReverse"> <tr v-show="moreOptionsVisible && !vIsReverse">
@@ -5699,7 +5700,7 @@ Vue.component("http-firewall-config-box", {
</td> </td>
</tr> </tr>
</tbody> </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"> <tbody v-show="moreOptionsVisible">
<tr> <tr>
<td>启用系统全局规则</td> <td>启用系统全局规则</td>
@@ -8614,11 +8615,15 @@ Vue.component("http-firewall-actions-box", {
} }
var defaultPageBody = `<!DOCTYPE html> var defaultPageBody = `<!DOCTYPE html>
<html> <html lang="en">
<title>403 Forbidden</title> <title>403 Forbidden</title>
\t<style>
\t\taddress { line-height: 1.8; }
\t</style>
<body> <body>
<h1>403 Forbidden</h1> <h1>403 Forbidden</h1>
<address>Request ID: \${requestId}.</address> <address>Connection: \${remoteAddr} (Client) -&gt; \${serverAddr} (Server)</address>
<address>Request ID: \${requestId}</address>
</body> </body>
</html>` </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> <span v-if="config.code == 'post_307' && config.options.life > 0">:有效期{{config.options.life}}秒</span>
<!-- record_ip --> <!-- 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 --> <!-- tag -->
<span v-if="config.code == 'tag'">{{config.options.tags.join(", ")}}</span> <span v-if="config.code == 'tag'">{{config.options.tags.join(", ")}}</span>
@@ -10178,17 +10183,21 @@ Vue.component("http-pages-and-shutdown-box", {
}, },
addShutdownHTMLTemplate: function () { addShutdownHTMLTemplate: function () {
this.shutdownConfig.body = `<!DOCTYPE html> this.shutdownConfig.body = `<!DOCTYPE html>
<html> <html lang="en">
<head> <head>
\t<title>升级中</title> \t<title>升级中</title>
\t<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> \t<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
\t<style>
\t\taddress { line-height: 1.8; }
\t</style>
</head> </head>
<body> <body>
<h1>网站升级中</h1> <h1>网站升级中</h1>
<p>为了给您提供更好的服务,我们正在升级网站,请稍后重新访问。</p> <p>为了给您提供更好的服务,我们正在升级网站,请稍后重新访问。</p>
<address>Request ID: \${requestId}.</address> <address>Connection: \${remoteAddr} (Client) -&gt; \${serverAddr} (Server)</address>
<address>Request ID: \${requestId}</address>
</body> </body>
</html>` </html>`
@@ -11560,23 +11569,6 @@ Vue.component("http-access-log-config-box", {
</div>` </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", { Vue.component("http-auth-basic-auth-user-box", {
props: ["v-users"], props: ["v-users"],
@@ -12041,7 +12033,8 @@ Vue.component("reverse-proxy-box", {
idleTimeout: {count: 0, unit: "second"}, idleTimeout: {count: 0, unit: "second"},
maxConns: 0, maxConns: 0,
maxIdleConns: 0, maxIdleConns: 0,
followRedirects: false followRedirects: false,
retry50X: true
} }
} }
if (reverseProxyConfig.addHeaders == null) { if (reverseProxyConfig.addHeaders == null) {
@@ -12181,6 +12174,7 @@ Vue.component("reverse-proxy-box", {
<input type="checkbox" v-model="reverseProxyRef.isOn"/> <input type="checkbox" v-model="reverseProxyRef.isOn"/>
<label></label> <label></label>
</div> </div>
<p class="comment">选中后,所有源站设置才会生效。</p>
</td> </td>
</tr> </tr>
<tr v-show="family == null || family == 'http'"> <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"> <div v-show="reverseProxyConfig.requestHostType == 2" style="margin-top: 0.8em">
<input type="text" placeholder="比如example.com" v-model="reverseProxyConfig.requestHost"/> <input type="text" placeholder="比如example.com" v-model="reverseProxyConfig.requestHost"/>
</div> </div>
<p class="comment">请求源站时的Host用于修改源站接收到的域名 <p class="comment">请求源站时的主机名(Host,用于修改源站接收到的域名
<span v-if="reverseProxyConfig.requestHostType == 0">"跟随CDN域名"是指源站接收到的域名和当前CDN访问域名保持一致</span> <span v-if="reverseProxyConfig.requestHostType == 0">"跟随CDN域名"是指源站接收到的域名和当前CDN访问域名保持一致</span>
<span v-if="reverseProxyConfig.requestHostType == 1">"跟随源站"是指源站接收到的域名仍然是填写的源站地址中的信息,不随代理服务域名改变而改变</span> <span v-if="reverseProxyConfig.requestHostType == 1">"跟随源站"是指源站接收到的域名仍然是填写的源站地址中的信息,不随代理服务域名改变而改变</span>
<span v-if="reverseProxyConfig.requestHostType == 2">自定义Host内容中支持请求变量</span>。</p> <span v-if="reverseProxyConfig.requestHostType == 2">自定义Host内容中支持请求变量</span>。</p>
@@ -12215,7 +12209,7 @@ Vue.component("reverse-proxy-box", {
</td> </td>
</tr> </tr>
<tr v-show="family == null || family == 'http'"> <tr v-show="family == null || family == 'http'">
<td>自动添加的Header</td> <td>自动添加报头</td>
<td> <td>
<div> <div>
<div style="width: 14em; float: left; margin-bottom: 1em" v-for="header in forwardHeaders" :key="header.name"> <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>
<div style="clear: both"></div> <div style="clear: both"></div>
</div> </div>
<p class="comment">选中后,会自动向源站请求添加这些Header。</p> <p class="comment">选中后,会自动向源站请求添加这些报头,以便于源站获取客户端信息。</p>
</td> </td>
</tr> </tr>
<tr v-show="family == null || family == 'http'"> <tr v-show="family == null || family == 'http'">
@@ -12314,6 +12308,13 @@ Vue.component("reverse-proxy-box", {
<p class="comment">源站保持等待的空闲超时时间0表示使用默认时间。</p> <p class="comment">源站保持等待的空闲超时时间0表示使用默认时间。</p>
</td> </td>
</tr> </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'"> <tr v-show="family != 'unix'">
<td>PROXY Protocol</td> <td>PROXY Protocol</td>
<td> <td>
@@ -12428,13 +12429,35 @@ Vue.component("http-remote-addr-config-box", {
isPrior: false, isPrior: false,
isOn: false, isOn: false,
value: "${rawRemoteAddr}", value: "${rawRemoteAddr}",
isCustomized: false type: "default",
requestHeaderName: ""
} }
} }
let optionValue = "" // type
if (!config.isCustomized && (config.value == "${remoteAddr}" || config.value == "${rawRemoteAddr}")) { if (config.type == null || config.type.length == 0) {
optionValue = config.value 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 { return {
@@ -12442,33 +12465,70 @@ Vue.component("http-remote-addr-config-box", {
options: [ options: [
{ {
name: "直接获取", name: "直接获取",
description: "用户直接访问边缘节点,即 \"用户 --> 边缘节点\" 模式,这时候可以直接连接中读取到真实的IP地址。", description: "用户直接访问边缘节点,即 \"用户 --> 边缘节点\" 模式,这时候系统会试图从直接连接中读取到客户端IP地址。",
value: "${rawRemoteAddr}" value: "${rawRemoteAddr}",
type: "default"
}, },
{ {
name: "从上级代理中获取", name: "从上级代理中获取",
description: "用户和边缘节点之间有别的代理服务转发,即 \"用户 --> [第三方代理服务] --> 边缘节点\"这时候只能从上级代理中获取传递的IP地址。", description: "用户和边缘节点之间有别的代理服务转发,即 \"用户 --> [第三方代理服务] --> 边缘节点\"这时候只能从上级代理中获取传递的IP地址;上级代理传递的请求报头中必须包含 X-Forwarded-For 或 X-Real-IP 信息。",
value: "${remoteAddr}" value: "${remoteAddr}",
type: "proxy"
}, },
{ {
name: "[自定义]", name: "从请求报头中读取",
description: "从自定义请求报头读取客户端IP。",
value: "",
type: "requestHeader"
},
{
name: "[自定义变量]",
description: "通过自定义变量来获取客户端真实的IP地址。", 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: { methods: {
isOn: function () { isOn: function () {
return ((!this.vIsLocation && !this.vIsGroup) || this.config.isPrior) && this.config.isOn return ((!this.vIsLocation && !this.vIsGroup) || this.config.isPrior) && this.config.isOn
}, },
changeOptionValue: function () { changeOptionType: function () {
if (this.optionValue.length > 0) { let that = this
this.config.value = this.optionValue
this.config.isCustomized = false switch(this.config.type) {
} else { case "default":
this.config.isCustomized = true 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"/> <input type="checkbox" value="1" v-model="config.isOn"/>
<label></label> <label></label>
</div> </div>
<p class="comment">选中后表示使用自定义的请求变量获取客户端IP。</p> <p class="comment">选中后表示使用自定义的请求变量获取客户端IP。</p>
</td> </td>
</tr> </tr>
</tbody> </tbody>
@@ -12492,20 +12552,28 @@ Vue.component("http-remote-addr-config-box", {
<tr> <tr>
<td>获取IP方式 *</td> <td>获取IP方式 *</td>
<td> <td>
<select class="ui dropdown auto-width" v-model="optionValue" @change="changeOptionValue"> <select class="ui dropdown auto-width" v-model="config.type" @change="changeOptionType">
<option v-for="option in options" :value="option.value">{{option.name}}</option> <option v-for="option in options" :value="option.type">{{option.name}}</option>
</select> </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> </td>
</tr> </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>读取IP变量值 *</td>
<td> <td>
<input type="hidden" v-model="config.value" maxlength="100"/> <input type="text" name="value" v-model="config.value" maxlength="100" ref="variableInput"/>
<div v-if="optionValue == ''" style="margin-top: 1em"> <p class="comment">通过此变量获取用户的IP地址。具体可用的请求变量列表可参考官方网站文档比如通过报头传递IP的情形可以使用<code-label>\${header.你的自定义报头}</code-label>(类似于<code-label>\${header.X-Forwarded-For}</code-label>,需要注意大小写规范)。</p>
<input type="text" v-model="config.value" maxlength="100"/>
<p class="comment">通过此变量获取用户的IP地址。具体可用的请求变量列表可参考官方网站文档。</p>
</div>
</td> </td>
</tr> </tr>
</tbody> </tbody>
@@ -12996,7 +13064,7 @@ Vue.component("http-webp-config-box", {
quality: 50, quality: 50,
minLength: {count: 0, "unit": "kb"}, minLength: {count: 0, "unit": "kb"},
maxLength: {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"], extensions: [".png", ".jpeg", ".jpg", ".bmp", ".ico"],
conds: null conds: null
} }
@@ -13062,7 +13130,7 @@ Vue.component("http-webp-config-box", {
<input type="checkbox" value="1" v-model="config.isOn"/> <input type="checkbox" value="1" v-model="config.isOn"/>
<label></label> <label></label>
</div> </div>
<p class="comment">选中后表示开启自动WebP压缩<span v-if="vRequireCache">;只有满足缓存条件的图片内容才会被转换</span>。</p> <p class="comment">选中后表示开启自动WebP压缩图片的宽和高均不能超过16383像素<span v-if="vRequireCache">;只有满足缓存条件的图片内容才会被转换</span>。</p>
</td> </td>
</tr> </tr>
</tbody> </tbody>
@@ -13296,6 +13364,44 @@ Vue.component("http-oss-bucket-params", {
</tbody>` </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", { Vue.component("http-request-cond-view", {
props: ["v-cond"], props: ["v-cond"],
data: function () { data: function () {
@@ -14923,7 +15029,8 @@ Vue.component("http-firewall-captcha-options", {
return { return {
options: options, options: options,
isEditing: false, isEditing: false,
summary: "" summary: "",
uiBodyWarning: ""
} }
}, },
watch: { watch: {
@@ -14967,6 +15074,13 @@ Vue.component("http-firewall-captcha-options", {
}, },
"options.uiIsOn": function (v) { "options.uiIsOn": function (v) {
this.updateSummary() this.updateSummary()
},
"options.uiBody": function (v) {
if (/<form(>|\s).+\$\{body}.*<\/form>/s.test(v)) {
this.uiBodyWarning = "页面模板中不能使用<form></form>标签包裹\${body}变量,否则将导致验证码表单无法提交。"
} else {
this.uiBodyWarning = ""
}
} }
}, },
methods: { methods: {
@@ -15101,7 +15215,7 @@ Vue.component("http-firewall-captcha-options", {
<td class="color-border">页面模板</td> <td class="color-border">页面模板</td>
<td> <td>
<textarea spellcheck="false" rows="2" v-model="options.uiBody"></textarea> <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> </td>
</tr> </tr>
</tbody> </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> <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-if="item.ipTo.length > 0"> - <keyword :v-word="keyword">{{item.ipTo}}</keyword></span></span>
<span v-else class="disabled">*</span> <span v-else class="disabled">*</span>
<div v-if="item.region != null && item.region.length > 0"> <div v-if="item.region != null && item.region.length > 0">
<span class="grey small">{{item.region}}</span> <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> <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>
<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"> <div v-if="item.createdTime != null">
<span class="small grey">添加于 {{item.createdTime}} <span class="small grey">添加于 {{item.createdTime}}
<span v-if="item.list != null && item.list.id > 0"> <span v-if="item.list != null && item.list.id > 0">
@@ -17799,7 +17916,7 @@ Vue.component("dot", {
}) })
Vue.component("time-duration-box", { 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 () { mounted: function () {
this.change() this.change()
}, },
@@ -17815,17 +17932,52 @@ Vue.component("time-duration-box", {
v["count"] = -1 v["count"] = -1
} }
let realName = "" let minUnit = this.vMinUnit
if (typeof this.name == "string" && this.name.length > 0) { let units = [
realName = this.name {
} else if (typeof this.vName == "string" && this.vName.length > 0) { code: "ms",
realName = this.vName 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 { return {
duration: v, duration: v,
countString: (v.count >= 0) ? v.count.toString() : "", countString: (v.count >= 0) ? v.count.toString() : "",
realName: realName units: units,
realMaxLength: maxLength
} }
}, },
watch: { watch: {
@@ -17848,23 +18000,41 @@ Vue.component("time-duration-box", {
} }
}, },
template: `<div class="ui fields inline" style="padding-bottom: 0; margin-bottom: 0"> 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"> <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>
<div class="ui field"> <div class="ui field">
<select class="ui dropdown" v-model="duration.unit" @change="change"> <select class="ui dropdown" v-model="duration.unit" @change="change">
<option value="ms">毫秒</option> <option v-for="unit in units" :value="unit.code">{{unit.name}}</option>
<option value="second">秒</option>
<option value="minute">分钟</option>
<option value="hour">小时</option>
<option value="day">天</option>
<option value="week">周</option>
</select> </select>
</div> </div>
</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", { Vue.component("not-found-box", {
props: ["message"], props: ["message"],
template: `<div style="text-align: center; margin-top: 5em;"> 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", { Vue.component("keyword", {
@@ -20481,6 +20651,9 @@ Vue.component("dns-route-selector", {
this.routes.$removeIf(function (k, v) { this.routes.$removeIf(function (k, v) {
return v.code + "@" + v.domainId == route.code + "@" + route.domainId return v.code + "@" + v.domainId == route.code + "@" + route.domainId
}) })
},
clearKeyword: function () {
this.keyword = ""
} }
}, },
watch: { watch: {
@@ -20514,16 +20687,22 @@ Vue.component("dns-route-selector", {
<tr> <tr>
<td class="title">所有线路</td> <td class="title">所有线路</td>
<td> <td>
<select class="ui dropdown auto-width" v-model="routeCode"> <span v-if="keyword.length > 0 && searchingRoutes.length == 0">没有和关键词“{{keyword}}”匹配的线路</span>
<option value="" v-if="keyword.length == 0">[请选择]</option> <span v-show="keyword.length == 0 || searchingRoutes.length > 0">
<option v-for="route in searchingRoutes" :value="route.code + '@' + route.domainId">{{route.name}}{{route.code}}/{{route.domainName}}</option> <select class="ui dropdown" v-model="routeCode">
</select> <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> </td>
</tr> </tr>
<tr> <tr>
<td>搜索</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> </td>
</tr> </tr>
</table> </table>
@@ -20987,7 +21166,7 @@ Vue.component("grant-selector", {
</div>` </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"}] 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", { 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 () { mounted: function () {
this.change() this.change()
}, },
@@ -15,17 +15,52 @@ Vue.component("time-duration-box", {
v["count"] = -1 v["count"] = -1
} }
let realName = "" let minUnit = this.vMinUnit
if (typeof this.name == "string" && this.name.length > 0) { let units = [
realName = this.name {
} else if (typeof this.vName == "string" && this.vName.length > 0) { code: "ms",
realName = this.vName 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 { return {
duration: v, duration: v,
countString: (v.count >= 0) ? v.count.toString() : "", countString: (v.count >= 0) ? v.count.toString() : "",
realName: realName units: units,
realMaxLength: maxLength
} }
}, },
watch: { watch: {
@@ -48,19 +83,37 @@ Vue.component("time-duration-box", {
} }
}, },
template: `<div class="ui fields inline" style="padding-bottom: 0; margin-bottom: 0"> 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"> <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>
<div class="ui field"> <div class="ui field">
<select class="ui dropdown" v-model="duration.unit" @change="change"> <select class="ui dropdown" v-model="duration.unit" @change="change">
<option value="ms">毫秒</option> <option v-for="unit in units" :value="unit.code">{{unit.name}}</option>
<option value="second">秒</option>
<option value="minute">分钟</option>
<option value="hour">小时</option>
<option value="day">天</option>
<option value="week">周</option>
</select> </select>
</div> </div>
</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) { this.routes.$removeIf(function (k, v) {
return v.code + "@" + v.domainId == route.code + "@" + route.domainId return v.code + "@" + v.domainId == route.code + "@" + route.domainId
}) })
},
clearKeyword: function () {
this.keyword = ""
} }
}, },
watch: { watch: {
@@ -103,16 +106,22 @@ Vue.component("dns-route-selector", {
<tr> <tr>
<td class="title">所有线路</td> <td class="title">所有线路</td>
<td> <td>
<select class="ui dropdown auto-width" v-model="routeCode"> <span v-if="keyword.length > 0 && searchingRoutes.length == 0">没有和关键词“{{keyword}}”匹配的线路</span>
<option value="" v-if="keyword.length == 0">[请选择]</option> <span v-show="keyword.length == 0 || searchingRoutes.length > 0">
<option v-for="route in searchingRoutes" :value="route.code + '@' + route.domainId">{{route.name}}{{route.code}}/{{route.domainName}}</option> <select class="ui dropdown" v-model="routeCode">
</select> <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> </td>
</tr> </tr>
<tr> <tr>
<td>搜索</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> </td>
</tr> </tr>
</table> </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> <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-if="item.ipTo.length > 0"> - <keyword :v-word="keyword">{{item.ipTo}}</keyword></span></span>
<span v-else class="disabled">*</span> <span v-else class="disabled">*</span>
<div v-if="item.region != null && item.region.length > 0"> <div v-if="item.region != null && item.region.length > 0">
<span class="grey small">{{item.region}}</span> <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> <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>
<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"> <div v-if="item.createdTime != null">
<span class="small grey">添加于 {{item.createdTime}} <span class="small grey">添加于 {{item.createdTime}}
<span v-if="item.list != null && item.list.id > 0"> <span v-if="item.list != null && item.list.id > 0">

View File

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

View File

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

View File

@@ -30,7 +30,8 @@ Vue.component("http-firewall-captcha-options", {
return { return {
options: options, options: options,
isEditing: false, isEditing: false,
summary: "" summary: "",
uiBodyWarning: ""
} }
}, },
watch: { watch: {
@@ -74,6 +75,13 @@ Vue.component("http-firewall-captcha-options", {
}, },
"options.uiIsOn": function (v) { "options.uiIsOn": function (v) {
this.updateSummary() this.updateSummary()
},
"options.uiBody": function (v) {
if (/<form(>|\s).+\$\{body}.*<\/form>/s.test(v)) {
this.uiBodyWarning = "页面模板中不能使用<form></form>标签包裹\${body}变量,否则将导致验证码表单无法提交。"
} else {
this.uiBodyWarning = ""
}
} }
}, },
methods: { methods: {
@@ -208,7 +216,7 @@ Vue.component("http-firewall-captcha-options", {
<td class="color-border">页面模板</td> <td class="color-border">页面模板</td>
<td> <td>
<textarea spellcheck="false" rows="2" v-model="options.uiBody"></textarea> <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> </td>
</tr> </tr>
</tbody> </tbody>

View File

@@ -53,7 +53,7 @@ Vue.component("http-firewall-config-box", {
</td> </td>
</tr> </tr>
</tbody> </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"> <tbody v-show="moreOptionsVisible">
<tr> <tr>
<td>启用系统全局规则</td> <td>启用系统全局规则</td>

View File

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

View File

@@ -7,13 +7,35 @@ Vue.component("http-remote-addr-config-box", {
isPrior: false, isPrior: false,
isOn: false, isOn: false,
value: "${rawRemoteAddr}", value: "${rawRemoteAddr}",
isCustomized: false type: "default",
requestHeaderName: ""
} }
} }
let optionValue = "" // type
if (!config.isCustomized && (config.value == "${remoteAddr}" || config.value == "${rawRemoteAddr}")) { if (config.type == null || config.type.length == 0) {
optionValue = config.value 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 { return {
@@ -21,33 +43,70 @@ Vue.component("http-remote-addr-config-box", {
options: [ options: [
{ {
name: "直接获取", name: "直接获取",
description: "用户直接访问边缘节点,即 \"用户 --> 边缘节点\" 模式,这时候可以直接连接中读取到真实的IP地址。", description: "用户直接访问边缘节点,即 \"用户 --> 边缘节点\" 模式,这时候系统会试图从直接连接中读取到客户端IP地址。",
value: "${rawRemoteAddr}" value: "${rawRemoteAddr}",
type: "default"
}, },
{ {
name: "从上级代理中获取", name: "从上级代理中获取",
description: "用户和边缘节点之间有别的代理服务转发,即 \"用户 --> [第三方代理服务] --> 边缘节点\"这时候只能从上级代理中获取传递的IP地址。", description: "用户和边缘节点之间有别的代理服务转发,即 \"用户 --> [第三方代理服务] --> 边缘节点\"这时候只能从上级代理中获取传递的IP地址;上级代理传递的请求报头中必须包含 X-Forwarded-For 或 X-Real-IP 信息。",
value: "${remoteAddr}" value: "${remoteAddr}",
type: "proxy"
}, },
{ {
name: "[自定义]", name: "从请求报头中读取",
description: "从自定义请求报头读取客户端IP。",
value: "",
type: "requestHeader"
},
{
name: "[自定义变量]",
description: "通过自定义变量来获取客户端真实的IP地址。", 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: { methods: {
isOn: function () { isOn: function () {
return ((!this.vIsLocation && !this.vIsGroup) || this.config.isPrior) && this.config.isOn return ((!this.vIsLocation && !this.vIsGroup) || this.config.isPrior) && this.config.isOn
}, },
changeOptionValue: function () { changeOptionType: function () {
if (this.optionValue.length > 0) { let that = this
this.config.value = this.optionValue
this.config.isCustomized = false switch(this.config.type) {
} else { case "default":
this.config.isCustomized = true 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"/> <input type="checkbox" value="1" v-model="config.isOn"/>
<label></label> <label></label>
</div> </div>
<p class="comment">选中后表示使用自定义的请求变量获取客户端IP。</p> <p class="comment">选中后表示使用自定义的请求变量获取客户端IP。</p>
</td> </td>
</tr> </tr>
</tbody> </tbody>
@@ -71,20 +130,28 @@ Vue.component("http-remote-addr-config-box", {
<tr> <tr>
<td>获取IP方式 *</td> <td>获取IP方式 *</td>
<td> <td>
<select class="ui dropdown auto-width" v-model="optionValue" @change="changeOptionValue"> <select class="ui dropdown auto-width" v-model="config.type" @change="changeOptionType">
<option v-for="option in options" :value="option.value">{{option.name}}</option> <option v-for="option in options" :value="option.type">{{option.name}}</option>
</select> </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> </td>
</tr> </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>读取IP变量值 *</td>
<td> <td>
<input type="hidden" v-model="config.value" maxlength="100"/> <input type="text" name="value" v-model="config.value" maxlength="100" ref="variableInput"/>
<div v-if="optionValue == ''" style="margin-top: 1em"> <p class="comment">通过此变量获取用户的IP地址。具体可用的请求变量列表可参考官方网站文档比如通过报头传递IP的情形可以使用<code-label>\${header.你的自定义报头}</code-label>(类似于<code-label>\${header.X-Forwarded-For}</code-label>,需要注意大小写规范)。</p>
<input type="text" v-model="config.value" maxlength="100"/>
<p class="comment">通过此变量获取用户的IP地址。具体可用的请求变量列表可参考官方网站文档。</p>
</div>
</td> </td>
</tr> </tr>
</tbody> </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, quality: 50,
minLength: {count: 0, "unit": "kb"}, minLength: {count: 0, "unit": "kb"},
maxLength: {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"], extensions: [".png", ".jpeg", ".jpg", ".bmp", ".ico"],
conds: null conds: null
} }
@@ -75,7 +75,7 @@ Vue.component("http-webp-config-box", {
<input type="checkbox" value="1" v-model="config.isOn"/> <input type="checkbox" value="1" v-model="config.isOn"/>
<label></label> <label></label>
</div> </div>
<p class="comment">选中后表示开启自动WebP压缩<span v-if="vRequireCache">;只有满足缓存条件的图片内容才会被转换</span>。</p> <p class="comment">选中后表示开启自动WebP压缩图片的宽和高均不能超过16383像素<span v-if="vRequireCache">;只有满足缓存条件的图片内容才会被转换</span>。</p>
</td> </td>
</tr> </tr>
</tbody> </tbody>

View File

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

View File

@@ -60,6 +60,18 @@ Tea.context(function () {
.params({}) .params({})
.success(function (resp) { .success(function (resp) {
this.globalMessageBadge = resp.data.count 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 () { .done(function () {
let delay = 6000 let delay = 6000

View File

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

View File

@@ -1 +1 @@
{"version":3,"sources":["@layout_popup.less"],"names":[],"mappings":";AACA;EACC,+CAAA;EACA,qBAAA;;AAGD;EACC,WAAA;;AAGD;EACC,aAAA;;AAGD;EACC,qBAAA;;AAGD,CAAC;AAAW,CAAC,SAAS;AAAQ,CAAC,SAAS;AAAS,IAAI;EACpD,WAAA;;AAGD,CAAC;AAAU,IAAI;AAAU,IAAI;EAC5B,cAAA;;AAGD,IAAI;AAAO,KAAK;AAAO,CAAC;EACvB,sBAAA;;AAGD,CAAC;EACA,iBAAA;;AAGD,IAAI;AAAM,GAAG;EACZ,cAAA;;AAGD,IAAI;EACH,cAAA;;AAGD,GAAG,IAAI;EACN,mBAAmB,8CAAnB;;AAGD;EACC,uBAAA;;AAGD,MAAM;EACL,sBAAA;;AAGD,MAAM;EACL,sBAAA;;AAGD,MAAM;EACL,sBAAA;;AAGD,MAAO;AAAI,MAAO;EACjB,gBAAA;;AAGD,CAAC;AAAU,GAAG;EACb,cAAA;EACA,kBAAA;EACA,mBAAA;EACA,qBAAA;;AAGD,CAAC,QAAS;AAAI,GAAG,QAAS;EACzB,6BAAA;;AAGD;EACC,mBAAA;EACA,2BAAA;EACA,gBAAA;EACA,uBAAA;;AAGD,GAAG;AAAS,CAAC;EACZ,eAAA;;;AAID,GAAG;EACF,UAAA;;AAGD,GAAG;EACF,YAAA;;AAGD,GAAG;EACF,UAAA;;AAGD,GAAG;EACF,WAAA;;;AAID,MAAM;EACL,cAAA;;;AAID;EACC,kBAAA;EACA,UAAA;EACA,UAAA;EACA,mBAAA;EACA,kBAAA;EACA,UAAA;;AAGD,mBAAqC;EACpC;IACC,SAAA;;;AAIF,KAAK;EACJ,SAAA;;AAGD,KAAK;EACJ,UAAA;;AAGD,mBAAqC;EACpC,KAAK;IACJ,SAAA;;;AAIF,KAAM,MAAM,GAAE;EACb,WAAA;;AAGD,KAAM,MAAM,GAAE;EACb,WAAA;;AAGD,KAAM,MAAM;EACX,mBAAA;;AAGD,KAAM,GAAE;EACP,8BAAA;;AAGD,KAAM,MAAM,GAAE;EACb,mBAAA;;AAGD,KAAM,MAAM,GAAE;EACb,sBAAA;;AAGD,KAAM,MAAM,GAAE,aAAc;EAC3B,mBAAA;;AAGD,KAAM,MAAM,GAAG;EACd,mBAAA;EACA,kBAAA;EACA,gBAAA;;AAGD,KAAM;EACL,mBAAA;EACA,iBAAA;;AAGD,KAAM,GAAG;EACR,gBAAA;;AAGD,KAAM,GAAG,KAAI;EACZ,cAAA;;AAGD,KAAM,GAAG;EACR,gBAAA;EACA,0BAAA;EACA,UAAA;;AAGD,KAAM,GAAG,EAAC;EACT,SAAS,GAAT;;AAGD,KAAM,GAAG,EAAC;EACT,SAAS,GAAT;;AAGD,KAAM;EACL,mBAAA;;AAGD,KAAM,GAAG,KAAI;EACZ,gBAAA;;AAGD,KAAM,QAAO;EACZ,gBAAA;EACA,cAAA;EACA,gBAAA;;;AAID,KAAK;EACJ,gBAAA;;AAGD,KAAK,KAAK;EACT,UAAA;EACA,WAAA;;;AAID;EACC,wBAAA;;;AAID,iBAAkB;EACjB,gBAAA;;AAGD,iBAAkB,MAAK;EACtB,UAAA;;AAGD,iBAAkB,MAAM;EACvB,2BAAA;;AAGD,MAAM;EACL,sBAAA;;;AAID,mBAAqC;EACpC,OAAO,IAAI;IACV,sBAAA;;;;AAKF,KAAK;EACJ,0BAAA;;AAGD,KAAK;EACJ,cAAA;;;AAOD,WAAY,MAAK;EAChB,wBAAA;EACA,2BAAA;;AAGD,WAAY;EACX,wBAAA;EACA,2BAAA;;AAGD,YAAa,MAAK;EACjB,wBAAA;EACA,2BAAA;;AAGD,YAAa,MAAK,KAAM;EACvB,kBAAA;;AAGD,YAAa;EACZ,wBAAA;;AAGD,KAAM;EACL,aAAA;;;AAID,IAAI;AAAQ,GAAG;EACd,cAAA;;AAGD,IAAI;EACH,8BAAA;;;AAID,QAAS;EACR,WAAA;EACA,kBAAA;;;AAID,SAAU,MAAM;AAAG,SAAU;EAC5B,gBAAA;;;AAID;EACC,eAAA;EAEA,2BAAA;;AAHD,KAKC;EACC,qBAAA;EACA,mBAAA;EACA,WAAA;EACA,iBAAA;EACA,SAAA;EACA,gBAAA;EACA,sBAAA;EACA,cAAA;;AAbF,KAgBC,EAAC;EACA,mBAAA;EACA,YAAA;;AAlBF,KAqBC,EAAC;EACA,gBAAA;;AAtBF,KAyBC;EACC,kBAAA;EACA,qBAAA;;AAKF;EACC,kBAAA;;AAGD,cAAc;AAAQ,aAAa;AAAQ,YAAY;EACtD,sBAAA;;AAGD;AAAgB;AAAe;EAC9B,sBAAA;;AAGD;EACC,2BAAA;;AAID,KAAK;EACJ,yBAAA;;AAID,QAAQ;EACP,4BAA4B,wBAA5B;EACA,gBAAA;;AAID,UAAW;EACV,gBAAA;EACA,gBAAA;EACA,kBAAA;EACA,2CAAA;EACA,aAAA;EACA,YAAA;;AAGD,UAAW,MAAK;EACf,UAAA;;AAID;EACC,gBAAA;EACA,wCAAA;EACA,0BAAA;EACA,wBAAA;EACA,YAAA;EACA,gBAAA;EACA,iBAAA;EACA,cAAA;EACA,qBAAA;EACA,gBAAA;EACA,wBAAA","file":"@layout_popup.css"} {"version":3,"sources":["@layout_popup.less"],"names":[],"mappings":";AACA;EACC,+CAAA;EACA,qBAAA;;AAGD;EACC,WAAA;;AAGD;EACC,aAAA;;AAGD;EACC,qBAAA;;AAGD,CAAC;AAAW,CAAC,SAAS;AAAQ,CAAC,SAAS;AAAS,IAAI;EACpD,WAAA;;AAGD,CAAC;AAAU,IAAI;AAAU,IAAI;EAC5B,cAAA;;AAGD,IAAI;AAAO,KAAK;AAAO,CAAC;EACvB,sBAAA;;AAGD,CAAC;EACA,iBAAA;;AAGD,IAAI;AAAM,GAAG;EACZ,cAAA;;AAGD,IAAI;EACH,cAAA;;AAGD,GAAG,IAAI;EACN,mBAAmB,8CAAnB;;AAGD;EACC,uBAAA;;AAGD,MAAM;EACL,sBAAA;;AAGD,MAAM;EACL,sBAAA;;AAGD,MAAM;EACL,sBAAA;;AAGD,MAAO;AAAI,MAAO;EACjB,gBAAA;;AAGD,CAAC;AAAU,GAAG;EACb,cAAA;EACA,kBAAA;EACA,mBAAA;EACA,qBAAA;EACA,gBAAA;;AAGD,CAAC,QAAS;AAAI,GAAG,QAAS;EACzB,6BAAA;;AAGD;EACC,mBAAA;EACA,2BAAA;EACA,gBAAA;EACA,uBAAA;;AAGD,GAAG;AAAS,CAAC;EACZ,eAAA;;;AAID,GAAG;EACF,UAAA;;AAGD,GAAG;EACF,YAAA;;AAGD,GAAG;EACF,UAAA;;AAGD,GAAG;EACF,WAAA;;;AAID,MAAM;EACL,cAAA;;;AAID;EACC,kBAAA;EACA,UAAA;EACA,UAAA;EACA,mBAAA;EACA,kBAAA;EACA,UAAA;;AAGD,mBAAqC;EACpC;IACC,SAAA;;;AAIF,KAAK;EACJ,SAAA;;AAGD,KAAK;EACJ,UAAA;;AAGD,mBAAqC;EACpC,KAAK;IACJ,SAAA;;;AAIF,KAAM,MAAM,GAAE;EACb,WAAA;;AAGD,KAAM,MAAM,GAAE;EACb,WAAA;;AAGD,KAAM,MAAM;EACX,mBAAA;;AAGD,KAAM,GAAE;EACP,8BAAA;;AAGD,KAAM,MAAM,GAAE;EACb,mBAAA;;AAGD,KAAM,MAAM,GAAE;EACb,sBAAA;;AAGD,KAAM,MAAM,GAAE,aAAc;EAC3B,mBAAA;;AAGD,KAAM,MAAM,GAAG;EACd,mBAAA;EACA,kBAAA;EACA,gBAAA;;AAGD,KAAM;EACL,mBAAA;EACA,iBAAA;;AAGD,KAAM,GAAG;EACR,gBAAA;;AAGD,KAAM,GAAG,KAAI;EACZ,cAAA;;AAGD,KAAM,GAAG;EACR,gBAAA;EACA,0BAAA;EACA,UAAA;;AAGD,KAAM,GAAG,EAAC;EACT,SAAS,GAAT;;AAGD,KAAM,GAAG,EAAC;EACT,SAAS,GAAT;;AAGD,KAAM;EACL,mBAAA;;AAGD,KAAM,GAAG,KAAI;EACZ,gBAAA;;AAGD,KAAM,QAAO;EACZ,gBAAA;EACA,cAAA;EACA,gBAAA;;;AAID,KAAK;EACJ,gBAAA;;AAGD,KAAK,KAAK;EACT,UAAA;EACA,WAAA;;;AAID;EACC,wBAAA;;;AAID,iBAAkB;EACjB,gBAAA;;AAGD,iBAAkB,MAAK;EACtB,UAAA;;AAGD,iBAAkB,MAAM;EACvB,2BAAA;;AAGD,MAAM;EACL,sBAAA;;;AAID,mBAAqC;EACpC,OAAO,IAAI;IACV,sBAAA;;;;AAKF,KAAK;EACJ,0BAAA;;AAGD,KAAK;EACJ,cAAA;;;AAOD,WAAY,MAAK;EAChB,wBAAA;EACA,2BAAA;;AAGD,WAAY;EACX,wBAAA;EACA,2BAAA;;AAGD,YAAa,MAAK;EACjB,wBAAA;EACA,2BAAA;;AAGD,YAAa,MAAK,KAAM;EACvB,kBAAA;;AAGD,YAAa;EACZ,wBAAA;;AAGD,KAAM;EACL,aAAA;;;AAID,IAAI;AAAQ,GAAG;EACd,cAAA;;AAGD,IAAI;EACH,8BAAA;;;AAID,QAAS;EACR,WAAA;EACA,kBAAA;;;AAID,SAAU,MAAM;AAAG,SAAU;EAC5B,gBAAA;;;AAID;EACC,eAAA;EAEA,2BAAA;;AAHD,KAKC;EACC,qBAAA;EACA,mBAAA;EACA,WAAA;EACA,iBAAA;EACA,SAAA;EACA,gBAAA;EACA,sBAAA;EACA,cAAA;;AAbF,KAgBC,EAAC;EACA,mBAAA;EACA,YAAA;;AAlBF,KAqBC,EAAC;EACA,gBAAA;;AAtBF,KAyBC;EACC,kBAAA;EACA,qBAAA;;AAKF;EACC,kBAAA;;AAGD,cAAc;AAAQ,aAAa;AAAQ,YAAY;EACtD,sBAAA;;AAGD;AAAgB;AAAe;EAC9B,sBAAA;;AAGD;EACC,2BAAA;;AAID,KAAK;EACJ,yBAAA;;AAID,QAAQ;EACP,4BAA4B,wBAA5B;EACA,gBAAA;;AAID,UAAW;EACV,gBAAA;EACA,gBAAA;EACA,kBAAA;EACA,2CAAA;EACA,aAAA;EACA,YAAA;;AAGD,UAAW,MAAK;EACf,UAAA;;AAID;EACC,gBAAA;EACA,wCAAA;EACA,0BAAA;EACA,wBAAA;EACA,YAAA;EACA,gBAAA;EACA,iBAAA;EACA,cAAA;EACA,qBAAA;EACA,gBAAA;EACA,wBAAA","file":"@layout_popup.css"}

View File

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

View File

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

View File

@@ -14,23 +14,35 @@
<td class="title color-border">禁止未绑定域名访问</td> <td class="title color-border">禁止未绑定域名访问</td>
<td> <td>
<checkbox name="httpAllMatchDomainStrictly" v-model="config.httpAll.matchDomainStrictly"></checkbox> <checkbox name="httpAllMatchDomainStrictly" v-model="config.httpAll.matchDomainStrictly"></checkbox>
<p class="comment">选中后表示禁止未绑定的域名和IP访问。</p> <p class="comment">选中后,表示禁止未在网站绑定的域名和IP访问。</p>
</td> </td>
</tr> </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 class="color-border">提示页面状态码</td>
<td> <td>
<input type="text" name="httpAllDomainMismatchActionStatusCode" v-model="httpAllDomainMismatchActionStatusCode" style="width: 4em" maxlength="3"/> <input type="text" name="httpAllDomainMismatchActionStatusCode" v-model="httpAllDomainMismatchActionStatusCode" style="width: 4em" maxlength="3"/>
<p class="comment">默认404。</p> <p class="comment">访问未绑定域名时的提示页面状态码,默认404。</p>
</td> </td>
</tr> </tr>
<tr v-show="config.httpAll.matchDomainStrictly"> <tr v-show="config.httpAll.matchDomainStrictly && httpAllDomainMismatchActionCode == 'page'">
<td class="color-border">未绑定域名页面提示</td> <td class="color-border">提示页面内容</td>
<td> <td>
<textarea rows="3" name="httpAllDomainMismatchActionContentHTML" v-model="httpAllDomainMismatchActionContentHTML"></textarea> <textarea rows="3" name="httpAllDomainMismatchActionContentHTML" v-model="httpAllDomainMismatchActionContentHTML"></textarea>
<p class="comment">访问未绑定的域名时提示的文字可以使用HTML仅限于HTTP请求不适于用HTTPSHTTPS会提示证书错误</p> <p class="comment">访问未绑定的域名时提示页面内容可以使用HTML仅限于HTTP请求不适于用HTTPSHTTPS会提示证书错误</p>
</td> </td>
</tr> </tr>
<tr v-show="config.httpAll.matchDomainStrictly"> <tr v-show="config.httpAll.matchDomainStrictly">
<td class="color-border">允许例外的域名</td> <td class="color-border">允许例外的域名</td>
<td> <td>
@@ -181,6 +193,24 @@
<p class="comment">选中后所有请求优先发送到L2或者更高级别节点。默认不开启的情况下只有可以缓存的内容请求才会发送到L2或者更高级别节点。</p> <p class="comment">选中后所有请求优先发送到L2或者更高级别节点。默认不开启的情况下只有可以缓存的内容请求才会发送到L2或者更高级别节点。</p>
</td> </td>
</tr> </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> </table>
<submit-btn></submit-btn> <submit-btn></submit-btn>

View File

@@ -34,6 +34,7 @@
<span v-if="task.type == 'nodeLevelChanged'">同步L2节点</span> <span v-if="task.type == 'nodeLevelChanged'">同步L2节点</span>
<span v-if="task.type == 'ddosProtectionChanged'">DDoS配置</span> <span v-if="task.type == 'ddosProtectionChanged'">DDoS配置</span>
<span v-if="task.type == 'userServersStateChanged'">用户网站状态</span> <span v-if="task.type == 'userServersStateChanged'">用户网站状态</span>
<span v-if="task.type != null && task.type.startsWith('ipListDeleted')">删除IP名单</span>
</td> </td>
<td> <td>
<span v-if="task.isDone" class="red">{{task.error}}</span> <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"> <div class="ui icon message error" v-if="!isLoading && nodeUpgradeInfo.count > 0">
<i class="icon warning circle"></i> <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> </div>
<!-- API节点升级提醒 --> <!-- API节点升级提醒 -->

View File

@@ -59,7 +59,7 @@
<tr v-if="protocol == 'https' || protocol == 'tls'"> <tr v-if="protocol == 'https' || protocol == 'tls'">
<td>{{protocol.toUpperCase()}}证书</td> <td>{{protocol.toUpperCase()}}证书</td>
<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> </td>
</tr> </tr>
<tr v-if="isHTTP"> <tr v-if="isHTTP">

View File

@@ -9,14 +9,14 @@
<td class="title">单个域名 *</td> <td class="title">单个域名 *</td>
<td> <td>
<input type="text" name="serverName" ref="focus" maxlength="1024" v-model="serverName.name"/> <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> </td>
</tr> </tr>
<tr v-if="mode == 'multiple'"> <tr v-if="mode == 'multiple'">
<td class="title">多个域名 *</td> <td class="title">多个域名 *</td>
<td> <td>
<textarea name="serverNames" ref="serverNames" v-model="multipleServerNames"></textarea> <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> </td>
</tr> </tr>
</table> </table>

View File

@@ -42,7 +42,7 @@
<tr v-show="reason == REASON_ISSUE_REPORT"> <tr v-show="reason == REASON_ISSUE_REPORT">
<td>操作建议</td> <td>操作建议</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> </td>
</tr> </tr>
<tr v-show="reason == REASON_BATCH_DELETE"> <tr v-show="reason == REASON_BATCH_DELETE">

View File

@@ -29,7 +29,7 @@
<td class="color-border">缓存磁盘最大用量 *</td> <td class="color-border">缓存磁盘最大用量 *</td>
<td> <td>
<size-capacity-box :v-name="'capacityJSON'" :v-count="128" :v-unit="'gb'" :key="'capacityJSON1'"></size-capacity-box> <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> </td>
</tr> </tr>
<tr> <tr>

View File

@@ -31,7 +31,7 @@
<td class="color-border">缓存磁盘最大用量</td> <td class="color-border">缓存磁盘最大用量</td>
<td> <td>
<size-capacity-box :v-name="'capacityJSON'" :v-value="cachePolicy.capacity" :v-count="0" :v-unit="'gb'"></size-capacity-box> <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> </td>
</tr> </tr>
<tr v-if="cachePolicy.options.memoryPolicy != null && cachePolicy.options.memoryPolicy.capacity != null"> <tr v-if="cachePolicy.options.memoryPolicy != null && cachePolicy.options.memoryPolicy.capacity != null">

View File

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

View File

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

View File

@@ -62,7 +62,7 @@
<tr v-if="protocol == 'https' || protocol == 'tls'"> <tr v-if="protocol == 'https' || protocol == 'tls'">
<td>{{protocol.toUpperCase()}}证书</td> <td>{{protocol.toUpperCase()}}证书</td>
<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> </td>
</tr> </tr>
<tr v-show="protocol != 'udp'"> <tr v-show="protocol != 'udp'">

View File

@@ -63,7 +63,7 @@
<tr v-if="origin.protocol == 'https' || origin.protocol == 'tls'"> <tr v-if="origin.protocol == 'https' || origin.protocol == 'tls'">
<td>{{origin.protocol.toUpperCase()}}证书</td> <td>{{origin.protocol.toUpperCase()}}证书</td>
<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> </td>
</tr> </tr>
<tr v-show="origin.protocol != 'udp'"> <tr v-show="origin.protocol != 'udp'">

View File

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

View File

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

View File

@@ -54,6 +54,20 @@
</tr> </tr>
</table> </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> <h4 style="margin-top: 0">网站流量统计</h4>
<table class="ui table definition selectable"> <table class="ui table definition selectable">
<tr> <tr>