Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
276a68bda6 | ||
|
|
fba89d197a | ||
|
|
b3259e2489 | ||
|
|
880704dda0 | ||
|
|
d5e92e9c09 | ||
|
|
df2f5692bd | ||
|
|
dacc99b8b6 | ||
|
|
0e8ade3b61 | ||
|
|
fe2d8f6261 | ||
|
|
927381039b | ||
|
|
6b95947acb | ||
|
|
b05e53ce58 | ||
|
|
a2546582f2 | ||
|
|
a023b3401e | ||
|
|
07142e9872 | ||
|
|
f5fea6c34d | ||
|
|
5eb0fb3422 | ||
|
|
fa915e7c35 | ||
|
|
2b7905d31c | ||
|
|
9cbbf37add | ||
|
|
642a0c3b0d | ||
|
|
ff47a19250 | ||
|
|
1fc1a3fbe3 | ||
|
|
996172bd69 | ||
|
|
df038314ef | ||
|
|
67881c2e34 | ||
|
|
9c691a17b2 | ||
|
|
54df86a4a2 | ||
|
|
70aff759d7 | ||
|
|
d903cc6e3b | ||
|
|
c520271ab5 | ||
|
|
f6936224d9 | ||
|
|
283984a6a6 | ||
|
|
c4fc09f72a | ||
|
|
efdbabfa04 | ||
|
|
a43af333fd | ||
|
|
1f5894ff82 | ||
|
|
79b4054e31 | ||
|
|
376d7d0c78 | ||
|
|
c8b85330ed | ||
|
|
c65dffee13 | ||
|
|
275320ad9b | ||
|
|
f71cdf9065 | ||
|
|
8e0a239de9 | ||
|
|
ed2b95bc3f | ||
|
|
2e23ee4c66 | ||
|
|
58def52e30 | ||
|
|
aea5ef3b68 | ||
|
|
f2e14cf0a6 | ||
|
|
bc9b6d1595 | ||
|
|
fd11b62390 | ||
|
|
8db00a5ab5 | ||
|
|
a6757e9374 | ||
|
|
e0fe385404 | ||
|
|
f9f4258ec1 | ||
|
|
f939d563ad | ||
|
|
fad03add54 | ||
|
|
9ab6dab081 | ||
|
|
5a55268830 | ||
|
|
fc3769239d | ||
|
|
c9fb3153eb | ||
|
|
a31f9ed9c5 | ||
|
|
2f75828ba4 | ||
|
|
89679ec358 | ||
|
|
98f77f52df | ||
|
|
ca647d44bb | ||
|
|
838b7dab5b |
@@ -54,7 +54,7 @@
|
|||||||
* [管理平台](https://github.com/TeaOSLab/EdgeAdmin)
|
* [管理平台](https://github.com/TeaOSLab/EdgeAdmin)
|
||||||
|
|
||||||
## 联系我们
|
## 联系我们
|
||||||
有什么问题和建议都可以加入QQ群 `659832182` 或者 [Telegram群](https://t.me/+5kVCMGxQhZxiODY9) 。
|
有什么问题和建议都可以加入 [Telegram群](https://t.me/+5kVCMGxQhZxiODY9)
|
||||||
|
|
||||||
## 企业版
|
## 企业版
|
||||||
* [GoEdge企业版](https://goedge.cn/commercial) - 功能更强大的CDN系统
|
* [GoEdge企业版](https://goedge.cn/commercial) - 功能更强大的CDN系统
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
这个目录下我们列举了所有需要公开声明的第三方License,如果有遗漏,烦请告知 iwind.liu@gmail.com。再次感谢这些开源软件项目和贡献人员!
|
这个目录下我们列举了所有需要公开声明的第三方License,如果有遗漏,烦请告知 goedge.cdn@gmail.com。再次感谢这些开源软件项目和贡献人员!
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
LABEL maintainer="iwind.liu@gmail.com"
|
LABEL maintainer="goedge.cdn@gmail.com"
|
||||||
ENV TZ "Asia/Shanghai"
|
ENV TZ "Asia/Shanghai"
|
||||||
ENV VERSION 0.6.4.2
|
ENV VERSION 1.1.0
|
||||||
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
|
||||||
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"
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ func AllModuleMaps() []maps.Map {
|
|||||||
"url": "/dashboard",
|
"url": "/dashboard",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "网站服务",
|
"name": "网站列表",
|
||||||
"code": AdminModuleCodeServer,
|
"code": AdminModuleCodeServer,
|
||||||
"url": "/servers",
|
"url": "/servers",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ func LoadSecurityConfig() (*systemconfigs.SecurityConfig, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
v := reflect.Indirect(reflect.ValueOf(config)).Interface().(systemconfigs.SecurityConfig)
|
var v = reflect.Indirect(reflect.ValueOf(config)).Interface().(systemconfigs.SecurityConfig)
|
||||||
return &v, nil
|
return &v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +83,12 @@ func loadSecurityConfig() (*systemconfigs.SecurityConfig, error) {
|
|||||||
return sharedSecurityConfig, nil
|
return sharedSecurityConfig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
config := &systemconfigs.SecurityConfig{}
|
var config = &systemconfigs.SecurityConfig{
|
||||||
|
Frame: FrameSameOrigin,
|
||||||
|
AllowLocal: true,
|
||||||
|
CheckClientFingerprint: false,
|
||||||
|
CheckClientRegion: true,
|
||||||
|
}
|
||||||
err = json.Unmarshal(resp.ValueJSON, config)
|
err = json.Unmarshal(resp.ValueJSON, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logs.Println("[SECURITY_MANAGER]" + err.Error())
|
logs.Println("[SECURITY_MANAGER]" + err.Error())
|
||||||
@@ -100,7 +105,9 @@ func loadSecurityConfig() (*systemconfigs.SecurityConfig, error) {
|
|||||||
|
|
||||||
func defaultSecurityConfig() *systemconfigs.SecurityConfig {
|
func defaultSecurityConfig() *systemconfigs.SecurityConfig {
|
||||||
return &systemconfigs.SecurityConfig{
|
return &systemconfigs.SecurityConfig{
|
||||||
Frame: FrameSameOrigin,
|
Frame: FrameSameOrigin,
|
||||||
AllowLocal: true,
|
AllowLocal: true,
|
||||||
|
CheckClientFingerprint: false,
|
||||||
|
CheckClientRegion: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package teaconst
|
package teaconst
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Version = "1.0.0"
|
Version = "1.1.0"
|
||||||
|
|
||||||
APINodeVersion = "1.0.0"
|
APINodeVersion = "1.1.0"
|
||||||
|
|
||||||
ProductName = "Edge Admin"
|
ProductName = "Edge Admin"
|
||||||
ProcessName = "edge-admin"
|
ProcessName = "edge-admin"
|
||||||
|
|||||||
@@ -85,6 +85,9 @@ func (this *AdminNode) Run() {
|
|||||||
// 启动API节点
|
// 启动API节点
|
||||||
this.startAPINode()
|
this.startAPINode()
|
||||||
|
|
||||||
|
// 启动IP库
|
||||||
|
this.startIPLibrary()
|
||||||
|
|
||||||
// 启动Web服务
|
// 启动Web服务
|
||||||
sessionManager, err := NewSessionManager()
|
sessionManager, err := NewSessionManager()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
18
internal/nodes/admin_node_ext.go
Normal file
18
internal/nodes/admin_node_ext.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||||
|
//go:build !plus
|
||||||
|
|
||||||
|
package nodes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
|
||||||
|
"github.com/iwind/TeaGo/logs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 启动IP库
|
||||||
|
func (this *AdminNode) startIPLibrary() {
|
||||||
|
logs.Println("[NODE]initializing ip library ...")
|
||||||
|
err := iplibrary.InitDefault()
|
||||||
|
if err != nil {
|
||||||
|
logs.Println("[NODE]initialize ip library failed: "+err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,6 +40,7 @@ func (this *SessionManager) Read(sid string) map[string]string {
|
|||||||
resp, err := rpcClient.LoginSessionRPC().FindLoginSession(rpcClient.Context(0), &pb.FindLoginSessionRequest{Sid: sid})
|
resp, err := rpcClient.LoginSessionRPC().FindLoginSession(rpcClient.Context(0), &pb.FindLoginSessionRequest{Sid: sid})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logs.Println("SESSION", "read '"+sid+"' failed: "+err.Error())
|
logs.Println("SESSION", "read '"+sid+"' failed: "+err.Error())
|
||||||
|
result["@error"] = err.Error()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ package rpc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,3 +31,23 @@ func SharedRPC() (*RPCClient, error) {
|
|||||||
sharedRPC = client
|
sharedRPC = client
|
||||||
return sharedRPC, nil
|
return sharedRPC, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsConnError 是否为连接错误
|
||||||
|
func IsConnError(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否为连接错误
|
||||||
|
statusErr, ok := status.FromError(err)
|
||||||
|
if ok {
|
||||||
|
var errorCode = statusErr.Code()
|
||||||
|
return errorCode == codes.Unavailable || errorCode == codes.Canceled
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(err.Error(), "code = Canceled") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
79
internal/utils/apinodeutils/deploy_file.go
Normal file
79
internal/utils/apinodeutils/deploy_file.go
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package apinodeutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DeployFile 部署文件描述
|
||||||
|
type DeployFile struct {
|
||||||
|
OS string
|
||||||
|
Arch string
|
||||||
|
Version string
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sum 计算概要
|
||||||
|
func (this *DeployFile) Sum() (string, error) {
|
||||||
|
fp, err := os.Open(this.Path)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = fp.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
m := md5.New()
|
||||||
|
buffer := make([]byte, 128*1024)
|
||||||
|
for {
|
||||||
|
n, err := fp.Read(buffer)
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
_, err = m.Write(buffer[:n])
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sum := m.Sum(nil)
|
||||||
|
return fmt.Sprintf("%x", sum), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read 读取一个片段数据
|
||||||
|
func (this *DeployFile) Read(offset int64) (data []byte, newOffset int64, err error) {
|
||||||
|
fp, err := os.Open(this.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, offset, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = fp.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
stat, err := fp.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return nil, offset, err
|
||||||
|
}
|
||||||
|
if offset >= stat.Size() {
|
||||||
|
return nil, offset, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = fp.Seek(offset, io.SeekStart)
|
||||||
|
if err != nil {
|
||||||
|
return nil, offset, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer := make([]byte, 128*1024)
|
||||||
|
n, err := fp.Read(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, offset, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer[:n], offset + int64(n), nil
|
||||||
|
}
|
||||||
96
internal/utils/apinodeutils/deploy_manager.go
Normal file
96
internal/utils/apinodeutils/deploy_manager.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
package apinodeutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/iwind/TeaGo/Tea"
|
||||||
|
"github.com/iwind/TeaGo/files"
|
||||||
|
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DeployManager 节点部署文件管理器
|
||||||
|
// 如果节点部署文件有变化,需要重启API节点以便于生效
|
||||||
|
type DeployManager struct {
|
||||||
|
dir string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDeployManager 获取新节点部署文件管理器
|
||||||
|
func NewDeployManager() *DeployManager {
|
||||||
|
var manager = &DeployManager{
|
||||||
|
dir: Tea.Root + "/edge-api/deploy",
|
||||||
|
}
|
||||||
|
manager.LoadNodeFiles()
|
||||||
|
manager.LoadNSNodeFiles()
|
||||||
|
return manager
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadNodeFiles 加载所有边缘节点文件
|
||||||
|
func (this *DeployManager) LoadNodeFiles() []*DeployFile {
|
||||||
|
var keyMap = map[string]*DeployFile{} // key => File
|
||||||
|
|
||||||
|
var reg = regexp.MustCompile(`^edge-node-(\w+)-(\w+)-v([0-9.]+)\.zip$`)
|
||||||
|
for _, file := range files.NewFile(this.dir).List() {
|
||||||
|
var name = file.Name()
|
||||||
|
if !reg.MatchString(name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var matches = reg.FindStringSubmatch(name)
|
||||||
|
var osName = matches[1]
|
||||||
|
var arch = matches[2]
|
||||||
|
var version = matches[3]
|
||||||
|
|
||||||
|
var key = osName + "_" + arch
|
||||||
|
oldFile, ok := keyMap[key]
|
||||||
|
if ok && stringutil.VersionCompare(oldFile.Version, version) > 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
keyMap[key] = &DeployFile{
|
||||||
|
OS: osName,
|
||||||
|
Arch: arch,
|
||||||
|
Version: version,
|
||||||
|
Path: file.Path(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = []*DeployFile{}
|
||||||
|
for _, v := range keyMap {
|
||||||
|
result = append(result, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadNSNodeFiles 加载所有NS节点安装文件
|
||||||
|
func (this *DeployManager) LoadNSNodeFiles() []*DeployFile {
|
||||||
|
var keyMap = map[string]*DeployFile{} // key => File
|
||||||
|
|
||||||
|
var reg = regexp.MustCompile(`^edge-dns-(\w+)-(\w+)-v([0-9.]+)\.zip$`)
|
||||||
|
for _, file := range files.NewFile(this.dir).List() {
|
||||||
|
var name = file.Name()
|
||||||
|
if !reg.MatchString(name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var matches = reg.FindStringSubmatch(name)
|
||||||
|
var osName = matches[1]
|
||||||
|
var arch = matches[2]
|
||||||
|
var version = matches[3]
|
||||||
|
|
||||||
|
var key = osName + "_" + arch
|
||||||
|
oldFile, ok := keyMap[key]
|
||||||
|
if ok && stringutil.VersionCompare(oldFile.Version, version) > 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
keyMap[key] = &DeployFile{
|
||||||
|
OS: osName,
|
||||||
|
Arch: arch,
|
||||||
|
Version: version,
|
||||||
|
Path: file.Path(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = []*DeployFile{}
|
||||||
|
for _, v := range keyMap {
|
||||||
|
result = append(result, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ package apinodeutils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
|
"context"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -62,7 +63,36 @@ func (this *Upgrader) Upgrade() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
versionResp, err := rpcClient.APINodeRPC().FindCurrentAPINodeVersion(sharedClient.Context(0), &pb.FindCurrentAPINodeVersionRequest{})
|
|
||||||
|
// 升级边缘节点
|
||||||
|
err = this.upgradeNodes(sharedClient.Context(0), rpcClient)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 升级NS节点
|
||||||
|
err = this.upgradeNSNodes(sharedClient.Context(0), rpcClient)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 升级API节点
|
||||||
|
err = this.upgradeAPINode(sharedClient.Context(0), rpcClient)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("upgrade api node failed: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Progress 查看升级进程
|
||||||
|
func (this *Upgrader) Progress() *Progress {
|
||||||
|
return this.progress
|
||||||
|
}
|
||||||
|
|
||||||
|
// 升级API节点
|
||||||
|
func (this *Upgrader) upgradeAPINode(ctx context.Context, rpcClient *rpc.RPCClient) error {
|
||||||
|
versionResp, err := rpcClient.APINodeRPC().FindCurrentAPINodeVersion(ctx, &pb.FindCurrentAPINodeVersionRequest{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -77,7 +107,7 @@ func (this *Upgrader) Upgrade() error {
|
|||||||
return errors.New(reason)
|
return errors.New(reason)
|
||||||
}
|
}
|
||||||
|
|
||||||
localVersion, err := localVersion()
|
localVersion, err := lookupLocalVersion()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("lookup version failed: " + err.Error())
|
return errors.New("lookup version failed: " + err.Error())
|
||||||
}
|
}
|
||||||
@@ -196,6 +226,124 @@ func (this *Upgrader) Upgrade() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *Upgrader) Progress() *Progress {
|
// 升级边缘节点
|
||||||
return this.progress
|
func (this *Upgrader) upgradeNodes(ctx context.Context, rpcClient *rpc.RPCClient) error {
|
||||||
|
// 本地的
|
||||||
|
var manager = NewDeployManager()
|
||||||
|
var localFileMap = map[string]*DeployFile{} // os_arch => *DeployFile
|
||||||
|
for _, deployFile := range manager.LoadNodeFiles() {
|
||||||
|
localFileMap[deployFile.OS+"_"+deployFile.Arch] = deployFile
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteFilesResp, err := rpcClient.APINodeRPC().FindLatestDeployFiles(ctx, &pb.FindLatestDeployFilesRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var remoteFileMap = map[string]*pb.FindLatestDeployFilesResponse_DeployFile{} // os_arch => *DeployFile
|
||||||
|
for _, nodeFile := range remoteFilesResp.NodeDeployFiles {
|
||||||
|
remoteFileMap[nodeFile.Os+"_"+nodeFile.Arch] = nodeFile
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对比版本
|
||||||
|
for key, deployFile := range localFileMap {
|
||||||
|
remoteDeployFile, ok := remoteFileMap[key]
|
||||||
|
if !ok || stringutil.VersionCompare(remoteDeployFile.Version, deployFile.Version) < 0 {
|
||||||
|
err = this.uploadNodeDeployFile(ctx, rpcClient, deployFile.Path)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("upload deploy file '" + filepath.Base(deployFile.Path) + "' failed: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 升级NS节点
|
||||||
|
func (this *Upgrader) upgradeNSNodes(ctx context.Context, rpcClient *rpc.RPCClient) error {
|
||||||
|
// 本地的
|
||||||
|
var manager = NewDeployManager()
|
||||||
|
var localFileMap = map[string]*DeployFile{} // os_arch => *DeployFile
|
||||||
|
for _, deployFile := range manager.LoadNSNodeFiles() {
|
||||||
|
localFileMap[deployFile.OS+"_"+deployFile.Arch] = deployFile
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteFilesResp, err := rpcClient.APINodeRPC().FindLatestDeployFiles(ctx, &pb.FindLatestDeployFilesRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var remoteFileMap = map[string]*pb.FindLatestDeployFilesResponse_DeployFile{} // os_arch => *DeployFile
|
||||||
|
for _, nodeFile := range remoteFilesResp.NsNodeDeployFiles {
|
||||||
|
remoteFileMap[nodeFile.Os+"_"+nodeFile.Arch] = nodeFile
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对比版本
|
||||||
|
for key, deployFile := range localFileMap {
|
||||||
|
remoteDeployFile, ok := remoteFileMap[key]
|
||||||
|
if !ok || stringutil.VersionCompare(remoteDeployFile.Version, deployFile.Version) < 0 {
|
||||||
|
err = this.uploadNodeDeployFile(ctx, rpcClient, deployFile.Path)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("upload deploy file '" + filepath.Base(deployFile.Path) + "' failed: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传节点文件
|
||||||
|
func (this *Upgrader) uploadNodeDeployFile(ctx context.Context, rpcClient *rpc.RPCClient, path string) error {
|
||||||
|
fp, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = fp.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
var buf = make([]byte, 128*4096)
|
||||||
|
var isFirst = true
|
||||||
|
|
||||||
|
var hash = md5.New()
|
||||||
|
|
||||||
|
for {
|
||||||
|
n, err := fp.Read(buf)
|
||||||
|
if n > 0 {
|
||||||
|
hash.Write(buf[:n])
|
||||||
|
|
||||||
|
_, uploadErr := rpcClient.APINodeRPC().UploadDeployFileToAPINode(ctx, &pb.UploadDeployFileToAPINodeRequest{
|
||||||
|
Filename: filepath.Base(path),
|
||||||
|
Sum: "",
|
||||||
|
ChunkData: buf[:n],
|
||||||
|
IsFirstChunk: isFirst,
|
||||||
|
IsLastChunk: false,
|
||||||
|
})
|
||||||
|
if uploadErr != nil {
|
||||||
|
return uploadErr
|
||||||
|
}
|
||||||
|
isFirst = false
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
|
||||||
|
_, uploadErr := rpcClient.APINodeRPC().UploadDeployFileToAPINode(ctx, &pb.UploadDeployFileToAPINodeRequest{
|
||||||
|
Filename: filepath.Base(path),
|
||||||
|
Sum: fmt.Sprintf("%x", hash.Sum(nil)),
|
||||||
|
ChunkData: nil,
|
||||||
|
IsFirstChunk: false,
|
||||||
|
IsLastChunk: true,
|
||||||
|
})
|
||||||
|
if uploadErr != nil {
|
||||||
|
return uploadErr
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ func CanUpgrade(apiVersion string, osName string, arch string) (canUpgrade bool,
|
|||||||
return false, "is directory"
|
return false, "is directory"
|
||||||
}
|
}
|
||||||
|
|
||||||
localVersion, err := localVersion()
|
localVersion, err := lookupLocalVersion()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, "lookup version failed: " + err.Error()
|
return false, "lookup version failed: " + err.Error()
|
||||||
}
|
}
|
||||||
@@ -53,9 +53,7 @@ func CanUpgrade(apiVersion string, osName string, arch string) (canUpgrade bool,
|
|||||||
return true, ""
|
return true, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func lookupLocalVersion() (string, error) {
|
||||||
|
|
||||||
func localVersion() (string, error) {
|
|
||||||
var cmd = exec.Command(apiExe(), "-V")
|
var cmd = exec.Command(apiExe(), "-V")
|
||||||
var output = &bytes.Buffer{}
|
var output = &bytes.Buffer{}
|
||||||
cmd.Stdout = output
|
cmd.Stdout = output
|
||||||
@@ -74,7 +72,6 @@ func localVersion() (string, error) {
|
|||||||
return localVersion, nil
|
return localVersion, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func apiExe() string {
|
func apiExe() string {
|
||||||
return Tea.Root + "/edge-api/bin/edge-api"
|
return Tea.Root + "/edge-api/bin/edge-api"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ func FormatCount(count int64) string {
|
|||||||
return fmt.Sprintf("%.1fB", float32(count)/1000/1000/1000)
|
return fmt.Sprintf("%.1fB", float32(count)/1000/1000/1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
func FormatFloat(f interface{}, decimal int) string {
|
func FormatFloat(f any, decimal int) string {
|
||||||
if f == nil {
|
if f == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -101,10 +101,29 @@ func FormatFloat(f interface{}, decimal int) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func FormatFloat2(f interface{}) string {
|
func FormatFloat2(f any) string {
|
||||||
return FormatFloat(f, 2)
|
return FormatFloat(f, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PadFloatZero 为浮点型数字字符串填充足够的0
|
||||||
|
func PadFloatZero(s string, countZero int) string {
|
||||||
|
if countZero <= 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
if len(s) == 0 {
|
||||||
|
s = "0"
|
||||||
|
}
|
||||||
|
var index = strings.Index(s, ".")
|
||||||
|
if index < 0 {
|
||||||
|
return s + "." + strings.Repeat("0", countZero)
|
||||||
|
}
|
||||||
|
var decimalLen = len(s) - 1 - index
|
||||||
|
if decimalLen < countZero {
|
||||||
|
return s + strings.Repeat("0", countZero-decimalLen)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
var decimalReg = regexp.MustCompile(`^(\d+\.\d+)([a-zA-Z]+)?$`)
|
var decimalReg = regexp.MustCompile(`^(\d+\.\d+)([a-zA-Z]+)?$`)
|
||||||
|
|
||||||
// TrimZeroSuffix 去除小数数字尾部多余的0
|
// TrimZeroSuffix 去除小数数字尾部多余的0
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ package numberutils_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
|
||||||
|
"github.com/iwind/TeaGo/assert"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -49,6 +50,24 @@ func TestFormatFloat(t *testing.T) {
|
|||||||
t.Log(numberutils.FormatFloat(-221745.12, 2))
|
t.Log(numberutils.FormatFloat(-221745.12, 2))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFormatFloat2(t *testing.T) {
|
||||||
|
t.Log(numberutils.FormatFloat2(0))
|
||||||
|
t.Log(numberutils.FormatFloat2(0.0))
|
||||||
|
t.Log(numberutils.FormatFloat2(1.23456))
|
||||||
|
t.Log(numberutils.FormatFloat2(1.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPadFloatZero(t *testing.T) {
|
||||||
|
var a = assert.NewAssertion(t)
|
||||||
|
a.IsTrue(numberutils.PadFloatZero("1", 0) == "1")
|
||||||
|
a.IsTrue(numberutils.PadFloatZero("1", 2) == "1.00")
|
||||||
|
a.IsTrue(numberutils.PadFloatZero("1.1", 2) == "1.10")
|
||||||
|
a.IsTrue(numberutils.PadFloatZero("1.12", 2) == "1.12")
|
||||||
|
a.IsTrue(numberutils.PadFloatZero("1.123", 2) == "1.123")
|
||||||
|
a.IsTrue(numberutils.PadFloatZero("10000.123", 2) == "10000.123")
|
||||||
|
a.IsTrue(numberutils.PadFloatZero("", 2) == "0.00")
|
||||||
|
}
|
||||||
|
|
||||||
func TestTrimZeroSuffix(t *testing.T) {
|
func TestTrimZeroSuffix(t *testing.T) {
|
||||||
for _, s := range []string{
|
for _, s := range []string{
|
||||||
"1",
|
"1",
|
||||||
|
|||||||
@@ -2,37 +2,49 @@ package actionutils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/iwind/TeaGo/actions"
|
"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定义
|
// Tabbar Tabbar定义
|
||||||
type Tabbar struct {
|
type Tabbar struct {
|
||||||
items []maps.Map
|
items []*TabItem
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTabbar 获取新对象
|
// NewTabbar 获取新对象
|
||||||
func NewTabbar() *Tabbar {
|
func NewTabbar() *Tabbar {
|
||||||
return &Tabbar{
|
return &Tabbar{
|
||||||
items: []maps.Map{},
|
items: []*TabItem{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add 添加菜单项
|
// Add 添加菜单项
|
||||||
func (this *Tabbar) Add(name string, subName string, url string, icon string, active bool) maps.Map {
|
func (this *Tabbar) Add(name string, subName string, url string, icon string, active bool) *TabItem {
|
||||||
m := maps.Map{
|
var m = &TabItem{
|
||||||
"name": name,
|
Name: name,
|
||||||
"subName": subName,
|
SubName: subName,
|
||||||
"url": url,
|
URL: url,
|
||||||
"icon": icon,
|
Icon: icon,
|
||||||
"active": active,
|
IsActive: active,
|
||||||
"right": false,
|
IsRight: false,
|
||||||
|
IsTitle: false,
|
||||||
|
IsDisabled: false,
|
||||||
}
|
}
|
||||||
this.items = append(this.items, m)
|
this.items = append(this.items, m)
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
// Items 取得所有的Items
|
// Items 取得所有的Items
|
||||||
func (this *Tabbar) Items() []maps.Map {
|
func (this *Tabbar) Items() []*TabItem {
|
||||||
return this.items
|
return this.items
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,17 @@ func (this *CreateBatchAction) RunGet(params struct {
|
|||||||
}
|
}
|
||||||
this.Data["leftMenuItems"] = leftMenuItems
|
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()
|
this.Show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -30,6 +30,11 @@ func (this *CreateNodeAction) Init() {
|
|||||||
func (this *CreateNodeAction) RunGet(params struct {
|
func (this *CreateNodeAction) RunGet(params struct {
|
||||||
ClusterId int64
|
ClusterId int64
|
||||||
}) {
|
}) {
|
||||||
|
if params.ClusterId <= 0 {
|
||||||
|
this.RedirectURL("/clusters")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var leftMenuItems = []maps.Map{
|
var leftMenuItems = []maps.Map{
|
||||||
{
|
{
|
||||||
"name": "单个创建",
|
"name": "单个创建",
|
||||||
@@ -92,6 +97,17 @@ func (this *CreateNodeAction) RunGet(params struct {
|
|||||||
// 安装文件下载
|
// 安装文件下载
|
||||||
this.Data["installerFiles"] = clusterutils.ListInstallerFiles()
|
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()
|
this.Show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -187,6 +187,8 @@ func (this *DetailAction) RunGet(params struct {
|
|||||||
"route": route.Name,
|
"route": route.Name,
|
||||||
"value": addr.Ip,
|
"value": addr.Ip,
|
||||||
"clusterName": cluster.Name,
|
"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{
|
this.Data["node"] = maps.Map{
|
||||||
"id": node.Id,
|
"id": node.Id,
|
||||||
"name": node.Name,
|
"name": node.Name,
|
||||||
"ipAddresses": ipAddressMaps,
|
"ipAddresses": ipAddressMaps,
|
||||||
"cluster": clusterMap,
|
"cluster": clusterMap,
|
||||||
"secondaryClusters": secondaryClustersMaps,
|
"secondaryClusters": secondaryClustersMaps,
|
||||||
"login": loginMap,
|
"login": loginMap,
|
||||||
"installDir": node.InstallDir,
|
"installDir": node.InstallDir,
|
||||||
"isInstalled": node.IsInstalled,
|
"isInstalled": node.IsInstalled,
|
||||||
"uniqueId": node.UniqueId,
|
"uniqueId": node.UniqueId,
|
||||||
"secret": node.Secret,
|
"secret": node.Secret,
|
||||||
"maxCPU": node.MaxCPU,
|
"maxCPU": node.MaxCPU,
|
||||||
"isOn": node.IsOn,
|
"isOn": node.IsOn,
|
||||||
"records": recordMaps,
|
"records": recordMaps,
|
||||||
"routes": routeMaps,
|
"routes": routeMaps,
|
||||||
"level": node.Level,
|
"level": node.Level,
|
||||||
"levelInfo": nodeconfigs.FindNodeLevel(int(node.Level)),
|
"levelInfo": nodeconfigs.FindNodeLevel(int(node.Level)),
|
||||||
"lnAddrs": lnAddrs,
|
"lnAddrs": lnAddrs,
|
||||||
"enableIPLists": node.EnableIPLists,
|
"enableIPLists": node.EnableIPLists,
|
||||||
"apiNodeAddrs": apiNodeAddrStrings,
|
"apiNodeAddrs": apiNodeAddrStrings,
|
||||||
|
"offlineDay": node.OfflineDay,
|
||||||
|
"isOffline": len(node.OfflineDay) > 0 && node.OfflineDay < timeutil.Format("Ymd"),
|
||||||
|
"isBackupForCluster": node.IsBackupForCluster,
|
||||||
|
"isBackupForGroup": node.IsBackupForGroup,
|
||||||
|
|
||||||
"status": maps.Map{
|
"status": maps.Map{
|
||||||
"isActive": status.IsActive,
|
"isActive": status.IsActive,
|
||||||
|
|||||||
@@ -209,6 +209,8 @@ func (this *NodesAction) RunGet(params struct {
|
|||||||
"isInstalled": node.IsInstalled,
|
"isInstalled": node.IsInstalled,
|
||||||
"isOn": node.IsOn,
|
"isOn": node.IsOn,
|
||||||
"isUp": node.IsUp,
|
"isUp": node.IsUp,
|
||||||
|
"isBackup": node.IsBackupForCluster || node.IsBackupForGroup,
|
||||||
|
"offlineDay": node.OfflineDay,
|
||||||
"installStatus": maps.Map{
|
"installStatus": maps.Map{
|
||||||
"isRunning": node.InstallStatus.IsRunning,
|
"isRunning": node.InstallStatus.IsRunning,
|
||||||
"isFinished": node.InstallStatus.IsFinished,
|
"isFinished": node.InstallStatus.IsFinished,
|
||||||
@@ -225,7 +227,7 @@ func (this *NodesAction) RunGet(params struct {
|
|||||||
"memUsageText": fmt.Sprintf("%.2f%%", status.MemoryUsage*100),
|
"memUsageText": fmt.Sprintf("%.2f%%", status.MemoryUsage*100),
|
||||||
"trafficInBytes": status.TrafficInBytes,
|
"trafficInBytes": status.TrafficInBytes,
|
||||||
"trafficOutBytes": status.TrafficOutBytes,
|
"trafficOutBytes": status.TrafficOutBytes,
|
||||||
"load1m": numberutils.FormatFloat2(status.Load1m),
|
"load1m": numberutils.PadFloatZero(numberutils.FormatFloat2(status.Load1m), 2),
|
||||||
"countConnections": status.ConnectionCount,
|
"countConnections": status.ConnectionCount,
|
||||||
},
|
},
|
||||||
"cluster": maps.Map{
|
"cluster": maps.Map{
|
||||||
|
|||||||
@@ -78,7 +78,8 @@ func (this *IndexAction) RunPost(params struct {
|
|||||||
HttpAllAllowNodeIP bool
|
HttpAllAllowNodeIP bool
|
||||||
HttpAllDefaultDomain string
|
HttpAllDefaultDomain string
|
||||||
|
|
||||||
HttpAllSupportsLowVersionHTTP bool
|
HttpAllSupportsLowVersionHTTP bool
|
||||||
|
HttpAllMatchCertFromAllServers bool
|
||||||
|
|
||||||
HttpAccessLogEnableRequestHeaders bool
|
HttpAccessLogEnableRequestHeaders bool
|
||||||
HttpAccessLogEnableResponseHeaders bool
|
HttpAccessLogEnableResponseHeaders bool
|
||||||
@@ -138,6 +139,7 @@ func (this *IndexAction) RunPost(params struct {
|
|||||||
|
|
||||||
// HTTP All
|
// HTTP All
|
||||||
config.HTTPAll.SupportsLowVersionHTTP = params.HttpAllSupportsLowVersionHTTP
|
config.HTTPAll.SupportsLowVersionHTTP = params.HttpAllSupportsLowVersionHTTP
|
||||||
|
config.HTTPAll.MatchCertFromAllServers = params.HttpAllMatchCertFromAllServers
|
||||||
|
|
||||||
// 访问日志
|
// 访问日志
|
||||||
config.HTTPAccessLog.EnableRequestHeaders = params.HttpAccessLogEnableRequestHeaders
|
config.HTTPAccessLog.EnableRequestHeaders = params.HttpAccessLogEnableRequestHeaders
|
||||||
|
|||||||
@@ -15,7 +15,17 @@ func (this *RunPopupAction) Init() {
|
|||||||
this.Nav("", "", "")
|
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()
|
this.Show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,9 +29,9 @@ func (this *ClusterHelper) BeforeAction(actionPtr actions.ActionWrapper) (goNext
|
|||||||
|
|
||||||
action.Data["teaMenu"] = "clusters"
|
action.Data["teaMenu"] = "clusters"
|
||||||
|
|
||||||
selectedTabbar := action.Data.GetString("mainTab")
|
var selectedTabbar = action.Data.GetString("mainTab")
|
||||||
clusterId := action.ParamInt64("clusterId")
|
var clusterId = action.ParamInt64("clusterId")
|
||||||
clusterIdString := strconv.FormatInt(clusterId, 10)
|
var clusterIdString = strconv.FormatInt(clusterId, 10)
|
||||||
action.Data["clusterId"] = clusterId
|
action.Data["clusterId"] = clusterId
|
||||||
|
|
||||||
if clusterId > 0 {
|
if clusterId > 0 {
|
||||||
@@ -57,14 +57,45 @@ func (this *ClusterHelper) BeforeAction(actionPtr actions.ActionWrapper) (goNext
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var nodeId = action.ParamInt64("nodeId")
|
||||||
|
var isInCluster = nodeId <= 0
|
||||||
|
|
||||||
var tabbar = actionutils.NewTabbar()
|
var tabbar = actionutils.NewTabbar()
|
||||||
tabbar.Add("集群列表", "", "/clusters", "", false)
|
{
|
||||||
if teaconst.IsPlus {
|
var url = "/clusters"
|
||||||
tabbar.Add("集群看板", "", "/clusters/cluster/boards?clusterId="+clusterIdString, "chart line area", selectedTabbar == "board")
|
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)
|
actionutils.SetTabbar(action, tabbar)
|
||||||
|
|
||||||
// 左侧菜单
|
// 左侧菜单
|
||||||
@@ -97,14 +128,39 @@ func (this *ClusterHelper) createSettingMenu(cluster *pb.NodeCluster, info *pb.F
|
|||||||
"isActive": selectedItem == "basic",
|
"isActive": selectedItem == "basic",
|
||||||
"isOn": true,
|
"isOn": true,
|
||||||
})
|
})
|
||||||
|
|
||||||
items = append(items, maps.Map{
|
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,
|
"url": "/clusters/cluster/settings/cache?clusterId=" + clusterId,
|
||||||
"isActive": selectedItem == "cache",
|
"isActive": selectedItem == "cache",
|
||||||
"isOn": cluster.HttpCachePolicyId > 0,
|
"isOn": cluster.HttpCachePolicyId > 0,
|
||||||
})
|
})
|
||||||
items = append(items, maps.Map{
|
items = append(items, maps.Map{
|
||||||
"name": "WAF设置",
|
"name": "WAF策略",
|
||||||
"url": "/clusters/cluster/settings/waf?clusterId=" + clusterId,
|
"url": "/clusters/cluster/settings/waf?clusterId=" + clusterId,
|
||||||
"isActive": selectedItem == "waf",
|
"isActive": selectedItem == "waf",
|
||||||
"isOn": cluster.HttpFirewallPolicyId > 0,
|
"isOn": cluster.HttpFirewallPolicyId > 0,
|
||||||
@@ -132,20 +188,6 @@ func (this *ClusterHelper) createSettingMenu(cluster *pb.NodeCluster, info *pb.F
|
|||||||
"isActive": false,
|
"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{
|
items = append(items, maps.Map{
|
||||||
"name": "DDoS防护",
|
"name": "DDoS防护",
|
||||||
"url": "/clusters/cluster/settings/ddos-protection?clusterId=" + clusterId,
|
"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,
|
"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{
|
items = append(items, maps.Map{
|
||||||
"name": "-",
|
"name": "-",
|
||||||
})
|
})
|
||||||
|
|||||||
52
internal/web/actions/default/clusters/createNode.go
Normal file
52
internal/web/actions/default/clusters/createNode.go
Normal 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()
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ func init() {
|
|||||||
Prefix("/clusters").
|
Prefix("/clusters").
|
||||||
Get("", new(IndexAction)).
|
Get("", new(IndexAction)).
|
||||||
GetPost("/create", new(CreateAction)).
|
GetPost("/create", new(CreateAction)).
|
||||||
|
GetPost("/createNode", new(CreateNodeAction)).
|
||||||
Post("/pin", new(PinAction)).
|
Post("/pin", new(PinAction)).
|
||||||
Get("/nodes", new(NodesAction)).
|
Get("/nodes", new(NodesAction)).
|
||||||
|
|
||||||
|
|||||||
@@ -211,6 +211,8 @@ func (this *NodesAction) RunGet(params struct {
|
|||||||
"isInstalled": node.IsInstalled,
|
"isInstalled": node.IsInstalled,
|
||||||
"isOn": node.IsOn,
|
"isOn": node.IsOn,
|
||||||
"isUp": node.IsUp,
|
"isUp": node.IsUp,
|
||||||
|
"isBackup": node.IsBackupForCluster || node.IsBackupForGroup,
|
||||||
|
"offlineDay": node.OfflineDay,
|
||||||
"installStatus": maps.Map{
|
"installStatus": maps.Map{
|
||||||
"isRunning": node.InstallStatus.IsRunning,
|
"isRunning": node.InstallStatus.IsRunning,
|
||||||
"isFinished": node.InstallStatus.IsFinished,
|
"isFinished": node.InstallStatus.IsFinished,
|
||||||
@@ -222,12 +224,12 @@ func (this *NodesAction) RunGet(params struct {
|
|||||||
"updatedAt": status.UpdatedAt,
|
"updatedAt": status.UpdatedAt,
|
||||||
"hostname": status.Hostname,
|
"hostname": status.Hostname,
|
||||||
"cpuUsage": status.CPUUsage,
|
"cpuUsage": status.CPUUsage,
|
||||||
"cpuUsageText": numberutils.FormatFloat2(status.CPUUsage * 100),
|
"cpuUsageText": numberutils.FormatFloat2(status.CPUUsage*100) + "%",
|
||||||
"memUsage": status.MemoryUsage,
|
"memUsage": status.MemoryUsage,
|
||||||
"memUsageText": numberutils.FormatFloat2(status.MemoryUsage * 100),
|
"memUsageText": numberutils.FormatFloat2(status.MemoryUsage*100) + "%",
|
||||||
"trafficInBytes": status.TrafficInBytes,
|
"trafficInBytes": status.TrafficInBytes,
|
||||||
"trafficOutBytes": status.TrafficOutBytes,
|
"trafficOutBytes": status.TrafficOutBytes,
|
||||||
"load1m": numberutils.FormatFloat2(status.Load1m),
|
"load1m": numberutils.PadFloatZero(numberutils.FormatFloat2(status.Load1m), 2),
|
||||||
"countConnections": status.ConnectionCount,
|
"countConnections": status.ConnectionCount,
|
||||||
},
|
},
|
||||||
"cluster": maps.Map{
|
"cluster": maps.Map{
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ func (this *CheckAction) RunPost(params struct {
|
|||||||
HasError bool
|
HasError bool
|
||||||
IsUpdated bool
|
IsUpdated bool
|
||||||
}) {
|
}) {
|
||||||
|
var isStream = this.Request.ProtoMajor >= 2
|
||||||
|
this.Data["isStream"] = isStream
|
||||||
|
|
||||||
var maxTries = 10
|
var maxTries = 10
|
||||||
for i := 0; i < maxTries; i++ {
|
for i := 0; i < maxTries; i++ {
|
||||||
resp, err := this.RPC().NodeTaskRPC().ExistsNodeTasks(this.AdminContext(), &pb.ExistsNodeTasksRequest{
|
resp, err := this.RPC().NodeTaskRPC().ExistsNodeTasks(this.AdminContext(), &pb.ExistsNodeTasksRequest{
|
||||||
@@ -26,7 +29,7 @@ func (this *CheckAction) RunPost(params struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 如果没有数据变化,继续查询
|
// 如果没有数据变化,继续查询
|
||||||
if i < maxTries-1 && params.IsUpdated && resp.ExistTasks == params.IsDoing && resp.ExistError == params.HasError {
|
if i < maxTries-1 && params.IsUpdated && resp.ExistTasks == params.IsDoing && resp.ExistError == params.HasError && isStream {
|
||||||
time.Sleep(3 * time.Second)
|
time.Sleep(3 * time.Second)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,6 +136,8 @@ func (this *ClusterAction) RunGet(params struct {
|
|||||||
"clusterId": node.NodeClusterId,
|
"clusterId": node.NodeClusterId,
|
||||||
"isResolved": isResolved,
|
"isResolved": isResolved,
|
||||||
"isInstalled": isInstalled,
|
"isInstalled": isInstalled,
|
||||||
|
"isBackup": node.IsBackupForCluster || node.IsBackupForGroup,
|
||||||
|
"isOffline": node.IsOffline,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -171,6 +173,8 @@ func (this *ClusterAction) RunGet(params struct {
|
|||||||
"clusterId": node.NodeClusterId,
|
"clusterId": node.NodeClusterId,
|
||||||
"isResolved": isResolved,
|
"isResolved": isResolved,
|
||||||
"isInstalled": isInstalled,
|
"isInstalled": isInstalled,
|
||||||
|
"isBackup": node.IsBackupForCluster || node.IsBackupForGroup,
|
||||||
|
"isOffline": node.IsOffline,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
"github.com/iwind/TeaGo/maps"
|
"github.com/iwind/TeaGo/maps"
|
||||||
|
"sort"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DomainOptionsAction 域名列表选项
|
// DomainOptionsAction 域名列表选项
|
||||||
@@ -21,10 +22,18 @@ func (this *DomainOptionsAction) RunPost(params struct {
|
|||||||
this.ErrorPage(err)
|
this.ErrorPage(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
domainMaps := []maps.Map{}
|
|
||||||
|
// 排序
|
||||||
|
if len(domainsResp.DnsDomains) > 0 {
|
||||||
|
sort.Slice(domainsResp.DnsDomains, func(i, j int) bool {
|
||||||
|
return domainsResp.DnsDomains[i].Name < domainsResp.DnsDomains[j].Name
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var domainMaps = []maps.Map{}
|
||||||
for _, domain := range domainsResp.DnsDomains {
|
for _, domain := range domainsResp.DnsDomains {
|
||||||
// 未开启或者已删除的先跳过
|
// 未开启或者已删除的先跳过
|
||||||
if !domain.IsOn || domain.IsDeleted {
|
if !domain.IsOn || domain.IsDeleted || !domain.IsUp {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ func (this *CheckAction) RunPost(params struct {
|
|||||||
HasError bool
|
HasError bool
|
||||||
IsUpdated bool
|
IsUpdated bool
|
||||||
}) {
|
}) {
|
||||||
|
var isStream = this.Request.ProtoMajor >= 2
|
||||||
|
this.Data["isStream"] = isStream
|
||||||
|
|
||||||
var maxTries = 10
|
var maxTries = 10
|
||||||
for i := 0; i < maxTries; i++ {
|
for i := 0; i < maxTries; i++ {
|
||||||
resp, err := this.RPC().DNSTaskRPC().ExistsDNSTasks(this.AdminContext(), &pb.ExistsDNSTasksRequest{})
|
resp, err := this.RPC().DNSTaskRPC().ExistsDNSTasks(this.AdminContext(), &pb.ExistsDNSTasksRequest{})
|
||||||
@@ -24,7 +27,7 @@ func (this *CheckAction) RunPost(params struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 如果没有数据变化,继续查询
|
// 如果没有数据变化,继续查询
|
||||||
if i < maxTries-1 && params.IsUpdated && resp.ExistTasks == params.IsDoing && resp.ExistError == params.HasError {
|
if i < maxTries-1 && params.IsUpdated && resp.ExistTasks == params.IsDoing && resp.ExistError == params.HasError && isStream {
|
||||||
time.Sleep(3 * time.Second)
|
time.Sleep(3 * time.Second)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,15 +13,19 @@ import (
|
|||||||
adminserverutils "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/server/admin-server-utils"
|
adminserverutils "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/server/admin-server-utils"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
"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/dao"
|
||||||
"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/types"
|
"github.com/iwind/TeaGo/types"
|
||||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const regionDenyMessage = "当前软件系统暂时不为你所在的区域提供服务。"
|
||||||
|
|
||||||
type IndexAction struct {
|
type IndexAction struct {
|
||||||
actionutils.ParentAction
|
actionutils.ParentAction
|
||||||
}
|
}
|
||||||
@@ -36,6 +40,11 @@ func (this *IndexAction) RunGet(params struct {
|
|||||||
|
|
||||||
Auth *helpers.UserShouldAuth
|
Auth *helpers.UserShouldAuth
|
||||||
}) {
|
}) {
|
||||||
|
if !this.checkRegion() {
|
||||||
|
this.WriteString(regionDenyMessage)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 是否自动从HTTP跳转到HTTPS
|
// 是否自动从HTTP跳转到HTTPS
|
||||||
if this.Request.TLS == nil {
|
if this.Request.TLS == nil {
|
||||||
httpsPort, _ := adminserverutils.ReadServerHTTPS()
|
httpsPort, _ := adminserverutils.ReadServerHTTPS()
|
||||||
@@ -50,8 +59,11 @@ func (this *IndexAction) RunGet(params struct {
|
|||||||
newHost += ":" + types.String(httpsPort)
|
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
|
Auth *helpers.UserShouldAuth
|
||||||
CSRF *actionutils.CSRF
|
CSRF *actionutils.CSRF
|
||||||
}) {
|
}) {
|
||||||
|
if !this.checkRegion() {
|
||||||
|
this.Fail(regionDenyMessage)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
params.Must.
|
params.Must.
|
||||||
Field("username", params.Username).
|
Field("username", params.Username).
|
||||||
Require("请输入用户名").
|
Require("请输入用户名").
|
||||||
@@ -213,3 +230,13 @@ func (this *IndexAction) RunPost(params struct {
|
|||||||
|
|
||||||
this.Success()
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,16 +4,41 @@ package loginutils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
|
||||||
"github.com/iwind/TeaGo/actions"
|
"github.com/iwind/TeaGo/actions"
|
||||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CalculateClientFingerprint 计算客户端指纹
|
// CalculateClientFingerprint 计算客户端指纹
|
||||||
func CalculateClientFingerprint(action *actions.ActionObject) string {
|
func CalculateClientFingerprint(action *actions.ActionObject) string {
|
||||||
return stringutil.Md5(action.RequestRemoteIP() + "@" + action.Request.UserAgent())
|
return stringutil.Md5(RemoteIP(action) + "@" + action.Request.UserAgent())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoteIP 获取客户端IP
|
||||||
|
// TODO 将来增加是否使用代理设置(即从X-Real-IP中获取IP)
|
||||||
|
func RemoteIP(action *actions.ActionObject) string {
|
||||||
|
ip, _, _ := net.SplitHostPort(action.Request.RemoteAddr)
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupIPRegion 查找登录区域
|
||||||
|
func LookupIPRegion(ip string) string {
|
||||||
|
if len(ip) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = iplibrary.LookupIP(ip)
|
||||||
|
if result != nil && result.IsOk() {
|
||||||
|
// 这里不需要网络运营商信息
|
||||||
|
return result.CountryName() + "@" + result.ProvinceName() + "@" + result.CityName() + "@" + result.TownName()
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCookie 设置Cookie
|
||||||
func SetCookie(action *actions.ActionObject, remember bool) {
|
func SetCookie(action *actions.ActionObject, remember bool) {
|
||||||
if remember {
|
if remember {
|
||||||
var cookie = &http.Cookie{
|
var cookie = &http.Cookie{
|
||||||
@@ -44,6 +69,7 @@ func SetCookie(action *actions.ActionObject, remember bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnsetCookie 重置Cookie
|
||||||
func UnsetCookie(action *actions.ActionObject) {
|
func UnsetCookie(action *actions.ActionObject) {
|
||||||
cookie := &http.Cookie{
|
cookie := &http.Cookie{
|
||||||
Name: teaconst.CookieSID,
|
Name: teaconst.CookieSID,
|
||||||
|
|||||||
@@ -18,31 +18,6 @@ func (this *CreateAction) Init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *CreateAction) RunGet(params struct{}) {
|
func (this *CreateAction) RunGet(params struct{}) {
|
||||||
// 获取所有可用的用户
|
|
||||||
usersResp, err := this.RPC().ACMEUserRPC().FindAllACMEUsers(this.AdminContext(), &pb.FindAllACMEUsersRequest{
|
|
||||||
AdminId: this.AdminId(),
|
|
||||||
UserId: 0,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
this.ErrorPage(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
userMaps := []maps.Map{}
|
|
||||||
for _, user := range usersResp.AcmeUsers {
|
|
||||||
description := user.Description
|
|
||||||
if len(description) > 0 {
|
|
||||||
description = "(" + description + ")"
|
|
||||||
}
|
|
||||||
|
|
||||||
userMaps = append(userMaps, maps.Map{
|
|
||||||
"id": user.Id,
|
|
||||||
"description": description,
|
|
||||||
"email": user.Email,
|
|
||||||
"providerCode": user.AcmeProviderCode,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
this.Data["users"] = userMaps
|
|
||||||
|
|
||||||
// 证书服务商
|
// 证书服务商
|
||||||
providersResp, err := this.RPC().ACMEProviderRPC().FindAllACMEProviders(this.AdminContext(), &pb.FindAllACMEProvidersRequest{})
|
providersResp, err := this.RPC().ACMEProviderRPC().FindAllACMEProviders(this.AdminContext(), &pb.FindAllACMEProvidersRequest{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -81,14 +56,15 @@ func (this *CreateAction) RunGet(params struct{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *CreateAction) RunPost(params struct {
|
func (this *CreateAction) RunPost(params struct {
|
||||||
TaskId int64
|
PlatformUserId int64
|
||||||
AuthType string
|
TaskId int64
|
||||||
AcmeUserId int64
|
AuthType string
|
||||||
DnsProviderId int64
|
AcmeUserId int64
|
||||||
DnsDomain string
|
DnsProviderId int64
|
||||||
Domains []string
|
DnsDomain string
|
||||||
AutoRenew bool
|
Domains []string
|
||||||
AuthURL string
|
AutoRenew bool
|
||||||
|
AuthURL string
|
||||||
|
|
||||||
Must *actions.Must
|
Must *actions.Must
|
||||||
}) {
|
}) {
|
||||||
@@ -117,7 +93,7 @@ func (this *CreateAction) RunPost(params struct {
|
|||||||
if len(params.Domains) == 0 {
|
if len(params.Domains) == 0 {
|
||||||
this.Fail("请输入证书域名列表")
|
this.Fail("请输入证书域名列表")
|
||||||
}
|
}
|
||||||
realDomains := []string{}
|
var realDomains = []string{}
|
||||||
for _, domain := range params.Domains {
|
for _, domain := range params.Domains {
|
||||||
domain = strings.ToLower(domain)
|
domain = strings.ToLower(domain)
|
||||||
if params.AuthType == "dns" { // DNS认证
|
if params.AuthType == "dns" { // DNS认证
|
||||||
@@ -134,6 +110,7 @@ func (this *CreateAction) RunPost(params struct {
|
|||||||
|
|
||||||
if params.TaskId == 0 {
|
if params.TaskId == 0 {
|
||||||
createResp, err := this.RPC().ACMETaskRPC().CreateACMETask(this.AdminContext(), &pb.CreateACMETaskRequest{
|
createResp, err := this.RPC().ACMETaskRPC().CreateACMETask(this.AdminContext(), &pb.CreateACMETaskRequest{
|
||||||
|
UserId: params.PlatformUserId,
|
||||||
AuthType: params.AuthType,
|
AuthType: params.AuthType,
|
||||||
AcmeUserId: params.AcmeUserId,
|
AcmeUserId: params.AcmeUserId,
|
||||||
DnsProviderId: params.DnsProviderId,
|
DnsProviderId: params.DnsProviderId,
|
||||||
|
|||||||
@@ -17,22 +17,48 @@ func (this *IndexAction) Init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *IndexAction) RunGet(params struct {
|
func (this *IndexAction) RunGet(params struct {
|
||||||
|
UserId int64
|
||||||
Type string
|
Type string
|
||||||
Keyword string
|
Keyword string
|
||||||
}) {
|
}) {
|
||||||
this.Data["type"] = params.Type
|
this.Data["type"] = params.Type
|
||||||
this.Data["keyword"] = params.Keyword
|
this.Data["keyword"] = params.Keyword
|
||||||
|
|
||||||
countAll := int64(0)
|
// 当前用户
|
||||||
countAvailable := int64(0)
|
this.Data["searchingUserId"] = params.UserId
|
||||||
countExpired := int64(0)
|
var userMap = maps.Map{
|
||||||
count7Days := int64(0)
|
"id": 0,
|
||||||
count30Days := int64(0)
|
"username": "",
|
||||||
|
"fullname": "",
|
||||||
|
}
|
||||||
|
if params.UserId > 0 {
|
||||||
|
userResp, err := this.RPC().UserRPC().FindEnabledUser(this.AdminContext(), &pb.FindEnabledUserRequest{UserId: params.UserId})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var user = userResp.User
|
||||||
|
if user != nil {
|
||||||
|
userMap = maps.Map{
|
||||||
|
"id": user.Id,
|
||||||
|
"username": user.Username,
|
||||||
|
"fullname": user.Fullname,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.Data["user"] = userMap
|
||||||
|
|
||||||
|
var countAll int64
|
||||||
|
var countAvailable int64
|
||||||
|
var countExpired int64
|
||||||
|
var count7Days int64
|
||||||
|
var count30Days int64
|
||||||
|
|
||||||
// 计算数量
|
// 计算数量
|
||||||
{
|
{
|
||||||
// all
|
// all
|
||||||
resp, err := this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
|
resp, err := this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
|
||||||
|
UserId: params.UserId,
|
||||||
Keyword: params.Keyword,
|
Keyword: params.Keyword,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -43,6 +69,7 @@ func (this *IndexAction) RunGet(params struct {
|
|||||||
|
|
||||||
// available
|
// available
|
||||||
resp, err = this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
|
resp, err = this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
|
||||||
|
UserId: params.UserId,
|
||||||
IsAvailable: true,
|
IsAvailable: true,
|
||||||
Keyword: params.Keyword,
|
Keyword: params.Keyword,
|
||||||
})
|
})
|
||||||
@@ -54,6 +81,7 @@ func (this *IndexAction) RunGet(params struct {
|
|||||||
|
|
||||||
// expired
|
// expired
|
||||||
resp, err = this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
|
resp, err = this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
|
||||||
|
UserId: params.UserId,
|
||||||
IsExpired: true,
|
IsExpired: true,
|
||||||
Keyword: params.Keyword,
|
Keyword: params.Keyword,
|
||||||
})
|
})
|
||||||
@@ -65,6 +93,7 @@ func (this *IndexAction) RunGet(params struct {
|
|||||||
|
|
||||||
// expire in 7 days
|
// expire in 7 days
|
||||||
resp, err = this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
|
resp, err = this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
|
||||||
|
UserId: params.UserId,
|
||||||
ExpiringDays: 7,
|
ExpiringDays: 7,
|
||||||
Keyword: params.Keyword,
|
Keyword: params.Keyword,
|
||||||
})
|
})
|
||||||
@@ -76,6 +105,7 @@ func (this *IndexAction) RunGet(params struct {
|
|||||||
|
|
||||||
// expire in 30 days
|
// expire in 30 days
|
||||||
resp, err = this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
|
resp, err = this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
|
||||||
|
UserId: params.UserId,
|
||||||
ExpiringDays: 30,
|
ExpiringDays: 30,
|
||||||
Keyword: params.Keyword,
|
Keyword: params.Keyword,
|
||||||
})
|
})
|
||||||
@@ -100,25 +130,51 @@ func (this *IndexAction) RunGet(params struct {
|
|||||||
case "":
|
case "":
|
||||||
page = this.NewPage(countAll)
|
page = this.NewPage(countAll)
|
||||||
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{
|
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{
|
||||||
|
UserId: params.UserId,
|
||||||
Offset: page.Offset,
|
Offset: page.Offset,
|
||||||
Size: page.Size,
|
Size: page.Size,
|
||||||
Keyword: params.Keyword,
|
Keyword: params.Keyword,
|
||||||
})
|
})
|
||||||
case "available":
|
case "available":
|
||||||
page = this.NewPage(countAvailable)
|
page = this.NewPage(countAvailable)
|
||||||
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{IsAvailable: true, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
|
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{
|
||||||
|
UserId: params.UserId,
|
||||||
|
IsAvailable: true,
|
||||||
|
Offset: page.Offset,
|
||||||
|
Size: page.Size,
|
||||||
|
Keyword: params.Keyword,
|
||||||
|
})
|
||||||
case "expired":
|
case "expired":
|
||||||
page = this.NewPage(countExpired)
|
page = this.NewPage(countExpired)
|
||||||
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{IsExpired: true, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
|
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{
|
||||||
|
UserId: params.UserId,
|
||||||
|
IsExpired: true,
|
||||||
|
Offset: page.Offset,
|
||||||
|
Size: page.Size,
|
||||||
|
Keyword: params.Keyword,
|
||||||
|
})
|
||||||
case "7days":
|
case "7days":
|
||||||
page = this.NewPage(count7Days)
|
page = this.NewPage(count7Days)
|
||||||
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{ExpiringDays: 7, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
|
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{
|
||||||
|
UserId: params.UserId,
|
||||||
|
ExpiringDays: 7,
|
||||||
|
Offset: page.Offset,
|
||||||
|
Size: page.Size,
|
||||||
|
Keyword: params.Keyword,
|
||||||
|
})
|
||||||
case "30days":
|
case "30days":
|
||||||
page = this.NewPage(count30Days)
|
page = this.NewPage(count30Days)
|
||||||
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{ExpiringDays: 30, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
|
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{
|
||||||
|
UserId: params.UserId,
|
||||||
|
ExpiringDays: 30,
|
||||||
|
Offset: page.Offset,
|
||||||
|
Size: page.Size,
|
||||||
|
Keyword: params.Keyword,
|
||||||
|
})
|
||||||
default:
|
default:
|
||||||
page = this.NewPage(countAll)
|
page = this.NewPage(countAll)
|
||||||
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{
|
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{
|
||||||
|
UserId: params.UserId,
|
||||||
Keyword: params.Keyword,
|
Keyword: params.Keyword,
|
||||||
Offset: page.Offset,
|
Offset: page.Offset,
|
||||||
Size: page.Size,
|
Size: page.Size,
|
||||||
@@ -131,7 +187,7 @@ func (this *IndexAction) RunGet(params struct {
|
|||||||
|
|
||||||
this.Data["page"] = page.AsHTML()
|
this.Data["page"] = page.AsHTML()
|
||||||
|
|
||||||
taskMaps := []maps.Map{}
|
var taskMaps = []maps.Map{}
|
||||||
for _, task := range tasksResp.AcmeTasks {
|
for _, task := range tasksResp.AcmeTasks {
|
||||||
if task.AcmeUser == nil {
|
if task.AcmeUser == nil {
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ type RunAction struct {
|
|||||||
func (this *RunAction) RunPost(params struct {
|
func (this *RunAction) RunPost(params struct {
|
||||||
TaskId int64
|
TaskId int64
|
||||||
}) {
|
}) {
|
||||||
|
defer this.CreateLogInfo("执行ACME任务 %d", params.TaskId)
|
||||||
|
|
||||||
runResp, err := this.RPC().ACMETaskRPC().RunACMETask(this.AdminContext(), &pb.RunACMETaskRequest{AcmeTaskId: params.TaskId})
|
runResp, err := this.RPC().ACMETaskRPC().RunACMETask(this.AdminContext(), &pb.RunACMETaskRequest{AcmeTaskId: params.TaskId})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.ErrorPage(err)
|
this.ErrorPage(err)
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||||
|
|
||||||
|
package acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/iwind/TeaGo/maps"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserOptionsAction struct {
|
||||||
|
actionutils.ParentAction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *UserOptionsAction) RunPost(params struct {
|
||||||
|
PlatformUserId int64
|
||||||
|
}) {
|
||||||
|
// 获取所有可用的用户
|
||||||
|
usersResp, err := this.RPC().ACMEUserRPC().FindAllACMEUsers(this.AdminContext(), &pb.FindAllACMEUsersRequest{
|
||||||
|
AdminId: 0,
|
||||||
|
UserId: params.PlatformUserId,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var userMaps = []maps.Map{}
|
||||||
|
for _, user := range usersResp.AcmeUsers {
|
||||||
|
description := user.Description
|
||||||
|
if len(description) > 0 {
|
||||||
|
description = "(" + description + ")"
|
||||||
|
}
|
||||||
|
|
||||||
|
userMaps = append(userMaps, maps.Map{
|
||||||
|
"id": user.Id,
|
||||||
|
"description": description,
|
||||||
|
"email": user.Email,
|
||||||
|
"providerCode": user.AcmeProviderCode,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.Data["users"] = userMaps
|
||||||
|
|
||||||
|
this.Success()
|
||||||
|
}
|
||||||
@@ -16,10 +16,30 @@ func (this *CreatePopupAction) Init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *CreatePopupAction) RunGet(params struct {
|
func (this *CreatePopupAction) RunGet(params struct {
|
||||||
ProviderCode string
|
PlatformUserId int64
|
||||||
|
ProviderCode string
|
||||||
}) {
|
}) {
|
||||||
|
this.Data["platformUserId"] = params.PlatformUserId
|
||||||
this.Data["providerCode"] = params.ProviderCode
|
this.Data["providerCode"] = params.ProviderCode
|
||||||
|
|
||||||
|
// 平台用户信息
|
||||||
|
this.Data["platformUser"] = nil
|
||||||
|
if params.PlatformUserId > 0 {
|
||||||
|
platformUserResp, err := this.RPC().UserRPC().FindEnabledUser(this.AdminContext(), &pb.FindEnabledUserRequest{UserId: params.PlatformUserId})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var platformUser = platformUserResp.User
|
||||||
|
if platformUser != nil {
|
||||||
|
this.Data["platformUser"] = maps.Map{
|
||||||
|
"id": platformUser.Id,
|
||||||
|
"username": platformUser.Username,
|
||||||
|
"fullname": platformUser.Fullname,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 服务商
|
// 服务商
|
||||||
providersResp, err := this.RPC().ACMEProviderRPC().FindAllACMEProviders(this.AdminContext(), &pb.FindAllACMEProvidersRequest{})
|
providersResp, err := this.RPC().ACMEProviderRPC().FindAllACMEProviders(this.AdminContext(), &pb.FindAllACMEProvidersRequest{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -40,10 +60,11 @@ func (this *CreatePopupAction) RunGet(params struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *CreatePopupAction) RunPost(params struct {
|
func (this *CreatePopupAction) RunPost(params struct {
|
||||||
Email string
|
PlatformUserId int64
|
||||||
ProviderCode string
|
Email string
|
||||||
AccountId int64
|
ProviderCode string
|
||||||
Description string
|
AccountId int64
|
||||||
|
Description string
|
||||||
|
|
||||||
Must *actions.Must
|
Must *actions.Must
|
||||||
CSRF *actionutils.CSRF
|
CSRF *actionutils.CSRF
|
||||||
@@ -85,6 +106,7 @@ func (this *CreatePopupAction) RunPost(params struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
createResp, err := this.RPC().ACMEUserRPC().CreateACMEUser(this.AdminContext(), &pb.CreateACMEUserRequest{
|
createResp, err := this.RPC().ACMEUserRPC().CreateACMEUser(this.AdminContext(), &pb.CreateACMEUserRequest{
|
||||||
|
UserId: params.PlatformUserId,
|
||||||
Email: params.Email,
|
Email: params.Email,
|
||||||
Description: params.Description,
|
Description: params.Description,
|
||||||
AcmeProviderCode: params.ProviderCode,
|
AcmeProviderCode: params.ProviderCode,
|
||||||
|
|||||||
@@ -19,12 +19,37 @@ func (this *IndexAction) Init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *IndexAction) RunGet(params struct {
|
func (this *IndexAction) RunGet(params struct {
|
||||||
|
UserId int64
|
||||||
Type string
|
Type string
|
||||||
Keyword string
|
Keyword string
|
||||||
}) {
|
}) {
|
||||||
this.Data["type"] = params.Type
|
this.Data["type"] = params.Type
|
||||||
this.Data["keyword"] = params.Keyword
|
this.Data["keyword"] = params.Keyword
|
||||||
|
|
||||||
|
// 当前用户
|
||||||
|
this.Data["searchingUserId"] = params.UserId
|
||||||
|
var userMap = maps.Map{
|
||||||
|
"id": 0,
|
||||||
|
"username": "",
|
||||||
|
"fullname": "",
|
||||||
|
}
|
||||||
|
if params.UserId > 0 {
|
||||||
|
userResp, err := this.RPC().UserRPC().FindEnabledUser(this.AdminContext(), &pb.FindEnabledUserRequest{UserId: params.UserId})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var user = userResp.User
|
||||||
|
if user != nil {
|
||||||
|
userMap = maps.Map{
|
||||||
|
"id": user.Id,
|
||||||
|
"username": user.Username,
|
||||||
|
"fullname": user.Fullname,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.Data["user"] = userMap
|
||||||
|
|
||||||
var countAll = int64(0)
|
var countAll = int64(0)
|
||||||
var countCA = int64(0)
|
var countCA = int64(0)
|
||||||
var countAvailable = int64(0)
|
var countAvailable = int64(0)
|
||||||
@@ -36,6 +61,7 @@ func (this *IndexAction) RunGet(params struct {
|
|||||||
{
|
{
|
||||||
// all
|
// all
|
||||||
resp, err := this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
|
resp, err := this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
|
||||||
|
UserId: params.UserId,
|
||||||
Keyword: params.Keyword,
|
Keyword: params.Keyword,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -46,6 +72,7 @@ func (this *IndexAction) RunGet(params struct {
|
|||||||
|
|
||||||
// CA
|
// CA
|
||||||
resp, err = this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
|
resp, err = this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
|
||||||
|
UserId: params.UserId,
|
||||||
IsCA: true,
|
IsCA: true,
|
||||||
Keyword: params.Keyword,
|
Keyword: params.Keyword,
|
||||||
})
|
})
|
||||||
@@ -57,6 +84,7 @@ func (this *IndexAction) RunGet(params struct {
|
|||||||
|
|
||||||
// available
|
// available
|
||||||
resp, err = this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
|
resp, err = this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
|
||||||
|
UserId: params.UserId,
|
||||||
IsAvailable: true,
|
IsAvailable: true,
|
||||||
Keyword: params.Keyword,
|
Keyword: params.Keyword,
|
||||||
})
|
})
|
||||||
@@ -68,6 +96,7 @@ func (this *IndexAction) RunGet(params struct {
|
|||||||
|
|
||||||
// expired
|
// expired
|
||||||
resp, err = this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
|
resp, err = this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
|
||||||
|
UserId: params.UserId,
|
||||||
IsExpired: true,
|
IsExpired: true,
|
||||||
Keyword: params.Keyword,
|
Keyword: params.Keyword,
|
||||||
})
|
})
|
||||||
@@ -79,6 +108,7 @@ func (this *IndexAction) RunGet(params struct {
|
|||||||
|
|
||||||
// expire in 7 days
|
// expire in 7 days
|
||||||
resp, err = this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
|
resp, err = this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
|
||||||
|
UserId: params.UserId,
|
||||||
ExpiringDays: 7,
|
ExpiringDays: 7,
|
||||||
Keyword: params.Keyword,
|
Keyword: params.Keyword,
|
||||||
})
|
})
|
||||||
@@ -90,6 +120,7 @@ func (this *IndexAction) RunGet(params struct {
|
|||||||
|
|
||||||
// expire in 30 days
|
// expire in 30 days
|
||||||
resp, err = this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
|
resp, err = this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
|
||||||
|
UserId: params.UserId,
|
||||||
ExpiringDays: 30,
|
ExpiringDays: 30,
|
||||||
Keyword: params.Keyword,
|
Keyword: params.Keyword,
|
||||||
})
|
})
|
||||||
@@ -115,28 +146,60 @@ func (this *IndexAction) RunGet(params struct {
|
|||||||
case "":
|
case "":
|
||||||
page = this.NewPage(countAll)
|
page = this.NewPage(countAll)
|
||||||
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
|
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
|
||||||
|
UserId: params.UserId,
|
||||||
Offset: page.Offset,
|
Offset: page.Offset,
|
||||||
Size: page.Size,
|
Size: page.Size,
|
||||||
Keyword: params.Keyword,
|
Keyword: params.Keyword,
|
||||||
})
|
})
|
||||||
case "ca":
|
case "ca":
|
||||||
page = this.NewPage(countCA)
|
page = this.NewPage(countCA)
|
||||||
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{IsCA: true, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
|
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
|
||||||
|
UserId: params.UserId,
|
||||||
|
IsCA: true,
|
||||||
|
Offset: page.Offset,
|
||||||
|
Size: page.Size,
|
||||||
|
Keyword: params.Keyword,
|
||||||
|
})
|
||||||
case "available":
|
case "available":
|
||||||
page = this.NewPage(countAvailable)
|
page = this.NewPage(countAvailable)
|
||||||
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{IsAvailable: true, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
|
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
|
||||||
|
UserId: params.UserId,
|
||||||
|
IsAvailable: true,
|
||||||
|
Offset: page.Offset,
|
||||||
|
Size: page.Size,
|
||||||
|
Keyword: params.Keyword,
|
||||||
|
})
|
||||||
case "expired":
|
case "expired":
|
||||||
page = this.NewPage(countExpired)
|
page = this.NewPage(countExpired)
|
||||||
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{IsExpired: true, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
|
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
|
||||||
|
UserId: params.UserId,
|
||||||
|
IsExpired: true,
|
||||||
|
Offset: page.Offset,
|
||||||
|
Size: page.Size,
|
||||||
|
Keyword: params.Keyword,
|
||||||
|
})
|
||||||
case "7days":
|
case "7days":
|
||||||
page = this.NewPage(count7Days)
|
page = this.NewPage(count7Days)
|
||||||
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{ExpiringDays: 7, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
|
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
|
||||||
|
UserId: params.UserId,
|
||||||
|
ExpiringDays: 7,
|
||||||
|
Offset: page.Offset,
|
||||||
|
Size: page.Size,
|
||||||
|
Keyword: params.Keyword,
|
||||||
|
})
|
||||||
case "30days":
|
case "30days":
|
||||||
page = this.NewPage(count30Days)
|
page = this.NewPage(count30Days)
|
||||||
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{ExpiringDays: 30, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
|
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
|
||||||
|
UserId: params.UserId,
|
||||||
|
ExpiringDays: 30,
|
||||||
|
Offset: page.Offset,
|
||||||
|
Size: page.Size,
|
||||||
|
Keyword: params.Keyword,
|
||||||
|
})
|
||||||
default:
|
default:
|
||||||
page = this.NewPage(countAll)
|
page = this.NewPage(countAll)
|
||||||
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
|
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
|
||||||
|
UserId: params.UserId,
|
||||||
Keyword: params.Keyword,
|
Keyword: params.Keyword,
|
||||||
Offset: page.Offset,
|
Offset: page.Offset,
|
||||||
Size: page.Size,
|
Size: page.Size,
|
||||||
@@ -158,7 +221,9 @@ func (this *IndexAction) RunGet(params struct {
|
|||||||
var certMaps = []maps.Map{}
|
var certMaps = []maps.Map{}
|
||||||
var nowTime = time.Now().Unix()
|
var nowTime = time.Now().Unix()
|
||||||
for _, certConfig := range certConfigs {
|
for _, certConfig := range certConfigs {
|
||||||
countServersResp, err := this.RPC().ServerRPC().CountAllEnabledServersWithSSLCertId(this.AdminContext(), &pb.CountAllEnabledServersWithSSLCertIdRequest{SslCertId: certConfig.Id})
|
countServersResp, err := this.RPC().ServerRPC().CountAllEnabledServersWithSSLCertId(this.AdminContext(), &pb.CountAllEnabledServersWithSSLCertIdRequest{
|
||||||
|
SslCertId: certConfig.Id,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.ErrorPage(err)
|
this.ErrorPage(err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ func init() {
|
|||||||
Post("/run", new(acme.RunAction)).
|
Post("/run", new(acme.RunAction)).
|
||||||
GetPost("/updateTaskPopup", new(acme.UpdateTaskPopupAction)).
|
GetPost("/updateTaskPopup", new(acme.UpdateTaskPopupAction)).
|
||||||
Post("/deleteTask", new(acme.DeleteTaskAction)).
|
Post("/deleteTask", new(acme.DeleteTaskAction)).
|
||||||
|
Post("/userOptions", new(acme.UserOptionsAction)).
|
||||||
|
|
||||||
// ACME用户
|
// ACME用户
|
||||||
Prefix("/servers/certs/acme/users").
|
Prefix("/servers/certs/acme/users").
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ func (this *SelectPopupAction) RunGet(params struct {
|
|||||||
SelectedCertIds string
|
SelectedCertIds string
|
||||||
Keyword string
|
Keyword string
|
||||||
}) {
|
}) {
|
||||||
|
this.Data["searchingServerId"] = params.ServerId
|
||||||
|
|
||||||
// 服务相关
|
// 服务相关
|
||||||
if params.ServerId > 0 {
|
if params.ServerId > 0 {
|
||||||
serverResp, err := this.RPC().ServerRPC().FindEnabledUserServerBasic(this.AdminContext(), &pb.FindEnabledUserServerBasicRequest{ServerId: params.ServerId})
|
serverResp, err := this.RPC().ServerRPC().FindEnabledUserServerBasic(this.AdminContext(), &pb.FindEnabledUserServerBasicRequest{ServerId: params.ServerId})
|
||||||
@@ -66,7 +68,8 @@ func (this *SelectPopupAction) RunGet(params struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 用户相关
|
// 用户相关
|
||||||
this.Data["userId"] = params.UserId
|
this.Data["userId"] = params.UserId // 可变
|
||||||
|
this.Data["searchingUserId"] = params.UserId
|
||||||
|
|
||||||
// 域名搜索相关
|
// 域名搜索相关
|
||||||
var url = this.Request.URL.Path
|
var url = this.Request.URL.Path
|
||||||
@@ -82,6 +85,7 @@ func (this *SelectPopupAction) RunGet(params struct {
|
|||||||
if len(searchingDomains) > maxDomains {
|
if len(searchingDomains) > maxDomains {
|
||||||
searchingDomains = searchingDomains[:maxDomains]
|
searchingDomains = searchingDomains[:maxDomains]
|
||||||
}
|
}
|
||||||
|
this.Data["allSearchingDomains"] = params.SearchingDomains
|
||||||
this.Data["searchingDomains"] = searchingDomains
|
this.Data["searchingDomains"] = searchingDomains
|
||||||
|
|
||||||
this.Data["keyword"] = params.Keyword
|
this.Data["keyword"] = params.Keyword
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func KeyFailReason(reasonCode string) string {
|
|||||||
case "requireDomain":
|
case "requireDomain":
|
||||||
return "找不到Key对应的域名"
|
return "找不到Key对应的域名"
|
||||||
case "requireServer":
|
case "requireServer":
|
||||||
return "找不到Key对应的网站服务"
|
return "找不到Key对应的网站"
|
||||||
case "requireUser":
|
case "requireUser":
|
||||||
return "该域名不属于当前用户"
|
return "该域名不属于当前用户"
|
||||||
case "requireClusterId":
|
case "requireClusterId":
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ func (this *IndexAction) RunPost(params struct {
|
|||||||
DefaultDomain string
|
DefaultDomain string
|
||||||
}) {
|
}) {
|
||||||
// 创建日志
|
// 创建日志
|
||||||
defer this.CreateLog(oplogs.LevelInfo, "保存网站服务全局配置")
|
defer this.CreateLog(oplogs.LevelInfo, "保存网站全局配置")
|
||||||
|
|
||||||
if len(params.GlobalConfigJSON) == 0 {
|
if len(params.GlobalConfigJSON) == 0 {
|
||||||
this.Fail("错误的配置信息,请刷新当前页面后重试")
|
this.Fail("错误的配置信息,请刷新当前页面后重试")
|
||||||
|
|||||||
@@ -45,14 +45,14 @@ func (this *CreateDeletePopupAction) RunPost(params struct {
|
|||||||
this.ErrorPage(err)
|
this.ErrorPage(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
policyConfig := &shared.HTTPHeaderPolicy{}
|
var policyConfig = &shared.HTTPHeaderPolicy{}
|
||||||
err = json.Unmarshal(policyConfigResp.HttpHeaderPolicyJSON, policyConfig)
|
err = json.Unmarshal(policyConfigResp.HttpHeaderPolicyJSON, policyConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.ErrorPage(err)
|
this.ErrorPage(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteHeaders := policyConfig.DeleteHeaders
|
var deleteHeaders = policyConfig.DeleteHeaders
|
||||||
deleteHeaders = append(deleteHeaders, params.Name)
|
deleteHeaders = append(deleteHeaders, params.Name)
|
||||||
_, err = this.RPC().HTTPHeaderPolicyRPC().UpdateHTTPHeaderPolicyDeletingHeaders(this.AdminContext(), &pb.UpdateHTTPHeaderPolicyDeletingHeadersRequest{
|
_, err = this.RPC().HTTPHeaderPolicyRPC().UpdateHTTPHeaderPolicyDeletingHeaders(this.AdminContext(), &pb.UpdateHTTPHeaderPolicyDeletingHeadersRequest{
|
||||||
HttpHeaderPolicyId: params.HeaderPolicyId,
|
HttpHeaderPolicyId: params.HeaderPolicyId,
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -17,22 +17,22 @@ func (this *DeleteDeletingHeaderAction) RunPost(params struct {
|
|||||||
HeaderName string
|
HeaderName string
|
||||||
}) {
|
}) {
|
||||||
// 日志
|
// 日志
|
||||||
defer this.CreateLog(oplogs.LevelInfo, "删除需要删除的请求Header,HeaderPolicyId:%d, HeaderName:%s", params.HeaderPolicyId, params.HeaderName)
|
defer this.CreateLog(oplogs.LevelInfo, "删除需要删除的Header,HeaderPolicyId:%d, HeaderName:%s", params.HeaderPolicyId, params.HeaderName)
|
||||||
|
|
||||||
policyConfigResp, err := this.RPC().HTTPHeaderPolicyRPC().FindEnabledHTTPHeaderPolicyConfig(this.AdminContext(), &pb.FindEnabledHTTPHeaderPolicyConfigRequest{HttpHeaderPolicyId: params.HeaderPolicyId})
|
policyConfigResp, err := this.RPC().HTTPHeaderPolicyRPC().FindEnabledHTTPHeaderPolicyConfig(this.AdminContext(), &pb.FindEnabledHTTPHeaderPolicyConfigRequest{HttpHeaderPolicyId: params.HeaderPolicyId})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.ErrorPage(err)
|
this.ErrorPage(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
policyConfigJSON := policyConfigResp.HttpHeaderPolicyJSON
|
var policyConfigJSON = policyConfigResp.HttpHeaderPolicyJSON
|
||||||
policyConfig := &shared.HTTPHeaderPolicy{}
|
var policyConfig = &shared.HTTPHeaderPolicy{}
|
||||||
err = json.Unmarshal(policyConfigJSON, policyConfig)
|
err = json.Unmarshal(policyConfigJSON, policyConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.ErrorPage(err)
|
this.ErrorPage(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
headerNames := []string{}
|
var headerNames = []string{}
|
||||||
for _, h := range policyConfig.DeleteHeaders {
|
for _, h := range policyConfig.DeleteHeaders {
|
||||||
if h == params.HeaderName {
|
if h == params.HeaderName {
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -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, "删除需要非标的Header,HeaderPolicyId:%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()
|
||||||
|
}
|
||||||
@@ -18,6 +18,8 @@ func init() {
|
|||||||
GetPost("/updateSetPopup", new(UpdateSetPopupAction)).
|
GetPost("/updateSetPopup", new(UpdateSetPopupAction)).
|
||||||
GetPost("/createDeletePopup", new(CreateDeletePopupAction)).
|
GetPost("/createDeletePopup", new(CreateDeletePopupAction)).
|
||||||
Post("/deleteDeletingHeader", new(DeleteDeletingHeaderAction)).
|
Post("/deleteDeletingHeader", new(DeleteDeletingHeaderAction)).
|
||||||
|
GetPost("/createNonStandardPopup", new(CreateNonStandardPopupAction)).
|
||||||
|
Post("/deleteNonStandardHeader", new(DeleteNonStandardHeaderAction)).
|
||||||
Post("/delete", new(DeleteAction)).
|
Post("/delete", new(DeleteAction)).
|
||||||
GetPost("/updateCORSPopup", new(UpdateCORSPopupAction)).
|
GetPost("/updateCORSPopup", new(UpdateCORSPopupAction)).
|
||||||
EndAll()
|
EndAll()
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ func (this *UpdateCORSPopupAction) RunPost(params struct {
|
|||||||
Must *actions.Must
|
Must *actions.Must
|
||||||
CSRF *actionutils.CSRF
|
CSRF *actionutils.CSRF
|
||||||
}) {
|
}) {
|
||||||
var config = &shared.HTTPCORSHeaderConfig{}
|
var config = shared.NewHTTPCORSHeaderConfig()
|
||||||
err := json.Unmarshal(params.CorsJSON, config)
|
err := json.Unmarshal(params.CorsJSON, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.Fail("配置校验失败:" + err.Error())
|
this.Fail("配置校验失败:" + err.Error())
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ func (this *IndexAction) RunGet(params struct {
|
|||||||
AllowEmpty: true,
|
AllowEmpty: true,
|
||||||
AllowSameDomain: true,
|
AllowSameDomain: true,
|
||||||
AllowDomains: nil,
|
AllowDomains: nil,
|
||||||
|
CheckOrigin: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ func (this *IndexAction) RunGet(params struct {
|
|||||||
AllowEmpty: true,
|
AllowEmpty: true,
|
||||||
AllowSameDomain: true,
|
AllowSameDomain: true,
|
||||||
AllowDomains: nil,
|
AllowDomains: nil,
|
||||||
|
CheckOrigin: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
|
||||||
"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/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||||||
"github.com/iwind/TeaGo/actions"
|
"github.com/iwind/TeaGo/actions"
|
||||||
"github.com/iwind/TeaGo/lists"
|
"github.com/iwind/TeaGo/lists"
|
||||||
"github.com/iwind/TeaGo/maps"
|
"github.com/iwind/TeaGo/maps"
|
||||||
@@ -41,7 +42,7 @@ func (this *CountriesAction) RunGet(params struct {
|
|||||||
this.NotFound("firewallPolicy", params.FirewallPolicyId)
|
this.NotFound("firewallPolicy", params.FirewallPolicyId)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
selectedCountryIds := []int64{}
|
var selectedCountryIds = []int64{}
|
||||||
if policyConfig.Inbound != nil && policyConfig.Inbound.Region != nil {
|
if policyConfig.Inbound != nil && policyConfig.Inbound.Region != nil {
|
||||||
selectedCountryIds = policyConfig.Inbound.Region.DenyCountryIds
|
selectedCountryIds = policyConfig.Inbound.Region.DenyCountryIds
|
||||||
}
|
}
|
||||||
@@ -51,7 +52,7 @@ func (this *CountriesAction) RunGet(params struct {
|
|||||||
this.ErrorPage(err)
|
this.ErrorPage(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
countryMaps := []maps.Map{}
|
var countryMaps = []maps.Map{}
|
||||||
for _, country := range countriesResp.RegionCountries {
|
for _, country := range countriesResp.RegionCountries {
|
||||||
countryMaps = append(countryMaps, maps.Map{
|
countryMaps = append(countryMaps, maps.Map{
|
||||||
"id": country.Id,
|
"id": country.Id,
|
||||||
@@ -62,6 +63,18 @@ func (this *CountriesAction) RunGet(params struct {
|
|||||||
}
|
}
|
||||||
this.Data["countries"] = countryMaps
|
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是否启用
|
// WAF是否启用
|
||||||
webConfig, err := dao.SharedHTTPWebDAO.FindWebConfigWithServerId(this.AdminContext(), params.ServerId)
|
webConfig, err := dao.SharedHTTPWebDAO.FindWebConfigWithServerId(this.AdminContext(), params.ServerId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -77,6 +90,9 @@ func (this *CountriesAction) RunPost(params struct {
|
|||||||
FirewallPolicyId int64
|
FirewallPolicyId int64
|
||||||
CountryIds []int64
|
CountryIds []int64
|
||||||
|
|
||||||
|
ExceptURLPatternsJSON []byte
|
||||||
|
OnlyURLPatternsJSON []byte
|
||||||
|
|
||||||
Must *actions.Must
|
Must *actions.Must
|
||||||
}) {
|
}) {
|
||||||
// 日志
|
// 日志
|
||||||
@@ -102,6 +118,34 @@ func (this *CountriesAction) RunPost(params struct {
|
|||||||
}
|
}
|
||||||
policyConfig.Inbound.Region.DenyCountryIds = params.CountryIds
|
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)
|
inboundJSON, err := json.Marshal(policyConfig.Inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.ErrorPage(err)
|
this.ErrorPage(err)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
|
||||||
"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/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||||||
"github.com/iwind/TeaGo/actions"
|
"github.com/iwind/TeaGo/actions"
|
||||||
"github.com/iwind/TeaGo/lists"
|
"github.com/iwind/TeaGo/lists"
|
||||||
"github.com/iwind/TeaGo/maps"
|
"github.com/iwind/TeaGo/maps"
|
||||||
@@ -41,7 +42,7 @@ func (this *ProvincesAction) RunGet(params struct {
|
|||||||
this.NotFound("firewallPolicy", params.FirewallPolicyId)
|
this.NotFound("firewallPolicy", params.FirewallPolicyId)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
selectedProvinceIds := []int64{}
|
var selectedProvinceIds = []int64{}
|
||||||
if policyConfig.Inbound != nil && policyConfig.Inbound.Region != nil {
|
if policyConfig.Inbound != nil && policyConfig.Inbound.Region != nil {
|
||||||
selectedProvinceIds = policyConfig.Inbound.Region.DenyProvinceIds
|
selectedProvinceIds = policyConfig.Inbound.Region.DenyProvinceIds
|
||||||
}
|
}
|
||||||
@@ -53,7 +54,7 @@ func (this *ProvincesAction) RunGet(params struct {
|
|||||||
this.ErrorPage(err)
|
this.ErrorPage(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
provinceMaps := []maps.Map{}
|
var provinceMaps = []maps.Map{}
|
||||||
for _, province := range provincesResp.RegionProvinces {
|
for _, province := range provincesResp.RegionProvinces {
|
||||||
provinceMaps = append(provinceMaps, maps.Map{
|
provinceMaps = append(provinceMaps, maps.Map{
|
||||||
"id": province.Id,
|
"id": province.Id,
|
||||||
@@ -63,6 +64,18 @@ func (this *ProvincesAction) RunGet(params struct {
|
|||||||
}
|
}
|
||||||
this.Data["provinces"] = provinceMaps
|
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是否启用
|
// WAF是否启用
|
||||||
webConfig, err := dao.SharedHTTPWebDAO.FindWebConfigWithServerId(this.AdminContext(), params.ServerId)
|
webConfig, err := dao.SharedHTTPWebDAO.FindWebConfigWithServerId(this.AdminContext(), params.ServerId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -78,6 +91,9 @@ func (this *ProvincesAction) RunPost(params struct {
|
|||||||
FirewallPolicyId int64
|
FirewallPolicyId int64
|
||||||
ProvinceIds []int64
|
ProvinceIds []int64
|
||||||
|
|
||||||
|
ExceptURLPatternsJSON []byte
|
||||||
|
OnlyURLPatternsJSON []byte
|
||||||
|
|
||||||
Must *actions.Must
|
Must *actions.Must
|
||||||
}) {
|
}) {
|
||||||
// 日志
|
// 日志
|
||||||
@@ -103,6 +119,34 @@ func (this *ProvincesAction) RunPost(params struct {
|
|||||||
}
|
}
|
||||||
policyConfig.Inbound.Region.DenyProvinceIds = params.ProvinceIds
|
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)
|
inboundJSON, err := json.Marshal(policyConfig.Inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.ErrorPage(err)
|
this.ErrorPage(err)
|
||||||
|
|||||||
@@ -106,8 +106,13 @@ func (this *ServerHelper) createLeftMenu(action *actions.ActionObject) {
|
|||||||
|
|
||||||
// TABBAR
|
// TABBAR
|
||||||
selectedTabbar, _ := action.Data["mainTab"]
|
selectedTabbar, _ := action.Data["mainTab"]
|
||||||
tabbar := actionutils.NewTabbar()
|
var tabbar = actionutils.NewTabbar()
|
||||||
tabbar.Add("服务列表", "", "/servers", "", false)
|
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 {
|
if teaconst.IsPlus {
|
||||||
tabbar.Add("看板", "", "/servers/server/boards?serverId="+serverIdString, "dashboard", selectedTabbar == "board")
|
tabbar.Add("看板", "", "/servers/server/boards?serverId="+serverIdString, "dashboard", selectedTabbar == "board")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"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/maps"
|
"github.com/iwind/TeaGo/maps"
|
||||||
|
"sort"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CleanAction struct {
|
type CleanAction struct {
|
||||||
@@ -16,11 +17,20 @@ func (this *CleanAction) Init() {
|
|||||||
this.Nav("", "", "clean")
|
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()
|
this.Show()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *CleanAction) RunPost(params struct {
|
func (this *CleanAction) RunPost(params struct {
|
||||||
|
OrderTable string
|
||||||
|
OrderSize string
|
||||||
|
|
||||||
Must *actions.Must
|
Must *actions.Must
|
||||||
}) {
|
}) {
|
||||||
tablesResp, err := this.RPC().DBRPC().FindAllDBTables(this.AdminContext(), &pb.FindAllDBTablesRequest{})
|
tablesResp, err := this.RPC().DBRPC().FindAllDBTables(this.AdminContext(), &pb.FindAllDBTablesRequest{})
|
||||||
@@ -28,9 +38,33 @@ func (this *CleanAction) RunPost(params struct {
|
|||||||
this.ErrorPage(err)
|
this.ErrorPage(err)
|
||||||
return
|
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) {
|
if !table.IsBaseTable || (!table.CanClean && !table.CanDelete) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,6 +79,9 @@ func (this *IndexAction) RunPost(params struct {
|
|||||||
DenySearchEngines bool
|
DenySearchEngines bool
|
||||||
DenySpiders bool
|
DenySpiders bool
|
||||||
|
|
||||||
|
CheckClientFingerprint bool
|
||||||
|
CheckClientRegion bool
|
||||||
|
|
||||||
DomainsJSON []byte
|
DomainsJSON []byte
|
||||||
|
|
||||||
Must *actions.Must
|
Must *actions.Must
|
||||||
@@ -150,6 +153,10 @@ func (this *IndexAction) RunPost(params struct {
|
|||||||
// 允许记住登录
|
// 允许记住登录
|
||||||
config.AllowRememberLogin = params.AllowRememberLogin
|
config.AllowRememberLogin = params.AllowRememberLogin
|
||||||
|
|
||||||
|
// Cookie检查
|
||||||
|
config.CheckClientFingerprint = params.CheckClientFingerprint
|
||||||
|
config.CheckClientRegion = params.CheckClientRegion
|
||||||
|
|
||||||
err = configloaders.UpdateSecurityConfig(config)
|
err = configloaders.UpdateSecurityConfig(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.ErrorPage(err)
|
this.ErrorPage(err)
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ func FindAllMenuMaps(nodeLogsType string, countUnreadNodeLogs int64, countUnread
|
|||||||
{
|
{
|
||||||
"code": "servers",
|
"code": "servers",
|
||||||
"module": configloaders.AdminModuleCodeServer,
|
"module": configloaders.AdminModuleCodeServer,
|
||||||
"name": "网站服务",
|
"name": "网站列表",
|
||||||
"subtitle": "服务列表",
|
"subtitle": "",
|
||||||
"icon": "clone outsize",
|
"icon": "clone outsize",
|
||||||
"subItems": []maps.Map{
|
"subItems": []maps.Map{
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -170,18 +170,42 @@ func (this *userMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramNam
|
|||||||
var adminId = session.GetInt64("adminId")
|
var adminId = session.GetInt64("adminId")
|
||||||
|
|
||||||
if adminId <= 0 {
|
if adminId <= 0 {
|
||||||
|
var errString = session.GetString("@error")
|
||||||
|
if len(errString) > 0 {
|
||||||
|
action.WriteString("read session failed: " + errString)
|
||||||
|
return false
|
||||||
|
}
|
||||||
this.login(action)
|
this.login(action)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查指纹
|
// 检查指纹
|
||||||
var clientFingerprint = session.GetString("@fingerprint")
|
if securityConfig != nil && securityConfig.CheckClientFingerprint {
|
||||||
if len(clientFingerprint) > 0 && clientFingerprint != loginutils.CalculateClientFingerprint(action) {
|
var clientFingerprint = session.GetString("@fingerprint")
|
||||||
loginutils.UnsetCookie(action)
|
if len(clientFingerprint) > 0 && clientFingerprint != loginutils.CalculateClientFingerprint(action) {
|
||||||
session.Delete()
|
loginutils.UnsetCookie(action)
|
||||||
|
session.Delete()
|
||||||
|
|
||||||
this.login(action)
|
this.login(action)
|
||||||
return false
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查区域
|
||||||
|
if securityConfig != nil && securityConfig.CheckClientRegion {
|
||||||
|
var oldClientIP = session.GetString("@ip")
|
||||||
|
var currentClientIP = loginutils.RemoteIP(action)
|
||||||
|
if len(oldClientIP) > 0 && len(currentClientIP) > 0 && oldClientIP != currentClientIP {
|
||||||
|
var oldRegion = loginutils.LookupIPRegion(oldClientIP)
|
||||||
|
var newRegion = loginutils.LookupIPRegion(currentClientIP)
|
||||||
|
if newRegion != oldRegion {
|
||||||
|
loginutils.UnsetCookie(action)
|
||||||
|
session.Delete()
|
||||||
|
|
||||||
|
this.login(action)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查用户是否存在
|
// 检查用户是否存在
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ func (this *UserShouldAuth) StoreAdmin(adminId int64, remember bool) {
|
|||||||
var session = this.action.Session()
|
var session = this.action.Session()
|
||||||
session.Write("adminId", numberutils.FormatInt64(adminId))
|
session.Write("adminId", numberutils.FormatInt64(adminId))
|
||||||
session.Write("@fingerprint", loginutils.CalculateClientFingerprint(this.action))
|
session.Write("@fingerprint", loginutils.CalculateClientFingerprint(this.action))
|
||||||
|
session.Write("@ip", loginutils.RemoteIP(this.action))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *UserShouldAuth) IsUser() bool {
|
func (this *UserShouldAuth) IsUser() bool {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
Vue.component("health-check-config-box", {
|
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 () {
|
data: function () {
|
||||||
let healthCheckConfig = this.vHealthCheckConfig
|
let healthCheckConfig = this.vHealthCheckConfig
|
||||||
let urlProtocol = "http"
|
let urlProtocol = "http"
|
||||||
@@ -69,6 +69,7 @@ Vue.component("health-check-config-box", {
|
|||||||
healthCheckConfig.countDown = 3
|
healthCheckConfig.countDown = 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
healthCheck: healthCheckConfig,
|
healthCheck: healthCheckConfig,
|
||||||
advancedVisible: false,
|
advancedVisible: false,
|
||||||
@@ -237,13 +238,13 @@ Vue.component("health-check-config-box", {
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>自动下线</td>
|
<td>自动下线<span v-if="vIsPlus">IP</span></td>
|
||||||
<td>
|
<td>
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" value="1" v-model="healthCheck.autoDown"/>
|
<input type="checkbox" value="1" v-model="healthCheck.autoDown"/>
|
||||||
<label></label>
|
<label></label>
|
||||||
</div>
|
</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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-show="healthCheck.autoDown">
|
<tr v-show="healthCheck.autoDown">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
Vue.component("time-duration-box", {
|
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 () {
|
mounted: function () {
|
||||||
this.change()
|
this.change()
|
||||||
},
|
},
|
||||||
@@ -14,9 +14,18 @@ Vue.component("time-duration-box", {
|
|||||||
if (typeof (v["count"]) != "number") {
|
if (typeof (v["count"]) != "number") {
|
||||||
v["count"] = -1
|
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 {
|
return {
|
||||||
duration: v,
|
duration: v,
|
||||||
countString: (v.count >= 0) ? v.count.toString() : ""
|
countString: (v.count >= 0) ? v.count.toString() : "",
|
||||||
|
realName: realName
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -39,7 +48,7 @@ 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="vName" :value="JSON.stringify(duration)"/>
|
<input type="hidden" :name="realName" :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="11" size="11" @keypress.enter.prevent="1"/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -95,6 +95,16 @@ Vue.component("message-row", {
|
|||||||
<div v-if="message.type == 'serverNamesRequireAuditing'" style="margin-top: 0.8em">
|
<div v-if="message.type == 'serverNamesRequireAuditing'" style="margin-top: 0.8em">
|
||||||
<a :href="'/servers/server/settings/serverNames?serverId=' + params.serverId" target="_top">去审核</a></a>
|
<a :href="'/servers/server/settings/serverNames?serverId=' + params.serverId" target="_top">去审核</a></a>
|
||||||
</div>
|
</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">查看调度状态 »</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">查看详情 »</a>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ Vue.component("http-access-log-box", {
|
|||||||
this.$refs.box.parentNode.style.cssText = ""
|
this.$refs.box.parentNode.style.cssText = ""
|
||||||
},
|
},
|
||||||
mismatch: function () {
|
mismatch: function () {
|
||||||
teaweb.warn("当前访问没有匹配到任何网站服务")
|
teaweb.warn("当前访问没有匹配到任何网站")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
template: `<div style="word-break: break-all" :style="{'color': (accessLog.status >= 400) ? '#dc143c' : ''}" ref="box">
|
template: `<div style="word-break: break-all" :style="{'color': (accessLog.status >= 400) ? '#dc143c' : ''}" ref="box">
|
||||||
@@ -81,7 +81,7 @@ Vue.component("http-access-log-box", {
|
|||||||
<a v-if="accessLog.node != null && accessLog.node.nodeCluster != null" :href="'/clusters/cluster/node?nodeId=' + accessLog.node.id + '&clusterId=' + accessLog.node.nodeCluster.id" title="点击查看节点详情" target="_top"><span class="grey">[{{accessLog.node.name}}<span v-if="!accessLog.node.name.endsWith('节点')">节点</span>]</span></a>
|
<a v-if="accessLog.node != null && accessLog.node.nodeCluster != null" :href="'/clusters/cluster/node?nodeId=' + accessLog.node.id + '&clusterId=' + accessLog.node.nodeCluster.id" title="点击查看节点详情" target="_top"><span class="grey">[{{accessLog.node.name}}<span v-if="!accessLog.node.name.endsWith('节点')">节点</span>]</span></a>
|
||||||
|
|
||||||
<!-- 服务 -->
|
<!-- 服务 -->
|
||||||
<a :href="'/servers/server/log?serverId=' + accessLog.serverId" title="点击到网站服务" v-if="vShowServerLink && accessLog.serverId > 0"><span class="grey">[服务]</span></a>
|
<a :href="'/servers/server/log?serverId=' + accessLog.serverId" title="点击到网站" v-if="vShowServerLink && accessLog.serverId > 0"><span class="grey">[服务]</span></a>
|
||||||
<span v-if="vShowServerLink && (accessLog.serverId == null || accessLog.serverId == 0)" @click.prevent="mismatch()"><span class="disabled">[服务]</span></span>
|
<span v-if="vShowServerLink && (accessLog.serverId == null || accessLog.serverId == 0)" @click.prevent="mismatch()"><span class="disabled">[服务]</span></span>
|
||||||
|
|
||||||
<span v-if="accessLog.region != null && accessLog.region.length > 0" class="grey"><ip-box :v-ip="accessLog.remoteAddr">[{{accessLog.region}}]</ip-box></span>
|
<span v-if="accessLog.region != null && accessLog.region.length > 0" class="grey"><ip-box :v-ip="accessLog.remoteAddr">[{{accessLog.region}}]</ip-box></span>
|
||||||
|
|||||||
@@ -11,23 +11,110 @@ Vue.component("http-cors-header-config-box", {
|
|||||||
exposeHeaders: [],
|
exposeHeaders: [],
|
||||||
maxAge: 0,
|
maxAge: 0,
|
||||||
requestHeaders: [],
|
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 {
|
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>
|
template: `<div>
|
||||||
<input type="hidden" name="corsJSON" :value="JSON.stringify(config)"/>
|
<input type="hidden" name="corsJSON" :value="JSON.stringify(config)"/>
|
||||||
<table class="ui table definition selectable">
|
<table class="ui table definition selectable">
|
||||||
<tr>
|
<tbody>
|
||||||
<td class="title">启用CORS自适应跨域</td>
|
<tr>
|
||||||
<td>
|
<td class="title">启用CORS自适应跨域</td>
|
||||||
<checkbox v-model="config.isOn"></checkbox>
|
<td>
|
||||||
</td>
|
<checkbox v-model="config.isOn"></checkbox>
|
||||||
</tr>
|
<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>
|
</table>
|
||||||
<div class="margin"></div>
|
<div class="margin"></div>
|
||||||
</div>`
|
</div>`
|
||||||
|
|||||||
@@ -96,6 +96,9 @@ Vue.component("http-firewall-actions-box", {
|
|||||||
pageBody: defaultPageBody,
|
pageBody: defaultPageBody,
|
||||||
defaultPageBody: defaultPageBody,
|
defaultPageBody: defaultPageBody,
|
||||||
|
|
||||||
|
redirectStatus: 307,
|
||||||
|
redirectURL: "",
|
||||||
|
|
||||||
goGroupName: "",
|
goGroupName: "",
|
||||||
goGroupId: 0,
|
goGroupId: 0,
|
||||||
goGroup: null,
|
goGroup: null,
|
||||||
@@ -105,7 +108,15 @@ Vue.component("http-firewall-actions-box", {
|
|||||||
|
|
||||||
jsCookieLife: "",
|
jsCookieLife: "",
|
||||||
jsCookieMaxFails: "",
|
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: {
|
watch: {
|
||||||
@@ -272,6 +283,9 @@ Vue.component("http-firewall-actions-box", {
|
|||||||
this.pageStatus = 403
|
this.pageStatus = 403
|
||||||
this.pageBody = this.defaultPageBody
|
this.pageBody = this.defaultPageBody
|
||||||
|
|
||||||
|
this.redirectStatus = 307
|
||||||
|
this.redirectURL = ""
|
||||||
|
|
||||||
this.goGroupName = ""
|
this.goGroupName = ""
|
||||||
this.goGroupId = 0
|
this.goGroupId = 0
|
||||||
this.goGroup = null
|
this.goGroup = null
|
||||||
@@ -398,7 +412,16 @@ Vue.component("http-firewall-actions-box", {
|
|||||||
if (config.options.body != null) {
|
if (config.options.body != null) {
|
||||||
this.pageBody = config.options.body
|
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
|
break
|
||||||
case "go_group":
|
case "go_group":
|
||||||
if (config.options != null) {
|
if (config.options != null) {
|
||||||
@@ -485,6 +508,23 @@ Vue.component("http-firewall-actions-box", {
|
|||||||
status: pageStatus,
|
status: pageStatus,
|
||||||
body: this.pageBody
|
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
|
} else if (this.actionCode == "go_group") { // go_group
|
||||||
let groupId = this.goGroupId
|
let groupId = this.goGroupId
|
||||||
if (typeof (groupId) == "string") {
|
if (typeof (groupId) == "string") {
|
||||||
@@ -625,6 +665,9 @@ Vue.component("http-firewall-actions-box", {
|
|||||||
<!-- page -->
|
<!-- page -->
|
||||||
<span v-if="config.code == 'page'">:[{{config.options.status}}]</span>
|
<span v-if="config.code == 'page'">:[{{config.options.status}}]</span>
|
||||||
|
|
||||||
|
<!-- redirect -->
|
||||||
|
<span v-if="config.code == 'redirect'">:{{config.options.url}}</span>
|
||||||
|
|
||||||
<!-- go_group -->
|
<!-- go_group -->
|
||||||
<span v-if="config.code == 'go_group'">:{{config.options.groupName}}</span>
|
<span v-if="config.code == 'go_group'">:{{config.options.groupName}}</span>
|
||||||
|
|
||||||
@@ -663,8 +706,8 @@ Vue.component("http-firewall-actions-box", {
|
|||||||
<option value="service">当前服务</option>
|
<option value="service">当前服务</option>
|
||||||
<option value="global">所有服务</option>
|
<option value="global">所有服务</option>
|
||||||
</select>
|
</select>
|
||||||
<p class="comment" v-if="blockScope == 'service'">只封禁用户对当前网站服务的访问,其他服务不受影响。</p>
|
<p class="comment" v-if="blockScope == 'service'">只封禁用户对当前网站的访问,其他服务不受影响。</p>
|
||||||
<p class="comment" v-if="blockScope =='global'">封禁用户对所有网站服务的访问。</p>
|
<p class="comment" v-if="blockScope =='global'">封禁用户对所有网站的访问。</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="actionCode == 'block'">
|
<tr v-if="actionCode == 'block'">
|
||||||
@@ -832,6 +875,22 @@ Vue.component("http-firewall-actions-box", {
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</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'">
|
<tr v-if="actionCode == 'go_group'">
|
||||||
<td>下一个分组 *</td>
|
<td>下一个分组 *</td>
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ Vue.component("http-firewall-captcha-options", {
|
|||||||
<td>失败全局封禁</td>
|
<td>失败全局封禁</td>
|
||||||
<td>
|
<td>
|
||||||
<checkbox v-model="options.failBlockScopeAll"></checkbox>
|
<checkbox v-model="options.failBlockScopeAll"></checkbox>
|
||||||
<p class="comment">是否在失败时全局封禁,默认为只封禁对单个网站服务的访问。</p>
|
<p class="comment">是否在失败时全局封禁,默认为只封禁对单个网站的访问。</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ Vue.component("http-firewall-config-box", {
|
|||||||
<td class="title">启用WAF</td>
|
<td class="title">启用WAF</td>
|
||||||
<td>
|
<td>
|
||||||
<checkbox v-model="firewall.isOn"></checkbox>
|
<checkbox v-model="firewall.isOn"></checkbox>
|
||||||
<p class="comment">选中后,表示启用当前网站服务的WAF功能。</p>
|
<p class="comment">选中后,表示启用当前网站的WAF功能。</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ Vue.component("http-firewall-rule-label", {
|
|||||||
|
|
||||||
<!-- cc2 -->
|
<!-- cc2 -->
|
||||||
<span v-if="rule.param == '\${cc2}'">
|
<span v-if="rule.param == '\${cc2}'">
|
||||||
{{rule.checkpointOptions.period}}秒/{{rule.checkpointOptions.threshold}}请求
|
{{rule.checkpointOptions.period}}秒内请求数
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<!-- refererBlock -->
|
<!-- refererBlock -->
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ Vue.component("http-firewall-rules-box", {
|
|||||||
|
|
||||||
<!-- cc2 -->
|
<!-- cc2 -->
|
||||||
<span v-if="rule.param == '\${cc2}'">
|
<span v-if="rule.param == '\${cc2}'">
|
||||||
{{rule.checkpointOptions.period}}秒/{{rule.checkpointOptions.threshold}}请求
|
{{rule.checkpointOptions.period}}秒内请求数
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<!-- refererBlock -->
|
<!-- refererBlock -->
|
||||||
|
|||||||
@@ -261,6 +261,7 @@ Vue.component("http-firewall-checkpoint-referer-block", {
|
|||||||
let allowSameDomain = true
|
let allowSameDomain = true
|
||||||
let allowDomains = []
|
let allowDomains = []
|
||||||
let denyDomains = []
|
let denyDomains = []
|
||||||
|
let checkOrigin = true
|
||||||
|
|
||||||
let options = {}
|
let options = {}
|
||||||
if (window.parent.UPDATING_RULE != null) {
|
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") {
|
if (options.denyDomains != null && typeof (options.denyDomains) == "object") {
|
||||||
denyDomains = options.denyDomains
|
denyDomains = options.denyDomains
|
||||||
}
|
}
|
||||||
|
if (typeof options.checkOrigin == "boolean") {
|
||||||
|
checkOrigin = options.checkOrigin
|
||||||
|
}
|
||||||
|
|
||||||
let that = this
|
let that = this
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
@@ -293,6 +297,7 @@ Vue.component("http-firewall-checkpoint-referer-block", {
|
|||||||
allowSameDomain: allowSameDomain,
|
allowSameDomain: allowSameDomain,
|
||||||
allowDomains: allowDomains,
|
allowDomains: allowDomains,
|
||||||
denyDomains: denyDomains,
|
denyDomains: denyDomains,
|
||||||
|
checkOrigin: checkOrigin,
|
||||||
options: {},
|
options: {},
|
||||||
value: 0
|
value: 0
|
||||||
}
|
}
|
||||||
@@ -303,6 +308,9 @@ Vue.component("http-firewall-checkpoint-referer-block", {
|
|||||||
},
|
},
|
||||||
allowSameDomain: function () {
|
allowSameDomain: function () {
|
||||||
this.change()
|
this.change()
|
||||||
|
},
|
||||||
|
checkOrigin: function () {
|
||||||
|
this.change()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -332,6 +340,10 @@ Vue.component("http-firewall-checkpoint-referer-block", {
|
|||||||
code: "denyDomains",
|
code: "denyDomains",
|
||||||
value: this.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>
|
<p class="comment">禁止的来源域名列表,比如<code-label>example.org</code-label>、<code-label>*.example.org</code-label>;除了这些禁止的来源域名外,其他域名都会被允许,除非限定了允许的来源域名。</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>同时检查Origin</td>
|
||||||
|
<td>
|
||||||
|
<checkbox v-model="checkOrigin"></checkbox>
|
||||||
|
<p class="comment">如果请求没有指定Referer Header,则尝试检查Origin Header,多用于跨站调用。</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>`
|
</div>`
|
||||||
})
|
})
|
||||||
@@ -29,6 +29,7 @@ Vue.component("http-header-policy-box", {
|
|||||||
// 请求相关
|
// 请求相关
|
||||||
let requestSettingHeaders = []
|
let requestSettingHeaders = []
|
||||||
let requestDeletingHeaders = []
|
let requestDeletingHeaders = []
|
||||||
|
let requestNonStandardHeaders = []
|
||||||
|
|
||||||
let requestPolicy = this.vRequestHeaderPolicy
|
let requestPolicy = this.vRequestHeaderPolicy
|
||||||
if (requestPolicy != null) {
|
if (requestPolicy != null) {
|
||||||
@@ -38,11 +39,15 @@ Vue.component("http-header-policy-box", {
|
|||||||
if (requestPolicy.deleteHeaders != null) {
|
if (requestPolicy.deleteHeaders != null) {
|
||||||
requestDeletingHeaders = requestPolicy.deleteHeaders
|
requestDeletingHeaders = requestPolicy.deleteHeaders
|
||||||
}
|
}
|
||||||
|
if (requestPolicy.nonStandardHeaders != null) {
|
||||||
|
requestNonStandardHeaders = requestPolicy.nonStandardHeaders
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 响应相关
|
// 响应相关
|
||||||
let responseSettingHeaders = []
|
let responseSettingHeaders = []
|
||||||
let responseDeletingHeaders = []
|
let responseDeletingHeaders = []
|
||||||
|
let responseNonStandardHeaders = []
|
||||||
|
|
||||||
let responsePolicy = this.vResponseHeaderPolicy
|
let responsePolicy = this.vResponseHeaderPolicy
|
||||||
if (responsePolicy != null) {
|
if (responsePolicy != null) {
|
||||||
@@ -52,6 +57,9 @@ Vue.component("http-header-policy-box", {
|
|||||||
if (responsePolicy.deleteHeaders != null) {
|
if (responsePolicy.deleteHeaders != null) {
|
||||||
responseDeletingHeaders = responsePolicy.deleteHeaders
|
responseDeletingHeaders = responsePolicy.deleteHeaders
|
||||||
}
|
}
|
||||||
|
if (responsePolicy.nonStandardHeaders != null) {
|
||||||
|
responseNonStandardHeaders = responsePolicy.nonStandardHeaders
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let responseCORS = {
|
let responseCORS = {
|
||||||
@@ -64,12 +72,16 @@ Vue.component("http-header-policy-box", {
|
|||||||
return {
|
return {
|
||||||
type: type,
|
type: type,
|
||||||
typeName: (type == "request") ? "请求" : "响应",
|
typeName: (type == "request") ? "请求" : "响应",
|
||||||
|
|
||||||
requestHeaderRef: requestHeaderRef,
|
requestHeaderRef: requestHeaderRef,
|
||||||
responseHeaderRef: responseHeaderRef,
|
responseHeaderRef: responseHeaderRef,
|
||||||
requestSettingHeaders: requestSettingHeaders,
|
requestSettingHeaders: requestSettingHeaders,
|
||||||
requestDeletingHeaders: requestDeletingHeaders,
|
requestDeletingHeaders: requestDeletingHeaders,
|
||||||
|
requestNonStandardHeaders: requestNonStandardHeaders,
|
||||||
|
|
||||||
responseSettingHeaders: responseSettingHeaders,
|
responseSettingHeaders: responseSettingHeaders,
|
||||||
responseDeletingHeaders: responseDeletingHeaders,
|
responseDeletingHeaders: responseDeletingHeaders,
|
||||||
|
responseNonStandardHeaders: responseNonStandardHeaders,
|
||||||
responseCORS: responseCORS
|
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) {
|
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 () {
|
callback: function () {
|
||||||
teaweb.successRefresh("保存成功")
|
teaweb.successRefresh("保存成功")
|
||||||
}
|
}
|
||||||
@@ -111,6 +130,17 @@ Vue.component("http-header-policy-box", {
|
|||||||
.refresh()
|
.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) {
|
deleteHeader: function (policyId, type, headerId) {
|
||||||
teaweb.confirm("确定要删除此Header吗?", function () {
|
teaweb.confirm("确定要删除此Header吗?", function () {
|
||||||
this.$post("/servers/server/settings/headers/delete")
|
this.$post("/servers/server/settings/headers/delete")
|
||||||
@@ -125,6 +155,7 @@ Vue.component("http-header-policy-box", {
|
|||||||
},
|
},
|
||||||
updateCORS: function (policyId) {
|
updateCORS: function (policyId) {
|
||||||
teaweb.popup("/servers/server/settings/headers/updateCORSPopup?" + this.vParams + "&headerPolicyId=" + policyId + "&type=" + this.type, {
|
teaweb.popup("/servers/server/settings/headers/updateCORSPopup?" + this.vParams + "&headerPolicyId=" + policyId + "&type=" + this.type, {
|
||||||
|
height: "30em",
|
||||||
callback: function () {
|
callback: function () {
|
||||||
teaweb.successRefresh("保存成功")
|
teaweb.successRefresh("保存成功")
|
||||||
}
|
}
|
||||||
@@ -156,7 +187,7 @@ Vue.component("http-header-policy-box", {
|
|||||||
<warning-message>由于已经在当前<a :href="vGroupSettingUrl + '#request'">服务分组</a>中进行了对应的配置,在这里的配置将不会生效。</warning-message>
|
<warning-message>由于已经在当前<a :href="vGroupSettingUrl + '#request'">服务分组</a>中进行了对应的配置,在这里的配置将不会生效。</warning-message>
|
||||||
</div>
|
</div>
|
||||||
<div :class="{'opacity-mask': vHasGroupRequestConfig}">
|
<div :class="{'opacity-mask': vHasGroupRequestConfig}">
|
||||||
<h4>设置请求Header <a href="" @click.prevent="addSettingHeader(vRequestHeaderPolicy.id)">[添加新Header]</a></h4>
|
<h4>设置请求Header <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>
|
<p class="comment" v-if="requestSettingHeaders.length == 0">暂时还没有Header。</p>
|
||||||
<table class="ui table selectable celled" v-if="requestSettingHeaders.length > 0">
|
<table class="ui table selectable celled" v-if="requestSettingHeaders.length > 0">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -185,20 +216,31 @@ Vue.component("http-header-policy-box", {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<h4>删除请求Header</h4>
|
<h4>其他设置</h4>
|
||||||
<p class="comment">这里可以设置需要从请求中删除的Header。</p>
|
|
||||||
|
|
||||||
<table class="ui table definition selectable">
|
<table class="ui table definition selectable">
|
||||||
<tr>
|
<tbody>
|
||||||
<td class="title">需要删除的Header</td>
|
<tr>
|
||||||
<td>
|
<td class="title">删除Header <tip-icon content="可以通过此功能删除转发到源站的请求报文中不需要的Header"></tip-icon></td>
|
||||||
<div v-if="requestDeletingHeaders.length > 0">
|
<td>
|
||||||
<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 v-if="requestDeletingHeaders.length > 0">
|
||||||
<div class="ui divider" ></div>
|
<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>
|
<div class="ui divider" ></div>
|
||||||
<button class="ui button small" type="button" @click.prevent="addDeletingHeader(vRequestHeaderPolicy.id, 'request')">+</button>
|
</div>
|
||||||
</td>
|
<button class="ui button small" type="button" @click.prevent="addDeletingHeader(vRequestHeaderPolicy.id, 'request')">+</button>
|
||||||
</tr>
|
</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>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -218,7 +260,7 @@ Vue.component("http-header-policy-box", {
|
|||||||
<warning-message>由于已经在当前<a :href="vGroupSettingUrl + '#response'">服务分组</a>中进行了对应的配置,在这里的配置将不会生效。</warning-message>
|
<warning-message>由于已经在当前<a :href="vGroupSettingUrl + '#response'">服务分组</a>中进行了对应的配置,在这里的配置将不会生效。</warning-message>
|
||||||
</div>
|
</div>
|
||||||
<div :class="{'opacity-mask': vHasGroupResponseConfig}">
|
<div :class="{'opacity-mask': vHasGroupResponseConfig}">
|
||||||
<h4>设置响应Header <a href="" @click.prevent="addSettingHeader(vResponseHeaderPolicy.id)">[添加新Header]</a></h4>
|
<h4>设置响应Header <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" style="margin-top: 0; padding-top: 0">将会覆盖已有的同名Header。</p>
|
||||||
<p class="comment" v-if="responseSettingHeaders.length == 0">暂时还没有Header。</p>
|
<p class="comment" v-if="responseSettingHeaders.length == 0">暂时还没有Header。</p>
|
||||||
<table class="ui table selectable celled" v-if="responseSettingHeaders.length > 0">
|
<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.disableRedirect">跳转禁用</grey-label>
|
||||||
<grey-label v-if="header.shouldReplace && header.replaceValues != null && header.replaceValues.length > 0">替换</grey-label>
|
<grey-label v-if="header.shouldReplace && header.replaceValues != null && header.replaceValues.length > 0">替换</grey-label>
|
||||||
</div>
|
</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>
|
||||||
<td>{{header.value}}</td>
|
<td>{{header.value}}</td>
|
||||||
<td><a href="" @click.prevent="updateSettingPopup(vResponseHeaderPolicy.id, header.id)">修改</a> <a href="" @click.prevent="deleteHeader(vResponseHeaderPolicy.id, 'setHeader', header.id)">删除</a> </td>
|
<td><a href="" @click.prevent="updateSettingPopup(vResponseHeaderPolicy.id, header.id)">修改</a> <a href="" @click.prevent="deleteHeader(vResponseHeaderPolicy.id, 'setHeader', header.id)">删除</a> </td>
|
||||||
@@ -248,31 +295,38 @@ Vue.component("http-header-policy-box", {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</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>
|
<h4>其他设置</h4>
|
||||||
|
|
||||||
<table class="ui table definition selectable">
|
<table class="ui table definition selectable">
|
||||||
<tr>
|
<tbody>
|
||||||
<td class="title">CORS自适应跨域</td>
|
<tr>
|
||||||
<td>
|
<td class="title">删除Header <tip-icon content="可以通过此功能删除响应报文中不需要的Header"></tip-icon></td>
|
||||||
<span v-if="responseCORS.isOn" class="green">已启用</span><span class="disabled" v-else="">未启用</span> <a href="" @click.prevent="updateCORS(vResponseHeaderPolicy.id)">[修改]</a>
|
<td>
|
||||||
</td>
|
<div v-if="responseDeletingHeaders.length > 0">
|
||||||
</tr>
|
<div class="ui label small basic" v-for="headerName in responseDeletingHeaders">{{headerName}} <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}} <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> <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>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
60
web/public/js/components/server/http-pages-box.js
Normal file
60
web/public/js/components/server/http-pages-box.js
Normal 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}} -> <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">根据响应状态码返回一些自定义页面,比如404,500等错误页面。</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div class="ui margin"></div>
|
||||||
|
</div>`
|
||||||
|
})
|
||||||
@@ -9,7 +9,8 @@ Vue.component("http-referers-config-box", {
|
|||||||
allowEmpty: true,
|
allowEmpty: true,
|
||||||
allowSameDomain: true,
|
allowSameDomain: true,
|
||||||
allowDomains: [],
|
allowDomains: [],
|
||||||
denyDomains: []
|
denyDomains: [],
|
||||||
|
checkOrigin: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (config.allowDomains == null) {
|
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>
|
<p class="comment">禁止的来源域名列表,比如<code-label>example.org</code-label>、<code-label>*.example.org</code-label>;除了这些禁止的来源域名外,其他域名都会被允许,除非限定了允许的来源域名。</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>同时检查Origin</td>
|
||||||
|
<td>
|
||||||
|
<checkbox v-model="config.checkOrigin"></checkbox>
|
||||||
|
<p class="comment">如果请求没有指定Referer Header,则尝试检查Origin Header,多用于跨站调用。</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div class="ui margin"></div>
|
<div class="ui margin"></div>
|
||||||
|
|||||||
@@ -67,7 +67,19 @@ Vue.component("origin-list-box", {
|
|||||||
Vue.component("origin-list-table", {
|
Vue.component("origin-list-table", {
|
||||||
props: ["v-origins", "v-origin-type"],
|
props: ["v-origins", "v-origin-type"],
|
||||||
data: function () {
|
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: {
|
methods: {
|
||||||
deleteOrigin: function (originId) {
|
deleteOrigin: function (originId) {
|
||||||
@@ -90,12 +102,14 @@ Vue.component("origin-list-table", {
|
|||||||
<tr v-for="origin in vOrigins">
|
<tr v-for="origin in vOrigins">
|
||||||
<td :class="{disabled:!origin.isOn}">
|
<td :class="{disabled:!origin.isOn}">
|
||||||
<a href="" @click.prevent="updateOrigin(origin.id)" :class="{disabled:!origin.isOn}">{{origin.addr}} <i class="icon expand small"></i></a>
|
<a href="" @click.prevent="updateOrigin(origin.id)" :class="{disabled:!origin.isOn}">{{origin.addr}} <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.name.length > 0">{{origin.name}}</tiny-basic-label>
|
||||||
<tiny-basic-label v-if="origin.hasCert">证书</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.host != null && origin.host.length > 0">主机名: {{origin.host}}</tiny-basic-label>
|
||||||
<tiny-basic-label v-if="origin.followPort">端口跟随</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-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>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td :class="{disabled:!origin.isOn}">{{origin.weight}}</td>
|
<td :class="{disabled:!origin.isOn}">{{origin.weight}}</td>
|
||||||
|
|||||||
@@ -24,9 +24,12 @@ Vue.component("user-selector", {
|
|||||||
} else {
|
} else {
|
||||||
this.$emit("change", 0)
|
this.$emit("change", 0)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
clear: function () {
|
||||||
|
this.$refs.comboBox.clear()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
template: `<div>
|
template: `<div>
|
||||||
<combo-box placeholder="选择用户" :data-url="dataURL" :data-key="'users'" data-search="on" name="userId" :v-value="userId" @change="change"></combo-box>
|
<combo-box placeholder="选择用户" :data-url="dataURL" :data-key="'users'" data-search="on" name="userId" :v-value="userId" @change="change" ref="comboBox"></combo-box>
|
||||||
</div>`
|
</div>`
|
||||||
})
|
})
|
||||||
@@ -2312,7 +2312,9 @@ window.Tea.Action = function (action, params) {
|
|||||||
.then(function () {
|
.then(function () {
|
||||||
// console.log("done");
|
// console.log("done");
|
||||||
if (typeof (_doneFn) == "function") {
|
if (typeof (_doneFn) == "function") {
|
||||||
_doneFn.call(Tea.Vue, {});
|
setTimeout(function () {
|
||||||
|
_doneFn.call(Tea.Vue, {});
|
||||||
|
})
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
<a href="https://goedge.cn/docs" target="_blank" class="item">文档</a>
|
<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" target="_blank" class="item">GitHub</a>
|
||||||
<a href="https://github.com/TeaOSLab/EdgeAdmin/issues" target="_blank" class="item">提Bug</a>
|
<a href="https://github.com/TeaOSLab/EdgeAdmin/issues" target="_blank" class="item">提Bug</a>
|
||||||
<a class="item" @click.prevent="showQQGroupQrcode()" title="点击弹出加群二维码">QQ讨论群 <i class="icon qrcode"></i> </a>
|
|
||||||
<a class="item" href="https://goedge.cn/community/telegram" target="_blank" title="点击跳转到加群页面">Telegram群 <i class="icon paper plane"></i></a>
|
<a class="item" href="https://goedge.cn/community/telegram" target="_blank" title="点击跳转到加群页面">Telegram群 <i class="icon paper plane"></i></a>
|
||||||
<a class="item right" href="https://goedge.cn/commercial" target="_blank" v-if="!teaIsPlus">企业版</a>
|
<a class="item right" href="https://goedge.cn/commercial" target="_blank" v-if="!teaIsPlus">企业版</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -52,20 +52,20 @@
|
|||||||
opacity: 0.1;
|
opacity: 0.1;
|
||||||
}
|
}
|
||||||
.left-box.tiny {
|
.left-box.tiny {
|
||||||
top: 10.5em;
|
top: 10em;
|
||||||
}
|
}
|
||||||
.left-box.without-tabbar {
|
.left-box.without-tabbar {
|
||||||
top: 3em;
|
top: 3em;
|
||||||
}
|
}
|
||||||
.left-box.with-menu {
|
.left-box.with-menu {
|
||||||
top: 10em;
|
top: 8.7em;
|
||||||
}
|
}
|
||||||
.left-box.without-menu {
|
.left-box.without-menu {
|
||||||
top: 6em;
|
top: 6em;
|
||||||
}
|
}
|
||||||
.right-box {
|
.right-box {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 7.5em;
|
top: 7em;
|
||||||
bottom: 1.3em;
|
bottom: 1.3em;
|
||||||
right: 0;
|
right: 0;
|
||||||
left: 18em;
|
left: 18em;
|
||||||
@@ -73,6 +73,12 @@
|
|||||||
padding-bottom: 2em;
|
padding-bottom: 2em;
|
||||||
overflow-y: auto;
|
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) {
|
@media screen and (max-width: 512px) {
|
||||||
.right-box {
|
.right-box {
|
||||||
left: 13em;
|
left: 13em;
|
||||||
@@ -83,7 +89,7 @@ body.expanded .right-box {
|
|||||||
left: 10em;
|
left: 10em;
|
||||||
}
|
}
|
||||||
.right-box.tiny {
|
.right-box.tiny {
|
||||||
top: 10.4em;
|
top: 10em;
|
||||||
left: 26.5em;
|
left: 26.5em;
|
||||||
}
|
}
|
||||||
.right-box::-webkit-scrollbar {
|
.right-box::-webkit-scrollbar {
|
||||||
@@ -93,7 +99,7 @@ body.expanded .right-box {
|
|||||||
top: 3em;
|
top: 3em;
|
||||||
}
|
}
|
||||||
.right-box.with-menu {
|
.right-box.with-menu {
|
||||||
top: 10em;
|
top: 8.6em;
|
||||||
}
|
}
|
||||||
.right-box.without-menu {
|
.right-box.without-menu {
|
||||||
top: 6em;
|
top: 6em;
|
||||||
@@ -146,6 +152,10 @@ body.expanded .right-box {
|
|||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
/** 通用 **/
|
/** 通用 **/
|
||||||
|
* {
|
||||||
|
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
.clear {
|
.clear {
|
||||||
clear: both;
|
clear: both;
|
||||||
}
|
}
|
||||||
@@ -582,16 +592,16 @@ body.expanded .main {
|
|||||||
background: black !important;
|
background: black !important;
|
||||||
}
|
}
|
||||||
.main-menu::-webkit-scrollbar {
|
.main-menu::-webkit-scrollbar {
|
||||||
width: 2px;
|
width: 4px;
|
||||||
}
|
}
|
||||||
.main .tab-menu {
|
.main .tab-menu {
|
||||||
margin-top: 1em !important;
|
margin-top: 0.3em !important;
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
}
|
}
|
||||||
.main .tab-menu .item {
|
.main .tab-menu .item {
|
||||||
padding: 1em !important;
|
padding: 0 1em !important;
|
||||||
}
|
}
|
||||||
.main .tab-menu .item var {
|
.main .tab-menu .item var {
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
@@ -603,8 +613,37 @@ body.expanded .main {
|
|||||||
.main .tab-menu .item .icon {
|
.main .tab-menu .item .icon {
|
||||||
margin-left: 0.6em;
|
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;
|
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 {
|
.main .tab-menu::-webkit-scrollbar {
|
||||||
height: 4px;
|
height: 4px;
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -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="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">
|
<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.active,right:item.right}" :href="item.url">
|
<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>{{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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ Tea.context(function () {
|
|||||||
if (!Tea.Vue.teaCheckNodeTasks) {
|
if (!Tea.Vue.teaCheckNodeTasks) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
let isStream = false
|
||||||
this.$post("/clusters/tasks/check")
|
this.$post("/clusters/tasks/check")
|
||||||
.params({
|
.params({
|
||||||
isDoing: this.doingNodeTasks.isDoing ? 1 : 0,
|
isDoing: this.doingNodeTasks.isDoing ? 1 : 0,
|
||||||
@@ -128,11 +129,12 @@ Tea.context(function () {
|
|||||||
this.doingNodeTasks.isDoing = resp.data.isDoing
|
this.doingNodeTasks.isDoing = resp.data.isDoing
|
||||||
this.doingNodeTasks.hasError = resp.data.hasError
|
this.doingNodeTasks.hasError = resp.data.hasError
|
||||||
this.doingNodeTasks.isUpdated = true
|
this.doingNodeTasks.isUpdated = true
|
||||||
|
isStream = resp.data.shouldWait
|
||||||
})
|
})
|
||||||
.done(function () {
|
.done(function () {
|
||||||
this.$delay(function () {
|
this.$delay(function () {
|
||||||
this.loadNodeTasks()
|
this.loadNodeTasks()
|
||||||
}, 5000)
|
}, isStream ? 5000 : 30000)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,6 +158,7 @@ Tea.context(function () {
|
|||||||
if (!Tea.Vue.teaCheckDNSTasks) {
|
if (!Tea.Vue.teaCheckDNSTasks) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
let isStream = false
|
||||||
this.$post("/dns/tasks/check")
|
this.$post("/dns/tasks/check")
|
||||||
.params({
|
.params({
|
||||||
isDoing: this.doingDNSTasks.isDoing ? 1 : 0,
|
isDoing: this.doingDNSTasks.isDoing ? 1 : 0,
|
||||||
@@ -167,11 +170,12 @@ Tea.context(function () {
|
|||||||
this.doingDNSTasks.isDoing = resp.data.isDoing
|
this.doingDNSTasks.isDoing = resp.data.isDoing
|
||||||
this.doingDNSTasks.hasError = resp.data.hasError
|
this.doingDNSTasks.hasError = resp.data.hasError
|
||||||
this.doingDNSTasks.isUpdated = true
|
this.doingDNSTasks.isUpdated = true
|
||||||
|
isStream = resp.data.isStream
|
||||||
})
|
})
|
||||||
.done(function () {
|
.done(function () {
|
||||||
this.$delay(function () {
|
this.$delay(function () {
|
||||||
this.loadDNSTasks()
|
this.loadDNSTasks()
|
||||||
}, 3000)
|
}, isStream ? 5000 : 30000)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,11 @@
|
|||||||
@import "./@grids";
|
@import "./@grids";
|
||||||
|
|
||||||
/** 通用 **/
|
/** 通用 **/
|
||||||
|
* {
|
||||||
|
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
.clear {
|
.clear {
|
||||||
clear: both;
|
clear: both;
|
||||||
}
|
}
|
||||||
@@ -568,18 +573,18 @@ body.expanded .main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.main-menu::-webkit-scrollbar {
|
.main-menu::-webkit-scrollbar {
|
||||||
width: 2px;
|
width: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
.tab-menu {
|
.tab-menu {
|
||||||
margin-top: 1em !important;
|
margin-top: 0.3em !important;
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
padding: 1em !important;
|
padding: 0 1em !important;
|
||||||
|
|
||||||
var {
|
var {
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
@@ -595,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;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -605,7 +646,6 @@ body.expanded .main {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.main .go-top-btn {
|
.main .go-top-btn {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 2.6em;
|
right: 2.6em;
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
/** 通用 **/
|
/** 通用 **/
|
||||||
|
* {
|
||||||
|
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
.clear {
|
.clear {
|
||||||
clear: both;
|
clear: both;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"version":3,"sources":["@layout_popup.less"],"names":[],"mappings":";AACA;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;;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"}
|
||||||
@@ -1,4 +1,9 @@
|
|||||||
/** 通用 **/
|
/** 通用 **/
|
||||||
|
* {
|
||||||
|
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
.clear {
|
.clear {
|
||||||
clear: both;
|
clear: both;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
.left-box {
|
||||||
|
width: 8em;
|
||||||
|
position: fixed;
|
||||||
|
top: 7.5em;
|
||||||
|
bottom: 2.4em;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
border-right: 1px #ddd solid;
|
||||||
|
}
|
||||||
|
.left-box .menu {
|
||||||
|
width: 90% !important;
|
||||||
|
}
|
||||||
|
.left-box .menu .item {
|
||||||
|
line-height: 1.2;
|
||||||
|
position: relative;
|
||||||
|
padding-left: 1em !important;
|
||||||
|
}
|
||||||
|
.left-box .menu .item .icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 0;
|
||||||
|
margin-top: -0.4em !important;
|
||||||
|
}
|
||||||
|
.left-box .menu .item.separator {
|
||||||
|
border-bottom: 1px #eee solid !important;
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
margin-top: 0 !important;
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
.left-box .menu .item.on span {
|
||||||
|
border-bottom: 1px #666 dashed;
|
||||||
|
}
|
||||||
|
.left-box .menu .item.off span var {
|
||||||
|
font-style: normal;
|
||||||
|
background: #db2828;
|
||||||
|
color: white;
|
||||||
|
font-size: 8px;
|
||||||
|
padding: 2px;
|
||||||
|
border-radius: 2px;
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
.left-box .menu .header {
|
||||||
|
border-bottom: 1px #ddd solid;
|
||||||
|
padding-left: 0 !important;
|
||||||
|
padding-bottom: 1em !important;
|
||||||
|
}
|
||||||
|
.left-box::-webkit-scrollbar {
|
||||||
|
width: 4px;
|
||||||
|
}
|
||||||
|
.left-box.disabled {
|
||||||
|
opacity: 0.1;
|
||||||
|
}
|
||||||
|
.left-box.tiny {
|
||||||
|
top: 10em;
|
||||||
|
}
|
||||||
|
.left-box.without-tabbar {
|
||||||
|
top: 3em;
|
||||||
|
}
|
||||||
|
.left-box.with-menu {
|
||||||
|
top: 8.7em;
|
||||||
|
}
|
||||||
|
.left-box.without-menu {
|
||||||
|
top: 6em;
|
||||||
|
}
|
||||||
|
.right-box {
|
||||||
|
position: fixed;
|
||||||
|
top: 7em;
|
||||||
|
bottom: 1.3em;
|
||||||
|
right: 0;
|
||||||
|
left: 18em;
|
||||||
|
padding-right: 2em;
|
||||||
|
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;
|
||||||
|
padding-right: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
body.expanded .right-box {
|
||||||
|
left: 10em;
|
||||||
|
}
|
||||||
|
.right-box.tiny {
|
||||||
|
top: 10em;
|
||||||
|
left: 26.5em;
|
||||||
|
}
|
||||||
|
.right-box::-webkit-scrollbar {
|
||||||
|
width: 4px;
|
||||||
|
}
|
||||||
|
.right-box.without-tabbar {
|
||||||
|
top: 3em;
|
||||||
|
}
|
||||||
|
.right-box.with-menu {
|
||||||
|
top: 8.6em;
|
||||||
|
}
|
||||||
|
.right-box.without-menu {
|
||||||
|
top: 6em;
|
||||||
|
}
|
||||||
|
.main.without-footer .left-box {
|
||||||
|
bottom: 0.2em;
|
||||||
|
}
|
||||||
|
.narrow-scrollbar::-webkit-scrollbar {
|
||||||
|
width: 4px;
|
||||||
|
}
|
||||||
|
/*# sourceMappingURL=@left_menu.css.map */
|
||||||
@@ -1 +1 @@
|
|||||||
undefined
|
{"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"}
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.left-box.tiny {
|
.left-box.tiny {
|
||||||
top: 10.5em;
|
top: 10em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.left-box.without-tabbar {
|
.left-box.without-tabbar {
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.left-box.with-menu {
|
.left-box.with-menu {
|
||||||
top: 10em;
|
top: 8.7em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.left-box.without-menu {
|
.left-box.without-menu {
|
||||||
@@ -89,13 +89,21 @@
|
|||||||
|
|
||||||
.right-box {
|
.right-box {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 7.5em;
|
top: 7em;
|
||||||
bottom: 1.3em;
|
bottom: 1.3em;
|
||||||
right: 0;
|
right: 0;
|
||||||
left: 18em;
|
left: 18em;
|
||||||
padding-right: 2em;
|
padding-right: 2em;
|
||||||
padding-bottom: 2em;
|
padding-bottom: 2em;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
|
h4:first-child {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-box > .comment:first-child {
|
||||||
|
margin-top: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 512px) {
|
@media screen and (max-width: 512px) {
|
||||||
@@ -110,7 +118,7 @@ body.expanded .right-box {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.right-box.tiny {
|
.right-box.tiny {
|
||||||
top: 10.4em;
|
top: 10em;
|
||||||
left: 26.5em;
|
left: 26.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +131,7 @@ body.expanded .right-box {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.right-box.with-menu {
|
.right-box.with-menu {
|
||||||
top: 10em;
|
top: 8.6em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.right-box.without-menu {
|
.right-box.without-menu {
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
h3 {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.main {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
table img {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
/*# sourceMappingURL=qq.css.map */
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"sources":["qq.less"],"names":[],"mappings":"AAAA;EACC,kBAAA;;AAGD;EACC,UAAA;;AAGD,KAAM;EACL,WAAA","file":"qq.css"}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{$layout "layout_popup"}
|
|
||||||
|
|
||||||
<h3>QQ群 <span>659832182</span></h3>
|
|
||||||
<table class="ui table">
|
|
||||||
<tr>
|
|
||||||
<td><img src="/images/qq-group-qrcode.png"/></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
h3 {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
table img {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
@@ -2,5 +2,6 @@
|
|||||||
<menu-item href="/clusters" code="index">集群 <span class="small">({{totalNodeClusters}})</span></menu-item>
|
<menu-item href="/clusters" code="index">集群 <span class="small">({{totalNodeClusters}})</span></menu-item>
|
||||||
<menu-item href="/clusters/nodes" code="node">节点 <span class="small">({{totalNodes}})</span></menu-item>
|
<menu-item href="/clusters/nodes" code="node">节点 <span class="small">({{totalNodes}})</span></menu-item>
|
||||||
<span class="disabled item">|</span>
|
<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>
|
</first-menu>
|
||||||
|
|||||||
@@ -6,7 +6,10 @@
|
|||||||
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
|
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
|
||||||
<csrf-token></csrf-token>
|
<csrf-token></csrf-token>
|
||||||
<input type="hidden" name="clusterId" :value="clusterId"/>
|
<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>
|
<tr>
|
||||||
<td class="title">节点IP列表</td>
|
<td class="title">节点IP列表</td>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
@@ -19,6 +19,9 @@
|
|||||||
<!-- 填写信息 -->
|
<!-- 填写信息 -->
|
||||||
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success" v-show="step == 'info'">
|
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success" v-show="step == 'info'">
|
||||||
<input type="hidden" name="clusterId" :value="clusterId"/>
|
<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">
|
<table class="ui table definition selectable">
|
||||||
<tr>
|
<tr>
|
||||||
<td class="title">节点名称 *</td>
|
<td class="title">节点名称 *</td>
|
||||||
|
|||||||
@@ -16,6 +16,21 @@
|
|||||||
<td>
|
<td>
|
||||||
<node-clusters-labels :v-primary-cluster="node.cluster" :v-secondary-clusters="node.secondaryClusters"></node-clusters-labels>
|
<node-clusters-labels :v-primary-cluster="node.cluster" :v-secondary-clusters="node.secondaryClusters"></node-clusters-labels>
|
||||||
</td>
|
</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>
|
||||||
|
<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)}} <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>
|
||||||
<tr>
|
<tr>
|
||||||
<td>IP地址</td>
|
<td>IP地址</td>
|
||||||
@@ -60,6 +75,7 @@
|
|||||||
<th>记录类型</th>
|
<th>记录类型</th>
|
||||||
<th>线路</th>
|
<th>线路</th>
|
||||||
<th>记录值</th>
|
<th>记录值</th>
|
||||||
|
<th>状态</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
@@ -72,6 +88,11 @@
|
|||||||
<span v-else class="disabled">默认</span>
|
<span v-else class="disabled">默认</span>
|
||||||
</td>
|
</td>
|
||||||
<td>{{record.value}}</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>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<p class="comment" v-if="!dnsIsExcludingLnNode">通过设置A记录可以将集群上的服务请求转发到不同线路的节点上。</p>
|
<p class="comment" v-if="!dnsIsExcludingLnNode">通过设置A记录可以将集群上的服务请求转发到不同线路的节点上。</p>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user