Compare commits

...

25 Commits

Author SHA1 Message Date
刘祥超
ee61feb581 优化IPBox交互 2021-08-15 15:44:24 +08:00
刘祥超
7f04f1ed62 IPBox增加地域、ISP、添加到黑名单等功能 2021-08-15 15:42:05 +08:00
刘祥超
82646c3576 改进细节 2021-08-15 10:39:22 +08:00
刘祥超
b3f62240c4 节点选择认证时增加推荐 2021-08-14 21:33:48 +08:00
刘祥超
86a5992e8a 优化节点创建流程 2021-08-14 18:06:24 +08:00
刘祥超
82a731ed06 修复在MySQL8下安装提示无法创建edgeTest的问题 2021-08-13 19:37:56 +08:00
刘祥超
e650529efb 可以远程停止和启动DNS节点 2021-08-12 11:47:27 +08:00
刘祥超
1de57e124f DNS节点可以修改SSH登录相关信息 2021-08-12 11:04:28 +08:00
刘祥超
9e72dcc390 实现DNS节点远程安装 2021-08-11 20:59:58 +08:00
刘祥超
80019e2071 修改文字 2021-08-11 17:16:36 +08:00
刘祥超
ab7cdcd1b1 增加全局访问日志 2021-08-11 15:38:49 +08:00
刘祥超
7afe1e0a30 安全设置检查IP时同时也检查直接连接管理平台的上游IP 2021-08-11 10:01:23 +08:00
刘祥超
a39eb80214 安全设置中增加允许记住登录选项 2021-08-11 09:36:16 +08:00
刘祥超
10e2a08cd2 访问日志显示节点信息 2021-08-10 11:14:41 +08:00
刘祥超
6da4949c98 优化自建DNS交互 2021-08-10 10:47:12 +08:00
刘祥超
4d092f329b 自建DNS增加解析测试 2021-08-09 18:42:00 +08:00
刘祥超
69c1d35406 EdgeDNS支持内置线路 2021-08-09 13:56:11 +08:00
刘祥超
a256a7328e DNS节点增加在线状态通知 2021-08-08 10:29:57 +08:00
刘祥超
75c8658366 访问日志搜索增加域名和IP搜索 2021-08-07 22:04:30 +08:00
刘祥超
c3e68915a3 优化代码/支持IP名单的更多格式的导入、导出 2021-08-07 18:26:50 +08:00
刘祥超
d97f4da7fa 修复节点无法修改线路的Bug 2021-08-07 16:11:04 +08:00
刘祥超
06e81dbe37 调整版本为0.2.9 2021-08-07 15:41:21 +08:00
刘祥超
4a46aaa880 自建DNS增加全局配置 2021-08-05 16:08:18 +08:00
刘祥超
e766372a81 域名解析支持华为云解析DNS 2021-08-04 22:14:35 +08:00
刘祥超
c5ee9f095a 调整版本号 2021-08-04 15:36:30 +08:00
139 changed files with 3492 additions and 421 deletions

View File

@@ -1,5 +1,17 @@
#!/usr/bin/env bash
ROOT=$(dirname $0)
# build all nodes
if [ -f $ROOT"/../../EdgeNode/build/build-all-plus.sh" ]; then
echo "=============================="
echo "build all edge-node"
echo "=============================="
cd $ROOT"/../../EdgeNode/build"
./build-all-plus.sh
cd -
fi
./build.sh linux amd64
./build.sh linux 386
./build.sh linux arm64

View File

@@ -23,7 +23,7 @@ function build() {
VERSION=$(lookup-version $ROOT/../internal/const/const.go)
ZIP="${NAME}-${OS}-${ARCH}-${TAG}-v${VERSION}.zip"
# check edge-api
# build edge-api
APINodeVersion=$(lookup-version $ROOT"/../../EdgeAPI/internal/const/const.go")
echo "building edge-api v${APINodeVersion} ..."
EDGE_API_BUILD_SCRIPT=$ROOT"/../../EdgeAPI/build/build.sh"

3
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/TeaOSLab/EdgeAdmin
go 1.15
go 1.16
replace github.com/TeaOSLab/EdgeCommon => ../EdgeCommon
@@ -9,7 +9,6 @@ require (
github.com/cespare/xxhash v1.1.0
github.com/go-sql-driver/mysql v1.5.0
github.com/go-yaml/yaml v2.1.0+incompatible
github.com/golang/protobuf v1.5.2
github.com/google/go-cmp v0.5.6 // indirect
github.com/iwind/TeaGo v0.0.0-20210720011303-fc255c995afa
github.com/iwind/gosock v0.0.0-20210722083328-12b2d66abec3

2
go.sum
View File

@@ -141,6 +141,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@@ -164,6 +165,7 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View File

@@ -1,7 +1,7 @@
package teaconst
const (
Version = "0.2.7"
Version = "0.2.9"
ProductName = "Edge Admin"
ProcessName = "edge-admin"

View File

@@ -71,6 +71,10 @@ func (this *RPCClient) NodeGrantRPC() pb.NodeGrantServiceClient {
return pb.NewNodeGrantServiceClient(this.pickConn())
}
func (this *RPCClient) NodeLoginRPC() pb.NodeLoginServiceClient {
return pb.NewNodeLoginServiceClient(this.pickConn())
}
func (this *RPCClient) NodeClusterRPC() pb.NodeClusterServiceClient {
return pb.NewNodeClusterServiceClient(this.pickConn())
}
@@ -404,6 +408,10 @@ func (this *RPCClient) NSRPC() pb.NSServiceClient {
return pb.NewNSServiceClient(this.pickConn())
}
func (this *RPCClient) NSQuestionOptionRPC() pb.NSQuestionOptionServiceClient {
return pb.NewNSQuestionOptionServiceClient(this.pickConn())
}
func (this *RPCClient) MetricItemRPC() pb.MetricItemServiceClient {
return pb.NewMetricItemServiceClient(this.pickConn())
}

View File

@@ -39,6 +39,10 @@ func (this *IndexAction) RunGet(params struct {
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
recipientMaps := []maps.Map{}
for _, recipient := range recipientsResp.MessageRecipients {

View File

@@ -35,6 +35,10 @@ func (this *IndexAction) RunGet(params struct {
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
instanceMaps := []maps.Map{}
for _, instance := range instancesResp.MessageMediaInstances {

View File

@@ -4,11 +4,13 @@ import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/grants/grantutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"strconv"
"strings"
)
// CreateNodeAction 创建节点
@@ -67,6 +69,22 @@ func (this *CreateNodeAction) RunGet(params struct {
}
this.Data["dnsRoutes"] = dnsRouteMaps
// API节点列表
apiNodesResp, err := this.RPC().APINodeRPC().FindAllEnabledAPINodes(this.AdminContext(), &pb.FindAllEnabledAPINodesRequest{})
if err != nil {
this.ErrorPage(err)
return
}
apiNodes := apiNodesResp.Nodes
apiEndpoints := []string{}
for _, apiNode := range apiNodes {
if !apiNode.IsOn {
continue
}
apiEndpoints = append(apiEndpoints, apiNode.AccessAddrs...)
}
this.Data["apiEndpoints"] = "\"" + strings.Join(apiEndpoints, "\", \"") + "\""
this.Show()
}
@@ -174,5 +192,57 @@ func (this *CreateNodeAction) RunPost(params struct {
// 创建日志
defer this.CreateLog(oplogs.LevelInfo, "创建节点 %d", nodeId)
// 响应数据
this.Data["nodeId"] = nodeId
nodeResp, err := this.RPC().NodeRPC().FindEnabledNode(this.AdminContext(), &pb.FindEnabledNodeRequest{NodeId: nodeId})
if err != nil {
this.ErrorPage(err)
return
}
if nodeResp.Node != nil {
var addresses = []string{}
for _, addrMap := range ipAddresses {
addresses = append(addresses, addrMap.GetString("ip"))
}
var grantMap maps.Map = nil
grantId := params.GrantId
if grantId > 0 {
grantResp, err := this.RPC().NodeGrantRPC().FindEnabledNodeGrant(this.AdminContext(), &pb.FindEnabledNodeGrantRequest{NodeGrantId: grantId})
if err != nil {
this.ErrorPage(err)
return
}
if grantResp.NodeGrant != nil && grantResp.NodeGrant.Id > 0 {
grantMap = maps.Map{
"id": grantResp.NodeGrant.Id,
"name": grantResp.NodeGrant.Name,
"method": grantResp.NodeGrant.Method,
"methodName": grantutils.FindGrantMethodName(grantResp.NodeGrant.Method),
"username": grantResp.NodeGrant.Username,
}
}
}
this.Data["node"] = maps.Map{
"id": nodeResp.Node.Id,
"name": nodeResp.Node.Name,
"uniqueId": nodeResp.Node.UniqueId,
"secret": nodeResp.Node.Secret,
"addresses": addresses,
"login": maps.Map{
"id": 0,
"name": "SSH",
"type": "ssh",
"params": maps.Map{
"grantId": params.GrantId,
"host": params.SshHost,
"port": params.SshPort,
},
},
"grant": grantMap,
}
}
this.Success()
}

View File

@@ -0,0 +1,76 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package cluster
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type CreateNodeInstallAction struct {
actionutils.ParentAction
}
func (this *CreateNodeInstallAction) RunPost(params struct {
NodeId int64
SshHost string
SshPort int
GrantId int64
Must *actions.Must
}) {
defer this.CreateLogInfo("安装节点 %d", params.NodeId)
params.Must.
Field("sshHost2", params.SshHost).
Require("请填写SSH主机地址").
Field("sshPort2", params.SshPort).
Gt(0, "请填写SSH主机端口").
Lt(65535, "SSH主机端口需要小于65535").
Field("grantId", params.GrantId).
Gt(0, "请选择SSH登录认证")
// 查询login
nodeResp, err := this.RPC().NodeRPC().FindEnabledNode(this.AdminContext(), &pb.FindEnabledNodeRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
var node = nodeResp.Node
if node == nil {
this.Fail("找不到要修改的节点")
}
var loginId int64
if node.NodeLogin != nil {
loginId = node.NodeLogin.Id
}
// 修改节点信息
_, err = this.RPC().NodeRPC().UpdateNodeLogin(this.AdminContext(), &pb.UpdateNodeLoginRequest{
NodeId: params.NodeId,
NodeLogin: &pb.NodeLogin{
Id: loginId,
Name: "SSH",
Type: "ssh",
Params: maps.Map{
"grantId": params.GrantId,
"host": params.SshHost,
"port": params.SshPort,
}.AsJSON(),
},
})
if err != nil {
this.ErrorPage(err)
return
}
// 开始安装
_, err = this.RPC().NodeRPC().InstallNode(this.AdminContext(), &pb.InstallNodeRequest{NodeId: params.NodeId})
if err != nil {
this.Fail("安装失败:" + err.Error())
}
this.Success()
}

View File

@@ -27,9 +27,11 @@ func init() {
Post("/upgradeStatus", new(UpgradeStatusAction)).
GetPost("/delete", new(DeleteAction)).
GetPost("/createNode", new(CreateNodeAction)).
Post("/createNodeInstall", new(CreateNodeInstallAction)).
GetPost("/createBatch", new(CreateBatchAction)).
GetPost("/updateNodeSSH", new(UpdateNodeSSHAction)).
GetPost("/installManual", new(InstallManualAction)).
Post("/suggestLoginPorts", new(SuggestLoginPortsAction)).
// 节点相关
Prefix("/clusters/cluster/node").
@@ -58,7 +60,6 @@ func init() {
// 看板相关
Prefix("/clusters/cluster/boards").
Get("", new(boards.IndexAction)).
EndAll()
})
}

View File

@@ -30,8 +30,8 @@ func (this *InstallManualAction) RunGet(params struct {
nodeMaps := []maps.Map{}
for _, node := range nodesResp.Nodes {
loginParams := maps.Map{}
if node.Login != nil && len(node.Login.Params) > 0 {
err := json.Unmarshal(node.Login.Params, &loginParams)
if node.NodeLogin != nil && len(node.NodeLogin.Params) > 0 {
err := json.Unmarshal(node.NodeLogin.Params, &loginParams)
if err != nil {
this.ErrorPage(err)
return
@@ -56,7 +56,7 @@ func (this *InstallManualAction) RunGet(params struct {
"isOn": node.IsOn,
"name": node.Name,
"addresses": node.IpAddresses,
"login": node.Login,
"login": node.NodeLogin,
"loginParams": loginParams,
"installStatus": installStatus,
})

View File

@@ -32,8 +32,8 @@ func (this *InstallRemoteAction) RunGet(params struct {
nodeMaps := []maps.Map{}
for _, node := range nodesResp.Nodes {
loginParams := maps.Map{}
if node.Login != nil && len(node.Login.Params) > 0 {
err := json.Unmarshal(node.Login.Params, &loginParams)
if node.NodeLogin != nil && len(node.NodeLogin.Params) > 0 {
err := json.Unmarshal(node.NodeLogin.Params, &loginParams)
if err != nil {
this.ErrorPage(err)
return
@@ -58,7 +58,7 @@ func (this *InstallRemoteAction) RunGet(params struct {
"isOn": node.IsOn,
"name": node.Name,
"addresses": node.IpAddresses,
"login": node.Login,
"login": node.NodeLogin,
"loginParams": loginParams,
"installStatus": installStatus,
})

View File

@@ -141,10 +141,10 @@ func (this *DetailAction) RunGet(params struct {
// 登录信息
var loginMap maps.Map = nil
if node.Login != nil {
if node.NodeLogin != nil {
loginParams := maps.Map{}
if len(node.Login.Params) > 0 {
err = json.Unmarshal(node.Login.Params, &loginParams)
if len(node.NodeLogin.Params) > 0 {
err = json.Unmarshal(node.NodeLogin.Params, &loginParams)
if err != nil {
this.ErrorPage(err)
return
@@ -165,14 +165,15 @@ func (this *DetailAction) RunGet(params struct {
"name": grantResp.NodeGrant.Name,
"method": grantResp.NodeGrant.Method,
"methodName": grantutils.FindGrantMethodName(grantResp.NodeGrant.Method),
"username": grantResp.NodeGrant.Username,
}
}
}
loginMap = maps.Map{
"id": node.Login.Id,
"name": node.Login.Name,
"type": node.Login.Type,
"id": node.NodeLogin.Id,
"name": node.NodeLogin.Name,
"type": node.NodeLogin.Type,
"params": loginParams,
"grant": grantMap,
}

View File

@@ -9,7 +9,7 @@ import (
"strings"
)
// 安装节点
// InstallAction 安装节点
type InstallAction struct {
actionutils.ParentAction
}
@@ -97,7 +97,7 @@ func (this *InstallAction) RunGet(params struct {
this.Show()
}
// 开始安装
// RunPost 开始安装
func (this *InstallAction) RunPost(params struct {
NodeId int64

View File

@@ -62,6 +62,10 @@ func (this *LogsAction) RunGet(params struct {
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
logs := []maps.Map{}
for _, log := range logsResp.NodeLogs {

View File

@@ -126,10 +126,10 @@ func (this *UpdateAction) RunGet(params struct {
// 登录信息
var loginMap maps.Map = nil
if node.Login != nil {
if node.NodeLogin != nil {
loginParams := maps.Map{}
if len(node.Login.Params) > 0 {
err = json.Unmarshal(node.Login.Params, &loginParams)
if len(node.NodeLogin.Params) > 0 {
err = json.Unmarshal(node.NodeLogin.Params, &loginParams)
if err != nil {
this.ErrorPage(err)
return
@@ -150,14 +150,15 @@ func (this *UpdateAction) RunGet(params struct {
"name": grantResp.NodeGrant.Name,
"method": grantResp.NodeGrant.Method,
"methodName": grantutils.FindGrantMethodName(grantResp.NodeGrant.Method),
"username": grantResp.NodeGrant.Username,
}
}
}
loginMap = maps.Map{
"id": node.Login.Id,
"name": node.Login.Name,
"type": node.Login.Type,
"id": node.NodeLogin.Id,
"name": node.NodeLogin.Name,
"type": node.NodeLogin.Type,
"params": loginParams,
"grant": grantMap,
}

View File

@@ -0,0 +1,36 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package cluster
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type SuggestLoginPortsAction struct {
actionutils.ParentAction
}
func (this *SuggestLoginPortsAction) RunPost(params struct {
Host string
}) {
portsResp, err := this.RPC().NodeLoginRPC().FindNodeLoginSuggestPorts(this.AdminContext(), &pb.FindNodeLoginSuggestPortsRequest{Host: params.Host})
if err != nil {
this.ErrorPage(err)
return
}
if len(portsResp.Ports) == 0 {
this.Data["ports"] = []int32{}
} else {
this.Data["ports"] = portsResp.Ports
}
if len(portsResp.AvailablePorts) == 0 {
this.Data["availablePorts"] = []int32{}
} else {
this.Data["availablePorts"] = portsResp.AvailablePorts
}
this.Success()
}

View File

@@ -36,6 +36,11 @@ func (this *UpdateNodeSSHAction) RunGet(params struct {
"id": node.Id,
"name": node.Name,
}
if nodeResp.Node.NodeCluster != nil {
this.Data["clusterId"] = nodeResp.Node.NodeCluster.Id
} else {
this.Data["clusterId"] = 0
}
// SSH
loginParams := maps.Map{
@@ -44,10 +49,10 @@ func (this *UpdateNodeSSHAction) RunGet(params struct {
"grantId": 0,
}
this.Data["loginId"] = 0
if node.Login != nil {
this.Data["loginId"] = node.Login.Id
if len(node.Login.Params) > 0 {
err = json.Unmarshal(node.Login.Params, &loginParams)
if node.NodeLogin != nil {
this.Data["loginId"] = node.NodeLogin.Id
if len(node.NodeLogin.Params) > 0 {
err = json.Unmarshal(node.NodeLogin.Params, &loginParams)
if err != nil {
this.ErrorPage(err)
return

View File

@@ -31,8 +31,8 @@ func (this *UpgradeRemoteAction) RunGet(params struct {
}
for _, node := range resp.Nodes {
loginParams := maps.Map{}
if node.Node.Login != nil && len(node.Node.Login.Params) > 0 {
err := json.Unmarshal(node.Node.Login.Params, &loginParams)
if node.Node.NodeLogin != nil && len(node.Node.NodeLogin.Params) > 0 {
err := json.Unmarshal(node.Node.NodeLogin.Params, &loginParams)
if err != nil {
this.ErrorPage(err)
return
@@ -46,7 +46,7 @@ func (this *UpgradeRemoteAction) RunGet(params struct {
"arch": node.Arch,
"oldVersion": node.OldVersion,
"newVersion": node.NewVersion,
"login": node.Node.Login,
"login": node.Node.NodeLogin,
"loginParams": loginParams,
"addresses": node.Node.IpAddresses,
"installStatus": node.Node.InstallStatus,

View File

@@ -16,7 +16,10 @@ func (this *SelectPopupAction) Init() {
this.Nav("", "", "")
}
func (this *SelectPopupAction) RunGet(params struct{}) {
func (this *SelectPopupAction) RunGet(params struct {
NodeClusterId int64
NsClusterId int64
}) {
// 所有的认证
grantsResp, err := this.RPC().NodeGrantRPC().FindAllEnabledNodeGrants(this.AdminContext(), &pb.FindAllEnabledNodeGrantsRequest{})
if err != nil {
@@ -37,6 +40,28 @@ func (this *SelectPopupAction) RunGet(params struct{}) {
}
this.Data["grants"] = grantMaps
// 推荐的认证
suggestGrantsResp, err := this.RPC().NodeGrantRPC().FindSuggestNodeGrants(this.AdminContext(), &pb.FindSuggestNodeGrantsRequest{
NodeClusterId: params.NodeClusterId,
NsClusterId: params.NsClusterId,
})
if err != nil {
this.ErrorPage(err)
return
}
var suggestGrantMaps = []maps.Map{}
for _, grant := range suggestGrantsResp.NodeGrants {
suggestGrantMaps = append(suggestGrantMaps, maps.Map{
"id": grant.Id,
"name": grant.Name,
"method": grant.Method,
"methodName": grantutils.FindGrantMethodName(grant.Method),
"username": grant.Username,
"description": grant.Description,
})
}
this.Data["suggestGrants"] = suggestGrantMaps
this.Show()
}

View File

@@ -51,6 +51,10 @@ func (this *IndexAction) RunGet(params struct {
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
logs := []maps.Map{}
for _, log := range logsResp.NodeLogs {

View File

@@ -67,6 +67,10 @@ func (this *CreatePopupAction) RunPost(params struct {
ParamAccessKeyId string
ParamAccessKeySecret string
// HuaweiDNS
ParamHuaweiAccessKeyId string
ParamHuaweiAccessKeySecret string
// DNS.COM
ParamApiKey string
ParamApiSecret string
@@ -111,6 +115,15 @@ func (this *CreatePopupAction) RunPost(params struct {
apiParams["accessKeyId"] = params.ParamAccessKeyId
apiParams["accessKeySecret"] = params.ParamAccessKeySecret
case "huaweiDNS":
params.Must.
Field("paramHuaweiAccessKeyId", params.ParamHuaweiAccessKeyId).
Require("请输入AccessKeyId").
Field("paramHuaweiAccessKeySecret", params.ParamHuaweiAccessKeySecret).
Require("请输入AccessKeySecret")
apiParams["accessKeyId"] = params.ParamHuaweiAccessKeyId
apiParams["accessKeySecret"] = params.ParamHuaweiAccessKeySecret
case "dnscom":
params.Must.
Field("paramApiKey", params.ParamApiKey).

View File

@@ -96,6 +96,10 @@ func (this *UpdatePopupAction) RunPost(params struct {
ParamAccessKeyId string
ParamAccessKeySecret string
// HuaweiDNS
ParamHuaweiAccessKeyId string
ParamHuaweiAccessKeySecret string
// DNS.COM
ParamApiKey string
ParamApiSecret string
@@ -142,6 +146,15 @@ func (this *UpdatePopupAction) RunPost(params struct {
apiParams["accessKeyId"] = params.ParamAccessKeyId
apiParams["accessKeySecret"] = params.ParamAccessKeySecret
case "huaweiDNS":
params.Must.
Field("paramHuaweiAccessKeyId", params.ParamHuaweiAccessKeyId).
Require("请输入AccessKeyId").
Field("paramHuaweiAccessKeySecret", params.ParamHuaweiAccessKeySecret).
Require("请输入AccessKeySecret")
apiParams["accessKeyId"] = params.ParamHuaweiAccessKeyId
apiParams["accessKeySecret"] = params.ParamHuaweiAccessKeySecret
case "dnscom":
params.Must.
Field("paramApiKey", params.ParamApiKey).

View File

@@ -56,19 +56,26 @@ func (this *IndexAction) RunGet(params struct {
this.Data["token"] = stringutil.Md5(TokenSalt+timestamp) + timestamp
this.Data["from"] = params.From
config, err := configloaders.LoadAdminUIConfig()
uiConfig, err := configloaders.LoadAdminUIConfig()
if err != nil {
this.ErrorPage(err)
return
}
this.Data["systemName"] = config.AdminSystemName
this.Data["showVersion"] = config.ShowVersion
if len(config.Version) > 0 {
this.Data["version"] = config.Version
this.Data["systemName"] = uiConfig.AdminSystemName
this.Data["showVersion"] = uiConfig.ShowVersion
if len(uiConfig.Version) > 0 {
this.Data["version"] = uiConfig.Version
} else {
this.Data["version"] = teaconst.Version
}
this.Data["faviconFileId"] = config.FaviconFileId
this.Data["faviconFileId"] = uiConfig.FaviconFileId
securityConfig, err := configloaders.LoadSecurityConfig()
if err != nil {
this.Data["rememberLogin"] = false
} else {
this.Data["rememberLogin"] = securityConfig.AllowRememberLogin
}
this.Show()
}

View File

@@ -55,6 +55,7 @@ func (this *IndexAction) RunGet(params struct{}) {
messages = append(messages, maps.Map{
"id": message.Id,
"role": message.Role,
"isRead": message.IsRead,
"body": message.Body,
"level": message.Level,

View File

@@ -112,7 +112,7 @@ func (this *IndexAction) RunGet(params struct {
"error": node.InstallStatus.Error,
},
"status": maps.Map{
"isActive": status.IsActive,
"isActive": node.IsActive,
"updatedAt": status.UpdatedAt,
"hostname": status.Hostname,
"cpuUsage": status.CPUUsage,

View File

@@ -21,6 +21,7 @@ func init() {
GetPost("/createNode", new(CreateNodeAction)).
Post("/deleteNode", new(DeleteNodeAction)).
Get("/upgradeRemote", new(UpgradeRemoteAction)).
GetPost("/updateNodeSSH", new(UpdateNodeSSHAction)).
// 节点相关
Prefix("/ns/clusters/cluster/node").
@@ -30,7 +31,8 @@ func init() {
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()
})
}

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"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/maps"
@@ -102,6 +103,46 @@ func (this *IndexAction) RunGet(params struct {
this.Data["newVersion"] = ""
}
// 登录信息
var loginMap maps.Map = nil
if node.NodeLogin != nil {
loginParams := maps.Map{}
if len(node.NodeLogin.Params) > 0 {
err = json.Unmarshal(node.NodeLogin.Params, &loginParams)
if err != nil {
this.ErrorPage(err)
return
}
}
grantMap := maps.Map{}
grantId := loginParams.GetInt64("grantId")
if grantId > 0 {
grantResp, err := this.RPC().NodeGrantRPC().FindEnabledNodeGrant(this.AdminContext(), &pb.FindEnabledNodeGrantRequest{NodeGrantId: grantId})
if err != nil {
this.ErrorPage(err)
return
}
if grantResp.NodeGrant != nil {
grantMap = maps.Map{
"id": grantResp.NodeGrant.Id,
"name": grantResp.NodeGrant.Name,
"method": grantResp.NodeGrant.Method,
"methodName": grantutils.FindGrantMethodName(grantResp.NodeGrant.Method),
"username": grantResp.NodeGrant.Username,
}
}
}
loginMap = maps.Map{
"id": node.NodeLogin.Id,
"name": node.NodeLogin.Name,
"type": node.NodeLogin.Type,
"params": loginParams,
"grant": grantMap,
}
}
this.Data["node"] = maps.Map{
"id": node.Id,
"name": node.Name,
@@ -114,7 +155,7 @@ func (this *IndexAction) RunGet(params struct {
"isOn": node.IsOn,
"status": maps.Map{
"isActive": status.IsActive,
"isActive": node.IsActive && status.IsActive,
"updatedAt": status.UpdatedAt,
"hostname": status.Hostname,
"cpuUsage": status.CPUUsage,
@@ -131,6 +172,8 @@ func (this *IndexAction) RunGet(params struct {
"cacheTotalDiskSize": numberutils.FormatBytes(status.CacheTotalDiskSize),
"cacheTotalMemorySize": numberutils.FormatBytes(status.CacheTotalMemorySize),
},
"login": loginMap,
}
this.Show()

View File

@@ -0,0 +1,30 @@
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type StartAction struct {
actionutils.ParentAction
}
func (this *StartAction) RunPost(params struct {
NodeId int64
}) {
resp, err := this.RPC().NSNodeRPC().StartNSNode(this.AdminContext(), &pb.StartNSNodeRequest{NsNodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
// 创建日志
defer this.CreateLog(oplogs.LevelInfo, "远程启动节点 %d", params.NodeId)
if resp.IsOk {
this.Success()
}
this.Fail("启动失败:" + resp.Error)
}

View File

@@ -0,0 +1,30 @@
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type StopAction struct {
actionutils.ParentAction
}
func (this *StopAction) RunPost(params struct {
NodeId int64
}) {
resp, err := this.RPC().NSNodeRPC().StopNSNode(this.AdminContext(), &pb.StopNSNodeRequest{NsNodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
// 创建日志
defer this.CreateLog(oplogs.LevelInfo, "远程停止节点 %d", params.NodeId)
if resp.IsOk {
this.Success()
}
this.Fail("执行失败:" + resp.Error)
}

View File

@@ -4,6 +4,7 @@ import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/grants/grantutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/nodes/ipAddresses/ipaddressutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
@@ -63,12 +64,53 @@ func (this *UpdateAction) RunGet(params struct {
})
}
// 登录信息
var loginMap maps.Map = nil
if node.NodeLogin != nil {
loginParams := maps.Map{}
if len(node.NodeLogin.Params) > 0 {
err = json.Unmarshal(node.NodeLogin.Params, &loginParams)
if err != nil {
this.ErrorPage(err)
return
}
}
grantMap := maps.Map{}
grantId := loginParams.GetInt64("grantId")
if grantId > 0 {
grantResp, err := this.RPC().NodeGrantRPC().FindEnabledNodeGrant(this.AdminContext(), &pb.FindEnabledNodeGrantRequest{NodeGrantId: grantId})
if err != nil {
this.ErrorPage(err)
return
}
if grantResp.NodeGrant != nil {
grantMap = maps.Map{
"id": grantResp.NodeGrant.Id,
"name": grantResp.NodeGrant.Name,
"method": grantResp.NodeGrant.Method,
"methodName": grantutils.FindGrantMethodName(grantResp.NodeGrant.Method),
"username": grantResp.NodeGrant.Username,
}
}
}
loginMap = maps.Map{
"id": node.NodeLogin.Id,
"name": node.NodeLogin.Name,
"type": node.NodeLogin.Type,
"params": loginParams,
"grant": grantMap,
}
}
this.Data["node"] = maps.Map{
"id": node.Id,
"name": node.Name,
"ipAddresses": ipAddressMaps,
"cluster": clusterMap,
"isOn": node.IsOn,
"login": loginMap,
}
// 所有集群
@@ -93,7 +135,11 @@ func (this *UpdateAction) RunGet(params struct {
}
func (this *UpdateAction) RunPost(params struct {
LoginId int64
LoginId int64
GrantId int64
SshHost string
SshPort int
NodeId int64
Name string
IPAddressesJSON []byte `alias:"ipAddressesJSON"`
@@ -139,12 +185,25 @@ func (this *UpdateAction) RunPost(params struct {
this.Fail("请至少输入一个IP地址")
}
// TODO 检查登录授权
loginInfo := &pb.NodeLogin{
Id: params.LoginId,
Name: "SSH",
Type: "ssh",
Params: maps.Map{
"grantId": params.GrantId,
"host": params.SshHost,
"port": params.SshPort,
}.AsJSON(),
}
// 保存
_, err = this.RPC().NSNodeRPC().UpdateNSNode(this.AdminContext(), &pb.UpdateNSNodeRequest{
NsNodeId: params.NodeId,
Name: params.Name,
NsClusterId: params.ClusterId,
IsOn: params.IsOn,
NodeLogin: loginInfo,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -28,7 +28,7 @@ func (this *IndexAction) RunGet(params struct {
return
}
accessLogRef := &dnsconfigs.AccessLogRef{}
accessLogRef := &dnsconfigs.NSAccessLogRef{}
if len(accessLogResp.AccessLogJSON) > 0 {
err = json.Unmarshal(accessLogResp.AccessLogJSON, accessLogRef)
if err != nil {
@@ -50,7 +50,7 @@ func (this *IndexAction) RunPost(params struct {
}) {
defer this.CreateLogInfo("修改域名服务集群 %d 访问日志配置", params.ClusterId)
ref := &dnsconfigs.AccessLogRef{}
ref := &dnsconfigs.NSAccessLogRef{}
err := json.Unmarshal(params.AccessLogJSON, ref)
if err != nil {
this.Fail("数据格式错误:" + err.Error())

View File

@@ -0,0 +1,129 @@
package cluster
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/grants/grantutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type UpdateNodeSSHAction struct {
actionutils.ParentAction
}
func (this *UpdateNodeSSHAction) Init() {
this.Nav("", "", "")
}
func (this *UpdateNodeSSHAction) RunGet(params struct {
NodeId int64
}) {
nodeResp, err := this.RPC().NSNodeRPC().FindEnabledNSNode(this.AdminContext(), &pb.FindEnabledNSNodeRequest{NsNodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
if nodeResp.NsNode == nil {
this.NotFound("node", params.NodeId)
return
}
node := nodeResp.NsNode
this.Data["node"] = maps.Map{
"id": node.Id,
"name": node.Name,
}
if nodeResp.NsNode.NsCluster != nil {
this.Data["clusterId"] = nodeResp.NsNode.NsCluster.Id
} else {
this.Data["clusterId"] = 0
}
// SSH
loginParams := maps.Map{
"host": "",
"port": "",
"grantId": 0,
}
this.Data["loginId"] = 0
if node.NodeLogin != nil {
this.Data["loginId"] = node.NodeLogin.Id
if len(node.NodeLogin.Params) > 0 {
err = json.Unmarshal(node.NodeLogin.Params, &loginParams)
if err != nil {
this.ErrorPage(err)
return
}
}
}
this.Data["params"] = loginParams
// 认证信息
grantId := loginParams.GetInt64("grantId")
grantResp, err := this.RPC().NodeGrantRPC().FindEnabledNodeGrant(this.AdminContext(), &pb.FindEnabledNodeGrantRequest{NodeGrantId: grantId})
if err != nil {
this.ErrorPage(err)
}
var grantMap maps.Map = nil
if grantResp.NodeGrant != nil {
grantMap = maps.Map{
"id": grantResp.NodeGrant.Id,
"name": grantResp.NodeGrant.Name,
"method": grantResp.NodeGrant.Method,
"methodName": grantutils.FindGrantMethodName(grantResp.NodeGrant.Method),
}
}
this.Data["grant"] = grantMap
this.Show()
}
func (this *UpdateNodeSSHAction) RunPost(params struct {
NodeId int64
LoginId int64
SshHost string
SshPort int
GrantId int64
Must *actions.Must
}) {
params.Must.
Field("sshHost", params.SshHost).
Require("请输入SSH主机地址").
Field("sshPort", params.SshPort).
Gt(0, "SSH主机端口需要大于0").
Lt(65535, "SSH主机端口需要小于65535")
if params.GrantId <= 0 {
this.Fail("需要选择或填写至少一个认证信息")
}
login := &pb.NodeLogin{
Id: params.LoginId,
Name: "SSH",
Type: "ssh",
Params: maps.Map{
"grantId": params.GrantId,
"host": params.SshHost,
"port": params.SshPort,
}.AsJSON(),
}
_, err := this.RPC().NSNodeRPC().UpdateNSNodeLogin(this.AdminContext(), &pb.UpdateNSNodeLoginRequest{
NsNodeId: params.NodeId,
NodeLogin: login,
})
if err != nil {
this.ErrorPage(err)
return
}
// 创建日志
defer this.CreateLog(oplogs.LevelInfo, "修改节点 %d 配置", params.NodeId)
this.Success()
}

View File

@@ -20,7 +20,7 @@ func (this *CreateAction) Init() {
func (this *CreateAction) RunGet(params struct{}) {
// 默认的访问日志设置
this.Data["accessLogRef"] = &dnsconfigs.AccessLogRef{
this.Data["accessLogRef"] = &dnsconfigs.NSAccessLogRef{
IsOn: true,
}
@@ -44,7 +44,7 @@ func (this *CreateAction) RunPost(params struct {
Require("请输入集群名称")
// 校验访问日志设置
ref := &dnsconfigs.AccessLogRef{}
ref := &dnsconfigs.NSAccessLogRef{}
err := json.Unmarshal(params.AccessLogJSON, ref)
if err != nil {
this.Fail("数据格式错误:" + err.Error())

View File

@@ -0,0 +1,96 @@
package domains
import (
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"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.FirstMenu("domain")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
UserId int64
Keyword string
}) {
if !teaconst.IsPlus {
this.RedirectURL("/")
return
}
this.Data["clusterId"] = params.ClusterId
this.Data["userId"] = params.UserId
this.Data["keyword"] = params.Keyword
// 集群数量
countClustersResp, err := this.RPC().NSClusterRPC().CountAllEnabledNSClusters(this.AdminContext(), &pb.CountAllEnabledNSClustersRequest{})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["countClusters"] = countClustersResp.Count
// 分页
countResp, err := this.RPC().NSDomainRPC().CountAllEnabledNSDomains(this.AdminContext(), &pb.CountAllEnabledNSDomainsRequest{
UserId: params.UserId,
NsClusterId: params.ClusterId,
Keyword: params.Keyword,
})
if err != nil {
this.ErrorPage(err)
return
}
page := this.NewPage(countResp.Count)
// 列表
domainsResp, err := this.RPC().NSDomainRPC().ListEnabledNSDomains(this.AdminContext(), &pb.ListEnabledNSDomainsRequest{
UserId: params.UserId,
NsClusterId: params.ClusterId,
Keyword: params.Keyword,
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
domainMaps := []maps.Map{}
for _, domain := range domainsResp.NsDomains {
// 集群信息
var clusterMap maps.Map
if domain.NsCluster != nil {
clusterMap = maps.Map{
"id": domain.NsCluster.Id,
"name": domain.NsCluster.Name,
}
}
// 用户信息
var userMap maps.Map
if domain.User != nil {
userMap = maps.Map{
"id": domain.User.Id,
"username": domain.User.Username,
"fullname": domain.User.Fullname,
}
}
domainMaps = append(domainMaps, maps.Map{
"id": domain.Id,
"name": domain.Name,
"isOn": domain.IsOn,
"cluster": clusterMap,
"user": userMap,
})
}
this.Data["domains"] = domainMaps
this.Show()
}

View File

@@ -54,7 +54,7 @@ func (this *CreatePopupAction) RunPost(params struct {
Value string
Ttl int32
Description string
RouteIds []int64
RouteCodes []string
Must *actions.Must
CSRF *actionutils.CSRF
@@ -76,13 +76,13 @@ func (this *CreatePopupAction) RunPost(params struct {
}
createResp, err := this.RPC().NSRecordRPC().CreateNSRecord(this.AdminContext(), &pb.CreateNSRecordRequest{
NsDomainId: params.DomainId,
Description: params.Description,
Name: params.Name,
Type: params.Type,
Value: params.Value,
Ttl: params.Ttl,
NsRouteIds: params.RouteIds,
NsDomainId: params.DomainId,
Description: params.Description,
Name: params.Name,
Type: params.Type,
Value: params.Value,
Ttl: params.Ttl,
NsRouteCodes: params.RouteCodes,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -22,7 +22,7 @@ func (this *IndexAction) RunGet(params struct {
DomainId int64
Type string
Keyword string
RouteId int64
RouteCode string
}) {
// 初始化域名信息
err := domainutils.InitDomain(this.Parent(), params.DomainId)
@@ -33,14 +33,14 @@ func (this *IndexAction) RunGet(params struct {
this.Data["type"] = params.Type
this.Data["keyword"] = params.Keyword
this.Data["routeId"] = params.RouteId
this.Data["routeCode"] = params.RouteCode
// 记录
countResp, err := this.RPC().NSRecordRPC().CountAllEnabledNSRecords(this.AdminContext(), &pb.CountAllEnabledNSRecordsRequest{
NsDomainId: params.DomainId,
Type: params.Type,
NsRouteId: params.RouteId,
Keyword: params.Keyword,
NsDomainId: params.DomainId,
Type: params.Type,
NsRouteCode: params.RouteCode,
Keyword: params.Keyword,
})
if err != nil {
this.ErrorPage(err)
@@ -51,12 +51,12 @@ func (this *IndexAction) RunGet(params struct {
this.Data["page"] = page.AsHTML()
recordsResp, err := this.RPC().NSRecordRPC().ListEnabledNSRecords(this.AdminContext(), &pb.ListEnabledNSRecordsRequest{
NsDomainId: params.DomainId,
Type: params.Type,
NsRouteId: params.RouteId,
Keyword: params.Keyword,
Offset: page.Offset,
Size: page.Size,
NsDomainId: params.DomainId,
Type: params.Type,
NsRouteCode: params.RouteCode,
Keyword: params.Keyword,
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -33,11 +33,6 @@ func (this *UpdatePopupAction) RunGet(params struct {
return
}
routeIds := []int64{}
for _, route := range record.NsRoutes {
routeIds = append(routeIds, route.Id)
}
this.Data["record"] = maps.Map{
"id": record.Id,
"name": record.Name,
@@ -47,7 +42,7 @@ func (this *UpdatePopupAction) RunGet(params struct {
"weight": record.Weight,
"description": record.Description,
"isOn": record.IsOn,
"routeIds": routeIds,
"routes": record.NsRoutes,
}
// 域名信息
@@ -83,7 +78,7 @@ func (this *UpdatePopupAction) RunPost(params struct {
Ttl int32
Description string
IsOn bool
RouteIds []int64
RouteCodes []string
Must *actions.Must
CSRF *actionutils.CSRF
@@ -102,14 +97,14 @@ func (this *UpdatePopupAction) RunPost(params struct {
}
_, err := this.RPC().NSRecordRPC().UpdateNSRecord(this.AdminContext(), &pb.UpdateNSRecordRequest{
NsRecordId: params.RecordId,
Description: params.Description,
Name: params.Name,
Type: params.Type,
Value: params.Value,
Ttl: params.Ttl,
IsOn: params.IsOn,
NsRouteIds: params.RouteIds,
NsRecordId: params.RecordId,
Description: params.Description,
Name: params.Name,
Type: params.Type,
Value: params.Value,
Ttl: params.Ttl,
IsOn: params.IsOn,
NsRouteCodes: params.RouteCodes,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -1,10 +1,13 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package ns
import (
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type IndexAction struct {
@@ -12,85 +15,113 @@ type IndexAction struct {
}
func (this *IndexAction) Init() {
this.FirstMenu("index")
this.Nav("", "", "dns")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
UserId int64
Keyword string
}) {
if !teaconst.IsPlus {
this.RedirectURL("/")
return
}
this.Data["clusterId"] = params.ClusterId
this.Data["userId"] = params.UserId
this.Data["keyword"] = params.Keyword
// 集群数量
countClustersResp, err := this.RPC().NSClusterRPC().CountAllEnabledNSClusters(this.AdminContext(), &pb.CountAllEnabledNSClustersRequest{})
func (this *IndexAction) RunGet(params struct{}) {
resp, err := this.RPC().NSRPC().ComposeNSBoard(this.AdminContext(), &pb.ComposeNSBoardRequest{})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["countClusters"] = countClustersResp.Count
// 分页
countResp, err := this.RPC().NSDomainRPC().CountAllEnabledNSDomains(this.AdminContext(), &pb.CountAllEnabledNSDomainsRequest{
UserId: params.UserId,
NsClusterId: params.ClusterId,
Keyword: params.Keyword,
})
if err != nil {
this.ErrorPage(err)
return
this.Data["board"] = maps.Map{
"countDomains": resp.CountNSDomains,
"countRecords": resp.CountNSRecords,
"countClusters": resp.CountNSClusters,
"countNodes": resp.CountNSNodes,
"countOfflineNodes": resp.CountOfflineNSNodes,
}
page := this.NewPage(countResp.Count)
// 列表
domainsResp, err := this.RPC().NSDomainRPC().ListEnabledNSDomains(this.AdminContext(), &pb.ListEnabledNSDomainsRequest{
UserId: params.UserId,
NsClusterId: params.ClusterId,
Keyword: params.Keyword,
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
domainMaps := []maps.Map{}
for _, domain := range domainsResp.NsDomains {
// 集群信息
var clusterMap maps.Map
if domain.NsCluster != nil {
clusterMap = maps.Map{
"id": domain.NsCluster.Id,
"name": domain.NsCluster.Name,
}
// 流量排行
{
var statMaps = []maps.Map{}
for _, stat := range resp.HourlyTrafficStats {
statMaps = append(statMaps, maps.Map{
"day": stat.Hour[4:6] + "月" + stat.Hour[6:8] + "日",
"hour": stat.Hour[8:],
"countRequests": stat.CountRequests,
"bytes": stat.Bytes,
})
}
// 用户信息
var userMap maps.Map
if domain.User != nil {
userMap = maps.Map{
"id": domain.User.Id,
"username": domain.User.Username,
"fullname": domain.User.Fullname,
}
}
domainMaps = append(domainMaps, maps.Map{
"id": domain.Id,
"name": domain.Name,
"isOn": domain.IsOn,
"cluster": clusterMap,
"user": userMap,
})
this.Data["hourlyStats"] = statMaps
}
{
var statMaps = []maps.Map{}
for _, stat := range resp.DailyTrafficStats {
statMaps = append(statMaps, maps.Map{
"day": stat.Day[4:6] + "月" + stat.Day[6:] + "日",
"countRequests": stat.CountRequests,
"bytes": stat.Bytes,
})
}
this.Data["dailyStats"] = statMaps
}
// 域名排行
{
var statMaps = []maps.Map{}
for _, stat := range resp.TopNSDomainStats {
statMaps = append(statMaps, maps.Map{
"domainId": stat.NsDomainId,
"domainName": stat.NsDomainName,
"countRequests": stat.CountRequests,
"bytes": stat.Bytes,
})
}
this.Data["topDomainStats"] = statMaps
}
// 节点排行
{
var statMaps = []maps.Map{}
for _, stat := range resp.TopNSNodeStats {
statMaps = append(statMaps, maps.Map{
"clusterId": stat.NsClusterId,
"nodeId": stat.NsNodeId,
"nodeName": stat.NsNodeName,
"countRequests": stat.CountRequests,
"bytes": stat.Bytes,
})
}
this.Data["topNodeStats"] = statMaps
}
// CPU
{
var statMaps = []maps.Map{}
for _, stat := range resp.CpuNodeValues {
statMaps = append(statMaps, maps.Map{
"time": timeutil.FormatTime("H:i", stat.CreatedAt),
"value": types.Float32(string(stat.ValueJSON)),
})
}
this.Data["cpuValues"] = statMaps
}
// Memory
{
var statMaps = []maps.Map{}
for _, stat := range resp.MemoryNodeValues {
statMaps = append(statMaps, maps.Map{
"time": timeutil.FormatTime("H:i", stat.CreatedAt),
"value": types.Float32(string(stat.ValueJSON)),
})
}
this.Data["memoryValues"] = statMaps
}
// Load
{
var statMaps = []maps.Map{}
for _, stat := range resp.LoadNodeValues {
statMaps = append(statMaps, maps.Map{
"time": timeutil.FormatTime("H:i", stat.CreatedAt),
"value": types.Float32(string(stat.ValueJSON)),
})
}
this.Data["loadValues"] = statMaps
}
this.Data["domains"] = domainMaps
this.Show()
}

View File

@@ -5,6 +5,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/ns/domains"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/ns/domains/keys"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/ns/domains/records"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/ns/settings"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
@@ -19,6 +20,8 @@ func init() {
// 域名相关
Prefix("/ns/domains").
Data("teaSubMenu", "domain").
Get("", new(domains.IndexAction)).
GetPost("/create", new(domains.CreateAction)).
Post("/delete", new(domains.DeleteAction)).
Get("/domain", new(domains.DomainAction)).
@@ -27,6 +30,7 @@ func init() {
// 域名密钥
Prefix("/ns/domains/keys").
Data("teaSubMenu", "domain").
Get("", new(keys.IndexAction)).
GetPost("/createPopup", new(keys.CreatePopupAction)).
GetPost("/updatePopup", new(keys.UpdatePopupAction)).
@@ -40,6 +44,9 @@ func init() {
GetPost("/updatePopup", new(records.UpdatePopupAction)).
Post("/delete", new(records.DeleteAction)).
// 设置
Prefix("/ns/settings").
Get("", new(settings.IndexAction)).
EndAll()
})
}

View File

@@ -4,8 +4,10 @@ package clusters
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/dnsconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
)
type OptionsAction struct {
@@ -17,6 +19,18 @@ func (this *OptionsAction) RunPost(params struct {
DomainId int64
UserId int64
}) {
var routeMaps = []maps.Map{}
// 默认线路
for _, route := range dnsconfigs.AllDefaultRoutes {
routeMaps = append(routeMaps, maps.Map{
"name": route.Name,
"code": route.Code,
"type": "default",
})
}
// 自定义
routesResp, err := this.RPC().NSRouteRPC().FindAllEnabledNSRoutes(this.AdminContext(), &pb.FindAllEnabledNSRoutesRequest{
NsClusterId: params.ClusterId,
NsDomainId: params.DomainId,
@@ -27,13 +41,45 @@ func (this *OptionsAction) RunPost(params struct {
return
}
routeMaps := []maps.Map{}
for _, route := range routesResp.NsRoutes {
if len(route.Code) == 0 {
route.Code = "id:" + types.String(route.Id)
}
routeMaps = append(routeMaps, maps.Map{
"id": route.Id,
"name": route.Name,
"code": route.Code,
"type": "user",
})
}
// 运营商
for _, route := range dnsconfigs.AllDefaultISPRoutes {
routeMaps = append(routeMaps, maps.Map{
"name": route.Name,
"code": route.Code,
"type": "isp",
})
}
// 中国
for _, route := range dnsconfigs.AllDefaultChinaProvinceRoutes {
routeMaps = append(routeMaps, maps.Map{
"name": route.Name,
"code": route.Code,
"type": "china",
})
}
// 全球
for _, route := range dnsconfigs.AllDefaultWorldRegionRoutes {
routeMaps = append(routeMaps, maps.Map{
"name": route.Name,
"code": route.Code,
"type": "world",
})
}
this.Data["routes"] = routeMaps
this.Success()

View File

@@ -0,0 +1,77 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package accesslogs
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/dnsconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
"github.com/iwind/TeaGo/actions"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "")
this.SecondMenu("accessLog")
}
func (this *IndexAction) RunGet(params struct{}) {
resp, err := this.RPC().SysSettingRPC().ReadSysSetting(this.AdminContext(), &pb.ReadSysSettingRequest{
Code: systemconfigs.SettingCodeNSAccessLogSetting,
})
if err != nil {
this.ErrorPage(err)
return
}
var config = &dnsconfigs.NSAccessLogRef{}
if len(resp.ValueJSON) > 0 {
err = json.Unmarshal(resp.ValueJSON, config)
if err != nil {
this.ErrorPage(err)
return
}
} else {
config.IsOn = true
config.LogMissingDomains = true
}
this.Data["config"] = config
this.Show()
}
func (this *IndexAction) RunPost(params struct {
AccessLogJSON []byte
Must *actions.Must
CSRF *actionutils.CSRF
}) {
// 校验配置
var config = &dnsconfigs.NSAccessLogRef{}
err := json.Unmarshal(params.AccessLogJSON, config)
if err != nil {
this.Fail("配置解析失败:" + err.Error())
}
err = config.Init()
if err != nil {
this.Fail("配置校验失败:" + err.Error())
}
_, err = this.RPC().SysSettingRPC().UpdateSysSetting(this.AdminContext(), &pb.UpdateSysSettingRequest{
Code: systemconfigs.SettingCodeNSAccessLogSetting,
ValueJSON: params.AccessLogJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,23 @@
// 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()
})
}

View File

@@ -0,0 +1,17 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package settings
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{}) {
this.RedirectURL("/ns/settings/accesslogs")
}

View File

@@ -0,0 +1,28 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package settingutils
import (
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type Helper struct {
}
func (this *Helper) BeforeAction(actionPtr actions.ActionWrapper) (goNext bool) {
var action = actionPtr.Object()
secondMenuItem := action.Data.GetString("secondMenuItem")
action.Data["leftMenuItems"] = this.createSettingMenu(secondMenuItem)
return true
}
func (this *Helper) createSettingMenu(selectedItem string) (items []maps.Map) {
return []maps.Map{
{
"name": "访问日志",
"url": "/ns/settings/accesslogs",
"isActive": selectedItem == "accessLog",
},
}
}

View File

@@ -0,0 +1,156 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package test
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dns/domains/domainutils"
"github.com/TeaOSLab/EdgeCommon/pkg/dnsconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"github.com/miekg/dns"
"net"
"regexp"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "")
}
func (this *IndexAction) RunGet(params struct{}) {
// 集群列表
clustersResp, err := this.RPC().NSClusterRPC().FindAllEnabledNSClusters(this.AdminContext(), &pb.FindAllEnabledNSClustersRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var clusterMaps = []maps.Map{}
for _, cluster := range clustersResp.NsClusters {
if !cluster.IsOn {
continue
}
countNodesResp, err := this.RPC().NSNodeRPC().CountAllEnabledNSNodesMatch(this.AdminContext(), &pb.CountAllEnabledNSNodesMatchRequest{
NsClusterId: cluster.Id,
InstallState: 0,
ActiveState: 0,
Keyword: "",
})
if err != nil {
this.ErrorPage(err)
return
}
var countNodes = countNodesResp.Count
if countNodes <= 0 {
continue
}
clusterMaps = append(clusterMaps, maps.Map{
"id": cluster.Id,
"name": cluster.Name,
"countNodes": countNodes,
})
}
this.Data["clusters"] = clusterMaps
// 记录类型
this.Data["recordTypes"] = dnsconfigs.FindAllRecordTypeDefinitions()
this.Show()
}
func (this *IndexAction) RunPost(params struct {
NodeId int64
Domain string
Type string
Ip string
ClientIP string
Must *actions.Must
}) {
nodeResp, err := this.RPC().NSNodeRPC().FindEnabledNSNode(this.AdminContext(), &pb.FindEnabledNSNodeRequest{NsNodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
var node = nodeResp.NsNode
if node == nil {
this.Fail("找不到要测试的节点")
}
var isOk = false
var errMsg string
var isNetError = false
var result string
defer func() {
this.Data["isOk"] = isOk
this.Data["err"] = errMsg
this.Data["isNetErr"] = isNetError
this.Data["result"] = result
this.Success()
}()
if !domainutils.ValidateDomainFormat(params.Domain) {
errMsg = "域名格式错误"
return
}
recordType, ok := dns.StringToType[params.Type]
if !ok {
errMsg = "不支持此记录类型"
return
}
if len(params.ClientIP) > 0 && net.ParseIP(params.ClientIP) == nil {
errMsg = "客户端IP格式不正确"
return
}
var optionId int64
if len(params.ClientIP) > 0 {
optionResp, err := this.RPC().NSQuestionOptionRPC().CreateNSQuestionOption(this.AdminContext(), &pb.CreateNSQuestionOptionRequest{
Name: "setRemoteAddr",
ValuesJSON: maps.Map{"ip": params.ClientIP}.AsJSON(),
})
if err != nil {
this.ErrorPage(err)
return
}
optionId = optionResp.NsQuestionOptionId
defer func() {
_, err = this.RPC().NSQuestionOptionRPC().DeleteNSQuestionOption(this.AdminContext(), &pb.DeleteNSQuestionOptionRequest{NsQuestionOptionId: optionId})
if err != nil {
this.ErrorPage(err)
}
}()
}
c := new(dns.Client)
m := new(dns.Msg)
var domain = params.Domain + "."
if optionId > 0 {
domain = "$" + types.String(optionId) + "-" + domain
}
m.SetQuestion(domain, recordType)
r, _, err := c.Exchange(m, params.Ip+":53")
if err != nil {
errMsg = "解析过程中出错:" + err.Error()
// 是否为网络错误
if regexp.MustCompile(`timeout|connect`).MatchString(err.Error()) {
isNetError = true
}
return
}
result = r.String()
result = regexp.MustCompile(`\$\d+-`).ReplaceAllString(result, "")
isOk = true
}

View File

@@ -0,0 +1,20 @@
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()
})
}

View File

@@ -0,0 +1,59 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package test
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type NodeOptionsAction struct {
actionutils.ParentAction
}
func (this *NodeOptionsAction) RunPost(params struct {
ClusterId int64
}) {
nodesResp, err := this.RPC().NSNodeRPC().FindAllEnabledNSNodesWithNSClusterId(this.AdminContext(), &pb.FindAllEnabledNSNodesWithNSClusterIdRequest{NsClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
var nodeMaps = []maps.Map{}
for _, node := range nodesResp.NsNodes {
if !node.IsOn {
continue
}
addressesResp, err := this.RPC().NodeIPAddressRPC().FindAllEnabledIPAddressesWithNodeId(this.AdminContext(), &pb.FindAllEnabledIPAddressesWithNodeIdRequest{
NodeId: node.Id,
Role: nodeconfigs.NodeRoleDNS,
})
if err != nil {
this.ErrorPage(err)
return
}
var addresses = addressesResp.Addresses
if len(addresses) == 0 {
continue
}
var addrs = []string{}
for _, addr := range addresses {
if addr.CanAccess {
addrs = append(addrs, addr.Ip)
}
}
nodeMaps = append(nodeMaps, maps.Map{
"id": node.Id,
"name": node.Name,
"addrs": addrs,
})
}
this.Data["nodes"] = nodeMaps
this.Success()
}

View File

@@ -0,0 +1,40 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package ipbox
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"strings"
"time"
)
type AddIPAction struct {
actionutils.ParentAction
}
func (this *AddIPAction) RunPost(params struct {
ListId int64
Ip string
}) {
var ipType = "ipv4"
if strings.Contains(params.Ip, ":") {
ipType = "ipv6"
}
_, err := this.RPC().IPItemRPC().CreateIPItem(this.AdminContext(), &pb.CreateIPItemRequest{
IpListId: params.ListId,
IpFrom: params.Ip,
IpTo: "",
ExpiredAt: time.Now().Unix() + 86400, // TODO 可以自定义时间
Reason: "从IPBox中加入名单",
Type: ipType,
EventLevel: "critical",
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -5,6 +5,7 @@ package ipbox
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
@@ -22,10 +23,62 @@ func (this *IndexAction) RunGet(params struct {
}) {
this.Data["ip"] = params.Ip
// IP信息
regionResp, err := this.RPC().IPLibraryRPC().LookupIPRegion(this.AdminContext(), &pb.LookupIPRegionRequest{Ip: params.Ip})
if err != nil {
this.ErrorPage(err)
return
}
if regionResp.IpRegion != nil {
this.Data["regions"] = regionResp.IpRegion.Summary
} else {
this.Data["regions"] = ""
}
this.Data["isp"] = regionResp.IpRegion.Isp
// IP列表
ipListResp, err := this.RPC().IPListRPC().FindEnabledIPListContainsIP(this.AdminContext(), &pb.FindEnabledIPListContainsIPRequest{Ip: params.Ip})
if err != nil {
this.ErrorPage(err)
return
}
var ipListMaps = []maps.Map{}
for _, ipList := range ipListResp.IpLists {
ipListMaps = append(ipListMaps, maps.Map{
"id": ipList.Id,
"name": ipList.Name,
"type": ipList.Type,
})
}
this.Data["ipLists"] = ipListMaps
// 所有公用的IP列表
publicBlackIPListResp, err := this.RPC().IPListRPC().ListEnabledIPLists(this.AdminContext(), &pb.ListEnabledIPListsRequest{
Type: "black",
IsPublic: true,
Keyword: "",
Offset: 0,
Size: 10, // TODO 将来考虑到支持更多的黑名单
})
if err != nil {
this.ErrorPage(err)
return
}
var publicBlackIPListMaps = []maps.Map{}
for _, ipList := range publicBlackIPListResp.IpLists {
publicBlackIPListMaps = append(publicBlackIPListMaps, maps.Map{
"id": ipList.Id,
"name": ipList.Name,
"type": ipList.Type,
})
}
this.Data["publicBlackIPLists"] = publicBlackIPListMaps
// 访问日志
accessLogsResp, err := this.RPC().HTTPAccessLogRPC().ListHTTPAccessLogs(this.AdminContext(), &pb.ListHTTPAccessLogsRequest{
Day: timeutil.Format("Ymd"),
Keyword: "ip:" + params.Ip,
Size: 10,
Day: timeutil.Format("Ymd"),
Ip: params.Ip,
Size: 20,
})
if err != nil {
this.ErrorPage(err)
@@ -35,9 +88,9 @@ func (this *IndexAction) RunGet(params struct {
if len(accessLogs) == 0 {
// 查询昨天
accessLogsResp, err = this.RPC().HTTPAccessLogRPC().ListHTTPAccessLogs(this.AdminContext(), &pb.ListHTTPAccessLogsRequest{
Day: timeutil.Format("Ymd", time.Now().AddDate(0, 0, -1)),
Keyword: "ip:" + params.Ip,
Size: 10,
Day: timeutil.Format("Ymd", time.Now().AddDate(0, 0, -1)),
Ip: params.Ip,
Size: 20,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -14,6 +14,7 @@ func init() {
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeServer)).
Prefix("/servers/ipbox").
Get("", new(IndexAction)).
Post("/addIP", new(AddIPAction)).
EndAll()
})
}
}

View File

@@ -3,10 +3,15 @@
package iplists
import (
"bytes"
"encoding/csv"
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/golang/protobuf/proto"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"github.com/tealeg/xlsx/v3"
"strconv"
)
@@ -20,10 +25,55 @@ func (this *ExportDataAction) Init() {
func (this *ExportDataAction) RunGet(params struct {
ListId int64
Format string
}) {
defer this.CreateLogInfo("导出IP名单 %d", params.ListId)
resp := &pb.ListIPItemsWithListIdResponse{}
var err error
var ext = ""
var jsonMaps = []maps.Map{}
var xlsxFile *xlsx.File
var xlsxSheet *xlsx.Sheet
var csvWriter *csv.Writer
var csvBuffer *bytes.Buffer
var data []byte
switch params.Format {
case "xlsx":
ext = ".xlsx"
xlsxFile = xlsx.NewFile()
if err != nil {
this.ErrorPage(err)
return
}
xlsxSheet, err = xlsxFile.AddSheet("IP名单")
if err != nil {
this.ErrorPage(err)
return
}
row := xlsxSheet.AddRow()
row.SetHeight(26)
row.AddCell().SetValue("开始IP")
row.AddCell().SetValue("结束IP")
row.AddCell().SetValue("过期时间戳")
row.AddCell().SetValue("类型")
row.AddCell().SetValue("级别")
row.AddCell().SetValue("备注")
case "csv":
ext = ".csv"
csvBuffer = &bytes.Buffer{}
csvWriter = csv.NewWriter(csvBuffer)
case "txt":
ext = ".txt"
case "json":
ext = ".json"
default:
this.WriteString("请选择正确的导出格式")
return
}
var offset int64 = 0
var size int64 = 1000
for {
@@ -40,18 +90,60 @@ func (this *ExportDataAction) RunGet(params struct {
break
}
for _, item := range itemsResp.IpItems {
resp.IpItems = append(resp.IpItems, item)
switch params.Format {
case "xlsx":
row := xlsxSheet.AddRow()
row.SetHeight(26)
row.AddCell().SetValue(item.IpFrom)
row.AddCell().SetValue(item.IpTo)
row.AddCell().SetValue(types.String(item.ExpiredAt))
row.AddCell().SetValue(item.Type)
row.AddCell().SetValue(item.EventLevel)
row.AddCell().SetValue(item.Reason)
case "csv":
err = csvWriter.Write([]string{item.IpFrom, item.IpTo, types.String(item.ExpiredAt), item.Type, item.EventLevel, item.Reason})
if err != nil {
this.ErrorPage(err)
return
}
case "txt":
data = append(data, item.IpFrom+","+item.IpTo+","+types.String(item.ExpiredAt)+","+item.Type+","+item.EventLevel+","+item.Reason...)
data = append(data, '\n')
case "json":
jsonMaps = append(jsonMaps, maps.Map{
"ipFrom": item.IpFrom,
"ipTo": item.IpTo,
"expiredAt": item.ExpiredAt,
"type": item.Type,
"eventLevel": item.EventLevel,
"reason": item.Reason,
})
}
}
offset += size
}
data, err := proto.Marshal(resp)
if err != nil {
this.ErrorPage(err)
return
switch params.Format {
case "xlsx":
var buf = &bytes.Buffer{}
err = xlsxFile.Write(buf)
if err != nil {
this.ErrorPage(err)
return
}
data = buf.Bytes()
case "csv":
csvWriter.Flush()
data = csvBuffer.Bytes()
case "json":
data, err = json.Marshal(jsonMaps)
if err != nil {
this.ErrorPage(err)
return
}
}
this.AddHeader("Content-Disposition", "attachment; filename=\"ip-list-"+numberutils.FormatInt64(params.ListId)+".data\";")
this.AddHeader("Content-Disposition", "attachment; filename=\"ip-list-"+numberutils.FormatInt64(params.ListId)+ext+"\";")
this.AddHeader("Content-Length", strconv.Itoa(len(data)))
this.Write(data)
}

View File

@@ -3,10 +3,19 @@
package iplists
import (
"bytes"
"encoding/csv"
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/golang/protobuf/proto"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/types"
"github.com/tealeg/xlsx/v3"
"io"
"net"
"regexp"
"strings"
)
type ImportAction struct {
@@ -51,20 +60,92 @@ func (this *ImportAction) RunPost(params struct {
this.Fail("请选择要导入的IP文件")
}
// 检查文件扩展名
if !regexp.MustCompile(`(?i)\.(xlsx|csv|json|txt)$`).MatchString(params.File.Filename) {
this.Fail("不支持当前格式的文件导入")
}
var ext = strings.ToLower(params.File.Ext)
data, err := params.File.Read()
if err != nil {
this.ErrorPage(err)
return
}
resp := &pb.ListIPItemsWithListIdResponse{}
err = proto.Unmarshal(data, resp)
if err != nil {
this.Fail("导入失败,文件格式错误:" + err.Error())
var countIgnore = 0
var items = []*pb.IPItem{}
switch ext {
case ".xlsx":
file, err := xlsx.OpenBinary(data)
if err != nil {
this.Fail("Excel读取错误" + err.Error())
}
if len(file.Sheets) > 0 {
var sheet = file.Sheets[0]
err = sheet.ForEachRow(func(r *xlsx.Row) error {
var values = []string{}
err = r.ForEachCell(func(c *xlsx.Cell) error {
values = append(values, c.Value)
return nil
})
if err != nil {
return err
}
if len(values) == 0 {
return nil
}
if values[0] == "开始IP" || values[0] == "IP" {
return nil
}
item := this.createItemFromValues(values, &countIgnore)
if item != nil {
items = append(items, item)
}
return nil
})
if err != nil {
this.Fail("Excel读取错误" + err.Error())
}
}
case ".csv":
reader := csv.NewReader(bytes.NewBuffer(data))
for {
values, err := reader.Read()
if err != nil {
if err == io.EOF {
break
}
this.Fail("CSV读取错误" + err.Error())
}
item := this.createItemFromValues(values, &countIgnore)
if item != nil {
items = append(items, item)
}
}
case ".json":
err = json.Unmarshal(data, &items)
if err != nil {
this.Fail("导入失败:" + err.Error())
}
case ".txt":
lines := bytes.Split(data, []byte{'\n'})
for _, line := range lines {
if len(line) == 0 {
continue
}
item := this.createItemFromValues(strings.SplitN(string(line), ",", 6), &countIgnore)
if item != nil {
items = append(items, item)
}
}
}
var count = 0
var countIgnore = 0
for _, item := range resp.IpItems {
lists.Reverse(items)
for _, item := range items {
_, err = this.RPC().IPItemRPC().CreateIPItem(this.AdminContext(), &pb.CreateIPItemRequest{
IpListId: params.ListId,
IpFrom: item.IpFrom,
@@ -85,3 +166,48 @@ func (this *ImportAction) RunPost(params struct {
this.Success()
}
func (this *ImportAction) createItemFromValues(values []string, countIgnore *int) *pb.IPItem {
// ipFrom, ipTo, expiredAt, type, eventType, reason
var item = &pb.IPItem{}
switch len(values) {
case 1:
item.IpFrom = values[0]
case 2:
item.IpFrom = values[0]
item.IpTo = values[1]
case 3:
item.IpFrom = values[0]
item.IpTo = values[1]
item.ExpiredAt = types.Int64(values[2])
case 4:
item.IpFrom = values[0]
item.IpTo = values[1]
item.ExpiredAt = types.Int64(values[2])
item.Type = values[3]
case 5:
item.IpFrom = values[0]
item.IpTo = values[1]
item.ExpiredAt = types.Int64(values[2])
item.Type = values[3]
item.EventLevel = values[4]
case 6:
item.IpFrom = values[0]
item.IpTo = values[1]
item.ExpiredAt = types.Int64(values[2])
item.Type = values[3]
item.EventLevel = values[4]
item.Reason = values[5]
}
if net.ParseIP(item.IpFrom) == nil {
*countIgnore++
return nil
}
if len(item.IpTo) > 0 && net.ParseIP(item.IpTo) == nil {
*countIgnore++
return nil
}
return item
}

View File

@@ -0,0 +1,127 @@
// 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"
"github.com/iwind/TeaGo/lists"
timeutil "github.com/iwind/TeaGo/utils/time"
"regexp"
"strings"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "")
}
func (this *IndexAction) RunGet(params struct {
Day string
Keyword string
Ip string
Domain string
HasError int
RequestId string
ServerId int64
}) {
if len(params.Day) == 0 {
params.Day = timeutil.Format("Y-m-d")
}
this.Data["serverId"] = 0
this.Data["path"] = this.Request.URL.Path
this.Data["day"] = params.Day
this.Data["keyword"] = params.Keyword
this.Data["ip"] = params.Ip
this.Data["domain"] = params.Domain
this.Data["accessLogs"] = []interface{}{}
this.Data["hasError"] = params.HasError
day := params.Day
ipList := []string{}
if len(day) > 0 && regexp.MustCompile(`\d{4}-\d{2}-\d{2}`).MatchString(day) {
day = strings.ReplaceAll(day, "-", "")
size := int64(10)
this.Data["hasError"] = params.HasError
resp, err := this.RPC().HTTPAccessLogRPC().ListHTTPAccessLogs(this.AdminContext(), &pb.ListHTTPAccessLogsRequest{
RequestId: params.RequestId,
ServerId: params.ServerId,
HasError: params.HasError > 0,
Day: day,
Keyword: params.Keyword,
Ip: params.Ip,
Domain: params.Domain,
Size: size,
})
if err != nil {
this.ErrorPage(err)
return
}
if len(resp.HttpAccessLogs) == 0 {
this.Data["accessLogs"] = []interface{}{}
} else {
this.Data["accessLogs"] = resp.HttpAccessLogs
for _, accessLog := range resp.HttpAccessLogs {
if len(accessLog.RemoteAddr) > 0 {
if !lists.ContainsString(ipList, accessLog.RemoteAddr) {
ipList = append(ipList, accessLog.RemoteAddr)
}
}
}
}
this.Data["hasMore"] = resp.HasMore
this.Data["nextRequestId"] = resp.RequestId
// 上一个requestId
this.Data["hasPrev"] = false
this.Data["lastRequestId"] = ""
if len(params.RequestId) > 0 {
this.Data["hasPrev"] = true
prevResp, err := this.RPC().HTTPAccessLogRPC().ListHTTPAccessLogs(this.AdminContext(), &pb.ListHTTPAccessLogsRequest{
RequestId: params.RequestId,
ServerId: params.ServerId,
HasError: params.HasError > 0,
Day: day,
Keyword: params.Keyword,
Ip: params.Ip,
Domain: params.Domain,
Size: size,
Reverse: true,
})
if err != nil {
this.ErrorPage(err)
return
}
if int64(len(prevResp.HttpAccessLogs)) == size {
this.Data["lastRequestId"] = prevResp.RequestId
}
}
}
// 根据IP查询区域
regionMap := map[string]string{} // ip => region
if len(ipList) > 0 {
resp, err := this.RPC().IPLibraryRPC().LookupIPRegions(this.AdminContext(), &pb.LookupIPRegionsRequest{IpList: ipList})
if err != nil {
this.ErrorPage(err)
return
}
if resp.IpRegionMap != nil {
for ip, region := range resp.IpRegionMap {
regionMap[ip] = region.Summary
}
}
}
this.Data["regions"] = regionMap
this.Show()
}

View File

@@ -0,0 +1,21 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
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.AdminModuleCodeServer)).
Data("teaMenu", "servers").
Data("teaSubMenu", "log").
Prefix("/servers/logs").
Get("", new(IndexAction)).
EndAll()
})
}

View File

@@ -22,6 +22,8 @@ func (this *HistoryAction) RunGet(params struct {
ServerId int64
Day string
Keyword string
Ip string
Domain string
RequestId string
HasError int
@@ -33,6 +35,8 @@ func (this *HistoryAction) RunGet(params struct {
this.Data["path"] = this.Request.URL.Path
this.Data["day"] = params.Day
this.Data["keyword"] = params.Keyword
this.Data["ip"] = params.Ip
this.Data["domain"] = params.Domain
this.Data["accessLogs"] = []interface{}{}
this.Data["hasError"] = params.HasError
@@ -51,6 +55,8 @@ func (this *HistoryAction) RunGet(params struct {
HasError: params.HasError > 0,
Day: day,
Keyword: params.Keyword,
Ip: params.Ip,
Domain: params.Domain,
Size: size,
})
if err != nil {
@@ -84,6 +90,8 @@ func (this *HistoryAction) RunGet(params struct {
HasError: params.HasError > 0,
Day: day,
Keyword: params.Keyword,
Ip: params.Ip,
Domain: params.Domain,
Size: size,
Reverse: true,
})

View File

@@ -20,10 +20,14 @@ func (this *IndexAction) Init() {
func (this *IndexAction) RunGet(params struct {
ServerId int64
RequestId string
Ip string
Domain string
Keyword string
}) {
this.Data["serverId"] = params.ServerId
this.Data["requestId"] = params.RequestId
this.Data["ip"] = params.Ip
this.Data["domain"] = params.Domain
this.Data["keyword"] = params.Keyword
this.Data["path"] = this.Request.URL.Path
@@ -44,6 +48,8 @@ func (this *IndexAction) RunPost(params struct {
ServerId int64
RequestId string
Keyword string
Ip string
Domain string
Must *actions.Must
}) {
@@ -54,6 +60,8 @@ func (this *IndexAction) RunPost(params struct {
Size: 20,
Day: timeutil.Format("Ymd"),
Keyword: params.Keyword,
Ip: params.Ip,
Domain: params.Domain,
Reverse: isReverse,
})
if err != nil {

View File

@@ -21,12 +21,16 @@ func (this *TodayAction) RunGet(params struct {
ServerId int64
HasError int
Keyword string
Ip string
Domain string
}) {
size := int64(10)
this.Data["path"] = this.Request.URL.Path
this.Data["hasError"] = params.HasError
this.Data["keyword"] = params.Keyword
this.Data["ip"] = params.Ip
this.Data["domain"] = params.Domain
resp, err := this.RPC().HTTPAccessLogRPC().ListHTTPAccessLogs(this.AdminContext(), &pb.ListHTTPAccessLogsRequest{
RequestId: params.RequestId,
@@ -34,6 +38,8 @@ func (this *TodayAction) RunGet(params struct {
HasError: params.HasError > 0,
Day: timeutil.Format("Ymd"),
Keyword: params.Keyword,
Ip: params.Ip,
Domain: params.Domain,
Size: size,
})
if err != nil {
@@ -67,6 +73,9 @@ func (this *TodayAction) RunGet(params struct {
ServerId: params.ServerId,
HasError: params.HasError > 0,
Day: timeutil.Format("Ymd"),
Keyword: params.Keyword,
Ip: params.Ip,
Domain: params.Domain,
Size: size,
Reverse: true,
})

View File

@@ -27,6 +27,7 @@ func (this *SortAction) RunPost(params struct {
}
if webConfig == nil {
this.Success()
return
}
refMap := map[int64]*serverconfigs.HTTPLocationRef{}
for _, ref := range webConfig.LocationRefs {

View File

@@ -121,6 +121,10 @@ func (this *SettingAction) RunPost(params struct {
MaxConns: types.Int32(reverseProxyConfig.MaxConns),
MaxIdleConns: types.Int32(reverseProxyConfig.MaxIdleConns),
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -69,11 +69,12 @@ func (this *IndexAction) RunGet(params struct{}) {
}
func (this *IndexAction) RunPost(params struct {
Frame string
CountryIdsJSON []byte
ProvinceIdsJSON []byte
AllowLocal bool
AllowIPs []string
Frame string
CountryIdsJSON []byte
ProvinceIdsJSON []byte
AllowLocal bool
AllowIPs []string
AllowRememberLogin bool
Must *actions.Must
CSRF *actionutils.CSRF
@@ -127,6 +128,9 @@ func (this *IndexAction) RunPost(params struct {
// 允许本地
config.AllowLocal = params.AllowLocal
// 允许记住登录
config.AllowRememberLogin = params.AllowRememberLogin
err = configloaders.UpdateSecurityConfig(config)
if err != nil {
this.ErrorPage(err)

View File

@@ -91,12 +91,12 @@ func (this *ValidateDbAction) RunPost(params struct {
}
// 检查权限
_, err = db.Exec("CREATE TABLE IF NOT EXISTS `edgeTest` ( `id` int )")
_, err = db.Exec("CREATE TABLE IF NOT EXISTS `edgeTest` (\n `id` int(11) NOT NULL AUTO_INCREMENT,\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;")
if err != nil {
this.Fail("当前连接的用户无法创建新表请检查CREATE权限设置" + err.Error())
}
_, err = db.Exec("ALTER TABLE `edgeTest` CHANGE `id` `id` int")
_, err = db.Exec("ALTER TABLE `edgeTest` CHANGE `id` `id` int(11) NOT NULL AUTO_INCREMENT")
if err != nil {
this.Fail("当前连接的用户无法修改表结构请检查ALTER权限设置" + err.Error())
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/setup"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"net"
"net/http"
"reflect"
"strings"
@@ -63,6 +64,11 @@ func (this *userMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramNam
action.ResponseWriter.WriteHeader(http.StatusForbidden)
return false
}
remoteAddr, _, _ := net.SplitHostPort(action.Request.RemoteAddr)
if len(remoteAddr) > 0 && remoteAddr != action.RequestRemoteIP() && !checkIP(securityConfig, remoteAddr) {
action.ResponseWriter.WriteHeader(http.StatusForbidden)
return false
}
// 检查系统是否已经配置过
if !setup.IsConfigured() {
@@ -174,6 +180,11 @@ func (this *userMustAuth) modules(adminId int64) []maps.Map {
"subtitle": "服务列表",
"icon": "clone outsize",
"subItems": []maps.Map{
{
"name": "访问日志",
"url": "/servers/logs",
"code": "log",
},
{
"name": "通用设置",
"url": "/servers/components",
@@ -261,13 +272,17 @@ func (this *userMustAuth) modules(adminId int64) []maps.Map {
},
},
{
"code": "ns",
"module": configloaders.AdminModuleCodeNS,
"name": "自建DNS",
"subtitle": "域名列表",
"icon": "cubes",
"isOn": teaconst.IsPlus,
"code": "ns",
"module": configloaders.AdminModuleCodeNS,
"name": "自建DNS",
"icon": "cubes",
"isOn": teaconst.IsPlus,
"subItems": []maps.Map{
{
"name": "域名管理",
"url": "/ns/domains",
"code": "domain",
},
{
"name": "集群管理",
"url": "/ns/clusters",
@@ -288,6 +303,16 @@ func (this *userMustAuth) modules(adminId int64) []maps.Map {
"url": "/ns/clusters/logs",
"code": "log",
},
{
"name": "全局配置",
"url": "/ns/settings",
"code": "setting",
},
{
"name": "解析测试",
"url": "/ns/test",
"code": "test",
},
},
},
{

View File

@@ -5,6 +5,7 @@ import (
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/iwind/TeaGo/actions"
"net"
"net/http"
)
@@ -35,6 +36,11 @@ func (this *UserShouldAuth) BeforeAction(actionPtr actions.ActionWrapper, paramN
action.ResponseWriter.WriteHeader(http.StatusForbidden)
return false
}
remoteAddr, _, _ := net.SplitHostPort(action.Request.RemoteAddr)
if len(remoteAddr) > 0 && remoteAddr != action.RequestRemoteIP() && !checkIP(securityConfig, remoteAddr) {
action.ResponseWriter.WriteHeader(http.StatusForbidden)
return false
}
return true
}

View File

@@ -50,6 +50,8 @@ import (
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/ns/clusters/cluster/settings/accessLog"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/ns/clusters/logs"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/ns/routes"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/ns/settings/accesslogs"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/ns/test"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/ns/users"
// 服务相关
@@ -61,6 +63,7 @@ import (
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/components/groups"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/components/log"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/components/waf"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/logs"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/metrics"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/metrics/charts"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server"

View File

@@ -10,7 +10,7 @@ Vue.component("copy-to-clipboard", {
methods: {
copy: function () {
new ClipboardJS('[data-clipboard-target]');
teaweb.success("已复制到剪切板")
teaweb.successToast("已复制到剪切板")
}
},
template: `<a href="" title="拷贝到剪切板" :data-clipboard-target="'#' + vTarget" @click.prevent="copy"><i class="ui icon copy small"></i></em></a>`

View File

@@ -44,9 +44,9 @@ Vue.component("dns-route-selector", {
this.isAdding = false
},
remove: function (route) {
this.routeCodes.$removeValue(route.code)
this.routeCodes.$removeValue(route.code + "@" + route.domainId)
this.routes.$removeIf(function (k, v) {
return v.code == route.code
return v.code + "@" + v.domainId == route.code + "@" + route.domainId
})
}
},

View File

@@ -1,36 +1,42 @@
Vue.component("grant-selector", {
props: ["vGrant"],
props: ["v-grant", "v-node-cluster-id", "v-ns-cluster-id"],
data: function () {
return {
grantId: (this.vGrant == null) ? 0 : this.vGrant.id,
grant: this.vGrant
grant: this.vGrant,
nodeClusterId: (this.vNodeClusterId != null) ? this.vNodeClusterId : 0,
nsClusterId: (this.vNsClusterId != null) ? this.vNsClusterId : 0
}
},
methods: {
// 选择授权
select: function () {
let that = this;
teaweb.popup("/clusters/grants/selectPopup", {
teaweb.popup("/clusters/grants/selectPopup?nodeClusterId=" + this.nodeClusterId + "&nsClusterId=" + this.nsClusterId, {
callback: (resp) => {
that.grantId = resp.data.grant.id;
if (that.grantId > 0) {
that.grant = resp.data.grant;
}
}
});
that.notifyUpdate()
},
height: "26em"
})
},
// 创建授权
create: function () {
let that = this
teaweb.popup("/clusters/grants/createPopup", {
height: "26em",
callback: (resp) => {
this.grantId = resp.data.grant.id;
if (this.grantId > 0) {
this.grant = resp.data.grant;
that.grantId = resp.data.grant.id;
if (that.grantId > 0) {
that.grant = resp.data.grant;
}
that.notifyUpdate()
}
});
})
},
// 修改授权
@@ -39,23 +45,29 @@ Vue.component("grant-selector", {
window.location.reload();
return;
}
let that = this
teaweb.popup("/clusters/grants/updatePopup?grantId=" + this.grant.id, {
height: "26em",
callback: (resp) => {
this.grant = resp.data.grant;
that.grant = resp.data.grant
that.notifyUpdate()
}
})
},
// 删除已选择授权
remove: function () {
this.grant = null;
this.grantId = 0;
this.grant = null
this.grantId = 0
this.notifyUpdate()
},
notifyUpdate: function () {
this.$emit("change", this.grant)
}
},
template: `<div>
<input type="hidden" name="grantId" :value="grantId"/>
<div class="ui label small basic" v-if="grant != null">{{grant.name}}<span class="small">{{grant.methodName}}</span> <a href="" title="修改" @click.prevent="update()"><i class="icon pencil small"></i></a> <a href="" title="删除" @click.prevent="remove()"><i class="icon remove"></i></a> </div>
<div class="ui label small basic" v-if="grant != null">{{grant.name}}<span class="small grey">{{grant.methodName}}</span><span class="small grey" v-if="grant.username != null && grant.username.length > 0">{{grant.username}}</span> <a href="" title="修改" @click.prevent="update()"><i class="icon pencil small"></i></a> <a href="" title="删除" @click.prevent="remove()"><i class="icon remove"></i></a> </div>
<div v-if="grant == null">
<a href="" @click.prevent="select()">[选择已有认证]</a> &nbsp; &nbsp; <a href="" @click.prevent="create()">[添加新认证]</a>
</div>

View File

@@ -41,11 +41,13 @@ Vue.component("message-row", {
<strong>{{message.datetime}}</strong>
<span v-if="message.cluster != null && message.cluster.id != null">
<span> | </span>
<a :href="'/clusters/cluster?clusterId=' + message.cluster.id" target="_top">集群:{{message.cluster.name}}</a>
<a :href="'/clusters/cluster?clusterId=' + message.cluster.id" target="_top" v-if="message.role == 'node'">集群:{{message.cluster.name}}</a>
<a :href="'/ns/clusters/cluster?clusterId=' + message.cluster.id" target="_top" v-if="message.role == 'dns'">DNS集群{{message.cluster.name}}</a>
</span>
<span v-if="message.node != null && message.node.id != null">
<span> | </span>
<a :href="'/clusters/cluster/node?clusterId=' + message.cluster.id + '&nodeId=' + message.node.id" target="_top">节点:{{message.node.name}}</a>
<a :href="'/clusters/cluster/node?clusterId=' + message.cluster.id + '&nodeId=' + message.node.id" target="_top" v-if="message.role == 'node'">节点:{{message.node.name}}</a>
<a :href="'/ns/clusters/cluster/node?clusterId=' + message.cluster.id + '&nodeId=' + message.node.id" target="_top" v-if="message.role == 'dns'">DNS节点{{message.node.name}}</a>
</span>
<a href="" style="position: absolute; right: 1em" @click.prevent="readMessage(message.id)" title="标为已读"><i class="icon check"></i></a>
</td>
@@ -73,6 +75,8 @@ Vue.component("message-row", {
<div v-if="message.type == 'SSLCertACMETaskSuccess'" style="margin-top: 0.8em">
<a href="" @click.prevent="viewCert(params.certId)" target="_top">查看证书</a> &nbsp;|&nbsp; <a :href="'/servers/certs/acme'" v-if="params != null && params.acmeTaskId > 0" target="_top">查看任务&raquo;</a>
</div>
<!-- 证书续期失败 -->
<div v-if="message.type == 'SSLCertACMETaskFailed'" style="margin-top: 0.8em">
<a href="" @click.prevent="viewCert(params.certId)" target="_top">查看证书</a> &nbsp;|&nbsp; <a :href="'/servers/certs/acme'" v-if="params != null && params.acmeTaskId > 0" target="_top">查看任务&raquo;</a>
</div>

View File

@@ -0,0 +1,60 @@
// 节点登录推荐端口
Vue.component("node-login-suggest-ports", {
data: function () {
return {
ports: [],
availablePorts: [],
autoSelected: false,
isLoading: false
}
},
methods: {
reload: function (host) {
let that = this
this.autoSelected = false
this.isLoading = true
Tea.action("/clusters/cluster/suggestLoginPorts")
.params({
host: host
})
.success(function (resp) {
if (resp.data.availablePorts != null) {
that.availablePorts = resp.data.availablePorts
if (that.availablePorts.length > 0) {
that.autoSelectPort(that.availablePorts[0])
that.autoSelected = true
}
}
if (resp.data.ports != null) {
that.ports = resp.data.ports
if (that.ports.length > 0 && !that.autoSelected) {
that.autoSelectPort(that.ports[0])
that.autoSelected = true
}
}
})
.done(function () {
that.isLoading = false
})
.post()
},
selectPort: function (port) {
this.$emit("select", port)
},
autoSelectPort: function (port) {
this.$emit("auto-select", port)
}
},
template: `<span>
<span v-if="isLoading">正在检查端口...</span>
<span v-if="availablePorts.length > 0">
可能端口:<a href="" v-for="port in availablePorts" @click.prevent="selectPort(port)" class="ui label tiny basic blue" style="border: 1px #2185d0 dashed; font-weight: normal">{{port}}</a>
&nbsp; &nbsp;
</span>
<span v-if="ports.length > 0">
常用端口:<a href="" v-for="port in ports" @click.prevent="selectPort(port)" class="ui label tiny basic blue" style="border: 1px #2185d0 dashed; font-weight: normal">{{port}}</a>
</span>
<span v-if="ports.length == 0">常用端口有22等。</span>
<span v-if="ports.length > 0" class="grey small">(可以点击要使用的端口)</span>
</span>`
})

View File

@@ -1,13 +1,17 @@
Vue.component("ns-access-log-ref-box", {
props:["v-access-log-ref"],
props: ["v-access-log-ref", "v-is-parent"],
data: function () {
let config = this.vAccessLogRef
if (config == null) {
config = {
isOn: false,
isPrior: false
isPrior: false,
logMissingDomains: false
}
}
if (typeof (config.logMissingDomains) == "undefined") {
config.logMissingDomains = false
}
return {
config: config
}
@@ -15,12 +19,22 @@ Vue.component("ns-access-log-ref-box", {
template: `<div>
<input type="hidden" name="accessLogJSON" :value="JSON.stringify(config)"/>
<table class="ui table definition selectable">
<tr>
<td class="title">是否启用</td>
<td>
<checkbox name="isOn" value="1" v-model="config.isOn"></checkbox>
</td>
</tr>
<prior-checkbox :v-config="config" v-if="!vIsParent"></prior-checkbox>
<tbody v-show="vIsParent || config.isPrior">
<tr>
<td class="title">是否启用</td>
<td>
<checkbox name="isOn" value="1" v-model="config.isOn"></checkbox>
</td>
</tr>
<tr>
<td>记录所有访问</td>
<td>
<checkbox name="logMissingDomains" value="1" v-model="config.logMissingDomains"></checkbox>
<p class="comment">包括对没有在系统里创建的域名访问。</p>
</td>
</tr>
</tbody>
</table>
<div class="margin"></div>
</div>`

View File

@@ -1,6 +1,6 @@
// 选择单一线路
Vue.component("ns-route-selector", {
props: ["v-route-id"],
props: ["v-route-code"],
mounted: function () {
let that = this
Tea.action("/ns/routes/options")
@@ -10,20 +10,20 @@ Vue.component("ns-route-selector", {
})
},
data: function () {
let routeId = this.vRouteId
if (routeId == null) {
routeId = 0
let routeCode = this.vRouteCode
if (routeCode == null) {
routeCode = ""
}
return {
routeId: routeId,
routeCode: routeCode,
routes: []
}
},
template: `<div>
<div v-if="routes.length > 0">
<select class="ui dropdown" name="routeId" v-model="routeId">
<option value="0">[线路]</option>
<option v-for="route in routes" :value="route.id">{{route.name}}</option>
<select class="ui dropdown" name="routeCode" v-model="routeCode">
<option value="">[线路]</option>
<option v-for="route in routes" :value="route.code">{{route.name}}</option>
</select>
</div>
</div>`

View File

@@ -1,66 +1,70 @@
// 选择多个线路
Vue.component("ns-routes-selector", {
props: ["v-route-ids"],
props: ["v-routes"],
mounted: function () {
let that = this
let routeIds = this.vRouteIds
if (routeIds == null) {
routeIds = []
}
Tea.action("/ns/routes/options")
.post()
.success(function (resp) {
that.allRoutes = resp.data.routes
that.allRoutes.forEach(function (v) {
v.isChecked = (routeIds.$contains(v.id))
})
that.routes = resp.data.routes
})
},
data: function () {
return {
routeId: 0,
allRoutes: [],
routes: [],
isAdding: false
let selectedRoutes = this.vRoutes
if (selectedRoutes == null) {
selectedRoutes = []
}
}
,
return {
routeCode: "default",
routes: [],
isAdding: false,
routeType: "default",
selectedRoutes: selectedRoutes
}
},
watch: {
routeType: function (v) {
this.routeCode = ""
let that = this
this.routes.forEach(function (route) {
if (route.type == v && that.routeCode.length == 0) {
that.routeCode = route.code
}
})
}
},
methods: {
add: function () {
this.isAdding = true
this.routes = this.allRoutes.$findAll(function (k, v) {
return !v.isChecked
})
this.routeId = 0
this.routeType = "default"
this.routeCode = "default"
},
cancel: function () {
this.isAdding = false
},
confirm: function () {
if (this.routeId == 0) {
if (this.routeCode.length == 0) {
return
}
let that = this
this.routes.forEach(function (v) {
if (v.id == that.routeId) {
v.isChecked = true
if (v.code == that.routeCode) {
that.selectedRoutes.push(v)
}
})
this.cancel()
},
remove: function (index) {
this.allRoutes[index].isChecked = false
Vue.set(this.allRoutes, index, this.allRoutes[index])
this.selectedRoutes.$remove(index)
}
}
,
template: `<div>
<div>
<div class="ui label basic text small" v-for="(route, index) in allRoutes" v-if="route.isChecked">
<input type="hidden" name="routeIds" :value="route.id"/>
<div class="ui label basic text small" v-for="(route, index) in selectedRoutes" style="margin-bottom: 0.3em">
<input type="hidden" name="routeCodes" :value="route.code"/>
{{route.name}} &nbsp; <a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove small"></i></a>
</div>
<div class="ui divider"></div>
@@ -68,11 +72,21 @@ Vue.component("ns-routes-selector", {
<div v-if="isAdding" style="margin-bottom: 1em">
<div class="ui fields inline">
<div class="ui field">
<select class="ui dropdown" name="routeId" v-model="routeId">
<option value="0">[线路]</option>
<option v-for="route in routes" :value="route.id">{{route.name}}</option>
<select class="ui dropdown" v-model="routeType">
<option value="default">[默认线路]</option>
<option value="user">自定义线路</option>
<option value="isp">运营商</option>
<option value="china">中国省市</option>
<option value="world">全球国家地区</option>
</select>
</div>
<div class="ui field">
<select class="ui dropdown" v-model="routeCode" style="width: 10em">
<option v-for="route in routes" :value="route.code" v-if="route.type == routeType">{{route.name}}</option>
</select>
</div>
<div class="ui field">
<button type="button" class="ui button tiny" @click.prevent="confirm">确定</button>
&nbsp; <a href="" title="取消" @click.prevent="cancel"><i class="icon remove small"></i></a>

View File

@@ -48,8 +48,8 @@ Vue.component("http-access-log-box", {
this.$refs.box.parentNode.style.cssText = ""
}
},
template: `<div :style="{'color': (accessLog.status >= 400) ? '#dc143c' : ''}" ref="box">
<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>&quot;<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}}&quot; </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">{{tag}}</code-label></span> - 耗时:{{formatCost(accessLog.requestTime)}} ms <span v-if="accessLog.humanTime != null && accessLog.humanTime.length > 0" class="grey small">&nbsp; ({{accessLog.humanTime}})</span>
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>&quot;<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}}&quot; </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">{{tag}}</code-label></span> - 耗时:{{formatCost(accessLog.requestTime)}} ms <span v-if="accessLog.humanTime != null && accessLog.humanTime.length > 0" class="grey small">&nbsp; ({{accessLog.humanTime}})</span>
&nbsp; <a href="" @click.prevent="showLog" title="查看详情"><i class="icon expand"></i></a>
</div>`
})

View File

@@ -0,0 +1,53 @@
// 访问日志搜索框
Vue.component("http-access-log-search-box", {
props: ["v-ip", "v-domain", "v-keyword"],
data: function () {
let ip = this.vIp
if (ip == null) {
ip = ""
}
let domain = this.vDomain
if (domain == null) {
domain = ""
}
let keyword = this.vKeyword
if (keyword == null) {
keyword = ""
}
return {
ip: ip,
domain: domain,
keyword: keyword
}
},
template: `<div>
<div class="margin"></div>
<div class="ui fields inline">
<div class="ui field">
<div class="ui input left labeled small">
<span class="ui label basic" style="font-weight: normal">IP</span>
<input type="text" name="ip" placeholder="x.x.x.x" size="15" v-model="ip"/>
</div>
</div>
<div class="ui field">
<div class="ui input left labeled small">
<span class="ui label basic" style="font-weight: normal">域名</span>
<input type="text" name="domain" placeholder="xxx.com" size="15" v-model="domain"/>
</div>
</div>
<div class="ui field">
<div class="ui input left labeled small">
<span class="ui label basic" style="font-weight: normal">关键词</span>
<input type="text" name="keyword" v-model="keyword" placeholder="路径、UserAgent等..." size="18"/>
</div>
</div>
<slot></slot>
<div class="ui field">
<button class="ui button small" type="submit">查找</button>
</div>
</div>
</div>`
})

20
web/public/js/world.js Normal file

File diff suppressed because one or more lines are too long

View File

@@ -4,4 +4,10 @@
.right-box {
top: 10em;
}
.row {
line-height: 4;
}
.step.active {
font-weight: bold;
}
/*# sourceMappingURL=createNode.css.map */

View File

@@ -1 +1 @@
{"version":3,"sources":["createNode.less"],"names":[],"mappings":"AAAA;EACC,SAAA;;AAGD;EACC,SAAA","file":"createNode.css"}
{"version":3,"sources":["createNode.less"],"names":[],"mappings":"AAAA;EACC,SAAA;;AAGD;EACC,SAAA;;AAGD;EACC,cAAA;;AAGD,KAAK;EACJ,iBAAA","file":"createNode.css"}

View File

@@ -1,9 +1,23 @@
{$layout}
{$template "/clusters/cluster/menu"}
{$template "/left_menu"}
{$template "/code_editor"}
<div class="right-box">
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<div class="ui steps small fluid">
<div class="ui step" :class="{active: step == 'info'}">
<div class="content">1. 填写节点信息</div>
</div>
<div class="ui step" :class="{active: step == 'install'}">
<div class="content">2. 安装节点</div>
</div>
<div class="ui step" :class="{active: step == 'finish'}">
<div class="content">3. 完成</div>
</div>
</div>
<!-- 填写信息 -->
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success" v-show="step == 'info'">
<input type="hidden" name="clusterId" :value="clusterId"/>
<table class="ui table definition selectable">
<tr>
@@ -51,7 +65,7 @@
<td>SSH主机地址</td>
<td>
<input type="text" name="sshHost" maxlength="64"/>
<p class="comment">比如192.168.1.100</p>
<p class="comment">比如192.168.1.100,用于远程安装节点应用程序。</p>
</td>
</tr>
<tr>
@@ -64,11 +78,93 @@
<tr>
<td>SSH登录认证</td>
<td>
<grant-selector></grant-selector>
<grant-selector :v-node-cluster-id="clusterId"></grant-selector>
</td>
</tr>
</tbody>
</table>
<submit-btn></submit-btn>
<submit-btn>下一步</submit-btn>
</form>
<!-- 安装节点 -->
<div v-if="step == 'install'">
<div class="ui tabular menu">
<a href="" class="item" :class="{active: installMethod == 'remote'}" @click.prevent="switchInstallMethod('remote')">远程安装</a>
<a href="" class="item" :class="{active: installMethod == 'manual'}" @click.prevent="switchInstallMethod('manual')">手动安装</a>
</div>
<!-- 远程安装 -->
<div v-if="installMethod == 'remote'">
<form class="ui form">
<table class="ui table definition selectable">
<tr>
<td class="title">SSH主机地址 *</td>
<td>
<input type="text" name="sshHost2" maxlength="64" v-model="sshHost" ref="installSSHHostRef" style="width: 16em" @change="changeSSHHost"/>
<div v-if="node.addresses != null && node.addresses.length > 1" style="margin-top: 1em">
<a href="" class="ui label small basic" v-for="addr in node.addresses" title="点击使用" @click.prevent="selectSSHHost(addr)">{{addr}}</a>
</div>
<p class="comment">比如192.168.1.100,用于远程安装节点应用程序。</p>
</td>
</tr>
<tr>
<td>SSH主机端口 *</td>
<td>
<input type="text" name="sshPort2" maxlength="5" v-model="sshPort" style="width:5em"/>
<p class="comment"><node-login-suggest-ports ref="nodeLoginSuggestPortsRef" @select="selectLoginPort" @auto-select="autoSelectLoginPort"></node-login-suggest-ports></p>
</td>
</tr>
<tr>
<td>SSH登录认证 *</td>
<td>
<grant-selector :v-grant="node.grant" :v-node-cluster-id="clusterId" @change="changeGrant"></grant-selector>
</td>
</tr>
</table>
<div v-if="installStatus != null && (installStatus.isRunning || installStatus.isFinished)"
class="ui segment installing-box">
<div v-if="installStatus.isRunning" class="blue">安装中...</div>
<div v-if="installStatus.isFinished">
<span v-if="installStatus.isOk" class="green">已安装成功</span>
<span v-if="!installStatus.isOk" class="red">安装过程中发生错误:{{installStatus.error}}</span>
</div>
</div>
<button class="ui primary button" type="button" @click.prevent="install" v-if="!isInstalling">远程安装</button>
<button class="ui button disabled" v-if="isInstalling">正在安装</button>
<a href="" @click.prevent="finish" v-if="!isInstalling" style="margin-left: 1em; float: right">跳过安装</a>
</form>
</div>
<!-- 手动安装 -->
<div v-if="installMethod == 'manual'">
<div class="row">在边缘节点安装目录下,复制<code-label>configs/api.template.yaml</code-label><code-label>configs/api.yaml</code-label>,然后修改文件里面的内容为以下内容:</div>
<source-code-box id="rpc-code" type="text/yaml">rpc:
endpoints: [ {{apiEndpoints}} ]
nodeId: "{{node.uniqueId}}"
secret: "{{node.secret}}"</source-code-box>
<div class="row">然后再使用<code-label>bin/edge-node start</code-label>命令启动节点。</div>
<div>
<div class="ui divider"></div>
<a href="" @click.prevent="finish">安装完成</a>
<a href="" @click.prevent="finish" style="float: right">跳过安装</a>
</div>
</div>
</div>
<!-- 完成 -->
<div v-show="step == 'finish'">
<div>
<div style="text-align: center; font-size: 1.4em; margin-top: 2.4em" v-if="isInstalled"><span class="green">"{{node.name}}"节点已被创建并安装成功。</span></div>
<div style="text-align: center; font-size: 1.4em; margin-top: 2.4em" v-if="!isInstalled"><span class="green">"{{node.name}}"节点已创建成功。</span></div>
<div style="text-align: center; margin-top: 3em">
<a :href="'/clusters/cluster/node?nodeId=' + nodeId + '&clusterId=' + clusterId" class="ui button primary" type="button">现在进入节点详情<i class="ui icon long arrow alternate right"></i></a>
</div>
<div style="text-align: center; margin-top: 1em">
<a href="" @click.prevent="createNext">继续创建下一个节点</a>
</div>
</div>
</div>
</div>

View File

@@ -1,3 +1,173 @@
Tea.context(function () {
this.success = NotifySuccess("保存成功", "/clusters/cluster/nodes?clusterId=" + this.clusterId);
});
this.nodeId = 0
this.node = {}
this.sshHost = ""
this.sshPort = ""
this.grantId = 0
this.step = "info"
this.success = function (resp) {
this.node = resp.data.node
this.nodeId = this.node.id
this.sshHost = this.node.login.params.host
if (this.node.login.params.port > 0) {
this.sshPort = this.node.login.params.port
}
if (this.node.addresses.length > 0) {
this.sshHost = this.node.addresses[0]
}
this.step = "install"
this.$delay(function () {
this.$refs.installSSHHostRef.focus()
this.$refs.nodeLoginSuggestPortsRef.reload(this.sshHost)
})
}
/**
* 安装
*/
this.isInstalled = false
this.installMethod = "remote" // remote | manual
this.isInstalling = false
this.switchInstallMethod = function (method) {
this.installMethod = method
}
this.selectSSHHost = function (host) {
this.sshHost = host
this.changeSSHHost()
}
this.changeSSHHost = function () {
if (this.$refs.nodeLoginSuggestPortsRef != null) {
this.$refs.nodeLoginSuggestPortsRef.reload(this.sshHost)
}
}
this.selectLoginPort = function (port) {
this.sshPort = port
}
this.autoSelectLoginPort = function (port) {
if (this.sshPort == null || this.sshPort <= 0) {
this.sshPort = port
}
}
this.install = function () {
if (this.node.grant != null) {
this.grantId = this.node.grant.id
}
this.isInstalling = true
this.$post(".createNodeInstall")
.params({
nodeId: this.node.id,
sshHost: this.sshHost,
sshPort: this.sshPort,
grantId: this.grantId
})
.timeout(30)
.success(function () {
this.$delay(function () {
this.isInstalling = true
this.reloadStatus(this.node.id)
})
})
.done(function () {
this.isInstalling = false
})
}
this.changeGrant = function (grant) {
if (grant != null) {
this.grantId = grant.id
} else {
this.grantId = 0
}
}
// 刷新状态
this.installStatus = null
this.reloadStatus = function (nodeId) {
let that = this
this.$post("/clusters/cluster/node/status")
.params({
nodeId: nodeId
})
.success(function (resp) {
this.installStatus = resp.data.installStatus
this.node.isInstalled = resp.data.isInstalled
if (this.node.isInstalled) {
this.isInstalling = false
this.isInstalled = true
this.finish()
}
if (!this.isInstalling) {
return
}
let nodeId = this.node.id
let errMsg = this.installStatus.error
if (this.installStatus.errorCode.length > 0 || errMsg.length > 0) {
this.isInstalling = false
}
switch (this.installStatus.errorCode) {
case "EMPTY_LOGIN":
case "EMPTY_SSH_HOST":
case "EMPTY_SSH_PORT":
case "EMPTY_GRANT":
teaweb.warn("需要填写SSH登录信息", function () {
teaweb.popup("/clusters/cluster/updateNodeSSH?nodeId=" + nodeId, {
height: "30em",
callback: function () {
that.install()
}
})
})
return
case "SSH_LOGIN_FAILED":
teaweb.warn("SSH登录失败请检查设置")
return
case "CREATE_ROOT_DIRECTORY_FAILED":
teaweb.warn("创建根目录失败,请检查目录权限或者手工创建:" + errMsg)
return
case "INSTALL_HELPER_FAILED":
teaweb.warn("安装助手失败:" + errMsg)
return
case "TEST_FAILED":
teaweb.warn("环境测试失败:" + errMsg)
return
case "RPC_TEST_FAILED":
teaweb.confirm("html:要安装的节点到API服务之间的RPC通讯测试失败具体错误" + errMsg + "<br/>现在修改API信息", function () {
window.location = "/api"
})
return
default:
shouldReload = true
//teaweb.warn("安装失败:" + errMsg)
}
})
.done(function () {
this.$delay(function () {
this.reloadStatus(nodeId)
}, 1000)
});
}
/**
* 完成
*/
this.finish = function () {
this.step = "finish"
}
this.createNext = function () {
teaweb.reload()
}
})

View File

@@ -4,4 +4,12 @@
.right-box {
top: 10em;
}
.row {
line-height: 4;
}
.step.active {
font-weight: bold;
}

View File

@@ -113,7 +113,7 @@
<td>SSH登录认证</td>
<td>
<div v-if="node.login != null && node.login.grant != null && node.login.grant.id > 0">
<a :href="'/clusters/grants/grant?grantId=' + node.login.grant.id">{{node.login.grant.name}}<span class="small">{{node.login.grant.methodName}}</span></a>
<a :href="'/clusters/grants/grant?grantId=' + node.login.grant.id">{{node.login.grant.name}}<span class="small grey">{{node.login.grant.methodName}}</span><span class="small grey" v-if="node.login.grant.username.length > 0">{{node.login.grant.username}}</span></a>
</div>
<span v-else class="disabled">
尚未设置

View File

@@ -68,7 +68,7 @@
<tr>
<td>SSH登录认证</td>
<td>
<grant-selector :v-grant="grant"></grant-selector>
<grant-selector :v-grant="grant" :v-node-cluster-id="clusterId"></grant-selector>
</td>
</tr>
<tr>

View File

@@ -35,7 +35,8 @@ Tea.context(function () {
id: this.node.login.grant.id,
name: this.node.login.grant.name,
method: this.node.login.grant.method,
methodName: this.node.login.grant.methodName
methodName: this.node.login.grant.methodName,
username: this.node.login.grant.username
}
}
}

View File

@@ -23,7 +23,7 @@
<tr>
<td>SSH登录认证 *</td>
<td>
<grant-selector :v-grant="grant"></grant-selector>
<grant-selector :v-grant="grant" :v-node-cluster-id="clusterId"></grant-selector>
</td>
</tr>
</table>

View File

@@ -0,0 +1,29 @@
.grants-box {
margin-top: 1em;
}
.grant-box {
float: left;
width: 12em;
height: 4.5em;
overflow-x: hidden;
overflow-y: auto;
margin-right: 0.5em;
border: 1px #ccc solid;
margin-bottom: 0.5em;
padding: 0.5em 0.3em;
text-align: left;
cursor: pointer;
}
.grant-box .small {
font-size: 0.8em;
}
.grant-box div.method {
margin-top: 0.3em;
}
.grant-box div.method .small {
font-size: 0.8em;
}
.grant-box::-webkit-scrollbar {
width: 4px;
}
/*# sourceMappingURL=selectPopup.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["selectPopup.less"],"names":[],"mappings":"AAAA;EACC,eAAA;;AAGD;EACC,WAAA;EACA,WAAA;EACA,aAAA;EACA,kBAAA;EACA,gBAAA;EACA,mBAAA;EACA,sBAAA;EACA,oBAAA;EACA,oBAAA;EACA,gBAAA;EACA,eAAA;;AAXD,UAaC;EACC,gBAAA;;AAdF,UAiBC,IAAG;EACF,iBAAA;;AAlBF,UAiBC,IAAG,OAGF;EACC,gBAAA;;AAKH,UAAU;EACT,UAAA","file":"selectPopup.css"}

View File

@@ -1,6 +1,6 @@
{$layout "layout_popup"}
<h3>选择认证</h3>
<h3>选择SSH认证</h3>
<form class="ui form">
<div class="ui fields inline">
@@ -10,12 +10,31 @@
</div>
</form>
<table class="ui table">
<tr>
<td>
<span v-if="grants.length == 0">暂时还没有可用的认证。</span>
<a class="ui label small basic" v-for="grant in grants" :class="{blue:grantId == grant.id}" @click.prevent="selectGrant(grant)" style="margin-bottom:0.5em">{{grant.name}} <span class="small">{{grant.methodName}}</span><span v-if="grant.username.length > 0" class="small">{{grant.username}}</span></a>
<p class="comment">请点击选中某个认证。</p>
</td>
</tr>
</table>
<div class="ui divider"></div>
<span v-if="grants.length == 0">暂时还没有可用的认证。</span>
<h4 v-if="suggestGrants.length > 0">可能的认证</h4>
<div class="grants-box" v-if="suggestGrants.length > 0">
<div class="grant-box" v-for="grant in suggestGrants">
<div :class="{blue:grantId == grant.id}" @click.prevent="selectGrant(grant)"><a href="">{{grant.name}}</a> <span v-if="grant.username.length > 0" class="small grey">{{grant.username}}</span>
<div class="method">
<span class="small grey">{{grant.methodName}}</span>
</div>
</div>
</div>
</div>
<div class="clear"></div>
<h4>全部认证</h4>
<div class="grants-box">
<div class="grant-box" v-for="grant in grants">
<div :class="{blue:grantId == grant.id}" @click.prevent="selectGrant(grant)"><a href="">{{grant.name}}</a> <span v-if="grant.username.length > 0" class="small grey">{{grant.username}}</span>
<div class="method">
<span class="small grey">{{grant.methodName}}</span>
</div>
</div>
</div>
</div>
<div class="clear"></div>
<p class="comment">请点击使用某个认证。</p>

View File

@@ -0,0 +1,33 @@
.grants-box {
margin-top: 1em;
}
.grant-box {
float: left;
width: 12em;
height: 4.5em;
overflow-x: hidden;
overflow-y: auto;
margin-right: 0.5em;
border: 1px #ccc solid;
margin-bottom: 0.5em;
padding: 0.5em 0.3em;
text-align: left;
cursor: pointer;
.small {
font-size: 0.8em;
}
div.method {
margin-top: 0.3em;
.small {
font-size: 0.8em;
}
}
}
.grant-box::-webkit-scrollbar {
width: 4px;
}

View File

@@ -4,11 +4,11 @@
<div class="ui four columns grid">
<div class="ui column">
<h4>域名<link-icon href="/ns"></link-icon></h4>
<h4>域名<link-icon href="/ns/domains"></link-icon></h4>
<div class="value"><span>{{board.countDomains}}</span></div>
</div>
<div class="ui column">
<h4>记录<link-icon href="/ns"></link-icon></h4>
<h4>记录<link-icon href="/ns/domains"></link-icon></h4>
<div class="value"><span>{{board.countRecords}}</span></div>
</div>
<div class="ui column">

View File

@@ -37,7 +37,10 @@ Tea.context(function () {
// 清理节点
this.cleanNode = function (nodeId) {
teaweb.popup("/db/cleanPopup?nodeId=" + nodeId)
teaweb.popup("/db/cleanPopup?nodeId=" + nodeId, {
width: "44em",
height: "26em"
})
}
// 显示错误信息

View File

@@ -61,6 +61,24 @@
</tr>
</tbody>
<!-- 华为云 -->
<tbody v-if="type == 'huaweiDNS'">
<tr>
<td>AccessKeyId *</td>
<td>
<input type="text" name="paramHuaweiAccessKeyId" maxlength="100"/>
<p class="comment">登录华为云控制台 -- 在"我的凭证 -- 访问密钥"中创建和获取。</p>
</td>
</tr>
<tr>
<td>AccessKeySecret *</td>
<td>
<input type="text" name="paramHuaweiAccessKeySecret" maxlength="100"/>
<p class="comment">登录华为云控制台 -- 在"我的凭证 -- 访问密钥"中创建和获取。</p>
</td>
</tr>
</tbody>
<!-- CloudFlare -->
<tbody v-if="type == 'cloudFlare'">
<tr>

View File

@@ -43,6 +43,18 @@
</tr>
</tbody>
<!-- HuaweiDNS -->
<tbody v-if="provider.type == 'huaweiDNS'">
<tr>
<td>AccessKeyId</td>
<td>{{provider.apiParams.accessKeyId}}</td>
</tr>
<tr>
<td>AccessKeySecret</td>
<td>{{provider.apiParams.accessKeySecret}}</td>
</tr>
</tbody>
<!-- CloudFlare -->
<tbody v-if="provider.type == 'cloudFlare'">
<tr>

View File

@@ -60,6 +60,24 @@
</tr>
</tbody>
<!-- 华为云 -->
<tbody v-if="provider.type == 'huaweiDNS'">
<tr>
<td>AccessKeyId *</td>
<td>
<input type="text" name="paramHuaweiAccessKeyId" maxlength="100" v-model="provider.params.accessKeyId"/>
<p class="comment">登录华为云控制台 -- 在"我的凭证 -- 访问密钥"中创建和获取。</p>
</td>
</tr>
<tr>
<td>AccessKeySecret *</td>
<td>
<input type="text" name="paramHuaweiAccessKeySecret" maxlength="100" v-model="provider.params.accessKeySecret"/>
<p class="comment">登录华为云控制台 -- 在"我的凭证 -- 访问密钥"中创建和获取。</p>
</td>
</tr>
</tbody>
<!-- CloudFlare -->
<tbody v-if="provider.type == 'cloudFlare'">

View File

@@ -47,10 +47,10 @@
<input type="text" name="otpCode" placeholder="请输入OTP动态密码" maxlength="6"/>
</div>
</div>
<div class="ui field">
<div class="ui field" v-if="rememberLogin">
<a href="" @click.prevent="showMoreOptions()">更多选项 <i class="icon angle" :class="{down:!moreOptionsVisible, up:moreOptionsVisible}"></i> </a>
</div>
<div class="ui field" v-show="moreOptionsVisible">
<div class="ui field" v-if="rememberLogin" v-show="moreOptionsVisible">
<div class="ui checkbox">
<input type="checkbox" name="remember" value="1" checked="checked"/>
<label>在这个电脑上记住登录14天</label>

View File

@@ -29,6 +29,46 @@
</div>
</td>
</tr>
<tr>
<td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr>
<tbody v-show="moreOptionsVisible">
<tr>
<td>SSH主机地址</td>
<td>
<div v-if="node.login != null && node.login.params != null && node.login.params.host != null">
<span v-if="node.login.params.host.length > 0">{{node.login.params.host}}</span>
<span v-else class="disabled">尚未设置</span>
</div>
<div v-else class="disabled">
尚未设置
</div>
</td>
</tr>
<tr>
<td>SSH主机端口</td>
<td>
<div v-if="node.login != null && node.login.params != null && node.login.params.host != null">
<span v-if="node.login.params.port > 0">{{node.login.params.port}}</span>
<span v-else class="disabled">尚未设置</span>
</div>
<span v-else class="disabled">
尚未设置
</span>
</td>
</tr>
<tr>
<td>SSH登录认证</td>
<td>
<div v-if="node.login != null && node.login.grant != null && node.login.grant.id > 0">
<a :href="'/clusters/grants/grant?grantId=' + node.login.grant.id">{{node.login.grant.name}}<span class="small grey">{{node.login.grant.methodName}}</span><span class="small grey" v-if="node.login.grant.username.length > 0">{{node.login.grant.username}}</span></a>
</div>
<span v-else class="disabled">
尚未设置
</span>
</td>
</tr>
</tbody>
</table>
<div class="ui divider"></div>
@@ -39,12 +79,12 @@
<td>
<div v-if="node.status.isActive">
<span class="green">运行中</span> &nbsp;
<!--<a href="" @click.prevent="stopNode()" v-if="!isStopping"><span>[通过SSH停止]</span></a>-->
<a href="" @click.prevent="stopNode()" v-if="!isStopping"><span>[通过SSH停止]</span></a>
<span v-if="isStopping">[停止中...]</span>
</div>
<div v-else>
<span class="red">已断开</span> &nbsp;
<!--<a href="" @click.prevent="startNode()" v-if="node.isInstalled && !isStarting"><span>[通过SSH启动]</span></a>-->
<a href="" @click.prevent="startNode()" v-if="node.isInstalled && !isStarting"><span>[通过SSH启动]</span></a>
<span v-if="node.isInstalled && isStarting">[启动中...]</span>
<a v-if="!node.isInstalled" :href="'/ns/clusters/cluster/node/install?clusterId=' + clusterId + '&nodeId=' + node.id" ><span>去安装&gt;</span></a>
</div>

View File

@@ -5,6 +5,7 @@
<h3>修改节点</h3>
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="nodeId" :value="node.id"/>
<input type="hidden" name="loginId" :value="loginId"/>
<table class="ui table definition selectable">
<tr>
<td class="title">节点名称 *</td>
@@ -31,6 +32,26 @@
<td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr>
<tbody v-show="moreOptionsVisible">
<tr>
<td>SSH主机地址</td>
<td>
<input type="text" name="sshHost" maxlength="64" v-model="sshHost"/>
<p class="comment">比如192.168.1.100</p>
</td>
</tr>
<tr>
<td>SSH主机端口</td>
<td>
<input type="text" name="sshPort" maxlength="5" v-model="sshPort"/>
<p class="comment">比如22。</p>
</td>
</tr>
<tr>
<td>SSH登录认证</td>
<td>
<grant-selector :v-grant="grant" :v-ns-cluster-id="clusterId"></grant-selector>
</td>
</tr>
<tr>
<td>是否启用</td>
<td>

View File

@@ -5,4 +5,31 @@ Tea.context(function () {
}
this.success = NotifySuccess("保存成功", "/ns/clusters/cluster/node?clusterId=" + this.clusterId + "&nodeId=" + this.node.id);
});
// 认证相关
this.grant = null
this.sshHost = ""
this.sshPort = ""
this.loginId = 0
if (this.node.login != null) {
this.loginId = this.node.login.id
if (this.node.login.params != null) {
this.sshHost = this.node.login.params.host
if (this.node.login.params.port > 0) {
this.sshPort = this.node.login.params.port
}
}
if (this.node.login.grant != null && typeof this.node.login.grant.id != "undefined") {
this.grant = {
id: this.node.login.grant.id,
name: this.node.login.grant.name,
method: this.node.login.grant.method,
methodName: this.node.login.grant.methodName,
username: this.node.login.grant.username
}
}
}
})

View File

@@ -5,7 +5,7 @@
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="clusterId" :value="clusterId"/>
<ns-access-log-ref-box :v-access-log-ref="accessLogRef"></ns-access-log-ref-box>
<ns-access-log-ref-box :v-access-log-ref="accessLogRef" :v-is-parent="false"></ns-access-log-ref-box>
<submit-btn></submit-btn>
</form>
</div>

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