Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbe7336f32 | ||
|
|
aac953f483 | ||
|
|
49bc469430 | ||
|
|
f3ac8a5cc5 | ||
|
|
847d08a9bb | ||
|
|
0563a363c2 | ||
|
|
f9dc0d6b54 | ||
|
|
40ef3604aa | ||
|
|
970604dc73 | ||
|
|
d6617f214d | ||
|
|
860fccbd4c | ||
|
|
b3adb839e0 | ||
|
|
5e9654c3bc | ||
|
|
43c6bff964 | ||
|
|
aef84189a4 | ||
|
|
04f8bfc975 | ||
|
|
d139e93160 | ||
|
|
1fcc0694ca | ||
|
|
7ba7858076 | ||
|
|
b1cbc433ed | ||
|
|
8beaf97306 | ||
|
|
e626364f45 | ||
|
|
ebaec51f67 | ||
|
|
36b90451af | ||
|
|
e0b1c2a6a4 | ||
|
|
42c6f4264e | ||
|
|
78641e7052 | ||
|
|
6bf0118d20 | ||
|
|
0415cd3719 | ||
|
|
a3412b2f95 | ||
|
|
6a4b3b026f | ||
|
|
f213976a5d | ||
|
|
3605f71f70 | ||
|
|
dc08847a7d | ||
|
|
a34204e25e | ||
|
|
25d73ac0a2 | ||
|
|
f67c0c0e75 | ||
|
|
08c8255d59 | ||
|
|
157efaa02e | ||
|
|
a56a29495e | ||
|
|
c454cd75b3 | ||
|
|
633684f576 | ||
|
|
de50b5e0a1 | ||
|
|
855f287e39 | ||
|
|
f018dee75e | ||
|
|
7d3b218e24 | ||
|
|
536382ce34 | ||
|
|
f6f003d524 | ||
|
|
8126de4048 | ||
|
|
4c41df85a0 | ||
|
|
2c9f78bb9e |
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
function build() {
|
||||
ROOT=$(dirname $0)
|
||||
ROOT=$(dirname "$0")
|
||||
JS_ROOT=$ROOT/../web/public/js
|
||||
NAME="edge-admin"
|
||||
DIST=$ROOT/"../dist/${NAME}"
|
||||
@@ -9,15 +9,15 @@ function build() {
|
||||
ARCH=${2}
|
||||
TAG=${3}
|
||||
|
||||
if [ -z $OS ]; then
|
||||
if [ -z "$OS" ]; then
|
||||
echo "usage: build.sh OS ARCH"
|
||||
exit
|
||||
fi
|
||||
if [ -z $ARCH ]; then
|
||||
if [ -z "$ARCH" ]; then
|
||||
echo "usage: build.sh OS ARCH"
|
||||
exit
|
||||
fi
|
||||
if [ -z $TAG ]; then
|
||||
if [ -z "$TAG" ]; then
|
||||
TAG="community"
|
||||
fi
|
||||
|
||||
@@ -25,7 +25,7 @@ function build() {
|
||||
echo "checking required commands ..."
|
||||
commands=("zip" "unzip" "go" "find" "sed")
|
||||
for cmd in "${commands[@]}"; do
|
||||
if [ `which ${cmd}` ]; then
|
||||
if [ "$(which "${cmd}")" ]; then
|
||||
echo "checking ${cmd}: ok"
|
||||
else
|
||||
echo "checking ${cmd}: not found"
|
||||
@@ -33,49 +33,49 @@ function build() {
|
||||
fi
|
||||
done
|
||||
|
||||
VERSION=$(lookup-version $ROOT/../internal/const/const.go)
|
||||
VERSION=$(lookup-version "$ROOT"/../internal/const/const.go)
|
||||
ZIP="${NAME}-${OS}-${ARCH}-${TAG}-v${VERSION}.zip"
|
||||
|
||||
# build edge-api
|
||||
APINodeVersion=$(lookup-version $ROOT"/../../EdgeAPI/internal/const/const.go")
|
||||
APINodeVersion=$(lookup-version "$ROOT""/../../EdgeAPI/internal/const/const.go")
|
||||
echo "building edge-api v${APINodeVersion} ..."
|
||||
EDGE_API_BUILD_SCRIPT=$ROOT"/../../EdgeAPI/build/build.sh"
|
||||
if [ ! -f $EDGE_API_BUILD_SCRIPT ]; then
|
||||
if [ ! -f "$EDGE_API_BUILD_SCRIPT" ]; then
|
||||
echo "unable to find edge-api build script 'EdgeAPI/build/build.sh'"
|
||||
exit
|
||||
fi
|
||||
|
||||
cd $ROOT"/../../EdgeAPI/build"
|
||||
cd "$ROOT""/../../EdgeAPI/build" || exit
|
||||
echo "=============================="
|
||||
./build.sh $OS $ARCH $TAG
|
||||
./build.sh "$OS" "$ARCH" $TAG
|
||||
echo "=============================="
|
||||
cd -
|
||||
cd - || exit
|
||||
|
||||
# generate files
|
||||
echo "generating files ..."
|
||||
go run -tags $TAG $ROOT/../cmd/edge-admin/main.go generate
|
||||
if [ `which uglifyjs` ]; then
|
||||
go run -tags $TAG "$ROOT"/../cmd/edge-admin/main.go generate
|
||||
if [ "$(which uglifyjs)" ]; then
|
||||
echo "compress to component.js ..."
|
||||
uglifyjs --compress --mangle -- ${JS_ROOT}/components.src.js > ${JS_ROOT}/components.js
|
||||
uglifyjs --compress --mangle -- "${JS_ROOT}"/components.src.js > "${JS_ROOT}"/components.js
|
||||
else
|
||||
echo "copy to component.js ..."
|
||||
cp ${JS_ROOT}/components.src.js ${JS_ROOT}/components.js
|
||||
cp "${JS_ROOT}"/components.src.js "${JS_ROOT}"/components.js
|
||||
fi
|
||||
|
||||
# create dir & copy files
|
||||
echo "copying ..."
|
||||
if [ ! -d $DIST ]; then
|
||||
mkdir $DIST
|
||||
mkdir $DIST/bin
|
||||
mkdir $DIST/configs
|
||||
mkdir $DIST/logs
|
||||
if [ ! -d "$DIST" ]; then
|
||||
mkdir "$DIST"
|
||||
mkdir "$DIST"/bin
|
||||
mkdir "$DIST"/configs
|
||||
mkdir "$DIST"/logs
|
||||
fi
|
||||
|
||||
cp -R $ROOT/../web $DIST/
|
||||
rm -f $DIST/web/tmp/*
|
||||
rm -rf $DIST/web/public/js/components
|
||||
rm -f $DIST/web/public/js/components.src.js
|
||||
cp $ROOT/configs/server.template.yaml $DIST/configs/
|
||||
cp -R "$ROOT"/../web "$DIST"/
|
||||
rm -f "$DIST"/web/tmp/*
|
||||
rm -rf "$DIST"/web/public/js/components
|
||||
rm -f "$DIST"/web/public/js/components.src.js
|
||||
cp "$ROOT"/configs/server.template.yaml "$DIST"/configs/
|
||||
|
||||
# change _plus.[ext] to .[ext]
|
||||
if [ "${TAG}" = "plus" ]; then
|
||||
@@ -83,30 +83,30 @@ function build() {
|
||||
exts=("html" "js" "css")
|
||||
for ext in "${exts[@]}"; do
|
||||
pattern="*_plus."${ext}
|
||||
find $DIST/web/views -type f -name $pattern | \
|
||||
find "$DIST"/web/views -type f -name "$pattern" | \
|
||||
while read filename; do
|
||||
mv ${filename} "${filename/_plus."${ext}"/."${ext}"}"
|
||||
mv "${filename}" "${filename/_plus."${ext}"/."${ext}"}"
|
||||
done
|
||||
done
|
||||
fi
|
||||
|
||||
EDGE_API_ZIP_FILE=$ROOT"/../../EdgeAPI/dist/edge-api-${OS}-${ARCH}-${TAG}-v${APINodeVersion}.zip"
|
||||
cp $EDGE_API_ZIP_FILE $DIST/
|
||||
cd $DIST/
|
||||
unzip -q $(basename $EDGE_API_ZIP_FILE)
|
||||
rm -f $(basename $EDGE_API_ZIP_FILE)
|
||||
cd -
|
||||
cp "$EDGE_API_ZIP_FILE" "$DIST"/
|
||||
cd "$DIST"/ || exit
|
||||
unzip -q "$(basename "$EDGE_API_ZIP_FILE")"
|
||||
rm -f "$(basename "$EDGE_API_ZIP_FILE")"
|
||||
cd - || exit
|
||||
|
||||
# build
|
||||
echo "building "${NAME}" ..."
|
||||
env GOOS=$OS GOARCH=$ARCH go build -tags $TAG -ldflags="-s -w" -o $DIST/bin/${NAME} $ROOT/../cmd/edge-admin/main.go
|
||||
echo "building ${NAME} ..."
|
||||
env GOOS="$OS" GOARCH="$ARCH" go build -trimpath -tags $TAG -ldflags="-s -w" -o "$DIST"/bin/${NAME} "$ROOT"/../cmd/edge-admin/main.go
|
||||
|
||||
# delete hidden files
|
||||
find $DIST -name ".DS_Store" -delete
|
||||
find $DIST -name ".gitignore" -delete
|
||||
find $DIST -name "*.less" -delete
|
||||
find $DIST -name "*.css.map" -delete
|
||||
find $DIST -name "*.js.map" -delete
|
||||
find "$DIST" -name ".DS_Store" -delete
|
||||
find "$DIST" -name ".gitignore" -delete
|
||||
find "$DIST" -name "*.less" -delete
|
||||
find "$DIST" -name "*.css.map" -delete
|
||||
find "$DIST" -name "*.js.map" -delete
|
||||
|
||||
# zip
|
||||
echo "zip files ..."
|
||||
@@ -123,15 +123,15 @@ function build() {
|
||||
|
||||
function lookup-version() {
|
||||
FILE=$1
|
||||
VERSION_DATA=$(cat $FILE)
|
||||
VERSION_DATA=$(cat "$FILE")
|
||||
re="Version[ ]+=[ ]+\"([0-9.]+)\""
|
||||
if [[ $VERSION_DATA =~ $re ]]; then
|
||||
VERSION=${BASH_REMATCH[1]}
|
||||
echo $VERSION
|
||||
echo "$VERSION"
|
||||
else
|
||||
echo "could not match version"
|
||||
exit
|
||||
fi
|
||||
}
|
||||
|
||||
build $1 $2 $3
|
||||
build "$1" "$2" "$3"
|
||||
|
||||
2
dist/.gitignore
vendored
2
dist/.gitignore
vendored
@@ -1,2 +1,2 @@
|
||||
*.zip
|
||||
shield-admin
|
||||
edge-admin
|
||||
31
go.mod
31
go.mod
@@ -1,27 +1,44 @@
|
||||
module github.com/TeaOSLab/EdgeAdmin
|
||||
|
||||
go 1.16
|
||||
go 1.18
|
||||
|
||||
replace github.com/TeaOSLab/EdgeCommon => ../EdgeCommon
|
||||
|
||||
require (
|
||||
github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000
|
||||
github.com/cespare/xxhash v1.1.0
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/frankban/quicktest v1.11.3 // indirect
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/iwind/TeaGo v0.0.0-20220304043459-0dd944a5b475
|
||||
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/miekg/dns v1.1.43
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/shirou/gopsutil/v3 v3.22.5
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/tealeg/xlsx/v3 v3.2.3
|
||||
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad
|
||||
google.golang.org/grpc v1.45.0
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/frankban/quicktest v1.11.3 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/btree v1.0.0 // indirect
|
||||
github.com/google/go-cmp v0.5.8 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kr/pretty v0.2.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/rogpeppe/fastuuid v1.2.0 // indirect
|
||||
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
)
|
||||
|
||||
2
go.sum
2
go.sum
@@ -12,8 +12,6 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
|
||||
@@ -84,10 +84,13 @@ func loadUserUIConfig() (*systemconfigs.UserUIConfig, error) {
|
||||
|
||||
func defaultUserUIConfig() *systemconfigs.UserUIConfig {
|
||||
return &systemconfigs.UserUIConfig{
|
||||
ProductName: "GoEdge",
|
||||
UserSystemName: "GoEdge用户系统",
|
||||
ShowOpenSourceInfo: true,
|
||||
ShowVersion: true,
|
||||
ShowFinance: true,
|
||||
ProductName: "GoEdge",
|
||||
UserSystemName: "GoEdge用户系统",
|
||||
ShowOpenSourceInfo: true,
|
||||
ShowVersion: true,
|
||||
ShowFinance: true,
|
||||
BandwidthUnit: systemconfigs.BandwidthUnitBit,
|
||||
ShowBandwidthCharts: true,
|
||||
ShowTrafficCharts: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@ import (
|
||||
// APIConfig API配置
|
||||
type APIConfig struct {
|
||||
RPC struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
DisableUpdate bool `yaml:"disableUpdate"`
|
||||
} `yaml:"rpc"`
|
||||
NodeId string `yaml:"nodeId"`
|
||||
Secret string `yaml:"secret"`
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
Version = "0.4.8"
|
||||
Version = "0.4.10"
|
||||
|
||||
APINodeVersion = "0.4.8"
|
||||
APINodeVersion = "0.4.10"
|
||||
|
||||
ProductName = "Edge Admin"
|
||||
ProcessName = "edge-admin"
|
||||
|
||||
@@ -424,6 +424,10 @@ func (this *RPCClient) UserAccessKeyRPC() pb.UserAccessKeyServiceClient {
|
||||
return pb.NewUserAccessKeyServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserIdentityRPC() pb.UserIdentityServiceClient {
|
||||
return pb.NewUserIdentityServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) LoginRPC() pb.LoginServiceClient {
|
||||
return pb.NewLoginServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
@@ -58,6 +58,16 @@ func (this *SyncAPINodesTask) Loop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
config, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 是否禁止自动升级
|
||||
if config.RPC.DisableUpdate {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 获取所有可用的节点
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
@@ -68,7 +78,7 @@ func (this *SyncAPINodesTask) Loop() error {
|
||||
return err
|
||||
}
|
||||
|
||||
newEndpoints := []string{}
|
||||
var newEndpoints = []string{}
|
||||
for _, node := range resp.ApiNodes {
|
||||
if !node.IsOn {
|
||||
continue
|
||||
@@ -77,10 +87,6 @@ func (this *SyncAPINodesTask) Loop() error {
|
||||
}
|
||||
|
||||
// 和现有的对比
|
||||
config, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if this.isSame(newEndpoints, config.RPC.Endpoints) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -27,14 +27,14 @@ func (this *UpdateAction) RunGet(params struct {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
admin := adminResp.Admin
|
||||
var admin = adminResp.Admin
|
||||
if admin == nil {
|
||||
this.NotFound("admin", params.AdminId)
|
||||
return
|
||||
}
|
||||
|
||||
// OTP认证
|
||||
otpLoginIsOn := false
|
||||
var otpLoginIsOn = false
|
||||
if admin.OtpLogin != nil {
|
||||
otpLoginIsOn = admin.OtpLogin.IsOn
|
||||
}
|
||||
@@ -45,7 +45,7 @@ func (this *UpdateAction) RunGet(params struct {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
countAccessKeys := countAccessKeyResp.Count
|
||||
var countAccessKeys = countAccessKeyResp.Count
|
||||
|
||||
this.Data["admin"] = maps.Map{
|
||||
"id": admin.Id,
|
||||
@@ -59,7 +59,7 @@ func (this *UpdateAction) RunGet(params struct {
|
||||
}
|
||||
|
||||
// 权限
|
||||
moduleMaps := configloaders.AllModuleMaps()
|
||||
var moduleMaps = configloaders.AllModuleMaps()
|
||||
for _, m := range moduleMaps {
|
||||
code := m.GetString("code")
|
||||
isChecked := false
|
||||
|
||||
@@ -2,11 +2,17 @@ package node
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
|
||||
"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/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"time"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
@@ -25,7 +31,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
node := nodeResp.ApiNode
|
||||
var node = nodeResp.ApiNode
|
||||
if node == nil {
|
||||
this.NotFound("apiNode", params.NodeId)
|
||||
return
|
||||
@@ -33,7 +39,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
|
||||
// 监听地址
|
||||
var hasHTTPS = false
|
||||
httpConfig := &serverconfigs.HTTPProtocolConfig{}
|
||||
var httpConfig = &serverconfigs.HTTPProtocolConfig{}
|
||||
if len(node.HttpJSON) > 0 {
|
||||
err = json.Unmarshal(node.HttpJSON, httpConfig)
|
||||
if err != nil {
|
||||
@@ -41,7 +47,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
return
|
||||
}
|
||||
}
|
||||
httpsConfig := &serverconfigs.HTTPSProtocolConfig{}
|
||||
var httpsConfig = &serverconfigs.HTTPSProtocolConfig{}
|
||||
if len(node.HttpsJSON) > 0 {
|
||||
err = json.Unmarshal(node.HttpsJSON, httpsConfig)
|
||||
if err != nil {
|
||||
@@ -52,21 +58,21 @@ func (this *IndexAction) RunGet(params struct {
|
||||
}
|
||||
|
||||
// 监听地址
|
||||
listens := []*serverconfigs.NetworkAddressConfig{}
|
||||
var listens = []*serverconfigs.NetworkAddressConfig{}
|
||||
listens = append(listens, httpConfig.Listen...)
|
||||
listens = append(listens, httpsConfig.Listen...)
|
||||
|
||||
// 证书信息
|
||||
certs := []*sslconfigs.SSLCertConfig{}
|
||||
var certs = []*sslconfigs.SSLCertConfig{}
|
||||
if httpsConfig.SSLPolicyRef != nil && httpsConfig.SSLPolicyRef.SSLPolicyId > 0 {
|
||||
sslPolicyConfigResp, err := this.RPC().SSLPolicyRPC().FindEnabledSSLPolicyConfig(this.AdminContext(), &pb.FindEnabledSSLPolicyConfigRequest{SslPolicyId: httpsConfig.SSLPolicyRef.SSLPolicyId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
sslPolicyConfigJSON := sslPolicyConfigResp.SslPolicyJSON
|
||||
var sslPolicyConfigJSON = sslPolicyConfigResp.SslPolicyJSON
|
||||
if len(sslPolicyConfigJSON) > 0 {
|
||||
sslPolicy := &sslconfigs.SSLPolicy{}
|
||||
var sslPolicy = &sslconfigs.SSLPolicy{}
|
||||
err = json.Unmarshal(sslPolicyConfigJSON, sslPolicy)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
@@ -77,7 +83,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
}
|
||||
|
||||
// 访问地址
|
||||
accessAddrs := []*serverconfigs.NetworkAddressConfig{}
|
||||
var accessAddrs = []*serverconfigs.NetworkAddressConfig{}
|
||||
if len(node.AccessAddrsJSON) > 0 {
|
||||
err = json.Unmarshal(node.AccessAddrsJSON, &accessAddrs)
|
||||
if err != nil {
|
||||
@@ -87,10 +93,10 @@ func (this *IndexAction) RunGet(params struct {
|
||||
}
|
||||
|
||||
// Rest地址
|
||||
restAccessAddrs := []*serverconfigs.NetworkAddressConfig{}
|
||||
var restAccessAddrs = []*serverconfigs.NetworkAddressConfig{}
|
||||
if node.RestIsOn {
|
||||
if len(node.RestHTTPJSON) > 0 {
|
||||
httpConfig := &serverconfigs.HTTPProtocolConfig{}
|
||||
var httpConfig = &serverconfigs.HTTPProtocolConfig{}
|
||||
err = json.Unmarshal(node.RestHTTPJSON, httpConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
@@ -102,7 +108,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
}
|
||||
|
||||
if len(node.RestHTTPSJSON) > 0 {
|
||||
httpsConfig := &serverconfigs.HTTPSProtocolConfig{}
|
||||
var httpsConfig = &serverconfigs.HTTPSProtocolConfig{}
|
||||
err = json.Unmarshal(node.RestHTTPSJSON, httpsConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
@@ -118,6 +124,27 @@ func (this *IndexAction) RunGet(params struct {
|
||||
}
|
||||
}
|
||||
|
||||
// 状态
|
||||
var status = &nodeconfigs.NodeStatus{}
|
||||
var statusIsValid = false
|
||||
this.Data["newVersion"] = ""
|
||||
if len(node.StatusJSON) > 0 {
|
||||
err = json.Unmarshal(node.StatusJSON, &status)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
if status.UpdatedAt >= time.Now().Unix()-300 {
|
||||
statusIsValid = true
|
||||
|
||||
// 是否为新版本
|
||||
if stringutil.VersionCompare(status.BuildVersion, teaconst.APINodeVersion) < 0 {
|
||||
this.Data["newVersion"] = teaconst.APINodeVersion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.Data["node"] = maps.Map{
|
||||
"id": node.Id,
|
||||
"name": node.Name,
|
||||
@@ -130,6 +157,26 @@ func (this *IndexAction) RunGet(params struct {
|
||||
"hasHTTPS": hasHTTPS,
|
||||
"certs": certs,
|
||||
"isPrimary": node.IsPrimary,
|
||||
"statusIsValid": statusIsValid,
|
||||
"status": maps.Map{
|
||||
"isActive": status.IsActive,
|
||||
"updatedAt": status.UpdatedAt,
|
||||
"hostname": status.Hostname,
|
||||
"cpuUsage": status.CPUUsage,
|
||||
"cpuUsageText": fmt.Sprintf("%.2f%%", status.CPUUsage*100),
|
||||
"memUsage": status.MemoryUsage,
|
||||
"memUsageText": fmt.Sprintf("%.2f%%", status.MemoryUsage*100),
|
||||
"connectionCount": status.ConnectionCount,
|
||||
"buildVersion": status.BuildVersion,
|
||||
"cpuPhysicalCount": status.CPUPhysicalCount,
|
||||
"cpuLogicalCount": status.CPULogicalCount,
|
||||
"load1m": numberutils.FormatFloat2(status.Load1m),
|
||||
"load5m": numberutils.FormatFloat2(status.Load5m),
|
||||
"load15m": numberutils.FormatFloat2(status.Load15m),
|
||||
"cacheTotalDiskSize": numberutils.FormatBytes(status.CacheTotalDiskSize),
|
||||
"cacheTotalMemorySize": numberutils.FormatBytes(status.CacheTotalMemorySize),
|
||||
"exePath": status.ExePath,
|
||||
},
|
||||
}
|
||||
|
||||
this.Show()
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/settings/dns"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/settings/ssh"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/settings/system"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/settings/thresholds"
|
||||
clusters "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/clusterutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
@@ -56,7 +55,6 @@ func init() {
|
||||
GetPost("/settings/system", new(system.IndexAction)).
|
||||
GetPost("/settings/ssh", new(ssh.IndexAction)).
|
||||
GetPost("/settings/ssh/test", new(ssh.TestAction)).
|
||||
GetPost("/settings/thresholds", new(thresholds.IndexAction)).
|
||||
GetPost("/settings/ddos-protection", new(ddosProtection.IndexAction)).
|
||||
Post("/settings/ddos-protection/status", new(ddosProtection.StatusAction)).
|
||||
|
||||
|
||||
@@ -321,6 +321,7 @@ func (this *DetailAction) RunGet(params struct {
|
||||
"load15m": numberutils.FormatFloat2(status.Load15m),
|
||||
"cacheTotalDiskSize": numberutils.FormatBytes(status.CacheTotalDiskSize),
|
||||
"cacheTotalMemorySize": numberutils.FormatBytes(status.CacheTotalMemorySize),
|
||||
"exePath": status.ExePath,
|
||||
},
|
||||
|
||||
"group": groupMap,
|
||||
|
||||
@@ -52,6 +52,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
this.Data["cnameRecords"] = dnsInfoResp.CnameRecords
|
||||
}
|
||||
this.Data["ttl"] = dnsInfoResp.Ttl
|
||||
this.Data["cnameAsDomain"] = dnsInfoResp.CnameAsDomain
|
||||
|
||||
this.Show()
|
||||
}
|
||||
@@ -65,6 +66,9 @@ func (this *IndexAction) RunPost(params struct {
|
||||
ServersAutoSync bool
|
||||
CnameRecords []string
|
||||
Ttl int32
|
||||
CnameAsDomain bool
|
||||
|
||||
ConfirmResetDomain bool // 是否确认重置域名
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
@@ -72,13 +76,15 @@ func (this *IndexAction) RunPost(params struct {
|
||||
// 创建日志
|
||||
defer this.CreateLog(oplogs.LevelInfo, "修改集群 %d DNS设置", params.ClusterId)
|
||||
|
||||
if params.DnsDomainId <= 0 {
|
||||
this.Fail("请选择集群的主域名")
|
||||
}
|
||||
if !params.ConfirmResetDomain {
|
||||
if params.DnsDomainId <= 0 {
|
||||
this.Fail("请选择集群的主域名")
|
||||
}
|
||||
|
||||
params.Must.
|
||||
Field("dnsName", params.DnsName).
|
||||
Require("请输入DNS子域名")
|
||||
params.Must.
|
||||
Field("dnsName", params.DnsName).
|
||||
Require("请输入DNS子域名")
|
||||
}
|
||||
|
||||
// 检查DNS名称
|
||||
if len(params.DnsName) > 0 {
|
||||
@@ -108,6 +114,7 @@ func (this *IndexAction) RunPost(params struct {
|
||||
ServersAutoSync: params.ServersAutoSync,
|
||||
CnameRecords: params.CnameRecords,
|
||||
Ttl: params.Ttl,
|
||||
CnameAsDomain: params.CnameAsDomain,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
|
||||
@@ -7,10 +7,8 @@ import (
|
||||
"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"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/health"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/message"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/metrics"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/services"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/thresholds"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/toa"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/waf"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/webp"
|
||||
@@ -42,12 +40,6 @@ func init() {
|
||||
GetPost("", new(dns.IndexAction)).
|
||||
Post("/randomName", new(dns.RandomNameAction)).
|
||||
|
||||
// 消息
|
||||
Prefix("/clusters/cluster/settings/message").
|
||||
GetPost("", new(message.IndexAction)).
|
||||
Get("/selectReceiverPopup", new(message.SelectReceiverPopupAction)).
|
||||
Post("/selectedReceivers", new(message.SelectedReceiversAction)).
|
||||
|
||||
// TOA
|
||||
Prefix("/clusters/cluster/settings/toa").
|
||||
GetPost("", new(toa.IndexAction)).
|
||||
@@ -64,13 +56,6 @@ func init() {
|
||||
GetPost("/updatePopup", new(firewallActions.UpdatePopupAction)).
|
||||
Post("/delete", new(firewallActions.DeleteAction)).
|
||||
|
||||
// 阈值
|
||||
Prefix("/clusters/cluster/settings/thresholds").
|
||||
Get("", new(thresholds.IndexAction)).
|
||||
GetPost("/createPopup", new(thresholds.CreatePopupAction)).
|
||||
GetPost("/updatePopup", new(thresholds.UpdatePopupAction)).
|
||||
Post("/delete", new(thresholds.DeleteAction)).
|
||||
|
||||
// 指标
|
||||
Prefix("/clusters/cluster/settings/metrics").
|
||||
Get("", new(metrics.IndexAction)).
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
package message
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "setting", "")
|
||||
this.SecondMenu("message")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct{}) {
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunPost(params struct {
|
||||
ClusterId int64
|
||||
ReceiversJSON []byte
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
defer this.CreateLogInfo("修改集群 %d 消息接收人", params.ClusterId)
|
||||
|
||||
receiverMaps := []maps.Map{}
|
||||
if len(params.ReceiversJSON) > 0 {
|
||||
err := json.Unmarshal(params.ReceiversJSON, &receiverMaps)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
pbReceiverOptions := &pb.UpdateMessageReceiversRequest_RecipientOptions{}
|
||||
for _, receiverMap := range receiverMaps {
|
||||
recipientId := int64(0)
|
||||
groupId := int64(0)
|
||||
receiverType := receiverMap.GetString("type")
|
||||
switch receiverType {
|
||||
case "recipient":
|
||||
recipientId = receiverMap.GetInt64("id")
|
||||
case "group":
|
||||
groupId = receiverMap.GetInt64("id")
|
||||
default:
|
||||
continue
|
||||
}
|
||||
pbReceiverOptions.RecipientOptions = append(pbReceiverOptions.RecipientOptions, &pb.UpdateMessageReceiversRequest_RecipientOption{
|
||||
MessageRecipientId: recipientId,
|
||||
MessageRecipientGroupId: groupId,
|
||||
})
|
||||
}
|
||||
|
||||
_, err := this.RPC().MessageReceiverRPC().UpdateMessageReceivers(this.AdminContext(), &pb.UpdateMessageReceiversRequest{
|
||||
NodeClusterId: params.ClusterId,
|
||||
NodeId: 0,
|
||||
ServerId: 0,
|
||||
ParamsJSON: nil,
|
||||
RecipientOptions: map[string]*pb.UpdateMessageReceiversRequest_RecipientOptions{
|
||||
"*": pbReceiverOptions,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package message
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type SelectReceiverPopupAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *SelectReceiverPopupAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *SelectReceiverPopupAction) RunGet(params struct {
|
||||
RecipientIds string
|
||||
GroupIds string
|
||||
}) {
|
||||
recipientIds := utils.SplitNumbers(params.RecipientIds)
|
||||
groupIds := utils.SplitNumbers(params.GroupIds)
|
||||
|
||||
// 所有接收人
|
||||
recipientsResp, err := this.RPC().MessageRecipientRPC().ListEnabledMessageRecipients(this.AdminContext(), &pb.ListEnabledMessageRecipientsRequest{
|
||||
AdminId: 0,
|
||||
MediaType: "",
|
||||
MessageRecipientGroupId: 0,
|
||||
Keyword: "",
|
||||
Offset: 0,
|
||||
Size: 1000, // TODO 支持搜索
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
recipientMaps := []maps.Map{}
|
||||
for _, recipient := range recipientsResp.MessageRecipients {
|
||||
if !recipient.IsOn {
|
||||
continue
|
||||
}
|
||||
if lists.ContainsInt64(recipientIds, recipient.Id) {
|
||||
continue
|
||||
}
|
||||
recipientMaps = append(recipientMaps, maps.Map{
|
||||
"id": recipient.Id,
|
||||
"name": recipient.Admin.Fullname,
|
||||
"instanceName": recipient.MessageMediaInstance.Name,
|
||||
})
|
||||
}
|
||||
this.Data["recipients"] = recipientMaps
|
||||
|
||||
// 所有分组
|
||||
groupsResp, err := this.RPC().MessageRecipientGroupRPC().FindAllEnabledMessageRecipientGroups(this.AdminContext(), &pb.FindAllEnabledMessageRecipientGroupsRequest{})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
groupMaps := []maps.Map{}
|
||||
for _, group := range groupsResp.MessageRecipientGroups {
|
||||
if !group.IsOn {
|
||||
continue
|
||||
}
|
||||
if lists.ContainsInt64(groupIds, group.Id) {
|
||||
continue
|
||||
}
|
||||
groupMaps = append(groupMaps, maps.Map{
|
||||
"id": group.Id,
|
||||
"name": group.Name,
|
||||
})
|
||||
}
|
||||
this.Data["groups"] = groupMaps
|
||||
|
||||
this.Show()
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package message
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type SelectedReceiversAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *SelectedReceiversAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *SelectedReceiversAction) RunPost(params struct {
|
||||
ClusterId int64
|
||||
NodeId int64
|
||||
ServerId int64
|
||||
}) {
|
||||
receiversResp, err := this.RPC().MessageReceiverRPC().FindAllEnabledMessageReceivers(this.AdminContext(), &pb.FindAllEnabledMessageReceiversRequest{
|
||||
NodeClusterId: params.ClusterId,
|
||||
NodeId: params.NodeId,
|
||||
ServerId: params.ServerId,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
receiverMaps := []maps.Map{}
|
||||
for _, receiver := range receiversResp.MessageReceivers {
|
||||
id := int64(0)
|
||||
name := ""
|
||||
receiverType := ""
|
||||
subName := ""
|
||||
if receiver.MessageRecipient != nil {
|
||||
id = receiver.MessageRecipient.Id
|
||||
name = receiver.MessageRecipient.Admin.Fullname
|
||||
subName = receiver.MessageRecipient.MessageMediaInstance.Name
|
||||
receiverType = "recipient"
|
||||
} else if receiver.MessageRecipientGroup != nil {
|
||||
id = receiver.MessageRecipientGroup.Id
|
||||
name = receiver.MessageRecipientGroup.Name
|
||||
receiverType = "group"
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
receiverMaps = append(receiverMaps, maps.Map{
|
||||
"id": id,
|
||||
"name": name,
|
||||
"subName": subName,
|
||||
"type": receiverType,
|
||||
})
|
||||
}
|
||||
this.Data["receivers"] = receiverMaps
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package thresholds
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
)
|
||||
|
||||
type CreatePopupAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *CreatePopupAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *CreatePopupAction) RunGet(params struct {
|
||||
ClusterId int64
|
||||
NodeId int64
|
||||
}) {
|
||||
this.Data["clusterId"] = params.ClusterId
|
||||
this.Data["nodeId"] = params.NodeId
|
||||
this.Data["items"] = nodeconfigs.FindAllNodeValueItemDefinitions()
|
||||
this.Data["operators"] = nodeconfigs.FindAllNodeValueOperatorDefinitions()
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *CreatePopupAction) RunPost(params struct {
|
||||
ClusterId int64
|
||||
NodeId int64
|
||||
Item string
|
||||
Param string
|
||||
SumMethod string
|
||||
Operator string
|
||||
Value string
|
||||
Duration int32
|
||||
DurationUnit string
|
||||
Message string
|
||||
NotifyDuration int32
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
if params.ClusterId <= 0 && params.NodeId >= 0 {
|
||||
this.Fail("集群或者节点至少需要填写其中一个参数")
|
||||
}
|
||||
|
||||
valueJSON, err := json.Marshal(params.Value)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
resp, err := this.RPC().NodeThresholdRPC().CreateNodeThreshold(this.AdminContext(), &pb.CreateNodeThresholdRequest{
|
||||
Role: "node",
|
||||
NodeClusterId: params.ClusterId,
|
||||
NodeId: params.NodeId,
|
||||
Item: params.Item,
|
||||
Param: params.Param,
|
||||
Operator: params.Operator,
|
||||
ValueJSON: valueJSON,
|
||||
Message: params.Message,
|
||||
Duration: params.Duration,
|
||||
DurationUnit: params.DurationUnit,
|
||||
SumMethod: params.SumMethod,
|
||||
NotifyDuration: params.NotifyDuration,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
defer this.CreateLogInfo("创建节点阈值 %d", resp.NodeThresholdId)
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package thresholds
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
)
|
||||
|
||||
// DeleteAction 删除阈值
|
||||
type DeleteAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *DeleteAction) RunPost(params struct {
|
||||
ThresholdId int64
|
||||
}) {
|
||||
defer this.CreateLogInfo("删除阈值 %d", params.ThresholdId)
|
||||
|
||||
_, err := this.RPC().NodeThresholdRPC().DeleteNodeThreshold(this.AdminContext(), &pb.DeleteNodeThresholdRequest{NodeThresholdId: params.ThresholdId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package thresholds
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "setting", "setting")
|
||||
this.SecondMenu("threshold")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct {
|
||||
ClusterId int64
|
||||
}) {
|
||||
// 列出所有阈值
|
||||
thresholdsResp, err := this.RPC().NodeThresholdRPC().FindAllEnabledNodeThresholds(this.AdminContext(), &pb.FindAllEnabledNodeThresholdsRequest{
|
||||
Role: "node",
|
||||
NodeClusterId: params.ClusterId,
|
||||
NodeId: 0,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
thresholdMaps := []maps.Map{}
|
||||
for _, threshold := range thresholdsResp.NodeThresholds {
|
||||
var nodeMap maps.Map = nil
|
||||
if threshold.Node != nil {
|
||||
nodeMap = maps.Map{
|
||||
"id": threshold.Node.Id,
|
||||
"name": threshold.Node.Name,
|
||||
}
|
||||
}
|
||||
|
||||
thresholdMaps = append(thresholdMaps, maps.Map{
|
||||
"id": threshold.Id,
|
||||
"itemName": nodeconfigs.FindNodeValueItemName(threshold.Item),
|
||||
"paramName": nodeconfigs.FindNodeValueItemParamName(threshold.Item, threshold.Param),
|
||||
"operatorName": nodeconfigs.FindNodeValueOperatorName(threshold.Operator),
|
||||
"value": nodeconfigs.UnmarshalNodeValue(threshold.ValueJSON),
|
||||
"sumMethodName": nodeconfigs.FindNodeValueSumMethodName(threshold.SumMethod),
|
||||
"duration": threshold.Duration,
|
||||
"durationUnitName": nodeconfigs.FindNodeValueDurationUnitName(threshold.DurationUnit),
|
||||
"isOn": threshold.IsOn,
|
||||
"node": nodeMap,
|
||||
})
|
||||
}
|
||||
this.Data["thresholds"] = thresholdMaps
|
||||
|
||||
this.Show()
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package thresholds
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type UpdatePopupAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *UpdatePopupAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *UpdatePopupAction) RunGet(params struct {
|
||||
ThresholdId int64
|
||||
}) {
|
||||
// 通用参数
|
||||
this.Data["items"] = nodeconfigs.FindAllNodeValueItemDefinitions()
|
||||
this.Data["operators"] = nodeconfigs.FindAllNodeValueOperatorDefinitions()
|
||||
|
||||
// 阈值详情
|
||||
thresholdResp, err := this.RPC().NodeThresholdRPC().FindEnabledNodeThreshold(this.AdminContext(), &pb.FindEnabledNodeThresholdRequest{NodeThresholdId: params.ThresholdId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
threshold := thresholdResp.NodeThreshold
|
||||
if threshold == nil {
|
||||
this.NotFound("nodeThreshold", params.ThresholdId)
|
||||
return
|
||||
}
|
||||
|
||||
valueInterface := new(interface{})
|
||||
err = json.Unmarshal(threshold.ValueJSON, valueInterface)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["threshold"] = maps.Map{
|
||||
"id": threshold.Id,
|
||||
"item": threshold.Item,
|
||||
"param": threshold.Param,
|
||||
"message": threshold.Message,
|
||||
"notifyDuration": threshold.NotifyDuration,
|
||||
"value": nodeconfigs.UnmarshalNodeValue(threshold.ValueJSON),
|
||||
"operator": threshold.Operator,
|
||||
"duration": threshold.Duration,
|
||||
"durationUnit": threshold.DurationUnit,
|
||||
"isOn": threshold.IsOn,
|
||||
}
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *UpdatePopupAction) RunPost(params struct {
|
||||
ThresholdId int64
|
||||
Item string
|
||||
Param string
|
||||
SumMethod string
|
||||
Operator string
|
||||
Value string
|
||||
Duration int32
|
||||
DurationUnit string
|
||||
Message string
|
||||
NotifyDuration int32
|
||||
IsOn bool
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
defer this.CreateLogInfo("修改节点阈值 %d", params.ThresholdId)
|
||||
|
||||
valueJSON, err := json.Marshal(params.Value)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
_, err = this.RPC().NodeThresholdRPC().UpdateNodeThreshold(this.AdminContext(), &pb.UpdateNodeThresholdRequest{
|
||||
NodeThresholdId: params.ThresholdId,
|
||||
Item: params.Item,
|
||||
Param: params.Param,
|
||||
Operator: params.Operator,
|
||||
ValueJSON: valueJSON,
|
||||
Message: params.Message,
|
||||
NotifyDuration: params.NotifyDuration,
|
||||
Duration: params.Duration,
|
||||
DurationUnit: params.DurationUnit,
|
||||
SumMethod: params.SumMethod,
|
||||
IsOn: params.IsOn,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -22,7 +22,7 @@ func NewClusterHelper() *ClusterHelper {
|
||||
}
|
||||
|
||||
func (this *ClusterHelper) BeforeAction(actionPtr actions.ActionWrapper) (goNext bool) {
|
||||
action := actionPtr.Object()
|
||||
var action = actionPtr.Object()
|
||||
if action.Request.Method != http.MethodGet {
|
||||
return true
|
||||
}
|
||||
@@ -57,7 +57,7 @@ func (this *ClusterHelper) BeforeAction(actionPtr actions.ActionWrapper) (goNext
|
||||
return
|
||||
}
|
||||
|
||||
tabbar := actionutils.NewTabbar()
|
||||
var tabbar = actionutils.NewTabbar()
|
||||
tabbar.Add("集群列表", "", "/clusters", "", false)
|
||||
if teaconst.IsPlus {
|
||||
tabbar.Add("集群看板", "", "/clusters/cluster/boards?clusterId="+clusterIdString, "board", selectedTabbar == "board")
|
||||
@@ -118,10 +118,25 @@ func (this *ClusterHelper) createSettingMenu(cluster *pb.NodeCluster, info *pb.F
|
||||
})
|
||||
|
||||
items = append(items, maps.Map{
|
||||
"name": "DDoS防护",
|
||||
"url": "/clusters/cluster/settings/ddos-protection?clusterId=" + clusterId,
|
||||
"isActive": selectedItem == "ddosProtection",
|
||||
"isOn": info != nil && info.HasDDoSProtection,
|
||||
"name": "WebP",
|
||||
"url": "/clusters/cluster/settings/webp?clusterId=" + clusterId,
|
||||
"isActive": selectedItem == "webp",
|
||||
"isOn": info != nil && info.WebpIsOn,
|
||||
})
|
||||
|
||||
items = filterMenuItems1(items, info, clusterId, selectedItem)
|
||||
|
||||
items = append(items, maps.Map{
|
||||
"name": "-",
|
||||
"url": "",
|
||||
"isActive": false,
|
||||
})
|
||||
|
||||
items = append(items, maps.Map{
|
||||
"name": "DNS设置",
|
||||
"url": "/clusters/cluster/settings/dns?clusterId=" + clusterId,
|
||||
"isActive": selectedItem == "dns",
|
||||
"isOn": cluster.DnsDomainId > 0 || len(cluster.DnsName) > 0,
|
||||
})
|
||||
|
||||
items = append(items, maps.Map{
|
||||
@@ -132,16 +147,10 @@ func (this *ClusterHelper) createSettingMenu(cluster *pb.NodeCluster, info *pb.F
|
||||
})
|
||||
|
||||
items = append(items, maps.Map{
|
||||
"name": "DNS设置",
|
||||
"url": "/clusters/cluster/settings/dns?clusterId=" + clusterId,
|
||||
"isActive": selectedItem == "dns",
|
||||
"isOn": cluster.DnsDomainId > 0 || len(cluster.DnsName) > 0,
|
||||
})
|
||||
items = append(items, maps.Map{
|
||||
"name": "WebP",
|
||||
"url": "/clusters/cluster/settings/webp?clusterId=" + clusterId,
|
||||
"isActive": selectedItem == "webp",
|
||||
"isOn": info != nil && info.WebpIsOn,
|
||||
"name": "DDoS防护",
|
||||
"url": "/clusters/cluster/settings/ddos-protection?clusterId=" + clusterId,
|
||||
"isActive": selectedItem == "ddosProtection",
|
||||
"isOn": info != nil && info.HasDDoSProtection,
|
||||
})
|
||||
|
||||
items = append(items, maps.Map{
|
||||
@@ -155,21 +164,7 @@ func (this *ClusterHelper) createSettingMenu(cluster *pb.NodeCluster, info *pb.F
|
||||
"isOn": info != nil && info.HasMetricItems,
|
||||
})
|
||||
|
||||
if teaconst.IsPlus {
|
||||
items = append(items, maps.Map{
|
||||
"name": "阈值设置",
|
||||
"url": "/clusters/cluster/settings/thresholds?clusterId=" + clusterId,
|
||||
"isActive": selectedItem == "threshold",
|
||||
"isOn": info != nil && info.HasThresholds,
|
||||
})
|
||||
|
||||
items = append(items, maps.Map{
|
||||
"name": "消息通知",
|
||||
"url": "/clusters/cluster/settings/message?clusterId=" + clusterId,
|
||||
"isActive": selectedItem == "message",
|
||||
"isOn": info != nil && info.HasMessageReceivers,
|
||||
})
|
||||
}
|
||||
items = filterMenuItems2(items, info, clusterId, selectedItem)
|
||||
|
||||
items = append(items, maps.Map{
|
||||
"name": "-",
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build !plus
|
||||
// +build !plus
|
||||
|
||||
package clusterutils
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
func filterMenuItems1(items []maps.Map, info *pb.FindEnabledNodeClusterConfigInfoResponse, clusterIdString string, selectedItem string) []maps.Map {
|
||||
return items
|
||||
}
|
||||
|
||||
func filterMenuItems2(items []maps.Map, info *pb.FindEnabledNodeClusterConfigInfoResponse, clusterIdString string, selectedItem string) []maps.Map {
|
||||
return items
|
||||
}
|
||||
169
internal/web/actions/default/dashboard/dashboardutils/utils.go
Normal file
169
internal/web/actions/default/dashboard/dashboardutils/utils.go
Normal file
@@ -0,0 +1,169 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package dashboardutils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils/sizes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"github.com/shirou/gopsutil/v3/disk"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// CheckDiskPartitions 检查服务器磁盘空间
|
||||
func CheckDiskPartitions(thresholdPercent float64) (path string, usage uint64, usagePercent float64, shouldWarning bool) {
|
||||
partitions, err := disk.Partitions(false)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !lists.ContainsString([]string{"darwin", "linux", "freebsd"}, runtime.GOOS) {
|
||||
return
|
||||
}
|
||||
|
||||
var rootFS = ""
|
||||
|
||||
for _, p := range partitions {
|
||||
if p.Mountpoint == "/" {
|
||||
rootFS = p.Fstype
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for _, p := range partitions {
|
||||
if p.Mountpoint == "/boot" {
|
||||
continue
|
||||
}
|
||||
if p.Fstype != rootFS {
|
||||
continue
|
||||
}
|
||||
stat, _ := disk.Usage(p.Mountpoint)
|
||||
if stat != nil {
|
||||
if stat.Used < 2*uint64(sizes.G) {
|
||||
continue
|
||||
}
|
||||
if stat.UsedPercent > thresholdPercent {
|
||||
path = stat.Path
|
||||
usage = stat.Used
|
||||
usagePercent = stat.UsedPercent
|
||||
shouldWarning = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CheckLocalAPINode 检查本地的API节点
|
||||
func CheckLocalAPINode(rpcClient *rpc.RPCClient, ctx context.Context) (exePath string, runtimeVersion string, fileVersion string, ok bool) {
|
||||
resp, err := rpcClient.APINodeRPC().FindCurrentAPINode(ctx, &pb.FindCurrentAPINodeRequest{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if resp.ApiNode == nil {
|
||||
return
|
||||
}
|
||||
var instanceCode = resp.ApiNode.InstanceCode
|
||||
if len(instanceCode) == 0 {
|
||||
return
|
||||
}
|
||||
var statusJSON = resp.ApiNode.StatusJSON
|
||||
if len(statusJSON) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var status = &nodeconfigs.NodeStatus{}
|
||||
err = json.Unmarshal(statusJSON, status)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
runtimeVersion = status.BuildVersion
|
||||
|
||||
if len(runtimeVersion) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if stringutil.VersionCompare(runtimeVersion, teaconst.APINodeVersion) >= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
exePath = status.ExePath
|
||||
if len(exePath) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
stat, err := os.Stat(exePath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if stat.IsDir() {
|
||||
return
|
||||
}
|
||||
|
||||
// 实例信息
|
||||
{
|
||||
var outputBuffer = &bytes.Buffer{}
|
||||
var cmd = exec.Command(exePath, "instance")
|
||||
cmd.Stdout = outputBuffer
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var outputBytes = outputBuffer.Bytes()
|
||||
if len(outputBytes) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var instanceMap = maps.Map{}
|
||||
err = json.Unmarshal(bytes.TrimSpace(outputBytes), &instanceMap)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if instanceMap.GetString("code") != instanceCode {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 文件版本
|
||||
{
|
||||
var outputBuffer = &bytes.Buffer{}
|
||||
var cmd = exec.Command(exePath, "-v")
|
||||
cmd.Stdout = outputBuffer
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var outputString = outputBuffer.String()
|
||||
if len(outputString) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var subMatch = regexp.MustCompile(`\s+v([\d.]+)\s+`).FindStringSubmatch(outputString)
|
||||
if len(subMatch) == 0 {
|
||||
return
|
||||
}
|
||||
fileVersion = subMatch[1]
|
||||
|
||||
// 文件版本是否为最新
|
||||
if fileVersion != teaconst.APINodeVersion {
|
||||
fileVersion = runtimeVersion
|
||||
}
|
||||
}
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
@@ -7,16 +7,13 @@ import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils/sizes"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dashboard/dashboardutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"github.com/shirou/gopsutil/v3/disk"
|
||||
"regexp"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
@@ -66,7 +63,7 @@ func (this *IndexAction) RunPost(params struct{}) {
|
||||
|
||||
// 检查当前服务器空间
|
||||
var diskUsageWarning = ""
|
||||
diskPath, diskUsage, diskUsagePercent, shouldWarning := this.checkDiskPartitions(90)
|
||||
diskPath, diskUsage, diskUsagePercent, shouldWarning := dashboardutils.CheckDiskPartitions(90)
|
||||
if shouldWarning {
|
||||
diskUsageWarning = "当前服务器磁盘空间不足,请立即扩充容量,文件路径:" + diskPath + ",已使用:" + types.String(diskUsage/1024/1024/1024) + "G,已使用比例:" + fmt.Sprintf("%.2f%%", diskUsagePercent) + ",仅剩余空间:" + fmt.Sprintf("%.2f%%", 100-diskUsagePercent) + "。"
|
||||
}
|
||||
@@ -263,49 +260,18 @@ func (this *IndexAction) RunPost(params struct{}) {
|
||||
this.Data["metricCharts"] = chartMaps
|
||||
}
|
||||
|
||||
// 当前API节点版本
|
||||
{
|
||||
exePath, runtimeVersion, fileVersion, ok := dashboardutils.CheckLocalAPINode(this.RPC(), this.AdminContext())
|
||||
if ok {
|
||||
this.Data["localLowerVersionAPINode"] = maps.Map{
|
||||
"exePath": exePath,
|
||||
"runtimeVersion": runtimeVersion,
|
||||
"fileVersion": fileVersion,
|
||||
"isRestarting": false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
|
||||
// 检查服务器磁盘空间
|
||||
func (this *IndexAction) checkDiskPartitions(thresholdPercent float64) (path string, usage uint64, usagePercent float64, shouldWarning bool) {
|
||||
partitions, err := disk.Partitions(false)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !lists.ContainsString([]string{"darwin", "linux", "freebsd"}, runtime.GOOS) {
|
||||
return
|
||||
}
|
||||
|
||||
var rootFS = ""
|
||||
|
||||
for _, p := range partitions {
|
||||
if p.Mountpoint == "/" {
|
||||
rootFS = p.Fstype
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for _, p := range partitions {
|
||||
if p.Mountpoint == "/boot" {
|
||||
continue
|
||||
}
|
||||
if p.Fstype != rootFS {
|
||||
continue
|
||||
}
|
||||
stat, _ := disk.Usage(p.Mountpoint)
|
||||
if stat != nil {
|
||||
if stat.Used < 2*uint64(sizes.G) {
|
||||
continue
|
||||
}
|
||||
if stat.UsedPercent > thresholdPercent {
|
||||
path = stat.Path
|
||||
usage = stat.Used
|
||||
usagePercent = stat.UsedPercent
|
||||
shouldWarning = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ func init() {
|
||||
Data("teaMenu", "dashboard").
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeCommon)).
|
||||
GetPost("", new(IndexAction)).
|
||||
Post("/restartLocalAPINode", new(RestartLocalAPINodeAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package dashboard
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RestartLocalAPINodeAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *RestartLocalAPINodeAction) RunPost(params struct {
|
||||
ExePath string
|
||||
}) {
|
||||
// 检查当前用户是超级用户
|
||||
adminResp, err := this.RPC().AdminRPC().FindEnabledAdmin(this.AdminContext(), &pb.FindEnabledAdminRequest{AdminId: this.AdminId()})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if adminResp.Admin == nil || !adminResp.Admin.IsSuper {
|
||||
this.Fail("请切换到超级用户进行此操作")
|
||||
}
|
||||
|
||||
var exePath = params.ExePath
|
||||
if len(exePath) == 0 {
|
||||
this.Fail("找不到要重启的API节点文件")
|
||||
}
|
||||
|
||||
{
|
||||
var stdoutBuffer = &bytes.Buffer{}
|
||||
var cmd = exec.Command(exePath, "restart")
|
||||
cmd.Stdout = stdoutBuffer
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
this.Fail("运行失败:输出:" + stdoutBuffer.String())
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否已启动
|
||||
var countTries = 120
|
||||
for {
|
||||
countTries--
|
||||
if countTries < 0 {
|
||||
this.Fail("启动超时,请尝试手动启动")
|
||||
break
|
||||
}
|
||||
|
||||
var stdoutBuffer = &bytes.Buffer{}
|
||||
var cmd = exec.Command(exePath, "status")
|
||||
cmd.Stdout = stdoutBuffer
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
if regexp.MustCompile(`pid:\s*\d+`).
|
||||
MatchString(stdoutBuffer.String()) {
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -78,10 +78,6 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
ParamHuaweiAccessKeyId string
|
||||
ParamHuaweiAccessKeySecret string
|
||||
|
||||
// DNS.COM
|
||||
ParamApiKey string
|
||||
ParamApiSecret string
|
||||
|
||||
// CloudFlare
|
||||
ParamCloudFlareAPIKey string
|
||||
ParamCloudFlareEmail string
|
||||
@@ -133,15 +129,6 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
|
||||
apiParams["accessKeyId"] = params.ParamHuaweiAccessKeyId
|
||||
apiParams["accessKeySecret"] = params.ParamHuaweiAccessKeySecret
|
||||
case "dnscom":
|
||||
params.Must.
|
||||
Field("paramApiKey", params.ParamApiKey).
|
||||
Require("请输入ApiKey").
|
||||
Field("paramApiSecret", params.ParamApiSecret).
|
||||
Require("请输入ApiSecret")
|
||||
|
||||
apiParams["apiKey"] = params.ParamApiKey
|
||||
apiParams["apiSecret"] = params.ParamApiSecret
|
||||
case "cloudFlare":
|
||||
params.Must.
|
||||
Field("paramCloudFlareAPIKey", params.ParamCloudFlareAPIKey).
|
||||
|
||||
@@ -105,10 +105,6 @@ func (this *UpdatePopupAction) RunPost(params struct {
|
||||
ParamHuaweiAccessKeyId string
|
||||
ParamHuaweiAccessKeySecret string
|
||||
|
||||
// DNS.COM
|
||||
ParamApiKey string
|
||||
ParamApiSecret string
|
||||
|
||||
// CloudFlare
|
||||
ParamCloudFlareAPIKey string
|
||||
ParamCloudFlareEmail string
|
||||
@@ -162,15 +158,6 @@ func (this *UpdatePopupAction) RunPost(params struct {
|
||||
|
||||
apiParams["accessKeyId"] = params.ParamHuaweiAccessKeyId
|
||||
apiParams["accessKeySecret"] = params.ParamHuaweiAccessKeySecret
|
||||
case "dnscom":
|
||||
params.Must.
|
||||
Field("paramApiKey", params.ParamApiKey).
|
||||
Require("请输入ApiKey").
|
||||
Field("paramApiSecret", params.ParamApiSecret).
|
||||
Require("请输入ApiSecret")
|
||||
|
||||
apiParams["apiKey"] = params.ParamApiKey
|
||||
apiParams["apiSecret"] = params.ParamApiSecret
|
||||
case "cloudFlare":
|
||||
params.Must.
|
||||
Field("paramCloudFlareAPIKey", params.ParamCloudFlareAPIKey).
|
||||
|
||||
62
internal/web/actions/default/files/file.go
Normal file
62
internal/web/actions/default/files/file.go
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package files
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"mime"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type FileAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *FileAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *FileAction) RunGet(params struct {
|
||||
FileId int64
|
||||
}) {
|
||||
fileResp, err := this.RPC().FileRPC().FindEnabledFile(this.AdminContext(), &pb.FindEnabledFileRequest{FileId: params.FileId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
var file = fileResp.File
|
||||
if file == nil {
|
||||
this.NotFound("File", params.FileId)
|
||||
return
|
||||
}
|
||||
|
||||
chunkIdsResp, err := this.RPC().FileChunkRPC().FindAllFileChunkIds(this.AdminContext(), &pb.FindAllFileChunkIdsRequest{FileId: file.Id})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.AddHeader("Content-Length", types.String(file.Size))
|
||||
if len(file.MimeType) > 0 {
|
||||
this.AddHeader("Content-Type", file.MimeType)
|
||||
} else if len(file.Filename) > 0 {
|
||||
var ext = filepath.Ext(file.Filename)
|
||||
var mimeType = mime.TypeByExtension(ext)
|
||||
this.AddHeader("Content-Type", mimeType)
|
||||
}
|
||||
|
||||
for _, chunkId := range chunkIdsResp.FileChunkIds {
|
||||
chunkResp, err := this.RPC().FileChunkRPC().DownloadFileChunk(this.AdminContext(), &pb.DownloadFileChunkRequest{FileChunkId: chunkId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if chunkResp.FileChunk == nil {
|
||||
continue
|
||||
}
|
||||
this.Write(chunkResp.FileChunk.Data)
|
||||
}
|
||||
}
|
||||
14
internal/web/actions/default/files/init.go
Normal file
14
internal/web/actions/default/files/init.go
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package files
|
||||
|
||||
import "github.com/iwind/TeaGo"
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Prefix("/files").
|
||||
Get("/file", new(FileAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -102,7 +102,8 @@ func SendMessageToCluster(ctx context.Context, clusterId int64, code string, msg
|
||||
|
||||
apiRPCClient, err := rpc.NewRPCClient(&configs.APIConfig{
|
||||
RPC: struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
DisableUpdate bool `yaml:"disableUpdate"`
|
||||
}{
|
||||
Endpoints: apiNode.AccessAddrs,
|
||||
},
|
||||
@@ -282,7 +283,8 @@ func SendMessageToNodeIds(ctx context.Context, nodeIds []int64, code string, msg
|
||||
|
||||
apiRPCClient, err := rpc.NewRPCClient(&configs.APIConfig{
|
||||
RPC: struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
DisableUpdate bool `yaml:"disableUpdate"`
|
||||
}{
|
||||
Endpoints: apiNode.AccessAddrs,
|
||||
},
|
||||
|
||||
@@ -36,7 +36,8 @@ func (this *UpdateHostsAction) RunPost(params struct {
|
||||
|
||||
client, err := rpc.NewRPCClient(&configs.APIConfig{
|
||||
RPC: struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
DisableUpdate bool `yaml:"disableUpdate"`
|
||||
}{
|
||||
Endpoints: []string{params.Protocol + "://" + configutils.QuoteIP(params.Host) + ":" + params.Port},
|
||||
},
|
||||
@@ -167,7 +168,8 @@ func (this *UpdateHostsAction) RunPost(params struct {
|
||||
// 修改api.yaml
|
||||
var apiConfig = &configs.APIConfig{
|
||||
RPC: struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
DisableUpdate bool `yaml:"disableUpdate"`
|
||||
}{
|
||||
Endpoints: endpoints,
|
||||
},
|
||||
|
||||
@@ -43,7 +43,8 @@ func (this *ValidateApiAction) RunPost(params struct {
|
||||
Require("请输入节点secret")
|
||||
client, err := rpc.NewRPCClient(&configs.APIConfig{
|
||||
RPC: struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
DisableUpdate bool `yaml:"disableUpdate"`
|
||||
}{
|
||||
Endpoints: []string{params.Protocol + "://" + configutils.QuoteIP(params.Host) + ":" + params.Port},
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package servers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
@@ -33,6 +34,10 @@ func (this *AddOriginPopupAction) RunPost(params struct {
|
||||
Protocol string
|
||||
Addr string
|
||||
|
||||
DomainsJSON []byte
|
||||
Host string
|
||||
FollowPort bool
|
||||
|
||||
Must *actions.Must
|
||||
}) {
|
||||
params.Must.
|
||||
@@ -50,7 +55,7 @@ func (this *AddOriginPopupAction) RunPost(params struct {
|
||||
}
|
||||
|
||||
addr = regexp.MustCompile(`\s+`).ReplaceAllString(addr, "")
|
||||
portIndex := strings.LastIndex(addr, ":")
|
||||
var portIndex = strings.LastIndex(addr, ":")
|
||||
if portIndex < 0 {
|
||||
if params.Protocol == "http" {
|
||||
addr += ":80"
|
||||
@@ -82,6 +87,21 @@ func (this *AddOriginPopupAction) RunPost(params struct {
|
||||
}
|
||||
}
|
||||
|
||||
// 专属域名
|
||||
var domains = []string{}
|
||||
if len(params.DomainsJSON) > 0 {
|
||||
err := json.Unmarshal(params.DomainsJSON, &domains)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 去除可能误加的斜杠
|
||||
for index, domain := range domains {
|
||||
domains[index] = strings.TrimSuffix(domain, "/")
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := this.RPC().OriginRPC().CreateOrigin(this.AdminContext(), &pb.CreateOriginRequest{
|
||||
Name: "",
|
||||
Addr: &pb.NetworkAddress{
|
||||
@@ -92,6 +112,9 @@ func (this *AddOriginPopupAction) RunPost(params struct {
|
||||
Description: "",
|
||||
Weight: 10,
|
||||
IsOn: true,
|
||||
Domains: domains,
|
||||
Host: params.Host,
|
||||
FollowPort: params.FollowPort,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
@@ -29,11 +30,25 @@ func (this *AddServerNamePopupAction) RunPost(params struct {
|
||||
Must *actions.Must
|
||||
}) {
|
||||
if params.Mode == "single" {
|
||||
var serverName = params.ServerName
|
||||
|
||||
// 去除空格
|
||||
serverName = regexp.MustCompile(`\s+`).ReplaceAllString(serverName, "")
|
||||
|
||||
// 处理URL
|
||||
if regexp.MustCompile(`^(?i)(http|https|ftp)://`).MatchString(serverName) {
|
||||
u, err := url.Parse(serverName)
|
||||
if err == nil && len(u.Host) > 0 {
|
||||
serverName = u.Host
|
||||
}
|
||||
}
|
||||
|
||||
params.Must.
|
||||
Field("serverName", params.ServerName).
|
||||
Field("serverName", serverName).
|
||||
Require("请输入域名")
|
||||
|
||||
this.Data["serverName"] = maps.Map{
|
||||
"name": params.ServerName,
|
||||
"name": serverName,
|
||||
"type": "full",
|
||||
}
|
||||
} else if params.Mode == "multiple" {
|
||||
@@ -41,14 +56,23 @@ func (this *AddServerNamePopupAction) RunPost(params struct {
|
||||
this.FailField("serverNames", "请输入至少域名")
|
||||
}
|
||||
|
||||
serverNames := []string{}
|
||||
var serverNames = []string{}
|
||||
for _, line := range strings.Split(params.ServerNames, "\n") {
|
||||
line := strings.TrimSpace(line)
|
||||
line = regexp.MustCompile(`\s+`).ReplaceAllString(line, "")
|
||||
if len(line) == 0 {
|
||||
var serverName = strings.TrimSpace(line)
|
||||
serverName = regexp.MustCompile(`\s+`).ReplaceAllString(serverName, "")
|
||||
if len(serverName) == 0 {
|
||||
continue
|
||||
}
|
||||
serverNames = append(serverNames, line)
|
||||
|
||||
// 处理URL
|
||||
if regexp.MustCompile(`^(?i)(http|https|ftp)://`).MatchString(serverName) {
|
||||
u, err := url.Parse(serverName)
|
||||
if err == nil && len(u.Host) > 0 {
|
||||
serverName = u.Host
|
||||
}
|
||||
}
|
||||
|
||||
serverNames = append(serverNames, serverName)
|
||||
}
|
||||
this.Data["serverName"] = maps.Map{
|
||||
"name": "",
|
||||
|
||||
@@ -15,6 +15,7 @@ func init() {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeServer)).
|
||||
Helper(NewHelper()).
|
||||
Data("teaMenu", "servers").
|
||||
Data("teaSubMenu", "cert").
|
||||
Prefix("/servers/certs").
|
||||
Data("leftMenuItem", "cert").
|
||||
|
||||
@@ -21,7 +21,9 @@ func (this *IndexAction) Init() {
|
||||
this.Nav("", "", "purge")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct{}) {
|
||||
func (this *IndexAction) RunGet(params struct {
|
||||
KeyType string
|
||||
}) {
|
||||
// 初始化菜单数据
|
||||
err := InitMenu(this.Parent())
|
||||
if err != nil {
|
||||
@@ -29,6 +31,8 @@ func (this *IndexAction) RunGet(params struct{}) {
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["keyType"] = params.KeyType
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeServer)).
|
||||
Data("teaMenu", "servers").
|
||||
Data("teaSubMenu", "global").
|
||||
Helper(NewHelper()).
|
||||
Prefix("/servers/components").
|
||||
|
||||
@@ -338,7 +338,7 @@ func (this *CreateAction) RunPost(params struct {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
reverseProxyRef := &serverconfigs.ReverseProxyRef{
|
||||
var reverseProxyRef = &serverconfigs.ReverseProxyRef{
|
||||
IsOn: true,
|
||||
ReverseProxyId: resp.ReverseProxyId,
|
||||
}
|
||||
@@ -355,7 +355,7 @@ func (this *CreateAction) RunPost(params struct {
|
||||
var rootJSON []byte
|
||||
var err error
|
||||
if len(params.WebRoot) > 0 {
|
||||
rootConfig := &serverconfigs.HTTPRootConfig{}
|
||||
var rootConfig = &serverconfigs.HTTPRootConfig{}
|
||||
rootConfig.IsOn = true
|
||||
rootConfig.Dir = params.WebRoot
|
||||
rootConfig.Indexes = []string{"index.html", "index.htm"}
|
||||
@@ -375,7 +375,7 @@ func (this *CreateAction) RunPost(params struct {
|
||||
}
|
||||
|
||||
// 包含条件
|
||||
includeNodes := []maps.Map{}
|
||||
var includeNodes = []maps.Map{}
|
||||
includeNodesJSON, err := json.Marshal(includeNodes)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
@@ -383,7 +383,7 @@ func (this *CreateAction) RunPost(params struct {
|
||||
}
|
||||
|
||||
// 排除条件
|
||||
excludeNodes := []maps.Map{}
|
||||
var excludeNodes = []maps.Map{}
|
||||
excludeNodesJSON, err := json.Marshal(excludeNodes)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
@@ -396,7 +396,7 @@ func (this *CreateAction) RunPost(params struct {
|
||||
AdminId: this.AdminId(),
|
||||
Type: params.ServerType,
|
||||
Name: params.Name,
|
||||
ServerNamesJON: []byte(params.ServerNames),
|
||||
ServerNamesJON: params.ServerNames,
|
||||
Description: params.Description,
|
||||
NodeClusterId: clusterId,
|
||||
IncludeNodesJSON: includeNodesJSON,
|
||||
|
||||
@@ -49,8 +49,8 @@ func (this *IndexAction) RunGet(params struct {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
count := countResp.Count
|
||||
page := this.NewPage(count)
|
||||
var count = countResp.Count
|
||||
var page = this.NewPage(count)
|
||||
this.Data["page"] = page.AsHTML()
|
||||
|
||||
// 服务列表
|
||||
@@ -64,7 +64,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
serverMaps := []maps.Map{}
|
||||
var serverMaps = []maps.Map{}
|
||||
for _, server := range serversResp.Servers {
|
||||
config := &serverconfigs.ServerConfig{}
|
||||
err = json.Unmarshal(server.Config, config)
|
||||
@@ -74,8 +74,8 @@ func (this *IndexAction) RunGet(params struct {
|
||||
}
|
||||
|
||||
// 端口列表
|
||||
portMaps := []maps.Map{}
|
||||
if len(server.HttpJSON) > 0 && config.HTTP.IsOn {
|
||||
var portMaps = []maps.Map{}
|
||||
if config.HTTP != nil && config.HTTP.IsOn {
|
||||
for _, listen := range config.HTTP.Listen {
|
||||
portMaps = append(portMaps, maps.Map{
|
||||
"protocol": listen.Protocol,
|
||||
@@ -125,7 +125,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
}
|
||||
|
||||
// 分组
|
||||
groupMaps := []maps.Map{}
|
||||
var groupMaps = []maps.Map{}
|
||||
if len(server.ServerGroups) > 0 {
|
||||
for _, group := range server.ServerGroups {
|
||||
groupMaps = append(groupMaps, maps.Map{
|
||||
@@ -136,11 +136,11 @@ func (this *IndexAction) RunGet(params struct {
|
||||
}
|
||||
|
||||
// 域名列表
|
||||
serverNames := []*serverconfigs.ServerNameConfig{}
|
||||
var serverNames = []*serverconfigs.ServerNameConfig{}
|
||||
if server.IsAuditing || (server.AuditingResult != nil && !server.AuditingResult.IsOk) {
|
||||
server.ServerNamesJSON = server.AuditingServerNamesJSON
|
||||
}
|
||||
auditingIsOk := true
|
||||
var auditingIsOk = true
|
||||
if !server.IsAuditing && server.AuditingResult != nil && !server.AuditingResult.IsOk {
|
||||
auditingIsOk = false
|
||||
}
|
||||
@@ -151,7 +151,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
return
|
||||
}
|
||||
}
|
||||
countServerNames := 0
|
||||
var countServerNames = 0
|
||||
for _, serverName := range serverNames {
|
||||
if len(serverName.SubNames) == 0 {
|
||||
countServerNames++
|
||||
|
||||
@@ -34,7 +34,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
reverseProxyRef := &serverconfigs.ReverseProxyRef{}
|
||||
var reverseProxyRef = &serverconfigs.ReverseProxyRef{}
|
||||
err = json.Unmarshal(reverseProxyResp.ReverseProxyRefJSON, reverseProxyRef)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
@@ -42,7 +42,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
}
|
||||
this.Data["reverseProxyRef"] = reverseProxyRef
|
||||
|
||||
reverseProxy := &serverconfigs.ReverseProxyConfig{}
|
||||
var reverseProxy = &serverconfigs.ReverseProxyConfig{}
|
||||
err = json.Unmarshal(reverseProxyResp.ReverseProxyJSON, reverseProxy)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
@@ -50,21 +50,22 @@ func (this *IndexAction) RunGet(params struct {
|
||||
}
|
||||
this.Data["reverseProxyConfig"] = reverseProxy
|
||||
|
||||
primaryOriginMaps := []maps.Map{}
|
||||
backupOriginMaps := []maps.Map{}
|
||||
var primaryOriginMaps = []maps.Map{}
|
||||
var backupOriginMaps = []maps.Map{}
|
||||
for _, originConfig := range reverseProxy.PrimaryOrigins {
|
||||
if len(originConfig.Domains) == 0 {
|
||||
originConfig.Domains = []string{}
|
||||
}
|
||||
m := maps.Map{
|
||||
"id": originConfig.Id,
|
||||
"weight": originConfig.Weight,
|
||||
"addr": originConfig.Addr.Protocol.String() + "://" + originConfig.Addr.Host + ":" + originConfig.Addr.PortRange,
|
||||
"name": originConfig.Name,
|
||||
"isOn": originConfig.IsOn,
|
||||
"domains": originConfig.Domains,
|
||||
"hasCert": originConfig.Cert != nil,
|
||||
"host": originConfig.RequestHost,
|
||||
var m = maps.Map{
|
||||
"id": originConfig.Id,
|
||||
"weight": originConfig.Weight,
|
||||
"addr": originConfig.Addr.Protocol.String() + "://" + originConfig.Addr.Host + ":" + originConfig.Addr.PortRange,
|
||||
"name": originConfig.Name,
|
||||
"isOn": originConfig.IsOn,
|
||||
"domains": originConfig.Domains,
|
||||
"hasCert": originConfig.Cert != nil,
|
||||
"host": originConfig.RequestHost,
|
||||
"followPort": originConfig.FollowPort,
|
||||
}
|
||||
primaryOriginMaps = append(primaryOriginMaps, m)
|
||||
}
|
||||
@@ -72,15 +73,16 @@ func (this *IndexAction) RunGet(params struct {
|
||||
if len(originConfig.Domains) == 0 {
|
||||
originConfig.Domains = []string{}
|
||||
}
|
||||
m := maps.Map{
|
||||
"id": originConfig.Id,
|
||||
"weight": originConfig.Weight,
|
||||
"addr": originConfig.Addr.Protocol.String() + "://" + originConfig.Addr.Host + ":" + originConfig.Addr.PortRange,
|
||||
"name": originConfig.Name,
|
||||
"isOn": originConfig.IsOn,
|
||||
"domains": originConfig.Domains,
|
||||
"hasCert": originConfig.Cert != nil,
|
||||
"host": originConfig.RequestHost,
|
||||
var m = maps.Map{
|
||||
"id": originConfig.Id,
|
||||
"weight": originConfig.Weight,
|
||||
"addr": originConfig.Addr.Protocol.String() + "://" + originConfig.Addr.Host + ":" + originConfig.Addr.PortRange,
|
||||
"name": originConfig.Name,
|
||||
"isOn": originConfig.IsOn,
|
||||
"domains": originConfig.Domains,
|
||||
"hasCert": originConfig.Cert != nil,
|
||||
"host": originConfig.RequestHost,
|
||||
"followPort": originConfig.FollowPort,
|
||||
}
|
||||
backupOriginMaps = append(backupOriginMaps, m)
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
reverseProxyRef := &serverconfigs.ReverseProxyRef{}
|
||||
var reverseProxyRef = &serverconfigs.ReverseProxyRef{}
|
||||
err = json.Unmarshal(reverseProxyResp.ReverseProxyRefJSON, reverseProxyRef)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
@@ -42,7 +42,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
}
|
||||
this.Data["reverseProxyRef"] = reverseProxyRef
|
||||
|
||||
reverseProxy := &serverconfigs.ReverseProxyConfig{}
|
||||
var reverseProxy = &serverconfigs.ReverseProxyConfig{}
|
||||
err = json.Unmarshal(reverseProxyResp.ReverseProxyJSON, reverseProxy)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
@@ -50,21 +50,22 @@ func (this *IndexAction) RunGet(params struct {
|
||||
}
|
||||
this.Data["reverseProxyConfig"] = reverseProxy
|
||||
|
||||
primaryOriginMaps := []maps.Map{}
|
||||
backupOriginMaps := []maps.Map{}
|
||||
var primaryOriginMaps = []maps.Map{}
|
||||
var backupOriginMaps = []maps.Map{}
|
||||
for _, originConfig := range reverseProxy.PrimaryOrigins {
|
||||
if len(originConfig.Domains) == 0 {
|
||||
originConfig.Domains = []string{}
|
||||
}
|
||||
m := maps.Map{
|
||||
"id": originConfig.Id,
|
||||
"weight": originConfig.Weight,
|
||||
"addr": originConfig.Addr.Protocol.String() + "://" + originConfig.Addr.Host + ":" + originConfig.Addr.PortRange,
|
||||
"name": originConfig.Name,
|
||||
"isOn": originConfig.IsOn,
|
||||
"domains": originConfig.Domains,
|
||||
"hasCert": originConfig.Cert != nil,
|
||||
"host": originConfig.RequestHost,
|
||||
"id": originConfig.Id,
|
||||
"weight": originConfig.Weight,
|
||||
"addr": originConfig.Addr.Protocol.String() + "://" + originConfig.Addr.Host + ":" + originConfig.Addr.PortRange,
|
||||
"name": originConfig.Name,
|
||||
"isOn": originConfig.IsOn,
|
||||
"domains": originConfig.Domains,
|
||||
"hasCert": originConfig.Cert != nil,
|
||||
"host": originConfig.RequestHost,
|
||||
"followPort": originConfig.FollowPort,
|
||||
}
|
||||
primaryOriginMaps = append(primaryOriginMaps, m)
|
||||
}
|
||||
@@ -73,14 +74,15 @@ func (this *IndexAction) RunGet(params struct {
|
||||
originConfig.Domains = []string{}
|
||||
}
|
||||
m := maps.Map{
|
||||
"id": originConfig.Id,
|
||||
"weight": originConfig.Weight,
|
||||
"addr": originConfig.Addr.Protocol.String() + "://" + originConfig.Addr.Host + ":" + originConfig.Addr.PortRange,
|
||||
"name": originConfig.Name,
|
||||
"isOn": originConfig.IsOn,
|
||||
"domains": originConfig.Domains,
|
||||
"hasCert": originConfig.Cert != nil,
|
||||
"host": originConfig.RequestHost,
|
||||
"id": originConfig.Id,
|
||||
"weight": originConfig.Weight,
|
||||
"addr": originConfig.Addr.Protocol.String() + "://" + originConfig.Addr.Host + ":" + originConfig.Addr.PortRange,
|
||||
"name": originConfig.Name,
|
||||
"isOn": originConfig.IsOn,
|
||||
"domains": originConfig.Domains,
|
||||
"hasCert": originConfig.Cert != nil,
|
||||
"host": originConfig.RequestHost,
|
||||
"followPort": originConfig.FollowPort,
|
||||
}
|
||||
backupOriginMaps = append(backupOriginMaps, m)
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
reverseProxyRef := &serverconfigs.ReverseProxyRef{}
|
||||
var reverseProxyRef = &serverconfigs.ReverseProxyRef{}
|
||||
err = json.Unmarshal(reverseProxyResp.ReverseProxyRefJSON, reverseProxyRef)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
@@ -42,7 +42,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
}
|
||||
this.Data["reverseProxyRef"] = reverseProxyRef
|
||||
|
||||
reverseProxy := &serverconfigs.ReverseProxyConfig{}
|
||||
var reverseProxy = &serverconfigs.ReverseProxyConfig{}
|
||||
err = json.Unmarshal(reverseProxyResp.ReverseProxyJSON, reverseProxy)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
@@ -50,21 +50,22 @@ func (this *IndexAction) RunGet(params struct {
|
||||
}
|
||||
this.Data["reverseProxyConfig"] = reverseProxy
|
||||
|
||||
primaryOriginMaps := []maps.Map{}
|
||||
backupOriginMaps := []maps.Map{}
|
||||
var primaryOriginMaps = []maps.Map{}
|
||||
var backupOriginMaps = []maps.Map{}
|
||||
for _, originConfig := range reverseProxy.PrimaryOrigins {
|
||||
if len(originConfig.Domains) == 0 {
|
||||
originConfig.Domains = []string{}
|
||||
}
|
||||
m := maps.Map{
|
||||
"id": originConfig.Id,
|
||||
"weight": originConfig.Weight,
|
||||
"addr": originConfig.Addr.Protocol.String() + "://" + originConfig.Addr.Host + ":" + originConfig.Addr.PortRange,
|
||||
"name": originConfig.Name,
|
||||
"isOn": originConfig.IsOn,
|
||||
"domains": originConfig.Domains,
|
||||
"hasCert": originConfig.Cert != nil,
|
||||
"host": originConfig.RequestHost,
|
||||
var m = maps.Map{
|
||||
"id": originConfig.Id,
|
||||
"weight": originConfig.Weight,
|
||||
"addr": originConfig.Addr.Protocol.String() + "://" + originConfig.Addr.Host + ":" + originConfig.Addr.PortRange,
|
||||
"name": originConfig.Name,
|
||||
"isOn": originConfig.IsOn,
|
||||
"domains": originConfig.Domains,
|
||||
"hasCert": originConfig.Cert != nil,
|
||||
"host": originConfig.RequestHost,
|
||||
"followPort": originConfig.FollowPort,
|
||||
}
|
||||
primaryOriginMaps = append(primaryOriginMaps, m)
|
||||
}
|
||||
@@ -72,15 +73,16 @@ func (this *IndexAction) RunGet(params struct {
|
||||
if len(originConfig.Domains) == 0 {
|
||||
originConfig.Domains = []string{}
|
||||
}
|
||||
m := maps.Map{
|
||||
"id": originConfig.Id,
|
||||
"weight": originConfig.Weight,
|
||||
"addr": originConfig.Addr.Protocol.String() + "://" + originConfig.Addr.Host + ":" + originConfig.Addr.PortRange,
|
||||
"name": originConfig.Name,
|
||||
"isOn": originConfig.IsOn,
|
||||
"domains": originConfig.Domains,
|
||||
"hasCert": originConfig.Cert != nil,
|
||||
"host": originConfig.RequestHost,
|
||||
var m = maps.Map{
|
||||
"id": originConfig.Id,
|
||||
"weight": originConfig.Weight,
|
||||
"addr": originConfig.Addr.Protocol.String() + "://" + originConfig.Addr.Host + ":" + originConfig.Addr.PortRange,
|
||||
"name": originConfig.Name,
|
||||
"isOn": originConfig.IsOn,
|
||||
"domains": originConfig.Domains,
|
||||
"hasCert": originConfig.Cert != nil,
|
||||
"host": originConfig.RequestHost,
|
||||
"followPort": originConfig.FollowPort,
|
||||
}
|
||||
backupOriginMaps = append(backupOriginMaps, m)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ func init() {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeServer)).
|
||||
Helper(NewHelper()).
|
||||
Data("teaMenu", "servers").
|
||||
Data("teaSubMenu", "group").
|
||||
Prefix("/servers/groups").
|
||||
Get("", new(IndexAction)).
|
||||
|
||||
@@ -27,6 +27,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
Keyword string
|
||||
AuditingFlag int32
|
||||
CheckDNS bool
|
||||
UserId int64
|
||||
|
||||
TrafficOutOrder string
|
||||
}) {
|
||||
@@ -36,6 +37,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
this.Data["auditingFlag"] = params.AuditingFlag
|
||||
this.Data["checkDNS"] = params.CheckDNS
|
||||
this.Data["hasOrder"] = len(params.TrafficOutOrder) > 0
|
||||
this.Data["userId"] = params.UserId
|
||||
|
||||
isSearching := params.AuditingFlag == 1 || params.ClusterId > 0 || params.GroupId > 0 || len(params.Keyword) > 0
|
||||
|
||||
@@ -44,7 +46,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
}
|
||||
|
||||
// 常用的服务
|
||||
latestServerMaps := []maps.Map{}
|
||||
var latestServerMaps = []maps.Map{}
|
||||
if !isSearching {
|
||||
serversResp, err := this.RPC().ServerRPC().FindLatestServers(this.AdminContext(), &pb.FindLatestServersRequest{Size: 6})
|
||||
if err != nil {
|
||||
@@ -76,13 +78,14 @@ func (this *IndexAction) RunGet(params struct {
|
||||
ServerGroupId: params.GroupId,
|
||||
Keyword: params.Keyword,
|
||||
AuditingFlag: params.AuditingFlag,
|
||||
UserId: params.UserId,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
count := countResp.Count
|
||||
page := this.NewPage(count)
|
||||
var count = countResp.Count
|
||||
var page = this.NewPage(count)
|
||||
this.Data["page"] = page.AsHTML()
|
||||
|
||||
// 服务列表
|
||||
@@ -95,14 +98,15 @@ func (this *IndexAction) RunGet(params struct {
|
||||
AuditingFlag: params.AuditingFlag,
|
||||
TrafficOutDesc: params.TrafficOutOrder == "desc",
|
||||
TrafficOutAsc: params.TrafficOutOrder == "asc",
|
||||
UserId: params.UserId,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
serverMaps := []maps.Map{}
|
||||
var serverMaps = []maps.Map{}
|
||||
for _, server := range serversResp.Servers {
|
||||
config := &serverconfigs.ServerConfig{}
|
||||
var config = &serverconfigs.ServerConfig{}
|
||||
err = json.Unmarshal(server.Config, config)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
@@ -110,8 +114,8 @@ func (this *IndexAction) RunGet(params struct {
|
||||
}
|
||||
|
||||
// 端口列表
|
||||
portMaps := []maps.Map{}
|
||||
if len(server.HttpJSON) > 0 && config.HTTP.IsOn {
|
||||
var portMaps = []maps.Map{}
|
||||
if config.HTTP != nil && config.HTTP.IsOn {
|
||||
for _, listen := range config.HTTP.Listen {
|
||||
portMaps = append(portMaps, maps.Map{
|
||||
"protocol": listen.Protocol,
|
||||
@@ -161,7 +165,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
}
|
||||
|
||||
// 分组
|
||||
groupMaps := []maps.Map{}
|
||||
var groupMaps = []maps.Map{}
|
||||
if len(server.ServerGroups) > 0 {
|
||||
for _, group := range server.ServerGroups {
|
||||
groupMaps = append(groupMaps, maps.Map{
|
||||
@@ -172,11 +176,11 @@ func (this *IndexAction) RunGet(params struct {
|
||||
}
|
||||
|
||||
// 域名列表
|
||||
serverNames := []*serverconfigs.ServerNameConfig{}
|
||||
var serverNames = []*serverconfigs.ServerNameConfig{}
|
||||
if server.IsAuditing || (server.AuditingResult != nil && !server.AuditingResult.IsOk) {
|
||||
server.ServerNamesJSON = server.AuditingServerNamesJSON
|
||||
}
|
||||
auditingIsOk := true
|
||||
var auditingIsOk = true
|
||||
if !server.IsAuditing && server.AuditingResult != nil && !server.AuditingResult.IsOk {
|
||||
auditingIsOk = false
|
||||
}
|
||||
@@ -187,7 +191,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
return
|
||||
}
|
||||
}
|
||||
countServerNames := 0
|
||||
var countServerNames = 0
|
||||
for _, serverName := range serverNames {
|
||||
if len(serverName.SubNames) == 0 {
|
||||
countServerNames++
|
||||
@@ -248,7 +252,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
clusterMaps := []maps.Map{}
|
||||
var clusterMaps = []maps.Map{}
|
||||
for _, cluster := range clustersResp.NodeClusters {
|
||||
clusterMaps = append(clusterMaps, maps.Map{
|
||||
"id": cluster.Id,
|
||||
@@ -263,9 +267,9 @@ func (this *IndexAction) RunGet(params struct {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
groupMaps := []maps.Map{}
|
||||
var groupMaps = []maps.Map{}
|
||||
for _, group := range groupsResp.ServerGroups {
|
||||
groupName := group.Name
|
||||
var groupName = group.Name
|
||||
groupMaps = append(groupMaps, maps.Map{
|
||||
"id": group.Id,
|
||||
"name": groupName,
|
||||
@@ -288,5 +292,13 @@ func (this *IndexAction) RunGet(params struct {
|
||||
}
|
||||
this.Data["countNeedFixLogs"] = countNeedFixLogsResp.Count
|
||||
|
||||
// 是否有用户
|
||||
countUsersResp, err := this.RPC().UserRPC().CountAllEnabledUsers(this.AdminContext(), &pb.CountAllEnabledUsersRequest{})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Data["hasUsers"] = countUsersResp.Count > 0
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
@@ -63,13 +64,15 @@ func (this *CreateIPPopupAction) RunPost(params struct {
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
// 校验IPList
|
||||
existsResp, err := this.RPC().IPListRPC().ExistsEnabledIPList(this.AdminContext(), &pb.ExistsEnabledIPListRequest{IpListId: params.ListId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if !existsResp.Exists {
|
||||
this.Fail("IP名单不存在")
|
||||
if params.ListId != firewallconfigs.GlobalListId {
|
||||
existsResp, err := this.RPC().IPListRPC().ExistsEnabledIPList(this.AdminContext(), &pb.ExistsEnabledIPListRequest{IpListId: params.ListId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if !existsResp.Exists {
|
||||
this.Fail("IP名单不存在")
|
||||
}
|
||||
}
|
||||
|
||||
type ipData struct {
|
||||
|
||||
@@ -26,14 +26,14 @@ func (this *IndexAction) RunGet(params struct {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
serverType := serverTypeResp.Type
|
||||
var serverType = serverTypeResp.Type
|
||||
|
||||
reverseProxyResp, err := this.RPC().HTTPLocationRPC().FindAndInitHTTPLocationReverseProxyConfig(this.AdminContext(), &pb.FindAndInitHTTPLocationReverseProxyConfigRequest{LocationId: params.LocationId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
reverseProxyRef := &serverconfigs.ReverseProxyRef{}
|
||||
var reverseProxyRef = &serverconfigs.ReverseProxyRef{}
|
||||
err = json.Unmarshal(reverseProxyResp.ReverseProxyRefJSON, reverseProxyRef)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
@@ -41,7 +41,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
}
|
||||
this.Data["reverseProxyRef"] = reverseProxyRef
|
||||
|
||||
reverseProxy := &serverconfigs.ReverseProxyConfig{}
|
||||
var reverseProxy = &serverconfigs.ReverseProxyConfig{}
|
||||
err = json.Unmarshal(reverseProxyResp.ReverseProxyJSON, reverseProxy)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
@@ -51,21 +51,22 @@ func (this *IndexAction) RunGet(params struct {
|
||||
|
||||
this.Data["serverType"] = serverType
|
||||
|
||||
primaryOriginMaps := []maps.Map{}
|
||||
backupOriginMaps := []maps.Map{}
|
||||
var primaryOriginMaps = []maps.Map{}
|
||||
var backupOriginMaps = []maps.Map{}
|
||||
for _, originConfig := range reverseProxy.PrimaryOrigins {
|
||||
if len(originConfig.Domains) == 0 {
|
||||
originConfig.Domains = []string{}
|
||||
}
|
||||
m := maps.Map{
|
||||
"id": originConfig.Id,
|
||||
"weight": originConfig.Weight,
|
||||
"addr": originConfig.Addr.Protocol.String() + "://" + originConfig.Addr.Host + ":" + originConfig.Addr.PortRange,
|
||||
"name": originConfig.Name,
|
||||
"isOn": originConfig.IsOn,
|
||||
"domains": originConfig.Domains,
|
||||
"hasCert": originConfig.Cert != nil,
|
||||
"host": originConfig.RequestHost,
|
||||
var m = maps.Map{
|
||||
"id": originConfig.Id,
|
||||
"weight": originConfig.Weight,
|
||||
"addr": originConfig.Addr.Protocol.String() + "://" + originConfig.Addr.Host + ":" + originConfig.Addr.PortRange,
|
||||
"name": originConfig.Name,
|
||||
"isOn": originConfig.IsOn,
|
||||
"domains": originConfig.Domains,
|
||||
"hasCert": originConfig.Cert != nil,
|
||||
"host": originConfig.RequestHost,
|
||||
"followPort": originConfig.FollowPort,
|
||||
}
|
||||
primaryOriginMaps = append(primaryOriginMaps, m)
|
||||
}
|
||||
@@ -73,15 +74,16 @@ func (this *IndexAction) RunGet(params struct {
|
||||
if len(originConfig.Domains) == 0 {
|
||||
originConfig.Domains = []string{}
|
||||
}
|
||||
m := maps.Map{
|
||||
"id": originConfig.Id,
|
||||
"weight": originConfig.Weight,
|
||||
"addr": originConfig.Addr.Protocol.String() + "://" + originConfig.Addr.Host + ":" + originConfig.Addr.PortRange,
|
||||
"name": originConfig.Name,
|
||||
"isOn": originConfig.IsOn,
|
||||
"domains": originConfig.Domains,
|
||||
"hasCert": originConfig.Cert != nil,
|
||||
"host": originConfig.RequestHost,
|
||||
var m = maps.Map{
|
||||
"id": originConfig.Id,
|
||||
"weight": originConfig.Weight,
|
||||
"addr": originConfig.Addr.Protocol.String() + "://" + originConfig.Addr.Host + ":" + originConfig.Addr.PortRange,
|
||||
"name": originConfig.Name,
|
||||
"isOn": originConfig.IsOn,
|
||||
"domains": originConfig.Domains,
|
||||
"hasCert": originConfig.Cert != nil,
|
||||
"host": originConfig.RequestHost,
|
||||
"followPort": originConfig.FollowPort,
|
||||
}
|
||||
backupOriginMaps = append(backupOriginMaps, m)
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ func (this *AddPopupAction) RunPost(params struct {
|
||||
|
||||
DomainsJSON []byte
|
||||
Host string
|
||||
FollowPort bool
|
||||
|
||||
Description string
|
||||
IsOn bool
|
||||
@@ -98,7 +99,7 @@ func (this *AddPopupAction) RunPost(params struct {
|
||||
} else if params.Protocol == "https" {
|
||||
addr += ":443"
|
||||
} else {
|
||||
this.Fail("地址中需要带有端口")
|
||||
this.FailField("addr", "源站地址中需要带有端口")
|
||||
}
|
||||
portIndex = strings.LastIndex(addr, ":")
|
||||
}
|
||||
@@ -107,19 +108,19 @@ func (this *AddPopupAction) RunPost(params struct {
|
||||
|
||||
// 检查端口号
|
||||
if port == "0" {
|
||||
this.Fail("端口号不能为0")
|
||||
this.FailField("addr", "源站端口号不能为0")
|
||||
}
|
||||
if !configutils.HasVariables(port) {
|
||||
// 必须是整数
|
||||
if !regexp.MustCompile(`^\d+$`).MatchString(port) {
|
||||
this.Fail("端口号只能为整数")
|
||||
this.FailField("addr", "源站端口号只能为整数")
|
||||
}
|
||||
var portInt = types.Int(port)
|
||||
if portInt == 0 {
|
||||
this.Fail("端口号不能为0")
|
||||
this.FailField("addr", "源站端口号不能为0")
|
||||
}
|
||||
if portInt > 65535 {
|
||||
this.Fail("端口号不能大于65535")
|
||||
this.FailField("addr", "源站端口号不能大于65535")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,6 +209,7 @@ func (this *AddPopupAction) RunPost(params struct {
|
||||
CertRefJSON: certRefJSON,
|
||||
Domains: domains,
|
||||
Host: params.Host,
|
||||
FollowPort: params.FollowPort,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
|
||||
@@ -110,6 +110,7 @@ func (this *UpdatePopupAction) RunGet(params struct {
|
||||
"cert": config.Cert,
|
||||
"domains": config.Domains,
|
||||
"host": config.RequestHost,
|
||||
"followPort": config.FollowPort,
|
||||
}
|
||||
|
||||
this.Show()
|
||||
@@ -135,6 +136,7 @@ func (this *UpdatePopupAction) RunPost(params struct {
|
||||
|
||||
DomainsJSON []byte
|
||||
Host string
|
||||
FollowPort bool
|
||||
|
||||
Description string
|
||||
IsOn bool
|
||||
@@ -164,7 +166,7 @@ func (this *UpdatePopupAction) RunPost(params struct {
|
||||
} else if params.Protocol == "https" {
|
||||
addr += ":443"
|
||||
} else {
|
||||
this.Fail("地址中需要带有端口")
|
||||
this.FailField("addr", "源站地址中需要带有端口")
|
||||
}
|
||||
portIndex = strings.LastIndex(addr, ":")
|
||||
}
|
||||
@@ -172,19 +174,19 @@ func (this *UpdatePopupAction) RunPost(params struct {
|
||||
var port = addr[portIndex+1:]
|
||||
// 检查端口号
|
||||
if port == "0" {
|
||||
this.Fail("端口号不能为0")
|
||||
this.FailField("addr", "源站端口号不能为0")
|
||||
}
|
||||
if !configutils.HasVariables(port) {
|
||||
// 必须是整数
|
||||
if !regexp.MustCompile(`^\d+$`).MatchString(port) {
|
||||
this.Fail("端口号只能为整数")
|
||||
this.FailField("addr", "源站端口号只能为整数")
|
||||
}
|
||||
var portInt = types.Int(port)
|
||||
if portInt == 0 {
|
||||
this.Fail("端口号不能为0")
|
||||
this.FailField("addr", "源站端口号不能为0")
|
||||
}
|
||||
if portInt > 65535 {
|
||||
this.Fail("端口号不能大于65535")
|
||||
this.FailField("addr", "源站端口号不能大于65535")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,6 +276,7 @@ func (this *UpdatePopupAction) RunPost(params struct {
|
||||
CertRefJSON: certRefJSON,
|
||||
Domains: domains,
|
||||
Host: params.Host,
|
||||
FollowPort: params.FollowPort,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
|
||||
@@ -26,7 +26,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
serverType := serverTypeResp.Type
|
||||
var serverType = serverTypeResp.Type
|
||||
|
||||
// 当前是否有分组设置
|
||||
groupResp, err := this.RPC().ServerGroupRPC().FindEnabledServerGroupConfigInfo(this.AdminContext(), &pb.FindEnabledServerGroupConfigInfoRequest{ServerId: params.ServerId})
|
||||
@@ -60,7 +60,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
reverseProxyRef := &serverconfigs.ReverseProxyRef{}
|
||||
var reverseProxyRef = &serverconfigs.ReverseProxyRef{}
|
||||
err = json.Unmarshal(reverseProxyResp.ReverseProxyRefJSON, reverseProxyRef)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
@@ -68,7 +68,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
}
|
||||
this.Data["reverseProxyRef"] = reverseProxyRef
|
||||
|
||||
reverseProxy := &serverconfigs.ReverseProxyConfig{}
|
||||
var reverseProxy = &serverconfigs.ReverseProxyConfig{}
|
||||
err = json.Unmarshal(reverseProxyResp.ReverseProxyJSON, reverseProxy)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
@@ -78,21 +78,22 @@ func (this *IndexAction) RunGet(params struct {
|
||||
|
||||
this.Data["serverType"] = serverType
|
||||
|
||||
primaryOriginMaps := []maps.Map{}
|
||||
backupOriginMaps := []maps.Map{}
|
||||
var primaryOriginMaps = []maps.Map{}
|
||||
var backupOriginMaps = []maps.Map{}
|
||||
for _, originConfig := range reverseProxy.PrimaryOrigins {
|
||||
if len(originConfig.Domains) == 0 {
|
||||
originConfig.Domains = []string{}
|
||||
}
|
||||
m := maps.Map{
|
||||
"id": originConfig.Id,
|
||||
"weight": originConfig.Weight,
|
||||
"addr": originConfig.Addr.Protocol.String() + "://" + originConfig.Addr.Host + ":" + originConfig.Addr.PortRange,
|
||||
"name": originConfig.Name,
|
||||
"isOn": originConfig.IsOn,
|
||||
"domains": originConfig.Domains,
|
||||
"hasCert": originConfig.Cert != nil,
|
||||
"host": originConfig.RequestHost,
|
||||
var m = maps.Map{
|
||||
"id": originConfig.Id,
|
||||
"weight": originConfig.Weight,
|
||||
"addr": originConfig.Addr.Protocol.String() + "://" + originConfig.Addr.Host + ":" + originConfig.Addr.PortRange,
|
||||
"name": originConfig.Name,
|
||||
"isOn": originConfig.IsOn,
|
||||
"domains": originConfig.Domains,
|
||||
"hasCert": originConfig.Cert != nil,
|
||||
"host": originConfig.RequestHost,
|
||||
"followPort": originConfig.FollowPort,
|
||||
}
|
||||
primaryOriginMaps = append(primaryOriginMaps, m)
|
||||
}
|
||||
@@ -100,15 +101,16 @@ func (this *IndexAction) RunGet(params struct {
|
||||
if len(originConfig.Domains) == 0 {
|
||||
originConfig.Domains = []string{}
|
||||
}
|
||||
m := maps.Map{
|
||||
"id": originConfig.Id,
|
||||
"weight": originConfig.Weight,
|
||||
"addr": originConfig.Addr.Protocol.String() + "://" + originConfig.Addr.Host + ":" + originConfig.Addr.PortRange,
|
||||
"name": originConfig.Name,
|
||||
"isOn": originConfig.IsOn,
|
||||
"domains": originConfig.Domains,
|
||||
"hasCert": originConfig.Cert != nil,
|
||||
"host": originConfig.RequestHost,
|
||||
var m = maps.Map{
|
||||
"id": originConfig.Id,
|
||||
"weight": originConfig.Weight,
|
||||
"addr": originConfig.Addr.Protocol.String() + "://" + originConfig.Addr.Host + ":" + originConfig.Addr.PortRange,
|
||||
"name": originConfig.Name,
|
||||
"isOn": originConfig.IsOn,
|
||||
"domains": originConfig.Domains,
|
||||
"hasCert": originConfig.Cert != nil,
|
||||
"host": originConfig.RequestHost,
|
||||
"followPort": originConfig.FollowPort,
|
||||
}
|
||||
backupOriginMaps = append(backupOriginMaps, m)
|
||||
}
|
||||
|
||||
@@ -55,9 +55,7 @@ func (this *SettingAction) RunPost(params struct {
|
||||
}) {
|
||||
defer this.CreateLogInfo("修改代理服务 %d 的反向代理设置", params.ServerId)
|
||||
|
||||
// TODO 校验配置
|
||||
|
||||
reverseProxyConfig := &serverconfigs.ReverseProxyConfig{}
|
||||
var reverseProxyConfig = &serverconfigs.ReverseProxyConfig{}
|
||||
err := json.Unmarshal(params.ReverseProxyJSON, reverseProxyConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
@@ -118,20 +116,21 @@ func (this *SettingAction) RunPost(params struct {
|
||||
|
||||
// 设置反向代理相关信息
|
||||
_, err = this.RPC().ReverseProxyRPC().UpdateReverseProxy(this.AdminContext(), &pb.UpdateReverseProxyRequest{
|
||||
ReverseProxyId: reverseProxyConfig.Id,
|
||||
RequestHostType: types.Int32(reverseProxyConfig.RequestHostType),
|
||||
RequestHost: reverseProxyConfig.RequestHost,
|
||||
RequestURI: reverseProxyConfig.RequestURI,
|
||||
StripPrefix: reverseProxyConfig.StripPrefix,
|
||||
AutoFlush: reverseProxyConfig.AutoFlush,
|
||||
AddHeaders: reverseProxyConfig.AddHeaders,
|
||||
ConnTimeoutJSON: connTimeoutJSON,
|
||||
ReadTimeoutJSON: readTimeoutJSON,
|
||||
IdleTimeoutJSON: idleTimeoutJSON,
|
||||
MaxConns: types.Int32(reverseProxyConfig.MaxConns),
|
||||
MaxIdleConns: types.Int32(reverseProxyConfig.MaxIdleConns),
|
||||
ProxyProtocolJSON: proxyProtocolJSON,
|
||||
FollowRedirects: reverseProxyConfig.FollowRedirects,
|
||||
ReverseProxyId: reverseProxyConfig.Id,
|
||||
RequestHostType: types.Int32(reverseProxyConfig.RequestHostType),
|
||||
RequestHost: reverseProxyConfig.RequestHost,
|
||||
RequestURI: reverseProxyConfig.RequestURI,
|
||||
StripPrefix: reverseProxyConfig.StripPrefix,
|
||||
AutoFlush: reverseProxyConfig.AutoFlush,
|
||||
AddHeaders: reverseProxyConfig.AddHeaders,
|
||||
ConnTimeoutJSON: connTimeoutJSON,
|
||||
ReadTimeoutJSON: readTimeoutJSON,
|
||||
IdleTimeoutJSON: idleTimeoutJSON,
|
||||
MaxConns: types.Int32(reverseProxyConfig.MaxConns),
|
||||
MaxIdleConns: types.Int32(reverseProxyConfig.MaxIdleConns),
|
||||
ProxyProtocolJSON: proxyProtocolJSON,
|
||||
FollowRedirects: reverseProxyConfig.FollowRedirects,
|
||||
RequestHostExcludingPort: reverseProxyConfig.RequestHostExcludingPort,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
|
||||
@@ -29,6 +29,7 @@ func (this *OptionsAction) RunPost(params struct {
|
||||
"id": user.Id,
|
||||
"fullname": user.Fullname,
|
||||
"username": user.Username,
|
||||
"name": user.Fullname + "(" + user.Username + ")",
|
||||
})
|
||||
}
|
||||
this.Data["users"] = userMaps
|
||||
|
||||
@@ -29,14 +29,14 @@ func (this *IndexAction) RunGet(params struct{}) {
|
||||
}
|
||||
|
||||
// 国家和地区
|
||||
countryMaps := []maps.Map{}
|
||||
var countryMaps = []maps.Map{}
|
||||
for _, countryId := range config.AllowCountryIds {
|
||||
countryResp, err := this.RPC().RegionCountryRPC().FindEnabledRegionCountry(this.AdminContext(), &pb.FindEnabledRegionCountryRequest{RegionCountryId: countryId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
country := countryResp.RegionCountry
|
||||
var country = countryResp.RegionCountry
|
||||
if country != nil {
|
||||
countryMaps = append(countryMaps, maps.Map{
|
||||
"id": country.Id,
|
||||
@@ -47,14 +47,14 @@ func (this *IndexAction) RunGet(params struct{}) {
|
||||
this.Data["countries"] = countryMaps
|
||||
|
||||
// 省份
|
||||
provinceMaps := []maps.Map{}
|
||||
var provinceMaps = []maps.Map{}
|
||||
for _, provinceId := range config.AllowProvinceIds {
|
||||
provinceResp, err := this.RPC().RegionProvinceRPC().FindEnabledRegionProvince(this.AdminContext(), &pb.FindEnabledRegionProvinceRequest{RegionProvinceId: provinceId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
province := provinceResp.RegionProvince
|
||||
var province = provinceResp.RegionProvince
|
||||
if province != nil {
|
||||
provinceMaps = append(provinceMaps, maps.Map{
|
||||
"id": province.Id,
|
||||
@@ -76,6 +76,11 @@ func (this *IndexAction) RunPost(params struct {
|
||||
AllowIPs []string
|
||||
AllowRememberLogin bool
|
||||
|
||||
DenySearchEngines bool
|
||||
DenySpiders bool
|
||||
|
||||
DomainsJSON []byte
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
@@ -91,7 +96,7 @@ func (this *IndexAction) RunPost(params struct {
|
||||
config.Frame = params.Frame
|
||||
|
||||
// 国家和地区
|
||||
countryIds := []int64{}
|
||||
var countryIds = []int64{}
|
||||
if len(params.CountryIdsJSON) > 0 {
|
||||
err = json.Unmarshal(params.CountryIdsJSON, &countryIds)
|
||||
if err != nil {
|
||||
@@ -102,7 +107,7 @@ func (this *IndexAction) RunPost(params struct {
|
||||
config.AllowCountryIds = countryIds
|
||||
|
||||
// 省份
|
||||
provinceIds := []int64{}
|
||||
var provinceIds = []int64{}
|
||||
if len(params.ProvinceIdsJSON) > 0 {
|
||||
err = json.Unmarshal(params.ProvinceIdsJSON, &provinceIds)
|
||||
if err != nil {
|
||||
@@ -128,6 +133,20 @@ func (this *IndexAction) RunPost(params struct {
|
||||
// 允许本地
|
||||
config.AllowLocal = params.AllowLocal
|
||||
|
||||
// 禁止搜索引擎和爬虫
|
||||
config.DenySearchEngines = params.DenySearchEngines
|
||||
config.DenySpiders = params.DenySpiders
|
||||
|
||||
// 允许的域名
|
||||
var domains = []string{}
|
||||
if len(params.DomainsJSON) > 0 {
|
||||
err = json.Unmarshal(params.DomainsJSON, &domains)
|
||||
if err != nil {
|
||||
this.Fail("解析允许访问的域名失败:" + err.Error())
|
||||
}
|
||||
}
|
||||
config.AllowDomains = domains
|
||||
|
||||
// 允许记住登录
|
||||
config.AllowRememberLogin = params.AllowRememberLogin
|
||||
|
||||
|
||||
@@ -48,6 +48,10 @@ func (this *IndexAction) RunPost(params struct {
|
||||
LogoFile *actions.File
|
||||
TimeZone string
|
||||
|
||||
ShowTrafficCharts bool
|
||||
ShowBandwidthCharts bool
|
||||
BandwidthUnit string
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
@@ -68,6 +72,9 @@ func (this *IndexAction) RunPost(params struct {
|
||||
config.ShowVersion = params.ShowVersion
|
||||
config.Version = params.Version
|
||||
config.ShowFinance = params.ShowFinance
|
||||
config.ShowTrafficCharts = params.ShowTrafficCharts
|
||||
config.ShowBandwidthCharts = params.ShowBandwidthCharts
|
||||
config.BandwidthUnit = params.BandwidthUnit
|
||||
config.TimeZone = params.TimeZone
|
||||
|
||||
// 上传Favicon文件
|
||||
|
||||
@@ -223,7 +223,8 @@ func (this *InstallAction) RunPost(params struct {
|
||||
// 写入API节点配置,完成安装
|
||||
apiConfig := &configs.APIConfig{
|
||||
RPC: struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
DisableUpdate bool `yaml:"disableUpdate"`
|
||||
}{
|
||||
Endpoints: []string{"http://" + configutils.QuoteIP(apiNodeMap.GetString("newHost")) + ":" + apiNodeMap.GetString("newPort")},
|
||||
},
|
||||
@@ -285,7 +286,8 @@ func (this *InstallAction) RunPost(params struct {
|
||||
// 构造RPC
|
||||
apiConfig := &configs.APIConfig{
|
||||
RPC: struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
DisableUpdate bool `yaml:"disableUpdate"`
|
||||
}{
|
||||
Endpoints: []string{apiNodeMap.GetString("oldProtocol") + "://" + configutils.QuoteIP(apiNodeMap.GetString("oldHost")) + ":" + apiNodeMap.GetString("oldPort")},
|
||||
},
|
||||
|
||||
@@ -85,7 +85,8 @@ func (this *ValidateApiAction) RunPost(params struct {
|
||||
Require("请输入节点secret")
|
||||
client, err := rpc.NewRPCClient(&configs.APIConfig{
|
||||
RPC: struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
DisableUpdate bool `yaml:"disableUpdate"`
|
||||
}{
|
||||
Endpoints: []string{params.OldProtocol + "://" + configutils.QuoteIP(params.OldHost) + ":" + params.OldPort},
|
||||
},
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/xlzd/gotp"
|
||||
)
|
||||
|
||||
type CreatePopupAction struct {
|
||||
@@ -30,6 +32,9 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
Remark string
|
||||
ClusterId int64
|
||||
|
||||
// OTP
|
||||
OtpOn bool
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
@@ -91,7 +96,28 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
defer this.CreateLogInfo("创建用户 %d", createResp.UserId)
|
||||
|
||||
var userId = createResp.UserId
|
||||
|
||||
defer this.CreateLogInfo("创建用户 %d", userId)
|
||||
|
||||
// OTP
|
||||
if params.OtpOn {
|
||||
_, err = this.RPC().LoginRPC().UpdateLogin(this.AdminContext(), &pb.UpdateLoginRequest{Login: &pb.Login{
|
||||
Id: 0,
|
||||
Type: "otp",
|
||||
ParamsJSON: maps.Map{
|
||||
"secret": gotp.RandomSecret(16), // TODO 改成可以设置secret长度
|
||||
}.AsJSON(),
|
||||
IsOn: true,
|
||||
AdminId: 0,
|
||||
UserId: userId,
|
||||
}})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
|
||||
@@ -65,19 +65,27 @@ func (this *IndexAction) RunGet(params struct {
|
||||
}
|
||||
}
|
||||
|
||||
isSubmittedResp, err := this.RPC().UserIdentityRPC().CheckUserIdentityIsSubmitted(this.AdminContext(), &pb.CheckUserIdentityIsSubmittedRequest{UserId: user.Id})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var identityIsSubmitted = isSubmittedResp.IsSubmitted
|
||||
|
||||
userMaps = append(userMaps, maps.Map{
|
||||
"id": user.Id,
|
||||
"username": user.Username,
|
||||
"isOn": user.IsOn,
|
||||
"fullname": user.Fullname,
|
||||
"email": user.Email,
|
||||
"mobile": user.Mobile,
|
||||
"tel": user.Tel,
|
||||
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", user.CreatedAt),
|
||||
"cluster": clusterMap,
|
||||
"registeredIP": user.RegisteredIP,
|
||||
"isVerified": user.IsVerified,
|
||||
"isRejected": user.IsRejected,
|
||||
"id": user.Id,
|
||||
"username": user.Username,
|
||||
"isOn": user.IsOn,
|
||||
"fullname": user.Fullname,
|
||||
"email": user.Email,
|
||||
"mobile": user.Mobile,
|
||||
"tel": user.Tel,
|
||||
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", user.CreatedAt),
|
||||
"cluster": clusterMap,
|
||||
"registeredIP": user.RegisteredIP,
|
||||
"isVerified": user.IsVerified,
|
||||
"isRejected": user.IsRejected,
|
||||
"identityIsSubmitted": identityIsSubmitted,
|
||||
})
|
||||
}
|
||||
this.Data["users"] = userMaps
|
||||
|
||||
@@ -20,6 +20,7 @@ func init() {
|
||||
Post("/delete", new(DeleteAction)).
|
||||
GetPost("/features", new(FeaturesAction)).
|
||||
GetPost("/verifyPopup", new(VerifyPopupAction)).
|
||||
Get("/otpQrcode", new(OtpQrcodeAction)).
|
||||
|
||||
// AccessKeys
|
||||
Prefix("/users/accessKeys").
|
||||
|
||||
75
internal/web/actions/default/users/otpQrcode.go
Normal file
75
internal/web/actions/default/users/otpQrcode.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package users
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/skip2/go-qrcode"
|
||||
"github.com/xlzd/gotp"
|
||||
)
|
||||
|
||||
type OtpQrcodeAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *OtpQrcodeAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *OtpQrcodeAction) RunGet(params struct {
|
||||
UserId int64
|
||||
}) {
|
||||
loginResp, err := this.RPC().LoginRPC().FindEnabledLogin(this.AdminContext(), &pb.FindEnabledLoginRequest{
|
||||
UserId: params.UserId,
|
||||
Type: "otp",
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
login := loginResp.Login
|
||||
if login == nil || !login.IsOn {
|
||||
this.NotFound("userLogin", params.UserId)
|
||||
return
|
||||
}
|
||||
|
||||
loginParams := maps.Map{}
|
||||
err = json.Unmarshal(login.ParamsJSON, &loginParams)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
secret := loginParams.GetString("secret")
|
||||
|
||||
// 当前用户信息
|
||||
userResp, err := this.RPC().UserRPC().FindEnabledUser(this.AdminContext(), &pb.FindEnabledUserRequest{UserId: params.UserId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var user = userResp.User
|
||||
if user == nil {
|
||||
this.NotFound("user", params.UserId)
|
||||
return
|
||||
}
|
||||
|
||||
uiConfig, err := configloaders.LoadAdminUIConfig()
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var productName = uiConfig.ProductName
|
||||
if len(productName) == 0 {
|
||||
productName = "GoEdge用户"
|
||||
}
|
||||
var url = gotp.NewDefaultTOTP(secret).ProvisioningUri(user.Username, productName)
|
||||
data, err := qrcode.Encode(url, qrcode.Medium, 256)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.AddHeader("Content-Type", "image/png")
|
||||
this.Write(data)
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/xlzd/gotp"
|
||||
)
|
||||
|
||||
type UpdateAction struct {
|
||||
@@ -30,7 +31,7 @@ func (this *UpdateAction) RunGet(params struct {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
user := userResp.User
|
||||
var user = userResp.User
|
||||
if user == nil {
|
||||
this.NotFound("user", params.UserId)
|
||||
return
|
||||
@@ -42,7 +43,20 @@ func (this *UpdateAction) RunGet(params struct {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
countAccessKeys := countAccessKeyResp.Count
|
||||
var countAccessKeys = countAccessKeyResp.Count
|
||||
|
||||
// 是否有实名认证
|
||||
hasNewIndividualIdentity, hasNewEnterpriseIdentity, identityTag, err := userutils.CheckUserIdentity(this.RPC(), this.AdminContext(), params.UserId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
// OTP认证
|
||||
var otpLoginIsOn = false
|
||||
if user.OtpLogin != nil {
|
||||
otpLoginIsOn = user.OtpLogin.IsOn
|
||||
}
|
||||
|
||||
this.Data["user"] = maps.Map{
|
||||
"id": user.Id,
|
||||
@@ -54,6 +68,14 @@ func (this *UpdateAction) RunGet(params struct {
|
||||
"mobile": user.Mobile,
|
||||
"isOn": user.IsOn,
|
||||
"countAccessKeys": countAccessKeys,
|
||||
|
||||
// 实名认证
|
||||
"hasNewIndividualIdentity": hasNewIndividualIdentity,
|
||||
"hasNewEnterpriseIdentity": hasNewEnterpriseIdentity,
|
||||
"identityTag": identityTag,
|
||||
|
||||
// otp
|
||||
"otpLoginIsOn": otpLoginIsOn,
|
||||
}
|
||||
|
||||
this.Data["clusterId"] = 0
|
||||
@@ -77,6 +99,9 @@ func (this *UpdateAction) RunPost(params struct {
|
||||
IsOn bool
|
||||
ClusterId int64
|
||||
|
||||
// OTP
|
||||
OtpOn bool
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
@@ -140,5 +165,50 @@ func (this *UpdateAction) RunPost(params struct {
|
||||
return
|
||||
}
|
||||
|
||||
// 修改OTP
|
||||
otpLoginResp, err := this.RPC().LoginRPC().FindEnabledLogin(this.AdminContext(), &pb.FindEnabledLoginRequest{
|
||||
UserId: params.UserId,
|
||||
Type: "otp",
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
{
|
||||
var otpLogin = otpLoginResp.Login
|
||||
if params.OtpOn {
|
||||
if otpLogin == nil {
|
||||
otpLogin = &pb.Login{
|
||||
Id: 0,
|
||||
Type: "otp",
|
||||
ParamsJSON: maps.Map{
|
||||
"secret": gotp.RandomSecret(16), // TODO 改成可以设置secret长度
|
||||
}.AsJSON(),
|
||||
IsOn: true,
|
||||
UserId: params.UserId,
|
||||
}
|
||||
} else {
|
||||
// 如果已经有了,就覆盖,这样可以保留既有的参数
|
||||
otpLogin.IsOn = true
|
||||
}
|
||||
|
||||
_, err = this.RPC().LoginRPC().UpdateLogin(this.AdminContext(), &pb.UpdateLoginRequest{Login: otpLogin})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
_, err = this.RPC().LoginRPC().UpdateLogin(this.AdminContext(), &pb.UpdateLoginRequest{Login: &pb.Login{
|
||||
Type: "otp",
|
||||
IsOn: false,
|
||||
UserId: params.UserId,
|
||||
}})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package users
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/users/userutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
@@ -34,7 +35,7 @@ func (this *UserAction) RunGet(params struct {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
user := userResp.User
|
||||
var user = userResp.User
|
||||
if user == nil {
|
||||
this.NotFound("user", params.UserId)
|
||||
return
|
||||
@@ -69,6 +70,28 @@ func (this *UserAction) RunGet(params struct {
|
||||
}
|
||||
}
|
||||
|
||||
// 是否有实名认证
|
||||
hasNewIndividualIdentity, hasNewEnterpriseIdentity, identityTag, err := userutils.CheckUserIdentity(this.RPC(), this.AdminContext(), params.UserId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
// OTP
|
||||
this.Data["otp"] = nil
|
||||
if user.OtpLogin != nil && user.OtpLogin.IsOn {
|
||||
loginParams := maps.Map{}
|
||||
err = json.Unmarshal(user.OtpLogin.ParamsJSON, &loginParams)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Data["otp"] = maps.Map{
|
||||
"isOn": true,
|
||||
"params": loginParams,
|
||||
}
|
||||
}
|
||||
|
||||
this.Data["user"] = maps.Map{
|
||||
"id": user.Id,
|
||||
"username": user.Username,
|
||||
@@ -85,6 +108,11 @@ func (this *UserAction) RunGet(params struct {
|
||||
"isVerified": user.IsVerified,
|
||||
"registeredIP": user.RegisteredIP,
|
||||
"registeredRegion": registeredRegion,
|
||||
|
||||
// 实名认证
|
||||
"hasNewIndividualIdentity": hasNewIndividualIdentity,
|
||||
"hasNewEnterpriseIdentity": hasNewEnterpriseIdentity,
|
||||
"identityTag": identityTag,
|
||||
}
|
||||
|
||||
this.Show()
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package userutils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var ErrUserNotFound = errors.New("not found user")
|
||||
@@ -28,11 +32,59 @@ func InitUser(p *actionutils.ParentAction, userId int64) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// 是否有实名认证
|
||||
hasNewIndividualIdentity, hasNewEnterpriseIdentity, identityTag, err := CheckUserIdentity(p.RPC(), p.AdminContext(), userId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.Data["user"] = maps.Map{
|
||||
"id": userId,
|
||||
"fullname": resp.User.Fullname,
|
||||
"username": resp.User.Username,
|
||||
"countAccessKeys": countAccessKeysResp.Count,
|
||||
"id": userId,
|
||||
"fullname": resp.User.Fullname,
|
||||
"username": resp.User.Username,
|
||||
"countAccessKeys": countAccessKeysResp.Count,
|
||||
"hasNewIndividualIdentity": hasNewIndividualIdentity,
|
||||
"hasNewEnterpriseIdentity": hasNewEnterpriseIdentity,
|
||||
"identityTag": identityTag,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckUserIdentity 实名认证信息
|
||||
func CheckUserIdentity(rpcClient *rpc.RPCClient, ctx context.Context, userId int64) (hasNewIndividualIdentity bool, hasNewEnterpriseIdentity bool, identityTag string, err error) {
|
||||
var tags = []string{}
|
||||
|
||||
// 个人
|
||||
individualIdentityResp, err := rpcClient.UserIdentityRPC().FindEnabledUserIdentityWithOrgType(ctx, &pb.FindEnabledUserIdentityWithOrgTypeRequest{
|
||||
UserId: userId,
|
||||
OrgType: userconfigs.UserIdentityOrgTypeIndividual,
|
||||
})
|
||||
if err != nil {
|
||||
return false, false, "", err
|
||||
}
|
||||
var individualIdentity = individualIdentityResp.UserIdentity
|
||||
hasNewIndividualIdentity = individualIdentity != nil && individualIdentity.Status == userconfigs.UserIdentityStatusSubmitted
|
||||
|
||||
if individualIdentity != nil && individualIdentity.Status == userconfigs.UserIdentityStatusVerified {
|
||||
tags = append(tags, "个人")
|
||||
}
|
||||
|
||||
// 企业
|
||||
enterpriseIdentityResp, err := rpcClient.UserIdentityRPC().FindEnabledUserIdentityWithOrgType(ctx, &pb.FindEnabledUserIdentityWithOrgTypeRequest{
|
||||
UserId: userId,
|
||||
OrgType: userconfigs.UserIdentityOrgTypeEnterprise,
|
||||
})
|
||||
if err != nil {
|
||||
return false, false, "", err
|
||||
}
|
||||
var enterpriseIdentity = enterpriseIdentityResp.UserIdentity
|
||||
hasNewEnterpriseIdentity = enterpriseIdentity != nil && enterpriseIdentity.Status == userconfigs.UserIdentityStatusSubmitted
|
||||
|
||||
if enterpriseIdentity != nil && enterpriseIdentity.Status == userconfigs.UserIdentityStatusVerified {
|
||||
tags = append(tags, "企业")
|
||||
}
|
||||
|
||||
identityTag = strings.Join(tags, "+")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -75,6 +75,13 @@ func (this *userMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramNam
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查请求
|
||||
if !checkRequestSecurity(securityConfig, action.Request) {
|
||||
action.ResponseWriter.WriteHeader(http.StatusForbidden)
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
// 检查系统是否已经配置过
|
||||
if !setup.IsConfigured() {
|
||||
action.RedirectURL("/setup")
|
||||
|
||||
@@ -22,7 +22,7 @@ func (this *UserShouldAuth) BeforeAction(actionPtr actions.ActionWrapper, paramN
|
||||
this.action = actionPtr.Object()
|
||||
|
||||
// 安全相关
|
||||
action := this.action
|
||||
var action = this.action
|
||||
securityConfig, _ := configloaders.LoadSecurityConfig()
|
||||
if securityConfig == nil {
|
||||
action.AddHeader("X-Frame-Options", "SAMEORIGIN")
|
||||
@@ -42,6 +42,12 @@ func (this *UserShouldAuth) BeforeAction(actionPtr actions.ActionWrapper, paramN
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查请求
|
||||
if !checkRequestSecurity(securityConfig, action.Request) {
|
||||
action.ResponseWriter.WriteHeader(http.StatusForbidden)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"net"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@@ -106,3 +108,41 @@ func checkIPWithoutCache(config *systemconfigs.SecurityConfig, ipAddr string) bo
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// 请求检查相关正则
|
||||
var searchEngineRegex = regexp.MustCompile(`60spider|adldxbot|adsbot-google|applebot|admantx|alexa|baidu|bingbot|bingpreview|facebookexternalhit|googlebot|proximic|slurp|sogou|twitterbot|yandex`)
|
||||
var spiderRegexp = regexp.MustCompile(`python|pycurl|http-client|httpclient|apachebench|nethttp|http_request|java|perl|ruby|scrapy|php|rust|curl|wget`) // 其中增加了curl和wget
|
||||
|
||||
// 检查请求
|
||||
func checkRequestSecurity(securityConfig *systemconfigs.SecurityConfig, req *http.Request) bool {
|
||||
if securityConfig == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
var userAgent = req.UserAgent()
|
||||
var referer = req.Referer()
|
||||
|
||||
// 检查搜索引擎
|
||||
if securityConfig.DenySearchEngines && (len(userAgent) == 0 || searchEngineRegex.MatchString(userAgent) || (len(referer) > 0 && searchEngineRegex.MatchString(referer))) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查爬虫
|
||||
if securityConfig.DenySpiders && (len(userAgent) == 0 || spiderRegexp.MatchString(userAgent) || (len(referer) > 0 && spiderRegexp.MatchString(referer))) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查允许访问的域名
|
||||
if len(securityConfig.AllowDomains) > 0 {
|
||||
var domain = req.Host
|
||||
realDomain, _, err := net.SplitHostPort(domain)
|
||||
if err == nil && len(realDomain) > 0 {
|
||||
domain = realDomain
|
||||
}
|
||||
if !lists.ContainsString(securityConfig.AllowDomains, domain) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package web
|
||||
import (
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/tasks"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/about"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/files"
|
||||
|
||||
// 系统用户
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/admins"
|
||||
@@ -39,6 +40,7 @@ import (
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/certs"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/components"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/components/cache"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/components/cache/batch"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/components/log"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/components/waf"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/groups"
|
||||
|
||||
@@ -417,48 +417,191 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="margin"></div>
|
||||
</div>`}),Vue.component("ns-route-ranges-box",{props:["v-ranges"],data:function(){let e=this.vRanges;return{ranges:e=null==e?[]:e,isAdding:!1,isAddingBatch:!1,ipRangeFrom:"",ipRangeTo:"",batchIPRange:""}},methods:{add:function(){this.isAdding=!0;let e=this;setTimeout(function(){e.$refs.ipRangeFrom.focus()},100)},remove:function(e){this.ranges.$remove(e)},cancelIPRange:function(){this.isAdding=!1,this.ipRangeFrom="",this.ipRangeTo=""},confirmIPRange:function(){let e=this;this.ipRangeFrom=this.ipRangeFrom.trim(),this.validateIP(this.ipRangeFrom)?(this.ipRangeTo=this.ipRangeTo.trim(),this.validateIP(this.ipRangeTo)?(this.ranges.push({type:"ipRange",params:{ipFrom:this.ipRangeFrom,ipTo:this.ipRangeTo}}),this.cancelIPRange()):teaweb.warn("结束IP填写错误",function(){e.$refs.ipRangeTo.focus()})):teaweb.warn("开始IP填写错误",function(){e.$refs.ipRangeFrom.focus()})},addBatch:function(){this.isAddingBatch=!0;let e=this;setTimeout(function(){e.$refs.batchIPRange.focus()},100)},cancelBatchIPRange:function(){this.isAddingBatch=!1,this.batchIPRange=""},confirmBatchIPRange:function(){let a=this,e=this.batchIPRange;if(0==e.length)teaweb.warn("请填写要加入的IP范围",function(){a.$refs.batchIPRange.focus()});else{let n=[],o="";e.split("\n").forEach(function(t){if(0!=(t=t.trim()).length){let e=(t=t.replace(",",",")).split(",");var i,s;2!=e.length?o=t:(i=e[0].trim(),s=e[1].trim(),a.validateIP(i)&&a.validateIP(s)?n.push({type:"ipRange",params:{ipFrom:i,ipTo:s}}):o=t)}}),0<o.length?teaweb.warn("'"+o+"'格式错误",function(){a.$refs.batchIPRange.focus()}):(n.forEach(function(e){a.ranges.push(e)}),this.cancelBatchIPRange())}},validateIP:function(e){if(!e.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/))return!1;let t=e.split("."),i=!0;return t.forEach(function(e){255<parseInt(e)&&(i=!1)}),i}},template:`<div>
|
||||
</div>`}),Vue.component("ns-route-ranges-box",{props:["v-ranges"],data:function(){let e=this.vRanges;return{ranges:e=null==e?[]:e,isAdding:!1,isAddingBatch:!1,rangeType:"ipRange",isReverse:!1,ipRangeFrom:"",ipRangeTo:"",batchIPRange:"",ipCIDR:"",batchIPCIDR:"",regions:[],regionType:"country"}},methods:{addIPRange:function(){this.isAdding=!0;let e=this;setTimeout(function(){e.$refs.ipRangeFrom.focus()},100)},addCIDR:function(){this.isAdding=!0;let e=this;setTimeout(function(){e.$refs.ipCIDR.focus()},100)},addRegions:function(){this.isAdding=!0},addRegion:function(e){this.regionType=e},remove:function(e){this.ranges.$remove(e)},cancelIPRange:function(){this.isAdding=!1,this.ipRangeFrom="",this.ipRangeTo="",this.isReverse=!1},cancelIPCIDR:function(){this.isAdding=!1,this.ipCIDR="",this.isReverse=!1},cancelRegions:function(){this.isAdding=!1,this.regions=[],this.regionType="country",this.isReverse=!1},confirmIPRange:function(){let e=this;this.ipRangeFrom=this.ipRangeFrom.trim(),this.validateIP(this.ipRangeFrom)?(this.ipRangeTo=this.ipRangeTo.trim(),this.validateIP(this.ipRangeTo)?(this.ranges.push({type:"ipRange",params:{ipFrom:this.ipRangeFrom,ipTo:this.ipRangeTo,isReverse:this.isReverse}}),this.cancelIPRange()):teaweb.warn("结束IP填写错误",function(){e.$refs.ipRangeTo.focus()})):teaweb.warn("开始IP填写错误",function(){e.$refs.ipRangeFrom.focus()})},confirmIPCIDR:function(){let e=this;0==this.ipCIDR.length?teaweb.warn("请填写CIDR",function(){e.$refs.ipCIDR.focus()}):this.validateCIDR(this.ipCIDR)?(this.ranges.push({type:"cidr",params:{cidr:this.ipCIDR,isReverse:this.isReverse}}),this.cancelIPCIDR()):teaweb.warn("请输入正确的CIDR",function(){e.$refs.ipCIDR.focus()})},confirmRegions:function(){0==this.regions.length||this.ranges.push({type:"region",params:{regions:this.regions,isReverse:this.isReverse}}),this.cancelRegions()},addBatchIPRange:function(){this.isAddingBatch=!0;let e=this;setTimeout(function(){e.$refs.batchIPRange.focus()},100)},addBatchCIDR:function(){this.isAddingBatch=!0;let e=this;setTimeout(function(){e.$refs.batchIPCIDR.focus()},100)},cancelBatchIPRange:function(){this.isAddingBatch=!1,this.batchIPRange="",this.isReverse=!1},cancelBatchIPCIDR:function(){this.isAddingBatch=!1,this.batchIPCIDR="",this.isReverse=!1},confirmBatchIPRange:function(){let a=this,e=this.batchIPRange;if(0==e.length)teaweb.warn("请填写要加入的IP范围",function(){a.$refs.batchIPRange.focus()});else{let n=[],o="";e.split("\n").forEach(function(t){if(0!=(t=t.trim()).length){let e=(t=t.replace(",",",")).split(",");var i,s;2!=e.length?o=t:(i=e[0].trim(),s=e[1].trim(),a.validateIP(i)&&a.validateIP(s)?n.push({type:"ipRange",params:{ipFrom:i,ipTo:s,isReverse:a.isReverse}}):o=t)}}),0<o.length?teaweb.warn("'"+o+"'格式错误",function(){a.$refs.batchIPRange.focus()}):(n.forEach(function(e){a.ranges.push(e)}),this.cancelBatchIPRange())}},confirmBatchIPCIDR:function(){let n=this,e=this.batchIPCIDR;if(0==e.length)teaweb.warn("请填写要加入的CIDR",function(){n.$refs.batchIPCIDR.focus()});else{let i=[],s="";e.split("\n").forEach(function(e){var t=e.trim();0!=t.length&&(n.validateCIDR(t)?i.push({type:"cidr",params:{cidr:t,isReverse:n.isReverse}}):s=e)}),0<s.length?teaweb.warn("'"+s+"'格式错误",function(){n.$refs.batchIPCIDR.focus()}):(i.forEach(function(e){n.ranges.push(e)}),this.cancelBatchIPCIDR())}},selectRegionCountry:function(e){null!=e&&(this.regions.push({type:"country",id:e.id,name:e.name}),this.$refs.regionCountryComboBox.clear())},selectRegionProvince:function(e){null!=e&&(this.regions.push({type:"province",id:e.id,name:e.name}),this.$refs.regionProvinceComboBox.clear())},selectRegionCity:function(e){null!=e&&(this.regions.push({type:"city",id:e.id,name:e.name}),this.$refs.regionCityComboBox.clear())},selectRegionProvider:function(e){null!=e&&(this.regions.push({type:"provider",id:e.id,name:e.name}),this.$refs.regionProviderComboBox.clear())},removeRegion:function(e){this.regions.$remove(e)},validateIP:function(i){if(0!=i.length){if(0<=i.indexOf(":")){let e=i.split(":");if(8<e.length)return!1;let t=!0;return e.forEach(function(e){/^[\da-fA-F]{0,4}$/.test(e)||(t=!1)}),t}if(!i.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/))return!1;let e=i.split("."),t=!0;return e.forEach(function(e){255<parseInt(e)&&(t=!1)}),t}},validateCIDR:function(e){var t=e.split("/");if(2!=t.length)return!1;var i=t[0];if(!this.validateIP(i))return!1;i=t[1];return!!/^\d{1,3}$/.test(i)&&(i=parseInt(i,10),0<=e.indexOf(":")?i<=128:i<=32)},updateRangeType:function(e){this.rangeType=e}},template:`<div>
|
||||
<input type="hidden" name="rangesJSON" :value="JSON.stringify(ranges)"/>
|
||||
<div v-if="ranges.length > 0">
|
||||
<div class="ui label tiny basic" v-for="(range, index) in ranges" style="margin-bottom: 0.3em">
|
||||
<span class="red" v-if="range.params.isReverse">[排除]</span>
|
||||
<span v-if="range.type == 'ipRange'">IP范围:</span>
|
||||
{{range.params.ipFrom}} - {{range.params.ipTo}} <a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove small"></i></a>
|
||||
<span v-if="range.type == 'cidr'">CIDR:</span>
|
||||
<span v-if="range.type == 'region'">区域:</span>
|
||||
<span v-if="range.type == 'ipRange'">{{range.params.ipFrom}} - {{range.params.ipTo}}</span>
|
||||
<span v-if="range.type == 'cidr'">{{range.params.cidr}}</span>
|
||||
<span v-if="range.type == 'region'"><span v-for="(region, index) in range.params.regions">{{region.name}}<span v-if="index < range.params.regions.length - 1">,</span></span></span>
|
||||
<a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
</div>
|
||||
|
||||
<!-- 添加单个 -->
|
||||
<div style="margin-bottom: 1em" v-show="isAdding">
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<input type="text" placeholder="开始IP" maxlength="15" size="15" v-model="ipRangeFrom" ref="ipRangeFrom" @keyup.enter="confirmIPRange" @keypress.enter.prevent="1"/>
|
||||
</div>
|
||||
<div class="ui field">-</div>
|
||||
<div class="ui field">
|
||||
<input type="text" placeholder="结束IP" maxlength="15" size="15" v-model="ipRangeTo" ref="ipRangeTo" @keyup.enter="confirmIPRange" @keypress.enter.prevent="1"/>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<button class="ui button tiny" type="button" @click.prevent="confirmIPRange">确定</button>
|
||||
<a href="" @click.prevent="cancelIPRange" title="取消"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
<!-- IP范围 -->
|
||||
<div v-if="rangeType == 'ipRange'">
|
||||
<!-- 添加单个IP范围 -->
|
||||
<div style="margin-bottom: 1em" v-show="isAdding">
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td class="title">开始IP *</td>
|
||||
<td>
|
||||
<input type="text" placeholder="开始IP" maxlength="40" size="40" style="width: 15em" v-model="ipRangeFrom" ref="ipRangeFrom" @keyup.enter="confirmIPRange" @keypress.enter.prevent="1"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>结束IP *</td>
|
||||
<td>
|
||||
<input type="text" placeholder="结束IP" maxlength="40" size="40" style="width: 15em" v-model="ipRangeTo" ref="ipRangeTo" @keyup.enter="confirmIPRange" @keypress.enter.prevent="1"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>排除</td>
|
||||
<td>
|
||||
<checkbox v-model="isReverse"></checkbox>
|
||||
<p class="comment">选中后表示线路中排除当前条件。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<button class="ui button tiny" type="button" @click.prevent="confirmIPRange">确定</button>
|
||||
<a href="" @click.prevent="cancelIPRange" title="取消"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 添加多个 -->
|
||||
<div style="margin-bottom: 1em" v-show="isAddingBatch">
|
||||
<div class="ui field">
|
||||
<textarea rows="5" ref="batchIPRange" v-model="batchIPRange"></textarea>
|
||||
<p class="comment">每行一条,格式为<code-label>开始IP,结束IP</code-label>,比如<code-label>192.168.1.100,192.168.1.200</code-label>。</p>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
|
||||
<!-- 添加多个IP范围 -->
|
||||
<div style="margin-bottom: 1em" v-show="isAddingBatch">
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td class="title">IP范围列表 *</td>
|
||||
<td>
|
||||
<textarea rows="5" ref="batchIPRange" v-model="batchIPRange"></textarea>
|
||||
<p class="comment">每行一条,格式为<code-label>开始IP,结束IP</code-label>,比如<code-label>192.168.1.100,192.168.1.200</code-label>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>排除</td>
|
||||
<td>
|
||||
<checkbox v-model="isReverse"></checkbox>
|
||||
<p class="comment">选中后表示线路中排除当前条件。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<button class="ui button tiny" type="button" @click.prevent="confirmBatchIPRange">确定</button>
|
||||
<a href="" @click.prevent="cancelBatchIPRange" title="取消"><i class="icon remove small"></i></a>
|
||||
<a href="" @click.prevent="cancelBatchIPRange" title="取消"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
|
||||
<div v-if="!isAdding && !isAddingBatch">
|
||||
<button class="ui button tiny" type="button" @click.prevent="addIPRange">添加单个IP范围</button>
|
||||
<button class="ui button tiny" type="button" @click.prevent="addBatchIPRange">批量添加IP范围</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!isAdding && !isAddingBatch">
|
||||
<button class="ui button tiny" type="button" @click.prevent="add">单个添加</button>
|
||||
<button class="ui button tiny" type="button" @click.prevent="addBatch">批量添加</button>
|
||||
<!-- CIDR -->
|
||||
<div v-if="rangeType == 'cidr'">
|
||||
<!-- 添加单个IP范围 -->
|
||||
<div style="margin-bottom: 1em" v-show="isAdding">
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td class="title">CIDR *</td>
|
||||
<td>
|
||||
<input type="text" placeholder="IP/MASK" maxlength="40" size="40" style="width: 15em" v-model="ipCIDR" ref="ipCIDR" @keyup.enter="confirmIPCIDR" @keypress.enter.prevent="1"/>
|
||||
<p class="comment">类似于<code-label>192.168.2.1/24</code-label>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>排除</td>
|
||||
<td>
|
||||
<checkbox v-model="isReverse"></checkbox>
|
||||
<p class="comment">选中后表示线路中排除当前条件。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<button class="ui button tiny" type="button" @click.prevent="confirmIPCIDR">确定</button>
|
||||
<a href="" @click.prevent="cancelIPCIDR" title="取消"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
|
||||
<!-- 添加多个IP范围 -->
|
||||
<div style="margin-bottom: 1em" v-show="isAddingBatch">
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td class="title">IP范围列表 *</td>
|
||||
<td>
|
||||
<textarea rows="5" ref="batchIPCIDR" v-model="batchIPCIDR"></textarea>
|
||||
<p class="comment">每行一条,格式为<code-label>IP/MASK</code-label>,比如<code-label>192.168.2.1/24</code-label>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>排除</td>
|
||||
<td>
|
||||
<checkbox v-model="isReverse"></checkbox>
|
||||
<p class="comment">选中后表示线路中排除当前条件。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<button class="ui button tiny" type="button" @click.prevent="confirmBatchIPCIDR">确定</button>
|
||||
<a href="" @click.prevent="cancelBatchIPCIDR" title="取消"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
|
||||
<div v-if="!isAdding && !isAddingBatch">
|
||||
<button class="ui button tiny" type="button" @click.prevent="addCIDR">添加单个CIDR</button>
|
||||
<button class="ui button tiny" type="button" @click.prevent="addBatchCIDR">批量添加CIDR</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 区域 -->
|
||||
<div v-if="rangeType == 'region'">
|
||||
<!-- 添加区域 -->
|
||||
<div v-if="isAdding">
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td>已添加</td>
|
||||
<td>
|
||||
<div v-for="(region, index) in regions" class="ui label small basic">
|
||||
{{region.name}} <a href="" title="删除" @click.prevent="removeRegion(index)"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title">添加新<span v-if="regionType == 'country'">国家/地区</span><span v-if="regionType == 'province'">省份</span><span v-if="regionType == 'city'">城市</span><span v-if="regionType == 'provider'">ISP</span>
|
||||
|
||||
*</td>
|
||||
<td>
|
||||
<!-- region country name -->
|
||||
<div v-if="regionType == 'country'">
|
||||
<combo-box title="" width="14em" data-url="/ui/countryOptions" data-key="countries" placeholder="点这里选择国家/地区" @change="selectRegionCountry" ref="regionCountryComboBox" key="combo-box-country"></combo-box>
|
||||
</div>
|
||||
|
||||
<!-- region province name -->
|
||||
<div v-if="regionType == 'province'" >
|
||||
<combo-box title="" data-url="/ui/provinceOptions" data-key="provinces" placeholder="点这里选择省份" @change="selectRegionProvince" ref="regionProvinceComboBox" key="combo-box-province"></combo-box>
|
||||
</div>
|
||||
|
||||
<!-- region city name -->
|
||||
<div v-if="regionType == 'city'" >
|
||||
<combo-box title="" data-url="/ui/cityOptions" data-key="cities" placeholder="点这里选择城市" @change="selectRegionCity" ref="regionCityComboBox" key="combo-box-city"></combo-box>
|
||||
</div>
|
||||
|
||||
<!-- ISP Name -->
|
||||
<div v-if="regionType == 'provider'" >
|
||||
<combo-box title="" data-url="/ui/providerOptions" data-key="providers" placeholder="点这里选择ISP" @change="selectRegionProvider" ref="regionProviderComboBox" key="combo-box-isp"></combo-box>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 1em">
|
||||
<button class="ui button tiny basic" :class="{blue: regionType == 'country'}" type="button" @click.prevent="addRegion('country')">添加国家/地区</button>
|
||||
<button class="ui button tiny basic" :class="{blue: regionType == 'province'}" type="button" @click.prevent="addRegion('province')">添加省份</button>
|
||||
<button class="ui button tiny basic" :class="{blue: regionType == 'city'}" type="button" @click.prevent="addRegion('city')">添加城市</button>
|
||||
<button class="ui button tiny basic" :class="{blue: regionType == 'provider'}" type="button" @click.prevent="addRegion('provider')">ISP</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>排除</td>
|
||||
<td>
|
||||
<checkbox v-model="isReverse"></checkbox>
|
||||
<p class="comment">选中后表示线路中排除当前条件。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<button class="ui button tiny" type="button" @click.prevent="confirmRegions">确定</button>
|
||||
<a href="" @click.prevent="cancelRegions" title="取消"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
<div v-if="!isAdding && !isAddingBatch">
|
||||
<button class="ui button tiny" type="button" @click.prevent="addRegions">添加区域</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`}),Vue.component("ns-route-selector",{props:["v-route-code"],mounted:function(){let t=this;Tea.action("/ns/routes/options").post().success(function(e){t.routes=e.data.routes})},data:function(){let e=this.vRouteCode;return{routeCode:e=null==e?"":e,routes:[]}},template:`<div>
|
||||
<div v-if="routes.length > 0">
|
||||
@@ -512,15 +655,15 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
按{{plan.bandwidthPrice.percentile}}th带宽计费
|
||||
<div>
|
||||
<div v-for="range in plan.bandwidthPrice.ranges">
|
||||
<span class="small grey">{{range.minMB}} - <span v-if="range.maxMB > 0">{{range.maxMB}}MB</span><span v-else>∞</span>: {{range.pricePerMB}}元/MB</span>
|
||||
<span class="small grey">{{range.minMB}} - <span v-if="range.maxMB > 0">{{range.maxMB}}MB</span><span v-else>∞</span>: <span v-if="range.totalPrice > 0">{{range.totalPrice}}元</span><span v-else="">{{range.pricePerMB}}元/MB</span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`}),Vue.component("plan-bandwidth-ranges",{props:["v-ranges"],data:function(){let e=this.vRanges;return{ranges:e=null==e?[]:e,isAdding:!1,minMB:"",maxMB:"",pricePerMB:"",addingRange:{minMB:0,maxMB:0,pricePerMB:0,totalPrice:0}}},methods:{add:function(){this.isAdding=!this.isAdding;let e=this;setTimeout(function(){e.$refs.minMB.focus()})},cancelAdding:function(){this.isAdding=!1},confirm:function(){this.isAdding=!1,this.minMB="",this.maxMB="",this.pricePerMB="",this.ranges.push(this.addingRange),this.ranges.$sort(function(e,t){return e.minMB<t.minMB?-1:e.minMB==t.minMB?0:1}),this.change(),this.addingRange={minMB:0,maxMB:0,pricePerMB:0,totalPrice:0}},remove:function(e){this.ranges.$remove(e),this.change()},change:function(){this.$emit("change",this.ranges)}},watch:{minMB:function(e){let t=parseInt(e.toString());(isNaN(t)||t<0)&&(t=0),this.addingRange.minMB=t},maxMB:function(e){let t=parseInt(e.toString());(isNaN(t)||t<0)&&(t=0),this.addingRange.maxMB=t},pricePerMB:function(e){let t=parseFloat(e.toString());(isNaN(t)||t<0)&&(t=0),this.addingRange.pricePerMB=t}},template:`<div>
|
||||
</div>`}),Vue.component("plan-bandwidth-ranges",{props:["v-ranges"],data:function(){let e=this.vRanges;return{ranges:e=null==e?[]:e,isAdding:!1,minMB:"",maxMB:"",pricePerMB:"",totalPrice:"",addingRange:{minMB:0,maxMB:0,pricePerMB:0,totalPrice:0}}},methods:{add:function(){this.isAdding=!this.isAdding;let e=this;setTimeout(function(){e.$refs.minMB.focus()})},cancelAdding:function(){this.isAdding=!1},confirm:function(){this.isAdding=!1,this.minMB="",this.maxMB="",this.pricePerMB="",this.totalPrice="",this.ranges.push(this.addingRange),this.ranges.$sort(function(e,t){return e.minMB<t.minMB?-1:e.minMB==t.minMB?0==t.maxMB||e.maxMB<t.maxMB?-1:0:1}),this.change(),this.addingRange={minMB:0,maxMB:0,pricePerMB:0,totalPrice:0}},remove:function(e){this.ranges.$remove(e),this.change()},change:function(){this.$emit("change",this.ranges)}},watch:{minMB:function(e){let t=parseInt(e.toString());(isNaN(t)||t<0)&&(t=0),this.addingRange.minMB=t},maxMB:function(e){let t=parseInt(e.toString());(isNaN(t)||t<0)&&(t=0),this.addingRange.maxMB=t},pricePerMB:function(e){let t=parseFloat(e.toString());(isNaN(t)||t<0)&&(t=0),this.addingRange.pricePerMB=t},totalPrice:function(e){let t=parseFloat(e.toString());(isNaN(t)||t<0)&&(t=0),this.addingRange.totalPrice=t}},template:`<div>
|
||||
<!-- 已有价格 -->
|
||||
<div v-if="ranges.length > 0">
|
||||
<div class="ui label basic small" v-for="(range, index) in ranges" style="margin-bottom: 0.5em">
|
||||
{{range.minMB}}MB - <span v-if="range.maxMB > 0">{{range.maxMB}}MB</span><span v-else>∞</span> 价格:{{range.pricePerMB}}元/MB
|
||||
{{range.minMB}}MB - <span v-if="range.maxMB > 0">{{range.maxMB}}MB</span><span v-else>∞</span> 价格:<span v-if="range.totalPrice > 0">{{range.totalPrice}}元</span><span v-else="">{{range.pricePerMB}}元/MB</span>
|
||||
<a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
@@ -530,7 +673,7 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<div v-if="isAdding">
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td class="title">带宽下限</td>
|
||||
<td class="title">带宽下限 *</td>
|
||||
<td>
|
||||
<div class="ui input right labeled">
|
||||
<input type="text" placeholder="最小带宽" style="width: 7em" maxlength="10" ref="minMB" @keyup.enter="confirm()" @keypress.enter.prevent="1" v-model="minMB"/>
|
||||
@@ -539,7 +682,7 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title">带宽上限</td>
|
||||
<td class="title">带宽上限 *</td>
|
||||
<td>
|
||||
<div class="ui input right labeled">
|
||||
<input type="text" placeholder="最大带宽" style="width: 7em" maxlength="10" @keyup.enter="confirm()" @keypress.enter.prevent="1" v-model="maxMB"/>
|
||||
@@ -548,6 +691,16 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<p class="comment">如果填0,表示上不封顶。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>总价格</td>
|
||||
<td>
|
||||
<div class="ui input right labeled">
|
||||
<input type="text" placeholder="总价格" style="width: 7em" maxlength="10" @keyup.enter="confirm()" @keypress.enter.prevent="1" v-model="totalPrice"/>
|
||||
<span class="ui label">元/MB</span>
|
||||
</div>
|
||||
<p class="comment">和单位价格二选一。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title">单位价格</td>
|
||||
<td>
|
||||
@@ -555,6 +708,7 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<input type="text" placeholder="单位价格" style="width: 7em" maxlength="10" @keyup.enter="confirm()" @keypress.enter.prevent="1" v-model="pricePerMB"/>
|
||||
<span class="ui label">元/MB</span>
|
||||
</div>
|
||||
<p class="comment">和总价格二选一。如果设置了单位价格,那么"总价格 = 单位价格 x 带宽"。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -566,7 +720,7 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<div v-if="!isAdding">
|
||||
<button class="ui button small" type="button" @click.prevent="add">+</button>
|
||||
</div>
|
||||
</div>`}),Vue.component("plan-price-config-box",{props:["v-price-type","v-monthly-price","v-seasonally-price","v-yearly-price","v-traffic-price","v-bandwidth-price","v-disable-period"],data:function(){let e=this.vPriceType,t=(null==e&&(e="bandwidth"),0),i=this.vMonthlyPrice,s=(null==i||i<=0?i="":(i=i.toString(),t=parseFloat(i),isNaN(t)&&(t=0)),0),n=this.vSeasonallyPrice,o=(null==n||n<=0?n="":(n=n.toString(),s=parseFloat(n),isNaN(s)&&(s=0)),0),a=this.vYearlyPrice,l=(null==a||a<=0?a="":(a=a.toString(),o=parseFloat(a),isNaN(o)&&(o=0)),this.vTrafficPrice),c=0,r=(null!=l?c=l.base:l={base:0},""),d=(0<c&&(r=c.toString()),this.vBandwidthPrice);return null==d?d={percentile:95,ranges:[]}:null==d.ranges&&(d.ranges=[]),{priceType:e,monthlyPrice:i,seasonallyPrice:n,yearlyPrice:a,monthlyPriceNumber:t,seasonallyPriceNumber:s,yearlyPriceNumber:o,trafficPriceBase:r,trafficPrice:l,bandwidthPrice:d,bandwidthPercentile:d.percentile}},methods:{changeBandwidthPriceRanges:function(e){this.bandwidthPrice.ranges=e}},watch:{monthlyPrice:function(e){let t=parseFloat(e);isNaN(t)&&(t=0),this.monthlyPriceNumber=t},seasonallyPrice:function(e){let t=parseFloat(e);isNaN(t)&&(t=0),this.seasonallyPriceNumber=t},yearlyPrice:function(e){let t=parseFloat(e);isNaN(t)&&(t=0),this.yearlyPriceNumber=t},trafficPriceBase:function(e){let t=parseFloat(e);isNaN(t)&&(t=0),this.trafficPrice.base=t},bandwidthPercentile:function(e){let t=parseInt(e);isNaN(t)||t<=0?t=95:100<t&&(t=100),this.bandwidthPrice.percentile=t}},template:`<div>
|
||||
</div>`}),Vue.component("plan-price-config-box",{props:["v-price-type","v-monthly-price","v-seasonally-price","v-yearly-price","v-traffic-price","v-bandwidth-price","v-disable-period"],data:function(){let e=this.vPriceType,t=(null==e&&(e="bandwidth"),0),i=this.vMonthlyPrice,s=(null==i||i<=0?i="":(i=i.toString(),t=parseFloat(i),isNaN(t)&&(t=0)),0),n=this.vSeasonallyPrice,o=(null==n||n<=0?n="":(n=n.toString(),s=parseFloat(n),isNaN(s)&&(s=0)),0),a=this.vYearlyPrice,l=(null==a||a<=0?a="":(a=a.toString(),o=parseFloat(a),isNaN(o)&&(o=0)),this.vTrafficPrice),r=0,c=(null!=l?r=l.base:l={base:0},""),d=(0<r&&(c=r.toString()),this.vBandwidthPrice);return null==d?d={percentile:95,ranges:[]}:null==d.ranges&&(d.ranges=[]),{priceType:e,monthlyPrice:i,seasonallyPrice:n,yearlyPrice:a,monthlyPriceNumber:t,seasonallyPriceNumber:s,yearlyPriceNumber:o,trafficPriceBase:c,trafficPrice:l,bandwidthPrice:d,bandwidthPercentile:d.percentile}},methods:{changeBandwidthPriceRanges:function(e){this.bandwidthPrice.ranges=e}},watch:{monthlyPrice:function(e){let t=parseFloat(e);isNaN(t)&&(t=0),this.monthlyPriceNumber=t},seasonallyPrice:function(e){let t=parseFloat(e);isNaN(t)&&(t=0),this.seasonallyPriceNumber=t},yearlyPrice:function(e){let t=parseFloat(e);isNaN(t)&&(t=0),this.yearlyPriceNumber=t},trafficPriceBase:function(e){let t=parseFloat(e);isNaN(t)&&(t=0),this.trafficPrice.base=t},bandwidthPercentile:function(e){let t=parseInt(e);isNaN(t)||t<=0?t=95:100<t&&(t=100),this.bandwidthPrice.percentile=t}},template:`<div>
|
||||
<input type="hidden" name="priceType" :value="priceType"/>
|
||||
<input type="hidden" name="monthlyPrice" :value="monthlyPriceNumber"/>
|
||||
<input type="hidden" name="seasonallyPrice" :value="seasonallyPriceNumber"/>
|
||||
@@ -1407,7 +1561,7 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>`}),Vue.component("http-firewall-checkpoint-cc",{props:["v-checkpoint"],data:function(){let e=[],t=60,i=1e3,s={},n=(null==(s=null!=window.parent.UPDATING_RULE?window.parent.UPDATING_RULE.checkpointOptions:s)&&(s={}),0==(e=null!=s.keys?s.keys:e).length&&(e=["${remoteAddr}","${requestPath}"]),null!=s.period&&(t=s.period),null!=s.threshold&&(i=s.threshold),this);return setTimeout(function(){n.change()},100),{keys:e,period:t,threshold:i,options:{},value:i}},watch:{period:function(){this.change()},threshold:function(){this.change()}},methods:{changeKeys:function(e){this.keys=e,this.change()},change:function(){let e=parseInt(this.period.toString()),t=((isNaN(e)||e<=0)&&(e=60),parseInt(this.threshold.toString()));(isNaN(t)||t<=0)&&(t=1e3),this.value=t,this.vCheckpoint.options=[{code:"keys",value:this.keys},{code:"period",value:e},{code:"threshold",value:t}]}},template:`<div>
|
||||
</div>`}),Vue.component("http-firewall-checkpoint-cc",{props:["v-checkpoint"],data:function(){let e=[],t=60,i=1e3,s=!1,n={},o=(null==(n=null!=window.parent.UPDATING_RULE?window.parent.UPDATING_RULE.checkpointOptions:n)&&(n={}),0==(e=null!=n.keys?n.keys:e).length&&(e=["${remoteAddr}","${requestPath}"]),null!=n.period&&(t=n.period),null!=n.threshold&&(i=n.threshold),null!=n.ignoreCommonFiles&&"boolean"==typeof n.ignoreCommonFiles&&(s=n.ignoreCommonFiles),this);return setTimeout(function(){o.change()},100),{keys:e,period:t,threshold:i,ignoreCommonFiles:s,options:{},value:i}},watch:{period:function(){this.change()},threshold:function(){this.change()},ignoreCommonFiles:function(){this.change()}},methods:{changeKeys:function(e){this.keys=e,this.change()},change:function(){let e=parseInt(this.period.toString()),t=((isNaN(e)||e<=0)&&(e=60),parseInt(this.threshold.toString())),i=((isNaN(t)||t<=0)&&(t=1e3),this.value=t,this.ignoreCommonFiles);"boolean"!=typeof i&&(i=!1),this.vCheckpoint.options=[{code:"keys",value:this.keys},{code:"period",value:e},{code:"threshold",value:t},{code:"ignoreCommonFiles",value:i}]}},template:`<div>
|
||||
<input type="hidden" name="operator" value="gt"/>
|
||||
<input type="hidden" name="value" :value="value"/>
|
||||
<table class="ui table">
|
||||
@@ -1432,6 +1586,13 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<input type="text" v-model="threshold" style="width: 6em" maxlength="8"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>忽略常见文件</td>
|
||||
<td>
|
||||
<checkbox v-model="ignoreCommonFiles"></checkbox>
|
||||
<p class="comment">忽略js、css、jpg等常见在网页里被引用的文件名。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>`}),Vue.component("http-firewall-checkpoint-referer-block",{props:["v-checkpoint"],data:function(){let e=!0,t=!0,i=[],s={},n=("boolean"==typeof(s=null==(s=null!=window.parent.UPDATING_RULE?window.parent.UPDATING_RULE.checkpointOptions:s)?{}:s).allowEmpty&&(e=s.allowEmpty),"boolean"==typeof s.allowSameDomain&&(t=s.allowSameDomain),null!=s.allowDomains&&"object"==typeof s.allowDomains&&(i=s.allowDomains),this);return setTimeout(function(){n.change()},100),{allowEmpty:e,allowSameDomain:t,allowDomains:i,options:{},value:0}},watch:{allowEmpty:function(){this.change()},allowSameDomain:function(){this.change()}},methods:{changeAllowDomains:function(e){this.allowDomains=e,this.change()},change:function(){this.vCheckpoint.options=[{code:"allowEmpty",value:this.allowEmpty},{code:"allowSameDomain",value:this.allowSameDomain},{code:"allowDomains",value:this.allowDomains}]}},template:`<div>
|
||||
<input type="hidden" name="operator" value="eq"/>
|
||||
@@ -1536,10 +1697,11 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
</thead>
|
||||
<tr v-for="origin in vOrigins">
|
||||
<td :class="{disabled:!origin.isOn}"><a href="" @click.prevent="updateOrigin(origin.id)">{{origin.addr}} <i class="icon expand small"></i></a>
|
||||
<div style="margin-top: 0.3em" v-if="origin.name.length > 0 || origin.hasCert || (origin.host != null && origin.host.length > 0) || (origin.domains != null && origin.domains.length > 0)">
|
||||
<div style="margin-top: 0.3em" v-if="origin.name.length > 0 || origin.hasCert || (origin.host != null && origin.host.length > 0) || origin.followPort || (origin.domains != null && origin.domains.length > 0)">
|
||||
<tiny-basic-label v-if="origin.name.length > 0">{{origin.name}}</tiny-basic-label>
|
||||
<tiny-basic-label v-if="origin.hasCert">证书</tiny-basic-label>
|
||||
<tiny-basic-label v-if="origin.host != null && origin.host.length > 0">主机名: {{origin.host}}</tiny-basic-label>
|
||||
<tiny-basic-label v-if="origin.followPort">端口跟随</tiny-basic-label>
|
||||
<span v-if="origin.domains != null && origin.domains.length > 0"><tiny-basic-label v-for="domain in origin.domains">匹配: {{domain}}</tiny-basic-label></span>
|
||||
</div>
|
||||
</td>
|
||||
@@ -2161,11 +2323,8 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<button class="ui button small" type="button" @click.prevent="add">+添加认证方式</button>
|
||||
</div>
|
||||
<div class="margin"></div>
|
||||
</div>`}),Vue.component("user-selector",{mounted:function(){let t=this;Tea.action("/servers/users/options").post().success(function(e){t.users=e.data.users})},props:["v-user-id"],data:function(){let e=this.vUserId;return{users:[],userId:e=null==e?0:e}},watch:{userId:function(e){this.$emit("change",e)}},template:`<div>
|
||||
<select class="ui dropdown auto-width" name="userId" v-model="userId">
|
||||
<option value="0">[选择用户]</option>
|
||||
<option v-for="user in users" :value="user.id">{{user.fullname}} ({{user.username}})</option>
|
||||
</select>
|
||||
</div>`}),Vue.component("user-selector",{props:["v-user-id"],data:function(){let e=this.vUserId;return{users:[],userId:e=null==e?0:e}},watch:{userId:function(e){this.$emit("change",e)}},template:`<div>
|
||||
<combo-box placeholder="选择用户" :data-url="'/servers/users/options'" :data-key="'users'" name="userId" :v-value="userId"></combo-box>
|
||||
</div>`}),Vue.component("uam-config-box",{props:["v-uam-config","v-is-location","v-is-group"],data:function(){let e=this.vUamConfig;return{config:e=null==e?{isPrior:!1,isOn:!1}:e}},template:`<div>
|
||||
<input type="hidden" name="uamJSON" :value="JSON.stringify(config)"/>
|
||||
<table class="ui table definition selectable">
|
||||
@@ -2416,7 +2575,7 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
</tr>
|
||||
</table>
|
||||
<div class="ui margin"></div>
|
||||
</div>`}),Vue.component("http-compression-config-box",{props:["v-compression-config","v-is-location","v-is-group"],mounted:function(){let e=this;sortLoad(function(){e.initSortableTypes()})},data:function(){let t=this.vCompressionConfig,e=(null==(t=null==t?{isPrior:!1,isOn:!1,useDefaultTypes:!0,types:["brotli","gzip","deflate"],level:5,decompressData:!1,gzipRef:null,deflateRef:null,brotliRef:null,minLength:{count:0,unit:"kb"},maxLength:{count:0,unit:"kb"},mimeTypes:["text/*","application/*","font/*"],extensions:[".js",".json",".html",".htm",".xml",".css",".woff2",".txt"],conds:null}:t).types&&(t.types=[]),null==t.mimeTypes&&(t.mimeTypes=[]),null==t.extensions&&(t.extensions=[]),[{name:"Gzip",code:"gzip",isOn:!0},{name:"Deflate",code:"deflate",isOn:!0},{name:"Brotli",code:"brotli",isOn:!0}]),i=[];return t.types.forEach(function(t){e.forEach(function(e){t==e.code&&(e.isOn=!0,i.push(e))})}),e.forEach(function(e){t.types.$contains(e.code)||(e.isOn=!1,i.push(e))}),{config:t,moreOptionsVisible:!1,allTypes:i}},watch:{"config.level":function(e){let t=parseInt(e);isNaN(t)||t<1?t=1:10<t&&(t=10),this.config.level=t}},methods:{isOn:function(){return(!this.vIsLocation&&!this.vIsGroup||this.config.isPrior)&&this.config.isOn},changeExtensions:function(i){i.forEach(function(e,t){0<e.length&&"."!=e[0]&&(i[t]="."+e)}),this.config.extensions=i},changeMimeTypes:function(e){this.config.mimeTypes=e},changeAdvancedVisible:function(){this.moreOptionsVisible=!this.moreOptionsVisible},changeConds:function(e){this.config.conds=e},changeType:function(){this.config.types=[];let t=this;this.allTypes.forEach(function(e){e.isOn&&t.config.types.push(e.code)})},initSortableTypes:function(){let s=document.querySelector("#compression-types-box"),n=this;Sortable.create(s,{draggable:".checkbox",handle:".icon.handle",onStart:function(){},onUpdate:function(e){let t=s.querySelectorAll(".checkbox"),i=[];t.forEach(function(e){e=e.getAttribute("data-code");i.push(e)}),n.config.types=i}})}},template:`<div>
|
||||
</div>`}),Vue.component("http-compression-config-box",{props:["v-compression-config","v-is-location","v-is-group"],mounted:function(){let e=this;sortLoad(function(){e.initSortableTypes()})},data:function(){let t=this.vCompressionConfig,e=(null==(t=null==t?{isPrior:!1,isOn:!1,useDefaultTypes:!0,types:["brotli","gzip","zstd","deflate"],level:5,decompressData:!1,gzipRef:null,deflateRef:null,brotliRef:null,minLength:{count:0,unit:"kb"},maxLength:{count:0,unit:"kb"},mimeTypes:["text/*","application/*","font/*"],extensions:[".js",".json",".html",".htm",".xml",".css",".woff2",".txt"],conds:null}:t).types&&(t.types=[]),null==t.mimeTypes&&(t.mimeTypes=[]),null==t.extensions&&(t.extensions=[]),[{name:"Gzip",code:"gzip",isOn:!0},{name:"Deflate",code:"deflate",isOn:!0},{name:"Brotli",code:"brotli",isOn:!0},{name:"ZSTD",code:"zstd",isOn:!0}]),i=[];return t.types.forEach(function(t){e.forEach(function(e){t==e.code&&(e.isOn=!0,i.push(e))})}),e.forEach(function(e){t.types.$contains(e.code)||(e.isOn=!1,i.push(e))}),{config:t,moreOptionsVisible:!1,allTypes:i}},watch:{"config.level":function(e){let t=parseInt(e);isNaN(t)||t<1?t=1:10<t&&(t=10),this.config.level=t}},methods:{isOn:function(){return(!this.vIsLocation&&!this.vIsGroup||this.config.isPrior)&&this.config.isOn},changeExtensions:function(i){i.forEach(function(e,t){0<e.length&&"."!=e[0]&&(i[t]="."+e)}),this.config.extensions=i},changeMimeTypes:function(e){this.config.mimeTypes=e},changeAdvancedVisible:function(){this.moreOptionsVisible=!this.moreOptionsVisible},changeConds:function(e){this.config.conds=e},changeType:function(){this.config.types=[];let t=this;this.allTypes.forEach(function(e){e.isOn&&t.config.types.push(e.code)})},initSortableTypes:function(){let s=document.querySelector("#compression-types-box"),n=this;Sortable.create(s,{draggable:".checkbox",handle:".icon.handle",onStart:function(){},onUpdate:function(e){let t=s.querySelectorAll(".checkbox"),i=[];t.forEach(function(e){e=e.getAttribute("data-code");i.push(e)}),n.config.types=i}})}},template:`<div>
|
||||
<input type="hidden" name="compressionJSON" :value="JSON.stringify(config)"/>
|
||||
<table class="ui table definition selectable">
|
||||
<prior-checkbox :v-config="config" v-if="vIsLocation || vIsGroup"></prior-checkbox>
|
||||
@@ -2463,7 +2622,7 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" v-model="config.useDefaultTypes" id="compression-use-default"/>
|
||||
<label v-if="config.useDefaultTypes" for="compression-use-default">使用默认顺序<span class="grey small">(brotli、gzip、deflate)</span></label>
|
||||
<label v-if="config.useDefaultTypes" for="compression-use-default">使用默认顺序<span class="grey small">(brotli、gzip、 zstd、deflate)</span></label>
|
||||
<label v-if="!config.useDefaultTypes" for="compression-use-default">使用默认顺序</label>
|
||||
</div>
|
||||
<div v-show="!config.useDefaultTypes">
|
||||
@@ -2600,10 +2759,14 @@ 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=""}},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})}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>
|
||||
<a v-if="accessLog.node != null && accessLog.node.nodeCluster != null" :href="'/clusters/cluster/node?nodeId=' + accessLog.node.id + '&clusterId=' + accessLog.node.nodeCluster.id" title="点击查看节点详情" target="_top"><span class="grey">[{{accessLog.node.name}}<span v-if="!accessLog.node.name.endsWith('节点')">节点</span>]</span></a>
|
||||
<a :href="'/servers/server/log?serverId=' + accessLog.serverId" title="点击到网站服务" v-if="vShowServerLink"><span class="grey">[服务]</span></a>
|
||||
|
||||
<!-- 服务 -->
|
||||
<a :href="'/servers/server/log?serverId=' + accessLog.serverId" title="点击到网站服务" v-if="vShowServerLink && accessLog.serverId > 0"><span class="grey">[服务]</span></a>
|
||||
<span v-if="vShowServerLink && (accessLog.serverId == null || accessLog.serverId == 0)" @click.prevent="mismatch()"><span class="disabled">[服务]</span></span>
|
||||
|
||||
<span v-if="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.wafInfo != null">
|
||||
@@ -2880,7 +3043,7 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
</div>
|
||||
</div>
|
||||
</div>`}),Vue.component("http-firewall-captcha-options-viewer",{props:["v-captcha-options"],mounted:function(){this.updateSummary()},data:function(){let e=this.vCaptchaOptions;return{options:e=null==e?{life:0,maxFails:0,failBlockTimeout:0,failBlockScopeAll:!1,uiIsOn:!1,uiTitle:"",uiPrompt:"",uiButtonTitle:"",uiShowRequestId:!1,uiCss:"",uiFooter:"",uiBody:"",cookieId:"",lang:""}:e,summary:""}},methods:{updateSummary:function(){let e=[];0<this.options.life&&e.push("有效时间"+this.options.life+"秒"),0<this.options.maxFails&&e.push("最多失败"+this.options.maxFails+"次"),0<this.options.failBlockTimeout&&e.push("失败拦截"+this.options.failBlockTimeout+"秒"),this.options.failBlockScopeAll&&e.push("全局封禁"),this.options.uiIsOn&&e.push("定制UI"),0==e.length?this.summary="默认配置":this.summary=e.join(" / ")}},template:`<div>{{summary}}</div>
|
||||
`}),Vue.component("reverse-proxy-box",{props:["v-reverse-proxy-ref","v-reverse-proxy-config","v-is-location","v-is-group","v-family"],data:function(){let e=this.vReverseProxyRef,t=(null==e&&(e={isPrior:!1,isOn:!1,reverseProxyId:0}),this.vReverseProxyConfig),i=(null==(t=null==t?{requestPath:"",stripPrefix:"",requestURI:"",requestHost:"",requestHostType:0,addHeaders:[],connTimeout:{count:0,unit:"second"},readTimeout:{count:0,unit:"second"},idleTimeout:{count:0,unit:"second"},maxConns:0,maxIdleConns:0,followRedirects:!1}:t).addHeaders&&(t.addHeaders=[]),null==t.connTimeout&&(t.connTimeout={count:0,unit:"second"}),null==t.readTimeout&&(t.readTimeout={count:0,unit:"second"}),null==t.idleTimeout&&(t.idleTimeout={count:0,unit:"second"}),null==t.proxyProtocol&&Vue.set(t,"proxyProtocol",{isOn:!1,version:1}),[{name:"X-Real-IP",isChecked:!1},{name:"X-Forwarded-For",isChecked:!1},{name:"X-Forwarded-By",isChecked:!1},{name:"X-Forwarded-Host",isChecked:!1},{name:"X-Forwarded-Proto",isChecked:!1}]);return i.forEach(function(e){e.isChecked=t.addHeaders.$contains(e.name)}),{reverseProxyRef:e,reverseProxyConfig:t,advancedVisible:!1,family:this.vFamily,forwardHeaders:i}},watch:{"reverseProxyConfig.requestHostType":function(e){let t=parseInt(e);isNaN(t)&&(t=0),this.reverseProxyConfig.requestHostType=t},"reverseProxyConfig.connTimeout.count":function(e){let t=parseInt(e);(isNaN(t)||t<0)&&(t=0),this.reverseProxyConfig.connTimeout.count=t},"reverseProxyConfig.readTimeout.count":function(e){let t=parseInt(e);(isNaN(t)||t<0)&&(t=0),this.reverseProxyConfig.readTimeout.count=t},"reverseProxyConfig.idleTimeout.count":function(e){let t=parseInt(e);(isNaN(t)||t<0)&&(t=0),this.reverseProxyConfig.idleTimeout.count=t},"reverseProxyConfig.maxConns":function(e){let t=parseInt(e);(isNaN(t)||t<0)&&(t=0),this.reverseProxyConfig.maxConns=t},"reverseProxyConfig.maxIdleConns":function(e){let t=parseInt(e);(isNaN(t)||t<0)&&(t=0),this.reverseProxyConfig.maxIdleConns=t},"reverseProxyConfig.proxyProtocol.version":function(e){let t=parseInt(e);isNaN(t)&&(t=1),this.reverseProxyConfig.proxyProtocol.version=t}},methods:{isOn:function(){return(!this.vIsLocation&&!this.vIsGroup||this.reverseProxyRef.isPrior)&&this.reverseProxyRef.isOn},changeAdvancedVisible:function(e){this.advancedVisible=e},changeAddHeader:function(){this.reverseProxyConfig.addHeaders=this.forwardHeaders.filter(function(e){return e.isChecked}).map(function(e){return e.name})}},template:`<div>
|
||||
`}),Vue.component("reverse-proxy-box",{props:["v-reverse-proxy-ref","v-reverse-proxy-config","v-is-location","v-is-group","v-family"],data:function(){let e=this.vReverseProxyRef,t=(null==e&&(e={isPrior:!1,isOn:!1,reverseProxyId:0}),this.vReverseProxyConfig),i=(null==(t=null==t?{requestPath:"",stripPrefix:"",requestURI:"",requestHost:"",requestHostType:0,requestHostExcludingPort:!1,addHeaders:[],connTimeout:{count:0,unit:"second"},readTimeout:{count:0,unit:"second"},idleTimeout:{count:0,unit:"second"},maxConns:0,maxIdleConns:0,followRedirects:!1}:t).addHeaders&&(t.addHeaders=[]),null==t.connTimeout&&(t.connTimeout={count:0,unit:"second"}),null==t.readTimeout&&(t.readTimeout={count:0,unit:"second"}),null==t.idleTimeout&&(t.idleTimeout={count:0,unit:"second"}),null==t.proxyProtocol&&Vue.set(t,"proxyProtocol",{isOn:!1,version:1}),[{name:"X-Real-IP",isChecked:!1},{name:"X-Forwarded-For",isChecked:!1},{name:"X-Forwarded-By",isChecked:!1},{name:"X-Forwarded-Host",isChecked:!1},{name:"X-Forwarded-Proto",isChecked:!1}]);return i.forEach(function(e){e.isChecked=t.addHeaders.$contains(e.name)}),{reverseProxyRef:e,reverseProxyConfig:t,advancedVisible:!1,family:this.vFamily,forwardHeaders:i}},watch:{"reverseProxyConfig.requestHostType":function(e){let t=parseInt(e);isNaN(t)&&(t=0),this.reverseProxyConfig.requestHostType=t},"reverseProxyConfig.connTimeout.count":function(e){let t=parseInt(e);(isNaN(t)||t<0)&&(t=0),this.reverseProxyConfig.connTimeout.count=t},"reverseProxyConfig.readTimeout.count":function(e){let t=parseInt(e);(isNaN(t)||t<0)&&(t=0),this.reverseProxyConfig.readTimeout.count=t},"reverseProxyConfig.idleTimeout.count":function(e){let t=parseInt(e);(isNaN(t)||t<0)&&(t=0),this.reverseProxyConfig.idleTimeout.count=t},"reverseProxyConfig.maxConns":function(e){let t=parseInt(e);(isNaN(t)||t<0)&&(t=0),this.reverseProxyConfig.maxConns=t},"reverseProxyConfig.maxIdleConns":function(e){let t=parseInt(e);(isNaN(t)||t<0)&&(t=0),this.reverseProxyConfig.maxIdleConns=t},"reverseProxyConfig.proxyProtocol.version":function(e){let t=parseInt(e);isNaN(t)&&(t=1),this.reverseProxyConfig.proxyProtocol.version=t}},methods:{isOn:function(){return(!this.vIsLocation&&!this.vIsGroup||this.reverseProxyRef.isPrior)&&this.reverseProxyRef.isOn},changeAdvancedVisible:function(e){this.advancedVisible=e},changeAddHeader:function(){this.reverseProxyConfig.addHeaders=this.forwardHeaders.filter(function(e){return e.isChecked}).map(function(e){return e.name})}},template:`<div>
|
||||
<input type="hidden" name="reverseProxyRefJSON" :value="JSON.stringify(reverseProxyRef)"/>
|
||||
<input type="hidden" name="reverseProxyJSON" :value="JSON.stringify(reverseProxyConfig)"/>
|
||||
<table class="ui table selectable definition">
|
||||
@@ -2898,18 +3061,24 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<tr v-show="family == null || family == 'http'">
|
||||
<td>回源主机名<em>(Host)</em></td>
|
||||
<td>
|
||||
<radio :v-value="0" v-model="reverseProxyConfig.requestHostType">跟随代理服务</radio>
|
||||
<radio :v-value="0" v-model="reverseProxyConfig.requestHostType">跟随CDN域名</radio>
|
||||
<radio :v-value="1" v-model="reverseProxyConfig.requestHostType">跟随源站</radio>
|
||||
<radio :v-value="2" v-model="reverseProxyConfig.requestHostType">自定义</radio>
|
||||
<div v-show="reverseProxyConfig.requestHostType == 2" style="margin-top: 0.8em">
|
||||
<input type="text" placeholder="比如example.com" v-model="reverseProxyConfig.requestHost"/>
|
||||
</div>
|
||||
<p class="comment">请求源站时的Host,用于修改源站接收到的域名
|
||||
<span v-if="reverseProxyConfig.requestHostType == 0">,"跟随代理服务"是指源站接收到的域名和当前代理服务保持一致</span>
|
||||
<span v-if="reverseProxyConfig.requestHostType == 0">,"跟随CDN域名"是指源站接收到的域名和当前CDN访问域名保持一致</span>
|
||||
<span v-if="reverseProxyConfig.requestHostType == 1">,"跟随源站"是指源站接收到的域名仍然是填写的源站地址中的信息,不随代理服务域名改变而改变</span>
|
||||
<span v-if="reverseProxyConfig.requestHostType == 2">,自定义Host内容中支持请求变量</span>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-show="family == null || family == 'http'">
|
||||
<td>回源主机名移除端口</td>
|
||||
<td><checkbox v-model="reverseProxyConfig.requestHostExcludingPort"></checkbox>
|
||||
<p class="comment">选中后表示移除回源主机名中的端口部分。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<more-options-tbody @change="changeAdvancedVisible" v-if="isOn()"></more-options-tbody>
|
||||
<tbody v-show="isOn() && advancedVisible">
|
||||
@@ -3796,7 +3965,7 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<input type="text" style="width: 5em" maxlength="9" v-model="options.failBlockTimeout" @keyup.enter="confirm()" @keypress.enter.prevent="1"/>
|
||||
<span class="ui label">秒</span>
|
||||
</div>
|
||||
<p class="comment">在达到最多失败次数(大于0)时,自动拦截的时间;如果为0表示不自动拦截。</p>
|
||||
<p class="comment">在达到最多失败次数(大于0)时,自动拦截的时长;如果为0表示不自动拦截。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -4112,7 +4281,7 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<button class="ui button tiny" type="button" @click.prevent="create()">+</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`}),Vue.component("datetime-input",{props:["v-name","v-timestamp"],mounted:function(){let t=this;teaweb.datepicker(this.$refs.dayInput,function(e){t.day=e,t.hour="23",t.minute="59",t.second="59",t.change()})},data:function(){let t=this.vTimestamp,i=(null!=t?(t=parseInt(t),isNaN(t)&&(t=0)):t=0,""),s="",n="",o="";if(0<t){let e=new Date;e.setTime(1e3*t);var a=e.getFullYear().toString(),l=this.leadingZero((e.getMonth()+1).toString(),2);i=a+"-"+l+"-"+this.leadingZero(e.getDate().toString(),2),s=this.leadingZero(e.getHours().toString(),2),n=this.leadingZero(e.getMinutes().toString(),2),o=this.leadingZero(e.getSeconds().toString(),2)}return{timestamp:t,day:i,hour:s,minute:n,second:o,hasDayError:!1,hasHourError:!1,hasMinuteError:!1,hasSecondError:!1}},methods:{change:function(){let e=new Date;var t,i;/^\d{4}-\d{1,2}-\d{1,2}$/.test(this.day)?(i=this.day.split("-"),t=parseInt(i[0]),e.setFullYear(t),(t=parseInt(i[1]))<1||12<t?this.hasDayError=!0:(e.setMonth(t-1),(t=parseInt(i[2]))<1||32<t?this.hasDayError=!0:(e.setDate(t),this.hasDayError=!1,/^\d+$/.test(this.hour)?(i=parseInt(this.hour),isNaN(i)||i<0||24<=i?this.hasHourError=!0:(this.hasHourError=!1,e.setHours(i),/^\d+$/.test(this.minute)?(t=parseInt(this.minute),isNaN(t)||t<0||60<=t?this.hasMinuteError=!0:(this.hasMinuteError=!1,e.setMinutes(t),/^\d+$/.test(this.second)?(i=parseInt(this.second),isNaN(i)||i<0||60<=i?this.hasSecondError=!0:(this.hasSecondError=!1,e.setSeconds(i),this.timestamp=Math.floor(e.getTime()/1e3))):this.hasSecondError=!0)):this.hasMinuteError=!0)):this.hasHourError=!0))):this.hasDayError=!0},leadingZero:function(t,i){if(i<=(t=t.toString()).length)return t;for(let e=0;e<i-t.length;e++)t="0"+t;return t},resultTimestamp:function(){return this.timestamp},nextDays:function(e){let t=new Date;t.setTime(t.getTime()+86400*e*1e3),this.day=t.getFullYear()+"-"+this.leadingZero(t.getMonth()+1,2)+"-"+this.leadingZero(t.getDate(),2),this.hour=this.leadingZero(t.getHours(),2),this.minute=this.leadingZero(t.getMinutes(),2),this.second=this.leadingZero(t.getSeconds(),2),this.change()}},template:`<div>
|
||||
</div>`}),Vue.component("datetime-input",{props:["v-name","v-timestamp"],mounted:function(){let t=this;teaweb.datepicker(this.$refs.dayInput,function(e){t.day=e,t.hour="23",t.minute="59",t.second="59",t.change()})},data:function(){let t=this.vTimestamp,i=(null!=t?(t=parseInt(t),isNaN(t)&&(t=0)):t=0,""),s="",n="",o="";if(0<t){let e=new Date;e.setTime(1e3*t);var a=e.getFullYear().toString(),l=this.leadingZero((e.getMonth()+1).toString(),2);i=a+"-"+l+"-"+this.leadingZero(e.getDate().toString(),2),s=this.leadingZero(e.getHours().toString(),2),n=this.leadingZero(e.getMinutes().toString(),2),o=this.leadingZero(e.getSeconds().toString(),2)}return{timestamp:t,day:i,hour:s,minute:n,second:o,hasDayError:!1,hasHourError:!1,hasMinuteError:!1,hasSecondError:!1}},methods:{change:function(){let e=new Date;var t,i;/^\d{4}-\d{1,2}-\d{1,2}$/.test(this.day)?(i=this.day.split("-"),t=parseInt(i[0]),e.setFullYear(t),(t=parseInt(i[1]))<1||12<t?this.hasDayError=!0:(e.setMonth(t-1),(t=parseInt(i[2]))<1||32<t?this.hasDayError=!0:(e.setDate(t),this.hasDayError=!1,/^\d+$/.test(this.hour)?(i=parseInt(this.hour),isNaN(i)||i<0||24<=i?this.hasHourError=!0:(this.hasHourError=!1,e.setHours(i),/^\d+$/.test(this.minute)?(t=parseInt(this.minute),isNaN(t)||t<0||60<=t?this.hasMinuteError=!0:(this.hasMinuteError=!1,e.setMinutes(t),/^\d+$/.test(this.second)?(i=parseInt(this.second),isNaN(i)||i<0||60<=i?this.hasSecondError=!0:(this.hasSecondError=!1,e.setSeconds(i),this.timestamp=Math.floor(e.getTime()/1e3))):this.hasSecondError=!0)):this.hasMinuteError=!0)):this.hasHourError=!0))):this.hasDayError=!0},leadingZero:function(t,i){if(i<=(t=t.toString()).length)return t;for(let e=0;e<i-t.length;e++)t="0"+t;return t},resultTimestamp:function(){return this.timestamp},nextDays:function(e){let t=new Date;t.setTime(t.getTime()+86400*e*1e3),this.day=t.getFullYear()+"-"+this.leadingZero(t.getMonth()+1,2)+"-"+this.leadingZero(t.getDate(),2),this.hour=this.leadingZero(t.getHours(),2),this.minute=this.leadingZero(t.getMinutes(),2),this.second=this.leadingZero(t.getSeconds(),2),this.change()},nextHours:function(e){let t=new Date;t.setTime(t.getTime()+3600*e*1e3),this.day=t.getFullYear()+"-"+this.leadingZero(t.getMonth()+1,2)+"-"+this.leadingZero(t.getDate(),2),this.hour=this.leadingZero(t.getHours(),2),this.minute=this.leadingZero(t.getMinutes(),2),this.second=this.leadingZero(t.getSeconds(),2),this.change()}},template:`<div>
|
||||
<input type="hidden" :name="vName" :value="timestamp"/>
|
||||
<div class="ui fields inline" style="padding: 0; margin:0">
|
||||
<div class="ui field" :class="{error: hasDayError}">
|
||||
@@ -4124,7 +4293,7 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<div class="ui field">:</div>
|
||||
<div class="ui field" :class="{error: hasSecondError}"><input type="text" v-model="second" maxlength="2" style="width:4em" placeholder="秒" @input="change"/></div>
|
||||
</div>
|
||||
<p class="comment">常用时间:<a href="" @click.prevent="nextDays(1)"> 1天 </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(3)"> 3天 </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(7)"> 一周 </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(30)"> 30天 </a> </p>
|
||||
<p class="comment">常用时间:<a href="" @click.prevent="nextHours(1)"> 1小时 </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(1)"> 1天 </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(3)"> 3天 </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(7)"> 1周 </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(30)"> 30天 </a> </p>
|
||||
</div>`}),Vue.component("label-on",{props:["v-is-on"],template:'<div><span v-if="vIsOn" class="ui label tiny green basic">已启用</span><span v-if="!vIsOn" class="ui label tiny red basic">已停用</span></div>'}),Vue.component("code-label",{methods:{click:function(e){this.$emit("click",e)}},template:'<span class="ui label basic tiny" style="padding: 3px;margin-left:2px;margin-right:2px" @click.prevent="click"><slot></slot></span>'}),Vue.component("code-label-plain",{template:'<span class="ui label basic tiny" style="padding: 3px;margin-left:2px;margin-right:2px"><slot></slot></span>'}),Vue.component("tiny-label",{template:'<span class="ui label tiny" style="margin-bottom: 0.5em"><slot></slot></span>'}),Vue.component("tiny-basic-label",{template:'<span class="ui label tiny basic" style="margin-bottom: 0.5em"><slot></slot></span>'}),Vue.component("micro-basic-label",{template:'<span class="ui label tiny basic" style="margin-bottom: 0.5em; font-size: 0.7em; padding: 4px"><slot></slot></span>'}),Vue.component("grey-label",{props:["color"],data:function(){let e="grey";return{labelColor:e=null!=this.color&&0<this.color.length?"red":e}},template:'<span class="ui label basic tiny" :class="labelColor" style="margin-top: 0.4em; font-size: 0.7em; border: 1px solid #ddd!important; font-weight: normal;"><slot></slot></span>'}),Vue.component("optional-label",{template:'<em><span class="grey">(可选)</span></em>'}),Vue.component("plus-label",{template:'<span style="color: #B18701;">Plus专属功能。</span>'}),Vue.component("pro-warning-label",{template:'<span><i class="icon warning circle yellow"></i>注意:通常不需要修改;如要修改,请在专家指导下进行。</span>'}),Vue.component("first-menu",{props:[],template:' \t\t<div class="first-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("more-options-indicator",{data:function(){return{visible:!1}},methods:{changeVisible:function(){this.visible=!this.visible,null!=Tea.Vue&&(Tea.Vue.moreOptionsVisible=this.visible),this.$emit("change",this.visible)}},template:'<a href="" style="font-weight: normal" @click.prevent="changeVisible()"><slot><span v-if="!visible">更多选项</span><span v-if="visible">收起选项</span></slot> <i class="icon angle" :class="{down:!visible, up:visible}"></i> </a>'}),Vue.component("page-size-selector",{data:function(){let t=window.location.search,i=10;if(0<t.length){let e=(t=t.substr(1)).split("&");e.forEach(function(t){t=t.split("=");if(2==t.length&&"pageSize"==t[0]){let e=t[1];e.match(/^\d+$/)&&(i=parseInt(e,10),(isNaN(i)||i<1)&&(i=10))}})}return{pageSize:i}},watch:{pageSize:function(){window.ChangePageSize(this.pageSize)}},template:`<select class="ui dropdown" style="height:34px;padding-top:0;padding-bottom:0;margin-left:1em;color:#666" v-model="pageSize">
|
||||
<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">
|
||||
@@ -4134,12 +4303,13 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<table class="ui table definition selectable">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="title">启用</td>
|
||||
<td class="title">启用健康检查</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" value="1" v-model="healthCheck.isOn"/>
|
||||
<label></label>
|
||||
</div>
|
||||
<p class="comment">通过访问节点上的网站URL来确定节点是否健康。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -4163,7 +4333,7 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<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>
|
||||
<p class="comment">已经部署到当前集群的一个域名;如果为空则使用节点IP作为域名。<span class="red" v-if="urlProtocol == 'https' && urlHost.length == 0">如果协议是https,这里必须填写一个已经设置了SSL证书的域名。</span></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -4227,24 +4397,28 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<td>允许的状态码</td>
|
||||
<td>
|
||||
<values-box :values="healthCheck.statusCodes" maxlength="3" @change="changeStatus"></values-box>
|
||||
<p class="comment">允许检测URL返回的状态码列表。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>超时时间</td>
|
||||
<td>
|
||||
<time-duration-box :v-value="healthCheck.timeout"></time-duration-box>
|
||||
<p class="comment">读取检测URL超时时间。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>连续尝试次数</td>
|
||||
<td>
|
||||
<input type="text" v-model="healthCheck.countTries" style="width: 5em" maxlength="2"/>
|
||||
<p class="comment">如果读取检测URL失败后需要再次尝试的次数。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>每次尝试间隔</td>
|
||||
<td>
|
||||
<time-duration-box :v-value="healthCheck.tryDelay"></time-duration-box>
|
||||
<p class="comment">如果读取检测URL失败后再次尝试时的间隔时间。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -4265,7 +4439,7 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<td>记录访问日志</td>
|
||||
<td>
|
||||
<checkbox v-model="healthCheck.accessLogIsOn"></checkbox>
|
||||
<p class="comment">是否记录健康检查的访问日志。</p>
|
||||
<p class="comment">记录健康检查的访问日志。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -4273,7 +4447,7 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<div class="margin"></div>
|
||||
</div>`}),Vue.component("request-variables-describer",{data:function(){return{vars:[]}},methods:{update:function(e){this.vars=[];let i=this;e.replace(/\${.+?}/g,function(e){var t=i.findVar(e);if(null==t)return e;i.vars.push(t)})},findVar:function(t){let i=null;return window.REQUEST_VARIABLES.forEach(function(e){e.code==t&&(i=e)}),i}},template:`<span>
|
||||
<span v-for="(v, index) in vars"><code-label :title="v.description">{{v.code}}</code-label> - {{v.name}}<span v-if="index < vars.length-1">;</span></span>
|
||||
</span>`}),Vue.component("combo-box",{props:["name","title","placeholder","size","v-items","v-value","data-url","data-key","width"],mounted:function(){var e=this.dataUrl;let i=this.dataKey,s=this;null!=e&&0<e.length&&null!=i&&Tea.action(e).post().success(function(t){if(null!=t.data&&"object"==typeof t.data[i]){let e=s.formatItems(t.data[i]);s.allItems=e,s.items=e.$copy(),null!=s.vValue&&e.forEach(function(e){e.value==s.vValue&&(s.selectedItem=e)})}});e=this.$refs.searchBox.offsetWidth;null!=e&&0<e?this.$refs.menu.style.width=e+"px":0<this.styleWidth.length&&(this.$refs.menu.style.width=this.styleWidth)},data:function(){let e=this.vItems,i=(null!=e&&e instanceof Array||(e=[]),e=this.formatItems(e),null);if(null!=this.vValue){let t=this;e.forEach(function(e){e.value==t.vValue&&(i=e)})}let t=this.width;return null==t||0==t.length?t="11em":/\d+$/.test(t)&&(t+="em"),{allItems:e,items:e.$copy(),selectedItem:i,keyword:"",visible:!1,hideTimer:null,hoverIndex:0,styleWidth:t}},methods:{formatItems:function(e){return e.forEach(function(e){null==e.value&&(e.value=e.id)}),e},reset:function(){this.selectedItem=null,this.change(),this.hoverIndex=0;let e=this;setTimeout(function(){e.$refs.searchBox&&e.$refs.searchBox.focus()})},clear:function(){this.selectedItem=null,this.change(),this.hoverIndex=0},changeKeyword:function(){this.hoverIndex=0;let t=this.keyword;0==t.length?this.items=this.allItems.$copy():this.items=this.allItems.$copy().filter(function(e){return!!(null!=e.fullname&&0<e.fullname.length&&teaweb.match(e.fullname,t))||teaweb.match(e.name,t)})},selectItem:function(e){this.selectedItem=e,this.change(),this.hoverIndex=0,this.keyword="",this.changeKeyword()},confirm:function(){this.items.length>this.hoverIndex&&this.selectItem(this.items[this.hoverIndex])},show:function(){this.visible=!0},hide:function(){let e=this;this.hideTimer=setTimeout(function(){e.visible=!1},500)},downItem:function(){this.hoverIndex++,this.hoverIndex>this.items.length-1&&(this.hoverIndex=0),this.focusItem()},upItem:function(){this.hoverIndex--,this.hoverIndex<0&&(this.hoverIndex=0),this.focusItem()},focusItem:function(){if(this.hoverIndex<this.items.length){this.$refs.itemRef[this.hoverIndex].focus();let e=this;setTimeout(function(){e.$refs.searchBox.focus(),null!=e.hideTimer&&(clearTimeout(e.hideTimer),e.hideTimer=null)})}},change:function(){this.$emit("change",this.selectedItem);let e=this;setTimeout(function(){null!=e.$refs.selectedLabel&&e.$refs.selectedLabel.focus()})},submitForm:function(e){if("A"==e.target.tagName){let e=this.$refs.selectedLabel.parentNode;for(;;){if(null==(e=e.parentNode)||"BODY"==e.tagName)return;if("FORM"==e.tagName){e.submit();break}}}}},template:`<div style="display: inline; z-index: 10; background: white" class="combo-box">
|
||||
</span>`}),Vue.component("combo-box",{props:["name","title","placeholder","size","v-items","v-value","data-url","data-key","width"],mounted:function(){var e=this.dataUrl;let i=this.dataKey,s=this;null!=e&&0<e.length&&null!=i&&Tea.action(e).post().success(function(t){if(null!=t.data&&"object"==typeof t.data[i]){let e=s.formatItems(t.data[i]);s.allItems=e,s.items=e.$copy(),null!=s.vValue&&e.forEach(function(e){e.value==s.vValue&&(s.selectedItem=e)})}});var e=this.$refs.searchBox;null!=e&&(null!=(e=e.offsetWidth)&&0<e?this.$refs.menu.style.width=e+"px":0<this.styleWidth.length&&(this.$refs.menu.style.width=this.styleWidth))},data:function(){let e=this.vItems,i=(null!=e&&e instanceof Array||(e=[]),e=this.formatItems(e),null);if(null!=this.vValue){let t=this;e.forEach(function(e){e.value==t.vValue&&(i=e)})}let t=this.width;return null==t||0==t.length?t="11em":/\d+$/.test(t)&&(t+="em"),{allItems:e,items:e.$copy(),selectedItem:i,keyword:"",visible:!1,hideTimer:null,hoverIndex:0,styleWidth:t}},methods:{formatItems:function(e){return e.forEach(function(e){null==e.value&&(e.value=e.id)}),e},reset:function(){this.selectedItem=null,this.change(),this.hoverIndex=0;let e=this;setTimeout(function(){e.$refs.searchBox&&e.$refs.searchBox.focus()})},clear:function(){this.selectedItem=null,this.change(),this.hoverIndex=0},changeKeyword:function(){this.hoverIndex=0;let t=this.keyword;0==t.length?this.items=this.allItems.$copy():this.items=this.allItems.$copy().filter(function(e){return!!(null!=e.fullname&&0<e.fullname.length&&teaweb.match(e.fullname,t))||teaweb.match(e.name,t)})},selectItem:function(e){this.selectedItem=e,this.change(),this.hoverIndex=0,this.keyword="",this.changeKeyword()},confirm:function(){this.items.length>this.hoverIndex&&this.selectItem(this.items[this.hoverIndex])},show:function(){this.visible=!0},hide:function(){let e=this;this.hideTimer=setTimeout(function(){e.visible=!1},500)},downItem:function(){this.hoverIndex++,this.hoverIndex>this.items.length-1&&(this.hoverIndex=0),this.focusItem()},upItem:function(){this.hoverIndex--,this.hoverIndex<0&&(this.hoverIndex=0),this.focusItem()},focusItem:function(){if(this.hoverIndex<this.items.length){this.$refs.itemRef[this.hoverIndex].focus();let e=this;setTimeout(function(){e.$refs.searchBox.focus(),null!=e.hideTimer&&(clearTimeout(e.hideTimer),e.hideTimer=null)})}},change:function(){this.$emit("change",this.selectedItem);let e=this;setTimeout(function(){null!=e.$refs.selectedLabel&&e.$refs.selectedLabel.focus()})},submitForm:function(e){if("A"==e.target.tagName){let e=this.$refs.selectedLabel.parentNode;for(;;){if(null==(e=e.parentNode)||"BODY"==e.tagName)return;if("FORM"==e.tagName){e.submit();break}}}}},template:`<div style="display: inline; z-index: 10; background: white" class="combo-box">
|
||||
<!-- 搜索框 -->
|
||||
<div v-if="selectedItem == null">
|
||||
<input type="text" v-model="keyword" :placeholder="placeholder" :size="size" :style="{'width': styleWidth}" @input="changeKeyword" @focus="show" @blur="hide" @keyup.enter="confirm()" @keypress.enter.prevent="1" ref="searchBox" @keyup.down="downItem" @keyup.up="upItem"/>
|
||||
@@ -4282,7 +4456,7 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<!-- 当前选中 -->
|
||||
<div v-if="selectedItem != null">
|
||||
<input type="hidden" :name="name" :value="selectedItem.value"/>
|
||||
<a href="" class="ui label basic" style="line-height: 1.4; font-weight: normal; font-size: 1em" ref="selectedLabel" @click.prevent="submitForm"><span>{{title}}:{{selectedItem.name}}</span>
|
||||
<a href="" class="ui label basic" style="line-height: 1.4; font-weight: normal; font-size: 1em" ref="selectedLabel" @click.prevent="submitForm"><span><span v-if="title != null && title.length > 0">{{title}}:</span>{{selectedItem.name}}</span>
|
||||
<span title="清除" @click.prevent="reset"><i class="icon remove small"></i></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1438,21 +1438,46 @@ Vue.component("ns-route-ranges-box", {
|
||||
isAdding: false,
|
||||
isAddingBatch: false,
|
||||
|
||||
// 类型
|
||||
rangeType: "ipRange",
|
||||
isReverse: false,
|
||||
|
||||
// IP范围
|
||||
ipRangeFrom: "",
|
||||
ipRangeTo: "",
|
||||
|
||||
batchIPRange: ""
|
||||
batchIPRange: "",
|
||||
|
||||
// CIDR
|
||||
ipCIDR: "",
|
||||
batchIPCIDR: "",
|
||||
|
||||
// region
|
||||
regions: [],
|
||||
regionType: "country"
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
add: function () {
|
||||
addIPRange: function () {
|
||||
this.isAdding = true
|
||||
let that = this
|
||||
setTimeout(function () {
|
||||
that.$refs.ipRangeFrom.focus()
|
||||
}, 100)
|
||||
},
|
||||
addCIDR: function () {
|
||||
this.isAdding = true
|
||||
let that = this
|
||||
setTimeout(function () {
|
||||
that.$refs.ipCIDR.focus()
|
||||
}, 100)
|
||||
},
|
||||
addRegions: function () {
|
||||
this.isAdding = true
|
||||
},
|
||||
addRegion: function (regionType) {
|
||||
this.regionType = regionType
|
||||
},
|
||||
remove: function (index) {
|
||||
this.ranges.$remove(index)
|
||||
},
|
||||
@@ -1460,6 +1485,18 @@ Vue.component("ns-route-ranges-box", {
|
||||
this.isAdding = false
|
||||
this.ipRangeFrom = ""
|
||||
this.ipRangeTo = ""
|
||||
this.isReverse = false
|
||||
},
|
||||
cancelIPCIDR: function () {
|
||||
this.isAdding = false
|
||||
this.ipCIDR = ""
|
||||
this.isReverse = false
|
||||
},
|
||||
cancelRegions: function () {
|
||||
this.isAdding = false
|
||||
this.regions = []
|
||||
this.regionType = "country"
|
||||
this.isReverse = false
|
||||
},
|
||||
confirmIPRange: function () {
|
||||
// 校验IP
|
||||
@@ -1484,21 +1521,74 @@ Vue.component("ns-route-ranges-box", {
|
||||
type: "ipRange",
|
||||
params: {
|
||||
ipFrom: this.ipRangeFrom,
|
||||
ipTo: this.ipRangeTo
|
||||
ipTo: this.ipRangeTo,
|
||||
isReverse: this.isReverse
|
||||
}
|
||||
})
|
||||
this.cancelIPRange()
|
||||
},
|
||||
addBatch: function () {
|
||||
confirmIPCIDR: function () {
|
||||
let that = this
|
||||
if (this.ipCIDR.length == 0) {
|
||||
teaweb.warn("请填写CIDR", function () {
|
||||
that.$refs.ipCIDR.focus()
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.validateCIDR(this.ipCIDR)) {
|
||||
teaweb.warn("请输入正确的CIDR", function () {
|
||||
that.$refs.ipCIDR.focus()
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
this.ranges.push({
|
||||
type: "cidr",
|
||||
params: {
|
||||
cidr: this.ipCIDR,
|
||||
isReverse: this.isReverse
|
||||
}
|
||||
})
|
||||
this.cancelIPCIDR()
|
||||
},
|
||||
confirmRegions: function () {
|
||||
if (this.regions.length == 0) {
|
||||
this.cancelRegions()
|
||||
return
|
||||
}
|
||||
this.ranges.push({
|
||||
type: "region",
|
||||
params: {
|
||||
regions: this.regions,
|
||||
isReverse: this.isReverse
|
||||
}
|
||||
})
|
||||
this.cancelRegions()
|
||||
},
|
||||
addBatchIPRange: function () {
|
||||
this.isAddingBatch = true
|
||||
let that = this
|
||||
setTimeout(function () {
|
||||
that.$refs.batchIPRange.focus()
|
||||
}, 100)
|
||||
},
|
||||
addBatchCIDR: function () {
|
||||
this.isAddingBatch = true
|
||||
let that = this
|
||||
setTimeout(function () {
|
||||
that.$refs.batchIPCIDR.focus()
|
||||
}, 100)
|
||||
},
|
||||
cancelBatchIPRange: function () {
|
||||
this.isAddingBatch = false
|
||||
this.batchIPRange = ""
|
||||
this.isReverse = false
|
||||
},
|
||||
cancelBatchIPCIDR: function () {
|
||||
this.isAddingBatch = false
|
||||
this.batchIPCIDR = ""
|
||||
this.isReverse = false
|
||||
},
|
||||
confirmBatchIPRange: function () {
|
||||
let that = this
|
||||
@@ -1533,7 +1623,8 @@ Vue.component("ns-route-ranges-box", {
|
||||
type: "ipRange",
|
||||
params: {
|
||||
ipFrom: ipFrom,
|
||||
ipTo: ipTo
|
||||
ipTo: ipTo,
|
||||
isReverse: that.isReverse
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1548,7 +1639,114 @@ Vue.component("ns-route-ranges-box", {
|
||||
})
|
||||
this.cancelBatchIPRange()
|
||||
},
|
||||
confirmBatchIPCIDR: function () {
|
||||
let that = this
|
||||
let rangesText = this.batchIPCIDR
|
||||
if (rangesText.length == 0) {
|
||||
teaweb.warn("请填写要加入的CIDR", function () {
|
||||
that.$refs.batchIPCIDR.focus()
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
let validRanges = []
|
||||
let invalidLine = ""
|
||||
rangesText.split("\n").forEach(function (line) {
|
||||
let cidr = line.trim()
|
||||
if (cidr.length == 0) {
|
||||
return
|
||||
}
|
||||
if (!that.validateCIDR(cidr)) {
|
||||
invalidLine = line
|
||||
return
|
||||
}
|
||||
validRanges.push({
|
||||
type: "cidr",
|
||||
params: {
|
||||
cidr: cidr,
|
||||
isReverse: that.isReverse
|
||||
}
|
||||
})
|
||||
})
|
||||
if (invalidLine.length > 0) {
|
||||
teaweb.warn("'" + invalidLine + "'格式错误", function () {
|
||||
that.$refs.batchIPCIDR.focus()
|
||||
})
|
||||
return
|
||||
}
|
||||
validRanges.forEach(function (v) {
|
||||
that.ranges.push(v)
|
||||
})
|
||||
this.cancelBatchIPCIDR()
|
||||
},
|
||||
selectRegionCountry: function (country) {
|
||||
if (country == null) {
|
||||
return
|
||||
}
|
||||
this.regions.push({
|
||||
type: "country",
|
||||
id: country.id,
|
||||
name: country.name
|
||||
})
|
||||
this.$refs.regionCountryComboBox.clear()
|
||||
},
|
||||
selectRegionProvince: function (province) {
|
||||
if (province == null) {
|
||||
return
|
||||
}
|
||||
this.regions.push({
|
||||
type: "province",
|
||||
id: province.id,
|
||||
name: province.name
|
||||
})
|
||||
this.$refs.regionProvinceComboBox.clear()
|
||||
},
|
||||
selectRegionCity: function (city) {
|
||||
if (city == null) {
|
||||
return
|
||||
}
|
||||
this.regions.push({
|
||||
type: "city",
|
||||
id: city.id,
|
||||
name: city.name
|
||||
})
|
||||
this.$refs.regionCityComboBox.clear()
|
||||
},
|
||||
selectRegionProvider: function (provider) {
|
||||
if (provider == null) {
|
||||
return
|
||||
}
|
||||
this.regions.push({
|
||||
type: "provider",
|
||||
id: provider.id,
|
||||
name: provider.name
|
||||
})
|
||||
this.$refs.regionProviderComboBox.clear()
|
||||
},
|
||||
removeRegion: function (index) {
|
||||
this.regions.$remove(index)
|
||||
},
|
||||
validateIP: function (ip) {
|
||||
if (ip.length == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
// IPv6
|
||||
if (ip.indexOf(":") >= 0) {
|
||||
let pieces = ip.split(":")
|
||||
if (pieces.length > 8) {
|
||||
return false
|
||||
}
|
||||
let isOk = true
|
||||
pieces.forEach(function (piece) {
|
||||
if (!/^[\da-fA-F]{0,4}$/.test(piece)) {
|
||||
isOk = false
|
||||
}
|
||||
})
|
||||
|
||||
return isOk
|
||||
}
|
||||
|
||||
if (!ip.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)) {
|
||||
return false
|
||||
}
|
||||
@@ -1561,50 +1759,215 @@ Vue.component("ns-route-ranges-box", {
|
||||
}
|
||||
})
|
||||
return isOk
|
||||
},
|
||||
validateCIDR: function (cidr) {
|
||||
let pieces = cidr.split("/")
|
||||
if (pieces.length != 2) {
|
||||
return false
|
||||
}
|
||||
let ip = pieces[0]
|
||||
if (!this.validateIP(ip)) {
|
||||
return false
|
||||
}
|
||||
let mask = pieces[1]
|
||||
if (!/^\d{1,3}$/.test(mask)) {
|
||||
return false
|
||||
}
|
||||
mask = parseInt(mask, 10)
|
||||
if (cidr.indexOf(":") >= 0) { // IPv6
|
||||
return mask <= 128
|
||||
}
|
||||
return mask <= 32
|
||||
},
|
||||
updateRangeType: function (rangeType) {
|
||||
this.rangeType = rangeType
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<input type="hidden" name="rangesJSON" :value="JSON.stringify(ranges)"/>
|
||||
<div v-if="ranges.length > 0">
|
||||
<div class="ui label tiny basic" v-for="(range, index) in ranges" style="margin-bottom: 0.3em">
|
||||
<span class="red" v-if="range.params.isReverse">[排除]</span>
|
||||
<span v-if="range.type == 'ipRange'">IP范围:</span>
|
||||
{{range.params.ipFrom}} - {{range.params.ipTo}} <a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove small"></i></a>
|
||||
<span v-if="range.type == 'cidr'">CIDR:</span>
|
||||
<span v-if="range.type == 'region'">区域:</span>
|
||||
<span v-if="range.type == 'ipRange'">{{range.params.ipFrom}} - {{range.params.ipTo}}</span>
|
||||
<span v-if="range.type == 'cidr'">{{range.params.cidr}}</span>
|
||||
<span v-if="range.type == 'region'"><span v-for="(region, index) in range.params.regions">{{region.name}}<span v-if="index < range.params.regions.length - 1">,</span></span></span>
|
||||
<a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
</div>
|
||||
|
||||
<!-- 添加单个 -->
|
||||
<div style="margin-bottom: 1em" v-show="isAdding">
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<input type="text" placeholder="开始IP" maxlength="15" size="15" v-model="ipRangeFrom" ref="ipRangeFrom" @keyup.enter="confirmIPRange" @keypress.enter.prevent="1"/>
|
||||
</div>
|
||||
<div class="ui field">-</div>
|
||||
<div class="ui field">
|
||||
<input type="text" placeholder="结束IP" maxlength="15" size="15" v-model="ipRangeTo" ref="ipRangeTo" @keyup.enter="confirmIPRange" @keypress.enter.prevent="1"/>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<button class="ui button tiny" type="button" @click.prevent="confirmIPRange">确定</button>
|
||||
<a href="" @click.prevent="cancelIPRange" title="取消"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
<!-- IP范围 -->
|
||||
<div v-if="rangeType == 'ipRange'">
|
||||
<!-- 添加单个IP范围 -->
|
||||
<div style="margin-bottom: 1em" v-show="isAdding">
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td class="title">开始IP *</td>
|
||||
<td>
|
||||
<input type="text" placeholder="开始IP" maxlength="40" size="40" style="width: 15em" v-model="ipRangeFrom" ref="ipRangeFrom" @keyup.enter="confirmIPRange" @keypress.enter.prevent="1"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>结束IP *</td>
|
||||
<td>
|
||||
<input type="text" placeholder="结束IP" maxlength="40" size="40" style="width: 15em" v-model="ipRangeTo" ref="ipRangeTo" @keyup.enter="confirmIPRange" @keypress.enter.prevent="1"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>排除</td>
|
||||
<td>
|
||||
<checkbox v-model="isReverse"></checkbox>
|
||||
<p class="comment">选中后表示线路中排除当前条件。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<button class="ui button tiny" type="button" @click.prevent="confirmIPRange">确定</button>
|
||||
<a href="" @click.prevent="cancelIPRange" title="取消"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 添加多个 -->
|
||||
<div style="margin-bottom: 1em" v-show="isAddingBatch">
|
||||
<div class="ui field">
|
||||
<textarea rows="5" ref="batchIPRange" v-model="batchIPRange"></textarea>
|
||||
<p class="comment">每行一条,格式为<code-label>开始IP,结束IP</code-label>,比如<code-label>192.168.1.100,192.168.1.200</code-label>。</p>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
|
||||
<!-- 添加多个IP范围 -->
|
||||
<div style="margin-bottom: 1em" v-show="isAddingBatch">
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td class="title">IP范围列表 *</td>
|
||||
<td>
|
||||
<textarea rows="5" ref="batchIPRange" v-model="batchIPRange"></textarea>
|
||||
<p class="comment">每行一条,格式为<code-label>开始IP,结束IP</code-label>,比如<code-label>192.168.1.100,192.168.1.200</code-label>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>排除</td>
|
||||
<td>
|
||||
<checkbox v-model="isReverse"></checkbox>
|
||||
<p class="comment">选中后表示线路中排除当前条件。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<button class="ui button tiny" type="button" @click.prevent="confirmBatchIPRange">确定</button>
|
||||
<a href="" @click.prevent="cancelBatchIPRange" title="取消"><i class="icon remove small"></i></a>
|
||||
<a href="" @click.prevent="cancelBatchIPRange" title="取消"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
|
||||
<div v-if="!isAdding && !isAddingBatch">
|
||||
<button class="ui button tiny" type="button" @click.prevent="addIPRange">添加单个IP范围</button>
|
||||
<button class="ui button tiny" type="button" @click.prevent="addBatchIPRange">批量添加IP范围</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!isAdding && !isAddingBatch">
|
||||
<button class="ui button tiny" type="button" @click.prevent="add">单个添加</button>
|
||||
<button class="ui button tiny" type="button" @click.prevent="addBatch">批量添加</button>
|
||||
<!-- CIDR -->
|
||||
<div v-if="rangeType == 'cidr'">
|
||||
<!-- 添加单个IP范围 -->
|
||||
<div style="margin-bottom: 1em" v-show="isAdding">
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td class="title">CIDR *</td>
|
||||
<td>
|
||||
<input type="text" placeholder="IP/MASK" maxlength="40" size="40" style="width: 15em" v-model="ipCIDR" ref="ipCIDR" @keyup.enter="confirmIPCIDR" @keypress.enter.prevent="1"/>
|
||||
<p class="comment">类似于<code-label>192.168.2.1/24</code-label>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>排除</td>
|
||||
<td>
|
||||
<checkbox v-model="isReverse"></checkbox>
|
||||
<p class="comment">选中后表示线路中排除当前条件。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<button class="ui button tiny" type="button" @click.prevent="confirmIPCIDR">确定</button>
|
||||
<a href="" @click.prevent="cancelIPCIDR" title="取消"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
|
||||
<!-- 添加多个IP范围 -->
|
||||
<div style="margin-bottom: 1em" v-show="isAddingBatch">
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td class="title">IP范围列表 *</td>
|
||||
<td>
|
||||
<textarea rows="5" ref="batchIPCIDR" v-model="batchIPCIDR"></textarea>
|
||||
<p class="comment">每行一条,格式为<code-label>IP/MASK</code-label>,比如<code-label>192.168.2.1/24</code-label>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>排除</td>
|
||||
<td>
|
||||
<checkbox v-model="isReverse"></checkbox>
|
||||
<p class="comment">选中后表示线路中排除当前条件。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<button class="ui button tiny" type="button" @click.prevent="confirmBatchIPCIDR">确定</button>
|
||||
<a href="" @click.prevent="cancelBatchIPCIDR" title="取消"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
|
||||
<div v-if="!isAdding && !isAddingBatch">
|
||||
<button class="ui button tiny" type="button" @click.prevent="addCIDR">添加单个CIDR</button>
|
||||
<button class="ui button tiny" type="button" @click.prevent="addBatchCIDR">批量添加CIDR</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 区域 -->
|
||||
<div v-if="rangeType == 'region'">
|
||||
<!-- 添加区域 -->
|
||||
<div v-if="isAdding">
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td>已添加</td>
|
||||
<td>
|
||||
<div v-for="(region, index) in regions" class="ui label small basic">
|
||||
{{region.name}} <a href="" title="删除" @click.prevent="removeRegion(index)"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title">添加新<span v-if="regionType == 'country'">国家/地区</span><span v-if="regionType == 'province'">省份</span><span v-if="regionType == 'city'">城市</span><span v-if="regionType == 'provider'">ISP</span>
|
||||
|
||||
*</td>
|
||||
<td>
|
||||
<!-- region country name -->
|
||||
<div v-if="regionType == 'country'">
|
||||
<combo-box title="" width="14em" data-url="/ui/countryOptions" data-key="countries" placeholder="点这里选择国家/地区" @change="selectRegionCountry" ref="regionCountryComboBox" key="combo-box-country"></combo-box>
|
||||
</div>
|
||||
|
||||
<!-- region province name -->
|
||||
<div v-if="regionType == 'province'" >
|
||||
<combo-box title="" data-url="/ui/provinceOptions" data-key="provinces" placeholder="点这里选择省份" @change="selectRegionProvince" ref="regionProvinceComboBox" key="combo-box-province"></combo-box>
|
||||
</div>
|
||||
|
||||
<!-- region city name -->
|
||||
<div v-if="regionType == 'city'" >
|
||||
<combo-box title="" data-url="/ui/cityOptions" data-key="cities" placeholder="点这里选择城市" @change="selectRegionCity" ref="regionCityComboBox" key="combo-box-city"></combo-box>
|
||||
</div>
|
||||
|
||||
<!-- ISP Name -->
|
||||
<div v-if="regionType == 'provider'" >
|
||||
<combo-box title="" data-url="/ui/providerOptions" data-key="providers" placeholder="点这里选择ISP" @change="selectRegionProvider" ref="regionProviderComboBox" key="combo-box-isp"></combo-box>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 1em">
|
||||
<button class="ui button tiny basic" :class="{blue: regionType == 'country'}" type="button" @click.prevent="addRegion('country')">添加国家/地区</button>
|
||||
<button class="ui button tiny basic" :class="{blue: regionType == 'province'}" type="button" @click.prevent="addRegion('province')">添加省份</button>
|
||||
<button class="ui button tiny basic" :class="{blue: regionType == 'city'}" type="button" @click.prevent="addRegion('city')">添加城市</button>
|
||||
<button class="ui button tiny basic" :class="{blue: regionType == 'provider'}" type="button" @click.prevent="addRegion('provider')">ISP</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>排除</td>
|
||||
<td>
|
||||
<checkbox v-model="isReverse"></checkbox>
|
||||
<p class="comment">选中后表示线路中排除当前条件。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<button class="ui button tiny" type="button" @click.prevent="confirmRegions">确定</button>
|
||||
<a href="" @click.prevent="cancelRegions" title="取消"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
<div v-if="!isAdding && !isAddingBatch">
|
||||
<button class="ui button tiny" type="button" @click.prevent="addRegions">添加区域</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
})
|
||||
@@ -1806,7 +2169,7 @@ Vue.component("plan-price-view", {
|
||||
按{{plan.bandwidthPrice.percentile}}th带宽计费
|
||||
<div>
|
||||
<div v-for="range in plan.bandwidthPrice.ranges">
|
||||
<span class="small grey">{{range.minMB}} - <span v-if="range.maxMB > 0">{{range.maxMB}}MB</span><span v-else>∞</span>: {{range.pricePerMB}}元/MB</span>
|
||||
<span class="small grey">{{range.minMB}} - <span v-if="range.maxMB > 0">{{range.maxMB}}MB</span><span v-else>∞</span>: <span v-if="range.totalPrice > 0">{{range.totalPrice}}元</span><span v-else="">{{range.pricePerMB}}元/MB</span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1827,6 +2190,7 @@ Vue.component("plan-bandwidth-ranges", {
|
||||
minMB: "",
|
||||
maxMB: "",
|
||||
pricePerMB: "",
|
||||
totalPrice: "",
|
||||
addingRange: {
|
||||
minMB: 0,
|
||||
maxMB: 0,
|
||||
@@ -1851,12 +2215,16 @@ Vue.component("plan-bandwidth-ranges", {
|
||||
this.minMB = ""
|
||||
this.maxMB = ""
|
||||
this.pricePerMB = ""
|
||||
this.totalPrice = ""
|
||||
this.ranges.push(this.addingRange)
|
||||
this.ranges.$sort(function (v1, v2) {
|
||||
if (v1.minMB < v2.minMB) {
|
||||
return -1
|
||||
}
|
||||
if (v1.minMB == v2.minMB) {
|
||||
if (v2.maxMB == 0 || v1.maxMB < v2.maxMB) {
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
@@ -1898,13 +2266,20 @@ Vue.component("plan-bandwidth-ranges", {
|
||||
pricePerMB = 0
|
||||
}
|
||||
this.addingRange.pricePerMB = pricePerMB
|
||||
},
|
||||
totalPrice: function (v) {
|
||||
let totalPrice = parseFloat(v.toString())
|
||||
if (isNaN(totalPrice) || totalPrice < 0) {
|
||||
totalPrice = 0
|
||||
}
|
||||
this.addingRange.totalPrice = totalPrice
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<!-- 已有价格 -->
|
||||
<div v-if="ranges.length > 0">
|
||||
<div class="ui label basic small" v-for="(range, index) in ranges" style="margin-bottom: 0.5em">
|
||||
{{range.minMB}}MB - <span v-if="range.maxMB > 0">{{range.maxMB}}MB</span><span v-else>∞</span> 价格:{{range.pricePerMB}}元/MB
|
||||
{{range.minMB}}MB - <span v-if="range.maxMB > 0">{{range.maxMB}}MB</span><span v-else>∞</span> 价格:<span v-if="range.totalPrice > 0">{{range.totalPrice}}元</span><span v-else="">{{range.pricePerMB}}元/MB</span>
|
||||
<a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
@@ -1914,7 +2289,7 @@ Vue.component("plan-bandwidth-ranges", {
|
||||
<div v-if="isAdding">
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td class="title">带宽下限</td>
|
||||
<td class="title">带宽下限 *</td>
|
||||
<td>
|
||||
<div class="ui input right labeled">
|
||||
<input type="text" placeholder="最小带宽" style="width: 7em" maxlength="10" ref="minMB" @keyup.enter="confirm()" @keypress.enter.prevent="1" v-model="minMB"/>
|
||||
@@ -1923,7 +2298,7 @@ Vue.component("plan-bandwidth-ranges", {
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title">带宽上限</td>
|
||||
<td class="title">带宽上限 *</td>
|
||||
<td>
|
||||
<div class="ui input right labeled">
|
||||
<input type="text" placeholder="最大带宽" style="width: 7em" maxlength="10" @keyup.enter="confirm()" @keypress.enter.prevent="1" v-model="maxMB"/>
|
||||
@@ -1932,6 +2307,16 @@ Vue.component("plan-bandwidth-ranges", {
|
||||
<p class="comment">如果填0,表示上不封顶。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>总价格</td>
|
||||
<td>
|
||||
<div class="ui input right labeled">
|
||||
<input type="text" placeholder="总价格" style="width: 7em" maxlength="10" @keyup.enter="confirm()" @keypress.enter.prevent="1" v-model="totalPrice"/>
|
||||
<span class="ui label">元/MB</span>
|
||||
</div>
|
||||
<p class="comment">和单位价格二选一。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title">单位价格</td>
|
||||
<td>
|
||||
@@ -1939,6 +2324,7 @@ Vue.component("plan-bandwidth-ranges", {
|
||||
<input type="text" placeholder="单位价格" style="width: 7em" maxlength="10" @keyup.enter="confirm()" @keypress.enter.prevent="1" v-model="pricePerMB"/>
|
||||
<span class="ui label">元/MB</span>
|
||||
</div>
|
||||
<p class="comment">和总价格二选一。如果设置了单位价格,那么"总价格 = 单位价格 x 带宽"。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -4440,6 +4826,7 @@ Vue.component("http-firewall-checkpoint-cc", {
|
||||
let keys = []
|
||||
let period = 60
|
||||
let threshold = 1000
|
||||
let ignoreCommonFiles = false
|
||||
|
||||
let options = {}
|
||||
if (window.parent.UPDATING_RULE != null) {
|
||||
@@ -4461,6 +4848,9 @@ Vue.component("http-firewall-checkpoint-cc", {
|
||||
if (options.threshold != null) {
|
||||
threshold = options.threshold
|
||||
}
|
||||
if (options.ignoreCommonFiles != null && typeof (options.ignoreCommonFiles) == "boolean") {
|
||||
ignoreCommonFiles = options.ignoreCommonFiles
|
||||
}
|
||||
|
||||
let that = this
|
||||
setTimeout(function () {
|
||||
@@ -4471,6 +4861,7 @@ Vue.component("http-firewall-checkpoint-cc", {
|
||||
keys: keys,
|
||||
period: period,
|
||||
threshold: threshold,
|
||||
ignoreCommonFiles: ignoreCommonFiles,
|
||||
options: {},
|
||||
value: threshold
|
||||
}
|
||||
@@ -4481,6 +4872,9 @@ Vue.component("http-firewall-checkpoint-cc", {
|
||||
},
|
||||
threshold: function () {
|
||||
this.change()
|
||||
},
|
||||
ignoreCommonFiles: function () {
|
||||
this.change()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -4500,6 +4894,11 @@ Vue.component("http-firewall-checkpoint-cc", {
|
||||
}
|
||||
this.value = threshold
|
||||
|
||||
let ignoreCommonFiles = this.ignoreCommonFiles
|
||||
if (typeof ignoreCommonFiles != "boolean") {
|
||||
ignoreCommonFiles = false
|
||||
}
|
||||
|
||||
this.vCheckpoint.options = [
|
||||
{
|
||||
code: "keys",
|
||||
@@ -4512,6 +4911,10 @@ Vue.component("http-firewall-checkpoint-cc", {
|
||||
{
|
||||
code: "threshold",
|
||||
value: threshold
|
||||
},
|
||||
{
|
||||
code: "ignoreCommonFiles",
|
||||
value: ignoreCommonFiles
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -4541,6 +4944,13 @@ Vue.component("http-firewall-checkpoint-cc", {
|
||||
<input type="text" v-model="threshold" style="width: 6em" maxlength="8"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>忽略常见文件</td>
|
||||
<td>
|
||||
<checkbox v-model="ignoreCommonFiles"></checkbox>
|
||||
<p class="comment">忽略js、css、jpg等常见在网页里被引用的文件名。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>`
|
||||
})
|
||||
@@ -5015,10 +5425,11 @@ Vue.component("origin-list-table", {
|
||||
</thead>
|
||||
<tr v-for="origin in vOrigins">
|
||||
<td :class="{disabled:!origin.isOn}"><a href="" @click.prevent="updateOrigin(origin.id)">{{origin.addr}} <i class="icon expand small"></i></a>
|
||||
<div style="margin-top: 0.3em" v-if="origin.name.length > 0 || origin.hasCert || (origin.host != null && origin.host.length > 0) || (origin.domains != null && origin.domains.length > 0)">
|
||||
<div style="margin-top: 0.3em" v-if="origin.name.length > 0 || origin.hasCert || (origin.host != null && origin.host.length > 0) || origin.followPort || (origin.domains != null && origin.domains.length > 0)">
|
||||
<tiny-basic-label v-if="origin.name.length > 0">{{origin.name}}</tiny-basic-label>
|
||||
<tiny-basic-label v-if="origin.hasCert">证书</tiny-basic-label>
|
||||
<tiny-basic-label v-if="origin.host != null && origin.host.length > 0">主机名: {{origin.host}}</tiny-basic-label>
|
||||
<tiny-basic-label v-if="origin.followPort">端口跟随</tiny-basic-label>
|
||||
<span v-if="origin.domains != null && origin.domains.length > 0"><tiny-basic-label v-for="domain in origin.domains">匹配: {{domain}}</tiny-basic-label></span>
|
||||
</div>
|
||||
</td>
|
||||
@@ -6662,15 +7073,6 @@ Vue.component("http-auth-config-box", {
|
||||
})
|
||||
|
||||
Vue.component("user-selector", {
|
||||
mounted: function () {
|
||||
let that = this
|
||||
|
||||
Tea.action("/servers/users/options")
|
||||
.post()
|
||||
.success(function (resp) {
|
||||
that.users = resp.data.users
|
||||
})
|
||||
},
|
||||
props: ["v-user-id"],
|
||||
data: function () {
|
||||
let userId = this.vUserId
|
||||
@@ -6688,10 +7090,7 @@ Vue.component("user-selector", {
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<select class="ui dropdown auto-width" name="userId" v-model="userId">
|
||||
<option value="0">[选择用户]</option>
|
||||
<option v-for="user in users" :value="user.id">{{user.fullname}} ({{user.username}})</option>
|
||||
</select>
|
||||
<combo-box placeholder="选择用户" :data-url="'/servers/users/options'" :data-key="'users'" name="userId" :v-value="userId"></combo-box>
|
||||
</div>`
|
||||
})
|
||||
|
||||
@@ -7237,7 +7636,7 @@ Vue.component("http-compression-config-box", {
|
||||
isPrior: false,
|
||||
isOn: false,
|
||||
useDefaultTypes: true,
|
||||
types: ["brotli", "gzip", "deflate"],
|
||||
types: ["brotli", "gzip", "zstd", "deflate"],
|
||||
level: 5,
|
||||
decompressData: false,
|
||||
gzipRef: null,
|
||||
@@ -7276,6 +7675,11 @@ Vue.component("http-compression-config-box", {
|
||||
name: "Brotli",
|
||||
code: "brotli",
|
||||
isOn: true
|
||||
},
|
||||
{
|
||||
name: "ZSTD",
|
||||
code: "zstd",
|
||||
isOn: true
|
||||
}
|
||||
]
|
||||
|
||||
@@ -7412,7 +7816,7 @@ Vue.component("http-compression-config-box", {
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" v-model="config.useDefaultTypes" id="compression-use-default"/>
|
||||
<label v-if="config.useDefaultTypes" for="compression-use-default">使用默认顺序<span class="grey small">(brotli、gzip、deflate)</span></label>
|
||||
<label v-if="config.useDefaultTypes" for="compression-use-default">使用默认顺序<span class="grey small">(brotli、gzip、 zstd、deflate)</span></label>
|
||||
<label v-if="!config.useDefaultTypes" for="compression-use-default">使用默认顺序</label>
|
||||
</div>
|
||||
<div v-show="!config.useDefaultTypes">
|
||||
@@ -7738,12 +8142,19 @@ Vue.component("http-access-log-box", {
|
||||
},
|
||||
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>
|
||||
<a :href="'/servers/server/log?serverId=' + accessLog.serverId" title="点击到网站服务" v-if="vShowServerLink"><span class="grey">[服务]</span></a>
|
||||
|
||||
<!-- 服务 -->
|
||||
<a :href="'/servers/server/log?serverId=' + accessLog.serverId" title="点击到网站服务" v-if="vShowServerLink && accessLog.serverId > 0"><span class="grey">[服务]</span></a>
|
||||
<span v-if="vShowServerLink && (accessLog.serverId == null || accessLog.serverId == 0)" @click.prevent="mismatch()"><span class="disabled">[服务]</span></span>
|
||||
|
||||
<span v-if="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.wafInfo != null">
|
||||
@@ -8378,6 +8789,7 @@ Vue.component("reverse-proxy-box", {
|
||||
requestURI: "",
|
||||
requestHost: "",
|
||||
requestHostType: 0,
|
||||
requestHostExcludingPort: false,
|
||||
addHeaders: [],
|
||||
connTimeout: {count: 0, unit: "second"},
|
||||
readTimeout: {count: 0, unit: "second"},
|
||||
@@ -8529,18 +8941,24 @@ Vue.component("reverse-proxy-box", {
|
||||
<tr v-show="family == null || family == 'http'">
|
||||
<td>回源主机名<em>(Host)</em></td>
|
||||
<td>
|
||||
<radio :v-value="0" v-model="reverseProxyConfig.requestHostType">跟随代理服务</radio>
|
||||
<radio :v-value="0" v-model="reverseProxyConfig.requestHostType">跟随CDN域名</radio>
|
||||
<radio :v-value="1" v-model="reverseProxyConfig.requestHostType">跟随源站</radio>
|
||||
<radio :v-value="2" v-model="reverseProxyConfig.requestHostType">自定义</radio>
|
||||
<div v-show="reverseProxyConfig.requestHostType == 2" style="margin-top: 0.8em">
|
||||
<input type="text" placeholder="比如example.com" v-model="reverseProxyConfig.requestHost"/>
|
||||
</div>
|
||||
<p class="comment">请求源站时的Host,用于修改源站接收到的域名
|
||||
<span v-if="reverseProxyConfig.requestHostType == 0">,"跟随代理服务"是指源站接收到的域名和当前代理服务保持一致</span>
|
||||
<span v-if="reverseProxyConfig.requestHostType == 0">,"跟随CDN域名"是指源站接收到的域名和当前CDN访问域名保持一致</span>
|
||||
<span v-if="reverseProxyConfig.requestHostType == 1">,"跟随源站"是指源站接收到的域名仍然是填写的源站地址中的信息,不随代理服务域名改变而改变</span>
|
||||
<span v-if="reverseProxyConfig.requestHostType == 2">,自定义Host内容中支持请求变量</span>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-show="family == null || family == 'http'">
|
||||
<td>回源主机名移除端口</td>
|
||||
<td><checkbox v-model="reverseProxyConfig.requestHostExcludingPort"></checkbox>
|
||||
<p class="comment">选中后表示移除回源主机名中的端口部分。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<more-options-tbody @change="changeAdvancedVisible" v-if="isOn()"></more-options-tbody>
|
||||
<tbody v-show="isOn() && advancedVisible">
|
||||
@@ -11014,7 +11432,7 @@ Vue.component("http-firewall-captcha-options", {
|
||||
<input type="text" style="width: 5em" maxlength="9" v-model="options.failBlockTimeout" @keyup.enter="confirm()" @keypress.enter.prevent="1"/>
|
||||
<span class="ui label">秒</span>
|
||||
</div>
|
||||
<p class="comment">在达到最多失败次数(大于0)时,自动拦截的时间;如果为0表示不自动拦截。</p>
|
||||
<p class="comment">在达到最多失败次数(大于0)时,自动拦截的时长;如果为0表示不自动拦截。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -12333,6 +12751,15 @@ Vue.component("datetime-input", {
|
||||
this.minute = this.leadingZero(date.getMinutes(), 2)
|
||||
this.second = this.leadingZero(date.getSeconds(), 2)
|
||||
this.change()
|
||||
},
|
||||
nextHours: function (hours) {
|
||||
let date = new Date()
|
||||
date.setTime(date.getTime() + hours * 3600 * 1000)
|
||||
this.day = date.getFullYear() + "-" + this.leadingZero(date.getMonth() + 1, 2) + "-" + this.leadingZero(date.getDate(), 2)
|
||||
this.hour = this.leadingZero(date.getHours(), 2)
|
||||
this.minute = this.leadingZero(date.getMinutes(), 2)
|
||||
this.second = this.leadingZero(date.getSeconds(), 2)
|
||||
this.change()
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
@@ -12347,7 +12774,7 @@ Vue.component("datetime-input", {
|
||||
<div class="ui field">:</div>
|
||||
<div class="ui field" :class="{error: hasSecondError}"><input type="text" v-model="second" maxlength="2" style="width:4em" placeholder="秒" @input="change"/></div>
|
||||
</div>
|
||||
<p class="comment">常用时间:<a href="" @click.prevent="nextDays(1)"> 1天 </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(3)"> 3天 </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(7)"> 一周 </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(30)"> 30天 </a> </p>
|
||||
<p class="comment">常用时间:<a href="" @click.prevent="nextHours(1)"> 1小时 </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(1)"> 1天 </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(3)"> 3天 </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(7)"> 1周 </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(30)"> 30天 </a> </p>
|
||||
</div>`
|
||||
})
|
||||
|
||||
@@ -12703,12 +13130,13 @@ Vue.component("health-check-config-box", {
|
||||
<table class="ui table definition selectable">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="title">启用</td>
|
||||
<td class="title">启用健康检查</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" value="1" v-model="healthCheck.isOn"/>
|
||||
<label></label>
|
||||
</div>
|
||||
<p class="comment">通过访问节点上的网站URL来确定节点是否健康。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -12732,7 +13160,7 @@ Vue.component("health-check-config-box", {
|
||||
<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>
|
||||
<p class="comment">已经部署到当前集群的一个域名;如果为空则使用节点IP作为域名。<span class="red" v-if="urlProtocol == 'https' && urlHost.length == 0">如果协议是https,这里必须填写一个已经设置了SSL证书的域名。</span></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -12796,24 +13224,28 @@ Vue.component("health-check-config-box", {
|
||||
<td>允许的状态码</td>
|
||||
<td>
|
||||
<values-box :values="healthCheck.statusCodes" maxlength="3" @change="changeStatus"></values-box>
|
||||
<p class="comment">允许检测URL返回的状态码列表。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>超时时间</td>
|
||||
<td>
|
||||
<time-duration-box :v-value="healthCheck.timeout"></time-duration-box>
|
||||
<p class="comment">读取检测URL超时时间。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>连续尝试次数</td>
|
||||
<td>
|
||||
<input type="text" v-model="healthCheck.countTries" style="width: 5em" maxlength="2"/>
|
||||
<p class="comment">如果读取检测URL失败后需要再次尝试的次数。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>每次尝试间隔</td>
|
||||
<td>
|
||||
<time-duration-box :v-value="healthCheck.tryDelay"></time-duration-box>
|
||||
<p class="comment">如果读取检测URL失败后再次尝试时的间隔时间。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -12834,7 +13266,7 @@ Vue.component("health-check-config-box", {
|
||||
<td>记录访问日志</td>
|
||||
<td>
|
||||
<checkbox v-model="healthCheck.accessLogIsOn"></checkbox>
|
||||
<p class="comment">是否记录健康检查的访问日志。</p>
|
||||
<p class="comment">记录健康检查的访问日志。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -12909,11 +13341,14 @@ Vue.component("combo-box", {
|
||||
}
|
||||
|
||||
// 设定菜单宽度
|
||||
let inputWidth = this.$refs.searchBox.offsetWidth
|
||||
if (inputWidth != null && inputWidth > 0) {
|
||||
this.$refs.menu.style.width = inputWidth + "px"
|
||||
} else if (this.styleWidth.length > 0) {
|
||||
this.$refs.menu.style.width = this.styleWidth
|
||||
let searchBox = this.$refs.searchBox
|
||||
if (searchBox != null) {
|
||||
let inputWidth = searchBox.offsetWidth
|
||||
if (inputWidth != null && inputWidth > 0) {
|
||||
this.$refs.menu.style.width = inputWidth + "px"
|
||||
} else if (this.styleWidth.length > 0) {
|
||||
this.$refs.menu.style.width = this.styleWidth
|
||||
}
|
||||
}
|
||||
},
|
||||
data: function () {
|
||||
@@ -13080,7 +13515,7 @@ Vue.component("combo-box", {
|
||||
<!-- 当前选中 -->
|
||||
<div v-if="selectedItem != null">
|
||||
<input type="hidden" :name="name" :value="selectedItem.value"/>
|
||||
<a href="" class="ui label basic" style="line-height: 1.4; font-weight: normal; font-size: 1em" ref="selectedLabel" @click.prevent="submitForm"><span>{{title}}:{{selectedItem.name}}</span>
|
||||
<a href="" class="ui label basic" style="line-height: 1.4; font-weight: normal; font-size: 1em" ref="selectedLabel" @click.prevent="submitForm"><span><span v-if="title != null && title.length > 0">{{title}}:</span>{{selectedItem.name}}</span>
|
||||
<span title="清除" @click.prevent="reset"><i class="icon remove small"></i></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -29,11 +29,14 @@ Vue.component("combo-box", {
|
||||
}
|
||||
|
||||
// 设定菜单宽度
|
||||
let inputWidth = this.$refs.searchBox.offsetWidth
|
||||
if (inputWidth != null && inputWidth > 0) {
|
||||
this.$refs.menu.style.width = inputWidth + "px"
|
||||
} else if (this.styleWidth.length > 0) {
|
||||
this.$refs.menu.style.width = this.styleWidth
|
||||
let searchBox = this.$refs.searchBox
|
||||
if (searchBox != null) {
|
||||
let inputWidth = searchBox.offsetWidth
|
||||
if (inputWidth != null && inputWidth > 0) {
|
||||
this.$refs.menu.style.width = inputWidth + "px"
|
||||
} else if (this.styleWidth.length > 0) {
|
||||
this.$refs.menu.style.width = this.styleWidth
|
||||
}
|
||||
}
|
||||
},
|
||||
data: function () {
|
||||
@@ -200,7 +203,7 @@ Vue.component("combo-box", {
|
||||
<!-- 当前选中 -->
|
||||
<div v-if="selectedItem != null">
|
||||
<input type="hidden" :name="name" :value="selectedItem.value"/>
|
||||
<a href="" class="ui label basic" style="line-height: 1.4; font-weight: normal; font-size: 1em" ref="selectedLabel" @click.prevent="submitForm"><span>{{title}}:{{selectedItem.name}}</span>
|
||||
<a href="" class="ui label basic" style="line-height: 1.4; font-weight: normal; font-size: 1em" ref="selectedLabel" @click.prevent="submitForm"><span><span v-if="title != null && title.length > 0">{{title}}:</span>{{selectedItem.name}}</span>
|
||||
<span title="清除" @click.prevent="reset"><i class="icon remove small"></i></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -155,6 +155,15 @@ Vue.component("datetime-input", {
|
||||
this.minute = this.leadingZero(date.getMinutes(), 2)
|
||||
this.second = this.leadingZero(date.getSeconds(), 2)
|
||||
this.change()
|
||||
},
|
||||
nextHours: function (hours) {
|
||||
let date = new Date()
|
||||
date.setTime(date.getTime() + hours * 3600 * 1000)
|
||||
this.day = date.getFullYear() + "-" + this.leadingZero(date.getMonth() + 1, 2) + "-" + this.leadingZero(date.getDate(), 2)
|
||||
this.hour = this.leadingZero(date.getHours(), 2)
|
||||
this.minute = this.leadingZero(date.getMinutes(), 2)
|
||||
this.second = this.leadingZero(date.getSeconds(), 2)
|
||||
this.change()
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
@@ -169,6 +178,6 @@ Vue.component("datetime-input", {
|
||||
<div class="ui field">:</div>
|
||||
<div class="ui field" :class="{error: hasSecondError}"><input type="text" v-model="second" maxlength="2" style="width:4em" placeholder="秒" @input="change"/></div>
|
||||
</div>
|
||||
<p class="comment">常用时间:<a href="" @click.prevent="nextDays(1)"> 1天 </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(3)"> 3天 </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(7)"> 一周 </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(30)"> 30天 </a> </p>
|
||||
<p class="comment">常用时间:<a href="" @click.prevent="nextHours(1)"> 1小时 </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(1)"> 1天 </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(3)"> 3天 </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(7)"> 1周 </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(30)"> 30天 </a> </p>
|
||||
</div>`
|
||||
})
|
||||
@@ -156,12 +156,13 @@ Vue.component("health-check-config-box", {
|
||||
<table class="ui table definition selectable">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="title">启用</td>
|
||||
<td class="title">启用健康检查</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" value="1" v-model="healthCheck.isOn"/>
|
||||
<label></label>
|
||||
</div>
|
||||
<p class="comment">通过访问节点上的网站URL来确定节点是否健康。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -185,7 +186,7 @@ Vue.component("health-check-config-box", {
|
||||
<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>
|
||||
<p class="comment">已经部署到当前集群的一个域名;如果为空则使用节点IP作为域名。<span class="red" v-if="urlProtocol == 'https' && urlHost.length == 0">如果协议是https,这里必须填写一个已经设置了SSL证书的域名。</span></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -249,24 +250,28 @@ Vue.component("health-check-config-box", {
|
||||
<td>允许的状态码</td>
|
||||
<td>
|
||||
<values-box :values="healthCheck.statusCodes" maxlength="3" @change="changeStatus"></values-box>
|
||||
<p class="comment">允许检测URL返回的状态码列表。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>超时时间</td>
|
||||
<td>
|
||||
<time-duration-box :v-value="healthCheck.timeout"></time-duration-box>
|
||||
<p class="comment">读取检测URL超时时间。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>连续尝试次数</td>
|
||||
<td>
|
||||
<input type="text" v-model="healthCheck.countTries" style="width: 5em" maxlength="2"/>
|
||||
<p class="comment">如果读取检测URL失败后需要再次尝试的次数。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>每次尝试间隔</td>
|
||||
<td>
|
||||
<time-duration-box :v-value="healthCheck.tryDelay"></time-duration-box>
|
||||
<p class="comment">如果读取检测URL失败后再次尝试时的间隔时间。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -287,7 +292,7 @@ Vue.component("health-check-config-box", {
|
||||
<td>记录访问日志</td>
|
||||
<td>
|
||||
<checkbox v-model="healthCheck.accessLogIsOn"></checkbox>
|
||||
<p class="comment">是否记录健康检查的访问日志。</p>
|
||||
<p class="comment">记录健康检查的访问日志。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -10,21 +10,46 @@ Vue.component("ns-route-ranges-box", {
|
||||
isAdding: false,
|
||||
isAddingBatch: false,
|
||||
|
||||
// 类型
|
||||
rangeType: "ipRange",
|
||||
isReverse: false,
|
||||
|
||||
// IP范围
|
||||
ipRangeFrom: "",
|
||||
ipRangeTo: "",
|
||||
|
||||
batchIPRange: ""
|
||||
batchIPRange: "",
|
||||
|
||||
// CIDR
|
||||
ipCIDR: "",
|
||||
batchIPCIDR: "",
|
||||
|
||||
// region
|
||||
regions: [],
|
||||
regionType: "country"
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
add: function () {
|
||||
addIPRange: function () {
|
||||
this.isAdding = true
|
||||
let that = this
|
||||
setTimeout(function () {
|
||||
that.$refs.ipRangeFrom.focus()
|
||||
}, 100)
|
||||
},
|
||||
addCIDR: function () {
|
||||
this.isAdding = true
|
||||
let that = this
|
||||
setTimeout(function () {
|
||||
that.$refs.ipCIDR.focus()
|
||||
}, 100)
|
||||
},
|
||||
addRegions: function () {
|
||||
this.isAdding = true
|
||||
},
|
||||
addRegion: function (regionType) {
|
||||
this.regionType = regionType
|
||||
},
|
||||
remove: function (index) {
|
||||
this.ranges.$remove(index)
|
||||
},
|
||||
@@ -32,6 +57,18 @@ Vue.component("ns-route-ranges-box", {
|
||||
this.isAdding = false
|
||||
this.ipRangeFrom = ""
|
||||
this.ipRangeTo = ""
|
||||
this.isReverse = false
|
||||
},
|
||||
cancelIPCIDR: function () {
|
||||
this.isAdding = false
|
||||
this.ipCIDR = ""
|
||||
this.isReverse = false
|
||||
},
|
||||
cancelRegions: function () {
|
||||
this.isAdding = false
|
||||
this.regions = []
|
||||
this.regionType = "country"
|
||||
this.isReverse = false
|
||||
},
|
||||
confirmIPRange: function () {
|
||||
// 校验IP
|
||||
@@ -56,21 +93,74 @@ Vue.component("ns-route-ranges-box", {
|
||||
type: "ipRange",
|
||||
params: {
|
||||
ipFrom: this.ipRangeFrom,
|
||||
ipTo: this.ipRangeTo
|
||||
ipTo: this.ipRangeTo,
|
||||
isReverse: this.isReverse
|
||||
}
|
||||
})
|
||||
this.cancelIPRange()
|
||||
},
|
||||
addBatch: function () {
|
||||
confirmIPCIDR: function () {
|
||||
let that = this
|
||||
if (this.ipCIDR.length == 0) {
|
||||
teaweb.warn("请填写CIDR", function () {
|
||||
that.$refs.ipCIDR.focus()
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.validateCIDR(this.ipCIDR)) {
|
||||
teaweb.warn("请输入正确的CIDR", function () {
|
||||
that.$refs.ipCIDR.focus()
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
this.ranges.push({
|
||||
type: "cidr",
|
||||
params: {
|
||||
cidr: this.ipCIDR,
|
||||
isReverse: this.isReverse
|
||||
}
|
||||
})
|
||||
this.cancelIPCIDR()
|
||||
},
|
||||
confirmRegions: function () {
|
||||
if (this.regions.length == 0) {
|
||||
this.cancelRegions()
|
||||
return
|
||||
}
|
||||
this.ranges.push({
|
||||
type: "region",
|
||||
params: {
|
||||
regions: this.regions,
|
||||
isReverse: this.isReverse
|
||||
}
|
||||
})
|
||||
this.cancelRegions()
|
||||
},
|
||||
addBatchIPRange: function () {
|
||||
this.isAddingBatch = true
|
||||
let that = this
|
||||
setTimeout(function () {
|
||||
that.$refs.batchIPRange.focus()
|
||||
}, 100)
|
||||
},
|
||||
addBatchCIDR: function () {
|
||||
this.isAddingBatch = true
|
||||
let that = this
|
||||
setTimeout(function () {
|
||||
that.$refs.batchIPCIDR.focus()
|
||||
}, 100)
|
||||
},
|
||||
cancelBatchIPRange: function () {
|
||||
this.isAddingBatch = false
|
||||
this.batchIPRange = ""
|
||||
this.isReverse = false
|
||||
},
|
||||
cancelBatchIPCIDR: function () {
|
||||
this.isAddingBatch = false
|
||||
this.batchIPCIDR = ""
|
||||
this.isReverse = false
|
||||
},
|
||||
confirmBatchIPRange: function () {
|
||||
let that = this
|
||||
@@ -105,7 +195,8 @@ Vue.component("ns-route-ranges-box", {
|
||||
type: "ipRange",
|
||||
params: {
|
||||
ipFrom: ipFrom,
|
||||
ipTo: ipTo
|
||||
ipTo: ipTo,
|
||||
isReverse: that.isReverse
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -120,7 +211,114 @@ Vue.component("ns-route-ranges-box", {
|
||||
})
|
||||
this.cancelBatchIPRange()
|
||||
},
|
||||
confirmBatchIPCIDR: function () {
|
||||
let that = this
|
||||
let rangesText = this.batchIPCIDR
|
||||
if (rangesText.length == 0) {
|
||||
teaweb.warn("请填写要加入的CIDR", function () {
|
||||
that.$refs.batchIPCIDR.focus()
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
let validRanges = []
|
||||
let invalidLine = ""
|
||||
rangesText.split("\n").forEach(function (line) {
|
||||
let cidr = line.trim()
|
||||
if (cidr.length == 0) {
|
||||
return
|
||||
}
|
||||
if (!that.validateCIDR(cidr)) {
|
||||
invalidLine = line
|
||||
return
|
||||
}
|
||||
validRanges.push({
|
||||
type: "cidr",
|
||||
params: {
|
||||
cidr: cidr,
|
||||
isReverse: that.isReverse
|
||||
}
|
||||
})
|
||||
})
|
||||
if (invalidLine.length > 0) {
|
||||
teaweb.warn("'" + invalidLine + "'格式错误", function () {
|
||||
that.$refs.batchIPCIDR.focus()
|
||||
})
|
||||
return
|
||||
}
|
||||
validRanges.forEach(function (v) {
|
||||
that.ranges.push(v)
|
||||
})
|
||||
this.cancelBatchIPCIDR()
|
||||
},
|
||||
selectRegionCountry: function (country) {
|
||||
if (country == null) {
|
||||
return
|
||||
}
|
||||
this.regions.push({
|
||||
type: "country",
|
||||
id: country.id,
|
||||
name: country.name
|
||||
})
|
||||
this.$refs.regionCountryComboBox.clear()
|
||||
},
|
||||
selectRegionProvince: function (province) {
|
||||
if (province == null) {
|
||||
return
|
||||
}
|
||||
this.regions.push({
|
||||
type: "province",
|
||||
id: province.id,
|
||||
name: province.name
|
||||
})
|
||||
this.$refs.regionProvinceComboBox.clear()
|
||||
},
|
||||
selectRegionCity: function (city) {
|
||||
if (city == null) {
|
||||
return
|
||||
}
|
||||
this.regions.push({
|
||||
type: "city",
|
||||
id: city.id,
|
||||
name: city.name
|
||||
})
|
||||
this.$refs.regionCityComboBox.clear()
|
||||
},
|
||||
selectRegionProvider: function (provider) {
|
||||
if (provider == null) {
|
||||
return
|
||||
}
|
||||
this.regions.push({
|
||||
type: "provider",
|
||||
id: provider.id,
|
||||
name: provider.name
|
||||
})
|
||||
this.$refs.regionProviderComboBox.clear()
|
||||
},
|
||||
removeRegion: function (index) {
|
||||
this.regions.$remove(index)
|
||||
},
|
||||
validateIP: function (ip) {
|
||||
if (ip.length == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
// IPv6
|
||||
if (ip.indexOf(":") >= 0) {
|
||||
let pieces = ip.split(":")
|
||||
if (pieces.length > 8) {
|
||||
return false
|
||||
}
|
||||
let isOk = true
|
||||
pieces.forEach(function (piece) {
|
||||
if (!/^[\da-fA-F]{0,4}$/.test(piece)) {
|
||||
isOk = false
|
||||
}
|
||||
})
|
||||
|
||||
return isOk
|
||||
}
|
||||
|
||||
if (!ip.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)) {
|
||||
return false
|
||||
}
|
||||
@@ -133,50 +331,215 @@ Vue.component("ns-route-ranges-box", {
|
||||
}
|
||||
})
|
||||
return isOk
|
||||
},
|
||||
validateCIDR: function (cidr) {
|
||||
let pieces = cidr.split("/")
|
||||
if (pieces.length != 2) {
|
||||
return false
|
||||
}
|
||||
let ip = pieces[0]
|
||||
if (!this.validateIP(ip)) {
|
||||
return false
|
||||
}
|
||||
let mask = pieces[1]
|
||||
if (!/^\d{1,3}$/.test(mask)) {
|
||||
return false
|
||||
}
|
||||
mask = parseInt(mask, 10)
|
||||
if (cidr.indexOf(":") >= 0) { // IPv6
|
||||
return mask <= 128
|
||||
}
|
||||
return mask <= 32
|
||||
},
|
||||
updateRangeType: function (rangeType) {
|
||||
this.rangeType = rangeType
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<input type="hidden" name="rangesJSON" :value="JSON.stringify(ranges)"/>
|
||||
<div v-if="ranges.length > 0">
|
||||
<div class="ui label tiny basic" v-for="(range, index) in ranges" style="margin-bottom: 0.3em">
|
||||
<span class="red" v-if="range.params.isReverse">[排除]</span>
|
||||
<span v-if="range.type == 'ipRange'">IP范围:</span>
|
||||
{{range.params.ipFrom}} - {{range.params.ipTo}} <a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove small"></i></a>
|
||||
<span v-if="range.type == 'cidr'">CIDR:</span>
|
||||
<span v-if="range.type == 'region'">区域:</span>
|
||||
<span v-if="range.type == 'ipRange'">{{range.params.ipFrom}} - {{range.params.ipTo}}</span>
|
||||
<span v-if="range.type == 'cidr'">{{range.params.cidr}}</span>
|
||||
<span v-if="range.type == 'region'"><span v-for="(region, index) in range.params.regions">{{region.name}}<span v-if="index < range.params.regions.length - 1">,</span></span></span>
|
||||
<a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
</div>
|
||||
|
||||
<!-- 添加单个 -->
|
||||
<div style="margin-bottom: 1em" v-show="isAdding">
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<input type="text" placeholder="开始IP" maxlength="15" size="15" v-model="ipRangeFrom" ref="ipRangeFrom" @keyup.enter="confirmIPRange" @keypress.enter.prevent="1"/>
|
||||
</div>
|
||||
<div class="ui field">-</div>
|
||||
<div class="ui field">
|
||||
<input type="text" placeholder="结束IP" maxlength="15" size="15" v-model="ipRangeTo" ref="ipRangeTo" @keyup.enter="confirmIPRange" @keypress.enter.prevent="1"/>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<button class="ui button tiny" type="button" @click.prevent="confirmIPRange">确定</button>
|
||||
<a href="" @click.prevent="cancelIPRange" title="取消"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
<!-- IP范围 -->
|
||||
<div v-if="rangeType == 'ipRange'">
|
||||
<!-- 添加单个IP范围 -->
|
||||
<div style="margin-bottom: 1em" v-show="isAdding">
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td class="title">开始IP *</td>
|
||||
<td>
|
||||
<input type="text" placeholder="开始IP" maxlength="40" size="40" style="width: 15em" v-model="ipRangeFrom" ref="ipRangeFrom" @keyup.enter="confirmIPRange" @keypress.enter.prevent="1"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>结束IP *</td>
|
||||
<td>
|
||||
<input type="text" placeholder="结束IP" maxlength="40" size="40" style="width: 15em" v-model="ipRangeTo" ref="ipRangeTo" @keyup.enter="confirmIPRange" @keypress.enter.prevent="1"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>排除</td>
|
||||
<td>
|
||||
<checkbox v-model="isReverse"></checkbox>
|
||||
<p class="comment">选中后表示线路中排除当前条件。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<button class="ui button tiny" type="button" @click.prevent="confirmIPRange">确定</button>
|
||||
<a href="" @click.prevent="cancelIPRange" title="取消"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 添加多个 -->
|
||||
<div style="margin-bottom: 1em" v-show="isAddingBatch">
|
||||
<div class="ui field">
|
||||
<textarea rows="5" ref="batchIPRange" v-model="batchIPRange"></textarea>
|
||||
<p class="comment">每行一条,格式为<code-label>开始IP,结束IP</code-label>,比如<code-label>192.168.1.100,192.168.1.200</code-label>。</p>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
|
||||
<!-- 添加多个IP范围 -->
|
||||
<div style="margin-bottom: 1em" v-show="isAddingBatch">
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td class="title">IP范围列表 *</td>
|
||||
<td>
|
||||
<textarea rows="5" ref="batchIPRange" v-model="batchIPRange"></textarea>
|
||||
<p class="comment">每行一条,格式为<code-label>开始IP,结束IP</code-label>,比如<code-label>192.168.1.100,192.168.1.200</code-label>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>排除</td>
|
||||
<td>
|
||||
<checkbox v-model="isReverse"></checkbox>
|
||||
<p class="comment">选中后表示线路中排除当前条件。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<button class="ui button tiny" type="button" @click.prevent="confirmBatchIPRange">确定</button>
|
||||
<a href="" @click.prevent="cancelBatchIPRange" title="取消"><i class="icon remove small"></i></a>
|
||||
<a href="" @click.prevent="cancelBatchIPRange" title="取消"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
|
||||
<div v-if="!isAdding && !isAddingBatch">
|
||||
<button class="ui button tiny" type="button" @click.prevent="addIPRange">添加单个IP范围</button>
|
||||
<button class="ui button tiny" type="button" @click.prevent="addBatchIPRange">批量添加IP范围</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!isAdding && !isAddingBatch">
|
||||
<button class="ui button tiny" type="button" @click.prevent="add">单个添加</button>
|
||||
<button class="ui button tiny" type="button" @click.prevent="addBatch">批量添加</button>
|
||||
<!-- CIDR -->
|
||||
<div v-if="rangeType == 'cidr'">
|
||||
<!-- 添加单个IP范围 -->
|
||||
<div style="margin-bottom: 1em" v-show="isAdding">
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td class="title">CIDR *</td>
|
||||
<td>
|
||||
<input type="text" placeholder="IP/MASK" maxlength="40" size="40" style="width: 15em" v-model="ipCIDR" ref="ipCIDR" @keyup.enter="confirmIPCIDR" @keypress.enter.prevent="1"/>
|
||||
<p class="comment">类似于<code-label>192.168.2.1/24</code-label>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>排除</td>
|
||||
<td>
|
||||
<checkbox v-model="isReverse"></checkbox>
|
||||
<p class="comment">选中后表示线路中排除当前条件。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<button class="ui button tiny" type="button" @click.prevent="confirmIPCIDR">确定</button>
|
||||
<a href="" @click.prevent="cancelIPCIDR" title="取消"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
|
||||
<!-- 添加多个IP范围 -->
|
||||
<div style="margin-bottom: 1em" v-show="isAddingBatch">
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td class="title">IP范围列表 *</td>
|
||||
<td>
|
||||
<textarea rows="5" ref="batchIPCIDR" v-model="batchIPCIDR"></textarea>
|
||||
<p class="comment">每行一条,格式为<code-label>IP/MASK</code-label>,比如<code-label>192.168.2.1/24</code-label>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>排除</td>
|
||||
<td>
|
||||
<checkbox v-model="isReverse"></checkbox>
|
||||
<p class="comment">选中后表示线路中排除当前条件。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<button class="ui button tiny" type="button" @click.prevent="confirmBatchIPCIDR">确定</button>
|
||||
<a href="" @click.prevent="cancelBatchIPCIDR" title="取消"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
|
||||
<div v-if="!isAdding && !isAddingBatch">
|
||||
<button class="ui button tiny" type="button" @click.prevent="addCIDR">添加单个CIDR</button>
|
||||
<button class="ui button tiny" type="button" @click.prevent="addBatchCIDR">批量添加CIDR</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 区域 -->
|
||||
<div v-if="rangeType == 'region'">
|
||||
<!-- 添加区域 -->
|
||||
<div v-if="isAdding">
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td>已添加</td>
|
||||
<td>
|
||||
<div v-for="(region, index) in regions" class="ui label small basic">
|
||||
{{region.name}} <a href="" title="删除" @click.prevent="removeRegion(index)"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title">添加新<span v-if="regionType == 'country'">国家/地区</span><span v-if="regionType == 'province'">省份</span><span v-if="regionType == 'city'">城市</span><span v-if="regionType == 'provider'">ISP</span>
|
||||
|
||||
*</td>
|
||||
<td>
|
||||
<!-- region country name -->
|
||||
<div v-if="regionType == 'country'">
|
||||
<combo-box title="" width="14em" data-url="/ui/countryOptions" data-key="countries" placeholder="点这里选择国家/地区" @change="selectRegionCountry" ref="regionCountryComboBox" key="combo-box-country"></combo-box>
|
||||
</div>
|
||||
|
||||
<!-- region province name -->
|
||||
<div v-if="regionType == 'province'" >
|
||||
<combo-box title="" data-url="/ui/provinceOptions" data-key="provinces" placeholder="点这里选择省份" @change="selectRegionProvince" ref="regionProvinceComboBox" key="combo-box-province"></combo-box>
|
||||
</div>
|
||||
|
||||
<!-- region city name -->
|
||||
<div v-if="regionType == 'city'" >
|
||||
<combo-box title="" data-url="/ui/cityOptions" data-key="cities" placeholder="点这里选择城市" @change="selectRegionCity" ref="regionCityComboBox" key="combo-box-city"></combo-box>
|
||||
</div>
|
||||
|
||||
<!-- ISP Name -->
|
||||
<div v-if="regionType == 'provider'" >
|
||||
<combo-box title="" data-url="/ui/providerOptions" data-key="providers" placeholder="点这里选择ISP" @change="selectRegionProvider" ref="regionProviderComboBox" key="combo-box-isp"></combo-box>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 1em">
|
||||
<button class="ui button tiny basic" :class="{blue: regionType == 'country'}" type="button" @click.prevent="addRegion('country')">添加国家/地区</button>
|
||||
<button class="ui button tiny basic" :class="{blue: regionType == 'province'}" type="button" @click.prevent="addRegion('province')">添加省份</button>
|
||||
<button class="ui button tiny basic" :class="{blue: regionType == 'city'}" type="button" @click.prevent="addRegion('city')">添加城市</button>
|
||||
<button class="ui button tiny basic" :class="{blue: regionType == 'provider'}" type="button" @click.prevent="addRegion('provider')">ISP</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>排除</td>
|
||||
<td>
|
||||
<checkbox v-model="isReverse"></checkbox>
|
||||
<p class="comment">选中后表示线路中排除当前条件。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<button class="ui button tiny" type="button" @click.prevent="confirmRegions">确定</button>
|
||||
<a href="" @click.prevent="cancelRegions" title="取消"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
<div v-if="!isAdding && !isAddingBatch">
|
||||
<button class="ui button tiny" type="button" @click.prevent="addRegions">添加区域</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
})
|
||||
@@ -12,6 +12,7 @@ Vue.component("plan-bandwidth-ranges", {
|
||||
minMB: "",
|
||||
maxMB: "",
|
||||
pricePerMB: "",
|
||||
totalPrice: "",
|
||||
addingRange: {
|
||||
minMB: 0,
|
||||
maxMB: 0,
|
||||
@@ -36,12 +37,16 @@ Vue.component("plan-bandwidth-ranges", {
|
||||
this.minMB = ""
|
||||
this.maxMB = ""
|
||||
this.pricePerMB = ""
|
||||
this.totalPrice = ""
|
||||
this.ranges.push(this.addingRange)
|
||||
this.ranges.$sort(function (v1, v2) {
|
||||
if (v1.minMB < v2.minMB) {
|
||||
return -1
|
||||
}
|
||||
if (v1.minMB == v2.minMB) {
|
||||
if (v2.maxMB == 0 || v1.maxMB < v2.maxMB) {
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
@@ -83,13 +88,20 @@ Vue.component("plan-bandwidth-ranges", {
|
||||
pricePerMB = 0
|
||||
}
|
||||
this.addingRange.pricePerMB = pricePerMB
|
||||
},
|
||||
totalPrice: function (v) {
|
||||
let totalPrice = parseFloat(v.toString())
|
||||
if (isNaN(totalPrice) || totalPrice < 0) {
|
||||
totalPrice = 0
|
||||
}
|
||||
this.addingRange.totalPrice = totalPrice
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<!-- 已有价格 -->
|
||||
<div v-if="ranges.length > 0">
|
||||
<div class="ui label basic small" v-for="(range, index) in ranges" style="margin-bottom: 0.5em">
|
||||
{{range.minMB}}MB - <span v-if="range.maxMB > 0">{{range.maxMB}}MB</span><span v-else>∞</span> 价格:{{range.pricePerMB}}元/MB
|
||||
{{range.minMB}}MB - <span v-if="range.maxMB > 0">{{range.maxMB}}MB</span><span v-else>∞</span> 价格:<span v-if="range.totalPrice > 0">{{range.totalPrice}}元</span><span v-else="">{{range.pricePerMB}}元/MB</span>
|
||||
<a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
@@ -99,7 +111,7 @@ Vue.component("plan-bandwidth-ranges", {
|
||||
<div v-if="isAdding">
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td class="title">带宽下限</td>
|
||||
<td class="title">带宽下限 *</td>
|
||||
<td>
|
||||
<div class="ui input right labeled">
|
||||
<input type="text" placeholder="最小带宽" style="width: 7em" maxlength="10" ref="minMB" @keyup.enter="confirm()" @keypress.enter.prevent="1" v-model="minMB"/>
|
||||
@@ -108,7 +120,7 @@ Vue.component("plan-bandwidth-ranges", {
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title">带宽上限</td>
|
||||
<td class="title">带宽上限 *</td>
|
||||
<td>
|
||||
<div class="ui input right labeled">
|
||||
<input type="text" placeholder="最大带宽" style="width: 7em" maxlength="10" @keyup.enter="confirm()" @keypress.enter.prevent="1" v-model="maxMB"/>
|
||||
@@ -117,6 +129,16 @@ Vue.component("plan-bandwidth-ranges", {
|
||||
<p class="comment">如果填0,表示上不封顶。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>总价格</td>
|
||||
<td>
|
||||
<div class="ui input right labeled">
|
||||
<input type="text" placeholder="总价格" style="width: 7em" maxlength="10" @keyup.enter="confirm()" @keypress.enter.prevent="1" v-model="totalPrice"/>
|
||||
<span class="ui label">元/MB</span>
|
||||
</div>
|
||||
<p class="comment">和单位价格二选一。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title">单位价格</td>
|
||||
<td>
|
||||
@@ -124,6 +146,7 @@ Vue.component("plan-bandwidth-ranges", {
|
||||
<input type="text" placeholder="单位价格" style="width: 7em" maxlength="10" @keyup.enter="confirm()" @keypress.enter.prevent="1" v-model="pricePerMB"/>
|
||||
<span class="ui label">元/MB</span>
|
||||
</div>
|
||||
<p class="comment">和总价格二选一。如果设置了单位价格,那么"总价格 = 单位价格 x 带宽"。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -26,7 +26,7 @@ Vue.component("plan-price-view", {
|
||||
按{{plan.bandwidthPrice.percentile}}th带宽计费
|
||||
<div>
|
||||
<div v-for="range in plan.bandwidthPrice.ranges">
|
||||
<span class="small grey">{{range.minMB}} - <span v-if="range.maxMB > 0">{{range.maxMB}}MB</span><span v-else>∞</span>: {{range.pricePerMB}}元/MB</span>
|
||||
<span class="small grey">{{range.minMB}} - <span v-if="range.maxMB > 0">{{range.maxMB}}MB</span><span v-else>∞</span>: <span v-if="range.totalPrice > 0">{{range.totalPrice}}元</span><span v-else="">{{range.pricePerMB}}元/MB</span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -59,12 +59,19 @@ Vue.component("http-access-log-box", {
|
||||
},
|
||||
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>
|
||||
<a :href="'/servers/server/log?serverId=' + accessLog.serverId" title="点击到网站服务" v-if="vShowServerLink"><span class="grey">[服务]</span></a>
|
||||
|
||||
<!-- 服务 -->
|
||||
<a :href="'/servers/server/log?serverId=' + accessLog.serverId" title="点击到网站服务" v-if="vShowServerLink && accessLog.serverId > 0"><span class="grey">[服务]</span></a>
|
||||
<span v-if="vShowServerLink && (accessLog.serverId == null || accessLog.serverId == 0)" @click.prevent="mismatch()"><span class="disabled">[服务]</span></span>
|
||||
|
||||
<span v-if="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.wafInfo != null">
|
||||
|
||||
@@ -14,7 +14,7 @@ Vue.component("http-compression-config-box", {
|
||||
isPrior: false,
|
||||
isOn: false,
|
||||
useDefaultTypes: true,
|
||||
types: ["brotli", "gzip", "deflate"],
|
||||
types: ["brotli", "gzip", "zstd", "deflate"],
|
||||
level: 5,
|
||||
decompressData: false,
|
||||
gzipRef: null,
|
||||
@@ -53,6 +53,11 @@ Vue.component("http-compression-config-box", {
|
||||
name: "Brotli",
|
||||
code: "brotli",
|
||||
isOn: true
|
||||
},
|
||||
{
|
||||
name: "ZSTD",
|
||||
code: "zstd",
|
||||
isOn: true
|
||||
}
|
||||
]
|
||||
|
||||
@@ -189,7 +194,7 @@ Vue.component("http-compression-config-box", {
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" v-model="config.useDefaultTypes" id="compression-use-default"/>
|
||||
<label v-if="config.useDefaultTypes" for="compression-use-default">使用默认顺序<span class="grey small">(brotli、gzip、deflate)</span></label>
|
||||
<label v-if="config.useDefaultTypes" for="compression-use-default">使用默认顺序<span class="grey small">(brotli、gzip、 zstd、deflate)</span></label>
|
||||
<label v-if="!config.useDefaultTypes" for="compression-use-default">使用默认顺序</label>
|
||||
</div>
|
||||
<div v-show="!config.useDefaultTypes">
|
||||
|
||||
@@ -140,7 +140,7 @@ Vue.component("http-firewall-captcha-options", {
|
||||
<input type="text" style="width: 5em" maxlength="9" v-model="options.failBlockTimeout" @keyup.enter="confirm()" @keypress.enter.prevent="1"/>
|
||||
<span class="ui label">秒</span>
|
||||
</div>
|
||||
<p class="comment">在达到最多失败次数(大于0)时,自动拦截的时间;如果为0表示不自动拦截。</p>
|
||||
<p class="comment">在达到最多失败次数(大于0)时,自动拦截的时长;如果为0表示不自动拦截。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -92,6 +92,7 @@ Vue.component("http-firewall-checkpoint-cc", {
|
||||
let keys = []
|
||||
let period = 60
|
||||
let threshold = 1000
|
||||
let ignoreCommonFiles = false
|
||||
|
||||
let options = {}
|
||||
if (window.parent.UPDATING_RULE != null) {
|
||||
@@ -113,6 +114,9 @@ Vue.component("http-firewall-checkpoint-cc", {
|
||||
if (options.threshold != null) {
|
||||
threshold = options.threshold
|
||||
}
|
||||
if (options.ignoreCommonFiles != null && typeof (options.ignoreCommonFiles) == "boolean") {
|
||||
ignoreCommonFiles = options.ignoreCommonFiles
|
||||
}
|
||||
|
||||
let that = this
|
||||
setTimeout(function () {
|
||||
@@ -123,6 +127,7 @@ Vue.component("http-firewall-checkpoint-cc", {
|
||||
keys: keys,
|
||||
period: period,
|
||||
threshold: threshold,
|
||||
ignoreCommonFiles: ignoreCommonFiles,
|
||||
options: {},
|
||||
value: threshold
|
||||
}
|
||||
@@ -133,6 +138,9 @@ Vue.component("http-firewall-checkpoint-cc", {
|
||||
},
|
||||
threshold: function () {
|
||||
this.change()
|
||||
},
|
||||
ignoreCommonFiles: function () {
|
||||
this.change()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -152,6 +160,11 @@ Vue.component("http-firewall-checkpoint-cc", {
|
||||
}
|
||||
this.value = threshold
|
||||
|
||||
let ignoreCommonFiles = this.ignoreCommonFiles
|
||||
if (typeof ignoreCommonFiles != "boolean") {
|
||||
ignoreCommonFiles = false
|
||||
}
|
||||
|
||||
this.vCheckpoint.options = [
|
||||
{
|
||||
code: "keys",
|
||||
@@ -164,6 +177,10 @@ Vue.component("http-firewall-checkpoint-cc", {
|
||||
{
|
||||
code: "threshold",
|
||||
value: threshold
|
||||
},
|
||||
{
|
||||
code: "ignoreCommonFiles",
|
||||
value: ignoreCommonFiles
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -193,6 +210,13 @@ Vue.component("http-firewall-checkpoint-cc", {
|
||||
<input type="text" v-model="threshold" style="width: 6em" maxlength="8"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>忽略常见文件</td>
|
||||
<td>
|
||||
<checkbox v-model="ignoreCommonFiles"></checkbox>
|
||||
<p class="comment">忽略js、css、jpg等常见在网页里被引用的文件名。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>`
|
||||
})
|
||||
|
||||
@@ -86,10 +86,11 @@ Vue.component("origin-list-table", {
|
||||
</thead>
|
||||
<tr v-for="origin in vOrigins">
|
||||
<td :class="{disabled:!origin.isOn}"><a href="" @click.prevent="updateOrigin(origin.id)">{{origin.addr}} <i class="icon expand small"></i></a>
|
||||
<div style="margin-top: 0.3em" v-if="origin.name.length > 0 || origin.hasCert || (origin.host != null && origin.host.length > 0) || (origin.domains != null && origin.domains.length > 0)">
|
||||
<div style="margin-top: 0.3em" v-if="origin.name.length > 0 || origin.hasCert || (origin.host != null && origin.host.length > 0) || origin.followPort || (origin.domains != null && origin.domains.length > 0)">
|
||||
<tiny-basic-label v-if="origin.name.length > 0">{{origin.name}}</tiny-basic-label>
|
||||
<tiny-basic-label v-if="origin.hasCert">证书</tiny-basic-label>
|
||||
<tiny-basic-label v-if="origin.host != null && origin.host.length > 0">主机名: {{origin.host}}</tiny-basic-label>
|
||||
<tiny-basic-label v-if="origin.followPort">端口跟随</tiny-basic-label>
|
||||
<span v-if="origin.domains != null && origin.domains.length > 0"><tiny-basic-label v-for="domain in origin.domains">匹配: {{domain}}</tiny-basic-label></span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@@ -18,6 +18,7 @@ Vue.component("reverse-proxy-box", {
|
||||
requestURI: "",
|
||||
requestHost: "",
|
||||
requestHostType: 0,
|
||||
requestHostExcludingPort: false,
|
||||
addHeaders: [],
|
||||
connTimeout: {count: 0, unit: "second"},
|
||||
readTimeout: {count: 0, unit: "second"},
|
||||
@@ -169,18 +170,24 @@ Vue.component("reverse-proxy-box", {
|
||||
<tr v-show="family == null || family == 'http'">
|
||||
<td>回源主机名<em>(Host)</em></td>
|
||||
<td>
|
||||
<radio :v-value="0" v-model="reverseProxyConfig.requestHostType">跟随代理服务</radio>
|
||||
<radio :v-value="0" v-model="reverseProxyConfig.requestHostType">跟随CDN域名</radio>
|
||||
<radio :v-value="1" v-model="reverseProxyConfig.requestHostType">跟随源站</radio>
|
||||
<radio :v-value="2" v-model="reverseProxyConfig.requestHostType">自定义</radio>
|
||||
<div v-show="reverseProxyConfig.requestHostType == 2" style="margin-top: 0.8em">
|
||||
<input type="text" placeholder="比如example.com" v-model="reverseProxyConfig.requestHost"/>
|
||||
</div>
|
||||
<p class="comment">请求源站时的Host,用于修改源站接收到的域名
|
||||
<span v-if="reverseProxyConfig.requestHostType == 0">,"跟随代理服务"是指源站接收到的域名和当前代理服务保持一致</span>
|
||||
<span v-if="reverseProxyConfig.requestHostType == 0">,"跟随CDN域名"是指源站接收到的域名和当前CDN访问域名保持一致</span>
|
||||
<span v-if="reverseProxyConfig.requestHostType == 1">,"跟随源站"是指源站接收到的域名仍然是填写的源站地址中的信息,不随代理服务域名改变而改变</span>
|
||||
<span v-if="reverseProxyConfig.requestHostType == 2">,自定义Host内容中支持请求变量</span>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-show="family == null || family == 'http'">
|
||||
<td>回源主机名移除端口</td>
|
||||
<td><checkbox v-model="reverseProxyConfig.requestHostExcludingPort"></checkbox>
|
||||
<p class="comment">选中后表示移除回源主机名中的端口部分。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<more-options-tbody @change="changeAdvancedVisible" v-if="isOn()"></more-options-tbody>
|
||||
<tbody v-show="isOn() && advancedVisible">
|
||||
|
||||
@@ -1,13 +1,4 @@
|
||||
Vue.component("user-selector", {
|
||||
mounted: function () {
|
||||
let that = this
|
||||
|
||||
Tea.action("/servers/users/options")
|
||||
.post()
|
||||
.success(function (resp) {
|
||||
that.users = resp.data.users
|
||||
})
|
||||
},
|
||||
props: ["v-user-id"],
|
||||
data: function () {
|
||||
let userId = this.vUserId
|
||||
@@ -25,9 +16,6 @@ Vue.component("user-selector", {
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<select class="ui dropdown auto-width" name="userId" v-model="userId">
|
||||
<option value="0">[选择用户]</option>
|
||||
<option v-for="user in users" :value="user.id">{{user.fullname}} ({{user.username}})</option>
|
||||
</select>
|
||||
<combo-box placeholder="选择用户" :data-url="'/servers/users/options'" :data-key="'users'" name="userId" :v-value="userId"></combo-box>
|
||||
</div>`
|
||||
})
|
||||
@@ -180,7 +180,7 @@ window.teaweb = {
|
||||
bytesAxis: function (stats, countFunc) {
|
||||
let max = Math.max.apply(this, stats.map(countFunc))
|
||||
let divider = 1
|
||||
let unit = ""
|
||||
let unit = "B"
|
||||
if (max >= Math.pow(1024, 6)) {
|
||||
unit = "E"
|
||||
divider = Math.pow(1024, 6)
|
||||
@@ -583,7 +583,10 @@ window.teaweb = {
|
||||
show: true,
|
||||
trigger: "item",
|
||||
formatter: function (args) {
|
||||
return tooltipFunc.apply(this, [args, values])
|
||||
if (tooltipFunc != null) {
|
||||
return tooltipFunc.apply(this, [args, values])
|
||||
}
|
||||
return null
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# robots.txt for GoEdge
|
||||
# robots.txt
|
||||
#
|
||||
|
||||
User-agent: *
|
||||
|
||||
@@ -4,7 +4,11 @@
|
||||
<title>{$.teaTitle}控制台</title>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0">
|
||||
<link rel="shortcut icon" href="/images/favicon.png"/>
|
||||
{$if eq .teaFaviconFileId 0}
|
||||
<link rel="shortcut icon" href="/images/favicon.png"/>
|
||||
{$else}
|
||||
<link rel="shortcut icon" href="/ui/image/{$.teaFaviconFileId}"/>
|
||||
{$end}
|
||||
<link rel="stylesheet" type="text/css" href="/_/@default/@layout_popup.css" media="all"/>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/css/semantic.iframe.min.css?v=bRafhK" media="all"/>
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
<td>认证二维码</td>
|
||||
<td>
|
||||
<img :src="'/admins/otpQrcode?adminId=' + admin.id"/>
|
||||
<p class="comment">可以通过二维码快速添加OTP认证信息到App中。</p>
|
||||
<p class="comment">可以通过二维码快速添加OTP认证信息到认证App中。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="otp != null && otp.isOn && moreOptionsVisible">
|
||||
|
||||
@@ -44,6 +44,24 @@
|
||||
<p class="comment">通过HTTP访问API节点的网络地址。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="node.statusIsValid">
|
||||
<td>CPU用量</td>
|
||||
<td>{{node.status.cpuUsageText}} <span v-if="node.status.cpuPhysicalCount > 0" class="small grey">({{node.status.cpuPhysicalCount}}核心/{{node.status.cpuLogicalCount}}线程)</span></td>
|
||||
</tr>
|
||||
<tr v-if="node.statusIsValid">
|
||||
<td>内存用量</td>
|
||||
<td>{{node.status.memUsageText}}</td>
|
||||
</tr>
|
||||
<tr v-if="node.statusIsValid">
|
||||
<td>版本</td>
|
||||
<td>v{{node.status.buildVersion}}
|
||||
<span class="red" v-if="newVersion.length > 0">需要升级到新版本v{{newVersion}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="node.statusIsValid">
|
||||
<td>主程序位置</td>
|
||||
<td>{{node.status.exePath}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>主节点</td>
|
||||
<td>
|
||||
|
||||
@@ -206,6 +206,10 @@
|
||||
<a :href="'/clusters/cluster/upgradeRemote?clusterId=' + clusterId" v-if="shouldUpgrade"><span class="red">发现新版本v{{newVersion}} »</span></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="node.status.exePath != null && node.status.exePath.length > 0">
|
||||
<td>主程序位置</td>
|
||||
<td>{{node.status.exePath}}</td>
|
||||
</tr>
|
||||
<tr v-if="nodeDatetime.length > 0">
|
||||
<td>上次更新时间</td>
|
||||
<td>
|
||||
|
||||
@@ -16,6 +16,13 @@
|
||||
<p class="comment">用于生成集群节点和网站服务的DNS解析记录<span v-if="domainId > 0">,修改后将自动删除旧域名中的相关记录</span>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="oldDomain.id > 0 && domain.id == 0">
|
||||
<td>不使用主域名</td>
|
||||
<td>
|
||||
<checkbox name="confirmResetDomain"></checkbox>
|
||||
<p class="comment">选中后,表示确认当前集群不使用主域名;否则必须选择一个主域名。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title">DNS子域名 *</td>
|
||||
<td>
|
||||
@@ -31,13 +38,20 @@
|
||||
<td>自动设置CNAME记录<optional-label></optional-label></td>
|
||||
<td>
|
||||
<values-box :values="cnameRecords" name="cnameRecords" placeholder="记录名" ref="cnameRecords"></values-box>
|
||||
<p class="comment">除集群已创建的网站服务之外,自动解析到集群的CNAME记录,比如<code-label @click.prevent="addCnameRecord('@')">@</code-label>、<code-label @click.prevent="addCnameRecord('www')">www</code-label>。</p>
|
||||
<p class="comment">除集群已创建的网站服务之外,自动解析到集群的CNAME记录,比如<code-label @click.prevent="addCnameRecord('www')">www</code-label>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><more-options-indicator></more-options-indicator></td>
|
||||
</tr>
|
||||
<tbody v-show="moreOptionsVisible">
|
||||
<tr>
|
||||
<td>允许通过CNAME访问网站服务</td>
|
||||
<td>
|
||||
<checkbox name="cnameAsDomain" v-model="cnameAsDomain"></checkbox>
|
||||
<p class="comment">选中后,表示允许使用CNAME直接访问网站服务;如果取消选中,则表示CNAME只作为DNS解析记录使用。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>记录TTL</td>
|
||||
<td>
|
||||
|
||||
@@ -2,6 +2,7 @@ Tea.context(function () {
|
||||
this.success = NotifyReloadSuccess("保存成功")
|
||||
|
||||
this.domain = {id: this.domainId, name: this.domainName}
|
||||
this.oldDomain = {id: this.domainId, name: this.domainName}
|
||||
this.changeDomain = function (domain) {
|
||||
this.domain.id = domain.id
|
||||
this.domain.name = domain.name
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<input type="hidden" name="clusterId" :value="clusterId"/>
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">启用</td>
|
||||
<td class="title">启用WebP功能</td>
|
||||
<td>
|
||||
<checkbox name="isOn" v-model="webpPolicy.isOn"></checkbox>
|
||||
<p class="comment">选中后,表示当前集群下的服务可以使用WebP转换功能。</p>
|
||||
|
||||
@@ -23,14 +23,24 @@
|
||||
<a href="" title="关闭" @click.prevent="closeMessage"><i class="ui icon remove small"></i></a>
|
||||
</div>
|
||||
|
||||
<!-- 升级提醒 -->
|
||||
<!-- 边缘节点升级提醒 -->
|
||||
<div class="ui icon message error" v-if="!isLoading && nodeUpgradeInfo.count > 0">
|
||||
<i class="icon warning circle"></i>
|
||||
<a href="/clusters">升级提醒:有 {{nodeUpgradeInfo.count}} 个边缘节点需要升级到 v{{nodeUpgradeInfo.version}} 版本,系统正在尝试自动升级...</a><a href="" title="关闭" @click.prevent="closeMessage"><i class="ui icon remove small"></i></a>
|
||||
</div>
|
||||
|
||||
<!-- API节点升级提醒 -->
|
||||
<div class="ui icon message error" v-if="!isLoading && apiNodeUpgradeInfo.count > 0">
|
||||
<i class="icon warning circle"></i>
|
||||
<a href="/api">升级提醒:有 {{apiNodeUpgradeInfo.count}} 个API节点需要升级到 v{{apiNodeUpgradeInfo.version}} 版本</a><a href="" title="关闭" @click.prevent="closeMessage"><i class="ui icon remove small"></i></a>
|
||||
<a href="/api">升级提醒:有 {{apiNodeUpgradeInfo.count}} 个API节点需要升级到 v{{apiNodeUpgradeInfo.version}} 版本;如果已经升级,请尝试重启API节点进程。</a><a href="" title="关闭" @click.prevent="closeMessage"><i class="ui icon remove small"></i></a>
|
||||
</div>
|
||||
|
||||
<!-- 本地API节点 -->
|
||||
<div class="ui icon message error" v-if="!isLoading && localLowerVersionAPINode != null">
|
||||
<i class="icon warning circle"></i>
|
||||
<span v-if="localLowerVersionAPINode.runtimeVersion == localLowerVersionAPINode.fileVersion">升级提醒:发现一个正在使用的本地API节点版本需要升级,文件位置:{{localLowerVersionAPINode.exePath}},当前版本:v{{localLowerVersionAPINode.runtimeVersion}}。</span>
|
||||
<span v-if="localLowerVersionAPINode.runtimeVersion != localLowerVersionAPINode.fileVersion">升级提醒:发现一个正在使用的本地API节点版本文件已经更新,但需要重启后生效,文件位置:{{localLowerVersionAPINode.exePath}}。 <a href="" @click.prevent="restartAPINode" v-if="!isRestartingLocalAPINode">[帮我重启]</a><span v-if="isRestartingLocalAPINode">尝试重启中...</span></span>
|
||||
<a href="" title="关闭" @click.prevent="closeMessage"><i class="ui icon remove small"></i></a>
|
||||
</div>
|
||||
|
||||
<!-- 没有磁盘空间提醒 -->
|
||||
|
||||
@@ -3,6 +3,7 @@ Tea.context(function () {
|
||||
this.trafficTab = "hourly"
|
||||
this.metricCharts = []
|
||||
this.dashboard = {}
|
||||
this.localLowerVersionAPINode = null
|
||||
|
||||
this.$delay(function () {
|
||||
this.$post("$")
|
||||
@@ -193,4 +194,29 @@ Tea.context(function () {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 重启本地API节点
|
||||
this.isRestartingLocalAPINode = false
|
||||
this.restartAPINode = function () {
|
||||
if (this.isRestartingLocalAPINode) {
|
||||
return
|
||||
}
|
||||
if (this.localLowerVersionAPINode == null) {
|
||||
return
|
||||
}
|
||||
this.isRestartingLocalAPINode = true
|
||||
this.localLowerVersionAPINode.isRestarting = true
|
||||
this.$post("/dashboard/restartLocalAPINode")
|
||||
.params({
|
||||
"exePath": this.localLowerVersionAPINode.exePath
|
||||
})
|
||||
.timeout(300)
|
||||
.success(function () {
|
||||
teaweb.reload()
|
||||
})
|
||||
.done(function () {
|
||||
this.isRestartingLocalAPINode = false
|
||||
this.localLowerVersionAPINode.isRestarting = false
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -113,24 +113,6 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<!-- DNS.COM -->
|
||||
<tbody v-if="type == 'dnscom'">
|
||||
<tr>
|
||||
<td>AccessKeyId *</td>
|
||||
<td>
|
||||
<input type="text" name="paramApiKey" maxlength="100"/>
|
||||
<p class="comment">登录DNS.COM控制台 -- 在"账户中心" -- "API设置"中创建和获取。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>AccessKeySecret *</td>
|
||||
<td>
|
||||
<input type="text" name="paramApiSecret" maxlength="100"/>
|
||||
<p class="comment">登录DNS.COM控制台 -- 在"账户中心" -- "API设置"中创建和获取。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<!-- GoDaddy -->
|
||||
<tbody v-if="type == 'godaddy'">
|
||||
<tr>
|
||||
@@ -148,6 +130,49 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<!-- ClouDNS -->
|
||||
<tbody v-if="type == 'cloudns'">
|
||||
<tr>
|
||||
<td>用户认证ID<em>(auth-id)</em></td>
|
||||
<td>
|
||||
<input type="text" name="paramClouDNSAuthId" maxlength="20"/>
|
||||
<p class="comment">和子用户认证ID二选一。可以在ClouDNS<a href="https://www.cloudns.net/api-settings/" target="_blank">API设置页面</a>添加。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>子用户认证ID<em>(sub-auth-id)</em></td>
|
||||
<td>
|
||||
<input type="text" name="paramClouDNSSubAuthId" maxlength="20"/>
|
||||
<p class="comment">和用户认证ID二选一。可以在ClouDNS<a href="https://www.cloudns.net/api-settings/" target="_blank">API设置页面</a>添加。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>认证密码 *<em>(auth-password)</em></td>
|
||||
<td>
|
||||
<input type="password" name="paramClouDNSAuthPassword" maxlength="100"/>
|
||||
<p class="comment">用户或者子用户的认证密码。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<!-- DNS.COM -->
|
||||
<tbody v-if="type == 'dnscom'">
|
||||
<tr>
|
||||
<td>API Key *</td>
|
||||
<td>
|
||||
<input type="text" name="paramDNSComKey" maxlength="100"/>
|
||||
<p class="comment">在DNS.COM控制台账号中心--API设置中创建和查看。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>API Secret *</td>
|
||||
<td>
|
||||
<input type="text" name="paramDNSComSecret" maxlength="100"/>
|
||||
<p class="comment">在DNS.COM控制台账号中心--API设置中创建和查看。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<!-- Edge DNS -->
|
||||
<tbody v-if="type == 'localEdgeDNS'">
|
||||
<tr>
|
||||
|
||||
@@ -97,6 +97,34 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<!-- ClouDNS -->
|
||||
<tbody v-if="provider.type == 'cloudns'">
|
||||
<tr v-if="provider.apiParams.authId > 0">
|
||||
<td class="color-border">用户认证ID<em>(auth-id)</em></td>
|
||||
<td>{{provider.apiParams.authId}}</td>
|
||||
</tr>
|
||||
<tr v-if="provider.apiParams.subAuthId > 0">
|
||||
<td class="color-border">子用户认证ID<em>(sub-auth-id)</em></td>
|
||||
<td>{{provider.apiParams.subAuthId}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="color-border">认证密码<em>(auth-password)</em></td>
|
||||
<td>{{provider.apiParams.authPassword}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<!-- DNS.COM -->
|
||||
<tbody v-if="provider.type == 'dnscom'">
|
||||
<tr>
|
||||
<td class="color-border">API Key</td>
|
||||
<td>{{provider.apiParams.key}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="color-border">API Secret</td>
|
||||
<td>{{provider.apiParams.secret}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<!-- Local EdgeDNS -->
|
||||
<tbody v-if="provider.type == 'localEdgeDNS'">
|
||||
<tr>
|
||||
|
||||
@@ -130,6 +130,49 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<!-- ClouDNS -->
|
||||
<tbody v-if="provider.type == 'cloudns'">
|
||||
<tr>
|
||||
<td>用户认证ID<em>(auth-id)</em></td>
|
||||
<td>
|
||||
<input type="text" name="paramClouDNSAuthId" maxlength="20" v-model="provider.params.authId"/>
|
||||
<p class="comment">和子用户认证ID二选一。可以在ClouDNS<a href="https://www.cloudns.net/api-settings/" target="_blank">API设置页面</a>添加。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>子用户认证ID<em>(sub-auth-id)</em></td>
|
||||
<td>
|
||||
<input type="text" name="paramClouDNSSubAuthId" maxlength="20" v-model="provider.params.subAuthId"/>
|
||||
<p class="comment">和用户认证ID二选一。可以在ClouDNS<a href="https://www.cloudns.net/api-settings/" target="_blank">API设置页面</a>添加。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>认证密码 *<em>(auth-password)</em></td>
|
||||
<td>
|
||||
<input type="password" name="paramClouDNSAuthPassword" maxlength="100" v-model="provider.params.authPassword"/>
|
||||
<p class="comment">用户或者子用户的认证密码。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<!-- DNS.COM -->
|
||||
<tbody v-if="provider.type == 'dnscom'">
|
||||
<tr>
|
||||
<td>API Key *</td>
|
||||
<td>
|
||||
<input type="text" name="paramDNSComKey" maxlength="100" v-model="provider.params.key"/>
|
||||
<p class="comment">在DNS.COM控制台账号中心--API设置中创建和查看。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>API Secret *</td>
|
||||
<td>
|
||||
<input type="text" name="paramDNSComSecret" maxlength="100" v-model="provider.params.secret"/>
|
||||
<p class="comment">在DNS.COM控制台账号中心--API设置中创建和查看。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<!-- Edge DNS -->
|
||||
<tbody v-if="provider.type == 'localEdgeDNS'">
|
||||
<tr>
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<td>自动设置CNAME记录</td>
|
||||
<td>
|
||||
<values-box :values="cnameRecords" name="cnameRecords" placeholder="记录名" ref="cnameRecords"></values-box>
|
||||
<p class="comment">除集群已创建的网站服务之外,自动解析到集群的CNAME记录,比如<code-label @click.prevent="addCnameRecord('@')">@</code-label>、<code-label @click.prevent="addCnameRecord('www')">www</code-label>。</p>
|
||||
<p class="comment">除集群已创建的网站服务之外,自动解析到集群的CNAME记录,比如<code-label @click.prevent="addCnameRecord('www')">www</code-label>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
</select>
|
||||
|
||||
<!-- TCP -->
|
||||
<select class="ui dropdown auto-width" name="protocol" v-if="serverType == 'tcpProxy'">
|
||||
<select class="ui dropdown auto-width" name="protocol" v-if="serverType == 'tcpProxy'" v-model="protocol">
|
||||
<option value="tcp">TCP</option>
|
||||
<option value="tls">TLS</option>
|
||||
</select>
|
||||
|
||||
<!-- UDP -->
|
||||
<select class="ui dropdown auto-width" name="protocol" v-if="serverType == 'udpProxy'">
|
||||
<select class="ui dropdown auto-width" name="protocol" v-if="serverType == 'udpProxy'" v-model="protocol">
|
||||
<option value="udp">UDP</option>
|
||||
</select>
|
||||
</td>
|
||||
@@ -32,6 +32,38 @@
|
||||
<p class="comment"><span class="red" v-if="addrError.length > 0">{{addrError}}</span>源站服务器地址,通常是一个IP(或域名)加端口<span v-if="serverType == 'httpProxy'">,不需要加 http:// 或 https://</span>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><more-options-indicator></more-options-indicator></td>
|
||||
</tr>
|
||||
<tbody v-show="moreOptionsVisible">
|
||||
<tr v-if="protocol == 'https' || protocol == 'tls'">
|
||||
<td>{{protocol.toUpperCase()}}证书</td>
|
||||
<td>
|
||||
<ssl-certs-box :v-single-mode="true" :v-view-size="'mini'" :v-description="'可以选择连接源站使用的证书。'"></ssl-certs-box>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="isHTTP || protocol == 'tls'">
|
||||
<td>回源主机名</td>
|
||||
<td>
|
||||
<input type="text" name="host" placeholder="比如example.com" maxlength="100"/>
|
||||
<p class="comment">请求源站时的Host,用于修改源站接收到的域名<span v-if="isHTTP">,自定义Host内容中支持请求变量</span>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="isHTTP">
|
||||
<td>专属域名</td>
|
||||
<td>
|
||||
<domains-box></domains-box>
|
||||
<p class="comment">默认不需要填写,表示支持所有域名。如果填写了专属域名,表示这些源站只会在所列的专属域名被访问时才生效。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>端口跟随</td>
|
||||
<td>
|
||||
<checkbox name="followPort"></checkbox>
|
||||
<p class="comment">选中后表示源站的端口保持和用户访问的服务端口保持一致;此时的源站地址中的端口号可以任意填写。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<submit-btn></submit-btn>
|
||||
</form>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user