Compare commits

...

80 Commits

Author SHA1 Message Date
刘祥超
f9f7cd723a 改进界面 2021-04-18 16:30:58 +08:00
刘祥超
4a813f08cd 改进界面 2021-04-18 15:58:52 +08:00
刘祥超
e75757f2bc 改进安装界面中的小细节 2021-04-18 15:41:47 +08:00
刘祥超
91010325eb 优化集群没有节点时的提示 2021-04-18 15:17:49 +08:00
刘祥超
441c0bd628 优化界面 2021-04-15 17:11:38 +08:00
刘祥超
a91b97ff2b 域名解析增加CloudFlare DNS支持 2021-04-15 17:02:35 +08:00
刘祥超
fb775a0ee9 版本认证中增加公司/组织名 2021-04-14 20:02:08 +08:00
刘祥超
8ed02c8103 增加认证节点管理 2021-04-13 21:23:13 +08:00
刘祥超
42a306ff22 增加企业版认证相关API 2021-04-13 20:01:43 +08:00
刘祥超
a637783249 实现发送消息到媒介 2021-04-12 19:19:59 +08:00
刘祥超
7ffff890a8 URL跳转增加匹配前缀和是否保留RequestURI选项 2021-04-07 11:20:23 +08:00
刘祥超
dbfbdddb3a 实现监控节点管理 2021-04-06 16:32:23 +08:00
刘祥超
f85b3a40ea 实现基础的通知媒介管理 2021-04-05 20:48:13 +08:00
刘祥超
27421bbd46 优化错误提示界面 2021-04-05 08:18:17 +08:00
刘祥超
129d2ccef0 系统用户增加是否允许登录选项 2021-03-30 11:00:06 +08:00
刘祥超
a553bad1ef 优化界面细节 2021-03-29 17:47:10 +08:00
刘祥超
ae7020437b 不在界面上显示SSH认证的密码,提升安全性 2021-03-28 16:21:46 +08:00
刘祥超
74e6c7a87c “系统设置 -- 安全管理”里可以单独添加允许访问的IP 2021-03-28 15:50:28 +08:00
刘祥超
658c5ae6dd 优化左侧菜单显示 2021-03-28 14:47:21 +08:00
刘祥超
7a24a69223 反向代理可以整体设置源站默认超时时间等参数 2021-03-26 22:08:18 +08:00
刘祥超
de90acaae4 反向代理中源站增加最大连接数、连接超时时间等参数 2021-03-25 21:18:50 +08:00
刘祥超
51080f2669 申请证书任务可以筛选和搜索 2021-03-16 18:10:00 +08:00
刘祥超
fa5d97b74c 访问日志默认为关闭 2021-03-16 10:24:22 +08:00
刘祥超
42ba081cd1 修改README中的文档链接地址 2021-03-11 14:42:45 +08:00
刘祥超
404d733ba1 增加编译脚本 2021-03-11 14:41:09 +08:00
刘祥超
2f3398e251 优化界面 2021-03-09 14:45:16 +08:00
刘祥超
5c09e272f3 修改版本号 2021-03-09 14:44:47 +08:00
刘祥超
d045f0526f 文件缓存策略支持二级缓存(内存 | 文件) 2021-03-02 19:38:13 +08:00
刘祥超
0766ec9d5a WAF动作增加显示HTML内容 2021-02-26 16:32:07 +08:00
刘祥超
4511b48382 可以设置管理界面和用户界面的浏览器图标和Logo 2021-02-25 20:54:30 +08:00
刘祥超
151e58fbb9 优化界面 2021-02-25 19:07:58 +08:00
刘祥超
c833ac2e96 因为健康检查下线的节点可以手动恢复上线 2021-02-24 19:27:33 +08:00
刘祥超
99983330a3 集群健康检查URL中可以输入主机名 2021-02-24 16:20:04 +08:00
刘祥超
a14e81a28f 优化界面显示 2021-02-24 15:01:45 +08:00
刘祥超
fc714a15a3 相关统计图表中X坐标显示所有标签 2021-02-24 11:11:41 +08:00
刘祥超
8a8f9d2912 修复重新加载RPC连接时可能死锁的问题 2021-02-24 10:44:53 +08:00
刘祥超
58a0521605 自动更新API节点配置 2021-02-24 09:00:12 +08:00
刘祥超
e129b52f1c 更新版本号 2021-02-24 08:46:05 +08:00
刘祥超
5cefd900a4 终端统计中显示系统和浏览器的版本号 2021-02-07 09:23:55 +08:00
刘祥超
857dc70b4d 修复一个流量格式化错误 2021-02-07 09:16:58 +08:00
刘祥超
e3036025bc 修复反向代理配置组件addHeaders错误 2021-02-07 09:07:18 +08:00
刘祥超
76035dc3c2 创建或修改服务域名时检查域名是否重复 2021-02-06 21:52:00 +08:00
刘祥超
22c55d0b83 修改节点分组的节点数显示错误 2021-02-06 19:50:00 +08:00
刘祥超
9780937a1f 修改版本号 2021-02-06 19:49:36 +08:00
刘祥超
b4eb6e92e0 优化交互 2021-02-06 19:40:29 +08:00
刘祥超
245c4374b4 增加IP级别和WAF动作 2021-02-06 17:37:09 +08:00
刘祥超
ed302370e9 优化系统消息交互 2021-02-03 11:18:41 +08:00
刘祥超
b2feb452e0 可以批量删除同步任务 2021-02-02 20:52:46 +08:00
刘祥超
8b7661d82d 在WAF中增加检查IP状态功能 2021-02-02 19:30:07 +08:00
刘祥超
33c9cd0819 单个服务里也增加国家/地区、省份封禁 2021-02-02 16:23:23 +08:00
刘祥超
a0635a97d5 IP名单新增IPv6和所有IP两种类型 2021-02-02 15:25:11 +08:00
刘祥超
e22a9d061c 修复$HOME未定义问题 2021-02-01 09:25:57 +08:00
刘祥超
1b9d62c7bd 优化界面 2021-02-01 09:25:31 +08:00
刘祥超
2ca31ffcb4 界面显示时增加默认线路值 2021-01-31 16:09:55 +08:00
刘祥超
14636ed82f 可以批量远程安装和升级节点 2021-01-31 16:03:52 +08:00
刘祥超
cc5a34c20e 节点详情中显示更多信息(版本、CPU、负载等) 2021-01-31 11:05:04 +08:00
刘祥超
3ebb47d915 消息提示可以单个标为已读 2021-01-29 10:39:19 +08:00
刘祥超
3eea58c1fc 优化JS加载速度/修改蓝色背景 2021-01-29 10:04:00 +08:00
刘祥超
255bf58abf 优化JS代码加载速度 2021-01-28 17:04:36 +08:00
刘祥超
b5b324bca0 DNS更新任务增加域名更新 2021-01-28 15:39:10 +08:00
刘祥超
74e654446e 优化界面 2021-01-28 11:35:22 +08:00
刘祥超
be01536a09 支持自定义HTTP DNS 2021-01-28 11:29:50 +08:00
刘祥超
4c6bcc7c19 增加DNS同步任务状态显示 2021-01-27 22:59:46 +08:00
刘祥超
98dceb20db 优化界面 2021-01-27 11:53:42 +08:00
刘祥超
25833bc81a 增加管理界面截图 2021-01-27 11:40:32 +08:00
刘祥超
0d5fc7ad85 systemd Service安装失败时直接返回 2021-01-26 21:32:03 +08:00
刘祥超
b9af3d4757 修改README,加入组件源码地址等 2021-01-26 21:31:32 +08:00
刘祥超
f179b0a60e 优化界面 2021-01-26 20:35:29 +08:00
刘祥超
875162cfb6 可以配置是否在反向代理中添加X-Real-IP和X-Forwarded-* 2021-01-26 20:29:29 +08:00
刘祥超
2f21effdbf 实现WAF统计 2021-01-26 18:41:23 +08:00
刘祥超
f7336235a1 缩短右上菜单中同步状态检测时间 2021-01-26 10:29:53 +08:00
刘祥超
6ed0791991 访问日志可以设置只记录WAF相关日志 2021-01-26 10:29:29 +08:00
刘祥超
045a18fb22 删除不必要的文件 2021-01-25 18:40:38 +08:00
刘祥超
dbe598a934 对服务增加基础的数据统计 2021-01-25 16:40:49 +08:00
刘祥超
2812b30b01 优化界面 2021-01-24 14:41:43 +08:00
刘祥超
386d4957e7 修正一处文字提示错误(白名单->黑名单) 2021-01-21 20:46:08 +08:00
刘祥超
ea36e60899 优化Dashboard界面 2021-01-21 19:22:06 +08:00
刘祥超
bbf7e2898f 实现Dashboard 2021-01-21 18:55:53 +08:00
刘祥超
beab50de4c 变更版本号 2021-01-21 11:32:43 +08:00
刘祥超
a3ac7678d9 补充README文档 2021-01-21 11:32:32 +08:00
578 changed files with 69433 additions and 1480 deletions

View File

@@ -1 +1,26 @@
![架构](doc/architect-zh.jpg) # GoEdge目标
做一款人人用得起的CDN & WAF系统。
![架构](doc/screenshot.png)
## 特性
* `免费` - 开源、免费、自由、开放
* `简单` - 架构简单清晰,安装简单,使用简单,运维简单
* `高扩展性` - 可以自由扩展新的节点,支持亿级数据
## 文档
[点这里查看文档](http://edge.teaos.cn/docs)
## 架构
![架构](doc/architect-zh.jpg)
其中的组件源码地址如下:
* [边缘节点](https://github.com/TeaOSLab/EdgeNode)
* [API节点](https://github.com/TeaOSLab/EdgeAPI)
* [管理平台](https://github.com/TeaOSLab/EdgeAdmin)
## 联系我们
有什么问题和建议都可以加入QQ群 `659832182`
## 感谢
* 感谢[JetBrains公司](https://www.jetbrains.com/)提供免费的IDE开发Licence。

8
build/build-all.sh Executable file
View File

@@ -0,0 +1,8 @@
#!/usr/bin/env bash
./build.sh linux amd64
./build.sh linux 386
./build.sh linux arm64
./build.sh linux mips64
./build.sh linux mips64le
./build.sh darwin amd64

View File

@@ -1,4 +1,4 @@
api.yaml
server.yaml server.yaml
api_db.yaml api_db.yaml
api-123.yaml
*.pem *.pem

View File

@@ -1,6 +0,0 @@
rpc:
endpoints:
- http://192.168.2.40:8003
nodeId: H6sjDf779jimnVPnBFSgZxvr6Ca0wQ0z
secret: hMHjmEng0SIcT3yiA3HIoUjogwAC9cur

View File

@@ -22,6 +22,7 @@ func main() {
err := nodes.NewAdminNode().InstallSystemService() err := nodes.NewAdminNode().InstallSystemService()
if err != nil { if err != nil {
fmt.Println("[ERROR]install failed: " + err.Error()) fmt.Println("[ERROR]install failed: " + err.Error())
return
} }
fmt.Println("done") fmt.Println("done")
}) })

BIN
doc/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

View File

@@ -10,15 +10,16 @@ import (
type AdminModuleCode = string type AdminModuleCode = string
const ( const (
AdminModuleCodeServer AdminModuleCode = "server" // 网站 AdminModuleCodeDashboard AdminModuleCode = "dashboard" // 看板
AdminModuleCodeNode AdminModuleCode = "node" // 节点 AdminModuleCodeServer AdminModuleCode = "server" // 网站
AdminModuleCodeDNS AdminModuleCode = "dns" // DNS AdminModuleCodeNode AdminModuleCode = "node" // 节点
AdminModuleCodeAdmin AdminModuleCode = "admin" // 系统用户 AdminModuleCodeDNS AdminModuleCode = "dns" // DNS
AdminModuleCodeUser AdminModuleCode = "user" // 平台用户 AdminModuleCodeAdmin AdminModuleCode = "admin" // 系统用户
AdminModuleCodeFinance AdminModuleCode = "finance" // 财务 AdminModuleCodeUser AdminModuleCode = "user" // 平台用户
AdminModuleCodeLog AdminModuleCode = "log" // 日志 AdminModuleCodeFinance AdminModuleCode = "finance" // 财务
AdminModuleCodeSetting AdminModuleCode = "setting" // 设置 AdminModuleCodeLog AdminModuleCode = "log" // 日志
AdminModuleCodeCommon AdminModuleCode = "common" // 只要登录就可以访问的模块 AdminModuleCodeSetting AdminModuleCode = "setting" // 设置
AdminModuleCodeCommon AdminModuleCode = "common" // 只要登录就可以访问的模块
) )
var sharedAdminModuleMapping = map[int64]*AdminModuleList{} // adminId => AdminModuleList var sharedAdminModuleMapping = map[int64]*AdminModuleList{} // adminId => AdminModuleList
@@ -109,7 +110,7 @@ func FindFirstAdminModule(adminId int64) (module AdminModuleCode, ok bool) {
list, ok2 := sharedAdminModuleMapping[adminId] list, ok2 := sharedAdminModuleMapping[adminId]
if ok2 { if ok2 {
if list.IsSuper { if list.IsSuper {
return AdminModuleCodeServer, true return AdminModuleCodeDashboard, true
} else if len(list.Modules) > 0 { } else if len(list.Modules) > 0 {
return list.Modules[0].Code, true return list.Modules[0].Code, true
} }
@@ -132,6 +133,11 @@ func FindAdminFullname(adminId int64) string {
// 所有权限列表 // 所有权限列表
func AllModuleMaps() []maps.Map { func AllModuleMaps() []maps.Map {
return []maps.Map{ return []maps.Map{
{
"name": "看板",
"code": AdminModuleCodeDashboard,
"url": "/dashboard",
},
{ {
"name": "网站服务", "name": "网站服务",
"code": AdminModuleCodeServer, "code": AdminModuleCodeServer,

View File

@@ -52,6 +52,10 @@ func UpdateSecurityConfig(securityConfig *systemconfigs.SecurityConfig) error {
if err != nil { if err != nil {
return err return err
} }
err = securityConfig.Init()
if err != nil {
return err
}
sharedSecurityConfig = securityConfig sharedSecurityConfig = securityConfig
// 通知更新 // 通知更新
@@ -86,6 +90,10 @@ func loadSecurityConfig() (*systemconfigs.SecurityConfig, error) {
sharedSecurityConfig = defaultSecurityConfig() sharedSecurityConfig = defaultSecurityConfig()
return sharedSecurityConfig, nil return sharedSecurityConfig, nil
} }
err = config.Init()
if err != nil {
return nil, err
}
sharedSecurityConfig = config sharedSecurityConfig = config
return sharedSecurityConfig, nil return sharedSecurityConfig, nil
} }

View File

@@ -9,7 +9,7 @@ import (
"path/filepath" "path/filepath"
) )
// API配置 // APIConfig API配置
type APIConfig struct { type APIConfig struct {
RPC struct { RPC struct {
Endpoints []string `yaml:"endpoints"` Endpoints []string `yaml:"endpoints"`
@@ -18,19 +18,20 @@ type APIConfig struct {
Secret string `yaml:"secret"` Secret string `yaml:"secret"`
} }
// 加载API配置 // LoadAPIConfig 加载API配置
func LoadAPIConfig() (*APIConfig, error) { func LoadAPIConfig() (*APIConfig, error) {
// 候选文件 // 候选文件
localFile := Tea.ConfigFile("api.yaml") localFile := Tea.ConfigFile("api.yaml")
isFromLocal := false isFromLocal := false
paths := []string{localFile} paths := []string{localFile}
homeDir, err := os.UserHomeDir() homeDir, homeErr := os.UserHomeDir()
if err == nil { if homeErr == nil {
paths = append(paths, homeDir+"/."+teaconst.ProcessName+"/api.yaml") paths = append(paths, homeDir+"/."+teaconst.ProcessName+"/api.yaml")
} }
paths = append(paths, "/etc/"+teaconst.ProcessName+"/api.yaml") paths = append(paths, "/etc/"+teaconst.ProcessName+"/api.yaml")
var data []byte var data []byte
var err error
for _, path := range paths { for _, path := range paths {
data, err = ioutil.ReadFile(path) data, err = ioutil.ReadFile(path)
if err == nil { if err == nil {
@@ -58,7 +59,7 @@ func LoadAPIConfig() (*APIConfig, error) {
return config, nil return config, nil
} }
// 写入API配置 // WriteFile 写入API配置
func (this *APIConfig) WriteFile(path string) error { func (this *APIConfig) WriteFile(path string) error {
data, err := yaml.Marshal(this) data, err := yaml.Marshal(this)
if err != nil { if err != nil {
@@ -69,8 +70,8 @@ func (this *APIConfig) WriteFile(path string) error {
// 写入 ~/ 和 /etc/ 目录,因为是备份需要,所以不需要提示错误信息 // 写入 ~/ 和 /etc/ 目录,因为是备份需要,所以不需要提示错误信息
// 写入 ~/.edge-admin/ // 写入 ~/.edge-admin/
filename := filepath.Base(path) filename := filepath.Base(path)
homeDir, err := os.UserHomeDir() homeDir, homeErr := os.UserHomeDir()
if err == nil { if homeErr == nil {
dir := homeDir + "/." + teaconst.ProcessName dir := homeDir + "/." + teaconst.ProcessName
stat, err := os.Stat(dir) stat, err := os.Stat(dir)
if err == nil && stat.IsDir() { if err == nil && stat.IsDir() {

View File

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

6
internal/const/plus.go Normal file
View File

@@ -0,0 +1,6 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package teaconst
// IsPlus 是否为企业版
var IsPlus = false

View File

@@ -87,7 +87,7 @@ func (this *AdminNode) Run() {
Start() Start()
} }
// 实现守护进程 // Daemon 实现守护进程
func (this *AdminNode) Daemon() { func (this *AdminNode) Daemon() {
path := os.TempDir() + "/edge-admin.sock" path := os.TempDir() + "/edge-admin.sock"
isDebug := lists.ContainsString(os.Args, "debug") isDebug := lists.ContainsString(os.Args, "debug")
@@ -132,7 +132,7 @@ func (this *AdminNode) Daemon() {
} }
} }
// 安装系统服务 // InstallSystemService 安装系统服务
func (this *AdminNode) InstallSystemService() error { func (this *AdminNode) InstallSystemService() error {
shortName := teaconst.SystemdServiceName shortName := teaconst.SystemdServiceName
@@ -149,7 +149,7 @@ func (this *AdminNode) InstallSystemService() error {
return nil return nil
} }
// 添加子PID // AddSubPID 添加子PID
func (this *AdminNode) AddSubPID(pid int) { func (this *AdminNode) AddSubPID(pid int) {
this.subPIDs = append(this.subPIDs, pid) this.subPIDs = append(this.subPIDs, pid)
} }

View File

@@ -71,6 +71,10 @@ func (this *RPCClient) NodeClusterRPC() pb.NodeClusterServiceClient {
return pb.NewNodeClusterServiceClient(this.pickConn()) return pb.NewNodeClusterServiceClient(this.pickConn())
} }
func (this *RPCClient) NodeClusterFirewallActionRPC() pb.NodeClusterFirewallActionServiceClient {
return pb.NewNodeClusterFirewallActionServiceClient(this.pickConn())
}
func (this *RPCClient) NodeGroupRPC() pb.NodeGroupServiceClient { func (this *RPCClient) NodeGroupRPC() pb.NodeGroupServiceClient {
return pb.NewNodeGroupServiceClient(this.pickConn()) return pb.NewNodeGroupServiceClient(this.pickConn())
} }
@@ -91,6 +95,34 @@ func (this *RPCClient) ServerRPC() pb.ServerServiceClient {
return pb.NewServerServiceClient(this.pickConn()) return pb.NewServerServiceClient(this.pickConn())
} }
func (this *RPCClient) ServerClientSystemMonthlyStatRPC() pb.ServerClientSystemMonthlyStatServiceClient {
return pb.NewServerClientSystemMonthlyStatServiceClient(this.pickConn())
}
func (this *RPCClient) ServerClientBrowserMonthlyStatRPC() pb.ServerClientBrowserMonthlyStatServiceClient {
return pb.NewServerClientBrowserMonthlyStatServiceClient(this.pickConn())
}
func (this *RPCClient) ServerRegionCountryMonthlyStatRPC() pb.ServerRegionCountryMonthlyStatServiceClient {
return pb.NewServerRegionCountryMonthlyStatServiceClient(this.pickConn())
}
func (this *RPCClient) ServerRegionProvinceMonthlyStatRPC() pb.ServerRegionProvinceMonthlyStatServiceClient {
return pb.NewServerRegionProvinceMonthlyStatServiceClient(this.pickConn())
}
func (this *RPCClient) ServerRegionCityMonthlyStatRPC() pb.ServerRegionCityMonthlyStatServiceClient {
return pb.NewServerRegionCityMonthlyStatServiceClient(this.pickConn())
}
func (this *RPCClient) ServerRegionProviderMonthlyStatRPC() pb.ServerRegionProviderMonthlyStatServiceClient {
return pb.NewServerRegionProviderMonthlyStatServiceClient(this.pickConn())
}
func (this *RPCClient) ServerHTTPFirewallDailyStatRPC() pb.ServerHTTPFirewallDailyStatServiceClient {
return pb.NewServerHTTPFirewallDailyStatServiceClient(this.pickConn())
}
func (this *RPCClient) ServerGroupRPC() pb.ServerGroupServiceClient { func (this *RPCClient) ServerGroupRPC() pb.ServerGroupServiceClient {
return pb.NewServerGroupServiceClient(this.pickConn()) return pb.NewServerGroupServiceClient(this.pickConn())
} }
@@ -107,6 +139,10 @@ func (this *RPCClient) DBNodeRPC() pb.DBNodeServiceClient {
return pb.NewDBNodeServiceClient(this.pickConn()) return pb.NewDBNodeServiceClient(this.pickConn())
} }
func (this *RPCClient) MonitorNodeRPC() pb.MonitorNodeServiceClient {
return pb.NewMonitorNodeServiceClient(this.pickConn())
}
func (this *RPCClient) DBRPC() pb.DBServiceClient { func (this *RPCClient) DBRPC() pb.DBServiceClient {
return pb.NewDBServiceClient(this.pickConn()) return pb.NewDBServiceClient(this.pickConn())
} }
@@ -171,7 +207,7 @@ func (this *RPCClient) HTTPRewriteRuleRPC() pb.HTTPRewriteRuleServiceClient {
return pb.NewHTTPRewriteRuleServiceClient(this.pickConn()) return pb.NewHTTPRewriteRuleServiceClient(this.pickConn())
} }
// 访问日志 // HTTPAccessLogRPC 访问日志
func (this *RPCClient) HTTPAccessLogRPC() pb.HTTPAccessLogServiceClient { func (this *RPCClient) HTTPAccessLogRPC() pb.HTTPAccessLogServiceClient {
return pb.NewHTTPAccessLogServiceClient(this.pickConn()) return pb.NewHTTPAccessLogServiceClient(this.pickConn())
} }
@@ -192,6 +228,34 @@ func (this *RPCClient) MessageRPC() pb.MessageServiceClient {
return pb.NewMessageServiceClient(this.pickConn()) return pb.NewMessageServiceClient(this.pickConn())
} }
func (this *RPCClient) MessageRecipientGroupRPC() pb.MessageRecipientGroupServiceClient {
return pb.NewMessageRecipientGroupServiceClient(this.pickConn())
}
func (this *RPCClient) MessageRecipientRPC() pb.MessageRecipientServiceClient {
return pb.NewMessageRecipientServiceClient(this.pickConn())
}
func (this *RPCClient) MessageMediaRPC() pb.MessageMediaServiceClient {
return pb.NewMessageMediaServiceClient(this.pickConn())
}
func (this *RPCClient) MessageMediaInstanceRPC() pb.MessageMediaInstanceServiceClient {
return pb.NewMessageMediaInstanceServiceClient(this.pickConn())
}
func (this *RPCClient) MessageTaskRPC() pb.MessageTaskServiceClient {
return pb.NewMessageTaskServiceClient(this.pickConn())
}
func (this *RPCClient) MessageTaskLogRPC() pb.MessageTaskLogServiceClient {
return pb.NewMessageTaskLogServiceClient(this.pickConn())
}
func (this *RPCClient) MessageReceiverRPC() pb.MessageReceiverServiceClient {
return pb.NewMessageReceiverServiceClient(this.pickConn())
}
func (this *RPCClient) IPLibraryRPC() pb.IPLibraryServiceClient { func (this *RPCClient) IPLibraryRPC() pb.IPLibraryServiceClient {
return pb.NewIPLibraryServiceClient(this.pickConn()) return pb.NewIPLibraryServiceClient(this.pickConn())
} }
@@ -236,6 +300,10 @@ func (this *RPCClient) DNSRPC() pb.DNSServiceClient {
return pb.NewDNSServiceClient(this.pickConn()) return pb.NewDNSServiceClient(this.pickConn())
} }
func (this *RPCClient) DNSTaskRPC() pb.DNSTaskServiceClient {
return pb.NewDNSTaskServiceClient(this.pickConn())
}
func (this *RPCClient) ACMEUserRPC() pb.ACMEUserServiceClient { func (this *RPCClient) ACMEUserRPC() pb.ACMEUserServiceClient {
return pb.NewACMEUserServiceClient(this.pickConn()) return pb.NewACMEUserServiceClient(this.pickConn())
} }
@@ -260,7 +328,15 @@ func (this *RPCClient) NodeTaskRPC() pb.NodeTaskServiceClient {
return pb.NewNodeTaskServiceClient(this.pickConn()) return pb.NewNodeTaskServiceClient(this.pickConn())
} }
// 构造Admin上下文 func (this *RPCClient) AuthorityKeyRPC() pb.AuthorityKeyServiceClient {
return pb.NewAuthorityKeyServiceClient(this.pickConn())
}
func (this *RPCClient) AuthorityNodeRPC() pb.AuthorityNodeServiceClient {
return pb.NewAuthorityNodeServiceClient(this.pickConn())
}
// Context 构造Admin上下文
func (this *RPCClient) Context(adminId int64) context.Context { func (this *RPCClient) Context(adminId int64) context.Context {
ctx := context.Background() ctx := context.Background()
m := maps.Map{ m := maps.Map{
@@ -283,7 +359,7 @@ func (this *RPCClient) Context(adminId int64) context.Context {
return ctx return ctx
} }
// 构造API上下文 // APIContext 构造API上下文
func (this *RPCClient) APIContext(apiNodeId int64) context.Context { func (this *RPCClient) APIContext(apiNodeId int64) context.Context {
ctx := context.Background() ctx := context.Background()
m := maps.Map{ m := maps.Map{
@@ -306,6 +382,12 @@ func (this *RPCClient) APIContext(apiNodeId int64) context.Context {
return ctx return ctx
} }
// UpdateConfig 修改配置
func (this *RPCClient) UpdateConfig(config *configs.APIConfig) error {
this.apiConfig = config
return this.init()
}
// 初始化 // 初始化
func (this *RPCClient) init() error { func (this *RPCClient) init() error {
// 重新连接 // 重新连接
@@ -333,6 +415,8 @@ func (this *RPCClient) init() error {
if len(conns) == 0 { if len(conns) == 0 {
return errors.New("[RPC]no available endpoints") return errors.New("[RPC]no available endpoints")
} }
// 这里不需要加锁因为会和pickConn冲突
this.conns = conns this.conns = conns
return nil return nil
} }

View File

@@ -6,7 +6,7 @@ import (
var isConfigured bool var isConfigured bool
// 判断系统是否已经配置过 // IsConfigured 判断系统是否已经配置过
func IsConfigured() bool { func IsConfigured() bool {
if isConfigured { if isConfigured {
return true return true

View File

@@ -0,0 +1,68 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
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/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/logs"
"time"
)
func init() {
events.On(events.EventStart, func() {
task := NewAuthorityTask()
go task.Start()
})
}
type AuthorityTask struct {
}
func NewAuthorityTask() *AuthorityTask {
return &AuthorityTask{}
}
func (this *AuthorityTask) Start() {
ticker := time.NewTicker(10 * time.Minute)
if Tea.IsTesting() {
// 快速测试
ticker = time.NewTicker(1 * time.Minute)
}
// 初始化的时候先获取一次
timeout := time.NewTimer(5 * time.Second)
<-timeout.C
err := this.Loop()
if err != nil {
logs.Println("[TASK][AuthorityTask]" + err.Error())
}
// 定时获取
for range ticker.C {
err := this.Loop()
if err != nil {
logs.Println("[TASK][AuthorityTask]" + err.Error())
}
}
}
func (this *AuthorityTask) Loop() error {
rpcClient, err := rpc.SharedRPC()
if err != nil {
return err
}
resp, err := rpcClient.AuthorityKeyRPC().ReadAuthorityKey(rpcClient.Context(0), &pb.ReadAuthorityKeyRequest{})
if err != nil {
return err
}
if resp.AuthorityKey != nil {
teaconst.IsPlus = true
} else {
teaconst.IsPlus = false
}
return nil
}

View File

@@ -0,0 +1,92 @@
package tasks
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
"github.com/TeaOSLab/EdgeAdmin/internal/events"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/logs"
"sort"
"strings"
"time"
)
func init() {
events.On(events.EventStart, func() {
task := NewSyncAPINodesTask()
go task.Start()
})
}
// API节点同步任务
type SyncAPINodesTask struct {
}
func NewSyncAPINodesTask() *SyncAPINodesTask {
return &SyncAPINodesTask{}
}
func (this *SyncAPINodesTask) Start() {
ticker := time.NewTicker(5 * time.Minute)
if Tea.IsTesting() {
// 快速测试
ticker = time.NewTicker(1 * time.Minute)
}
for range ticker.C {
err := this.Loop()
if err != nil {
logs.Println("[TASK][SYNC_API_NODES]" + err.Error())
}
}
}
func (this *SyncAPINodesTask) Loop() error {
// 获取所有可用的节点
rpcClient, err := rpc.SharedRPC()
if err != nil {
return err
}
resp, err := rpcClient.APINodeRPC().FindAllEnabledAPINodes(rpcClient.Context(0), &pb.FindAllEnabledAPINodesRequest{})
if err != nil {
return err
}
newEndpoints := []string{}
for _, node := range resp.Nodes {
if !node.IsOn {
continue
}
newEndpoints = append(newEndpoints, node.AccessAddrs...)
}
// 和现有的对比
config, err := configs.LoadAPIConfig()
if err != nil {
return err
}
if this.isSame(newEndpoints, config.RPC.Endpoints) {
return nil
}
// 修改RPC对象配置
config.RPC.Endpoints = newEndpoints
err = rpcClient.UpdateConfig(config)
if err != nil {
return err
}
// 保存到文件
err = config.WriteFile(Tea.ConfigFile("api.yaml"))
if err != nil {
return err
}
return nil
}
func (this *SyncAPINodesTask) isSame(endpoints1 []string, endpoints2 []string) bool {
sort.Strings(endpoints1)
sort.Strings(endpoints2)
return strings.Join(endpoints1, "&") == strings.Join(endpoints2, "&")
}

View File

@@ -0,0 +1,15 @@
package tasks
import (
_ "github.com/iwind/TeaGo/bootstrap"
"testing"
)
func TestSyncAPINodesTask_Loop(t *testing.T) {
task := NewSyncAPINodesTask()
err := task.Loop()
if err != nil {
t.Fatal(err)
}
t.Log("ok")
}

View File

@@ -2,18 +2,42 @@ package utils
import ( import (
"encoding/binary" "encoding/binary"
"math/big"
"net" "net"
"regexp"
"strings"
) )
// 将IP转换为整型 // 将IP转换为整型
func IP2Long(ip string) uint32 { func IP2Long(ip string) uint64 {
s := net.ParseIP(ip) s := net.ParseIP(ip)
if s == nil { if len(s) != 16 {
return 0 return 0
} }
if len(s) == 16 { if strings.Contains(ip, ":") { // IPv6
return binary.BigEndian.Uint32(s[12:16]) bigInt := big.NewInt(0)
bigInt.SetBytes(s.To16())
return bigInt.Uint64()
} }
return binary.BigEndian.Uint32(s) return uint64(binary.BigEndian.Uint32(s.To4()))
}
// 判断是否为IPv4
func IsIPv4(ip string) bool {
if !regexp.MustCompile(`^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$`).MatchString(ip) {
return false
}
if IP2Long(ip) == 0 {
return false
}
return true
}
// 判断是否为IPv6
func IsIPv6(ip string) bool {
if !strings.Contains(ip, ":") {
return false
}
return len(net.ParseIP(ip)) == net.IPv6len
} }

View File

@@ -0,0 +1,85 @@
package utils
import (
"testing"
)
func TestIP2Long(t *testing.T) {
for _, ip := range []string{
"0.0.0.1",
"0.0.0.2",
"127.0.0.1",
"192.0.0.2",
"255.255.255.255",
"2001:db8:0:1::101",
"2001:db8:0:1::102",
"2406:8c00:0:3409:133:18:203:0",
"2406:8c00:0:3409:133:18:203:158",
"2406:8c00:0:3409:133:18:203:159",
"2406:8c00:0:3409:133:18:203:160",
} {
t.Log(ip, " -> ", IP2Long(ip))
}
}
func TestIsIPv4(t *testing.T) {
type testIP struct {
ip string
ok bool
}
for _, item := range []testIP{
{
ip: "1",
ok: false,
},
{
ip: "192.168.0.1",
ok: true,
},
{
ip: "1.1.0.1",
ok: true,
},
{
ip: "255.255.255.255",
ok: true,
},
{
ip: "192.168.0.1233",
ok: false,
},
} {
if IsIPv4(item.ip) != item.ok {
t.Fatal(item.ip, "should be", item.ok)
}
}
}
func TestIsIPv6(t *testing.T) {
type testIP struct {
ip string
ok bool
}
for _, item := range []testIP{
{
ip: "1",
ok: false,
},
{
ip: "2406:8c00:0:3409:133:18:203:158",
ok: true,
},
{
ip: "2406::8c00:0:3409:133:18:203:158",
ok: false,
},
{
ip: "2001:db8:0:1::101",
ok: true,
},
} {
if IsIPv6(item.ip) != item.ok {
t.Fatal(item.ip, "should be", item.ok)
}
}
}

View File

@@ -17,12 +17,26 @@ func FormatBytes(bytes int64) string {
if bytes < 1024 { if bytes < 1024 {
return FormatInt64(bytes) + "B" return FormatInt64(bytes) + "B"
} else if bytes < 1024*1024 { } else if bytes < 1024*1024 {
return fmt.Sprintf("%.2fK", float64(bytes)/1024) return fmt.Sprintf("%.2fKB", float64(bytes)/1024)
} else if bytes < 1024*1024*1024 { } else if bytes < 1024*1024*1024 {
return fmt.Sprintf("%.2fM", float64(bytes)/1024/1024) return fmt.Sprintf("%.2fMB", float64(bytes)/1024/1024)
} else if bytes < 1024*1024*1024*1024 { } else if bytes < 1024*1024*1024*1024 {
return fmt.Sprintf("%.2fG", float64(bytes)/1024/1024/1024) return fmt.Sprintf("%.2fGB", float64(bytes)/1024/1024/1024)
} else { } else {
return fmt.Sprintf("%.2fP", float64(bytes)/1024/1024/1024/1024) return fmt.Sprintf("%.2fPB", float64(bytes)/1024/1024/1024/1024)
}
}
func FormatBits(bits int64) string {
if bits < 1000 {
return FormatInt64(bits) + "B"
} else if bits < 1000*1000 {
return fmt.Sprintf("%.2fKB", float64(bits)/1000)
} else if bits < 1000*1000*1000 {
return fmt.Sprintf("%.2fMB", float64(bits)/1000/1000)
} else if bits < 1000*1000*1000*1000 {
return fmt.Sprintf("%.2fGB", float64(bits)/1000/1000/1000)
} else {
return fmt.Sprintf("%.2fPB", float64(bits)/1000/1000/1000/1000)
} }
} }

View File

@@ -5,6 +5,7 @@ import (
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const" teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/logs" "github.com/iwind/TeaGo/logs"
"net/http"
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
@@ -25,7 +26,21 @@ func FailPage(action actions.ActionWrapper, err error) {
if err != nil { if err != nil {
logs.Println("[" + reflect.TypeOf(action).String() + "]" + findStack(err.Error())) logs.Println("[" + reflect.TypeOf(action).String() + "]" + findStack(err.Error()))
} }
action.Object().WriteString(teaconst.ErrServer) action.Object().ResponseWriter.WriteHeader(http.StatusInternalServerError)
if len(action.Object().Request.Header.Get("X-Requested-With")) > 0 {
action.Object().WriteString(teaconst.ErrServer)
} else {
action.Object().WriteString(`<!DOCTYPE html>
<html>
<head></head>
<body>
<div style="background: #eee; border: 1px #ccc solid; padding: 10px; font-size: 12px; line-height: 1.8">
` + teaconst.ErrServer + `
<div>可以通过查看 <strong><em>$安装目录/logs/run.log</em></strong> 日志文件查看具体的错误提示。</div>
</div>
</body>
</html>`)
}
} }
// 判断动作的文件路径是否相当 // 判断动作的文件路径是否相当

View File

@@ -36,6 +36,7 @@ func (this *AdminAction) RunGet(params struct {
"username": admin.Username, "username": admin.Username,
"isOn": admin.IsOn, "isOn": admin.IsOn,
"isSuper": admin.IsSuper, "isSuper": admin.IsSuper,
"canLogin": admin.CanLogin,
} }
// 权限 // 权限

View File

@@ -31,6 +31,7 @@ func (this *CreatePopupAction) RunPost(params struct {
Pass2 string Pass2 string
ModuleCodes []string ModuleCodes []string
IsSuper bool IsSuper bool
CanLogin bool
// OTP // OTP
OtpOn bool OtpOn bool
@@ -88,6 +89,7 @@ func (this *CreatePopupAction) RunPost(params struct {
Fullname: params.Fullname, Fullname: params.Fullname,
ModulesJSON: modulesJSON, ModulesJSON: modulesJSON,
IsSuper: params.IsSuper, IsSuper: params.IsSuper,
CanLogin: params.CanLogin,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)

View File

@@ -42,6 +42,7 @@ func (this *IndexAction) RunGet(params struct{}) {
"fullname": admin.Fullname, "fullname": admin.Fullname,
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", admin.CreatedAt), "createdTime": timeutil.FormatTime("Y-m-d H:i:s", admin.CreatedAt),
"otpLoginIsOn": admin.OtpLogin != nil && admin.OtpLogin.IsOn, "otpLoginIsOn": admin.OtpLogin != nil && admin.OtpLogin.IsOn,
"canLogin": admin.CanLogin,
}) })
} }
this.Data["admins"] = adminMaps this.Data["admins"] = adminMaps

View File

@@ -18,6 +18,7 @@ func init() {
Post("/delete", new(DeleteAction)). Post("/delete", new(DeleteAction)).
Get("/admin", new(AdminAction)). Get("/admin", new(AdminAction)).
Get("/otpQrcode", new(OtpQrcodeAction)). Get("/otpQrcode", new(OtpQrcodeAction)).
Post("/options", new(OptionsAction)).
EndAll() EndAll()
}) })
} }

View File

@@ -0,0 +1,40 @@
package admins
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
// 系统用户选项
// 组件中需要用到的系统用户选项
type OptionsAction struct {
actionutils.ParentAction
}
func (this *OptionsAction) Init() {
this.Nav("", "", "")
}
func (this *OptionsAction) RunPost(params struct{}) {
// TODO 实现关键词搜索
adminsResp, err := this.RPC().AdminRPC().ListEnabledAdmins(this.AdminContext(), &pb.ListEnabledAdminsRequest{
Offset: 0,
Size: 1000,
})
if err != nil {
this.ErrorPage(err)
return
}
adminMaps := []maps.Map{}
for _, admin := range adminsResp.Admins {
adminMaps = append(adminMaps, maps.Map{
"id": admin.Id,
"name": admin.Fullname,
"username": admin.Username,
})
}
this.Data["admins"] = adminMaps
this.Success()
}

View File

@@ -0,0 +1,58 @@
package recipients
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
)
type CreatePopupAction struct {
actionutils.ParentAction
}
func (this *CreatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreatePopupAction) RunGet(params struct{}) {
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
AdminId int64
InstanceId int64
User string
TelegramToken string
GroupIds string
Description string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.
Field("adminId", params.AdminId).
Gt(0, "请选择系统用户").
Field("instanceId", params.InstanceId).
Gt(0, "请选择媒介")
groupIds := utils.SplitNumbers(params.GroupIds)
resp, err := this.RPC().MessageRecipientRPC().CreateMessageRecipient(this.AdminContext(), &pb.CreateMessageRecipientRequest{
AdminId: params.AdminId,
MessageMediaInstanceId: params.InstanceId,
User: params.User,
MessageRecipientGroupIds: groupIds,
Description: params.Description,
})
if err != nil {
this.ErrorPage(err)
return
}
defer this.CreateLogInfo("创建媒介接收人 %d", resp.MessageRecipientId)
this.Success()
}

View File

@@ -0,0 +1,24 @@
package recipients
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 {
RecipientId int64
}) {
defer this.CreateLogInfo("删除媒介接收人 %d", params.RecipientId)
_, err := this.RPC().MessageRecipientRPC().DeleteMessageRecipient(this.AdminContext(), &pb.DeleteMessageRecipientRequest{MessageRecipientId: params.RecipientId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,38 @@
package groups
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{}) {
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
Name string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.
Field("name", params.Name).
Require("请输入分组名称")
_, err := this.RPC().MessageRecipientGroupRPC().CreateMessageRecipientGroup(this.AdminContext(), &pb.CreateMessageRecipientGroupRequest{Name: params.Name})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,22 @@
package groups
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 {
GroupId int64
}) {
_, err := this.RPC().MessageRecipientGroupRPC().DeleteMessageRecipientGroup(this.AdminContext(), &pb.DeleteMessageRecipientGroupRequest{MessageRecipientGroupId: params.GroupId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,34 @@
package groups
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "group")
}
func (this *IndexAction) RunGet(params struct{}) {
groupsResp, err := this.RPC().MessageRecipientGroupRPC().FindAllEnabledMessageRecipientGroups(this.AdminContext(), &pb.FindAllEnabledMessageRecipientGroupsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
groupMaps := []maps.Map{}
for _, group := range groupsResp.MessageRecipientGroups {
groupMaps = append(groupMaps, maps.Map{
"id": group.Id,
"name": group.Name,
"isOn": group.IsOn,
})
}
this.Data["groups"] = groupMaps
this.Show()
}

View File

@@ -0,0 +1,23 @@
package groups
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.AdminModuleCodeAdmin)).
Data("teaMenu", "admins").
Data("teaSubMenu", "recipients").
Prefix("/admins/recipients/groups").
Get("", new(IndexAction)).
GetPost("/createPopup", new(CreatePopupAction)).
GetPost("/updatePopup", new(UpdatePopupAction)).
Post("/delete", new(DeleteAction)).
Get("/selectPopup", new(SelectPopupAction)).
EndAll()
})
}

View File

@@ -0,0 +1,39 @@
package groups
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
)
type SelectPopupAction struct {
actionutils.ParentAction
}
func (this *SelectPopupAction) RunGet(params struct {
GroupIds string
}) {
selectedGroupIds := utils.SplitNumbers(params.GroupIds)
groupsResp, err := this.RPC().MessageRecipientGroupRPC().FindAllEnabledMessageRecipientGroups(this.AdminContext(), &pb.FindAllEnabledMessageRecipientGroupsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
groupMaps := []maps.Map{}
for _, group := range groupsResp.MessageRecipientGroups {
if lists.ContainsInt64(selectedGroupIds, group.Id) {
continue
}
groupMaps = append(groupMaps, maps.Map{
"id": group.Id,
"name": group.Name,
"isOn": group.IsOn,
})
}
this.Data["groups"] = groupMaps
this.Show()
}

View File

@@ -0,0 +1,64 @@
package groups
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 UpdatePopupAction struct {
actionutils.ParentAction
}
func (this *UpdatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdatePopupAction) RunGet(params struct {
GroupId int64
}) {
groupResp, err := this.RPC().MessageRecipientGroupRPC().FindEnabledMessageRecipientGroup(this.AdminContext(), &pb.FindEnabledMessageRecipientGroupRequest{MessageRecipientGroupId: params.GroupId})
if err != nil {
this.ErrorPage(err)
return
}
group := groupResp.MessageRecipientGroup
if group == nil {
this.NotFound("messageRecipientGroup", params.GroupId)
return
}
this.Data["group"] = maps.Map{
"id": group.Id,
"name": group.Name,
"isOn": group.IsOn,
}
this.Show()
}
func (this *UpdatePopupAction) RunPost(params struct {
GroupId int64
Name string
IsOn bool
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.
Field("name", params.Name).
Require("请输入分组名称")
_, err := this.RPC().MessageRecipientGroupRPC().UpdateMessageRecipientGroup(this.AdminContext(), &pb.UpdateMessageRecipientGroupRequest{
MessageRecipientGroupId: params.GroupId,
Name: params.Name,
IsOn: params.IsOn,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,70 @@
package recipients
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "recipient")
}
func (this *IndexAction) RunGet(params struct {
}) {
// TODO 增加系统用户、媒介类型等条件搜索
countResp, err := this.RPC().MessageRecipientRPC().CountAllEnabledMessageRecipients(this.AdminContext(), &pb.CountAllEnabledMessageRecipientsRequest{
AdminId: 0,
MediaType: "",
MessageRecipientGroupId: 0,
Keyword: "",
})
if err != nil {
this.ErrorPage(err)
return
}
count := countResp.Count
page := this.NewPage(count)
this.Data["page"] = page.AsHTML()
recipientsResp, err := this.RPC().MessageRecipientRPC().ListEnabledMessageRecipients(this.AdminContext(), &pb.ListEnabledMessageRecipientsRequest{
AdminId: 0,
MediaType: "",
MessageRecipientGroupId: 0,
Keyword: "",
Offset: page.Offset,
Size: page.Size,
})
recipientMaps := []maps.Map{}
for _, recipient := range recipientsResp.MessageRecipients {
if recipient.Admin == nil {
continue
}
if recipient.MessageMediaInstance == nil {
continue
}
recipientMaps = append(recipientMaps, maps.Map{
"id": recipient.Id,
"admin": maps.Map{
"id": recipient.Admin.Id,
"fullname": recipient.Admin.Fullname,
"username": recipient.Admin.Username,
},
"groups": recipient.MessageRecipientGroups,
"isOn": recipient.IsOn,
"instance": maps.Map{
"name": recipient.MessageMediaInstance.Name,
},
"user": recipient.User,
"description": recipient.Description,
})
}
this.Data["recipients"] = recipientMaps
this.Show()
}

View File

@@ -0,0 +1,25 @@
package recipients
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.AdminModuleCodeAdmin)).
Data("teaMenu", "admins").
Data("teaSubMenu", "recipients").
Prefix("/admins/recipients").
Get("", new(IndexAction)).
GetPost("/createPopup", new(CreatePopupAction)).
GetPost("/update", new(UpdateAction)).
Post("/delete", new(DeleteAction)).
Post("/mediaOptions", new(MediaOptionsAction)).
Get("/recipient", new(RecipientAction)).
GetPost("/test", new(TestAction)).
EndAll()
})
}

View File

@@ -0,0 +1,248 @@
package instances
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type CreatePopupAction struct {
actionutils.ParentAction
}
func (this *CreatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreatePopupAction) RunGet(params struct{}) {
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
Name string
MediaType string
EmailSmtp string
EmailUsername string
EmailPassword string
EmailFrom string
WebHookURL string
WebHookMethod string
WebHookHeaderNames []string
WebHookHeaderValues []string
WebHookContentType string
WebHookParamNames []string
WebHookParamValues []string
WebHookBody string
ScriptType string
ScriptPath string
ScriptLang string
ScriptCode string
ScriptCwd string
ScriptEnvNames []string
ScriptEnvValues []string
DingTalkWebHookURL string
QyWeixinCorporateId string
QyWeixinAgentId string
QyWeixinAppSecret string
QyWeixinTextFormat string
QyWeixinRobotWebHookURL string
QyWeixinRobotTextFormat string
AliyunSmsSign string
AliyunSmsTemplateCode string
AliyunSmsTemplateVarNames []string
AliyunSmsTemplateVarValues []string
AliyunSmsAccessKeyId string
AliyunSmsAccessKeySecret string
TelegramToken string
Description string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.
Field("name", params.Name).
Require("请输入媒介名称").
Field("mediaType", params.MediaType).
Require("请选择媒介类型")
options := maps.Map{}
switch params.MediaType {
case "email":
params.Must.
Field("emailSmtp", params.EmailSmtp).
Require("请输入SMTP地址").
Field("emailUsername", params.EmailUsername).
Require("请输入邮箱账号").
Field("emailPassword", params.EmailPassword).
Require("请输入密码或授权码")
options["smtp"] = params.EmailSmtp
options["username"] = params.EmailUsername
options["password"] = params.EmailPassword
options["from"] = params.EmailFrom
case "webHook":
params.Must.
Field("webHookURL", params.WebHookURL).
Require("请输入URL地址").
Match("(?i)^(http|https)://", "URL地址必须以http或https开头").
Field("webHookMethod", params.WebHookMethod).
Require("请选择请求方法")
options["url"] = params.WebHookURL
options["method"] = params.WebHookMethod
options["contentType"] = params.WebHookContentType
headers := []maps.Map{}
if len(params.WebHookHeaderNames) > 0 {
for index, name := range params.WebHookHeaderNames {
if index < len(params.WebHookHeaderValues) {
headers = append(headers, maps.Map{
"name": name,
"value": params.WebHookHeaderValues[index],
})
}
}
}
options["headers"] = headers
if params.WebHookContentType == "params" {
webHookParams := []maps.Map{}
for index, name := range params.WebHookParamNames {
if index < len(params.WebHookParamValues) {
webHookParams = append(webHookParams, maps.Map{
"name": name,
"value": params.WebHookParamValues[index],
})
}
}
options["params"] = webHookParams
} else if params.WebHookContentType == "body" {
options["body"] = params.WebHookBody
}
case "script":
if params.ScriptType == "path" {
params.Must.
Field("scriptPath", params.ScriptPath).
Require("请输入脚本路径")
} else if params.ScriptType == "code" {
params.Must.
Field("scriptCode", params.ScriptCode).
Require("请输入脚本代码")
} else {
params.Must.
Field("scriptPath", params.ScriptPath).
Require("请输入脚本路径")
}
options["scriptType"] = params.ScriptType
options["path"] = params.ScriptPath
options["scriptLang"] = params.ScriptLang
options["script"] = params.ScriptCode
options["cwd"] = params.ScriptCwd
env := []maps.Map{}
for index, envName := range params.ScriptEnvNames {
if index < len(params.ScriptEnvValues) {
env = append(env, maps.Map{
"name": envName,
"value": params.ScriptEnvValues[index],
})
}
}
options["env"] = env
case "dingTalk":
params.Must.
Field("dingTalkWebHookURL", params.DingTalkWebHookURL).
Require("请输入Hook地址").
Match("^https:", "Hook地址必须以https://开头")
options["webHookURL"] = params.DingTalkWebHookURL
case "qyWeixin":
params.Must.
Field("qyWeixinCorporateId", params.QyWeixinCorporateId).
Require("请输入企业ID").
Field("qyWeixinAgentId", params.QyWeixinAgentId).
Require("请输入应用AgentId").
Field("qyWeixinSecret", params.QyWeixinAppSecret).
Require("请输入应用Secret")
options["corporateId"] = params.QyWeixinCorporateId
options["agentId"] = params.QyWeixinAgentId
options["appSecret"] = params.QyWeixinAppSecret
options["textFormat"] = params.QyWeixinTextFormat
case "qyWeixinRobot":
params.Must.
Field("qyWeixinRobotWebHookURL", params.QyWeixinRobotWebHookURL).
Require("请输入WebHook地址").
Match("^https:", "WebHook地址必须以https://开头")
options["webHookURL"] = params.QyWeixinRobotWebHookURL
options["textFormat"] = params.QyWeixinRobotTextFormat
case "aliyunSms":
params.Must.
Field("aliyunSmsSign", params.AliyunSmsSign).
Require("请输入签名名称").
Field("aliyunSmsTemplateCode", params.AliyunSmsTemplateCode).
Require("请输入模板CODE").
Field("aliyunSmsAccessKeyId", params.AliyunSmsAccessKeyId).
Require("请输入AccessKey ID").
Field("aliyunSmsAccessKeySecret", params.AliyunSmsAccessKeySecret).
Require("请输入AccessKey Secret")
options["sign"] = params.AliyunSmsSign
options["templateCode"] = params.AliyunSmsTemplateCode
options["accessKeyId"] = params.AliyunSmsAccessKeyId
options["accessKeySecret"] = params.AliyunSmsAccessKeySecret
variables := []maps.Map{}
for index, name := range params.AliyunSmsTemplateVarNames {
if index < len(params.AliyunSmsTemplateVarValues) {
variables = append(variables, maps.Map{
"name": name,
"value": params.AliyunSmsTemplateVarValues[index],
})
}
}
options["variables"] = variables
case "telegram":
params.Must.
Field("telegramToken", params.TelegramToken).
Require("请输入机器人Token")
options["token"] = params.TelegramToken
default:
this.Fail("错误的媒介类型")
}
optionsJSON, err := json.Marshal(options)
if err != nil {
this.ErrorPage(err)
return
}
resp, err := this.RPC().MessageMediaInstanceRPC().CreateMessageMediaInstance(this.AdminContext(), &pb.CreateMessageMediaInstanceRequest{
Name: params.Name,
MediaType: params.MediaType,
ParamsJSON: optionsJSON,
Description: params.Description,
})
if err != nil {
this.ErrorPage(err)
return
}
defer this.CreateLogInfo("创建消息媒介 %d", resp.MessageMediaInstanceId)
this.Success()
}

View File

@@ -0,0 +1,24 @@
package instances
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 {
InstanceId int64
}) {
defer this.CreateLogInfo("删除消息媒介 %d", params.InstanceId)
_, err := this.RPC().MessageMediaInstanceRPC().DeleteMessageMediaInstance(this.AdminContext(), &pb.DeleteMessageMediaInstanceRequest{MessageMediaInstanceId: params.InstanceId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,57 @@
package instances
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "instance")
}
func (this *IndexAction) RunGet(params struct {
}) {
// TODO 增加系统用户、媒介类型等条件搜索
countResp, err := this.RPC().MessageMediaInstanceRPC().CountAllEnabledMessageMediaInstances(this.AdminContext(), &pb.CountAllEnabledMessageMediaInstancesRequest{
MediaType: "",
Keyword: "",
})
if err != nil {
this.ErrorPage(err)
return
}
count := countResp.Count
page := this.NewPage(count)
this.Data["page"] = page.AsHTML()
instancesResp, err := this.RPC().MessageMediaInstanceRPC().ListEnabledMessageMediaInstances(this.AdminContext(), &pb.ListEnabledMessageMediaInstancesRequest{
MediaType: "",
Keyword: "",
Offset: page.Offset,
Size: page.Size,
})
instanceMaps := []maps.Map{}
for _, instance := range instancesResp.MessageMediaInstances {
if instance.MessageMedia == nil {
continue
}
instanceMaps = append(instanceMaps, maps.Map{
"id": instance.Id,
"name": instance.Name,
"isOn": instance.IsOn,
"media": maps.Map{
"name": instance.MessageMedia.Name,
},
"description": instance.Description,
})
}
this.Data["instances"] = instanceMaps
this.Show()
}

View File

@@ -0,0 +1,25 @@
package instances
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.AdminModuleCodeAdmin)).
Data("teaMenu", "admins").
Data("teaSubMenu", "instances").
Prefix("/admins/recipients/instances").
Get("", new(IndexAction)).
GetPost("/createPopup", new(CreatePopupAction)).
GetPost("/update", new(UpdateAction)).
Post("/delete", new(DeleteAction)).
Get("/instance", new(InstanceAction)).
GetPost("/test", new(TestAction)).
Post("/options", new(OptionsAction)).
EndAll()
})
}

View File

@@ -0,0 +1,54 @@
package instances
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type InstanceAction struct {
actionutils.ParentAction
}
func (this *InstanceAction) Init() {
this.Nav("", "", "instance")
}
func (this *InstanceAction) RunGet(params struct {
InstanceId int64
}) {
instanceResp, err := this.RPC().MessageMediaInstanceRPC().FindEnabledMessageMediaInstance(this.AdminContext(), &pb.FindEnabledMessageMediaInstanceRequest{MessageMediaInstanceId: params.InstanceId})
if err != nil {
this.ErrorPage(err)
return
}
instance := instanceResp.MessageMediaInstance
if instance == nil || instance.MessageMedia == nil {
this.NotFound("messageMediaInstance", params.InstanceId)
return
}
mediaParams := maps.Map{}
if len(instance.ParamsJSON) > 0 {
err = json.Unmarshal(instance.ParamsJSON, &mediaParams)
if err != nil {
this.ErrorPage(err)
return
}
}
this.Data["instance"] = maps.Map{
"id": instance.Id,
"name": instance.Name,
"isOn": instance.IsOn,
"media": maps.Map{
"type": instance.MessageMedia.Type,
"name": instance.MessageMedia.Name,
},
"description": instance.Description,
"params": mediaParams,
}
this.Show()
}

View File

@@ -0,0 +1,39 @@
package instances
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
// 媒介类型选项
type OptionsAction struct {
actionutils.ParentAction
}
func (this *OptionsAction) RunPost(params struct{}) {
resp, err := this.RPC().MessageMediaInstanceRPC().ListEnabledMessageMediaInstances(this.AdminContext(), &pb.ListEnabledMessageMediaInstancesRequest{
Offset: 0,
Size: 1000,
})
if err != nil {
this.ErrorPage(err)
return
}
instanceMaps := []maps.Map{}
for _, instance := range resp.MessageMediaInstances {
instanceMaps = append(instanceMaps, maps.Map{
"id": instance.Id,
"name": instance.Name,
"description": instance.Description,
"media": maps.Map{
"type": instance.MessageMedia.Type,
"name": instance.MessageMedia.Name,
"userDescription": instance.MessageMedia.UserDescription,
},
})
}
this.Data["instances"] = instanceMaps
this.Success()
}

View File

@@ -0,0 +1,87 @@
package instances
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type TestAction struct {
actionutils.ParentAction
}
func (this *TestAction) Init() {
this.Nav("", "", "test")
}
func (this *TestAction) RunGet(params struct {
InstanceId int64
}) {
instanceResp, err := this.RPC().MessageMediaInstanceRPC().FindEnabledMessageMediaInstance(this.AdminContext(), &pb.FindEnabledMessageMediaInstanceRequest{MessageMediaInstanceId: params.InstanceId})
if err != nil {
this.ErrorPage(err)
return
}
instance := instanceResp.MessageMediaInstance
if instance == nil || instance.MessageMedia == nil {
this.NotFound("messageMediaInstance", params.InstanceId)
return
}
mediaParams := maps.Map{}
if len(instance.ParamsJSON) > 0 {
err = json.Unmarshal(instance.ParamsJSON, &mediaParams)
if err != nil {
this.ErrorPage(err)
return
}
}
this.Data["instance"] = maps.Map{
"id": instance.Id,
"isOn": instance.IsOn,
"media": maps.Map{
"type": instance.MessageMedia.Type,
"name": instance.MessageMedia.Name,
"userDescription": instance.MessageMedia.UserDescription,
},
"description": instance.Description,
"params": mediaParams,
}
this.Show()
}
func (this *TestAction) RunPost(params struct {
InstanceId int64
Subject string
Body string
User string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.
Field("instanceId", params.InstanceId).
Gt(0, "请选择正确的媒介")
resp, err := this.RPC().MessageTaskRPC().CreateMessageTask(this.AdminContext(), &pb.CreateMessageTaskRequest{
RecipientId: 0,
InstanceId: params.InstanceId,
User: params.User,
Subject: params.Subject,
Body: params.Body,
IsPrimary: true,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["taskId"] = resp.MessageTaskId
defer this.CreateLogInfo("创建媒介测试任务 %d", resp.MessageTaskId)
this.Success()
}

View File

@@ -0,0 +1,287 @@
package instances
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type UpdateAction struct {
actionutils.ParentAction
}
func (this *UpdateAction) Init() {
this.Nav("", "", "update")
}
func (this *UpdateAction) RunGet(params struct {
InstanceId int64
}) {
instanceResp, err := this.RPC().MessageMediaInstanceRPC().FindEnabledMessageMediaInstance(this.AdminContext(), &pb.FindEnabledMessageMediaInstanceRequest{MessageMediaInstanceId: params.InstanceId})
if err != nil {
this.ErrorPage(err)
return
}
instance := instanceResp.MessageMediaInstance
if instance == nil || instance.MessageMedia == nil {
this.NotFound("messageMediaInstance", params.InstanceId)
return
}
mediaParams := maps.Map{}
if len(instance.ParamsJSON) > 0 {
err = json.Unmarshal(instance.ParamsJSON, &mediaParams)
if err != nil {
this.ErrorPage(err)
return
}
}
this.Data["instance"] = maps.Map{
"id": instance.Id,
"name": instance.Name,
"isOn": instance.IsOn,
"media": maps.Map{
"type": instance.MessageMedia.Type,
"name": instance.MessageMedia.Name,
},
"description": instance.Description,
"params": mediaParams,
}
this.Show()
}
func (this *UpdateAction) RunPost(params struct {
InstanceId int64
Name string
MediaType string
EmailSmtp string
EmailUsername string
EmailPassword string
EmailFrom string
WebHookURL string
WebHookMethod string
WebHookHeaderNames []string
WebHookHeaderValues []string
WebHookContentType string
WebHookParamNames []string
WebHookParamValues []string
WebHookBody string
ScriptType string
ScriptPath string
ScriptLang string
ScriptCode string
ScriptCwd string
ScriptEnvNames []string
ScriptEnvValues []string
DingTalkWebHookURL string
QyWeixinCorporateId string
QyWeixinAgentId string
QyWeixinAppSecret string
QyWeixinTextFormat string
QyWeixinRobotWebHookURL string
QyWeixinRobotTextFormat string
AliyunSmsSign string
AliyunSmsTemplateCode string
AliyunSmsTemplateVarNames []string
AliyunSmsTemplateVarValues []string
AliyunSmsAccessKeyId string
AliyunSmsAccessKeySecret string
TelegramToken string
GroupIds string
Description string
IsOn bool
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo("修改消息媒介 %d", params.InstanceId)
params.Must.
Field("name", params.Name).
Require("请输入媒介名称").
Field("mediaType", params.MediaType).
Require("请选择媒介类型")
options := maps.Map{}
switch params.MediaType {
case "email":
params.Must.
Field("emailSmtp", params.EmailSmtp).
Require("请输入SMTP地址").
Field("emailUsername", params.EmailUsername).
Require("请输入邮箱账号").
Field("emailPassword", params.EmailPassword).
Require("请输入密码或授权码")
options["smtp"] = params.EmailSmtp
options["username"] = params.EmailUsername
options["password"] = params.EmailPassword
options["from"] = params.EmailFrom
case "webHook":
params.Must.
Field("webHookURL", params.WebHookURL).
Require("请输入URL地址").
Match("(?i)^(http|https)://", "URL地址必须以http或https开头").
Field("webHookMethod", params.WebHookMethod).
Require("请选择请求方法")
options["url"] = params.WebHookURL
options["method"] = params.WebHookMethod
options["contentType"] = params.WebHookContentType
headers := []maps.Map{}
if len(params.WebHookHeaderNames) > 0 {
for index, name := range params.WebHookHeaderNames {
if index < len(params.WebHookHeaderValues) {
headers = append(headers, maps.Map{
"name": name,
"value": params.WebHookHeaderValues[index],
})
}
}
}
options["headers"] = headers
if params.WebHookContentType == "params" {
webHookParams := []maps.Map{}
for index, name := range params.WebHookParamNames {
if index < len(params.WebHookParamValues) {
webHookParams = append(webHookParams, maps.Map{
"name": name,
"value": params.WebHookParamValues[index],
})
}
}
options["params"] = webHookParams
} else if params.WebHookContentType == "body" {
options["body"] = params.WebHookBody
}
case "script":
if params.ScriptType == "path" {
params.Must.
Field("scriptPath", params.ScriptPath).
Require("请输入脚本路径")
} else if params.ScriptType == "code" {
params.Must.
Field("scriptCode", params.ScriptCode).
Require("请输入脚本代码")
} else {
params.Must.
Field("scriptPath", params.ScriptPath).
Require("请输入脚本路径")
}
options["scriptType"] = params.ScriptType
options["path"] = params.ScriptPath
options["scriptLang"] = params.ScriptLang
options["script"] = params.ScriptCode
options["cwd"] = params.ScriptCwd
env := []maps.Map{}
for index, envName := range params.ScriptEnvNames {
if index < len(params.ScriptEnvValues) {
env = append(env, maps.Map{
"name": envName,
"value": params.ScriptEnvValues[index],
})
}
}
options["env"] = env
case "dingTalk":
params.Must.
Field("dingTalkWebHookURL", params.DingTalkWebHookURL).
Require("请输入Hook地址").
Match("^https:", "Hook地址必须以https://开头")
options["webHookURL"] = params.DingTalkWebHookURL
case "qyWeixin":
params.Must.
Field("qyWeixinCorporateId", params.QyWeixinCorporateId).
Require("请输入企业ID").
Field("qyWeixinAgentId", params.QyWeixinAgentId).
Require("请输入应用AgentId").
Field("qyWeixinSecret", params.QyWeixinAppSecret).
Require("请输入应用Secret")
options["corporateId"] = params.QyWeixinCorporateId
options["agentId"] = params.QyWeixinAgentId
options["appSecret"] = params.QyWeixinAppSecret
options["textFormat"] = params.QyWeixinTextFormat
case "qyWeixinRobot":
params.Must.
Field("qyWeixinRobotWebHookURL", params.QyWeixinRobotWebHookURL).
Require("请输入WebHook地址").
Match("^https:", "WebHook地址必须以https://开头")
options["webHookURL"] = params.QyWeixinRobotWebHookURL
options["textFormat"] = params.QyWeixinRobotTextFormat
case "aliyunSms":
params.Must.
Field("aliyunSmsSign", params.AliyunSmsSign).
Require("请输入签名名称").
Field("aliyunSmsTemplateCode", params.AliyunSmsTemplateCode).
Require("请输入模板CODE").
Field("aliyunSmsAccessKeyId", params.AliyunSmsAccessKeyId).
Require("请输入AccessKey ID").
Field("aliyunSmsAccessKeySecret", params.AliyunSmsAccessKeySecret).
Require("请输入AccessKey Secret")
options["sign"] = params.AliyunSmsSign
options["templateCode"] = params.AliyunSmsTemplateCode
options["accessKeyId"] = params.AliyunSmsAccessKeyId
options["accessKeySecret"] = params.AliyunSmsAccessKeySecret
variables := []maps.Map{}
for index, name := range params.AliyunSmsTemplateVarNames {
if index < len(params.AliyunSmsTemplateVarValues) {
variables = append(variables, maps.Map{
"name": name,
"value": params.AliyunSmsTemplateVarValues[index],
})
}
}
options["variables"] = variables
case "telegram":
params.Must.
Field("telegramToken", params.TelegramToken).
Require("请输入机器人Token")
options["token"] = params.TelegramToken
default:
this.Fail("错误的媒介类型")
}
optionsJSON, err := json.Marshal(options)
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().MessageMediaInstanceRPC().UpdateMessageMediaInstance(this.AdminContext(), &pb.UpdateMessageMediaInstanceRequest{
MessageMediaInstanceId: params.InstanceId,
Name: params.Name,
MediaType: params.MediaType,
ParamsJSON: optionsJSON,
Description: params.Description,
IsOn: params.IsOn,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,61 @@
package logs
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "log")
}
func (this *IndexAction) RunGet(params struct{}) {
countResp, err := this.RPC().MessageTaskLogRPC().CountMessageTaskLogs(this.AdminContext(), &pb.CountMessageTaskLogsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
page := this.NewPage(countResp.Count)
this.Data["page"] = page.AsHTML()
logsResp, err := this.RPC().MessageTaskLogRPC().ListMessageTaskLogs(this.AdminContext(), &pb.ListMessageTaskLogsRequest{
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
logMaps := []maps.Map{}
for _, log := range logsResp.MessageTaskLogs {
if log.MessageTask.MessageRecipient != nil {
log.MessageTask.User = log.MessageTask.MessageRecipient.User
}
logMaps = append(logMaps, maps.Map{
"task": maps.Map{
"id": log.MessageTask.Id,
"user": log.MessageTask.User,
"subject": log.MessageTask.Subject,
"body": log.MessageTask.Body,
"instance": maps.Map{
"id": log.MessageTask.MessageMediaInstance.Id,
"name": log.MessageTask.MessageMediaInstance.Name,
},
},
"isOk": log.IsOk,
"error": log.Error,
"response": log.Response,
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", log.CreatedAt),
})
}
this.Data["logs"] = logMaps
this.Show()
}

View File

@@ -0,0 +1,19 @@
package logs
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeAdmin)).
Data("teaMenu", "admins").
Data("teaSubMenu", "recipients").
Prefix("/admins/recipients/logs").
Get("", new(IndexAction)).
EndAll()
})
}

View File

@@ -0,0 +1,32 @@
package recipients
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
// 媒介类型选项
type MediaOptionsAction struct {
actionutils.ParentAction
}
func (this *MediaOptionsAction) RunPost(params struct{}) {
resp, err := this.RPC().MessageMediaRPC().FindAllMessageMedias(this.AdminContext(), &pb.FindAllMessageMediasRequest{})
if err != nil {
this.ErrorPage(err)
return
}
mediaMaps := []maps.Map{}
for _, media := range resp.MessageMedias {
mediaMaps = append(mediaMaps, maps.Map{
"id": media.Id,
"type": media.Type,
"name": media.Name,
"description": media.Description,
})
}
this.Data["medias"] = mediaMaps
this.Success()
}

View File

@@ -0,0 +1,50 @@
package recipients
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type RecipientAction struct {
actionutils.ParentAction
}
func (this *RecipientAction) Init() {
this.Nav("", "", "recipient")
}
func (this *RecipientAction) RunGet(params struct {
RecipientId int64
}) {
recipientResp, err := this.RPC().MessageRecipientRPC().FindEnabledMessageRecipient(this.AdminContext(), &pb.FindEnabledMessageRecipientRequest{MessageRecipientId: params.RecipientId})
if err != nil {
this.ErrorPage(err)
return
}
recipient := recipientResp.MessageRecipient
if recipient == nil || recipient.Admin == nil || recipient.MessageMediaInstance == nil {
this.NotFound("messageRecipient", params.RecipientId)
return
}
this.Data["recipient"] = maps.Map{
"id": recipient.Id,
"admin": maps.Map{
"id": recipient.Admin.Id,
"fullname": recipient.Admin.Fullname,
"username": recipient.Admin.Username,
},
"groups": recipient.MessageRecipientGroups,
"isOn": recipient.IsOn,
"instance": maps.Map{
"id": recipient.MessageMediaInstance.Id,
"name": recipient.MessageMediaInstance.Name,
"description": recipient.MessageMediaInstance.Description,
},
"user": recipient.User,
"description": recipient.Description,
}
this.Show()
}

View File

@@ -0,0 +1,19 @@
package tasks
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.AdminModuleCodeAdmin)).
Data("teaMenu", "admins").
Data("teaSubMenu", "recipients").
Prefix("/admins/recipients/tasks").
Post("/taskInfo", new(TaskInfoAction)).
EndAll()
})
}

View File

@@ -0,0 +1,40 @@
package tasks
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type TaskInfoAction struct {
actionutils.ParentAction
}
func (this *TaskInfoAction) Init() {
this.Nav("", "", "")
}
func (this *TaskInfoAction) RunPost(params struct {
TaskId int64
}) {
resp, err := this.RPC().MessageTaskRPC().FindEnabledMessageTask(this.AdminContext(), &pb.FindEnabledMessageTaskRequest{MessageTaskId: params.TaskId})
if err != nil {
this.ErrorPage(err)
return
}
if resp.MessageTask == nil {
this.NotFound("messageTask", params.TaskId)
return
}
result := resp.MessageTask.Result
this.Data["status"] = resp.MessageTask.Status
this.Data["result"] = maps.Map{
"isOk": result.IsOk,
"response": result.Response,
"error": result.Error,
}
this.Success()
}

View File

@@ -0,0 +1,113 @@
package recipients
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type TestAction struct {
actionutils.ParentAction
}
func (this *TestAction) Init() {
this.Nav("", "", "test")
}
func (this *TestAction) RunGet(params struct {
RecipientId int64
}) {
recipientResp, err := this.RPC().MessageRecipientRPC().FindEnabledMessageRecipient(this.AdminContext(), &pb.FindEnabledMessageRecipientRequest{MessageRecipientId: params.RecipientId})
if err != nil {
this.ErrorPage(err)
return
}
recipient := recipientResp.MessageRecipient
if recipient == nil || recipient.Admin == nil || recipient.MessageMediaInstance == nil {
this.NotFound("messageRecipient", params.RecipientId)
return
}
this.Data["recipient"] = maps.Map{
"id": recipient.Id,
"admin": maps.Map{
"id": recipient.Admin.Id,
"fullname": recipient.Admin.Fullname,
"username": recipient.Admin.Username,
},
"instance": maps.Map{
"id": recipient.MessageMediaInstance.Id,
"name": recipient.MessageMediaInstance.Name,
"description": recipient.MessageMediaInstance.Description,
},
"user": recipient.User,
}
instanceResp, err := this.RPC().MessageMediaInstanceRPC().FindEnabledMessageMediaInstance(this.AdminContext(), &pb.FindEnabledMessageMediaInstanceRequest{MessageMediaInstanceId: recipient.MessageMediaInstance.Id})
if err != nil {
this.ErrorPage(err)
return
}
instance := instanceResp.MessageMediaInstance
if instance == nil || instance.MessageMedia == nil {
this.NotFound("messageMediaInstance", recipient.MessageMediaInstance.Id)
return
}
mediaParams := maps.Map{}
if len(instance.ParamsJSON) > 0 {
err = json.Unmarshal(instance.ParamsJSON, &mediaParams)
if err != nil {
this.ErrorPage(err)
return
}
}
this.Data["instance"] = maps.Map{
"id": instance.Id,
"isOn": instance.IsOn,
"media": maps.Map{
"type": instance.MessageMedia.Type,
"name": instance.MessageMedia.Name,
"userDescription": instance.MessageMedia.UserDescription,
},
"description": instance.Description,
"params": mediaParams,
}
this.Show()
}
func (this *TestAction) RunPost(params struct {
InstanceId int64
Subject string
Body string
User string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.
Field("instanceId", params.InstanceId).
Gt(0, "请选择正确的媒介")
resp, err := this.RPC().MessageTaskRPC().CreateMessageTask(this.AdminContext(), &pb.CreateMessageTaskRequest{
RecipientId: 0,
InstanceId: params.InstanceId,
User: params.User,
Subject: params.Subject,
Body: params.Body,
IsPrimary: true,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["taskId"] = resp.MessageTaskId
defer this.CreateLogInfo("创建媒介测试任务 %d", resp.MessageTaskId)
this.Success()
}

View File

@@ -0,0 +1,92 @@
package recipients
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type UpdateAction struct {
actionutils.ParentAction
}
func (this *UpdateAction) Init() {
this.Nav("", "", "update")
}
func (this *UpdateAction) RunGet(params struct {
RecipientId int64
}) {
recipientResp, err := this.RPC().MessageRecipientRPC().FindEnabledMessageRecipient(this.AdminContext(), &pb.FindEnabledMessageRecipientRequest{MessageRecipientId: params.RecipientId})
if err != nil {
this.ErrorPage(err)
return
}
recipient := recipientResp.MessageRecipient
if recipient == nil || recipient.Admin == nil || recipient.MessageMediaInstance == nil {
this.NotFound("messageRecipient", params.RecipientId)
return
}
this.Data["recipient"] = maps.Map{
"id": recipient.Id,
"admin": maps.Map{
"id": recipient.Admin.Id,
"fullname": recipient.Admin.Fullname,
"username": recipient.Admin.Username,
},
"groups": recipient.MessageRecipientGroups,
"isOn": recipient.IsOn,
"instance": maps.Map{
"id": recipient.MessageMediaInstance.Id,
"name": recipient.MessageMediaInstance.Name,
},
"user": recipient.User,
"description": recipient.Description,
}
this.Show()
}
func (this *UpdateAction) RunPost(params struct {
RecipientId int64
AdminId int64
User string
InstanceId int64
GroupIds string
Description string
IsOn bool
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo("修改媒介接收人 %d", params.RecipientId)
params.Must.
Field("adminId", params.AdminId).
Gt(0, "请选择系统用户").
Field("instanceId", params.InstanceId).
Gt(0, "请选择媒介")
groupIds := utils.SplitNumbers(params.GroupIds)
_, err := this.RPC().MessageRecipientRPC().UpdateMessageRecipient(this.AdminContext(), &pb.UpdateMessageRecipientRequest{
MessageRecipientId: params.RecipientId,
AdminId: params.AdminId,
MessageMediaInstanceId: params.InstanceId,
User: params.User,
MessageRecipientGroupIds: groupIds,
Description: params.Description,
IsOn: params.IsOn,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -45,6 +45,7 @@ func (this *UpdateAction) RunGet(params struct {
"username": admin.Username, "username": admin.Username,
"isOn": admin.IsOn, "isOn": admin.IsOn,
"isSuper": admin.IsSuper, "isSuper": admin.IsSuper,
"canLogin": admin.CanLogin,
"otpLoginIsOn": otpLoginIsOn, "otpLoginIsOn": otpLoginIsOn,
} }
@@ -76,6 +77,7 @@ func (this *UpdateAction) RunPost(params struct {
ModuleCodes []string ModuleCodes []string
IsOn bool IsOn bool
IsSuper bool IsSuper bool
CanLogin bool
// OTP // OTP
OtpOn bool OtpOn bool
@@ -139,6 +141,7 @@ func (this *UpdateAction) RunPost(params struct {
ModulesJSON: modulesJSON, ModulesJSON: modulesJSON,
IsSuper: params.IsSuper, IsSuper: params.IsSuper,
IsOn: params.IsOn, IsOn: params.IsOn,
CanLogin: params.CanLogin,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)

View File

@@ -36,6 +36,15 @@ func (this *IndexAction) RunGet(params struct {
this.Data["activeState"] = params.ActiveState this.Data["activeState"] = params.ActiveState
this.Data["keyword"] = params.Keyword 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{ countResp, err := this.RPC().NodeRPC().CountAllEnabledNodesMatch(this.AdminContext(), &pb.CountAllEnabledNodesMatchRequest{
NodeClusterId: params.ClusterId, NodeClusterId: params.ClusterId,
GroupId: params.GroupId, GroupId: params.GroupId,

View File

@@ -36,6 +36,7 @@ func init() {
Get("/node/logs", new(node.LogsAction)). Get("/node/logs", new(node.LogsAction)).
Post("/node/start", new(node.StartAction)). Post("/node/start", new(node.StartAction)).
Post("/node/stop", new(node.StopAction)). Post("/node/stop", new(node.StopAction)).
Post("/node/up", new(node.UpAction)).
// 分组相关 // 分组相关
Get("/groups", new(groups.IndexAction)). Get("/groups", new(groups.IndexAction)).

View File

@@ -19,7 +19,7 @@ func (this *InstallManualAction) Init() {
func (this *InstallManualAction) RunGet(params struct { func (this *InstallManualAction) RunGet(params struct {
ClusterId int64 ClusterId int64
}) { }) {
this.Data["leftMenuItems"] = LeftMenuItemsForInstall(params.ClusterId, "manual") this.Data["leftMenuItems"] = LeftMenuItemsForInstall(this.AdminContext(), params.ClusterId, "manual")
nodesResp, err := this.RPC().NodeRPC().FindAllNotInstalledNodesWithClusterId(this.AdminContext(), &pb.FindAllNotInstalledNodesWithClusterIdRequest{NodeClusterId: params.ClusterId}) nodesResp, err := this.RPC().NodeRPC().FindAllNotInstalledNodesWithClusterId(this.AdminContext(), &pb.FindAllNotInstalledNodesWithClusterIdRequest{NodeClusterId: params.ClusterId})
if err != nil { if err != nil {

View File

@@ -19,7 +19,7 @@ func (this *InstallNodesAction) Init() {
func (this *InstallNodesAction) RunGet(params struct { func (this *InstallNodesAction) RunGet(params struct {
ClusterId int64 ClusterId int64
}) { }) {
this.Data["leftMenuItems"] = LeftMenuItemsForInstall(params.ClusterId, "register") this.Data["leftMenuItems"] = LeftMenuItemsForInstall(this.AdminContext(), params.ClusterId, "register")
clusterResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeCluster(this.AdminContext(), &pb.FindEnabledNodeClusterRequest{NodeClusterId: params.ClusterId}) clusterResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeCluster(this.AdminContext(), &pb.FindEnabledNodeClusterRequest{NodeClusterId: params.ClusterId})
if err != nil { if err != nil {

View File

@@ -21,7 +21,7 @@ func (this *InstallRemoteAction) Init() {
func (this *InstallRemoteAction) RunGet(params struct { func (this *InstallRemoteAction) RunGet(params struct {
ClusterId int64 ClusterId int64
}) { }) {
this.Data["leftMenuItems"] = LeftMenuItemsForInstall(params.ClusterId, "install") this.Data["leftMenuItems"] = LeftMenuItemsForInstall(this.AdminContext(), params.ClusterId, "install")
nodesResp, err := this.RPC().NodeRPC().FindAllNotInstalledNodesWithClusterId(this.AdminContext(), &pb.FindAllNotInstalledNodesWithClusterIdRequest{NodeClusterId: params.ClusterId}) nodesResp, err := this.RPC().NodeRPC().FindAllNotInstalledNodesWithClusterId(this.AdminContext(), &pb.FindAllNotInstalledNodesWithClusterIdRequest{NodeClusterId: params.ClusterId})
if err != nil { if err != nil {

View File

@@ -89,6 +89,12 @@ func (this *NodeAction) RunGet(params struct {
}) })
} }
} }
if len(dnsRouteMaps) == 0 {
dnsRouteMaps = append(dnsRouteMaps, maps.Map{
"name": "",
"code": "",
})
}
this.Data["dnsRoutes"] = dnsRouteMaps this.Data["dnsRoutes"] = dnsRouteMaps
this.Data["dnsRecordName"] = recordName this.Data["dnsRecordName"] = recordName
this.Data["dnsRecordValue"] = recordValue this.Data["dnsRecordValue"] = recordValue
@@ -143,6 +149,24 @@ func (this *NodeAction) RunGet(params struct {
status.IsActive = status.IsActive && time.Now().Unix()-status.UpdatedAt <= 60 // N秒之内认为活跃 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 var groupMap maps.Map = nil
if node.Group != nil { if node.Group != nil {
@@ -175,14 +199,20 @@ func (this *NodeAction) RunGet(params struct {
"isOn": node.IsOn, "isOn": node.IsOn,
"status": maps.Map{ "status": maps.Map{
"isActive": status.IsActive, "isActive": status.IsActive,
"updatedAt": status.UpdatedAt, "updatedAt": status.UpdatedAt,
"hostname": status.Hostname, "hostname": status.Hostname,
"cpuUsage": status.CPUUsage, "cpuUsage": status.CPUUsage,
"cpuUsageText": fmt.Sprintf("%.2f%%", status.CPUUsage*100), "cpuUsageText": fmt.Sprintf("%.2f%%", status.CPUUsage*100),
"memUsage": status.MemoryUsage, "memUsage": status.MemoryUsage,
"memUsageText": fmt.Sprintf("%.2f%%", status.MemoryUsage*100), "memUsageText": fmt.Sprintf("%.2f%%", status.MemoryUsage*100),
"connectionCount": status.ConnectionCount, "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),
}, },
"group": groupMap, "group": groupMap,

View File

@@ -1,8 +1,8 @@
package node package node
import ( import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
) )
@@ -34,6 +34,7 @@ func (this *StatusAction) RunPost(params struct {
"isOk": node.InstallStatus.IsOk, "isOk": node.InstallStatus.IsOk,
"updatedAt": node.InstallStatus.UpdatedAt, "updatedAt": node.InstallStatus.UpdatedAt,
"error": node.InstallStatus.Error, "error": node.InstallStatus.Error,
"errorCode": node.InstallStatus.ErrorCode,
} }
} else { } else {
this.Data["installStatus"] = nil this.Data["installStatus"] = nil

View File

@@ -0,0 +1,28 @@
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
// 手动上线
type UpAction struct {
actionutils.ParentAction
}
func (this *UpAction) RunPost(params struct {
NodeId int64
}) {
defer this.CreateLogInfo("手动上线节点 %d", params.NodeId)
_, err := this.RPC().NodeRPC().UpdateNodeUp(this.AdminContext(), &pb.UpdateNodeUpRequest{
NodeId: params.NodeId,
IsUp: true,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,125 @@
package firewallActions
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/iwind/TeaGo/actions"
)
type CreatePopupAction struct {
actionutils.ParentAction
}
func (this *CreatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreatePopupAction) RunGet(params struct {
ClusterId int64
}) {
this.Data["clusterId"] = params.ClusterId
this.Data["actionTypes"] = firewallconfigs.FindAllFirewallActionTypes()
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
ClusterId int64
Name string
EventLevel string
Type string
// ipset
IpsetWhiteName string
IpsetBlackName string
IpsetAutoAddToIPTables bool
IpsetAutoAddToFirewalld bool
// script
ScriptPath string
// http api
HttpAPIURL string
// html
HtmlContent string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo("创建WAF动作")
params.Must.
Field("name", params.Name).
Require("请输入动作名称").
Field("type", params.Type).
Require("请选择动作类型")
var actionParams interface{} = nil
switch params.Type {
case firewallconfigs.FirewallActionTypeIPSet:
params.Must.
Field("ipsetWhiteName", params.IpsetWhiteName).
Require("请输入IPSet白名单名称").
Match(`^\w+$`, "请输入正确的IPSet白名单名称").
Field("ipsetBlackName", params.IpsetBlackName).
Require("请输入IPSet黑名单名称").
Match(`^\w+$`, "请输入正确的IPSet黑名单名称")
actionParams = &firewallconfigs.FirewallActionIPSetConfig{
WhiteName: params.IpsetWhiteName,
BlackName: params.IpsetBlackName,
AutoAddToIPTables: params.IpsetAutoAddToIPTables,
AutoAddToFirewalld: params.IpsetAutoAddToFirewalld,
}
case firewallconfigs.FirewallActionTypeIPTables:
actionParams = &firewallconfigs.FirewallActionIPTablesConfig{}
case firewallconfigs.FirewallActionTypeFirewalld:
actionParams = &firewallconfigs.FirewallActionFirewalldConfig{}
case firewallconfigs.FirewallActionTypeScript:
params.Must.
Field("scriptPath", params.ScriptPath).
Require("请输入脚本路径")
actionParams = &firewallconfigs.FirewallActionScriptConfig{
Path: params.ScriptPath,
}
case firewallconfigs.FirewallActionTypeHTTPAPI:
params.Must.
Field("httpAPIURL", params.HttpAPIURL).
Require("请输入API URL").
Match(`^(http|https):`, "API地址必须以http://或https://开头")
actionParams = &firewallconfigs.FirewallActionHTTPAPIConfig{
URL: params.HttpAPIURL,
}
case firewallconfigs.FirewallActionTypeHTML:
params.Must.
Field("htmlContent", params.HtmlContent).
Require("请输入HTML内容")
actionParams = &firewallconfigs.FirewallActionHTMLConfig{
Content: params.HtmlContent,
}
default:
this.Fail("选择的类型'" + params.Type + "'暂时不支持")
}
actionParamsJSON, err := json.Marshal(actionParams)
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().NodeClusterFirewallActionRPC().CreateNodeClusterFirewallAction(this.AdminContext(), &pb.CreateNodeClusterFirewallActionRequest{
NodeClusterId: params.ClusterId,
Name: params.Name,
EventLevel: params.EventLevel,
Type: params.Type,
ParamsJSON: actionParamsJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,24 @@
package firewallActions
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 {
ActionId int64
}) {
defer this.CreateLogInfo("删除WAF动作 %d", params.ActionId)
_, err := this.RPC().NodeClusterFirewallActionRPC().DeleteNodeClusterFirewallAction(this.AdminContext(), &pb.DeleteNodeClusterFirewallActionRequest{NodeClusterFirewallActionId: params.ActionId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,65 @@
package firewallActions
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "setting", "")
this.SecondMenu("firewallAction")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
}) {
actionsResp, err := this.RPC().NodeClusterFirewallActionRPC().FindAllEnabledNodeClusterFirewallActions(this.AdminContext(), &pb.FindAllEnabledNodeClusterFirewallActionsRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
levelMaps := map[string][]maps.Map{} // level => actionMaps
for _, action := range actionsResp.NodeClusterFirewallActions {
actionMaps, ok := levelMaps[action.EventLevel]
if !ok {
actionMaps = []maps.Map{}
}
actionMaps = append(actionMaps, maps.Map{
"id": action.Id,
"name": action.Name,
"type": action.Type,
"typeName": firewallconfigs.FindFirewallActionTypeName(action.Type),
})
levelMaps[action.EventLevel] = actionMaps
}
levelMaps2 := []maps.Map{} // []levelMap
hasActions := false
for _, level := range firewallconfigs.FindAllFirewallEventLevels() {
actionMaps, ok := levelMaps[level.Code]
if !ok {
actionMaps = []maps.Map{}
} else {
hasActions = true
}
levelMaps2 = append(levelMaps2, maps.Map{
"name": level.Name,
"code": level.Code,
"actions": actionMaps,
})
}
this.Data["levels"] = levelMaps2
this.Data["hasActions"] = hasActions
this.Show()
}

View File

@@ -0,0 +1,154 @@
package firewallActions
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type UpdatePopupAction struct {
actionutils.ParentAction
}
func (this *UpdatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdatePopupAction) RunGet(params struct {
ActionId int64
}) {
actionResp, err := this.RPC().NodeClusterFirewallActionRPC().FindEnabledNodeClusterFirewallAction(this.AdminContext(), &pb.FindEnabledNodeClusterFirewallActionRequest{NodeClusterFirewallActionId: params.ActionId})
if err != nil {
this.ErrorPage(err)
return
}
action := actionResp.NodeClusterFirewallAction
if action == nil {
this.NotFound("nodeClusterFirewallAction", params.ActionId)
return
}
actionParams := maps.Map{}
if len(action.ParamsJSON) > 0 {
err = json.Unmarshal(action.ParamsJSON, &actionParams)
if err != nil {
this.ErrorPage(err)
return
}
}
this.Data["action"] = maps.Map{
"id": action.Id,
"name": action.Name,
"eventLevel": action.EventLevel,
"params": actionParams,
"type": action.Type,
}
// 通用参数
this.Data["actionTypes"] = firewallconfigs.FindAllFirewallActionTypes()
this.Show()
}
func (this *UpdatePopupAction) RunPost(params struct {
ActionId int64
Name string
EventLevel string
Type string
// ipset
IpsetWhiteName string
IpsetBlackName string
IpsetAutoAddToIPTables bool
IpsetAutoAddToFirewalld bool
// script
ScriptPath string
// http api
HttpAPIURL string
// HTML内容
HtmlContent string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo("修改WAF动作 %d", params.ActionId)
params.Must.
Field("name", params.Name).
Require("请输入动作名称").
Field("type", params.Type).
Require("请选择动作类型")
var actionParams interface{} = nil
switch params.Type {
case firewallconfigs.FirewallActionTypeIPSet:
params.Must.
Field("ipsetWhiteName", params.IpsetWhiteName).
Require("请输入IPSet白名单名称").
Match(`^\w+$`, "请输入正确的IPSet白名单名称").
Field("ipsetBlackName", params.IpsetBlackName).
Require("请输入IPSet黑名单名称").
Match(`^\w+$`, "请输入正确的IPSet黑名单名称")
actionParams = &firewallconfigs.FirewallActionIPSetConfig{
WhiteName: params.IpsetWhiteName,
BlackName: params.IpsetBlackName,
AutoAddToIPTables: params.IpsetAutoAddToIPTables,
AutoAddToFirewalld: params.IpsetAutoAddToFirewalld,
}
case firewallconfigs.FirewallActionTypeIPTables:
actionParams = &firewallconfigs.FirewallActionIPTablesConfig{}
case firewallconfigs.FirewallActionTypeFirewalld:
actionParams = &firewallconfigs.FirewallActionFirewalldConfig{}
case firewallconfigs.FirewallActionTypeScript:
params.Must.
Field("scriptPath", params.ScriptPath).
Require("请输入脚本路径")
actionParams = &firewallconfigs.FirewallActionScriptConfig{
Path: params.ScriptPath,
}
case firewallconfigs.FirewallActionTypeHTTPAPI:
params.Must.
Field("httpAPIURL", params.HttpAPIURL).
Require("请输入API URL").
Match(`^(http|https):`, "API地址必须以http://或https://开头")
actionParams = &firewallconfigs.FirewallActionHTTPAPIConfig{
URL: params.HttpAPIURL,
}
case firewallconfigs.FirewallActionTypeHTML:
params.Must.
Field("htmlContent", params.HtmlContent).
Require("请输入HTML内容")
actionParams = &firewallconfigs.FirewallActionHTMLConfig{
Content: params.HtmlContent,
}
default:
this.Fail("选择的类型'" + params.Type + "'暂时不支持")
}
actionParamsJSON, err := json.Marshal(actionParams)
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().NodeClusterFirewallActionRPC().UpdateNodeClusterFirewallAction(this.AdminContext(), &pb.UpdateNodeClusterFirewallActionRequest{
NodeClusterFirewallActionId: params.ActionId,
Name: params.Name,
EventLevel: params.EventLevel,
Type: params.Type,
ParamsJSON: actionParamsJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -1,4 +1,4 @@
package settings package health
import ( import (
"encoding/json" "encoding/json"
@@ -9,16 +9,16 @@ import (
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
) )
type HealthAction struct { type IndexAction struct {
actionutils.ParentAction actionutils.ParentAction
} }
func (this *HealthAction) Init() { func (this *IndexAction) Init() {
this.Nav("", "setting", "") this.Nav("", "setting", "")
this.SecondMenu("health") this.SecondMenu("health")
} }
func (this *HealthAction) RunGet(params struct { func (this *IndexAction) RunGet(params struct {
ClusterId int64 ClusterId int64
}) { }) {
configResp, err := this.RPC().NodeClusterRPC().FindNodeClusterHealthCheckConfig(this.AdminContext(), &pb.FindNodeClusterHealthCheckConfigRequest{NodeClusterId: params.ClusterId}) configResp, err := this.RPC().NodeClusterRPC().FindNodeClusterHealthCheckConfig(this.AdminContext(), &pb.FindNodeClusterHealthCheckConfigRequest{NodeClusterId: params.ClusterId})
@@ -40,7 +40,7 @@ func (this *HealthAction) RunGet(params struct {
this.Show() this.Show()
} }
func (this *HealthAction) RunPost(params struct { func (this *IndexAction) RunPost(params struct {
ClusterId int64 ClusterId int64
HealthCheckJSON []byte HealthCheckJSON []byte
Must *actions.Must Must *actions.Must

View File

@@ -1,4 +1,4 @@
package settings package health
import ( import (
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs" "github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
@@ -7,20 +7,19 @@ import (
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
) )
type HealthRunPopupAction struct { type RunPopupAction struct {
actionutils.ParentAction actionutils.ParentAction
} }
func (this *HealthRunPopupAction) Init() { func (this *RunPopupAction) Init() {
this.Nav("", "", "") this.Nav("", "", "")
} }
func (this *HealthRunPopupAction) RunGet(params struct{}) { func (this *RunPopupAction) RunGet(params struct{}) {
this.Show() this.Show()
} }
func (this *HealthRunPopupAction) RunPost(params struct { func (this *RunPopupAction) RunPost(params struct {
ClusterId int64 ClusterId int64
Must *actions.Must Must *actions.Must

View File

@@ -4,6 +4,9 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders" "github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/cache" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/cache"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/dns" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/dns"
firewallActions "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/firewall-actions"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/health"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/message"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/services" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/services"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/toa" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/toa"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/waf" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/waf"
@@ -21,8 +24,8 @@ func init() {
GetPost("", new(IndexAction)). GetPost("", new(IndexAction)).
// 健康检查 // 健康检查
GetPost("/health", new(HealthAction)). GetPost("/health", new(health.IndexAction)).
GetPost("/healthRunPopup", new(HealthRunPopupAction)). GetPost("/health/runPopup", new(health.RunPopupAction)).
// 缓存 // 缓存
GetPost("/cache", new(cache.IndexAction)). GetPost("/cache", new(cache.IndexAction)).
@@ -34,6 +37,12 @@ func init() {
Prefix("/clusters/cluster/settings/dns"). Prefix("/clusters/cluster/settings/dns").
GetPost("", new(dns.IndexAction)). GetPost("", new(dns.IndexAction)).
// 消息
Prefix("/clusters/cluster/settings/message").
GetPost("", new(message.IndexAction)).
Get("/selectReceiverPopup", new(message.SelectReceiverPopupAction)).
Post("/selectedReceivers", new(message.SelectedReceiversAction)).
// TOA // TOA
Prefix("/clusters/cluster/settings/toa"). Prefix("/clusters/cluster/settings/toa").
GetPost("", new(toa.IndexAction)). GetPost("", new(toa.IndexAction)).
@@ -43,6 +52,13 @@ func init() {
GetPost("", new(services.IndexAction)). GetPost("", new(services.IndexAction)).
GetPost("/status", new(services.StatusAction)). GetPost("/status", new(services.StatusAction)).
// 防火墙动作
Prefix("/clusters/cluster/settings/firewall-actions").
Get("", new(firewallActions.IndexAction)).
GetPost("/createPopup", new(firewallActions.CreatePopupAction)).
GetPost("/updatePopup", new(firewallActions.UpdatePopupAction)).
Post("/delete", new(firewallActions.DeleteAction)).
EndAll() EndAll()
}) })
} }

View File

@@ -0,0 +1,75 @@
package message
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "setting", "")
this.SecondMenu("message")
}
func (this *IndexAction) RunGet(params struct{}) {
this.Show()
}
func (this *IndexAction) RunPost(params struct {
ClusterId int64
ReceiversJSON []byte
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo("修改集群 %d 消息接收人", params.ClusterId)
receiverMaps := []maps.Map{}
if len(params.ReceiversJSON) > 0 {
err := json.Unmarshal(params.ReceiversJSON, &receiverMaps)
if err != nil {
this.ErrorPage(err)
return
}
}
pbReceiverOptions := &pb.UpdateMessageReceiversRequest_RecipientOptions{}
for _, receiverMap := range receiverMaps {
recipientId := int64(0)
groupId := int64(0)
receiverType := receiverMap.GetString("type")
switch receiverType {
case "recipient":
recipientId = receiverMap.GetInt64("id")
case "group":
groupId = receiverMap.GetInt64("id")
default:
continue
}
pbReceiverOptions.RecipientOptions = append(pbReceiverOptions.RecipientOptions, &pb.UpdateMessageReceiversRequest_RecipientOption{
MessageRecipientId: recipientId,
MessageRecipientGroupId: groupId,
})
}
_, err := this.RPC().MessageReceiverRPC().UpdateMessageReceivers(this.AdminContext(), &pb.UpdateMessageReceiversRequest{
NodeClusterId: params.ClusterId,
NodeId: 0,
ServerId: 0,
ParamsJSON: nil,
RecipientOptions: map[string]*pb.UpdateMessageReceiversRequest_RecipientOptions{
"*": pbReceiverOptions,
},
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,77 @@
package message
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
)
type SelectReceiverPopupAction struct {
actionutils.ParentAction
}
func (this *SelectReceiverPopupAction) Init() {
this.Nav("", "", "")
}
func (this *SelectReceiverPopupAction) RunGet(params struct {
RecipientIds string
GroupIds string
}) {
recipientIds := utils.SplitNumbers(params.RecipientIds)
groupIds := utils.SplitNumbers(params.GroupIds)
// 所有接收人
recipientsResp, err := this.RPC().MessageRecipientRPC().ListEnabledMessageRecipients(this.AdminContext(), &pb.ListEnabledMessageRecipientsRequest{
AdminId: 0,
MediaType: "",
MessageRecipientGroupId: 0,
Keyword: "",
Offset: 0,
Size: 1000, // TODO 支持搜索
})
if err != nil {
this.ErrorPage(err)
return
}
recipientMaps := []maps.Map{}
for _, recipient := range recipientsResp.MessageRecipients {
if !recipient.IsOn {
continue
}
if lists.ContainsInt64(recipientIds, recipient.Id) {
continue
}
recipientMaps = append(recipientMaps, maps.Map{
"id": recipient.Id,
"name": recipient.Admin.Fullname,
"instanceName": recipient.MessageMediaInstance.Name,
})
}
this.Data["recipients"] = recipientMaps
// 所有分组
groupsResp, err := this.RPC().MessageRecipientGroupRPC().FindAllEnabledMessageRecipientGroups(this.AdminContext(), &pb.FindAllEnabledMessageRecipientGroupsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
groupMaps := []maps.Map{}
for _, group := range groupsResp.MessageRecipientGroups {
if !group.IsOn {
continue
}
if lists.ContainsInt64(groupIds, group.Id) {
continue
}
groupMaps = append(groupMaps, maps.Map{
"id": group.Id,
"name": group.Name,
})
}
this.Data["groups"] = groupMaps
this.Show()
}

View File

@@ -0,0 +1,61 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package message
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type SelectedReceiversAction struct {
actionutils.ParentAction
}
func (this *SelectedReceiversAction) Init() {
this.Nav("", "", "")
}
func (this *SelectedReceiversAction) RunPost(params struct {
ClusterId int64
NodeId int64
ServerId int64
}) {
receiversResp, err := this.RPC().MessageReceiverRPC().FindAllMessageReceivers(this.AdminContext(), &pb.FindAllMessageReceiversRequest{
NodeClusterId: params.ClusterId,
NodeId: params.NodeId,
ServerId: params.ServerId,
})
if err != nil {
this.ErrorPage(err)
return
}
receiverMaps := []maps.Map{}
for _, receiver := range receiversResp.MessageReceivers {
id := int64(0)
name := ""
receiverType := ""
subName := ""
if receiver.MessageRecipient != nil {
id = receiver.MessageRecipient.Id
name = receiver.MessageRecipient.Admin.Fullname
subName = receiver.MessageRecipient.MessageMediaInstance.Name
receiverType = "recipient"
} else if receiver.MessageRecipientGroup != nil {
id = receiver.MessageRecipientGroup.Id
name = receiver.MessageRecipientGroup.Name
receiverType = "group"
} else {
continue
}
receiverMaps = append(receiverMaps, maps.Map{
"id": id,
"name": name,
"subName": subName,
"type": receiverType,
})
}
this.Data["receivers"] = receiverMaps
this.Success()
}

View File

@@ -21,7 +21,7 @@ func (this *UpgradeRemoteAction) Init() {
func (this *UpgradeRemoteAction) RunGet(params struct { func (this *UpgradeRemoteAction) RunGet(params struct {
ClusterId int64 ClusterId int64
}) { }) {
this.Data["leftMenuItems"] = LeftMenuItemsForInstall(params.ClusterId, "upgrade") this.Data["leftMenuItems"] = LeftMenuItemsForInstall(this.AdminContext(), params.ClusterId, "upgrade")
nodes := []maps.Map{} nodes := []maps.Map{}
resp, err := this.RPC().NodeRPC().FindAllUpgradeNodesWithClusterId(this.AdminContext(), &pb.FindAllUpgradeNodesWithClusterIdRequest{NodeClusterId: params.ClusterId}) resp, err := this.RPC().NodeRPC().FindAllUpgradeNodesWithClusterId(this.AdminContext(), &pb.FindAllUpgradeNodesWithClusterIdRequest{NodeClusterId: params.ClusterId})

View File

@@ -1,12 +1,34 @@
package cluster package cluster
import ( import (
"context"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils" "github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
"strconv"
) )
// 安装升级相关的左侧菜单 // 安装升级相关的左侧菜单
func LeftMenuItemsForInstall(clusterId int64, selectedItem string) []maps.Map { func LeftMenuItemsForInstall(ctx context.Context, clusterId int64, selectedItem string) []maps.Map {
rpcClient, _ := rpc.SharedRPC()
countNotInstalled := int64(0)
countUpgrade := int64(0)
if rpcClient != nil {
{
resp, err := rpcClient.NodeRPC().CountAllNotInstalledNodesWithClusterId(ctx, &pb.CountAllNotInstalledNodesWithClusterIdRequest{NodeClusterId: clusterId})
if err == nil {
countNotInstalled = resp.Count
}
}
{
resp, err := rpcClient.NodeRPC().CountAllUpgradeNodesWithClusterId(ctx, &pb.CountAllUpgradeNodesWithClusterIdRequest{NodeClusterId: clusterId})
if err == nil {
countUpgrade = resp.Count
}
}
}
return []maps.Map{ return []maps.Map{
{ {
"name": "手动安装", "name": "手动安装",
@@ -19,12 +41,12 @@ func LeftMenuItemsForInstall(clusterId int64, selectedItem string) []maps.Map {
"isActive": selectedItem == "register", "isActive": selectedItem == "register",
}, },
{ {
"name": "远程安装", "name": "远程安装(" + strconv.FormatInt(countNotInstalled, 10) + ")",
"url": "/clusters/cluster/installRemote?clusterId=" + numberutils.FormatInt64(clusterId), "url": "/clusters/cluster/installRemote?clusterId=" + numberutils.FormatInt64(clusterId),
"isActive": selectedItem == "install", "isActive": selectedItem == "install",
}, },
{ {
"name": "远程升级", "name": "远程升级(" + strconv.FormatInt(countUpgrade, 10) + ")",
"url": "/clusters/cluster/upgradeRemote?clusterId=" + numberutils.FormatInt64(clusterId), "url": "/clusters/cluster/upgradeRemote?clusterId=" + numberutils.FormatInt64(clusterId),
"isActive": selectedItem == "upgrade", "isActive": selectedItem == "upgrade",
}, },

View File

@@ -1,11 +1,14 @@
package clusterutils package clusterutils
import ( import (
"encoding/json"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc" "github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils" "github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/logs" "github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
@@ -86,16 +89,47 @@ func (this *ClusterHelper) createSettingMenu(cluster *pb.NodeCluster, selectedIt
"isActive": selectedItem == "waf", "isActive": selectedItem == "waf",
"isOn": cluster.HttpFirewallPolicyId > 0, "isOn": cluster.HttpFirewallPolicyId > 0,
}) })
items = append(items, maps.Map{
"name": "健康检查", {
"url": "/clusters/cluster/settings/health?clusterId=" + clusterId, hasActions, _ := this.checkFirewallActions(cluster.Id)
"isActive": selectedItem == "health", items = append(items, maps.Map{
}) "name": "WAF动作",
"url": "/clusters/cluster/settings/firewall-actions?clusterId=" + clusterId,
"isActive": selectedItem == "firewallAction",
"isOn": hasActions,
})
}
{
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{ items = append(items, maps.Map{
"name": "DNS设置", "name": "DNS设置",
"url": "/clusters/cluster/settings/dns?clusterId=" + clusterId, "url": "/clusters/cluster/settings/dns?clusterId=" + clusterId,
"isActive": selectedItem == "dns", "isActive": selectedItem == "dns",
"isOn": cluster.DnsDomainId > 0 || len(cluster.DnsName) > 0,
}) })
if teaconst.IsPlus {
items = append(items, maps.Map{
"name": "消息通知",
"url": "/clusters/cluster/settings/message?clusterId=" + clusterId,
"isActive": selectedItem == "message",
})
}
items = append(items, maps.Map{
"name": "-",
"url": "",
"isActive": false,
})
items = append(items, maps.Map{ items = append(items, maps.Map{
"name": "系统服务", "name": "系统服务",
"url": "/clusters/cluster/settings/services?clusterId=" + clusterId, "url": "/clusters/cluster/settings/services?clusterId=" + clusterId,
@@ -108,3 +142,37 @@ func (this *ClusterHelper) createSettingMenu(cluster *pb.NodeCluster, selectedIt
}) })
return 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
}

View File

@@ -5,6 +5,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/grants/grantutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/grants/grantutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
"strings"
) )
type GrantAction struct { type GrantAction struct {
@@ -37,7 +38,7 @@ func (this *GrantAction) RunGet(params struct {
"method": grant.Method, "method": grant.Method,
"methodName": grantutils.FindGrantMethodName(grant.Method), "methodName": grantutils.FindGrantMethodName(grant.Method),
"username": grant.Username, "username": grant.Username,
"password": grant.Password, "password": strings.Repeat("*", len(grant.Password)),
"privateKey": grant.PrivateKey, "privateKey": grant.PrivateKey,
"description": grant.Description, "description": grant.Description,
"su": grant.Su, "su": grant.Su,

View File

@@ -0,0 +1,24 @@
package tasks
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteBatchAction struct {
actionutils.ParentAction
}
func (this *DeleteBatchAction) RunPost(params struct {
TaskIds []int64
}) {
defer this.CreateLogInfo("批量删除节点同步任务")
_, err := this.RPC().NodeTaskRPC().DeleteNodeTasks(this.AdminContext(), &pb.DeleteNodeTasksRequest{NodeTaskIds: params.TaskIds})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -16,6 +16,7 @@ func init() {
GetPost("/listPopup", new(ListPopupAction)). GetPost("/listPopup", new(ListPopupAction)).
Post("/check", new(CheckAction)). Post("/check", new(CheckAction)).
Post("/delete", new(DeleteAction)). Post("/delete", new(DeleteAction)).
Post("/deleteBatch", new(DeleteBatchAction)).
EndAll() EndAll()
}) })

View File

@@ -2,7 +2,12 @@ package dashboard
import ( import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders" "github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
"math"
"regexp"
) )
type IndexAction struct { type IndexAction struct {
@@ -17,13 +22,75 @@ func (this *IndexAction) RunGet(params struct{}) {
// 取得用户的权限 // 取得用户的权限
module, ok := configloaders.FindFirstAdminModule(this.AdminId()) module, ok := configloaders.FindFirstAdminModule(this.AdminId())
if ok { if ok {
for _, m := range configloaders.AllModuleMaps() { if module != "dashboard" {
if m.GetString("code") == module { for _, m := range configloaders.AllModuleMaps() {
this.RedirectURL(m.GetString("url")) if m.GetString("code") == module {
return 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.FormatBits(todayTrafficBytes * 8)
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{
"count": math.Ceil((float64(stat.Bytes)*8/1000/1000/1000)*1000) / 1000,
"hour": stat.Hour[8:],
})
}
this.Data["hourlyTrafficStats"] = statMaps
}
// 15天流量趋势
{
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,
"day": stat.Day[4:6] + "月" + stat.Day[6:] + "日",
})
}
this.Data["dailyTrafficStats"] = statMaps
}
this.Show() this.Show()
} }

View File

@@ -9,6 +9,7 @@ import (
func init() { func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) { TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.Prefix("/dashboard"). server.Prefix("/dashboard").
Data("teaMenu", "dashboard").
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeCommon)). Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeCommon)).
GetPost("", new(IndexAction)). GetPost("", new(IndexAction)).
EndAll() EndAll()

View File

@@ -6,6 +6,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/rands"
) )
type CreatePopupAction struct { type CreatePopupAction struct {
@@ -32,6 +33,9 @@ func (this *CreatePopupAction) RunGet(params struct{}) {
} }
this.Data["types"] = typeMaps this.Data["types"] = typeMaps
// 自动生成CustomHTTP私钥
this.Data["paramCustomHTTPSecret"] = rands.HexString(32)
this.Show() this.Show()
} }
@@ -51,6 +55,14 @@ func (this *CreatePopupAction) RunPost(params struct {
ParamApiKey string ParamApiKey string
ParamApiSecret string ParamApiSecret string
// CloudFlare
CloudFlareAPIKey string
CloudFlareEmail string
// CustomHTTP
ParamCustomHTTPURL string
ParamCustomHTTPSecret string
Must *actions.Must Must *actions.Must
CSRF *actionutils.CSRF CSRF *actionutils.CSRF
}) { }) {
@@ -89,6 +101,23 @@ func (this *CreatePopupAction) RunPost(params struct {
apiParams["apiKey"] = params.ParamApiKey apiParams["apiKey"] = params.ParamApiKey
apiParams["apiSecret"] = params.ParamApiSecret apiParams["apiSecret"] = params.ParamApiSecret
case "cloudFlare":
params.Must.
Field("cloudFlareAPIKey", params.CloudFlareAPIKey).
Require("请输入API密钥").
Field("cloudFlareEmail", params.CloudFlareEmail).
Email("请输入正确格式的邮箱地址")
apiParams["apiKey"] = params.CloudFlareAPIKey
apiParams["email"] = params.CloudFlareEmail
case "customHTTP":
params.Must.
Field("paramCustomHTTPURL", params.ParamCustomHTTPURL).
Require("请输入HTTP URL").
Match("^(?i)(http|https):", "URL必须以http://或者https://开头").
Field("paramCustomHTTPSecret", params.ParamCustomHTTPSecret).
Require("请输入私钥")
apiParams["url"] = params.ParamCustomHTTPURL
apiParams["secret"] = params.ParamCustomHTTPSecret
default: default:
this.Fail("暂时不支持此服务商'" + params.Type + "'") this.Fail("暂时不支持此服务商'" + params.Type + "'")
} }

View File

@@ -84,6 +84,14 @@ func (this *UpdatePopupAction) RunPost(params struct {
ParamApiKey string ParamApiKey string
ParamApiSecret string ParamApiSecret string
// CloudFlare
CloudFlareAPIKey string
CloudFlareEmail string
// CustomHTTP
ParamCustomHTTPURL string
ParamCustomHTTPSecret string
Must *actions.Must Must *actions.Must
CSRF *actionutils.CSRF CSRF *actionutils.CSRF
}) { }) {
@@ -124,6 +132,23 @@ func (this *UpdatePopupAction) RunPost(params struct {
apiParams["apiKey"] = params.ParamApiKey apiParams["apiKey"] = params.ParamApiKey
apiParams["apiSecret"] = params.ParamApiSecret apiParams["apiSecret"] = params.ParamApiSecret
case "cloudFlare":
params.Must.
Field("cloudFlareAPIKey", params.CloudFlareAPIKey).
Require("请输入API密钥").
Field("cloudFlareEmail", params.CloudFlareEmail).
Email("请输入正确格式的邮箱地址")
apiParams["apiKey"] = params.CloudFlareAPIKey
apiParams["email"] = params.CloudFlareEmail
case "customHTTP":
params.Must.
Field("paramCustomHTTPURL", params.ParamCustomHTTPURL).
Require("请输入HTTP URL").
Match("^(?i)(http|https):", "URL必须以http://或者https://开头").
Field("paramCustomHTTPSecret", params.ParamCustomHTTPSecret).
Require("请输入私钥")
apiParams["url"] = params.ParamCustomHTTPURL
apiParams["secret"] = params.ParamCustomHTTPSecret
default: default:
this.Fail("暂时不支持此服务商'" + params.Type + "'") this.Fail("暂时不支持此服务商'" + params.Type + "'")
} }

View File

@@ -0,0 +1,23 @@
package tasks
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type CheckAction struct {
actionutils.ParentAction
}
func (this *CheckAction) RunPost(params struct{}) {
resp, err := this.RPC().DNSTaskRPC().ExistsDNSTasks(this.AdminContext(), &pb.ExistsDNSTasksRequest{})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["isDoing"] = resp.ExistTasks
this.Data["hasError"] = resp.ExistError
this.Success()
}

View File

@@ -0,0 +1,24 @@
package tasks
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 {
TaskId int64
}) {
defer this.CreateLogInfo("删除DNS同步任务 %d", params.TaskId)
_, err := this.RPC().DNSTaskRPC().DeleteDNSTask(this.AdminContext(), &pb.DeleteDNSTaskRequest{DnsTaskId: params.TaskId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,22 @@
package tasks
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/clusterutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeDNS)).
Helper(clusterutils.NewClustersHelper()).
Prefix("/dns/tasks").
GetPost("/listPopup", new(ListPopupAction)).
Post("/check", new(CheckAction)).
Post("/delete", new(DeleteAction)).
EndAll()
})
}

View File

@@ -0,0 +1,84 @@
package tasks
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"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type ListPopupAction struct {
actionutils.ParentAction
}
func (this *ListPopupAction) Init() {
this.Nav("", "", "")
}
func (this *ListPopupAction) RunGet(params struct{}) {
this.retrieveTasks()
this.Show()
}
func (this *ListPopupAction) RunPost(params struct {
Must *actions.Must
}) {
this.retrieveTasks()
this.Success()
}
func (this *ListPopupAction) retrieveTasks() {
resp, err := this.RPC().DNSTaskRPC().FindAllDoingDNSTasks(this.AdminContext(), &pb.FindAllDoingDNSTasksRequest{})
if err != nil {
this.ErrorPage(err)
return
}
taskMaps := []maps.Map{}
for _, task := range resp.DnsTasks {
var clusterMap maps.Map = nil
var nodeMap maps.Map = nil
var serverMap maps.Map = nil
var domainMap maps.Map = nil
if task.NodeCluster != nil {
clusterMap = maps.Map{
"id": task.NodeCluster.Id,
"name": task.NodeCluster.Name,
}
}
if task.Node != nil {
nodeMap = maps.Map{
"id": task.Node.Id,
"name": task.Node.Name,
}
}
if task.Server != nil {
serverMap = maps.Map{
"id": task.Server.Id,
"name": task.Server.Name,
}
}
if task.DnsDomain != nil {
domainMap = maps.Map{
"id": task.DnsDomain.Id,
"name": task.DnsDomain.Name,
}
}
taskMaps = append(taskMaps, maps.Map{
"id": task.Id,
"type": task.Type,
"isDone": task.IsDone,
"isOk": task.IsOk,
"error": task.Error,
"updatedTime": timeutil.FormatTime("Y-m-d H:i:s", task.UpdatedAt),
"cluster": clusterMap,
"node": nodeMap,
"server": serverMap,
"domain": domainMap,
})
}
this.Data["tasks"] = taskMaps
}

View File

@@ -65,6 +65,7 @@ func (this *IndexAction) RunGet(params struct {
} else { } else {
this.Data["version"] = teaconst.Version this.Data["version"] = teaconst.Version
} }
this.Data["faviconFileId"] = config.FaviconFileId
this.Show() this.Show()
} }

View File

@@ -16,30 +16,121 @@ func (this *IndexAction) Init() {
this.SecondMenu("list") this.SecondMenu("list")
} }
func (this *IndexAction) RunGet(params struct{}) { func (this *IndexAction) RunGet(params struct {
countResp, err := this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{ Type string
AdminId: this.AdminId(), Keyword string
UserId: 0, }) {
}) this.Data["type"] = params.Type
if err != nil { this.Data["keyword"] = params.Keyword
this.ErrorPage(err)
return
}
count := countResp.Count
page := this.NewPage(count)
this.Data["page"] = page.AsHTML()
tasksResp, err := this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{ countAll := int64(0)
AdminId: this.AdminId(), countAvailable := int64(0)
UserId: 0, countExpired := int64(0)
Offset: page.Offset, count7Days := int64(0)
Size: page.Size, count30Days := int64(0)
})
// 计算数量
{
// all
resp, err := this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
Keyword: params.Keyword,
})
if err != nil {
this.ErrorPage(err)
return
}
countAll = resp.Count
// available
resp, err = this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
IsAvailable: true,
Keyword: params.Keyword,
})
if err != nil {
this.ErrorPage(err)
return
}
countAvailable = resp.Count
// expired
resp, err = this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
IsExpired: true,
Keyword: params.Keyword,
})
if err != nil {
this.ErrorPage(err)
return
}
countExpired = resp.Count
// expire in 7 days
resp, err = this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
ExpiringDays: 7,
Keyword: params.Keyword,
})
if err != nil {
this.ErrorPage(err)
return
}
count7Days = resp.Count
// expire in 30 days
resp, err = this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
ExpiringDays: 30,
Keyword: params.Keyword,
})
if err != nil {
this.ErrorPage(err)
return
}
count30Days = resp.Count
}
this.Data["countAll"] = countAll
this.Data["countAvailable"] = countAvailable
this.Data["countExpired"] = countExpired
this.Data["count7Days"] = count7Days
this.Data["count30Days"] = count30Days
// 分页
var page *actionutils.Page
var tasksResp *pb.ListEnabledACMETasksResponse
var err error
switch params.Type {
case "":
page = this.NewPage(countAll)
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
})
case "available":
page = this.NewPage(countAvailable)
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{IsAvailable: true, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
case "expired":
page = this.NewPage(countExpired)
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{IsExpired: true, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
case "7days":
page = this.NewPage(count7Days)
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{ExpiringDays: 7, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
case "30days":
page = this.NewPage(count30Days)
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{ExpiringDays: 30, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
default:
page = this.NewPage(countAll)
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{
Keyword: params.Keyword,
Offset: page.Offset,
Size: page.Size,
})
}
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
this.Data["page"] = page.AsHTML()
taskMaps := []maps.Map{} taskMaps := []maps.Map{}
for _, task := range tasksResp.AcmeTasks { for _, task := range tasksResp.AcmeTasks {
if task.AcmeUser == nil { if task.AcmeUser == nil {

View File

@@ -138,6 +138,8 @@ func (this *IndexAction) RunGet(params struct {
page = this.NewPage(countAll) page = this.NewPage(countAll)
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{ listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
Keyword: params.Keyword, Keyword: params.Keyword,
Offset: page.Offset,
Size: page.Size,
}) })
} }
if err != nil { if err != nil {

View File

@@ -6,6 +6,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
) )
@@ -28,7 +29,8 @@ func (this *CreatePopupAction) RunPost(params struct {
Type string Type string
// file // file
FileDir string FileDir string
FileMemoryCapacityJSON []byte
CapacityJSON []byte CapacityJSON []byte
MaxSizeJSON []byte MaxSizeJSON []byte
@@ -50,8 +52,21 @@ func (this *CreatePopupAction) RunPost(params struct {
params.Must. params.Must.
Field("fileDir", params.FileDir). Field("fileDir", params.FileDir).
Require("请输入缓存目录") Require("请输入缓存目录")
memoryCapacity := &shared.SizeCapacity{}
if len(params.FileMemoryCapacityJSON) > 0 {
err := json.Unmarshal(params.FileMemoryCapacityJSON, memoryCapacity)
if err != nil {
this.ErrorPage(err)
return
}
}
options = &serverconfigs.HTTPFileCacheStorage{ options = &serverconfigs.HTTPFileCacheStorage{
Dir: params.FileDir, Dir: params.FileDir,
MemoryPolicy: &serverconfigs.HTTPCachePolicy{
Capacity: memoryCapacity,
},
} }
case serverconfigs.CachePolicyStorageMemory: case serverconfigs.CachePolicyStorageMemory:
options = &serverconfigs.HTTPMemoryCacheStorage{ options = &serverconfigs.HTTPMemoryCacheStorage{

View File

@@ -6,6 +6,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
) )
@@ -52,7 +53,8 @@ func (this *UpdateAction) RunPost(params struct {
Type string Type string
// file // file
FileDir string FileDir string
FileMemoryCapacityJSON []byte
CapacityJSON []byte CapacityJSON []byte
MaxSizeJSON []byte MaxSizeJSON []byte
@@ -74,8 +76,21 @@ func (this *UpdateAction) RunPost(params struct {
params.Must. params.Must.
Field("fileDir", params.FileDir). Field("fileDir", params.FileDir).
Require("请输入缓存目录") Require("请输入缓存目录")
memoryCapacity := &shared.SizeCapacity{}
if len(params.FileMemoryCapacityJSON) > 0 {
err := json.Unmarshal(params.FileMemoryCapacityJSON, memoryCapacity)
if err != nil {
this.ErrorPage(err)
return
}
}
options = &serverconfigs.HTTPFileCacheStorage{ options = &serverconfigs.HTTPFileCacheStorage{
Dir: params.FileDir, Dir: params.FileDir,
MemoryPolicy: &serverconfigs.HTTPCachePolicy{
Capacity: memoryCapacity,
},
} }
case serverconfigs.CachePolicyStorageMemory: case serverconfigs.CachePolicyStorageMemory:
options = &serverconfigs.HTTPMemoryCacheStorage{ options = &serverconfigs.HTTPMemoryCacheStorage{
@@ -91,14 +106,14 @@ func (this *UpdateAction) RunPost(params struct {
} }
_, err = this.RPC().HTTPCachePolicyRPC().UpdateHTTPCachePolicy(this.AdminContext(), &pb.UpdateHTTPCachePolicyRequest{ _, err = this.RPC().HTTPCachePolicyRPC().UpdateHTTPCachePolicy(this.AdminContext(), &pb.UpdateHTTPCachePolicyRequest{
HttpCachePolicyId: params.CachePolicyId, HttpCachePolicyId: params.CachePolicyId,
IsOn: params.IsOn, IsOn: params.IsOn,
Name: params.Name, Name: params.Name,
Description: params.Description, Description: params.Description,
CapacityJSON: params.CapacityJSON, CapacityJSON: params.CapacityJSON,
MaxKeys: params.MaxKeys, MaxKeys: params.MaxKeys,
MaxSizeJSON: params.MaxSizeJSON, MaxSizeJSON: params.MaxSizeJSON,
Type: params.Type, Type: params.Type,
OptionsJSON: optionsJSON, OptionsJSON: optionsJSON,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)

View File

@@ -48,6 +48,7 @@ func init() {
GetPost("/ipadmin/createIPPopup", new(ipadmin.CreateIPPopupAction)). GetPost("/ipadmin/createIPPopup", new(ipadmin.CreateIPPopupAction)).
GetPost("/ipadmin/updateIPPopup", new(ipadmin.UpdateIPPopupAction)). GetPost("/ipadmin/updateIPPopup", new(ipadmin.UpdateIPPopupAction)).
Post("/ipadmin/deleteIP", new(ipadmin.DeleteIPAction)). Post("/ipadmin/deleteIP", new(ipadmin.DeleteIPAction)).
GetPost("/ipadmin/test", new(ipadmin.TestAction)).
EndAll() EndAll()
}) })

View File

@@ -21,7 +21,7 @@ func (this *CreateIPPopupAction) RunGet(params struct {
FirewallPolicyId int64 FirewallPolicyId int64
Type string Type string
}) { }) {
this.Data["type"] = params.Type this.Data["listType"] = params.Type
listId, err := dao.SharedHTTPFirewallPolicyDAO.FindEnabledPolicyIPListIdWithType(this.AdminContext(), params.FirewallPolicyId, params.Type) listId, err := dao.SharedHTTPFirewallPolicyDAO.FindEnabledPolicyIPListIdWithType(this.AdminContext(), params.FirewallPolicyId, params.Type)
if err != nil { if err != nil {
@@ -40,41 +40,57 @@ func (this *CreateIPPopupAction) RunPost(params struct {
IpTo string IpTo string
ExpiredAt int64 ExpiredAt int64
Reason string Reason string
Type string
EventLevel string
Must *actions.Must Must *actions.Must
CSRF *actionutils.CSRF CSRF *actionutils.CSRF
}) { }) {
// TODO 校验ListId所属用户 // TODO 校验ListId所属用户
params.Must. switch params.Type {
Field("ipFrom", params.IpFrom). case "ipv4":
Require("请输入开始IP") params.Must.
Field("ipFrom", params.IpFrom).
Require("请输入开始IP")
// 校验IP格式ipFrom/ipTo // 校验IP格式ipFrom/ipTo
ipFromLong := utils.IP2Long(params.IpFrom) var ipFromLong uint64
if len(params.IpFrom) > 0 { if !utils.IsIPv4(params.IpFrom) {
if ipFromLong == 0 {
this.Fail("请输入正确的开始IP") this.Fail("请输入正确的开始IP")
} }
} ipFromLong = utils.IP2Long(params.IpFrom)
ipToLong := utils.IP2Long(params.IpTo) var ipToLong uint64
if len(params.IpTo) > 0 { if len(params.IpTo) > 0 && !utils.IsIPv4(params.IpTo) {
if ipToLong == 0 { ipToLong = utils.IP2Long(params.IpTo)
this.Fail("请输入正确的结束IP") this.Fail("请输入正确的结束IP")
} }
}
if ipFromLong > 0 && ipToLong > 0 && ipFromLong > ipToLong { if ipFromLong > 0 && ipToLong > 0 && ipFromLong > ipToLong {
params.IpTo, params.IpFrom = params.IpFrom, params.IpTo 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{ createResp, err := this.RPC().IPItemRPC().CreateIPItem(this.AdminContext(), &pb.CreateIPItemRequest{
IpListId: params.ListId, IpListId: params.ListId,
IpFrom: params.IpFrom, IpFrom: params.IpFrom,
IpTo: params.IpTo, IpTo: params.IpTo,
ExpiredAt: params.ExpiredAt, ExpiredAt: params.ExpiredAt,
Reason: params.Reason, Reason: params.Reason,
Type: params.Type,
EventLevel: params.EventLevel,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)

View File

@@ -4,6 +4,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time" timeutil "github.com/iwind/TeaGo/utils/time"
) )
@@ -58,11 +59,13 @@ func (this *ListsAction) RunGet(params struct {
} }
itemMaps = append(itemMaps, maps.Map{ itemMaps = append(itemMaps, maps.Map{
"id": item.Id, "id": item.Id,
"ipFrom": item.IpFrom, "ipFrom": item.IpFrom,
"ipTo": item.IpTo, "ipTo": item.IpTo,
"expiredTime": expiredTime, "expiredTime": expiredTime,
"reason": item.Reason, "reason": item.Reason,
"type": item.Type,
"eventLevelName": firewallconfigs.FindFirewallEventLevelName(item.EventLevel),
}) })
} }
this.Data["items"] = itemMaps this.Data["items"] = itemMaps

View File

@@ -0,0 +1,87 @@
package ipadmin
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type TestAction struct {
actionutils.ParentAction
}
func (this *TestAction) Init() {
this.Nav("", "", "ipadmin")
}
func (this *TestAction) RunGet(params struct {
FirewallPolicyId int64
}) {
this.Data["subMenuItem"] = "test"
this.Show()
}
func (this *TestAction) RunPost(params struct {
FirewallPolicyId int64
Ip string
Must *actions.Must
}) {
resp, err := this.RPC().HTTPFirewallPolicyRPC().CheckHTTPFirewallPolicyIPStatus(this.AdminContext(), &pb.CheckHTTPFirewallPolicyIPStatusRequest{
HttpFirewallPolicyId: params.FirewallPolicyId,
Ip: params.Ip,
})
if err != nil {
this.ErrorPage(err)
return
}
resultMap := maps.Map{
"isDone": true,
"isFound": resp.IsFound,
"isOk": resp.IsOk,
"error": resp.Error,
"isAllowed": resp.IsAllowed,
}
if resp.IpList != nil {
resultMap["list"] = maps.Map{
"id": resp.IpList.Id,
"name": resp.IpList.Name,
}
}
if resp.IpItem != nil {
resultMap["item"] = maps.Map{
"id": resp.IpItem.Id,
"ipFrom": resp.IpItem.IpFrom,
"ipTo": resp.IpItem.IpTo,
"reason": resp.IpItem.Reason,
"expiredAt": resp.IpItem.ExpiredAt,
"expiredTime": timeutil.FormatTime("Y-m-d H:i:s", resp.IpItem.ExpiredAt),
"type": resp.IpItem.Type,
"eventLevelName": firewallconfigs.FindFirewallEventLevelName(resp.IpItem.EventLevel),
}
}
if resp.RegionCountry != nil {
resultMap["country"] = maps.Map{
"id": resp.RegionCountry.Id,
"name": resp.RegionCountry.Name,
}
}
if resp.RegionProvince != nil {
resultMap["province"] = maps.Map{
"id": resp.RegionProvince.Id,
"name": resp.RegionProvince.Name,
}
}
this.Data["result"] = resultMap
this.Success()
}

View File

@@ -32,13 +32,17 @@ func (this *UpdateIPPopupAction) RunGet(params struct {
} }
this.Data["item"] = maps.Map{ this.Data["item"] = maps.Map{
"id": item.Id, "id": item.Id,
"ipFrom": item.IpFrom, "ipFrom": item.IpFrom,
"ipTo": item.IpTo, "ipTo": item.IpTo,
"expiredAt": item.ExpiredAt, "expiredAt": item.ExpiredAt,
"reason": item.Reason, "reason": item.Reason,
"type": item.Type,
"eventLevel": item.EventLevel,
} }
this.Data["type"] = item.Type
this.Show() this.Show()
} }
@@ -46,10 +50,12 @@ func (this *UpdateIPPopupAction) RunPost(params struct {
FirewallPolicyId int64 FirewallPolicyId int64
ItemId int64 ItemId int64
IpFrom string IpFrom string
IpTo string IpTo string
ExpiredAt int64 ExpiredAt int64
Reason string Reason string
Type string
EventLevel string
Must *actions.Must Must *actions.Must
CSRF *actionutils.CSRF CSRF *actionutils.CSRF
@@ -59,35 +65,49 @@ func (this *UpdateIPPopupAction) RunPost(params struct {
// TODO 校验ItemId所属用户 // TODO 校验ItemId所属用户
params.Must. switch params.Type {
Field("ipFrom", params.IpFrom). case "ipv4":
Require("请输入开始IP") params.Must.
Field("ipFrom", params.IpFrom).
Require("请输入开始IP")
// 校验IP格式ipFrom/ipTo // 校验IP格式ipFrom/ipTo
ipFromLong := utils.IP2Long(params.IpFrom) var ipFromLong uint64
if len(params.IpFrom) > 0 { if !utils.IsIPv4(params.IpFrom) {
if ipFromLong == 0 {
this.Fail("请输入正确的开始IP") this.Fail("请输入正确的开始IP")
} }
} ipFromLong = utils.IP2Long(params.IpFrom)
ipToLong := utils.IP2Long(params.IpTo) var ipToLong uint64
if len(params.IpTo) > 0 { if len(params.IpTo) > 0 && !utils.IsIPv4(params.IpTo) {
if ipToLong == 0 { ipToLong = utils.IP2Long(params.IpTo)
this.Fail("请输入正确的结束IP") this.Fail("请输入正确的结束IP")
} }
}
if ipFromLong > 0 && ipToLong > 0 && ipFromLong > ipToLong { if ipFromLong > 0 && ipToLong > 0 && ipFromLong > ipToLong {
params.IpTo, params.IpFrom = params.IpFrom, params.IpTo 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"
} }
_, err := this.RPC().IPItemRPC().UpdateIPItem(this.AdminContext(), &pb.UpdateIPItemRequest{ _, err := this.RPC().IPItemRPC().UpdateIPItem(this.AdminContext(), &pb.UpdateIPItemRequest{
IpItemId: params.ItemId, IpItemId: params.ItemId,
IpFrom: params.IpFrom, IpFrom: params.IpFrom,
IpTo: params.IpTo, IpTo: params.IpTo,
ExpiredAt: params.ExpiredAt, ExpiredAt: params.ExpiredAt,
Reason: params.Reason, Reason: params.Reason,
Type: params.Type,
EventLevel: params.EventLevel,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)

View File

@@ -10,6 +10,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
"strings"
) )
type CreateAction struct { type CreateAction struct {
@@ -215,6 +216,22 @@ func (this *CreateAction) RunPost(params struct {
if err != nil { if err != nil {
this.Fail("域名解析失败:" + err.Error()) this.Fail("域名解析失败:" + err.Error())
} }
// 检查域名是否已经存在
allServerNames := serverconfigs.PlainServerNames(serverNames)
if len(allServerNames) > 0 {
dupResp, err := this.RPC().ServerRPC().CheckServerNameDuplicationInNodeCluster(this.AdminContext(), &pb.CheckServerNameDuplicationInNodeClusterRequest{
ServerNames: allServerNames,
NodeClusterId: params.ClusterId,
})
if err != nil {
this.ErrorPage(err)
return
}
if len(dupResp.DuplicatedServerNames) > 0 {
this.Fail("域名 " + strings.Join(dupResp.DuplicatedServerNames, ", ") + " 已经被其他服务所占用,不能重复使用")
}
}
} }
// 源站地址 // 源站地址

View File

@@ -86,6 +86,7 @@ func (this *SettingAction) RunPost(params struct {
RequestURI: reverseProxyConfig.RequestURI, RequestURI: reverseProxyConfig.RequestURI,
StripPrefix: reverseProxyConfig.StripPrefix, StripPrefix: reverseProxyConfig.StripPrefix,
AutoFlush: reverseProxyConfig.AutoFlush, AutoFlush: reverseProxyConfig.AutoFlush,
AddHeaders: reverseProxyConfig.AddHeaders,
}) })
this.Success() this.Success()

View File

@@ -7,6 +7,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
"regexp" "regexp"
"strings" "strings"
@@ -33,6 +34,9 @@ func (this *AddPopupAction) RunGet(params struct {
serverType := serverTypeResp.Type serverType := serverTypeResp.Type
this.Data["serverType"] = serverType this.Data["serverType"] = serverType
// 是否为HTTP
this.Data["isHTTP"] = serverType == "httpProxy" || serverType == "httpWeb"
this.Show() this.Show()
} }
@@ -44,8 +48,15 @@ func (this *AddPopupAction) RunPost(params struct {
Protocol string Protocol string
Addr string Addr string
Name string Name string
Description string
IsOn bool ConnTimeout int
ReadTimeout int
MaxConns int32
MaxIdleConns int32
IdleTimeout int
Description string
IsOn bool
Must *actions.Must Must *actions.Must
}) { }) {
@@ -61,6 +72,33 @@ func (this *AddPopupAction) RunPost(params struct {
host := addr[:portIndex] host := addr[:portIndex]
port := addr[portIndex+1:] port := addr[portIndex+1:]
connTimeoutJSON, err := (&shared.TimeDuration{
Count: int64(params.ConnTimeout),
Unit: shared.TimeDurationUnitSecond,
}).AsJSON()
if err != nil {
this.ErrorPage(err)
return
}
readTimeoutJSON, err := (&shared.TimeDuration{
Count: int64(params.ReadTimeout),
Unit: shared.TimeDurationUnitSecond,
}).AsJSON()
if err != nil {
this.ErrorPage(err)
return
}
idleTimeoutJSON, err := (&shared.TimeDuration{
Count: int64(params.IdleTimeout),
Unit: shared.TimeDurationUnitSecond,
}).AsJSON()
if err != nil {
this.ErrorPage(err)
return
}
createResp, err := this.RPC().OriginRPC().CreateOrigin(this.AdminContext(), &pb.CreateOriginRequest{ createResp, err := this.RPC().OriginRPC().CreateOrigin(this.AdminContext(), &pb.CreateOriginRequest{
Name: params.Name, Name: params.Name,
Addr: &pb.NetworkAddress{ Addr: &pb.NetworkAddress{
@@ -68,9 +106,14 @@ func (this *AddPopupAction) RunPost(params struct {
Host: host, Host: host,
PortRange: port, PortRange: port,
}, },
Description: params.Description, Description: params.Description,
Weight: params.Weight, Weight: params.Weight,
IsOn: params.IsOn, IsOn: params.IsOn,
ConnTimeoutJSON: connTimeoutJSON,
ReadTimeoutJSON: readTimeoutJSON,
IdleTimeoutJSON: idleTimeoutJSON,
MaxConns: params.MaxConns,
MaxIdleConns: params.MaxIdleConns,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)

View File

@@ -6,8 +6,10 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"regexp" "regexp"
"strings" "strings"
) )
@@ -39,6 +41,10 @@ func (this *UpdatePopupAction) RunGet(params struct {
return return
} }
this.Data["serverType"] = serverTypeResp.Type this.Data["serverType"] = serverTypeResp.Type
serverType := serverTypeResp.Type
// 是否为HTTP
this.Data["isHTTP"] = serverType == "httpProxy" || serverType == "httpWeb"
// 源站信息 // 源站信息
originResp, err := this.RPC().OriginRPC().FindEnabledOriginConfig(this.AdminContext(), &pb.FindEnabledOriginConfigRequest{OriginId: params.OriginId}) originResp, err := this.RPC().OriginRPC().FindEnabledOriginConfig(this.AdminContext(), &pb.FindEnabledOriginConfigRequest{OriginId: params.OriginId})
@@ -54,14 +60,34 @@ func (this *UpdatePopupAction) RunGet(params struct {
return return
} }
connTimeout := 0
readTimeout := 0
idleTimeout := 0
if config.ConnTimeout != nil {
connTimeout = types.Int(config.ConnTimeout.Count)
}
if config.ReadTimeout != nil {
readTimeout = types.Int(config.ReadTimeout.Count)
}
if config.IdleTimeout != nil {
idleTimeout = types.Int(config.IdleTimeout.Count)
}
this.Data["origin"] = maps.Map{ this.Data["origin"] = maps.Map{
"id": config.Id, "id": config.Id,
"protocol": config.Addr.Protocol, "protocol": config.Addr.Protocol,
"addr": config.Addr.Host + ":" + config.Addr.PortRange, "addr": config.Addr.Host + ":" + config.Addr.PortRange,
"weight": config.Weight, "weight": config.Weight,
"name": config.Name, "name": config.Name,
"description": config.Description, "description": config.Description,
"isOn": config.IsOn, "isOn": config.IsOn,
"connTimeout": connTimeout,
"readTimeout": readTimeout,
"idleTimeout": idleTimeout,
"maxConns": config.MaxConns,
"maxIdleConns": config.MaxIdleConns,
} }
this.Show() this.Show()
@@ -76,8 +102,15 @@ func (this *UpdatePopupAction) RunPost(params struct {
Addr string Addr string
Weight int32 Weight int32
Name string Name string
Description string
IsOn bool ConnTimeout int
ReadTimeout int
MaxConns int32
MaxIdleConns int32
IdleTimeout int
Description string
IsOn bool
Must *actions.Must Must *actions.Must
}) { }) {
@@ -93,7 +126,34 @@ func (this *UpdatePopupAction) RunPost(params struct {
host := addr[:portIndex] host := addr[:portIndex]
port := addr[portIndex+1:] port := addr[portIndex+1:]
_, err := this.RPC().OriginRPC().UpdateOrigin(this.AdminContext(), &pb.UpdateOriginRequest{ connTimeoutJSON, err := (&shared.TimeDuration{
Count: int64(params.ConnTimeout),
Unit: shared.TimeDurationUnitSecond,
}).AsJSON()
if err != nil {
this.ErrorPage(err)
return
}
readTimeoutJSON, err := (&shared.TimeDuration{
Count: int64(params.ReadTimeout),
Unit: shared.TimeDurationUnitSecond,
}).AsJSON()
if err != nil {
this.ErrorPage(err)
return
}
idleTimeoutJSON, err := (&shared.TimeDuration{
Count: int64(params.IdleTimeout),
Unit: shared.TimeDurationUnitSecond,
}).AsJSON()
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().OriginRPC().UpdateOrigin(this.AdminContext(), &pb.UpdateOriginRequest{
OriginId: params.OriginId, OriginId: params.OriginId,
Name: params.Name, Name: params.Name,
Addr: &pb.NetworkAddress{ Addr: &pb.NetworkAddress{
@@ -101,9 +161,14 @@ func (this *UpdatePopupAction) RunPost(params struct {
Host: host, Host: host,
PortRange: port, PortRange: port,
}, },
Description: params.Description, Description: params.Description,
Weight: params.Weight, Weight: params.Weight,
IsOn: params.IsOn, IsOn: params.IsOn,
ConnTimeoutJSON: connTimeoutJSON,
ReadTimeoutJSON: readTimeoutJSON,
IdleTimeoutJSON: idleTimeoutJSON,
MaxConns: params.MaxConns,
MaxIdleConns: params.MaxIdleConns,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)

View File

@@ -24,9 +24,12 @@ func (this *CreatePopupAction) RunGet(params struct {
} }
func (this *CreatePopupAction) RunPost(params struct { func (this *CreatePopupAction) RunPost(params struct {
BeforeURL string BeforeURL string
AfterURL string AfterURL string
Status int MatchPrefix bool
KeepRequestURI bool
Status int
Must *actions.Must Must *actions.Must
CSRF *actionutils.CSRF CSRF *actionutils.CSRF
@@ -69,10 +72,12 @@ func (this *CreatePopupAction) RunPost(params struct {
Gte(0, "请选择正确的跳转状态码") Gte(0, "请选择正确的跳转状态码")
this.Data["redirect"] = maps.Map{ this.Data["redirect"] = maps.Map{
"status": params.Status, "status": params.Status,
"beforeURL": params.BeforeURL, "beforeURL": params.BeforeURL,
"afterURL": params.AfterURL, "afterURL": params.AfterURL,
"isOn": true, "matchPrefix": params.MatchPrefix,
"keepRequestURI": params.KeepRequestURI,
"isOn": true,
} }
this.Success() this.Success()

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