Compare commits

...

64 Commits

Author SHA1 Message Date
刘祥超
fcbed5f63e 修复DNS看板中Y坐标轴不正确的问题 2021-07-21 09:02:28 +08:00
刘祥超
8d203a1d7c 优化界面 2021-07-21 08:07:46 +08:00
刘祥超
d6efb2dd12 域名记录可以停用/启用 2021-07-20 19:02:45 +08:00
刘祥超
6ea3938f74 自动替换API节点时增加对新节点的测试 2021-07-20 18:10:22 +08:00
刘祥超
63e773ce96 增加恢复模式 2021-07-20 17:15:17 +08:00
刘祥超
60b8e1041c 在各个地方支持IPv6 2021-07-20 10:55:25 +08:00
刘祥超
fb263c926b 创建网站服务后自动开启访问日志 2021-07-20 08:48:55 +08:00
刘祥超
dbbae30f5a 创建网站服务后自动开启Websocket 2021-07-20 08:29:50 +08:00
刘祥超
7b73c86fa4 增加内置的统计指标 2021-07-19 20:38:19 +08:00
刘祥超
c16c6ea6a6 优化指标图表 2021-07-19 18:19:33 +08:00
刘祥超
4ecba0040a Dashboard增加指标图表 2021-07-19 17:58:02 +08:00
刘祥超
f65aabe897 指标中列出使用当前指标的集群 2021-07-19 15:48:36 +08:00
刘祥超
de2337afe7 实现公用的统计指标 2021-07-19 15:23:20 +08:00
刘祥超
3360f2fc08 实现新的CC 2021-07-19 10:48:53 +08:00
刘祥超
7d8f3a9d9b IP名单中可以通过IP查找访问日志 2021-07-18 17:09:31 +08:00
刘祥超
63884bc836 优化WAF动作 2021-07-18 15:52:50 +08:00
刘祥超
9c231a2b8c WAF支持更多动作 2021-07-14 22:45:52 +08:00
刘祥超
6a5c979d2a 修复一个指标图表无法显示时间的Bug 2021-07-14 22:44:13 +08:00
刘祥超
094734ae1f 安装时不检查API地址是否可以绑定 2021-07-14 15:40:23 +08:00
刘祥超
97b4d457fd IP测试时同时也检查绑定的IP名单 2021-07-13 15:50:09 +08:00
刘祥超
9669c0ddda 路径规则改成路由规则 2021-07-13 14:28:06 +08:00
刘祥超
daf8257a1f 优化看板 2021-07-13 11:04:18 +08:00
刘祥超
40f32c7cf9 访问日志增加更容易可视化的时间显示 2021-07-12 18:06:43 +08:00
刘祥超
b8295e5cfc 改进界面 2021-07-12 17:37:10 +08:00
刘祥超
aba489217a 修复<keyword>组件的安全问题 2021-07-12 17:35:33 +08:00
刘祥超
7775689dfa 实现数据看板--WAF 2021-07-12 16:56:56 +08:00
刘祥超
90397b2b6f 修改一处文字 2021-07-12 10:27:35 +08:00
刘祥超
32683a2b59 管理界面可以切换风格 2021-07-12 10:21:17 +08:00
刘祥超
9e36847397 实现数据看板-DNS 2021-07-11 21:44:03 +08:00
刘祥超
4c82804622 实现数据看板--用户 2021-07-11 18:05:49 +08:00
刘祥超
cb911c474e 数据看板使用字节代替比特 2021-07-11 11:09:41 +08:00
刘祥超
ce2537d69d 节点看板磁盘缓存用量增加剩余磁盘空间 2021-07-08 19:47:49 +08:00
刘祥超
38746e83c5 节点看板增加缓存目录用量 2021-07-08 19:43:39 +08:00
刘祥超
be8988d309 优化关键词显示 2021-07-08 16:23:51 +08:00
刘祥超
ed59994db8 实现服务看板(企业版) 2021-07-07 19:55:32 +08:00
刘祥超
11a8d3ba70 优化交互 2021-07-07 19:02:39 +08:00
刘祥超
adf91bf3d0 节点列表增加下行流量,节点列表可以按CPU、内存、下行流量排序 2021-07-07 15:17:41 +08:00
刘祥超
7729b88a00 取消节点的监控图表,使用节点看板代替 2021-07-06 20:09:47 +08:00
刘祥超
e93aecabb4 实现节点看板(仅对企业版开放) 2021-07-06 20:06:20 +08:00
刘祥超
3cf43b2cc1 优化API节点详情页 2021-07-06 15:31:09 +08:00
刘祥超
3cba8cf98d 优化ACME任务修改页面 2021-07-06 15:30:06 +08:00
刘祥超
e2ca94c93b 实现集群看板(暂时只对企业版开放) 2021-07-05 11:38:07 +08:00
刘祥超
1c3e25ba1a 指标增加一些易读的名称等信息 2021-07-03 18:05:55 +08:00
刘祥超
f297f4ec52 实现基本的图表管理 2021-07-03 15:44:49 +08:00
刘祥超
534f10b8a7 指标数据增加占比数据 2021-07-01 10:39:07 +08:00
刘祥超
a7aed3d49e 实现基础的统计指标 2021-06-30 19:59:59 +08:00
刘祥超
9a915a48b5 SSH认证--私钥认证方式增加用户名选项 2021-06-30 14:55:21 +08:00
刘祥超
81a3b299f0 优化交互 2021-06-28 21:09:22 +08:00
刘祥超
a4edae6692 修改版本为0.2.5 2021-06-28 10:31:56 +08:00
刘祥超
047f6c409f API节点相关操作增加一些提示 2021-06-28 09:38:26 +08:00
刘祥超
963be2bc63 阶段性提交 2021-06-27 21:59:06 +08:00
刘祥超
bd34a9bd37 服务列表可以搜索端口号 2021-06-25 11:04:45 +08:00
刘祥超
32b93b2bd9 安装时默认设置访问日志保留30天 2021-06-24 21:06:43 +08:00
刘祥超
e7c4507b0b 集群设置左侧菜单显示TOA设置状态 2021-06-24 18:03:33 +08:00
刘祥超
17ae34c44f ACME申请证书时可以设置回调URL 2021-06-24 09:58:44 +08:00
刘祥超
b466ea2fd1 ACME申请证书时可以设置回调URL 2021-06-24 09:27:11 +08:00
刘祥超
8e4ee54f03 实现公用的IP名单 2021-06-23 13:12:33 +08:00
刘祥超
c99547d9e3 变更版本 2021-06-21 14:44:13 +08:00
刘祥超
1d22125d6e 管理员也支持AccessKey 2021-06-20 19:23:02 +08:00
刘祥超
d4689571cb 优化界面 2021-06-19 22:20:15 +08:00
刘祥超
cb1cb62e30 访问控制支持基本认证和子请求认证 2021-06-19 21:35:38 +08:00
刘祥超
77ad860d35 阶段性提交 2021-06-17 21:17:43 +08:00
刘祥超
5082561dc9 调整版本为0.2.3 2021-06-17 18:46:07 +08:00
刘祥超
7d4c80eedb 调整版本为0.2.2 2021-06-17 18:08:14 +08:00
432 changed files with 14715 additions and 2625 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*_plus.go

View File

@@ -1,7 +1,7 @@
# GoEdge目标
做一款人人用得起的CDN & WAF系统。
![架构](doc/screenshot.png)
![截图](doc/screenshot.png)
## 特性
* `免费` - 开源、免费、自由、开放
@@ -10,7 +10,8 @@
## 文档
* [新手指南](https://edge.teaos.cn/docs/QuickStart/Index.md)
* [文档](http://edge.teaos.cn/docs)
* [完整文档](https://edge.teaos.cn/docs)
* [开发者指南](https://edge.teaos.cn/docs/Developer/Build.md)
## 架构
![架构](doc/architect-zh.jpg)

View File

@@ -1,4 +1,5 @@
api.yaml
server.yaml
api_db.yaml
*.pem
*.pem
*.cache.json

View File

@@ -8,20 +8,22 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/nodes"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web"
_ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/gosock/pkg/gosock"
)
func main() {
app := apps.NewAppCmd().
Version(teaconst.Version).
Product(teaconst.ProductName).
Usage(teaconst.ProcessName+" [-v|start|stop|restart|service|daemon|reset]").
Usage(teaconst.ProcessName+" [-v|start|stop|restart|service|daemon|reset|recover]").
Option("-h", "show this help").
Option("-v", "show version").
Option("start", "start the service").
Option("stop", "stop the service").
Option("service", "register service into systemd").
Option("daemon", "start the service with daemon").
Option("reset", "reset configs")
Option("reset", "reset configs").
Option("recover", "enter recovery mode")
app.On("daemon", func() {
nodes.NewAdminNode().Daemon()
@@ -42,6 +44,19 @@ func main() {
}
fmt.Println("done")
})
app.On("recover", func() {
sock := gosock.NewTmpSock(teaconst.ProcessName)
if !sock.IsListening() {
fmt.Println("[ERROR]the service not started yet, you should start the service first")
return
}
_, err := sock.Send(&gosock.Command{Code: "recover"})
if err != nil {
fmt.Println("[ERROR]enter recovery mode failed: " + err.Error())
return
}
fmt.Println("enter recovery mode successfully")
})
app.Run(func() {
adminNode := nodes.NewAdminNode()
adminNode.Run()

11
go.mod
View File

@@ -9,12 +9,17 @@ 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/iwind/TeaGo v0.0.0-20210411134150-ddf57e240c2f
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-20210720054405-4030f271aa3f
github.com/miekg/dns v1.1.35
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/tealeg/xlsx/v3 v3.2.3
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c
google.golang.org/grpc v1.32.0
golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22
google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced // indirect
google.golang.org/grpc v1.38.0
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
)

72
go.sum
View File

@@ -13,6 +13,7 @@ github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -20,6 +21,7 @@ github.com/dgryski/go-rendezvous v0.0.0-20200624174652-8d2f3be8b2d9/go.mod h1:cu
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/frankban/quicktest v1.5.0 h1:Tb4jWdSpdjKzTUicPnY61PZxKbDoGa7ABbrReT3gQVY=
github.com/frankban/quicktest v1.5.0/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
@@ -42,22 +44,33 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/iwind/TeaGo v0.0.0-20210411134150-ddf57e240c2f h1:r2O8PONj/KiuZjJHVHn7KlCePUIjNtgAmvLfgRafQ8o=
github.com/iwind/TeaGo v0.0.0-20210411134150-ddf57e240c2f/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
github.com/iwind/TeaGo v0.0.0-20210628135026-38575a4ab060/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
github.com/iwind/TeaGo v0.0.0-20210720011303-fc255c995afa h1:woN88uEmRRUNFD7pRZEtX9heDcjFn0ClMxjF5ButKow=
github.com/iwind/TeaGo v0.0.0-20210720011303-fc255c995afa/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
github.com/iwind/gosock v0.0.0-20210706152215-08fe64b7a8a3 h1:BVHIjJU5RUX0grmvDtr3ZZ2z0hX+ideJRpg4NtBXZfs=
github.com/iwind/gosock v0.0.0-20210706152215-08fe64b7a8a3/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA=
github.com/iwind/gosock v0.0.0-20210720054405-4030f271aa3f h1:+mKLTd5tCCLXK+iY5Xjz58hau6qFv9chGqcZ4FWUiIs=
github.com/iwind/gosock v0.0.0-20210720054405-4030f271aa3f/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -65,10 +78,12 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs=
github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
@@ -97,12 +112,14 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tealeg/xlsx/v3 v3.2.3 h1:MXnVh+9Y8cUglowItTy2HL3Kv6z+q/0aNjeKuTsVqZQ=
github.com/tealeg/xlsx/v3 v3.2.3/go.mod h1:0hGmAEoZ48SS1ZAE6eqZJkJVXgOMY+8a33vjXa8S8HA=
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119 h1:YyPWX3jLOtYKulBR6AScGIs74lLrJcgeKRwcbAuQOG4=
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119/go.mod h1:/nuTSlK+okRfR/vnIPqR89fFKonnWPiZymN5ydRJkX8=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.opentelemetry.io/otel v0.7.0/go.mod h1:aZMyHG5TqDOXEgH2tyLiXSUKly1jT3yqE9PmrzIeCdo=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
@@ -115,9 +132,11 @@ golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+o
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -126,13 +145,16 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
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 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
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=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -146,35 +168,50 @@ golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced h1:c5geK1iMU3cDKtFrCVQIcjR3W+JOZMuhIyICMCTbtus=
google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -183,8 +220,10 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
@@ -192,6 +231,7 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@@ -2,17 +2,19 @@ package apps
import (
"fmt"
"github.com/iwind/TeaGo/Tea"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"github.com/iwind/gosock/pkg/gosock"
"os"
"os/exec"
"runtime"
"strconv"
"syscall"
"time"
)
// App命令帮助
// AppCmd App命令帮助
type AppCmd struct {
product string
version string
@@ -21,10 +23,14 @@ type AppCmd struct {
appendStrings []string
directives []*Directive
sock *gosock.Sock
}
func NewAppCmd() *AppCmd {
return &AppCmd{}
return &AppCmd{
sock: gosock.NewTmpSock(teaconst.ProcessName),
}
}
type CommandHelpOption struct {
@@ -32,25 +38,25 @@ type CommandHelpOption struct {
Description string
}
// 产品
// Product 产品
func (this *AppCmd) Product(product string) *AppCmd {
this.product = product
return this
}
// 版本
// Version 版本
func (this *AppCmd) Version(version string) *AppCmd {
this.version = version
return this
}
// 使用方法
// Usage 使用方法
func (this *AppCmd) Usage(usage string) *AppCmd {
this.usage = usage
return this
}
// 选项
// Option 选项
func (this *AppCmd) Option(code string, description string) *AppCmd {
this.options = append(this.options, &CommandHelpOption{
Code: code,
@@ -59,13 +65,13 @@ func (this *AppCmd) Option(code string, description string) *AppCmd {
return this
}
// 附加内容
// Append 附加内容
func (this *AppCmd) Append(appendString string) *AppCmd {
this.appendStrings = append(this.appendStrings, appendString)
return this
}
// 打印
// Print 打印
func (this *AppCmd) Print() {
fmt.Println(this.product + " v" + this.version)
@@ -104,7 +110,7 @@ func (this *AppCmd) Print() {
}
}
// 添加指令
// On 添加指令
func (this *AppCmd) On(arg string, callback func()) {
this.directives = append(this.directives, &Directive{
Arg: arg,
@@ -112,7 +118,7 @@ func (this *AppCmd) On(arg string, callback func()) {
})
}
// 运行
// Run 运行
func (this *AppCmd) Run(main func()) {
// 获取参数
args := os.Args[1:]
@@ -151,9 +157,6 @@ func (this *AppCmd) Run(main func()) {
return
}
// 记录PID
_ = this.writePid()
// 日志
writer := new(LogWriter)
writer.Init()
@@ -175,9 +178,9 @@ func (this *AppCmd) runHelp() {
// 启动
func (this *AppCmd) runStart() {
proc := this.checkPid()
if proc != nil {
fmt.Println(this.product+" already started, pid:", proc.Pid)
var pid = this.getPID()
if pid > 0 {
fmt.Println(this.product+" already started, pid:", pid)
return
}
@@ -193,18 +196,15 @@ func (this *AppCmd) runStart() {
// 停止
func (this *AppCmd) runStop() {
proc := this.checkPid()
if proc == nil {
var pid = this.getPID()
if pid == 0 {
fmt.Println(this.product + " not started yet")
return
}
// 停止进程
_ = proc.Signal(syscall.SIGQUIT)
_, _ = this.sock.Send(&gosock.Command{Code: "stop"})
// 在Windows上经常不能及时释放资源
_ = DeletePid(Tea.Root + "/bin/pid")
fmt.Println(this.product+" stopped ok, pid:", proc.Pid)
fmt.Println(this.product+" stopped ok, pid:", types.String(pid))
}
// 重启
@@ -216,20 +216,24 @@ func (this *AppCmd) runRestart() {
// 状态
func (this *AppCmd) runStatus() {
proc := this.checkPid()
if proc == nil {
var pid = this.getPID()
if pid == 0 {
fmt.Println(this.product + " not started yet")
} else {
fmt.Println(this.product + " is running, pid: " + fmt.Sprintf("%d", proc.Pid))
return
}
fmt.Println(this.product + " is running, pid: " + types.String(pid))
}
// 检查PID
func (this *AppCmd) checkPid() *os.Process {
return CheckPid(Tea.Root + "/bin/pid")
}
// 获取当前的PID
func (this *AppCmd) getPID() int {
if !this.sock.IsListening() {
return 0
}
// 写入PID
func (this *AppCmd) writePid() error {
return WritePid(Tea.Root + "/bin/pid")
reply, err := this.sock.Send(&gosock.Command{Code: "pid"})
if err != nil {
return 0
}
return maps.NewMap(reply.Params).GetInt("pid")
}

View File

@@ -1,17 +0,0 @@
// +build !windows
package apps
import (
"os"
"syscall"
)
// lock file
func LockFile(fp *os.File) error {
return syscall.Flock(int(fp.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
}
func UnlockFile(fp *os.File) error {
return syscall.Flock(int(fp.Fd()), syscall.LOCK_UN)
}

View File

@@ -1,17 +0,0 @@
// +build windows
package apps
import (
"errors"
"os"
)
// lock file
func LockFile(fp *os.File) error {
return errors.New("not implemented on windows")
}
func UnlockFile(fp *os.File) error {
return errors.New("not implemented on windows")
}

View File

@@ -1,113 +0,0 @@
package apps
import (
"fmt"
"github.com/iwind/TeaGo/types"
"io/ioutil"
"os"
"runtime"
)
var pidFileList = []*os.File{}
// 检查Pid
func CheckPid(path string) *os.Process {
// windows上打开的文件是不能删除的
if runtime.GOOS == "windows" {
if os.Remove(path) == nil {
return nil
}
}
file, err := os.Open(path)
if err != nil {
return nil
}
defer func() {
_ = file.Close()
}()
// 是否能取得Lock
err = LockFile(file)
if err == nil {
_ = UnlockFile(file)
return nil
}
pidBytes, err := ioutil.ReadAll(file)
if err != nil {
return nil
}
pid := types.Int(string(pidBytes))
if pid <= 0 {
return nil
}
proc, _ := os.FindProcess(pid)
return proc
}
// 写入Pid
func WritePid(path string) error {
fp, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY|os.O_RDONLY, 0666)
if err != nil {
return err
}
if runtime.GOOS != "windows" {
err = LockFile(fp)
if err != nil {
return err
}
}
pidFileList = append(pidFileList, fp) // hold the file pointers
_, err = fp.WriteString(fmt.Sprintf("%d", os.Getpid()))
if err != nil {
return err
}
return nil
}
// 写入Ppid
func WritePpid(path string) error {
fp, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY|os.O_RDONLY, 0666)
if err != nil {
return err
}
if runtime.GOOS != "windows" {
err = LockFile(fp)
if err != nil {
return err
}
}
pidFileList = append(pidFileList, fp) // hold the file pointers
_, err = fp.WriteString(fmt.Sprintf("%d", os.Getppid()))
if err != nil {
return err
}
return nil
}
// 删除Pid
func DeletePid(path string) error {
_, err := os.Stat(path)
if err != nil {
if !os.IsNotExist(err) {
return nil
}
return err
}
for _, fp := range pidFileList {
_ = UnlockFile(fp)
_ = fp.Close()
}
return os.Remove(path)
}

View File

@@ -44,6 +44,7 @@ func loadAdminModuleMapping() (map[int64]*AdminModuleList, error) {
list := &AdminModuleList{
IsSuper: m.IsSuper,
Fullname: m.Fullname,
Theme: m.Theme,
}
for _, pbModule := range m.Modules {
@@ -132,6 +133,29 @@ func FindAdminFullname(adminId int64) string {
return ""
}
// FindAdminTheme 查找某个管理员选择的风格
func FindAdminTheme(adminId int64) string {
locker.Lock()
defer locker.Unlock()
list, ok := sharedAdminModuleMapping[adminId]
if ok {
return list.Theme
}
return ""
}
// UpdateAdminTheme 设置某个管理员的风格
func UpdateAdminTheme(adminId int64, theme string) {
locker.Lock()
defer locker.Unlock()
list, ok := sharedAdminModuleMapping[adminId]
if ok {
list.Theme = theme
}
}
// AllModuleMaps 所有权限列表
func AllModuleMaps() []maps.Map {
m := []maps.Map{

View File

@@ -6,6 +6,7 @@ type AdminModuleList struct {
IsSuper bool
Modules []*systemconfigs.AdminModule
Fullname string
Theme string
}
func (this *AdminModuleList) Allow(module string) bool {

View File

@@ -108,7 +108,6 @@ func (this *APIConfig) WriteFile(path string) error {
if err != nil {
return err
}
err = ioutil.WriteFile(path, data, 0666)
// 写入 ~/ 和 /etc/ 目录,因为是备份需要,所以不需要提示错误信息
// 写入 ~/.edge-admin/
@@ -141,5 +140,10 @@ func (this *APIConfig) WriteFile(path string) error {
}
}
return err
err = ioutil.WriteFile(path, data, 0666)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,40 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package configs
import (
"encoding/json"
"github.com/iwind/TeaGo/Tea"
"io/ioutil"
)
var plusConfigFile = "plus.cache.json"
type PlusConfig struct {
IsPlus bool `json:"isPlus"`
}
func ReadPlusConfig() *PlusConfig {
data, err := ioutil.ReadFile(Tea.ConfigFile(plusConfigFile))
if err != nil {
return &PlusConfig{IsPlus: false}
}
var config = &PlusConfig{IsPlus: false}
err = json.Unmarshal(data, config)
if err != nil {
return config
}
return config
}
func WritePlusConfig(config *PlusConfig) error {
configJSON, err := json.Marshal(config)
if err != nil {
return err
}
err = ioutil.WriteFile(Tea.ConfigFile(plusConfigFile), configJSON, 0777)
if err != nil {
return err
}
return nil
}

View File

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

7
internal/const/vars.go Normal file
View File

@@ -0,0 +1,7 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package teaconst
var (
IsRecoverMode = false
)

View File

@@ -10,11 +10,12 @@ import (
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/sessions"
"github.com/iwind/gosock/pkg/gosock"
"io/ioutil"
"log"
"net"
"os"
"os/exec"
"os/signal"
@@ -25,6 +26,7 @@ import (
var SharedAdminNode *AdminNode = nil
type AdminNode struct {
sock *gosock.Sock
subPIDs []int
}
@@ -42,7 +44,7 @@ func (this *AdminNode) Run() {
// 本地Sock
err := this.listenSock()
if err != nil {
logs.Println("NODE" + err.Error())
logs.Println("[NODE]", err.Error())
return
}
@@ -89,11 +91,11 @@ func (this *AdminNode) Run() {
// Daemon 实现守护进程
func (this *AdminNode) Daemon() {
path := os.TempDir() + "/edge-admin.sock"
var sock = gosock.NewTmpSock(teaconst.ProcessName)
isDebug := lists.ContainsString(os.Args, "debug")
isDebug = true
for {
conn, err := net.DialTimeout("unix", path, 1*time.Second)
conn, err := sock.Dial()
if err != nil {
if isDebug {
log.Println("[DAEMON]starting ...")
@@ -281,37 +283,59 @@ func (this *AdminNode) genSecret() string {
// 监听本地sock
func (this *AdminNode) listenSock() error {
path := os.TempDir() + "/edge-admin.sock"
this.sock = gosock.NewTmpSock(teaconst.ProcessName)
// 检查是否已经存
_, err := os.Stat(path)
if err == nil {
conn, err := net.Dial("unix", path)
if err != nil {
_ = os.Remove(path)
// 检查是否在运行
if this.sock.IsListening() {
reply, err := this.sock.Send(&gosock.Command{Code: "pid"})
if err == nil {
return errors.New("error: the process is already running, pid: " + maps.NewMap(reply.Params).GetString("pid"))
} else {
_ = conn.Close()
return errors.New("error: the process is already running")
}
}
// 新的监听任务
listener, err := net.Listen("unix", path)
if err != nil {
return err
}
events.On(events.EventQuit, func() {
logs.Println("NODE", "quit unix sock")
_ = listener.Close()
})
// 启动监听
go func() {
for {
_, err := listener.Accept()
if err != nil {
return
this.sock.OnCommand(func(cmd *gosock.Command) {
switch cmd.Code {
case "pid":
_ = cmd.Reply(&gosock.Command{
Code: "pid",
Params: map[string]interface{}{
"pid": os.Getpid(),
},
})
case "stop":
_ = cmd.ReplyOk()
// 关闭子进程
for _, pid := range this.subPIDs {
p, err := os.FindProcess(pid)
if err == nil && p != nil {
_ = p.Kill()
}
}
// 退出主进程
events.Notify(events.EventQuit)
os.Exit(0)
case "recover":
teaconst.IsRecoverMode = true
_ = cmd.ReplyOk()
}
})
err := this.sock.Listen()
if err != nil {
logs.Println("NODE", err.Error())
}
}()
events.On(events.EventQuit, func() {
logs.Println("NODE", "quit unix sock")
_ = this.sock.Close()
})
return nil
}

View File

@@ -1,7 +0,0 @@
package rpc
import "context"
type ContextInterface interface {
AdminContext() context.Context
}

View File

@@ -51,6 +51,10 @@ func NewRPCClient(apiConfig *configs.APIConfig) (*RPCClient, error) {
return client, nil
}
func (this *RPCClient) APITokenRPC() pb.APITokenServiceClient {
return pb.NewAPITokenServiceClient(this.pickConn())
}
func (this *RPCClient) AdminRPC() pb.AdminServiceClient {
return pb.NewAdminServiceClient(this.pickConn())
}
@@ -207,6 +211,10 @@ func (this *RPCClient) HTTPFirewallRuleSetRPC() pb.HTTPFirewallRuleSetServiceCli
return pb.NewHTTPFirewallRuleSetServiceClient(this.pickConn())
}
func (this *RPCClient) FirewallRPC() pb.FirewallServiceClient {
return pb.NewFirewallServiceClient(this.pickConn())
}
func (this *RPCClient) HTTPLocationRPC() pb.HTTPLocationServiceClient {
return pb.NewHTTPLocationServiceClient(this.pickConn())
}
@@ -228,6 +236,10 @@ func (this *RPCClient) HTTPFastcgiRPC() pb.HTTPFastcgiServiceClient {
return pb.NewHTTPFastcgiServiceClient(this.pickConn())
}
func (this *RPCClient) HTTPAuthPolicyRPC() pb.HTTPAuthPolicyServiceClient {
return pb.NewHTTPAuthPolicyServiceClient(this.pickConn())
}
func (this *RPCClient) SSLCertRPC() pb.SSLCertServiceClient {
return pb.NewSSLCertServiceClient(this.pickConn())
}
@@ -384,6 +396,34 @@ func (this *RPCClient) NSAccessLogRPC() pb.NSAccessLogServiceClient {
return pb.NewNSAccessLogServiceClient(this.pickConn())
}
func (this *RPCClient) NSRPC() pb.NSServiceClient {
return pb.NewNSServiceClient(this.pickConn())
}
func (this *RPCClient) MetricItemRPC() pb.MetricItemServiceClient {
return pb.NewMetricItemServiceClient(this.pickConn())
}
func (this *RPCClient) MetricStatRPC() pb.MetricStatServiceClient {
return pb.NewMetricStatServiceClient(this.pickConn())
}
func (this *RPCClient) MetricChartRPC() pb.MetricChartServiceClient {
return pb.NewMetricChartServiceClient(this.pickConn())
}
func (this *RPCClient) NodeClusterMetricItemRPC() pb.NodeClusterMetricItemServiceClient {
return pb.NewNodeClusterMetricItemServiceClient(this.pickConn())
}
func (this *RPCClient) ServerStatBoardRPC() pb.ServerStatBoardServiceClient {
return pb.NewServerStatBoardServiceClient(this.pickConn())
}
func (this *RPCClient) ServerStatBoardChartRPC() pb.ServerStatBoardChartServiceClient {
return pb.NewServerStatBoardChartServiceClient(this.pickConn())
}
// Context 构造Admin上下文
func (this *RPCClient) Context(adminId int64) context.Context {
ctx := context.Background()

View File

@@ -3,6 +3,7 @@
package tasks
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/events"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
@@ -10,6 +11,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/logs"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
@@ -28,6 +30,13 @@ func NewAuthorityTask() *AuthorityTask {
}
func (this *AuthorityTask) Start() {
// 从缓存中读取
config := configs.ReadPlusConfig()
if config != nil {
teaconst.IsPlus = config.IsPlus
}
// 开始计时器
ticker := time.NewTicker(10 * time.Minute)
if Tea.IsTesting() {
// 快速测试
@@ -65,10 +74,16 @@ func (this *AuthorityTask) Loop() error {
if err != nil {
return err
}
if resp.AuthorityKey != nil {
var oldState = teaconst.IsPlus
if resp.AuthorityKey != nil && len(resp.AuthorityKey.Value) > 0 && resp.AuthorityKey.DayTo >= timeutil.Format("Y-m-d") {
teaconst.IsPlus = true
} else {
teaconst.IsPlus = false
}
if oldState != teaconst.IsPlus {
_ = configs.WritePlusConfig(&configs.PlusConfig{IsPlus: teaconst.IsPlus})
}
return nil
}

View File

@@ -1,15 +1,22 @@
package tasks
import (
"context"
"crypto/tls"
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/events"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/setup"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/logs"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"net/url"
"sort"
"strings"
"sync"
"time"
)
@@ -20,7 +27,7 @@ func init() {
})
}
// API节点同步任务
// SyncAPINodesTask API节点同步任务
type SyncAPINodesTask struct {
}
@@ -44,7 +51,7 @@ func (this *SyncAPINodesTask) Start() {
func (this *SyncAPINodesTask) Loop() error {
// 如果还没有安装直接返回
if !setup.IsConfigured() {
if !setup.IsConfigured() || teaconst.IsRecoverMode {
return nil
}
@@ -75,6 +82,12 @@ func (this *SyncAPINodesTask) Loop() error {
return nil
}
// 测试是否有API节点可用
hasOk := this.testEndpoints(newEndpoints)
if !hasOk {
return nil
}
// 修改RPC对象配置
config.RPC.Endpoints = newEndpoints
err = rpcClient.UpdateConfig(config)
@@ -96,3 +109,47 @@ func (this *SyncAPINodesTask) isSame(endpoints1 []string, endpoints2 []string) b
sort.Strings(endpoints2)
return strings.Join(endpoints1, "&") == strings.Join(endpoints2, "&")
}
func (this *SyncAPINodesTask) testEndpoints(endpoints []string) bool {
if len(endpoints) == 0 {
return false
}
var wg = sync.WaitGroup{}
wg.Add(len(endpoints))
var ok = false
for _, endpoint := range endpoints {
go func(endpoint string) {
defer wg.Done()
u, err := url.Parse(endpoint)
if err != nil {
return
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer func() {
cancel()
}()
var conn *grpc.ClientConn
if u.Scheme == "http" {
conn, err = grpc.DialContext(ctx, u.Host, grpc.WithInsecure(), grpc.WithBlock())
} else if u.Scheme == "https" {
conn, err = grpc.DialContext(ctx, u.Host, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
InsecureSkipVerify: true,
})), grpc.WithBlock())
}
if err != nil {
return
}
_ = conn.Close()
ok = true
}(endpoint)
}
wg.Wait()
return ok
}

View File

@@ -1,6 +1,7 @@
package tasks
import (
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/events"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/setup"
@@ -40,7 +41,7 @@ func (this *SyncClusterTask) Start() {
func (this *SyncClusterTask) loop() error {
// 如果还没有安装直接返回
if !setup.IsConfigured() {
if !setup.IsConfigured() || teaconst.IsRecoverMode {
return nil
}

View File

@@ -4,7 +4,7 @@ import (
"github.com/miekg/dns"
)
// 获取CNAME
// LookupCNAME 获取CNAME
func LookupCNAME(host string) (string, error) {
config, err := dns.ClientConfigFromFile("/etc/resolv.conf")
if err != nil {

View File

@@ -0,0 +1,15 @@
package actionutils
import (
"context"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/iwind/TeaGo/maps"
)
type ActionInterface interface {
RPC() *rpc.RPCClient
AdminContext() context.Context
ViewData() maps.Map
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
"net/http"
"strconv"
)
@@ -126,3 +127,8 @@ func (this *ParentAction) AdminContext() context.Context {
}
return this.rpcClient.Context(this.AdminId())
}
// ViewData 视图里可以使用的数据
func (this *ParentAction) ViewData() maps.Map {
return this.Data
}

View File

@@ -0,0 +1,49 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package accesskeys
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
)
type CreatePopupAction struct {
actionutils.ParentAction
}
func (this *CreatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreatePopupAction) RunGet(params struct {
AdminId int64
}) {
this.Data["adminId"] = params.AdminId
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
AdminId int64
Description string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.
Field("description", params.Description).
Require("请输入备注")
accessKeyIdResp, err := this.RPC().UserAccessKeyRPC().CreateUserAccessKey(this.AdminContext(), &pb.CreateUserAccessKeyRequest{
AdminId: params.AdminId,
Description: params.Description,
})
if err != nil {
this.ErrorPage(err)
return
}
defer this.CreateLogInfo("创建AccessKey %d", accessKeyIdResp.UserAccessKeyId)
this.Success()
}

View File

@@ -0,0 +1,24 @@
package accesskeys
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteAction struct {
actionutils.ParentAction
}
func (this *DeleteAction) RunPost(params struct {
AccessKeyId int64
}) {
defer this.CreateLogInfo("删除AccessKey %d", params.AccessKeyId)
_, err := this.RPC().UserAccessKeyRPC().DeleteUserAccessKey(this.AdminContext(), &pb.DeleteUserAccessKeyRequest{UserAccessKeyId: params.AccessKeyId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,54 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package accesskeys
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/admins/adminutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "accessKey")
}
func (this *IndexAction) RunGet(params struct {
AdminId int64
}) {
err := adminutils.InitAdmin(this.Parent(), params.AdminId)
if err != nil {
this.ErrorPage(err)
return
}
accessKeysResp, err := this.RPC().UserAccessKeyRPC().FindAllEnabledUserAccessKeys(this.AdminContext(), &pb.FindAllEnabledUserAccessKeysRequest{AdminId: params.AdminId})
if err != nil {
this.ErrorPage(err)
return
}
accessKeyMaps := []maps.Map{}
for _, accessKey := range accessKeysResp.UserAccessKeys {
var accessedTime string
if accessKey.AccessedAt > 0 {
accessedTime = timeutil.FormatTime("Y-m-d H:i:s", accessKey.AccessedAt)
}
accessKeyMaps = append(accessKeyMaps, maps.Map{
"id": accessKey.Id,
"isOn": accessKey.IsOn,
"uniqueId": accessKey.UniqueId,
"secret": accessKey.Secret,
"description": accessKey.Description,
"accessedTime": accessedTime,
})
}
this.Data["accessKeys"] = accessKeyMaps
this.Show()
}

View File

@@ -0,0 +1,28 @@
package accesskeys
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type UpdateIsOnAction struct {
actionutils.ParentAction
}
func (this *UpdateIsOnAction) RunPost(params struct {
AccessKeyId int64
IsOn bool
}) {
defer this.CreateLogInfo("设置AccessKey %d 启用状态", params.AccessKeyId)
_, err := this.RPC().UserAccessKeyRPC().UpdateUserAccessKeyIsOn(this.AdminContext(), &pb.UpdateUserAccessKeyIsOnRequest{
UserAccessKeyId: params.AccessKeyId,
IsOn: params.IsOn,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -30,13 +30,22 @@ func (this *AdminAction) RunGet(params struct {
return
}
// AccessKey数量
countAccessKeyResp, err := this.RPC().UserAccessKeyRPC().CountAllEnabledUserAccessKeys(this.AdminContext(), &pb.CountAllEnabledUserAccessKeysRequest{AdminId: params.AdminId})
if err != nil {
this.ErrorPage(err)
return
}
countAccessKeys := countAccessKeyResp.Count
this.Data["admin"] = maps.Map{
"id": admin.Id,
"fullname": admin.Fullname,
"username": admin.Username,
"isOn": admin.IsOn,
"isSuper": admin.IsSuper,
"canLogin": admin.CanLogin,
"id": admin.Id,
"fullname": admin.Fullname,
"username": admin.Username,
"isOn": admin.IsOn,
"isSuper": admin.IsSuper,
"canLogin": admin.CanLogin,
"countAccessKeys": countAccessKeys,
}
// 权限

View File

@@ -0,0 +1,38 @@
package adminutils
import (
"errors"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
// InitAdmin 查找管理员基本信息
func InitAdmin(p *actionutils.ParentAction, adminId int64) error {
// 管理员信息
resp, err := p.RPC().AdminRPC().FindEnabledAdmin(p.AdminContext(), &pb.FindEnabledAdminRequest{AdminId: adminId})
if err != nil {
return err
}
if resp.Admin == nil {
return errors.New("not found admin")
}
var admin = resp.Admin
// AccessKey数量
countAccessKeysResp, err := p.RPC().UserAccessKeyRPC().CountAllEnabledUserAccessKeys(p.AdminContext(), &pb.CountAllEnabledUserAccessKeysRequest{
AdminId: adminId,
UserId: 0,
})
if err != nil {
return err
}
p.Data["admin"] = maps.Map{
"id": admin.Id,
"username": admin.Username,
"fullname": admin.Fullname,
"countAccessKeys": countAccessKeysResp.Count,
}
return nil
}

View File

@@ -2,6 +2,7 @@ package admins
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/admins/accesskeys"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
@@ -19,6 +20,15 @@ func init() {
Get("/admin", new(AdminAction)).
Get("/otpQrcode", new(OtpQrcodeAction)).
Post("/options", new(OptionsAction)).
// AccessKeys
Prefix("/admins/accessKeys").
Get("", new(accesskeys.IndexAction)).
GetPost("/createPopup", new(accesskeys.CreatePopupAction)).
Post("/delete", new(accesskeys.DeleteAction)).
Post("/updateIsOn", new(accesskeys.UpdateIsOnAction)).
EndAll()
})
}

View File

@@ -39,14 +39,23 @@ func (this *UpdateAction) RunGet(params struct {
otpLoginIsOn = admin.OtpLogin.IsOn
}
// AccessKey数量
countAccessKeyResp, err := this.RPC().UserAccessKeyRPC().CountAllEnabledUserAccessKeys(this.AdminContext(), &pb.CountAllEnabledUserAccessKeysRequest{AdminId: params.AdminId})
if err != nil {
this.ErrorPage(err)
return
}
countAccessKeys := countAccessKeyResp.Count
this.Data["admin"] = maps.Map{
"id": admin.Id,
"fullname": admin.Fullname,
"username": admin.Username,
"isOn": admin.IsOn,
"isSuper": admin.IsSuper,
"canLogin": admin.CanLogin,
"otpLoginIsOn": otpLoginIsOn,
"id": admin.Id,
"fullname": admin.Fullname,
"username": admin.Username,
"isOn": admin.IsOn,
"isSuper": admin.IsSuper,
"canLogin": admin.CanLogin,
"otpLoginIsOn": otpLoginIsOn,
"countAccessKeys": countAccessKeys,
}
// 权限

View File

@@ -32,6 +32,7 @@ func (this *IndexAction) RunGet(params struct {
}
// 监听地址
var hasHTTPS = false
httpConfig := &serverconfigs.HTTPProtocolConfig{}
if len(node.HttpJSON) > 0 {
err = json.Unmarshal(node.HttpJSON, httpConfig)
@@ -47,6 +48,7 @@ func (this *IndexAction) RunGet(params struct {
this.ErrorPage(err)
return
}
hasHTTPS = len(httpsConfig.Listen) > 0
}
// 监听地址
@@ -56,7 +58,6 @@ func (this *IndexAction) RunGet(params struct {
// 证书信息
certs := []*sslconfigs.SSLCertConfig{}
sslPolicyId := int64(0)
if httpsConfig.SSLPolicyRef != nil && httpsConfig.SSLPolicyRef.SSLPolicyId > 0 {
sslPolicyConfigResp, err := this.RPC().SSLPolicyRPC().FindEnabledSSLPolicyConfig(this.AdminContext(), &pb.FindEnabledSSLPolicyConfigRequest{SslPolicyId: httpsConfig.SSLPolicyRef.SSLPolicyId})
if err != nil {
@@ -65,8 +66,6 @@ func (this *IndexAction) RunGet(params struct {
}
sslPolicyConfigJSON := sslPolicyConfigResp.SslPolicyJSON
if len(sslPolicyConfigJSON) > 0 {
sslPolicyId = httpsConfig.SSLPolicyRef.SSLPolicyId
sslPolicy := &sslconfigs.SSLPolicy{}
err = json.Unmarshal(sslPolicyConfigJSON, sslPolicy)
if err != nil {
@@ -112,6 +111,10 @@ func (this *IndexAction) RunGet(params struct {
if httpsConfig.IsOn && len(httpsConfig.Listen) > 0 {
restAccessAddrs = append(restAccessAddrs, httpsConfig.Listen...)
}
if !hasHTTPS {
hasHTTPS = len(httpsConfig.Listen) > 0
}
}
}
@@ -124,7 +127,7 @@ func (this *IndexAction) RunGet(params struct {
"accessAddrs": accessAddrs,
"restIsOn": node.RestIsOn,
"restAccessAddrs": restAccessAddrs,
"hasHTTPS": sslPolicyId > 0,
"hasHTTPS": hasHTTPS,
"certs": certs,
}

View File

@@ -127,7 +127,7 @@ func (this *UpdateAction) RunGet(params struct {
this.Show()
}
// 保存基础设置
// RunPost 保存基础设置
func (this *UpdateAction) RunPost(params struct {
NodeId int64
Name string

View File

@@ -0,0 +1,182 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package boards
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/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
timeutil "github.com/iwind/TeaGo/utils/time"
"strconv"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "board", "")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
}) {
if !teaconst.IsPlus {
this.RedirectURL("/clusters/cluster?clusterId=" + strconv.FormatInt(params.ClusterId, 10))
return
}
resp, err := this.RPC().ServerStatBoardRPC().ComposeServerStatNodeClusterBoard(this.AdminContext(), &pb.ComposeServerStatNodeClusterBoardRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["board"] = maps.Map{
"countUsers": resp.CountUsers,
"countActiveNodes": resp.CountActiveNodes,
"countInactiveNodes": resp.CountInactiveNodes,
"countServers": resp.CountServers,
}
// 24小时流量趋势
{
var statMaps = []maps.Map{}
for _, stat := range resp.HourlyTrafficStats {
statMaps = append(statMaps, maps.Map{
"bytes": stat.Bytes,
"cachedBytes": stat.CachedBytes,
"countRequests": stat.CountRequests,
"countCachedRequests": stat.CountCachedRequests,
"countAttackRequests": stat.CountAttackRequests,
"attackBytes": stat.AttackBytes,
"day": stat.Hour[4:6] + "月" + stat.Hour[6:8] + "日",
"hour": stat.Hour[8:],
})
}
this.Data["hourlyStats"] = statMaps
}
// 15天流量趋势
{
var statMaps = []maps.Map{}
for _, stat := range resp.DailyTrafficStats {
statMaps = append(statMaps, maps.Map{
"bytes": stat.Bytes,
"cachedBytes": stat.CachedBytes,
"countRequests": stat.CountRequests,
"countCachedRequests": stat.CountCachedRequests,
"countAttackRequests": stat.CountAttackRequests,
"attackBytes": stat.AttackBytes,
"day": stat.Day[4:6] + "月" + stat.Day[6:] + "日",
})
}
this.Data["dailyStats"] = statMaps
}
// 节点排行
{
var statMaps = []maps.Map{}
for _, stat := range resp.TopNodeStats {
statMaps = append(statMaps, maps.Map{
"nodeId": stat.NodeId,
"nodeName": stat.NodeName,
"countRequests": stat.CountRequests,
"bytes": stat.Bytes,
})
}
this.Data["topNodeStats"] = statMaps
}
// 域名排行
{
var statMaps = []maps.Map{}
for _, stat := range resp.TopDomainStats {
statMaps = append(statMaps, maps.Map{
"serverId": stat.ServerId,
"domain": stat.Domain,
"countRequests": stat.CountRequests,
"bytes": stat.Bytes,
})
}
this.Data["topDomainStats"] = 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
}
// 指标
{
var chartMaps = []maps.Map{}
for _, chart := range resp.MetricDataCharts {
var statMaps = []maps.Map{}
for _, stat := range chart.MetricStats {
statMaps = append(statMaps, maps.Map{
"keys": stat.Keys,
"time": stat.Time,
"value": stat.Value,
"count": stat.SumCount,
"total": stat.SumTotal,
})
}
chartMaps = append(chartMaps, maps.Map{
"chart": maps.Map{
"id": chart.MetricChart.Id,
"name": chart.MetricChart.Name,
"widthDiv": chart.MetricChart.WidthDiv,
"isOn": chart.MetricChart.IsOn,
"maxItems": chart.MetricChart.MaxItems,
"type": chart.MetricChart.Type,
},
"item": maps.Map{
"id": chart.MetricChart.MetricItem.Id,
"name": chart.MetricChart.MetricItem.Name,
"period": chart.MetricChart.MetricItem.Period,
"periodUnit": chart.MetricChart.MetricItem.PeriodUnit,
"valueType": serverconfigs.FindMetricValueType(chart.MetricChart.MetricItem.Category, chart.MetricChart.MetricItem.Value),
"valueTypeName": serverconfigs.FindMetricValueName(chart.MetricChart.MetricItem.Category, chart.MetricChart.MetricItem.Value),
"keys": chart.MetricChart.MetricItem.Keys,
},
"stats": statMaps,
})
}
this.Data["metricCharts"] = chartMaps
}
this.Show()
}

View File

@@ -1,16 +1,11 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package cluster
import (
"encoding/json"
"fmt"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"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/logs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"strconv"
"time"
)
type IndexAction struct {
@@ -18,207 +13,15 @@ type IndexAction struct {
}
func (this *IndexAction) Init() {
this.Nav("", "node", "index")
this.SecondMenu("nodes")
this.Nav("", "", "")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
GroupId int64
RegionId int64
InstalledState int
ActiveState int
Keyword string
ClusterId int64
}) {
this.Data["groupId"] = params.GroupId
this.Data["regionId"] = params.RegionId
this.Data["installState"] = params.InstalledState
this.Data["activeState"] = params.ActiveState
this.Data["keyword"] = params.Keyword
countAllResp, err := this.RPC().NodeRPC().CountAllEnabledNodesMatch(this.AdminContext(), &pb.CountAllEnabledNodesMatchRequest{
NodeClusterId: params.ClusterId,
})
if err != nil {
this.ErrorPage(err)
return
if teaconst.IsPlus {
this.RedirectURL("/clusters/cluster/boards?clusterId=" + strconv.FormatInt(params.ClusterId, 10))
} else {
this.RedirectURL("/clusters/cluster/nodes?clusterId=" + strconv.FormatInt(params.ClusterId, 10))
}
this.Data["countAll"] = countAllResp.Count
countResp, err := this.RPC().NodeRPC().CountAllEnabledNodesMatch(this.AdminContext(), &pb.CountAllEnabledNodesMatchRequest{
NodeClusterId: params.ClusterId,
NodeGroupId: params.GroupId,
NodeRegionId: params.RegionId,
InstallState: types.Int32(params.InstalledState),
ActiveState: types.Int32(params.ActiveState),
Keyword: params.Keyword,
})
if err != nil {
this.ErrorPage(err)
return
}
page := this.NewPage(countResp.Count)
this.Data["page"] = page.AsHTML()
nodesResp, err := this.RPC().NodeRPC().ListEnabledNodesMatch(this.AdminContext(), &pb.ListEnabledNodesMatchRequest{
Offset: page.Offset,
Size: page.Size,
NodeClusterId: params.ClusterId,
NodeGroupId: params.GroupId,
NodeRegionId: params.RegionId,
InstallState: types.Int32(params.InstalledState),
ActiveState: types.Int32(params.ActiveState),
Keyword: params.Keyword,
})
if err != nil {
this.ErrorPage(err)
return
}
nodeMaps := []maps.Map{}
for _, node := range nodesResp.Nodes {
// 状态
isSynced := false
status := &nodeconfigs.NodeStatus{}
if len(node.StatusJSON) > 0 {
err = json.Unmarshal(node.StatusJSON, &status)
if err != nil {
logs.Error(err)
continue
}
status.IsActive = status.IsActive && time.Now().Unix()-status.UpdatedAt <= 60 // N秒之内认为活跃
isSynced = status.ConfigVersion == node.Version
}
// IP
ipAddressesResp, err := this.RPC().NodeIPAddressRPC().FindAllEnabledIPAddressesWithNodeId(this.AdminContext(), &pb.FindAllEnabledIPAddressesWithNodeIdRequest{
NodeId: node.Id,
Role: nodeconfigs.NodeRoleNode,
})
if err != nil {
this.ErrorPage(err)
return
}
ipAddresses := []maps.Map{}
for _, addr := range ipAddressesResp.Addresses {
ipAddresses = append(ipAddresses, maps.Map{
"id": addr.Id,
"name": addr.Name,
"ip": addr.Ip,
"canAccess": addr.CanAccess,
})
}
// 分组
var groupMap maps.Map = nil
if node.NodeGroup != nil {
groupMap = maps.Map{
"id": node.NodeGroup.Id,
"name": node.NodeGroup.Name,
}
}
// 区域
var regionMap maps.Map = nil
if node.NodeRegion != nil {
regionMap = maps.Map{
"id": node.NodeRegion.Id,
"name": node.NodeRegion.Name,
}
}
// DNS
dnsRouteNames := []string{}
for _, route := range node.DnsRoutes {
dnsRouteNames = append(dnsRouteNames, route.Name)
}
nodeMaps = append(nodeMaps, maps.Map{
"id": node.Id,
"name": node.Name,
"isInstalled": node.IsInstalled,
"isOn": node.IsOn,
"isUp": node.IsUp,
"installStatus": maps.Map{
"isRunning": node.InstallStatus.IsRunning,
"isFinished": node.InstallStatus.IsFinished,
"isOk": node.InstallStatus.IsOk,
"error": node.InstallStatus.Error,
},
"status": maps.Map{
"isActive": status.IsActive,
"updatedAt": status.UpdatedAt,
"hostname": status.Hostname,
"cpuUsage": status.CPUUsage,
"cpuUsageText": fmt.Sprintf("%.2f%%", status.CPUUsage*100),
"memUsage": status.MemoryUsage,
"memUsageText": fmt.Sprintf("%.2f%%", status.MemoryUsage*100),
},
"cluster": maps.Map{
"id": node.NodeCluster.Id,
"name": node.NodeCluster.Name,
},
"isSynced": isSynced,
"ipAddresses": ipAddresses,
"group": groupMap,
"region": regionMap,
"dnsRouteNames": dnsRouteNames,
})
}
this.Data["nodes"] = nodeMaps
// 所有分组
groupMaps := []maps.Map{}
groupsResp, err := this.RPC().NodeGroupRPC().FindAllEnabledNodeGroupsWithNodeClusterId(this.AdminContext(), &pb.FindAllEnabledNodeGroupsWithNodeClusterIdRequest{
NodeClusterId: params.ClusterId,
})
if err != nil {
this.ErrorPage(err)
return
}
for _, group := range groupsResp.NodeGroups {
countResp, err := this.RPC().NodeRPC().CountAllEnabledNodesWithNodeGroupId(this.AdminContext(), &pb.CountAllEnabledNodesWithNodeGroupIdRequest{NodeGroupId: group.Id})
if err != nil {
this.ErrorPage(err)
return
}
countNodes := countResp.Count
groupName := group.Name
if countNodes > 0 {
groupName += "(" + strconv.FormatInt(countNodes, 10) + ")"
}
groupMaps = append(groupMaps, maps.Map{
"id": group.Id,
"name": groupName,
"countNodes": countNodes,
})
}
this.Data["groups"] = groupMaps
// 所有区域
regionsResp, err := this.RPC().NodeRegionRPC().FindAllEnabledAndOnNodeRegions(this.AdminContext(), &pb.FindAllEnabledAndOnNodeRegionsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
regionMaps := []maps.Map{}
for _, region := range regionsResp.NodeRegions {
regionMaps = append(regionMaps, maps.Map{
"id": region.Id,
"name": region.Name,
})
}
this.Data["regions"] = regionMaps
// 记录最近访问
_, err = this.RPC().LatestItemRPC().IncreaseLatestItem(this.AdminContext(), &pb.IncreaseLatestItemRequest{
ItemType: "cluster",
ItemId: params.ClusterId,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Show()
}

View File

@@ -2,9 +2,10 @@ package cluster
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/boards"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/groups"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/monitor"
nodeboards "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/boards"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/thresholds"
clusters "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/clusterutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
@@ -18,6 +19,7 @@ func init() {
Helper(clusters.NewClusterHelper()).
Prefix("/clusters/cluster").
Get("", new(IndexAction)).
Get("/nodes", new(NodesAction)).
GetPost("/installNodes", new(InstallNodesAction)).
GetPost("/installRemote", new(InstallRemoteAction)).
Post("/installStatus", new(InstallStatusAction)).
@@ -30,31 +32,32 @@ func init() {
GetPost("/installManual", new(InstallManualAction)).
// 节点相关
Get("/node", new(node.IndexAction)).
GetPost("/node/update", new(node.UpdateAction)).
GetPost("/node/install", new(node.InstallAction)).
Post("/node/updateInstallStatus", new(node.UpdateInstallStatusAction)).
Post("/node/status", new(node.StatusAction)).
Get("/node/logs", new(node.LogsAction)).
Post("/node/start", new(node.StartAction)).
Post("/node/stop", new(node.StopAction)).
Post("/node/up", new(node.UpAction)).
Get("/node/monitor", new(monitor.IndexAction)).
Post("/node/monitor/cpu", new(monitor.CpuAction)).
Post("/node/monitor/memory", new(monitor.MemoryAction)).
Post("/node/monitor/load", new(monitor.LoadAction)).
Post("/node/monitor/trafficIn", new(monitor.TrafficInAction)).
Post("/node/monitor/trafficOut", new(monitor.TrafficOutAction)).
Post("/node/monitor/connections", new(monitor.ConnectionsAction)).
Get("/node/thresholds", new(thresholds.IndexAction)).
Prefix("/clusters/cluster/node").
Get("", new(node.IndexAction)).
GetPost("/update", new(node.UpdateAction)).
GetPost("/install", new(node.InstallAction)).
Post("/updateInstallStatus", new(node.UpdateInstallStatusAction)).
Post("/status", new(node.StatusAction)).
Get("/logs", new(node.LogsAction)).
Post("/start", new(node.StartAction)).
Post("/stop", new(node.StopAction)).
Post("/up", new(node.UpAction)).
Get("/thresholds", new(thresholds.IndexAction)).
Get("/detail", new(node.DetailAction)).
GetPost("/boards", new(nodeboards.IndexAction)).
// 分组相关
Get("/groups", new(groups.IndexAction)).
GetPost("/groups/createPopup", new(groups.CreatePopupAction)).
GetPost("/groups/updatePopup", new(groups.UpdatePopupAction)).
Post("/groups/delete", new(groups.DeleteAction)).
Post("/groups/sort", new(groups.SortAction)).
GetPost("/groups/selectPopup", new(groups.SelectPopupAction)).
Prefix("/clusters/cluster/groups").
Get("", new(groups.IndexAction)).
GetPost("/createPopup", new(groups.CreatePopupAction)).
GetPost("/updatePopup", new(groups.UpdatePopupAction)).
Post("/delete", new(groups.DeleteAction)).
Post("/sort", new(groups.SortAction)).
GetPost("/selectPopup", new(groups.SelectPopupAction)).
// 看板相关
Prefix("/clusters/cluster/boards").
Get("", new(boards.IndexAction)).
EndAll()
})

View File

@@ -0,0 +1,232 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package boards
import (
"encoding/json"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
timeutil "github.com/iwind/TeaGo/utils/time"
"strconv"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "node", "board")
this.SecondMenu("nodes")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
NodeId int64
}) {
err := nodeutils.InitNodeInfo(this, params.NodeId)
if err != nil {
this.ErrorPage(err)
return
}
if !teaconst.IsPlus {
this.RedirectURL("/clusters/cluster/node?clusterId=" + strconv.FormatInt(params.ClusterId, 10) + "&nodeId=" + strconv.FormatInt(params.NodeId, 10))
return
}
resp, err := this.RPC().ServerStatBoardRPC().ComposeServerStatNodeBoard(this.AdminContext(), &pb.ComposeServerStatNodeBoardRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["board"] = maps.Map{
"isActive": resp.IsActive,
"trafficInBytes": resp.TrafficInBytes,
"trafficOutBytes": resp.TrafficOutBytes,
"countConnections": resp.CountConnections,
"countRequests": resp.CountRequests,
"countAttackRequests": resp.CountAttackRequests,
"cpuUsage": resp.CpuUsage,
"memoryUsage": resp.MemoryUsage,
"memoryTotalSize": resp.MemoryTotalSize,
"load": resp.Load,
"cacheDiskSize": resp.CacheDiskSize,
"cacheMemorySize": resp.CacheMemorySize,
}
// 24小时流量趋势
{
var statMaps = []maps.Map{}
for _, stat := range resp.HourlyTrafficStats {
statMaps = append(statMaps, maps.Map{
"bytes": stat.Bytes,
"cachedBytes": stat.CachedBytes,
"countRequests": stat.CountRequests,
"countCachedRequests": stat.CountCachedRequests,
"countAttackRequests": stat.CountAttackRequests,
"attackBytes": stat.AttackBytes,
"day": stat.Hour[4:6] + "月" + stat.Hour[6:8] + "日",
"hour": stat.Hour[8:],
})
}
this.Data["hourlyStats"] = statMaps
}
// 15天流量趋势
{
var statMaps = []maps.Map{}
for _, stat := range resp.DailyTrafficStats {
statMaps = append(statMaps, maps.Map{
"bytes": stat.Bytes,
"cachedBytes": stat.CachedBytes,
"countRequests": stat.CountRequests,
"countCachedRequests": stat.CountCachedRequests,
"countAttackRequests": stat.CountAttackRequests,
"attackBytes": stat.AttackBytes,
"day": stat.Day[4:6] + "月" + stat.Day[6:] + "日",
})
}
this.Data["dailyStats"] = statMaps
}
// 域名排行
{
var statMaps = []maps.Map{}
for _, stat := range resp.TopDomainStats {
statMaps = append(statMaps, maps.Map{
"serverId": stat.ServerId,
"domain": stat.Domain,
"countRequests": stat.CountRequests,
"bytes": stat.Bytes,
})
}
this.Data["topDomainStats"] = 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
}
// CacheDirs
{
var statMaps = []maps.Map{}
for _, stat := range resp.CacheDirsValues {
var m = maps.Map{}
err = json.Unmarshal(stat.ValueJSON, &m)
if err != nil {
continue
}
statMaps = append(statMaps, maps.Map{
"time": timeutil.FormatTime("H:i", stat.CreatedAt),
"value": m,
})
}
this.Data["cacheDirValues"] = statMaps
}
// 指标
{
var chartMaps = []maps.Map{}
for _, chart := range resp.MetricDataCharts {
var statMaps = []maps.Map{}
for _, stat := range chart.MetricStats {
statMaps = append(statMaps, maps.Map{
"keys": stat.Keys,
"time": stat.Time,
"value": stat.Value,
"count": stat.SumCount,
"total": stat.SumTotal,
})
}
chartMaps = append(chartMaps, maps.Map{
"chart": maps.Map{
"id": chart.MetricChart.Id,
"name": chart.MetricChart.Name,
"widthDiv": chart.MetricChart.WidthDiv,
"isOn": chart.MetricChart.IsOn,
"maxItems": chart.MetricChart.MaxItems,
"type": chart.MetricChart.Type,
},
"item": maps.Map{
"id": chart.MetricChart.MetricItem.Id,
"name": chart.MetricChart.MetricItem.Name,
"period": chart.MetricChart.MetricItem.Period,
"periodUnit": chart.MetricChart.MetricItem.PeriodUnit,
"valueType": serverconfigs.FindMetricValueType(chart.MetricChart.MetricItem.Category, chart.MetricChart.MetricItem.Value),
"valueTypeName": serverconfigs.FindMetricValueName(chart.MetricChart.MetricItem.Category, chart.MetricChart.MetricItem.Value),
"keys": chart.MetricChart.MetricItem.Keys,
},
"stats": statMaps,
})
}
this.Data["metricCharts"] = chartMaps
}
this.Show()
}
func (this *IndexAction) RunPost(params struct {
ClusterId int64
NodeId int64
}) {
resp, err := this.RPC().ServerStatBoardRPC().ComposeServerStatNodeBoard(this.AdminContext(), &pb.ComposeServerStatNodeBoardRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["board"] = maps.Map{
"isActive": resp.IsActive,
"trafficInBytes": resp.TrafficInBytes,
"trafficOutBytes": resp.TrafficOutBytes,
"countConnections": resp.CountConnections,
"countRequests": resp.CountRequests,
"countAttackRequests": resp.CountAttackRequests,
"cpuUsage": resp.CpuUsage,
"memoryUsage": resp.MemoryUsage,
"memoryTotalSize": resp.MemoryTotalSize,
"load": resp.Load,
"cacheDiskSize": resp.CacheDiskSize,
"cacheMemorySize": resp.CacheMemorySize,
}
this.Success()
}

View File

@@ -0,0 +1,259 @@
package node
import (
"encoding/json"
"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"
"time"
)
type DetailAction struct {
actionutils.ParentAction
}
func (this *DetailAction) Init() {
this.Nav("", "node", "node")
this.SecondMenu("nodes")
}
func (this *DetailAction) RunGet(params struct {
NodeId int64
}) {
this.Data["nodeId"] = params.NodeId
nodeResp, err := this.RPC().NodeRPC().FindEnabledNode(this.AdminContext(), &pb.FindEnabledNodeRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
node := nodeResp.Node
if node == nil {
this.WriteString("找不到要操作的节点")
return
}
var clusterMap maps.Map = nil
if node.NodeCluster != nil {
clusterId := node.NodeCluster.Id
clusterResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeCluster(this.AdminContext(), &pb.FindEnabledNodeClusterRequest{NodeClusterId: clusterId})
if err != nil {
this.ErrorPage(err)
return
}
cluster := clusterResp.NodeCluster
if cluster != nil {
clusterMap = maps.Map{
"id": cluster.Id,
"name": cluster.Name,
"installDir": cluster.InstallDir,
}
}
}
// IP地址
ipAddressesResp, err := this.RPC().NodeIPAddressRPC().FindAllEnabledIPAddressesWithNodeId(this.AdminContext(), &pb.FindAllEnabledIPAddressesWithNodeIdRequest{
NodeId: params.NodeId,
Role: nodeconfigs.NodeRoleNode,
})
if err != nil {
this.ErrorPage(err)
return
}
ipAddressMaps := []maps.Map{}
for _, addr := range ipAddressesResp.Addresses {
ipAddressMaps = append(ipAddressMaps, maps.Map{
"id": addr.Id,
"name": addr.Name,
"ip": addr.Ip,
"canAccess": addr.CanAccess,
})
}
// DNS相关
dnsInfoResp, err := this.RPC().NodeRPC().FindEnabledNodeDNS(this.AdminContext(), &pb.FindEnabledNodeDNSRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
dnsRouteMaps := []maps.Map{}
recordName := ""
recordValue := ""
if dnsInfoResp.Node != nil {
recordName = dnsInfoResp.Node.NodeClusterDNSName + "." + dnsInfoResp.Node.DnsDomainName
recordValue = dnsInfoResp.Node.IpAddr
for _, dnsInfo := range dnsInfoResp.Node.Routes {
dnsRouteMaps = append(dnsRouteMaps, maps.Map{
"name": dnsInfo.Name,
"code": dnsInfo.Code,
})
}
}
if len(dnsRouteMaps) == 0 {
dnsRouteMaps = append(dnsRouteMaps, maps.Map{
"name": "",
"code": "",
})
}
this.Data["dnsRoutes"] = dnsRouteMaps
this.Data["dnsRecordName"] = recordName
this.Data["dnsRecordValue"] = recordValue
// 登录信息
var loginMap maps.Map = nil
if node.Login != nil {
loginParams := maps.Map{}
if len(node.Login.Params) > 0 {
err = json.Unmarshal(node.Login.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),
}
}
}
loginMap = maps.Map{
"id": node.Login.Id,
"name": node.Login.Name,
"type": node.Login.Type,
"params": loginParams,
"grant": grantMap,
}
}
// 运行状态
status := &nodeconfigs.NodeStatus{}
if len(node.StatusJSON) > 0 {
err = json.Unmarshal(node.StatusJSON, &status)
if err != nil {
this.ErrorPage(err)
return
}
status.IsActive = status.IsActive && time.Now().Unix()-status.UpdatedAt <= 60 // N秒之内认为活跃
}
// 检查是否有新版本
if len(status.OS) > 0 {
checkVersionResp, err := this.RPC().NodeRPC().CheckNodeLatestVersion(this.AdminContext(), &pb.CheckNodeLatestVersionRequest{
Os: status.OS,
Arch: status.Arch,
CurrentVersion: status.BuildVersion,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["shouldUpgrade"] = checkVersionResp.HasNewVersion
this.Data["newVersion"] = checkVersionResp.NewVersion
} else {
this.Data["shouldUpgrade"] = false
this.Data["newVersion"] = ""
}
// 分组
var groupMap maps.Map = nil
if node.NodeGroup != nil {
groupMap = maps.Map{
"id": node.NodeGroup.Id,
"name": node.NodeGroup.Name,
}
}
// 区域
var regionMap maps.Map = nil
if node.NodeRegion != nil {
regionMap = maps.Map{
"id": node.NodeRegion.Id,
"name": node.NodeRegion.Name,
}
}
// 缓存硬盘 & 内存容量
var maxCacheDiskCapacity maps.Map = nil
if node.MaxCacheDiskCapacity != nil {
maxCacheDiskCapacity = maps.Map{
"count": node.MaxCacheDiskCapacity.Count,
"unit": node.MaxCacheDiskCapacity.Unit,
}
} else {
maxCacheDiskCapacity = maps.Map{
"count": 0,
"unit": "gb",
}
}
var maxCacheMemoryCapacity maps.Map = nil
if node.MaxCacheMemoryCapacity != nil {
maxCacheMemoryCapacity = maps.Map{
"count": node.MaxCacheMemoryCapacity.Count,
"unit": node.MaxCacheMemoryCapacity.Unit,
}
} else {
maxCacheMemoryCapacity = maps.Map{
"count": 0,
"unit": "gb",
}
}
this.Data["node"] = maps.Map{
"id": node.Id,
"name": node.Name,
"ipAddresses": ipAddressMaps,
"cluster": clusterMap,
"login": loginMap,
"installDir": node.InstallDir,
"isInstalled": node.IsInstalled,
"uniqueId": node.UniqueId,
"secret": node.Secret,
"maxCPU": node.MaxCPU,
"isOn": node.IsOn,
"status": maps.Map{
"isActive": status.IsActive,
"updatedAt": status.UpdatedAt,
"hostname": status.Hostname,
"cpuUsage": status.CPUUsage,
"cpuUsageText": fmt.Sprintf("%.2f%%", status.CPUUsage*100),
"memUsage": status.MemoryUsage,
"memUsageText": fmt.Sprintf("%.2f%%", status.MemoryUsage*100),
"connectionCount": status.ConnectionCount,
"buildVersion": status.BuildVersion,
"cpuPhysicalCount": status.CPUPhysicalCount,
"cpuLogicalCount": status.CPULogicalCount,
"load1m": fmt.Sprintf("%.2f", status.Load1m),
"load5m": fmt.Sprintf("%.2f", status.Load5m),
"load15m": fmt.Sprintf("%.2f", status.Load15m),
"cacheTotalDiskSize": numberutils.FormatBytes(status.CacheTotalDiskSize),
"cacheTotalMemorySize": numberutils.FormatBytes(status.CacheTotalMemorySize),
},
"group": groupMap,
"region": regionMap,
"maxCacheDiskCapacity": maxCacheDiskCapacity,
"maxCacheMemoryCapacity": maxCacheMemoryCapacity,
}
this.Show()
}

View File

@@ -1,15 +1,11 @@
package node
import (
"encoding/json"
"fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"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"
"time"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
"strconv"
)
type IndexAction struct {
@@ -24,236 +20,15 @@ func (this *IndexAction) Init() {
func (this *IndexAction) RunGet(params struct {
NodeId int64
}) {
this.Data["nodeId"] = params.NodeId
nodeResp, err := this.RPC().NodeRPC().FindEnabledNode(this.AdminContext(), &pb.FindEnabledNodeRequest{NodeId: params.NodeId})
err := nodeutils.InitNodeInfo(this, params.NodeId)
if err != nil {
this.ErrorPage(err)
return
}
node := nodeResp.Node
if node == nil {
this.WriteString("找不到要操作的节点")
return
}
var clusterMap maps.Map = nil
if node.NodeCluster != nil {
clusterId := node.NodeCluster.Id
clusterResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeCluster(this.AdminContext(), &pb.FindEnabledNodeClusterRequest{NodeClusterId: clusterId})
if err != nil {
this.ErrorPage(err)
return
}
cluster := clusterResp.NodeCluster
if cluster != nil {
clusterMap = maps.Map{
"id": cluster.Id,
"name": cluster.Name,
"installDir": cluster.InstallDir,
}
}
}
// IP地址
ipAddressesResp, err := this.RPC().NodeIPAddressRPC().FindAllEnabledIPAddressesWithNodeId(this.AdminContext(), &pb.FindAllEnabledIPAddressesWithNodeIdRequest{
NodeId: params.NodeId,
Role: nodeconfigs.NodeRoleNode,
})
if err != nil {
this.ErrorPage(err)
return
}
ipAddressMaps := []maps.Map{}
for _, addr := range ipAddressesResp.Addresses {
ipAddressMaps = append(ipAddressMaps, maps.Map{
"id": addr.Id,
"name": addr.Name,
"ip": addr.Ip,
"canAccess": addr.CanAccess,
})
}
// DNS相关
dnsInfoResp, err := this.RPC().NodeRPC().FindEnabledNodeDNS(this.AdminContext(), &pb.FindEnabledNodeDNSRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
dnsRouteMaps := []maps.Map{}
recordName := ""
recordValue := ""
if dnsInfoResp.Node != nil {
recordName = dnsInfoResp.Node.NodeClusterDNSName + "." + dnsInfoResp.Node.DnsDomainName
recordValue = dnsInfoResp.Node.IpAddr
for _, dnsInfo := range dnsInfoResp.Node.Routes {
dnsRouteMaps = append(dnsRouteMaps, maps.Map{
"name": dnsInfo.Name,
"code": dnsInfo.Code,
})
}
}
if len(dnsRouteMaps) == 0 {
dnsRouteMaps = append(dnsRouteMaps, maps.Map{
"name": "",
"code": "",
})
}
this.Data["dnsRoutes"] = dnsRouteMaps
this.Data["dnsRecordName"] = recordName
this.Data["dnsRecordValue"] = recordValue
// 登录信息
var loginMap maps.Map = nil
if node.Login != nil {
loginParams := maps.Map{}
if len(node.Login.Params) > 0 {
err = json.Unmarshal(node.Login.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),
}
}
}
loginMap = maps.Map{
"id": node.Login.Id,
"name": node.Login.Name,
"type": node.Login.Type,
"params": loginParams,
"grant": grantMap,
}
}
// 运行状态
status := &nodeconfigs.NodeStatus{}
if len(node.StatusJSON) > 0 {
err = json.Unmarshal(node.StatusJSON, &status)
if err != nil {
this.ErrorPage(err)
return
}
status.IsActive = status.IsActive && time.Now().Unix()-status.UpdatedAt <= 60 // N秒之内认为活跃
}
// 检查是否有新版本
if len(status.OS) > 0 {
checkVersionResp, err := this.RPC().NodeRPC().CheckNodeLatestVersion(this.AdminContext(), &pb.CheckNodeLatestVersionRequest{
Os: status.OS,
Arch: status.Arch,
CurrentVersion: status.BuildVersion,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["shouldUpgrade"] = checkVersionResp.HasNewVersion
this.Data["newVersion"] = checkVersionResp.NewVersion
if teaconst.IsPlus {
this.RedirectURL("/clusters/cluster/node/boards?clusterId=" + fmt.Sprintf("%d", this.Data["clusterId"]) + "&nodeId=" + strconv.FormatInt(params.NodeId, 10))
} else {
this.Data["shouldUpgrade"] = false
this.Data["newVersion"] = ""
this.RedirectURL("/clusters/cluster/node/detail?clusterId=" + fmt.Sprintf("%d", this.Data["clusterId"]) + strconv.FormatInt(params.NodeId, 10))
}
// 分组
var groupMap maps.Map = nil
if node.NodeGroup != nil {
groupMap = maps.Map{
"id": node.NodeGroup.Id,
"name": node.NodeGroup.Name,
}
}
// 区域
var regionMap maps.Map = nil
if node.NodeRegion != nil {
regionMap = maps.Map{
"id": node.NodeRegion.Id,
"name": node.NodeRegion.Name,
}
}
// 缓存硬盘 & 内存容量
var maxCacheDiskCapacity maps.Map = nil
if node.MaxCacheDiskCapacity != nil {
maxCacheDiskCapacity = maps.Map{
"count": node.MaxCacheDiskCapacity.Count,
"unit": node.MaxCacheDiskCapacity.Unit,
}
} else {
maxCacheDiskCapacity = maps.Map{
"count": 0,
"unit": "gb",
}
}
var maxCacheMemoryCapacity maps.Map = nil
if node.MaxCacheMemoryCapacity != nil {
maxCacheMemoryCapacity = maps.Map{
"count": node.MaxCacheMemoryCapacity.Count,
"unit": node.MaxCacheMemoryCapacity.Unit,
}
} else {
maxCacheMemoryCapacity = maps.Map{
"count": 0,
"unit": "gb",
}
}
this.Data["node"] = maps.Map{
"id": node.Id,
"name": node.Name,
"ipAddresses": ipAddressMaps,
"cluster": clusterMap,
"login": loginMap,
"installDir": node.InstallDir,
"isInstalled": node.IsInstalled,
"uniqueId": node.UniqueId,
"secret": node.Secret,
"maxCPU": node.MaxCPU,
"isOn": node.IsOn,
"status": maps.Map{
"isActive": status.IsActive,
"updatedAt": status.UpdatedAt,
"hostname": status.Hostname,
"cpuUsage": status.CPUUsage,
"cpuUsageText": fmt.Sprintf("%.2f%%", status.CPUUsage*100),
"memUsage": status.MemoryUsage,
"memUsageText": fmt.Sprintf("%.2f%%", status.MemoryUsage*100),
"connectionCount": status.ConnectionCount,
"buildVersion": status.BuildVersion,
"cpuPhysicalCount": status.CPUPhysicalCount,
"cpuLogicalCount": status.CPULogicalCount,
"load1m": fmt.Sprintf("%.2f", status.Load1m),
"load5m": fmt.Sprintf("%.2f", status.Load5m),
"load15m": fmt.Sprintf("%.2f", status.Load15m),
"cacheTotalDiskSize": numberutils.FormatBytes(status.CacheTotalDiskSize),
"cacheTotalMemorySize": numberutils.FormatBytes(status.CacheTotalMemorySize),
},
"group": groupMap,
"region": regionMap,
"maxCacheDiskCapacity": maxCacheDiskCapacity,
"maxCacheMemoryCapacity": maxCacheMemoryCapacity,
}
this.Show()
}

View File

@@ -2,6 +2,7 @@ package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
@@ -23,6 +24,13 @@ func (this *LogsAction) RunGet(params struct {
Keyword string
Level string
}) {
// 初始化节点信息(用于菜单)
err := nodeutils.InitNodeInfo(this, params.NodeId)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["nodeId"] = params.NodeId
this.Data["dayFrom"] = params.DayFrom
this.Data["dayTo"] = params.DayTo

View File

@@ -1,73 +0,0 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package monitor
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
type ConnectionsAction struct {
actionutils.ParentAction
}
func (this *ConnectionsAction) RunPost(params struct {
NodeId int64
}) {
resp, err := this.RPC().NodeValueRPC().ListNodeValues(this.AdminContext(), &pb.ListNodeValuesRequest{
Role: "node",
NodeId: params.NodeId,
Item: nodeconfigs.NodeValueItemConnections,
Range: nodeconfigs.NodeValueRangeMinute,
})
if err != nil {
this.ErrorPage(err)
return
}
valuesMap := map[string]int64{} // YmdHi => count
for _, v := range resp.NodeValues {
if len(v.ValueJSON) == 0 {
continue
}
valueMap := maps.Map{}
err = json.Unmarshal(v.ValueJSON, &valueMap)
if err != nil {
this.ErrorPage(err)
return
}
valuesMap[timeutil.FormatTime("YmdHi", v.CreatedAt)] = valueMap.GetInt64("total")
}
// 过去一个小时
result := []maps.Map{}
for i := 60; i >= 1; i-- {
timestamp := time.Now().Unix() - int64(i)*60
minute := timeutil.FormatTime("YmdHi", timestamp)
total, ok := valuesMap[minute]
if ok {
result = append(result, maps.Map{
"label": timeutil.FormatTime("H:i", timestamp),
"value": total,
"text": numberutils.FormatInt64(total),
})
} else {
result = append(result, maps.Map{
"label": timeutil.FormatTime("H:i", timestamp),
"value": 0,
"text": "0",
})
}
}
this.Data["values"] = result
this.Success()
}

View File

@@ -1,73 +0,0 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package monitor
import (
"encoding/json"
"fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
type CpuAction struct {
actionutils.ParentAction
}
func (this *CpuAction) RunPost(params struct {
NodeId int64
}) {
resp, err := this.RPC().NodeValueRPC().ListNodeValues(this.AdminContext(), &pb.ListNodeValuesRequest{
Role: "node",
NodeId: params.NodeId,
Item: nodeconfigs.NodeValueItemCPU,
Range: nodeconfigs.NodeValueRangeMinute,
})
if err != nil {
this.ErrorPage(err)
return
}
valuesMap := map[string]float32{} // YmdHi => usage
for _, v := range resp.NodeValues {
if len(v.ValueJSON) == 0 {
continue
}
valueMap := maps.Map{}
err = json.Unmarshal(v.ValueJSON, &valueMap)
if err != nil {
this.ErrorPage(err)
return
}
valuesMap[timeutil.FormatTime("YmdHi", v.CreatedAt)] = valueMap.GetFloat32("usage") * 100
}
// 过去一个小时
result := []maps.Map{}
for i := 60; i >= 1; i-- {
timestamp := time.Now().Unix() - int64(i)*60
minute := timeutil.FormatTime("YmdHi", timestamp)
total, ok := valuesMap[minute]
if ok {
result = append(result, maps.Map{
"label": timeutil.FormatTime("H:i", timestamp),
"value": total,
"text": fmt.Sprintf("%.2f%%", total),
})
} else {
result = append(result, maps.Map{
"label": timeutil.FormatTime("H:i", timestamp),
"value": 0,
"text": "0.0%",
})
}
}
this.Data["values"] = result
this.Success()
}

View File

@@ -1,21 +0,0 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package monitor
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "node", "monitor")
}
func (this *IndexAction) RunGet(params struct {
NodeId int64
}) {
this.Data["nodeId"] = params.NodeId
this.Show()
}

View File

@@ -1,73 +0,0 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package monitor
import (
"encoding/json"
"fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
type LoadAction struct {
actionutils.ParentAction
}
func (this *LoadAction) RunPost(params struct {
NodeId int64
}) {
resp, err := this.RPC().NodeValueRPC().ListNodeValues(this.AdminContext(), &pb.ListNodeValuesRequest{
Role: "node",
NodeId: params.NodeId,
Item: nodeconfigs.NodeValueItemLoad,
Range: nodeconfigs.NodeValueRangeMinute,
})
if err != nil {
this.ErrorPage(err)
return
}
valuesMap := map[string]float32{} // YmdHi => load5m
for _, v := range resp.NodeValues {
if len(v.ValueJSON) == 0 {
continue
}
valueMap := maps.Map{}
err = json.Unmarshal(v.ValueJSON, &valueMap)
if err != nil {
this.ErrorPage(err)
return
}
valuesMap[timeutil.FormatTime("YmdHi", v.CreatedAt)] = valueMap.GetFloat32("load5m")
}
// 过去一个小时
result := []maps.Map{}
for i := 60; i >= 1; i-- {
timestamp := time.Now().Unix() - int64(i)*60
minute := timeutil.FormatTime("YmdHi", timestamp)
total, ok := valuesMap[minute]
if ok {
result = append(result, maps.Map{
"label": timeutil.FormatTime("H:i", timestamp),
"value": total,
"text": fmt.Sprintf("5分钟: %.2f", total),
})
} else {
result = append(result, maps.Map{
"label": timeutil.FormatTime("H:i", timestamp),
"value": 0,
"text": "5分钟: 0.0",
})
}
}
this.Data["values"] = result
this.Success()
}

View File

@@ -1,73 +0,0 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package monitor
import (
"encoding/json"
"fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
type MemoryAction struct {
actionutils.ParentAction
}
func (this *MemoryAction) RunPost(params struct {
NodeId int64
}) {
resp, err := this.RPC().NodeValueRPC().ListNodeValues(this.AdminContext(), &pb.ListNodeValuesRequest{
Role: "node",
NodeId: params.NodeId,
Item: nodeconfigs.NodeValueItemMemory,
Range: nodeconfigs.NodeValueRangeMinute,
})
if err != nil {
this.ErrorPage(err)
return
}
valuesMap := map[string]float32{} // YmdHi => usage
for _, v := range resp.NodeValues {
if len(v.ValueJSON) == 0 {
continue
}
valueMap := maps.Map{}
err = json.Unmarshal(v.ValueJSON, &valueMap)
if err != nil {
this.ErrorPage(err)
return
}
valuesMap[timeutil.FormatTime("YmdHi", v.CreatedAt)] = valueMap.GetFloat32("usage") * 100
}
// 过去一个小时
result := []maps.Map{}
for i := 60; i >= 1; i-- {
timestamp := time.Now().Unix() - int64(i)*60
minute := timeutil.FormatTime("YmdHi", timestamp)
total, ok := valuesMap[minute]
if ok {
result = append(result, maps.Map{
"label": timeutil.FormatTime("H:i", timestamp),
"value": total,
"text": fmt.Sprintf("%.2f%%", total),
})
} else {
result = append(result, maps.Map{
"label": timeutil.FormatTime("H:i", timestamp),
"value": 0,
"text": "0.0%",
})
}
}
this.Data["values"] = result
this.Success()
}

View File

@@ -1,73 +0,0 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package monitor
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
type TrafficInAction struct {
actionutils.ParentAction
}
func (this *TrafficInAction) RunPost(params struct {
NodeId int64
}) {
resp, err := this.RPC().NodeValueRPC().ListNodeValues(this.AdminContext(), &pb.ListNodeValuesRequest{
Role: "node",
NodeId: params.NodeId,
Item: nodeconfigs.NodeValueItemTrafficIn,
Range: nodeconfigs.NodeValueRangeMinute,
})
if err != nil {
this.ErrorPage(err)
return
}
valuesMap := map[string]int64{} // YmdHi => bytes
for _, v := range resp.NodeValues {
if len(v.ValueJSON) == 0 {
continue
}
valueMap := maps.Map{}
err = json.Unmarshal(v.ValueJSON, &valueMap)
if err != nil {
this.ErrorPage(err)
return
}
valuesMap[timeutil.FormatTime("YmdHi", v.CreatedAt)] = valueMap.GetInt64("total")
}
// 过去一个小时
result := []maps.Map{}
for i := 60; i >= 1; i-- {
timestamp := time.Now().Unix() - int64(i)*60
minute := timeutil.FormatTime("YmdHi", timestamp)
total, ok := valuesMap[minute]
if ok {
result = append(result, maps.Map{
"label": timeutil.FormatTime("H:i", timestamp),
"value": total / 60,
"text": numberutils.FormatBytes(total / 60),
})
} else {
result = append(result, maps.Map{
"label": timeutil.FormatTime("H:i", timestamp),
"value": 0,
"text": numberutils.FormatBytes(0),
})
}
}
this.Data["values"] = result
this.Success()
}

View File

@@ -1,73 +0,0 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package monitor
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
type TrafficOutAction struct {
actionutils.ParentAction
}
func (this *TrafficOutAction) RunPost(params struct {
NodeId int64
}) {
resp, err := this.RPC().NodeValueRPC().ListNodeValues(this.AdminContext(), &pb.ListNodeValuesRequest{
Role: "node",
NodeId: params.NodeId,
Item: nodeconfigs.NodeValueItemTrafficOut,
Range: nodeconfigs.NodeValueRangeMinute,
})
if err != nil {
this.ErrorPage(err)
return
}
valuesMap := map[string]int64{} // YmdHi => bytes
for _, v := range resp.NodeValues {
if len(v.ValueJSON) == 0 {
continue
}
valueMap := maps.Map{}
err = json.Unmarshal(v.ValueJSON, &valueMap)
if err != nil {
this.ErrorPage(err)
return
}
valuesMap[timeutil.FormatTime("YmdHi", v.CreatedAt)] = valueMap.GetInt64("total")
}
// 过去一个小时
result := []maps.Map{}
for i := 60; i >= 1; i-- {
timestamp := time.Now().Unix() - int64(i)*60
minute := timeutil.FormatTime("YmdHi", timestamp)
total, ok := valuesMap[minute]
if ok {
result = append(result, maps.Map{
"label": timeutil.FormatTime("H:i", timestamp),
"value": total / 60,
"text": numberutils.FormatBytes(total / 60),
})
} else {
result = append(result, maps.Map{
"label": timeutil.FormatTime("H:i", timestamp),
"value": 0,
"text": numberutils.FormatBytes(0),
})
}
}
this.Data["values"] = result
this.Success()
}

View File

@@ -0,0 +1,32 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package nodeutils
import (
"errors"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
"strconv"
)
// InitNodeInfo 初始化节点信息
func InitNodeInfo(action actionutils.ActionInterface, nodeId int64) error {
// 节点信息(用于菜单)
nodeResp, err := action.RPC().NodeRPC().FindEnabledNode(action.AdminContext(), &pb.FindEnabledNodeRequest{NodeId: nodeId})
if err != nil {
return err
}
if nodeResp.Node == nil {
return errors.New("node '" + strconv.FormatInt(nodeId, 10) + "' not found")
}
var node = nodeResp.Node
action.ViewData()["node"] = maps.Map{
"id": node.Id,
"name": node.Name,
}
if node.NodeCluster != nil {
action.ViewData()["clusterId"] = node.NodeCluster.Id
}
return nil
}

View File

@@ -4,6 +4,7 @@ package thresholds
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
@@ -23,6 +24,13 @@ func (this *IndexAction) RunGet(params struct {
}) {
this.Data["nodeId"] = params.NodeId
// 初始化节点信息(用于菜单)
err := nodeutils.InitNodeInfo(this, params.NodeId)
if err != nil {
this.ErrorPage(err)
return
}
// 列出所有阈值
thresholdsResp, err := this.RPC().NodeThresholdRPC().FindAllEnabledNodeThresholds(this.AdminContext(), &pb.FindAllEnabledNodeThresholdsRequest{
Role: "node",

View File

@@ -0,0 +1,249 @@
package cluster
import (
"encoding/json"
"fmt"
"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/logs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"strconv"
"time"
)
type NodesAction struct {
actionutils.ParentAction
}
func (this *NodesAction) Init() {
this.Nav("", "node", "index")
this.SecondMenu("nodes")
}
func (this *NodesAction) RunGet(params struct {
ClusterId int64
GroupId int64
RegionId int64
InstalledState int
ActiveState int
Keyword string
CpuOrder string
MemoryOrder string
TrafficInOrder string
TrafficOutOrder string
}) {
this.Data["groupId"] = params.GroupId
this.Data["regionId"] = params.RegionId
this.Data["installState"] = params.InstalledState
this.Data["activeState"] = params.ActiveState
this.Data["keyword"] = params.Keyword
countAllResp, err := this.RPC().NodeRPC().CountAllEnabledNodesMatch(this.AdminContext(), &pb.CountAllEnabledNodesMatchRequest{
NodeClusterId: params.ClusterId,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["countAll"] = countAllResp.Count
countResp, err := this.RPC().NodeRPC().CountAllEnabledNodesMatch(this.AdminContext(), &pb.CountAllEnabledNodesMatchRequest{
NodeClusterId: params.ClusterId,
NodeGroupId: params.GroupId,
NodeRegionId: params.RegionId,
InstallState: types.Int32(params.InstalledState),
ActiveState: types.Int32(params.ActiveState),
Keyword: params.Keyword,
})
if err != nil {
this.ErrorPage(err)
return
}
page := this.NewPage(countResp.Count)
this.Data["page"] = page.AsHTML()
var req = &pb.ListEnabledNodesMatchRequest{
Offset: page.Offset,
Size: page.Size,
NodeClusterId: params.ClusterId,
NodeGroupId: params.GroupId,
NodeRegionId: params.RegionId,
InstallState: types.Int32(params.InstalledState),
ActiveState: types.Int32(params.ActiveState),
Keyword: params.Keyword,
}
if params.CpuOrder == "asc" {
req.CpuAsc = true
} else if params.CpuOrder == "desc" {
req.CpuDesc = true
} else if params.MemoryOrder == "asc" {
req.MemoryAsc = true
} else if params.MemoryOrder == "desc" {
req.MemoryDesc = true
} else if params.TrafficInOrder == "asc" {
req.TrafficInAsc = true
} else if params.TrafficInOrder == "desc" {
req.TrafficInDesc = true
} else if params.TrafficOutOrder == "asc" {
req.TrafficOutAsc = true
} else if params.TrafficOutOrder == "desc" {
req.TrafficOutDesc = true
}
nodesResp, err := this.RPC().NodeRPC().ListEnabledNodesMatch(this.AdminContext(), req)
if err != nil {
this.ErrorPage(err)
return
}
nodeMaps := []maps.Map{}
for _, node := range nodesResp.Nodes {
// 状态
isSynced := false
status := &nodeconfigs.NodeStatus{}
if len(node.StatusJSON) > 0 {
err = json.Unmarshal(node.StatusJSON, &status)
if err != nil {
logs.Error(err)
continue
}
status.IsActive = status.IsActive && time.Now().Unix()-status.UpdatedAt <= 60 // N秒之内认为活跃
isSynced = status.ConfigVersion == node.Version
}
// IP
ipAddressesResp, err := this.RPC().NodeIPAddressRPC().FindAllEnabledIPAddressesWithNodeId(this.AdminContext(), &pb.FindAllEnabledIPAddressesWithNodeIdRequest{
NodeId: node.Id,
Role: nodeconfigs.NodeRoleNode,
})
if err != nil {
this.ErrorPage(err)
return
}
ipAddresses := []maps.Map{}
for _, addr := range ipAddressesResp.Addresses {
ipAddresses = append(ipAddresses, maps.Map{
"id": addr.Id,
"name": addr.Name,
"ip": addr.Ip,
"canAccess": addr.CanAccess,
})
}
// 分组
var groupMap maps.Map = nil
if node.NodeGroup != nil {
groupMap = maps.Map{
"id": node.NodeGroup.Id,
"name": node.NodeGroup.Name,
}
}
// 区域
var regionMap maps.Map = nil
if node.NodeRegion != nil {
regionMap = maps.Map{
"id": node.NodeRegion.Id,
"name": node.NodeRegion.Name,
}
}
// DNS
dnsRouteNames := []string{}
for _, route := range node.DnsRoutes {
dnsRouteNames = append(dnsRouteNames, route.Name)
}
nodeMaps = append(nodeMaps, maps.Map{
"id": node.Id,
"name": node.Name,
"isInstalled": node.IsInstalled,
"isOn": node.IsOn,
"isUp": node.IsUp,
"installStatus": maps.Map{
"isRunning": node.InstallStatus.IsRunning,
"isFinished": node.InstallStatus.IsFinished,
"isOk": node.InstallStatus.IsOk,
"error": node.InstallStatus.Error,
},
"status": maps.Map{
"isActive": status.IsActive,
"updatedAt": status.UpdatedAt,
"hostname": status.Hostname,
"cpuUsage": status.CPUUsage,
"cpuUsageText": fmt.Sprintf("%.2f%%", status.CPUUsage*100),
"memUsage": status.MemoryUsage,
"memUsageText": fmt.Sprintf("%.2f%%", status.MemoryUsage*100),
"trafficInBytes": status.TrafficInBytes,
"trafficOutBytes": status.TrafficOutBytes,
},
"cluster": maps.Map{
"id": node.NodeCluster.Id,
"name": node.NodeCluster.Name,
},
"isSynced": isSynced,
"ipAddresses": ipAddresses,
"group": groupMap,
"region": regionMap,
"dnsRouteNames": dnsRouteNames,
})
}
this.Data["nodes"] = nodeMaps
// 所有分组
groupMaps := []maps.Map{}
groupsResp, err := this.RPC().NodeGroupRPC().FindAllEnabledNodeGroupsWithNodeClusterId(this.AdminContext(), &pb.FindAllEnabledNodeGroupsWithNodeClusterIdRequest{
NodeClusterId: params.ClusterId,
})
if err != nil {
this.ErrorPage(err)
return
}
for _, group := range groupsResp.NodeGroups {
countResp, err := this.RPC().NodeRPC().CountAllEnabledNodesWithNodeGroupId(this.AdminContext(), &pb.CountAllEnabledNodesWithNodeGroupIdRequest{NodeGroupId: group.Id})
if err != nil {
this.ErrorPage(err)
return
}
countNodes := countResp.Count
groupName := group.Name
if countNodes > 0 {
groupName += "(" + strconv.FormatInt(countNodes, 10) + ")"
}
groupMaps = append(groupMaps, maps.Map{
"id": group.Id,
"name": groupName,
"countNodes": countNodes,
})
}
this.Data["groups"] = groupMaps
// 所有区域
regionsResp, err := this.RPC().NodeRegionRPC().FindAllEnabledAndOnNodeRegions(this.AdminContext(), &pb.FindAllEnabledAndOnNodeRegionsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
regionMaps := []maps.Map{}
for _, region := range regionsResp.NodeRegions {
regionMaps = append(regionMaps, maps.Map{
"id": region.Id,
"name": region.Name,
})
}
this.Data["regions"] = regionMaps
// 记录最近访问
_, err = this.RPC().LatestItemRPC().IncreaseLatestItem(this.AdminContext(), &pb.IncreaseLatestItemRequest{
ItemType: "cluster",
ItemId: params.ClusterId,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Show()
}

View File

@@ -32,6 +32,9 @@ func (this *RunPopupAction) RunPost(params struct {
this.Fail(err.Error())
}
if resp.Results == nil {
resp.Results = []*pb.ExecuteNodeClusterHealthCheckResponse_Result{}
}
this.Data["results"] = resp.Results
this.Success()
}

View File

@@ -7,6 +7,7 @@ import (
firewallActions "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/firewall-actions"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/health"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/message"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/metrics"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/services"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/thresholds"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/toa"
@@ -67,6 +68,12 @@ func init() {
GetPost("/updatePopup", new(thresholds.UpdatePopupAction)).
Post("/delete", new(thresholds.DeleteAction)).
// 指标
Prefix("/clusters/cluster/settings/metrics").
Get("", new(metrics.IndexAction)).
GetPost("/createPopup", new(metrics.CreatePopupAction)).
Post("/delete", new(metrics.DeleteAction)).
EndAll()
})
}

View File

@@ -0,0 +1,100 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package metrics
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type CreatePopupAction struct {
actionutils.ParentAction
}
func (this *CreatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreatePopupAction) RunGet(params struct {
ClusterId int64
Category string
}) {
if len(params.Category) == 0 {
params.Category = "http"
}
this.Data["category"] = params.Category
this.Data["clusterId"] = params.ClusterId
countResp, err := this.RPC().MetricItemRPC().CountAllEnabledMetricItems(this.AdminContext(), &pb.CountAllEnabledMetricItemsRequest{Category: params.Category})
if err != nil {
this.ErrorPage(err)
return
}
var count = countResp.Count
page := this.NewPage(count)
this.Data["page"] = page.AsHTML()
itemsResp, err := this.RPC().MetricItemRPC().ListEnabledMetricItems(this.AdminContext(), &pb.ListEnabledMetricItemsRequest{
Category: params.Category,
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
var itemMaps = []maps.Map{}
for _, item := range itemsResp.MetricItems {
// 是否已添加
existsResp, err := this.RPC().NodeClusterMetricItemRPC().ExistsNodeClusterMetricItem(this.AdminContext(), &pb.ExistsNodeClusterMetricItemRequest{
NodeClusterId: params.ClusterId,
MetricItemId: item.Id,
})
if err != nil {
this.ErrorPage(err)
return
}
var exists = existsResp.Exists
itemMaps = append(itemMaps, maps.Map{
"id": item.Id,
"name": item.Name,
"code": item.Code,
"isOn": item.IsOn,
"period": item.Period,
"periodUnit": item.PeriodUnit,
"periodUnitName": serverconfigs.FindMetricPeriodUnitName(item.PeriodUnit),
"keys": item.Keys,
"value": item.Value,
"valueName": serverconfigs.FindMetricValueName(item.Category, item.Value),
"category": item.Category,
"isPublic": item.IsPublic,
"isChecked": exists,
})
}
this.Data["items"] = itemMaps
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
ClusterId int64
ItemId int64
Must *actions.Must
}) {
defer this.CreateLogInfo("添加指标 %d 到集群 %d", params.ItemId, params.ClusterId)
_, err := this.RPC().NodeClusterMetricItemRPC().EnableNodeClusterMetricItem(this.AdminContext(), &pb.EnableNodeClusterMetricItemRequest{
NodeClusterId: params.ClusterId,
MetricItemId: params.ItemId,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,30 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package metrics
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteAction struct {
actionutils.ParentAction
}
func (this *DeleteAction) RunPost(params struct {
ClusterId int64
ItemId int64
}) {
defer this.CreateLogInfo("从集群 %d 中移除指标 %d", params.ClusterId, params.ItemId)
_, err := this.RPC().NodeClusterMetricItemRPC().DisableNodeClusterMetricItem(this.AdminContext(), &pb.DisableNodeClusterMetricItemRequest{
NodeClusterId: params.ClusterId,
MetricItemId: params.ItemId,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,59 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package metrics
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "setting", "setting")
this.SecondMenu("metric")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
Category string
}) {
if len(params.Category) == 0 {
params.Category = "http"
}
this.Data["category"] = params.Category
itemsResp, err := this.RPC().NodeClusterMetricItemRPC().FindAllNodeClusterMetricItems(this.AdminContext(), &pb.FindAllNodeClusterMetricItemsRequest{
NodeClusterId: params.ClusterId,
Category: params.Category,
})
if err != nil {
this.ErrorPage(err)
return
}
var itemMaps = []maps.Map{}
for _, item := range itemsResp.MetricItems {
itemMaps = append(itemMaps, maps.Map{
"id": item.Id,
"name": item.Name,
"code": item.Code,
"isOn": item.IsOn,
"period": item.Period,
"periodUnit": item.PeriodUnit,
"periodUnitName": serverconfigs.FindMetricPeriodUnitName(item.PeriodUnit),
"keys": item.Keys,
"value": item.Value,
"valueName": serverconfigs.FindMetricValueName(item.Category, item.Value),
"category": item.Category,
"isPublic": item.IsPublic,
})
}
this.Data["items"] = itemMaps
this.Show()
}

View File

@@ -1,14 +1,11 @@
package clusterutils
import (
"encoding/json"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
@@ -38,7 +35,8 @@ func (this *ClusterHelper) BeforeAction(actionPtr actions.ActionWrapper) (goNext
action.Data["clusterId"] = clusterId
if clusterId > 0 {
cluster, err := dao.SharedNodeClusterDAO.FindEnabledNodeCluster(actionPtr.(rpc.ContextInterface).AdminContext(), clusterId)
var ctx = actionPtr.(actionutils.ActionInterface).AdminContext()
cluster, err := dao.SharedNodeClusterDAO.FindEnabledNodeCluster(ctx, clusterId)
if err != nil {
logs.Error(err)
return
@@ -48,9 +46,22 @@ func (this *ClusterHelper) BeforeAction(actionPtr actions.ActionWrapper) (goNext
return
}
clusterInfo, err := dao.SharedNodeClusterDAO.FindEnabledNodeClusterConfigInfo(ctx, clusterId)
if err != nil {
logs.Error(err)
return
}
if clusterInfo == nil {
action.WriteString("can not find cluster info")
return
}
tabbar := actionutils.NewTabbar()
tabbar.Add("集群列表", "", "/clusters", "", false)
tabbar.Add("集群节点", "", "/clusters/cluster?clusterId="+clusterIdString, "server", selectedTabbar == "node")
if teaconst.IsPlus {
tabbar.Add("集群看板", "", "/clusters/cluster/boards?clusterId="+clusterIdString, "board", selectedTabbar == "board")
}
tabbar.Add("集群节点", "", "/clusters/cluster/nodes?clusterId="+clusterIdString, "server", selectedTabbar == "node")
tabbar.Add("集群设置", "", "/clusters/cluster/settings?clusterId="+clusterIdString, "setting", selectedTabbar == "setting")
tabbar.Add("删除集群", "", "/clusters/cluster/delete?clusterId="+clusterIdString, "trash", selectedTabbar == "delete")
@@ -64,7 +75,7 @@ func (this *ClusterHelper) BeforeAction(actionPtr actions.ActionWrapper) (goNext
secondMenuItem := action.Data.GetString("secondMenuItem")
switch selectedTabbar {
case "setting":
action.Data["leftMenuItems"] = this.createSettingMenu(cluster, secondMenuItem)
action.Data["leftMenuItems"] = this.createSettingMenu(cluster, clusterInfo, secondMenuItem)
}
}
@@ -72,7 +83,7 @@ func (this *ClusterHelper) BeforeAction(actionPtr actions.ActionWrapper) (goNext
}
// 设置菜单
func (this *ClusterHelper) createSettingMenu(cluster *pb.NodeCluster, selectedItem string) (items []maps.Map) {
func (this *ClusterHelper) createSettingMenu(cluster *pb.NodeCluster, info *pb.FindEnabledNodeClusterConfigInfoResponse, selectedItem string) (items []maps.Map) {
clusterId := numberutils.FormatInt64(cluster.Id)
items = append(items, maps.Map{
"name": "基础设置",
@@ -92,25 +103,19 @@ func (this *ClusterHelper) createSettingMenu(cluster *pb.NodeCluster, selectedIt
"isOn": cluster.HttpFirewallPolicyId > 0,
})
{
hasActions, _ := this.checkFirewallActions(cluster.Id)
items = append(items, maps.Map{
"name": "WAF动作",
"url": "/clusters/cluster/settings/firewall-actions?clusterId=" + clusterId,
"isActive": selectedItem == "firewallAction",
"isOn": hasActions,
})
}
items = append(items, maps.Map{
"name": "WAF动作",
"url": "/clusters/cluster/settings/firewall-actions?clusterId=" + clusterId,
"isActive": selectedItem == "firewallAction",
"isOn": info != nil && info.HasFirewallActions,
})
{
healthCheckIsOn, _ := this.checkHealthCheckIsOn(cluster.Id)
items = append(items, maps.Map{
"name": "健康检查",
"url": "/clusters/cluster/settings/health?clusterId=" + clusterId,
"isActive": selectedItem == "health",
"isOn": healthCheckIsOn,
})
}
items = append(items, maps.Map{
"name": "健康检查",
"url": "/clusters/cluster/settings/health?clusterId=" + clusterId,
"isActive": selectedItem == "health",
"isOn": info != nil && info.HealthCheckIsOn,
})
items = append(items, maps.Map{
"name": "DNS设置",
@@ -118,22 +123,26 @@ func (this *ClusterHelper) createSettingMenu(cluster *pb.NodeCluster, selectedIt
"isActive": selectedItem == "dns",
"isOn": cluster.DnsDomainId > 0 || len(cluster.DnsName) > 0,
})
items = append(items, maps.Map{
"name": "统计指标",
"url": "/clusters/cluster/settings/metrics?clusterId=" + clusterId,
"isActive": selectedItem == "metric",
"isOn": info != nil && info.HasMetricItems,
})
if teaconst.IsPlus {
hasThresholds, _ := this.checkThresholds(cluster.Id)
items = append(items, maps.Map{
"name": "阈值设置",
"url": "/clusters/cluster/settings/thresholds?clusterId=" + clusterId,
"isActive": selectedItem == "threshold",
"isOn": hasThresholds,
"isOn": info != nil && info.HasThresholds,
})
}
if teaconst.IsPlus {
hasMessageReceivers, _ := this.checkMessages(cluster.Id)
items = append(items, maps.Map{
"name": "消息通知",
"url": "/clusters/cluster/settings/message?clusterId=" + clusterId,
"isActive": selectedItem == "message",
"isOn": hasMessageReceivers,
"isOn": info != nil && info.HasMessageReceivers,
})
}
@@ -148,75 +157,13 @@ func (this *ClusterHelper) createSettingMenu(cluster *pb.NodeCluster, selectedIt
"url": "/clusters/cluster/settings/services?clusterId=" + clusterId,
"isActive": selectedItem == "service",
})
items = append(items, maps.Map{
"name": "TOA设置",
"url": "/clusters/cluster/settings/toa?clusterId=" + clusterId,
"isActive": selectedItem == "toa",
})
{
items = append(items, maps.Map{
"name": "TOA设置",
"url": "/clusters/cluster/settings/toa?clusterId=" + clusterId,
"isActive": selectedItem == "toa",
"isOn": info != nil && info.IsTOAEnabled,
})
}
return
}
// 检查健康检查是否开启
func (this *ClusterHelper) checkHealthCheckIsOn(clusterId int64) (bool, error) {
rpcClient, err := rpc.SharedRPC()
if err != nil {
return false, err
}
resp, err := rpcClient.NodeClusterRPC().FindNodeClusterHealthCheckConfig(rpcClient.Context(0), &pb.FindNodeClusterHealthCheckConfigRequest{NodeClusterId: clusterId})
if err != nil {
return false, err
}
if len(resp.HealthCheckJSON) > 0 {
healthCheckConfig := &serverconfigs.HealthCheckConfig{}
err = json.Unmarshal(resp.HealthCheckJSON, healthCheckConfig)
if err != nil {
return false, err
}
return healthCheckConfig.IsOn, nil
}
return false, nil
}
// 检查是否有WAF动作
func (this *ClusterHelper) checkFirewallActions(clusterId int64) (bool, error) {
rpcClient, err := rpc.SharedRPC()
if err != nil {
return false, err
}
resp, err := rpcClient.NodeClusterFirewallActionRPC().CountAllEnabledNodeClusterFirewallActions(rpcClient.Context(0), &pb.CountAllEnabledNodeClusterFirewallActionsRequest{NodeClusterId: clusterId})
if err != nil {
return false, err
}
return resp.Count > 0, nil
}
// 检查阈值是否已经设置
func (this *ClusterHelper) checkThresholds(clusterId int64) (bool, error) {
rpcClient, err := rpc.SharedRPC()
if err != nil {
return false, err
}
resp, err := rpcClient.NodeThresholdRPC().CountAllEnabledNodeThresholds(rpcClient.Context(0), &pb.CountAllEnabledNodeThresholdsRequest{
Role: "node",
NodeClusterId: clusterId,
})
if err != nil {
return false, err
}
return resp.Count > 0, nil
}
// 检查消息通知是否已经设置
func (this *ClusterHelper) checkMessages(clusterId int64) (bool, error) {
rpcClient, err := rpc.SharedRPC()
if err != nil {
return false, err
}
resp, err := rpcClient.MessageReceiverRPC().CountAllEnabledMessageReceivers(rpcClient.Context(0), &pb.CountAllEnabledMessageReceiversRequest{
NodeClusterId: clusterId,
})
if err != nil {
return false, err
}
return resp.Count > 0, nil
}

View File

@@ -42,6 +42,9 @@ func (this *CreateAction) RunPost(params struct {
this.FailField("username", "请输入SSH登录用户名")
}
case "privateKey":
if len(params.Username) == 0 {
this.FailField("username", "请输入SSH登录用户名")
}
if len(params.PrivateKey) == 0 {
this.FailField("privateKey", "请输入RSA私钥")
}

View File

@@ -74,6 +74,9 @@ func (this *UpdateAction) RunPost(params struct {
this.FailField("username", "请输入SSH登录用户名")
}
case "privateKey":
if len(params.Username) == 0 {
this.FailField("username", "请输入SSH登录用户名")
}
if len(params.PrivateKey) == 0 {
this.FailField("privateKey", "请输入RSA私钥")
}

View File

@@ -115,6 +115,12 @@ func (this *IndexAction) RunGet(params struct {
}
}
// 服务数
countServersResp, err := this.RPC().ServerRPC().CountAllEnabledServersWithNodeClusterId(this.AdminContext(), &pb.CountAllEnabledServersWithNodeClusterIdRequest{NodeClusterId: cluster.Id})
if err != nil {
this.ErrorPage(err)
}
clusterMaps = append(clusterMaps, maps.Map{
"id": cluster.Id,
"name": cluster.Name,
@@ -125,6 +131,7 @@ func (this *IndexAction) RunGet(params struct {
"dnsDomainId": cluster.DnsDomainId,
"dnsName": cluster.DnsName,
"dnsDomainName": dnsDomainName,
"countServers": countServersResp.Count,
})
}
}

View File

@@ -0,0 +1,133 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package boards
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 DnsAction struct {
actionutils.ParentAction
}
func (this *DnsAction) Init() {
this.Nav("", "", "dns")
}
func (this *DnsAction) RunGet(params struct{}) {
if !teaconst.IsPlus {
this.RedirectURL("/dashboard")
return
}
resp, err := this.RPC().NSRPC().ComposeNSBoard(this.AdminContext(), &pb.ComposeNSBoardRequest{})
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,
}
// 流量排行
{
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,
})
}
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.Show()
}

View File

@@ -0,0 +1,249 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package boards
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/maps"
"regexp"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "index")
}
func (this *IndexAction) RunGet(params struct{}) {
if !teaconst.IsPlus {
this.RedirectURL("/dashboard")
return
}
// 取得用户的权限
module, ok := configloaders.FindFirstAdminModule(this.AdminId())
if ok {
if module != "dashboard" {
for _, m := range configloaders.AllModuleMaps() {
if m.GetString("code") == module {
this.RedirectURL(m.GetString("url"))
return
}
}
}
}
// 读取看板数据
resp, err := this.RPC().AdminRPC().ComposeAdminDashboard(this.AdminContext(), &pb.ComposeAdminDashboardRequest{})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["dashboard"] = maps.Map{
"countServers": resp.CountServers,
"countNodeClusters": resp.CountNodeClusters,
"countNodes": resp.CountNodes,
"countUsers": resp.CountUsers,
"countAPINodes": resp.CountAPINodes,
"countDBNodes": resp.CountDBNodes,
"countUserNodes": resp.CountUserNodes,
"canGoServers": configloaders.AllowModule(this.AdminId(), configloaders.AdminModuleCodeServer),
"canGoNodes": configloaders.AllowModule(this.AdminId(), configloaders.AdminModuleCodeNode),
"canGoSettings": configloaders.AllowModule(this.AdminId(), configloaders.AdminModuleCodeSetting),
"canGoUsers": configloaders.AllowModule(this.AdminId(), configloaders.AdminModuleCodeUser),
}
// 今日流量
todayTrafficBytes := int64(0)
if len(resp.DailyTrafficStats) > 0 {
todayTrafficBytes = resp.DailyTrafficStats[len(resp.DailyTrafficStats)-1].Bytes
}
todayTrafficString := numberutils.FormatBytes(todayTrafficBytes)
result := regexp.MustCompile(`^(?U)(.+)([a-zA-Z]+)$`).FindStringSubmatch(todayTrafficString)
if len(result) > 2 {
this.Data["todayTraffic"] = result[1]
this.Data["todayTrafficUnit"] = result[2]
} else {
this.Data["todayTraffic"] = todayTrafficString
this.Data["todayTrafficUnit"] = ""
}
// 24小时流量趋势
{
statMaps := []maps.Map{}
for _, stat := range resp.HourlyTrafficStats {
statMaps = append(statMaps, maps.Map{
"bytes": stat.Bytes,
"cachedBytes": stat.CachedBytes,
"countRequests": stat.CountRequests,
"countCachedRequests": stat.CountCachedRequests,
"countAttackRequests": stat.CountAttackRequests,
"attackBytes": stat.AttackBytes,
"day": stat.Hour[4:6] + "月" + stat.Hour[6:8] + "日",
"hour": stat.Hour[8:],
})
}
this.Data["hourlyTrafficStats"] = statMaps
}
// 15天流量趋势
{
statMaps := []maps.Map{}
for _, stat := range resp.DailyTrafficStats {
statMaps = append(statMaps, maps.Map{
"bytes": stat.Bytes,
"cachedBytes": stat.CachedBytes,
"countRequests": stat.CountRequests,
"countCachedRequests": stat.CountCachedRequests,
"countAttackRequests": stat.CountAttackRequests,
"attackBytes": stat.AttackBytes,
"day": stat.Day[4:6] + "月" + stat.Day[6:] + "日",
})
}
this.Data["dailyTrafficStats"] = statMaps
}
// 节点排行
{
var statMaps = []maps.Map{}
for _, stat := range resp.TopNodeStats {
statMaps = append(statMaps, maps.Map{
"nodeId": stat.NodeId,
"nodeName": stat.NodeName,
"countRequests": stat.CountRequests,
"bytes": stat.Bytes,
})
}
this.Data["topNodeStats"] = statMaps
}
// 域名排行
{
var statMaps = []maps.Map{}
for _, stat := range resp.TopDomainStats {
statMaps = append(statMaps, maps.Map{
"serverId": stat.ServerId,
"domain": stat.Domain,
"countRequests": stat.CountRequests,
"bytes": stat.Bytes,
})
}
this.Data["topDomainStats"] = statMaps
}
// 版本升级
if resp.NodeUpgradeInfo != nil {
this.Data["nodeUpgradeInfo"] = maps.Map{
"count": resp.NodeUpgradeInfo.CountNodes,
"version": resp.NodeUpgradeInfo.NewVersion,
}
} else {
this.Data["nodeUpgradeInfo"] = maps.Map{
"count": 0,
"version": "",
}
}
if resp.MonitorNodeUpgradeInfo != nil {
this.Data["monitorNodeUpgradeInfo"] = maps.Map{
"count": resp.MonitorNodeUpgradeInfo.CountNodes,
"version": resp.MonitorNodeUpgradeInfo.NewVersion,
}
} else {
this.Data["monitorNodeUpgradeInfo"] = maps.Map{
"count": 0,
"version": "",
}
}
if resp.ApiNodeUpgradeInfo != nil {
this.Data["apiNodeUpgradeInfo"] = maps.Map{
"count": resp.ApiNodeUpgradeInfo.CountNodes,
"version": resp.ApiNodeUpgradeInfo.NewVersion,
}
} else {
this.Data["apiNodeUpgradeInfo"] = maps.Map{
"count": 0,
"version": "",
}
}
if resp.UserNodeUpgradeInfo != nil {
this.Data["userNodeUpgradeInfo"] = maps.Map{
"count": resp.UserNodeUpgradeInfo.CountNodes,
"version": resp.UserNodeUpgradeInfo.NewVersion,
}
} else {
this.Data["userNodeUpgradeInfo"] = maps.Map{
"count": 0,
"version": 0,
}
}
if resp.AuthorityNodeUpgradeInfo != nil {
this.Data["authorityNodeUpgradeInfo"] = maps.Map{
"count": resp.AuthorityNodeUpgradeInfo.CountNodes,
"version": resp.AuthorityNodeUpgradeInfo.NewVersion,
}
} else {
this.Data["authorityNodeUpgradeInfo"] = maps.Map{
"count": 0,
"version": "",
}
}
if resp.NsNodeUpgradeInfo != nil {
this.Data["nsNodeUpgradeInfo"] = maps.Map{
"count": resp.NsNodeUpgradeInfo.CountNodes,
"version": resp.NsNodeUpgradeInfo.NewVersion,
}
} else {
this.Data["nsNodeUpgradeInfo"] = maps.Map{
"count": 0,
"version": "",
}
}
// 指标
{
var chartMaps = []maps.Map{}
for _, chart := range resp.MetricDataCharts {
var statMaps = []maps.Map{}
for _, stat := range chart.MetricStats {
statMaps = append(statMaps, maps.Map{
"keys": stat.Keys,
"time": stat.Time,
"value": stat.Value,
"count": stat.SumCount,
"total": stat.SumTotal,
})
}
chartMaps = append(chartMaps, maps.Map{
"chart": maps.Map{
"id": chart.MetricChart.Id,
"name": chart.MetricChart.Name,
"widthDiv": chart.MetricChart.WidthDiv,
"isOn": chart.MetricChart.IsOn,
"maxItems": chart.MetricChart.MaxItems,
"type": chart.MetricChart.Type,
},
"item": maps.Map{
"id": chart.MetricChart.MetricItem.Id,
"name": chart.MetricChart.MetricItem.Name,
"period": chart.MetricChart.MetricItem.Period,
"periodUnit": chart.MetricChart.MetricItem.PeriodUnit,
"valueType": serverconfigs.FindMetricValueType(chart.MetricChart.MetricItem.Category, chart.MetricChart.MetricItem.Value),
"valueTypeName": serverconfigs.FindMetricValueName(chart.MetricChart.MetricItem.Category, chart.MetricChart.MetricItem.Value),
"keys": chart.MetricChart.MetricItem.Keys,
},
"stats": statMaps,
})
}
this.Data["metricCharts"] = chartMaps
}
this.Show()
}

View File

@@ -0,0 +1,103 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package boards
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 UserAction struct {
actionutils.ParentAction
}
func (this *UserAction) Init() {
this.Nav("", "", "user")
}
func (this *UserAction) RunGet(params struct{}) {
if !teaconst.IsPlus {
this.RedirectURL("/dashboard")
return
}
resp, err := this.RPC().UserRPC().ComposeUserGlobalBoard(this.AdminContext(), &pb.ComposeUserGlobalBoardRequest{})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["board"] = maps.Map{
"totalUsers": resp.TotalUsers,
"countTodayUsers": resp.CountTodayUsers,
"countWeeklyUsers": resp.CountWeeklyUsers,
"countUserNodes": resp.CountUserNodes,
"countOfflineUserNodes": resp.CountOfflineUserNodes,
}
{
statMaps := []maps.Map{}
for _, stat := range resp.DailyStats {
statMaps = append(statMaps, maps.Map{
"day": stat.Day,
"count": stat.Count,
})
}
this.Data["dailyStats"] = 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
}
// 流量排行
{
var statMaps = []maps.Map{}
for _, stat := range resp.TopTrafficStats {
statMaps = append(statMaps, maps.Map{
"userId": stat.UserId,
"userName": stat.UserName,
"countRequests": stat.CountRequests,
"bytes": stat.Bytes,
})
}
this.Data["topTrafficStats"] = statMaps
}
this.Show()
}

View File

@@ -0,0 +1,76 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package boards
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 WafAction struct {
actionutils.ParentAction
}
func (this *WafAction) Init() {
this.Nav("", "", "waf")
}
func (this *WafAction) RunGet(params struct{}) {
if !teaconst.IsPlus {
this.RedirectURL("/dashboard")
return
}
resp, err := this.RPC().FirewallRPC().ComposeFirewallGlobalBoard(this.AdminContext(), &pb.ComposeFirewallGlobalBoardRequest{})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["board"] = maps.Map{
"countDailyLogs": resp.CountDailyLogs,
"countDailyBlocks": resp.CountDailyBlocks,
"countDailyCaptcha": resp.CountDailyCaptcha,
"countWeeklyBlocks": resp.CountWeeklyBlocks,
}
{
var statMaps = []maps.Map{}
for _, stat := range resp.HourlyStats {
statMaps = append(statMaps, maps.Map{
"hour": stat.Hour,
"countLogs": stat.CountLogs,
"countCaptcha": stat.CountCaptcha,
"countBlocks": stat.CountBlocks,
})
}
this.Data["hourlyStats"] = statMaps
}
{
var statMaps = []maps.Map{}
for _, stat := range resp.DailyStats {
statMaps = append(statMaps, maps.Map{
"day": stat.Day,
"countLogs": stat.CountLogs,
"countCaptcha": stat.CountCaptcha,
"countBlocks": stat.CountBlocks,
})
}
this.Data["dailyStats"] = statMaps
}
{
var statMaps = []maps.Map{}
for _, stat := range resp.HttpFirewallRuleGroups {
statMaps = append(statMaps, maps.Map{
"name": stat.HttpFirewallRuleGroup.Name,
"count": stat.Count,
})
}
this.Data["groupStats"] = statMaps
}
this.Show()
}

View File

@@ -0,0 +1,28 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package boards
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type WafLogsAction struct {
actionutils.ParentAction
}
func (this *WafLogsAction) RunPost(params struct{}) {
resp, err := this.RPC().HTTPAccessLogRPC().ListHTTPAccessLogs(this.AdminContext(), &pb.ListHTTPAccessLogsRequest{
HasFirewallPolicy: true,
Reverse: false,
Day: timeutil.Format("Ymd"),
Size: 5,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["accessLogs"] = resp.HttpAccessLogs
this.Success()
}

View File

@@ -1,12 +1,15 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package dashboard
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/maps"
"math"
"regexp"
)
@@ -19,6 +22,11 @@ func (this *IndexAction) Init() {
}
func (this *IndexAction) RunGet(params struct{}) {
if teaconst.IsPlus {
this.RedirectURL("/dashboard/boards")
return
}
// 取得用户的权限
module, ok := configloaders.FindFirstAdminModule(this.AdminId())
if ok {
@@ -58,7 +66,7 @@ func (this *IndexAction) RunGet(params struct{}) {
if len(resp.DailyTrafficStats) > 0 {
todayTrafficBytes = resp.DailyTrafficStats[len(resp.DailyTrafficStats)-1].Bytes
}
todayTrafficString := numberutils.FormatBits(todayTrafficBytes * 8)
todayTrafficString := numberutils.FormatBytes(todayTrafficBytes)
result := regexp.MustCompile(`^(?U)(.+)([a-zA-Z]+)$`).FindStringSubmatch(todayTrafficString)
if len(result) > 2 {
this.Data["todayTraffic"] = result[1]
@@ -73,7 +81,7 @@ func (this *IndexAction) RunGet(params struct{}) {
statMaps := []maps.Map{}
for _, stat := range resp.HourlyTrafficStats {
statMaps = append(statMaps, maps.Map{
"count": math.Ceil((float64(stat.Bytes)*8/1000/1000/1000)*1000) / 1000,
"bytes": stat.Bytes,
"hour": stat.Hour[8:],
})
}
@@ -85,7 +93,7 @@ func (this *IndexAction) RunGet(params struct{}) {
statMaps := []maps.Map{}
for _, stat := range resp.DailyTrafficStats {
statMaps = append(statMaps, maps.Map{
"count": math.Ceil((float64(stat.Bytes)*8/1000/1000/1000)*1000) / 1000,
"bytes": stat.Bytes,
"day": stat.Day[4:6] + "月" + stat.Day[6:] + "日",
})
}
@@ -160,5 +168,43 @@ func (this *IndexAction) RunGet(params struct{}) {
}
}
// 指标
{
var chartMaps = []maps.Map{}
for _, chart := range resp.MetricDataCharts {
var statMaps = []maps.Map{}
for _, stat := range chart.MetricStats {
statMaps = append(statMaps, maps.Map{
"keys": stat.Keys,
"time": stat.Time,
"value": stat.Value,
"count": stat.SumCount,
"total": stat.SumTotal,
})
}
chartMaps = append(chartMaps, maps.Map{
"chart": maps.Map{
"id": chart.MetricChart.Id,
"name": chart.MetricChart.Name,
"widthDiv": chart.MetricChart.WidthDiv,
"isOn": chart.MetricChart.IsOn,
"maxItems": chart.MetricChart.MaxItems,
"type": chart.MetricChart.Type,
},
"item": maps.Map{
"id": chart.MetricChart.MetricItem.Id,
"name": chart.MetricChart.MetricItem.Name,
"period": chart.MetricChart.MetricItem.Period,
"periodUnit": chart.MetricChart.MetricItem.PeriodUnit,
"valueType": serverconfigs.FindMetricValueType(chart.MetricChart.MetricItem.Category, chart.MetricChart.MetricItem.Value),
"valueTypeName": serverconfigs.FindMetricValueName(chart.MetricChart.MetricItem.Category, chart.MetricChart.MetricItem.Value),
"keys": chart.MetricChart.MetricItem.Keys,
},
"stats": statMaps,
})
}
this.Data["metricCharts"] = chartMaps
}
this.Show()
}

View File

@@ -2,6 +2,7 @@ package dashboard
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dashboard/boards"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
@@ -12,6 +13,15 @@ func init() {
Data("teaMenu", "dashboard").
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeCommon)).
GetPost("", new(IndexAction)).
// 看板
Prefix("/dashboard/boards").
Get("", new(boards.IndexAction)).
Get("/waf", new(boards.WafAction)).
Post("/wafLogs", new(boards.WafLogsAction)).
Get("/dns", new(boards.DnsAction)).
Get("/user", new(boards.UserAction)).
EndAll()
})
}

View File

@@ -70,7 +70,7 @@ func (this *IndexAction) RunGet(params struct {
this.Show()
}
// 提交
// RunPost 提交
func (this *IndexAction) RunPost(params struct {
Token string
Username string

View File

@@ -39,7 +39,7 @@ func (this *ClusterHelper) BeforeAction(actionPtr actions.ActionWrapper) (goNext
logs.Error(err)
return
}
clusterResp, err := rpcClient.NSClusterRPC().FindEnabledNSCluster(actionPtr.(rpc.ContextInterface).AdminContext(), &pb.FindEnabledNSClusterRequest{
clusterResp, err := rpcClient.NSClusterRPC().FindEnabledNSCluster(actionPtr.(actionutils.ActionInterface).AdminContext(), &pb.FindEnabledNSClusterRequest{
NsClusterId: clusterId,
})
if err != nil {

View File

@@ -88,6 +88,7 @@ func (this *IndexAction) RunGet(params struct {
"ttl": record.Ttl,
"weight": record.Weight,
"description": record.Description,
"isOn": record.IsOn,
"routes": routeMaps,
})
}

View File

@@ -46,6 +46,7 @@ func (this *UpdatePopupAction) RunGet(params struct {
"ttl": record.Ttl,
"weight": record.Weight,
"description": record.Description,
"isOn": record.IsOn,
"routeIds": routeIds,
}
@@ -81,6 +82,7 @@ func (this *UpdatePopupAction) RunPost(params struct {
Value string
Ttl int32
Description string
IsOn bool
RouteIds []int64
Must *actions.Must
@@ -106,6 +108,7 @@ func (this *UpdatePopupAction) RunPost(params struct {
Type: params.Type,
Value: params.Value,
Ttl: params.Ttl,
IsOn: params.IsOn,
NsRouteIds: params.RouteIds,
})
if err != nil {

View File

@@ -1,6 +1,7 @@
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"
@@ -19,6 +20,11 @@ func (this *IndexAction) RunGet(params struct {
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

View File

@@ -0,0 +1,17 @@
package recover
import (
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/iwind/TeaGo/actions"
)
type Helper struct {
}
func (this *Helper) BeforeAction(actionPtr actions.ActionWrapper) (goNext bool) {
if !teaconst.IsRecoverMode {
actionPtr.Object().RedirectURL("/")
return false
}
return true
}

View File

@@ -1,4 +1,6 @@
package board
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package recover
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
@@ -7,8 +9,7 @@ type IndexAction struct {
}
func (this *IndexAction) Init() {
this.Nav("", "board", "")
this.SecondMenu("index")
this.Nav("", "", "")
}
func (this *IndexAction) RunGet(params struct{}) {

View File

@@ -0,0 +1,15 @@
package recover
import "github.com/iwind/TeaGo"
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(new(Helper)).
Prefix("/recover").
Get("", new(IndexAction)).
Post("/validateApi", new(ValidateApiAction)).
Post("/updateHosts", new(UpdateHostsAction)).
EndAll()
})
}

View File

@@ -0,0 +1,192 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package recover
import (
"bytes"
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/lists"
)
type UpdateHostsAction struct {
actionutils.ParentAction
}
func (this *UpdateHostsAction) RunPost(params struct {
Protocol string
Host string
Port string
NodeId string
NodeSecret string
OldHosts []string
NewHosts []string
}) {
if len(params.OldHosts) != len(params.NewHosts) {
this.Fail("参数配置错误,请刷新页面后重试")
}
client, err := rpc.NewRPCClient(&configs.APIConfig{
RPC: struct {
Endpoints []string `yaml:"endpoints"`
}{
Endpoints: []string{params.Protocol + "://" + configutils.QuoteIP(params.Host) + ":" + params.Port},
},
NodeId: params.NodeId,
Secret: params.NodeSecret,
})
if err != nil {
this.FailField("host", "测试API节点时出错请检查配置错误信息"+err.Error())
}
_, err = client.APINodeRPC().FindCurrentAPINodeVersion(client.APIContext(0), &pb.FindCurrentAPINodeVersionRequest{})
if err != nil {
this.FailField("host", "无法连接此API节点错误信息"+err.Error())
}
// 获取管理员节点信息
apiTokensResp, err := client.APITokenRPC().FindAllEnabledAPITokens(client.APIContext(0), &pb.FindAllEnabledAPITokensRequest{Role: "admin"})
if err != nil {
this.Fail("读取管理员令牌失败:" + err.Error())
}
var apiTokens = apiTokensResp.ApiTokens
if len(apiTokens) == 0 {
this.Fail("数据库中没有管理员令牌信息,请确认数据是否完整")
}
var adminAPIToken = apiTokens[0]
// API节点列表
nodesResp, err := client.APINodeRPC().FindAllEnabledAPINodes(client.Context(0), &pb.FindAllEnabledAPINodesRequest{})
if err != nil {
this.Fail("获取API节点列表失败错误信息" + err.Error())
}
var endpoints = []string{}
for _, node := range nodesResp.Nodes {
if !node.IsOn {
continue
}
// http
if len(node.HttpJSON) > 0 {
for index, oldHost := range params.OldHosts {
if len(params.NewHosts[index]) == 0 {
continue
}
node.HttpJSON = bytes.ReplaceAll(node.HttpJSON, []byte("\""+oldHost+"\""), []byte("\""+params.NewHosts[index]+"\""))
}
}
// https
if len(node.HttpsJSON) > 0 {
for index, oldHost := range params.OldHosts {
if len(params.NewHosts[index]) == 0 {
continue
}
node.HttpsJSON = bytes.ReplaceAll(node.HttpsJSON, []byte("\""+oldHost+"\""), []byte("\""+params.NewHosts[index]+"\""))
}
}
// restHTTP
if len(node.RestHTTPJSON) > 0 {
for index, oldHost := range params.OldHosts {
if len(params.NewHosts[index]) == 0 {
continue
}
node.RestHTTPJSON = bytes.ReplaceAll(node.RestHTTPJSON, []byte("\""+oldHost+"\""), []byte("\""+params.NewHosts[index]+"\""))
}
}
// restHTTPS
if len(node.RestHTTPSJSON) > 0 {
for index, oldHost := range params.OldHosts {
if len(params.NewHosts[index]) == 0 {
continue
}
node.RestHTTPSJSON = bytes.ReplaceAll(node.RestHTTPSJSON, []byte("\""+oldHost+"\""), []byte("\""+params.NewHosts[index]+"\""))
}
}
// access addrs
if len(node.AccessAddrsJSON) > 0 {
for index, oldHost := range params.OldHosts {
if len(params.NewHosts[index]) == 0 {
continue
}
node.AccessAddrsJSON = bytes.ReplaceAll(node.AccessAddrsJSON, []byte("\""+oldHost+"\""), []byte("\""+params.NewHosts[index]+"\""))
}
var addrs []*serverconfigs.NetworkAddressConfig
err = json.Unmarshal(node.AccessAddrsJSON, &addrs)
if err != nil {
this.Fail("读取节点访问地址失败:" + err.Error())
}
for _, addr := range addrs {
err = addr.Init()
if err != nil {
// 暂时不提示错误
continue
}
for _, a := range addr.FullAddresses() {
if !lists.ContainsString(endpoints, a) {
endpoints = append(endpoints, a)
}
}
}
}
// 保存
_, err = client.APINodeRPC().UpdateAPINode(client.Context(0), &pb.UpdateAPINodeRequest{
NodeId: node.Id,
Name: node.Name,
Description: node.Description,
HttpJSON: node.HttpJSON,
HttpsJSON: node.HttpsJSON,
AccessAddrsJSON: node.AccessAddrsJSON,
IsOn: node.IsOn,
RestIsOn: node.RestIsOn,
RestHTTPJSON: node.RestHTTPJSON,
RestHTTPSJSON: node.RestHTTPSJSON,
})
if err != nil {
this.Fail("保存API节点信息失败" + err.Error())
}
}
// 修改api.yaml
var apiConfig = &configs.APIConfig{
RPC: struct {
Endpoints []string `yaml:"endpoints"`
}{
Endpoints: endpoints,
},
NodeId: adminAPIToken.NodeId,
Secret: adminAPIToken.Secret,
}
err = apiConfig.WriteFile(Tea.Root + "/configs/api.yaml")
if err != nil {
this.Fail("保存configs/api.yaml失败" + err.Error())
}
// 加载api.yaml
rpcClient, err := rpc.SharedRPC()
if err != nil {
this.Fail("初始化RPC失败" + err.Error())
}
err = rpcClient.UpdateConfig(apiConfig)
if err != nil {
this.Fail("修改API配置失败" + err.Error())
}
// 退出恢复模式
teaconst.IsRecoverMode = false
this.Success()
}

View File

@@ -0,0 +1,153 @@
package recover
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
"strings"
)
type ValidateApiAction struct {
actionutils.ParentAction
}
func (this *ValidateApiAction) RunPost(params struct {
Protocol string
Host string
Port string
NodeId string
NodeSecret string
Must *actions.Must
}) {
params.NodeId = strings.Trim(params.NodeId, "\"' ")
params.NodeSecret = strings.Trim(params.NodeSecret, "\"' ")
// 使用已有的API节点
params.Must.
Field("host", params.Host).
Require("请输入主机地址").
Field("port", params.Port).
Require("请输入服务端口").
Match(`^\d+$`, "服务端口只能是数字").
Field("nodeId", params.NodeId).
Require("请输入节点nodeId").
Field("nodeSecret", params.NodeSecret).
Require("请输入节点secret")
client, err := rpc.NewRPCClient(&configs.APIConfig{
RPC: struct {
Endpoints []string `yaml:"endpoints"`
}{
Endpoints: []string{params.Protocol + "://" + configutils.QuoteIP(params.Host) + ":" + params.Port},
},
NodeId: params.NodeId,
Secret: params.NodeSecret,
})
if err != nil {
this.FailField("host", "测试API节点时出错请检查配置错误信息"+err.Error())
}
_, err = client.APINodeRPC().FindCurrentAPINodeVersion(client.APIContext(0), &pb.FindCurrentAPINodeVersionRequest{})
if err != nil {
this.FailField("host", "无法连接此API节点错误信息"+err.Error())
}
// API节点列表
nodesResp, err := client.APINodeRPC().FindAllEnabledAPINodes(client.Context(0), &pb.FindAllEnabledAPINodesRequest{})
if err != nil {
this.Fail("获取API节点列表失败错误信息" + err.Error())
}
var hosts = []string{}
for _, node := range nodesResp.Nodes {
if !node.IsOn {
continue
}
// http
if len(node.HttpJSON) > 0 {
var config = &serverconfigs.HTTPProtocolConfig{}
err = json.Unmarshal(node.HttpJSON, config)
if err != nil {
this.Fail("读取节点HTTP信息失败" + err.Error())
}
for _, listen := range config.Listen {
if len(listen.Host) > 0 && !lists.ContainsString(hosts, listen.Host) {
hosts = append(hosts, listen.Host)
}
}
}
// https
if len(node.HttpsJSON) > 0 {
var config = &serverconfigs.HTTPSProtocolConfig{}
err = json.Unmarshal(node.HttpsJSON, config)
if err != nil {
this.Fail("读取节点HTTPS信息失败" + err.Error())
}
for _, listen := range config.Listen {
if len(listen.Host) > 0 && !lists.ContainsString(hosts, listen.Host) {
hosts = append(hosts, listen.Host)
}
}
}
// restHTTP
if len(node.RestHTTPJSON) > 0 {
var config = &serverconfigs.HTTPProtocolConfig{}
err = json.Unmarshal(node.RestHTTPJSON, config)
if err != nil {
this.Fail("读取节点REST HTTP信息失败" + err.Error())
}
for _, listen := range config.Listen {
if len(listen.Host) > 0 && !lists.ContainsString(hosts, listen.Host) {
hosts = append(hosts, listen.Host)
}
}
}
// restHTTPS
if len(node.RestHTTPSJSON) > 0 {
var config = &serverconfigs.HTTPSProtocolConfig{}
err = json.Unmarshal(node.RestHTTPSJSON, config)
if err != nil {
this.Fail("读取节点REST HTTPS信息失败" + err.Error())
}
for _, listen := range config.Listen {
if len(listen.Host) > 0 && !lists.ContainsString(hosts, listen.Host) {
hosts = append(hosts, listen.Host)
}
}
}
// access addrs
if len(node.AccessAddrsJSON) > 0 {
var addrs []*serverconfigs.NetworkAddressConfig
err = json.Unmarshal(node.AccessAddrsJSON, &addrs)
if err != nil {
this.Fail("读取节点访问地址失败:" + err.Error())
}
for _, addr := range addrs {
if len(addr.Host) > 0 && !lists.ContainsString(hosts, addr.Host) {
hosts = append(hosts, addr.Host)
}
}
}
}
this.Data["apiNode"] = maps.Map{
"protocol": params.Protocol,
"host": params.Host,
"port": params.Port,
"nodeId": params.NodeId,
"nodeSecret": params.NodeSecret,
"hosts": hosts,
}
this.Success()
}

View File

@@ -20,7 +20,10 @@ func (this *AddPortPopupAction) Init() {
func (this *AddPortPopupAction) RunGet(params struct {
ServerType string
Protocol string
From string
}) {
this.Data["from"] = params.From
protocols := serverconfigs.AllServerProtocolsForType(params.ServerType)
if len(params.Protocol) > 0 {
result := []maps.Map{}

View File

@@ -72,6 +72,7 @@ func (this *CreateAction) RunPost(params struct {
DnsDomain string
Domains []string
AutoRenew bool
AuthURL string
Must *actions.Must
}) {
@@ -123,6 +124,7 @@ func (this *CreateAction) RunPost(params struct {
DnsDomain: dnsDomain,
Domains: realDomains,
AutoRenew: params.AutoRenew,
AuthURL: params.AuthURL,
})
if err != nil {
this.ErrorPage(err)
@@ -138,6 +140,7 @@ func (this *CreateAction) RunPost(params struct {
DnsDomain: dnsDomain,
Domains: realDomains,
AutoRenew: params.AutoRenew,
AuthURL: params.AuthURL,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -61,6 +61,7 @@ func (this *UpdateTaskPopupAction) RunGet(params struct {
"domains": task.Domains,
"autoRenew": task.AutoRenew,
"isOn": task.IsOn,
"authURL": task.AuthURL,
"dnsProvider": dnsProviderMap,
}
@@ -94,6 +95,7 @@ func (this *UpdateTaskPopupAction) RunPost(params struct {
DnsDomain string
Domains []string
AutoRenew bool
AuthURL string
Must *actions.Must
CSRF *actionutils.CSRF
@@ -146,6 +148,7 @@ func (this *UpdateTaskPopupAction) RunPost(params struct {
DnsDomain: dnsDomain,
Domains: realDomains,
AutoRenew: params.AutoRenew,
AuthURL: params.AuthURL,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -9,7 +9,6 @@ import (
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"strconv"
"strings"
)
type CreateSetPopupAction struct {
@@ -53,6 +52,7 @@ func (this *CreateSetPopupAction) RunGet(params struct {
},
}
// 所有可选的动作
actionMaps := []maps.Map{}
for _, action := range firewallconfigs.AllActions {
actionMaps = append(actionMaps, maps.Map{
@@ -69,10 +69,10 @@ func (this *CreateSetPopupAction) RunGet(params struct {
func (this *CreateSetPopupAction) RunPost(params struct {
GroupId int64
Name string
RulesJSON []byte
Connector string
Action string
Name string
RulesJSON []byte
Connector string
ActionsJSON []byte
Must *actions.Must
}) {
@@ -96,32 +96,34 @@ func (this *CreateSetPopupAction) RunPost(params struct {
err = json.Unmarshal(params.RulesJSON, &rules)
if err != nil {
this.ErrorPage(err)
return
}
if len(rules) == 0 {
this.Fail("请添加至少一个规则")
}
setConfig := &firewallconfigs.HTTPFirewallRuleSet{
Id: 0,
IsOn: true,
Name: params.Name,
Code: "",
Description: "",
Connector: params.Connector,
RuleRefs: nil,
Rules: rules,
Action: params.Action,
ActionOptions: maps.Map{},
var actionConfigs = []*firewallconfigs.HTTPFirewallActionConfig{}
if len(params.ActionsJSON) > 0 {
err = json.Unmarshal(params.ActionsJSON, &actionConfigs)
if err != nil {
this.ErrorPage(err)
return
}
}
if len(actionConfigs) == 0 {
this.Fail("请添加至少一个动作")
}
for k, v := range this.ParamsMap {
if len(v) == 0 {
continue
}
index := strings.Index(k, "action_")
if index > -1 {
setConfig.ActionOptions[k[len("action_"):]] = v[0]
}
setConfig := &firewallconfigs.HTTPFirewallRuleSet{
Id: 0,
IsOn: true,
Name: params.Name,
Code: "",
Description: "",
Connector: params.Connector,
RuleRefs: nil,
Rules: rules,
Actions: actionConfigs,
}
setConfigJSON, err := json.Marshal(setConfig)

View File

@@ -6,7 +6,6 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
"strconv"
"strings"
)
@@ -54,31 +53,19 @@ func (this *GroupAction) RunGet(params struct {
set := v.(*firewallconfigs.HTTPFirewallRuleSet)
// 动作说明
actionLinks := []maps.Map{}
if set.Action == firewallconfigs.HTTPFirewallActionGoGroup {
nextGroup := firewallPolicy.FindRuleGroup(set.ActionOptions.GetInt64("groupId"))
if nextGroup != nil {
actionLinks = append(actionLinks, maps.Map{
"name": nextGroup.Name,
"url": "/servers/components/waf/group?firewallPolicyId=" + strconv.FormatInt(params.FirewallPolicyId, 10) + "&type=" + params.Type + "&groupId=" + strconv.FormatInt(nextGroup.Id, 10),
})
var actionMaps = []maps.Map{}
for _, action := range set.Actions {
def := firewallconfigs.FindActionDefinition(action.Code)
if def == nil {
continue
}
} else if set.Action == firewallconfigs.HTTPFirewallActionGoSet {
nextGroup := firewallPolicy.FindRuleGroup(set.ActionOptions.GetInt64("groupId"))
if nextGroup != nil {
actionLinks = append(actionLinks, maps.Map{
"name": nextGroup.Name,
"url": "/servers/components/waf/group?firewallPolicyId=" + strconv.FormatInt(params.FirewallPolicyId, 10) + "&type=" + params.Type + "&groupId=" + strconv.FormatInt(nextGroup.Id, 10),
})
nextSet := nextGroup.FindRuleSet(set.ActionOptions.GetInt64("setId"))
if nextSet != nil {
actionLinks = append(actionLinks, maps.Map{
"name": nextSet.Name,
"url": "/servers/components/waf/group?firewallPolicyId=" + strconv.FormatInt(params.FirewallPolicyId, 10) + "&type=" + params.Type + "&groupId=" + strconv.FormatInt(nextGroup.Id, 10),
})
}
}
actionMaps = append(actionMaps, maps.Map{
"code": strings.ToUpper(action.Code),
"name": def.Name,
"category": def.Category,
"options": action.Options,
})
}
return maps.Map{
@@ -93,14 +80,12 @@ func (this *GroupAction) RunGet(params struct {
"value": rule.Value,
"isCaseInsensitive": rule.IsCaseInsensitive,
"isComposed": firewallconfigs.CheckCheckpointIsComposed(rule.Prefix()),
"checkpointOptions": rule.CheckpointOptions,
}
}),
"isOn": set.IsOn,
"action": strings.ToUpper(set.Action),
"actionOptions": set.ActionOptions,
"actionName": firewallconfigs.FindActionName(set.Action),
"actionLinks": actionLinks,
"connector": strings.ToUpper(set.Connector),
"isOn": set.IsOn,
"actions": actionMaps,
"connector": strings.ToUpper(set.Connector),
}
})

View File

@@ -22,6 +22,7 @@ func (this *ListsAction) RunGet(params struct {
Type string
}) {
this.Data["subMenuItem"] = params.Type
this.Data["type"] = params.Type
listId, err := dao.SharedHTTPFirewallPolicyDAO.FindEnabledPolicyIPListIdWithType(this.AdminContext(), params.FirewallPolicyId, params.Type)
if err != nil {

View File

@@ -9,7 +9,6 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"strings"
)
type UpdateSetPopupAction struct {
@@ -79,6 +78,14 @@ func (this *UpdateSetPopupAction) RunGet(params struct {
}
this.Data["setConfig"] = setConfig
// action configs
actionConfigs, err := dao.SharedHTTPFirewallPolicyDAO.FindHTTPFirewallActionConfigs(this.AdminContext(), setConfig.Actions)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["actionConfigs"] = actionConfigs
this.Show()
}
@@ -86,10 +93,10 @@ func (this *UpdateSetPopupAction) RunPost(params struct {
GroupId int64
SetId int64
Name string
RulesJSON []byte
Connector string
Action string
Name string
RulesJSON []byte
Connector string
ActionsJSON []byte
Must *actions.Must
}) {
@@ -115,26 +122,28 @@ func (this *UpdateSetPopupAction) RunPost(params struct {
err = json.Unmarshal(params.RulesJSON, &rules)
if err != nil {
this.ErrorPage(err)
return
}
if len(rules) == 0 {
this.Fail("请添加至少一个规则")
}
var actionConfigs = []*firewallconfigs.HTTPFirewallActionConfig{}
if len(params.ActionsJSON) > 0 {
err = json.Unmarshal(params.ActionsJSON, &actionConfigs)
if err != nil {
this.ErrorPage(err)
return
}
}
if len(actionConfigs) == 0 {
this.Fail("请添加至少一个动作")
}
setConfig.Name = params.Name
setConfig.Connector = params.Connector
setConfig.Rules = rules
setConfig.Action = params.Action
setConfig.ActionOptions = maps.Map{}
for k, v := range this.ParamsMap {
if len(v) == 0 {
continue
}
index := strings.Index(k, "action_")
if index > -1 {
setConfig.ActionOptions[k[len("action_"):]] = v[0]
}
}
setConfig.Actions = actionConfigs
setConfigJSON, err := json.Marshal(setConfig)
if err != nil {

View File

@@ -5,11 +5,14 @@ import (
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"strings"
)
@@ -68,6 +71,9 @@ func (this *CreateAction) RunPost(params struct {
CertIdsJSON []byte
Origins string
AccessLogIsOn bool
WebsocketIsOn bool
WebRoot string
Must *actions.Must
@@ -415,6 +421,70 @@ func (this *CreateAction) RunPost(params struct {
this.ErrorPage(err)
return
}
var serverId = createResp.ServerId
// 开启访问日志和Websocket
if params.ServerType == serverconfigs.ServerTypeHTTPProxy {
webConfig, err := dao.SharedHTTPWebDAO.FindWebConfigWithServerId(this.AdminContext(), serverId)
if err != nil {
logs.Error(err)
} else {
// 访问日志
if params.AccessLogIsOn {
_, err = this.RPC().HTTPWebRPC().UpdateHTTPWebAccessLog(this.AdminContext(), &pb.UpdateHTTPWebAccessLogRequest{
WebId: webConfig.Id,
AccessLogJSON: []byte(`{
"isPrior": false,
"isOn": true,
"fields": [],
"status1": true,
"status2": true,
"status3": true,
"status4": true,
"status5": true,
"storageOnly": false,
"storagePolicies": [],
"firewallOnly": false
}`),
})
if err != nil {
logs.Error(err)
}
}
// websocket
if params.WebsocketIsOn {
createWebSocketResp, err := this.RPC().HTTPWebsocketRPC().CreateHTTPWebsocket(this.AdminContext(), &pb.CreateHTTPWebsocketRequest{
HandshakeTimeoutJSON: []byte(`{
"count": 30,
"unit": "second"
}`),
AllowAllOrigins: true,
AllowedOrigins: nil,
RequestSameOrigin: true,
RequestOrigin: "",
})
if err != nil {
logs.Error(err)
} else {
websocketId := createWebSocketResp.WebsocketId
_, err = this.RPC().HTTPWebRPC().UpdateHTTPWebWebsocket(this.AdminContext(), &pb.UpdateHTTPWebWebsocketRequest{
WebId: webConfig.Id,
WebsocketJSON: []byte(` {
"isPrior": false,
"isOn": true,
"websocketId": ` + types.String(websocketId) + `
}`),
})
if err != nil {
logs.Error(err)
}
}
}
}
}
// 创建日志
defer this.CreateLog(oplogs.LevelInfo, "创建代理服务 %d", createResp.ServerId)

View File

@@ -0,0 +1,55 @@
// 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"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "")
}
func (this *IndexAction) RunGet(params struct {
Ip string
}) {
this.Data["ip"] = params.Ip
accessLogsResp, err := this.RPC().HTTPAccessLogRPC().ListHTTPAccessLogs(this.AdminContext(), &pb.ListHTTPAccessLogsRequest{
Day: timeutil.Format("Ymd"),
Keyword: "ip:" + params.Ip,
Size: 10,
})
if err != nil {
this.ErrorPage(err)
return
}
var accessLogs = accessLogsResp.HttpAccessLogs
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,
})
if err != nil {
this.ErrorPage(err)
return
}
accessLogs = accessLogsResp.HttpAccessLogs
if len(accessLogs) == 0 {
accessLogs = []*pb.HTTPAccessLog{}
}
}
this.Data["accessLogs"] = accessLogs
this.Show()
}

View File

@@ -0,0 +1,19 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package ipbox
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeServer)).
Prefix("/servers/ipbox").
Get("", new(IndexAction)).
EndAll()
})
}

View File

@@ -0,0 +1,51 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package iplists
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type AccessLogsPopupAction struct {
actionutils.ParentAction
}
func (this *AccessLogsPopupAction) Init() {
this.Nav("", "", "")
}
func (this *AccessLogsPopupAction) RunGet(params struct {
ItemId int64
}) {
itemResp, err := this.RPC().IPItemRPC().FindEnabledIPItem(this.AdminContext(), &pb.FindEnabledIPItemRequest{IpItemId: params.ItemId})
if err != nil {
this.ErrorPage(err)
return
}
var item = itemResp.IpItem
if item == nil {
this.NotFound("ipItem", params.ItemId)
return
}
this.Data["ipFrom"] = item.IpFrom
this.Data["ipTo"] = item.IpTo
accessLogsResp, err := this.RPC().HTTPAccessLogRPC().ListHTTPAccessLogs(this.AdminContext(), &pb.ListHTTPAccessLogsRequest{
Day: timeutil.Format("Ymd"),
Keyword: "ip:" + item.IpFrom + "," + item.IpTo,
Size: 10,
})
if err != nil {
this.ErrorPage(err)
return
}
var accessLogs = accessLogsResp.HttpAccessLogs
if len(accessLogs) == 0 {
accessLogs = []*pb.HTTPAccessLog{}
}
this.Data["accessLogs"] = accessLogs
this.Show()
}

View File

@@ -0,0 +1,141 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package iplists
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
)
type BindHTTPFirewallPopupAction struct {
actionutils.ParentAction
}
func (this *BindHTTPFirewallPopupAction) Init() {
this.Nav("", "", "")
}
func (this *BindHTTPFirewallPopupAction) RunGet(params struct {
HttpFirewallPolicyId int64
Type string
}) {
this.Data["httpFirewallPolicyId"] = params.HttpFirewallPolicyId
// 获取已经选中的名单IDs
var selectedIds = []int64{}
inboundConfig, err := dao.SharedHTTPFirewallPolicyDAO.FindEnabledHTTPFirewallPolicyInboundConfig(this.AdminContext(), params.HttpFirewallPolicyId)
if err != nil {
this.ErrorPage(err)
return
}
if inboundConfig != nil {
for _, ref := range inboundConfig.PublicAllowListRefs {
selectedIds = append(selectedIds, ref.ListId)
}
for _, ref := range inboundConfig.PublicDenyListRefs {
selectedIds = append(selectedIds, ref.ListId)
}
}
// 公共的名单
countResp, err := this.RPC().IPListRPC().CountAllEnabledIPLists(this.AdminContext(), &pb.CountAllEnabledIPListsRequest{
Type: params.Type,
IsPublic: true,
Keyword: "",
})
if err != nil {
this.ErrorPage(err)
return
}
count := countResp.Count
page := this.NewPage(count)
this.Data["page"] = page.AsHTML()
listsResp, err := this.RPC().IPListRPC().ListEnabledIPLists(this.AdminContext(), &pb.ListEnabledIPListsRequest{
Type: params.Type,
IsPublic: true,
Keyword: "",
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
var listMaps = []maps.Map{}
for _, list := range listsResp.IpLists {
// 包含的IP数量
countItemsResp, err := this.RPC().IPItemRPC().CountIPItemsWithListId(this.AdminContext(), &pb.CountIPItemsWithListIdRequest{IpListId: list.Id})
if err != nil {
this.ErrorPage(err)
return
}
var countItems = countItemsResp.Count
listMaps = append(listMaps, maps.Map{
"id": list.Id,
"isOn": list.IsOn,
"name": list.Name,
"description": list.Description,
"countItems": countItems,
"type": list.Type,
"isSelected": lists.ContainsInt64(selectedIds, list.Id),
})
}
this.Data["lists"] = listMaps
this.Show()
}
func (this *BindHTTPFirewallPopupAction) RunPost(params struct {
HttpFirewallPolicyId int64
ListId int64
Must *actions.Must
}) {
defer this.CreateLogInfo("绑定IP名单 %d 到WAF策略 %d", params.ListId, params.HttpFirewallPolicyId)
// List类型
listResp, err := this.RPC().IPListRPC().FindEnabledIPList(this.AdminContext(), &pb.FindEnabledIPListRequest{IpListId: params.ListId})
if err != nil {
this.ErrorPage(err)
return
}
var list = listResp.IpList
if list == nil {
this.Fail("找不到要使用的IP名单")
}
// 已经绑定的
inboundConfig, err := dao.SharedHTTPFirewallPolicyDAO.FindEnabledHTTPFirewallPolicyInboundConfig(this.AdminContext(), params.HttpFirewallPolicyId)
if err != nil {
this.ErrorPage(err)
return
}
if inboundConfig == nil {
inboundConfig = &firewallconfigs.HTTPFirewallInboundConfig{IsOn: true}
}
inboundConfig.AddPublicList(list.Id, list.Type)
inboundJSON, err := json.Marshal(inboundConfig)
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().HTTPFirewallPolicyRPC().UpdateHTTPFirewallInboundConfig(this.AdminContext(), &pb.UpdateHTTPFirewallInboundConfigRequest{
HttpFirewallPolicyId: params.HttpFirewallPolicyId,
InboundJSON: inboundJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,103 @@
package iplists
import (
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
)
type CreateIPPopupAction struct {
actionutils.ParentAction
}
func (this *CreateIPPopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreateIPPopupAction) RunGet(params struct {
ListId int64
}) {
this.Data["listId"] = params.ListId
this.Show()
}
func (this *CreateIPPopupAction) RunPost(params struct {
ListId int64
IpFrom string
IpTo string
ExpiredAt int64
Reason string
Type string
EventLevel string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
// 校验IPList
existsResp, err := this.RPC().IPListRPC().ExistsEnabledIPList(this.AdminContext(), &pb.ExistsEnabledIPListRequest{IpListId: params.ListId})
if err != nil {
this.ErrorPage(err)
return
}
if !existsResp.Exists {
this.Fail("IP名单不存在")
}
switch params.Type {
case "ipv4":
params.Must.
Field("ipFrom", params.IpFrom).
Require("请输入开始IP")
// 校验IP格式ipFrom/ipTo
var ipFromLong uint64
if !utils.IsIPv4(params.IpFrom) {
this.Fail("请输入正确的开始IP")
}
ipFromLong = utils.IP2Long(params.IpFrom)
var ipToLong uint64
if len(params.IpTo) > 0 && !utils.IsIPv4(params.IpTo) {
ipToLong = utils.IP2Long(params.IpTo)
this.Fail("请输入正确的结束IP")
}
if ipFromLong > 0 && ipToLong > 0 && ipFromLong > ipToLong {
params.IpTo, params.IpFrom = params.IpFrom, params.IpTo
}
case "ipv6":
params.Must.
Field("ipFrom", params.IpFrom).
Require("请输入IP")
// 校验IP格式ipFrom
if !utils.IsIPv6(params.IpFrom) {
this.Fail("请输入正确的IPv6地址")
}
case "all":
params.IpFrom = "0.0.0.0"
}
createResp, err := this.RPC().IPItemRPC().CreateIPItem(this.AdminContext(), &pb.CreateIPItemRequest{
IpListId: params.ListId,
IpFrom: params.IpFrom,
IpTo: params.IpTo,
ExpiredAt: params.ExpiredAt,
Reason: params.Reason,
Type: params.Type,
EventLevel: params.EventLevel,
})
if err != nil {
this.ErrorPage(err)
return
}
itemId := createResp.IpItemId
// 日志
defer this.CreateLog(oplogs.LevelInfo, "在IP名单中添加IP %d", itemId)
this.Success()
}

View File

@@ -0,0 +1,64 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package iplists
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 CreatePopupAction struct {
actionutils.ParentAction
}
func (this *CreatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreatePopupAction) RunGet(params struct {
Type string
}) {
this.Data["type"] = params.Type
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
Name string
Type string
Description string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
var listId int64 = 0
defer func() {
defer this.CreateLogInfo("创建IP名单 %d", listId)
}()
params.Must.
Field("name", params.Name).
Require("请输入名称")
createResp, err := this.RPC().IPListRPC().CreateIPList(this.AdminContext(), &pb.CreateIPListRequest{
Type: params.Type,
Name: params.Name,
Code: "",
TimeoutJSON: nil,
IsPublic: true,
Description: params.Description,
})
if err != nil {
this.ErrorPage(err)
return
}
listId = createResp.IpListId
this.Data["list"] = maps.Map{
"type": params.Type,
}
this.Success()
}

View File

@@ -0,0 +1,27 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package iplists
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteAction struct {
actionutils.ParentAction
}
func (this *DeleteAction) RunPost(params struct {
ListId int64
}) {
defer this.CreateLogInfo("删除IP名单 %d", params.ListId)
// 删除
_, err := this.RPC().IPListRPC().DeleteIPList(this.AdminContext(), &pb.DeleteIPListRequest{IpListId: params.ListId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,26 @@
package iplists
import (
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteIPAction struct {
actionutils.ParentAction
}
func (this *DeleteIPAction) RunPost(params struct {
ItemId int64
}) {
// 日志
defer this.CreateLog(oplogs.LevelInfo, "从IP名单中删除IP %d", params.ItemId)
_, err := this.RPC().IPItemRPC().DeleteIPItem(this.AdminContext(), &pb.DeleteIPItemRequest{IpItemId: params.ItemId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,25 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package iplists
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
type ExportAction struct {
actionutils.ParentAction
}
func (this *ExportAction) Init() {
this.Nav("", "", "export")
}
func (this *ExportAction) RunGet(params struct {
ListId int64
}) {
err := InitIPList(this.Parent(), params.ListId)
if err != nil {
this.ErrorPage(err)
return
}
this.Show()
}

View File

@@ -0,0 +1,57 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package iplists
import (
"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"
"strconv"
)
type ExportDataAction struct {
actionutils.ParentAction
}
func (this *ExportDataAction) Init() {
this.Nav("", "", "")
}
func (this *ExportDataAction) RunGet(params struct {
ListId int64
}) {
defer this.CreateLogInfo("导出IP名单 %d", params.ListId)
resp := &pb.ListIPItemsWithListIdResponse{}
var offset int64 = 0
var size int64 = 1000
for {
itemsResp, err := this.RPC().IPItemRPC().ListIPItemsWithListId(this.AdminContext(), &pb.ListIPItemsWithListIdRequest{
IpListId: params.ListId,
Offset: offset,
Size: size,
})
if err != nil {
this.ErrorPage(err)
return
}
if len(itemsResp.IpItems) == 0 {
break
}
for _, item := range itemsResp.IpItems {
resp.IpItems = append(resp.IpItems, item)
}
offset += size
}
data, err := proto.Marshal(resp)
if err != nil {
this.ErrorPage(err)
return
}
this.AddHeader("Content-Disposition", "attachment; filename=\"ip-list-"+numberutils.FormatInt64(params.ListId)+".data\";")
this.AddHeader("Content-Length", strconv.Itoa(len(data)))
this.Write(data)
}

View File

@@ -0,0 +1,59 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package iplists
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/ipconfigs"
"github.com/iwind/TeaGo/maps"
)
// HttpFirewallAction 显示已经绑定的IP名单
type HttpFirewallAction struct {
actionutils.ParentAction
}
func (this *HttpFirewallAction) RunPost(params struct {
HttpFirewallPolicyId int64
Type string
}) {
inboundConfig, err := dao.SharedHTTPFirewallPolicyDAO.FindEnabledHTTPFirewallPolicyInboundConfig(this.AdminContext(), params.HttpFirewallPolicyId)
if err != nil {
this.ErrorPage(err)
return
}
if inboundConfig == nil {
inboundConfig = &firewallconfigs.HTTPFirewallInboundConfig{IsOn: true}
}
var refs []*ipconfigs.IPListRef
switch params.Type {
case ipconfigs.IPListTypeBlack:
refs = inboundConfig.PublicDenyListRefs
case ipconfigs.IPListTypeWhite:
refs = inboundConfig.PublicAllowListRefs
}
listMaps := []maps.Map{}
for _, ref := range refs {
listResp, err := this.RPC().IPListRPC().FindEnabledIPList(this.AdminContext(), &pb.FindEnabledIPListRequest{IpListId: ref.ListId})
if err != nil {
this.ErrorPage(err)
return
}
var list = listResp.IpList
if list == nil {
continue
}
listMaps = append(listMaps, maps.Map{
"id": list.Id,
"name": list.Name,
})
}
this.Data["lists"] = listMaps
this.Success()
}

View File

@@ -0,0 +1,87 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package iplists
import (
"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"
)
type ImportAction struct {
actionutils.ParentAction
}
func (this *ImportAction) Init() {
this.Nav("", "", "import")
}
func (this *ImportAction) RunGet(params struct {
ListId int64
}) {
err := InitIPList(this.Parent(), params.ListId)
if err != nil {
this.ErrorPage(err)
return
}
this.Show()
}
func (this *ImportAction) RunPost(params struct {
ListId int64
File *actions.File
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo("导入IP名单 %d", params.ListId)
existsResp, err := this.RPC().IPListRPC().ExistsEnabledIPList(this.AdminContext(), &pb.ExistsEnabledIPListRequest{IpListId: params.ListId})
if err != nil {
this.ErrorPage(err)
return
}
if !existsResp.Exists {
this.Fail("IP名单不存在")
}
if params.File == nil {
this.Fail("请选择要导入的IP文件")
}
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 count = 0
var countIgnore = 0
for _, item := range resp.IpItems {
_, err = this.RPC().IPItemRPC().CreateIPItem(this.AdminContext(), &pb.CreateIPItemRequest{
IpListId: params.ListId,
IpFrom: item.IpFrom,
IpTo: item.IpTo,
ExpiredAt: item.ExpiredAt,
Reason: item.Reason,
Type: item.Type,
EventLevel: item.EventLevel,
})
if err != nil {
this.Fail("导入过程中出错:" + err.Error())
}
count++
}
this.Data["count"] = count
this.Data["countIgnore"] = countIgnore
this.Success()
}

View File

@@ -0,0 +1,76 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package iplists
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/ipconfigs"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "index")
}
func (this *IndexAction) RunGet(params struct {
Type string
Keyword string
}) {
if len(params.Type) == 0 {
params.Type = ipconfigs.IPListTypeBlack
}
this.Data["type"] = params.Type
this.Data["keyword"] = params.Keyword
countResp, err := this.RPC().IPListRPC().CountAllEnabledIPLists(this.AdminContext(), &pb.CountAllEnabledIPListsRequest{
Type: params.Type,
IsPublic: true,
Keyword: params.Keyword,
})
if err != nil {
this.ErrorPage(err)
return
}
count := countResp.Count
page := this.NewPage(count)
this.Data["page"] = page.AsHTML()
listsResp, err := this.RPC().IPListRPC().ListEnabledIPLists(this.AdminContext(), &pb.ListEnabledIPListsRequest{
Type: params.Type,
IsPublic: true,
Keyword: params.Keyword,
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
var listMaps = []maps.Map{}
for _, list := range listsResp.IpLists {
// 包含的IP数量
countItemsResp, err := this.RPC().IPItemRPC().CountIPItemsWithListId(this.AdminContext(), &pb.CountIPItemsWithListIdRequest{IpListId: list.Id})
if err != nil {
this.ErrorPage(err)
return
}
var countItems = countItemsResp.Count
listMaps = append(listMaps, maps.Map{
"id": list.Id,
"isOn": list.IsOn,
"name": list.Name,
"description": list.Description,
"countItems": countItems,
"type": list.Type,
})
}
this.Data["lists"] = listMaps
this.Show()
}

View File

@@ -0,0 +1,44 @@
package iplists
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", "iplist").
Prefix("/servers/iplists").
Get("", new(IndexAction)).
GetPost("/createPopup", new(CreatePopupAction)).
Get("/list", new(ListAction)).
GetPost("/import", new(ImportAction)).
GetPost("/export", new(ExportAction)).
Get("/exportData", new(ExportDataAction)).
Post("/delete", new(DeleteAction)).
GetPost("/test", new(TestAction)).
GetPost("/update", new(UpdateAction)).
Get("/items", new(ItemsAction)).
Get("/selectPopup", new(SelectPopupAction)).
// IP相关
GetPost("/createIPPopup", new(CreateIPPopupAction)).
GetPost("/updateIPPopup", new(UpdateIPPopupAction)).
Post("/deleteIP", new(DeleteIPAction)).
Get("/accessLogsPopup", new(AccessLogsPopupAction)).
// 防火墙
GetPost("/bindHTTPFirewallPopup", new(BindHTTPFirewallPopupAction)).
Post("/unbindHTTPFirewall", new(UnbindHTTPFirewallAction)).
Post("/httpFirewall", new(HttpFirewallAction)).
// 选项数据
Post("/levelOptions", new(LevelOptionsAction)).
EndAll()
})
}

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