Compare commits

...

45 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
438 changed files with 64254 additions and 379 deletions

View File

@@ -9,7 +9,7 @@
* `高扩展性` - 可以自由扩展新的节点,支持亿级数据
## 文档
[点这里查看文档](https://github.com/TeaOSLab/EdgeDocs)
[点这里查看文档](http://edge.teaos.cn/docs)
## 架构
![架构](doc/architect-zh.jpg)

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

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

View File

@@ -9,7 +9,7 @@ import (
"path/filepath"
)
// API配置
// APIConfig API配置
type APIConfig struct {
RPC struct {
Endpoints []string `yaml:"endpoints"`
@@ -18,7 +18,7 @@ type APIConfig struct {
Secret string `yaml:"secret"`
}
// 加载API配置
// LoadAPIConfig 加载API配置
func LoadAPIConfig() (*APIConfig, error) {
// 候选文件
localFile := Tea.ConfigFile("api.yaml")
@@ -59,7 +59,7 @@ func LoadAPIConfig() (*APIConfig, error) {
return config, nil
}
// 写入API配置
// WriteFile 写入API配置
func (this *APIConfig) WriteFile(path string) error {
data, err := yaml.Marshal(this)
if err != nil {

View File

@@ -1,7 +1,7 @@
package teaconst
const (
Version = "0.0.10"
Version = "0.0.13"
ProductName = "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()
}
// 实现守护进程
// Daemon 实现守护进程
func (this *AdminNode) Daemon() {
path := os.TempDir() + "/edge-admin.sock"
isDebug := lists.ContainsString(os.Args, "debug")
@@ -132,7 +132,7 @@ func (this *AdminNode) Daemon() {
}
}
// 安装系统服务
// InstallSystemService 安装系统服务
func (this *AdminNode) InstallSystemService() error {
shortName := teaconst.SystemdServiceName
@@ -149,7 +149,7 @@ func (this *AdminNode) InstallSystemService() error {
return nil
}
// 添加子PID
// AddSubPID 添加子PID
func (this *AdminNode) AddSubPID(pid int) {
this.subPIDs = append(this.subPIDs, pid)
}

View File

@@ -139,6 +139,10 @@ func (this *RPCClient) DBNodeRPC() pb.DBNodeServiceClient {
return pb.NewDBNodeServiceClient(this.pickConn())
}
func (this *RPCClient) MonitorNodeRPC() pb.MonitorNodeServiceClient {
return pb.NewMonitorNodeServiceClient(this.pickConn())
}
func (this *RPCClient) DBRPC() pb.DBServiceClient {
return pb.NewDBServiceClient(this.pickConn())
}
@@ -203,7 +207,7 @@ func (this *RPCClient) HTTPRewriteRuleRPC() pb.HTTPRewriteRuleServiceClient {
return pb.NewHTTPRewriteRuleServiceClient(this.pickConn())
}
// 访问日志
// HTTPAccessLogRPC 访问日志
func (this *RPCClient) HTTPAccessLogRPC() pb.HTTPAccessLogServiceClient {
return pb.NewHTTPAccessLogServiceClient(this.pickConn())
}
@@ -224,6 +228,34 @@ func (this *RPCClient) MessageRPC() pb.MessageServiceClient {
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 {
return pb.NewIPLibraryServiceClient(this.pickConn())
}
@@ -296,7 +328,15 @@ func (this *RPCClient) NodeTaskRPC() pb.NodeTaskServiceClient {
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 {
ctx := context.Background()
m := maps.Map{
@@ -319,7 +359,7 @@ func (this *RPCClient) Context(adminId int64) context.Context {
return ctx
}
// 构造API上下文
// APIContext 构造API上下文
func (this *RPCClient) APIContext(apiNodeId int64) context.Context {
ctx := context.Background()
m := maps.Map{
@@ -342,6 +382,12 @@ func (this *RPCClient) APIContext(apiNodeId int64) context.Context {
return ctx
}
// UpdateConfig 修改配置
func (this *RPCClient) UpdateConfig(config *configs.APIConfig) error {
this.apiConfig = config
return this.init()
}
// 初始化
func (this *RPCClient) init() error {
// 重新连接
@@ -369,6 +415,8 @@ func (this *RPCClient) init() error {
if len(conns) == 0 {
return errors.New("[RPC]no available endpoints")
}
// 这里不需要加锁因为会和pickConn冲突
this.conns = conns
return nil
}

View File

@@ -6,7 +6,7 @@ import (
var isConfigured bool
// 判断系统是否已经配置过
// IsConfigured 判断系统是否已经配置过
func IsConfigured() bool {
if isConfigured {
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

@@ -35,7 +35,7 @@ func FormatBits(bits int64) string {
} 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/10001000)
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"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/logs"
"net/http"
"os"
"path/filepath"
"reflect"
@@ -25,7 +26,21 @@ func FailPage(action actions.ActionWrapper, err error) {
if err != nil {
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,
"isOn": admin.IsOn,
"isSuper": admin.IsSuper,
"canLogin": admin.CanLogin,
}
// 权限

View File

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

View File

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

View File

@@ -18,6 +18,7 @@ func init() {
Post("/delete", new(DeleteAction)).
Get("/admin", new(AdminAction)).
Get("/otpQrcode", new(OtpQrcodeAction)).
Post("/options", new(OptionsAction)).
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,
"isOn": admin.IsOn,
"isSuper": admin.IsSuper,
"canLogin": admin.CanLogin,
"otpLoginIsOn": otpLoginIsOn,
}
@@ -76,6 +77,7 @@ func (this *UpdateAction) RunPost(params struct {
ModuleCodes []string
IsOn bool
IsSuper bool
CanLogin bool
// OTP
OtpOn bool
@@ -139,6 +141,7 @@ func (this *UpdateAction) RunPost(params struct {
ModulesJSON: modulesJSON,
IsSuper: params.IsSuper,
IsOn: params.IsOn,
CanLogin: params.CanLogin,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -36,6 +36,15 @@ func (this *IndexAction) RunGet(params struct {
this.Data["activeState"] = params.ActiveState
this.Data["keyword"] = params.Keyword
countAllResp, err := this.RPC().NodeRPC().CountAllEnabledNodesMatch(this.AdminContext(), &pb.CountAllEnabledNodesMatchRequest{
NodeClusterId: params.ClusterId,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["countAll"] = countAllResp.Count
countResp, err := this.RPC().NodeRPC().CountAllEnabledNodesMatch(this.AdminContext(), &pb.CountAllEnabledNodesMatchRequest{
NodeClusterId: params.ClusterId,
GroupId: params.GroupId,

View File

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

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

@@ -43,6 +43,9 @@ func (this *CreatePopupAction) RunPost(params struct {
// http api
HttpAPIURL string
// html
HtmlContent string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
@@ -90,6 +93,13 @@ func (this *CreatePopupAction) RunPost(params struct {
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 + "'暂时不支持")
}

View File

@@ -72,6 +72,9 @@ func (this *UpdatePopupAction) RunPost(params struct {
// http api
HttpAPIURL string
// HTML内容
HtmlContent string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
@@ -119,6 +122,13 @@ func (this *UpdatePopupAction) RunPost(params struct {
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 + "'暂时不支持")
}

View File

@@ -1,4 +1,4 @@
package settings
package health
import (
"encoding/json"
@@ -9,16 +9,16 @@ import (
"github.com/iwind/TeaGo/actions"
)
type HealthAction struct {
type IndexAction struct {
actionutils.ParentAction
}
func (this *HealthAction) Init() {
func (this *IndexAction) Init() {
this.Nav("", "setting", "")
this.SecondMenu("health")
}
func (this *HealthAction) RunGet(params struct {
func (this *IndexAction) RunGet(params struct {
ClusterId int64
}) {
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()
}
func (this *HealthAction) RunPost(params struct {
func (this *IndexAction) RunPost(params struct {
ClusterId int64
HealthCheckJSON []byte
Must *actions.Must

View File

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

View File

@@ -5,6 +5,8 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/cache"
"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/toa"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/waf"
@@ -22,8 +24,8 @@ func init() {
GetPost("", new(IndexAction)).
// 健康检查
GetPost("/health", new(HealthAction)).
GetPost("/healthRunPopup", new(HealthRunPopupAction)).
GetPost("/health", new(health.IndexAction)).
GetPost("/health/runPopup", new(health.RunPopupAction)).
// 缓存
GetPost("/cache", new(cache.IndexAction)).
@@ -35,6 +37,12 @@ func init() {
Prefix("/clusters/cluster/settings/dns").
GetPost("", new(dns.IndexAction)).
// 消息
Prefix("/clusters/cluster/settings/message").
GetPost("", new(message.IndexAction)).
Get("/selectReceiverPopup", new(message.SelectReceiverPopupAction)).
Post("/selectedReceivers", new(message.SelectedReceiversAction)).
// TOA
Prefix("/clusters/cluster/settings/toa").
GetPost("", new(toa.IndexAction)).

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

@@ -1,11 +1,14 @@
package clusterutils
import (
"encoding/json"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
@@ -86,22 +89,47 @@ func (this *ClusterHelper) createSettingMenu(cluster *pb.NodeCluster, selectedIt
"isActive": selectedItem == "waf",
"isOn": cluster.HttpFirewallPolicyId > 0,
})
items = append(items, maps.Map{
"name": "WAF动作",
"url": "/clusters/cluster/settings/firewall-actions?clusterId=" + clusterId,
"isActive": selectedItem == "firewallAction",
})
items = append(items, maps.Map{
"name": "健康检查",
"url": "/clusters/cluster/settings/health?clusterId=" + clusterId,
"isActive": selectedItem == "health",
})
{
hasActions, _ := this.checkFirewallActions(cluster.Id)
items = append(items, maps.Map{
"name": "WAF动作",
"url": "/clusters/cluster/settings/firewall-actions?clusterId=" + clusterId,
"isActive": selectedItem == "firewallAction",
"isOn": hasActions,
})
}
{
healthCheckIsOn, _ := this.checkHealthCheckIsOn(cluster.Id)
items = append(items, maps.Map{
"name": "健康检查",
"url": "/clusters/cluster/settings/health?clusterId=" + clusterId,
"isActive": selectedItem == "health",
"isOn": healthCheckIsOn,
})
}
items = append(items, maps.Map{
"name": "DNS设置",
"url": "/clusters/cluster/settings/dns?clusterId=" + clusterId,
"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{
"name": "系统服务",
"url": "/clusters/cluster/settings/services?clusterId=" + clusterId,
@@ -114,3 +142,37 @@ func (this *ClusterHelper) createSettingMenu(cluster *pb.NodeCluster, selectedIt
})
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/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
"strings"
)
type GrantAction struct {
@@ -37,7 +38,7 @@ func (this *GrantAction) RunGet(params struct {
"method": grant.Method,
"methodName": grantutils.FindGrantMethodName(grant.Method),
"username": grant.Username,
"password": grant.Password,
"password": strings.Repeat("*", len(grant.Password)),
"privateKey": grant.PrivateKey,
"description": grant.Description,
"su": grant.Su,

View File

@@ -55,6 +55,10 @@ func (this *CreatePopupAction) RunPost(params struct {
ParamApiKey string
ParamApiSecret string
// CloudFlare
CloudFlareAPIKey string
CloudFlareEmail string
// CustomHTTP
ParamCustomHTTPURL string
ParamCustomHTTPSecret string
@@ -97,6 +101,14 @@ func (this *CreatePopupAction) RunPost(params struct {
apiParams["apiKey"] = params.ParamApiKey
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).

View File

@@ -84,6 +84,10 @@ func (this *UpdatePopupAction) RunPost(params struct {
ParamApiKey string
ParamApiSecret string
// CloudFlare
CloudFlareAPIKey string
CloudFlareEmail string
// CustomHTTP
ParamCustomHTTPURL string
ParamCustomHTTPSecret string
@@ -128,6 +132,14 @@ func (this *UpdatePopupAction) RunPost(params struct {
apiParams["apiKey"] = params.ParamApiKey
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).

View File

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

View File

@@ -16,30 +16,121 @@ func (this *IndexAction) Init() {
this.SecondMenu("list")
}
func (this *IndexAction) RunGet(params struct{}) {
countResp, err := this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
AdminId: this.AdminId(),
UserId: 0,
})
if err != nil {
this.ErrorPage(err)
return
}
count := countResp.Count
page := this.NewPage(count)
this.Data["page"] = page.AsHTML()
func (this *IndexAction) RunGet(params struct {
Type string
Keyword string
}) {
this.Data["type"] = params.Type
this.Data["keyword"] = params.Keyword
tasksResp, err := this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{
AdminId: this.AdminId(),
UserId: 0,
Offset: page.Offset,
Size: page.Size,
})
countAll := int64(0)
countAvailable := int64(0)
countExpired := int64(0)
count7Days := int64(0)
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 {
this.ErrorPage(err)
return
}
this.Data["page"] = page.AsHTML()
taskMaps := []maps.Map{}
for _, task := range tasksResp.AcmeTasks {
if task.AcmeUser == nil {

View File

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

View File

@@ -6,6 +6,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
@@ -28,7 +29,8 @@ func (this *CreatePopupAction) RunPost(params struct {
Type string
// file
FileDir string
FileDir string
FileMemoryCapacityJSON []byte
CapacityJSON []byte
MaxSizeJSON []byte
@@ -50,8 +52,21 @@ func (this *CreatePopupAction) RunPost(params struct {
params.Must.
Field("fileDir", params.FileDir).
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{
Dir: params.FileDir,
MemoryPolicy: &serverconfigs.HTTPCachePolicy{
Capacity: memoryCapacity,
},
}
case serverconfigs.CachePolicyStorageMemory:
options = &serverconfigs.HTTPMemoryCacheStorage{

View File

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

View File

@@ -10,6 +10,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"strings"
)
type CreateAction struct {
@@ -215,6 +216,22 @@ func (this *CreateAction) RunPost(params struct {
if err != nil {
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

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

View File

@@ -6,8 +6,10 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"regexp"
"strings"
)
@@ -39,6 +41,10 @@ func (this *UpdatePopupAction) RunGet(params struct {
return
}
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})
@@ -54,14 +60,34 @@ func (this *UpdatePopupAction) RunGet(params struct {
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{
"id": config.Id,
"protocol": config.Addr.Protocol,
"addr": config.Addr.Host + ":" + config.Addr.PortRange,
"weight": config.Weight,
"name": config.Name,
"description": config.Description,
"isOn": config.IsOn,
"id": config.Id,
"protocol": config.Addr.Protocol,
"addr": config.Addr.Host + ":" + config.Addr.PortRange,
"weight": config.Weight,
"name": config.Name,
"description": config.Description,
"isOn": config.IsOn,
"connTimeout": connTimeout,
"readTimeout": readTimeout,
"idleTimeout": idleTimeout,
"maxConns": config.MaxConns,
"maxIdleConns": config.MaxIdleConns,
}
this.Show()
@@ -76,8 +102,15 @@ func (this *UpdatePopupAction) RunPost(params struct {
Addr string
Weight int32
Name string
Description string
IsOn bool
ConnTimeout int
ReadTimeout int
MaxConns int32
MaxIdleConns int32
IdleTimeout int
Description string
IsOn bool
Must *actions.Must
}) {
@@ -93,7 +126,34 @@ func (this *UpdatePopupAction) RunPost(params struct {
host := addr[:portIndex]
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,
Name: params.Name,
Addr: &pb.NetworkAddress{
@@ -101,9 +161,14 @@ func (this *UpdatePopupAction) RunPost(params struct {
Host: host,
PortRange: port,
},
Description: params.Description,
Weight: params.Weight,
IsOn: params.IsOn,
Description: params.Description,
Weight: params.Weight,
IsOn: params.IsOn,
ConnTimeoutJSON: connTimeoutJSON,
ReadTimeoutJSON: readTimeoutJSON,
IdleTimeoutJSON: idleTimeoutJSON,
MaxConns: params.MaxConns,
MaxIdleConns: params.MaxIdleConns,
})
if err != nil {
this.ErrorPage(err)

View File

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

View File

@@ -5,6 +5,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/types"
)
@@ -68,6 +69,33 @@ func (this *SettingAction) RunPost(params struct {
this.Fail("配置校验失败:" + err.Error())
}
if reverseProxyConfig.ConnTimeout == nil {
reverseProxyConfig.ConnTimeout = &shared.TimeDuration{Count: 0, Unit: "second"}
}
connTimeoutJSON, err := json.Marshal(reverseProxyConfig.ConnTimeout)
if err != nil {
this.ErrorPage(err)
return
}
if reverseProxyConfig.ReadTimeout == nil {
reverseProxyConfig.ReadTimeout = &shared.TimeDuration{Count: 0, Unit: "second"}
}
readTimeoutJSON, err := json.Marshal(reverseProxyConfig.ReadTimeout)
if err != nil {
this.ErrorPage(err)
return
}
if reverseProxyConfig.IdleTimeout == nil {
reverseProxyConfig.IdleTimeout = &shared.TimeDuration{Count: 0, Unit: "second"}
}
idleTimeoutJSON, err := json.Marshal(reverseProxyConfig.IdleTimeout)
if err != nil {
this.ErrorPage(err)
return
}
// 设置是否启用
_, err = this.RPC().ServerRPC().UpdateServerReverseProxy(this.AdminContext(), &pb.UpdateServerReverseProxyRequest{
ServerId: params.ServerId,
@@ -87,6 +115,11 @@ func (this *SettingAction) RunPost(params struct {
StripPrefix: reverseProxyConfig.StripPrefix,
AutoFlush: reverseProxyConfig.AutoFlush,
AddHeaders: reverseProxyConfig.AddHeaders,
ConnTimeoutJSON: connTimeoutJSON,
ReadTimeoutJSON: readTimeoutJSON,
IdleTimeoutJSON: idleTimeoutJSON,
MaxConns: types.Int32(reverseProxyConfig.MaxConns),
MaxIdleConns: types.Int32(reverseProxyConfig.MaxIdleConns),
})
this.Success()

View File

@@ -9,6 +9,7 @@ import (
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
"strings"
)
// 域名管理
@@ -74,6 +75,34 @@ func (this *IndexAction) RunPost(params struct {
this.Fail("域名解析失败:" + err.Error())
}
serverResp, err := this.RPC().ServerRPC().FindEnabledUserServerBasic(this.AdminContext(), &pb.FindEnabledUserServerBasicRequest{ServerId: params.ServerId})
if err != nil {
this.ErrorPage(err)
return
}
if serverResp.Server == nil || serverResp.Server.NodeCluster == nil {
this.NotFound("server", params.ServerId)
return
}
clusterId := serverResp.Server.NodeCluster.Id
// 检查域名是否已经存在
allServerNames := serverconfigs.PlainServerNames(serverNames)
if len(allServerNames) > 0 {
dupResp, err := this.RPC().ServerRPC().CheckServerNameDuplicationInNodeCluster(this.AdminContext(), &pb.CheckServerNameDuplicationInNodeClusterRequest{
ServerNames: allServerNames,
NodeClusterId: clusterId,
ExcludeServerId: params.ServerId,
})
if err != nil {
this.ErrorPage(err)
return
}
if len(dupResp.DuplicatedServerNames) > 0 {
this.Fail("域名 " + strings.Join(dupResp.DuplicatedServerNames, ", ") + " 已经被其他服务所占用,不能重复使用")
}
}
_, err = this.RPC().ServerRPC().UpdateServerNames(this.AdminContext(), &pb.UpdateServerNamesRequest{
ServerId: params.ServerId,
ServerNamesJSON: []byte(params.ServerNames),

View File

@@ -75,7 +75,7 @@ func (this *ClientsAction) RunGet(params struct {
"count": stat.Count,
"system": maps.Map{
"id": stat.ClientSystem.Id,
"name": stat.ClientSystem.Name,
"name": stat.ClientSystem.Name + " " + stat.Version,
},
})
}
@@ -97,7 +97,7 @@ func (this *ClientsAction) RunGet(params struct {
"count": stat.Count,
"browser": maps.Map{
"id": stat.ClientBrowser.Id,
"name": stat.ClientBrowser.Name,
"name": stat.ClientBrowser.Name + " " + stat.Version,
},
})
}

View File

@@ -0,0 +1,70 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package server
import (
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "index")
}
func (this *IndexAction) RunGet(params struct{}) {
keyResp, err := this.RPC().AuthorityKeyRPC().ReadAuthorityKey(this.AdminContext(), &pb.ReadAuthorityKeyRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var keyMap maps.Map = nil
teaconst.IsPlus = false
if keyResp.AuthorityKey != nil {
if len(keyResp.AuthorityKey.MacAddresses) == 0 {
keyResp.AuthorityKey.MacAddresses = []string{}
}
isActive := len(keyResp.AuthorityKey.DayTo) > 0 && keyResp.AuthorityKey.DayTo >= timeutil.Format("Y-m-d")
if isActive {
teaconst.IsPlus = true
}
keyMap = maps.Map{
"dayFrom": keyResp.AuthorityKey.DayFrom,
"dayTo": keyResp.AuthorityKey.DayTo,
"macAddresses": keyResp.AuthorityKey.MacAddresses,
"hostname": keyResp.AuthorityKey.Hostname,
"company": keyResp.AuthorityKey.Company,
"isExpired": !isActive,
"updatedTime": timeutil.FormatTime("Y-m-d H:i:s", keyResp.AuthorityKey.UpdatedAt),
}
}
this.Data["key"] = keyMap
// 检查是否有认证节点,如果没有认证节点,则自动生成一个
countResp, err := this.RPC().AuthorityNodeRPC().CountAllEnabledAuthorityNodes(this.AdminContext(), &pb.CountAllEnabledAuthorityNodesRequest{})
if err != nil {
this.ErrorPage(err)
return
}
if countResp.Count == 0 {
_, err = this.RPC().AuthorityNodeRPC().CreateAuthorityNode(this.AdminContext(), &pb.CreateAuthorityNodeRequest{
Name: "默认节点",
Description: "系统自动生成的默认节点",
IsOn: true,
})
if err != nil {
this.ErrorPage(err)
return
}
}
this.Show()
}

View File

@@ -0,0 +1,19 @@
package server
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/settingutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeSetting)).
Helper(settingutils.NewAdvancedHelper("authority")).
Prefix("/settings/authority").
Get("", new(IndexAction)).
EndAll()
})
}

View File

@@ -0,0 +1,28 @@
package nodes
import (
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"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 {
NodeId int64
}) {
// TODO 检查权限
_, err := this.RPC().AuthorityNodeRPC().DeleteAuthorityNode(this.AdminContext(), &pb.DeleteAuthorityNodeRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
// 创建日志
defer this.CreateLog(oplogs.LevelInfo, "删除认证节点 %d", params.NodeId)
this.Success()
}

View File

@@ -0,0 +1,15 @@
package nodes
import (
"github.com/iwind/TeaGo/actions"
)
type Helper struct {
}
func NewHelper() *Helper {
return &Helper{}
}
func (this *Helper) BeforeAction(action *actions.ActionObject) {
}

View File

@@ -0,0 +1,75 @@
package nodes
import (
"encoding/json"
"fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
"time"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "node", "node")
}
func (this *IndexAction) RunGet(params struct{}) {
countResp, err := this.RPC().AuthorityNodeRPC().CountAllEnabledAuthorityNodes(this.AdminContext(), &pb.CountAllEnabledAuthorityNodesRequest{})
if err != nil {
this.ErrorPage(err)
return
}
count := countResp.Count
page := this.NewPage(count)
this.Data["page"] = page.AsHTML()
nodeMaps := []maps.Map{}
if count > 0 {
nodesResp, err := this.RPC().AuthorityNodeRPC().ListEnabledAuthorityNodes(this.AdminContext(), &pb.ListEnabledAuthorityNodesRequest{
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
for _, node := range nodesResp.Nodes {
// 状态
status := &nodeconfigs.NodeStatus{}
if len(node.StatusJSON) > 0 {
err = json.Unmarshal(node.StatusJSON, &status)
if err != nil {
logs.Error(err)
continue
}
status.IsActive = status.IsActive && time.Now().Unix()-status.UpdatedAt <= 60 // N秒之内认为活跃
}
nodeMaps = append(nodeMaps, maps.Map{
"id": node.Id,
"isOn": node.IsOn,
"name": node.Name,
"status": maps.Map{
"isActive": status.IsActive,
"updatedAt": status.UpdatedAt,
"hostname": status.Hostname,
"cpuUsage": status.CPUUsage,
"cpuUsageText": fmt.Sprintf("%.2f%%", status.CPUUsage*100),
"memUsage": status.MemoryUsage,
"memUsageText": fmt.Sprintf("%.2f%%", status.MemoryUsage*100),
"buildVersion": status.BuildVersion,
},
})
}
}
this.Data["nodes"] = nodeMaps
this.Show()
}

View File

@@ -0,0 +1,23 @@
package nodes
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/authority/nodes/node"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/settingutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeSetting)).
Helper(NewHelper()).
Helper(settingutils.NewAdvancedHelper("authority")).
Prefix("/settings/authority/nodes").
Get("", new(IndexAction)).
GetPost("/node/createPopup", new(node.CreatePopupAction)).
Post("/delete", new(DeleteAction)).
EndAll()
})
}

View File

@@ -0,0 +1,47 @@
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"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("", "node", "create")
}
func (this *CreatePopupAction) RunGet(params struct{}) {
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
Name string
Description string
IsOn bool
Must *actions.Must
}) {
params.Must.
Field("name", params.Name).
Require("请输入认证节点名称")
createResp, err := this.RPC().AuthorityNodeRPC().CreateAuthorityNode(this.AdminContext(), &pb.CreateAuthorityNodeRequest{
Name: params.Name,
Description: params.Description,
IsOn: params.IsOn,
})
if err != nil {
this.ErrorPage(err)
return
}
// 创建日志
defer this.CreateLog(oplogs.LevelInfo, "创建认证节点 %d", createResp.NodeId)
this.Success()
}

View File

@@ -0,0 +1,21 @@
package node
import (
"github.com/iwind/TeaGo/actions"
"net/http"
)
type Helper struct {
}
func NewHelper() *Helper {
return &Helper{}
}
func (this *Helper) BeforeAction(action *actions.ActionObject) (goNext bool) {
if action.Request.Method != http.MethodGet {
return true
}
return true
}

View File

@@ -0,0 +1,39 @@
package node
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("", "", "index")
}
func (this *IndexAction) RunGet(params struct {
NodeId int64
}) {
nodeResp, err := this.RPC().AuthorityNodeRPC().FindEnabledAuthorityNode(this.AdminContext(), &pb.FindEnabledAuthorityNodeRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
node := nodeResp.Node
if node == nil {
this.NotFound("authorityNode", params.NodeId)
return
}
this.Data["node"] = maps.Map{
"id": node.Id,
"name": node.Name,
"description": node.Description,
"isOn": node.IsOn,
}
this.Show()
}

View File

@@ -0,0 +1,25 @@
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/settingutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeSetting)).
Helper(settingutils.NewAdvancedHelper("authority")).
Prefix("/settings/authority/nodes/node").
// 节点相关
Helper(NewHelper()).
Get("", new(IndexAction)).
GetPost("/update", new(UpdateAction)).
Get("/install", new(InstallAction)).
EndAll()
})
}

View File

@@ -0,0 +1,57 @@
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
"strings"
)
type InstallAction struct {
actionutils.ParentAction
}
func (this *InstallAction) Init() {
this.Nav("", "", "install")
}
func (this *InstallAction) RunGet(params struct {
NodeId int64
}) {
// 认证节点信息
nodeResp, err := this.RPC().AuthorityNodeRPC().FindEnabledAuthorityNode(this.AdminContext(), &pb.FindEnabledAuthorityNodeRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
node := nodeResp.Node
if node == nil {
this.NotFound("authorityNode", params.NodeId)
return
}
this.Data["node"] = maps.Map{
"id": node.Id,
"name": node.Name,
"uniqueId": node.UniqueId,
"secret": node.Secret,
}
// API节点列表
apiNodesResp, err := this.RPC().APINodeRPC().FindAllEnabledAPINodes(this.AdminContext(), &pb.FindAllEnabledAPINodesRequest{})
if err != nil {
this.ErrorPage(err)
return
}
apiNodes := apiNodesResp.Nodes
apiEndpoints := []string{}
for _, apiNode := range apiNodes {
if !apiNode.IsOn {
continue
}
apiEndpoints = append(apiEndpoints, apiNode.AccessAddrs...)
}
this.Data["apiEndpoints"] = "\"" + strings.Join(apiEndpoints, "\", \"") + "\""
this.Show()
}

View File

@@ -0,0 +1,73 @@
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"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 {
NodeId int64
}) {
nodeResp, err := this.RPC().AuthorityNodeRPC().FindEnabledAuthorityNode(this.AdminContext(), &pb.FindEnabledAuthorityNodeRequest{
NodeId: params.NodeId,
})
if err != nil {
this.ErrorPage(err)
return
}
node := nodeResp.Node
if node == nil {
this.WriteString("要操作的节点不存在")
return
}
this.Data["node"] = maps.Map{
"id": node.Id,
"name": node.Name,
"description": node.Description,
"isOn": node.IsOn,
}
this.Show()
}
// 保存基础设置
func (this *UpdateAction) RunPost(params struct {
NodeId int64
Name string
Description string
IsOn bool
Must *actions.Must
}) {
params.Must.
Field("name", params.Name).
Require("请输入认证节点名称")
_, err := this.RPC().AuthorityNodeRPC().UpdateAuthorityNode(this.AdminContext(), &pb.UpdateAuthorityNodeRequest{
NodeId: params.NodeId,
Name: params.Name,
Description: params.Description,
IsOn: params.IsOn,
})
if err != nil {
this.ErrorPage(err)
return
}
// 创建日志
defer this.CreateLog(oplogs.LevelInfo, "修改认证节点 %d", params.NodeId)
this.Success()
}

View File

@@ -91,6 +91,7 @@ func (this *UploadPopupAction) RunPost(params struct {
_, err = this.RPC().FileRPC().UpdateFileFinished(this.AdminContext(), &pb.UpdateFileFinishedRequest{FileId: fileId})
if err != nil {
this.ErrorPage(err)
return
}
// 保存

View File

@@ -0,0 +1,28 @@
package monitornodes
import (
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"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 {
NodeId int64
}) {
// TODO 检查权限
_, err := this.RPC().MonitorNodeRPC().DeleteMonitorNode(this.AdminContext(), &pb.DeleteMonitorNodeRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
// 创建日志
defer this.CreateLog(oplogs.LevelInfo, "删除监控节点 %d", params.NodeId)
this.Success()
}

View File

@@ -0,0 +1,15 @@
package monitornodes
import (
"github.com/iwind/TeaGo/actions"
)
type Helper struct {
}
func NewHelper() *Helper {
return &Helper{}
}
func (this *Helper) BeforeAction(action *actions.ActionObject) {
}

View File

@@ -0,0 +1,75 @@
package monitornodes
import (
"encoding/json"
"fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
"time"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "node", "index")
}
func (this *IndexAction) RunGet(params struct{}) {
countResp, err := this.RPC().MonitorNodeRPC().CountAllEnabledMonitorNodes(this.AdminContext(), &pb.CountAllEnabledMonitorNodesRequest{})
if err != nil {
this.ErrorPage(err)
return
}
count := countResp.Count
page := this.NewPage(count)
this.Data["page"] = page.AsHTML()
nodeMaps := []maps.Map{}
if count > 0 {
nodesResp, err := this.RPC().MonitorNodeRPC().ListEnabledMonitorNodes(this.AdminContext(), &pb.ListEnabledMonitorNodesRequest{
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
for _, node := range nodesResp.Nodes {
// 状态
status := &nodeconfigs.NodeStatus{}
if len(node.StatusJSON) > 0 {
err = json.Unmarshal(node.StatusJSON, &status)
if err != nil {
logs.Error(err)
continue
}
status.IsActive = status.IsActive && time.Now().Unix()-status.UpdatedAt <= 60 // N秒之内认为活跃
}
nodeMaps = append(nodeMaps, maps.Map{
"id": node.Id,
"isOn": node.IsOn,
"name": node.Name,
"status": maps.Map{
"isActive": status.IsActive,
"updatedAt": status.UpdatedAt,
"hostname": status.Hostname,
"cpuUsage": status.CPUUsage,
"cpuUsageText": fmt.Sprintf("%.2f%%", status.CPUUsage*100),
"memUsage": status.MemoryUsage,
"memUsageText": fmt.Sprintf("%.2f%%", status.MemoryUsage*100),
"buildVersion": status.BuildVersion,
},
})
}
}
this.Data["nodes"] = nodeMaps
this.Show()
}

View File

@@ -0,0 +1,23 @@
package monitornodes
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/monitor-nodes/node"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/settingutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeSetting)).
Helper(NewHelper()).
Helper(settingutils.NewAdvancedHelper("monitorNodes")).
Prefix("/settings/monitorNodes").
Get("", new(IndexAction)).
GetPost("/node/createPopup", new(node.CreatePopupAction)).
Post("/delete", new(DeleteAction)).
EndAll()
})
}

View File

@@ -0,0 +1,47 @@
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"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("", "node", "create")
}
func (this *CreatePopupAction) RunGet(params struct{}) {
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
Name string
Description string
IsOn bool
Must *actions.Must
}) {
params.Must.
Field("name", params.Name).
Require("请输入监控节点名称")
createResp, err := this.RPC().MonitorNodeRPC().CreateMonitorNode(this.AdminContext(), &pb.CreateMonitorNodeRequest{
Name: params.Name,
Description: params.Description,
IsOn: params.IsOn,
})
if err != nil {
this.ErrorPage(err)
return
}
// 创建日志
defer this.CreateLog(oplogs.LevelInfo, "创建监控节点 %d", createResp.NodeId)
this.Success()
}

View File

@@ -0,0 +1,21 @@
package node
import (
"github.com/iwind/TeaGo/actions"
"net/http"
)
type Helper struct {
}
func NewHelper() *Helper {
return &Helper{}
}
func (this *Helper) BeforeAction(action *actions.ActionObject) (goNext bool) {
if action.Request.Method != http.MethodGet {
return true
}
return true
}

View File

@@ -0,0 +1,39 @@
package node
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("", "", "index")
}
func (this *IndexAction) RunGet(params struct {
NodeId int64
}) {
nodeResp, err := this.RPC().MonitorNodeRPC().FindEnabledMonitorNode(this.AdminContext(), &pb.FindEnabledMonitorNodeRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
node := nodeResp.Node
if node == nil {
this.NotFound("monitorNode", params.NodeId)
return
}
this.Data["node"] = maps.Map{
"id": node.Id,
"name": node.Name,
"description": node.Description,
"isOn": node.IsOn,
}
this.Show()
}

View File

@@ -0,0 +1,25 @@
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/settingutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeSetting)).
Helper(settingutils.NewAdvancedHelper("monitorNodes")).
Prefix("/settings/monitorNodes/node").
// 节点相关
Helper(NewHelper()).
Get("", new(IndexAction)).
GetPost("/update", new(UpdateAction)).
Get("/install", new(InstallAction)).
EndAll()
})
}

View File

@@ -0,0 +1,57 @@
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
"strings"
)
type InstallAction struct {
actionutils.ParentAction
}
func (this *InstallAction) Init() {
this.Nav("", "", "install")
}
func (this *InstallAction) RunGet(params struct {
NodeId int64
}) {
// 监控节点信息
nodeResp, err := this.RPC().MonitorNodeRPC().FindEnabledMonitorNode(this.AdminContext(), &pb.FindEnabledMonitorNodeRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
node := nodeResp.Node
if node == nil {
this.NotFound("monitorNode", params.NodeId)
return
}
this.Data["node"] = maps.Map{
"id": node.Id,
"name": node.Name,
"uniqueId": node.UniqueId,
"secret": node.Secret,
}
// API节点列表
apiNodesResp, err := this.RPC().APINodeRPC().FindAllEnabledAPINodes(this.AdminContext(), &pb.FindAllEnabledAPINodesRequest{})
if err != nil {
this.ErrorPage(err)
return
}
apiNodes := apiNodesResp.Nodes
apiEndpoints := []string{}
for _, apiNode := range apiNodes {
if !apiNode.IsOn {
continue
}
apiEndpoints = append(apiEndpoints, apiNode.AccessAddrs...)
}
this.Data["apiEndpoints"] = "\"" + strings.Join(apiEndpoints, "\", \"") + "\""
this.Show()
}

View File

@@ -0,0 +1,73 @@
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"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 {
NodeId int64
}) {
nodeResp, err := this.RPC().MonitorNodeRPC().FindEnabledMonitorNode(this.AdminContext(), &pb.FindEnabledMonitorNodeRequest{
NodeId: params.NodeId,
})
if err != nil {
this.ErrorPage(err)
return
}
node := nodeResp.Node
if node == nil {
this.WriteString("要操作的节点不存在")
return
}
this.Data["node"] = maps.Map{
"id": node.Id,
"name": node.Name,
"description": node.Description,
"isOn": node.IsOn,
}
this.Show()
}
// 保存基础设置
func (this *UpdateAction) RunPost(params struct {
NodeId int64
Name string
Description string
IsOn bool
Must *actions.Must
}) {
params.Must.
Field("name", params.Name).
Require("请输入监控节点名称")
_, err := this.RPC().MonitorNodeRPC().UpdateMonitorNode(this.AdminContext(), &pb.UpdateMonitorNodeRequest{
NodeId: params.NodeId,
Name: params.Name,
Description: params.Description,
IsOn: params.IsOn,
})
if err != nil {
this.ErrorPage(err)
return
}
// 创建日志
defer this.CreateLog(oplogs.LevelInfo, "修改监控节点 %d", params.NodeId)
this.Success()
}

View File

@@ -5,6 +5,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
@@ -23,6 +24,9 @@ func (this *IndexAction) RunGet(params struct{}) {
this.ErrorPage(err)
return
}
if config.AllowIPs == nil {
config.AllowIPs = []string{}
}
// 国家和地区
countryMaps := []maps.Map{}
@@ -69,6 +73,7 @@ func (this *IndexAction) RunPost(params struct {
CountryIdsJSON []byte
ProvinceIdsJSON []byte
AllowLocal bool
AllowIPs []string
Must *actions.Must
CSRF *actionutils.CSRF
@@ -106,6 +111,19 @@ func (this *IndexAction) RunPost(params struct {
}
config.AllowProvinceIds = provinceIds
// 允许的IP
if len(params.AllowIPs) > 0 {
for _, ip := range params.AllowIPs {
_, err := shared.ParseIPRange(ip)
if err != nil {
this.Fail("允许访问的IP '" + ip + "' 格式错误:" + err.Error())
}
}
config.AllowIPs = params.AllowIPs
} else {
config.AllowIPs = []string{}
}
// 允许本地
config.AllowLocal = params.AllowLocal

View File

@@ -2,6 +2,7 @@ package settingutils
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/iwind/TeaGo/actions"
)
@@ -34,6 +35,11 @@ func (this *AdvancedHelper) BeforeAction(actionPtr actions.ActionWrapper) (goNex
tabbar.Add("API节点", "", "/api", "", this.tab == "apiNodes")
tabbar.Add("用户节点", "", "/settings/userNodes", "", this.tab == "userNodes")
tabbar.Add("日志数据库", "", "/db", "", this.tab == "dbNodes")
if teaconst.IsPlus {
tabbar.Add("监控节点", "", "/settings/monitorNodes", "", this.tab == "monitorNodes")
}
tabbar.Add("企业版认证", "", "/settings/authority", "", this.tab == "authority")
//tabbar.Add("备份", "", "/settings/backup", "", this.tab == "backup")
}
actionutils.SetTabbar(actionPtr, tabbar)

View File

@@ -3,7 +3,9 @@ package ui
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"io"
)
type IndexAction struct {
@@ -32,10 +34,14 @@ func (this *IndexAction) RunPost(params struct {
ShowFinance bool
ShowVersion bool
Version string
FaviconFile *actions.File
LogoFile *actions.File
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo("修改管理界面设置")
params.Must.
Field("productName", params.ProductName).
Require("请输入产品名称").
@@ -53,6 +59,101 @@ func (this *IndexAction) RunPost(params struct {
config.ShowFinance = params.ShowFinance
config.ShowVersion = params.ShowVersion
config.Version = params.Version
// 上传Favicon文件
if params.FaviconFile != nil {
createResp, err := this.RPC().FileRPC().CreateFile(this.AdminContext(), &pb.CreateFileRequest{
Filename: params.FaviconFile.Filename,
Size: params.FaviconFile.Size,
IsPublic: true,
})
if err != nil {
this.ErrorPage(err)
return
}
fileId := createResp.FileId
// 上传内容
buf := make([]byte, 512*1024)
reader, err := params.FaviconFile.OriginFile.Open()
if err != nil {
this.ErrorPage(err)
return
}
for {
n, err := reader.Read(buf)
if n > 0 {
_, err = this.RPC().FileChunkRPC().CreateFileChunk(this.AdminContext(), &pb.CreateFileChunkRequest{
FileId: fileId,
Data: buf[:n],
})
if err != nil {
this.Fail("上传失败:" + err.Error())
}
}
if err != nil {
if err == io.EOF {
break
}
this.Fail("上传失败:" + err.Error())
}
}
// 置为已完成
_, err = this.RPC().FileRPC().UpdateFileFinished(this.AdminContext(), &pb.UpdateFileFinishedRequest{FileId: fileId})
if err != nil {
this.ErrorPage(err)
}
config.FaviconFileId = fileId
}
// 上传Logo文件
if params.LogoFile != nil {
createResp, err := this.RPC().FileRPC().CreateFile(this.AdminContext(), &pb.CreateFileRequest{
Filename: params.LogoFile.Filename,
Size: params.LogoFile.Size,
IsPublic: true,
})
if err != nil {
this.ErrorPage(err)
return
}
fileId := createResp.FileId
// 上传内容
buf := make([]byte, 512*1024)
reader, err := params.LogoFile.OriginFile.Open()
if err != nil {
this.ErrorPage(err)
return
}
for {
n, err := reader.Read(buf)
if n > 0 {
_, err = this.RPC().FileChunkRPC().CreateFileChunk(this.AdminContext(), &pb.CreateFileChunkRequest{
FileId: fileId,
Data: buf[:n],
})
if err != nil {
this.Fail("上传失败:" + err.Error())
}
}
if err != nil {
if err == io.EOF {
break
}
this.Fail("上传失败:" + err.Error())
}
}
// 置为已完成
_, err = this.RPC().FileRPC().UpdateFileFinished(this.AdminContext(), &pb.UpdateFileFinishedRequest{FileId: fileId})
if err != nil {
this.ErrorPage(err)
}
config.LogoFileId = fileId
}
err = configloaders.UpdateAdminUIConfig(config)
if err != nil {
this.ErrorPage(err)

View File

@@ -3,7 +3,9 @@ package userui
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"io"
)
type IndexAction struct {
@@ -32,6 +34,8 @@ func (this *IndexAction) RunPost(params struct {
ShowVersion bool
Version string
ShowFinance bool
FaviconFile *actions.File
LogoFile *actions.File
Must *actions.Must
CSRF *actionutils.CSRF
@@ -53,6 +57,101 @@ func (this *IndexAction) RunPost(params struct {
config.ShowVersion = params.ShowVersion
config.Version = params.Version
config.ShowFinance = params.ShowFinance
// 上传Favicon文件
if params.FaviconFile != nil {
createResp, err := this.RPC().FileRPC().CreateFile(this.AdminContext(), &pb.CreateFileRequest{
Filename: params.FaviconFile.Filename,
Size: params.FaviconFile.Size,
IsPublic: true,
})
if err != nil {
this.ErrorPage(err)
return
}
fileId := createResp.FileId
// 上传内容
buf := make([]byte, 512*1024)
reader, err := params.FaviconFile.OriginFile.Open()
if err != nil {
this.ErrorPage(err)
return
}
for {
n, err := reader.Read(buf)
if n > 0 {
_, err = this.RPC().FileChunkRPC().CreateFileChunk(this.AdminContext(), &pb.CreateFileChunkRequest{
FileId: fileId,
Data: buf[:n],
})
if err != nil {
this.Fail("上传失败:" + err.Error())
}
}
if err != nil {
if err == io.EOF {
break
}
this.Fail("上传失败:" + err.Error())
}
}
// 置为已完成
_, err = this.RPC().FileRPC().UpdateFileFinished(this.AdminContext(), &pb.UpdateFileFinishedRequest{FileId: fileId})
if err != nil {
this.ErrorPage(err)
}
config.FaviconFileId = fileId
}
// 上传Logo文件
if params.LogoFile != nil {
createResp, err := this.RPC().FileRPC().CreateFile(this.AdminContext(), &pb.CreateFileRequest{
Filename: params.LogoFile.Filename,
Size: params.LogoFile.Size,
IsPublic: true,
})
if err != nil {
this.ErrorPage(err)
return
}
fileId := createResp.FileId
// 上传内容
buf := make([]byte, 512*1024)
reader, err := params.LogoFile.OriginFile.Open()
if err != nil {
this.ErrorPage(err)
return
}
for {
n, err := reader.Read(buf)
if n > 0 {
_, err = this.RPC().FileChunkRPC().CreateFileChunk(this.AdminContext(), &pb.CreateFileChunkRequest{
FileId: fileId,
Data: buf[:n],
})
if err != nil {
this.Fail("上传失败:" + err.Error())
}
}
if err != nil {
if err == io.EOF {
break
}
this.Fail("上传失败:" + err.Error())
}
}
// 置为已完成
_, err = this.RPC().FileRPC().UpdateFileFinished(this.AdminContext(), &pb.UpdateFileFinishedRequest{FileId: fileId})
if err != nil {
this.ErrorPage(err)
}
config.LogoFileId = fileId
}
err = configloaders.UpdateUserUIConfig(config)
if err != nil {
this.ErrorPage(err)

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