Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34bf5028c3 | ||
|
|
8b727aa939 | ||
|
|
9432600de6 | ||
|
|
bb790ec687 | ||
|
|
8bad658d7c | ||
|
|
a66cc9c08a | ||
|
|
0dc24fb342 | ||
|
|
b965ac6232 | ||
|
|
08d61013c8 | ||
|
|
7796f65814 | ||
|
|
02a3afb9ad | ||
|
|
15a11c384b | ||
|
|
57b8496d89 | ||
|
|
7bbe44f5d2 | ||
|
|
ac11ab0431 | ||
|
|
12e352964c | ||
|
|
7daefc4384 | ||
|
|
29541becd0 | ||
|
|
ef6dc82f88 | ||
|
|
6d66d93180 | ||
|
|
3ded17b920 | ||
|
|
d35651f570 | ||
|
|
235300d034 | ||
|
|
2f7ebf5166 | ||
|
|
4819cc87c6 | ||
|
|
c5da383d1e | ||
|
|
2da17329fa | ||
|
|
70d84acf76 | ||
|
|
fef99a0023 | ||
|
|
76d0935e8f | ||
|
|
42dfe5ada9 | ||
|
|
ef136c25e1 | ||
|
|
4251635a8a | ||
|
|
13f00104dd | ||
|
|
b0b82b29c1 | ||
|
|
e0d43ad3d9 | ||
|
|
ae15115af7 | ||
|
|
a2890c6cb0 | ||
|
|
2b00ece07f | ||
|
|
2e3c34571f | ||
|
|
b8b10b5176 | ||
|
|
e6e62bbd24 | ||
|
|
05e9bbf1d6 | ||
|
|
bf94c2395f | ||
|
|
4a749ca345 | ||
|
|
80ffe6c9a3 | ||
|
|
5414b5a8f9 | ||
|
|
e3ba85a326 | ||
|
|
154fa69dbb | ||
|
|
76c44973e9 | ||
|
|
26fdc82cc1 | ||
|
|
dcc587182c | ||
|
|
7cee1ff5ff | ||
|
|
78b8e3bf0b | ||
|
|
bd4c014c12 | ||
|
|
daa263cd68 |
@@ -7,17 +7,20 @@ import (
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/gen"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/nodes"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := apps.NewAppCmd().
|
||||
var app = apps.NewAppCmd().
|
||||
Version(teaconst.Version).
|
||||
Product(teaconst.ProductName).
|
||||
Usage(teaconst.ProcessName+" [-v|start|stop|restart|service|daemon|reset|recover|demo]").
|
||||
Usage(teaconst.ProcessName+" [-v|start|stop|restart|service|daemon|reset|recover|demo|upgrade]").
|
||||
Usage(teaconst.ProcessName+" [dev|prod]").
|
||||
Option("-h", "show this help").
|
||||
Option("-v", "show version").
|
||||
@@ -30,7 +33,8 @@ func main() {
|
||||
Option("recover", "enter recovery mode").
|
||||
Option("demo", "switch to demo mode").
|
||||
Option("dev", "switch to 'dev' mode").
|
||||
Option("prod", "switch to 'prod' mode")
|
||||
Option("prod", "switch to 'prod' mode").
|
||||
Option("upgrade", "upgrade from official site")
|
||||
|
||||
app.On("daemon", func() {
|
||||
nodes.NewAdminNode().Daemon()
|
||||
@@ -115,8 +119,42 @@ func main() {
|
||||
fmt.Println("switch to '" + env + "' ok")
|
||||
}
|
||||
})
|
||||
app.On("upgrade", func() {
|
||||
var manager = utils.NewUpgradeManager("admin")
|
||||
log.Println("checking latest version ...")
|
||||
var ticker = time.NewTicker(1 * time.Second)
|
||||
go func() {
|
||||
var lastProgress float32 = 0
|
||||
var isStarted = false
|
||||
for range ticker.C {
|
||||
if manager.IsDownloading() {
|
||||
if !isStarted {
|
||||
log.Println("start downloading v" + manager.NewVersion() + " ...")
|
||||
isStarted = true
|
||||
}
|
||||
var progress = manager.Progress()
|
||||
if progress >= 0 {
|
||||
if progress == 0 || progress == 1 || progress-lastProgress >= 0.1 {
|
||||
lastProgress = progress
|
||||
log.Println(fmt.Sprintf("%.2f%%", manager.Progress()*100))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
err := manager.Start()
|
||||
if err != nil {
|
||||
log.Println("upgrade failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
log.Println("finished!")
|
||||
log.Println("restarting ...")
|
||||
app.RunRestart()
|
||||
})
|
||||
app.Run(func() {
|
||||
adminNode := nodes.NewAdminNode()
|
||||
var adminNode = nodes.NewAdminNode()
|
||||
adminNode.Run()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ func (this *AppCmd) On(arg string, callback func()) {
|
||||
// Run 运行
|
||||
func (this *AppCmd) Run(main func()) {
|
||||
// 获取参数
|
||||
args := os.Args[1:]
|
||||
var args = os.Args[1:]
|
||||
if len(args) > 0 {
|
||||
switch args[0] {
|
||||
case "-v", "version", "-version", "--version":
|
||||
@@ -139,7 +139,7 @@ func (this *AppCmd) Run(main func()) {
|
||||
this.runStop()
|
||||
return
|
||||
case "restart":
|
||||
this.runRestart()
|
||||
this.RunRestart()
|
||||
return
|
||||
case "status":
|
||||
this.runStatus()
|
||||
@@ -160,7 +160,7 @@ func (this *AppCmd) Run(main func()) {
|
||||
}
|
||||
|
||||
// 日志
|
||||
writer := new(LogWriter)
|
||||
var writer = new(LogWriter)
|
||||
writer.Init()
|
||||
logs.SetWriter(writer)
|
||||
|
||||
@@ -210,7 +210,7 @@ func (this *AppCmd) runStop() {
|
||||
}
|
||||
|
||||
// 重启
|
||||
func (this *AppCmd) runRestart() {
|
||||
func (this *AppCmd) RunRestart() {
|
||||
this.runStop()
|
||||
time.Sleep(1 * time.Second)
|
||||
this.runStart()
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
func TestLoadAdminModuleMapping(t *testing.T) {
|
||||
m, err := LoadAdminModuleMapping()
|
||||
m, err := loadAdminModuleMapping()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
func TestLoadUIConfig(t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
before := time.Now()
|
||||
config, err := LoadUIConfig()
|
||||
config, err := LoadAdminUIConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -20,7 +20,7 @@ func TestLoadUIConfig(t *testing.T) {
|
||||
|
||||
func TestLoadUIConfig2(t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
config, err := LoadUIConfig()
|
||||
config, err := LoadAdminUIConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -14,13 +14,7 @@ func TestLoadAPIConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAPIConfig_WriteFile(t *testing.T) {
|
||||
config := &APIConfig{
|
||||
RPC: struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
}{},
|
||||
NodeId: "1",
|
||||
Secret: "2",
|
||||
}
|
||||
config := &APIConfig{}
|
||||
err := config.WriteFile("/tmp/api_config.yaml")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
Version = "0.5.2.1"
|
||||
Version = "0.5.5"
|
||||
|
||||
APINodeVersion = "0.5.2.1"
|
||||
APINodeVersion = "0.5.5"
|
||||
|
||||
ProductName = "Edge Admin"
|
||||
ProcessName = "edge-admin"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/errors"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/events"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
"github.com/iwind/TeaGo"
|
||||
@@ -90,7 +90,7 @@ func (this *AdminNode) Run() {
|
||||
EndAll().
|
||||
Session(sessions.NewFileSessionManager(86400, secret), teaconst.CookieSID).
|
||||
ReadHeaderTimeout(3 * time.Second).
|
||||
ReadTimeout(600 * time.Second).
|
||||
ReadTimeout(1200 * time.Second).
|
||||
Start()
|
||||
}
|
||||
|
||||
|
||||
@@ -174,18 +174,10 @@ func (this *RPCClient) APIMethodStatRPC() pb.APIMethodStatServiceClient {
|
||||
return pb.NewAPIMethodStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserNodeRPC() pb.UserNodeServiceClient {
|
||||
return pb.NewUserNodeServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) DBNodeRPC() pb.DBNodeServiceClient {
|
||||
return pb.NewDBNodeServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MonitorNodeRPC() pb.MonitorNodeServiceClient {
|
||||
return pb.NewMonitorNodeServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) DBRPC() pb.DBServiceClient {
|
||||
return pb.NewDBServiceClient(this.pickConn())
|
||||
}
|
||||
@@ -407,26 +399,6 @@ func (this *RPCClient) UserRPC() pb.UserServiceClient {
|
||||
return pb.NewUserServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserBillRPC() pb.UserBillServiceClient {
|
||||
return pb.NewUserBillServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerBillRPC() pb.ServerBillServiceClient {
|
||||
return pb.NewServerBillServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserAccountRPC() pb.UserAccountServiceClient {
|
||||
return pb.NewUserAccountServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserAccountLogRPC() pb.UserAccountLogServiceClient {
|
||||
return pb.NewUserAccountLogServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserAccountDailyStatRPC() pb.UserAccountDailyStatServiceClient {
|
||||
return pb.NewUserAccountDailyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserAccessKeyRPC() pb.UserAccessKeyServiceClient {
|
||||
return pb.NewUserAccessKeyServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -143,8 +144,9 @@ func (this *SyncAPINodesTask) testEndpoints(endpoints []string) bool {
|
||||
cancel()
|
||||
}()
|
||||
var conn *grpc.ClientConn
|
||||
|
||||
if u.Scheme == "http" {
|
||||
conn, err = grpc.DialContext(ctx, u.Host, grpc.WithInsecure(), grpc.WithBlock())
|
||||
conn, err = grpc.DialContext(ctx, u.Host, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock())
|
||||
} else if u.Scheme == "https" {
|
||||
conn, err = grpc.DialContext(ctx, u.Host, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
|
||||
12
internal/utils/email.go
Normal file
12
internal/utils/email.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package utils
|
||||
|
||||
import "regexp"
|
||||
|
||||
var emailReg = regexp.MustCompile(`(?i)^[a-z\d]+([._+-]*[a-z\d]+)*@([a-z\d]+[a-z\d-]*[a-z\d]+\.)+[a-z\d]+$`)
|
||||
|
||||
// ValidateEmail 校验电子邮箱格式
|
||||
func ValidateEmail(email string) bool {
|
||||
return emailReg.MatchString(email)
|
||||
}
|
||||
22
internal/utils/email_test.go
Normal file
22
internal/utils/email_test.go
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package utils_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/utils"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValidateEmail(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
a.IsTrue(utils.ValidateEmail("aaaa@gmail.com"))
|
||||
a.IsTrue(utils.ValidateEmail("a.b@gmail.com"))
|
||||
a.IsTrue(utils.ValidateEmail("a.b.c.d@gmail.com"))
|
||||
a.IsTrue(utils.ValidateEmail("aaaa@gmail.com.cn"))
|
||||
a.IsTrue(utils.ValidateEmail("hello.world.123@gmail.123.com"))
|
||||
a.IsTrue(utils.ValidateEmail("10000@qq.com"))
|
||||
a.IsFalse(utils.ValidateEmail("aaaa.@gmail.com"))
|
||||
a.IsFalse(utils.ValidateEmail("aaaa@gmail"))
|
||||
a.IsFalse(utils.ValidateEmail("aaaa@123"))
|
||||
}
|
||||
@@ -111,7 +111,8 @@ func (this *ServiceManager) installSystemdService(systemd, exePath string, args
|
||||
shortName := teaconst.SystemdServiceName
|
||||
longName := "GoEdge Admin" // TODO 将来可以修改
|
||||
|
||||
desc := `# Provides: ` + shortName + `
|
||||
desc := `### BEGIN INIT INFO
|
||||
# Provides: ` + shortName + `
|
||||
# Required-Start: $all
|
||||
# Required-Stop:
|
||||
# Default-Start: 2 3 4 5
|
||||
|
||||
292
internal/utils/upgrade_manager.go
Normal file
292
internal/utils/upgrade_manager.go
Normal file
@@ -0,0 +1,292 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type UpgradeFileWriter struct {
|
||||
rawWriter io.Writer
|
||||
written int64
|
||||
}
|
||||
|
||||
func NewUpgradeFileWriter(rawWriter io.Writer) *UpgradeFileWriter {
|
||||
return &UpgradeFileWriter{rawWriter: rawWriter}
|
||||
}
|
||||
|
||||
func (this *UpgradeFileWriter) Write(p []byte) (n int, err error) {
|
||||
n, err = this.rawWriter.Write(p)
|
||||
this.written += int64(n)
|
||||
return
|
||||
}
|
||||
|
||||
func (this *UpgradeFileWriter) TotalWritten() int64 {
|
||||
return this.written
|
||||
}
|
||||
|
||||
type UpgradeManager struct {
|
||||
client *http.Client
|
||||
|
||||
component string
|
||||
|
||||
newVersion string
|
||||
contentLength int64
|
||||
isDownloading bool
|
||||
writer *UpgradeFileWriter
|
||||
body io.ReadCloser
|
||||
isCancelled bool
|
||||
}
|
||||
|
||||
func NewUpgradeManager(component string) *UpgradeManager {
|
||||
return &UpgradeManager{
|
||||
component: component,
|
||||
client: &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
},
|
||||
CheckRedirect: nil,
|
||||
Jar: nil,
|
||||
Timeout: 30 * time.Minute,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (this *UpgradeManager) Start() error {
|
||||
if this.isDownloading {
|
||||
return errors.New("another process is running")
|
||||
}
|
||||
|
||||
this.isDownloading = true
|
||||
|
||||
defer func() {
|
||||
this.client.CloseIdleConnections()
|
||||
this.isDownloading = false
|
||||
}()
|
||||
|
||||
// 检查unzip
|
||||
unzipExe, _ := exec.LookPath("unzip")
|
||||
if len(unzipExe) == 0 {
|
||||
// TODO install unzip automatically or pack with a static 'unzip' file
|
||||
return errors.New("can not find 'unzip' command")
|
||||
}
|
||||
|
||||
// 检查cp
|
||||
cpExe, _ := exec.LookPath("cp")
|
||||
if len(cpExe) == 0 {
|
||||
return errors.New("can not find 'cp' command")
|
||||
}
|
||||
|
||||
// 检查新版本
|
||||
var downloadURL = ""
|
||||
{
|
||||
var url = teaconst.UpdatesURL
|
||||
var osName = runtime.GOOS
|
||||
if Tea.IsTesting() && osName == "darwin" {
|
||||
osName = "linux"
|
||||
}
|
||||
url = strings.ReplaceAll(url, "${os}", osName)
|
||||
url = strings.ReplaceAll(url, "${arch}", runtime.GOARCH)
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return errors.New("create url request failed: " + err.Error())
|
||||
}
|
||||
|
||||
resp, err := this.client.Do(req)
|
||||
if err != nil {
|
||||
return errors.New("read latest version failed: " + err.Error())
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return errors.New("read latest version failed: invalid response code '" + types.String(resp.StatusCode) + "'")
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return errors.New("read latest version failed: " + err.Error())
|
||||
}
|
||||
|
||||
var m = maps.Map{}
|
||||
err = json.Unmarshal(data, &m)
|
||||
if err != nil {
|
||||
return errors.New("invalid response data: " + err.Error() + ", origin data: " + string(data))
|
||||
}
|
||||
|
||||
var code = m.GetInt("code")
|
||||
if code != 200 {
|
||||
return errors.New(m.GetString("message"))
|
||||
}
|
||||
|
||||
var dataMap = m.GetMap("data")
|
||||
var downloadHost = dataMap.GetString("host")
|
||||
var versions = dataMap.GetSlice("versions")
|
||||
var downloadPath = ""
|
||||
for _, component := range versions {
|
||||
var componentMap = maps.NewMap(component)
|
||||
if componentMap.Has("version") {
|
||||
if componentMap.GetString("code") == this.component {
|
||||
var version = componentMap.GetString("version")
|
||||
if stringutil.VersionCompare(version, teaconst.Version) > 0 {
|
||||
this.newVersion = version
|
||||
downloadPath = componentMap.GetString("url")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(downloadPath) == 0 {
|
||||
return errors.New("no latest version to download")
|
||||
}
|
||||
|
||||
downloadURL = downloadHost + downloadPath
|
||||
}
|
||||
|
||||
{
|
||||
req, err := http.NewRequest(http.MethodGet, downloadURL, nil)
|
||||
if err != nil {
|
||||
return errors.New("create download request failed: " + err.Error())
|
||||
}
|
||||
|
||||
resp, err := this.client.Do(req)
|
||||
if err != nil {
|
||||
return errors.New("download failed: " + downloadURL + ": " + err.Error())
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return errors.New("download failed: " + downloadURL + ": invalid response code '" + types.String(resp.StatusCode) + "'")
|
||||
}
|
||||
|
||||
this.contentLength = resp.ContentLength
|
||||
this.body = resp.Body
|
||||
|
||||
// download to tmp
|
||||
var tmpDir = os.TempDir()
|
||||
var filename = filepath.Base(downloadURL)
|
||||
|
||||
var destFile = tmpDir + "/" + filename
|
||||
_ = os.Remove(destFile)
|
||||
|
||||
fp, err := os.Create(destFile)
|
||||
if err != nil {
|
||||
return errors.New("create file failed: " + err.Error())
|
||||
}
|
||||
|
||||
defer func() {
|
||||
// 删除安装文件
|
||||
_ = os.Remove(destFile)
|
||||
}()
|
||||
|
||||
this.writer = NewUpgradeFileWriter(fp)
|
||||
|
||||
_, err = io.Copy(this.writer, resp.Body)
|
||||
if err != nil {
|
||||
_ = fp.Close()
|
||||
if this.isCancelled {
|
||||
return nil
|
||||
}
|
||||
return errors.New("download failed: " + err.Error())
|
||||
}
|
||||
|
||||
_ = fp.Close()
|
||||
|
||||
// unzip
|
||||
var unzipDir = tmpDir + "/edge-" + this.component + "-tmp"
|
||||
stat, err := os.Stat(unzipDir)
|
||||
if err == nil && stat.IsDir() {
|
||||
err = os.RemoveAll(unzipDir)
|
||||
if err != nil {
|
||||
return errors.New("remove old dir '" + unzipDir + "' failed: " + err.Error())
|
||||
}
|
||||
}
|
||||
var unzipCmd = exec.Command(unzipExe, "-q", "-o", destFile, "-d", unzipDir)
|
||||
var unzipStderr = &bytes.Buffer{}
|
||||
unzipCmd.Stderr = unzipStderr
|
||||
err = unzipCmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("unzip installation file failed: " + err.Error() + ": " + unzipStderr.String())
|
||||
}
|
||||
|
||||
installationFiles, err := filepath.Glob(unzipDir + "/edge-" + this.component + "/*")
|
||||
if err != nil {
|
||||
return errors.New("lookup installation files failed: " + err.Error())
|
||||
}
|
||||
|
||||
// cp to target dir
|
||||
currentExe, err := os.Executable()
|
||||
if err != nil {
|
||||
return errors.New("reveal current executable file path failed: " + err.Error())
|
||||
}
|
||||
var targetDir = filepath.Dir(filepath.Dir(currentExe))
|
||||
if !Tea.IsTesting() {
|
||||
for _, installationFile := range installationFiles {
|
||||
var cpCmd = exec.Command(cpExe, "-R", "-f", installationFile, targetDir)
|
||||
var cpStderr = &bytes.Buffer{}
|
||||
cpCmd.Stderr = cpStderr
|
||||
err = cpCmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("overwrite installation files failed: '" + cpCmd.String() + "': " + cpStderr.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove tmp
|
||||
_ = os.RemoveAll(unzipDir)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *UpgradeManager) IsDownloading() bool {
|
||||
return this.isDownloading
|
||||
}
|
||||
|
||||
func (this *UpgradeManager) Progress() float32 {
|
||||
if this.contentLength <= 0 {
|
||||
return -1
|
||||
}
|
||||
if this.writer == nil {
|
||||
return -1
|
||||
}
|
||||
return float32(this.writer.TotalWritten()) / float32(this.contentLength)
|
||||
}
|
||||
|
||||
func (this *UpgradeManager) NewVersion() string {
|
||||
return this.newVersion
|
||||
}
|
||||
|
||||
func (this *UpgradeManager) Cancel() error {
|
||||
this.isCancelled = true
|
||||
this.isDownloading = false
|
||||
|
||||
if this.body != nil {
|
||||
_ = this.body.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
35
internal/utils/upgrade_manager_test.go
Normal file
35
internal/utils/upgrade_manager_test.go
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package utils_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewUpgradeManager(t *testing.T) {
|
||||
var manager = utils.NewUpgradeManager("admin")
|
||||
|
||||
var ticker = time.NewTicker(2 * time.Second)
|
||||
go func() {
|
||||
for range ticker.C {
|
||||
if manager.IsDownloading() {
|
||||
t.Logf("%.2f%%", manager.Progress()*100)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
/**go func() {
|
||||
time.Sleep(5 * time.Second)
|
||||
if manager.IsDownloading() {
|
||||
t.Log("cancel downloading")
|
||||
_ = manager.Cancel()
|
||||
}
|
||||
}()**/
|
||||
|
||||
err := manager.Start()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -18,6 +19,7 @@ import (
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Fail 提示服务器错误信息
|
||||
@@ -45,6 +47,25 @@ func FailPage(action actions.ActionWrapper, err error) {
|
||||
|
||||
var isRPCConnError bool
|
||||
err, isRPCConnError = rpcerrors.HumanError(err, apiEndpoints, Tea.ConfigFile("api.yaml"))
|
||||
var apiNodeIsStarting = false
|
||||
var apiNodeProgress = ""
|
||||
if isRPCConnError {
|
||||
// API节点是否正在启动
|
||||
var sock = gosock.NewTmpSock("edge-api")
|
||||
reply, err := sock.SendTimeout(&gosock.Command{
|
||||
Code: "starting",
|
||||
Params: nil,
|
||||
}, 1*time.Second)
|
||||
if err == nil && reply != nil {
|
||||
var params = maps.NewMap(reply.Params)
|
||||
if params.GetBool("isStarting") {
|
||||
apiNodeIsStarting = true
|
||||
|
||||
var progressMap = params.GetMap("progress")
|
||||
apiNodeProgress = progressMap.GetString("description")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
action.Object().ResponseWriter.WriteHeader(http.StatusInternalServerError)
|
||||
if len(action.Object().Request.Header.Get("X-Requested-With")) > 0 {
|
||||
@@ -90,14 +111,25 @@ func FailPage(action actions.ActionWrapper, err error) {
|
||||
</head>
|
||||
<body>
|
||||
<div style="background: #eee; border: 1px #ccc solid; padding: 10px; font-size: 12px; line-height: 1.8">
|
||||
` + teaconst.ErrServer + `
|
||||
`
|
||||
if apiNodeIsStarting { // API节点正在启动
|
||||
html += "<div class=\"red\">API节点正在启动,请耐心等待完成"
|
||||
|
||||
if len(apiNodeProgress) > 0 {
|
||||
html += ":" + apiNodeProgress
|
||||
}
|
||||
|
||||
html += "</div>"
|
||||
} else {
|
||||
html += teaconst.ErrServer + `
|
||||
<div>可以通过查看 <strong><em>$安装目录/logs/run.log</em></strong> 日志文件查看具体的错误提示。</div>
|
||||
<hr/>
|
||||
<div class="red">Error: ` + err.Error() + `</div>`
|
||||
|
||||
if len(issuesHTML) > 0 {
|
||||
html += ` <hr/>
|
||||
if len(issuesHTML) > 0 {
|
||||
html += ` <hr/>
|
||||
<div class="red">` + issuesHTML + `</div>`
|
||||
}
|
||||
}
|
||||
|
||||
action.Object().WriteString(html + `
|
||||
|
||||
@@ -25,6 +25,7 @@ func (this *DeleteAction) RunPost(params struct {
|
||||
var apiNode = nodeResp.ApiNode
|
||||
if apiNode == nil {
|
||||
this.Success()
|
||||
return
|
||||
}
|
||||
if apiNode.IsOn {
|
||||
countResp, err := this.RPC().APINodeRPC().CountAllEnabledAndOnAPINodes(this.AdminContext(), &pb.CountAllEnabledAndOnAPINodesRequest{})
|
||||
|
||||
@@ -5,11 +5,14 @@ import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/clusterutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/grants/grantutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"net"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
@@ -27,7 +30,7 @@ func (this *CreateNodeAction) Init() {
|
||||
func (this *CreateNodeAction) RunGet(params struct {
|
||||
ClusterId int64
|
||||
}) {
|
||||
leftMenuItems := []maps.Map{
|
||||
var leftMenuItems = []maps.Map{
|
||||
{
|
||||
"name": "单个创建",
|
||||
"url": "/clusters/cluster/createNode?clusterId=" + strconv.FormatInt(params.ClusterId, 10),
|
||||
@@ -47,7 +50,7 @@ func (this *CreateNodeAction) RunGet(params struct {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
dnsRouteMaps := []maps.Map{}
|
||||
var dnsRouteMaps = []maps.Map{}
|
||||
this.Data["dnsDomainId"] = 0
|
||||
if clusterDNSResp.Domain != nil {
|
||||
domainId := clusterDNSResp.Domain.Id
|
||||
@@ -76,8 +79,8 @@ func (this *CreateNodeAction) RunGet(params struct {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
apiNodes := apiNodesResp.ApiNodes
|
||||
apiEndpoints := []string{}
|
||||
var apiNodes = apiNodesResp.ApiNodes
|
||||
var apiEndpoints = []string{}
|
||||
for _, apiNode := range apiNodes {
|
||||
if !apiNode.IsOn {
|
||||
continue
|
||||
@@ -86,6 +89,9 @@ func (this *CreateNodeAction) RunGet(params struct {
|
||||
}
|
||||
this.Data["apiEndpoints"] = "\"" + strings.Join(apiEndpoints, "\", \"") + "\""
|
||||
|
||||
// 安装文件下载
|
||||
this.Data["installerFiles"] = clusterutils.ListInstallerFiles()
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
@@ -118,7 +124,7 @@ func (this *CreateNodeAction) RunPost(params struct {
|
||||
}
|
||||
|
||||
// IP地址
|
||||
ipAddresses := []maps.Map{}
|
||||
var ipAddresses = []maps.Map{}
|
||||
if len(params.IpAddressesJSON) > 0 {
|
||||
err := json.Unmarshal(params.IpAddressesJSON, &ipAddresses)
|
||||
if err != nil {
|
||||
@@ -127,10 +133,29 @@ func (this *CreateNodeAction) RunPost(params struct {
|
||||
}
|
||||
}
|
||||
if len(ipAddresses) == 0 {
|
||||
this.Fail("请至少输入一个IP地址")
|
||||
// 检查Name中是否包含IP
|
||||
var ipv4Reg = regexp.MustCompile(`\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}`)
|
||||
var ipMatches = ipv4Reg.FindStringSubmatch(params.Name)
|
||||
if len(ipMatches) > 0 {
|
||||
var nodeIP = ipMatches[0]
|
||||
if net.ParseIP(nodeIP) != nil {
|
||||
ipAddresses = []maps.Map{
|
||||
{
|
||||
"ip": nodeIP,
|
||||
"canAccess": true,
|
||||
"isOn": true,
|
||||
"isUp": true,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(ipAddresses) == 0 {
|
||||
this.Fail("请至少输入一个IP地址")
|
||||
}
|
||||
}
|
||||
|
||||
dnsRouteCodes := []string{}
|
||||
var dnsRouteCodes = []string{}
|
||||
if len(params.DnsRoutesJSON) > 0 {
|
||||
err := json.Unmarshal(params.DnsRoutesJSON, &dnsRouteCodes)
|
||||
if err != nil {
|
||||
@@ -140,7 +165,7 @@ func (this *CreateNodeAction) RunPost(params struct {
|
||||
}
|
||||
|
||||
// TODO 检查登录授权
|
||||
loginInfo := &pb.NodeLogin{
|
||||
var loginInfo = &pb.NodeLogin{
|
||||
Id: 0,
|
||||
Name: "SSH",
|
||||
Type: "ssh",
|
||||
@@ -165,7 +190,7 @@ func (this *CreateNodeAction) RunPost(params struct {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
nodeId := createResp.NodeId
|
||||
var nodeId = createResp.NodeId
|
||||
|
||||
// IP地址
|
||||
var resultIPAddresses = []string{}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type DownloadInstallerAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *DownloadInstallerAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *DownloadInstallerAction) RunGet(params struct {
|
||||
Name string
|
||||
}) {
|
||||
if len(params.Name) == 0 {
|
||||
this.ResponseWriter.WriteHeader(http.StatusNotFound)
|
||||
this.WriteString("file not found")
|
||||
return
|
||||
}
|
||||
|
||||
// 检查文件名
|
||||
// 以防止路径穿越等风险
|
||||
if !regexp.MustCompile(`^[a-zA-Z0-9.-]+$`).MatchString(params.Name) {
|
||||
this.ResponseWriter.WriteHeader(http.StatusNotFound)
|
||||
this.WriteString("file not found")
|
||||
return
|
||||
}
|
||||
|
||||
var zipFile = Tea.Root + "/edge-api/deploy/" + params.Name
|
||||
fp, err := os.OpenFile(zipFile, os.O_RDWR, 0444)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
this.ResponseWriter.WriteHeader(http.StatusNotFound)
|
||||
this.WriteString("file not found")
|
||||
return
|
||||
}
|
||||
|
||||
this.ResponseWriter.WriteHeader(http.StatusInternalServerError)
|
||||
this.WriteString("file can not be opened")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = fp.Close()
|
||||
}()
|
||||
|
||||
stat, err := fp.Stat()
|
||||
if err != nil {
|
||||
this.ResponseWriter.WriteHeader(http.StatusInternalServerError)
|
||||
this.WriteString("file can not be opened")
|
||||
return
|
||||
}
|
||||
|
||||
this.AddHeader("Content-Disposition", "attachment; filename=\""+params.Name+"\";")
|
||||
this.AddHeader("Content-Type", "application/zip")
|
||||
this.AddHeader("Content-Length", types.String(stat.Size()))
|
||||
_, _ = io.Copy(this.ResponseWriter, fp)
|
||||
}
|
||||
@@ -48,7 +48,7 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
}
|
||||
|
||||
// 创建日志
|
||||
defer this.CreateLog(oplogs.LevelInfo, "创建节点分组", createResp.NodeGroupId)
|
||||
defer this.CreateLog(oplogs.LevelInfo, "创建节点分组 %d", createResp.NodeGroupId)
|
||||
|
||||
this.Success()
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ func init() {
|
||||
GetPost("/updateNodeSSH", new(UpdateNodeSSHAction)).
|
||||
GetPost("/installManual", new(InstallManualAction)).
|
||||
Post("/suggestLoginPorts", new(SuggestLoginPortsAction)).
|
||||
Get("/downloadInstaller", new(DownloadInstallerAction)).
|
||||
|
||||
// 节点相关
|
||||
Prefix("/clusters/cluster/node").
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/clusterutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -32,6 +36,20 @@ func (this *InstallAction) RunGet(params struct {
|
||||
return
|
||||
}
|
||||
|
||||
// 最近运行目录
|
||||
var exeRoot = ""
|
||||
if len(node.StatusJSON) > 0 {
|
||||
var nodeStatus = &nodeconfigs.NodeStatus{}
|
||||
err = json.Unmarshal(node.StatusJSON, nodeStatus)
|
||||
if err == nil {
|
||||
var exePath = nodeStatus.ExePath
|
||||
if len(exePath) > 0 {
|
||||
exeRoot = filepath.Dir(filepath.Dir(exePath))
|
||||
}
|
||||
}
|
||||
}
|
||||
this.Data["exeRoot"] = exeRoot
|
||||
|
||||
// 安装信息
|
||||
if node.InstallStatus != nil {
|
||||
this.Data["installStatus"] = maps.Map{
|
||||
@@ -70,7 +88,7 @@ func (this *InstallAction) RunGet(params struct {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
apiNodes := apiNodesResp.ApiNodes
|
||||
var apiNodes = apiNodesResp.ApiNodes
|
||||
apiEndpoints := []string{}
|
||||
for _, apiNode := range apiNodes {
|
||||
if !apiNode.IsOn {
|
||||
@@ -87,6 +105,10 @@ func (this *InstallAction) RunGet(params struct {
|
||||
nodeMap["secret"] = node.Secret
|
||||
nodeMap["cluster"] = clusterMap
|
||||
|
||||
// 安装文件
|
||||
var installerFiles = clusterutils.ListInstallerFiles()
|
||||
this.Data["installerFiles"] = installerFiles
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dns/domains/domainutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
@@ -41,10 +42,23 @@ func (this *IndexAction) RunGet(params struct {
|
||||
this.Data["dnsName"] = dnsInfoResp.Name
|
||||
this.Data["nodesAutoSync"] = dnsInfoResp.NodesAutoSync
|
||||
this.Data["serversAutoSync"] = dnsInfoResp.ServersAutoSync
|
||||
|
||||
var domainProviderMap = maps.Map{
|
||||
"id": 0,
|
||||
"name": "",
|
||||
}
|
||||
if dnsInfoResp.Domain != nil {
|
||||
this.Data["domainId"] = dnsInfoResp.Domain.Id
|
||||
this.Data["domainName"] = dnsInfoResp.Domain.Name
|
||||
|
||||
if dnsInfoResp.Provider != nil {
|
||||
domainProviderMap = maps.Map{
|
||||
"id": dnsInfoResp.Provider.Id,
|
||||
"name": dnsInfoResp.Provider.Name,
|
||||
}
|
||||
}
|
||||
}
|
||||
this.Data["domainProvider"] = domainProviderMap
|
||||
|
||||
if len(dnsInfoResp.CnameRecords) == 0 {
|
||||
this.Data["cnameRecords"] = []string{}
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package globalServerConfig
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "setting", "")
|
||||
this.SecondMenu("globalServerConfig")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct {
|
||||
ClusterId int64
|
||||
}) {
|
||||
configResp, err := this.RPC().NodeClusterRPC().FindNodeClusterGlobalServerConfig(this.AdminContext(), &pb.FindNodeClusterGlobalServerConfigRequest{NodeClusterId: params.ClusterId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
var configJSON = configResp.GlobalServerConfigJSON
|
||||
var config = serverconfigs.DefaultGlobalServerConfig()
|
||||
if len(configJSON) > 0 {
|
||||
err = json.Unmarshal(configJSON, config)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
this.Data["config"] = config
|
||||
|
||||
var httpAllDomainMismatchActionContentHTML = ""
|
||||
if config.HTTPAll.DomainMismatchAction != nil {
|
||||
httpAllDomainMismatchActionContentHTML = config.HTTPAll.DomainMismatchAction.Options.GetString("contentHTML")
|
||||
} else {
|
||||
httpAllDomainMismatchActionContentHTML = `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<title>404 not found</title>
|
||||
<style>
|
||||
* { font-family: Roboto, system-ui, sans-serif; }
|
||||
h3, p { text-align: center; }
|
||||
p { color: grey; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h3>Error: 404 Page Not Found</h3>
|
||||
<h3>找不到您要访问的页面。</h3>
|
||||
|
||||
<p>原因:找不到当前访问域名对应的网站,请联系网站管理员。</p>
|
||||
|
||||
</body>
|
||||
</html>`
|
||||
}
|
||||
this.Data["httpAllDomainMismatchActionContentHTML"] = httpAllDomainMismatchActionContentHTML
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunPost(params struct {
|
||||
ClusterId int64
|
||||
|
||||
HttpAllMatchDomainStrictly bool
|
||||
HttpAllDomainMismatchActionContentHTML string
|
||||
HttpAllAllowMismatchDomainsJSON []byte
|
||||
HttpAllAllowNodeIP bool
|
||||
HttpAllDefaultDomain string
|
||||
|
||||
LogRecordServerError bool
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
defer this.CreateLogInfo("修改集群 %d 全局配置", params.ClusterId)
|
||||
|
||||
configResp, err := this.RPC().NodeClusterRPC().FindNodeClusterGlobalServerConfig(this.AdminContext(), &pb.FindNodeClusterGlobalServerConfigRequest{NodeClusterId: params.ClusterId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
var configJSON = configResp.GlobalServerConfigJSON
|
||||
var config = serverconfigs.DefaultGlobalServerConfig()
|
||||
if len(configJSON) > 0 {
|
||||
err = json.Unmarshal(configJSON, config)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
config.HTTPAll.MatchDomainStrictly = params.HttpAllMatchDomainStrictly
|
||||
config.HTTPAll.DomainMismatchAction = &serverconfigs.DomainMismatchAction{
|
||||
Code: serverconfigs.DomainMismatchActionPage,
|
||||
Options: maps.Map{
|
||||
"statusCode": 404,
|
||||
"contentHTML": params.HttpAllDomainMismatchActionContentHTML,
|
||||
},
|
||||
}
|
||||
|
||||
var allowMismatchDomains = []string{}
|
||||
if len(params.HttpAllAllowMismatchDomainsJSON) > 0 {
|
||||
err = json.Unmarshal(params.HttpAllAllowMismatchDomainsJSON, &allowMismatchDomains)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
config.HTTPAll.AllowMismatchDomains = allowMismatchDomains
|
||||
config.HTTPAll.AllowNodeIP = params.HttpAllAllowNodeIP
|
||||
config.HTTPAll.DefaultDomain = params.HttpAllDefaultDomain
|
||||
|
||||
config.Log.RecordServerError = params.LogRecordServerError
|
||||
|
||||
err = config.Init()
|
||||
if err != nil {
|
||||
this.Fail("配置校验失败:" + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
configJSON, err = json.Marshal(config)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = this.RPC().NodeClusterRPC().UpdateNodeClusterGlobalServerConfig(this.AdminContext(), &pb.UpdateNodeClusterGlobalServerConfigRequest{
|
||||
NodeClusterId: params.ClusterId,
|
||||
GlobalServerConfigJSON: configJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package health
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type CheckDomainAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *CheckDomainAction) RunPost(params struct {
|
||||
Host string
|
||||
ClusterId int64
|
||||
}) {
|
||||
this.Data["isOk"] = true // 默认为TRUE
|
||||
|
||||
var host = params.Host
|
||||
if len(host) > 0 &&
|
||||
!strings.Contains(host, "{") /** 包含变量 **/ {
|
||||
h, _, err := net.SplitHostPort(host)
|
||||
if err == nil && len(h) > 0 {
|
||||
host = h
|
||||
}
|
||||
|
||||
// 是否为IP
|
||||
if net.ParseIP(host) != nil {
|
||||
this.Success()
|
||||
return
|
||||
}
|
||||
|
||||
host = strings.ToLower(host)
|
||||
resp, err := this.RPC().ServerRPC().CheckServerNameDuplicationInNodeCluster(this.AdminContext(), &pb.CheckServerNameDuplicationInNodeClusterRequest{
|
||||
NodeClusterId: params.ClusterId,
|
||||
ServerNames: []string{host},
|
||||
SupportWildcard: true,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if len(resp.DuplicatedServerNames) == 0 {
|
||||
this.Data["isOk"] = false
|
||||
}
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/grants/grantutils"
|
||||
@@ -65,13 +66,29 @@ func (this *IndexAction) RunGet(params struct {
|
||||
}
|
||||
this.Data["timeZoneLocation"] = nodeconfigs.FindTimeZoneLocation(cluster.TimeZone)
|
||||
|
||||
// 时钟
|
||||
var clockConfig = nodeconfigs.DefaultClockConfig()
|
||||
if len(cluster.ClockJSON) > 0 {
|
||||
err = json.Unmarshal(cluster.ClockJSON, clockConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if clockConfig == nil {
|
||||
clockConfig = nodeconfigs.DefaultClockConfig()
|
||||
}
|
||||
}
|
||||
|
||||
this.Data["cluster"] = maps.Map{
|
||||
"id": cluster.Id,
|
||||
"name": cluster.Name,
|
||||
"installDir": cluster.InstallDir,
|
||||
"timeZone": cluster.TimeZone,
|
||||
"nodeMaxThreads": cluster.NodeMaxThreads,
|
||||
"autoOpenPorts": cluster.AutoOpenPorts,
|
||||
"id": cluster.Id,
|
||||
"name": cluster.Name,
|
||||
"installDir": cluster.InstallDir,
|
||||
"timeZone": cluster.TimeZone,
|
||||
"nodeMaxThreads": cluster.NodeMaxThreads,
|
||||
"autoOpenPorts": cluster.AutoOpenPorts,
|
||||
"clock": clockConfig,
|
||||
"autoRemoteStart": cluster.AutoRemoteStart,
|
||||
"autoInstallNftables": cluster.AutoInstallNftables,
|
||||
}
|
||||
|
||||
// 默认值
|
||||
@@ -84,13 +101,17 @@ func (this *IndexAction) RunGet(params struct {
|
||||
|
||||
// RunPost 保存设置
|
||||
func (this *IndexAction) RunPost(params struct {
|
||||
ClusterId int64
|
||||
Name string
|
||||
GrantId int64
|
||||
InstallDir string
|
||||
TimeZone string
|
||||
NodeMaxThreads int32
|
||||
AutoOpenPorts bool
|
||||
ClusterId int64
|
||||
Name string
|
||||
GrantId int64
|
||||
InstallDir string
|
||||
TimeZone string
|
||||
NodeMaxThreads int32
|
||||
AutoOpenPorts bool
|
||||
ClockAutoSync bool
|
||||
ClockServer string
|
||||
AutoRemoteStart bool
|
||||
AutoInstallNftables bool
|
||||
|
||||
Must *actions.Must
|
||||
}) {
|
||||
@@ -108,14 +129,32 @@ func (this *IndexAction) RunPost(params struct {
|
||||
Lte(int64(nodeconfigs.DefaultMaxThreadsMax), "单节点最大线程数最大值不能大于"+types.String(nodeconfigs.DefaultMaxThreadsMax))
|
||||
}
|
||||
|
||||
_, err := this.RPC().NodeClusterRPC().UpdateNodeCluster(this.AdminContext(), &pb.UpdateNodeClusterRequest{
|
||||
NodeClusterId: params.ClusterId,
|
||||
Name: params.Name,
|
||||
NodeGrantId: params.GrantId,
|
||||
InstallDir: params.InstallDir,
|
||||
TimeZone: params.TimeZone,
|
||||
NodeMaxThreads: params.NodeMaxThreads,
|
||||
AutoOpenPorts: params.AutoOpenPorts,
|
||||
var clockConfig = nodeconfigs.DefaultClockConfig()
|
||||
clockConfig.AutoSync = params.ClockAutoSync
|
||||
clockConfig.Server = params.ClockServer
|
||||
clockConfigJSON, err := json.Marshal(clockConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
err = clockConfig.Init()
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = this.RPC().NodeClusterRPC().UpdateNodeCluster(this.AdminContext(), &pb.UpdateNodeClusterRequest{
|
||||
NodeClusterId: params.ClusterId,
|
||||
Name: params.Name,
|
||||
NodeGrantId: params.GrantId,
|
||||
InstallDir: params.InstallDir,
|
||||
TimeZone: params.TimeZone,
|
||||
NodeMaxThreads: params.NodeMaxThreads,
|
||||
AutoOpenPorts: params.AutoOpenPorts,
|
||||
ClockJSON: clockConfigJSON,
|
||||
AutoRemoteStart: params.AutoRemoteStart,
|
||||
AutoInstallNftables: params.AutoInstallNftables,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
ddosProtection "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/ddos-protection"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/dns"
|
||||
firewallActions "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/firewall-actions"
|
||||
globalServerConfig "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/global-server-config"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/health"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/metrics"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/services"
|
||||
@@ -28,6 +29,7 @@ func init() {
|
||||
// 健康检查
|
||||
GetPost("/health", new(health.IndexAction)).
|
||||
GetPost("/health/runPopup", new(health.RunPopupAction)).
|
||||
Post("/health/checkDomain", new(health.CheckDomainAction)).
|
||||
|
||||
// 缓存
|
||||
GetPost("/cache", new(cache.IndexAction)).
|
||||
@@ -71,6 +73,10 @@ func init() {
|
||||
GetPost("", new(ddosProtection.IndexAction)).
|
||||
GetPost("/status", new(ddosProtection.StatusAction)).
|
||||
|
||||
// 全局服务配置
|
||||
Prefix("/clusters/cluster/settings/global-server-config").
|
||||
GetPost("", new(globalServerConfig.IndexAction)).
|
||||
|
||||
//
|
||||
EndAll()
|
||||
})
|
||||
|
||||
@@ -153,6 +153,12 @@ func (this *ClusterHelper) createSettingMenu(cluster *pb.NodeCluster, info *pb.F
|
||||
"isOn": info != nil && info.HasDDoSProtection,
|
||||
})
|
||||
|
||||
items = append(items, maps.Map{
|
||||
"name": "服务设置",
|
||||
"url": "/clusters/cluster/settings/global-server-config?clusterId=" + clusterId,
|
||||
"isActive": selectedItem == "globalServerConfig",
|
||||
})
|
||||
|
||||
items = append(items, maps.Map{
|
||||
"name": "-",
|
||||
})
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package clusterutils
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type installerFile struct {
|
||||
Name string `json:"name"`
|
||||
OS string `json:"os"`
|
||||
Arch string `json:"arch"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
func ListInstallerFiles() []*installerFile {
|
||||
var dir = Tea.Root + "/edge-api/deploy"
|
||||
matches, err := filepath.Glob(dir + "/edge-node-*.zip")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var result = []*installerFile{}
|
||||
var reg = regexp.MustCompile(`^edge-node-(\w+)-(\w+)-v([\w.]+)\.zip$`)
|
||||
for _, match := range matches {
|
||||
var baseName = filepath.Base(match)
|
||||
var subMatches = reg.FindStringSubmatch(baseName)
|
||||
if len(subMatches) >= 4 {
|
||||
var osName = subMatches[1]
|
||||
if len(osName) > 0 {
|
||||
osName = strings.ToUpper(osName[:1]) + osName[1:]
|
||||
}
|
||||
|
||||
var arch = subMatches[2]
|
||||
if arch == "amd64" {
|
||||
arch = "x86_64"
|
||||
}
|
||||
|
||||
var version = subMatches[3]
|
||||
if version != teaconst.Version { // 只能下载当前版本
|
||||
continue
|
||||
}
|
||||
|
||||
result = append(result, &installerFile{
|
||||
Name: subMatches[0],
|
||||
OS: osName,
|
||||
Arch: arch,
|
||||
Version: version,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 排序,将x86_64排在最上面
|
||||
if len(result) > 0 {
|
||||
sort.Slice(result, func(i, j int) bool {
|
||||
return result[i].Arch == "x86_64"
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dns/domains/domainutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
)
|
||||
|
||||
@@ -55,14 +56,19 @@ func (this *CreateAction) RunPost(params struct {
|
||||
// WAF策略
|
||||
HttpFirewallPolicyId int64
|
||||
|
||||
// 服务配置
|
||||
MatchDomainStrictly bool
|
||||
|
||||
// SSH相关
|
||||
GrantId int64
|
||||
InstallDir string
|
||||
SystemdServiceIsOn bool
|
||||
GrantId int64
|
||||
InstallDir string
|
||||
SystemdServiceIsOn bool
|
||||
AutoInstallNftables bool
|
||||
|
||||
// DNS相关
|
||||
DnsDomainId int64
|
||||
DnsName string
|
||||
DnsTTL int32
|
||||
|
||||
Must *actions.Must
|
||||
}) {
|
||||
@@ -92,8 +98,17 @@ func (this *CreateAction) RunPost(params struct {
|
||||
|
||||
// TODO 检查DnsDomainId的有效性
|
||||
|
||||
// 全局服务配置
|
||||
var globalServerConfig = serverconfigs.DefaultGlobalServerConfig()
|
||||
globalServerConfig.HTTPAll.MatchDomainStrictly = params.MatchDomainStrictly
|
||||
globalServerConfigJSON, err := json.Marshal(globalServerConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 系统服务
|
||||
systemServices := map[string]interface{}{}
|
||||
var systemServices = map[string]any{}
|
||||
if params.SystemdServiceIsOn {
|
||||
systemServices[nodeconfigs.SystemServiceTypeSystemd] = &nodeconfigs.SystemdServiceConfig{
|
||||
IsOn: true,
|
||||
@@ -106,14 +121,17 @@ func (this *CreateAction) RunPost(params struct {
|
||||
}
|
||||
|
||||
createResp, err := this.RPC().NodeClusterRPC().CreateNodeCluster(this.AdminContext(), &pb.CreateNodeClusterRequest{
|
||||
Name: params.Name,
|
||||
NodeGrantId: params.GrantId,
|
||||
InstallDir: params.InstallDir,
|
||||
DnsDomainId: params.DnsDomainId,
|
||||
DnsName: params.DnsName,
|
||||
HttpCachePolicyId: params.CachePolicyId,
|
||||
HttpFirewallPolicyId: params.HttpFirewallPolicyId,
|
||||
SystemServicesJSON: systemServicesJSON,
|
||||
Name: params.Name,
|
||||
NodeGrantId: params.GrantId,
|
||||
InstallDir: params.InstallDir,
|
||||
DnsDomainId: params.DnsDomainId,
|
||||
DnsName: params.DnsName,
|
||||
DnsTTL: params.DnsTTL,
|
||||
HttpCachePolicyId: params.CachePolicyId,
|
||||
HttpFirewallPolicyId: params.HttpFirewallPolicyId,
|
||||
SystemServicesJSON: systemServicesJSON,
|
||||
GlobalServerConfigJSON: globalServerConfigJSON,
|
||||
AutoInstallNftables: params.AutoInstallNftables,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
|
||||
@@ -48,7 +48,7 @@ func (this *UpdatePopupAction) RunPost(params struct {
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
defer this.CreateLogInfo("修改流量价格项目", params.ItemId)
|
||||
defer this.CreateLogInfo("修改流量价格项目 %d", params.ItemId)
|
||||
|
||||
params.Must.
|
||||
Field("name", params.Name).
|
||||
|
||||
@@ -14,7 +14,7 @@ type DomainOptionsAction struct {
|
||||
func (this *DomainOptionsAction) RunPost(params struct {
|
||||
ProviderId int64
|
||||
}) {
|
||||
domainsResp, err := this.RPC().DNSDomainRPC().FindAllEnabledBasicDNSDomainsWithDNSProviderId(this.AdminContext(), &pb.FindAllEnabledBasicDNSDomainsWithDNSProviderIdRequest{
|
||||
domainsResp, err := this.RPC().DNSDomainRPC().FindAllBasicDNSDomainsWithDNSProviderId(this.AdminContext(), &pb.FindAllBasicDNSDomainsWithDNSProviderIdRequest{
|
||||
DnsProviderId: params.ProviderId,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -18,7 +18,7 @@ func (this *ClustersPopupAction) RunGet(params struct {
|
||||
DomainId int64
|
||||
}) {
|
||||
// 域名信息
|
||||
domainResp, err := this.RPC().DNSDomainRPC().FindEnabledBasicDNSDomain(this.AdminContext(), &pb.FindEnabledBasicDNSDomainRequest{
|
||||
domainResp, err := this.RPC().DNSDomainRPC().FindBasicDNSDomain(this.AdminContext(), &pb.FindBasicDNSDomainRequest{
|
||||
DnsDomainId: params.DomainId,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -17,9 +17,9 @@ func ValidateDomainFormat(domain string) bool {
|
||||
if piece == "-" ||
|
||||
strings.HasPrefix(piece, "-") ||
|
||||
strings.HasSuffix(piece, "-") ||
|
||||
//strings.Contains(piece, "--") ||
|
||||
len(piece) > 63 ||
|
||||
!regexp.MustCompile(`^[a-z0-9-]+$`).MatchString(piece) {
|
||||
// 我们允许中文、大写字母、下划线,防止有些特殊场景下需要
|
||||
!regexp.MustCompile(`^[\p{Han}_a-zA-Z0-9-]+$`).MatchString(piece) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,8 @@ func ValidateRecordName(name string) bool {
|
||||
strings.HasSuffix(piece, "-") ||
|
||||
//strings.Contains(piece, "--") ||
|
||||
len(piece) > 63 ||
|
||||
!regexp.MustCompile(`^[_a-z0-9-]+$`).MatchString(piece) {
|
||||
// 我们允许中文、大写字母、下划线,防止有些特殊场景下需要
|
||||
!regexp.MustCompile(`^[\p{Han}_a-zA-Z0-9-]+$`).MatchString(piece) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ func (this *NodesPopupAction) RunGet(params struct {
|
||||
DomainId int64
|
||||
}) {
|
||||
// 域名信息
|
||||
domainResp, err := this.RPC().DNSDomainRPC().FindEnabledBasicDNSDomain(this.AdminContext(), &pb.FindEnabledBasicDNSDomainRequest{
|
||||
domainResp, err := this.RPC().DNSDomainRPC().FindBasicDNSDomain(this.AdminContext(), &pb.FindBasicDNSDomainRequest{
|
||||
DnsDomainId: params.DomainId,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -25,12 +25,12 @@ func (this *SelectPopupAction) RunGet(params struct {
|
||||
|
||||
// 域名信息
|
||||
if params.DomainId > 0 {
|
||||
domainResp, err := this.RPC().DNSDomainRPC().FindEnabledDNSDomain(this.AdminContext(), &pb.FindEnabledDNSDomainRequest{DnsDomainId: params.DomainId})
|
||||
domainResp, err := this.RPC().DNSDomainRPC().FindDNSDomain(this.AdminContext(), &pb.FindDNSDomainRequest{DnsDomainId: params.DomainId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
domain := domainResp.DnsDomain
|
||||
var domain = domainResp.DnsDomain
|
||||
if domain != nil {
|
||||
this.Data["domainId"] = domain.Id
|
||||
this.Data["domainName"] = domain.Name
|
||||
@@ -53,7 +53,7 @@ func (this *SelectPopupAction) RunGet(params struct {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
providerTypeMaps := []maps.Map{}
|
||||
var providerTypeMaps = []maps.Map{}
|
||||
for _, providerType := range providerTypesResp.ProviderTypes {
|
||||
providerTypeMaps = append(providerTypeMaps, maps.Map{
|
||||
"name": providerType.Name,
|
||||
@@ -73,15 +73,29 @@ func (this *SelectPopupAction) RunPost(params struct {
|
||||
}) {
|
||||
this.Data["domainId"] = params.DomainId
|
||||
this.Data["domainName"] = ""
|
||||
this.Data["providerName"] = ""
|
||||
|
||||
if params.DomainId > 0 {
|
||||
domainResp, err := this.RPC().DNSDomainRPC().FindEnabledDNSDomain(this.AdminContext(), &pb.FindEnabledDNSDomainRequest{DnsDomainId: params.DomainId})
|
||||
domainResp, err := this.RPC().DNSDomainRPC().FindDNSDomain(this.AdminContext(), &pb.FindDNSDomainRequest{DnsDomainId: params.DomainId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if domainResp.DnsDomain != nil {
|
||||
this.Data["domainName"] = domainResp.DnsDomain.Name
|
||||
|
||||
// 服务商名称
|
||||
var providerId = domainResp.DnsDomain.ProviderId
|
||||
if providerId > 0 {
|
||||
providerResp, err := this.RPC().DNSProviderRPC().FindEnabledDNSProvider(this.AdminContext(), &pb.FindEnabledDNSProviderRequest{DnsProviderId: providerId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if providerResp.DnsProvider != nil {
|
||||
this.Data["providerName"] = providerResp.DnsProvider.Name
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.Data["domainId"] = 0
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ func (this *ServersPopupAction) RunGet(params struct {
|
||||
DomainId int64
|
||||
}) {
|
||||
// 域名信息
|
||||
domainResp, err := this.RPC().DNSDomainRPC().FindEnabledBasicDNSDomain(this.AdminContext(), &pb.FindEnabledBasicDNSDomainRequest{
|
||||
domainResp, err := this.RPC().DNSDomainRPC().FindBasicDNSDomain(this.AdminContext(), &pb.FindBasicDNSDomainRequest{
|
||||
DnsDomainId: params.DomainId,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -21,7 +21,7 @@ func (this *UpdatePopupAction) Init() {
|
||||
func (this *UpdatePopupAction) RunGet(params struct {
|
||||
DomainId int64
|
||||
}) {
|
||||
domainResp, err := this.RPC().DNSDomainRPC().FindEnabledDNSDomain(this.AdminContext(), &pb.FindEnabledDNSDomainRequest{DnsDomainId: params.DomainId})
|
||||
domainResp, err := this.RPC().DNSDomainRPC().FindDNSDomain(this.AdminContext(), &pb.FindDNSDomainRequest{DnsDomainId: params.DomainId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
|
||||
@@ -47,7 +47,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
providerTypeName := ""
|
||||
|
||||
if cluster.DnsDomainId > 0 {
|
||||
domainResp, err := this.RPC().DNSDomainRPC().FindEnabledBasicDNSDomain(this.AdminContext(), &pb.FindEnabledBasicDNSDomainRequest{DnsDomainId: domainId})
|
||||
domainResp, err := this.RPC().DNSDomainRPC().FindBasicDNSDomain(this.AdminContext(), &pb.FindBasicDNSDomainRequest{DnsDomainId: domainId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
|
||||
@@ -73,6 +73,12 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
ParamCustomHTTPURL string
|
||||
ParamCustomHTTPSecret string
|
||||
|
||||
// EdgeDNS API
|
||||
ParamEdgeDNSAPIRole string
|
||||
ParamEdgeDNSAPIHost string
|
||||
ParamEdgeDNSAPIAccessKeyId string
|
||||
ParamEdgeDNSAPIAccessKeySecret string
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
@@ -121,6 +127,20 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
Email("请输入正确格式的邮箱地址")
|
||||
apiParams["apiKey"] = params.ParamCloudFlareAPIKey
|
||||
apiParams["email"] = params.ParamCloudFlareEmail
|
||||
case "edgeDNSAPI":
|
||||
params.Must.
|
||||
Field("paramEdgeDNSAPIHost", params.ParamEdgeDNSAPIHost).
|
||||
Require("请输入API地址").
|
||||
Field("paramEdgeDNSAPIRole", params.ParamEdgeDNSAPIRole).
|
||||
Require("请选择AccessKey类型").
|
||||
Field("paramEdgeDNSAPIAccessKeyId", params.ParamEdgeDNSAPIAccessKeyId).
|
||||
Require("请输入AccessKey ID").
|
||||
Field("paramEdgeDNSAPIAccessKeySecret", params.ParamEdgeDNSAPIAccessKeySecret).
|
||||
Require("请输入AccessKey密钥")
|
||||
apiParams["host"] = params.ParamEdgeDNSAPIHost
|
||||
apiParams["role"] = params.ParamEdgeDNSAPIRole
|
||||
apiParams["accessKeyId"] = params.ParamEdgeDNSAPIAccessKeyId
|
||||
apiParams["accessKeySecret"] = params.ParamEdgeDNSAPIAccessKeySecret
|
||||
case "customHTTP":
|
||||
params.Must.
|
||||
Field("paramCustomHTTPURL", params.ParamCustomHTTPURL).
|
||||
|
||||
@@ -65,7 +65,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
}
|
||||
|
||||
// 域名
|
||||
countDomainsResp, err := this.RPC().DNSDomainRPC().CountAllEnabledDNSDomainsWithDNSProviderId(this.AdminContext(), &pb.CountAllEnabledDNSDomainsWithDNSProviderIdRequest{
|
||||
countDomainsResp, err := this.RPC().DNSDomainRPC().CountAllDNSDomainsWithDNSProviderId(this.AdminContext(), &pb.CountAllDNSDomainsWithDNSProviderIdRequest{
|
||||
DnsProviderId: provider.Id,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -18,19 +18,24 @@ func (this *ProviderAction) Init() {
|
||||
|
||||
func (this *ProviderAction) RunGet(params struct {
|
||||
ProviderId int64
|
||||
Page int
|
||||
Filter string
|
||||
}) {
|
||||
this.Data["pageNo"] = params.Page
|
||||
this.Data["filter"] = params.Filter
|
||||
|
||||
providerResp, err := this.RPC().DNSProviderRPC().FindEnabledDNSProvider(this.AdminContext(), &pb.FindEnabledDNSProviderRequest{DnsProviderId: params.ProviderId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
provider := providerResp.DnsProvider
|
||||
var provider = providerResp.DnsProvider
|
||||
if provider == nil {
|
||||
this.NotFound("dnsProvider", params.ProviderId)
|
||||
return
|
||||
}
|
||||
|
||||
apiParams := maps.Map{}
|
||||
var apiParams = maps.Map{}
|
||||
if len(provider.ApiParamsJSON) > 0 {
|
||||
err = json.Unmarshal(provider.ApiParamsJSON, &apiParams)
|
||||
if err != nil {
|
||||
@@ -55,13 +60,33 @@ func (this *ProviderAction) RunGet(params struct {
|
||||
"localEdgeDNS": localEdgeDNSMap,
|
||||
}
|
||||
|
||||
// 域名
|
||||
domainsResp, err := this.RPC().DNSDomainRPC().FindAllEnabledDNSDomainsWithDNSProviderId(this.AdminContext(), &pb.FindAllEnabledDNSDomainsWithDNSProviderIdRequest{DnsProviderId: provider.Id})
|
||||
// 域名数量
|
||||
countDomainsResp, err := this.RPC().DNSDomainRPC().CountAllDNSDomainsWithDNSProviderId(this.AdminContext(), &pb.CountAllDNSDomainsWithDNSProviderIdRequest{
|
||||
DnsProviderId: params.ProviderId,
|
||||
IsDeleted: params.Filter == "deleted",
|
||||
IsDown: params.Filter == "down",
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
domainMaps := []maps.Map{}
|
||||
var countDomains = countDomainsResp.Count
|
||||
var page = this.NewPage(countDomains)
|
||||
this.Data["page"] = page.AsHTML()
|
||||
|
||||
// 域名
|
||||
domainsResp, err := this.RPC().DNSDomainRPC().ListBasicDNSDomainsWithDNSProviderId(this.AdminContext(), &pb.ListBasicDNSDomainsWithDNSProviderIdRequest{
|
||||
DnsProviderId: params.ProviderId,
|
||||
IsDeleted: params.Filter == "deleted",
|
||||
IsDown: params.Filter == "down",
|
||||
Offset: page.Offset,
|
||||
Size: page.Size,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var domainMaps = []maps.Map{}
|
||||
for _, domain := range domainsResp.DnsDomains {
|
||||
dataUpdatedTime := ""
|
||||
if domain.DataUpdatedAt > 0 {
|
||||
|
||||
@@ -100,6 +100,12 @@ func (this *UpdatePopupAction) RunPost(params struct {
|
||||
ParamCustomHTTPURL string
|
||||
ParamCustomHTTPSecret string
|
||||
|
||||
// EdgeDNS API
|
||||
ParamEdgeDNSAPIHost string
|
||||
ParamEdgeDNSAPIRole string
|
||||
ParamEdgeDNSAPIAccessKeyId string
|
||||
ParamEdgeDNSAPIAccessKeySecret string
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
@@ -150,6 +156,20 @@ func (this *UpdatePopupAction) RunPost(params struct {
|
||||
Email("请输入正确格式的邮箱地址")
|
||||
apiParams["apiKey"] = params.ParamCloudFlareAPIKey
|
||||
apiParams["email"] = params.ParamCloudFlareEmail
|
||||
case "edgeDNSAPI":
|
||||
params.Must.
|
||||
Field("paramEdgeDNSAPIHost", params.ParamEdgeDNSAPIHost).
|
||||
Require("请输入API地址").
|
||||
Field("paramEdgeDNSAPIRole", params.ParamEdgeDNSAPIRole).
|
||||
Require("请选择AccessKey类型").
|
||||
Field("paramEdgeDNSAPIAccessKeyId", params.ParamEdgeDNSAPIAccessKeyId).
|
||||
Require("请输入AccessKey ID").
|
||||
Field("paramEdgeDNSAPIAccessKeySecret", params.ParamEdgeDNSAPIAccessKeySecret).
|
||||
Require("请输入AccessKey密钥")
|
||||
apiParams["host"] = params.ParamEdgeDNSAPIHost
|
||||
apiParams["role"] = params.ParamEdgeDNSAPIRole
|
||||
apiParams["accessKeyId"] = params.ParamEdgeDNSAPIAccessKeyId
|
||||
apiParams["accessKeySecret"] = params.ParamEdgeDNSAPIAccessKeySecret
|
||||
case "customHTTP":
|
||||
params.Must.
|
||||
Field("paramCustomHTTPURL", params.ParamCustomHTTPURL).
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -65,7 +64,7 @@ func (this *IndexAction) RunPost(params struct {
|
||||
DefaultDomain string
|
||||
}) {
|
||||
// 创建日志
|
||||
defer this.CreateLog(oplogs.LevelInfo, "保存代理服务全局配置")
|
||||
defer this.CreateLog(oplogs.LevelInfo, "保存网站服务全局配置")
|
||||
|
||||
if len(params.GlobalConfigJSON) == 0 {
|
||||
this.Fail("错误的配置信息,请刷新当前页面后重试")
|
||||
@@ -84,27 +83,6 @@ func (this *IndexAction) RunPost(params struct {
|
||||
allowMismatchDomains = append(allowMismatchDomains, domain)
|
||||
}
|
||||
}
|
||||
globalConfig.HTTPAll.AllowMismatchDomains = allowMismatchDomains
|
||||
|
||||
// 不匹配域名的动作
|
||||
switch params.DomainMismatchAction {
|
||||
case "close":
|
||||
globalConfig.HTTPAll.DomainMismatchAction = &serverconfigs.DomainMismatchAction{
|
||||
Code: "close",
|
||||
Options: nil,
|
||||
}
|
||||
case "page":
|
||||
if params.DomainMismatchActionPageStatusCode <= 0 {
|
||||
params.DomainMismatchActionPageStatusCode = 404
|
||||
}
|
||||
globalConfig.HTTPAll.DomainMismatchAction = &serverconfigs.DomainMismatchAction{
|
||||
Code: "page",
|
||||
Options: maps.Map{
|
||||
"statusCode": params.DomainMismatchActionPageStatusCode,
|
||||
"contentHTML": params.DomainMismatchActionPageContentHTML,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TCP端口范围
|
||||
if params.TcpAllPortRangeMin < 1024 {
|
||||
|
||||
@@ -80,9 +80,9 @@ func (this *UpdateIPPopupAction) RunPost(params struct {
|
||||
|
||||
var ipToLong uint64
|
||||
if len(params.IpTo) > 0 && !utils.IsIPv4(params.IpTo) {
|
||||
ipToLong = utils.IP2Long(params.IpTo)
|
||||
this.Fail("请输入正确的结束IP")
|
||||
}
|
||||
ipToLong = utils.IP2Long(params.IpTo)
|
||||
|
||||
if ipFromLong > 0 && ipToLong > 0 && ipFromLong > ipToLong {
|
||||
params.IpTo, params.IpFrom = params.IpFrom, params.IpTo
|
||||
|
||||
@@ -109,6 +109,10 @@ func (this *SettingAction) RunPost(params struct {
|
||||
FollowRedirects: reverseProxyConfig.FollowRedirects,
|
||||
ProxyProtocolJSON: proxyProtocolJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
|
||||
@@ -24,5 +24,4 @@ func (this *IndexAction) RunGet(params struct {
|
||||
} else {
|
||||
this.RedirectURL("/servers/groups/group/settings/httpReverseProxy?groupId=" + types.String(params.GroupId))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -109,6 +109,10 @@ func (this *SettingAction) RunPost(params struct {
|
||||
FollowRedirects: reverseProxyConfig.FollowRedirects,
|
||||
ProxyProtocolJSON: proxyProtocolJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
|
||||
@@ -96,8 +96,12 @@ func (this *SettingAction) RunPost(params struct {
|
||||
StripPrefix: reverseProxyConfig.StripPrefix,
|
||||
AutoFlush: reverseProxyConfig.AutoFlush,
|
||||
AddHeaders: reverseProxyConfig.AddHeaders,
|
||||
FollowRedirects: reverseProxyConfig.FollowRedirects,
|
||||
FollowRedirects: reverseProxyConfig.FollowRedirects,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
|
||||
@@ -79,9 +79,9 @@ func (this *UpdateIPPopupAction) RunPost(params struct {
|
||||
|
||||
var ipToLong uint64
|
||||
if len(params.IpTo) > 0 && !utils.IsIPv4(params.IpTo) {
|
||||
ipToLong = utils.IP2Long(params.IpTo)
|
||||
this.Fail("请输入正确的结束IP")
|
||||
}
|
||||
ipToLong = utils.IP2Long(params.IpTo)
|
||||
|
||||
if ipFromLong > 0 && ipToLong > 0 && ipFromLong > ipToLong {
|
||||
params.IpTo, params.IpFrom = params.IpFrom, params.IpTo
|
||||
|
||||
@@ -90,15 +90,16 @@ func (this *IndexAction) RunGet(params struct {
|
||||
|
||||
// 服务列表
|
||||
serversResp, err := this.RPC().ServerRPC().ListEnabledServersMatch(this.AdminContext(), &pb.ListEnabledServersMatchRequest{
|
||||
Offset: page.Offset,
|
||||
Size: page.Size,
|
||||
NodeClusterId: params.ClusterId,
|
||||
ServerGroupId: params.GroupId,
|
||||
Keyword: params.Keyword,
|
||||
AuditingFlag: params.AuditingFlag,
|
||||
TrafficOutDesc: params.TrafficOutOrder == "desc",
|
||||
TrafficOutAsc: params.TrafficOutOrder == "asc",
|
||||
UserId: params.UserId,
|
||||
Offset: page.Offset,
|
||||
Size: page.Size,
|
||||
NodeClusterId: params.ClusterId,
|
||||
ServerGroupId: params.GroupId,
|
||||
Keyword: params.Keyword,
|
||||
AuditingFlag: params.AuditingFlag,
|
||||
TrafficOutDesc: params.TrafficOutOrder == "desc",
|
||||
TrafficOutAsc: params.TrafficOutOrder == "asc",
|
||||
UserId: params.UserId,
|
||||
IgnoreServerNames: true,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
@@ -176,27 +177,35 @@ func (this *IndexAction) RunGet(params struct {
|
||||
}
|
||||
|
||||
// 域名列表
|
||||
var serverNames = []*serverconfigs.ServerNameConfig{}
|
||||
if server.IsAuditing || (server.AuditingResult != nil && !server.AuditingResult.IsOk) {
|
||||
server.ServerNamesJSON = server.AuditingServerNamesJSON
|
||||
|
||||
if len(config.ServerNames) == 0 {
|
||||
// 审核中的域名
|
||||
if len(server.ServerNamesJSON) > 0 {
|
||||
var serverNames = []*serverconfigs.ServerNameConfig{}
|
||||
err = json.Unmarshal(server.ServerNamesJSON, &serverNames)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
config.ServerNames = serverNames
|
||||
}
|
||||
}
|
||||
}
|
||||
var auditingIsOk = true
|
||||
if !server.IsAuditing && server.AuditingResult != nil && !server.AuditingResult.IsOk {
|
||||
auditingIsOk = false
|
||||
}
|
||||
if len(server.ServerNamesJSON) > 0 {
|
||||
err = json.Unmarshal(server.ServerNamesJSON, &serverNames)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
var firstServerName = ""
|
||||
for _, serverNameConfig := range config.ServerNames {
|
||||
if len(serverNameConfig.Name) > 0 {
|
||||
firstServerName = serverNameConfig.Name
|
||||
break
|
||||
}
|
||||
}
|
||||
var countServerNames = 0
|
||||
for _, serverName := range serverNames {
|
||||
if len(serverName.SubNames) == 0 {
|
||||
countServerNames++
|
||||
} else {
|
||||
countServerNames += len(serverName.SubNames)
|
||||
if len(serverNameConfig.SubNames) > 0 {
|
||||
firstServerName = serverNameConfig.SubNames[0]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,8 +241,8 @@ func (this *IndexAction) RunGet(params struct {
|
||||
"ports": portMaps,
|
||||
"serverTypeName": serverconfigs.FindServerType(server.Type).GetString("name"),
|
||||
"groups": groupMaps,
|
||||
"serverNames": serverNames,
|
||||
"countServerNames": countServerNames,
|
||||
"firstServerName": firstServerName,
|
||||
"countServerNames": server.CountServerNames,
|
||||
"isAuditing": server.IsAuditing,
|
||||
"auditingIsOk": auditingIsOk,
|
||||
"user": userMap,
|
||||
|
||||
@@ -97,9 +97,9 @@ func (this *CreateIPPopupAction) RunPost(params struct {
|
||||
|
||||
var ipToLong uint64
|
||||
if len(params.IpTo) > 0 && !utils.IsIPv4(params.IpTo) {
|
||||
ipToLong = utils.IP2Long(params.IpTo)
|
||||
this.Fail("请输入正确的结束IP")
|
||||
}
|
||||
ipToLong = utils.IP2Long(params.IpTo)
|
||||
|
||||
if ipFromLong > 0 && ipToLong > 0 && ipFromLong > ipToLong {
|
||||
params.IpTo, params.IpFrom = params.IpFrom, params.IpTo
|
||||
|
||||
@@ -79,9 +79,9 @@ func (this *UpdateIPPopupAction) RunPost(params struct {
|
||||
|
||||
var ipToLong uint64
|
||||
if len(params.IpTo) > 0 && !utils.IsIPv4(params.IpTo) {
|
||||
ipToLong = utils.IP2Long(params.IpTo)
|
||||
this.Fail("请输入正确的结束IP")
|
||||
}
|
||||
ipToLong = utils.IP2Long(params.IpTo)
|
||||
|
||||
if ipFromLong > 0 && ipToLong > 0 && ipFromLong > ipToLong {
|
||||
params.IpTo, params.IpFrom = params.IpFrom, params.IpTo
|
||||
|
||||
@@ -41,9 +41,6 @@ func (this *UpdateRefsAction) RunPost(params struct {
|
||||
|
||||
// 校验配置
|
||||
var cacheConfig = webConfig.Cache
|
||||
if webConfig == nil {
|
||||
this.Success()
|
||||
}
|
||||
|
||||
var refs = []*serverconfigs.HTTPCacheRef{}
|
||||
err = json.Unmarshal(params.RefsJSON, &refs)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type CreateSetPopupAction struct {
|
||||
@@ -45,6 +46,8 @@ func (this *CreateSetPopupAction) RunPost(params struct {
|
||||
// 日志
|
||||
defer this.CreateLog(oplogs.LevelInfo, "设置请求Header,HeaderPolicyId:%d, Name:%s, Value:%s", params.HeaderPolicyId, params.Name, params.Value)
|
||||
|
||||
params.Name = strings.TrimSuffix(params.Name, ":")
|
||||
|
||||
params.Must.
|
||||
Field("name", params.Name).
|
||||
Require("请输入Header名称")
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type UpdateSetPopupAction struct {
|
||||
@@ -60,6 +61,8 @@ func (this *UpdateSetPopupAction) RunPost(params struct {
|
||||
// 日志
|
||||
defer this.CreateLog(oplogs.LevelInfo, "修改设置请求Header,HeaderPolicyId:%d, Name:%s, Value:%s", params.HeaderId, params.Name, params.Value)
|
||||
|
||||
params.Name = strings.TrimSuffix(params.Name, ":")
|
||||
|
||||
params.Must.
|
||||
Field("name", params.Name).
|
||||
Require("请输入Header名称")
|
||||
|
||||
@@ -103,6 +103,12 @@ func (this *LocationHelper) createMenus(serverIdString string, locationIdString
|
||||
"isActive": secondMenuItem == "access",
|
||||
"isOn": locationConfig != nil && locationConfig.Web != nil && locationConfig.Web.Auth != nil && locationConfig.Web.Auth.IsPrior,
|
||||
})
|
||||
menuItems = append(menuItems, maps.Map{
|
||||
"name": "防盗链",
|
||||
"url": "/servers/server/settings/locations/referers?serverId=" + serverIdString + "&locationId=" + locationIdString,
|
||||
"isActive": secondMenuItem == "referer",
|
||||
"isOn": locationConfig.Web != nil && locationConfig.Web.Referers != nil && locationConfig.Web.Referers.IsPrior,
|
||||
})
|
||||
menuItems = append(menuItems, maps.Map{
|
||||
"name": "字符编码",
|
||||
"url": "/servers/server/settings/locations/charset?serverId=" + serverIdString + "&locationId=" + locationIdString,
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package uam
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "setting", "index")
|
||||
this.SecondMenu("referer")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct {
|
||||
LocationId int64
|
||||
}) {
|
||||
webConfig, err := dao.SharedHTTPWebDAO.FindWebConfigWithLocationId(this.AdminContext(), params.LocationId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["webId"] = webConfig.Id
|
||||
|
||||
var referersConfig = webConfig.Referers
|
||||
if referersConfig == nil {
|
||||
referersConfig = &serverconfigs.ReferersConfig{
|
||||
IsPrior: false,
|
||||
IsOn: false,
|
||||
AllowEmpty: true,
|
||||
AllowSameDomain: true,
|
||||
AllowDomains: nil,
|
||||
}
|
||||
}
|
||||
|
||||
this.Data["referersConfig"] = referersConfig
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunPost(params struct {
|
||||
WebId int64
|
||||
ReferersJSON []byte
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
defer this.CreateLogInfo("修改Web %d 防盗链设置", params.WebId)
|
||||
|
||||
_, err := this.RPC().HTTPWebRPC().UpdateHTTPWebReferers(this.AdminContext(), &pb.UpdateHTTPWebReferersRequest{
|
||||
HttpWebId: params.WebId,
|
||||
ReferersJSON: params.ReferersJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package uam
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/locations/locationutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/serverutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeServer)).
|
||||
Helper(locationutils.NewLocationHelper()).
|
||||
Helper(serverutils.NewServerHelper()).
|
||||
Data("tinyMenuItem", "referer").
|
||||
Prefix("/servers/server/settings/locations/referers").
|
||||
GetPost("", new(IndexAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -100,6 +100,10 @@ func (this *SettingAction) RunPost(params struct {
|
||||
FollowRedirects: reverseProxyConfig.FollowRedirects,
|
||||
ProxyProtocolJSON: proxyProtocolJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package uam
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "setting", "index")
|
||||
this.SecondMenu("referer")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct {
|
||||
ServerId int64
|
||||
}) {
|
||||
this.Data["serverId"] = params.ServerId
|
||||
|
||||
webConfig, err := dao.SharedHTTPWebDAO.FindWebConfigWithServerId(this.AdminContext(), params.ServerId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["webId"] = webConfig.Id
|
||||
|
||||
var referersConfig = webConfig.Referers
|
||||
if referersConfig == nil {
|
||||
referersConfig = &serverconfigs.ReferersConfig{
|
||||
IsPrior: false,
|
||||
IsOn: false,
|
||||
AllowEmpty: true,
|
||||
AllowSameDomain: true,
|
||||
AllowDomains: nil,
|
||||
}
|
||||
}
|
||||
|
||||
this.Data["referersConfig"] = referersConfig
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunPost(params struct {
|
||||
WebId int64
|
||||
ReferersJSON []byte
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
defer this.CreateLogInfo("修改Web %d 防盗链设置", params.WebId)
|
||||
|
||||
_, err := this.RPC().HTTPWebRPC().UpdateHTTPWebReferers(this.AdminContext(), &pb.UpdateHTTPWebReferersRequest{
|
||||
HttpWebId: params.WebId,
|
||||
ReferersJSON: params.ReferersJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package uam
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/serverutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeServer)).
|
||||
Helper(serverutils.NewServerHelper()).
|
||||
Prefix("/servers/server/settings/referers").
|
||||
GetPost("", new(IndexAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -79,9 +79,9 @@ func (this *UpdateIPPopupAction) RunPost(params struct {
|
||||
|
||||
var ipToLong uint64
|
||||
if len(params.IpTo) > 0 && !utils.IsIPv4(params.IpTo) {
|
||||
ipToLong = utils.IP2Long(params.IpTo)
|
||||
this.Fail("请输入正确的结束IP")
|
||||
}
|
||||
ipToLong = utils.IP2Long(params.IpTo)
|
||||
|
||||
if ipFromLong > 0 && ipToLong > 0 && ipFromLong > ipToLong {
|
||||
params.IpTo, params.IpFrom = params.IpFrom, params.IpTo
|
||||
|
||||
@@ -309,6 +309,12 @@ func (this *ServerHelper) createSettingsMenu(secondMenuItem string, serverIdStri
|
||||
"isActive": secondMenuItem == "access",
|
||||
"isOn": serverConfig.Web != nil && serverConfig.Web.Auth != nil && serverConfig.Web.Auth.IsOn,
|
||||
})
|
||||
menuItems = append(menuItems, maps.Map{
|
||||
"name": "防盗链",
|
||||
"url": "/servers/server/settings/referers?serverId=" + serverIdString,
|
||||
"isActive": secondMenuItem == "referer",
|
||||
"isOn": serverConfig.Web != nil && serverConfig.Web.Referers != nil && serverConfig.Web.Referers.IsOn,
|
||||
})
|
||||
menuItems = append(menuItems, maps.Map{
|
||||
"name": "字符编码",
|
||||
"url": "/servers/server/settings/charset?serverId=" + serverIdString,
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"io"
|
||||
)
|
||||
@@ -37,6 +38,8 @@ func (this *IndexAction) RunGet(params struct{}) {
|
||||
}
|
||||
this.Data["timeZoneLocation"] = nodeconfigs.FindTimeZoneLocation(config.TimeZone)
|
||||
|
||||
this.filterConfig(config)
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
@@ -52,6 +55,9 @@ func (this *IndexAction) RunPost(params struct {
|
||||
DefaultPageSize int
|
||||
TimeZone string
|
||||
|
||||
SupportModuleCDN bool
|
||||
SupportModuleNS bool
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
@@ -85,6 +91,14 @@ func (this *IndexAction) RunPost(params struct {
|
||||
config.DefaultPageSize = 10
|
||||
}
|
||||
|
||||
config.Modules = []userconfigs.UserModule{}
|
||||
if params.SupportModuleCDN {
|
||||
config.Modules = append(config.Modules, userconfigs.UserModuleCDN)
|
||||
}
|
||||
if params.SupportModuleNS {
|
||||
config.Modules = append(config.Modules, userconfigs.UserModuleNS)
|
||||
}
|
||||
|
||||
// 上传Favicon文件
|
||||
if params.FaviconFile != nil {
|
||||
createResp, err := this.RPC().FileRPC().CreateFile(this.AdminContext(), &pb.CreateFileRequest{
|
||||
|
||||
12
internal/web/actions/default/settings/ui/index_ext.go
Normal file
12
internal/web/actions/default/settings/ui/index_ext.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build !plus
|
||||
|
||||
package ui
|
||||
|
||||
import "github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
|
||||
func (this *IndexAction) filterConfig(config *systemconfigs.AdminUIConfig) {
|
||||
this.Data["supportModuleCDN"] = true
|
||||
this.Data["supportModuleNS"] = true
|
||||
this.Data["nsIsVisible"] = false
|
||||
}
|
||||
@@ -48,25 +48,25 @@ func (this *ValidateApiAction) RunPost(params struct {
|
||||
if params.Mode == "new" {
|
||||
params.Must.
|
||||
Field("newPort", params.NewPort).
|
||||
Require("请输入节点端口").
|
||||
Match(`^\d+$`, "节点端口只能是数字").
|
||||
MinLength(4, "请输入4位以上的数字").
|
||||
MaxLength(5, "请输入5位以下的数字")
|
||||
newPort := types.Int(params.NewPort)
|
||||
Require("请输入API节点端口").
|
||||
Match(`^\d+$`, "API节点端口只能是数字").
|
||||
MinLength(4, "请输入4位以上的数字端口号").
|
||||
MaxLength(5, "请输入5位以下的数字端口号")
|
||||
var newPort = types.Int(params.NewPort)
|
||||
if newPort < 1024 {
|
||||
this.FailField("newPort", "端口号不能小于1024")
|
||||
this.FailField("newPort", "API端口号不能小于1024")
|
||||
}
|
||||
if newPort > 65534 {
|
||||
this.FailField("newPort", "端口号不能大于65534")
|
||||
this.FailField("newPort", "API端口号不能大于65534")
|
||||
}
|
||||
|
||||
if net.ParseIP(params.NewHost) == nil {
|
||||
this.FailField("newHost", "请输入正确的节点主机地址")
|
||||
this.FailField("newHost", "请输入正确的API节点主机地址")
|
||||
}
|
||||
|
||||
params.Must.
|
||||
Field("newHost", params.NewHost).
|
||||
Require("请输入节点主机地址")
|
||||
Require("请输入API节点主机地址")
|
||||
|
||||
this.Success()
|
||||
return
|
||||
|
||||
@@ -26,7 +26,7 @@ func (this *CountryOptionsAction) RunPost(params struct{}) {
|
||||
}
|
||||
|
||||
var letter = ""
|
||||
if len(country.Pinyin) > 0 && len(country.Pinyin) > 0 && len(country.Pinyin[0]) > 0 {
|
||||
if len(country.Pinyin) > 0 && len(country.Pinyin[0]) > 0 {
|
||||
letter = strings.ToUpper(country.Pinyin[0][:1])
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,10 @@ import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/setup"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"net"
|
||||
@@ -192,7 +195,7 @@ func (this *userMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramNam
|
||||
return true
|
||||
}
|
||||
|
||||
config, err := configloaders.LoadAdminUIConfig()
|
||||
uiConfig, err := configloaders.LoadAdminUIConfig()
|
||||
if err != nil {
|
||||
action.WriteString(err.Error())
|
||||
return false
|
||||
@@ -203,11 +206,11 @@ func (this *userMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramNam
|
||||
return action.Data["teaTitle"].(string)
|
||||
})
|
||||
|
||||
action.Data["teaShowVersion"] = config.ShowVersion
|
||||
action.Data["teaTitle"] = config.AdminSystemName
|
||||
action.Data["teaName"] = config.ProductName
|
||||
action.Data["teaFaviconFileId"] = config.FaviconFileId
|
||||
action.Data["teaLogoFileId"] = config.LogoFileId
|
||||
action.Data["teaShowVersion"] = uiConfig.ShowVersion
|
||||
action.Data["teaTitle"] = uiConfig.AdminSystemName
|
||||
action.Data["teaName"] = uiConfig.ProductName
|
||||
action.Data["teaFaviconFileId"] = uiConfig.FaviconFileId
|
||||
action.Data["teaLogoFileId"] = uiConfig.LogoFileId
|
||||
action.Data["teaUsername"] = configloaders.FindAdminFullname(adminId)
|
||||
action.Data["teaTheme"] = configloaders.FindAdminTheme(adminId)
|
||||
|
||||
@@ -216,15 +219,15 @@ func (this *userMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramNam
|
||||
if !action.Data.Has("teaMenu") {
|
||||
action.Data["teaMenu"] = ""
|
||||
}
|
||||
action.Data["teaModules"] = this.modules(actionPtr, adminId)
|
||||
action.Data["teaModules"] = this.modules(actionPtr, adminId, uiConfig)
|
||||
action.Data["teaSubMenus"] = []map[string]interface{}{}
|
||||
action.Data["teaTabbar"] = []map[string]interface{}{}
|
||||
if len(config.Version) == 0 {
|
||||
if len(uiConfig.Version) == 0 {
|
||||
action.Data["teaVersion"] = teaconst.Version
|
||||
} else {
|
||||
action.Data["teaVersion"] = config.Version
|
||||
action.Data["teaVersion"] = uiConfig.Version
|
||||
}
|
||||
action.Data["teaShowOpenSourceInfo"] = config.ShowOpenSourceInfo
|
||||
action.Data["teaShowOpenSourceInfo"] = uiConfig.ShowOpenSourceInfo
|
||||
action.Data["teaIsSuper"] = false
|
||||
action.Data["teaIsPlus"] = teaconst.IsPlus
|
||||
action.Data["teaDemoEnabled"] = teaconst.IsDemoMode
|
||||
@@ -251,7 +254,7 @@ func (this *userMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramNam
|
||||
}
|
||||
|
||||
// 菜单配置
|
||||
func (this *userMustAuth) modules(actionPtr actions.ActionWrapper, adminId int64) []maps.Map {
|
||||
func (this *userMustAuth) modules(actionPtr actions.ActionWrapper, adminId int64, adminUIConfig *systemconfigs.AdminUIConfig) []maps.Map {
|
||||
// 父级动作
|
||||
var action = actionPtr.Object()
|
||||
|
||||
@@ -269,14 +272,27 @@ func (this *userMustAuth) modules(actionPtr actions.ActionWrapper, adminId int64
|
||||
}
|
||||
}
|
||||
|
||||
result := []maps.Map{}
|
||||
var result = []maps.Map{}
|
||||
for _, m := range FindAllMenuMaps(nodeLogsType, countUnreadNodeLogs, countUnreadIPItems) {
|
||||
if m.GetString("code") == "finance" && !configloaders.ShowFinance() {
|
||||
continue
|
||||
}
|
||||
|
||||
module := m.GetString("module")
|
||||
var module = m.GetString("module")
|
||||
if configloaders.AllowModule(adminId, module) {
|
||||
if module == "ns" && !adminUIConfig.ContainsModule(userconfigs.UserModuleNS) {
|
||||
continue
|
||||
}
|
||||
if lists.ContainsString([]string{
|
||||
configloaders.AdminModuleCodeNode,
|
||||
configloaders.AdminModuleCodeDNS,
|
||||
configloaders.AdminModuleCodePlan,
|
||||
configloaders.AdminModuleCodeServer,
|
||||
configloaders.AdminModuleCodeDashboard,
|
||||
}, module) && !adminUIConfig.ContainsModule(userconfigs.UserModuleCDN) {
|
||||
continue
|
||||
}
|
||||
|
||||
result = append(result, m)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ import (
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/locations/http"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/locations/location"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/locations/pages"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/locations/referers"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/locations/remoteAddr"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/locations/requestLimit"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/locations/reverseProxy"
|
||||
@@ -91,6 +92,7 @@ import (
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/origins"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/pages"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/redirects"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/referers"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/remoteAddr"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/requestLimit"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/reverseProxy"
|
||||
|
||||
@@ -456,10 +456,17 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>记录所有访问</td>
|
||||
<td>只记录失败查询</td>
|
||||
<td>
|
||||
<checkbox v-model="config.missingRecordsOnly"></checkbox>
|
||||
<p class="comment">选中后,表示只记录查询失败的日志。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>包含未添加的域名</td>
|
||||
<td>
|
||||
<checkbox name="logMissingDomains" value="1" v-model="config.logMissingDomains"></checkbox>
|
||||
<p class="comment">包括对没有在系统里创建的域名访问。</p>
|
||||
<p class="comment">选中后,表示日志中包含对没有在系统里创建的域名访问。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -798,7 +805,7 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
</div>
|
||||
</div>`}),Vue.component("ns-user-selector",{props:["v-user-id"],data:function(){return{}},methods:{change:function(e){this.$emit("change",e)}},template:`<div>
|
||||
<user-selector :v-user-id="vUserId" data-url="/ns/users/options" @change="change"></user-selector>
|
||||
</div>`}),Vue.component("ns-access-log-box",{props:["v-access-log","v-keyword"],data:function(){return{accessLog:this.vAccessLog}},methods:{showLog:function(){let e=this;var t=this.accessLog.requestId;this.$parent.$children.forEach(function(e){null!=e.deselect&&e.deselect()}),this.select(),teaweb.popup("/ns/clusters/accessLogs/viewPopup?requestId="+t,{width:"50em",height:"24em",onClose:function(){e.deselect()}})},select:function(){this.$refs.box.parentNode.style.cssText="background: rgba(0, 0, 0, 0.1)"},deselect:function(){this.$refs.box.parentNode.style.cssText=""}},template:`<div class="access-log-row" :style="{'color': (!accessLog.isRecursive && (accessLog.nsRecordId == null || accessLog.nsRecordId == 0) || (accessLog.isRecursive && accessLog.recordValue != null && accessLog.recordValue.length == 0)) ? '#dc143c' : ''}" ref="box">
|
||||
</div>`}),Vue.component("ns-access-log-box",{props:["v-access-log","v-keyword"],data:function(){var e=this.vAccessLog;let t=!1;return e.isRecursive||"SOA"==e.recordType||"NS"==e.recordType?null!=e.recordValue&&0!=e.recordValue.length||(t=!0):null!=e.nsRecordId&&0!=e.nsRecordId||(t=!0),{accessLog:e,isFailure:t}},methods:{showLog:function(){let e=this;var t=this.accessLog.requestId;this.$parent.$children.forEach(function(e){null!=e.deselect&&e.deselect()}),this.select(),teaweb.popup("/ns/clusters/accessLogs/viewPopup?requestId="+t,{width:"50em",height:"24em",onClose:function(){e.deselect()}})},select:function(){this.$refs.box.parentNode.style.cssText="background: rgba(0, 0, 0, 0.1)"},deselect:function(){this.$refs.box.parentNode.style.cssText=""}},template:`<div class="access-log-row" :style="{'color': isFailure ? '#dc143c' : ''}" ref="box">
|
||||
<span v-if="accessLog.region != null && accessLog.region.length > 0" class="grey">[{{accessLog.region}}]</span> <keyword :v-word="vKeyword">{{accessLog.remoteAddr}}</keyword> [{{accessLog.timeLocal}}] [{{accessLog.networking}}] <em>{{accessLog.questionType}} <keyword :v-word="vKeyword">{{accessLog.questionName}}</keyword></em> ->
|
||||
|
||||
<span v-if="accessLog.recordType != null && accessLog.recordType.length > 0"><em>{{accessLog.recordType}} <keyword :v-word="vKeyword">{{accessLog.recordValue}}</keyword></em></span>
|
||||
@@ -817,8 +824,8 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<option value="0">[选择集群]</option>
|
||||
<option v-for="cluster in clusters" :value="cluster.id">{{cluster.name}}</option>
|
||||
</select>
|
||||
</div>`}),Vue.component("ns-cluster-combo-box",{props:["v-cluster-id"],data:function(){let t=this;return Tea.action("/ns/clusters/options").post().success(function(e){t.clusters=e.data.clusters}),{clusters:[]}},methods:{change:function(e){null==e?this.$emit("change",0):this.$emit("change",e.value)}},template:`<div v-if="clusters.length > 0" style="min-width: 10.4em">
|
||||
<combo-box title="集群" placeholder="集群名称" :v-items="clusters" name="clusterId" :v-value="vClusterId" @change="change"></combo-box>
|
||||
</div>`}),Vue.component("ns-cluster-combo-box",{props:["v-cluster-id","name"],data:function(){let t=this,e=(Tea.action("/ns/clusters/options").post().success(function(e){t.clusters=e.data.clusters}),"clusterId");return{clusters:[],inputName:e=null!=this.name&&0<this.name.length?this.name:e}},methods:{change:function(e){null==e?this.$emit("change",0):this.$emit("change",e.value)}},template:`<div v-if="clusters.length > 0" style="min-width: 10.4em">
|
||||
<combo-box title="集群" placeholder="集群名称" :v-items="clusters" :name="inputName" :v-value="vClusterId" @change="change"></combo-box>
|
||||
</div>`}),Vue.component("plan-user-selector",{props:["v-user-id"],data:function(){return{}},methods:{change:function(e){this.$emit("change",e)}},template:`<div>
|
||||
<user-selector :v-user-id="vUserId" data-url="/plans/users/options" @change="change"></user-selector>
|
||||
</div>`}),Vue.component("plan-price-view",{props:["v-plan"],data:function(){return{plan:this.vPlan}},template:`<div>
|
||||
@@ -1309,11 +1316,12 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<th class="width10">缓存时间</th>
|
||||
</tr>
|
||||
<tr v-for="(cacheRef, index) in refs">
|
||||
<td :class="{'color-border': cacheRef.conds.connector == 'and', disabled: !cacheRef.isOn}" :style="{'border-left':cacheRef.isReverse ? '1px #db2828 solid' : ''}">
|
||||
<td :class="{'color-border': cacheRef.conds != null && cacheRef.conds.connector == 'and', disabled: !cacheRef.isOn}" :style="{'border-left':cacheRef.isReverse ? '1px #db2828 solid' : ''}">
|
||||
<http-request-conds-view :v-conds="cacheRef.conds" :class="{disabled: !cacheRef.isOn}" v-if="cacheRef.conds != null && cacheRef.conds.groups != null"></http-request-conds-view>
|
||||
<http-request-cond-view :v-cond="cacheRef.simpleCond" v-if="cacheRef.simpleCond != null"></http-request-cond-view>
|
||||
|
||||
<!-- 特殊参数 -->
|
||||
<grey-label v-if="cacheRef.key != null && cacheRef.key.indexOf('\${args}') < 0">忽略URI参数</grey-label>
|
||||
<grey-label v-if="cacheRef.minSize != null && cacheRef.minSize.count > 0">
|
||||
{{cacheRef.minSize.count}}{{cacheRef.minSize.unit}}
|
||||
<span v-if="cacheRef.maxSize != null && cacheRef.maxSize.count > 0">- {{cacheRef.maxSize.count}}{{cacheRef.maxSize.unit}}</span>
|
||||
@@ -1327,8 +1335,11 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<grey-label v-if="cacheRef.enableIfModifiedSince">If-Modified-Since</grey-label>
|
||||
</td>
|
||||
<td :class="{disabled: !cacheRef.isOn}">
|
||||
<span v-if="cacheRef.conds.connector == 'and'">和</span>
|
||||
<span v-if="cacheRef.conds.connector == 'or'">或</span>
|
||||
<span v-if="cacheRef.conds != null">
|
||||
<span v-if="cacheRef.conds.connector == 'and'">和</span>
|
||||
<span v-if="cacheRef.conds.connector == 'or'">或</span>
|
||||
</span>
|
||||
<span v-else>或</span>
|
||||
</td>
|
||||
<td :class="{disabled: !cacheRef.isOn}">
|
||||
<span v-if="!cacheRef.isReverse">{{cacheRef.life.count}} {{timeUnitName(cacheRef.life.unit)}}</span>
|
||||
@@ -1410,7 +1421,7 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<p class="comment" v-if="redirects.length > 1">所有规则匹配顺序为从上到下,可以拖动左侧的<i class="icon bars"></i>排序。</p>
|
||||
</div>
|
||||
<div class="margin"></div>
|
||||
</div>`}),Vue.component("http-cache-ref-box",{props:["v-cache-ref","v-is-reverse"],mounted:function(){this.$refs.variablesDescriber.update(this.ref.key),null!=this.ref.simpleCond?(this.condType=this.ref.simpleCond.type,this.changeCondType(this.ref.simpleCond.type,!0),this.condCategory="simple"):null!=this.ref.conds&&null!=this.ref.conds.groups&&(this.condCategory="complex"),this.changeCondCategory(this.condCategory)},data:function(){let e=this.vCacheRef;null==(e=null==e?{isOn:!0,cachePolicyId:0,key:"${scheme}://${host}${requestPath}${isArgs}${args}",life:{count:2,unit:"hour"},status:[200],maxSize:{count:32,unit:"mb"},minSize:{count:0,unit:"kb"},skipCacheControlValues:["private","no-cache","no-store"],skipSetCookie:!0,enableRequestCachePragma:!1,conds:null,simpleCond:null,allowChunkedEncoding:!0,allowPartialContent:!1,enableIfNoneMatch:!1,enableIfModifiedSince:!1,isReverse:this.vIsReverse,methods:[],expiresTime:{isPrior:!1,isOn:!1,overwrite:!0,autoCalculate:!0,duration:{count:-1,unit:"hour"}}}:e).key&&(e.key=""),null==e.methods&&(e.methods=[]),null==e.life&&(e.life={count:2,unit:"hour"}),null==e.maxSize&&(e.maxSize={count:32,unit:"mb"}),null==e.minSize&&(e.minSize={count:0,unit:"kb"});var t=window.REQUEST_COND_COMPONENTS.$find(function(e,t){return"url-extension"==t.type});return{ref:e,moreOptionsVisible:!1,condCategory:"simple",condType:"url-extension",condComponent:t,condIsCaseInsensitive:null==e.simpleCond||e.simpleCond.isCaseInsensitive,components:window.REQUEST_COND_COMPONENTS}},methods:{changeOptionsVisible:function(e){this.moreOptionsVisible=e},changeLife:function(e){this.ref.life=e},changeMaxSize:function(e){this.ref.maxSize=e},changeMinSize:function(e){this.ref.minSize=e},changeConds:function(e){this.ref.conds=e,this.ref.simpleCond=null},changeStatusList:function(e){let t=[];e.forEach(function(e){e=parseInt(e);isNaN(e)||e<100||999<e||t.push(e)}),this.ref.status=t},changeMethods:function(e){this.ref.methods=e.map(function(e){return e.toUpperCase()})},changeKey:function(e){this.$refs.variablesDescriber.update(e)},changeExpiresTime:function(e){this.ref.expiresTime=e},changeCondCategory:function(t){this.condCategory=t;let i=window.parent.document.querySelector("*[role='dialog']");switch(t){case"simple":i.style.width="40em";break;case"complex":let e=window.parent.innerWidth;1024<e&&(e=1024),i.style.width=e+"px"}},changeCondType:function(i,e){e||null==this.ref.simpleCond||(this.ref.simpleCond.value=null);e=this.components.$find(function(e,t){return t.type==i});null!=e&&(this.condComponent=e)}},template:`<tbody>
|
||||
</div>`}),Vue.component("http-cache-ref-box",{props:["v-cache-ref","v-is-reverse"],mounted:function(){this.$refs.variablesDescriber.update(this.ref.key),null!=this.ref.simpleCond?(this.condType=this.ref.simpleCond.type,this.changeCondType(this.ref.simpleCond.type,!0),this.condCategory="simple"):null!=this.ref.conds&&null!=this.ref.conds.groups&&(this.condCategory="complex"),this.changeCondCategory(this.condCategory)},data:function(){let e=this.vCacheRef;null==(e=null==e?{isOn:!0,cachePolicyId:0,key:"${scheme}://${host}${requestPath}${isArgs}${args}",life:{count:2,unit:"hour"},status:[200],maxSize:{count:32,unit:"mb"},minSize:{count:0,unit:"kb"},skipCacheControlValues:["private","no-cache","no-store"],skipSetCookie:!0,enableRequestCachePragma:!1,conds:null,simpleCond:null,allowChunkedEncoding:!0,allowPartialContent:!0,enableIfNoneMatch:!1,enableIfModifiedSince:!1,isReverse:this.vIsReverse,methods:[],expiresTime:{isPrior:!1,isOn:!1,overwrite:!0,autoCalculate:!0,duration:{count:-1,unit:"hour"}}}:e).key&&(e.key=""),null==e.methods&&(e.methods=[]),null==e.life&&(e.life={count:2,unit:"hour"}),null==e.maxSize&&(e.maxSize={count:32,unit:"mb"}),null==e.minSize&&(e.minSize={count:0,unit:"kb"});var t=window.REQUEST_COND_COMPONENTS.$find(function(e,t){return"url-extension"==t.type});return{ref:e,keyIgnoreArgs:"string"==typeof e.key&&e.key.indexOf("${args}")<0,moreOptionsVisible:!1,condCategory:"simple",condType:"url-extension",condComponent:t,condIsCaseInsensitive:null==e.simpleCond||e.simpleCond.isCaseInsensitive,components:window.REQUEST_COND_COMPONENTS}},watch:{keyIgnoreArgs:function(e){"string"==typeof this.ref.key&&(e?this.ref.key=this.ref.key.replace("${isArgs}${args}",""):(this.ref.key.indexOf("${isArgs}")<0&&(this.ref.key=this.ref.key+"${isArgs}"),this.ref.key.indexOf("${args}")<0&&(this.ref.key=this.ref.key+"${args}")))}},methods:{changeOptionsVisible:function(e){this.moreOptionsVisible=e},changeLife:function(e){this.ref.life=e},changeMaxSize:function(e){this.ref.maxSize=e},changeMinSize:function(e){this.ref.minSize=e},changeConds:function(e){this.ref.conds=e,this.ref.simpleCond=null},changeStatusList:function(e){let t=[];e.forEach(function(e){e=parseInt(e);isNaN(e)||e<100||999<e||t.push(e)}),this.ref.status=t},changeMethods:function(e){this.ref.methods=e.map(function(e){return e.toUpperCase()})},changeKey:function(e){this.$refs.variablesDescriber.update(e)},changeExpiresTime:function(e){this.ref.expiresTime=e},changeCondCategory:function(t){this.condCategory=t;let i=window.parent.document.querySelector("*[role='dialog']");if(null!=i)switch(t){case"simple":i.style.width="40em";break;case"complex":let e=window.parent.innerWidth;1024<e&&(e=1024),i.style.width=e+"px"}},changeCondType:function(i,e){e||null==this.ref.simpleCond||(this.ref.simpleCond.value=null);e=this.components.$find(function(e,t){return t.type==i});null!=e&&(this.condComponent=e)}},template:`<tbody>
|
||||
<tr v-if="condCategory == 'simple'">
|
||||
<td class="title color-border">条件类型 *</td>
|
||||
<td>
|
||||
@@ -1455,12 +1466,19 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-show="!vIsReverse">
|
||||
<td>缓存Key *</td>
|
||||
<td class="color-border">缓存Key *</td>
|
||||
<td>
|
||||
<input type="text" v-model="ref.key" @input="changeKey(ref.key)"/>
|
||||
<p class="comment">用来区分不同缓存内容的唯一Key。<request-variables-describer ref="variablesDescriber"></request-variables-describer>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-show="!vIsReverse">
|
||||
<td class="color-border">忽略URI参数</td>
|
||||
<td>
|
||||
<checkbox v-model="keyIgnoreArgs"></checkbox>
|
||||
<p class="comment">选中后,表示缓存Key中不包含URI参数(即问号(?))后面的内容。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-show="!vIsReverse">
|
||||
<td colspan="2"><more-options-indicator @change="changeOptionsVisible"></more-options-indicator></td>
|
||||
</tr>
|
||||
@@ -1502,7 +1520,7 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<td>支持缓存区间内容</td>
|
||||
<td>
|
||||
<checkbox name="allowPartialContent" value="1" v-model="ref.allowPartialContent"></checkbox>
|
||||
<p class="comment">选中后,支持缓存源站返回的某个区间的内容,该内容通过<code-label>206 Partial Content</code-label>状态码返回。此功能目前为<code-label>试验性质</code-label>。</p>
|
||||
<p class="comment">选中后,支持缓存源站返回的某个区间的内容,该内容通过<code-label>206 Partial Content</code-label>状态码返回。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-show="moreOptionsVisible && !vIsReverse">
|
||||
@@ -1779,7 +1797,7 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<http-cache-refs-config-box :v-cache-config="cacheConfig" :v-cache-refs="cacheConfig.cacheRefs" :v-web-id="vWebId"></http-cache-refs-config-box>
|
||||
</div>
|
||||
<div class="margin"></div>
|
||||
</div>`});let defaultGeneralHeaders=["Cache-Control","Connection","Date","Pragma","Trailer","Transfer-Encoding","Upgrade","Via","Warning"];function sortTable(n){let e=document.createElement("script");e.setAttribute("src","/js/sortable.min.js"),e.addEventListener("load",function(){let s=document.querySelector("#sortable-table");null!=s&&Sortable.create(s,{draggable:"tbody",handle:".icon.handle",onStart:function(){},onUpdate:function(e){let t=s.querySelectorAll("tbody"),i=[];t.forEach(function(e){i.push(parseInt(e.getAttribute("v-id")))}),n(i)}})}),document.head.appendChild(e)}function sortLoad(e){let t=document.createElement("script");t.setAttribute("src","/js/sortable.min.js"),t.addEventListener("load",function(){"function"==typeof e&&e()}),document.head.appendChild(t)}function emitClick(e,arguments){let t=["click"];for(let e=0;e<arguments.length;e++)t.push(arguments[e]);e.$emit.apply(e,t)}Vue.component("http-cond-general-header-length",{props:["v-checkpoint"],data:function(){let e=null,t=null;var i;null!=window.parent.UPDATING_RULE&&(null!=(i=window.parent.UPDATING_RULE.checkpointOptions).headers&&Array.$isArray(i.headers)&&(e=i.headers),null!=i.length&&(t=i.length)),null==e&&(e=defaultGeneralHeaders),null==t&&(t=128);let s=this;return setTimeout(function(){s.change()},100),{headers:e,length:t}},watch:{length:function(e){let t=parseInt(e);(t=isNaN(t)?0:t)<0&&(t=0),this.length=t,this.change()}},methods:{change:function(){this.vCheckpoint.options=[{code:"headers",value:this.headers},{code:"length",value:this.length}]}},template:`<div>
|
||||
</div>`});let defaultGeneralHeaders=["Cache-Control","Connection","Date","Pragma","Trailer","Transfer-Encoding","Upgrade","Via","Warning"];Vue.component("http-cond-general-header-length",{props:["v-checkpoint"],data:function(){let e=null,t=null;var i;null!=window.parent.UPDATING_RULE&&(null!=(i=window.parent.UPDATING_RULE.checkpointOptions).headers&&Array.$isArray(i.headers)&&(e=i.headers),null!=i.length&&(t=i.length)),null==e&&(e=defaultGeneralHeaders),null==t&&(t=128);let s=this;return setTimeout(function(){s.change()},100),{headers:e,length:t}},watch:{length:function(e){let t=parseInt(e);(t=isNaN(t)?0:t)<0&&(t=0),this.length=t,this.change()}},methods:{change:function(){this.vCheckpoint.options=[{code:"headers",value:this.headers},{code:"length",value:this.length}]}},template:`<div>
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td class="title">通用Header列表</td>
|
||||
@@ -1883,11 +1901,12 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<tbody v-for="(cacheRef, index) in refs" :key="cacheRef.id" :v-id="cacheRef.id">
|
||||
<tr>
|
||||
<td style="text-align: center;"><i class="icon bars handle grey"></i> </td>
|
||||
<td :class="{'color-border': cacheRef.conds.connector == 'and', disabled: !cacheRef.isOn}" :style="{'border-left':cacheRef.isReverse ? '1px #db2828 solid' : ''}">
|
||||
<td :class="{'color-border': cacheRef.conds != null && cacheRef.conds.connector == 'and', disabled: !cacheRef.isOn}" :style="{'border-left':cacheRef.isReverse ? '1px #db2828 solid' : ''}">
|
||||
<http-request-conds-view :v-conds="cacheRef.conds" ref="cacheRef" :class="{disabled: !cacheRef.isOn}" v-if="cacheRef.conds != null && cacheRef.conds.groups != null"></http-request-conds-view>
|
||||
<http-request-cond-view :v-cond="cacheRef.simpleCond" ref="cacheRef" v-if="cacheRef.simpleCond != null"></http-request-cond-view>
|
||||
|
||||
<!-- 特殊参数 -->
|
||||
<grey-label v-if="cacheRef.key != null && cacheRef.key.indexOf('\${args}') < 0">忽略URI参数</grey-label>
|
||||
<grey-label v-if="cacheRef.minSize != null && cacheRef.minSize.count > 0">
|
||||
{{cacheRef.minSize.count}}{{cacheRef.minSize.unit}}
|
||||
<span v-if="cacheRef.maxSize != null && cacheRef.maxSize.count > 0">- {{cacheRef.maxSize.count}}{{cacheRef.maxSize.unit}}</span>
|
||||
@@ -1901,8 +1920,11 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<grey-label v-if="cacheRef.enableIfModifiedSince">If-Modified-Since</grey-label>
|
||||
</td>
|
||||
<td :class="{disabled: !cacheRef.isOn}">
|
||||
<span v-if="cacheRef.conds.connector == 'and'">和</span>
|
||||
<span v-if="cacheRef.conds.connector == 'or'">或</span>
|
||||
<span v-if="cacheRef.conds != null">
|
||||
<span v-if="cacheRef.conds.connector == 'and'">和</span>
|
||||
<span v-if="cacheRef.conds.connector == 'or'">或</span>
|
||||
</span>
|
||||
<span v-else>或</span>
|
||||
</td>
|
||||
<td :class="{disabled: !cacheRef.isOn}">
|
||||
<span v-if="!cacheRef.isReverse">{{cacheRef.life.count}} {{timeUnitName(cacheRef.life.unit)}}</span>
|
||||
@@ -2148,8 +2170,8 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
已启用 / <span>空连接次数:{{config.minAttempts}}次/分钟</span> / 封禁时间:{{config.timeoutSeconds}}秒 <span v-if="config.ignoreLocal">/ 忽略局域网访问</span>
|
||||
</span>
|
||||
<span v-else>未启用</span>
|
||||
</div>`}),Vue.component("domains-box",{props:["v-domains"],data:function(){let e=this.vDomains;return{domains:e=null==e?[]:e,isAdding:!1,addingDomain:""}},methods:{add:function(){this.isAdding=!0;let e=this;setTimeout(function(){e.$refs.addingDomain.focus()},100)},confirm:function(){let t=this;if(this.addingDomain=this.addingDomain.replace(/\s/g,""),0==this.addingDomain.length)teaweb.warn("请输入要添加的域名",function(){t.$refs.addingDomain.focus()});else{if("~"==this.addingDomain[0]){var e=this.addingDomain.substring(1);try{new RegExp(e)}catch(e){return void teaweb.warn("正则表达式错误:"+e.message,function(){t.$refs.addingDomain.focus()})}}this.domains.push(this.addingDomain),this.cancel()}},remove:function(e){this.domains.$remove(e)},cancel:function(){this.isAdding=!1,this.addingDomain=""}},template:`<div>
|
||||
<input type="hidden" name="domainsJSON" :value="JSON.stringify(domains)"/>
|
||||
</div>`}),Vue.component("domains-box",{props:["v-domains","name"],data:function(){let e=this.vDomains,t=(null==e&&(e=[]),"domainsJSON");return null!=this.name&&"string"==typeof this.name&&(t=this.name),{domains:e,isAdding:!1,addingDomain:"",realName:t}},methods:{add:function(){this.isAdding=!0;let e=this;setTimeout(function(){e.$refs.addingDomain.focus()},100)},confirm:function(){let t=this;if(this.addingDomain=this.addingDomain.replace(/\s/g,""),0==this.addingDomain.length)teaweb.warn("请输入要添加的域名",function(){t.$refs.addingDomain.focus()});else{if("~"==this.addingDomain[0]){var e=this.addingDomain.substring(1);try{new RegExp(e)}catch(e){return void teaweb.warn("正则表达式错误:"+e.message,function(){t.$refs.addingDomain.focus()})}}this.domains.push(this.addingDomain),this.cancel()}},remove:function(e){this.domains.$remove(e)},cancel:function(){this.isAdding=!1,this.addingDomain=""}},template:`<div>
|
||||
<input type="hidden" :name="realName" :value="JSON.stringify(domains)"/>
|
||||
<div v-if="domains.length > 0">
|
||||
<span class="ui label small basic" v-for="(domain, index) in domains">
|
||||
<span v-if="domain.length > 0 && domain[0] == '~'" class="grey" style="font-style: normal">[正则]</span>
|
||||
@@ -2176,6 +2198,47 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<div style="margin-top: 0.5em" v-if="!isAdding">
|
||||
<button class="ui button tiny" type="button" @click.prevent="add">+</button>
|
||||
</div>
|
||||
</div>`}),Vue.component("http-referers-config-box",{props:["v-referers-config","v-is-location","v-is-group"],data:function(){let e=this.vReferersConfig;return null==(e=null==e?{isPrior:!1,isOn:!1,allowEmpty:!0,allowSameDomain:!0,allowDomains:[]}:e).allowDomains&&(e.allowDomains=[]),{config:e}},methods:{isOn:function(){return(!this.vIsLocation&&!this.vIsGroup||this.config.isPrior)&&this.config.isOn},changeAllowDomains:function(e){}},template:`<div>
|
||||
<input type="hidden" name="referersJSON" :value="JSON.stringify(config)"/>
|
||||
<table class="ui table selectable definition">
|
||||
<prior-checkbox :v-config="config" v-if="vIsLocation || vIsGroup"></prior-checkbox>
|
||||
<tbody v-show="(!vIsLocation && !vIsGroup) || config.isPrior">
|
||||
<tr>
|
||||
<td class="title">启用防盗链</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" value="1" v-model="config.isOn"/>
|
||||
<label></label>
|
||||
</div>
|
||||
<p class="comment">选中后表示开启防盗链。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody v-show="isOn()">
|
||||
<tr>
|
||||
<td class="title">允许直接访问网站</td>
|
||||
<td>
|
||||
<checkbox v-model="config.allowEmpty"></checkbox>
|
||||
<p class="comment">允许用户直接访问网站,用户第一次访问网站时来源域名通常为空。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>来源域名允许一致</td>
|
||||
<td>
|
||||
<checkbox v-model="config.allowSameDomain"></checkbox>
|
||||
<p class="comment">允许来源域名和当前访问的域名一致,相当于在站内访问。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>允许的来源域名</td>
|
||||
<td>
|
||||
<values-box :values="config.allowDomains" @change="changeAllowDomains"></values-box>
|
||||
<p class="comment">允许的其他来源域名列表,比如<code-label>example.com</code-label>、<code-label>*.example.com</code-label>。单个星号<code-label>*</code-label>表示允许所有域名。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="ui margin"></div>
|
||||
</div>`}),Vue.component("http-redirect-to-https-box",{props:["v-redirect-to-https-config","v-is-location"],data:function(){let e=this.vRedirectToHttpsConfig;return null==e?e={isPrior:!1,isOn:!1,host:"",port:0,status:0,onlyDomains:[],exceptDomains:[]}:(null==e.onlyDomains&&(e.onlyDomains=[]),null==e.exceptDomains&&(e.exceptDomains=[])),{redirectToHttpsConfig:e,portString:0<e.port?e.port.toString():"",moreOptionsVisible:!1,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:{"redirectToHttpsConfig.status":function(){this.redirectToHttpsConfig.status=parseInt(this.redirectToHttpsConfig.status)},portString:function(e){e=parseInt(e);isNaN(e)?this.redirectToHttpsConfig.port=0:this.redirectToHttpsConfig.port=e}},methods:{changeMoreOptions:function(e){this.moreOptionsVisible=e},changeOnlyDomains:function(e){this.redirectToHttpsConfig.onlyDomains=e,this.$forceUpdate()},changeExceptDomains:function(e){this.redirectToHttpsConfig.exceptDomains=e,this.$forceUpdate()}},template:`<div>
|
||||
<input type="hidden" name="redirectToHTTPSJSON" :value="JSON.stringify(redirectToHttpsConfig)"/>
|
||||
|
||||
@@ -2772,7 +2835,7 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
</tr>
|
||||
</table>
|
||||
<div class="margin"></div>
|
||||
</div>`}),Vue.component("http-cache-policy-selector",{props:["v-cache-policy"],mounted:function(){let t=this;Tea.action("/servers/components/cache/count").post().success(function(e){t.count=e.data.count})},data:function(){return{count:0,cachePolicy:this.vCachePolicy}},methods:{remove:function(){this.cachePolicy=null},select:function(){let t=this;teaweb.popup("/servers/components/cache/selectPopup",{callback:function(e){t.cachePolicy=e.data.cachePolicy}})},create:function(){let t=this;teaweb.popup("/servers/components/cache/createPopup",{height:"26em",callback:function(e){t.cachePolicy=e.data.cachePolicy}})}},template:`<div>
|
||||
</div>`}),Vue.component("http-cache-policy-selector",{props:["v-cache-policy"],mounted:function(){let t=this;Tea.action("/servers/components/cache/count").post().success(function(e){t.count=e.data.count})},data:function(){return{count:0,cachePolicy:this.vCachePolicy}},methods:{remove:function(){this.cachePolicy=null},select:function(){let t=this;teaweb.popup("/servers/components/cache/selectPopup",{width:"42em",height:"26em",callback:function(e){t.cachePolicy=e.data.cachePolicy}})},create:function(){let t=this;teaweb.popup("/servers/components/cache/createPopup",{height:"26em",callback:function(e){t.cachePolicy=e.data.cachePolicy}})}},template:`<div>
|
||||
<div v-if="cachePolicy != null" class="ui label basic">
|
||||
<input type="hidden" name="cachePolicyId" :value="cachePolicy.id"/>
|
||||
{{cachePolicy.name}} <a :href="'/servers/components/cache/policy?cachePolicyId=' + cachePolicy.id" target="_blank" title="修改"><i class="icon pencil small"></i></a> <a href="" @click.prevent="remove()" title="删除"><i class="icon remove small"></i></a>
|
||||
@@ -3050,7 +3113,7 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>`}),Vue.component("http-access-log-box",{props:["v-access-log","v-keyword","v-show-server-link"],data:function(){let e=this.vAccessLog;if(null!=e.header&&null!=e.header.Upgrade&&null!=e.header.Upgrade.values&&e.header.Upgrade.values.$contains("websocket")&&("http"==e.scheme?e.scheme="ws":"https"==e.scheme&&(e.scheme="wss")),null!=e.tags&&0<e.tags.length){let s={};e.tags=e.tags.$filter(function(e,t){var i=void 0===s[t];return s[t]=!0,i})}return{accessLog:e}},methods:{formatCost:function(e){if(null==e)return"0";let t=(1e3*e).toString(),i=t.split(".");return i.length<2?t:i[0]+"."+i[1].substring(0,3)},showLog:function(){let e=this;var t=this.accessLog.requestId;this.$parent.$children.forEach(function(e){null!=e.deselect&&e.deselect()}),this.select(),teaweb.popup("/servers/server/log/viewPopup?requestId="+t,{width:"50em",height:"28em",onClose:function(){e.deselect()}})},select:function(){this.$refs.box.parentNode.style.cssText="background: rgba(0, 0, 0, 0.1)"},deselect:function(){this.$refs.box.parentNode.style.cssText=""},mismatch:function(){teaweb.warn("当前访问没有匹配到任何网站服务")}},template:`<div style="word-break: break-all" :style="{'color': (accessLog.status >= 400) ? '#dc143c' : ''}" ref="box">
|
||||
</div>`}),Vue.component("http-access-log-box",{props:["v-access-log","v-keyword","v-show-server-link"],data:function(){let e=this.vAccessLog;if(null!=e.header&&null!=e.header.Upgrade&&null!=e.header.Upgrade.values&&e.header.Upgrade.values.$contains("websocket")&&("http"==e.scheme?e.scheme="ws":"https"==e.scheme&&(e.scheme="wss")),null!=e.tags&&0<e.tags.length){let s={};e.tags=e.tags.$filter(function(e,t){var i=void 0===s[t];return s[t]=!0,i})}var t;return e.unicodeHost="",null!=e.host&&e.host.startsWith("xn--")&&(t=e.host.indexOf(":"),e.unicodeHost=0<t?punycode.ToUnicode(e.host.substring(0,t)):punycode.ToUnicode(e.host)),{accessLog:e}},methods:{formatCost:function(e){if(null==e)return"0";let t=(1e3*e).toString(),i=t.split(".");return i.length<2?t:i[0]+"."+i[1].substring(0,3)},showLog:function(){let e=this;var t=this.accessLog.requestId;this.$parent.$children.forEach(function(e){null!=e.deselect&&e.deselect()}),this.select(),teaweb.popup("/servers/server/log/viewPopup?requestId="+t,{width:"50em",height:"28em",onClose:function(){e.deselect()}})},select:function(){this.$refs.box.parentNode.style.cssText="background: rgba(0, 0, 0, 0.1)"},deselect:function(){this.$refs.box.parentNode.style.cssText=""},mismatch:function(){teaweb.warn("当前访问没有匹配到任何网站服务")}},template:`<div style="word-break: break-all" :style="{'color': (accessLog.status >= 400) ? '#dc143c' : ''}" ref="box">
|
||||
<div>
|
||||
<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>
|
||||
|
||||
@@ -3058,8 +3121,19 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<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="accessLog.region != null && accessLog.region.length > 0" class="grey"><ip-box :v-ip="accessLog.remoteAddr">[{{accessLog.region}}]</ip-box></span> <ip-box><keyword :v-word="vKeyword">{{accessLog.remoteAddr}}</keyword></ip-box> [{{accessLog.timeLocal}}] <em>"<keyword :v-word="vKeyword">{{accessLog.requestMethod}}</keyword> {{accessLog.scheme}}://<keyword :v-word="vKeyword">{{accessLog.host}}</keyword><keyword :v-word="vKeyword">{{accessLog.requestURI}}</keyword> <a :href="accessLog.scheme + '://' + accessLog.host + accessLog.requestURI" target="_blank" title="新窗口打开" class="disabled"><i class="external icon tiny"></i> </a> {{accessLog.proto}}" </em> <keyword :v-word="vKeyword">{{accessLog.status}}</keyword> <code-label v-if="accessLog.attrs != null && (accessLog.attrs['cache.status'] == 'HIT' || accessLog.attrs['cache.status'] == 'STALE')">cache {{accessLog.attrs['cache.status'].toLowerCase()}}</code-label> <code-label v-if="accessLog.firewallActions != null && accessLog.firewallActions.length > 0">waf {{accessLog.firewallActions}}</code-label> <span v-if="accessLog.tags != null && accessLog.tags.length > 0">- <code-label v-for="tag in accessLog.tags" :key="tag">{{tag}}</code-label></span>
|
||||
<span v-if="accessLog.region != null && accessLog.region.length > 0" class="grey"><ip-box :v-ip="accessLog.remoteAddr">[{{accessLog.region}}]</ip-box></span>
|
||||
<ip-box><keyword :v-word="vKeyword">{{accessLog.remoteAddr}}</keyword></ip-box> [{{accessLog.timeLocal}}] <em>"<keyword :v-word="vKeyword">{{accessLog.requestMethod}}</keyword> {{accessLog.scheme}}://<keyword :v-word="vKeyword">{{accessLog.host}}</keyword><keyword :v-word="vKeyword">{{accessLog.requestURI}}</keyword> <a :href="accessLog.scheme + '://' + accessLog.host + accessLog.requestURI" target="_blank" title="新窗口打开" class="disabled"><i class="external icon tiny"></i> </a> {{accessLog.proto}}" </em> <keyword :v-word="vKeyword">{{accessLog.status}}</keyword>
|
||||
|
||||
<code-label v-if="accessLog.unicodeHost != null && accessLog.unicodeHost.length > 0">{{accessLog.unicodeHost}}</code-label>
|
||||
|
||||
<!-- attrs -->
|
||||
<code-label v-if="accessLog.attrs != null && (accessLog.attrs['cache.status'] == 'HIT' || accessLog.attrs['cache.status'] == 'STALE')">cache {{accessLog.attrs['cache.status'].toLowerCase()}}</code-label>
|
||||
<!-- waf -->
|
||||
<code-label v-if="accessLog.firewallActions != null && accessLog.firewallActions.length > 0">waf {{accessLog.firewallActions}}</code-label>
|
||||
|
||||
<!-- tags -->
|
||||
<span v-if="accessLog.tags != null && accessLog.tags.length > 0">- <code-label v-for="tag in accessLog.tags" :key="tag">{{tag}}</code-label>
|
||||
</span>
|
||||
<span v-if="accessLog.wafInfo != null">
|
||||
<a :href="(accessLog.wafInfo.policy.serverId == 0) ? '/servers/components/waf/group?firewallPolicyId=' + accessLog.firewallPolicyId + '&type=inbound&groupId=' + accessLog.firewallRuleGroupId+ '#set' + accessLog.firewallRuleSetId : '/servers/server/settings/waf/group?serverId=' + accessLog.serverId + '&firewallPolicyId=' + accessLog.firewallPolicyId + '&type=inbound&groupId=' + accessLog.firewallRuleGroupId + '#set' + accessLog.firewallRuleSetId" target="_blank">
|
||||
<code-label-plain>
|
||||
@@ -3075,7 +3149,7 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<span v-if="accessLog.requestTime != null"> - 耗时:{{formatCost(accessLog.requestTime)}} ms </span><span v-if="accessLog.humanTime != null && accessLog.humanTime.length > 0" class="grey small"> ({{accessLog.humanTime}})</span>
|
||||
<a href="" @click.prevent="showLog" title="查看详情"><i class="icon expand"></i></a>
|
||||
</div>
|
||||
</div>`}),Vue.component("http-firewall-block-options-viewer",{props:["v-block-options"],data:function(){return{options:this.vBlockOptions}},template:`<div>
|
||||
</div>`});var punycode=new function(){this.utf16={decode:function(e){for(var t,i,s=[],n=0,o=e.length;n<o;){if(55296==(63488&(t=e.charCodeAt(n++)))){if(i=e.charCodeAt(n++),55296!=(64512&t)||56320!=(64512&i))throw new RangeError("UTF-16(decode): Illegal UTF-16 sequence");t=((1023&t)<<10)+(1023&i)+65536}s.push(t)}return s},encode:function(e){for(var t,i=[],s=0,n=e.length;s<n;){if(55296==(63488&(t=e[s++])))throw new RangeError("UTF-16(encode): Illegal UTF-16 value");65535<t&&(t-=65536,i.push(String.fromCharCode(t>>>10&1023|55296)),t=56320|1023&t),i.push(String.fromCharCode(t))}return i.join("")}};var b=2147483647;function y(e,t){return e+22+75*(e<26)-((0!=t)<<5)}function x(e,t,i){var s;for(e=i?Math.floor(e/700):e>>1,e+=Math.floor(e/t),s=0;455<e;s+=36)e=Math.floor(e/35);return Math.floor(s+36*e/(e+38))}this.decode=function(e,t){var i,s,n,o,a,l,c,r,d=[],p=[],u=e.length,h=128,v=0,m=72,f=e.lastIndexOf("-");for(f<0&&(f=0),s=0;s<f;++s){if(t&&(p[d.length]=e.charCodeAt(s)-65<26),128<=e.charCodeAt(s))throw new RangeError("Illegal input >= 0x80");d.push(e.charCodeAt(s))}for(n=0<f?f+1:0;n<u;){for(o=v,a=1,l=36;;l+=36){if(u<=n)throw RangeError("punycode_bad_input(1)");if(36<=(r=(r=e.charCodeAt(n++))-48<10?r-22:r-65<26?r-65:r-97<26?r-97:36))throw RangeError("punycode_bad_input(2)");if(r>Math.floor((b-v)/a))throw RangeError("punycode_overflow(1)");if(v+=r*a,r<(r=l<=m?1:m+26<=l?26:l-m))break;if(a>Math.floor(b/(36-r)))throw RangeError("punycode_overflow(2)");a*=36-r}if(m=x(v-o,i=d.length+1,0===o),Math.floor(v/i)>b-h)throw RangeError("punycode_overflow(3)");h+=Math.floor(v/i),v%=i,t&&p.splice(v,0,e.charCodeAt(n-1)-65<26),d.splice(v,0,h),v++}if(t)for(v=0,c=d.length;v<c;v++)p[v]&&(d[v]=String.fromCharCode(d[v]).toUpperCase().charCodeAt(0));return this.utf16.encode(d)},this.encode=function(e,t){t&&(r=this.utf16.decode(e));var i,s,n,o,a,l,c,r,d=(e=this.utf16.decode(e.toLowerCase())).length;if(t)for(g=0;g<d;g++)r[g]=e[g]!=r[g];for(var p,u,h=[],v=128,m=0,f=72,g=0;g<d;++g)e[g]<128&&h.push(String.fromCharCode(r?(p=e[g],u=r[g],(p-=(p-97<26)<<5)+((!u&&p-65<26)<<5)):e[g]));for(i=s=h.length,0<s&&h.push("-");i<d;){for(n=b,g=0;g<d;++g)v<=(c=e[g])&&c<n&&(n=c);if(n-v>Math.floor((b-m)/(i+1)))throw RangeError("punycode_overflow (1)");for(m+=(n-v)*(i+1),v=n,g=0;g<d;++g){if((c=e[g])<v&&++m>b)return Error("punycode_overflow(2)");if(c==v){for(o=m,a=36;!(o<(l=a<=f?1:f+26<=a?26:a-f));a+=36)h.push(String.fromCharCode(y(l+(o-l)%(36-l),0))),o=Math.floor((o-l)/(36-l));h.push(String.fromCharCode(y(o,t&&r[g]?1:0))),f=x(m,i+1,i==s),m=0,++i}}++m,++v}return h.join("")},this.ToASCII=function(e){for(var t=e.split("."),i=[],s=0;s<t.length;++s){var n=t[s];i.push(n.match(/[^A-Za-z0-9-]/)?"xn--"+punycode.encode(n):n)}return i.join(".")},this.ToUnicode=function(e){for(var t=e.split("."),i=[],s=0;s<t.length;++s){var n=t[s];i.push(n.match(/^xn--/)?punycode.decode(n.slice(4)):n)}return i.join(".")}};function sortTable(n){let e=document.createElement("script");e.setAttribute("src","/js/sortable.min.js"),e.addEventListener("load",function(){let s=document.querySelector("#sortable-table");null!=s&&Sortable.create(s,{draggable:"tbody",handle:".icon.handle",onStart:function(){},onUpdate:function(e){let t=s.querySelectorAll("tbody"),i=[];t.forEach(function(e){i.push(parseInt(e.getAttribute("v-id")))}),n(i)}})}),document.head.appendChild(e)}function sortLoad(e){let t=document.createElement("script");t.setAttribute("src","/js/sortable.min.js"),t.addEventListener("load",function(){"function"==typeof e&&e()}),document.head.appendChild(t)}function emitClick(e,arguments){let t=["click"];for(let e=0;e<arguments.length;e++)t.push(arguments[e]);e.$emit.apply(e,t)}Vue.component("http-firewall-block-options-viewer",{props:["v-block-options"],data:function(){return{options:this.vBlockOptions}},template:`<div>
|
||||
<span v-if="options == null">默认设置</span>
|
||||
<div v-else>
|
||||
状态码:{{options.statusCode}} / 提示内容:<span v-if="options.body != null && options.body.length > 0">[{{options.body.length}}字符]</span><span v-else class="disabled">[无]</span> / 超时时间:{{options.timeout}}秒
|
||||
@@ -3220,7 +3294,7 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<http-location-labels-label v-if="location.web != null && configIsOn(location.web.root)" :href="url('/web')">文档根目录</http-location-labels-label>
|
||||
|
||||
<!-- 反向代理 -->
|
||||
<http-location-labels-label v-if="refIsOn(location.reverseProxyRef, location.reverseProxy)" :v-href="url('/reverseProxy')">反向代理</http-location-labels-label>
|
||||
<http-location-labels-label v-if="refIsOn(location.reverseProxyRef, location.reverseProxy)" :v-href="url('/reverseProxy')">源站</http-location-labels-label>
|
||||
|
||||
<!-- WAF -->
|
||||
<!-- TODO -->
|
||||
@@ -3352,7 +3426,7 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<prior-checkbox :v-config="reverseProxyRef" v-if="vIsLocation || vIsGroup"></prior-checkbox>
|
||||
<tbody v-show="(!vIsLocation && !vIsGroup) || reverseProxyRef.isPrior">
|
||||
<tr>
|
||||
<td class="title">启用反向代理</td>
|
||||
<td class="title">启用源站</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" v-model="reverseProxyRef.isOn"/>
|
||||
@@ -4562,7 +4636,7 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<tr>
|
||||
<td colspan="2"><a href="" @click.prevent="show()"><span v-if="!isVisible">更多选项</span><span v-if="isVisible">收起选项</span><i class="icon angle" :class="{down:!isVisible, up:isVisible}"></i></a></td>
|
||||
</tr>
|
||||
</tbody>`}),Vue.component("download-link",{props:["v-element","v-file","v-value"],created:function(){let e=this;setTimeout(function(){e.url=e.composeURL()},1e3)},data:function(){let e=this.vFile;return{file:e=null!=e&&0!=e.length?e:"unknown-file",url:this.composeURL()}},methods:{composeURL:function(){let e="";if(null!=this.vValue)e=this.vValue;else{var t=document.getElementById(this.vElement);if(null==t)return void teaweb.warn("找不到要下载的内容");null==(e=t.innerText)&&(e=t.textContent)}return Tea.url("/ui/download",{file:this.file,text:e})}},template:'<a :href="url" target="_blank" style="font-weight: normal"><slot></slot></a>'}),Vue.component("values-box",{props:["values","v-values","size","maxlength","name","placeholder"],data:function(){let e=this.values;return null==e&&(e=[]),{realValues:e=null!=this.vValues&&"object"==typeof this.vValues?this.vValues:e,isUpdating:!1,isAdding:!1,index:0,value:"",isEditing:!1}},methods:{create:function(){this.isAdding=!0;var e=this;setTimeout(function(){e.$refs.value.focus()},200)},update:function(e){this.cancel(),this.isUpdating=!0,this.index=e,this.value=this.realValues[e];var t=this;setTimeout(function(){t.$refs.value.focus()},200)},confirm:function(){0!=this.value.length&&(this.isUpdating?Vue.set(this.realValues,this.index,this.value):this.realValues.push(this.value),this.cancel(),this.$emit("change",this.realValues))},remove:function(e){this.realValues.$remove(e),this.$emit("change",this.realValues)},cancel:function(){this.isUpdating=!1,this.isAdding=!1,this.value=""},updateAll:function(e){this.realValues=e},addValue:function(e){this.realValues.push(e)},startEditing:function(){this.isEditing=!this.isEditing},allValues:function(){return this.realValues}},template:`<div>
|
||||
</tbody>`}),Vue.component("download-link",{props:["v-element","v-file","v-value"],created:function(){let e=this;setTimeout(function(){e.url=e.composeURL()},1e3)},data:function(){let e=this.vFile;return{file:e=null!=e&&0!=e.length?e:"unknown-file",url:this.composeURL()}},methods:{composeURL:function(){let e="";if(null!=this.vValue)e=this.vValue;else{var t=document.getElementById(this.vElement);if(null==t)return;null==(e=t.innerText)&&(e=t.textContent)}return Tea.url("/ui/download",{file:this.file,text:e})}},template:'<a :href="url" target="_blank" style="font-weight: normal"><slot></slot></a>'}),Vue.component("values-box",{props:["values","v-values","size","maxlength","name","placeholder"],data:function(){let e=this.values;return null==e&&(e=[]),{realValues:e=null!=this.vValues&&"object"==typeof this.vValues?this.vValues:e,isUpdating:!1,isAdding:!1,index:0,value:"",isEditing:!1}},methods:{create:function(){this.isAdding=!0;var e=this;setTimeout(function(){e.$refs.value.focus()},200)},update:function(e){this.cancel(),this.isUpdating=!0,this.index=e,this.value=this.realValues[e];var t=this;setTimeout(function(){t.$refs.value.focus()},200)},confirm:function(){0!=this.value.length&&(this.isUpdating?Vue.set(this.realValues,this.index,this.value):this.realValues.push(this.value),this.cancel(),this.$emit("change",this.realValues))},remove:function(e){this.realValues.$remove(e),this.$emit("change",this.realValues)},cancel:function(){this.isUpdating=!1,this.isAdding=!1,this.value=""},updateAll:function(e){this.realValues=e},addValue:function(e){this.realValues.push(e)},startEditing:function(){this.isEditing=!this.isEditing},allValues:function(){return this.realValues}},template:`<div>
|
||||
<div v-show="!isEditing && realValues.length > 0">
|
||||
<div class="ui label tiny basic" v-for="(value, index) in realValues" style="margin-top:0.4em;margin-bottom:0.4em">{{value}}</div>
|
||||
<a href="" @click.prevent="startEditing" style="font-size: 0.8em; margin-left: 0.2em">[修改]</a>
|
||||
@@ -4611,7 +4685,7 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<option value="10">[每页]</option><option value="10" selected="selected">10条</option><option value="20">20条</option><option value="30">30条</option><option value="40">40条</option><option value="50">50条</option><option value="60">60条</option><option value="70">70条</option><option value="80">80条</option><option value="90">90条</option><option value="100">100条</option>
|
||||
</select>`}),Vue.component("second-menu",{template:' \t\t<div class="second-menu"> \t\t\t<div class="ui menu text blue small">\t\t\t\t<slot></slot>\t\t\t</div> \t\t\t<div class="ui divider"></div> \t\t</div>'}),Vue.component("loading-message",{template:`<div class="ui message loading">
|
||||
<div class="ui active inline loader small"></div> <slot></slot>
|
||||
</div>`}),Vue.component("more-options-angle",{data:function(){return{isVisible:!1}},methods:{show:function(){this.isVisible=!this.isVisible,this.$emit("change",this.isVisible)}},template:'<a href="" @click.prevent="show()"><span v-if="!isVisible">更多选项</span><span v-if="isVisible">收起选项</span><i class="icon angle" :class="{down:!isVisible, up:isVisible}"></i></a>'}),Vue.component("inner-menu-item",{props:["href","active","code"],data:function(){var e,t=this.active;return void 0===t&&(e="",t=(e=void 0!==window.TEA.ACTION.data.firstMenuItem?window.TEA.ACTION.data.firstMenuItem:e)==this.code),{vHref:null==this.href?"":this.href,vActive:t}},template:'\t\t<a :href="vHref" class="item right" style="color:#4183c4" :class="{active:vActive}">[<slot></slot>]</a> \t\t'}),Vue.component("health-check-config-box",{props:["v-health-check-config"],data:function(){let t=this.vHealthCheckConfig,i="http",s="",n="/",o="";if(null==t){t={isOn:!1,url:"",interval:{count:60,unit:"second"},statusCodes:[200],timeout:{count:10,unit:"second"},countTries:3,tryDelay:{count:100,unit:"ms"},autoDown:!0,countUp:1,countDown:3,userAgent:"",onlyBasicRequest:!0,accessLogIsOn:!0};let e=this;setTimeout(function(){e.changeURL()},500)}else{try{let e=new URL(t.url);i=e.protocol.substring(0,e.protocol.length-1);var a=(o="%24%7Bhost%7D"==(o=e.host)?"${host}":o).indexOf(":");0<a&&(o=o.substring(0,a)),s=e.port,n=e.pathname,0<e.search.length&&(n+=e.search)}catch(e){}null==t.statusCodes&&(t.statusCodes=[200]),null==t.interval&&(t.interval={count:60,unit:"second"}),null==t.timeout&&(t.timeout={count:10,unit:"second"}),null==t.tryDelay&&(t.tryDelay={count:100,unit:"ms"}),(null==t.countUp||t.countUp<1)&&(t.countUp=1),(null==t.countDown||t.countDown<1)&&(t.countDown=3)}return{healthCheck:t,advancedVisible:!1,urlProtocol:i,urlHost:o,urlPort:s,urlRequestURI:n,urlIsEditing:0==t.url.length}},watch:{urlRequestURI:function(){0<this.urlRequestURI.length&&"/"!=this.urlRequestURI[0]&&(this.urlRequestURI="/"+this.urlRequestURI),this.changeURL()},urlPort:function(e){let t=parseInt(e);isNaN(t)?this.urlPort="":this.urlPort=t.toString(),this.changeURL()},urlProtocol:function(){this.changeURL()},urlHost:function(){this.changeURL()},"healthCheck.countTries":function(e){e=parseInt(e);isNaN(e)?this.healthCheck.countTries=0:this.healthCheck.countTries=e},"healthCheck.countUp":function(e){e=parseInt(e);isNaN(e)?this.healthCheck.countUp=0:this.healthCheck.countUp=e},"healthCheck.countDown":function(e){e=parseInt(e);isNaN(e)?this.healthCheck.countDown=0:this.healthCheck.countDown=e}},methods:{showAdvanced:function(){this.advancedVisible=!this.advancedVisible},changeURL:function(){let e=this.urlHost;0==e.length&&(e="${host}"),this.healthCheck.url=this.urlProtocol+"://"+e+(0<this.urlPort.length?":"+this.urlPort:"")+this.urlRequestURI},changeStatus:function(e){this.healthCheck.statusCodes=e.$map(function(e,t){t=parseInt(t);return isNaN(t)?0:t})},editURL:function(){this.urlIsEditing=!this.urlIsEditing}},template:`<div>
|
||||
</div>`}),Vue.component("more-options-angle",{data:function(){return{isVisible:!1}},methods:{show:function(){this.isVisible=!this.isVisible,this.$emit("change",this.isVisible)}},template:'<a href="" @click.prevent="show()"><span v-if="!isVisible">更多选项</span><span v-if="isVisible">收起选项</span><i class="icon angle" :class="{down:!isVisible, up:isVisible}"></i></a>'}),Vue.component("inner-menu-item",{props:["href","active","code"],data:function(){var e,t=this.active;return void 0===t&&(e="",t=(e=void 0!==window.TEA.ACTION.data.firstMenuItem?window.TEA.ACTION.data.firstMenuItem:e)==this.code),{vHref:null==this.href?"":this.href,vActive:t}},template:'\t\t<a :href="vHref" class="item right" style="color:#4183c4" :class="{active:vActive}">[<slot></slot>]</a> \t\t'}),Vue.component("health-check-config-box",{props:["v-health-check-config","v-check-domain-url"],data:function(){let t=this.vHealthCheckConfig,i="http",s="",n="/",o="";if(null==t){t={isOn:!1,url:"",interval:{count:60,unit:"second"},statusCodes:[200],timeout:{count:10,unit:"second"},countTries:3,tryDelay:{count:100,unit:"ms"},autoDown:!0,countUp:1,countDown:3,userAgent:"",onlyBasicRequest:!0,accessLogIsOn:!0};let e=this;setTimeout(function(){e.changeURL()},500)}else{try{let e=new URL(t.url);i=e.protocol.substring(0,e.protocol.length-1);var a=(o="%24%7Bhost%7D"==(o=e.host)?"${host}":o).indexOf(":");0<a&&(o=o.substring(0,a)),s=e.port,n=e.pathname,0<e.search.length&&(n+=e.search)}catch(e){}null==t.statusCodes&&(t.statusCodes=[200]),null==t.interval&&(t.interval={count:60,unit:"second"}),null==t.timeout&&(t.timeout={count:10,unit:"second"}),null==t.tryDelay&&(t.tryDelay={count:100,unit:"ms"}),(null==t.countUp||t.countUp<1)&&(t.countUp=1),(null==t.countDown||t.countDown<1)&&(t.countDown=3)}return{healthCheck:t,advancedVisible:!1,urlProtocol:i,urlHost:o,urlPort:s,urlRequestURI:n,urlIsEditing:0==t.url.length,hostErr:""}},watch:{urlRequestURI:function(){0<this.urlRequestURI.length&&"/"!=this.urlRequestURI[0]&&(this.urlRequestURI="/"+this.urlRequestURI),this.changeURL()},urlPort:function(e){let t=parseInt(e);isNaN(t)?this.urlPort="":this.urlPort=t.toString(),this.changeURL()},urlProtocol:function(){this.changeURL()},urlHost:function(){this.changeURL(),this.hostErr=""},"healthCheck.countTries":function(e){e=parseInt(e);isNaN(e)?this.healthCheck.countTries=0:this.healthCheck.countTries=e},"healthCheck.countUp":function(e){e=parseInt(e);isNaN(e)?this.healthCheck.countUp=0:this.healthCheck.countUp=e},"healthCheck.countDown":function(e){e=parseInt(e);isNaN(e)?this.healthCheck.countDown=0:this.healthCheck.countDown=e}},methods:{showAdvanced:function(){this.advancedVisible=!this.advancedVisible},changeURL:function(){let e=this.urlHost;0==e.length&&(e="${host}"),this.healthCheck.url=this.urlProtocol+"://"+e+(0<this.urlPort.length?":"+this.urlPort:"")+this.urlRequestURI},changeStatus:function(e){this.healthCheck.statusCodes=e.$map(function(e,t){t=parseInt(t);return isNaN(t)?0:t})},onChangeURLHost:function(){var e=this.vCheckDomainUrl;if(null!=e&&0!=e.length){let t=this;Tea.action(e).params({host:this.urlHost}).success(function(e){e.data.isOk?t.hostErr="":t.hostErr="在当前集群中找不到此域名,可能会影响健康检查结果。"}).post()}},editURL:function(){this.urlIsEditing=!this.urlIsEditing}},template:`<div>
|
||||
<input type="hidden" name="healthCheckJSON" :value="JSON.stringify(healthCheck)"/>
|
||||
<table class="ui table definition selectable">
|
||||
<tbody>
|
||||
@@ -4645,8 +4719,8 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<tr>
|
||||
<td>域名</td>
|
||||
<td>
|
||||
<input type="text" v-model="urlHost"/>
|
||||
<p class="comment">已经部署到当前集群的一个域名;如果为空则使用节点IP作为域名。<span class="red" v-if="urlProtocol == 'https' && urlHost.length == 0">如果协议是https,这里必须填写一个已经设置了SSL证书的域名。</span></p>
|
||||
<input type="text" v-model="urlHost" @change="onChangeURLHost"/>
|
||||
<p class="comment"><span v-if="hostErr.length > 0" class="red">{{hostErr}}</span>已经部署到当前集群的一个域名;如果为空则使用节点IP作为域名。<span class="red" v-if="urlProtocol == 'https' && urlHost.length == 0">如果协议是https,这里必须填写一个已经设置了SSL证书的域名。</span></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -4803,7 +4877,7 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<p class="comment">{{message}}<slot></slot></p>
|
||||
</div>`}),Vue.component("warning-message",{template:'<div class="ui icon message warning"><i class="icon warning circle"></i><div class="content"><slot></slot></div></div>'});let checkboxId=0,radioId=(Vue.component("checkbox",{props:["name","value","v-value","id","checked"],data:function(){checkboxId++;let e=this.id,t=(null==e&&(e="checkbox"+checkboxId),this.vValue),i=(null==t&&(t="1"),this.value);return null==i&&"checked"==this.checked&&(i=t),{elementId:e,elementValue:t,newValue:i}},methods:{change:function(){this.$emit("input",this.newValue)},check:function(){this.newValue=this.elementValue},uncheck:function(){this.newValue=""},isChecked:function(){return this.newValue==this.elementValue}},watch:{value:function(e){"boolean"==typeof e&&(this.newValue=e)}},template:`<div class="ui checkbox">
|
||||
<input type="checkbox" :name="name" :value="elementValue" :id="elementId" @change="change" v-model="newValue"/>
|
||||
<label :for="elementId" style="font-size: 0.85em!important;"><slot></slot></label>
|
||||
<label :for="elementId"><slot></slot></label>
|
||||
</div>`}),Vue.component("network-addresses-view",{props:["v-addresses"],template:`<div>
|
||||
<div class="ui label tiny basic" v-if="vAddresses != null" v-for="addr in vAddresses">
|
||||
{{addr.protocol}}://<span v-if="addr.host.length > 0">{{addr.host.quoteIP()}}</span><span v-else>*</span>:{{addr.portRange}}
|
||||
@@ -4817,7 +4891,7 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<div class="content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>`}),Vue.component("digit-input",{props:["value","maxlength","size","min","max","required","placeholder"],mounted:function(){let e=this;setTimeout(function(){e.check()})},data:function(){let e=this.maxlength,t=(null==e&&(e=20),this.size);return null==t&&(t=6),{realValue:this.value,realMaxLength:e,realSize:t,isValid:!0}},watch:{realValue:function(e){this.notifyChange()}},methods:{notifyChange:function(){let e=parseInt(this.realValue.toString(),10);isNaN(e)&&(e=0),this.check(),this.$emit("input",e)},check:function(){var e;null!=this.realValue&&(e=this.realValue.toString(),/^\d+$/.test(e)?(e=parseInt(e,10),isNaN(e)?this.isValid=!1:this.required?this.isValid=(null==this.min||this.min<=e)&&(null==this.max||this.max>=e):this.isValid=0==e||(null==this.min||this.min<=e)&&(null==this.max||this.max>=e)):this.isValid=!1)}},template:'<input type="text" v-model="realValue" :maxlength="realMaxLength" :size="realSize" :class="{error: !this.isValid}" :placeholder="placeholder"/>'}),Vue.component("keyword",{props:["v-word"],data:function(){let e=this.vWord;e=null==e?"":(e=(e=(e=(e=(e=(e=(e=(e=(e=e.replace(/\)/g,"\\)")).replace(/\(/g,"\\(")).replace(/\+/g,"\\+")).replace(/\^/g,"\\^")).replace(/\$/g,"\\$")).replace(/\?/,"\\?")).replace(/\*/,"\\*")).replace(/\[/,"\\[")).replace(/{/,"\\{")).replace(/\./,"\\.");let t=this.$slots.default[0].text;if(0<e.length){let i=this,s=[],n=0;t=t.replaceAll(new RegExp("("+e+")","ig"),function(e){n++;var e='<span style="border: 1px #ccc dashed; color: #ef4d58">'+i.encodeHTML(e)+"</span>",t="$TMP__KEY__"+n.toString()+"$";return s.push([t,e]),t}),t=this.encodeHTML(t),s.forEach(function(e){t=t.replace(e[0],e[1])})}else t=this.encodeHTML(t);return{word:e,text:t}},methods:{encodeHTML:function(e){return e=(e=(e=(e=e.replace(/&/g,"&")).replace(/</g,"<")).replace(/>/g,">")).replace(/"/g,""")}},template:'<span><span style="display: none"><slot></slot></span><span v-html="text"></span></span>'}),Vue.component("node-log-row",{props:["v-log","v-keyword"],data:function(){return{log:this.vLog,keyword:this.vKeyword}},template:`<div>
|
||||
</div>`}),Vue.component("digit-input",{props:["value","maxlength","size","min","max","required","placeholder"],mounted:function(){let e=this;setTimeout(function(){e.check()})},data:function(){let e=this.maxlength,t=(null==e&&(e=20),this.size);return null==t&&(t=6),{realValue:this.value,realMaxLength:e,realSize:t,isValid:!0}},watch:{realValue:function(e){this.notifyChange()}},methods:{notifyChange:function(){let e=parseInt(this.realValue.toString(),10);isNaN(e)&&(e=0),this.check(),this.$emit("input",e)},check:function(){var e;null!=this.realValue&&(e=this.realValue.toString(),/^\d+$/.test(e)?(e=parseInt(e,10),isNaN(e)?this.isValid=!1:this.required?this.isValid=(null==this.min||this.min<=e)&&(null==this.max||this.max>=e):this.isValid=0==e||(null==this.min||this.min<=e)&&(null==this.max||this.max>=e)):this.isValid=!1)}},template:'<input type="text" v-model="realValue" :maxlength="realMaxLength" :size="realSize" :class="{error: !this.isValid}" :placeholder="placeholder"/>'}),Vue.component("keyword",{props:["v-word"],data:function(){let e=this.vWord;e=null==e?"":(e=(e=(e=(e=(e=(e=(e=(e=(e=e.replace(/\)/g,"\\)")).replace(/\(/g,"\\(")).replace(/\+/g,"\\+")).replace(/\^/g,"\\^")).replace(/\$/g,"\\$")).replace(/\?/g,"\\?")).replace(/\*/g,"\\*")).replace(/\[/g,"\\[")).replace(/{/g,"\\{")).replace(/\./g,"\\.");let t=this.$slots.default[0].text;if(0<e.length){let i=this,s=[],n=0;t=t.replaceAll(new RegExp("("+e+")","ig"),function(e){n++;var e='<span style="border: 1px #ccc dashed; color: #ef4d58">'+i.encodeHTML(e)+"</span>",t="$TMP__KEY__"+n.toString()+"$";return s.push([t,e]),t}),t=this.encodeHTML(t),s.forEach(function(e){t=t.replace(e[0],e[1])})}else t=this.encodeHTML(t);return{word:e,text:t}},methods:{encodeHTML:function(e){return e=(e=(e=(e=e.replace(/&/g,"&")).replace(/</g,"<")).replace(/>/g,">")).replace(/"/g,""")}},template:'<span><span style="display: none"><slot></slot></span><span v-html="text"></span></span>'}),Vue.component("node-log-row",{props:["v-log","v-keyword"],data:function(){return{log:this.vLog,keyword:this.vKeyword}},template:`<div>
|
||||
<pre class="log-box" style="margin: 0; padding: 0"><span :class="{red:log.level == 'error', orange:log.level == 'warning', green: log.level == 'success'}"><span v-if="!log.isToday">[{{log.createdTime}}]</span><strong v-if="log.isToday">[{{log.createdTime}}]</strong><keyword :v-word="keyword">[{{log.tag}}]{{log.description}}</keyword></span> <span v-if="log.count > 1" class="ui label tiny" :class="{red:log.level == 'error', orange:log.level == 'warning'}">共{{log.count}}条</span> <span v-if="log.server != null && log.server.id > 0"><a :href="'/servers/server?serverId=' + log.server.id" class="ui label tiny basic">{{log.server.name}}</a></span></pre>
|
||||
</div>`}),Vue.component("provinces-selector",{props:["v-provinces"],data:function(){let e=this.vProvinces;var t=(e=null==e?[]:e).$map(function(e,t){return t.id});return{provinces:e,provinceIds:t}},methods:{add:function(){let e=this.provinceIds.map(function(e){return e.toString()}),t=this;teaweb.popup("/ui/selectProvincesPopup?provinceIds="+e.join(","),{width:"48em",height:"23em",callback:function(e){t.provinces=e.data.provinces,t.change()}})},remove:function(e){this.provinces.$remove(e),this.change()},change:function(){this.provinceIds=this.provinces.$map(function(e,t){return t.id})}},template:`<div>
|
||||
<input type="hidden" name="provinceIdsJSON" :value="JSON.stringify(provinceIds)"/>
|
||||
@@ -5201,11 +5275,11 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`}),Vue.component("dns-domain-selector",{props:["v-domain-id","v-domain-name"],data:function(){let e=this.vDomainId,t=(null==e&&(e=0),this.vDomainName);return null==t&&(t=""),{domainId:e,domainName:t}},methods:{select:function(){let t=this;teaweb.popup("/dns/domains/selectPopup",{callback:function(e){t.domainId=e.data.domainId,t.domainName=e.data.domainName,t.change()}})},remove:function(){this.domainId=0,this.domainName="",this.change()},update:function(){let t=this;teaweb.popup("/dns/domains/selectPopup?domainId="+this.domainId,{callback:function(e){t.domainId=e.data.domainId,t.domainName=e.data.domainName,t.change()}})},change:function(){this.$emit("change",{id:this.domainId,name:this.domainName})}},template:`<div>
|
||||
</div>`}),Vue.component("dns-domain-selector",{props:["v-domain-id","v-domain-name","v-provider-name"],data:function(){let e=this.vDomainId,t=(null==e&&(e=0),this.vDomainName),i=(null==t&&(t=""),this.vProviderName);return null==i&&(i=""),{domainId:e,domainName:t,providerName:i}},methods:{select:function(){let t=this;teaweb.popup("/dns/domains/selectPopup",{callback:function(e){t.domainId=e.data.domainId,t.domainName=e.data.domainName,t.providerName=e.data.providerName,t.change()}})},remove:function(){this.domainId=0,this.domainName="",this.change()},update:function(){let t=this;teaweb.popup("/dns/domains/selectPopup?domainId="+this.domainId,{callback:function(e){t.domainId=e.data.domainId,t.domainName=e.data.domainName,t.providerName=e.data.providerName,t.change()}})},change:function(){this.$emit("change",{id:this.domainId,name:this.domainName})}},template:`<div>
|
||||
<input type="hidden" name="dnsDomainId" :value="domainId"/>
|
||||
<div v-if="domainName.length > 0">
|
||||
<span class="ui label small basic">
|
||||
{{domainName}}
|
||||
<span v-if="providerName != null && providerName.length > 0">{{providerName}} » </span> {{domainName}}
|
||||
<a href="" @click.prevent="update"><i class="icon pencil small"></i></a>
|
||||
<a href="" @click.prevent="remove()"><i class="icon remove"></i></a>
|
||||
</span>
|
||||
|
||||
@@ -1508,10 +1508,17 @@ Vue.component("ns-access-log-ref-box", {
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>记录所有访问</td>
|
||||
<td>只记录失败查询</td>
|
||||
<td>
|
||||
<checkbox v-model="config.missingRecordsOnly"></checkbox>
|
||||
<p class="comment">选中后,表示只记录查询失败的日志。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>包含未添加的域名</td>
|
||||
<td>
|
||||
<checkbox name="logMissingDomains" value="1" v-model="config.logMissingDomains"></checkbox>
|
||||
<p class="comment">包括对没有在系统里创建的域名访问。</p>
|
||||
<p class="comment">选中后,表示日志中包含对没有在系统里创建的域名访问。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -2374,8 +2381,25 @@ Vue.component("ns-access-log-box", {
|
||||
props: ["v-access-log", "v-keyword"],
|
||||
data: function () {
|
||||
let accessLog = this.vAccessLog
|
||||
let isFailure = false
|
||||
|
||||
if (accessLog.isRecursive) {
|
||||
if (accessLog.recordValue == null || accessLog.recordValue.length == 0) {
|
||||
isFailure = true
|
||||
}
|
||||
} else {
|
||||
if (accessLog.recordType == "SOA" || accessLog.recordType == "NS") {
|
||||
if (accessLog.recordValue == null || accessLog.recordValue.length == 0) {
|
||||
isFailure = true
|
||||
}
|
||||
} else if (accessLog.nsRecordId == null || accessLog.nsRecordId == 0) {
|
||||
isFailure = true
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
accessLog: accessLog
|
||||
accessLog: accessLog,
|
||||
isFailure: isFailure
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -2404,7 +2428,7 @@ Vue.component("ns-access-log-box", {
|
||||
this.$refs.box.parentNode.style.cssText = ""
|
||||
}
|
||||
},
|
||||
template: `<div class="access-log-row" :style="{'color': (!accessLog.isRecursive && (accessLog.nsRecordId == null || accessLog.nsRecordId == 0) || (accessLog.isRecursive && accessLog.recordValue != null && accessLog.recordValue.length == 0)) ? '#dc143c' : ''}" ref="box">
|
||||
template: `<div class="access-log-row" :style="{'color': isFailure ? '#dc143c' : ''}" ref="box">
|
||||
<span v-if="accessLog.region != null && accessLog.region.length > 0" class="grey">[{{accessLog.region}}]</span> <keyword :v-word="vKeyword">{{accessLog.remoteAddr}}</keyword> [{{accessLog.timeLocal}}] [{{accessLog.networking}}] <em>{{accessLog.questionType}} <keyword :v-word="vKeyword">{{accessLog.questionName}}</keyword></em> ->
|
||||
|
||||
<span v-if="accessLog.recordType != null && accessLog.recordType.length > 0"><em>{{accessLog.recordType}} <keyword :v-word="vKeyword">{{accessLog.recordValue}}</keyword></em></span>
|
||||
@@ -2451,7 +2475,7 @@ Vue.component("ns-cluster-selector", {
|
||||
})
|
||||
|
||||
Vue.component("ns-cluster-combo-box", {
|
||||
props: ["v-cluster-id"],
|
||||
props: ["v-cluster-id", "name"],
|
||||
data: function () {
|
||||
let that = this
|
||||
Tea.action("/ns/clusters/options")
|
||||
@@ -2459,8 +2483,16 @@ Vue.component("ns-cluster-combo-box", {
|
||||
.success(function (resp) {
|
||||
that.clusters = resp.data.clusters
|
||||
})
|
||||
|
||||
|
||||
let inputName = "clusterId"
|
||||
if (this.name != null && this.name.length > 0) {
|
||||
inputName = this.name
|
||||
}
|
||||
|
||||
return {
|
||||
clusters: []
|
||||
clusters: [],
|
||||
inputName: inputName
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -2473,7 +2505,7 @@ Vue.component("ns-cluster-combo-box", {
|
||||
}
|
||||
},
|
||||
template: `<div v-if="clusters.length > 0" style="min-width: 10.4em">
|
||||
<combo-box title="集群" placeholder="集群名称" :v-items="clusters" name="clusterId" :v-value="vClusterId" @change="change"></combo-box>
|
||||
<combo-box title="集群" placeholder="集群名称" :v-items="clusters" :name="inputName" :v-value="vClusterId" @change="change"></combo-box>
|
||||
</div>`
|
||||
})
|
||||
|
||||
@@ -3731,11 +3763,12 @@ Vue.component("http-cache-refs-box", {
|
||||
<th class="width10">缓存时间</th>
|
||||
</tr>
|
||||
<tr v-for="(cacheRef, index) in refs">
|
||||
<td :class="{'color-border': cacheRef.conds.connector == 'and', disabled: !cacheRef.isOn}" :style="{'border-left':cacheRef.isReverse ? '1px #db2828 solid' : ''}">
|
||||
<td :class="{'color-border': cacheRef.conds != null && cacheRef.conds.connector == 'and', disabled: !cacheRef.isOn}" :style="{'border-left':cacheRef.isReverse ? '1px #db2828 solid' : ''}">
|
||||
<http-request-conds-view :v-conds="cacheRef.conds" :class="{disabled: !cacheRef.isOn}" v-if="cacheRef.conds != null && cacheRef.conds.groups != null"></http-request-conds-view>
|
||||
<http-request-cond-view :v-cond="cacheRef.simpleCond" v-if="cacheRef.simpleCond != null"></http-request-cond-view>
|
||||
|
||||
<!-- 特殊参数 -->
|
||||
<grey-label v-if="cacheRef.key != null && cacheRef.key.indexOf('\${args}') < 0">忽略URI参数</grey-label>
|
||||
<grey-label v-if="cacheRef.minSize != null && cacheRef.minSize.count > 0">
|
||||
{{cacheRef.minSize.count}}{{cacheRef.minSize.unit}}
|
||||
<span v-if="cacheRef.maxSize != null && cacheRef.maxSize.count > 0">- {{cacheRef.maxSize.count}}{{cacheRef.maxSize.unit}}</span>
|
||||
@@ -3749,8 +3782,11 @@ Vue.component("http-cache-refs-box", {
|
||||
<grey-label v-if="cacheRef.enableIfModifiedSince">If-Modified-Since</grey-label>
|
||||
</td>
|
||||
<td :class="{disabled: !cacheRef.isOn}">
|
||||
<span v-if="cacheRef.conds.connector == 'and'">和</span>
|
||||
<span v-if="cacheRef.conds.connector == 'or'">或</span>
|
||||
<span v-if="cacheRef.conds != null">
|
||||
<span v-if="cacheRef.conds.connector == 'and'">和</span>
|
||||
<span v-if="cacheRef.conds.connector == 'or'">或</span>
|
||||
</span>
|
||||
<span v-else>或</span>
|
||||
</td>
|
||||
<td :class="{disabled: !cacheRef.isOn}">
|
||||
<span v-if="!cacheRef.isReverse">{{cacheRef.life.count}} {{timeUnitName(cacheRef.life.unit)}}</span>
|
||||
@@ -4047,7 +4083,7 @@ Vue.component("http-cache-ref-box", {
|
||||
conds: null, // 复杂条件
|
||||
simpleCond: null, // 简单条件
|
||||
allowChunkedEncoding: true,
|
||||
allowPartialContent: false,
|
||||
allowPartialContent: true,
|
||||
enableIfNoneMatch: false,
|
||||
enableIfModifiedSince: false,
|
||||
isReverse: this.vIsReverse,
|
||||
@@ -4085,6 +4121,9 @@ Vue.component("http-cache-ref-box", {
|
||||
|
||||
return {
|
||||
ref: ref,
|
||||
|
||||
keyIgnoreArgs: typeof ref.key == "string" && ref.key.indexOf("${args}") < 0,
|
||||
|
||||
moreOptionsVisible: false,
|
||||
|
||||
condCategory: "simple", // 条件分类:simple|complex
|
||||
@@ -4095,6 +4134,23 @@ Vue.component("http-cache-ref-box", {
|
||||
components: window.REQUEST_COND_COMPONENTS
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
keyIgnoreArgs: function (b) {
|
||||
if (typeof this.ref.key != "string") {
|
||||
return
|
||||
}
|
||||
if (b) {
|
||||
this.ref.key = this.ref.key.replace("${isArgs}${args}", "")
|
||||
return;
|
||||
}
|
||||
if (this.ref.key.indexOf("${isArgs}") < 0) {
|
||||
this.ref.key = this.ref.key + "${isArgs}"
|
||||
}
|
||||
if (this.ref.key.indexOf("${args}") < 0) {
|
||||
this.ref.key = this.ref.key + "${args}"
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeOptionsVisible: function (v) {
|
||||
this.moreOptionsVisible = v
|
||||
@@ -4141,6 +4197,9 @@ Vue.component("http-cache-ref-box", {
|
||||
|
||||
// resize window
|
||||
let dialog = window.parent.document.querySelector("*[role='dialog']")
|
||||
if (dialog == null) {
|
||||
return
|
||||
}
|
||||
switch (condCategory) {
|
||||
case "simple":
|
||||
dialog.style.width = "40em"
|
||||
@@ -4212,12 +4271,19 @@ Vue.component("http-cache-ref-box", {
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-show="!vIsReverse">
|
||||
<td>缓存Key *</td>
|
||||
<td class="color-border">缓存Key *</td>
|
||||
<td>
|
||||
<input type="text" v-model="ref.key" @input="changeKey(ref.key)"/>
|
||||
<p class="comment">用来区分不同缓存内容的唯一Key。<request-variables-describer ref="variablesDescriber"></request-variables-describer>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-show="!vIsReverse">
|
||||
<td class="color-border">忽略URI参数</td>
|
||||
<td>
|
||||
<checkbox v-model="keyIgnoreArgs"></checkbox>
|
||||
<p class="comment">选中后,表示缓存Key中不包含URI参数(即问号(?))后面的内容。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-show="!vIsReverse">
|
||||
<td colspan="2"><more-options-indicator @change="changeOptionsVisible"></more-options-indicator></td>
|
||||
</tr>
|
||||
@@ -4259,7 +4325,7 @@ Vue.component("http-cache-ref-box", {
|
||||
<td>支持缓存区间内容</td>
|
||||
<td>
|
||||
<checkbox name="allowPartialContent" value="1" v-model="ref.allowPartialContent"></checkbox>
|
||||
<p class="comment">选中后,支持缓存源站返回的某个区间的内容,该内容通过<code-label>206 Partial Content</code-label>状态码返回。此功能目前为<code-label>试验性质</code-label>。</p>
|
||||
<p class="comment">选中后,支持缓存源站返回的某个区间的内容,该内容通过<code-label>206 Partial Content</code-label>状态码返回。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-show="moreOptionsVisible && !vIsReverse">
|
||||
@@ -5803,11 +5869,12 @@ Vue.component("http-cache-refs-config-box", {
|
||||
<tbody v-for="(cacheRef, index) in refs" :key="cacheRef.id" :v-id="cacheRef.id">
|
||||
<tr>
|
||||
<td style="text-align: center;"><i class="icon bars handle grey"></i> </td>
|
||||
<td :class="{'color-border': cacheRef.conds.connector == 'and', disabled: !cacheRef.isOn}" :style="{'border-left':cacheRef.isReverse ? '1px #db2828 solid' : ''}">
|
||||
<td :class="{'color-border': cacheRef.conds != null && cacheRef.conds.connector == 'and', disabled: !cacheRef.isOn}" :style="{'border-left':cacheRef.isReverse ? '1px #db2828 solid' : ''}">
|
||||
<http-request-conds-view :v-conds="cacheRef.conds" ref="cacheRef" :class="{disabled: !cacheRef.isOn}" v-if="cacheRef.conds != null && cacheRef.conds.groups != null"></http-request-conds-view>
|
||||
<http-request-cond-view :v-cond="cacheRef.simpleCond" ref="cacheRef" v-if="cacheRef.simpleCond != null"></http-request-cond-view>
|
||||
|
||||
<!-- 特殊参数 -->
|
||||
<grey-label v-if="cacheRef.key != null && cacheRef.key.indexOf('\${args}') < 0">忽略URI参数</grey-label>
|
||||
<grey-label v-if="cacheRef.minSize != null && cacheRef.minSize.count > 0">
|
||||
{{cacheRef.minSize.count}}{{cacheRef.minSize.unit}}
|
||||
<span v-if="cacheRef.maxSize != null && cacheRef.maxSize.count > 0">- {{cacheRef.maxSize.count}}{{cacheRef.maxSize.unit}}</span>
|
||||
@@ -5821,8 +5888,11 @@ Vue.component("http-cache-refs-config-box", {
|
||||
<grey-label v-if="cacheRef.enableIfModifiedSince">If-Modified-Since</grey-label>
|
||||
</td>
|
||||
<td :class="{disabled: !cacheRef.isOn}">
|
||||
<span v-if="cacheRef.conds.connector == 'and'">和</span>
|
||||
<span v-if="cacheRef.conds.connector == 'or'">或</span>
|
||||
<span v-if="cacheRef.conds != null">
|
||||
<span v-if="cacheRef.conds.connector == 'and'">和</span>
|
||||
<span v-if="cacheRef.conds.connector == 'or'">或</span>
|
||||
</span>
|
||||
<span v-else>或</span>
|
||||
</td>
|
||||
<td :class="{disabled: !cacheRef.isOn}">
|
||||
<span v-if="!cacheRef.isReverse">{{cacheRef.life.count}} {{timeUnitName(cacheRef.life.unit)}}</span>
|
||||
@@ -6454,16 +6524,22 @@ Vue.component("firewall-syn-flood-config-viewer", {
|
||||
|
||||
// 域名列表
|
||||
Vue.component("domains-box", {
|
||||
props: ["v-domains"],
|
||||
props: ["v-domains", "name"],
|
||||
data: function () {
|
||||
let domains = this.vDomains
|
||||
if (domains == null) {
|
||||
domains = []
|
||||
}
|
||||
|
||||
let realName = "domainsJSON"
|
||||
if (this.name != null && typeof this.name == "string") {
|
||||
realName = this.name
|
||||
}
|
||||
return {
|
||||
domains: domains,
|
||||
isAdding: false,
|
||||
addingDomain: ""
|
||||
addingDomain: "",
|
||||
realName: realName
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -6513,7 +6589,7 @@ Vue.component("domains-box", {
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<input type="hidden" name="domainsJSON" :value="JSON.stringify(domains)"/>
|
||||
<input type="hidden" :name="realName" :value="JSON.stringify(domains)"/>
|
||||
<div v-if="domains.length > 0">
|
||||
<span class="ui label small basic" v-for="(domain, index) in domains">
|
||||
<span v-if="domain.length > 0 && domain[0] == '~'" class="grey" style="font-style: normal">[正则]</span>
|
||||
@@ -6543,6 +6619,77 @@ Vue.component("domains-box", {
|
||||
</div>`
|
||||
})
|
||||
|
||||
Vue.component("http-referers-config-box", {
|
||||
props: ["v-referers-config", "v-is-location", "v-is-group"],
|
||||
data: function () {
|
||||
let config = this.vReferersConfig
|
||||
if (config == null) {
|
||||
config = {
|
||||
isPrior: false,
|
||||
isOn: false,
|
||||
allowEmpty: true,
|
||||
allowSameDomain: true,
|
||||
allowDomains: []
|
||||
}
|
||||
}
|
||||
if (config.allowDomains == null) {
|
||||
config.allowDomains = []
|
||||
}
|
||||
return {
|
||||
config: config
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isOn: function () {
|
||||
return ((!this.vIsLocation && !this.vIsGroup) || this.config.isPrior) && this.config.isOn
|
||||
},
|
||||
changeAllowDomains: function (domains) {
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<input type="hidden" name="referersJSON" :value="JSON.stringify(config)"/>
|
||||
<table class="ui table selectable definition">
|
||||
<prior-checkbox :v-config="config" v-if="vIsLocation || vIsGroup"></prior-checkbox>
|
||||
<tbody v-show="(!vIsLocation && !vIsGroup) || config.isPrior">
|
||||
<tr>
|
||||
<td class="title">启用防盗链</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" value="1" v-model="config.isOn"/>
|
||||
<label></label>
|
||||
</div>
|
||||
<p class="comment">选中后表示开启防盗链。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody v-show="isOn()">
|
||||
<tr>
|
||||
<td class="title">允许直接访问网站</td>
|
||||
<td>
|
||||
<checkbox v-model="config.allowEmpty"></checkbox>
|
||||
<p class="comment">允许用户直接访问网站,用户第一次访问网站时来源域名通常为空。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>来源域名允许一致</td>
|
||||
<td>
|
||||
<checkbox v-model="config.allowSameDomain"></checkbox>
|
||||
<p class="comment">允许来源域名和当前访问的域名一致,相当于在站内访问。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>允许的来源域名</td>
|
||||
<td>
|
||||
<values-box :values="config.allowDomains" @change="changeAllowDomains"></values-box>
|
||||
<p class="comment">允许的其他来源域名列表,比如<code-label>example.com</code-label>、<code-label>*.example.com</code-label>。单个星号<code-label>*</code-label>表示允许所有域名。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="ui margin"></div>
|
||||
</div>`
|
||||
})
|
||||
|
||||
Vue.component("http-redirect-to-https-box", {
|
||||
props: ["v-redirect-to-https-config", "v-is-location"],
|
||||
data: function () {
|
||||
@@ -8064,6 +8211,8 @@ Vue.component("http-cache-policy-selector", {
|
||||
select: function () {
|
||||
let that = this
|
||||
teaweb.popup("/servers/components/cache/selectPopup", {
|
||||
width: "42em",
|
||||
height: "26em",
|
||||
callback: function (resp) {
|
||||
that.cachePolicy = resp.data.cachePolicy
|
||||
}
|
||||
@@ -8737,6 +8886,18 @@ Vue.component("http-access-log-box", {
|
||||
})
|
||||
}
|
||||
|
||||
// 域名
|
||||
accessLog.unicodeHost = ""
|
||||
if (accessLog.host != null && accessLog.host.startsWith("xn--")) {
|
||||
// port
|
||||
let portIndex = accessLog.host.indexOf(":")
|
||||
if (portIndex > 0) {
|
||||
accessLog.unicodeHost = punycode.ToUnicode(accessLog.host.substring(0, portIndex))
|
||||
} else {
|
||||
accessLog.unicodeHost = punycode.ToUnicode(accessLog.host)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
accessLog: accessLog
|
||||
}
|
||||
@@ -8789,8 +8950,19 @@ Vue.component("http-access-log-box", {
|
||||
<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="accessLog.region != null && accessLog.region.length > 0" class="grey"><ip-box :v-ip="accessLog.remoteAddr">[{{accessLog.region}}]</ip-box></span> <ip-box><keyword :v-word="vKeyword">{{accessLog.remoteAddr}}</keyword></ip-box> [{{accessLog.timeLocal}}] <em>"<keyword :v-word="vKeyword">{{accessLog.requestMethod}}</keyword> {{accessLog.scheme}}://<keyword :v-word="vKeyword">{{accessLog.host}}</keyword><keyword :v-word="vKeyword">{{accessLog.requestURI}}</keyword> <a :href="accessLog.scheme + '://' + accessLog.host + accessLog.requestURI" target="_blank" title="新窗口打开" class="disabled"><i class="external icon tiny"></i> </a> {{accessLog.proto}}" </em> <keyword :v-word="vKeyword">{{accessLog.status}}</keyword> <code-label v-if="accessLog.attrs != null && (accessLog.attrs['cache.status'] == 'HIT' || accessLog.attrs['cache.status'] == 'STALE')">cache {{accessLog.attrs['cache.status'].toLowerCase()}}</code-label> <code-label v-if="accessLog.firewallActions != null && accessLog.firewallActions.length > 0">waf {{accessLog.firewallActions}}</code-label> <span v-if="accessLog.tags != null && accessLog.tags.length > 0">- <code-label v-for="tag in accessLog.tags" :key="tag">{{tag}}</code-label></span>
|
||||
<span v-if="accessLog.region != null && accessLog.region.length > 0" class="grey"><ip-box :v-ip="accessLog.remoteAddr">[{{accessLog.region}}]</ip-box></span>
|
||||
<ip-box><keyword :v-word="vKeyword">{{accessLog.remoteAddr}}</keyword></ip-box> [{{accessLog.timeLocal}}] <em>"<keyword :v-word="vKeyword">{{accessLog.requestMethod}}</keyword> {{accessLog.scheme}}://<keyword :v-word="vKeyword">{{accessLog.host}}</keyword><keyword :v-word="vKeyword">{{accessLog.requestURI}}</keyword> <a :href="accessLog.scheme + '://' + accessLog.host + accessLog.requestURI" target="_blank" title="新窗口打开" class="disabled"><i class="external icon tiny"></i> </a> {{accessLog.proto}}" </em> <keyword :v-word="vKeyword">{{accessLog.status}}</keyword>
|
||||
|
||||
<code-label v-if="accessLog.unicodeHost != null && accessLog.unicodeHost.length > 0">{{accessLog.unicodeHost}}</code-label>
|
||||
|
||||
<!-- attrs -->
|
||||
<code-label v-if="accessLog.attrs != null && (accessLog.attrs['cache.status'] == 'HIT' || accessLog.attrs['cache.status'] == 'STALE')">cache {{accessLog.attrs['cache.status'].toLowerCase()}}</code-label>
|
||||
<!-- waf -->
|
||||
<code-label v-if="accessLog.firewallActions != null && accessLog.firewallActions.length > 0">waf {{accessLog.firewallActions}}</code-label>
|
||||
|
||||
<!-- tags -->
|
||||
<span v-if="accessLog.tags != null && accessLog.tags.length > 0">- <code-label v-for="tag in accessLog.tags" :key="tag">{{tag}}</code-label>
|
||||
</span>
|
||||
<span v-if="accessLog.wafInfo != null">
|
||||
<a :href="(accessLog.wafInfo.policy.serverId == 0) ? '/servers/components/waf/group?firewallPolicyId=' + accessLog.firewallPolicyId + '&type=inbound&groupId=' + accessLog.firewallRuleGroupId+ '#set' + accessLog.firewallRuleSetId : '/servers/server/settings/waf/group?serverId=' + accessLog.serverId + '&firewallPolicyId=' + accessLog.firewallPolicyId + '&type=inbound&groupId=' + accessLog.firewallRuleGroupId + '#set' + accessLog.firewallRuleSetId" target="_blank">
|
||||
<code-label-plain>
|
||||
@@ -8809,6 +8981,333 @@ Vue.component("http-access-log-box", {
|
||||
</div>`
|
||||
})
|
||||
|
||||
// Javascript Punycode converter derived from example in RFC3492.
|
||||
// This implementation is created by some@domain.name and released into public domain
|
||||
// 代码来自:https://stackoverflow.com/questions/183485/converting-punycode-with-dash-character-to-unicode
|
||||
var punycode = new function Punycode() {
|
||||
// This object converts to and from puny-code used in IDN
|
||||
//
|
||||
// punycode.ToASCII ( domain )
|
||||
//
|
||||
// Returns a puny coded representation of "domain".
|
||||
// It only converts the part of the domain name that
|
||||
// has non ASCII characters. I.e. it dosent matter if
|
||||
// you call it with a domain that already is in ASCII.
|
||||
//
|
||||
// punycode.ToUnicode (domain)
|
||||
//
|
||||
// Converts a puny-coded domain name to unicode.
|
||||
// It only converts the puny-coded parts of the domain name.
|
||||
// I.e. it dosent matter if you call it on a string
|
||||
// that already has been converted to unicode.
|
||||
//
|
||||
//
|
||||
this.utf16 = {
|
||||
// The utf16-class is necessary to convert from javascripts internal character representation to unicode and back.
|
||||
decode: function (input) {
|
||||
var output = [], i = 0, len = input.length, value, extra;
|
||||
while (i < len) {
|
||||
value = input.charCodeAt(i++);
|
||||
if ((value & 0xF800) === 0xD800) {
|
||||
extra = input.charCodeAt(i++);
|
||||
if (((value & 0xFC00) !== 0xD800) || ((extra & 0xFC00) !== 0xDC00)) {
|
||||
throw new RangeError("UTF-16(decode): Illegal UTF-16 sequence");
|
||||
}
|
||||
value = ((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000;
|
||||
}
|
||||
output.push(value);
|
||||
}
|
||||
return output;
|
||||
},
|
||||
encode: function (input) {
|
||||
var output = [], i = 0, len = input.length, value;
|
||||
while (i < len) {
|
||||
value = input[i++];
|
||||
if ((value & 0xF800) === 0xD800) {
|
||||
throw new RangeError("UTF-16(encode): Illegal UTF-16 value");
|
||||
}
|
||||
if (value > 0xFFFF) {
|
||||
value -= 0x10000;
|
||||
output.push(String.fromCharCode(((value >>> 10) & 0x3FF) | 0xD800));
|
||||
value = 0xDC00 | (value & 0x3FF);
|
||||
}
|
||||
output.push(String.fromCharCode(value));
|
||||
}
|
||||
return output.join("");
|
||||
}
|
||||
}
|
||||
|
||||
//Default parameters
|
||||
var initial_n = 0x80;
|
||||
var initial_bias = 72;
|
||||
var delimiter = "\x2D";
|
||||
var base = 36;
|
||||
var damp = 700;
|
||||
var tmin = 1;
|
||||
var tmax = 26;
|
||||
var skew = 38;
|
||||
var maxint = 0x7FFFFFFF;
|
||||
|
||||
// decode_digit(cp) returns the numeric value of a basic code
|
||||
// point (for use in representing integers) in the range 0 to
|
||||
// base-1, or base if cp is does not represent a value.
|
||||
|
||||
function decode_digit(cp) {
|
||||
return cp - 48 < 10 ? cp - 22 : cp - 65 < 26 ? cp - 65 : cp - 97 < 26 ? cp - 97 : base;
|
||||
}
|
||||
|
||||
// encode_digit(d,flag) returns the basic code point whose value
|
||||
// (when used for representing integers) is d, which needs to be in
|
||||
// the range 0 to base-1. The lowercase form is used unless flag is
|
||||
// nonzero, in which case the uppercase form is used. The behavior
|
||||
// is undefined if flag is nonzero and digit d has no uppercase form.
|
||||
|
||||
function encode_digit(d, flag) {
|
||||
return d + 22 + 75 * (d < 26) - ((flag != 0) << 5);
|
||||
// 0..25 map to ASCII a..z or A..Z
|
||||
// 26..35 map to ASCII 0..9
|
||||
}
|
||||
|
||||
//** Bias adaptation function **
|
||||
function adapt(delta, numpoints, firsttime) {
|
||||
var k;
|
||||
delta = firsttime ? Math.floor(delta / damp) : (delta >> 1);
|
||||
delta += Math.floor(delta / numpoints);
|
||||
|
||||
for (k = 0; delta > (((base - tmin) * tmax) >> 1); k += base) {
|
||||
delta = Math.floor(delta / (base - tmin));
|
||||
}
|
||||
return Math.floor(k + (base - tmin + 1) * delta / (delta + skew));
|
||||
}
|
||||
|
||||
// encode_basic(bcp,flag) forces a basic code point to lowercase if flag is zero,
|
||||
// uppercase if flag is nonzero, and returns the resulting code point.
|
||||
// The code point is unchanged if it is caseless.
|
||||
// The behavior is undefined if bcp is not a basic code point.
|
||||
|
||||
function encode_basic(bcp, flag) {
|
||||
bcp -= (bcp - 97 < 26) << 5;
|
||||
return bcp + ((!flag && (bcp - 65 < 26)) << 5);
|
||||
}
|
||||
|
||||
// Main decode
|
||||
this.decode = function (input, preserveCase) {
|
||||
// Dont use utf16
|
||||
var output = [];
|
||||
var case_flags = [];
|
||||
var input_length = input.length;
|
||||
|
||||
var n, out, i, bias, basic, j, ic, oldi, w, k, digit, t, len;
|
||||
|
||||
// Initialize the state:
|
||||
|
||||
n = initial_n;
|
||||
i = 0;
|
||||
bias = initial_bias;
|
||||
|
||||
// Handle the basic code points: Let basic be the number of input code
|
||||
// points before the last delimiter, or 0 if there is none, then
|
||||
// copy the first basic code points to the output.
|
||||
|
||||
basic = input.lastIndexOf(delimiter);
|
||||
if (basic < 0) basic = 0;
|
||||
|
||||
for (j = 0; j < basic; ++j) {
|
||||
if (preserveCase) case_flags[output.length] = (input.charCodeAt(j) - 65 < 26);
|
||||
if (input.charCodeAt(j) >= 0x80) {
|
||||
throw new RangeError("Illegal input >= 0x80");
|
||||
}
|
||||
output.push(input.charCodeAt(j));
|
||||
}
|
||||
|
||||
// Main decoding loop: Start just after the last delimiter if any
|
||||
// basic code points were copied; start at the beginning otherwise.
|
||||
|
||||
for (ic = basic > 0 ? basic + 1 : 0; ic < input_length;) {
|
||||
|
||||
// ic is the index of the next character to be consumed,
|
||||
|
||||
// Decode a generalized variable-length integer into delta,
|
||||
// which gets added to i. The overflow checking is easier
|
||||
// if we increase i as we go, then subtract off its starting
|
||||
// value at the end to obtain delta.
|
||||
for (oldi = i, w = 1, k = base; ; k += base) {
|
||||
if (ic >= input_length) {
|
||||
throw RangeError("punycode_bad_input(1)");
|
||||
}
|
||||
digit = decode_digit(input.charCodeAt(ic++));
|
||||
|
||||
if (digit >= base) {
|
||||
throw RangeError("punycode_bad_input(2)");
|
||||
}
|
||||
if (digit > Math.floor((maxint - i) / w)) {
|
||||
throw RangeError("punycode_overflow(1)");
|
||||
}
|
||||
i += digit * w;
|
||||
t = k <= bias ? tmin : k >= bias + tmax ? tmax : k - bias;
|
||||
if (digit < t) {
|
||||
break;
|
||||
}
|
||||
if (w > Math.floor(maxint / (base - t))) {
|
||||
throw RangeError("punycode_overflow(2)");
|
||||
}
|
||||
w *= (base - t);
|
||||
}
|
||||
|
||||
out = output.length + 1;
|
||||
bias = adapt(i - oldi, out, oldi === 0);
|
||||
|
||||
// i was supposed to wrap around from out to 0,
|
||||
// incrementing n each time, so we'll fix that now:
|
||||
if (Math.floor(i / out) > maxint - n) {
|
||||
throw RangeError("punycode_overflow(3)");
|
||||
}
|
||||
n += Math.floor(i / out);
|
||||
i %= out;
|
||||
|
||||
// Insert n at position i of the output:
|
||||
// Case of last character determines uppercase flag:
|
||||
if (preserveCase) {
|
||||
case_flags.splice(i, 0, input.charCodeAt(ic - 1) - 65 < 26);
|
||||
}
|
||||
|
||||
output.splice(i, 0, n);
|
||||
i++;
|
||||
}
|
||||
if (preserveCase) {
|
||||
for (i = 0, len = output.length; i < len; i++) {
|
||||
if (case_flags[i]) {
|
||||
output[i] = (String.fromCharCode(output[i]).toUpperCase()).charCodeAt(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.utf16.encode(output);
|
||||
};
|
||||
|
||||
//** Main encode function **
|
||||
|
||||
this.encode = function (input, preserveCase) {
|
||||
//** Bias adaptation function **
|
||||
|
||||
var n, delta, h, b, bias, j, m, q, k, t, ijv, case_flags;
|
||||
|
||||
if (preserveCase) {
|
||||
// Preserve case, step1 of 2: Get a list of the unaltered string
|
||||
case_flags = this.utf16.decode(input);
|
||||
}
|
||||
// Converts the input in UTF-16 to Unicode
|
||||
input = this.utf16.decode(input.toLowerCase());
|
||||
|
||||
var input_length = input.length; // Cache the length
|
||||
|
||||
if (preserveCase) {
|
||||
// Preserve case, step2 of 2: Modify the list to true/false
|
||||
for (j = 0; j < input_length; j++) {
|
||||
case_flags[j] = input[j] != case_flags[j];
|
||||
}
|
||||
}
|
||||
|
||||
var output = [];
|
||||
|
||||
|
||||
// Initialize the state:
|
||||
n = initial_n;
|
||||
delta = 0;
|
||||
bias = initial_bias;
|
||||
|
||||
// Handle the basic code points:
|
||||
for (j = 0; j < input_length; ++j) {
|
||||
if (input[j] < 0x80) {
|
||||
output.push(
|
||||
String.fromCharCode(
|
||||
case_flags ? encode_basic(input[j], case_flags[j]) : input[j]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
h = b = output.length;
|
||||
|
||||
// h is the number of code points that have been handled, b is the
|
||||
// number of basic code points
|
||||
|
||||
if (b > 0) output.push(delimiter);
|
||||
|
||||
// Main encoding loop:
|
||||
//
|
||||
while (h < input_length) {
|
||||
// All non-basic code points < n have been
|
||||
// handled already. Find the next larger one:
|
||||
|
||||
for (m = maxint, j = 0; j < input_length; ++j) {
|
||||
ijv = input[j];
|
||||
if (ijv >= n && ijv < m) m = ijv;
|
||||
}
|
||||
|
||||
// Increase delta enough to advance the decoder's
|
||||
// <n,i> state to <m,0>, but guard against overflow:
|
||||
|
||||
if (m - n > Math.floor((maxint - delta) / (h + 1))) {
|
||||
throw RangeError("punycode_overflow (1)");
|
||||
}
|
||||
delta += (m - n) * (h + 1);
|
||||
n = m;
|
||||
|
||||
for (j = 0; j < input_length; ++j) {
|
||||
ijv = input[j];
|
||||
|
||||
if (ijv < n) {
|
||||
if (++delta > maxint) return Error("punycode_overflow(2)");
|
||||
}
|
||||
|
||||
if (ijv == n) {
|
||||
// Represent delta as a generalized variable-length integer:
|
||||
for (q = delta, k = base; ; k += base) {
|
||||
t = k <= bias ? tmin : k >= bias + tmax ? tmax : k - bias;
|
||||
if (q < t) break;
|
||||
output.push(String.fromCharCode(encode_digit(t + (q - t) % (base - t), 0)));
|
||||
q = Math.floor((q - t) / (base - t));
|
||||
}
|
||||
output.push(String.fromCharCode(encode_digit(q, preserveCase && case_flags[j] ? 1 : 0)));
|
||||
bias = adapt(delta, h + 1, h == b);
|
||||
delta = 0;
|
||||
++h;
|
||||
}
|
||||
}
|
||||
|
||||
++delta, ++n;
|
||||
}
|
||||
return output.join("");
|
||||
}
|
||||
|
||||
this.ToASCII = function (domain) {
|
||||
var domain_array = domain.split(".");
|
||||
var out = [];
|
||||
for (var i = 0; i < domain_array.length; ++i) {
|
||||
var s = domain_array[i];
|
||||
out.push(
|
||||
s.match(/[^A-Za-z0-9-]/) ?
|
||||
"xn--" + punycode.encode(s) :
|
||||
s
|
||||
);
|
||||
}
|
||||
return out.join(".");
|
||||
}
|
||||
this.ToUnicode = function (domain) {
|
||||
var domain_array = domain.split(".");
|
||||
var out = [];
|
||||
for (var i = 0; i < domain_array.length; ++i) {
|
||||
var s = domain_array[i];
|
||||
out.push(
|
||||
s.match(/^xn--/) ?
|
||||
punycode.decode(s.slice(4)) :
|
||||
s
|
||||
);
|
||||
}
|
||||
return out.join(".");
|
||||
}
|
||||
}();
|
||||
|
||||
Vue.component("http-firewall-block-options-viewer", {
|
||||
props: ["v-block-options"],
|
||||
data: function () {
|
||||
@@ -9132,7 +9631,7 @@ Vue.component("http-location-labels", {
|
||||
<http-location-labels-label v-if="location.web != null && configIsOn(location.web.root)" :href="url('/web')">文档根目录</http-location-labels-label>
|
||||
|
||||
<!-- 反向代理 -->
|
||||
<http-location-labels-label v-if="refIsOn(location.reverseProxyRef, location.reverseProxy)" :v-href="url('/reverseProxy')">反向代理</http-location-labels-label>
|
||||
<http-location-labels-label v-if="refIsOn(location.reverseProxyRef, location.reverseProxy)" :v-href="url('/reverseProxy')">源站</http-location-labels-label>
|
||||
|
||||
<!-- WAF -->
|
||||
<!-- TODO -->
|
||||
@@ -9579,7 +10078,7 @@ Vue.component("reverse-proxy-box", {
|
||||
<prior-checkbox :v-config="reverseProxyRef" v-if="vIsLocation || vIsGroup"></prior-checkbox>
|
||||
<tbody v-show="(!vIsLocation && !vIsGroup) || reverseProxyRef.isPrior">
|
||||
<tr>
|
||||
<td class="title">启用反向代理</td>
|
||||
<td class="title">启用源站</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" v-model="reverseProxyRef.isOn"/>
|
||||
@@ -13229,7 +13728,7 @@ Vue.component("download-link", {
|
||||
} else {
|
||||
let e = document.getElementById(this.vElement)
|
||||
if (e == null) {
|
||||
teaweb.warn("找不到要下载的内容")
|
||||
// 不提示错误,因为此时可能页面未加载完整
|
||||
return
|
||||
}
|
||||
text = e.innerText
|
||||
@@ -13735,7 +14234,7 @@ Vue.component("inner-menu-item", {
|
||||
});
|
||||
|
||||
Vue.component("health-check-config-box", {
|
||||
props: ["v-health-check-config"],
|
||||
props: ["v-health-check-config", "v-check-domain-url"],
|
||||
data: function () {
|
||||
let healthCheckConfig = this.vHealthCheckConfig
|
||||
let urlProtocol = "http"
|
||||
@@ -13812,7 +14311,9 @@ Vue.component("health-check-config-box", {
|
||||
urlHost: urlHost,
|
||||
urlPort: urlPort,
|
||||
urlRequestURI: urlRequestURI,
|
||||
urlIsEditing: healthCheckConfig.url.length == 0
|
||||
urlIsEditing: healthCheckConfig.url.length == 0,
|
||||
|
||||
hostErr: ""
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -13836,6 +14337,7 @@ Vue.component("health-check-config-box", {
|
||||
},
|
||||
urlHost: function () {
|
||||
this.changeURL()
|
||||
this.hostErr = ""
|
||||
},
|
||||
"healthCheck.countTries": function (v) {
|
||||
let count = parseInt(v)
|
||||
@@ -13883,6 +14385,24 @@ Vue.component("health-check-config-box", {
|
||||
}
|
||||
})
|
||||
},
|
||||
onChangeURLHost: function () {
|
||||
let checkDomainURL = this.vCheckDomainUrl
|
||||
if (checkDomainURL == null || checkDomainURL.length == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
let that = this
|
||||
Tea.action(checkDomainURL)
|
||||
.params({host: this.urlHost})
|
||||
.success(function (resp) {
|
||||
if (!resp.data.isOk) {
|
||||
that.hostErr = "在当前集群中找不到此域名,可能会影响健康检查结果。"
|
||||
} else {
|
||||
that.hostErr = ""
|
||||
}
|
||||
})
|
||||
.post()
|
||||
},
|
||||
editURL: function () {
|
||||
this.urlIsEditing = !this.urlIsEditing
|
||||
}
|
||||
@@ -13921,8 +14441,8 @@ Vue.component("health-check-config-box", {
|
||||
<tr>
|
||||
<td>域名</td>
|
||||
<td>
|
||||
<input type="text" v-model="urlHost"/>
|
||||
<p class="comment">已经部署到当前集群的一个域名;如果为空则使用节点IP作为域名。<span class="red" v-if="urlProtocol == 'https' && urlHost.length == 0">如果协议是https,这里必须填写一个已经设置了SSL证书的域名。</span></p>
|
||||
<input type="text" v-model="urlHost" @change="onChangeURLHost"/>
|
||||
<p class="comment"><span v-if="hostErr.length > 0" class="red">{{hostErr}}</span>已经部署到当前集群的一个域名;如果为空则使用节点IP作为域名。<span class="red" v-if="urlProtocol == 'https' && urlHost.length == 0">如果协议是https,这里必须填写一个已经设置了SSL证书的域名。</span></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -14470,7 +14990,7 @@ Vue.component("checkbox", {
|
||||
},
|
||||
template: `<div class="ui checkbox">
|
||||
<input type="checkbox" :name="name" :value="elementValue" :id="elementId" @change="change" v-model="newValue"/>
|
||||
<label :for="elementId" style="font-size: 0.85em!important;"><slot></slot></label>
|
||||
<label :for="elementId"><slot></slot></label>
|
||||
</div>`
|
||||
})
|
||||
|
||||
@@ -14605,11 +15125,11 @@ Vue.component("keyword", {
|
||||
word = word.replace(/\+/g, "\\+")
|
||||
word = word.replace(/\^/g, "\\^")
|
||||
word = word.replace(/\$/g, "\\$")
|
||||
word = word.replace(/\?/, "\\?")
|
||||
word = word.replace(/\*/, "\\*")
|
||||
word = word.replace(/\[/, "\\[")
|
||||
word = word.replace(/{/, "\\{")
|
||||
word = word.replace(/\./, "\\.")
|
||||
word = word.replace(/\?/g, "\\?")
|
||||
word = word.replace(/\*/g, "\\*")
|
||||
word = word.replace(/\[/g, "\\[")
|
||||
word = word.replace(/{/g, "\\{")
|
||||
word = word.replace(/\./g, "\\.")
|
||||
}
|
||||
|
||||
let slot = this.$slots["default"][0]
|
||||
@@ -16326,7 +16846,7 @@ Vue.component("dns-route-selector", {
|
||||
})
|
||||
|
||||
Vue.component("dns-domain-selector", {
|
||||
props: ["v-domain-id", "v-domain-name"],
|
||||
props: ["v-domain-id", "v-domain-name", "v-provider-name"],
|
||||
data: function () {
|
||||
let domainId = this.vDomainId
|
||||
if (domainId == null) {
|
||||
@@ -16336,9 +16856,16 @@ Vue.component("dns-domain-selector", {
|
||||
if (domainName == null) {
|
||||
domainName = ""
|
||||
}
|
||||
|
||||
let providerName = this.vProviderName
|
||||
if (providerName == null) {
|
||||
providerName = ""
|
||||
}
|
||||
|
||||
return {
|
||||
domainId: domainId,
|
||||
domainName: domainName
|
||||
domainName: domainName,
|
||||
providerName: providerName
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -16348,6 +16875,7 @@ Vue.component("dns-domain-selector", {
|
||||
callback: function (resp) {
|
||||
that.domainId = resp.data.domainId
|
||||
that.domainName = resp.data.domainName
|
||||
that.providerName = resp.data.providerName
|
||||
that.change()
|
||||
}
|
||||
})
|
||||
@@ -16363,6 +16891,7 @@ Vue.component("dns-domain-selector", {
|
||||
callback: function (resp) {
|
||||
that.domainId = resp.data.domainId
|
||||
that.domainName = resp.data.domainName
|
||||
that.providerName = resp.data.providerName
|
||||
that.change()
|
||||
}
|
||||
})
|
||||
@@ -16378,7 +16907,7 @@ Vue.component("dns-domain-selector", {
|
||||
<input type="hidden" name="dnsDomainId" :value="domainId"/>
|
||||
<div v-if="domainName.length > 0">
|
||||
<span class="ui label small basic">
|
||||
{{domainName}}
|
||||
<span v-if="providerName != null && providerName.length > 0">{{providerName}} » </span> {{domainName}}
|
||||
<a href="" @click.prevent="update"><i class="icon pencil small"></i></a>
|
||||
<a href="" @click.prevent="remove()"><i class="icon remove"></i></a>
|
||||
</span>
|
||||
|
||||
@@ -47,6 +47,6 @@ Vue.component("checkbox", {
|
||||
},
|
||||
template: `<div class="ui checkbox">
|
||||
<input type="checkbox" :name="name" :value="elementValue" :id="elementId" @change="change" v-model="newValue"/>
|
||||
<label :for="elementId" style="font-size: 0.85em!important;"><slot></slot></label>
|
||||
<label :for="elementId"><slot></slot></label>
|
||||
</div>`
|
||||
})
|
||||
@@ -24,7 +24,7 @@ Vue.component("download-link", {
|
||||
} else {
|
||||
let e = document.getElementById(this.vElement)
|
||||
if (e == null) {
|
||||
teaweb.warn("找不到要下载的内容")
|
||||
// 不提示错误,因为此时可能页面未加载完整
|
||||
return
|
||||
}
|
||||
text = e.innerText
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Vue.component("health-check-config-box", {
|
||||
props: ["v-health-check-config"],
|
||||
props: ["v-health-check-config", "v-check-domain-url"],
|
||||
data: function () {
|
||||
let healthCheckConfig = this.vHealthCheckConfig
|
||||
let urlProtocol = "http"
|
||||
@@ -76,7 +76,9 @@ Vue.component("health-check-config-box", {
|
||||
urlHost: urlHost,
|
||||
urlPort: urlPort,
|
||||
urlRequestURI: urlRequestURI,
|
||||
urlIsEditing: healthCheckConfig.url.length == 0
|
||||
urlIsEditing: healthCheckConfig.url.length == 0,
|
||||
|
||||
hostErr: ""
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -100,6 +102,7 @@ Vue.component("health-check-config-box", {
|
||||
},
|
||||
urlHost: function () {
|
||||
this.changeURL()
|
||||
this.hostErr = ""
|
||||
},
|
||||
"healthCheck.countTries": function (v) {
|
||||
let count = parseInt(v)
|
||||
@@ -147,6 +150,24 @@ Vue.component("health-check-config-box", {
|
||||
}
|
||||
})
|
||||
},
|
||||
onChangeURLHost: function () {
|
||||
let checkDomainURL = this.vCheckDomainUrl
|
||||
if (checkDomainURL == null || checkDomainURL.length == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
let that = this
|
||||
Tea.action(checkDomainURL)
|
||||
.params({host: this.urlHost})
|
||||
.success(function (resp) {
|
||||
if (!resp.data.isOk) {
|
||||
that.hostErr = "在当前集群中找不到此域名,可能会影响健康检查结果。"
|
||||
} else {
|
||||
that.hostErr = ""
|
||||
}
|
||||
})
|
||||
.post()
|
||||
},
|
||||
editURL: function () {
|
||||
this.urlIsEditing = !this.urlIsEditing
|
||||
}
|
||||
@@ -185,8 +206,8 @@ Vue.component("health-check-config-box", {
|
||||
<tr>
|
||||
<td>域名</td>
|
||||
<td>
|
||||
<input type="text" v-model="urlHost"/>
|
||||
<p class="comment">已经部署到当前集群的一个域名;如果为空则使用节点IP作为域名。<span class="red" v-if="urlProtocol == 'https' && urlHost.length == 0">如果协议是https,这里必须填写一个已经设置了SSL证书的域名。</span></p>
|
||||
<input type="text" v-model="urlHost" @change="onChangeURLHost"/>
|
||||
<p class="comment"><span v-if="hostErr.length > 0" class="red">{{hostErr}}</span>已经部署到当前集群的一个域名;如果为空则使用节点IP作为域名。<span class="red" v-if="urlProtocol == 'https' && urlHost.length == 0">如果协议是https,这里必须填写一个已经设置了SSL证书的域名。</span></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -10,11 +10,11 @@ Vue.component("keyword", {
|
||||
word = word.replace(/\+/g, "\\+")
|
||||
word = word.replace(/\^/g, "\\^")
|
||||
word = word.replace(/\$/g, "\\$")
|
||||
word = word.replace(/\?/, "\\?")
|
||||
word = word.replace(/\*/, "\\*")
|
||||
word = word.replace(/\[/, "\\[")
|
||||
word = word.replace(/{/, "\\{")
|
||||
word = word.replace(/\./, "\\.")
|
||||
word = word.replace(/\?/g, "\\?")
|
||||
word = word.replace(/\*/g, "\\*")
|
||||
word = word.replace(/\[/g, "\\[")
|
||||
word = word.replace(/{/g, "\\{")
|
||||
word = word.replace(/\./g, "\\.")
|
||||
}
|
||||
|
||||
let slot = this.$slots["default"][0]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Vue.component("dns-domain-selector", {
|
||||
props: ["v-domain-id", "v-domain-name"],
|
||||
props: ["v-domain-id", "v-domain-name", "v-provider-name"],
|
||||
data: function () {
|
||||
let domainId = this.vDomainId
|
||||
if (domainId == null) {
|
||||
@@ -9,9 +9,16 @@ Vue.component("dns-domain-selector", {
|
||||
if (domainName == null) {
|
||||
domainName = ""
|
||||
}
|
||||
|
||||
let providerName = this.vProviderName
|
||||
if (providerName == null) {
|
||||
providerName = ""
|
||||
}
|
||||
|
||||
return {
|
||||
domainId: domainId,
|
||||
domainName: domainName
|
||||
domainName: domainName,
|
||||
providerName: providerName
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -21,6 +28,7 @@ Vue.component("dns-domain-selector", {
|
||||
callback: function (resp) {
|
||||
that.domainId = resp.data.domainId
|
||||
that.domainName = resp.data.domainName
|
||||
that.providerName = resp.data.providerName
|
||||
that.change()
|
||||
}
|
||||
})
|
||||
@@ -36,6 +44,7 @@ Vue.component("dns-domain-selector", {
|
||||
callback: function (resp) {
|
||||
that.domainId = resp.data.domainId
|
||||
that.domainName = resp.data.domainName
|
||||
that.providerName = resp.data.providerName
|
||||
that.change()
|
||||
}
|
||||
})
|
||||
@@ -51,7 +60,7 @@ Vue.component("dns-domain-selector", {
|
||||
<input type="hidden" name="dnsDomainId" :value="domainId"/>
|
||||
<div v-if="domainName.length > 0">
|
||||
<span class="ui label small basic">
|
||||
{{domainName}}
|
||||
<span v-if="providerName != null && providerName.length > 0">{{providerName}} » </span> {{domainName}}
|
||||
<a href="" @click.prevent="update"><i class="icon pencil small"></i></a>
|
||||
<a href="" @click.prevent="remove()"><i class="icon remove"></i></a>
|
||||
</span>
|
||||
|
||||
@@ -2,8 +2,25 @@ Vue.component("ns-access-log-box", {
|
||||
props: ["v-access-log", "v-keyword"],
|
||||
data: function () {
|
||||
let accessLog = this.vAccessLog
|
||||
let isFailure = false
|
||||
|
||||
if (accessLog.isRecursive) {
|
||||
if (accessLog.recordValue == null || accessLog.recordValue.length == 0) {
|
||||
isFailure = true
|
||||
}
|
||||
} else {
|
||||
if (accessLog.recordType == "SOA" || accessLog.recordType == "NS") {
|
||||
if (accessLog.recordValue == null || accessLog.recordValue.length == 0) {
|
||||
isFailure = true
|
||||
}
|
||||
} else if (accessLog.nsRecordId == null || accessLog.nsRecordId == 0) {
|
||||
isFailure = true
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
accessLog: accessLog
|
||||
accessLog: accessLog,
|
||||
isFailure: isFailure
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -32,7 +49,7 @@ Vue.component("ns-access-log-box", {
|
||||
this.$refs.box.parentNode.style.cssText = ""
|
||||
}
|
||||
},
|
||||
template: `<div class="access-log-row" :style="{'color': (!accessLog.isRecursive && (accessLog.nsRecordId == null || accessLog.nsRecordId == 0) || (accessLog.isRecursive && accessLog.recordValue != null && accessLog.recordValue.length == 0)) ? '#dc143c' : ''}" ref="box">
|
||||
template: `<div class="access-log-row" :style="{'color': isFailure ? '#dc143c' : ''}" ref="box">
|
||||
<span v-if="accessLog.region != null && accessLog.region.length > 0" class="grey">[{{accessLog.region}}]</span> <keyword :v-word="vKeyword">{{accessLog.remoteAddr}}</keyword> [{{accessLog.timeLocal}}] [{{accessLog.networking}}] <em>{{accessLog.questionType}} <keyword :v-word="vKeyword">{{accessLog.questionName}}</keyword></em> ->
|
||||
|
||||
<span v-if="accessLog.recordType != null && accessLog.recordType.length > 0"><em>{{accessLog.recordType}} <keyword :v-word="vKeyword">{{accessLog.recordValue}}</keyword></em></span>
|
||||
|
||||
@@ -28,10 +28,17 @@ Vue.component("ns-access-log-ref-box", {
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>记录所有访问</td>
|
||||
<td>只记录失败查询</td>
|
||||
<td>
|
||||
<checkbox v-model="config.missingRecordsOnly"></checkbox>
|
||||
<p class="comment">选中后,表示只记录查询失败的日志。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>包含未添加的域名</td>
|
||||
<td>
|
||||
<checkbox name="logMissingDomains" value="1" v-model="config.logMissingDomains"></checkbox>
|
||||
<p class="comment">包括对没有在系统里创建的域名访问。</p>
|
||||
<p class="comment">选中后,表示日志中包含对没有在系统里创建的域名访问。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Vue.component("ns-cluster-combo-box", {
|
||||
props: ["v-cluster-id"],
|
||||
props: ["v-cluster-id", "name"],
|
||||
data: function () {
|
||||
let that = this
|
||||
Tea.action("/ns/clusters/options")
|
||||
@@ -7,8 +7,16 @@ Vue.component("ns-cluster-combo-box", {
|
||||
.success(function (resp) {
|
||||
that.clusters = resp.data.clusters
|
||||
})
|
||||
|
||||
|
||||
let inputName = "clusterId"
|
||||
if (this.name != null && this.name.length > 0) {
|
||||
inputName = this.name
|
||||
}
|
||||
|
||||
return {
|
||||
clusters: []
|
||||
clusters: [],
|
||||
inputName: inputName
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -21,6 +29,6 @@ Vue.component("ns-cluster-combo-box", {
|
||||
}
|
||||
},
|
||||
template: `<div v-if="clusters.length > 0" style="min-width: 10.4em">
|
||||
<combo-box title="集群" placeholder="集群名称" :v-items="clusters" name="clusterId" :v-value="vClusterId" @change="change"></combo-box>
|
||||
<combo-box title="集群" placeholder="集群名称" :v-items="clusters" :name="inputName" :v-value="vClusterId" @change="change"></combo-box>
|
||||
</div>`
|
||||
})
|
||||
@@ -1,15 +1,21 @@
|
||||
// 域名列表
|
||||
Vue.component("domains-box", {
|
||||
props: ["v-domains"],
|
||||
props: ["v-domains", "name"],
|
||||
data: function () {
|
||||
let domains = this.vDomains
|
||||
if (domains == null) {
|
||||
domains = []
|
||||
}
|
||||
|
||||
let realName = "domainsJSON"
|
||||
if (this.name != null && typeof this.name == "string") {
|
||||
realName = this.name
|
||||
}
|
||||
return {
|
||||
domains: domains,
|
||||
isAdding: false,
|
||||
addingDomain: ""
|
||||
addingDomain: "",
|
||||
realName: realName
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -59,7 +65,7 @@ Vue.component("domains-box", {
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<input type="hidden" name="domainsJSON" :value="JSON.stringify(domains)"/>
|
||||
<input type="hidden" :name="realName" :value="JSON.stringify(domains)"/>
|
||||
<div v-if="domains.length > 0">
|
||||
<span class="ui label small basic" v-for="(domain, index) in domains">
|
||||
<span v-if="domain.length > 0 && domain[0] == '~'" class="grey" style="font-style: normal">[正则]</span>
|
||||
|
||||
@@ -20,6 +20,18 @@ Vue.component("http-access-log-box", {
|
||||
})
|
||||
}
|
||||
|
||||
// 域名
|
||||
accessLog.unicodeHost = ""
|
||||
if (accessLog.host != null && accessLog.host.startsWith("xn--")) {
|
||||
// port
|
||||
let portIndex = accessLog.host.indexOf(":")
|
||||
if (portIndex > 0) {
|
||||
accessLog.unicodeHost = punycode.ToUnicode(accessLog.host.substring(0, portIndex))
|
||||
} else {
|
||||
accessLog.unicodeHost = punycode.ToUnicode(accessLog.host)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
accessLog: accessLog
|
||||
}
|
||||
@@ -72,8 +84,19 @@ Vue.component("http-access-log-box", {
|
||||
<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="accessLog.region != null && accessLog.region.length > 0" class="grey"><ip-box :v-ip="accessLog.remoteAddr">[{{accessLog.region}}]</ip-box></span> <ip-box><keyword :v-word="vKeyword">{{accessLog.remoteAddr}}</keyword></ip-box> [{{accessLog.timeLocal}}] <em>"<keyword :v-word="vKeyword">{{accessLog.requestMethod}}</keyword> {{accessLog.scheme}}://<keyword :v-word="vKeyword">{{accessLog.host}}</keyword><keyword :v-word="vKeyword">{{accessLog.requestURI}}</keyword> <a :href="accessLog.scheme + '://' + accessLog.host + accessLog.requestURI" target="_blank" title="新窗口打开" class="disabled"><i class="external icon tiny"></i> </a> {{accessLog.proto}}" </em> <keyword :v-word="vKeyword">{{accessLog.status}}</keyword> <code-label v-if="accessLog.attrs != null && (accessLog.attrs['cache.status'] == 'HIT' || accessLog.attrs['cache.status'] == 'STALE')">cache {{accessLog.attrs['cache.status'].toLowerCase()}}</code-label> <code-label v-if="accessLog.firewallActions != null && accessLog.firewallActions.length > 0">waf {{accessLog.firewallActions}}</code-label> <span v-if="accessLog.tags != null && accessLog.tags.length > 0">- <code-label v-for="tag in accessLog.tags" :key="tag">{{tag}}</code-label></span>
|
||||
<span v-if="accessLog.region != null && accessLog.region.length > 0" class="grey"><ip-box :v-ip="accessLog.remoteAddr">[{{accessLog.region}}]</ip-box></span>
|
||||
<ip-box><keyword :v-word="vKeyword">{{accessLog.remoteAddr}}</keyword></ip-box> [{{accessLog.timeLocal}}] <em>"<keyword :v-word="vKeyword">{{accessLog.requestMethod}}</keyword> {{accessLog.scheme}}://<keyword :v-word="vKeyword">{{accessLog.host}}</keyword><keyword :v-word="vKeyword">{{accessLog.requestURI}}</keyword> <a :href="accessLog.scheme + '://' + accessLog.host + accessLog.requestURI" target="_blank" title="新窗口打开" class="disabled"><i class="external icon tiny"></i> </a> {{accessLog.proto}}" </em> <keyword :v-word="vKeyword">{{accessLog.status}}</keyword>
|
||||
|
||||
<code-label v-if="accessLog.unicodeHost != null && accessLog.unicodeHost.length > 0">{{accessLog.unicodeHost}}</code-label>
|
||||
|
||||
<!-- attrs -->
|
||||
<code-label v-if="accessLog.attrs != null && (accessLog.attrs['cache.status'] == 'HIT' || accessLog.attrs['cache.status'] == 'STALE')">cache {{accessLog.attrs['cache.status'].toLowerCase()}}</code-label>
|
||||
<!-- waf -->
|
||||
<code-label v-if="accessLog.firewallActions != null && accessLog.firewallActions.length > 0">waf {{accessLog.firewallActions}}</code-label>
|
||||
|
||||
<!-- tags -->
|
||||
<span v-if="accessLog.tags != null && accessLog.tags.length > 0">- <code-label v-for="tag in accessLog.tags" :key="tag">{{tag}}</code-label>
|
||||
</span>
|
||||
<span v-if="accessLog.wafInfo != null">
|
||||
<a :href="(accessLog.wafInfo.policy.serverId == 0) ? '/servers/components/waf/group?firewallPolicyId=' + accessLog.firewallPolicyId + '&type=inbound&groupId=' + accessLog.firewallRuleGroupId+ '#set' + accessLog.firewallRuleSetId : '/servers/server/settings/waf/group?serverId=' + accessLog.serverId + '&firewallPolicyId=' + accessLog.firewallPolicyId + '&type=inbound&groupId=' + accessLog.firewallRuleGroupId + '#set' + accessLog.firewallRuleSetId" target="_blank">
|
||||
<code-label-plain>
|
||||
@@ -90,4 +113,331 @@ Vue.component("http-access-log-box", {
|
||||
<a href="" @click.prevent="showLog" title="查看详情"><i class="icon expand"></i></a>
|
||||
</div>
|
||||
</div>`
|
||||
})
|
||||
})
|
||||
|
||||
// Javascript Punycode converter derived from example in RFC3492.
|
||||
// This implementation is created by some@domain.name and released into public domain
|
||||
// 代码来自:https://stackoverflow.com/questions/183485/converting-punycode-with-dash-character-to-unicode
|
||||
var punycode = new function Punycode() {
|
||||
// This object converts to and from puny-code used in IDN
|
||||
//
|
||||
// punycode.ToASCII ( domain )
|
||||
//
|
||||
// Returns a puny coded representation of "domain".
|
||||
// It only converts the part of the domain name that
|
||||
// has non ASCII characters. I.e. it dosent matter if
|
||||
// you call it with a domain that already is in ASCII.
|
||||
//
|
||||
// punycode.ToUnicode (domain)
|
||||
//
|
||||
// Converts a puny-coded domain name to unicode.
|
||||
// It only converts the puny-coded parts of the domain name.
|
||||
// I.e. it dosent matter if you call it on a string
|
||||
// that already has been converted to unicode.
|
||||
//
|
||||
//
|
||||
this.utf16 = {
|
||||
// The utf16-class is necessary to convert from javascripts internal character representation to unicode and back.
|
||||
decode: function (input) {
|
||||
var output = [], i = 0, len = input.length, value, extra;
|
||||
while (i < len) {
|
||||
value = input.charCodeAt(i++);
|
||||
if ((value & 0xF800) === 0xD800) {
|
||||
extra = input.charCodeAt(i++);
|
||||
if (((value & 0xFC00) !== 0xD800) || ((extra & 0xFC00) !== 0xDC00)) {
|
||||
throw new RangeError("UTF-16(decode): Illegal UTF-16 sequence");
|
||||
}
|
||||
value = ((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000;
|
||||
}
|
||||
output.push(value);
|
||||
}
|
||||
return output;
|
||||
},
|
||||
encode: function (input) {
|
||||
var output = [], i = 0, len = input.length, value;
|
||||
while (i < len) {
|
||||
value = input[i++];
|
||||
if ((value & 0xF800) === 0xD800) {
|
||||
throw new RangeError("UTF-16(encode): Illegal UTF-16 value");
|
||||
}
|
||||
if (value > 0xFFFF) {
|
||||
value -= 0x10000;
|
||||
output.push(String.fromCharCode(((value >>> 10) & 0x3FF) | 0xD800));
|
||||
value = 0xDC00 | (value & 0x3FF);
|
||||
}
|
||||
output.push(String.fromCharCode(value));
|
||||
}
|
||||
return output.join("");
|
||||
}
|
||||
}
|
||||
|
||||
//Default parameters
|
||||
var initial_n = 0x80;
|
||||
var initial_bias = 72;
|
||||
var delimiter = "\x2D";
|
||||
var base = 36;
|
||||
var damp = 700;
|
||||
var tmin = 1;
|
||||
var tmax = 26;
|
||||
var skew = 38;
|
||||
var maxint = 0x7FFFFFFF;
|
||||
|
||||
// decode_digit(cp) returns the numeric value of a basic code
|
||||
// point (for use in representing integers) in the range 0 to
|
||||
// base-1, or base if cp is does not represent a value.
|
||||
|
||||
function decode_digit(cp) {
|
||||
return cp - 48 < 10 ? cp - 22 : cp - 65 < 26 ? cp - 65 : cp - 97 < 26 ? cp - 97 : base;
|
||||
}
|
||||
|
||||
// encode_digit(d,flag) returns the basic code point whose value
|
||||
// (when used for representing integers) is d, which needs to be in
|
||||
// the range 0 to base-1. The lowercase form is used unless flag is
|
||||
// nonzero, in which case the uppercase form is used. The behavior
|
||||
// is undefined if flag is nonzero and digit d has no uppercase form.
|
||||
|
||||
function encode_digit(d, flag) {
|
||||
return d + 22 + 75 * (d < 26) - ((flag != 0) << 5);
|
||||
// 0..25 map to ASCII a..z or A..Z
|
||||
// 26..35 map to ASCII 0..9
|
||||
}
|
||||
|
||||
//** Bias adaptation function **
|
||||
function adapt(delta, numpoints, firsttime) {
|
||||
var k;
|
||||
delta = firsttime ? Math.floor(delta / damp) : (delta >> 1);
|
||||
delta += Math.floor(delta / numpoints);
|
||||
|
||||
for (k = 0; delta > (((base - tmin) * tmax) >> 1); k += base) {
|
||||
delta = Math.floor(delta / (base - tmin));
|
||||
}
|
||||
return Math.floor(k + (base - tmin + 1) * delta / (delta + skew));
|
||||
}
|
||||
|
||||
// encode_basic(bcp,flag) forces a basic code point to lowercase if flag is zero,
|
||||
// uppercase if flag is nonzero, and returns the resulting code point.
|
||||
// The code point is unchanged if it is caseless.
|
||||
// The behavior is undefined if bcp is not a basic code point.
|
||||
|
||||
function encode_basic(bcp, flag) {
|
||||
bcp -= (bcp - 97 < 26) << 5;
|
||||
return bcp + ((!flag && (bcp - 65 < 26)) << 5);
|
||||
}
|
||||
|
||||
// Main decode
|
||||
this.decode = function (input, preserveCase) {
|
||||
// Dont use utf16
|
||||
var output = [];
|
||||
var case_flags = [];
|
||||
var input_length = input.length;
|
||||
|
||||
var n, out, i, bias, basic, j, ic, oldi, w, k, digit, t, len;
|
||||
|
||||
// Initialize the state:
|
||||
|
||||
n = initial_n;
|
||||
i = 0;
|
||||
bias = initial_bias;
|
||||
|
||||
// Handle the basic code points: Let basic be the number of input code
|
||||
// points before the last delimiter, or 0 if there is none, then
|
||||
// copy the first basic code points to the output.
|
||||
|
||||
basic = input.lastIndexOf(delimiter);
|
||||
if (basic < 0) basic = 0;
|
||||
|
||||
for (j = 0; j < basic; ++j) {
|
||||
if (preserveCase) case_flags[output.length] = (input.charCodeAt(j) - 65 < 26);
|
||||
if (input.charCodeAt(j) >= 0x80) {
|
||||
throw new RangeError("Illegal input >= 0x80");
|
||||
}
|
||||
output.push(input.charCodeAt(j));
|
||||
}
|
||||
|
||||
// Main decoding loop: Start just after the last delimiter if any
|
||||
// basic code points were copied; start at the beginning otherwise.
|
||||
|
||||
for (ic = basic > 0 ? basic + 1 : 0; ic < input_length;) {
|
||||
|
||||
// ic is the index of the next character to be consumed,
|
||||
|
||||
// Decode a generalized variable-length integer into delta,
|
||||
// which gets added to i. The overflow checking is easier
|
||||
// if we increase i as we go, then subtract off its starting
|
||||
// value at the end to obtain delta.
|
||||
for (oldi = i, w = 1, k = base; ; k += base) {
|
||||
if (ic >= input_length) {
|
||||
throw RangeError("punycode_bad_input(1)");
|
||||
}
|
||||
digit = decode_digit(input.charCodeAt(ic++));
|
||||
|
||||
if (digit >= base) {
|
||||
throw RangeError("punycode_bad_input(2)");
|
||||
}
|
||||
if (digit > Math.floor((maxint - i) / w)) {
|
||||
throw RangeError("punycode_overflow(1)");
|
||||
}
|
||||
i += digit * w;
|
||||
t = k <= bias ? tmin : k >= bias + tmax ? tmax : k - bias;
|
||||
if (digit < t) {
|
||||
break;
|
||||
}
|
||||
if (w > Math.floor(maxint / (base - t))) {
|
||||
throw RangeError("punycode_overflow(2)");
|
||||
}
|
||||
w *= (base - t);
|
||||
}
|
||||
|
||||
out = output.length + 1;
|
||||
bias = adapt(i - oldi, out, oldi === 0);
|
||||
|
||||
// i was supposed to wrap around from out to 0,
|
||||
// incrementing n each time, so we'll fix that now:
|
||||
if (Math.floor(i / out) > maxint - n) {
|
||||
throw RangeError("punycode_overflow(3)");
|
||||
}
|
||||
n += Math.floor(i / out);
|
||||
i %= out;
|
||||
|
||||
// Insert n at position i of the output:
|
||||
// Case of last character determines uppercase flag:
|
||||
if (preserveCase) {
|
||||
case_flags.splice(i, 0, input.charCodeAt(ic - 1) - 65 < 26);
|
||||
}
|
||||
|
||||
output.splice(i, 0, n);
|
||||
i++;
|
||||
}
|
||||
if (preserveCase) {
|
||||
for (i = 0, len = output.length; i < len; i++) {
|
||||
if (case_flags[i]) {
|
||||
output[i] = (String.fromCharCode(output[i]).toUpperCase()).charCodeAt(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.utf16.encode(output);
|
||||
};
|
||||
|
||||
//** Main encode function **
|
||||
|
||||
this.encode = function (input, preserveCase) {
|
||||
//** Bias adaptation function **
|
||||
|
||||
var n, delta, h, b, bias, j, m, q, k, t, ijv, case_flags;
|
||||
|
||||
if (preserveCase) {
|
||||
// Preserve case, step1 of 2: Get a list of the unaltered string
|
||||
case_flags = this.utf16.decode(input);
|
||||
}
|
||||
// Converts the input in UTF-16 to Unicode
|
||||
input = this.utf16.decode(input.toLowerCase());
|
||||
|
||||
var input_length = input.length; // Cache the length
|
||||
|
||||
if (preserveCase) {
|
||||
// Preserve case, step2 of 2: Modify the list to true/false
|
||||
for (j = 0; j < input_length; j++) {
|
||||
case_flags[j] = input[j] != case_flags[j];
|
||||
}
|
||||
}
|
||||
|
||||
var output = [];
|
||||
|
||||
|
||||
// Initialize the state:
|
||||
n = initial_n;
|
||||
delta = 0;
|
||||
bias = initial_bias;
|
||||
|
||||
// Handle the basic code points:
|
||||
for (j = 0; j < input_length; ++j) {
|
||||
if (input[j] < 0x80) {
|
||||
output.push(
|
||||
String.fromCharCode(
|
||||
case_flags ? encode_basic(input[j], case_flags[j]) : input[j]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
h = b = output.length;
|
||||
|
||||
// h is the number of code points that have been handled, b is the
|
||||
// number of basic code points
|
||||
|
||||
if (b > 0) output.push(delimiter);
|
||||
|
||||
// Main encoding loop:
|
||||
//
|
||||
while (h < input_length) {
|
||||
// All non-basic code points < n have been
|
||||
// handled already. Find the next larger one:
|
||||
|
||||
for (m = maxint, j = 0; j < input_length; ++j) {
|
||||
ijv = input[j];
|
||||
if (ijv >= n && ijv < m) m = ijv;
|
||||
}
|
||||
|
||||
// Increase delta enough to advance the decoder's
|
||||
// <n,i> state to <m,0>, but guard against overflow:
|
||||
|
||||
if (m - n > Math.floor((maxint - delta) / (h + 1))) {
|
||||
throw RangeError("punycode_overflow (1)");
|
||||
}
|
||||
delta += (m - n) * (h + 1);
|
||||
n = m;
|
||||
|
||||
for (j = 0; j < input_length; ++j) {
|
||||
ijv = input[j];
|
||||
|
||||
if (ijv < n) {
|
||||
if (++delta > maxint) return Error("punycode_overflow(2)");
|
||||
}
|
||||
|
||||
if (ijv == n) {
|
||||
// Represent delta as a generalized variable-length integer:
|
||||
for (q = delta, k = base; ; k += base) {
|
||||
t = k <= bias ? tmin : k >= bias + tmax ? tmax : k - bias;
|
||||
if (q < t) break;
|
||||
output.push(String.fromCharCode(encode_digit(t + (q - t) % (base - t), 0)));
|
||||
q = Math.floor((q - t) / (base - t));
|
||||
}
|
||||
output.push(String.fromCharCode(encode_digit(q, preserveCase && case_flags[j] ? 1 : 0)));
|
||||
bias = adapt(delta, h + 1, h == b);
|
||||
delta = 0;
|
||||
++h;
|
||||
}
|
||||
}
|
||||
|
||||
++delta, ++n;
|
||||
}
|
||||
return output.join("");
|
||||
}
|
||||
|
||||
this.ToASCII = function (domain) {
|
||||
var domain_array = domain.split(".");
|
||||
var out = [];
|
||||
for (var i = 0; i < domain_array.length; ++i) {
|
||||
var s = domain_array[i];
|
||||
out.push(
|
||||
s.match(/[^A-Za-z0-9-]/) ?
|
||||
"xn--" + punycode.encode(s) :
|
||||
s
|
||||
);
|
||||
}
|
||||
return out.join(".");
|
||||
}
|
||||
this.ToUnicode = function (domain) {
|
||||
var domain_array = domain.split(".");
|
||||
var out = [];
|
||||
for (var i = 0; i < domain_array.length; ++i) {
|
||||
var s = domain_array[i];
|
||||
out.push(
|
||||
s.match(/^xn--/) ?
|
||||
punycode.decode(s.slice(4)) :
|
||||
s
|
||||
);
|
||||
}
|
||||
return out.join(".");
|
||||
}
|
||||
}();
|
||||
@@ -22,6 +22,8 @@ Vue.component("http-cache-policy-selector", {
|
||||
select: function () {
|
||||
let that = this
|
||||
teaweb.popup("/servers/components/cache/selectPopup", {
|
||||
width: "42em",
|
||||
height: "26em",
|
||||
callback: function (resp) {
|
||||
that.cachePolicy = resp.data.cachePolicy
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ Vue.component("http-cache-ref-box", {
|
||||
conds: null, // 复杂条件
|
||||
simpleCond: null, // 简单条件
|
||||
allowChunkedEncoding: true,
|
||||
allowPartialContent: false,
|
||||
allowPartialContent: true,
|
||||
enableIfNoneMatch: false,
|
||||
enableIfModifiedSince: false,
|
||||
isReverse: this.vIsReverse,
|
||||
@@ -67,6 +67,9 @@ Vue.component("http-cache-ref-box", {
|
||||
|
||||
return {
|
||||
ref: ref,
|
||||
|
||||
keyIgnoreArgs: typeof ref.key == "string" && ref.key.indexOf("${args}") < 0,
|
||||
|
||||
moreOptionsVisible: false,
|
||||
|
||||
condCategory: "simple", // 条件分类:simple|complex
|
||||
@@ -77,6 +80,23 @@ Vue.component("http-cache-ref-box", {
|
||||
components: window.REQUEST_COND_COMPONENTS
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
keyIgnoreArgs: function (b) {
|
||||
if (typeof this.ref.key != "string") {
|
||||
return
|
||||
}
|
||||
if (b) {
|
||||
this.ref.key = this.ref.key.replace("${isArgs}${args}", "")
|
||||
return;
|
||||
}
|
||||
if (this.ref.key.indexOf("${isArgs}") < 0) {
|
||||
this.ref.key = this.ref.key + "${isArgs}"
|
||||
}
|
||||
if (this.ref.key.indexOf("${args}") < 0) {
|
||||
this.ref.key = this.ref.key + "${args}"
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeOptionsVisible: function (v) {
|
||||
this.moreOptionsVisible = v
|
||||
@@ -123,6 +143,9 @@ Vue.component("http-cache-ref-box", {
|
||||
|
||||
// resize window
|
||||
let dialog = window.parent.document.querySelector("*[role='dialog']")
|
||||
if (dialog == null) {
|
||||
return
|
||||
}
|
||||
switch (condCategory) {
|
||||
case "simple":
|
||||
dialog.style.width = "40em"
|
||||
@@ -194,12 +217,19 @@ Vue.component("http-cache-ref-box", {
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-show="!vIsReverse">
|
||||
<td>缓存Key *</td>
|
||||
<td class="color-border">缓存Key *</td>
|
||||
<td>
|
||||
<input type="text" v-model="ref.key" @input="changeKey(ref.key)"/>
|
||||
<p class="comment">用来区分不同缓存内容的唯一Key。<request-variables-describer ref="variablesDescriber"></request-variables-describer>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-show="!vIsReverse">
|
||||
<td class="color-border">忽略URI参数</td>
|
||||
<td>
|
||||
<checkbox v-model="keyIgnoreArgs"></checkbox>
|
||||
<p class="comment">选中后,表示缓存Key中不包含URI参数(即问号(?))后面的内容。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-show="!vIsReverse">
|
||||
<td colspan="2"><more-options-indicator @change="changeOptionsVisible"></more-options-indicator></td>
|
||||
</tr>
|
||||
@@ -241,7 +271,7 @@ Vue.component("http-cache-ref-box", {
|
||||
<td>支持缓存区间内容</td>
|
||||
<td>
|
||||
<checkbox name="allowPartialContent" value="1" v-model="ref.allowPartialContent"></checkbox>
|
||||
<p class="comment">选中后,支持缓存源站返回的某个区间的内容,该内容通过<code-label>206 Partial Content</code-label>状态码返回。此功能目前为<code-label>试验性质</code-label>。</p>
|
||||
<p class="comment">选中后,支持缓存源站返回的某个区间的内容,该内容通过<code-label>206 Partial Content</code-label>状态码返回。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-show="moreOptionsVisible && !vIsReverse">
|
||||
|
||||
@@ -42,11 +42,12 @@ Vue.component("http-cache-refs-box", {
|
||||
<th class="width10">缓存时间</th>
|
||||
</tr>
|
||||
<tr v-for="(cacheRef, index) in refs">
|
||||
<td :class="{'color-border': cacheRef.conds.connector == 'and', disabled: !cacheRef.isOn}" :style="{'border-left':cacheRef.isReverse ? '1px #db2828 solid' : ''}">
|
||||
<td :class="{'color-border': cacheRef.conds != null && cacheRef.conds.connector == 'and', disabled: !cacheRef.isOn}" :style="{'border-left':cacheRef.isReverse ? '1px #db2828 solid' : ''}">
|
||||
<http-request-conds-view :v-conds="cacheRef.conds" :class="{disabled: !cacheRef.isOn}" v-if="cacheRef.conds != null && cacheRef.conds.groups != null"></http-request-conds-view>
|
||||
<http-request-cond-view :v-cond="cacheRef.simpleCond" v-if="cacheRef.simpleCond != null"></http-request-cond-view>
|
||||
|
||||
<!-- 特殊参数 -->
|
||||
<grey-label v-if="cacheRef.key != null && cacheRef.key.indexOf('\${args}') < 0">忽略URI参数</grey-label>
|
||||
<grey-label v-if="cacheRef.minSize != null && cacheRef.minSize.count > 0">
|
||||
{{cacheRef.minSize.count}}{{cacheRef.minSize.unit}}
|
||||
<span v-if="cacheRef.maxSize != null && cacheRef.maxSize.count > 0">- {{cacheRef.maxSize.count}}{{cacheRef.maxSize.unit}}</span>
|
||||
@@ -60,8 +61,11 @@ Vue.component("http-cache-refs-box", {
|
||||
<grey-label v-if="cacheRef.enableIfModifiedSince">If-Modified-Since</grey-label>
|
||||
</td>
|
||||
<td :class="{disabled: !cacheRef.isOn}">
|
||||
<span v-if="cacheRef.conds.connector == 'and'">和</span>
|
||||
<span v-if="cacheRef.conds.connector == 'or'">或</span>
|
||||
<span v-if="cacheRef.conds != null">
|
||||
<span v-if="cacheRef.conds.connector == 'and'">和</span>
|
||||
<span v-if="cacheRef.conds.connector == 'or'">或</span>
|
||||
</span>
|
||||
<span v-else>或</span>
|
||||
</td>
|
||||
<td :class="{disabled: !cacheRef.isOn}">
|
||||
<span v-if="!cacheRef.isReverse">{{cacheRef.life.count}} {{timeUnitName(cacheRef.life.unit)}}</span>
|
||||
|
||||
@@ -175,11 +175,12 @@ Vue.component("http-cache-refs-config-box", {
|
||||
<tbody v-for="(cacheRef, index) in refs" :key="cacheRef.id" :v-id="cacheRef.id">
|
||||
<tr>
|
||||
<td style="text-align: center;"><i class="icon bars handle grey"></i> </td>
|
||||
<td :class="{'color-border': cacheRef.conds.connector == 'and', disabled: !cacheRef.isOn}" :style="{'border-left':cacheRef.isReverse ? '1px #db2828 solid' : ''}">
|
||||
<td :class="{'color-border': cacheRef.conds != null && cacheRef.conds.connector == 'and', disabled: !cacheRef.isOn}" :style="{'border-left':cacheRef.isReverse ? '1px #db2828 solid' : ''}">
|
||||
<http-request-conds-view :v-conds="cacheRef.conds" ref="cacheRef" :class="{disabled: !cacheRef.isOn}" v-if="cacheRef.conds != null && cacheRef.conds.groups != null"></http-request-conds-view>
|
||||
<http-request-cond-view :v-cond="cacheRef.simpleCond" ref="cacheRef" v-if="cacheRef.simpleCond != null"></http-request-cond-view>
|
||||
|
||||
<!-- 特殊参数 -->
|
||||
<grey-label v-if="cacheRef.key != null && cacheRef.key.indexOf('\${args}') < 0">忽略URI参数</grey-label>
|
||||
<grey-label v-if="cacheRef.minSize != null && cacheRef.minSize.count > 0">
|
||||
{{cacheRef.minSize.count}}{{cacheRef.minSize.unit}}
|
||||
<span v-if="cacheRef.maxSize != null && cacheRef.maxSize.count > 0">- {{cacheRef.maxSize.count}}{{cacheRef.maxSize.unit}}</span>
|
||||
@@ -193,8 +194,11 @@ Vue.component("http-cache-refs-config-box", {
|
||||
<grey-label v-if="cacheRef.enableIfModifiedSince">If-Modified-Since</grey-label>
|
||||
</td>
|
||||
<td :class="{disabled: !cacheRef.isOn}">
|
||||
<span v-if="cacheRef.conds.connector == 'and'">和</span>
|
||||
<span v-if="cacheRef.conds.connector == 'or'">或</span>
|
||||
<span v-if="cacheRef.conds != null">
|
||||
<span v-if="cacheRef.conds.connector == 'and'">和</span>
|
||||
<span v-if="cacheRef.conds.connector == 'or'">或</span>
|
||||
</span>
|
||||
<span v-else>或</span>
|
||||
</td>
|
||||
<td :class="{disabled: !cacheRef.isOn}">
|
||||
<span v-if="!cacheRef.isReverse">{{cacheRef.life.count}} {{timeUnitName(cacheRef.life.unit)}}</span>
|
||||
|
||||
@@ -41,7 +41,7 @@ Vue.component("http-location-labels", {
|
||||
<http-location-labels-label v-if="location.web != null && configIsOn(location.web.root)" :href="url('/web')">文档根目录</http-location-labels-label>
|
||||
|
||||
<!-- 反向代理 -->
|
||||
<http-location-labels-label v-if="refIsOn(location.reverseProxyRef, location.reverseProxy)" :v-href="url('/reverseProxy')">反向代理</http-location-labels-label>
|
||||
<http-location-labels-label v-if="refIsOn(location.reverseProxyRef, location.reverseProxy)" :v-href="url('/reverseProxy')">源站</http-location-labels-label>
|
||||
|
||||
<!-- WAF -->
|
||||
<!-- TODO -->
|
||||
|
||||
70
web/public/js/components/server/http-referers-config-box.js
Normal file
70
web/public/js/components/server/http-referers-config-box.js
Normal file
@@ -0,0 +1,70 @@
|
||||
Vue.component("http-referers-config-box", {
|
||||
props: ["v-referers-config", "v-is-location", "v-is-group"],
|
||||
data: function () {
|
||||
let config = this.vReferersConfig
|
||||
if (config == null) {
|
||||
config = {
|
||||
isPrior: false,
|
||||
isOn: false,
|
||||
allowEmpty: true,
|
||||
allowSameDomain: true,
|
||||
allowDomains: []
|
||||
}
|
||||
}
|
||||
if (config.allowDomains == null) {
|
||||
config.allowDomains = []
|
||||
}
|
||||
return {
|
||||
config: config
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isOn: function () {
|
||||
return ((!this.vIsLocation && !this.vIsGroup) || this.config.isPrior) && this.config.isOn
|
||||
},
|
||||
changeAllowDomains: function (domains) {
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<input type="hidden" name="referersJSON" :value="JSON.stringify(config)"/>
|
||||
<table class="ui table selectable definition">
|
||||
<prior-checkbox :v-config="config" v-if="vIsLocation || vIsGroup"></prior-checkbox>
|
||||
<tbody v-show="(!vIsLocation && !vIsGroup) || config.isPrior">
|
||||
<tr>
|
||||
<td class="title">启用防盗链</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" value="1" v-model="config.isOn"/>
|
||||
<label></label>
|
||||
</div>
|
||||
<p class="comment">选中后表示开启防盗链。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody v-show="isOn()">
|
||||
<tr>
|
||||
<td class="title">允许直接访问网站</td>
|
||||
<td>
|
||||
<checkbox v-model="config.allowEmpty"></checkbox>
|
||||
<p class="comment">允许用户直接访问网站,用户第一次访问网站时来源域名通常为空。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>来源域名允许一致</td>
|
||||
<td>
|
||||
<checkbox v-model="config.allowSameDomain"></checkbox>
|
||||
<p class="comment">允许来源域名和当前访问的域名一致,相当于在站内访问。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>允许的来源域名</td>
|
||||
<td>
|
||||
<values-box :values="config.allowDomains" @change="changeAllowDomains"></values-box>
|
||||
<p class="comment">允许的其他来源域名列表,比如<code-label>example.com</code-label>、<code-label>*.example.com</code-label>。单个星号<code-label>*</code-label>表示允许所有域名。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="ui margin"></div>
|
||||
</div>`
|
||||
})
|
||||
@@ -159,7 +159,7 @@ Vue.component("reverse-proxy-box", {
|
||||
<prior-checkbox :v-config="reverseProxyRef" v-if="vIsLocation || vIsGroup"></prior-checkbox>
|
||||
<tbody v-show="(!vIsLocation && !vIsGroup) || reverseProxyRef.isPrior">
|
||||
<tr>
|
||||
<td class="title">启用反向代理</td>
|
||||
<td class="title">启用源站</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" v-model="reverseProxyRef.isOn"/>
|
||||
|
||||
@@ -909,7 +909,7 @@ window.teaweb = {
|
||||
s = s.replace(/&/g, "&")
|
||||
s = s.replace(/</g, "<")
|
||||
s = s.replace(/>/g, ">")
|
||||
s = s.replace(/"/, """)
|
||||
s = s.replace(/"/g, """)
|
||||
return s
|
||||
},
|
||||
chartColor: function (color) {
|
||||
|
||||
1
web/views/@default/@blank.html
Normal file
1
web/views/@default/@blank.html
Normal file
@@ -0,0 +1 @@
|
||||
{$layout}
|
||||
@@ -510,6 +510,9 @@ body.expanded .main {
|
||||
.main h4 {
|
||||
font-weight: normal;
|
||||
}
|
||||
.main form h4 {
|
||||
margin-top: 0.6em;
|
||||
}
|
||||
.main td span.small {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -476,6 +476,10 @@ body.expanded .main {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.main form h4 {
|
||||
margin-top: 0.6em;
|
||||
}
|
||||
|
||||
.main td span.small {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
@@ -274,6 +274,10 @@ var.dash {
|
||||
.page a:hover {
|
||||
background: #eee;
|
||||
}
|
||||
.page select {
|
||||
padding-top: 0.3em !important;
|
||||
padding-bottom: 0.3em !important;
|
||||
}
|
||||
.swal2-html-container {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
@@ -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,yBAAA;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,GAAG;EACF,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;;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","file":"@layout_popup.css"}
|
||||
{"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,yBAAA;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,GAAG;EACF,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","file":"@layout_popup.css"}
|
||||
@@ -332,6 +332,11 @@ var.dash {
|
||||
a:hover {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
select {
|
||||
padding-top: 0.3em !important;
|
||||
padding-bottom: 0.3em !important;
|
||||
}
|
||||
}
|
||||
|
||||
// popup
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<first-menu>
|
||||
<menu-item href="/admins/recipients" code="recipient">接收人</menu-item>
|
||||
<menu-item href="/admins/recipients/groups" code="group">接收人分组</menu-item>
|
||||
<menu-item href="/admins/recipients/instances" code="instance">媒介</menu-item>
|
||||
<menu-item href="/admins/recipients/logs" code="log">发送记录</menu-item>
|
||||
<menu-item href="/admins/recipients/tasks" code="task">任务队列</menu-item>
|
||||
</first-menu>
|
||||
@@ -1,7 +0,0 @@
|
||||
<first-menu>
|
||||
<menu-item href="/admins/recipients">所有接收人</menu-item>
|
||||
<span class="item">|</span>
|
||||
<menu-item :href="'/admins/recipients/recipient?recipientId=' + recipient.id" code="recipient">"{{recipient.admin.fullname}} <span class="small grey">({{recipient.instance.name}})</span>"详情</menu-item>
|
||||
<menu-item :href="'/admins/recipients/update?recipientId=' + recipient.id" code="update">修改</menu-item>
|
||||
<menu-item :href="'/admins/recipients/test?recipientId=' + recipient.id" code="test">测试</menu-item>
|
||||
</first-menu>
|
||||
@@ -1,103 +0,0 @@
|
||||
{$layout "layout_popup"}
|
||||
{$template "/code_editor"}
|
||||
|
||||
<h3>创建接收人媒介</h3>
|
||||
<form class="ui form" data-tea-action="$" data-tea-success="success">
|
||||
<csrf-token></csrf-token>
|
||||
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">系统用户 *</td>
|
||||
<td>
|
||||
<admin-selector></admin-selector>
|
||||
<p class="comment">选择关联的系统用户。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>媒介 *</td>
|
||||
<td>
|
||||
<message-media-instance-selector @change="changeInstance"></message-media-instance-selector>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>接收人标识</td>
|
||||
<td>
|
||||
<input type="text" name="user" maxlength="300"/>
|
||||
<p class="comment">{{userDescription}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>分组</td>
|
||||
<td>
|
||||
<message-recipient-group-selector></message-recipient-group-selector>
|
||||
<p class="comment">选择当前接收人所属分组。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><more-options-indicator></more-options-indicator></td>
|
||||
</tr>
|
||||
<tbody v-show="moreOptionsVisible">
|
||||
<tr>
|
||||
<td>发送时间</td>
|
||||
<td>
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
开始时间:
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<div class="ui input right labeled">
|
||||
<input type="text" name="timeFromHour" size="2" maxlength="2" placeholder="" v-model="timeFromHour"/>
|
||||
<span class="ui label">时</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<div class="ui input right labeled">
|
||||
<input type="text" name="timeFromMinute" size="2" maxlength="2" placeholder="" v-model="timeFromMinute"/>
|
||||
<span class="ui label">分</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<div class="ui input right labeled">
|
||||
<input type="text" name="timeFromSecond" size="2" maxlength="2" placeholder="" v-model="timeFromSecond"/>
|
||||
<span class="ui label">秒</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
结束时间:
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<div class="ui input right labeled">
|
||||
<input type="text" name="timeToHour" size="2" maxlength="2" placeholder="" v-model="timeToHour"/>
|
||||
<span class="ui label">时</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<div class="ui input right labeled">
|
||||
<input type="text" name="timeToMinute" size="2" maxlength="2" placeholder="" v-model="timeToMinute"/>
|
||||
<span class="ui label">分</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<div class="ui input right labeled">
|
||||
<input type="text" name="timeToSecond" size="2" maxlength="2" placeholder="" v-model="timeToSecond"/>
|
||||
<span class="ui label">秒</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="comment">24小时制,即小时从0到23。如果填写了发送时间,系统只会在这个时间段内给当前接收人发送消息。 <a href="" v-if="timeFromHour.length > 0 || timeFromMinute.length > 0 || timeFromSecond.length > 0 || timeToHour.length > 0 || timeToMinute.length > 0 || timeToSecond.length > 0" @click.prevent="clearTime">[清除]</a></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>备注</td>
|
||||
<td>
|
||||
<textarea rows="3" name="description" maxlength="100"></textarea>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<submit-btn></submit-btn>
|
||||
</form>
|
||||
@@ -1,30 +0,0 @@
|
||||
Tea.context(function () {
|
||||
this.userDescription = ""
|
||||
|
||||
this.changeInstance = function (instance) {
|
||||
if (instance != null) {
|
||||
this.userDescription = instance.media.userDescription
|
||||
} else {
|
||||
this.userDescription = ""
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送时间
|
||||
*/
|
||||
this.timeFromHour = ""
|
||||
this.timeFromMinute = ""
|
||||
this.timeFromSecond = ""
|
||||
this.timeToHour = ""
|
||||
this.timeToMinute = ""
|
||||
this.timeToSecond = ""
|
||||
|
||||
this.clearTime = function () {
|
||||
this.timeFromHour = ""
|
||||
this.timeFromMinute = ""
|
||||
this.timeFromSecond = ""
|
||||
this.timeToHour = ""
|
||||
this.timeToMinute = ""
|
||||
this.timeToSecond = ""
|
||||
}
|
||||
})
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user