Compare commits

...

48 Commits

Author SHA1 Message Date
刘祥超
276a68bda6 Update Dockerfile 2023-05-29 09:08:59 +08:00
刘祥超
fba89d197a 优化交互 2023-05-28 20:24:58 +08:00
刘祥超
b3259e2489 commit components.js 2023-05-28 19:22:05 +08:00
刘祥超
880704dda0 优化WAF“跳转‘动作显示 2023-05-28 17:22:20 +08:00
刘祥超
d5e92e9c09 WAF增加“跳转”动作 2023-05-28 17:11:45 +08:00
刘祥超
df2f5692bd 版本号改为1.1.0 2023-05-28 16:06:05 +08:00
刘祥超
dacc99b8b6 优化文字 2023-05-28 15:17:33 +08:00
刘祥超
0e8ade3b61 手动执行健康检查时提示用户当前集群尚未部署网站 2023-05-28 15:06:53 +08:00
刘祥超
fe2d8f6261 优化界面 2023-05-28 14:41:38 +08:00
刘祥超
927381039b 优化界面 2023-05-28 11:31:59 +08:00
刘祥超
6b95947acb 集群列表页也增加“创建节点”链接 2023-05-27 19:55:06 +08:00
刘祥超
b05e53ce58 登录界面屏蔽个别敏感区域 2023-05-27 18:12:40 +08:00
刘祥超
a2546582f2 优化界面 2023-05-27 17:11:18 +08:00
刘祥超
a023b3401e 优化界面 2023-05-27 17:05:17 +08:00
刘祥超
07142e9872 删除EdgePlus 2023-05-27 16:46:30 +08:00
刘祥超
f5fea6c34d 创建节点表单显示剩余配额 2023-05-27 15:33:08 +08:00
刘祥超
5eb0fb3422 正则表达式填写多行时提示用户需要转换成竖杠(|)符号 2023-05-27 14:44:39 +08:00
刘祥超
fa915e7c35 部分“服务”文字改为“网站” 2023-05-26 11:55:21 +08:00
刘祥超
2b7905d31c 优化文字和界面 2023-05-26 10:52:45 +08:00
刘祥超
9cbbf37add 优化界面 2023-05-26 10:31:03 +08:00
刘祥超
642a0c3b0d 优化界面 2023-05-25 17:42:22 +08:00
刘祥超
ff47a19250 优化界面 2023-05-25 17:29:55 +08:00
刘祥超
1fc1a3fbe3 WAF国家/地区封禁、省份封禁增加例外URL、限制URL 2023-05-25 12:02:19 +08:00
刘祥超
996172bd69 改进界面 2023-05-25 10:59:18 +08:00
刘祥超
df038314ef commit components.js 2023-05-24 17:21:05 +08:00
刘祥超
67881c2e34 网站全局设置中增加“自动匹配证书”选项 2023-05-24 17:20:28 +08:00
刘祥超
9c691a17b2 优化集群菜单 2023-05-23 19:50:19 +08:00
刘祥超
54df86a4a2 优化文字提示 2023-05-23 19:15:35 +08:00
刘祥超
70aff759d7 优化文字显示 2023-05-23 11:25:25 +08:00
刘祥超
d903cc6e3b 优化源站列表专属域名显示 2023-05-23 11:25:13 +08:00
刘祥超
c520271ab5 实现自定义页面组件 2023-05-22 17:30:46 +08:00
刘祥超
f6936224d9 调整集群设置菜单位置 2023-05-22 09:49:56 +08:00
刘祥超
283984a6a6 HTTP Header中支持设置非标Header 2023-05-19 19:52:10 +08:00
刘祥超
c4fc09f72a 优化HTTP Header页面 2023-05-19 17:40:00 +08:00
刘祥超
efdbabfa04 优化HTTP Header页面文字提示 2023-05-19 17:34:54 +08:00
刘祥超
a43af333fd 优化文字提示 2023-05-19 16:51:49 +08:00
刘祥超
1f5894ff82 HTTP Header - CORS跨域设置增加多个选项 2023-05-19 16:32:28 +08:00
刘祥超
79b4054e31 在节点列表显示租期、是否为备用节点等信息 2023-05-19 11:11:34 +08:00
刘祥超
376d7d0c78 修改部分代码的联系方式 2023-05-19 11:09:57 +08:00
刘祥超
c8b85330ed 删除QQ群信息 2023-05-17 19:22:56 +08:00
刘祥超
c65dffee13 实现基础的智能调度 2023-05-17 18:41:27 +08:00
刘祥超
275320ad9b 如果管理系统前端有反向代理,则不要自动从HTTP跳转到HTTPS 2023-05-07 09:28:18 +08:00
刘祥超
f71cdf9065 防盗链增加”同时检查Origin选项“ 2023-05-02 17:05:35 +08:00
刘祥超
8e0a239de9 优化WAF CC2规则说明文字 2023-05-02 17:04:40 +08:00
刘祥超
ed2b95bc3f 集群设置中“服务设置”改为“网站设置” 2023-04-26 14:04:49 +08:00
刘祥超
2e23ee4c66 优化健康检查界面 2023-04-26 10:48:44 +08:00
刘祥超
58def52e30 数据库手动清理页面增加按表名和按占用空间排序/优化数据库相关界面 2023-04-26 09:49:28 +08:00
刘祥超
aea5ef3b68 Update Dockerfile 2023-04-24 10:29:31 +08:00
112 changed files with 2794 additions and 407 deletions

View File

@@ -54,7 +54,7 @@
* [管理平台](https://github.com/TeaOSLab/EdgeAdmin)
## 联系我们
有什么问题和建议都可以加入QQ群 `659832182` 或者 [Telegram群](https://t.me/+5kVCMGxQhZxiODY9)
有什么问题和建议都可以加入 [Telegram群](https://t.me/+5kVCMGxQhZxiODY9)
## 企业版
* [GoEdge企业版](https://goedge.cn/commercial) - 功能更强大的CDN系统

View File

@@ -1 +1 @@
这个目录下我们列举了所有需要公开声明的第三方License如果有遗漏烦请告知 iwind.liu@gmail.com。再次感谢这些开源软件项目和贡献人员
这个目录下我们列举了所有需要公开声明的第三方License如果有遗漏烦请告知 goedge.cdn@gmail.com。再次感谢这些开源软件项目和贡献人员

View File

@@ -1,7 +1,7 @@
FROM alpine:latest
LABEL maintainer="iwind.liu@gmail.com"
LABEL maintainer="goedge.cdn@gmail.com"
ENV TZ "Asia/Shanghai"
ENV VERSION 1.0.1
ENV VERSION 1.1.0
ENV ROOT_DIR /usr/local/goedge
ENV TAR_FILE 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"

View File

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

View File

@@ -2,37 +2,49 @@ package actionutils
import (
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type TabItem struct {
Name string `json:"name"`
SubName string `json:"subName"`
URL string `json:"url"`
Icon string `json:"icon"`
IsActive bool `json:"isActive"`
IsRight bool `json:"isRight"`
IsTitle bool `json:"isTitle"`
IsDisabled bool `json:"isDisabled"`
}
// Tabbar Tabbar定义
type Tabbar struct {
items []maps.Map
items []*TabItem
}
// NewTabbar 获取新对象
func NewTabbar() *Tabbar {
return &Tabbar{
items: []maps.Map{},
items: []*TabItem{},
}
}
// Add 添加菜单项
func (this *Tabbar) Add(name string, subName string, url string, icon string, active bool) maps.Map {
m := maps.Map{
"name": name,
"subName": subName,
"url": url,
"icon": icon,
"active": active,
"right": false,
func (this *Tabbar) Add(name string, subName string, url string, icon string, active bool) *TabItem {
var m = &TabItem{
Name: name,
SubName: subName,
URL: url,
Icon: icon,
IsActive: active,
IsRight: false,
IsTitle: false,
IsDisabled: false,
}
this.items = append(this.items, m)
return m
}
// Items 取得所有的Items
func (this *Tabbar) Items() []maps.Map {
func (this *Tabbar) Items() []*TabItem {
return this.items
}

View File

@@ -39,6 +39,17 @@ func (this *CreateBatchAction) RunGet(params struct {
}
this.Data["leftMenuItems"] = leftMenuItems
// 限额
maxNodes, leftNodes, err := this.findNodesQuota()
if err != nil {
this.ErrorPage(err)
return
}
this.Data["quota"] = maps.Map{
"maxNodes": maxNodes,
"leftNodes": leftNodes,
}
this.Show()
}

View File

@@ -0,0 +1,8 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build !plus
package cluster
func (this *CreateBatchAction) findNodesQuota() (maxNodes int32, leftNodes int32, err error) {
return
}

View File

@@ -30,6 +30,11 @@ func (this *CreateNodeAction) Init() {
func (this *CreateNodeAction) RunGet(params struct {
ClusterId int64
}) {
if params.ClusterId <= 0 {
this.RedirectURL("/clusters")
return
}
var leftMenuItems = []maps.Map{
{
"name": "单个创建",
@@ -92,6 +97,17 @@ func (this *CreateNodeAction) RunGet(params struct {
// 安装文件下载
this.Data["installerFiles"] = clusterutils.ListInstallerFiles()
// 限额
maxNodes, leftNodes, err := this.findNodesQuota()
if err != nil {
this.ErrorPage(err)
return
}
this.Data["quota"] = maps.Map{
"maxNodes": maxNodes,
"leftNodes": leftNodes,
}
this.Show()
}

View File

@@ -0,0 +1,8 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build !plus
package cluster
func (this *CreateNodeAction) findNodesQuota() (maxNodes int32, leftNodes int32, err error) {
return
}

View File

@@ -187,6 +187,8 @@ func (this *DetailAction) RunGet(params struct {
"route": route.Name,
"value": addr.Ip,
"clusterName": cluster.Name,
"isBackup": dnsInfo.IsBackupForCluster || dnsInfo.IsBackupForGroup,
"isOffline": dnsInfo.IsOffline,
})
}
}
@@ -343,25 +345,29 @@ func (this *DetailAction) RunGet(params struct {
}
this.Data["node"] = maps.Map{
"id": node.Id,
"name": node.Name,
"ipAddresses": ipAddressMaps,
"cluster": clusterMap,
"secondaryClusters": secondaryClustersMaps,
"login": loginMap,
"installDir": node.InstallDir,
"isInstalled": node.IsInstalled,
"uniqueId": node.UniqueId,
"secret": node.Secret,
"maxCPU": node.MaxCPU,
"isOn": node.IsOn,
"records": recordMaps,
"routes": routeMaps,
"level": node.Level,
"levelInfo": nodeconfigs.FindNodeLevel(int(node.Level)),
"lnAddrs": lnAddrs,
"enableIPLists": node.EnableIPLists,
"apiNodeAddrs": apiNodeAddrStrings,
"id": node.Id,
"name": node.Name,
"ipAddresses": ipAddressMaps,
"cluster": clusterMap,
"secondaryClusters": secondaryClustersMaps,
"login": loginMap,
"installDir": node.InstallDir,
"isInstalled": node.IsInstalled,
"uniqueId": node.UniqueId,
"secret": node.Secret,
"maxCPU": node.MaxCPU,
"isOn": node.IsOn,
"records": recordMaps,
"routes": routeMaps,
"level": node.Level,
"levelInfo": nodeconfigs.FindNodeLevel(int(node.Level)),
"lnAddrs": lnAddrs,
"enableIPLists": node.EnableIPLists,
"apiNodeAddrs": apiNodeAddrStrings,
"offlineDay": node.OfflineDay,
"isOffline": len(node.OfflineDay) > 0 && node.OfflineDay < timeutil.Format("Ymd"),
"isBackupForCluster": node.IsBackupForCluster,
"isBackupForGroup": node.IsBackupForGroup,
"status": maps.Map{
"isActive": status.IsActive,

View File

@@ -209,6 +209,8 @@ func (this *NodesAction) RunGet(params struct {
"isInstalled": node.IsInstalled,
"isOn": node.IsOn,
"isUp": node.IsUp,
"isBackup": node.IsBackupForCluster || node.IsBackupForGroup,
"offlineDay": node.OfflineDay,
"installStatus": maps.Map{
"isRunning": node.InstallStatus.IsRunning,
"isFinished": node.InstallStatus.IsFinished,

View File

@@ -78,7 +78,8 @@ func (this *IndexAction) RunPost(params struct {
HttpAllAllowNodeIP bool
HttpAllDefaultDomain string
HttpAllSupportsLowVersionHTTP bool
HttpAllSupportsLowVersionHTTP bool
HttpAllMatchCertFromAllServers bool
HttpAccessLogEnableRequestHeaders bool
HttpAccessLogEnableResponseHeaders bool
@@ -138,6 +139,7 @@ func (this *IndexAction) RunPost(params struct {
// HTTP All
config.HTTPAll.SupportsLowVersionHTTP = params.HttpAllSupportsLowVersionHTTP
config.HTTPAll.MatchCertFromAllServers = params.HttpAllMatchCertFromAllServers
// 访问日志
config.HTTPAccessLog.EnableRequestHeaders = params.HttpAccessLogEnableRequestHeaders

View File

@@ -15,7 +15,17 @@ func (this *RunPopupAction) Init() {
this.Nav("", "", "")
}
func (this *RunPopupAction) RunGet(params struct{}) {
func (this *RunPopupAction) RunGet(params struct {
ClusterId int64
}) {
// 检查是否已部署服务
countServersResp, err := this.RPC().ServerRPC().CountAllEnabledServersWithNodeClusterId(this.AdminContext(), &pb.CountAllEnabledServersWithNodeClusterIdRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["hasServers"] = countServersResp.Count > 0
this.Show()
}

View File

@@ -29,9 +29,9 @@ func (this *ClusterHelper) BeforeAction(actionPtr actions.ActionWrapper) (goNext
action.Data["teaMenu"] = "clusters"
selectedTabbar := action.Data.GetString("mainTab")
clusterId := action.ParamInt64("clusterId")
clusterIdString := strconv.FormatInt(clusterId, 10)
var selectedTabbar = action.Data.GetString("mainTab")
var clusterId = action.ParamInt64("clusterId")
var clusterIdString = strconv.FormatInt(clusterId, 10)
action.Data["clusterId"] = clusterId
if clusterId > 0 {
@@ -57,14 +57,45 @@ func (this *ClusterHelper) BeforeAction(actionPtr actions.ActionWrapper) (goNext
return
}
var nodeId = action.ParamInt64("nodeId")
var isInCluster = nodeId <= 0
var tabbar = actionutils.NewTabbar()
tabbar.Add("集群列表", "", "/clusters", "", false)
if teaconst.IsPlus {
tabbar.Add("集群看板", "", "/clusters/cluster/boards?clusterId="+clusterIdString, "chart line area", selectedTabbar == "board")
{
var url = "/clusters"
if !isInCluster {
url = "/clusters/cluster/nodes?clusterId=" + clusterIdString
}
tabbar.Add("", "", url, "arrow left", false)
}
{
var url = "/clusters/cluster?clusterId=" + clusterIdString
if !isInCluster {
url = "/clusters/cluster/nodes?clusterId=" + clusterIdString
}
var item = tabbar.Add(cluster.Name, "", url, "angle right", true)
item.IsTitle = true
}
if teaconst.IsPlus {
{
var item = tabbar.Add("集群看板", "", "/clusters/cluster/boards?clusterId="+clusterIdString, "chart line area", selectedTabbar == "board")
item.IsDisabled = !isInCluster
}
}
{
var item = tabbar.Add("节点列表", "", "/clusters/cluster/nodes?clusterId="+clusterIdString, "server", selectedTabbar == "node")
item.IsDisabled = !isInCluster
}
{
var item = tabbar.Add("集群设置", "", "/clusters/cluster/settings?clusterId="+clusterIdString, "setting", selectedTabbar == "setting")
item.IsDisabled = !isInCluster
}
{
var item = tabbar.Add("删除集群", "", "/clusters/cluster/delete?clusterId="+clusterIdString, "trash", selectedTabbar == "delete")
item.IsDisabled = !isInCluster
}
tabbar.Add("集群节点", "", "/clusters/cluster/nodes?clusterId="+clusterIdString, "server", selectedTabbar == "node")
tabbar.Add("集群设置", "", "/clusters/cluster/settings?clusterId="+clusterIdString, "setting", selectedTabbar == "setting")
tabbar.Add("删除集群", "", "/clusters/cluster/delete?clusterId="+clusterIdString, "trash", selectedTabbar == "delete")
actionutils.SetTabbar(action, tabbar)
// 左侧菜单
@@ -97,14 +128,39 @@ func (this *ClusterHelper) createSettingMenu(cluster *pb.NodeCluster, info *pb.F
"isActive": selectedItem == "basic",
"isOn": true,
})
items = append(items, maps.Map{
"name": "缓存设置",
"name": "DNS设置",
"url": "/clusters/cluster/settings/dns?clusterId=" + clusterId,
"isActive": selectedItem == "dns",
"isOn": cluster.DnsDomainId > 0 || len(cluster.DnsName) > 0,
})
items = append(items, maps.Map{
"name": "健康检查",
"url": "/clusters/cluster/settings/health?clusterId=" + clusterId,
"isActive": selectedItem == "health",
"isOn": info != nil && info.HealthCheckIsOn,
})
items = append(items, maps.Map{
"name": "-",
})
items = append(items, maps.Map{
"name": "网站设置",
"url": "/clusters/cluster/settings/global-server-config?clusterId=" + clusterId,
"isActive": selectedItem == "globalServerConfig",
"isOn": true,
})
items = append(items, maps.Map{
"name": "缓存策略",
"url": "/clusters/cluster/settings/cache?clusterId=" + clusterId,
"isActive": selectedItem == "cache",
"isOn": cluster.HttpCachePolicyId > 0,
})
items = append(items, maps.Map{
"name": "WAF设置",
"name": "WAF策略",
"url": "/clusters/cluster/settings/waf?clusterId=" + clusterId,
"isActive": selectedItem == "waf",
"isOn": cluster.HttpFirewallPolicyId > 0,
@@ -132,20 +188,6 @@ func (this *ClusterHelper) createSettingMenu(cluster *pb.NodeCluster, info *pb.F
"isActive": false,
})
items = append(items, maps.Map{
"name": "DNS设置",
"url": "/clusters/cluster/settings/dns?clusterId=" + clusterId,
"isActive": selectedItem == "dns",
"isOn": cluster.DnsDomainId > 0 || len(cluster.DnsName) > 0,
})
items = append(items, maps.Map{
"name": "健康检查",
"url": "/clusters/cluster/settings/health?clusterId=" + clusterId,
"isActive": selectedItem == "health",
"isOn": info != nil && info.HealthCheckIsOn,
})
items = append(items, maps.Map{
"name": "DDoS防护",
"url": "/clusters/cluster/settings/ddos-protection?clusterId=" + clusterId,
@@ -153,13 +195,6 @@ func (this *ClusterHelper) createSettingMenu(cluster *pb.NodeCluster, info *pb.F
"isOn": info != nil && info.HasDDoSProtection,
})
items = append(items, maps.Map{
"name": "服务设置",
"url": "/clusters/cluster/settings/global-server-config?clusterId=" + clusterId,
"isActive": selectedItem == "globalServerConfig",
"isOn": true,
})
items = append(items, maps.Map{
"name": "-",
})

View File

@@ -0,0 +1,52 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package clusters
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/types"
)
type CreateNodeAction struct {
actionutils.ParentAction
}
func (this *CreateNodeAction) Init() {
this.Nav("", "cluster", "createNode")
}
func (this *CreateNodeAction) RunGet(params struct{}) {
// 集群总数
totalClustersResp, err := this.RPC().NodeClusterRPC().CountAllEnabledNodeClusters(this.AdminContext(), &pb.CountAllEnabledNodeClustersRequest{})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["totalNodeClusters"] = totalClustersResp.Count
// 节点总数
totalNodesResp, err := this.RPC().NodeRPC().CountAllEnabledNodes(this.AdminContext(), &pb.CountAllEnabledNodesRequest{})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["totalNodes"] = totalNodesResp.Count
// 如果只有一个默认集群,那么直接跳转到集群
clustersResp, err := this.RPC().NodeClusterRPC().ListEnabledNodeClusters(this.AdminContext(), &pb.ListEnabledNodeClustersRequest{
Offset: 0,
Size: 2,
Keyword: "",
})
if err != nil {
this.ErrorPage(err)
return
}
if len(clustersResp.NodeClusters) == 1 {
this.RedirectURL("/clusters/cluster/createNode?clusterId=" + types.String(clustersResp.NodeClusters[0].Id))
return
}
this.Show()
}

View File

@@ -16,6 +16,7 @@ func init() {
Prefix("/clusters").
Get("", new(IndexAction)).
GetPost("/create", new(CreateAction)).
GetPost("/createNode", new(CreateNodeAction)).
Post("/pin", new(PinAction)).
Get("/nodes", new(NodesAction)).

View File

@@ -211,6 +211,8 @@ func (this *NodesAction) RunGet(params struct {
"isInstalled": node.IsInstalled,
"isOn": node.IsOn,
"isUp": node.IsUp,
"isBackup": node.IsBackupForCluster || node.IsBackupForGroup,
"offlineDay": node.OfflineDay,
"installStatus": maps.Map{
"isRunning": node.InstallStatus.IsRunning,
"isFinished": node.InstallStatus.IsFinished,
@@ -222,9 +224,9 @@ func (this *NodesAction) RunGet(params struct {
"updatedAt": status.UpdatedAt,
"hostname": status.Hostname,
"cpuUsage": status.CPUUsage,
"cpuUsageText": numberutils.FormatFloat2(status.CPUUsage * 100) + "%",
"cpuUsageText": numberutils.FormatFloat2(status.CPUUsage*100) + "%",
"memUsage": status.MemoryUsage,
"memUsageText": numberutils.FormatFloat2(status.MemoryUsage * 100) + "%",
"memUsageText": numberutils.FormatFloat2(status.MemoryUsage*100) + "%",
"trafficInBytes": status.TrafficInBytes,
"trafficOutBytes": status.TrafficOutBytes,
"load1m": numberutils.PadFloatZero(numberutils.FormatFloat2(status.Load1m), 2),

View File

@@ -136,6 +136,8 @@ func (this *ClusterAction) RunGet(params struct {
"clusterId": node.NodeClusterId,
"isResolved": isResolved,
"isInstalled": isInstalled,
"isBackup": node.IsBackupForCluster || node.IsBackupForGroup,
"isOffline": node.IsOffline,
})
}
} else {
@@ -171,6 +173,8 @@ func (this *ClusterAction) RunGet(params struct {
"clusterId": node.NodeClusterId,
"isResolved": isResolved,
"isInstalled": isInstalled,
"isBackup": node.IsBackupForCluster || node.IsBackupForGroup,
"isOffline": node.IsOffline,
})
}
}

View File

@@ -13,15 +13,19 @@ import (
adminserverutils "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/server/admin-server-utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/types"
stringutil "github.com/iwind/TeaGo/utils/string"
"net"
"time"
)
const regionDenyMessage = "当前软件系统暂时不为你所在的区域提供服务。"
type IndexAction struct {
actionutils.ParentAction
}
@@ -36,6 +40,11 @@ func (this *IndexAction) RunGet(params struct {
Auth *helpers.UserShouldAuth
}) {
if !this.checkRegion() {
this.WriteString(regionDenyMessage)
return
}
// 是否自动从HTTP跳转到HTTPS
if this.Request.TLS == nil {
httpsPort, _ := adminserverutils.ReadServerHTTPS()
@@ -50,8 +59,11 @@ func (this *IndexAction) RunGet(params struct {
newHost += ":" + types.String(httpsPort)
}
this.RedirectURL("https://" + newHost + this.Request.RequestURI)
return
// 如果没有前端反向代理,则跳转
if len(this.Request.Header.Get("X-Forwarded-For")) == 0 && len(this.Request.Header.Get("X-Real-Ip")) == 0 {
this.RedirectURL("https://" + newHost + this.Request.RequestURI)
return
}
}
}
@@ -122,6 +134,11 @@ func (this *IndexAction) RunPost(params struct {
Auth *helpers.UserShouldAuth
CSRF *actionutils.CSRF
}) {
if !this.checkRegion() {
this.Fail(regionDenyMessage)
return
}
params.Must.
Field("username", params.Username).
Require("请输入用户名").
@@ -213,3 +230,13 @@ func (this *IndexAction) RunPost(params struct {
this.Success()
}
// 检查登录区域
func (this *IndexAction) checkRegion() bool {
var ip = this.RequestRemoteIP()
var result = iplibrary.LookupIP(ip)
if result != nil && result.IsOk() && result.CountryId() > 0 && lists.ContainsInt64([]int64{10}, result.CountryId()) {
return false
}
return true
}

View File

@@ -45,14 +45,14 @@ func (this *CreateDeletePopupAction) RunPost(params struct {
this.ErrorPage(err)
return
}
policyConfig := &shared.HTTPHeaderPolicy{}
var policyConfig = &shared.HTTPHeaderPolicy{}
err = json.Unmarshal(policyConfigResp.HttpHeaderPolicyJSON, policyConfig)
if err != nil {
this.ErrorPage(err)
return
}
deleteHeaders := policyConfig.DeleteHeaders
var deleteHeaders = policyConfig.DeleteHeaders
deleteHeaders = append(deleteHeaders, params.Name)
_, err = this.RPC().HTTPHeaderPolicyRPC().UpdateHTTPHeaderPolicyDeletingHeaders(this.AdminContext(), &pb.UpdateHTTPHeaderPolicyDeletingHeadersRequest{
HttpHeaderPolicyId: params.HeaderPolicyId,

View File

@@ -0,0 +1,67 @@
package headers
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/actions"
)
type CreateNonStandardPopupAction struct {
actionutils.ParentAction
}
func (this *CreateNonStandardPopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreateNonStandardPopupAction) RunGet(params struct {
HeaderPolicyId int64
Type string
}) {
this.Data["headerPolicyId"] = params.HeaderPolicyId
this.Data["type"] = params.Type
this.Show()
}
func (this *CreateNonStandardPopupAction) RunPost(params struct {
HeaderPolicyId int64
Name string
Must *actions.Must
}) {
// 日志
defer this.CreateLog(oplogs.LevelInfo, "添加非标的Header HeaderPolicyId: %d, Name: %s", params.HeaderPolicyId, params.Name)
params.Must.
Field("name", params.Name).
Require("名称不能为空")
policyConfigResp, err := this.RPC().HTTPHeaderPolicyRPC().FindEnabledHTTPHeaderPolicyConfig(this.AdminContext(), &pb.FindEnabledHTTPHeaderPolicyConfigRequest{HttpHeaderPolicyId: params.HeaderPolicyId})
if err != nil {
this.ErrorPage(err)
return
}
var policyConfig = &shared.HTTPHeaderPolicy{}
err = json.Unmarshal(policyConfigResp.HttpHeaderPolicyJSON, policyConfig)
if err != nil {
this.ErrorPage(err)
return
}
var nonStandardHeaders = policyConfig.NonStandardHeaders
nonStandardHeaders = append(nonStandardHeaders, params.Name)
_, err = this.RPC().HTTPHeaderPolicyRPC().UpdateHTTPHeaderPolicyNonStandardHeaders(this.AdminContext(), &pb.UpdateHTTPHeaderPolicyNonStandardHeadersRequest{
HttpHeaderPolicyId: params.HeaderPolicyId,
HeaderNames: nonStandardHeaders,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -17,22 +17,22 @@ func (this *DeleteDeletingHeaderAction) RunPost(params struct {
HeaderName string
}) {
// 日志
defer this.CreateLog(oplogs.LevelInfo, "删除需要删除的请求HeaderHeaderPolicyId:%d, HeaderName:%s", params.HeaderPolicyId, params.HeaderName)
defer this.CreateLog(oplogs.LevelInfo, "删除需要删除的HeaderHeaderPolicyId:%d, HeaderName:%s", params.HeaderPolicyId, params.HeaderName)
policyConfigResp, err := this.RPC().HTTPHeaderPolicyRPC().FindEnabledHTTPHeaderPolicyConfig(this.AdminContext(), &pb.FindEnabledHTTPHeaderPolicyConfigRequest{HttpHeaderPolicyId: params.HeaderPolicyId})
if err != nil {
this.ErrorPage(err)
return
}
policyConfigJSON := policyConfigResp.HttpHeaderPolicyJSON
policyConfig := &shared.HTTPHeaderPolicy{}
var policyConfigJSON = policyConfigResp.HttpHeaderPolicyJSON
var policyConfig = &shared.HTTPHeaderPolicy{}
err = json.Unmarshal(policyConfigJSON, policyConfig)
if err != nil {
this.ErrorPage(err)
return
}
headerNames := []string{}
var headerNames = []string{}
for _, h := range policyConfig.DeleteHeaders {
if h == params.HeaderName {
continue

View File

@@ -0,0 +1,52 @@
package headers
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
)
type DeleteNonStandardHeaderAction struct {
actionutils.ParentAction
}
func (this *DeleteNonStandardHeaderAction) RunPost(params struct {
HeaderPolicyId int64
HeaderName string
}) {
// 日志
defer this.CreateLog(oplogs.LevelInfo, "删除需要非标的HeaderHeaderPolicyId:%d, HeaderName:%s", params.HeaderPolicyId, params.HeaderName)
policyConfigResp, err := this.RPC().HTTPHeaderPolicyRPC().FindEnabledHTTPHeaderPolicyConfig(this.AdminContext(), &pb.FindEnabledHTTPHeaderPolicyConfigRequest{HttpHeaderPolicyId: params.HeaderPolicyId})
if err != nil {
this.ErrorPage(err)
return
}
var policyConfigJSON = policyConfigResp.HttpHeaderPolicyJSON
var policyConfig = &shared.HTTPHeaderPolicy{}
err = json.Unmarshal(policyConfigJSON, policyConfig)
if err != nil {
this.ErrorPage(err)
return
}
var headerNames = []string{}
for _, h := range policyConfig.NonStandardHeaders {
if h == params.HeaderName {
continue
}
headerNames = append(headerNames, h)
}
_, err = this.RPC().HTTPHeaderPolicyRPC().UpdateHTTPHeaderPolicyNonStandardHeaders(this.AdminContext(), &pb.UpdateHTTPHeaderPolicyNonStandardHeadersRequest{
HttpHeaderPolicyId: params.HeaderPolicyId,
HeaderNames: headerNames,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -18,6 +18,8 @@ func init() {
GetPost("/updateSetPopup", new(UpdateSetPopupAction)).
GetPost("/createDeletePopup", new(CreateDeletePopupAction)).
Post("/deleteDeletingHeader", new(DeleteDeletingHeaderAction)).
GetPost("/createNonStandardPopup", new(CreateNonStandardPopupAction)).
Post("/deleteNonStandardHeader", new(DeleteNonStandardHeaderAction)).
Post("/delete", new(DeleteAction)).
GetPost("/updateCORSPopup", new(UpdateCORSPopupAction)).
EndAll()

View File

@@ -49,7 +49,7 @@ func (this *UpdateCORSPopupAction) RunPost(params struct {
Must *actions.Must
CSRF *actionutils.CSRF
}) {
var config = &shared.HTTPCORSHeaderConfig{}
var config = shared.NewHTTPCORSHeaderConfig()
err := json.Unmarshal(params.CorsJSON, config)
if err != nil {
this.Fail("配置校验失败:" + err.Error())

View File

@@ -38,6 +38,7 @@ func (this *IndexAction) RunGet(params struct {
AllowEmpty: true,
AllowSameDomain: true,
AllowDomains: nil,
CheckOrigin: true,
}
}

View File

@@ -40,6 +40,7 @@ func (this *IndexAction) RunGet(params struct {
AllowEmpty: true,
AllowSameDomain: true,
AllowDomains: nil,
CheckOrigin: true,
}
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
@@ -41,7 +42,7 @@ func (this *CountriesAction) RunGet(params struct {
this.NotFound("firewallPolicy", params.FirewallPolicyId)
return
}
selectedCountryIds := []int64{}
var selectedCountryIds = []int64{}
if policyConfig.Inbound != nil && policyConfig.Inbound.Region != nil {
selectedCountryIds = policyConfig.Inbound.Region.DenyCountryIds
}
@@ -51,7 +52,7 @@ func (this *CountriesAction) RunGet(params struct {
this.ErrorPage(err)
return
}
countryMaps := []maps.Map{}
var countryMaps = []maps.Map{}
for _, country := range countriesResp.RegionCountries {
countryMaps = append(countryMaps, maps.Map{
"id": country.Id,
@@ -62,6 +63,18 @@ func (this *CountriesAction) RunGet(params struct {
}
this.Data["countries"] = countryMaps
// except & only URL Patterns
this.Data["exceptURLPatterns"] = []*shared.URLPattern{}
this.Data["onlyURLPatterns"] = []*shared.URLPattern{}
if policyConfig.Inbound != nil && policyConfig.Inbound.Region != nil {
if len(policyConfig.Inbound.Region.CountryExceptURLPatterns) > 0 {
this.Data["exceptURLPatterns"] = policyConfig.Inbound.Region.CountryExceptURLPatterns
}
if len(policyConfig.Inbound.Region.CountryOnlyURLPatterns) > 0 {
this.Data["onlyURLPatterns"] = policyConfig.Inbound.Region.CountryOnlyURLPatterns
}
}
// WAF是否启用
webConfig, err := dao.SharedHTTPWebDAO.FindWebConfigWithServerId(this.AdminContext(), params.ServerId)
if err != nil {
@@ -77,6 +90,9 @@ func (this *CountriesAction) RunPost(params struct {
FirewallPolicyId int64
CountryIds []int64
ExceptURLPatternsJSON []byte
OnlyURLPatternsJSON []byte
Must *actions.Must
}) {
// 日志
@@ -102,6 +118,34 @@ func (this *CountriesAction) RunPost(params struct {
}
policyConfig.Inbound.Region.DenyCountryIds = params.CountryIds
// 例外URL
var exceptURLPatterns = []*shared.URLPattern{}
if len(params.ExceptURLPatternsJSON) > 0 {
err = json.Unmarshal(params.ExceptURLPatternsJSON, &exceptURLPatterns)
if err != nil {
this.Fail("校验例外URL参数失败" + err.Error())
return
}
}
policyConfig.Inbound.Region.CountryExceptURLPatterns = exceptURLPatterns
// 限制URL
var onlyURLPatterns = []*shared.URLPattern{}
if len(params.OnlyURLPatternsJSON) > 0 {
err = json.Unmarshal(params.OnlyURLPatternsJSON, &onlyURLPatterns)
if err != nil {
this.Fail("校验限制URL参数失败" + err.Error())
return
}
}
policyConfig.Inbound.Region.CountryOnlyURLPatterns = onlyURLPatterns
err = policyConfig.Init()
if err != nil {
this.Fail("配置校验失败:" + err.Error())
return
}
inboundJSON, err := json.Marshal(policyConfig.Inbound)
if err != nil {
this.ErrorPage(err)

View File

@@ -7,6 +7,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
@@ -41,7 +42,7 @@ func (this *ProvincesAction) RunGet(params struct {
this.NotFound("firewallPolicy", params.FirewallPolicyId)
return
}
selectedProvinceIds := []int64{}
var selectedProvinceIds = []int64{}
if policyConfig.Inbound != nil && policyConfig.Inbound.Region != nil {
selectedProvinceIds = policyConfig.Inbound.Region.DenyProvinceIds
}
@@ -53,7 +54,7 @@ func (this *ProvincesAction) RunGet(params struct {
this.ErrorPage(err)
return
}
provinceMaps := []maps.Map{}
var provinceMaps = []maps.Map{}
for _, province := range provincesResp.RegionProvinces {
provinceMaps = append(provinceMaps, maps.Map{
"id": province.Id,
@@ -63,6 +64,18 @@ func (this *ProvincesAction) RunGet(params struct {
}
this.Data["provinces"] = provinceMaps
// except & only URL Patterns
this.Data["exceptURLPatterns"] = []*shared.URLPattern{}
this.Data["onlyURLPatterns"] = []*shared.URLPattern{}
if policyConfig.Inbound != nil && policyConfig.Inbound.Region != nil {
if len(policyConfig.Inbound.Region.ProvinceExceptURLPatterns) > 0 {
this.Data["exceptURLPatterns"] = policyConfig.Inbound.Region.ProvinceExceptURLPatterns
}
if len(policyConfig.Inbound.Region.ProvinceOnlyURLPatterns) > 0 {
this.Data["onlyURLPatterns"] = policyConfig.Inbound.Region.ProvinceOnlyURLPatterns
}
}
// WAF是否启用
webConfig, err := dao.SharedHTTPWebDAO.FindWebConfigWithServerId(this.AdminContext(), params.ServerId)
if err != nil {
@@ -78,6 +91,9 @@ func (this *ProvincesAction) RunPost(params struct {
FirewallPolicyId int64
ProvinceIds []int64
ExceptURLPatternsJSON []byte
OnlyURLPatternsJSON []byte
Must *actions.Must
}) {
// 日志
@@ -103,6 +119,34 @@ func (this *ProvincesAction) RunPost(params struct {
}
policyConfig.Inbound.Region.DenyProvinceIds = params.ProvinceIds
// 例外URL
var exceptURLPatterns = []*shared.URLPattern{}
if len(params.ExceptURLPatternsJSON) > 0 {
err = json.Unmarshal(params.ExceptURLPatternsJSON, &exceptURLPatterns)
if err != nil {
this.Fail("校验例外URL参数失败" + err.Error())
return
}
}
policyConfig.Inbound.Region.ProvinceExceptURLPatterns = exceptURLPatterns
// 限制URL
var onlyURLPatterns = []*shared.URLPattern{}
if len(params.OnlyURLPatternsJSON) > 0 {
err = json.Unmarshal(params.OnlyURLPatternsJSON, &onlyURLPatterns)
if err != nil {
this.Fail("校验限制URL参数失败" + err.Error())
return
}
}
policyConfig.Inbound.Region.ProvinceOnlyURLPatterns = onlyURLPatterns
err = policyConfig.Init()
if err != nil {
this.Fail("配置校验失败:" + err.Error())
return
}
inboundJSON, err := json.Marshal(policyConfig.Inbound)
if err != nil {
this.ErrorPage(err)

View File

@@ -106,8 +106,13 @@ func (this *ServerHelper) createLeftMenu(action *actions.ActionObject) {
// TABBAR
selectedTabbar, _ := action.Data["mainTab"]
tabbar := actionutils.NewTabbar()
tabbar.Add("网站列表", "", "/servers", "", false)
var tabbar = actionutils.NewTabbar()
tabbar.Add("", "", "/servers", "left arrow", false)
if len(serverConfig.Name) > 0 {
var item = tabbar.Add(serverConfig.Name, "", "/servers/server?serverId="+serverIdString, "angle right", true)
item.IsTitle = true
}
if teaconst.IsPlus {
tabbar.Add("看板", "", "/servers/server/boards?serverId="+serverIdString, "dashboard", selectedTabbar == "board")
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"sort"
)
type CleanAction struct {
@@ -16,11 +17,20 @@ func (this *CleanAction) Init() {
this.Nav("", "", "clean")
}
func (this *CleanAction) RunGet(params struct{}) {
func (this *CleanAction) RunGet(params struct {
OrderTable string
OrderSize string
}) {
this.Data["orderTable"] = params.OrderTable
this.Data["orderSize"] = params.OrderSize
this.Show()
}
func (this *CleanAction) RunPost(params struct {
OrderTable string
OrderSize string
Must *actions.Must
}) {
tablesResp, err := this.RPC().DBRPC().FindAllDBTables(this.AdminContext(), &pb.FindAllDBTablesRequest{})
@@ -28,9 +38,33 @@ func (this *CleanAction) RunPost(params struct {
this.ErrorPage(err)
return
}
var tables = tablesResp.DbTables
tableMaps := []maps.Map{}
for _, table := range tablesResp.DbTables {
// 排序
switch params.OrderTable {
case "asc":
sort.Slice(tables, func(i, j int) bool {
return tables[i].Name < tables[j].Name
})
case "desc":
sort.Slice(tables, func(i, j int) bool {
return tables[i].Name > tables[j].Name
})
}
switch params.OrderSize {
case "asc":
sort.Slice(tables, func(i, j int) bool {
return tables[i].DataLength+tables[i].IndexLength < tables[j].DataLength+tables[j].IndexLength
})
case "desc":
sort.Slice(tables, func(i, j int) bool {
return tables[i].DataLength+tables[i].IndexLength > tables[j].DataLength+tables[j].IndexLength
})
}
var tableMaps = []maps.Map{}
for _, table := range tables {
if !table.IsBaseTable || (!table.CanClean && !table.CanDelete) {
continue
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
Vue.component("health-check-config-box", {
props: ["v-health-check-config", "v-check-domain-url"],
props: ["v-health-check-config", "v-check-domain-url", "v-is-plus"],
data: function () {
let healthCheckConfig = this.vHealthCheckConfig
let urlProtocol = "http"
@@ -69,6 +69,7 @@ Vue.component("health-check-config-box", {
healthCheckConfig.countDown = 3
}
}
return {
healthCheck: healthCheckConfig,
advancedVisible: false,
@@ -237,13 +238,13 @@ Vue.component("health-check-config-box", {
</td>
</tr>
<tr>
<td>自动下线</td>
<td>自动下线<span v-if="vIsPlus">IP</span></td>
<td>
<div class="ui checkbox">
<input type="checkbox" value="1" v-model="healthCheck.autoDown"/>
<label></label>
</div>
<p class="comment">选中后系统会根据健康检查的结果自动标记节点的上线/下线状态并可能自动同步DNS设置。</p>
<p class="comment">选中后系统会根据健康检查的结果自动标记<span v-if="vIsPlus">节点IP</span><span v-else>节点</span>的上线/下线状态并可能自动同步DNS设置。<span v-if="!vIsPlus">注意免费版的只能整体上下线整个节点商业版的可以下线单个IP。</span></p>
</td>
</tr>
<tr v-show="healthCheck.autoDown">

View File

@@ -1,5 +1,5 @@
Vue.component("time-duration-box", {
props: ["v-name", "v-value", "v-count", "v-unit"],
props: ["name", "v-name", "v-value", "v-count", "v-unit"],
mounted: function () {
this.change()
},
@@ -14,9 +14,18 @@ Vue.component("time-duration-box", {
if (typeof (v["count"]) != "number") {
v["count"] = -1
}
let realName = ""
if (typeof this.name == "string" && this.name.length > 0) {
realName = this.name
} else if (typeof this.vName == "string" && this.vName.length > 0) {
realName = this.vName
}
return {
duration: v,
countString: (v.count >= 0) ? v.count.toString() : ""
countString: (v.count >= 0) ? v.count.toString() : "",
realName: realName
}
},
watch: {
@@ -39,7 +48,7 @@ Vue.component("time-duration-box", {
}
},
template: `<div class="ui fields inline" style="padding-bottom: 0; margin-bottom: 0">
<input type="hidden" :name="vName" :value="JSON.stringify(duration)"/>
<input type="hidden" :name="realName" :value="JSON.stringify(duration)"/>
<div class="ui field">
<input type="text" v-model="countString" maxlength="11" size="11" @keypress.enter.prevent="1"/>
</div>

View File

@@ -95,6 +95,16 @@ Vue.component("message-row", {
<div v-if="message.type == 'serverNamesRequireAuditing'" style="margin-top: 0.8em">
<a :href="'/servers/server/settings/serverNames?serverId=' + params.serverId" target="_top">去审核</a></a>
</div>
<!-- 节点调度 -->
<div v-if="message.type == 'NodeSchedule'" style="margin-top: 0.8em">
<a :href="'/clusters/cluster/node/settings/schedule?clusterId=' + message.cluster.id + '&nodeId=' + message.node.id" target="_top">查看调度状态 &raquo;</a>
</div>
<!-- 节点租期结束 -->
<div v-if="message.type == 'NodeOfflineDay'" style="margin-top: 0.8em">
<a :href="'/clusters/cluster/node/detail?clusterId=' + message.cluster.id + '&nodeId=' + message.node.id" target="_top">查看详情 &raquo;</a>
</div>
</td>
</tr>
</table>

View File

@@ -11,23 +11,110 @@ Vue.component("http-cors-header-config-box", {
exposeHeaders: [],
maxAge: 0,
requestHeaders: [],
requestMethod: ""
requestMethod: "",
optionsMethodOnly: false
}
}
if (config.allowMethods == null) {
config.allowMethods = []
}
if (config.exposeHeaders == null) {
config.exposeHeaders = []
}
let maxAgeSecondsString = config.maxAge.toString()
if (maxAgeSecondsString == "0") {
maxAgeSecondsString = ""
}
return {
config: config
config: config,
maxAgeSecondsString: maxAgeSecondsString,
moreOptionsVisible: false
}
},
watch: {
maxAgeSecondsString: function (v) {
let seconds = parseInt(v)
if (isNaN(seconds)) {
seconds = 0
}
this.config.maxAge = seconds
}
},
methods: {
changeMoreOptions: function (visible) {
this.moreOptionsVisible = visible
},
addDefaultAllowMethods: function () {
let that = this
let defaultMethods = ["PUT", "GET", "POST", "DELETE", "HEAD", "OPTIONS", "PATCH"]
defaultMethods.forEach(function (method) {
if (!that.config.allowMethods.$contains(method)) {
that.config.allowMethods.push(method)
}
})
}
},
template: `<div>
<input type="hidden" name="corsJSON" :value="JSON.stringify(config)"/>
<table class="ui table definition selectable">
<tr>
<td class="title">启用CORS自适应跨域</td>
<td>
<checkbox v-model="config.isOn"></checkbox>
</td>
</tr>
<tbody>
<tr>
<td class="title">启用CORS自适应跨域</td>
<td>
<checkbox v-model="config.isOn"></checkbox>
<p class="comment">启用后自动在响应Header中增加对应的<code-label>Access-Control-*</code-label>相关内容。</p>
</td>
</tr>
</tbody>
<tbody v-show="config.isOn">
<tr>
<td colspan="2"><more-options-indicator @change="changeMoreOptions"></more-options-indicator></td>
</tr>
</tbody>
<tbody v-show="config.isOn && moreOptionsVisible">
<tr>
<td>允许的请求方法列表</td>
<td>
<http-methods-box :v-methods="config.allowMethods"></http-methods-box>
<p class="comment"><a href="" @click.prevent="addDefaultAllowMethods">[添加默认]</a>。<code-label>Access-Control-Allow-Methods</code-label>值设置。所访问资源允许使用的方法列表,不设置则表示默认为<code-label>PUT</code-label>、<code-label>GET</code-label>、<code-label>POST</code-label>、<code-label>DELETE</code-label>、<code-label>HEAD</code-label>、<code-label>OPTIONS</code-label>、<code-label>PATCH</code-label>。</p>
</td>
</tr>
<tr>
<td>预检结果缓存时间</td>
<td>
<div class="ui input right labeled">
<input type="text" style="width: 6em" maxlength="6" v-model="maxAgeSecondsString"/>
<span class="ui label">秒</span>
</div>
<p class="comment"><code-label>Access-Control-Max-Age</code-label>值设置。预检结果缓存时间0或者不填表示使用浏览器默认设置。注意每个浏览器有不同的缓存时间上限。</p>
</td>
</tr>
<tr>
<td>允许服务器暴露的Header</td>
<td>
<values-box :v-values="config.exposeHeaders"></values-box>
<p class="comment"><code-label>Access-Control-Expose-Headers</code-label>值设置。允许服务器暴露的Header请注意Header的大小写。</p>
</td>
</tr>
<tr>
<td>实际请求方法</td>
<td>
<input type="text" v-model="config.requestMethod"/>
<p class="comment"><code-label>Access-Control-Request-Method</code-label>值设置。实际请求服务器时使用的方法,比如<code-label>POST</code-label>。</p>
</td>
</tr>
<tr>
<td>仅OPTIONS有效</td>
<td>
<checkbox v-model="config.optionsMethodOnly"></checkbox>
<p class="comment">选中后表示当前CORS设置仅在OPTIONS方法请求时有效。</p>
</td>
</tr>
</tbody>
</table>
<div class="margin"></div>
</div>`

View File

@@ -96,6 +96,9 @@ Vue.component("http-firewall-actions-box", {
pageBody: defaultPageBody,
defaultPageBody: defaultPageBody,
redirectStatus: 307,
redirectURL: "",
goGroupName: "",
goGroupId: 0,
goGroup: null,
@@ -105,7 +108,15 @@ Vue.component("http-firewall-actions-box", {
jsCookieLife: "",
jsCookieMaxFails: "",
jsCookieFailBlockTimeout: ""
jsCookieFailBlockTimeout: "",
statusOptions: [
{"code": 301, "text": "Moved Permanently"},
{"code": 308, "text": "Permanent Redirect"},
{"code": 302, "text": "Found"},
{"code": 303, "text": "See Other"},
{"code": 307, "text": "Temporary Redirect"}
]
}
},
watch: {
@@ -272,6 +283,9 @@ Vue.component("http-firewall-actions-box", {
this.pageStatus = 403
this.pageBody = this.defaultPageBody
this.redirectStatus = 307
this.redirectURL = ""
this.goGroupName = ""
this.goGroupId = 0
this.goGroup = null
@@ -398,7 +412,16 @@ Vue.component("http-firewall-actions-box", {
if (config.options.body != null) {
this.pageBody = config.options.body
}
break
case "redirect":
this.redirectStatus = 307
this.redirectURL = ""
if (config.options.status != null) {
this.redirectStatus = config.options.status
}
if (config.options.url != null) {
this.redirectURL = config.options.url
}
break
case "go_group":
if (config.options != null) {
@@ -485,6 +508,23 @@ Vue.component("http-firewall-actions-box", {
status: pageStatus,
body: this.pageBody
}
} else if (this.actionCode == "redirect") {
let redirectStatus = this.redirectStatus.toString()
if (!redirectStatus.match(/^\d{3}$/)) {
redirectStatus = 307
} else {
redirectStatus = parseInt(redirectStatus)
}
if (this.redirectURL.length == 0) {
teaweb.warn("请输入跳转到URL")
return
}
this.actionOptions = {
status: redirectStatus,
url: this.redirectURL
}
} else if (this.actionCode == "go_group") { // go_group
let groupId = this.goGroupId
if (typeof (groupId) == "string") {
@@ -625,6 +665,9 @@ Vue.component("http-firewall-actions-box", {
<!-- page -->
<span v-if="config.code == 'page'">[{{config.options.status}}]</span>
<!-- redirect -->
<span v-if="config.code == 'redirect'">{{config.options.url}}</span>
<!-- go_group -->
<span v-if="config.code == 'go_group'">{{config.options.groupName}}</span>
@@ -832,6 +875,22 @@ Vue.component("http-firewall-actions-box", {
</td>
</tr>
<!-- redirect -->
<tr v-if="actionCode == 'redirect'">
<td>状态码 *</td>
<td>
<select class="ui dropdown auto-width" v-model="redirectStatus">
<option v-for="status in statusOptions" :value="status.code">{{status.code}} {{status.text}}</option>
</select>
</td>
</tr>
<tr v-if="actionCode == 'redirect'">
<td>跳转到URL</td>
<td>
<input type="text" v-model="redirectURL"/>
</td>
</tr>
<!-- 规则分组 -->
<tr v-if="actionCode == 'go_group'">
<td>下一个分组 *</td>

View File

@@ -32,7 +32,7 @@ Vue.component("http-firewall-rule-label", {
<!-- cc2 -->
<span v-if="rule.param == '\${cc2}'">
{{rule.checkpointOptions.period}}秒/{{rule.checkpointOptions.threshold}}请求
{{rule.checkpointOptions.period}}秒请求
</span>
<!-- refererBlock -->

View File

@@ -60,7 +60,7 @@ Vue.component("http-firewall-rules-box", {
<!-- cc2 -->
<span v-if="rule.param == '\${cc2}'">
{{rule.checkpointOptions.period}}秒/{{rule.checkpointOptions.threshold}}请求
{{rule.checkpointOptions.period}}秒请求
</span>
<!-- refererBlock -->

View File

@@ -261,6 +261,7 @@ Vue.component("http-firewall-checkpoint-referer-block", {
let allowSameDomain = true
let allowDomains = []
let denyDomains = []
let checkOrigin = true
let options = {}
if (window.parent.UPDATING_RULE != null) {
@@ -282,6 +283,9 @@ Vue.component("http-firewall-checkpoint-referer-block", {
if (options.denyDomains != null && typeof (options.denyDomains) == "object") {
denyDomains = options.denyDomains
}
if (typeof options.checkOrigin == "boolean") {
checkOrigin = options.checkOrigin
}
let that = this
setTimeout(function () {
@@ -293,6 +297,7 @@ Vue.component("http-firewall-checkpoint-referer-block", {
allowSameDomain: allowSameDomain,
allowDomains: allowDomains,
denyDomains: denyDomains,
checkOrigin: checkOrigin,
options: {},
value: 0
}
@@ -303,6 +308,9 @@ Vue.component("http-firewall-checkpoint-referer-block", {
},
allowSameDomain: function () {
this.change()
},
checkOrigin: function () {
this.change()
}
},
methods: {
@@ -332,6 +340,10 @@ Vue.component("http-firewall-checkpoint-referer-block", {
code: "denyDomains",
value: this.denyDomains
},
{
code: "checkOrigin",
value: this.checkOrigin
}
]
}
},
@@ -367,6 +379,13 @@ Vue.component("http-firewall-checkpoint-referer-block", {
<p class="comment">禁止的来源域名列表,比如<code-label>example.org</code-label>、<code-label>*.example.org</code-label>;除了这些禁止的来源域名外,其他域名都会被允许,除非限定了允许的来源域名。</p>
</td>
</tr>
<tr>
<td>同时检查Origin</td>
<td>
<checkbox v-model="checkOrigin"></checkbox>
<p class="comment">如果请求没有指定Referer Header则尝试检查Origin Header多用于跨站调用。</p>
</td>
</tr>
</table>
</div>`
})

View File

@@ -29,6 +29,7 @@ Vue.component("http-header-policy-box", {
// 请求相关
let requestSettingHeaders = []
let requestDeletingHeaders = []
let requestNonStandardHeaders = []
let requestPolicy = this.vRequestHeaderPolicy
if (requestPolicy != null) {
@@ -38,11 +39,15 @@ Vue.component("http-header-policy-box", {
if (requestPolicy.deleteHeaders != null) {
requestDeletingHeaders = requestPolicy.deleteHeaders
}
if (requestPolicy.nonStandardHeaders != null) {
requestNonStandardHeaders = requestPolicy.nonStandardHeaders
}
}
// 响应相关
let responseSettingHeaders = []
let responseDeletingHeaders = []
let responseNonStandardHeaders = []
let responsePolicy = this.vResponseHeaderPolicy
if (responsePolicy != null) {
@@ -52,6 +57,9 @@ Vue.component("http-header-policy-box", {
if (responsePolicy.deleteHeaders != null) {
responseDeletingHeaders = responsePolicy.deleteHeaders
}
if (responsePolicy.nonStandardHeaders != null) {
responseNonStandardHeaders = responsePolicy.nonStandardHeaders
}
}
let responseCORS = {
@@ -64,12 +72,16 @@ Vue.component("http-header-policy-box", {
return {
type: type,
typeName: (type == "request") ? "请求" : "响应",
requestHeaderRef: requestHeaderRef,
responseHeaderRef: responseHeaderRef,
requestSettingHeaders: requestSettingHeaders,
requestDeletingHeaders: requestDeletingHeaders,
requestNonStandardHeaders: requestNonStandardHeaders,
responseSettingHeaders: responseSettingHeaders,
responseDeletingHeaders: responseDeletingHeaders,
responseNonStandardHeaders: responseNonStandardHeaders,
responseCORS: responseCORS
}
},
@@ -93,8 +105,15 @@ Vue.component("http-header-policy-box", {
}
})
},
addNonStandardHeader: function (policyId, type) {
teaweb.popup("/servers/server/settings/headers/createNonStandardPopup?" + this.vParams + "&headerPolicyId=" + policyId + "&type=" + type, {
callback: function () {
teaweb.successRefresh("保存成功")
}
})
},
updateSettingPopup: function (policyId, headerId) {
teaweb.popup("/servers/server/settings/headers/updateSetPopup?" + this.vParams + "&headerPolicyId=" + policyId + "&headerId=" + headerId+ "&type=" + this.type, {
teaweb.popup("/servers/server/settings/headers/updateSetPopup?" + this.vParams + "&headerPolicyId=" + policyId + "&headerId=" + headerId + "&type=" + this.type, {
callback: function () {
teaweb.successRefresh("保存成功")
}
@@ -111,6 +130,17 @@ Vue.component("http-header-policy-box", {
.refresh()
})
},
deleteNonStandardHeader: function (policyId, headerName) {
teaweb.confirm("确定要删除'" + headerName + "'吗?", function () {
Tea.action("/servers/server/settings/headers/deleteNonStandardHeader")
.params({
headerPolicyId: policyId,
headerName: headerName
})
.post()
.refresh()
})
},
deleteHeader: function (policyId, type, headerId) {
teaweb.confirm("确定要删除此Header吗", function () {
this.$post("/servers/server/settings/headers/delete")
@@ -125,6 +155,7 @@ Vue.component("http-header-policy-box", {
},
updateCORS: function (policyId) {
teaweb.popup("/servers/server/settings/headers/updateCORSPopup?" + this.vParams + "&headerPolicyId=" + policyId + "&type=" + this.type, {
height: "30em",
callback: function () {
teaweb.successRefresh("保存成功")
}
@@ -156,7 +187,7 @@ Vue.component("http-header-policy-box", {
<warning-message>由于已经在当前<a :href="vGroupSettingUrl + '#request'">服务分组</a>中进行了对应的配置,在这里的配置将不会生效。</warning-message>
</div>
<div :class="{'opacity-mask': vHasGroupRequestConfig}">
<h4>设置请求Header <a href="" @click.prevent="addSettingHeader(vRequestHeaderPolicy.id)">[添加新Header]</a></h4>
<h4>设置请求Header &nbsp; <a href="" @click.prevent="addSettingHeader(vRequestHeaderPolicy.id)" style="font-size: 0.8em">[添加新Header]</a></h4>
<p class="comment" v-if="requestSettingHeaders.length == 0">暂时还没有Header。</p>
<table class="ui table selectable celled" v-if="requestSettingHeaders.length > 0">
<thead>
@@ -185,20 +216,31 @@ Vue.component("http-header-policy-box", {
</tbody>
</table>
<h4>删除请求Header</h4>
<p class="comment">这里可以设置需要从请求中删除的Header。</p>
<h4>其他设置</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">需要删除的Header</td>
<td>
<div v-if="requestDeletingHeaders.length > 0">
<div class="ui label small basic" v-for="headerName in requestDeletingHeaders">{{headerName}} <a href=""><i class="icon remove" title="删除" @click.prevent="deleteDeletingHeader(vRequestHeaderPolicy.id, headerName)"></i></a> </div>
<div class="ui divider" ></div>
</div>
<button class="ui button small" type="button" @click.prevent="addDeletingHeader(vRequestHeaderPolicy.id, 'request')">+</button>
</td>
</tr>
<tbody>
<tr>
<td class="title">删除Header <tip-icon content="可以通过此功能删除转发到源站的请求报文中不需要的Header"></tip-icon></td>
<td>
<div v-if="requestDeletingHeaders.length > 0">
<div class="ui label small basic" v-for="headerName in requestDeletingHeaders">{{headerName}} <a href=""><i class="icon remove" title="删除" @click.prevent="deleteDeletingHeader(vRequestHeaderPolicy.id, headerName)"></i></a> </div>
<div class="ui divider" ></div>
</div>
<button class="ui button small" type="button" @click.prevent="addDeletingHeader(vRequestHeaderPolicy.id, 'request')">+</button>
</td>
</tr>
<tr>
<td class="title">非标Header <tip-icon content="可以通过此功能设置转发到源站的请求报文中非标准的Header比如hello_world"></tip-icon></td>
<td>
<div v-if="requestNonStandardHeaders.length > 0">
<div class="ui label small basic" v-for="headerName in requestNonStandardHeaders">{{headerName}} <a href=""><i class="icon remove" title="删除" @click.prevent="deleteNonStandardHeader(vRequestHeaderPolicy.id, headerName)"></i></a> </div>
<div class="ui divider" ></div>
</div>
<button class="ui button small" type="button" @click.prevent="addNonStandardHeader(vRequestHeaderPolicy.id, 'request')">+</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
@@ -218,7 +260,7 @@ Vue.component("http-header-policy-box", {
<warning-message>由于已经在当前<a :href="vGroupSettingUrl + '#response'">服务分组</a>中进行了对应的配置,在这里的配置将不会生效。</warning-message>
</div>
<div :class="{'opacity-mask': vHasGroupResponseConfig}">
<h4>设置响应Header <a href="" @click.prevent="addSettingHeader(vResponseHeaderPolicy.id)">[添加新Header]</a></h4>
<h4>设置响应Header &nbsp; <a href="" @click.prevent="addSettingHeader(vResponseHeaderPolicy.id)" style="font-size: 0.8em">[添加新Header]</a></h4>
<p class="comment" style="margin-top: 0; padding-top: 0">将会覆盖已有的同名Header。</p>
<p class="comment" v-if="responseSettingHeaders.length == 0">暂时还没有Header。</p>
<table class="ui table selectable celled" v-if="responseSettingHeaders.length > 0">
@@ -241,6 +283,11 @@ Vue.component("http-header-policy-box", {
<grey-label v-if="header.disableRedirect">跳转禁用</grey-label>
<grey-label v-if="header.shouldReplace && header.replaceValues != null && header.replaceValues.length > 0">替换</grey-label>
</div>
<!-- CORS -->
<div v-if="header.name == 'Access-Control-Allow-Origin' && header.value == '*'">
<span class="red small">建议使用当前页面下方的"CORS自适应跨域"功能代替Access-Control-*-*相关Header。</span>
</div>
</td>
<td>{{header.value}}</td>
<td><a href="" @click.prevent="updateSettingPopup(vResponseHeaderPolicy.id, header.id)">修改</a> &nbsp; <a href="" @click.prevent="deleteHeader(vResponseHeaderPolicy.id, 'setHeader', header.id)">删除</a> </td>
@@ -248,31 +295,38 @@ Vue.component("http-header-policy-box", {
</tbody>
</table>
<h4>删除响应Header</h4>
<p class="comment">这里可以设置需要从响应中删除的Header。</p>
<table class="ui table definition selectable">
<tr>
<td class="title">需要删除的Header</td>
<td>
<div v-if="responseDeletingHeaders.length > 0">
<div class="ui label small basic" v-for="headerName in responseDeletingHeaders">{{headerName}} <a href=""><i class="icon remove" title="删除" @click.prevent="deleteDeletingHeader(vResponseHeaderPolicy.id, headerName)"></i></a> </div>
<div class="ui divider" ></div>
</div>
<button class="ui button small" type="button" @click.prevent="addDeletingHeader(vResponseHeaderPolicy.id, 'response')">+</button>
</td>
</tr>
</table>
<h4>其他设置</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">CORS自适应跨域</td>
<td>
<span v-if="responseCORS.isOn" class="green">已启用</span><span class="disabled" v-else="">未启用</span> &nbsp; <a href="" @click.prevent="updateCORS(vResponseHeaderPolicy.id)">[修改]</a>
</td>
</tr>
<tbody>
<tr>
<td class="title">删除Header <tip-icon content="可以通过此功能删除响应报文中不需要的Header"></tip-icon></td>
<td>
<div v-if="responseDeletingHeaders.length > 0">
<div class="ui label small basic" v-for="headerName in responseDeletingHeaders">{{headerName}} &nbsp; <a href=""><i class="icon remove small" title="删除" @click.prevent="deleteDeletingHeader(vResponseHeaderPolicy.id, headerName)"></i></a></div>
<div class="ui divider" ></div>
</div>
<button class="ui button small" type="button" @click.prevent="addDeletingHeader(vResponseHeaderPolicy.id, 'response')">+</button>
</td>
</tr>
<tr>
<td>非标Header <tip-icon content="可以通过此功能设置响应报文中非标准的Header比如hello_world"></tip-icon></td>
<td>
<div v-if="responseNonStandardHeaders.length > 0">
<div class="ui label small basic" v-for="headerName in responseNonStandardHeaders">{{headerName}} &nbsp; <a href=""><i class="icon remove small" title="删除" @click.prevent="deleteNonStandardHeader(vResponseHeaderPolicy.id, headerName)"></i></a></div>
<div class="ui divider" ></div>
</div>
<button class="ui button small" type="button" @click.prevent="addNonStandardHeader(vResponseHeaderPolicy.id, 'response')">+</button>
</td>
</tr>
<tr>
<td class="title">CORS自适应跨域</td>
<td>
<span v-if="responseCORS.isOn" class="green">已启用</span><span class="disabled" v-else="">未启用</span> &nbsp; <a href="" @click.prevent="updateCORS(vResponseHeaderPolicy.id)">[修改]</a>
<p class="comment"><span v-if="!responseCORS.isOn">启用后,服务器可以</span><span v-else>服务器会</span>自动生成<code-label>Access-Control-*-*</code-label>相关的Header。</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>

View File

@@ -0,0 +1,60 @@
Vue.component("http-pages-box", {
props: ["v-pages"],
data: function () {
let pages = []
if (this.vPages != null) {
pages = this.vPages
}
return {
pages: pages
}
},
methods: {
addPage: function () {
let that = this
teaweb.popup("/servers/server/settings/pages/createPopup", {
height: "26em",
callback: function (resp) {
that.pages.push(resp.data.page)
}
})
},
updatePage: function (pageIndex, pageId) {
let that = this
teaweb.popup("/servers/server/settings/pages/updatePopup?pageId=" + pageId, {
height: "26em",
callback: function (resp) {
Vue.set(that.pages, pageIndex, resp.data.page)
}
})
},
removePage: function (pageIndex) {
let that = this
teaweb.confirm("确定要移除此页面吗?", function () {
that.pages.$remove(pageIndex)
})
}
},
template: `<div>
<input type="hidden" name="pagesJSON" :value="JSON.stringify(pages)"/>
<table class="ui table selectable definition">
<tr>
<td class="title">自定义页面</td>
<td>
<div v-if="pages.length > 0">
<div class="ui label small basic" v-for="(page,index) in pages">
{{page.status}} -&gt; <span v-if="page.bodyType == 'url'">{{page.url}}</span><span v-if="page.bodyType == 'html'">[HTML内容]</span> <a href="" title="修改" @click.prevent="updatePage(index, page.id)"><i class="icon pencil small"></i></a> <a href="" title="删除" @click.prevent="removePage(index)"><i class="icon remove"></i></a>
</div>
<div class="ui divider"></div>
</div>
<div>
<button class="ui button small" type="button" @click.prevent="addPage()">+</button>
</div>
<p class="comment">根据响应状态码返回一些自定义页面比如404500等错误页面。</p>
</td>
</tr>
</table>
<div class="ui margin"></div>
</div>`
})

View File

@@ -9,7 +9,8 @@ Vue.component("http-referers-config-box", {
allowEmpty: true,
allowSameDomain: true,
allowDomains: [],
denyDomains: []
denyDomains: [],
checkOrigin: true
}
}
if (config.allowDomains == null) {
@@ -84,6 +85,13 @@ Vue.component("http-referers-config-box", {
<p class="comment">禁止的来源域名列表,比如<code-label>example.org</code-label>、<code-label>*.example.org</code-label>;除了这些禁止的来源域名外,其他域名都会被允许,除非限定了允许的来源域名。</p>
</td>
</tr>
<tr>
<td>同时检查Origin</td>
<td>
<checkbox v-model="config.checkOrigin"></checkbox>
<p class="comment">如果请求没有指定Referer Header则尝试检查Origin Header多用于跨站调用。</p>
</td>
</tr>
</tbody>
</table>
<div class="ui margin"></div>

View File

@@ -67,7 +67,19 @@ Vue.component("origin-list-box", {
Vue.component("origin-list-table", {
props: ["v-origins", "v-origin-type"],
data: function () {
return {}
let hasMatchedDomains = false
let origins = this.vOrigins
if (origins != null && origins.length > 0) {
origins.forEach(function (origin) {
if (origin.domains != null && origin.domains.length > 0) {
hasMatchedDomains = true
}
})
}
return {
hasMatchedDomains: hasMatchedDomains
}
},
methods: {
deleteOrigin: function (originId) {
@@ -90,12 +102,14 @@ Vue.component("origin-list-table", {
<tr v-for="origin in vOrigins">
<td :class="{disabled:!origin.isOn}">
<a href="" @click.prevent="updateOrigin(origin.id)" :class="{disabled:!origin.isOn}">{{origin.addr}} &nbsp;<i class="icon expand small"></i></a>
<div style="margin-top: 0.3em" v-if="origin.name.length > 0 || origin.hasCert || (origin.host != null && origin.host.length > 0) || origin.followPort || (origin.domains != null && origin.domains.length > 0)">
<div style="margin-top: 0.3em">
<tiny-basic-label v-if="origin.name.length > 0">{{origin.name}}</tiny-basic-label>
<tiny-basic-label v-if="origin.hasCert">证书</tiny-basic-label>
<tiny-basic-label v-if="origin.host != null && origin.host.length > 0">主机名: {{origin.host}}</tiny-basic-label>
<tiny-basic-label v-if="origin.followPort">端口跟随</tiny-basic-label>
<span v-if="origin.domains != null && origin.domains.length > 0"><tiny-basic-label v-for="domain in origin.domains">匹配: {{domain}}</tiny-basic-label></span>
<span v-else-if="hasMatchedDomains"><tiny-basic-label>匹配: 所有域名</tiny-basic-label></span>
</div>
</td>
<td :class="{disabled:!origin.isOn}">{{origin.weight}}</td>

View File

@@ -4,7 +4,6 @@
<a href="https://goedge.cn/docs" target="_blank" class="item">文档</a>
<a href="https://github.com/TeaOSLab/EdgeAdmin" target="_blank" class="item">GitHub</a>
<a href="https://github.com/TeaOSLab/EdgeAdmin/issues" target="_blank" class="item">提Bug</a>
<a class="item" @click.prevent="showQQGroupQrcode()" title="点击弹出加群二维码">QQ讨论群 &nbsp;<i class="icon qrcode"></i> </a>
<a class="item" href="https://goedge.cn/community/telegram" target="_blank" title="点击跳转到加群页面">Telegram群 &nbsp;<i class="icon paper plane"></i></a>
<a class="item right" href="https://goedge.cn/commercial" target="_blank" v-if="!teaIsPlus">企业版</a>
</div>

View File

@@ -52,20 +52,20 @@
opacity: 0.1;
}
.left-box.tiny {
top: 10.5em;
top: 10em;
}
.left-box.without-tabbar {
top: 3em;
}
.left-box.with-menu {
top: 10em;
top: 8.7em;
}
.left-box.without-menu {
top: 6em;
}
.right-box {
position: fixed;
top: 7.5em;
top: 7em;
bottom: 1.3em;
right: 0;
left: 18em;
@@ -73,6 +73,12 @@
padding-bottom: 2em;
overflow-y: auto;
}
.right-box h4:first-child {
margin-top: 1em;
}
.right-box > .comment:first-child {
margin-top: 0.5em;
}
@media screen and (max-width: 512px) {
.right-box {
left: 13em;
@@ -83,7 +89,7 @@ body.expanded .right-box {
left: 10em;
}
.right-box.tiny {
top: 10.4em;
top: 10em;
left: 26.5em;
}
.right-box::-webkit-scrollbar {
@@ -93,7 +99,7 @@ body.expanded .right-box {
top: 3em;
}
.right-box.with-menu {
top: 10em;
top: 8.6em;
}
.right-box.without-menu {
top: 6em;
@@ -589,13 +595,13 @@ body.expanded .main {
width: 4px;
}
.main .tab-menu {
margin-top: 1em !important;
margin-top: 0.3em !important;
margin-bottom: 0 !important;
overflow-x: auto;
overflow-y: hidden;
}
.main .tab-menu .item {
padding: 1em !important;
padding: 0 1em !important;
}
.main .tab-menu .item var {
font-style: normal;
@@ -607,8 +613,37 @@ body.expanded .main {
.main .tab-menu .item .icon {
margin-left: 0.6em;
}
.main .tab-menu .item.active {
.main .tab-menu .item.active.title {
font-weight: normal !important;
margin-right: 1em !important;
border-radius: 0 !important;
}
.main .tab-menu .item:hover {
background: #f8f8f9 !important;
border-width: 1px;
}
.main .tab-menu .item.active:not(.title) {
font-weight: normal !important;
border: none;
border-radius: 0 !important;
color: #2185d0 !important;
}
.main .tab-menu .item.active:not(.title) .bottom-indicator {
border-bottom: 1px #2185d0 solid;
position: absolute;
left: 0;
right: 0;
bottom: 1px;
}
.main .tab-menu .item.active:not(.title).icon .bottom-indicator {
border-bottom: 1px #2185d0 solid;
position: absolute;
left: 0;
right: 0.6em;
bottom: 1px;
}
.main .tab-menu .item.active.blue {
font-weight: bold !important;
}
.main .tab-menu::-webkit-scrollbar {
height: 4px;

File diff suppressed because one or more lines are too long

View File

@@ -106,9 +106,11 @@
<!-- 右侧主操作栏 -->
<div class="main" :class="{'without-menu':teaSubMenus.menus == null || teaSubMenus.menus.length == 0 || (teaSubMenus.menus.length == 1 && teaSubMenus.menus[0].alwaysActive), 'without-secondary-menu':teaSubMenus.alwaysMenu == null || teaSubMenus.alwaysMenu.items.length <= 1, 'without-footer':!teaShowOpenSourceInfo}" v-cloak="">
<!-- 操作菜单 -->
<div class="ui top menu tabular tab-menu small" v-if="teaTabbar.length > 0">
<a class="item" v-for="item in teaTabbar" :class="{'active':item.active,right:item.right}" :href="item.url">
<div class="ui top menu tabular tab-menu small" v-if="teaTabbar.length > 1">
<a class="item" v-for="item in teaTabbar" :class="{'active':item.isActive && !item.isDisabled, right:item.isRight, title: item.isTitle, icon: item.icon != null && item.icon.length > 0, disabled: item.isDisabled}" :href="item.url">
<var>{{item.name}}<span v-if="item.subName.length > 0">({{item.subName}})</span><i class="icon small" :class="item.icon" v-if="item.icon != null && item.icon.length > 0"></i> </var>
<var v-if="item.isTitle && typeof _data.node == 'object'">{{node.name}}</var>
<div class="bottom-indicator" v-if="item.isActive && !item.isTitle"></div>
</a>
</div>

View File

@@ -578,13 +578,13 @@ body.expanded .main {
.main {
.tab-menu {
margin-top: 1em !important;
margin-top: 0.3em !important;
margin-bottom: 0 !important;
overflow-x: auto;
overflow-y: hidden;
.item {
padding: 1em !important;
padding: 0 1em !important;
var {
font-style: normal;
@@ -600,8 +600,44 @@ body.expanded .main {
}
}
.item.active {
.item.active.title {
font-weight: normal !important;
margin-right: 1em !important;
border-radius: 0 !important;
}
.item:hover {
background: #f8f8f9 !important;
border-width: 1px;
}
.item.active:not(.title) {
font-weight: normal !important;
border: none;
border-radius: 0 !important;
color: #2185d0 !important;
.bottom-indicator {
border-bottom: 1px #2185d0 solid;
position: absolute;
left: 0;
right: 0;
bottom: 1px;
}
}
.item.active:not(.title).icon {
.bottom-indicator {
border-bottom: 1px #2185d0 solid;
position: absolute;
left: 0;
right: 0.6em;
bottom: 1px;
}
}
.item.active.blue {
font-weight: bold !important;
}
}
@@ -610,7 +646,6 @@ body.expanded .main {
}
}
.main .go-top-btn {
position: fixed;
right: 2.6em;

View File

@@ -52,20 +52,20 @@
opacity: 0.1;
}
.left-box.tiny {
top: 10.5em;
top: 10em;
}
.left-box.without-tabbar {
top: 3em;
}
.left-box.with-menu {
top: 10em;
top: 8.7em;
}
.left-box.without-menu {
top: 6em;
}
.right-box {
position: fixed;
top: 7.5em;
top: 7em;
bottom: 1.3em;
right: 0;
left: 18em;
@@ -73,6 +73,12 @@
padding-bottom: 2em;
overflow-y: auto;
}
.right-box h4:first-child {
margin-top: 1em;
}
.right-box > .comment:first-child {
margin-top: 0.5em;
}
@media screen and (max-width: 512px) {
.right-box {
left: 13em;
@@ -83,7 +89,7 @@ body.expanded .right-box {
left: 10em;
}
.right-box.tiny {
top: 10.4em;
top: 10em;
left: 26.5em;
}
.right-box::-webkit-scrollbar {
@@ -93,7 +99,7 @@ body.expanded .right-box {
top: 3em;
}
.right-box.with-menu {
top: 10em;
top: 8.6em;
}
.right-box.without-menu {
top: 6em;

View File

@@ -1 +1 @@
{"version":3,"sources":["@left_menu.less"],"names":[],"mappings":"AAAA;EACC,UAAA;EACA,eAAA;EACA,UAAA;EACA,aAAA;EACA,gBAAA;EACA,kBAAA;EACA,4BAAA;;AAPD,SASC;EACC,qBAAA;;AAVF,SASC,MAGC;EACC,gBAAA;EACA,kBAAA;EACA,4BAAA;;AAfH,SASC,MAGC,MAKC;EACC,kBAAA;EACA,QAAA;EACA,OAAA;EACA,kBAAA;;AArBJ,SASC,MAgBC,MAAK;EACJ,6BAAA;EACA,cAAA;EACA,iBAAA;EACA,wBAAA;EACA,2BAAA;;AA9BH,SASC,MAwBC,MAAK,GACJ;EACC,8BAAA;;AAnCJ,SASC,MA8BC,MAAK,IACJ,KACC;EACC,kBAAA;EACA,mBAAA;EACA,YAAA;EACA,cAAA;EACA,YAAA;EACA,kBAAA;EACA,gBAAA;;AAhDL,SASC,MA6CC;EACC,6BAAA;EACA,0BAAA;EACA,8BAAA;;AAQH,SAAS;EACR,UAAA;;AAGD,SAAS;EACR,YAAA;;AAGD,SAAS;EACR,WAAA;;AAGD,SAAS;EACR,QAAA;;AAGD,SAAS;EACR,SAAA;;AAGD,SAAS;EACR,QAAA;;AAGD;EACC,eAAA;EACA,UAAA;EACA,aAAA;EACA,QAAA;EACA,UAAA;EACA,kBAAA;EACA,mBAAA;EACA,gBAAA;;AAGD,mBAAqC;EACpC;IACC,UAAA;IACA,kBAAA;;;AAIF,IAAI,SAAU;EACb,UAAA;;AAGD,UAAU;EACT,WAAA;EACA,YAAA;;AAGD,UAAU;EACT,UAAA;;AAGD,UAAU;EACT,QAAA;;AAGD,UAAU;EACT,SAAA;;AAGD,UAAU;EACT,QAAA;;AAID,KAAK,eAAgB;EACpB,aAAA;;AAID,iBAAiB;EAChB,UAAA","file":"@left_menu.css"}
{"version":3,"sources":["@left_menu.less"],"names":[],"mappings":"AAAA;EACC,UAAA;EACA,eAAA;EACA,UAAA;EACA,aAAA;EACA,gBAAA;EACA,kBAAA;EACA,4BAAA;;AAPD,SASC;EACC,qBAAA;;AAVF,SASC,MAGC;EACC,gBAAA;EACA,kBAAA;EACA,4BAAA;;AAfH,SASC,MAGC,MAKC;EACC,kBAAA;EACA,QAAA;EACA,OAAA;EACA,kBAAA;;AArBJ,SASC,MAgBC,MAAK;EACJ,6BAAA;EACA,cAAA;EACA,iBAAA;EACA,wBAAA;EACA,2BAAA;;AA9BH,SASC,MAwBC,MAAK,GACJ;EACC,8BAAA;;AAnCJ,SASC,MA8BC,MAAK,IACJ,KACC;EACC,kBAAA;EACA,mBAAA;EACA,YAAA;EACA,cAAA;EACA,YAAA;EACA,kBAAA;EACA,gBAAA;;AAhDL,SASC,MA6CC;EACC,6BAAA;EACA,0BAAA;EACA,8BAAA;;AAQH,SAAS;EACR,UAAA;;AAGD,SAAS;EACR,YAAA;;AAGD,SAAS;EACR,SAAA;;AAGD,SAAS;EACR,QAAA;;AAGD,SAAS;EACR,UAAA;;AAGD,SAAS;EACR,QAAA;;AAGD;EACC,eAAA;EACA,QAAA;EACA,aAAA;EACA,QAAA;EACA,UAAA;EACA,kBAAA;EACA,mBAAA;EACA,gBAAA;;AARD,UAUC,GAAE;EACD,eAAA;;AAIF,UAAW,WAAU;EACpB,iBAAA;;AAGD,mBAAqC;EACpC;IACC,UAAA;IACA,kBAAA;;;AAIF,IAAI,SAAU;EACb,UAAA;;AAGD,UAAU;EACT,SAAA;EACA,YAAA;;AAGD,UAAU;EACT,UAAA;;AAGD,UAAU;EACT,QAAA;;AAGD,UAAU;EACT,UAAA;;AAGD,UAAU;EACT,QAAA;;AAID,KAAK,eAAgB;EACpB,aAAA;;AAID,iBAAiB;EAChB,UAAA","file":"@left_menu.css"}

View File

@@ -72,7 +72,7 @@
}
.left-box.tiny {
top: 10.5em;
top: 10em;
}
.left-box.without-tabbar {
@@ -80,7 +80,7 @@
}
.left-box.with-menu {
top: 10em;
top: 8.7em;
}
.left-box.without-menu {
@@ -89,13 +89,21 @@
.right-box {
position: fixed;
top: 7.5em;
top: 7em;
bottom: 1.3em;
right: 0;
left: 18em;
padding-right: 2em;
padding-bottom: 2em;
overflow-y: auto;
h4:first-child {
margin-top: 1em;
}
}
.right-box > .comment:first-child {
margin-top: 0.5em;
}
@media screen and (max-width: 512px) {
@@ -110,7 +118,7 @@ body.expanded .right-box {
}
.right-box.tiny {
top: 10.4em;
top: 10em;
left: 26.5em;
}
@@ -123,7 +131,7 @@ body.expanded .right-box {
}
.right-box.with-menu {
top: 10em;
top: 8.6em;
}
.right-box.without-menu {

View File

@@ -1,10 +0,0 @@
h3 {
text-align: center;
}
.main {
padding: 0;
}
table img {
width: 100%;
}
/*# sourceMappingURL=qq.css.map */

View File

@@ -1 +0,0 @@
{"version":3,"sources":["qq.less"],"names":[],"mappings":"AAAA;EACC,kBAAA;;AAGD;EACC,UAAA;;AAGD,KAAM;EACL,WAAA","file":"qq.css"}

View File

@@ -1,8 +0,0 @@
{$layout "layout_popup"}
<h3>QQ群 &nbsp;<span>659832182</span></h3>
<table class="ui table">
<tr>
<td><img src="/images/qq-group-qrcode.png"/></td>
</tr>
</table>

View File

@@ -1,11 +0,0 @@
h3 {
text-align: center;
}
.main {
padding: 0;
}
table img {
width: 100%;
}

View File

@@ -2,5 +2,6 @@
<menu-item href="/clusters" code="index">集群&nbsp;<span class="small">({{totalNodeClusters}})</span></menu-item>
<menu-item href="/clusters/nodes" code="node">节点&nbsp;<span class="small">({{totalNodes}})</span></menu-item>
<span class="disabled item">|</span>
<menu-item href="/clusters/create" code="create">[创建集群]</menu-item>
<menu-item href="/clusters/create" code="create">[创建集群]</menu-item>
<menu-item href="/clusters/createNode" code="createNode">[创建节点]</menu-item>
</first-menu>

View File

@@ -6,7 +6,10 @@
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="clusterId" :value="clusterId"/>
<table class="ui table definition selectable">
<div v-if="quota.maxNodes > 0 && quota.leftNodes >= 0"><span style="color: #959da6">当前授权最多支持节点数:{{quota.maxNodes}}个,剩余节点数:{{quota.leftNodes}}个。</span></div>
<table class="ui table definition selectable">
<tr>
<td class="title">节点IP列表</td>
<td>

View File

@@ -19,6 +19,9 @@
<!-- 填写信息 -->
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success" v-show="step == 'info'">
<input type="hidden" name="clusterId" :value="clusterId"/>
<div v-if="quota.maxNodes > 0 && quota.leftNodes >= 0"><span style="color: #959da6">当前授权最多节点数:{{quota.maxNodes}}个,剩余节点数:{{quota.leftNodes}}个。</span></div>
<table class="ui table definition selectable">
<tr>
<td class="title">节点名称 *</td>

View File

@@ -16,6 +16,21 @@
<td>
<node-clusters-labels :v-primary-cluster="node.cluster" :v-secondary-clusters="node.secondaryClusters"></node-clusters-labels>
</td>
</tr>
<tr v-show="node.isBackupForCluster || node.isBackupForGroup">
<td>备用节点</td>
<td>
<span class="ui label basic small" v-if="node.isBackupForCluster">集群备用节点</span>
<span class="ui label basic small" v-if="node.isBackupForGroup">分组备用节点</span>
&nbsp; <a :href="'/clusters/cluster/node/settings/schedule?clusterId=' + clusterId + '&nodeId=' + node.id" style="font-size: 0.8em">[修改]</a>
</td>
</tr>
<tr v-show="node.offlineDay.length > 0">
<td>租期结束日期</td>
<td>
{{node.offlineDay.substring(0, 4)}}-{{node.offlineDay.substring(4, 6)}}-{{node.offlineDay.substring(6, 8)}} &nbsp; <a :href="'/clusters/cluster/node/settings/schedule?clusterId=' + clusterId + '&nodeId=' + node.id" style="font-size: 0.8em">[修改]</a>
<p class="comment" v-if="node.isOffline"><span class="red">已到期</span></p>
</td>
</tr>
<tr>
<td>IP地址</td>
@@ -60,6 +75,7 @@
<th>记录类型</th>
<th>线路</th>
<th>记录值</th>
<th>状态</th>
</tr>
</thead>
@@ -72,6 +88,11 @@
<span v-else class="disabled">默认</span>
</td>
<td>{{record.value}}</td>
<td>
<span v-if="record.isBackup" class="red">备用节点</span>
<span v-else-if="record.isOffline" class="red">已下线</span>
<span v-else>正常</span>
</td>
</tr>
</table>
<p class="comment" v-if="!dnsIsExcludingLnNode">通过设置A记录可以将集群上的服务请求转发到不同线路的节点上。</p>

View File

@@ -76,6 +76,11 @@
<tr v-for="(node, nodeIndex) in nodes">
<td class="node-name-td"><a :href="'/clusters/cluster/node?clusterId=' + clusterId + '&nodeId=' + node.id">{{node.name}}<sup v-if="node.level > 1"><span class="blue"> &nbsp;L{{node.level}}</span></sup></a>
<a :href="'/clusters/cluster/node/settings/schedule?clusterId=' + node.cluster.id + '&nodeId=' + node.id">
<sup v-if="node.isBackup"><span class="blue"> &nbsp;备用</span></sup>
<sup v-if="node.offlineDay != null && node.offlineDay.length > 0"><span class="blue"> &nbsp;到{{node.offlineDay.substring(0, 4)}}-{{node.offlineDay.substring(4, 6)}}-{{node.offlineDay.substring(6, 8)}}</span></sup>
</a>
<a :href="'/clusters/cluster/node/update?clusterId=' + clusterId + '&nodeId=' + node.id" title="设置"><i class="icon setting grey"></i></a>
<div v-if="node.region != null">

View File

@@ -131,6 +131,13 @@
<p class="comment">选中后表示支持HTTP/1.0、HTTP/0.9等低于HTTP/1.1版本的HTTP协议。低版本HTTP协议不支持分段传输内容且无法保持连接对系统性能有严重的负面影响。建议只有在你的用户正在使用非常老旧的设备时才启用此选项。</p>
</td>
</tr>
<tr>
<td>自动匹配证书</td>
<td>
<checkbox name="httpAllMatchCertFromAllServers" v-model="config.httpAll.matchCertFromAllServers"></checkbox>
<p class="comment">选中后,表示找不到证书时自动查找其他网站设置的证书。此功能仅仅为了兼容以往系统版本,可能会导致用户访问的网站混乱,所以请不要轻易启用。</p>
</td>
</tr>
</table>
<submit-btn></submit-btn>

View File

@@ -5,7 +5,7 @@
<div class="right-box with-menu">
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="clusterId" :value="clusterId"/>
<health-check-config-box :v-health-check-config="healthCheckConfig" :v-check-domain-url="'/clusters/cluster/settings/health/checkDomain?clusterId=' + clusterId"></health-check-config-box>
<health-check-config-box :v-health-check-config="healthCheckConfig" :v-check-domain-url="'/clusters/cluster/settings/health/checkDomain?clusterId=' + clusterId" :v-is-plus="teaIsPlus"></health-check-config-box>
<submit-btn></submit-btn> &nbsp; <a href="" @click.prevent="run()" v-if="healthCheckConfig != null && healthCheckConfig.isOn">立即检查</a>
</form>
</div>

View File

@@ -4,7 +4,7 @@ Tea.context(function () {
this.run = function () {
teaweb.confirm("确定要对当前集群下的所有节点进行健康检查吗?", function () {
teaweb.popup("/clusters/cluster/settings/health/runPopup?clusterId=" + this.clusterId, {
height: "25em"
height: "30em"
})
})
}

View File

@@ -2,27 +2,32 @@
<h3>健康检查</h3>
<span class="red" v-if="isRequesting">正在执行中,请等待执行完毕...</span>
<span class="red" v-if="!isRequesting && errorString.length > 0">{{errorString}}</span>
<div v-show="!hasServers">
<span class="red">当前集群尚未部署网站,无法进行健康检查;请至少部署至少一个网站后再试。</span>
</div>
<div v-show="hasServers">
<span class="red" v-if="isRequesting">正在执行中,请等待执行完毕...</span>
<span class="red" v-if="!isRequesting && errorString.length > 0">{{errorString}}</span>
<form method="post" class="ui form" v-if="!isRequesting && errorString.length == 0">
<p>成功节点:<span class="green">{{countSuccess}}</span> &nbsp; 失败节点:<span class="red">{{countFail}}</span></p>
<table class="ui table selectable celled" v-if="results.length > 0">
<thead>
<tr>
<th>节点</th>
<th>结果</th>
<th nowrap="">耗时</th>
</tr>
</thead>
<tr v-for="result in results">
<td>{{result.node.name}}<span class="small" v-if="result.nodeAddr != null && result.nodeAddr.length > 0">{{result.nodeAddr}}</span></td>
<td>
<span v-if="!result.isOk" class="red">失败:{{result.error}}</span>
<span v-else class="green">成功</span>
</td>
<td>{{result.costMs}}ms</td>
</tr>
</table>
<button class="ui button primary" type="button" @click.prevent="success">完成</button>
</form>
<form method="post" class="ui form" v-if="!isRequesting && errorString.length == 0">
<p>成功节点:<span class="green">{{countSuccess}}</span> &nbsp; 失败节点:<span class="red">{{countFail}}</span></p>
<table class="ui table selectable celled" v-if="results.length > 0">
<thead>
<tr>
<th>节点</th>
<th>结果</th>
<th nowrap="">耗时</th>
</tr>
</thead>
<tr v-for="result in results">
<td>{{result.node.name}}<span class="small" v-if="result.nodeAddr != null && result.nodeAddr.length > 0">{{result.nodeAddr}}</span></td>
<td>
<span v-if="!result.isOk" class="red">失败:{{result.error}}</span>
<span v-else class="green">成功</span>
</td>
<td>{{result.costMs}}ms</td>
</tr>
</table>
<button class="ui button primary" type="button" @click.prevent="success">完成</button>
</form>
</div>

View File

@@ -8,7 +8,9 @@ Tea.context(function () {
this.errorString = ""
this.$delay(function () {
this.run()
if (this.hasServers) {
this.run()
}
})
this.run = function () {

View File

@@ -10,7 +10,7 @@
<span class="item disabled">|</span>
<menu-item @click.prevent="createItem">[添加指标]</menu-item>
<span class="item disabled">|</span>
<span class="item"><tip-icon content="在这里设置的指标,会自动应用到部署在当前集群上的所有服务上。<br/><br/>指标收集和运算通常需要消耗一定量的边缘节点系统资源,所以请谨慎选择。"></tip-icon></span>
<span class="item"><tip-icon content="在这里设置的指标,会自动应用到部署在当前集群上的所有网站上。<br/><br/>指标收集和运算通常需要消耗一定量的边缘节点系统资源,所以请谨慎选择。"></tip-icon></span>
</first-menu>
<p class="comment" v-if="items.length == 0">暂时还没有添加指标。</p>

View File

@@ -3,7 +3,9 @@
{$template "/left_menu_with_menu"}
<div class="right-box with-menu">
<tip-icon content="TCP Option Address(TOA)可以在TCP选项中传递客户端IP多用在TCP负载均衡的源站需要获取客户端真实IP的场景。<br/><br/>注意HTTP协议通常不需要此设置。<br/><br/>如需修改配置,请在专业人士指导下操作。"></tip-icon>
<first-menu>
<menu-item><tip-icon content="TCP Option Address(TOA)可以在TCP选项中传递客户端IP多用在TCP负载均衡的源站需要获取客户端真实IP的场景。<br/><br/>注意HTTP协议通常不需要此设置。<br/><br/>如需修改配置,请在专业人士指导下操作。"></tip-icon></menu-item>
</first-menu>
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="clusterId" :value="clusterId"/>
<csrf-token></csrf-token>

View File

@@ -11,7 +11,7 @@
<td class="title">启用WebP功能</td>
<td>
<checkbox name="isOn" v-model="webpPolicy.isOn"></checkbox>
<p class="comment">选中后,表示当前集群下的服务可以使用WebP转换功能。</p>
<p class="comment">选中后,表示当前集群下的网站可以使用WebP转换功能。</p>
</td>
</tr>
<tbody v-show="webpPolicy.isOn">

View File

@@ -0,0 +1,14 @@
{$layout}
{$template "menu"}
<div class="margin"></div>
<form class="ui form">
<table class="ui table definition selectable">
<tr>
<td class="title">选择所属集群 *</td>
<td><node-cluster-combo-box @change="changeCluster"></node-cluster-combo-box></td>
</tr>
</table>
<button type="button" class="ui button primary" :class="{disabled: clusterId <= 0}" @click.prevent="goNext">下一步 <i class="icon right arrow"></i></button>
</form>

View File

@@ -0,0 +1,13 @@
Tea.context(function () {
this.clusterId = 0
this.changeCluster = function(clusterId) {
this.clusterId = clusterId
}
this.goNext = function () {
if (this.clusterId > 0) {
window.location = "/clusters/cluster/createNode?clusterId=" + this.clusterId
}
}
})

View File

@@ -35,7 +35,7 @@
<th>集群名称</th>
<th class="center width10">节点数</th>
<th class="center width10">在线节点数</th>
<th class="center width10">服务</th>
<th class="center width10">网站</th>
<th>DNS域名</th>
<th class="two op">操作</th>
</tr>

View File

@@ -68,7 +68,13 @@
</tr>
</thead>
<tr v-for="(node, nodeIndex) in nodes">
<td class="node-name-td"><a :href="'/clusters/cluster/node?clusterId=' + node.cluster.id + '&nodeId=' + node.id">{{node.name}}<sup v-if="node.level > 1"><span class="blue"> &nbsp;L{{node.level}}</span></sup></a>
<td class="node-name-td"><a :href="'/clusters/cluster/node?clusterId=' + node.cluster.id + '&nodeId=' + node.id">{{node.name}}
<sup v-if="node.level > 1"><span class="blue"> &nbsp;L{{node.level}}</span></sup>
</a>
<a :href="'/clusters/cluster/node/settings/schedule?clusterId=' + node.cluster.id + '&nodeId=' + node.id">
<sup v-if="node.isBackup"><span class="blue"> &nbsp;备用</span></sup>
<sup v-if="node.offlineDay != null && node.offlineDay.length > 0"><span class="blue"> &nbsp;到{{node.offlineDay.substring(0, 4)}}-{{node.offlineDay.substring(4, 6)}}-{{node.offlineDay.substring(6, 8)}}</span></sup>
</a>
<a :href="'/clusters/cluster/node/update?clusterId=' + node.cluster.id + '&nodeId=' + node.id" title="设置"><i class="icon setting grey"></i></a>

View File

@@ -32,7 +32,7 @@
<span v-if="task.type == 'scriptsChanged'">同步脚本</span>
<span v-if="task.type == 'nodeLevelChanged'">同步L2节点</span>
<span v-if="task.type == 'ddosProtectionChanged'">DDoS配置</span>
<span v-if="task.type == 'userServersStateChanged'">用户服务状态</span>
<span v-if="task.type == 'userServersStateChanged'">用户网站状态</span>
</td>
<td>
<span v-if="task.isDone" class="red">{{task.error}}</span>

View File

@@ -67,7 +67,7 @@
</thead>
<tr v-for="task in tasks">
<td>
<span v-if="task.type == 'clusterChange'">{{task.cluster.name}}
<span v-if="(task.type == 'clusterChange' || task.type == 'clusterNodesChange') && task.cluster != null">{{task.cluster.name}}
<link-icon :href="'/dns/clusters/cluster?clusterId=' + task.cluster.id" target="_top"></link-icon>
</span>
<span v-if="task.type == 'nodeChange'">{{task.node.name}}</span>
@@ -75,9 +75,9 @@
<span v-if="task.type == 'domainChange'">{{task.domain.name}}</span>
</td>
<td>
<span v-if="task.type == 'clusterChange'">集群</span>
<span v-if="task.type == 'clusterChange' || task.type == 'clusterNodesChange'">集群</span>
<span v-if="task.type == 'nodeChange'">节点</span>
<span v-if="task.type == 'serverChange'">服务</span>
<span v-if="task.type == 'serverChange'">网站</span>
<span v-if="task.type == 'domainChange'">域名</span>
</td>
<td style="word-break: break-word; width: 26em">
@@ -164,11 +164,15 @@
<link-red v-else title="点击设置" @click.prevent="updateNode(node.clusterId, node.id, node.ipAddrId)">没有设置</link-red>
</td>
<td>
<span v-if="node.isInstalled">
<span class="green" v-if="node.isResolved">解析</span>
<span v-else class="red">未解析</span>
</span>
<link-red :href="'/clusters/cluster/node/install?clusterId=' + cluster.id + '&nodeId=' + node.id" v-if="!node.isInstalled" title="节点未安装"><span class="red">未安装</span></link-red>
<span v-if="node.isBackup" class="red">备用节点</span>
<span v-else-if="node.isOffline" class="red">下线</span>
<div v-else="">
<span v-if="node.isInstalled">
<span class="green" v-if="node.isResolved">已解析</span>
<span v-else class="red">未解析</span>
</span>
<link-red :href="'/clusters/cluster/node/install?clusterId=' + cluster.id + '&nodeId=' + node.id" v-if="!node.isInstalled" title="节点未安装"><span class="red">未安装</span></link-red>
</div>
</td>
<td>
<link-popup @click.prevent="updateNode(node.clusterId, node.id, node.ipAddrId)">修改</link-popup>
@@ -182,7 +186,7 @@
<table class="ui table selectable celled" v-if="servers.length > 0">
<thead>
<tr>
<th>服务</th>
<th>网站</th>
<th>子域名</th>
<th>记录类型</th>
<th>记录值</th>

View File

@@ -1,6 +1,6 @@
{$layout "layout_popup"}
<h3>使用域名"{{domain}}"的服务</h3>
<h3>使用域名"{{domain}}"的网站</h3>
<form class="ui form" action="/dns/domains/serversPopup" method="get">
<input type="hidden" name="domainId" :value="domainId"/>
<div class="ui fields inline">
@@ -21,7 +21,7 @@
<thead>
<tr>
<th>集群</th>
<th>服务</th>
<th>网站</th>
<th>子域名</th>
<th>CNAME</th>
<th class="width10">解析状态</th>

View File

@@ -211,7 +211,7 @@
<th class="center" style="width: 7em">线路</th>
<th class="center" style="width: 6em">集群</th>
<th class="center" style="width: 7em">节点域名</th>
<th class="center" style="width: 7em">服务域名</th>
<th class="center" style="width: 7em">网站域名</th>
<th>数据更新时间</th>
<th class="center width10">状态</th>
<th class="three op">操作</th>

View File

@@ -15,7 +15,7 @@
</thead>
<tr v-for="task in tasks">
<td nowrap="">
<span v-if="(task.type == 'clusterChange' || task.type == 'clusterRemoveDomain') && task.cluster != null">{{task.cluster.name}}
<span v-if="(task.type == 'clusterChange' || task.type == 'clusterNodesChange' || task.type == 'clusterRemoveDomain') && task.cluster != null">{{task.cluster.name}}
<link-icon :href="'/dns/clusters/cluster?clusterId=' + task.cluster.id" target="_top"></link-icon>
</span>
<span v-if="task.type == 'nodeChange'">{{task.node.name}}</span>
@@ -23,9 +23,9 @@
<span v-if="task.type == 'domainChange'">{{task.domain.name}}</span>
</td>
<td nowrap="">
<span v-if="task.type == 'clusterChange' || task.type == 'clusterRemoveDomain'">集群</span>
<span v-if="task.type == 'clusterChange' || task.type == 'clusterNodesChange' || task.type == 'clusterRemoveDomain'">集群</span>
<span v-if="task.type == 'nodeChange'">节点</span>
<span v-if="task.type == 'serverChange'">服务</span>
<span v-if="task.type == 'serverChange'">网站</span>
<span v-if="task.type == 'domainChange'">域名</span>
</td>
<td style="word-break: break-word; width: 26em">

View File

@@ -36,7 +36,7 @@
<td>回源主机名</td>
<td>
<input type="text" name="host" placeholder="比如example.com" maxlength="100"/>
<p class="comment">请求源站时的Host字段值用于修改源站接收到的域名<span v-if="isHTTP">,支持请求变量</span></p>
<p class="comment">请求源站时的Host字段值用于设置源站接收到的域名<span v-if="isHTTP">,支持请求变量</span></p>
</td>
</tr>
<tr>

View File

@@ -4,7 +4,7 @@ Tea.context(function () {
this.addrError = ""
// 当前服务协议
// 当前网站协议
this.isHTTP = (this.serverType == "httpProxy" || this.serverType == "httpWeb")
if (this.serverType == "httpProxy") {
this.protocol = "http"

View File

@@ -1,6 +1,6 @@
{$layout "layout_popup"}
<h3>选择证书 <span v-if="searchingDomains.length > 0">(当前服务域名:{{searchingDomains[0]}}<var style="font-style: normal" v-if="searchingDomains.length > 1">等{{searchingDomains.length}}个域名</var></span></h3>
<h3>选择证书 <span v-if="searchingDomains.length > 0">(当前网站域名:{{searchingDomains[0]}}<var style="font-style: normal" v-if="searchingDomains.length > 1">等{{searchingDomains.length}}个域名</var></span></h3>
<!-- 搜索表单 -->
<form class="ui form" action="/servers/certs/selectPopup" ref="searchForm">

View File

@@ -116,6 +116,7 @@
<!-- 其余数据 -->
<textarea rows="3" maxlength="4096" name="value" v-model="rule.value" @input="changeRuleValue" v-else></textarea>
<p class="comment" v-if="(rule.operator == 'match' || rule.operator == 'not match') && rule.value.match(/\n/)"><span class="red">警告:发现你填写的正则表达式中包含了换行符,如果你的意图是每行都表示不同的选项,那么请使用竖杠(<code-label>|</code-label>)符号代替换行符,比如把<code-label>a换行b换行c换行</code-label>改成<code-label>a|b|c</code-label><a href="" @click.prevent="convertValueLine">[帮我转换]</a></span></p>
<!-- 特殊规则 -->
<div style="margin-top: 1em">

View File

@@ -122,6 +122,21 @@ Tea.context(function () {
}
}
this.convertValueLine = function () {
let value = this.rule.value
if (value != null && value.length > 0) {
let lines = value.split(/\n/)
let resultLines = []
lines.forEach(function (line) {
line = line.trim()
if (line.length > 0) {
resultLines.push(line)
}
})
this.rule.value = resultLines.join("|")
}
}
/**
* 正则测试
*/

View File

@@ -10,7 +10,7 @@
<td class="title">所属用户<optional-label></optional-label></td>
<td>
<user-selector @change="changeUserId"></user-selector>
<p class="comment">当前服务所属平台用户。</p>
<p class="comment">当前网站所属平台用户。</p>
</td>
</tr>
<tr v-if="plans.length > 0">
@@ -27,7 +27,7 @@
<td>
<div v-if="userId == 0">
<node-cluster-combo-box></node-cluster-combo-box>
<p class="comment">当前服务将会部署到所选集群的节点上。</p>
<p class="comment">当前网站将会部署到所选集群的节点上。</p>
</div>
<div v-else>跟随用户设置。</div>
</td>
@@ -89,7 +89,7 @@
<h3>次要信息:</h3>
<table class="ui table selectable definition">
<tr>
<td class="title">服务类型</td>
<td class="title">网站类型</td>
<td>
<select class="ui dropdown auto-width" name="serverType" v-model="serverType" @change="changeServerType()">
<option v-for="s in serverTypes" :value="s.code">{{s.name}}</option>
@@ -98,7 +98,7 @@
</td>
</tr>
<tr>
<td class="title">服务名称</td>
<td class="title">网站名称</td>
<td>
<input type="text" name="name" maxlength="60"/>
<p class="comment">可以是网站用途或者域名等。</p>

View File

@@ -17,7 +17,7 @@
<table class="ui table selectable celled" v-if="servers.length > 0">
<thead>
<tr>
<th>服务名称</th>
<th>网站名称</th>
<th>所属用户</th>
<th>部署集群</th>
<th>域名</th>

View File

@@ -3,6 +3,7 @@
{$template "/left_menu_without_menu"}
<div class="right-box without-menu">
<div class="margin"></div>
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="webId" :value="webId"/>
<http-header-policy-box

View File

@@ -58,7 +58,7 @@
<table class="ui table selectable celled" v-if="servers.length > 0">
<thead>
<tr>
<th>服务名称</th>
<th>网站名称</th>
<th>所属用户</th>
<th>部署集群</th>
<th>域名</th>

View File

@@ -11,5 +11,5 @@
</first-menu>
<div class="delete-box">
<button class="ui button red large fluid" type="button" @click.prevent="deleteServer(serverId)">删除当前服务</button>
<button class="ui button red large fluid" type="button" @click.prevent="deleteServer(serverId)">删除当前网站</button>
</div>

View File

@@ -1,6 +1,6 @@
Tea.context(function () {
this.deleteServer = function (serverId) {
teaweb.confirm("html:确定要删除当前服务吗?<br/>请慎重操作,删除后无法恢复!", function () {
teaweb.confirm("html:确定要删除当前网站吗?<br/>请慎重操作,删除后无法恢复!", function () {
this.$post("$")
.params({
"serverId": serverId

View File

@@ -3,6 +3,7 @@
{$template "/left_menu_with_menu"}
<div class="right-box with-menu">
<div class="margin"></div>
<div :class="{'opacity-mask': hasGroupConfig}">
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>

View File

@@ -8,7 +8,7 @@
<input type="hidden" name="serverId" :value="serverId"/>
<table class="ui table selectable definition">
<tr>
<td class="title">当前服务CNAME</td>
<td class="title">当前网站CNAME</td>
<td>
<span id="cname-text">{{dnsName}}.<span v-if="dnsDomain.length > 0">{{dnsDomain}}</span><span v-else>根域名</span></span> &nbsp; <copy-to-clipboard :v-target="'cname-text'"></copy-to-clipboard> &nbsp;<a href="" @click.prevent="regenerateCNAME()" style="font-size: 0.8em">[重新生成]</a> &nbsp; <a href="" @click.prevent="updateCNAME()" style="font-size: 0.8em">[手动修改]</a>
<p class="comment">你需要为你的每个<a :href="'/servers/server/settings/serverNames?serverId=' + serverId">网站域名</a>设置一个CNAME解析值为上面内容。</p>

View File

@@ -0,0 +1,16 @@
{$layout "layout_popup"}
<h3>添加非标Header</h3>
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="headerPolicyId" :value="headerPolicyId"/>
<table class="ui table definition selectable">
<tr>
<td class="title">名称<em>Name</em></td>
<td>
<input type="text" name="name" maxlength="100" ref="focus" v-model="headerName"/>
<p class="comment">比如<code-label>hello_world</code-label></p>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,7 @@
Tea.context(function () {
this.headerName = ""
this.selectHeader = function (headerName) {
this.headerName = headerName
}
})

View File

@@ -3,6 +3,7 @@
{$template "/left_menu_with_menu"}
<div class="right-box with-menu">
<div class="margin"></div>
<http-header-policy-box
:v-request-header-policy="requestHeaderPolicy"
:v-response-header-policy="responseHeaderPolicy"

View File

@@ -16,13 +16,13 @@
</div>
<div v-show="userSelectorVisible">
<user-selector style="display:inline-block"></user-selector>
<p class="comment"><span class="red">此操作同时会将与当前服务相关联的证书等数据自动修改为此用户所属。</span></p>
<p class="comment"><span class="red">此操作同时会将与当前网站相关联的证书等数据自动修改为此用户所属。</span></p>
</div>
</div>
</td>
</tr>
<tr>
<td class="title">服务名称 *</td>
<td class="title">网站名称 *</td>
<td>
<input type="text" name="name" maxlength="60" ref="focus" v-model="server.name"/>
</td>
@@ -48,11 +48,11 @@
<td class="color-border">是否保留原集群配置</td>
<td>
<checkbox name="keepOldConfigs" checked="checked"></checkbox>
<p class="comment">选中表示在先前的集群节点上仍然保留当前服务的配置,直至节点配置全部刷新时才会删除;不选中,则表示立即删除原集群上关于当前服务的配置。</p>
<p class="comment">选中表示在先前的集群节点上仍然保留当前网站的配置,直至节点配置全部刷新时才会删除;不选中,则表示立即删除原集群上关于当前网站的配置。</p>
</td>
</tr>
<tr>
<td>服务类型 *</td>
<td>网站类型 *</td>
<td>
{{typeName}}
</td>
@@ -74,13 +74,13 @@
</td>
</tr>
<tr>
<td>启用当前服务</td>
<td>启用当前网站</td>
<td>
<div class="ui checkbox">
<input type="checkbox" name="isOn" value="1" v-model="server.isOn"/>
<label></label>
</div>
<p class="comment">可以使用此选项整体关闭当前服务</p>
<p class="comment">可以使用此选项整体关闭当前网站</p>
</td>
</tr>
</tbody>

View File

@@ -1,5 +1,5 @@
<first-menu>
<menu-item :href="'/servers/server/settings/locations?serverId=' + serverId">&laquo; 返回服务设置</menu-item>
<menu-item :href="'/servers/server/settings/locations?serverId=' + serverId">&laquo; 返回网站设置</menu-item>
<span class="item disabled" style="padding-left:0;padding-right:0">|</span>
<menu-item :href="'/servers/server/settings/locations?serverId=' + serverId">路由规则</menu-item>
<raquo-item></raquo-item>

View File

@@ -7,6 +7,7 @@
{$template "../left_menu"}
<div class="right-box tiny">
<div class="margin"></div>
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="webId" :value="webId"/>
<http-header-policy-box

View File

@@ -39,7 +39,7 @@
<td>回源主机名</td>
<td>
<input type="text" name="host" placeholder="比如example.com" maxlength="100"/>
<p class="comment">请求源站时的Host字段值用于修改源站接收到的域名<span v-if="isHTTP">,支持请求变量</span></p>
<p class="comment">请求源站时的Host字段值用于设置源站接收到的域名<span v-if="isHTTP">,支持请求变量</span></p>
</td>
</tr>
<tr>

View File

@@ -40,7 +40,7 @@
<td>回源主机名</td>
<td>
<input type="text" name="host" v-model="origin.host" placeholder="比如example.com" maxlength="100"/>
<p class="comment">请求源站时的Host字段值用于修改源站接收到的域名<span v-if="isHTTP">,支持请求变量</span></p>
<p class="comment">请求源站时的Host字段值用于设置源站接收到的域名<span v-if="isHTTP">,支持请求变量</span></p>
</td>
</tr>
<tr>

Some files were not shown because too many files have changed in this diff Show More