Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
939e5999ca | ||
|
|
1f91e57d56 | ||
|
|
81e749dc60 | ||
|
|
241b2afda8 | ||
|
|
94cc29f227 | ||
|
|
6d6659eee1 | ||
|
|
5220be0775 | ||
|
|
07ebbf0863 | ||
|
|
b60c767fc5 | ||
|
|
371c3b78c3 | ||
|
|
6a3aa219d5 | ||
|
|
df586ddfdd | ||
|
|
f3b2bbfec0 | ||
|
|
3ea2114798 | ||
|
|
809cf70e0e | ||
|
|
390619535f | ||
|
|
3392ac1fa8 | ||
|
|
b09d94abbe | ||
|
|
03ac01d21f | ||
|
|
12b1d785e5 | ||
|
|
71c58e9d2e | ||
|
|
13c2997a52 | ||
|
|
47b840cac9 | ||
|
|
3f7f243f50 | ||
|
|
1d1e83b18d | ||
|
|
6f3a602c76 | ||
|
|
afb7a4c6a7 | ||
|
|
d0c950d4ca | ||
|
|
63ee7d5211 | ||
|
|
6b0d875745 | ||
|
|
a47a9b9c0c | ||
|
|
27040a3e5c | ||
|
|
c7a8a40e22 | ||
|
|
408de6af63 | ||
|
|
8a324afaa1 | ||
|
|
54bc4cede0 | ||
|
|
4cfbea80b0 | ||
|
|
64cb8286bd | ||
|
|
3e92e0afc6 | ||
|
|
8a91308280 | ||
|
|
3da861d71e | ||
|
|
3566e18e99 | ||
|
|
6e608e627a | ||
|
|
65f7fb979b |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,5 @@
|
||||
*_plus.go
|
||||
*-plus.sh
|
||||
*_plus.html
|
||||
*_plus.js
|
||||
*@plus.js
|
||||
@@ -60,7 +60,7 @@ function build() {
|
||||
|
||||
# build
|
||||
echo "building "${NAME}" ..."
|
||||
env GOOS=$OS GOARCH=$GOARCH go build -tags $TAG -ldflags="-s -w" -o $DIST/bin/${NAME} $ROOT/../cmd/edge-admin/main.go
|
||||
env GOOS=$OS GOARCH=$ARCH go build -tags $TAG -ldflags="-s -w" -o $DIST/bin/${NAME} $ROOT/../cmd/edge-admin/main.go
|
||||
|
||||
# delete hidden files
|
||||
find $DIST -name ".DS_Store" -delete
|
||||
|
||||
4
go.mod
4
go.mod
@@ -10,9 +10,11 @@ require (
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible
|
||||
github.com/google/go-cmp v0.5.6 // indirect
|
||||
github.com/iwind/TeaGo v0.0.0-20210720011303-fc255c995afa
|
||||
github.com/iwind/TeaGo v0.0.0-20211026123858-7de7a21cad24
|
||||
github.com/iwind/gosock v0.0.0-20210722083328-12b2d66abec3
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/miekg/dns v1.1.35
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
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
|
||||
|
||||
12
go.sum
12
go.sum
@@ -62,12 +62,16 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/iwind/TeaGo v0.0.0-20210628135026-38575a4ab060/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
|
||||
github.com/iwind/TeaGo v0.0.0-20210720011303-fc255c995afa h1:woN88uEmRRUNFD7pRZEtX9heDcjFn0ClMxjF5ButKow=
|
||||
github.com/iwind/TeaGo v0.0.0-20210720011303-fc255c995afa/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
|
||||
github.com/iwind/TeaGo v0.0.0-20210831140440-a2a442471b13 h1:HuEJ5xJfujW1Q6rNDhOu5LQXEBB2qLPah3jYslT8Gz4=
|
||||
github.com/iwind/TeaGo v0.0.0-20210831140440-a2a442471b13/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
|
||||
github.com/iwind/TeaGo v0.0.0-20211026123858-7de7a21cad24 h1:1cGulkD2SNJJRok5OKwyhP/Ddm+PgSWKOupn0cR36/A=
|
||||
github.com/iwind/TeaGo v0.0.0-20211026123858-7de7a21cad24/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
|
||||
github.com/iwind/gosock v0.0.0-20210722083328-12b2d66abec3 h1:aBSonas7vFcgTj9u96/bWGILGv1ZbUSTLiOzcI1ZT6c=
|
||||
github.com/iwind/gosock v0.0.0-20210722083328-12b2d66abec3/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA=
|
||||
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
@@ -77,8 +81,12 @@ github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs=
|
||||
github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
|
||||
@@ -19,6 +19,7 @@ const (
|
||||
AdminModuleCodeAdmin AdminModuleCode = "admin" // 系统用户
|
||||
AdminModuleCodeUser AdminModuleCode = "user" // 平台用户
|
||||
AdminModuleCodeFinance AdminModuleCode = "finance" // 财务
|
||||
AdminModuleCodePlan AdminModuleCode = "plan" // 套餐
|
||||
AdminModuleCodeLog AdminModuleCode = "log" // 日志
|
||||
AdminModuleCodeSetting AdminModuleCode = "setting" // 设置
|
||||
AdminModuleCodeCommon AdminModuleCode = "common" // 只要登录就可以访问的模块
|
||||
@@ -203,6 +204,11 @@ func AllModuleMaps() []maps.Map {
|
||||
"code": AdminModuleCodeFinance,
|
||||
"url": "/finance",
|
||||
},
|
||||
{
|
||||
"name": "套餐管理",
|
||||
"code": AdminModuleCodePlan,
|
||||
"url": "/plans",
|
||||
},
|
||||
{
|
||||
"name": "日志审计",
|
||||
"code": AdminModuleCodeLog,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
Version = "0.3.2"
|
||||
Version = "0.3.3"
|
||||
|
||||
APINodeVersion = "0.3.2"
|
||||
APINodeVersion = "0.3.3"
|
||||
|
||||
ProductName = "Edge Admin"
|
||||
ProcessName = "edge-admin"
|
||||
|
||||
@@ -464,6 +464,14 @@ func (this *RPCClient) ServerStatBoardChartRPC() pb.ServerStatBoardChartServiceC
|
||||
return pb.NewServerStatBoardChartServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) PlanRPC() pb.PlanServiceClient {
|
||||
return pb.NewPlanServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserPlanRPC() pb.UserPlanServiceClient {
|
||||
return pb.NewUserPlanServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
// Context 构造Admin上下文
|
||||
func (this *RPCClient) Context(adminId int64) context.Context {
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
@@ -16,13 +17,19 @@ func LookupCNAME(host string) (string, error) {
|
||||
|
||||
m.SetQuestion(host+".", dns.TypeCNAME)
|
||||
m.RecursionDesired = true
|
||||
r, _, err := c.Exchange(m, config.Servers[0]+":"+config.Port)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(r.Answer) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return r.Answer[0].(*dns.CNAME).Target, nil
|
||||
var lastErr error
|
||||
for _, serverAddr := range config.Servers {
|
||||
r, _, err := c.Exchange(m, configutils.QuoteIP(serverAddr)+":"+config.Port)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
if len(r.Answer) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
return r.Answer[0].(*dns.CNAME).Target, nil
|
||||
}
|
||||
return "", lastErr
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ type ParentAction struct {
|
||||
actions.ActionObject
|
||||
|
||||
rpcClient *rpc.RPCClient
|
||||
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// Parent 可以调用自身的一个简便方法
|
||||
@@ -117,6 +119,10 @@ func (this *ParentAction) RPC() *rpc.RPCClient {
|
||||
|
||||
// AdminContext 获取Context
|
||||
func (this *ParentAction) AdminContext() context.Context {
|
||||
if this.ctx != nil {
|
||||
return this.ctx
|
||||
}
|
||||
|
||||
if this.rpcClient == nil {
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
@@ -125,7 +131,8 @@ func (this *ParentAction) AdminContext() context.Context {
|
||||
}
|
||||
this.rpcClient = rpcClient
|
||||
}
|
||||
return this.rpcClient.Context(this.AdminId())
|
||||
this.ctx = this.rpcClient.Context(this.AdminId())
|
||||
return this.ctx
|
||||
}
|
||||
|
||||
// ViewData 视图里可以使用的数据
|
||||
|
||||
@@ -19,6 +19,7 @@ func init() {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeNode)).
|
||||
Helper(clusters.NewClusterHelper()).
|
||||
Data("teaMenu", "clusters").
|
||||
Prefix("/clusters/cluster").
|
||||
Get("", new(IndexAction)).
|
||||
Get("/nodes", new(NodesAction)).
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/grants/grantutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
@@ -21,6 +22,7 @@ func (this *IndexAction) Init() {
|
||||
func (this *IndexAction) RunGet(params struct {
|
||||
ClusterId int64
|
||||
}) {
|
||||
// 基本信息
|
||||
clusterResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeCluster(this.AdminContext(), &pb.FindEnabledNodeClusterRequest{NodeClusterId: params.ClusterId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
@@ -53,10 +55,20 @@ func (this *IndexAction) RunGet(params struct {
|
||||
}
|
||||
this.Data["grant"] = grantMap
|
||||
|
||||
// 时区
|
||||
this.Data["timeZoneGroups"] = nodeconfigs.FindAllTimeZoneGroups()
|
||||
this.Data["timeZoneLocations"] = nodeconfigs.FindAllTimeZoneLocations()
|
||||
|
||||
if len(cluster.TimeZone) == 0 {
|
||||
cluster.TimeZone = nodeconfigs.DefaultTimeZoneLocation
|
||||
}
|
||||
this.Data["timeZoneLocation"] = nodeconfigs.FindTimeZoneLocation(cluster.TimeZone)
|
||||
|
||||
this.Data["cluster"] = maps.Map{
|
||||
"id": cluster.Id,
|
||||
"name": cluster.Name,
|
||||
"installDir": cluster.InstallDir,
|
||||
"timeZone": cluster.TimeZone,
|
||||
}
|
||||
|
||||
this.Show()
|
||||
@@ -68,6 +80,7 @@ func (this *IndexAction) RunPost(params struct {
|
||||
Name string
|
||||
GrantId int64
|
||||
InstallDir string
|
||||
TimeZone string
|
||||
|
||||
Must *actions.Must
|
||||
}) {
|
||||
@@ -83,6 +96,7 @@ func (this *IndexAction) RunPost(params struct {
|
||||
Name: params.Name,
|
||||
NodeGrantId: params.GrantId,
|
||||
InstallDir: params.InstallDir,
|
||||
TimeZone: params.TimeZone,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
|
||||
@@ -12,7 +12,8 @@ func NewClustersHelper() *ClustersHelper {
|
||||
return &ClustersHelper{}
|
||||
}
|
||||
|
||||
func (this *ClustersHelper) BeforeAction(action *actions.ActionObject) {
|
||||
func (this *ClustersHelper) BeforeAction(actionPtr actions.ActionWrapper) {
|
||||
var action = actionPtr.Object()
|
||||
if action.Request.Method != http.MethodGet {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ func init() {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeNode)).
|
||||
Helper(clusterutils.NewClustersHelper()).
|
||||
Data("teaMenu", "clusters").
|
||||
Data("teaSubMenu", "grant").
|
||||
Prefix("/clusters/grants").
|
||||
|
||||
|
||||
@@ -121,6 +121,10 @@ func (this *IndexAction) RunGet(params struct {
|
||||
this.ErrorPage(err)
|
||||
}
|
||||
|
||||
if cluster.TimeZone == nodeconfigs.DefaultTimeZoneLocation {
|
||||
cluster.TimeZone = ""
|
||||
}
|
||||
|
||||
clusterMaps = append(clusterMaps, maps.Map{
|
||||
"id": cluster.Id,
|
||||
"name": cluster.Name,
|
||||
@@ -132,6 +136,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
"dnsName": cluster.DnsName,
|
||||
"dnsDomainName": dnsDomainName,
|
||||
"countServers": countServersResp.Count,
|
||||
"timeZone": cluster.TimeZone,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ func init() {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeNode)).
|
||||
Helper(clusterutils.NewClustersHelper()).
|
||||
Data("teaMenu", "clusters").
|
||||
Prefix("/clusters").
|
||||
Get("", new(IndexAction)).
|
||||
GetPost("/create", new(CreateAction)).
|
||||
|
||||
@@ -2,6 +2,7 @@ package logs
|
||||
|
||||
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"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
@@ -12,6 +13,11 @@ type IndexAction struct {
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
if this.ParamString("type") == "unread" {
|
||||
this.FirstMenu("unread")
|
||||
} else {
|
||||
this.FirstMenu("index")
|
||||
}
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct {
|
||||
@@ -19,19 +25,34 @@ func (this *IndexAction) RunGet(params struct {
|
||||
DayTo string
|
||||
Keyword string
|
||||
Level string
|
||||
Type string
|
||||
}) {
|
||||
this.Data["dayFrom"] = params.DayFrom
|
||||
this.Data["dayTo"] = params.DayTo
|
||||
this.Data["keyword"] = params.Keyword
|
||||
this.Data["level"] = params.Level
|
||||
this.Data["type"] = params.Type
|
||||
|
||||
// 未读数量
|
||||
countUnreadResp, err := this.RPC().NodeLogRPC().CountNodeLogs(this.AdminContext(), &pb.CountNodeLogsRequest{
|
||||
Role: nodeconfigs.NodeRoleNode,
|
||||
IsUnread: true,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Data["countUnreadLogs"] = countUnreadResp.Count
|
||||
|
||||
// 日志数量
|
||||
countResp, err := this.RPC().NodeLogRPC().CountNodeLogs(this.AdminContext(), &pb.CountNodeLogsRequest{
|
||||
NodeId: 0,
|
||||
Role: "node",
|
||||
DayFrom: params.DayFrom,
|
||||
DayTo: params.DayTo,
|
||||
Keyword: params.Keyword,
|
||||
Level: params.Level,
|
||||
NodeId: 0,
|
||||
Role: nodeconfigs.NodeRoleNode,
|
||||
DayFrom: params.DayFrom,
|
||||
DayTo: params.DayTo,
|
||||
Keyword: params.Keyword,
|
||||
Level: params.Level,
|
||||
IsUnread: params.Type == "unread",
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
@@ -42,14 +63,15 @@ func (this *IndexAction) RunGet(params struct {
|
||||
this.Data["page"] = page.AsHTML()
|
||||
|
||||
logsResp, err := this.RPC().NodeLogRPC().ListNodeLogs(this.AdminContext(), &pb.ListNodeLogsRequest{
|
||||
NodeId: 0,
|
||||
Role: "node",
|
||||
DayFrom: params.DayFrom,
|
||||
DayTo: params.DayTo,
|
||||
Keyword: params.Keyword,
|
||||
Level: params.Level,
|
||||
Offset: page.Offset,
|
||||
Size: page.Size,
|
||||
NodeId: 0,
|
||||
Role: nodeconfigs.NodeRoleNode,
|
||||
DayFrom: params.DayFrom,
|
||||
DayTo: params.DayTo,
|
||||
Keyword: params.Keyword,
|
||||
Level: params.Level,
|
||||
IsUnread: params.Type == "unread",
|
||||
Offset: page.Offset,
|
||||
Size: page.Size,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
@@ -69,12 +91,14 @@ func (this *IndexAction) RunGet(params struct {
|
||||
}
|
||||
|
||||
logs = append(logs, maps.Map{
|
||||
"id": log.Id,
|
||||
"tag": log.Tag,
|
||||
"description": log.Description,
|
||||
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", log.CreatedAt),
|
||||
"level": log.Level,
|
||||
"isToday": timeutil.FormatTime("Y-m-d", log.CreatedAt) == timeutil.Format("Y-m-d"),
|
||||
"count": log.Count,
|
||||
"isRead": log.IsRead,
|
||||
"node": maps.Map{
|
||||
"id": node.Id,
|
||||
"cluster": maps.Map{
|
||||
|
||||
@@ -14,6 +14,7 @@ func init() {
|
||||
Data("teaSubMenu", "log").
|
||||
Prefix("/clusters/logs").
|
||||
Get("", new(IndexAction)).
|
||||
Post("/readLogs", new(ReadLogsAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
|
||||
26
internal/web/actions/default/clusters/logs/readLogs.go
Normal file
26
internal/web/actions/default/clusters/logs/readLogs.go
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
)
|
||||
|
||||
type ReadLogsAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *ReadLogsAction) RunPost(params struct {
|
||||
LogIds []int64
|
||||
}) {
|
||||
_, err := this.RPC().NodeLogRPC().UpdateNodeLogsRead(this.AdminContext(), &pb.UpdateNodeLogsReadRequest{
|
||||
NodeLogIds: params.LogIds,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -21,6 +21,7 @@ func (this *SelectPopupAction) Init() {
|
||||
func (this *SelectPopupAction) RunGet(params struct {
|
||||
SelectedClusterIds string
|
||||
Keyword string
|
||||
PageSize int64
|
||||
}) {
|
||||
this.Data["keyword"] = params.Keyword
|
||||
|
||||
@@ -36,6 +37,9 @@ func (this *SelectPopupAction) RunGet(params struct {
|
||||
var count = countResp.Count
|
||||
var page = this.NewPage(count)
|
||||
page.Size = 6
|
||||
if params.PageSize > 0 {
|
||||
page.Size = params.PageSize
|
||||
}
|
||||
this.Data["page"] = page.AsHTML()
|
||||
|
||||
clustersResp, err := this.RPC().NodeClusterRPC().ListEnabledNodeClusters(this.AdminContext(), &pb.ListEnabledNodeClustersRequest{
|
||||
|
||||
@@ -77,7 +77,7 @@ func ValidateRecordName(name string) bool {
|
||||
strings.HasSuffix(piece, "-") ||
|
||||
strings.Contains(piece, "--") ||
|
||||
len(piece) > 63 ||
|
||||
!regexp.MustCompile(`^[a-z0-9-]+$`).MatchString(piece) {
|
||||
!regexp.MustCompile(`^[_a-z0-9-]+$`).MatchString(piece) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
package bills
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
"time"
|
||||
)
|
||||
|
||||
type GenerateAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *GenerateAction) Init() {
|
||||
this.Nav("", "", "generate")
|
||||
}
|
||||
|
||||
func (this *GenerateAction) RunGet(params struct{}) {
|
||||
this.Data["month"] = timeutil.Format("Ym", time.Now().AddDate(0, -1, 0))
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *GenerateAction) RunPost(params struct {
|
||||
Month string
|
||||
|
||||
Must *actions.Must
|
||||
}) {
|
||||
defer this.CreateLogInfo("手动生成上个月(" + params.Month + ")账单")
|
||||
|
||||
_, err := this.RPC().UserBillRPC().GenerateAllUserBills(this.AdminContext(), &pb.GenerateAllUserBillsRequest{Month: params.Month})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Success()
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package bills
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "", "index")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct {
|
||||
PaidFlag int32 `default:"-1"`
|
||||
UserId int64
|
||||
Month string
|
||||
}) {
|
||||
countResp, err := this.RPC().UserBillRPC().CountAllUserBills(this.AdminContext(), &pb.CountAllUserBillsRequest{})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
page := this.NewPage(countResp.Count)
|
||||
this.Data["page"] = page.AsHTML()
|
||||
|
||||
billsResp, err := this.RPC().UserBillRPC().ListUserBills(this.AdminContext(), &pb.ListUserBillsRequest{
|
||||
PaidFlag: params.PaidFlag,
|
||||
UserId: params.UserId,
|
||||
Month: params.Month,
|
||||
Offset: page.Offset,
|
||||
Size: page.Size,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
billMaps := []maps.Map{}
|
||||
for _, bill := range billsResp.UserBills {
|
||||
var userMap maps.Map = nil
|
||||
if bill.User != nil {
|
||||
userMap = maps.Map{
|
||||
"id": bill.User.Id,
|
||||
"fullname": bill.User.Fullname,
|
||||
}
|
||||
}
|
||||
billMaps = append(billMaps, maps.Map{
|
||||
"id": bill.Id,
|
||||
"isPaid": bill.IsPaid,
|
||||
"month": bill.Month,
|
||||
"amount": fmt.Sprintf("%.2f", bill.Amount),
|
||||
"typeName": bill.TypeName,
|
||||
"user": userMap,
|
||||
"description": bill.Description,
|
||||
})
|
||||
}
|
||||
this.Data["bills"] = billMaps
|
||||
|
||||
this.Show()
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package bills
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeFinance)).
|
||||
Data("teaMenu", "finance").
|
||||
|
||||
// 财务管理
|
||||
Prefix("/finance/bills").
|
||||
Get("", new(IndexAction)).
|
||||
GetPost("/generate", new(GenerateAction)).
|
||||
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package finance
|
||||
|
||||
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct{}) {
|
||||
// TODO 暂时先跳转到账单页,将来做成Dashboard
|
||||
this.RedirectURL("/finance/bills")
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package finance
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeFinance)).
|
||||
|
||||
// 财务管理
|
||||
Prefix("/finance").
|
||||
Get("", new(IndexAction)).
|
||||
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeNS)).
|
||||
Data("teaMenu", "ns").
|
||||
Data("teaSubMenu", "accessLog").
|
||||
Prefix("/ns/clusters/accessLogs").
|
||||
Get("", new(IndexAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/ns/clusters/cluster/node"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/ns/clusters/clusterutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeNS)).
|
||||
Helper(new(clusterutils.ClusterHelper)).
|
||||
Data("teaMenu", "ns").
|
||||
Data("teaSubMenu", "cluster").
|
||||
Prefix("/ns/clusters/cluster").
|
||||
Get("", new(IndexAction)).
|
||||
GetPost("/delete", new(DeleteAction)).
|
||||
GetPost("/createNode", new(CreateNodeAction)).
|
||||
Post("/deleteNode", new(DeleteNodeAction)).
|
||||
Get("/upgradeRemote", new(UpgradeRemoteAction)).
|
||||
GetPost("/updateNodeSSH", new(UpdateNodeSSHAction)).
|
||||
|
||||
// 节点相关
|
||||
Prefix("/ns/clusters/cluster/node").
|
||||
Get("", new(node.IndexAction)).
|
||||
Get("/logs", new(node.LogsAction)).
|
||||
GetPost("/update", new(node.UpdateAction)).
|
||||
GetPost("/install", new(node.InstallAction)).
|
||||
Post("/status", new(node.StatusAction)).
|
||||
Post("/updateInstallStatus", new(node.UpdateInstallStatusAction)).
|
||||
Post("/start", new(node.StartAction)).
|
||||
Post("/stop", new(node.StopAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package accessLog
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/ns/clusters/clusterutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeNS)).
|
||||
Helper(new(clusterutils.ClusterHelper)).
|
||||
Data("teaMenu", "ns").
|
||||
Data("teaSubMenu", "cluster").
|
||||
Prefix("/ns/clusters/cluster/settings/accessLog").
|
||||
GetPost("", new(IndexAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/ns/clusters/clusterutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeNS)).
|
||||
Helper(new(clusterutils.ClusterHelper)).
|
||||
Data("teaMenu", "ns").
|
||||
Data("teaSubMenu", "cluster").
|
||||
Prefix("/ns/clusters/cluster/settings").
|
||||
GetPost("", new(IndexAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package recursion
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/ns/clusters/clusterutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeNS)).
|
||||
Helper(new(clusterutils.ClusterHelper)).
|
||||
Data("teaMenu", "ns").
|
||||
Data("teaSubMenu", "cluster").
|
||||
Prefix("/ns/clusters/cluster/settings/recursion").
|
||||
GetPost("", new(IndexAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package clusters
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeNS)).
|
||||
Data("teaMenu", "ns").
|
||||
Data("teaSubMenu", "cluster").
|
||||
Prefix("/ns/clusters").
|
||||
Get("", new(IndexAction)).
|
||||
GetPost("/create", new(CreateAction)).
|
||||
Post("/options", new(OptionsAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeNS)).
|
||||
Data("teaMenu", "ns").
|
||||
Data("teaSubMenu", "log").
|
||||
Prefix("/ns/clusters/logs").
|
||||
Get("", new(IndexAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package clusters
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeNS)).
|
||||
Data("teaMenu", "ns").
|
||||
Data("teaSubMenu", "route").
|
||||
Prefix("/ns/routes").
|
||||
Get("", new(IndexAction)).
|
||||
Get("/route", new(RouteAction)).
|
||||
GetPost("/createPopup", new(CreatePopupAction)).
|
||||
GetPost("/updatePopup", new(UpdatePopupAction)).
|
||||
Post("/delete", new(DeleteAction)).
|
||||
Post("/sort", new(SortAction)).
|
||||
Post("/options", new(OptionsAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package accesslogs
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/ns/settings/settingutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeNS)).
|
||||
Helper(new(settingutils.Helper)).
|
||||
Data("teaMenu", "ns").
|
||||
Data("teaSubMenu", "setting").
|
||||
Prefix("/ns/settings/accesslogs").
|
||||
GetPost("", new(IndexAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeNS)).
|
||||
Data("teaMenu", "ns").
|
||||
Data("teaSubMenu", "test").
|
||||
Prefix("/ns/test").
|
||||
GetPost("", new(IndexAction)).
|
||||
Post("/nodeOptions", new(NodeOptionsAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package users
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeNS)).
|
||||
Data("teaMenu", "ns").
|
||||
Data("teaSubMenu", "domain").
|
||||
Prefix("/ns/users").
|
||||
Post("/options", new(OptionsAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package ipbox
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeServer)).
|
||||
Prefix("/servers/accesslogs").
|
||||
Data("teaMenu", "servers").
|
||||
Data("teaSubMenu", "accesslog").
|
||||
Get("", new(IndexAction)).
|
||||
GetPost("/createPopup", new(CreatePopupAction)).
|
||||
Get("/policy", new(PolicyAction)).
|
||||
GetPost("/test", new(TestAction)).
|
||||
GetPost("/update", new(UpdateAction)).
|
||||
Post("/delete", new(DeleteAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -19,6 +19,7 @@ func init() {
|
||||
GetPost("/createPopup", new(CreatePopupAction)).
|
||||
Post("/delete", new(DeleteAction)).
|
||||
Get("/policy", new(PolicyAction)).
|
||||
Post("/upgradeTemplate", new(UpgradeTemplateAction)).
|
||||
Get("/groups", new(GroupsAction)).
|
||||
Get("/group", new(GroupAction)).
|
||||
Get("/log", new(LogAction)).
|
||||
|
||||
@@ -47,6 +47,33 @@ func (this *PolicyAction) RunGet(params struct {
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否有升级
|
||||
var templatePolicy = firewallconfigs.HTTPFirewallTemplate()
|
||||
var upgradeItems = []string{}
|
||||
if templatePolicy.Inbound != nil {
|
||||
for _, group := range templatePolicy.Inbound.Groups {
|
||||
if len(group.Code) == 0 {
|
||||
continue
|
||||
}
|
||||
var oldGroup = firewallPolicy.FindRuleGroupWithCode(group.Code)
|
||||
if oldGroup == nil {
|
||||
upgradeItems = append(upgradeItems, group.Name)
|
||||
continue
|
||||
}
|
||||
for _, set := range group.Sets {
|
||||
if len(set.Code) == 0 {
|
||||
continue
|
||||
}
|
||||
var oldSet = oldGroup.FindRuleSetWithCode(set.Code)
|
||||
if oldSet == nil {
|
||||
upgradeItems = append(upgradeItems, group.Name+" -- "+set.Name)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.Data["upgradeItems"] = upgradeItems
|
||||
|
||||
// 模式
|
||||
if len(firewallPolicy.Mode) == 0 {
|
||||
firewallPolicy.Mode = firewallconfigs.FirewallModeDefend
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package waf
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
)
|
||||
|
||||
type UpgradeTemplateAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *UpgradeTemplateAction) RunPost(params struct {
|
||||
PolicyId int64
|
||||
}) {
|
||||
defer this.CreateLogInfo("升级WAF %d 内置规则", params.PolicyId)
|
||||
|
||||
policy, err := dao.SharedHTTPFirewallPolicyDAO.FindEnabledHTTPFirewallPolicyConfig(this.AdminContext(), params.PolicyId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if policy == nil {
|
||||
this.NotFound("firewallPolicy", params.PolicyId)
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否有升级
|
||||
var templatePolicy = firewallconfigs.HTTPFirewallTemplate()
|
||||
if templatePolicy.Inbound != nil {
|
||||
for _, group := range templatePolicy.Inbound.Groups {
|
||||
if len(group.Code) == 0 {
|
||||
continue
|
||||
}
|
||||
var oldGroup = policy.FindRuleGroupWithCode(group.Code)
|
||||
if oldGroup == nil {
|
||||
createGroupResp, err := this.RPC().HTTPFirewallRuleGroupRPC().CreateHTTPFirewallRuleGroup(this.AdminContext(), &pb.CreateHTTPFirewallRuleGroupRequest{
|
||||
IsOn: true,
|
||||
Name: group.Name,
|
||||
Code: group.Code,
|
||||
Description: group.Description,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var groupId = createGroupResp.FirewallRuleGroupId
|
||||
policy.Inbound.GroupRefs = append(policy.Inbound.GroupRefs, &firewallconfigs.HTTPFirewallRuleGroupRef{
|
||||
IsOn: true,
|
||||
GroupId: groupId,
|
||||
})
|
||||
|
||||
for _, set := range group.Sets {
|
||||
setJSON, err := json.Marshal(set)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
_, err = this.RPC().HTTPFirewallRuleGroupRPC().AddHTTPFirewallRuleGroupSet(this.AdminContext(), &pb.AddHTTPFirewallRuleGroupSetRequest{
|
||||
FirewallRuleGroupId: groupId,
|
||||
FirewallRuleSetConfigJSON: setJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
for _, set := range group.Sets {
|
||||
if len(set.Code) == 0 {
|
||||
continue
|
||||
}
|
||||
var oldSet = oldGroup.FindRuleSetWithCode(set.Code)
|
||||
if oldSet == nil {
|
||||
setJSON, err := json.Marshal(set)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
_, err = this.RPC().HTTPFirewallRuleGroupRPC().AddHTTPFirewallRuleGroupSet(this.AdminContext(), &pb.AddHTTPFirewallRuleGroupSetRequest{
|
||||
FirewallRuleGroupId: oldGroup.Id,
|
||||
FirewallRuleSetConfigJSON: setJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 保存inbound
|
||||
inboundJSON, err := policy.InboundJSON()
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
outboundJSON, err := policy.OutboundJSON()
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = this.RPC().HTTPFirewallPolicyRPC().UpdateHTTPFirewallPolicyGroups(this.AdminContext(), &pb.UpdateHTTPFirewallPolicyGroupsRequest{
|
||||
HttpFirewallPolicyId: params.PolicyId,
|
||||
InboundJSON: inboundJSON,
|
||||
OutboundJSON: outboundJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
@@ -71,8 +72,11 @@ func (this *CreateAction) RunPost(params struct {
|
||||
CertIdsJSON []byte
|
||||
Origins string
|
||||
|
||||
AccessLogIsOn bool
|
||||
WebsocketIsOn bool
|
||||
AccessLogIsOn bool
|
||||
WebsocketIsOn bool
|
||||
CacheIsOn bool
|
||||
WafIsOn bool
|
||||
RemoteAddrIsOn bool
|
||||
|
||||
WebRoot string
|
||||
|
||||
@@ -483,6 +487,75 @@ func (this *CreateAction) RunPost(params struct {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// cache
|
||||
if params.CacheIsOn {
|
||||
var cacheConfig = &serverconfigs.HTTPCacheConfig{
|
||||
IsPrior: false,
|
||||
IsOn: true,
|
||||
AddStatusHeader: true,
|
||||
PurgeIsOn: false,
|
||||
PurgeKey: "",
|
||||
CacheRefs: nil,
|
||||
}
|
||||
cacheConfigJSON, err := json.Marshal(cacheConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
_, err = this.RPC().HTTPWebRPC().UpdateHTTPWebCache(this.AdminContext(), &pb.UpdateHTTPWebCacheRequest{
|
||||
WebId: webConfig.Id,
|
||||
CacheJSON: cacheConfigJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// waf
|
||||
if params.WafIsOn {
|
||||
var firewallRef = &firewallconfigs.HTTPFirewallRef{
|
||||
IsPrior: false,
|
||||
IsOn: true,
|
||||
FirewallPolicyId: 0,
|
||||
}
|
||||
firewallRefJSON, err := json.Marshal(firewallRef)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
_, err = this.RPC().HTTPWebRPC().UpdateHTTPWebFirewall(this.AdminContext(), &pb.UpdateHTTPWebFirewallRequest{
|
||||
WebId: webConfig.Id,
|
||||
FirewallJSON: firewallRefJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// remoteAddr
|
||||
var remoteAddrConfig = &serverconfigs.HTTPRemoteAddrConfig{
|
||||
IsOn: true,
|
||||
Value: "${rawRemoteAddr}",
|
||||
}
|
||||
if params.RemoteAddrIsOn {
|
||||
remoteAddrConfig.Value = "${remoteAddr}"
|
||||
}
|
||||
remoteAddrConfigJSON, err := json.Marshal(remoteAddrConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
_, err = this.RPC().HTTPWebRPC().UpdateHTTPWebRemoteAddr(this.AdminContext(), &pb.UpdateHTTPWebRemoteAddrRequest{
|
||||
WebId: webConfig.Id,
|
||||
RemoteAddrJSON: remoteAddrConfigJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -87,15 +87,26 @@ func (this *SettingAction) RunPost(params struct {
|
||||
return
|
||||
}
|
||||
|
||||
// PROXY Protocol
|
||||
var proxyProtocolJSON = []byte{}
|
||||
if reverseProxyConfig.ProxyProtocol != nil {
|
||||
proxyProtocolJSON, err = json.Marshal(reverseProxyConfig.ProxyProtocol)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 设置反向代理相关信息
|
||||
_, 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,
|
||||
ReverseProxyId: reverseProxyConfig.Id,
|
||||
RequestHostType: types.Int32(reverseProxyConfig.RequestHostType),
|
||||
RequestHost: reverseProxyConfig.RequestHost,
|
||||
RequestURI: reverseProxyConfig.RequestURI,
|
||||
StripPrefix: reverseProxyConfig.StripPrefix,
|
||||
AutoFlush: reverseProxyConfig.AutoFlush,
|
||||
AddHeaders: reverseProxyConfig.AddHeaders,
|
||||
ProxyProtocolJSON: proxyProtocolJSON,
|
||||
})
|
||||
|
||||
this.Success()
|
||||
|
||||
@@ -87,15 +87,26 @@ func (this *SettingAction) RunPost(params struct {
|
||||
return
|
||||
}
|
||||
|
||||
// PROXY Protocol
|
||||
var proxyProtocolJSON = []byte{}
|
||||
if reverseProxyConfig.ProxyProtocol != nil {
|
||||
proxyProtocolJSON, err = json.Marshal(reverseProxyConfig.ProxyProtocol)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 设置反向代理相关信息
|
||||
_, 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,
|
||||
ReverseProxyId: reverseProxyConfig.Id,
|
||||
RequestHostType: types.Int32(reverseProxyConfig.RequestHostType),
|
||||
RequestHost: reverseProxyConfig.RequestHost,
|
||||
RequestURI: reverseProxyConfig.RequestURI,
|
||||
StripPrefix: reverseProxyConfig.StripPrefix,
|
||||
AutoFlush: reverseProxyConfig.AutoFlush,
|
||||
AddHeaders: reverseProxyConfig.AddHeaders,
|
||||
ProxyProtocolJSON: proxyProtocolJSON,
|
||||
})
|
||||
|
||||
this.Success()
|
||||
|
||||
@@ -19,8 +19,11 @@ func (this *ItemsAction) Init() {
|
||||
}
|
||||
|
||||
func (this *ItemsAction) RunGet(params struct {
|
||||
ListId int64
|
||||
ListId int64
|
||||
Keyword string
|
||||
}) {
|
||||
this.Data["keyword"] = params.Keyword
|
||||
|
||||
err := InitIPList(this.Parent(), params.ListId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
@@ -29,7 +32,10 @@ func (this *ItemsAction) RunGet(params struct {
|
||||
|
||||
// 数量
|
||||
var listId = params.ListId
|
||||
countResp, err := this.RPC().IPItemRPC().CountIPItemsWithListId(this.AdminContext(), &pb.CountIPItemsWithListIdRequest{IpListId: listId})
|
||||
countResp, err := this.RPC().IPItemRPC().CountIPItemsWithListId(this.AdminContext(), &pb.CountIPItemsWithListIdRequest{
|
||||
IpListId: listId,
|
||||
Keyword: params.Keyword,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
@@ -41,6 +47,7 @@ func (this *ItemsAction) RunGet(params struct {
|
||||
// 列表
|
||||
itemsResp, err := this.RPC().IPItemRPC().ListIPItemsWithListId(this.AdminContext(), &pb.ListIPItemsWithListIdRequest{
|
||||
IpListId: listId,
|
||||
Keyword: params.Keyword,
|
||||
Offset: page.Offset,
|
||||
Size: page.Size,
|
||||
})
|
||||
|
||||
@@ -3,6 +3,7 @@ package dns
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
@@ -28,6 +29,28 @@ func (this *IndexAction) RunGet(params struct {
|
||||
} else {
|
||||
this.Data["dnsDomain"] = ""
|
||||
}
|
||||
this.Data["supportCNAME"] = dnsInfoResp.SupportCNAME
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunPost(params struct {
|
||||
ServerId int64
|
||||
SupportCNAME bool
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
defer this.CreateLogInfo("修改服务 %d 的DNS设置", params.ServerId)
|
||||
|
||||
_, err := this.RPC().ServerRPC().UpdateServerDNS(this.AdminContext(), &pb.UpdateServerDNSRequest{
|
||||
ServerId: params.ServerId,
|
||||
SupportCNAME: params.SupportCNAME,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ func init() {
|
||||
Helper(serverutils.NewServerHelper()).
|
||||
Prefix("/servers/server/settings/dns").
|
||||
GetPost("", new(IndexAction)).
|
||||
Post("/regenerateCNAME", new(RegenerateCNAMEAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
)
|
||||
|
||||
type RegenerateCNAMEAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *RegenerateCNAMEAction) RunPost(params struct {
|
||||
ServerId int64
|
||||
}) {
|
||||
defer this.CreateLogInfo("重新生成服务 %d 的CNAME", params.ServerId)
|
||||
|
||||
_, err := this.RPC().ServerRPC().RegenerateServerCNAME(this.AdminContext(), &pb.RegenerateServerCNAMERequest{ServerId: params.ServerId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -78,15 +78,26 @@ func (this *SettingAction) RunPost(params struct {
|
||||
return
|
||||
}
|
||||
|
||||
// PROXY Protocol
|
||||
var proxyProtocolJSON = []byte{}
|
||||
if reverseProxyConfig.ProxyProtocol != nil {
|
||||
proxyProtocolJSON, err = json.Marshal(reverseProxyConfig.ProxyProtocol)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 设置反向代理相关信息
|
||||
_, 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,
|
||||
ReverseProxyId: reverseProxyConfig.Id,
|
||||
RequestHostType: types.Int32(reverseProxyConfig.RequestHostType),
|
||||
RequestHost: reverseProxyConfig.RequestHost,
|
||||
RequestURI: reverseProxyConfig.RequestURI,
|
||||
StripPrefix: reverseProxyConfig.StripPrefix,
|
||||
AutoFlush: reverseProxyConfig.AutoFlush,
|
||||
AddHeaders: reverseProxyConfig.AddHeaders,
|
||||
ProxyProtocolJSON: proxyProtocolJSON,
|
||||
})
|
||||
|
||||
this.Success()
|
||||
|
||||
@@ -106,20 +106,31 @@ func (this *SettingAction) RunPost(params struct {
|
||||
return
|
||||
}
|
||||
|
||||
// PROXY Protocol
|
||||
var proxyProtocolJSON = []byte{}
|
||||
if reverseProxyConfig.ProxyProtocol != nil {
|
||||
proxyProtocolJSON, err = json.Marshal(reverseProxyConfig.ProxyProtocol)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 设置反向代理相关信息
|
||||
_, 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),
|
||||
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,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
|
||||
@@ -348,6 +348,15 @@ func (this *ServerHelper) createSettingsMenu(secondMenuItem string, serverIdStri
|
||||
"isActive": secondMenuItem == "remoteAddr",
|
||||
"isOn": serverConfig.Web != nil && serverConfig.Web.RemoteAddr != nil && serverConfig.Web.RemoteAddr.IsOn,
|
||||
})
|
||||
|
||||
if teaconst.IsPlus {
|
||||
menuItems = append(menuItems, maps.Map{
|
||||
"name": "带宽限制",
|
||||
"url": "/servers/server/settings/bandwidth?serverId=" + serverIdString,
|
||||
"isActive": secondMenuItem == "bandwidth",
|
||||
"isOn": serverConfig.BandwidthLimit != nil && serverConfig.BandwidthLimit.IsOn,
|
||||
})
|
||||
}
|
||||
} else if serverConfig.IsTCPFamily() {
|
||||
menuItems = append(menuItems, maps.Map{
|
||||
"name": "TCP",
|
||||
|
||||
@@ -2,6 +2,7 @@ package settingutils
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
)
|
||||
@@ -31,7 +32,9 @@ func (this *Helper) BeforeAction(actionPtr actions.ActionWrapper) (goNext bool)
|
||||
if configloaders.AllowModule(adminId, configloaders.AdminModuleCodeSetting) {
|
||||
tabbar.Add("Web服务", "", "/settings/server", "", this.tab == "server")
|
||||
tabbar.Add("管理界面设置", "", "/settings/ui", "", this.tab == "ui")
|
||||
tabbar.Add("用户界面设置", "", "/settings/user-ui", "", this.tab == "userUI")
|
||||
if teaconst.IsPlus {
|
||||
tabbar.Add("用户界面设置", "", "/settings/user-ui", "", this.tab == "userUI")
|
||||
}
|
||||
tabbar.Add("安全设置", "", "/settings/security", "", this.tab == "security")
|
||||
tabbar.Add("IP库", "", "/settings/ip-library", "", this.tab == "ipLibrary")
|
||||
}
|
||||
|
||||
@@ -4,7 +4,11 @@ import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/setup"
|
||||
"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/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -130,7 +134,7 @@ func (this *userMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramNam
|
||||
if !action.Data.Has("teaMenu") {
|
||||
action.Data["teaMenu"] = ""
|
||||
}
|
||||
action.Data["teaModules"] = this.modules(adminId)
|
||||
action.Data["teaModules"] = this.modules(actionPtr, adminId)
|
||||
action.Data["teaSubMenus"] = []map[string]interface{}{}
|
||||
action.Data["teaTabbar"] = []map[string]interface{}{}
|
||||
if len(config.Version) == 0 {
|
||||
@@ -165,7 +169,36 @@ func (this *userMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramNam
|
||||
}
|
||||
|
||||
// 菜单配置
|
||||
func (this *userMustAuth) modules(adminId int64) []maps.Map {
|
||||
func (this *userMustAuth) modules(actionPtr actions.ActionWrapper, adminId int64) []maps.Map {
|
||||
var countUnreadNodeLogs int64 = 0
|
||||
var nodeLogsType = ""
|
||||
|
||||
// 父级动作
|
||||
parentAction, ok := actionPtr.(actionutils.ActionInterface)
|
||||
if ok {
|
||||
var action = actionPtr.Object()
|
||||
|
||||
// 未读日志数
|
||||
if action.Data.GetString("teaMenu") == "clusters" {
|
||||
countNodeLogsResp, err := parentAction.RPC().NodeLogRPC().CountNodeLogs(parentAction.AdminContext(), &pb.CountNodeLogsRequest{
|
||||
Role: nodeconfigs.NodeRoleNode,
|
||||
IsUnread: true,
|
||||
})
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
} else {
|
||||
var countNodeLogs = countNodeLogsResp.Count
|
||||
if countNodeLogs > 0 {
|
||||
countUnreadNodeLogs = countNodeLogs
|
||||
if countUnreadNodeLogs >= 1000 {
|
||||
countUnreadNodeLogs = 999
|
||||
}
|
||||
nodeLogsType = "unread"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
allMaps := []maps.Map{
|
||||
{
|
||||
"code": "dashboard",
|
||||
@@ -236,9 +269,10 @@ func (this *userMustAuth) modules(adminId int64) []maps.Map {
|
||||
"icon": "cloud",
|
||||
"subItems": []maps.Map{
|
||||
{
|
||||
"name": "运行日志",
|
||||
"url": "/clusters/logs",
|
||||
"code": "log",
|
||||
"name": "运行日志",
|
||||
"url": "/clusters/logs?type=" + nodeLogsType,
|
||||
"code": "log",
|
||||
"badge": countUnreadNodeLogs,
|
||||
},
|
||||
{
|
||||
"name": "IP地址",
|
||||
@@ -340,6 +374,21 @@ func (this *userMustAuth) modules(adminId int64) []maps.Map {
|
||||
"icon": "yen sign",
|
||||
"isOn": teaconst.IsPlus,
|
||||
},
|
||||
/**{
|
||||
"code": "plans",
|
||||
"module": configloaders.AdminModuleCodePlan,
|
||||
"name": "套餐管理",
|
||||
"icon": "puzzle piece",
|
||||
"isOn": teaconst.IsPlus,
|
||||
"subItems": []maps.Map{
|
||||
{
|
||||
"name": "已购套餐",
|
||||
"url": "/plans/userPlans",
|
||||
"code": "userPlans",
|
||||
"isOn": teaconst.IsPlus,
|
||||
},
|
||||
},
|
||||
},**/
|
||||
{
|
||||
"code": "admins",
|
||||
"module": configloaders.AdminModuleCodeAdmin,
|
||||
|
||||
@@ -27,8 +27,6 @@ import (
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/db"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dns"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dns/tasks"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/finance"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/finance/bills"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/index"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/log"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/logout"
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"name": "codemirror",
|
||||
"version": "5.41.0",
|
||||
"main": "lib/codemirror.js",
|
||||
"style": "lib/codemirror.css",
|
||||
"author": {
|
||||
"name": "Marijn Haverbeke",
|
||||
"email": "marijnh@gmail.com",
|
||||
"url": "http://marijnhaverbeke.nl"
|
||||
},
|
||||
"description": "Full-featured in-browser code editor",
|
||||
"license": "MIT",
|
||||
"directories": {
|
||||
"lib": "./lib"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "rollup -c",
|
||||
"watch": "rollup -w -c",
|
||||
"prepare": "npm run-script build",
|
||||
"test": "node ./test/run.js",
|
||||
"lint": "bin/lint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"blint": "^1",
|
||||
"node-static": "0.7.11",
|
||||
"phantomjs-prebuilt": "^2.1.12",
|
||||
"rollup": "^0.66.2",
|
||||
"rollup-plugin-buble": "^0.19.2",
|
||||
"rollup-watch": "^4.3.1"
|
||||
},
|
||||
"bugs": "http://github.com/codemirror/CodeMirror/issues",
|
||||
"keywords": [
|
||||
"JavaScript",
|
||||
"CodeMirror",
|
||||
"Editor"
|
||||
],
|
||||
"homepage": "https://codemirror.net",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/codemirror/CodeMirror.git"
|
||||
},
|
||||
"jspm": {
|
||||
"directories": {},
|
||||
"dependencies": {},
|
||||
"devDependencies": {}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import buble from 'rollup-plugin-buble';
|
||||
|
||||
export default {
|
||||
input: "src/codemirror.js",
|
||||
output: {
|
||||
banner: `// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: https://codemirror.net/LICENSE
|
||||
|
||||
// This is CodeMirror (https://codemirror.net), a code editor
|
||||
// implemented in JavaScript on top of the browser's DOM.
|
||||
//
|
||||
// You can find some technical background for some of the code below
|
||||
// at http://marijnhaverbeke.nl/blog/#cm-internals .
|
||||
`,
|
||||
format: "umd",
|
||||
file: "lib/codemirror.js",
|
||||
name: "CodeMirror"
|
||||
},
|
||||
plugins: [ buble({namedFunctionExpressions: false}) ]
|
||||
};
|
||||
34
web/public/js/components/common/datepicker.js
Normal file
34
web/public/js/components/common/datepicker.js
Normal file
@@ -0,0 +1,34 @@
|
||||
Vue.component("datepicker", {
|
||||
props: ["v-name", "v-value", "v-bottom-left"],
|
||||
mounted: function () {
|
||||
let that = this
|
||||
teaweb.datepicker(this.$refs.dayInput, function (v) {
|
||||
that.day = v
|
||||
that.change()
|
||||
}, !!this.vBottomLeft)
|
||||
},
|
||||
data: function () {
|
||||
let name = this.vName
|
||||
if (name == null) {
|
||||
name = "day"
|
||||
}
|
||||
|
||||
let day = this.vValue
|
||||
if (day == null) {
|
||||
day = ""
|
||||
}
|
||||
|
||||
return {
|
||||
name: name,
|
||||
day: day
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
change: function () {
|
||||
this.$emit("change", this.day)
|
||||
}
|
||||
},
|
||||
template: `<div style="display: inline-block">
|
||||
<input type="text" :name="name" v-model="day" placeholder="YYYY-MM-DD" style="width:8.6em" maxlength="10" @input="change" ref="dayInput" autocomplete="off"/>
|
||||
</div>`
|
||||
})
|
||||
@@ -18,7 +18,9 @@ Vue.component("health-check-config-box", {
|
||||
tryDelay: {count: 100, unit: "ms"},
|
||||
autoDown: true,
|
||||
countUp: 1,
|
||||
countDown: 3
|
||||
countDown: 3,
|
||||
userAgent: "",
|
||||
onlyBasicRequest: false
|
||||
}
|
||||
let that = this
|
||||
setTimeout(function () {
|
||||
@@ -72,7 +74,8 @@ Vue.component("health-check-config-box", {
|
||||
urlProtocol: urlProtocol,
|
||||
urlHost: urlHost,
|
||||
urlPort: urlPort,
|
||||
urlRequestURI: urlRequestURI
|
||||
urlRequestURI: urlRequestURI,
|
||||
urlIsEditing: healthCheckConfig.url.length == 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -142,6 +145,9 @@ Vue.component("health-check-config-box", {
|
||||
return status
|
||||
}
|
||||
})
|
||||
},
|
||||
editURL: function () {
|
||||
this.urlIsEditing = !this.urlIsEditing
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
@@ -162,41 +168,41 @@ Vue.component("health-check-config-box", {
|
||||
<tr>
|
||||
<td>URL *</td>
|
||||
<td>
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td class="title">协议</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" v-model="urlProtocol">
|
||||
<option value="http">http://</option>
|
||||
<option value="https">https://</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>域名</td>
|
||||
<td>
|
||||
<input type="text" v-model="urlHost"/>
|
||||
<p class="comment">在此集群上可以访问到的一个域名。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>端口</td>
|
||||
<td>
|
||||
<input type="text" maxlength="5" style="width:5.4em" placeholder="端口" v-model="urlPort"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>RequestURI</td>
|
||||
<td><input type="text" v-model="urlRequestURI" placeholder="/" style="width:20em"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="ui divider"></div>
|
||||
<p class="comment" v-if="healthCheck.url.length > 0">拼接后的URL:<code-label>{{healthCheck.url}}</code-label>,其中\${host}指的是域名。</p>
|
||||
<div v-if="healthCheck.url.length > 0" style="margin-bottom: 1em"><code-label>{{healthCheck.url}}</code-label> <a href="" @click.prevent="editURL"><span class="small">修改 <i class="icon angle" :class="{down: !urlIsEditing, up: urlIsEditing}"></i></span></a> </div>
|
||||
<div v-show="urlIsEditing">
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td class="title">协议</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" v-model="urlProtocol">
|
||||
<option value="http">http://</option>
|
||||
<option value="https">https://</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>域名</td>
|
||||
<td>
|
||||
<input type="text" v-model="urlHost"/>
|
||||
<p class="comment">在此集群上可以访问到的一个域名。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>端口</td>
|
||||
<td>
|
||||
<input type="text" maxlength="5" style="width:5.4em" placeholder="端口" v-model="urlPort"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>RequestURI</td>
|
||||
<td><input type="text" v-model="urlRequestURI" placeholder="/" style="width:20em"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="ui divider"></div>
|
||||
<p class="comment" v-if="healthCheck.url.length > 0">拼接后的URL:<code-label>{{healthCheck.url}}</code-label>,其中\${host}指的是域名。</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>检测时间间隔</td>
|
||||
<td>
|
||||
@@ -258,6 +264,20 @@ Vue.component("health-check-config-box", {
|
||||
<time-duration-box :v-value="healthCheck.tryDelay"></time-duration-box>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>终端信息<em>(User-Agent)</em></td>
|
||||
<td>
|
||||
<input type="text" v-model="healthCheck.userAgent" maxlength="200"/>
|
||||
<p class="comment">发送到服务器的User-Agent值,不填写表示使用默认值。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>只基础请求</td>
|
||||
<td>
|
||||
<checkbox v-model="healthCheck.onlyBasicRequest"></checkbox>
|
||||
<p class="comment">只做基础的请求,不处理反向代理(不检查源站)、WAF等。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="margin"></div>
|
||||
|
||||
37
web/public/js/components/common/node-role-name.js
Normal file
37
web/public/js/components/common/node-role-name.js
Normal file
@@ -0,0 +1,37 @@
|
||||
// 节点角色名称
|
||||
Vue.component("node-role-name", {
|
||||
props: ["v-role"],
|
||||
data: function () {
|
||||
let roleName = ""
|
||||
switch (this.vRole) {
|
||||
case "node":
|
||||
roleName = "边缘节点"
|
||||
break
|
||||
case "monitor":
|
||||
roleName = "监控节点"
|
||||
break
|
||||
case "api":
|
||||
roleName = "API节点"
|
||||
break
|
||||
case "user":
|
||||
roleName = "用户平台"
|
||||
break
|
||||
case "admin":
|
||||
roleName = "管理平台"
|
||||
break
|
||||
case "database":
|
||||
roleName = "数据库节点"
|
||||
break
|
||||
case "dns":
|
||||
roleName = "DNS节点"
|
||||
break
|
||||
case "report":
|
||||
roleName = "区域监控终端"
|
||||
break
|
||||
}
|
||||
return {
|
||||
roleName: roleName
|
||||
}
|
||||
},
|
||||
template: `<span>{{roleName}}</span>`
|
||||
})
|
||||
@@ -60,6 +60,8 @@ Vue.component("size-capacity-box", {
|
||||
<option value="kb">KB</option>
|
||||
<option value="mb">MB</option>
|
||||
<option value="gb">GB</option>
|
||||
<option value="tb">TB</option>
|
||||
<option value="pb">PB</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
@@ -80,7 +80,7 @@ Vue.component("dns-route-selector", {
|
||||
return
|
||||
}
|
||||
this.searchingRoutes = this.vAllRoutes.filter(function (route) {
|
||||
return teaweb.match(route.name, keyword)
|
||||
return teaweb.match(route.name, keyword) || teaweb.match(route.domainName, keyword)
|
||||
})
|
||||
if (this.searchingRoutes.length > 0) {
|
||||
this.routeCode = this.searchingRoutes[0].code + "@" + this.searchingRoutes[0].domainId
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
Vue.component("ip-list-table", {
|
||||
props: ["v-items"],
|
||||
props: ["v-items", "v-keyword"],
|
||||
data: function () {
|
||||
return {
|
||||
items: this.vItems
|
||||
items: this.vItems,
|
||||
keyword: (this.vKeyword != null) ? this.vKeyword : ""
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -33,7 +34,7 @@ Vue.component("ip-list-table", {
|
||||
</thead>
|
||||
<tr v-for="item in items">
|
||||
<td>
|
||||
<span v-if="item.type != 'all'">{{item.ipFrom}}<span v-if="item.ipTo.length > 0"> - {{item.ipTo}}</span></span>
|
||||
<span v-if="item.type != 'all'"><keyword :v-word="keyword">{{item.ipFrom}}</keyword><span v-if="item.ipTo.length > 0"> - <keyword :v-word="keyword">{{item.ipTo}}</keyword></span></span>
|
||||
<span v-else class="disabled">*</span>
|
||||
</td>
|
||||
<td>
|
||||
|
||||
166
web/public/js/components/plans/plan-price-config-box.js
Normal file
166
web/public/js/components/plans/plan-price-config-box.js
Normal file
@@ -0,0 +1,166 @@
|
||||
// 套餐价格配置
|
||||
Vue.component("plan-price-config-box", {
|
||||
props: ["v-price-type", "v-monthly-price", "v-seasonally-price", "v-yearly-price", "v-bandwidth-price"],
|
||||
data: function () {
|
||||
let priceType = this.vPriceType
|
||||
if (priceType == null) {
|
||||
priceType = "period"
|
||||
}
|
||||
|
||||
let monthlyPriceNumber = 0
|
||||
let monthlyPrice = this.vMonthlyPrice
|
||||
if (monthlyPrice == null || monthlyPrice <= 0) {
|
||||
monthlyPrice = ""
|
||||
} else {
|
||||
monthlyPrice = monthlyPrice.toString()
|
||||
monthlyPriceNumber = parseFloat(monthlyPrice)
|
||||
if (isNaN(monthlyPriceNumber)) {
|
||||
monthlyPriceNumber = 0
|
||||
}
|
||||
}
|
||||
|
||||
let seasonallyPriceNumber = 0
|
||||
let seasonallyPrice = this.vSeasonallyPrice
|
||||
if (seasonallyPrice == null || seasonallyPrice <= 0) {
|
||||
seasonallyPrice = ""
|
||||
} else {
|
||||
seasonallyPrice = seasonallyPrice.toString()
|
||||
seasonallyPriceNumber = parseFloat(seasonallyPrice)
|
||||
if (isNaN(seasonallyPriceNumber)) {
|
||||
seasonallyPriceNumber = 0
|
||||
}
|
||||
}
|
||||
|
||||
let yearlyPriceNumber = 0
|
||||
let yearlyPrice = this.vYearlyPrice
|
||||
if (yearlyPrice == null || yearlyPrice <= 0) {
|
||||
yearlyPrice = ""
|
||||
} else {
|
||||
yearlyPrice = yearlyPrice.toString()
|
||||
yearlyPriceNumber = parseFloat(yearlyPrice)
|
||||
if (isNaN(yearlyPriceNumber)) {
|
||||
yearlyPriceNumber = 0
|
||||
}
|
||||
}
|
||||
|
||||
let bandwidthPrice = this.vBandwidthPrice
|
||||
let bandwidthPriceBaseNumber = 0
|
||||
if (bandwidthPrice != null) {
|
||||
bandwidthPriceBaseNumber = bandwidthPrice.base
|
||||
} else {
|
||||
bandwidthPrice = {
|
||||
base: 0
|
||||
}
|
||||
}
|
||||
let bandwidthPriceBase = ""
|
||||
if (bandwidthPriceBaseNumber > 0) {
|
||||
bandwidthPriceBase = bandwidthPriceBaseNumber.toString()
|
||||
}
|
||||
|
||||
return {
|
||||
priceType: priceType,
|
||||
monthlyPrice: monthlyPrice,
|
||||
seasonallyPrice: seasonallyPrice,
|
||||
yearlyPrice: yearlyPrice,
|
||||
|
||||
monthlyPriceNumber: monthlyPriceNumber,
|
||||
seasonallyPriceNumber: seasonallyPriceNumber,
|
||||
yearlyPriceNumber: yearlyPriceNumber,
|
||||
|
||||
bandwidthPriceBase: bandwidthPriceBase,
|
||||
bandwidthPrice: bandwidthPrice
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
monthlyPrice: function (v) {
|
||||
let price = parseFloat(v)
|
||||
if (isNaN(price)) {
|
||||
price = 0
|
||||
}
|
||||
this.monthlyPriceNumber = price
|
||||
},
|
||||
seasonallyPrice: function (v) {
|
||||
let price = parseFloat(v)
|
||||
if (isNaN(price)) {
|
||||
price = 0
|
||||
}
|
||||
this.seasonallyPriceNumber = price
|
||||
},
|
||||
yearlyPrice: function (v) {
|
||||
let price = parseFloat(v)
|
||||
if (isNaN(price)) {
|
||||
price = 0
|
||||
}
|
||||
this.yearlyPriceNumber = price
|
||||
},
|
||||
bandwidthPriceBase: function (v) {
|
||||
let price = parseFloat(v)
|
||||
if (isNaN(price)) {
|
||||
price = 0
|
||||
}
|
||||
this.bandwidthPrice.base = price
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<input type="hidden" name="priceType" :value="priceType"/>
|
||||
<input type="hidden" name="monthlyPrice" :value="monthlyPriceNumber"/>
|
||||
<input type="hidden" name="seasonallyPrice" :value="seasonallyPriceNumber"/>
|
||||
<input type="hidden" name="yearlyPrice" :value="yearlyPriceNumber"/>
|
||||
<input type="hidden" name="bandwidthPriceJSON" :value="JSON.stringify(bandwidthPrice)"/>
|
||||
|
||||
<div>
|
||||
<radio :v-value="'period'" :value="priceType" v-model="priceType"> 按时间周期</radio>
|
||||
<radio :v-value="'bandwidth'" :value="priceType" v-model="priceType"> 按带宽用量</radio>
|
||||
</div>
|
||||
|
||||
<!-- 按时间周期 -->
|
||||
<div v-show="priceType == 'period'">
|
||||
<div class="ui divider"></div>
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td class="title">月度价格</td>
|
||||
<td>
|
||||
<div class="ui input right labeled">
|
||||
<input type="text" style="width: 7em" maxlength="10" v-model="monthlyPrice"/>
|
||||
<span class="ui label">元</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title">季度价格</td>
|
||||
<td>
|
||||
<div class="ui input right labeled">
|
||||
<input type="text" style="width: 7em" maxlength="10" v-model="seasonallyPrice"/>
|
||||
<span class="ui label">元</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title">年度价格</td>
|
||||
<td>
|
||||
<div class="ui input right labeled">
|
||||
<input type="text" style="width: 7em" maxlength="10" v-model="yearlyPrice"/>
|
||||
<span class="ui label">元</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 按带宽 -->
|
||||
<div v-show="priceType =='bandwidth'">
|
||||
<div class="ui divider"></div>
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td class="title">基础带宽费用</td>
|
||||
<td>
|
||||
<div class="ui input right labeled">
|
||||
<input type="text" v-model="bandwidthPriceBase" maxlength="10" style="width: 7em"/>
|
||||
<span class="ui label">元/GB</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>`
|
||||
})
|
||||
18
web/public/js/components/plans/plan-price-view.js
Normal file
18
web/public/js/components/plans/plan-price-view.js
Normal file
@@ -0,0 +1,18 @@
|
||||
Vue.component("plan-price-view", {
|
||||
props: ["v-plan"],
|
||||
data: function () {
|
||||
return {
|
||||
plan: this.vPlan
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<span v-if="plan.priceType == 'period'">
|
||||
<span v-if="plan.monthlyPrice > 0">月度:¥{{plan.monthlyPrice}}元<br/></span>
|
||||
<span v-if="plan.seasonallyPrice > 0">季度:¥{{plan.seasonallyPrice}}元<br/></span>
|
||||
<span v-if="plan.yearlyPrice > 0">年度:¥{{plan.yearlyPrice}}元</span>
|
||||
</span>
|
||||
<span v-if="plan.priceType == 'bandwidth'">
|
||||
基础价格:¥{{plan.bandwidthPrice.base}}元/GB
|
||||
</span>
|
||||
</div>`
|
||||
})
|
||||
28
web/public/js/components/plans/plan-user-selector.js
Normal file
28
web/public/js/components/plans/plan-user-selector.js
Normal file
@@ -0,0 +1,28 @@
|
||||
Vue.component("plan-user-selector", {
|
||||
mounted: function () {
|
||||
let that = this
|
||||
|
||||
Tea.action("/plans/users/options")
|
||||
.post()
|
||||
.success(function (resp) {
|
||||
that.users = resp.data.users
|
||||
})
|
||||
},
|
||||
props: ["v-user-id"],
|
||||
data: function () {
|
||||
let userId = this.vUserId
|
||||
if (userId == null) {
|
||||
userId = 0
|
||||
}
|
||||
return {
|
||||
users: [],
|
||||
userId: userId
|
||||
}
|
||||
},
|
||||
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>`
|
||||
})
|
||||
102
web/public/js/components/server/bandwidth-limit-config-box.js
Normal file
102
web/public/js/components/server/bandwidth-limit-config-box.js
Normal file
@@ -0,0 +1,102 @@
|
||||
Vue.component("bandwidth-limit-config-box", {
|
||||
props: ["v-bandwidth-limit"],
|
||||
data: function () {
|
||||
let config = this.vBandwidthLimit
|
||||
if (config == null) {
|
||||
config = {
|
||||
isOn: false,
|
||||
dailySize: {
|
||||
count: -1,
|
||||
unit: "gb"
|
||||
},
|
||||
monthlySize: {
|
||||
count: -1,
|
||||
unit: "gb"
|
||||
},
|
||||
totalSize: {
|
||||
count: -1,
|
||||
unit: "gb"
|
||||
},
|
||||
noticePageBody: ""
|
||||
}
|
||||
}
|
||||
if (config.dailySize == null) {
|
||||
config.dailySize = {
|
||||
count: -1,
|
||||
unit: "gb"
|
||||
}
|
||||
}
|
||||
if (config.monthlySize == null) {
|
||||
config.monthlySize = {
|
||||
count: -1,
|
||||
unit: "gb"
|
||||
}
|
||||
}
|
||||
if (config.totalSize == null) {
|
||||
config.totalSize = {
|
||||
count: -1,
|
||||
unit: "gb"
|
||||
}
|
||||
}
|
||||
return {
|
||||
config: config
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showBodyTemplate: function () {
|
||||
this.config.noticePageBody = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Bandwidth Limit Exceeded Warning</title>
|
||||
<body>
|
||||
|
||||
The site bandwidth has exceeded the limit. Please contact with the site administrator.
|
||||
|
||||
</body>
|
||||
</html>`
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<input type="hidden" name="bandwidthLimitJSON" :value="JSON.stringify(config)"/>
|
||||
<table class="ui table selectable definition">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="title">是否启用</td>
|
||||
<td>
|
||||
<checkbox v-model="config.isOn"></checkbox>
|
||||
<p class="comment">注意:由于带宽统计是每5分钟统计一次,所以超出带宽限制后,对用户的提醒也会有所延迟。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody v-show="config.isOn">
|
||||
<tr>
|
||||
<td>日带宽限制</td>
|
||||
<td>
|
||||
<size-capacity-box :v-value="config.dailySize"></size-capacity-box>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>月带宽限制</td>
|
||||
<td>
|
||||
<size-capacity-box :v-value="config.monthlySize"></size-capacity-box>
|
||||
</td>
|
||||
</tr>
|
||||
<!--<tr>
|
||||
<td>总体限制</td>
|
||||
<td>
|
||||
<size-capacity-box :v-value="config.totalSize"></size-capacity-box>
|
||||
<p class="comment"></p>
|
||||
</td>
|
||||
</tr>-->
|
||||
<tr>
|
||||
<td>网页提示内容</td>
|
||||
<td>
|
||||
<textarea v-model="config.noticePageBody"></textarea>
|
||||
<p class="comment"><a href="" @click.prevent="showBodyTemplate">[使用模板]</a>。当达到带宽限制时网页显示的HTML内容,不填写则显示默认的提示内容。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="margin"></div>
|
||||
</div>`
|
||||
})
|
||||
16
web/public/js/components/server/bandwidth-limit-view.js
Normal file
16
web/public/js/components/server/bandwidth-limit-view.js
Normal file
@@ -0,0 +1,16 @@
|
||||
// 显示带宽限制说明
|
||||
Vue.component("bandwidth-limit-view", {
|
||||
props: ["v-bandwidth-limit"],
|
||||
data: function () {
|
||||
return {
|
||||
config: this.vBandwidthLimit
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<div v-if="config.isOn">
|
||||
<span v-if="config.dailySize != null && config.dailySize.count > 0">日带宽限制:{{config.dailySize.count}}{{config.dailySize.unit.toUpperCase()}}<br/></span>
|
||||
<span v-if="config.monthlySize != null && config.monthlySize.count > 0">月带宽限制:{{config.monthlySize.count}}{{config.monthlySize.unit.toUpperCase()}}<br/></span>
|
||||
</div>
|
||||
<span v-else class="disabled">没有限制。</span>
|
||||
</div>`
|
||||
})
|
||||
@@ -1,5 +1,5 @@
|
||||
Vue.component("http-access-log-box", {
|
||||
props: ["v-access-log", "v-keyword"],
|
||||
props: ["v-access-log", "v-keyword", "v-show-server-link"],
|
||||
data: function () {
|
||||
let accessLog = this.vAccessLog
|
||||
if (accessLog.header != null && accessLog.header.Upgrade != null && accessLog.header.Upgrade.values != null && accessLog.header.Upgrade.values.$contains("websocket")) {
|
||||
@@ -49,7 +49,9 @@ Vue.component("http-access-log-box", {
|
||||
}
|
||||
},
|
||||
template: `<div style="word-break: break-all" :style="{'color': (accessLog.status >= 400) ? '#dc143c' : ''}" ref="box">
|
||||
<a v-if="accessLog.node != null && accessLog.node.nodeCluster != null" :href="'/clusters/cluster/node?nodeId=' + accessLog.node.id + '&clusterId=' + accessLog.node.nodeCluster.id" title="点击查看节点详情" target="_top"><span class="grey">[{{accessLog.node.name}}<span v-if="!accessLog.node.name.endsWith('节点')">节点</span>]</span></a><span v-if="accessLog.region != null && accessLog.region.length > 0" class="grey">[{{accessLog.region}}]</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'">cache hit</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> - 耗时:{{formatCost(accessLog.requestTime)}} ms <span v-if="accessLog.humanTime != null && accessLog.humanTime.length > 0" class="grey small"> ({{accessLog.humanTime}})</span>
|
||||
<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>
|
||||
<span v-if="accessLog.region != null && accessLog.region.length > 0" class="grey">[{{accessLog.region}}]</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'">cache hit</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> - 耗时:{{formatCost(accessLog.requestTime)}} ms <span v-if="accessLog.humanTime != null && accessLog.humanTime.length > 0" class="grey small"> ({{accessLog.humanTime}})</span>
|
||||
<a href="" @click.prevent="showLog" title="查看详情"><i class="icon expand"></i></a>
|
||||
</div>`
|
||||
})
|
||||
@@ -7,7 +7,9 @@ Vue.component("http-cache-config-box", {
|
||||
isPrior: false,
|
||||
isOn: false,
|
||||
addStatusHeader: true,
|
||||
cacheRefs: []
|
||||
cacheRefs: [],
|
||||
purgeIsOn: false,
|
||||
purgeKey: ""
|
||||
}
|
||||
}
|
||||
return {
|
||||
@@ -17,6 +19,16 @@ Vue.component("http-cache-config-box", {
|
||||
methods: {
|
||||
isOn: function () {
|
||||
return ((!this.vIsLocation && !this.vIsGroup) || this.cacheConfig.isPrior) && this.cacheConfig.isOn
|
||||
},
|
||||
generatePurgeKey: function () {
|
||||
let r = Math.random().toString() + Math.random().toString()
|
||||
let s = r.replace(/0\./g, "")
|
||||
.replace(/\./g, "")
|
||||
let result = ""
|
||||
for (let i = 0; i < s.length; i++) {
|
||||
result += String.fromCharCode(parseInt(s.substring(i, i + 1)) + ((Math.random() < 0.5) ? "a" : "A").charCodeAt(0))
|
||||
}
|
||||
this.cacheConfig.purgeKey = result
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
@@ -51,6 +63,20 @@ Vue.component("http-cache-config-box", {
|
||||
<p class="comment">选中后自动在响应Header中增加<code-label>X-Cache: BYPASS|MISS|HIT</code-label>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>允许PURGE</td>
|
||||
<td>
|
||||
<checkbox v-model="cacheConfig.purgeIsOn"></checkbox>
|
||||
<p class="comment">允许使用PURGE方法清除某个URL缓存。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-show="cacheConfig.purgeIsOn">
|
||||
<td>PURGE Key *</td>
|
||||
<td>
|
||||
<input type="text" maxlength="200" v-model="cacheConfig.purgeKey"/>
|
||||
<p class="comment"><a href="" @click.prevent="generatePurgeKey">[随机生成]</a>。需要在PURGE方法调用时加入<code-label>Edge-Purge-Key: {{cacheConfig.purgeKey}}</code-label> Header。只能包含字符、数字、下划线。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -14,8 +14,9 @@ Vue.component("http-compression-config-box", {
|
||||
isPrior: false,
|
||||
isOn: false,
|
||||
useDefaultTypes: true,
|
||||
types: ["gzip", "deflate", "brotli"],
|
||||
types: ["brotli", "gzip", "deflate"],
|
||||
level: 5,
|
||||
decompressData: false,
|
||||
gzipRef: null,
|
||||
deflateRef: null,
|
||||
brotliRef: null,
|
||||
@@ -188,7 +189,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">(gzip、deflate、brotli)</span></label>
|
||||
<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">使用默认顺序</label>
|
||||
</div>
|
||||
<div v-show="!config.useDefaultTypes">
|
||||
@@ -204,26 +205,33 @@ Vue.component("http-compression-config-box", {
|
||||
<p class="comment">选择支持的压缩算法和优先顺序,拖动<i class="icon list small grey"></i>图表排序。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>支持已压缩内容</td>
|
||||
<td>
|
||||
<checkbox v-model="config.decompressData"></checkbox>
|
||||
<p class="comment">支持对已压缩内容尝试重新使用新的算法压缩。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>内容最小长度</td>
|
||||
<td>
|
||||
<size-capacity-box :v-name="'minLength'" :v-value="config.minLength" :v-unit="'kb'"></size-capacity-box>
|
||||
<p class="comment">0表示不限制,内容长度从文件尺寸或Content-Length中获取。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>内容最大长度</td>
|
||||
<td>
|
||||
<size-capacity-box :v-name="'maxLength'" :v-value="config.maxLength" :v-unit="'mb'"></size-capacity-box>
|
||||
<p class="comment">0表示不限制,内容长度从文件尺寸或Content-Length中获取。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>匹配条件</td>
|
||||
<td>
|
||||
<http-request-conds-box :v-conds="config.conds" @change="changeConds"></http-request-conds-box>
|
||||
</td>
|
||||
</tr>
|
||||
<td>
|
||||
<size-capacity-box :v-name="'minLength'" :v-value="config.minLength" :v-unit="'kb'"></size-capacity-box>
|
||||
<p class="comment">0表示不限制,内容长度从文件尺寸或Content-Length中获取。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>内容最大长度</td>
|
||||
<td>
|
||||
<size-capacity-box :v-name="'maxLength'" :v-value="config.maxLength" :v-unit="'mb'"></size-capacity-box>
|
||||
<p class="comment">0表示不限制,内容长度从文件尺寸或Content-Length中获取。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>匹配条件</td>
|
||||
<td>
|
||||
<http-request-conds-box :v-conds="config.conds" @change="changeConds"></http-request-conds-box>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="margin"></div>
|
||||
|
||||
@@ -46,6 +46,14 @@ Vue.component("http-firewall-actions-box", {
|
||||
})
|
||||
}
|
||||
|
||||
var defaultPageBody = `<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
403 Forbidden
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
|
||||
return {
|
||||
id: id,
|
||||
|
||||
@@ -63,6 +71,8 @@ Vue.component("http-firewall-actions-box", {
|
||||
|
||||
// 动作参数
|
||||
blockTimeout: "",
|
||||
blockScope: "global",
|
||||
|
||||
captchaLife: "",
|
||||
get302Life: "",
|
||||
post307Life: "",
|
||||
@@ -74,6 +84,10 @@ Vue.component("http-firewall-actions-box", {
|
||||
|
||||
tagTags: [],
|
||||
|
||||
pageStatus: 403,
|
||||
pageBody: defaultPageBody,
|
||||
defaultPageBody: defaultPageBody,
|
||||
|
||||
goGroupName: "",
|
||||
goGroupId: 0,
|
||||
goGroup: null,
|
||||
@@ -97,6 +111,9 @@ Vue.component("http-firewall-actions-box", {
|
||||
this.actionOptions["timeout"] = v
|
||||
}
|
||||
},
|
||||
blockScope: function (v) {
|
||||
this.actionOptions["scope"] = v
|
||||
},
|
||||
captchaLife: function (v) {
|
||||
v = parseInt(v)
|
||||
if (isNaN(v)) {
|
||||
@@ -169,6 +186,7 @@ Vue.component("http-firewall-actions-box", {
|
||||
|
||||
// 动作参数
|
||||
this.blockTimeout = ""
|
||||
this.blockScope = "global"
|
||||
this.captchaLife = ""
|
||||
this.get302Life = ""
|
||||
this.post307Life = ""
|
||||
@@ -181,6 +199,9 @@ Vue.component("http-firewall-actions-box", {
|
||||
|
||||
this.tagTags = []
|
||||
|
||||
this.pageStatus = 403
|
||||
this.pageBody = this.defaultPageBody
|
||||
|
||||
this.goGroupName = ""
|
||||
this.goGroupId = 0
|
||||
this.goGroup = null
|
||||
@@ -220,6 +241,11 @@ Vue.component("http-firewall-actions-box", {
|
||||
if (config.options.timeout != null || config.options.timeout > 0) {
|
||||
this.blockTimeout = config.options.timeout.toString()
|
||||
}
|
||||
if (config.options.scope != null && config.options.scope.length > 0) {
|
||||
this.blockScope = config.options.scope
|
||||
} else {
|
||||
this.blockScope = "service" // 兼容先前版本遗留的默认值
|
||||
}
|
||||
break
|
||||
case "allow":
|
||||
break
|
||||
@@ -266,6 +292,17 @@ Vue.component("http-firewall-actions-box", {
|
||||
if (config.options.tags != null) {
|
||||
this.tagTags = config.options.tags
|
||||
}
|
||||
break
|
||||
case "page":
|
||||
this.pageStatus = 403
|
||||
this.pageBody = this.defaultPageBody
|
||||
if (config.options.status != null) {
|
||||
this.pageStatus = config.options.status
|
||||
}
|
||||
if (config.options.body != null) {
|
||||
this.pageBody = config.options.body
|
||||
}
|
||||
|
||||
break
|
||||
case "go_group":
|
||||
if (config.options != null) {
|
||||
@@ -340,6 +377,18 @@ Vue.component("http-firewall-actions-box", {
|
||||
this.actionOptions = {
|
||||
tags: this.tagTags
|
||||
}
|
||||
} else if (this.actionCode == "page") {
|
||||
let pageStatus = this.pageStatus.toString()
|
||||
if (!pageStatus.match(/^\d{3}$/)) {
|
||||
pageStatus = 403
|
||||
} else {
|
||||
pageStatus = parseInt(pageStatus)
|
||||
}
|
||||
|
||||
this.actionOptions = {
|
||||
status: pageStatus,
|
||||
body: this.pageBody
|
||||
}
|
||||
} else if (this.actionCode == "go_group") { // go_group
|
||||
let groupId = this.goGroupId
|
||||
if (typeof (groupId) == "string") {
|
||||
@@ -450,7 +499,7 @@ Vue.component("http-firewall-actions-box", {
|
||||
<input type="hidden" name="actionsJSON" :value="JSON.stringify(configs)"/>
|
||||
<div v-show="configs.length > 0" style="margin-bottom: 0.5em" id="actions-box">
|
||||
<div v-for="(config, index) in configs" :data-index="index" :key="config.id" class="ui label small basic" :class="{blue: index == editingIndex}" style="margin-bottom: 0.4em">
|
||||
{{config.name}} ({{config.code.toUpperCase()}})
|
||||
{{config.name}} <span class="small">({{config.code.toUpperCase()}})</span>
|
||||
|
||||
<!-- block -->
|
||||
<span v-if="config.code == 'block' && config.options.timeout > 0">:有效期{{config.options.timeout}}秒</span>
|
||||
@@ -470,12 +519,22 @@ Vue.component("http-firewall-actions-box", {
|
||||
<!-- tag -->
|
||||
<span v-if="config.code == 'tag'">:{{config.options.tags.join(", ")}}</span>
|
||||
|
||||
<!-- page -->
|
||||
<span v-if="config.code == 'page'">:[{{config.options.status}}]</span>
|
||||
|
||||
<!-- go_group -->
|
||||
<span v-if="config.code == 'go_group'">:{{config.options.groupName}}</span>
|
||||
|
||||
<!-- go_set -->
|
||||
<span v-if="config.code == 'go_set'">:{{config.options.groupName}} / {{config.options.setName}}</span>
|
||||
|
||||
<!-- 范围 -->
|
||||
<span v-if="config.options.scope != null && config.options.scope.length > 0" class="small grey">
|
||||
|
||||
<span v-if="config.options.scope == 'global'">[所有服务]</span>
|
||||
<span v-if="config.options.scope == 'service'">[当前服务]</span>
|
||||
</span>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<a href="" title="修改" @click.prevent="update(index, config)"><i class="icon pencil small"></i></a> <a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove small"></i></a> <a href="" title="拖动改变顺序"><i class="icon bars handle"></i></a>
|
||||
</div>
|
||||
@@ -503,6 +562,15 @@ Vue.component("http-firewall-actions-box", {
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="actionCode == 'block'">
|
||||
<td>封锁范围</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" v-model="blockScope">
|
||||
<option value="service">当前服务</option>
|
||||
<option value="global">所有服务</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- captcha -->
|
||||
<tr v-if="actionCode == 'captcha'">
|
||||
@@ -585,6 +653,18 @@ Vue.component("http-firewall-actions-box", {
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- page -->
|
||||
<tr v-if="actionCode == 'page'">
|
||||
<td>状态码 *</td>
|
||||
<td><input type="text" style="width: 4em" maxlength="3" v-model="pageStatus"/></td>
|
||||
</tr>
|
||||
<tr v-if="actionCode == 'page'">
|
||||
<td>网页内容</td>
|
||||
<td>
|
||||
<textarea v-model="pageBody"></textarea>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- 规则分组 -->
|
||||
<tr v-if="actionCode == 'go_group'">
|
||||
<td>下一个分组 *</td>
|
||||
|
||||
@@ -21,6 +21,12 @@ Vue.component("http-firewall-rule-label", {
|
||||
<span v-if="rule.param == '\${cc2}'">
|
||||
{{rule.checkpointOptions.period}}秒/{{rule.checkpointOptions.threshold}}请求
|
||||
</span>
|
||||
|
||||
<!-- refererBlock -->
|
||||
<span v-if="rule.param == '\${refererBlock}'">
|
||||
{{rule.checkpointOptions.allowDomains}}
|
||||
</span>
|
||||
|
||||
<span v-else>
|
||||
<span v-if="rule.paramFilters != null && rule.paramFilters.length > 0" v-for="paramFilter in rule.paramFilters"> | {{paramFilter.code}}</span>
|
||||
<var :class="{dash:rule.isCaseInsensitive}" :title="rule.isCaseInsensitive ? '大小写不敏感':''" v-if="!rule.isComposed">{{rule.operator}}</var>
|
||||
|
||||
@@ -45,6 +45,12 @@ Vue.component("http-firewall-rules-box", {
|
||||
<span v-if="rule.param == '\${cc2}'">
|
||||
{{rule.checkpointOptions.period}}秒/{{rule.checkpointOptions.threshold}}请求
|
||||
</span>
|
||||
|
||||
<!-- refererBlock -->
|
||||
<span v-if="rule.param == '\${refererBlock}'">
|
||||
{{rule.checkpointOptions.allowDomains}}
|
||||
</span>
|
||||
|
||||
<span v-else>
|
||||
<span v-if="rule.paramFilters != null && rule.paramFilters.length > 0" v-for="paramFilter in rule.paramFilters"> | {{paramFilter.code}}</span> <var>{{rule.operator}}</var> {{rule.value}}
|
||||
</span>
|
||||
|
||||
@@ -195,4 +195,102 @@ Vue.component("http-firewall-checkpoint-cc", {
|
||||
</tr>
|
||||
</table>
|
||||
</div>`
|
||||
})
|
||||
|
||||
// 防盗链
|
||||
Vue.component("http-firewall-checkpoint-referer-block", {
|
||||
props: ["v-checkpoint"],
|
||||
data: function () {
|
||||
let allowEmpty = true
|
||||
let allowSameDomain = true
|
||||
let allowDomains = []
|
||||
|
||||
let options = {}
|
||||
if (window.parent.UPDATING_RULE != null) {
|
||||
options = window.parent.UPDATING_RULE.checkpointOptions
|
||||
}
|
||||
|
||||
if (options == null) {
|
||||
options = {}
|
||||
}
|
||||
if (typeof (options.allowEmpty) == "boolean") {
|
||||
allowEmpty = options.allowEmpty
|
||||
}
|
||||
if (typeof (options.allowSameDomain) == "boolean") {
|
||||
allowSameDomain = options.allowSameDomain
|
||||
}
|
||||
if (options.allowDomains != null && typeof (options.allowDomains) == "object") {
|
||||
allowDomains = options.allowDomains
|
||||
}
|
||||
|
||||
let that = this
|
||||
setTimeout(function () {
|
||||
that.change()
|
||||
}, 100)
|
||||
|
||||
return {
|
||||
allowEmpty: allowEmpty,
|
||||
allowSameDomain: allowSameDomain,
|
||||
allowDomains: allowDomains,
|
||||
options: {},
|
||||
value: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
allowEmpty: function () {
|
||||
this.change()
|
||||
},
|
||||
allowSameDomain: function () {
|
||||
this.change()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeAllowDomains: function (values) {
|
||||
this.allowDomains = values
|
||||
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"/>
|
||||
<input type="hidden" name="value" :value="value"/>
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td class="title">来源域名允许为空</td>
|
||||
<td>
|
||||
<checkbox v-model="allowEmpty"></checkbox>
|
||||
<p class="comment">允许不带来源的访问。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>来源域名允许一致</td>
|
||||
<td>
|
||||
<checkbox v-model="allowSameDomain"></checkbox>
|
||||
<p class="comment">允许来源域名和当前访问的域名一致,相当于在站内访问。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>允许的来源域名</td>
|
||||
<td>
|
||||
<values-box :values="allowDomains" @change="changeAllowDomains"></values-box>
|
||||
<p class="comment">允许的来源域名列表,比如<code-label>example.com</code-label>、<code-label>*.example.com</code-label>。单个星号<code-label>*</code-label>表示允许所有域名。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>`
|
||||
})
|
||||
@@ -6,16 +6,49 @@ Vue.component("http-remote-addr-config-box", {
|
||||
config = {
|
||||
isPrior: false,
|
||||
isOn: false,
|
||||
value: "${remoteAddr}"
|
||||
value: "${rawRemoteAddr}",
|
||||
isCustomized: false
|
||||
}
|
||||
}
|
||||
|
||||
let optionValue = ""
|
||||
if (!config.isCustomized && (config.value == "${remoteAddr}" || config.value == "${rawRemoteAddr}")) {
|
||||
optionValue = config.value
|
||||
}
|
||||
|
||||
return {
|
||||
config: config
|
||||
config: config,
|
||||
options: [
|
||||
{
|
||||
name: "直接获取",
|
||||
description: "用户直接访问边缘节点,即 \"用户 --> 边缘节点\" 模式,这时候可以直接从连接中读取到真实的IP地址。",
|
||||
value: "${rawRemoteAddr}"
|
||||
},
|
||||
{
|
||||
name: "从上级代理中获取",
|
||||
description: "用户和边缘节点之间有别的代理服务转发,即 \"用户 --> [第三方代理服务] --> 边缘节点\",这时候只能从上级代理中获取传递的IP地址。",
|
||||
value: "${remoteAddr}"
|
||||
},
|
||||
{
|
||||
name: "[自定义]",
|
||||
description: "通过自定义变量来获取客户端真实的IP地址。",
|
||||
value: ""
|
||||
}
|
||||
],
|
||||
optionValue: optionValue
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isOn: function () {
|
||||
return ((!this.vIsLocation && !this.vIsGroup) || this.config.isPrior) && this.config.isOn
|
||||
},
|
||||
changeOptionValue: function () {
|
||||
if (this.optionValue.length > 0) {
|
||||
this.config.value = this.optionValue
|
||||
this.config.isCustomized = false
|
||||
} else {
|
||||
this.config.isCustomized = true
|
||||
}
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
@@ -36,10 +69,22 @@ Vue.component("http-remote-addr-config-box", {
|
||||
</tbody>
|
||||
<tbody v-show="isOn()">
|
||||
<tr>
|
||||
<td>请求变量</td>
|
||||
<td>获取IP方式 *</td>
|
||||
<td>
|
||||
<input type="text" v-model="config.value" maxlength="100"/>
|
||||
<p class="comment">通过此变量获取用户的IP地址。具体可用的请求变量列表可参考官方网站文档。</p>
|
||||
<select class="ui dropdown auto-width" v-model="optionValue" @change="changeOptionValue">
|
||||
<option v-for="option in options" :value="option.value">{{option.name}}</option>
|
||||
</select>
|
||||
<p class="comment" v-for="option in options" v-if="option.value == optionValue && option.description.length > 0">{{option.description}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-show="optionValue.length == 0">
|
||||
<td>读取IP变量值 *</td>
|
||||
<td>
|
||||
<input type="hidden" v-model="config.value" maxlength="100"/>
|
||||
<div v-if="optionValue == ''" style="margin-top: 1em">
|
||||
<input type="text" v-model="config.value" maxlength="100"/>
|
||||
<p class="comment">通过此变量获取用户的IP地址。具体可用的请求变量列表可参考官方网站文档。</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -9,8 +9,8 @@ Vue.component("http-webp-config-box", {
|
||||
quality: 50,
|
||||
minLength: {count: 0, "unit": "kb"},
|
||||
maxLength: {count: 0, "unit": "kb"},
|
||||
mimeTypes: ["image/*"],
|
||||
extensions: [".png", ".jpeg", ".jpg"],
|
||||
mimeTypes: ["image/png", "image/jpeg", "image/bmp", "image/x-ico", "image/gif"],
|
||||
extensions: [".png", ".jpeg", ".jpg", ".bmp", ".ico"],
|
||||
conds: null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,14 @@ Vue.component("reverse-proxy-box", {
|
||||
reverseProxyConfig.idleTimeout = {count: 0, unit: "second"}
|
||||
}
|
||||
|
||||
if (reverseProxyConfig.proxyProtocol == null) {
|
||||
// 如果直接赋值Vue将不会触发变更通知
|
||||
Vue.set(reverseProxyConfig, "proxyProtocol", {
|
||||
isOn: false,
|
||||
version: 1
|
||||
})
|
||||
}
|
||||
|
||||
let forwardHeaders = [
|
||||
{
|
||||
name: "X-Real-IP",
|
||||
@@ -116,6 +124,13 @@ Vue.component("reverse-proxy-box", {
|
||||
}
|
||||
this.reverseProxyConfig.maxIdleConns = maxIdleConns
|
||||
},
|
||||
"reverseProxyConfig.proxyProtocol.version": function (v) {
|
||||
let version = parseInt(v)
|
||||
if (isNaN(version)) {
|
||||
version = 1
|
||||
}
|
||||
this.reverseProxyConfig.proxyProtocol.version = version
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isOn: function () {
|
||||
@@ -194,7 +209,7 @@ Vue.component("reverse-proxy-box", {
|
||||
<p class="comment">可以把请求的路径部分前缀去除后再查找文件,比如把 <span class="ui label tiny">/web/app/index.html</span> 去除前缀 <span class="ui label tiny">/web</span> 后就变成 <span class="ui label tiny">/app/index.html</span>。 </p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr v-if="family == null || family == 'http'">
|
||||
<td>是否自动刷新缓存区<em>(AutoFlush)</em></td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
@@ -205,7 +220,7 @@ Vue.component("reverse-proxy-box", {
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="family == null || family == 'http'">
|
||||
<td class="color-border">源站默认连接失败超时时间</td>
|
||||
<td class="color-border">源站连接失败超时时间</td>
|
||||
<td>
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
@@ -219,7 +234,7 @@ Vue.component("reverse-proxy-box", {
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="family == null || family == 'http'">
|
||||
<td class="color-border">源站默认读取超时时间</td>
|
||||
<td class="color-border">源站读取超时时间</td>
|
||||
<td>
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
@@ -233,7 +248,7 @@ Vue.component("reverse-proxy-box", {
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="family == null || family == 'http'">
|
||||
<td class="color-border">源站默认最大并发连接数</td>
|
||||
<td class="color-border">源站最大并发连接数</td>
|
||||
<td>
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
@@ -244,7 +259,7 @@ Vue.component("reverse-proxy-box", {
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="family == null || family == 'http'">
|
||||
<td class="color-border">源站默认最大空闲连接数</td>
|
||||
<td class="color-border">源站最大空闲连接数</td>
|
||||
<td>
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
@@ -255,7 +270,7 @@ Vue.component("reverse-proxy-box", {
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="family == null || family == 'http'">
|
||||
<td class="color-border">源站默认最大空闲超时时间</td>
|
||||
<td class="color-border">源站最大空闲超时时间</td>
|
||||
<td>
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
@@ -268,6 +283,24 @@ Vue.component("reverse-proxy-box", {
|
||||
<p class="comment">源站保持等待的空闲超时时间,0表示使用默认时间。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-show="family != 'unix'">
|
||||
<td>PROXY Protocol</td>
|
||||
<td>
|
||||
<checkbox name="proxyProtocolIsOn" v-model="reverseProxyConfig.proxyProtocol.isOn"></checkbox>
|
||||
<p class="comment">选中后表示启用PROXY Protocol,每次连接源站时都会在头部写入客户端地址信息。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-show="family != 'unix' && reverseProxyConfig.proxyProtocol.isOn">
|
||||
<td>PROXY Protocol版本</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" name="proxyProtocolVersion" v-model="reverseProxyConfig.proxyProtocol.version">
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
</select>
|
||||
<p class="comment" v-if="reverseProxyConfig.proxyProtocol.version == 1">发送类似于<code-label>PROXY TCP4 192.168.1.1 192.168.1.10 32567 443</code-label>的头部信息。</p>
|
||||
<p class="comment" v-if="reverseProxyConfig.proxyProtocol.version == 2">发送二进制格式的头部信息。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="margin"></div>
|
||||
|
||||
@@ -69,13 +69,13 @@ window.teaweb = {
|
||||
}
|
||||
document.head.append(element)
|
||||
},
|
||||
datepicker: function (element, callback) {
|
||||
datepicker: function (element, callback, bottomLeft) {
|
||||
// 加载
|
||||
if (typeof Pikaday == "undefined") {
|
||||
let that = this
|
||||
this.loadJS("/js/moment.min.js", function () {
|
||||
that.loadJS("/js/pikaday.js", function () {
|
||||
that.datepicker(element, callback)
|
||||
that.datepicker(element, callback, bottomLeft)
|
||||
})
|
||||
})
|
||||
this.loadCSS("/js/pikaday.css")
|
||||
@@ -89,8 +89,8 @@ window.teaweb = {
|
||||
if (typeof (element) == "string") {
|
||||
element = document.getElementById(element);
|
||||
}
|
||||
var year = new Date().getFullYear();
|
||||
var picker = new Pikaday({
|
||||
let year = new Date().getFullYear();
|
||||
let picker = new Pikaday({
|
||||
field: element,
|
||||
firstDay: 1,
|
||||
minDate: new Date(year - 1, 0, 1),
|
||||
@@ -109,8 +109,9 @@ window.teaweb = {
|
||||
if (typeof (callback) == "function") {
|
||||
callback.call(Tea.Vue, picker.toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
reposition: !bottomLeft
|
||||
})
|
||||
},
|
||||
|
||||
formatBytes: function (bytes) {
|
||||
@@ -256,6 +257,21 @@ window.teaweb = {
|
||||
}
|
||||
});
|
||||
},
|
||||
popupSuccess: function (url, width, height) {
|
||||
let options = {}
|
||||
if (width != null) {
|
||||
options["width"] = width
|
||||
}
|
||||
if (height != null) {
|
||||
options["height"] = height
|
||||
}
|
||||
options["callback"] = function () {
|
||||
teaweb.success("保存成功", function () {
|
||||
teaweb.reload()
|
||||
})
|
||||
}
|
||||
this.popup(url, options)
|
||||
},
|
||||
popupFinish: function () {
|
||||
if (window.POPUP_CALLBACK != null) {
|
||||
window.POPUP_CALLBACK.apply(window, arguments);
|
||||
|
||||
@@ -239,12 +239,20 @@ p.margin {
|
||||
}
|
||||
.main-menu .ui.menu .sub-items .item {
|
||||
padding-left: 2.8em !important;
|
||||
padding-right: 0.4em !important;
|
||||
}
|
||||
.main-menu .ui.menu .sub-items .item .icon {
|
||||
position: absolute;
|
||||
left: 1.1em;
|
||||
top: 0.93em;
|
||||
}
|
||||
.main-menu .ui.menu .sub-items .item .label {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
padding-left: 0.4em;
|
||||
padding-right: 0.4em;
|
||||
min-width: 2em;
|
||||
}
|
||||
@media screen and (max-width: 512px) {
|
||||
.main-menu .ui.menu .sub-items .item {
|
||||
padding-left: 1em !important;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -92,7 +92,9 @@
|
||||
<div class="subtitle" v-if="module.subtitle != null && module.subtitle.length > 0">{{module.subtitle}}</div>
|
||||
</a>
|
||||
<div v-if="teaMenu == module.code" class="sub-items">
|
||||
<a class="item" v-for="subItem in module.subItems" v-if="subItem.isOn !== false" :href="subItem.url"><i class="icon angle right" v-if="subItem.code == teaSubMenu"></i> {{subItem.name}}</a>
|
||||
<a class="item" v-for="subItem in module.subItems" v-if="subItem.isOn !== false" :href="subItem.url"><i class="icon angle right" v-if="subItem.code == teaSubMenu"></i> {{subItem.name}}
|
||||
<span class="ui label tiny red" v-if="subItem.badge != null && subItem.badge > 0">{{subItem.badge}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
Tea.context(function () {
|
||||
this.moreOptionsVisible = false
|
||||
this.globalMessageBadge = 0
|
||||
this.moreOptionsVisible = false
|
||||
this.globalMessageBadge = 0
|
||||
|
||||
if (typeof this.leftMenuItemIsDisabled == "undefined") {
|
||||
this.leftMenuItemIsDisabled = false
|
||||
}
|
||||
if (typeof this.leftMenuItemIsDisabled == "undefined") {
|
||||
this.leftMenuItemIsDisabled = false
|
||||
}
|
||||
|
||||
this.$delay(function () {
|
||||
if (this.$refs.focus != null) {
|
||||
this.$refs.focus.focus()
|
||||
}
|
||||
this.$delay(function () {
|
||||
if (this.$refs.focus != null) {
|
||||
this.$refs.focus.focus()
|
||||
}
|
||||
|
||||
// 检查消息
|
||||
this.checkMessages()
|
||||
if (!window.IS_POPUP) {
|
||||
// 检查消息
|
||||
this.checkMessages()
|
||||
|
||||
// 检查集群节点同步
|
||||
this.loadNodeTasks();
|
||||
// 检查集群节点同步
|
||||
this.loadNodeTasks();
|
||||
|
||||
// 检查DNS同步
|
||||
this.loadDNSTasks()
|
||||
})
|
||||
// 检查DNS同步
|
||||
this.loadDNSTasks()
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 切换模板
|
||||
@@ -32,186 +34,192 @@ Tea.context(function () {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 左侧子菜单
|
||||
*/
|
||||
this.showSubMenu = function (menu) {
|
||||
if (menu.alwaysActive) {
|
||||
return
|
||||
}
|
||||
if (this.teaSubMenus.menus != null && this.teaSubMenus.menus.length > 0) {
|
||||
this.teaSubMenus.menus.$each(function (k, v) {
|
||||
if (menu.id == v.id) {
|
||||
return
|
||||
}
|
||||
v.isActive = false
|
||||
})
|
||||
}
|
||||
menu.isActive = !menu.isActive
|
||||
};
|
||||
/**
|
||||
* 左侧子菜单
|
||||
*/
|
||||
this.showSubMenu = function (menu) {
|
||||
if (menu.alwaysActive) {
|
||||
return
|
||||
}
|
||||
if (this.teaSubMenus.menus != null && this.teaSubMenus.menus.length > 0) {
|
||||
this.teaSubMenus.menus.$each(function (k, v) {
|
||||
if (menu.id == v.id) {
|
||||
return
|
||||
}
|
||||
v.isActive = false
|
||||
})
|
||||
}
|
||||
menu.isActive = !menu.isActive
|
||||
};
|
||||
|
||||
/**
|
||||
* 检查消息
|
||||
*/
|
||||
this.checkMessages = function () {
|
||||
this.$post("/messages/badge")
|
||||
.params({})
|
||||
.success(function (resp) {
|
||||
this.globalMessageBadge = resp.data.count
|
||||
})
|
||||
.done(function () {
|
||||
let delay = 6000
|
||||
if (this.globalMessageBadge > 0) {
|
||||
delay = 30000
|
||||
}
|
||||
this.$delay(function () {
|
||||
this.checkMessages()
|
||||
}, delay)
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 检查消息
|
||||
*/
|
||||
this.checkMessages = function () {
|
||||
this.$post("/messages/badge")
|
||||
.params({})
|
||||
.success(function (resp) {
|
||||
this.globalMessageBadge = resp.data.count
|
||||
})
|
||||
.done(function () {
|
||||
let delay = 6000
|
||||
if (this.globalMessageBadge > 0) {
|
||||
delay = 30000
|
||||
}
|
||||
this.$delay(function () {
|
||||
this.checkMessages()
|
||||
}, delay)
|
||||
})
|
||||
}
|
||||
|
||||
this.checkMessagesOnce = function () {
|
||||
this.$post("/messages/badge")
|
||||
.params({})
|
||||
.success(function (resp) {
|
||||
this.globalMessageBadge = resp.data.count
|
||||
})
|
||||
}
|
||||
this.checkMessagesOnce = function () {
|
||||
this.$post("/messages/badge")
|
||||
.params({})
|
||||
.success(function (resp) {
|
||||
this.globalMessageBadge = resp.data.count
|
||||
})
|
||||
}
|
||||
|
||||
this.showMessages = function () {
|
||||
teaweb.popup("/messages", {
|
||||
height: "24em",
|
||||
width: "50em"
|
||||
})
|
||||
}
|
||||
this.showMessages = function () {
|
||||
teaweb.popup("/messages", {
|
||||
height: "24em",
|
||||
width: "50em"
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 底部伸展框
|
||||
*/
|
||||
this.showQQGroupQrcode = function () {
|
||||
teaweb.popup("/about/qq", {
|
||||
width: "21em",
|
||||
height: "24em"
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 底部伸展框
|
||||
*/
|
||||
this.showQQGroupQrcode = function () {
|
||||
teaweb.popup("/about/qq", {
|
||||
width: "21em",
|
||||
height: "24em"
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹窗中默认成功回调
|
||||
*/
|
||||
if (window.IS_POPUP === true) {
|
||||
this.success = window.NotifyPopup
|
||||
}
|
||||
/**
|
||||
* 弹窗中默认成功回调
|
||||
*/
|
||||
if (window.IS_POPUP === true) {
|
||||
this.success = window.NotifyPopup
|
||||
}
|
||||
|
||||
/**
|
||||
* 节点同步任务
|
||||
*/
|
||||
this.doingNodeTasks = {
|
||||
isDoing: false,
|
||||
hasError: false,
|
||||
isUpdated: false
|
||||
}
|
||||
/**
|
||||
* 节点同步任务
|
||||
*/
|
||||
this.doingNodeTasks = {
|
||||
isDoing: false,
|
||||
hasError: false,
|
||||
isUpdated: false
|
||||
}
|
||||
|
||||
this.loadNodeTasks = function () {
|
||||
if (!Tea.Vue.teaCheckNodeTasks) {
|
||||
return
|
||||
}
|
||||
this.$post("/clusters/tasks/check")
|
||||
.success(function (resp) {
|
||||
this.doingNodeTasks.isDoing = resp.data.isDoing
|
||||
this.doingNodeTasks.hasError = resp.data.hasError
|
||||
this.doingNodeTasks.isUpdated = true
|
||||
})
|
||||
.done(function () {
|
||||
this.$delay(function () {
|
||||
this.loadNodeTasks()
|
||||
}, 3000)
|
||||
})
|
||||
}
|
||||
this.loadNodeTasks = function () {
|
||||
if (!Tea.Vue.teaCheckNodeTasks) {
|
||||
return
|
||||
}
|
||||
this.$post("/clusters/tasks/check")
|
||||
.success(function (resp) {
|
||||
this.doingNodeTasks.isDoing = resp.data.isDoing
|
||||
this.doingNodeTasks.hasError = resp.data.hasError
|
||||
this.doingNodeTasks.isUpdated = true
|
||||
})
|
||||
.done(function () {
|
||||
this.$delay(function () {
|
||||
this.loadNodeTasks()
|
||||
}, 3000)
|
||||
})
|
||||
}
|
||||
|
||||
this.showNodeTasks = function () {
|
||||
teaweb.popup("/clusters/tasks/listPopup", {
|
||||
height: "24em",
|
||||
width: "50em"
|
||||
})
|
||||
}
|
||||
this.showNodeTasks = function () {
|
||||
teaweb.popup("/clusters/tasks/listPopup", {
|
||||
height: "24em",
|
||||
width: "50em"
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* DNS同步任务
|
||||
*/
|
||||
this.doingDNSTasks = {
|
||||
isDoing: false,
|
||||
hasError: false,
|
||||
isUpdated: false
|
||||
}
|
||||
/**
|
||||
* DNS同步任务
|
||||
*/
|
||||
this.doingDNSTasks = {
|
||||
isDoing: false,
|
||||
hasError: false,
|
||||
isUpdated: false
|
||||
}
|
||||
|
||||
this.loadDNSTasks = function () {
|
||||
if (!Tea.Vue.teaCheckDNSTasks) {
|
||||
return
|
||||
}
|
||||
this.$post("/dns/tasks/check")
|
||||
.success(function (resp) {
|
||||
this.doingDNSTasks.isDoing = resp.data.isDoing
|
||||
this.doingDNSTasks.hasError = resp.data.hasError
|
||||
this.doingDNSTasks.isUpdated = true
|
||||
})
|
||||
.done(function () {
|
||||
this.$delay(function () {
|
||||
this.loadDNSTasks()
|
||||
}, 3000)
|
||||
})
|
||||
}
|
||||
this.loadDNSTasks = function () {
|
||||
if (!Tea.Vue.teaCheckDNSTasks) {
|
||||
return
|
||||
}
|
||||
this.$post("/dns/tasks/check")
|
||||
.success(function (resp) {
|
||||
this.doingDNSTasks.isDoing = resp.data.isDoing
|
||||
this.doingDNSTasks.hasError = resp.data.hasError
|
||||
this.doingDNSTasks.isUpdated = true
|
||||
})
|
||||
.done(function () {
|
||||
this.$delay(function () {
|
||||
this.loadDNSTasks()
|
||||
}, 3000)
|
||||
})
|
||||
}
|
||||
|
||||
this.showDNSTasks = function () {
|
||||
teaweb.popup("/dns/tasks/listPopup", {
|
||||
height: "24em",
|
||||
width: "50em"
|
||||
})
|
||||
}
|
||||
this.showDNSTasks = function () {
|
||||
teaweb.popup("/dns/tasks/listPopup", {
|
||||
height: "24em",
|
||||
width: "50em"
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
window.NotifySuccess = function (message, url, params) {
|
||||
if (typeof (url) == "string" && url.length > 0) {
|
||||
if (url[0] != "/") {
|
||||
url = Tea.url(url, params);
|
||||
}
|
||||
}
|
||||
return function () {
|
||||
teaweb.success(message, function () {
|
||||
window.location = url;
|
||||
});
|
||||
};
|
||||
if (typeof (url) == "string" && url.length > 0) {
|
||||
if (url[0] != "/") {
|
||||
url = Tea.url(url, params);
|
||||
}
|
||||
}
|
||||
return function () {
|
||||
teaweb.success(message, function () {
|
||||
window.location = url;
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
window.NotifyReloadSuccess = function (message) {
|
||||
return function () {
|
||||
teaweb.success(message, function () {
|
||||
window.location.reload()
|
||||
})
|
||||
}
|
||||
return function () {
|
||||
teaweb.success(message, function () {
|
||||
window.location.reload()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
window.NotifyDelete = function (message, url, params) {
|
||||
teaweb.confirm(message, function () {
|
||||
Tea.Vue.$post(url)
|
||||
.params(params)
|
||||
.refresh();
|
||||
});
|
||||
teaweb.confirm(message, function () {
|
||||
Tea.Vue.$post(url)
|
||||
.params(params)
|
||||
.refresh();
|
||||
});
|
||||
};
|
||||
|
||||
window.NotifyPopup = function (resp) {
|
||||
window.parent.teaweb.popupFinish(resp);
|
||||
window.parent.teaweb.popupFinish(resp);
|
||||
};
|
||||
|
||||
window.ChangePageSize = function (size) {
|
||||
let url = window.location.toString();
|
||||
if (url.indexOf("pageSize") > 0) {
|
||||
url = url.replace(/pageSize=\d+/g, "pageSize=" + size);
|
||||
} else {
|
||||
if (url.indexOf("?") > 0) {
|
||||
url += "&pageSize=" + size;
|
||||
} else {
|
||||
url += "?pageSize=" + size;
|
||||
}
|
||||
}
|
||||
window.location = url;
|
||||
let url = window.location.toString();
|
||||
url = url.replace(/page=\d+/g, "page=1")
|
||||
if (url.indexOf("pageSize") > 0) {
|
||||
url = url.replace(/pageSize=\d+/g, "pageSize=" + size)
|
||||
} else {
|
||||
if (url.indexOf("?") > 0) {
|
||||
let anchorIndex = url.indexOf("#")
|
||||
if (anchorIndex < 0) {
|
||||
url += "&pageSize=" + size;
|
||||
} else {
|
||||
url = url.substring(0, anchorIndex) + "&pageSize=" + size + url.substr(anchorIndex);
|
||||
}
|
||||
} else {
|
||||
url += "?pageSize=" + size;
|
||||
}
|
||||
}
|
||||
window.location = url;
|
||||
};
|
||||
@@ -179,12 +179,21 @@ div.margin, p.margin {
|
||||
.sub-items {
|
||||
.item {
|
||||
padding-left: 2.8em !important;
|
||||
padding-right: 0.4em !important;
|
||||
|
||||
.icon {
|
||||
position: absolute;
|
||||
left: 1.1em;
|
||||
top: 0.93em;
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
padding-left: 0.4em;
|
||||
padding-right: 0.4em;
|
||||
min-width: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 512px) {
|
||||
|
||||
@@ -54,6 +54,7 @@ p.comment,
|
||||
div.comment {
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
padding-top: 0.4em;
|
||||
font-weight: normal;
|
||||
}
|
||||
p.comment em,
|
||||
div.comment em {
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"sources":["@layout_popup.less"],"names":[],"mappings":";AACA;EACC,WAAA;;AAGD;EACC,aAAA;;AAGD;EACC,qBAAA;;AAGD,CAAC;AAAW,CAAC,SAAS;AAAQ,CAAC,SAAS;AAAS,IAAI;EACpD,sBAAA;;AAGD,CAAC;AAAU,IAAI;AAAU,IAAI;EAC5B,cAAA;;AAGD,IAAI;AAAO,KAAK;AAAO,CAAC;EACvB,sBAAA;;AAGD,CAAC;EACA,iBAAA;;AAGD,IAAI;AAAM,GAAG;EACZ,cAAA;;AAGD,GAAG,IAAI;EACN,mBAAmB,8CAAnB;;AAGD;EACC,uBAAA;;AAGD,MAAM;EACL,sBAAA;;AAGD,MAAM;EACL,sBAAA;;AAGD,MAAM;EACL,sBAAA;;AAGD,MAAO;AAAI,MAAO;EACjB,2BAAA;;AAGD,CAAC;AAAU,GAAG;EACb,yBAAA;EACA,kBAAA;;AAGD,CAAC,QAAS;AAAI,GAAG,QAAS;EACzB,6BAAA;;AAGD;EACC,mBAAA;EACA,2BAAA;EACA,gBAAA;EACA,uBAAA;;AAGD,GAAG;AAAS,CAAC;EACZ,eAAA;;;AAID,GAAG;EACF,UAAA;;AAGD,GAAG;EACF,YAAA;;AAGD,GAAG;EACF,UAAA;;AAGD,GAAG;EACF,WAAA;;;AAID,MAAM;EACL,aAAA;;;AAID;EACC,kBAAA;EACA,UAAA;EACA,UAAA;EACA,mBAAA;EACA,kBAAA;EACA,UAAA;;AASD,mBANqC;EACpC;IACC,SAAA;;;AAIF,KAAK;EACJ,SAAA;;AAGD,KAAK;EACJ,UAAA;;AASD,mBANqC;EACpC,KAAK;IACJ,SAAA;;;AAIF,KAAM,MAAM,GAAE;EACb,WAAA;;AAGD,KAAM,MAAM,GAAE;EACb,WAAA;;AAGD,KAAM,MAAM;EACX,mBAAA;;AAGD,KAAM,MAAM,GAAE;EACb,yCAAA;;AAGD,KAAM,MAAM,GAAE;EACb,mBAAA;;AAGD,KAAM,MAAM,GAAE;EACb,sBAAA;;AAGD,KAAM,MAAM,GAAE,aAAc;EAC3B,mBAAA;;AAGD,KAAM,MAAM,GAAG;EACd,mBAAA;EACA,kBAAA;EACA,gBAAA;;AAGD,KAAM;EACL,mBAAA;EACA,4BAAA;;AAGD,KAAM,GAAG;EACR,gBAAA;;AAGD,KAAM,GAAG,KAAI;EACZ,cAAA;;AAGD,KAAM,GAAG;EACR,gBAAA;EACA,0BAAA;EACA,UAAA;;AAGD,KAAM,GAAG,EAAC;EACT,SAAS,GAAT;;AAGD,KAAM,GAAG,EAAC;EACT,SAAS,GAAT;;AAGD,KAAM;EACL,mBAAA;;AAGD,KAAM,GAAG,KAAI;EACZ,gBAAA;;AAGD,KAAM,QAAO;EACZ,gBAAA;EACA,cAAA;EACA,gBAAA;;;AAID,KAAK;EACJ,gBAAA;;AAGD,KAAK,KAAK;EACT,UAAA;EACA,WAAA;;;AAID;EACC,wBAAA;;;AAID,iBAAkB;EACjB,2BAAA;;AAGD,iBAAkB,MAAK;EACtB,UAAA;;AAGD,iBAAkB,MAAM;EACvB,2BAAA;;AAGD,MAAM;EACL,sBAAA;;;AAWD,mBAPqC;EACpC,OAAO,IAAI;IACV,sBAAA;;;;AAKF,KAAK;EACJ,0BAAA;;AAGD,KAAK;EACJ,yBAAA;;;AAOD,WAAY,MAAK;EAChB,wBAAA;EACA,2BAAA;;AAGD,WAAY;EACX,wBAAA;EACA,2BAAA;;AAGD,YAAa,MAAK;EACjB,wBAAA;EACA,2BAAA;;AAGD,YAAa,MAAK,KAAM;EACvB,kBAAA;;AAGD,YAAa;EACZ,wBAAA;;AAGD,KAAM;EACL,aAAA;;;AAID,IAAI;AAAQ,GAAG;EACd,yBAAA;;AAGD,GAAG;EACF,8BAAA;;;AAID,QAAS;EACR,WAAA;EACA,kBAAA;;;AAID,SAAU,MAAM;AAAG,SAAU;EAC5B,2BAAA;;;AAID;EACC,eAAA;EAEA,2BAAA;;AAHD,KAKC;EACC,qBAAA;EACA,mBAAA;EACA,WAAA;EACA,iBAAA;EACA,SAAA;EACA,gBAAA;EACA,sBAAA;EACA,cAAA;;AAbF,KAgBC,EAAC;EACA,8BAAA;EACA,YAAA;;AAlBF,KAqBC,EAAC;EACA,gBAAA;;AAKF;EACC,kBAAA;;AAGD,cAAc;AAAQ,aAAa;AAAQ,YAAY;EACtD,iCAAA;;AAGD;AAAgB;AAAe;EAC9B,iCAAA;;AAGD;EACC,2BAAA;;AAID,KAAK;EACJ,oCAAA;;AAID,QAAQ;EACP,4BAA4B,wBAA5B;EACA,2BAAA","file":"@layout_popup.css"}
|
||||
{"version":3,"sources":["@layout_popup.less"],"names":[],"mappings":";AACA;EACC,WAAA;;AAGD;EACC,aAAA;;AAGD;EACC,qBAAA;;AAGD,CAAC;AAAW,CAAC,SAAS;AAAQ,CAAC,SAAS;AAAS,IAAI;EACpD,sBAAA;;AAGD,CAAC;AAAU,IAAI;AAAU,IAAI;EAC5B,cAAA;;AAGD,IAAI;AAAO,KAAK;AAAO,CAAC;EACvB,sBAAA;;AAGD,CAAC;EACA,iBAAA;;AAGD,IAAI;AAAM,GAAG;EACZ,cAAA;;AAGD,GAAG,IAAI;EACN,mBAAmB,8CAAnB;;AAGD;EACC,uBAAA;;AAGD,MAAM;EACL,sBAAA;;AAGD,MAAM;EACL,sBAAA;;AAGD,MAAM;EACL,sBAAA;;AAGD,MAAO;AAAI,MAAO;EACjB,2BAAA;;AAGD,CAAC;AAAU,GAAG;EACb,yBAAA;EACA,kBAAA;EACA,mBAAA;;AAGD,CAAC,QAAS;AAAI,GAAG,QAAS;EACzB,6BAAA;;AAGD;EACC,mBAAA;EACA,2BAAA;EACA,gBAAA;EACA,uBAAA;;AAGD,GAAG;AAAS,CAAC;EACZ,eAAA;;;AAID,GAAG;EACF,UAAA;;AAGD,GAAG;EACF,YAAA;;AAGD,GAAG;EACF,UAAA;;AAGD,GAAG;EACF,WAAA;;;AAID,MAAM;EACL,aAAA;;;AAID;EACC,kBAAA;EACA,UAAA;EACA,UAAA;EACA,mBAAA;EACA,kBAAA;EACA,UAAA;;AASD,mBANqC;EACpC;IACC,SAAA;;;AAIF,KAAK;EACJ,SAAA;;AAGD,KAAK;EACJ,UAAA;;AASD,mBANqC;EACpC,KAAK;IACJ,SAAA;;;AAIF,KAAM,MAAM,GAAE;EACb,WAAA;;AAGD,KAAM,MAAM,GAAE;EACb,WAAA;;AAGD,KAAM,MAAM;EACX,mBAAA;;AAGD,KAAM,MAAM,GAAE;EACb,yCAAA;;AAGD,KAAM,MAAM,GAAE;EACb,mBAAA;;AAGD,KAAM,MAAM,GAAE;EACb,sBAAA;;AAGD,KAAM,MAAM,GAAE,aAAc;EAC3B,mBAAA;;AAGD,KAAM,MAAM,GAAG;EACd,mBAAA;EACA,kBAAA;EACA,gBAAA;;AAGD,KAAM;EACL,mBAAA;EACA,4BAAA;;AAGD,KAAM,GAAG;EACR,gBAAA;;AAGD,KAAM,GAAG,KAAI;EACZ,cAAA;;AAGD,KAAM,GAAG;EACR,gBAAA;EACA,0BAAA;EACA,UAAA;;AAGD,KAAM,GAAG,EAAC;EACT,SAAS,GAAT;;AAGD,KAAM,GAAG,EAAC;EACT,SAAS,GAAT;;AAGD,KAAM;EACL,mBAAA;;AAGD,KAAM,GAAG,KAAI;EACZ,gBAAA;;AAGD,KAAM,QAAO;EACZ,gBAAA;EACA,cAAA;EACA,gBAAA;;;AAID,KAAK;EACJ,gBAAA;;AAGD,KAAK,KAAK;EACT,UAAA;EACA,WAAA;;;AAID;EACC,wBAAA;;;AAID,iBAAkB;EACjB,2BAAA;;AAGD,iBAAkB,MAAK;EACtB,UAAA;;AAGD,iBAAkB,MAAM;EACvB,2BAAA;;AAGD,MAAM;EACL,sBAAA;;;AAWD,mBAPqC;EACpC,OAAO,IAAI;IACV,sBAAA;;;;AAKF,KAAK;EACJ,0BAAA;;AAGD,KAAK;EACJ,yBAAA;;;AAOD,WAAY,MAAK;EAChB,wBAAA;EACA,2BAAA;;AAGD,WAAY;EACX,wBAAA;EACA,2BAAA;;AAGD,YAAa,MAAK;EACjB,wBAAA;EACA,2BAAA;;AAGD,YAAa,MAAK,KAAM;EACvB,kBAAA;;AAGD,YAAa;EACZ,wBAAA;;AAGD,KAAM;EACL,aAAA;;;AAID,IAAI;AAAQ,GAAG;EACd,yBAAA;;AAGD,GAAG;EACF,8BAAA;;;AAID,QAAS;EACR,WAAA;EACA,kBAAA;;;AAID,SAAU,MAAM;AAAG,SAAU;EAC5B,2BAAA;;;AAID;EACC,eAAA;EAEA,2BAAA;;AAHD,KAKC;EACC,qBAAA;EACA,mBAAA;EACA,WAAA;EACA,iBAAA;EACA,SAAA;EACA,gBAAA;EACA,sBAAA;EACA,cAAA;;AAbF,KAgBC,EAAC;EACA,8BAAA;EACA,YAAA;;AAlBF,KAqBC,EAAC;EACA,gBAAA;;AAKF;EACC,kBAAA;;AAGD,cAAc;AAAQ,aAAa;AAAQ,YAAY;EACtD,iCAAA;;AAGD;AAAgB;AAAe;EAC9B,iCAAA;;AAGD;EACC,2BAAA;;AAID,KAAK;EACJ,oCAAA;;AAID,QAAQ;EACP,4BAA4B,wBAA5B;EACA,2BAAA","file":"@layout_popup.css"}
|
||||
@@ -58,6 +58,7 @@ tbody {
|
||||
p.comment, div.comment {
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
padding-top: 0.4em;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
p.comment em, div.comment em {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
{$template "/left_menu"}
|
||||
|
||||
<div class="right-box">
|
||||
<h3>基础设置</h3>
|
||||
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
|
||||
<input type="hidden" name="clusterId" :value="cluster.id"/>
|
||||
<table class="ui table selectable definition">
|
||||
@@ -24,6 +23,24 @@
|
||||
<p class="comment">当节点没有单独设置安装目录时,默认使用此设置。如果集群和节点都没有设置安装目录,则使用<span class="ui label tiny">/$登录用户/edge-node</span> 目录。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>时区</td>
|
||||
<td>
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<select class="ui dropdown" v-model="timeZoneGroupCode">
|
||||
<option v-for="timeZoneGroup in timeZoneGroups" :value="timeZoneGroup.code">{{timeZoneGroup.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<select class="ui dropdown" name="timeZone" v-model="cluster.timeZone">
|
||||
<option v-for="timeZoneLocation in timeZoneLocations" :value="timeZoneLocation.name" v-if="timeZoneLocation.group == timeZoneGroupCode">{{timeZoneLocation.name}} ({{timeZoneLocation.offset}})</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<p class="comment">节点记录日志使用的时区。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<submit-btn></submit-btn>
|
||||
</form>
|
||||
|
||||
@@ -1,3 +1,35 @@
|
||||
Tea.context(function () {
|
||||
this.success = NotifyReloadSuccess("保存成功")
|
||||
|
||||
this.timeZoneGroupCode = "asia"
|
||||
if (this.timeZoneLocation != null) {
|
||||
this.timeZoneGroupCode = this.timeZoneLocation.group
|
||||
}
|
||||
|
||||
let oldTimeZoneGroupCode = this.timeZoneGroupCode
|
||||
let oldTimeZoneName = ""
|
||||
if (this.timeZoneLocation != null) {
|
||||
oldTimeZoneName = this.timeZoneLocation.name
|
||||
}
|
||||
|
||||
this.$delay(function () {
|
||||
this.$watch("timeZoneGroupCode", function (groupCode) {
|
||||
if (groupCode == oldTimeZoneGroupCode && oldTimeZoneName.length > 0) {
|
||||
this.cluster.timeZone = oldTimeZoneName
|
||||
return
|
||||
}
|
||||
let firstLocation = null
|
||||
this.timeZoneLocations.forEach(function (v) {
|
||||
if (firstLocation != null) {
|
||||
return
|
||||
}
|
||||
if (v.group == groupCode) {
|
||||
firstLocation = v
|
||||
}
|
||||
})
|
||||
if (firstLocation != null) {
|
||||
this.cluster.timeZone = firstLocation.name
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -41,7 +41,12 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tr v-for="cluster in clusters">
|
||||
<td><a :href="'/clusters/cluster?clusterId=' + cluster.id"><keyword :v-word="keyword">{{cluster.name}}</keyword></a></td>
|
||||
<td>
|
||||
<a :href="'/clusters/cluster?clusterId=' + cluster.id"><keyword :v-word="keyword">{{cluster.name}}</keyword></a>
|
||||
<div v-if="cluster.timeZone != null && cluster.timeZone.length > 0">
|
||||
<grey-label>时区:{{cluster.timeZone}}</grey-label>
|
||||
</div>
|
||||
</td>
|
||||
<td class="center">
|
||||
<a :href="'/clusters/cluster/nodes?clusterId=' + cluster.id" v-if="cluster.countAllNodes > 0"><span :class="{red:cluster.countAllNodes > cluster.countActiveNodes}">{{cluster.countAllNodes}}</span></a>
|
||||
<span class="disabled" v-else="">-</span>
|
||||
@@ -66,7 +71,8 @@
|
||||
<span v-else class="disabled">-</span>
|
||||
</td>
|
||||
<td>
|
||||
<a :href="'/clusters/cluster?clusterId=' + cluster.id">详情</a>
|
||||
<a :href="'/clusters/cluster?clusterId=' + cluster.id">详情</a>
|
||||
<a :href="'/clusters/cluster/settings?clusterId=' + cluster.id">设置</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
4
web/views/@default/clusters/logs/@menu.html
Normal file
4
web/views/@default/clusters/logs/@menu.html
Normal file
@@ -0,0 +1,4 @@
|
||||
<first-menu>
|
||||
<menu-item href="/clusters/logs" code="index">所有</menu-item>
|
||||
<menu-item href="/clusters/logs?type=unread" code="unread">未读<span :class="{red: countUnreadLogs > 0}">({{countUnreadLogs}})</span></menu-item>
|
||||
</first-menu>
|
||||
@@ -1,9 +1,11 @@
|
||||
{$layout}
|
||||
{$template "/datepicker"}
|
||||
{$template "menu"}
|
||||
|
||||
<div class="margin"></div>
|
||||
|
||||
<form method="get" action="/clusters/logs" class="ui form" autocomplete="off">
|
||||
<input type="hidden" name="type" :value="type"/>
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<input type="text" name="dayFrom" placeholder="开始日期" v-model="dayFrom" value="" style="width:8em" id="day-from-picker"/>
|
||||
@@ -27,11 +29,18 @@
|
||||
<button type="submit" class="ui button">查询</button>
|
||||
</div>
|
||||
<div class="ui field" v-if="dayFrom.length > 0 || dayTo.length > 0 || keyword.length > 0 || level.length > 0">
|
||||
<a href="/clusters/logs">[清除条件]</a>
|
||||
<a :href="'/clusters/logs?type=' + type">[清除条件]</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div v-if="type == 'unread'">
|
||||
<div class="ui divider" style="margin-bottom: 0"></div>
|
||||
<second-menu v-if="logs.length > 0">
|
||||
<a href="" class="item" @click.prevent="updatePageRead()">[本页已读]</a>
|
||||
</second-menu>
|
||||
</div>
|
||||
|
||||
<p class="comment" v-if="logs.length == 0">暂时还没有日志。</p>
|
||||
|
||||
<table class="ui table selectable celled" v-if="logs.length > 0">
|
||||
@@ -40,6 +49,7 @@
|
||||
<th>集群</th>
|
||||
<th>节点</th>
|
||||
<th>信息</th>
|
||||
<th class="one op">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr v-for="log in logs">
|
||||
@@ -48,6 +58,9 @@
|
||||
<td>
|
||||
<node-log-row :v-log="log" :v-keyword="keyword"></node-log-row>
|
||||
</td>
|
||||
<td>
|
||||
<a href="" @click.prevent="updateRead(log.id)" v-if="!log.isRead">已读</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -3,4 +3,27 @@ Tea.context(function () {
|
||||
teaweb.datepicker("day-from-picker")
|
||||
teaweb.datepicker("day-to-picker")
|
||||
})
|
||||
|
||||
this.updateRead = function (logId) {
|
||||
this.$post(".readLogs")
|
||||
.params({
|
||||
logIds: [logId]
|
||||
})
|
||||
.success(function () {
|
||||
teaweb.reload()
|
||||
})
|
||||
}
|
||||
|
||||
this.updatePageRead = function () {
|
||||
let logIds = this.logs.map(function (v) {
|
||||
return v.id
|
||||
})
|
||||
this.$post(".readLogs")
|
||||
.params({
|
||||
logIds: logIds
|
||||
})
|
||||
.success(function () {
|
||||
teaweb.reload()
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -3,4 +3,5 @@
|
||||
<menu-item href="/dashboard/boards/waf" code="waf">WAF</menu-item>
|
||||
<menu-item href="/dashboard/boards/dns" code="dns">DNS</menu-item>
|
||||
<menu-item href="/dashboard/boards/user" code="user">用户</menu-item>
|
||||
<menu-item href="/dashboard/boards/events" code="event">事件<span :class="{red: countEvents > 0}">({{countEvents}})</span></menu-item>
|
||||
</first-menu>
|
||||
66
web/views/@default/dashboard/boards/events.html
Normal file
66
web/views/@default/dashboard/boards/events.html
Normal file
@@ -0,0 +1,66 @@
|
||||
{$layout}
|
||||
{$template "menu"}
|
||||
|
||||
<p class="comment" v-if="logs.length == 0">暂时还没有事件。</p>
|
||||
|
||||
<second-menu v-if="logs.length > 0">
|
||||
<a href="" class="item" @click.prevent="updatePageRead()">[本页已读]</a>
|
||||
</second-menu>
|
||||
|
||||
<table class="ui table selectable celled" v-if="logs.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th nowrap="">节点类型</th>
|
||||
<th nowrap="">集群</th>
|
||||
<th nowrap="">节点</th>
|
||||
<th>信息</th>
|
||||
<th class="one op">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr v-for="log in logs">
|
||||
<td>
|
||||
<node-role-name :v-role="log.role"></node-role-name>
|
||||
</td>
|
||||
<td nowrap="">
|
||||
<span v-if="log.role == 'node'">
|
||||
<link-icon :href="'/clusters/cluster?clusterId=' + log.node.cluster.id">{{log.node.cluster.name}}</link-icon>
|
||||
</span>
|
||||
<span v-else-if="log.role == 'dns'">
|
||||
<link-icon :href="'/ns/clusters/cluster?clusterId=' + log.node.cluster.id">{{log.node.cluster.name}}</link-icon>
|
||||
</span>
|
||||
<span v-else class="disabled">-</span>
|
||||
</td>
|
||||
<td nowrap="">
|
||||
<span v-if="log.role == 'node'">
|
||||
<link-icon :href="'/clusters/cluster/node?clusterId=' + log.node.cluster.id + '&nodeId=' + log.node.id">{{log.node.name}}</link-icon>
|
||||
</span>
|
||||
<span v-if="log.role == 'api'">
|
||||
<link-icon :href="'/api/node?nodeId=' + log.node.id">{{log.node.name}}</link-icon>
|
||||
</span>
|
||||
<span v-if="log.role == 'dns'">
|
||||
<link-icon :href="'/ns/clusters/cluster/node?clusterId=' + log.node.cluster.id + '&nodeId=' + log.node.id">{{log.node.name}}</link-icon>
|
||||
</span>
|
||||
<span v-if="log.role == 'database'">
|
||||
<link-icon :href="'/db/node?nodeId=' + log.node.id">{{log.node.name}}</link-icon>
|
||||
</span>
|
||||
<span v-if="log.role == 'admin'">管理平台</span>
|
||||
<span v-if="log.role == 'user'">
|
||||
<link-icon :href="'/settings/userNodes/node?nodeId=' + log.node.id">{{log.node.name}}</link-icon>
|
||||
</span>
|
||||
<span v-if="log.role == 'monitor'">
|
||||
<link-icon :href="'/settings/monitorNodes/node?nodeId=' + log.node.id">{{log.node.name}}</link-icon>
|
||||
</span>
|
||||
<span v-if="log.role == 'report'">
|
||||
<link-icon :href="'/clusters/monitors/reporters/reporter?reporterId=' + log.node.id">{{log.node.name}}</link-icon>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<node-log-row :v-log="log" :v-keyword="keyword"></node-log-row>
|
||||
</td>
|
||||
<td>
|
||||
<a href="" @click.prevent="updateRead(log.id)">已读</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="page" v-html="page"></div>
|
||||
24
web/views/@default/dashboard/boards/events.js
Normal file
24
web/views/@default/dashboard/boards/events.js
Normal file
@@ -0,0 +1,24 @@
|
||||
Tea.context(function () {
|
||||
this.updateRead = function (logId) {
|
||||
this.$post(".readEvents")
|
||||
.params({
|
||||
logIds: [logId]
|
||||
})
|
||||
.success(function () {
|
||||
teaweb.reload()
|
||||
})
|
||||
}
|
||||
|
||||
this.updatePageRead = function () {
|
||||
let logIds = this.logs.map(function (v) {
|
||||
return v.id
|
||||
})
|
||||
this.$post(".readEvents")
|
||||
.params({
|
||||
logIds: logIds
|
||||
})
|
||||
.success(function () {
|
||||
teaweb.reload()
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -1,4 +0,0 @@
|
||||
<first-menu>
|
||||
<menu-item href="/finance/bills" code="index">账单</menu-item>
|
||||
<menu-item href="/finance/bills/generate" code="generate">生成账单</menu-item>
|
||||
</first-menu>
|
||||
@@ -1,8 +0,0 @@
|
||||
{$layout}
|
||||
{$template "menu"}
|
||||
|
||||
<div class="margin"></div>
|
||||
<div>
|
||||
<button type="button" class="ui primary button" @click.prevent="generateBills()">生成上个月({{month}})账单</button>
|
||||
<p class="comment">多次点击不会重复生成同样的账单。</p>
|
||||
</div>
|
||||
@@ -1,16 +0,0 @@
|
||||
Tea.context(function () {
|
||||
this.generateBills = function () {
|
||||
let that = this
|
||||
teaweb.confirm("确定要生成上个月的账单吗?", function () {
|
||||
that.$post(".generate")
|
||||
.params({
|
||||
month: that.month
|
||||
})
|
||||
.success(function () {
|
||||
teaweb.success("生成成功", function () {
|
||||
window.location = "/finance/bills"
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -1,30 +0,0 @@
|
||||
{$layout}
|
||||
{$template "menu"}
|
||||
|
||||
<p class="comment" v-if="bills.length == 0">暂时还没有账单。</p>
|
||||
|
||||
<table class="ui table selectable" v-if="bills.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="width10">用户</th>
|
||||
<th class="width10">月份</th>
|
||||
<th class="width10">项目</th>
|
||||
<th class="width10">金额</th>
|
||||
<th>描述</th>
|
||||
<th class="width10">已支付</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr v-for="bill in bills">
|
||||
<td>{{bill.user.fullname}}</td>
|
||||
<td>{{bill.month}}</td>
|
||||
<td>{{bill.typeName}}</td>
|
||||
<td>¥{{bill.amount}}元</td>
|
||||
<td>{{bill.description}}</td>
|
||||
<td>
|
||||
<span class="green" v-if="bill.isPaid">Y</span>
|
||||
<span v-else class="disabled">N</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="page" v-html="page"></div>
|
||||
@@ -9,7 +9,7 @@
|
||||
<menu-item :href="'/servers/certs/acme?type=available&keyword=' + keyword" :active="type == 'available'">有效证书({{countAvailable}})</menu-item>
|
||||
<menu-item :href="'/servers/certs/acme?type=expired&keyword=' + keyword" :active="type == 'expired'">过期证书<span :class="{red: countExpired > 0}">({{countExpired}})</span></menu-item>
|
||||
<menu-item :href="'/servers/certs/acme?type=7days&keyword=' + keyword" :active="type == '7days'">7天内过期<span :class="{red: count7Days > 0}">({{count7Days}})</span></menu-item>
|
||||
<menu-item :href="'/servers/certs/acme?type=30days&keyword=' + keyword" :active="type == '30days'">30天过期({{count30Days}})</menu-item>
|
||||
<menu-item :href="'/servers/certs/acme?type=30days&keyword=' + keyword" :active="type == '30days'">30天内过期({{count30Days}})</menu-item>
|
||||
<div class="item right">
|
||||
<form class="ui form">
|
||||
<div class="ui fields inline">
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
<menu-item :href="'/servers/certs?type=available&keyword=' + keyword" :active="type == 'available'">有效证书({{countAvailable}})</menu-item>
|
||||
<menu-item :href="'/servers/certs?type=expired&keyword=' + keyword" :active="type == 'expired'">过期证书<span :class="{red: countExpired > 0}">({{countExpired}})</span></menu-item>
|
||||
<menu-item :href="'/servers/certs?type=7days&keyword=' + keyword" :active="type == '7days'">7天内过期<span :class="{red: count7Days > 0}">({{count7Days}})</span></menu-item>
|
||||
<menu-item :href="'/servers/certs?type=30days&keyword=' + keyword" :active="type == '30days'">30天过期({{count30Days}})</menu-item>
|
||||
<span class="item">|</span>
|
||||
<menu-item :href="'/servers/certs?type=30days&keyword=' + keyword" :active="type == '30days'">30天内过期({{count30Days}})</menu-item>
|
||||
<span class="item disabled">|</span>
|
||||
<a href="" class="item" @click.prevent="uploadCert">[上传证书]</a>
|
||||
</second-menu>
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user