Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9f7cd723a | ||
|
|
4a813f08cd | ||
|
|
e75757f2bc | ||
|
|
91010325eb | ||
|
|
441c0bd628 | ||
|
|
a91b97ff2b | ||
|
|
fb775a0ee9 | ||
|
|
8ed02c8103 | ||
|
|
42a306ff22 | ||
|
|
a637783249 | ||
|
|
7ffff890a8 | ||
|
|
dbfbdddb3a | ||
|
|
f85b3a40ea | ||
|
|
27421bbd46 | ||
|
|
129d2ccef0 | ||
|
|
a553bad1ef | ||
|
|
ae7020437b | ||
|
|
74e6c7a87c | ||
|
|
658c5ae6dd | ||
|
|
7a24a69223 | ||
|
|
de90acaae4 | ||
|
|
51080f2669 | ||
|
|
fa5d97b74c | ||
|
|
42ba081cd1 | ||
|
|
404d733ba1 | ||
|
|
2f3398e251 | ||
|
|
5c09e272f3 |
@@ -9,7 +9,7 @@
|
|||||||
* `高扩展性` - 可以自由扩展新的节点,支持亿级数据
|
* `高扩展性` - 可以自由扩展新的节点,支持亿级数据
|
||||||
|
|
||||||
## 文档
|
## 文档
|
||||||
[点这里查看文档](https://github.com/TeaOSLab/EdgeDocs)
|
[点这里查看文档](http://edge.teaos.cn/docs)
|
||||||
|
|
||||||
## 架构
|
## 架构
|
||||||

|

|
||||||
|
|||||||
8
build/build-all.sh
Executable file
8
build/build-all.sh
Executable 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
|
||||||
@@ -52,6 +52,10 @@ func UpdateSecurityConfig(securityConfig *systemconfigs.SecurityConfig) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
err = securityConfig.Init()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
sharedSecurityConfig = securityConfig
|
sharedSecurityConfig = securityConfig
|
||||||
|
|
||||||
// 通知更新
|
// 通知更新
|
||||||
@@ -86,6 +90,10 @@ func loadSecurityConfig() (*systemconfigs.SecurityConfig, error) {
|
|||||||
sharedSecurityConfig = defaultSecurityConfig()
|
sharedSecurityConfig = defaultSecurityConfig()
|
||||||
return sharedSecurityConfig, nil
|
return sharedSecurityConfig, nil
|
||||||
}
|
}
|
||||||
|
err = config.Init()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
sharedSecurityConfig = config
|
sharedSecurityConfig = config
|
||||||
return sharedSecurityConfig, nil
|
return sharedSecurityConfig, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
// API配置
|
// APIConfig API配置
|
||||||
type APIConfig struct {
|
type APIConfig struct {
|
||||||
RPC struct {
|
RPC struct {
|
||||||
Endpoints []string `yaml:"endpoints"`
|
Endpoints []string `yaml:"endpoints"`
|
||||||
@@ -18,7 +18,7 @@ type APIConfig struct {
|
|||||||
Secret string `yaml:"secret"`
|
Secret string `yaml:"secret"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载API配置
|
// LoadAPIConfig 加载API配置
|
||||||
func LoadAPIConfig() (*APIConfig, error) {
|
func LoadAPIConfig() (*APIConfig, error) {
|
||||||
// 候选文件
|
// 候选文件
|
||||||
localFile := Tea.ConfigFile("api.yaml")
|
localFile := Tea.ConfigFile("api.yaml")
|
||||||
@@ -59,7 +59,7 @@ func LoadAPIConfig() (*APIConfig, error) {
|
|||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 写入API配置
|
// WriteFile 写入API配置
|
||||||
func (this *APIConfig) WriteFile(path string) error {
|
func (this *APIConfig) WriteFile(path string) error {
|
||||||
data, err := yaml.Marshal(this)
|
data, err := yaml.Marshal(this)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package teaconst
|
package teaconst
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Version = "0.0.12"
|
Version = "0.0.13"
|
||||||
|
|
||||||
ProductName = "Edge Admin"
|
ProductName = "Edge Admin"
|
||||||
ProcessName = "edge-admin"
|
ProcessName = "edge-admin"
|
||||||
|
|||||||
6
internal/const/plus.go
Normal file
6
internal/const/plus.go
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package teaconst
|
||||||
|
|
||||||
|
// IsPlus 是否为企业版
|
||||||
|
var IsPlus = false
|
||||||
@@ -87,7 +87,7 @@ func (this *AdminNode) Run() {
|
|||||||
Start()
|
Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 实现守护进程
|
// Daemon 实现守护进程
|
||||||
func (this *AdminNode) Daemon() {
|
func (this *AdminNode) Daemon() {
|
||||||
path := os.TempDir() + "/edge-admin.sock"
|
path := os.TempDir() + "/edge-admin.sock"
|
||||||
isDebug := lists.ContainsString(os.Args, "debug")
|
isDebug := lists.ContainsString(os.Args, "debug")
|
||||||
@@ -132,7 +132,7 @@ func (this *AdminNode) Daemon() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 安装系统服务
|
// InstallSystemService 安装系统服务
|
||||||
func (this *AdminNode) InstallSystemService() error {
|
func (this *AdminNode) InstallSystemService() error {
|
||||||
shortName := teaconst.SystemdServiceName
|
shortName := teaconst.SystemdServiceName
|
||||||
|
|
||||||
@@ -149,7 +149,7 @@ func (this *AdminNode) InstallSystemService() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加子PID
|
// AddSubPID 添加子PID
|
||||||
func (this *AdminNode) AddSubPID(pid int) {
|
func (this *AdminNode) AddSubPID(pid int) {
|
||||||
this.subPIDs = append(this.subPIDs, pid)
|
this.subPIDs = append(this.subPIDs, pid)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,6 +139,10 @@ func (this *RPCClient) DBNodeRPC() pb.DBNodeServiceClient {
|
|||||||
return pb.NewDBNodeServiceClient(this.pickConn())
|
return pb.NewDBNodeServiceClient(this.pickConn())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *RPCClient) MonitorNodeRPC() pb.MonitorNodeServiceClient {
|
||||||
|
return pb.NewMonitorNodeServiceClient(this.pickConn())
|
||||||
|
}
|
||||||
|
|
||||||
func (this *RPCClient) DBRPC() pb.DBServiceClient {
|
func (this *RPCClient) DBRPC() pb.DBServiceClient {
|
||||||
return pb.NewDBServiceClient(this.pickConn())
|
return pb.NewDBServiceClient(this.pickConn())
|
||||||
}
|
}
|
||||||
@@ -203,7 +207,7 @@ func (this *RPCClient) HTTPRewriteRuleRPC() pb.HTTPRewriteRuleServiceClient {
|
|||||||
return pb.NewHTTPRewriteRuleServiceClient(this.pickConn())
|
return pb.NewHTTPRewriteRuleServiceClient(this.pickConn())
|
||||||
}
|
}
|
||||||
|
|
||||||
// 访问日志
|
// HTTPAccessLogRPC 访问日志
|
||||||
func (this *RPCClient) HTTPAccessLogRPC() pb.HTTPAccessLogServiceClient {
|
func (this *RPCClient) HTTPAccessLogRPC() pb.HTTPAccessLogServiceClient {
|
||||||
return pb.NewHTTPAccessLogServiceClient(this.pickConn())
|
return pb.NewHTTPAccessLogServiceClient(this.pickConn())
|
||||||
}
|
}
|
||||||
@@ -224,6 +228,34 @@ func (this *RPCClient) MessageRPC() pb.MessageServiceClient {
|
|||||||
return pb.NewMessageServiceClient(this.pickConn())
|
return pb.NewMessageServiceClient(this.pickConn())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *RPCClient) MessageRecipientGroupRPC() pb.MessageRecipientGroupServiceClient {
|
||||||
|
return pb.NewMessageRecipientGroupServiceClient(this.pickConn())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *RPCClient) MessageRecipientRPC() pb.MessageRecipientServiceClient {
|
||||||
|
return pb.NewMessageRecipientServiceClient(this.pickConn())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *RPCClient) MessageMediaRPC() pb.MessageMediaServiceClient {
|
||||||
|
return pb.NewMessageMediaServiceClient(this.pickConn())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *RPCClient) MessageMediaInstanceRPC() pb.MessageMediaInstanceServiceClient {
|
||||||
|
return pb.NewMessageMediaInstanceServiceClient(this.pickConn())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *RPCClient) MessageTaskRPC() pb.MessageTaskServiceClient {
|
||||||
|
return pb.NewMessageTaskServiceClient(this.pickConn())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *RPCClient) MessageTaskLogRPC() pb.MessageTaskLogServiceClient {
|
||||||
|
return pb.NewMessageTaskLogServiceClient(this.pickConn())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *RPCClient) MessageReceiverRPC() pb.MessageReceiverServiceClient {
|
||||||
|
return pb.NewMessageReceiverServiceClient(this.pickConn())
|
||||||
|
}
|
||||||
|
|
||||||
func (this *RPCClient) IPLibraryRPC() pb.IPLibraryServiceClient {
|
func (this *RPCClient) IPLibraryRPC() pb.IPLibraryServiceClient {
|
||||||
return pb.NewIPLibraryServiceClient(this.pickConn())
|
return pb.NewIPLibraryServiceClient(this.pickConn())
|
||||||
}
|
}
|
||||||
@@ -296,7 +328,15 @@ func (this *RPCClient) NodeTaskRPC() pb.NodeTaskServiceClient {
|
|||||||
return pb.NewNodeTaskServiceClient(this.pickConn())
|
return pb.NewNodeTaskServiceClient(this.pickConn())
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构造Admin上下文
|
func (this *RPCClient) AuthorityKeyRPC() pb.AuthorityKeyServiceClient {
|
||||||
|
return pb.NewAuthorityKeyServiceClient(this.pickConn())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *RPCClient) AuthorityNodeRPC() pb.AuthorityNodeServiceClient {
|
||||||
|
return pb.NewAuthorityNodeServiceClient(this.pickConn())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context 构造Admin上下文
|
||||||
func (this *RPCClient) Context(adminId int64) context.Context {
|
func (this *RPCClient) Context(adminId int64) context.Context {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
m := maps.Map{
|
m := maps.Map{
|
||||||
@@ -319,7 +359,7 @@ func (this *RPCClient) Context(adminId int64) context.Context {
|
|||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构造API上下文
|
// APIContext 构造API上下文
|
||||||
func (this *RPCClient) APIContext(apiNodeId int64) context.Context {
|
func (this *RPCClient) APIContext(apiNodeId int64) context.Context {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
m := maps.Map{
|
m := maps.Map{
|
||||||
@@ -342,7 +382,7 @@ func (this *RPCClient) APIContext(apiNodeId int64) context.Context {
|
|||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// 修改配置
|
// UpdateConfig 修改配置
|
||||||
func (this *RPCClient) UpdateConfig(config *configs.APIConfig) error {
|
func (this *RPCClient) UpdateConfig(config *configs.APIConfig) error {
|
||||||
this.apiConfig = config
|
this.apiConfig = config
|
||||||
return this.init()
|
return this.init()
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
|
|
||||||
var isConfigured bool
|
var isConfigured bool
|
||||||
|
|
||||||
// 判断系统是否已经配置过
|
// IsConfigured 判断系统是否已经配置过
|
||||||
func IsConfigured() bool {
|
func IsConfigured() bool {
|
||||||
if isConfigured {
|
if isConfigured {
|
||||||
return true
|
return true
|
||||||
|
|||||||
68
internal/tasks/task_authority.go
Normal file
68
internal/tasks/task_authority.go
Normal 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
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||||
"github.com/iwind/TeaGo/actions"
|
"github.com/iwind/TeaGo/actions"
|
||||||
"github.com/iwind/TeaGo/logs"
|
"github.com/iwind/TeaGo/logs"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -25,7 +26,21 @@ func FailPage(action actions.ActionWrapper, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
logs.Println("[" + reflect.TypeOf(action).String() + "]" + findStack(err.Error()))
|
logs.Println("[" + reflect.TypeOf(action).String() + "]" + findStack(err.Error()))
|
||||||
}
|
}
|
||||||
action.Object().WriteString(teaconst.ErrServer)
|
action.Object().ResponseWriter.WriteHeader(http.StatusInternalServerError)
|
||||||
|
if len(action.Object().Request.Header.Get("X-Requested-With")) > 0 {
|
||||||
|
action.Object().WriteString(teaconst.ErrServer)
|
||||||
|
} else {
|
||||||
|
action.Object().WriteString(`<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head></head>
|
||||||
|
<body>
|
||||||
|
<div style="background: #eee; border: 1px #ccc solid; padding: 10px; font-size: 12px; line-height: 1.8">
|
||||||
|
` + teaconst.ErrServer + `
|
||||||
|
<div>可以通过查看 <strong><em>$安装目录/logs/run.log</em></strong> 日志文件查看具体的错误提示。</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断动作的文件路径是否相当
|
// 判断动作的文件路径是否相当
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ func (this *AdminAction) RunGet(params struct {
|
|||||||
"username": admin.Username,
|
"username": admin.Username,
|
||||||
"isOn": admin.IsOn,
|
"isOn": admin.IsOn,
|
||||||
"isSuper": admin.IsSuper,
|
"isSuper": admin.IsSuper,
|
||||||
|
"canLogin": admin.CanLogin,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 权限
|
// 权限
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ func (this *CreatePopupAction) RunPost(params struct {
|
|||||||
Pass2 string
|
Pass2 string
|
||||||
ModuleCodes []string
|
ModuleCodes []string
|
||||||
IsSuper bool
|
IsSuper bool
|
||||||
|
CanLogin bool
|
||||||
|
|
||||||
// OTP
|
// OTP
|
||||||
OtpOn bool
|
OtpOn bool
|
||||||
@@ -88,6 +89,7 @@ func (this *CreatePopupAction) RunPost(params struct {
|
|||||||
Fullname: params.Fullname,
|
Fullname: params.Fullname,
|
||||||
ModulesJSON: modulesJSON,
|
ModulesJSON: modulesJSON,
|
||||||
IsSuper: params.IsSuper,
|
IsSuper: params.IsSuper,
|
||||||
|
CanLogin: params.CanLogin,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.ErrorPage(err)
|
this.ErrorPage(err)
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ func (this *IndexAction) RunGet(params struct{}) {
|
|||||||
"fullname": admin.Fullname,
|
"fullname": admin.Fullname,
|
||||||
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", admin.CreatedAt),
|
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", admin.CreatedAt),
|
||||||
"otpLoginIsOn": admin.OtpLogin != nil && admin.OtpLogin.IsOn,
|
"otpLoginIsOn": admin.OtpLogin != nil && admin.OtpLogin.IsOn,
|
||||||
|
"canLogin": admin.CanLogin,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.Data["admins"] = adminMaps
|
this.Data["admins"] = adminMaps
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ func init() {
|
|||||||
Post("/delete", new(DeleteAction)).
|
Post("/delete", new(DeleteAction)).
|
||||||
Get("/admin", new(AdminAction)).
|
Get("/admin", new(AdminAction)).
|
||||||
Get("/otpQrcode", new(OtpQrcodeAction)).
|
Get("/otpQrcode", new(OtpQrcodeAction)).
|
||||||
|
Post("/options", new(OptionsAction)).
|
||||||
EndAll()
|
EndAll()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
40
internal/web/actions/default/admins/options.go
Normal file
40
internal/web/actions/default/admins/options.go
Normal 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()
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
24
internal/web/actions/default/admins/recipients/delete.go
Normal file
24
internal/web/actions/default/admins/recipients/delete.go
Normal 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()
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
70
internal/web/actions/default/admins/recipients/index.go
Normal file
70
internal/web/actions/default/admins/recipients/index.go
Normal 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()
|
||||||
|
}
|
||||||
25
internal/web/actions/default/admins/recipients/init.go
Normal file
25
internal/web/actions/default/admins/recipients/init.go
Normal 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()
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
61
internal/web/actions/default/admins/recipients/logs/index.go
Normal file
61
internal/web/actions/default/admins/recipients/logs/index.go
Normal 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()
|
||||||
|
}
|
||||||
19
internal/web/actions/default/admins/recipients/logs/init.go
Normal file
19
internal/web/actions/default/admins/recipients/logs/init.go
Normal 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()
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
50
internal/web/actions/default/admins/recipients/recipient.go
Normal file
50
internal/web/actions/default/admins/recipients/recipient.go
Normal 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()
|
||||||
|
}
|
||||||
19
internal/web/actions/default/admins/recipients/tasks/init.go
Normal file
19
internal/web/actions/default/admins/recipients/tasks/init.go
Normal 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()
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
113
internal/web/actions/default/admins/recipients/test.go
Normal file
113
internal/web/actions/default/admins/recipients/test.go
Normal 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()
|
||||||
|
}
|
||||||
92
internal/web/actions/default/admins/recipients/update.go
Normal file
92
internal/web/actions/default/admins/recipients/update.go
Normal 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()
|
||||||
|
}
|
||||||
@@ -45,6 +45,7 @@ func (this *UpdateAction) RunGet(params struct {
|
|||||||
"username": admin.Username,
|
"username": admin.Username,
|
||||||
"isOn": admin.IsOn,
|
"isOn": admin.IsOn,
|
||||||
"isSuper": admin.IsSuper,
|
"isSuper": admin.IsSuper,
|
||||||
|
"canLogin": admin.CanLogin,
|
||||||
"otpLoginIsOn": otpLoginIsOn,
|
"otpLoginIsOn": otpLoginIsOn,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,6 +77,7 @@ func (this *UpdateAction) RunPost(params struct {
|
|||||||
ModuleCodes []string
|
ModuleCodes []string
|
||||||
IsOn bool
|
IsOn bool
|
||||||
IsSuper bool
|
IsSuper bool
|
||||||
|
CanLogin bool
|
||||||
|
|
||||||
// OTP
|
// OTP
|
||||||
OtpOn bool
|
OtpOn bool
|
||||||
@@ -139,6 +141,7 @@ func (this *UpdateAction) RunPost(params struct {
|
|||||||
ModulesJSON: modulesJSON,
|
ModulesJSON: modulesJSON,
|
||||||
IsSuper: params.IsSuper,
|
IsSuper: params.IsSuper,
|
||||||
IsOn: params.IsOn,
|
IsOn: params.IsOn,
|
||||||
|
CanLogin: params.CanLogin,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.ErrorPage(err)
|
this.ErrorPage(err)
|
||||||
|
|||||||
@@ -36,6 +36,15 @@ func (this *IndexAction) RunGet(params struct {
|
|||||||
this.Data["activeState"] = params.ActiveState
|
this.Data["activeState"] = params.ActiveState
|
||||||
this.Data["keyword"] = params.Keyword
|
this.Data["keyword"] = params.Keyword
|
||||||
|
|
||||||
|
countAllResp, err := this.RPC().NodeRPC().CountAllEnabledNodesMatch(this.AdminContext(), &pb.CountAllEnabledNodesMatchRequest{
|
||||||
|
NodeClusterId: params.ClusterId,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.Data["countAll"] = countAllResp.Count
|
||||||
|
|
||||||
countResp, err := this.RPC().NodeRPC().CountAllEnabledNodesMatch(this.AdminContext(), &pb.CountAllEnabledNodesMatchRequest{
|
countResp, err := this.RPC().NodeRPC().CountAllEnabledNodesMatch(this.AdminContext(), &pb.CountAllEnabledNodesMatchRequest{
|
||||||
NodeClusterId: params.ClusterId,
|
NodeClusterId: params.ClusterId,
|
||||||
GroupId: params.GroupId,
|
GroupId: params.GroupId,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/dns"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/dns"
|
||||||
firewallActions "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/firewall-actions"
|
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/health"
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/message"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/services"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/services"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/toa"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/toa"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/waf"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/waf"
|
||||||
@@ -36,6 +37,12 @@ func init() {
|
|||||||
Prefix("/clusters/cluster/settings/dns").
|
Prefix("/clusters/cluster/settings/dns").
|
||||||
GetPost("", new(dns.IndexAction)).
|
GetPost("", new(dns.IndexAction)).
|
||||||
|
|
||||||
|
// 消息
|
||||||
|
Prefix("/clusters/cluster/settings/message").
|
||||||
|
GetPost("", new(message.IndexAction)).
|
||||||
|
Get("/selectReceiverPopup", new(message.SelectReceiverPopupAction)).
|
||||||
|
Post("/selectedReceivers", new(message.SelectedReceiversAction)).
|
||||||
|
|
||||||
// TOA
|
// TOA
|
||||||
Prefix("/clusters/cluster/settings/toa").
|
Prefix("/clusters/cluster/settings/toa").
|
||||||
GetPost("", new(toa.IndexAction)).
|
GetPost("", new(toa.IndexAction)).
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package clusterutils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
@@ -115,6 +116,20 @@ func (this *ClusterHelper) createSettingMenu(cluster *pb.NodeCluster, selectedIt
|
|||||||
"isActive": selectedItem == "dns",
|
"isActive": selectedItem == "dns",
|
||||||
"isOn": cluster.DnsDomainId > 0 || len(cluster.DnsName) > 0,
|
"isOn": cluster.DnsDomainId > 0 || len(cluster.DnsName) > 0,
|
||||||
})
|
})
|
||||||
|
if teaconst.IsPlus {
|
||||||
|
items = append(items, maps.Map{
|
||||||
|
"name": "消息通知",
|
||||||
|
"url": "/clusters/cluster/settings/message?clusterId=" + clusterId,
|
||||||
|
"isActive": selectedItem == "message",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
items = append(items, maps.Map{
|
||||||
|
"name": "-",
|
||||||
|
"url": "",
|
||||||
|
"isActive": false,
|
||||||
|
})
|
||||||
|
|
||||||
items = append(items, maps.Map{
|
items = append(items, maps.Map{
|
||||||
"name": "系统服务",
|
"name": "系统服务",
|
||||||
"url": "/clusters/cluster/settings/services?clusterId=" + clusterId,
|
"url": "/clusters/cluster/settings/services?clusterId=" + clusterId,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/grants/grantutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/grants/grantutils"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
"github.com/iwind/TeaGo/maps"
|
"github.com/iwind/TeaGo/maps"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GrantAction struct {
|
type GrantAction struct {
|
||||||
@@ -37,7 +38,7 @@ func (this *GrantAction) RunGet(params struct {
|
|||||||
"method": grant.Method,
|
"method": grant.Method,
|
||||||
"methodName": grantutils.FindGrantMethodName(grant.Method),
|
"methodName": grantutils.FindGrantMethodName(grant.Method),
|
||||||
"username": grant.Username,
|
"username": grant.Username,
|
||||||
"password": grant.Password,
|
"password": strings.Repeat("*", len(grant.Password)),
|
||||||
"privateKey": grant.PrivateKey,
|
"privateKey": grant.PrivateKey,
|
||||||
"description": grant.Description,
|
"description": grant.Description,
|
||||||
"su": grant.Su,
|
"su": grant.Su,
|
||||||
|
|||||||
@@ -55,6 +55,10 @@ func (this *CreatePopupAction) RunPost(params struct {
|
|||||||
ParamApiKey string
|
ParamApiKey string
|
||||||
ParamApiSecret string
|
ParamApiSecret string
|
||||||
|
|
||||||
|
// CloudFlare
|
||||||
|
CloudFlareAPIKey string
|
||||||
|
CloudFlareEmail string
|
||||||
|
|
||||||
// CustomHTTP
|
// CustomHTTP
|
||||||
ParamCustomHTTPURL string
|
ParamCustomHTTPURL string
|
||||||
ParamCustomHTTPSecret string
|
ParamCustomHTTPSecret string
|
||||||
@@ -97,6 +101,14 @@ func (this *CreatePopupAction) RunPost(params struct {
|
|||||||
|
|
||||||
apiParams["apiKey"] = params.ParamApiKey
|
apiParams["apiKey"] = params.ParamApiKey
|
||||||
apiParams["apiSecret"] = params.ParamApiSecret
|
apiParams["apiSecret"] = params.ParamApiSecret
|
||||||
|
case "cloudFlare":
|
||||||
|
params.Must.
|
||||||
|
Field("cloudFlareAPIKey", params.CloudFlareAPIKey).
|
||||||
|
Require("请输入API密钥").
|
||||||
|
Field("cloudFlareEmail", params.CloudFlareEmail).
|
||||||
|
Email("请输入正确格式的邮箱地址")
|
||||||
|
apiParams["apiKey"] = params.CloudFlareAPIKey
|
||||||
|
apiParams["email"] = params.CloudFlareEmail
|
||||||
case "customHTTP":
|
case "customHTTP":
|
||||||
params.Must.
|
params.Must.
|
||||||
Field("paramCustomHTTPURL", params.ParamCustomHTTPURL).
|
Field("paramCustomHTTPURL", params.ParamCustomHTTPURL).
|
||||||
|
|||||||
@@ -84,6 +84,10 @@ func (this *UpdatePopupAction) RunPost(params struct {
|
|||||||
ParamApiKey string
|
ParamApiKey string
|
||||||
ParamApiSecret string
|
ParamApiSecret string
|
||||||
|
|
||||||
|
// CloudFlare
|
||||||
|
CloudFlareAPIKey string
|
||||||
|
CloudFlareEmail string
|
||||||
|
|
||||||
// CustomHTTP
|
// CustomHTTP
|
||||||
ParamCustomHTTPURL string
|
ParamCustomHTTPURL string
|
||||||
ParamCustomHTTPSecret string
|
ParamCustomHTTPSecret string
|
||||||
@@ -128,6 +132,14 @@ func (this *UpdatePopupAction) RunPost(params struct {
|
|||||||
|
|
||||||
apiParams["apiKey"] = params.ParamApiKey
|
apiParams["apiKey"] = params.ParamApiKey
|
||||||
apiParams["apiSecret"] = params.ParamApiSecret
|
apiParams["apiSecret"] = params.ParamApiSecret
|
||||||
|
case "cloudFlare":
|
||||||
|
params.Must.
|
||||||
|
Field("cloudFlareAPIKey", params.CloudFlareAPIKey).
|
||||||
|
Require("请输入API密钥").
|
||||||
|
Field("cloudFlareEmail", params.CloudFlareEmail).
|
||||||
|
Email("请输入正确格式的邮箱地址")
|
||||||
|
apiParams["apiKey"] = params.CloudFlareAPIKey
|
||||||
|
apiParams["email"] = params.CloudFlareEmail
|
||||||
case "customHTTP":
|
case "customHTTP":
|
||||||
params.Must.
|
params.Must.
|
||||||
Field("paramCustomHTTPURL", params.ParamCustomHTTPURL).
|
Field("paramCustomHTTPURL", params.ParamCustomHTTPURL).
|
||||||
|
|||||||
@@ -16,30 +16,121 @@ func (this *IndexAction) Init() {
|
|||||||
this.SecondMenu("list")
|
this.SecondMenu("list")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *IndexAction) RunGet(params struct{}) {
|
func (this *IndexAction) RunGet(params struct {
|
||||||
countResp, err := this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
|
Type string
|
||||||
AdminId: this.AdminId(),
|
Keyword string
|
||||||
UserId: 0,
|
}) {
|
||||||
})
|
this.Data["type"] = params.Type
|
||||||
if err != nil {
|
this.Data["keyword"] = params.Keyword
|
||||||
this.ErrorPage(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
count := countResp.Count
|
|
||||||
page := this.NewPage(count)
|
|
||||||
this.Data["page"] = page.AsHTML()
|
|
||||||
|
|
||||||
tasksResp, err := this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{
|
countAll := int64(0)
|
||||||
AdminId: this.AdminId(),
|
countAvailable := int64(0)
|
||||||
UserId: 0,
|
countExpired := int64(0)
|
||||||
Offset: page.Offset,
|
count7Days := int64(0)
|
||||||
Size: page.Size,
|
count30Days := int64(0)
|
||||||
})
|
|
||||||
|
// 计算数量
|
||||||
|
{
|
||||||
|
// all
|
||||||
|
resp, err := this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
|
||||||
|
Keyword: params.Keyword,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
countAll = resp.Count
|
||||||
|
|
||||||
|
// available
|
||||||
|
resp, err = this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
|
||||||
|
IsAvailable: true,
|
||||||
|
Keyword: params.Keyword,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
countAvailable = resp.Count
|
||||||
|
|
||||||
|
// expired
|
||||||
|
resp, err = this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
|
||||||
|
IsExpired: true,
|
||||||
|
Keyword: params.Keyword,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
countExpired = resp.Count
|
||||||
|
|
||||||
|
// expire in 7 days
|
||||||
|
resp, err = this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
|
||||||
|
ExpiringDays: 7,
|
||||||
|
Keyword: params.Keyword,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
count7Days = resp.Count
|
||||||
|
|
||||||
|
// expire in 30 days
|
||||||
|
resp, err = this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
|
||||||
|
ExpiringDays: 30,
|
||||||
|
Keyword: params.Keyword,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
count30Days = resp.Count
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Data["countAll"] = countAll
|
||||||
|
this.Data["countAvailable"] = countAvailable
|
||||||
|
this.Data["countExpired"] = countExpired
|
||||||
|
this.Data["count7Days"] = count7Days
|
||||||
|
this.Data["count30Days"] = count30Days
|
||||||
|
|
||||||
|
// 分页
|
||||||
|
var page *actionutils.Page
|
||||||
|
var tasksResp *pb.ListEnabledACMETasksResponse
|
||||||
|
var err error
|
||||||
|
switch params.Type {
|
||||||
|
case "":
|
||||||
|
page = this.NewPage(countAll)
|
||||||
|
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{
|
||||||
|
Offset: page.Offset,
|
||||||
|
Size: page.Size,
|
||||||
|
Keyword: params.Keyword,
|
||||||
|
})
|
||||||
|
case "available":
|
||||||
|
page = this.NewPage(countAvailable)
|
||||||
|
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{IsAvailable: true, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
|
||||||
|
case "expired":
|
||||||
|
page = this.NewPage(countExpired)
|
||||||
|
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{IsExpired: true, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
|
||||||
|
case "7days":
|
||||||
|
page = this.NewPage(count7Days)
|
||||||
|
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{ExpiringDays: 7, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
|
||||||
|
case "30days":
|
||||||
|
page = this.NewPage(count30Days)
|
||||||
|
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{ExpiringDays: 30, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
|
||||||
|
default:
|
||||||
|
page = this.NewPage(countAll)
|
||||||
|
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{
|
||||||
|
Keyword: params.Keyword,
|
||||||
|
Offset: page.Offset,
|
||||||
|
Size: page.Size,
|
||||||
|
})
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.ErrorPage(err)
|
this.ErrorPage(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.Data["page"] = page.AsHTML()
|
||||||
|
|
||||||
taskMaps := []maps.Map{}
|
taskMaps := []maps.Map{}
|
||||||
for _, task := range tasksResp.AcmeTasks {
|
for _, task := range tasksResp.AcmeTasks {
|
||||||
if task.AcmeUser == nil {
|
if task.AcmeUser == nil {
|
||||||
|
|||||||
@@ -138,6 +138,8 @@ func (this *IndexAction) RunGet(params struct {
|
|||||||
page = this.NewPage(countAll)
|
page = this.NewPage(countAll)
|
||||||
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
|
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
|
||||||
Keyword: params.Keyword,
|
Keyword: params.Keyword,
|
||||||
|
Offset: page.Offset,
|
||||||
|
Size: page.Size,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||||||
"github.com/iwind/TeaGo/actions"
|
"github.com/iwind/TeaGo/actions"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -33,6 +34,9 @@ func (this *AddPopupAction) RunGet(params struct {
|
|||||||
serverType := serverTypeResp.Type
|
serverType := serverTypeResp.Type
|
||||||
this.Data["serverType"] = serverType
|
this.Data["serverType"] = serverType
|
||||||
|
|
||||||
|
// 是否为HTTP
|
||||||
|
this.Data["isHTTP"] = serverType == "httpProxy" || serverType == "httpWeb"
|
||||||
|
|
||||||
this.Show()
|
this.Show()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,8 +48,15 @@ func (this *AddPopupAction) RunPost(params struct {
|
|||||||
Protocol string
|
Protocol string
|
||||||
Addr string
|
Addr string
|
||||||
Name string
|
Name string
|
||||||
Description string
|
|
||||||
IsOn bool
|
ConnTimeout int
|
||||||
|
ReadTimeout int
|
||||||
|
MaxConns int32
|
||||||
|
MaxIdleConns int32
|
||||||
|
IdleTimeout int
|
||||||
|
|
||||||
|
Description string
|
||||||
|
IsOn bool
|
||||||
|
|
||||||
Must *actions.Must
|
Must *actions.Must
|
||||||
}) {
|
}) {
|
||||||
@@ -61,6 +72,33 @@ func (this *AddPopupAction) RunPost(params struct {
|
|||||||
host := addr[:portIndex]
|
host := addr[:portIndex]
|
||||||
port := addr[portIndex+1:]
|
port := addr[portIndex+1:]
|
||||||
|
|
||||||
|
connTimeoutJSON, err := (&shared.TimeDuration{
|
||||||
|
Count: int64(params.ConnTimeout),
|
||||||
|
Unit: shared.TimeDurationUnitSecond,
|
||||||
|
}).AsJSON()
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
readTimeoutJSON, err := (&shared.TimeDuration{
|
||||||
|
Count: int64(params.ReadTimeout),
|
||||||
|
Unit: shared.TimeDurationUnitSecond,
|
||||||
|
}).AsJSON()
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
idleTimeoutJSON, err := (&shared.TimeDuration{
|
||||||
|
Count: int64(params.IdleTimeout),
|
||||||
|
Unit: shared.TimeDurationUnitSecond,
|
||||||
|
}).AsJSON()
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
createResp, err := this.RPC().OriginRPC().CreateOrigin(this.AdminContext(), &pb.CreateOriginRequest{
|
createResp, err := this.RPC().OriginRPC().CreateOrigin(this.AdminContext(), &pb.CreateOriginRequest{
|
||||||
Name: params.Name,
|
Name: params.Name,
|
||||||
Addr: &pb.NetworkAddress{
|
Addr: &pb.NetworkAddress{
|
||||||
@@ -68,9 +106,14 @@ func (this *AddPopupAction) RunPost(params struct {
|
|||||||
Host: host,
|
Host: host,
|
||||||
PortRange: port,
|
PortRange: port,
|
||||||
},
|
},
|
||||||
Description: params.Description,
|
Description: params.Description,
|
||||||
Weight: params.Weight,
|
Weight: params.Weight,
|
||||||
IsOn: params.IsOn,
|
IsOn: params.IsOn,
|
||||||
|
ConnTimeoutJSON: connTimeoutJSON,
|
||||||
|
ReadTimeoutJSON: readTimeoutJSON,
|
||||||
|
IdleTimeoutJSON: idleTimeoutJSON,
|
||||||
|
MaxConns: params.MaxConns,
|
||||||
|
MaxIdleConns: params.MaxIdleConns,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.ErrorPage(err)
|
this.ErrorPage(err)
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||||||
"github.com/iwind/TeaGo/actions"
|
"github.com/iwind/TeaGo/actions"
|
||||||
"github.com/iwind/TeaGo/maps"
|
"github.com/iwind/TeaGo/maps"
|
||||||
|
"github.com/iwind/TeaGo/types"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -39,6 +41,10 @@ func (this *UpdatePopupAction) RunGet(params struct {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.Data["serverType"] = serverTypeResp.Type
|
this.Data["serverType"] = serverTypeResp.Type
|
||||||
|
serverType := serverTypeResp.Type
|
||||||
|
|
||||||
|
// 是否为HTTP
|
||||||
|
this.Data["isHTTP"] = serverType == "httpProxy" || serverType == "httpWeb"
|
||||||
|
|
||||||
// 源站信息
|
// 源站信息
|
||||||
originResp, err := this.RPC().OriginRPC().FindEnabledOriginConfig(this.AdminContext(), &pb.FindEnabledOriginConfigRequest{OriginId: params.OriginId})
|
originResp, err := this.RPC().OriginRPC().FindEnabledOriginConfig(this.AdminContext(), &pb.FindEnabledOriginConfigRequest{OriginId: params.OriginId})
|
||||||
@@ -54,14 +60,34 @@ func (this *UpdatePopupAction) RunGet(params struct {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
connTimeout := 0
|
||||||
|
readTimeout := 0
|
||||||
|
idleTimeout := 0
|
||||||
|
if config.ConnTimeout != nil {
|
||||||
|
connTimeout = types.Int(config.ConnTimeout.Count)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.ReadTimeout != nil {
|
||||||
|
readTimeout = types.Int(config.ReadTimeout.Count)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.IdleTimeout != nil {
|
||||||
|
idleTimeout = types.Int(config.IdleTimeout.Count)
|
||||||
|
}
|
||||||
|
|
||||||
this.Data["origin"] = maps.Map{
|
this.Data["origin"] = maps.Map{
|
||||||
"id": config.Id,
|
"id": config.Id,
|
||||||
"protocol": config.Addr.Protocol,
|
"protocol": config.Addr.Protocol,
|
||||||
"addr": config.Addr.Host + ":" + config.Addr.PortRange,
|
"addr": config.Addr.Host + ":" + config.Addr.PortRange,
|
||||||
"weight": config.Weight,
|
"weight": config.Weight,
|
||||||
"name": config.Name,
|
"name": config.Name,
|
||||||
"description": config.Description,
|
"description": config.Description,
|
||||||
"isOn": config.IsOn,
|
"isOn": config.IsOn,
|
||||||
|
"connTimeout": connTimeout,
|
||||||
|
"readTimeout": readTimeout,
|
||||||
|
"idleTimeout": idleTimeout,
|
||||||
|
"maxConns": config.MaxConns,
|
||||||
|
"maxIdleConns": config.MaxIdleConns,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.Show()
|
this.Show()
|
||||||
@@ -76,8 +102,15 @@ func (this *UpdatePopupAction) RunPost(params struct {
|
|||||||
Addr string
|
Addr string
|
||||||
Weight int32
|
Weight int32
|
||||||
Name string
|
Name string
|
||||||
Description string
|
|
||||||
IsOn bool
|
ConnTimeout int
|
||||||
|
ReadTimeout int
|
||||||
|
MaxConns int32
|
||||||
|
MaxIdleConns int32
|
||||||
|
IdleTimeout int
|
||||||
|
|
||||||
|
Description string
|
||||||
|
IsOn bool
|
||||||
|
|
||||||
Must *actions.Must
|
Must *actions.Must
|
||||||
}) {
|
}) {
|
||||||
@@ -93,7 +126,34 @@ func (this *UpdatePopupAction) RunPost(params struct {
|
|||||||
host := addr[:portIndex]
|
host := addr[:portIndex]
|
||||||
port := addr[portIndex+1:]
|
port := addr[portIndex+1:]
|
||||||
|
|
||||||
_, err := this.RPC().OriginRPC().UpdateOrigin(this.AdminContext(), &pb.UpdateOriginRequest{
|
connTimeoutJSON, err := (&shared.TimeDuration{
|
||||||
|
Count: int64(params.ConnTimeout),
|
||||||
|
Unit: shared.TimeDurationUnitSecond,
|
||||||
|
}).AsJSON()
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
readTimeoutJSON, err := (&shared.TimeDuration{
|
||||||
|
Count: int64(params.ReadTimeout),
|
||||||
|
Unit: shared.TimeDurationUnitSecond,
|
||||||
|
}).AsJSON()
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
idleTimeoutJSON, err := (&shared.TimeDuration{
|
||||||
|
Count: int64(params.IdleTimeout),
|
||||||
|
Unit: shared.TimeDurationUnitSecond,
|
||||||
|
}).AsJSON()
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = this.RPC().OriginRPC().UpdateOrigin(this.AdminContext(), &pb.UpdateOriginRequest{
|
||||||
OriginId: params.OriginId,
|
OriginId: params.OriginId,
|
||||||
Name: params.Name,
|
Name: params.Name,
|
||||||
Addr: &pb.NetworkAddress{
|
Addr: &pb.NetworkAddress{
|
||||||
@@ -101,9 +161,14 @@ func (this *UpdatePopupAction) RunPost(params struct {
|
|||||||
Host: host,
|
Host: host,
|
||||||
PortRange: port,
|
PortRange: port,
|
||||||
},
|
},
|
||||||
Description: params.Description,
|
Description: params.Description,
|
||||||
Weight: params.Weight,
|
Weight: params.Weight,
|
||||||
IsOn: params.IsOn,
|
IsOn: params.IsOn,
|
||||||
|
ConnTimeoutJSON: connTimeoutJSON,
|
||||||
|
ReadTimeoutJSON: readTimeoutJSON,
|
||||||
|
IdleTimeoutJSON: idleTimeoutJSON,
|
||||||
|
MaxConns: params.MaxConns,
|
||||||
|
MaxIdleConns: params.MaxIdleConns,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.ErrorPage(err)
|
this.ErrorPage(err)
|
||||||
|
|||||||
@@ -24,9 +24,12 @@ func (this *CreatePopupAction) RunGet(params struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *CreatePopupAction) RunPost(params struct {
|
func (this *CreatePopupAction) RunPost(params struct {
|
||||||
BeforeURL string
|
BeforeURL string
|
||||||
AfterURL string
|
AfterURL string
|
||||||
Status int
|
MatchPrefix bool
|
||||||
|
KeepRequestURI bool
|
||||||
|
|
||||||
|
Status int
|
||||||
|
|
||||||
Must *actions.Must
|
Must *actions.Must
|
||||||
CSRF *actionutils.CSRF
|
CSRF *actionutils.CSRF
|
||||||
@@ -69,10 +72,12 @@ func (this *CreatePopupAction) RunPost(params struct {
|
|||||||
Gte(0, "请选择正确的跳转状态码")
|
Gte(0, "请选择正确的跳转状态码")
|
||||||
|
|
||||||
this.Data["redirect"] = maps.Map{
|
this.Data["redirect"] = maps.Map{
|
||||||
"status": params.Status,
|
"status": params.Status,
|
||||||
"beforeURL": params.BeforeURL,
|
"beforeURL": params.BeforeURL,
|
||||||
"afterURL": params.AfterURL,
|
"afterURL": params.AfterURL,
|
||||||
"isOn": true,
|
"matchPrefix": params.MatchPrefix,
|
||||||
|
"keepRequestURI": params.KeepRequestURI,
|
||||||
|
"isOn": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.Success()
|
this.Success()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||||||
"github.com/iwind/TeaGo/actions"
|
"github.com/iwind/TeaGo/actions"
|
||||||
"github.com/iwind/TeaGo/types"
|
"github.com/iwind/TeaGo/types"
|
||||||
)
|
)
|
||||||
@@ -68,6 +69,33 @@ func (this *SettingAction) RunPost(params struct {
|
|||||||
this.Fail("配置校验失败:" + err.Error())
|
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{
|
_, err = this.RPC().ServerRPC().UpdateServerReverseProxy(this.AdminContext(), &pb.UpdateServerReverseProxyRequest{
|
||||||
ServerId: params.ServerId,
|
ServerId: params.ServerId,
|
||||||
@@ -87,6 +115,11 @@ func (this *SettingAction) RunPost(params struct {
|
|||||||
StripPrefix: reverseProxyConfig.StripPrefix,
|
StripPrefix: reverseProxyConfig.StripPrefix,
|
||||||
AutoFlush: reverseProxyConfig.AutoFlush,
|
AutoFlush: reverseProxyConfig.AutoFlush,
|
||||||
AddHeaders: reverseProxyConfig.AddHeaders,
|
AddHeaders: reverseProxyConfig.AddHeaders,
|
||||||
|
ConnTimeoutJSON: connTimeoutJSON,
|
||||||
|
ReadTimeoutJSON: readTimeoutJSON,
|
||||||
|
IdleTimeoutJSON: idleTimeoutJSON,
|
||||||
|
MaxConns: types.Int32(reverseProxyConfig.MaxConns),
|
||||||
|
MaxIdleConns: types.Int32(reverseProxyConfig.MaxIdleConns),
|
||||||
})
|
})
|
||||||
|
|
||||||
this.Success()
|
this.Success()
|
||||||
|
|||||||
70
internal/web/actions/default/settings/authority/index.go
Normal file
70
internal/web/actions/default/settings/authority/index.go
Normal 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()
|
||||||
|
}
|
||||||
19
internal/web/actions/default/settings/authority/init.go
Normal file
19
internal/web/actions/default/settings/authority/init.go
Normal 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()
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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) {
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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) {
|
||||||
|
}
|
||||||
75
internal/web/actions/default/settings/monitor-nodes/index.go
Normal file
75
internal/web/actions/default/settings/monitor-nodes/index.go
Normal 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()
|
||||||
|
}
|
||||||
23
internal/web/actions/default/settings/monitor-nodes/init.go
Normal file
23
internal/web/actions/default/settings/monitor-nodes/init.go
Normal 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()
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||||||
"github.com/iwind/TeaGo/actions"
|
"github.com/iwind/TeaGo/actions"
|
||||||
"github.com/iwind/TeaGo/maps"
|
"github.com/iwind/TeaGo/maps"
|
||||||
)
|
)
|
||||||
@@ -23,6 +24,9 @@ func (this *IndexAction) RunGet(params struct{}) {
|
|||||||
this.ErrorPage(err)
|
this.ErrorPage(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if config.AllowIPs == nil {
|
||||||
|
config.AllowIPs = []string{}
|
||||||
|
}
|
||||||
|
|
||||||
// 国家和地区
|
// 国家和地区
|
||||||
countryMaps := []maps.Map{}
|
countryMaps := []maps.Map{}
|
||||||
@@ -69,6 +73,7 @@ func (this *IndexAction) RunPost(params struct {
|
|||||||
CountryIdsJSON []byte
|
CountryIdsJSON []byte
|
||||||
ProvinceIdsJSON []byte
|
ProvinceIdsJSON []byte
|
||||||
AllowLocal bool
|
AllowLocal bool
|
||||||
|
AllowIPs []string
|
||||||
|
|
||||||
Must *actions.Must
|
Must *actions.Must
|
||||||
CSRF *actionutils.CSRF
|
CSRF *actionutils.CSRF
|
||||||
@@ -106,6 +111,19 @@ func (this *IndexAction) RunPost(params struct {
|
|||||||
}
|
}
|
||||||
config.AllowProvinceIds = provinceIds
|
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
|
config.AllowLocal = params.AllowLocal
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package settingutils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||||
|
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
"github.com/iwind/TeaGo/actions"
|
"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("API节点", "", "/api", "", this.tab == "apiNodes")
|
||||||
tabbar.Add("用户节点", "", "/settings/userNodes", "", this.tab == "userNodes")
|
tabbar.Add("用户节点", "", "/settings/userNodes", "", this.tab == "userNodes")
|
||||||
tabbar.Add("日志数据库", "", "/db", "", this.tab == "dbNodes")
|
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")
|
//tabbar.Add("备份", "", "/settings/backup", "", this.tab == "backup")
|
||||||
}
|
}
|
||||||
actionutils.SetTabbar(actionPtr, tabbar)
|
actionutils.SetTabbar(actionPtr, tabbar)
|
||||||
|
|||||||
@@ -140,10 +140,11 @@ func (this *userMustAuth) modules(adminId int64) []maps.Map {
|
|||||||
"icon": "dashboard",
|
"icon": "dashboard",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "servers",
|
"code": "servers",
|
||||||
"module": configloaders.AdminModuleCodeServer,
|
"module": configloaders.AdminModuleCodeServer,
|
||||||
"name": "网站服务",
|
"name": "网站服务",
|
||||||
"icon": "clone outsize",
|
"subtitle": "服务列表",
|
||||||
|
"icon": "clone outsize",
|
||||||
"subItems": []maps.Map{
|
"subItems": []maps.Map{
|
||||||
{
|
{
|
||||||
"name": "通用设置",
|
"name": "通用设置",
|
||||||
@@ -173,10 +174,11 @@ func (this *userMustAuth) modules(adminId int64) []maps.Map {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "clusters",
|
"code": "clusters",
|
||||||
"module": configloaders.AdminModuleCodeNode,
|
"module": configloaders.AdminModuleCodeNode,
|
||||||
"name": "边缘节点",
|
"name": "边缘节点",
|
||||||
"icon": "cloud",
|
"subtitle": "集群列表",
|
||||||
|
"icon": "cloud",
|
||||||
"subItems": []maps.Map{
|
"subItems": []maps.Map{
|
||||||
{
|
{
|
||||||
"name": "SSH认证",
|
"name": "SSH认证",
|
||||||
@@ -191,10 +193,11 @@ func (this *userMustAuth) modules(adminId int64) []maps.Map {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "dns",
|
"code": "dns",
|
||||||
"module": configloaders.AdminModuleCodeDNS,
|
"module": configloaders.AdminModuleCodeDNS,
|
||||||
"name": "域名解析",
|
"name": "域名解析",
|
||||||
"icon": "globe",
|
"subtitle": "集群列表",
|
||||||
|
"icon": "globe",
|
||||||
"subItems": []maps.Map{
|
"subItems": []maps.Map{
|
||||||
{
|
{
|
||||||
"name": "问题修复",
|
"name": "问题修复",
|
||||||
@@ -221,10 +224,19 @@ func (this *userMustAuth) modules(adminId int64) []maps.Map {
|
|||||||
"icon": "yen sign",
|
"icon": "yen sign",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "admins",
|
"code": "admins",
|
||||||
"module": configloaders.AdminModuleCodeAdmin,
|
"module": configloaders.AdminModuleCodeAdmin,
|
||||||
"name": "系统用户",
|
"name": "系统用户",
|
||||||
"icon": "user secret",
|
"subtitle": "用户列表",
|
||||||
|
"icon": "user secret",
|
||||||
|
"subItems": []maps.Map{
|
||||||
|
{
|
||||||
|
"name": "通知媒介",
|
||||||
|
"url": "/admins/recipients",
|
||||||
|
"code": "recipients",
|
||||||
|
"isOn": teaconst.IsPlus,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "log",
|
"code": "log",
|
||||||
@@ -233,10 +245,11 @@ func (this *userMustAuth) modules(adminId int64) []maps.Map {
|
|||||||
"icon": "history",
|
"icon": "history",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "settings",
|
"code": "settings",
|
||||||
"module": configloaders.AdminModuleCodeSetting,
|
"module": configloaders.AdminModuleCodeSetting,
|
||||||
"name": "系统设置",
|
"name": "系统设置",
|
||||||
"icon": "setting",
|
"subtitle": "基本设置",
|
||||||
|
"icon": "setting",
|
||||||
"subItems": []maps.Map{
|
"subItems": []maps.Map{
|
||||||
{
|
{
|
||||||
"name": "高级设置",
|
"name": "高级设置",
|
||||||
|
|||||||
@@ -89,6 +89,16 @@ func checkIPWithoutCache(config *systemconfigs.SecurityConfig, ipAddr string) bo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查单独允许的IP
|
||||||
|
if len(config.AllowIPRanges()) > 0 {
|
||||||
|
for _, r := range config.AllowIPRanges() {
|
||||||
|
if r.Contains(ipAddr) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ import (
|
|||||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/tasks"
|
_ "github.com/TeaOSLab/EdgeAdmin/internal/tasks"
|
||||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/about"
|
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/about"
|
||||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/admins"
|
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/admins"
|
||||||
|
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/admins/recipients"
|
||||||
|
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/admins/recipients/groups"
|
||||||
|
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/admins/recipients/instances"
|
||||||
|
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/admins/recipients/logs"
|
||||||
|
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/admins/recipients/tasks"
|
||||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/api"
|
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/api"
|
||||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/api/node"
|
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/api/node"
|
||||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters"
|
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters"
|
||||||
@@ -79,10 +84,15 @@ import (
|
|||||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/websocket"
|
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/websocket"
|
||||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/stat"
|
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/stat"
|
||||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings"
|
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings"
|
||||||
|
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/authority"
|
||||||
|
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/authority/nodes"
|
||||||
|
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/authority/nodes/node"
|
||||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/backup"
|
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/backup"
|
||||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/database"
|
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/database"
|
||||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/ip-library"
|
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/ip-library"
|
||||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/login"
|
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/login"
|
||||||
|
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/monitor-nodes"
|
||||||
|
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/monitor-nodes/node"
|
||||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/profile"
|
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/profile"
|
||||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/security"
|
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/security"
|
||||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/server"
|
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/server"
|
||||||
|
|||||||
773
web/public/codemirror/AUTHORS
Normal file
773
web/public/codemirror/AUTHORS
Normal file
@@ -0,0 +1,773 @@
|
|||||||
|
List of CodeMirror contributors. Updated before every release.
|
||||||
|
|
||||||
|
4oo4
|
||||||
|
4r2r
|
||||||
|
Aaron Brooks
|
||||||
|
Abdelouahab
|
||||||
|
Abdussalam Abdurrahman
|
||||||
|
Abe Fettig
|
||||||
|
Abhishek Gahlot
|
||||||
|
Adam Ahmed
|
||||||
|
Adam King
|
||||||
|
Adam Particka
|
||||||
|
adanlobato
|
||||||
|
Adán Lobato
|
||||||
|
Adrian Aichner
|
||||||
|
Adrian Heine
|
||||||
|
Adrien Bertrand
|
||||||
|
aeroson
|
||||||
|
Ahmad Amireh
|
||||||
|
Ahmad M. Zawawi
|
||||||
|
ahoward
|
||||||
|
Akeksandr Motsjonov
|
||||||
|
Alasdair Smith
|
||||||
|
AlbertHilb
|
||||||
|
Alberto González Palomo
|
||||||
|
Alberto Pose
|
||||||
|
Albert Xing
|
||||||
|
Alexander Pavlov
|
||||||
|
Alexander Schepanovski
|
||||||
|
Alexander Shvets
|
||||||
|
Alexander Solovyov
|
||||||
|
Alexandre Bique
|
||||||
|
alexey-k
|
||||||
|
Alex Piggott
|
||||||
|
Aliaksei Chapyzhenka
|
||||||
|
Allen Sarkisyan
|
||||||
|
Ami Fischman
|
||||||
|
Amin Shali
|
||||||
|
Amin Ullah Khan
|
||||||
|
amshali@google.com
|
||||||
|
Amsul
|
||||||
|
amuntean
|
||||||
|
Amy
|
||||||
|
Ananya Sen
|
||||||
|
anaran
|
||||||
|
AndersMad
|
||||||
|
Anders Nawroth
|
||||||
|
Anderson Mesquita
|
||||||
|
Anders Wåglund
|
||||||
|
Andrea G
|
||||||
|
Andreas Reischuck
|
||||||
|
Andres Taylor
|
||||||
|
Andre von Houck
|
||||||
|
Andrew Cheng
|
||||||
|
Andrew Dassonville
|
||||||
|
Andrey Fedorov
|
||||||
|
Andrey Klyuchnikov
|
||||||
|
Andrey Lushnikov
|
||||||
|
Andrey Shchekin
|
||||||
|
Andy Joslin
|
||||||
|
Andy Kimball
|
||||||
|
Andy Li
|
||||||
|
Angelo
|
||||||
|
angelozerr
|
||||||
|
angelo.zerr@gmail.com
|
||||||
|
Ankit
|
||||||
|
Ankit Ahuja
|
||||||
|
Ansel Santosa
|
||||||
|
Anthony Dugois
|
||||||
|
anthonygego
|
||||||
|
Anthony Gégo
|
||||||
|
Anthony Grimes
|
||||||
|
Anton Kovalyov
|
||||||
|
Apollo Zhu
|
||||||
|
AQNOUCH Mohammed
|
||||||
|
Aram Shatakhtsyan
|
||||||
|
areos
|
||||||
|
Arnab Bose
|
||||||
|
Arsène von Wyss
|
||||||
|
Arthur Müller
|
||||||
|
Arun Narasani
|
||||||
|
as3boyan
|
||||||
|
asolove
|
||||||
|
atelierbram
|
||||||
|
AtomicPages LLC
|
||||||
|
Atul Bhouraskar
|
||||||
|
Aurelian Oancea
|
||||||
|
Axel Lewenhaupt
|
||||||
|
Baptiste Augrain
|
||||||
|
Barret Rennie
|
||||||
|
Bartosz Dziewoński
|
||||||
|
Basarat Ali Syed
|
||||||
|
Bastian Müller
|
||||||
|
belhaj
|
||||||
|
Bem Jones-Bey
|
||||||
|
benbro
|
||||||
|
Beni Cherniavsky-Paskin
|
||||||
|
Benjamin DeCoste
|
||||||
|
Benjamin Young
|
||||||
|
Ben Keen
|
||||||
|
Ben Miller
|
||||||
|
Ben Mosher
|
||||||
|
Bernhard Sirlinger
|
||||||
|
Bert Chang
|
||||||
|
Bharad
|
||||||
|
BigBlueHat
|
||||||
|
Billy Moon
|
||||||
|
binny
|
||||||
|
Bjorn Hansen
|
||||||
|
B Krishna Chaitanya
|
||||||
|
Blaine G
|
||||||
|
blukat29
|
||||||
|
Bo
|
||||||
|
boomyjee
|
||||||
|
Bo Peng
|
||||||
|
borawjm
|
||||||
|
Brad Metcalf
|
||||||
|
Brandon Frohs
|
||||||
|
Brandon Wamboldt
|
||||||
|
Bret Little
|
||||||
|
Brett Zamir
|
||||||
|
Brian Grinstead
|
||||||
|
Brian Sletten
|
||||||
|
brrd
|
||||||
|
Bruce Mitchener
|
||||||
|
Bryan Massoth
|
||||||
|
Caitlin Potter
|
||||||
|
Calin Barbat
|
||||||
|
callodacity
|
||||||
|
Camilo Roca
|
||||||
|
Casey Klebba
|
||||||
|
César González Íñiguez
|
||||||
|
Chad Jolly
|
||||||
|
Chandra Sekhar Pydi
|
||||||
|
Charles Skelton
|
||||||
|
Cheah Chu Yeow
|
||||||
|
Chris Coyier
|
||||||
|
Chris Ford
|
||||||
|
Chris Granger
|
||||||
|
Chris Houseknecht
|
||||||
|
Chris Lohfink
|
||||||
|
Chris Morgan
|
||||||
|
Chris Reeves
|
||||||
|
Chris Smith
|
||||||
|
Christian Gruen
|
||||||
|
Christian Oyarzun
|
||||||
|
Christian Petrov
|
||||||
|
christopherblaser
|
||||||
|
Christopher Brown
|
||||||
|
Christopher Kramer
|
||||||
|
Christopher Mitchell
|
||||||
|
Christopher Pfohl
|
||||||
|
Chunliang Lyu
|
||||||
|
ciaranj
|
||||||
|
CodeAnimal
|
||||||
|
CodeBitt
|
||||||
|
coderaiser
|
||||||
|
Cole R Lawrence
|
||||||
|
ComFreek
|
||||||
|
Cristian Prieto
|
||||||
|
Curran Kelleher
|
||||||
|
Curtis Gagliardi
|
||||||
|
dagsta
|
||||||
|
daines
|
||||||
|
Dale Jung
|
||||||
|
Dan Bentley
|
||||||
|
Dan Heberden
|
||||||
|
Daniel, Dao Quang Minh
|
||||||
|
Daniele Di Sarli
|
||||||
|
Daniel Faust
|
||||||
|
Daniel Hanggi
|
||||||
|
Daniel Huigens
|
||||||
|
Daniel Kesler
|
||||||
|
Daniel KJ
|
||||||
|
Daniel Neel
|
||||||
|
Daniel Parnell
|
||||||
|
Daniel Thwaites
|
||||||
|
Danila Malyutin
|
||||||
|
Danny Yoo
|
||||||
|
darealshinji
|
||||||
|
Darius Roberts
|
||||||
|
databricks-david-lewis
|
||||||
|
Dave Brondsema
|
||||||
|
Dave MacLachlan
|
||||||
|
Dave Myers
|
||||||
|
David Barnett
|
||||||
|
David H. Bronke
|
||||||
|
David Mignot
|
||||||
|
David Pathakjee
|
||||||
|
David Santana
|
||||||
|
David Vázquez
|
||||||
|
David Whittington
|
||||||
|
deebugger
|
||||||
|
Deep Thought
|
||||||
|
Devin Abbott
|
||||||
|
Devon Carew
|
||||||
|
Dick Choi
|
||||||
|
dignifiedquire
|
||||||
|
Dimage Sapelkin
|
||||||
|
dmaclach
|
||||||
|
Dmitry Kiselyov
|
||||||
|
domagoj412
|
||||||
|
Dominator008
|
||||||
|
Domizio Demichelis
|
||||||
|
Doug Blank
|
||||||
|
Doug Wikle
|
||||||
|
Drew Bratcher
|
||||||
|
Drew Hintz
|
||||||
|
Drew Khoury
|
||||||
|
Drini Cami
|
||||||
|
Dror BG
|
||||||
|
duralog
|
||||||
|
dwelle
|
||||||
|
eborden
|
||||||
|
edsharp
|
||||||
|
ekhaled
|
||||||
|
Elisée
|
||||||
|
Emmanuel Schanzer
|
||||||
|
Enam Mijbah Noor
|
||||||
|
Eric Allam
|
||||||
|
Eric Bogard
|
||||||
|
Erik Welander
|
||||||
|
eustas
|
||||||
|
Fabien Dubosson
|
||||||
|
Fabien O'Carroll
|
||||||
|
Fabio Zendhi Nagao
|
||||||
|
Faiza Alsaied
|
||||||
|
Fauntleroy
|
||||||
|
fbuchinger
|
||||||
|
feizhang365
|
||||||
|
Felipe Lalanne
|
||||||
|
Felix Raab
|
||||||
|
ficristo
|
||||||
|
Filip Noetzel
|
||||||
|
Filip Stollár
|
||||||
|
Filype Pereira
|
||||||
|
finalfantasia
|
||||||
|
flack
|
||||||
|
Florian Felten
|
||||||
|
ForbesLindesay
|
||||||
|
Forbes Lindesay
|
||||||
|
Ford_Lawnmower
|
||||||
|
Forrest Oliphant
|
||||||
|
Franco Catena
|
||||||
|
Frank Wiegand
|
||||||
|
Fredrik Borg
|
||||||
|
Gabriel Gheorghian
|
||||||
|
Gabriel Horner
|
||||||
|
Gabriel Nahmias
|
||||||
|
galambalazs
|
||||||
|
Gary Sheng
|
||||||
|
Gautam Mehta
|
||||||
|
Gavin Douglas
|
||||||
|
gekkoe
|
||||||
|
Geordie Hall
|
||||||
|
George Stephanis
|
||||||
|
geowarin
|
||||||
|
Gerard Braad
|
||||||
|
Gergely Hegykozi
|
||||||
|
Giovanni Calò
|
||||||
|
Glebov Boris
|
||||||
|
Glenn Jorde
|
||||||
|
Glenn Ruehle
|
||||||
|
goldsmcb
|
||||||
|
Golevka
|
||||||
|
Google LLC
|
||||||
|
Gordon Smith
|
||||||
|
Grant Skinner
|
||||||
|
greengiant
|
||||||
|
Gregory Koberger
|
||||||
|
Grzegorz Mazur
|
||||||
|
Guan Gui
|
||||||
|
Guillaume Massé
|
||||||
|
Guillaume Massé
|
||||||
|
guraga
|
||||||
|
Gustavo Rodrigues
|
||||||
|
Hakan Tunc
|
||||||
|
Hans Engel
|
||||||
|
Harald Schilly
|
||||||
|
Hardest
|
||||||
|
Harshvardhan Gupta
|
||||||
|
Hasan Karahan
|
||||||
|
Heanes
|
||||||
|
Hector Oswaldo Caballero
|
||||||
|
Hélio
|
||||||
|
Hendrik Wallbaum
|
||||||
|
Henrik Haugbølle
|
||||||
|
Herculano Campos
|
||||||
|
hidaiy
|
||||||
|
Hiroyuki Makino
|
||||||
|
hitsthings
|
||||||
|
Hocdoc
|
||||||
|
Hugues Malphettes
|
||||||
|
Ian Beck
|
||||||
|
Ian Davies
|
||||||
|
Ian Dickinson
|
||||||
|
Ian Rose
|
||||||
|
Ian Wehrman
|
||||||
|
Ian Wetherbee
|
||||||
|
Ice White
|
||||||
|
ICHIKAWA, Yuji
|
||||||
|
idleberg
|
||||||
|
ilvalle
|
||||||
|
Ingo Richter
|
||||||
|
Irakli Gozalishvili
|
||||||
|
Ivan Kurnosov
|
||||||
|
Ivoah
|
||||||
|
Jacob Lee
|
||||||
|
Jaimin
|
||||||
|
Jake Peyser
|
||||||
|
Jakob Miland
|
||||||
|
Jakub Vrana
|
||||||
|
Jakub Vrána
|
||||||
|
James Campos
|
||||||
|
James Howard
|
||||||
|
James Thorne
|
||||||
|
Jamie Hill
|
||||||
|
Jamie Morris
|
||||||
|
Jan Jongboom
|
||||||
|
jankeromnes
|
||||||
|
Jan Keromnes
|
||||||
|
Jan Odvarko
|
||||||
|
Jan Schär
|
||||||
|
Jan T. Sott
|
||||||
|
Jared Dean
|
||||||
|
Jared Forsyth
|
||||||
|
Jared Jacobs
|
||||||
|
Jason
|
||||||
|
Jason Barnabe
|
||||||
|
Jason Grout
|
||||||
|
Jason Johnston
|
||||||
|
Jason San Jose
|
||||||
|
Jason Siefken
|
||||||
|
Jayaprabhakar
|
||||||
|
Jaydeep Solanki
|
||||||
|
Jean Boussier
|
||||||
|
Jeff Blaisdell
|
||||||
|
Jeff Hanke
|
||||||
|
Jeff Jenkins
|
||||||
|
jeffkenton
|
||||||
|
Jeff Pickhardt
|
||||||
|
jem (graphite)
|
||||||
|
Jeremy Parmenter
|
||||||
|
Jim
|
||||||
|
Jim Avery
|
||||||
|
jkaplon
|
||||||
|
JobJob
|
||||||
|
jochenberger
|
||||||
|
Jochen Berger
|
||||||
|
Joel Einbinder
|
||||||
|
joelpinheiro
|
||||||
|
Johan Ask
|
||||||
|
John Connor
|
||||||
|
John-David Dalton
|
||||||
|
John Engler
|
||||||
|
John Lees-Miller
|
||||||
|
John Snelson
|
||||||
|
John Van Der Loo
|
||||||
|
Jon Ander Peñalba
|
||||||
|
Jonas Döbertin
|
||||||
|
Jonas Helfer
|
||||||
|
Jonathan Hart
|
||||||
|
Jonathan Malmaud
|
||||||
|
Jon Gacnik
|
||||||
|
jongalloway
|
||||||
|
Jon Malmaud
|
||||||
|
Jon Sangster
|
||||||
|
Joost-Wim Boekesteijn
|
||||||
|
Joseph Pecoraro
|
||||||
|
Josh Barnes
|
||||||
|
Josh Cohen
|
||||||
|
Josh Soref
|
||||||
|
Joshua Newman
|
||||||
|
Josh Watzman
|
||||||
|
jots
|
||||||
|
jsoojeon
|
||||||
|
ju1ius
|
||||||
|
Juan Benavides Romero
|
||||||
|
Jucovschi Constantin
|
||||||
|
Juho Vuori
|
||||||
|
Julien CROUZET
|
||||||
|
Julien Rebetez
|
||||||
|
Justin Andresen
|
||||||
|
Justin Hileman
|
||||||
|
jwallers@gmail.com
|
||||||
|
kaniga
|
||||||
|
karevn
|
||||||
|
Karol
|
||||||
|
Kayur Patel
|
||||||
|
Kazuhito Hokamura
|
||||||
|
Ken Newman
|
||||||
|
ken restivo
|
||||||
|
Ken Rockot
|
||||||
|
Kevin Earls
|
||||||
|
Kevin Kwok
|
||||||
|
Kevin Muret
|
||||||
|
Kevin Sawicki
|
||||||
|
Kevin Ushey
|
||||||
|
Kier Darby
|
||||||
|
Klaus Silveira
|
||||||
|
Koh Zi Han, Cliff
|
||||||
|
komakino
|
||||||
|
Konstantin Lopuhin
|
||||||
|
koops
|
||||||
|
Kris Ciccarello
|
||||||
|
ks-ifware
|
||||||
|
kubelsmieci
|
||||||
|
KwanEsq
|
||||||
|
Kyle Kelley
|
||||||
|
KyleMcNutt
|
||||||
|
Lanfei
|
||||||
|
Lanny
|
||||||
|
laobubu
|
||||||
|
Laszlo Vidacs
|
||||||
|
leaf corcoran
|
||||||
|
Lemmon
|
||||||
|
Leonid Khachaturov
|
||||||
|
Leon Sorokin
|
||||||
|
Leonya Khachaturov
|
||||||
|
Liam Newman
|
||||||
|
Libo Cannici
|
||||||
|
Lior Goldberg
|
||||||
|
Lior Shub
|
||||||
|
LloydMilligan
|
||||||
|
LM
|
||||||
|
lochel
|
||||||
|
Lorenzo Simionato
|
||||||
|
Lorenzo Stoakes
|
||||||
|
Louis Mauchet
|
||||||
|
Luca Fabbri
|
||||||
|
Luciano Longo
|
||||||
|
Lu Fangjian
|
||||||
|
Luke Browning
|
||||||
|
Luke Granger-Brown
|
||||||
|
Luke Stagner
|
||||||
|
lynschinzer
|
||||||
|
M1cha
|
||||||
|
Madhura Jayaratne
|
||||||
|
Maksim Lin
|
||||||
|
Maksym Taran
|
||||||
|
Malay Majithia
|
||||||
|
Manideep
|
||||||
|
Manuel Rego Casasnovas
|
||||||
|
Marat Dreizin
|
||||||
|
Marcel Gerber
|
||||||
|
Marcelo Camargo
|
||||||
|
Marco Aurélio
|
||||||
|
Marco Munizaga
|
||||||
|
Marcus Bointon
|
||||||
|
Marek Rudnicki
|
||||||
|
Marijn Haverbeke
|
||||||
|
Mário Gonçalves
|
||||||
|
Mario Pietsch
|
||||||
|
Mark Anderson
|
||||||
|
Mark Lentczner
|
||||||
|
Marko Bonaci
|
||||||
|
Mark Peace
|
||||||
|
Markus Bordihn
|
||||||
|
Markus Olsson
|
||||||
|
Martin Balek
|
||||||
|
Martín Gaitán
|
||||||
|
Martin Hasoň
|
||||||
|
Martin Hunt
|
||||||
|
Martin Laine
|
||||||
|
Martin Zagora
|
||||||
|
Mason Malone
|
||||||
|
Mateusz Paprocki
|
||||||
|
Mathias Bynens
|
||||||
|
mats cronqvist
|
||||||
|
Matt Gaide
|
||||||
|
Matthew Bauer
|
||||||
|
Matthew Beale
|
||||||
|
matthewhayes
|
||||||
|
Matthew Rathbone
|
||||||
|
Matthew Suozzo
|
||||||
|
Matthias Bussonnier
|
||||||
|
Matthias BUSSONNIER
|
||||||
|
Matt MacPherson
|
||||||
|
Matt McDonald
|
||||||
|
Matt Pass
|
||||||
|
Matt Sacks
|
||||||
|
mauricio
|
||||||
|
Maximilian Hils
|
||||||
|
Maxim Kraev
|
||||||
|
Max Kirsch
|
||||||
|
Max Schaefer
|
||||||
|
Max Xiantu
|
||||||
|
mbarkhau
|
||||||
|
McBrainy
|
||||||
|
mce2
|
||||||
|
melpon
|
||||||
|
meshuamam
|
||||||
|
Metatheos
|
||||||
|
Micah Dubinko
|
||||||
|
Michael
|
||||||
|
Michael Goderbauer
|
||||||
|
Michael Grey
|
||||||
|
Michael Kaminsky
|
||||||
|
Michael Lehenbauer
|
||||||
|
Michael Walker
|
||||||
|
Michael Zhou
|
||||||
|
Michal Čihař
|
||||||
|
Michal Dorner
|
||||||
|
Michal Kapiczynski
|
||||||
|
Mighty Guava
|
||||||
|
Miguel Castillo
|
||||||
|
mihailik
|
||||||
|
Mika Andrianarijaona
|
||||||
|
Mike
|
||||||
|
Mike Bostock
|
||||||
|
Mike Brevoort
|
||||||
|
Mike Diaz
|
||||||
|
Mike Ivanov
|
||||||
|
Mike Kadin
|
||||||
|
Mike Kobit
|
||||||
|
Milan Szekely
|
||||||
|
MinRK
|
||||||
|
Miraculix87
|
||||||
|
misfo
|
||||||
|
mkaminsky11
|
||||||
|
mloginov
|
||||||
|
Moritz Schubotz (physikerwelt)
|
||||||
|
Moritz Schwörer
|
||||||
|
Moshe Wajnberg
|
||||||
|
mps
|
||||||
|
ms
|
||||||
|
mtaran-google
|
||||||
|
Mu-An Chiou
|
||||||
|
Mu-An ✌️ Chiou
|
||||||
|
mzabuawala
|
||||||
|
Narciso Jaramillo
|
||||||
|
Nathan Williams
|
||||||
|
ndr
|
||||||
|
Neil Anderson
|
||||||
|
neon-dev
|
||||||
|
nerbert
|
||||||
|
NetworkNode
|
||||||
|
nextrevision
|
||||||
|
ngn
|
||||||
|
nguillaumin
|
||||||
|
Ng Zhi An
|
||||||
|
Nicholas Bollweg
|
||||||
|
Nicholas Bollweg (Nick)
|
||||||
|
NickKolok
|
||||||
|
Nick Kreeger
|
||||||
|
Nick Small
|
||||||
|
Nicolas Kick
|
||||||
|
Nicolò Ribaudo
|
||||||
|
Niels van Groningen
|
||||||
|
nightwing
|
||||||
|
Nikita Beloglazov
|
||||||
|
Nikita Vasilyev
|
||||||
|
Nikolaj Kappler
|
||||||
|
Nikolay Kostov
|
||||||
|
nilp0inter
|
||||||
|
Nisarg Jhaveri
|
||||||
|
nlwillia
|
||||||
|
noragrossman
|
||||||
|
Norman Rzepka
|
||||||
|
opl-
|
||||||
|
Oreoluwa Onatemowo
|
||||||
|
Oskar Segersvärd
|
||||||
|
overdodactyl
|
||||||
|
pablo
|
||||||
|
pabloferz
|
||||||
|
Pablo Zubieta
|
||||||
|
paddya
|
||||||
|
Page
|
||||||
|
paladox
|
||||||
|
Panupong Pasupat
|
||||||
|
paris
|
||||||
|
Paris
|
||||||
|
Paris Kasidiaris
|
||||||
|
Patil Arpith
|
||||||
|
Patrick Stoica
|
||||||
|
Patrick Strawderman
|
||||||
|
Paul Garvin
|
||||||
|
Paul Ivanov
|
||||||
|
Paul Masson
|
||||||
|
Pavel
|
||||||
|
Pavel Feldman
|
||||||
|
Pavel Petržela
|
||||||
|
Pavel Strashkin
|
||||||
|
Paweł Bartkiewicz
|
||||||
|
peteguhl
|
||||||
|
peter
|
||||||
|
Peter Flynn
|
||||||
|
peterkroon
|
||||||
|
Peter Kroon
|
||||||
|
Philipp A
|
||||||
|
Philipp Markovics
|
||||||
|
Philip Stadermann
|
||||||
|
Pi Delport
|
||||||
|
Pierre Gerold
|
||||||
|
Pieter Ouwerkerk
|
||||||
|
Pontus Melke
|
||||||
|
prasanthj
|
||||||
|
Prasanth J
|
||||||
|
Prayag Verma
|
||||||
|
Prendota
|
||||||
|
Qiang Li
|
||||||
|
Radek Piórkowski
|
||||||
|
Rahul
|
||||||
|
Rahul Anand
|
||||||
|
ramwin1
|
||||||
|
Randall Mason
|
||||||
|
Randy Burden
|
||||||
|
Randy Edmunds
|
||||||
|
Randy Luecke
|
||||||
|
Raphael Amorim
|
||||||
|
Rasmus Erik Voel Jensen
|
||||||
|
Rasmus Schultz
|
||||||
|
Raymond Hill
|
||||||
|
ray ratchup
|
||||||
|
Ray Ratchup
|
||||||
|
Remi Nyborg
|
||||||
|
Renaud Durlin
|
||||||
|
Reynold Xin
|
||||||
|
Richard Denton
|
||||||
|
Richard van der Meer
|
||||||
|
Richard Z.H. Wang
|
||||||
|
Rishi Goomar
|
||||||
|
Robert Brignull
|
||||||
|
Robert Crossfield
|
||||||
|
Roberto Abdelkader Martínez Pérez
|
||||||
|
robertop23
|
||||||
|
Robert Plummer
|
||||||
|
Rrandom
|
||||||
|
Rrrandom
|
||||||
|
Ruslan Osmanov
|
||||||
|
Ryan Petrello
|
||||||
|
Ryan Prior
|
||||||
|
ryu-sato
|
||||||
|
sabaca
|
||||||
|
Sam Lee
|
||||||
|
Sam Rawlins
|
||||||
|
Samuel Ainsworth
|
||||||
|
Sam Wilson
|
||||||
|
sandeepshetty
|
||||||
|
Sander AKA Redsandro
|
||||||
|
Sander Verweij
|
||||||
|
santec
|
||||||
|
Sarah McAlear and Wenlin Zhang
|
||||||
|
Sascha Peilicke
|
||||||
|
satamas
|
||||||
|
satchmorun
|
||||||
|
sathyamoorthi
|
||||||
|
Saul Costa
|
||||||
|
S. Chris Colbert
|
||||||
|
SCLINIC\jdecker
|
||||||
|
Scott Aikin
|
||||||
|
Scott Goodhew
|
||||||
|
Sebastian Wilzbach
|
||||||
|
Sebastian Zaha
|
||||||
|
Seren D
|
||||||
|
Sergey Goder
|
||||||
|
Sergey Tselovalnikov
|
||||||
|
Se-Won Kim
|
||||||
|
Shane Liesegang
|
||||||
|
shaund
|
||||||
|
shaun gilchrist
|
||||||
|
Shawn A
|
||||||
|
Shea Bunge
|
||||||
|
sheopory
|
||||||
|
Shil S
|
||||||
|
Shiv Deepak
|
||||||
|
Shmuel Englard
|
||||||
|
Shubham Jain
|
||||||
|
Siamak Mokhtari
|
||||||
|
silverwind
|
||||||
|
Simon Edwards
|
||||||
|
sinkuu
|
||||||
|
snasa
|
||||||
|
soliton4
|
||||||
|
sonson
|
||||||
|
Sorab Bisht
|
||||||
|
spastorelli
|
||||||
|
srajanpaliwal
|
||||||
|
Stanislav Oaserele
|
||||||
|
stan-z
|
||||||
|
Stas Kobzar
|
||||||
|
Stefan Borsje
|
||||||
|
Steffen Beyer
|
||||||
|
Steffen Bruchmann
|
||||||
|
Steffen Kowalski
|
||||||
|
Stephane Moore
|
||||||
|
Stephen Lavelle
|
||||||
|
Steve Champagne
|
||||||
|
Steve Hoover
|
||||||
|
Steve O'Hara
|
||||||
|
stoskov
|
||||||
|
Stu Kennedy
|
||||||
|
Sungho Kim
|
||||||
|
sverweij
|
||||||
|
Taha Jahangir
|
||||||
|
takamori
|
||||||
|
Tako Schotanus
|
||||||
|
Takuji Shimokawa
|
||||||
|
Takuya Matsuyama
|
||||||
|
Tarmil
|
||||||
|
TDaglis
|
||||||
|
tel
|
||||||
|
Tentone
|
||||||
|
tfjgeorge
|
||||||
|
Thaddee Tyl
|
||||||
|
thanasis
|
||||||
|
TheHowl
|
||||||
|
themrmax
|
||||||
|
think
|
||||||
|
Thomas Brouard
|
||||||
|
Thomas Dvornik
|
||||||
|
Thomas Kluyver
|
||||||
|
Thomas Schmid
|
||||||
|
Tim Alby
|
||||||
|
Tim Baumann
|
||||||
|
Timothy Farrell
|
||||||
|
Timothy Gu
|
||||||
|
Timothy Hatcher
|
||||||
|
Tobias Bertelsen
|
||||||
|
TobiasBg
|
||||||
|
Todd Berman
|
||||||
|
Todd Kennedy
|
||||||
|
Tomas-A
|
||||||
|
Tomas Varaneckas
|
||||||
|
Tom Erik Støwer
|
||||||
|
Tom Klancer
|
||||||
|
Tom MacWright
|
||||||
|
Tom McLaughlin
|
||||||
|
Tony Jian
|
||||||
|
tophf
|
||||||
|
totalamd
|
||||||
|
Travis Heppe
|
||||||
|
Triangle717
|
||||||
|
Tristan Tarrant
|
||||||
|
TSUYUSATO Kitsune
|
||||||
|
Tugrul Elmas
|
||||||
|
twifkak
|
||||||
|
Tyler Long
|
||||||
|
Vadzim Ramanenka
|
||||||
|
Vaibhav Sagar
|
||||||
|
VapidWorx
|
||||||
|
Vestimir Markov
|
||||||
|
vf
|
||||||
|
Victor Bocharsky
|
||||||
|
Vincent Woo
|
||||||
|
Volker Mische
|
||||||
|
vtripolitakis
|
||||||
|
Weiyan Shao
|
||||||
|
wenli
|
||||||
|
Wes Cossick
|
||||||
|
Wesley Wiser
|
||||||
|
Weston Ruter
|
||||||
|
Will Binns-Smith
|
||||||
|
Will Dean
|
||||||
|
William Jamieson
|
||||||
|
William Stein
|
||||||
|
Willy
|
||||||
|
Wojtek Ptak
|
||||||
|
wonderboyjon
|
||||||
|
Wu Cheng-Han
|
||||||
|
Xavier Mendez
|
||||||
|
Yassin N. Hassan
|
||||||
|
YNH Webdev
|
||||||
|
Yunchi Luo
|
||||||
|
Yuvi Panda
|
||||||
|
Yvonnick Esnault
|
||||||
|
Zac Anger
|
||||||
|
Zachary Dremann
|
||||||
|
Zeno Rocha
|
||||||
|
Zhang Hao
|
||||||
|
Ziv
|
||||||
|
zziuni
|
||||||
|
魏鹏刚
|
||||||
1486
web/public/codemirror/CHANGELOG.md
Normal file
1486
web/public/codemirror/CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load Diff
92
web/public/codemirror/CONTRIBUTING.md
Normal file
92
web/public/codemirror/CONTRIBUTING.md
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
# How to contribute
|
||||||
|
|
||||||
|
- [Getting help](#getting-help)
|
||||||
|
- [Submitting bug reports](#submitting-bug-reports)
|
||||||
|
- [Contributing code](#contributing-code)
|
||||||
|
|
||||||
|
## Getting help
|
||||||
|
|
||||||
|
Community discussion, questions, and informal bug reporting is done on the
|
||||||
|
[discuss.CodeMirror forum](http://discuss.codemirror.net).
|
||||||
|
|
||||||
|
## Submitting bug reports
|
||||||
|
|
||||||
|
The preferred way to report bugs is to use the
|
||||||
|
[GitHub issue tracker](http://github.com/codemirror/CodeMirror/issues). Before
|
||||||
|
reporting a bug, read these pointers.
|
||||||
|
|
||||||
|
**Note:** The issue tracker is for *bugs*, not requests for help. Questions
|
||||||
|
should be asked on the
|
||||||
|
[discuss.CodeMirror forum](http://discuss.codemirror.net) instead.
|
||||||
|
|
||||||
|
### Reporting bugs effectively
|
||||||
|
|
||||||
|
- CodeMirror is maintained by volunteers. They don't owe you anything, so be
|
||||||
|
polite. Reports with an indignant or belligerent tone tend to be moved to the
|
||||||
|
bottom of the pile.
|
||||||
|
|
||||||
|
- Include information about **the browser in which the problem occurred**. Even
|
||||||
|
if you tested several browsers, and the problem occurred in all of them,
|
||||||
|
mention this fact in the bug report. Also include browser version numbers and
|
||||||
|
the operating system that you're on.
|
||||||
|
|
||||||
|
- Mention which release of CodeMirror you're using. Preferably, try also with
|
||||||
|
the current development snapshot, to ensure the problem has not already been
|
||||||
|
fixed.
|
||||||
|
|
||||||
|
- Mention very precisely what went wrong. "X is broken" is not a good bug
|
||||||
|
report. What did you expect to happen? What happened instead? Describe the
|
||||||
|
exact steps a maintainer has to take to make the problem occur. We can not
|
||||||
|
fix something that we can not observe.
|
||||||
|
|
||||||
|
- If the problem can not be reproduced in any of the demos included in the
|
||||||
|
CodeMirror distribution, please provide an HTML document that demonstrates
|
||||||
|
the problem. The best way to do this is to go to
|
||||||
|
[jsbin.com](http://jsbin.com/ihunin/edit), enter it there, press save, and
|
||||||
|
include the resulting link in your bug report.
|
||||||
|
|
||||||
|
## Contributing code
|
||||||
|
|
||||||
|
Note that we are not accepting any new addons or modes into the main
|
||||||
|
distribution. If you've written such a module, please distribute it as
|
||||||
|
a separate NPM package.
|
||||||
|
|
||||||
|
- Make sure you have a [GitHub Account](https://github.com/signup/free)
|
||||||
|
- Fork [CodeMirror](https://github.com/codemirror/CodeMirror/)
|
||||||
|
([how to fork a repo](https://help.github.com/articles/fork-a-repo))
|
||||||
|
- Make your changes
|
||||||
|
- If your changes are easy to test or likely to regress, add tests.
|
||||||
|
Tests for the core go into `test/test.js`, some modes have their own
|
||||||
|
test suite under `mode/XXX/test.js`. Feel free to add new test
|
||||||
|
suites to modes that don't have one yet (be sure to link the new
|
||||||
|
tests into `test/index.html`).
|
||||||
|
- Follow the general code style of the rest of the project (see
|
||||||
|
below). Run `bin/lint` to verify that the linter is happy.
|
||||||
|
- Make sure all tests pass. Visit `test/index.html` in your browser to
|
||||||
|
run them.
|
||||||
|
- Submit a pull request
|
||||||
|
([how to create a pull request](https://help.github.com/articles/fork-a-repo)).
|
||||||
|
Don't put more than one feature/fix in a single pull request.
|
||||||
|
|
||||||
|
By contributing code to CodeMirror you
|
||||||
|
|
||||||
|
- agree to license the contributed code under CodeMirror's [MIT
|
||||||
|
license](https://codemirror.net/LICENSE).
|
||||||
|
|
||||||
|
- confirm that you have the right to contribute and license the code
|
||||||
|
in question. (Either you hold all rights on the code, or the rights
|
||||||
|
holder has explicitly granted the right to use it like this,
|
||||||
|
through a compatible open source license or through a direct
|
||||||
|
agreement with you.)
|
||||||
|
|
||||||
|
### Coding standards
|
||||||
|
|
||||||
|
- 2 spaces per indentation level, no tabs.
|
||||||
|
|
||||||
|
- Note that the linter (`bin/lint`) which is run after each commit
|
||||||
|
complains about unused variables and functions. Prefix their names
|
||||||
|
with an underscore to muffle it.
|
||||||
|
|
||||||
|
- CodeMirror does *not* follow JSHint or JSLint prescribed style.
|
||||||
|
Patches that try to 'fix' code to pass one of these linters will be
|
||||||
|
unceremoniously discarded.
|
||||||
21
web/public/codemirror/LICENSE
Normal file
21
web/public/codemirror/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (C) 2017 by Marijn Haverbeke <marijnh@gmail.com> and others
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
49
web/public/codemirror/README.md
Normal file
49
web/public/codemirror/README.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# CodeMirror
|
||||||
|
|
||||||
|
[](https://travis-ci.org/codemirror/CodeMirror)
|
||||||
|
[](https://www.npmjs.org/package/codemirror)
|
||||||
|
[](https://gitter.im/codemirror/CodeMirror)
|
||||||
|
[Funding status: ](https://marijnhaverbeke.nl/fund/)
|
||||||
|
|
||||||
|
CodeMirror is a versatile text editor implemented in JavaScript for
|
||||||
|
the browser. It is specialized for editing code, and comes with over
|
||||||
|
100 language modes and various addons that implement more advanced
|
||||||
|
editing functionality. Every language comes with fully-featured code
|
||||||
|
and syntax highlighting to help with reading and editing complex code.
|
||||||
|
|
||||||
|
A rich programming API and a CSS theming system are available for
|
||||||
|
customizing CodeMirror to fit your application, and extending it with
|
||||||
|
new functionality.
|
||||||
|
|
||||||
|
You can find more information (and the
|
||||||
|
[manual](https://codemirror.net/doc/manual.html)) on the [project
|
||||||
|
page](https://codemirror.net). For questions and discussion, use the
|
||||||
|
[discussion forum](https://discuss.codemirror.net/).
|
||||||
|
|
||||||
|
See
|
||||||
|
[CONTRIBUTING.md](https://github.com/codemirror/CodeMirror/blob/master/CONTRIBUTING.md)
|
||||||
|
for contributing guidelines.
|
||||||
|
|
||||||
|
The CodeMirror community aims to be welcoming to everybody. We use the
|
||||||
|
[Contributor Covenant
|
||||||
|
(1.1)](http://contributor-covenant.org/version/1/1/0/) as our code of
|
||||||
|
conduct.
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
Either get the [zip file](https://codemirror.net/codemirror.zip) with
|
||||||
|
the latest version, or make sure you have [Node](https://nodejs.org/)
|
||||||
|
installed and run:
|
||||||
|
|
||||||
|
npm install codemirror
|
||||||
|
|
||||||
|
**NOTE**: This is the source repository for the library, and not the
|
||||||
|
distribution channel. Cloning it is not the recommended way to install
|
||||||
|
the library, and will in fact not work unless you also run the build
|
||||||
|
step.
|
||||||
|
|
||||||
|
### Quickstart
|
||||||
|
|
||||||
|
To build the project, make sure you have Node.js installed (at least version 6)
|
||||||
|
and then `npm install`. To run, just open `index.html` in your
|
||||||
|
browser (you don't need to run a webserver). Run the tests with `npm test`.
|
||||||
209
web/public/codemirror/addon/comment/comment.js
vendored
Normal file
209
web/public/codemirror/addon/comment/comment.js
vendored
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: https://codemirror.net/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var noOptions = {};
|
||||||
|
var nonWS = /[^\s\u00a0]/;
|
||||||
|
var Pos = CodeMirror.Pos;
|
||||||
|
|
||||||
|
function firstNonWS(str) {
|
||||||
|
var found = str.search(nonWS);
|
||||||
|
return found == -1 ? 0 : found;
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeMirror.commands.toggleComment = function(cm) {
|
||||||
|
cm.toggleComment();
|
||||||
|
};
|
||||||
|
|
||||||
|
CodeMirror.defineExtension("toggleComment", function(options) {
|
||||||
|
if (!options) options = noOptions;
|
||||||
|
var cm = this;
|
||||||
|
var minLine = Infinity, ranges = this.listSelections(), mode = null;
|
||||||
|
for (var i = ranges.length - 1; i >= 0; i--) {
|
||||||
|
var from = ranges[i].from(), to = ranges[i].to();
|
||||||
|
if (from.line >= minLine) continue;
|
||||||
|
if (to.line >= minLine) to = Pos(minLine, 0);
|
||||||
|
minLine = from.line;
|
||||||
|
if (mode == null) {
|
||||||
|
if (cm.uncomment(from, to, options)) mode = "un";
|
||||||
|
else { cm.lineComment(from, to, options); mode = "line"; }
|
||||||
|
} else if (mode == "un") {
|
||||||
|
cm.uncomment(from, to, options);
|
||||||
|
} else {
|
||||||
|
cm.lineComment(from, to, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Rough heuristic to try and detect lines that are part of multi-line string
|
||||||
|
function probablyInsideString(cm, pos, line) {
|
||||||
|
return /\bstring\b/.test(cm.getTokenTypeAt(Pos(pos.line, 0))) && !/^[\'\"\`]/.test(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMode(cm, pos) {
|
||||||
|
var mode = cm.getMode()
|
||||||
|
return mode.useInnerComments === false || !mode.innerMode ? mode : cm.getModeAt(pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeMirror.defineExtension("lineComment", function(from, to, options) {
|
||||||
|
if (!options) options = noOptions;
|
||||||
|
var self = this, mode = getMode(self, from);
|
||||||
|
var firstLine = self.getLine(from.line);
|
||||||
|
if (firstLine == null || probablyInsideString(self, from, firstLine)) return;
|
||||||
|
|
||||||
|
var commentString = options.lineComment || mode.lineComment;
|
||||||
|
if (!commentString) {
|
||||||
|
if (options.blockCommentStart || mode.blockCommentStart) {
|
||||||
|
options.fullLines = true;
|
||||||
|
self.blockComment(from, to, options);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var end = Math.min(to.ch != 0 || to.line == from.line ? to.line + 1 : to.line, self.lastLine() + 1);
|
||||||
|
var pad = options.padding == null ? " " : options.padding;
|
||||||
|
var blankLines = options.commentBlankLines || from.line == to.line;
|
||||||
|
|
||||||
|
self.operation(function() {
|
||||||
|
if (options.indent) {
|
||||||
|
var baseString = null;
|
||||||
|
for (var i = from.line; i < end; ++i) {
|
||||||
|
var line = self.getLine(i);
|
||||||
|
var whitespace = line.slice(0, firstNonWS(line));
|
||||||
|
if (baseString == null || baseString.length > whitespace.length) {
|
||||||
|
baseString = whitespace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var i = from.line; i < end; ++i) {
|
||||||
|
var line = self.getLine(i), cut = baseString.length;
|
||||||
|
if (!blankLines && !nonWS.test(line)) continue;
|
||||||
|
if (line.slice(0, cut) != baseString) cut = firstNonWS(line);
|
||||||
|
self.replaceRange(baseString + commentString + pad, Pos(i, 0), Pos(i, cut));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (var i = from.line; i < end; ++i) {
|
||||||
|
if (blankLines || nonWS.test(self.getLine(i)))
|
||||||
|
self.replaceRange(commentString + pad, Pos(i, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
CodeMirror.defineExtension("blockComment", function(from, to, options) {
|
||||||
|
if (!options) options = noOptions;
|
||||||
|
var self = this, mode = getMode(self, from);
|
||||||
|
var startString = options.blockCommentStart || mode.blockCommentStart;
|
||||||
|
var endString = options.blockCommentEnd || mode.blockCommentEnd;
|
||||||
|
if (!startString || !endString) {
|
||||||
|
if ((options.lineComment || mode.lineComment) && options.fullLines != false)
|
||||||
|
self.lineComment(from, to, options);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (/\bcomment\b/.test(self.getTokenTypeAt(Pos(from.line, 0)))) return
|
||||||
|
|
||||||
|
var end = Math.min(to.line, self.lastLine());
|
||||||
|
if (end != from.line && to.ch == 0 && nonWS.test(self.getLine(end))) --end;
|
||||||
|
|
||||||
|
var pad = options.padding == null ? " " : options.padding;
|
||||||
|
if (from.line > end) return;
|
||||||
|
|
||||||
|
self.operation(function() {
|
||||||
|
if (options.fullLines != false) {
|
||||||
|
var lastLineHasText = nonWS.test(self.getLine(end));
|
||||||
|
self.replaceRange(pad + endString, Pos(end));
|
||||||
|
self.replaceRange(startString + pad, Pos(from.line, 0));
|
||||||
|
var lead = options.blockCommentLead || mode.blockCommentLead;
|
||||||
|
if (lead != null) for (var i = from.line + 1; i <= end; ++i)
|
||||||
|
if (i != end || lastLineHasText)
|
||||||
|
self.replaceRange(lead + pad, Pos(i, 0));
|
||||||
|
} else {
|
||||||
|
self.replaceRange(endString, to);
|
||||||
|
self.replaceRange(startString, from);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
CodeMirror.defineExtension("uncomment", function(from, to, options) {
|
||||||
|
if (!options) options = noOptions;
|
||||||
|
var self = this, mode = getMode(self, from);
|
||||||
|
var end = Math.min(to.ch != 0 || to.line == from.line ? to.line : to.line - 1, self.lastLine()), start = Math.min(from.line, end);
|
||||||
|
|
||||||
|
// Try finding line comments
|
||||||
|
var lineString = options.lineComment || mode.lineComment, lines = [];
|
||||||
|
var pad = options.padding == null ? " " : options.padding, didSomething;
|
||||||
|
lineComment: {
|
||||||
|
if (!lineString) break lineComment;
|
||||||
|
for (var i = start; i <= end; ++i) {
|
||||||
|
var line = self.getLine(i);
|
||||||
|
var found = line.indexOf(lineString);
|
||||||
|
if (found > -1 && !/comment/.test(self.getTokenTypeAt(Pos(i, found + 1)))) found = -1;
|
||||||
|
if (found == -1 && nonWS.test(line)) break lineComment;
|
||||||
|
if (found > -1 && nonWS.test(line.slice(0, found))) break lineComment;
|
||||||
|
lines.push(line);
|
||||||
|
}
|
||||||
|
self.operation(function() {
|
||||||
|
for (var i = start; i <= end; ++i) {
|
||||||
|
var line = lines[i - start];
|
||||||
|
var pos = line.indexOf(lineString), endPos = pos + lineString.length;
|
||||||
|
if (pos < 0) continue;
|
||||||
|
if (line.slice(endPos, endPos + pad.length) == pad) endPos += pad.length;
|
||||||
|
didSomething = true;
|
||||||
|
self.replaceRange("", Pos(i, pos), Pos(i, endPos));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (didSomething) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try block comments
|
||||||
|
var startString = options.blockCommentStart || mode.blockCommentStart;
|
||||||
|
var endString = options.blockCommentEnd || mode.blockCommentEnd;
|
||||||
|
if (!startString || !endString) return false;
|
||||||
|
var lead = options.blockCommentLead || mode.blockCommentLead;
|
||||||
|
var startLine = self.getLine(start), open = startLine.indexOf(startString)
|
||||||
|
if (open == -1) return false
|
||||||
|
var endLine = end == start ? startLine : self.getLine(end)
|
||||||
|
var close = endLine.indexOf(endString, end == start ? open + startString.length : 0);
|
||||||
|
var insideStart = Pos(start, open + 1), insideEnd = Pos(end, close + 1)
|
||||||
|
if (close == -1 ||
|
||||||
|
!/comment/.test(self.getTokenTypeAt(insideStart)) ||
|
||||||
|
!/comment/.test(self.getTokenTypeAt(insideEnd)) ||
|
||||||
|
self.getRange(insideStart, insideEnd, "\n").indexOf(endString) > -1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Avoid killing block comments completely outside the selection.
|
||||||
|
// Positions of the last startString before the start of the selection, and the first endString after it.
|
||||||
|
var lastStart = startLine.lastIndexOf(startString, from.ch);
|
||||||
|
var firstEnd = lastStart == -1 ? -1 : startLine.slice(0, from.ch).indexOf(endString, lastStart + startString.length);
|
||||||
|
if (lastStart != -1 && firstEnd != -1 && firstEnd + endString.length != from.ch) return false;
|
||||||
|
// Positions of the first endString after the end of the selection, and the last startString before it.
|
||||||
|
firstEnd = endLine.indexOf(endString, to.ch);
|
||||||
|
var almostLastStart = endLine.slice(to.ch).lastIndexOf(startString, firstEnd - to.ch);
|
||||||
|
lastStart = (firstEnd == -1 || almostLastStart == -1) ? -1 : to.ch + almostLastStart;
|
||||||
|
if (firstEnd != -1 && lastStart != -1 && lastStart != to.ch) return false;
|
||||||
|
|
||||||
|
self.operation(function() {
|
||||||
|
self.replaceRange("", Pos(end, close - (pad && endLine.slice(close - pad.length, close) == pad ? pad.length : 0)),
|
||||||
|
Pos(end, close + endString.length));
|
||||||
|
var openEnd = open + startString.length;
|
||||||
|
if (pad && startLine.slice(openEnd, openEnd + pad.length) == pad) openEnd += pad.length;
|
||||||
|
self.replaceRange("", Pos(start, open), Pos(start, openEnd));
|
||||||
|
if (lead) for (var i = start + 1; i <= end; ++i) {
|
||||||
|
var line = self.getLine(i), found = line.indexOf(lead);
|
||||||
|
if (found == -1 || nonWS.test(line.slice(0, found))) continue;
|
||||||
|
var foundEnd = found + lead.length;
|
||||||
|
if (pad && line.slice(foundEnd, foundEnd + pad.length) == pad) foundEnd += pad.length;
|
||||||
|
self.replaceRange("", Pos(i, found), Pos(i, foundEnd));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
});
|
||||||
78
web/public/codemirror/addon/comment/continuecomment.js
vendored
Normal file
78
web/public/codemirror/addon/comment/continuecomment.js
vendored
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: https://codemirror.net/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
function continueComment(cm) {
|
||||||
|
if (cm.getOption("disableInput")) return CodeMirror.Pass;
|
||||||
|
var ranges = cm.listSelections(), mode, inserts = [];
|
||||||
|
for (var i = 0; i < ranges.length; i++) {
|
||||||
|
var pos = ranges[i].head
|
||||||
|
if (!/\bcomment\b/.test(cm.getTokenTypeAt(pos))) return CodeMirror.Pass;
|
||||||
|
var modeHere = cm.getModeAt(pos)
|
||||||
|
if (!mode) mode = modeHere;
|
||||||
|
else if (mode != modeHere) return CodeMirror.Pass;
|
||||||
|
|
||||||
|
var insert = null;
|
||||||
|
if (mode.blockCommentStart && mode.blockCommentContinue) {
|
||||||
|
var line = cm.getLine(pos.line).slice(0, pos.ch)
|
||||||
|
var end = line.lastIndexOf(mode.blockCommentEnd), found
|
||||||
|
if (end != -1 && end == pos.ch - mode.blockCommentEnd.length) {
|
||||||
|
// Comment ended, don't continue it
|
||||||
|
} else if ((found = line.lastIndexOf(mode.blockCommentStart)) > -1 && found > end) {
|
||||||
|
insert = line.slice(0, found)
|
||||||
|
if (/\S/.test(insert)) {
|
||||||
|
insert = ""
|
||||||
|
for (var j = 0; j < found; ++j) insert += " "
|
||||||
|
}
|
||||||
|
} else if ((found = line.indexOf(mode.blockCommentContinue)) > -1 && !/\S/.test(line.slice(0, found))) {
|
||||||
|
insert = line.slice(0, found)
|
||||||
|
}
|
||||||
|
if (insert != null) insert += mode.blockCommentContinue
|
||||||
|
}
|
||||||
|
if (insert == null && mode.lineComment && continueLineCommentEnabled(cm)) {
|
||||||
|
var line = cm.getLine(pos.line), found = line.indexOf(mode.lineComment);
|
||||||
|
if (found > -1) {
|
||||||
|
insert = line.slice(0, found);
|
||||||
|
if (/\S/.test(insert)) insert = null;
|
||||||
|
else insert += mode.lineComment + line.slice(found + mode.lineComment.length).match(/^\s*/)[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (insert == null) return CodeMirror.Pass;
|
||||||
|
inserts[i] = "\n" + insert;
|
||||||
|
}
|
||||||
|
|
||||||
|
cm.operation(function() {
|
||||||
|
for (var i = ranges.length - 1; i >= 0; i--)
|
||||||
|
cm.replaceRange(inserts[i], ranges[i].from(), ranges[i].to(), "+insert");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function continueLineCommentEnabled(cm) {
|
||||||
|
var opt = cm.getOption("continueComments");
|
||||||
|
if (opt && typeof opt == "object")
|
||||||
|
return opt.continueLineComment !== false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeMirror.defineOption("continueComments", null, function(cm, val, prev) {
|
||||||
|
if (prev && prev != CodeMirror.Init)
|
||||||
|
cm.removeKeyMap("continueComment");
|
||||||
|
if (val) {
|
||||||
|
var key = "Enter";
|
||||||
|
if (typeof val == "string")
|
||||||
|
key = val;
|
||||||
|
else if (typeof val == "object" && val.key)
|
||||||
|
key = val.key;
|
||||||
|
var map = {name: "continueComment"};
|
||||||
|
map[key] = continueComment;
|
||||||
|
cm.addKeyMap(map);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
32
web/public/codemirror/addon/dialog/dialog.css
vendored
Normal file
32
web/public/codemirror/addon/dialog/dialog.css
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
.CodeMirror-dialog {
|
||||||
|
position: absolute;
|
||||||
|
left: 0; right: 0;
|
||||||
|
background: inherit;
|
||||||
|
z-index: 15;
|
||||||
|
padding: .1em .8em;
|
||||||
|
overflow: hidden;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-dialog-top {
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-dialog-bottom {
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-dialog input {
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
background: transparent;
|
||||||
|
width: 20em;
|
||||||
|
color: inherit;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-dialog button {
|
||||||
|
font-size: 70%;
|
||||||
|
}
|
||||||
161
web/public/codemirror/addon/dialog/dialog.js
vendored
Normal file
161
web/public/codemirror/addon/dialog/dialog.js
vendored
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: https://codemirror.net/LICENSE
|
||||||
|
|
||||||
|
// Open simple dialogs on top of an editor. Relies on dialog.css.
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
function dialogDiv(cm, template, bottom) {
|
||||||
|
var wrap = cm.getWrapperElement();
|
||||||
|
var dialog;
|
||||||
|
dialog = wrap.appendChild(document.createElement("div"));
|
||||||
|
if (bottom)
|
||||||
|
dialog.className = "CodeMirror-dialog CodeMirror-dialog-bottom";
|
||||||
|
else
|
||||||
|
dialog.className = "CodeMirror-dialog CodeMirror-dialog-top";
|
||||||
|
|
||||||
|
if (typeof template == "string") {
|
||||||
|
dialog.innerHTML = template;
|
||||||
|
} else { // Assuming it's a detached DOM element.
|
||||||
|
dialog.appendChild(template);
|
||||||
|
}
|
||||||
|
CodeMirror.addClass(wrap, 'dialog-opened');
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeNotification(cm, newVal) {
|
||||||
|
if (cm.state.currentNotificationClose)
|
||||||
|
cm.state.currentNotificationClose();
|
||||||
|
cm.state.currentNotificationClose = newVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeMirror.defineExtension("openDialog", function(template, callback, options) {
|
||||||
|
if (!options) options = {};
|
||||||
|
|
||||||
|
closeNotification(this, null);
|
||||||
|
|
||||||
|
var dialog = dialogDiv(this, template, options.bottom);
|
||||||
|
var closed = false, me = this;
|
||||||
|
function close(newVal) {
|
||||||
|
if (typeof newVal == 'string') {
|
||||||
|
inp.value = newVal;
|
||||||
|
} else {
|
||||||
|
if (closed) return;
|
||||||
|
closed = true;
|
||||||
|
CodeMirror.rmClass(dialog.parentNode, 'dialog-opened');
|
||||||
|
dialog.parentNode.removeChild(dialog);
|
||||||
|
me.focus();
|
||||||
|
|
||||||
|
if (options.onClose) options.onClose(dialog);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var inp = dialog.getElementsByTagName("input")[0], button;
|
||||||
|
if (inp) {
|
||||||
|
inp.focus();
|
||||||
|
|
||||||
|
if (options.value) {
|
||||||
|
inp.value = options.value;
|
||||||
|
if (options.selectValueOnOpen !== false) {
|
||||||
|
inp.select();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.onInput)
|
||||||
|
CodeMirror.on(inp, "input", function(e) { options.onInput(e, inp.value, close);});
|
||||||
|
if (options.onKeyUp)
|
||||||
|
CodeMirror.on(inp, "keyup", function(e) {options.onKeyUp(e, inp.value, close);});
|
||||||
|
|
||||||
|
CodeMirror.on(inp, "keydown", function(e) {
|
||||||
|
if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { return; }
|
||||||
|
if (e.keyCode == 27 || (options.closeOnEnter !== false && e.keyCode == 13)) {
|
||||||
|
inp.blur();
|
||||||
|
CodeMirror.e_stop(e);
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
if (e.keyCode == 13) callback(inp.value, e);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (options.closeOnBlur !== false) CodeMirror.on(inp, "blur", close);
|
||||||
|
} else if (button = dialog.getElementsByTagName("button")[0]) {
|
||||||
|
CodeMirror.on(button, "click", function() {
|
||||||
|
close();
|
||||||
|
me.focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (options.closeOnBlur !== false) CodeMirror.on(button, "blur", close);
|
||||||
|
|
||||||
|
button.focus();
|
||||||
|
}
|
||||||
|
return close;
|
||||||
|
});
|
||||||
|
|
||||||
|
CodeMirror.defineExtension("openConfirm", function(template, callbacks, options) {
|
||||||
|
closeNotification(this, null);
|
||||||
|
var dialog = dialogDiv(this, template, options && options.bottom);
|
||||||
|
var buttons = dialog.getElementsByTagName("button");
|
||||||
|
var closed = false, me = this, blurring = 1;
|
||||||
|
function close() {
|
||||||
|
if (closed) return;
|
||||||
|
closed = true;
|
||||||
|
CodeMirror.rmClass(dialog.parentNode, 'dialog-opened');
|
||||||
|
dialog.parentNode.removeChild(dialog);
|
||||||
|
me.focus();
|
||||||
|
}
|
||||||
|
buttons[0].focus();
|
||||||
|
for (var i = 0; i < buttons.length; ++i) {
|
||||||
|
var b = buttons[i];
|
||||||
|
(function(callback) {
|
||||||
|
CodeMirror.on(b, "click", function(e) {
|
||||||
|
CodeMirror.e_preventDefault(e);
|
||||||
|
close();
|
||||||
|
if (callback) callback(me);
|
||||||
|
});
|
||||||
|
})(callbacks[i]);
|
||||||
|
CodeMirror.on(b, "blur", function() {
|
||||||
|
--blurring;
|
||||||
|
setTimeout(function() { if (blurring <= 0) close(); }, 200);
|
||||||
|
});
|
||||||
|
CodeMirror.on(b, "focus", function() { ++blurring; });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* openNotification
|
||||||
|
* Opens a notification, that can be closed with an optional timer
|
||||||
|
* (default 5000ms timer) and always closes on click.
|
||||||
|
*
|
||||||
|
* If a notification is opened while another is opened, it will close the
|
||||||
|
* currently opened one and open the new one immediately.
|
||||||
|
*/
|
||||||
|
CodeMirror.defineExtension("openNotification", function(template, options) {
|
||||||
|
closeNotification(this, close);
|
||||||
|
var dialog = dialogDiv(this, template, options && options.bottom);
|
||||||
|
var closed = false, doneTimer;
|
||||||
|
var duration = options && typeof options.duration !== "undefined" ? options.duration : 5000;
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
if (closed) return;
|
||||||
|
closed = true;
|
||||||
|
clearTimeout(doneTimer);
|
||||||
|
CodeMirror.rmClass(dialog.parentNode, 'dialog-opened');
|
||||||
|
dialog.parentNode.removeChild(dialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeMirror.on(dialog, 'click', function(e) {
|
||||||
|
CodeMirror.e_preventDefault(e);
|
||||||
|
close();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (duration)
|
||||||
|
doneTimer = setTimeout(close, duration);
|
||||||
|
|
||||||
|
return close;
|
||||||
|
});
|
||||||
|
});
|
||||||
47
web/public/codemirror/addon/display/autorefresh.js
vendored
Normal file
47
web/public/codemirror/addon/display/autorefresh.js
vendored
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: https://codemirror.net/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"))
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror"], mod)
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror)
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
"use strict"
|
||||||
|
|
||||||
|
CodeMirror.defineOption("autoRefresh", false, function(cm, val) {
|
||||||
|
if (cm.state.autoRefresh) {
|
||||||
|
stopListening(cm, cm.state.autoRefresh)
|
||||||
|
cm.state.autoRefresh = null
|
||||||
|
}
|
||||||
|
if (val && cm.display.wrapper.offsetHeight == 0)
|
||||||
|
startListening(cm, cm.state.autoRefresh = {delay: val.delay || 250})
|
||||||
|
})
|
||||||
|
|
||||||
|
function startListening(cm, state) {
|
||||||
|
function check() {
|
||||||
|
if (cm.display.wrapper.offsetHeight) {
|
||||||
|
stopListening(cm, state)
|
||||||
|
if (cm.display.lastWrapHeight != cm.display.wrapper.clientHeight)
|
||||||
|
cm.refresh()
|
||||||
|
} else {
|
||||||
|
state.timeout = setTimeout(check, state.delay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.timeout = setTimeout(check, state.delay)
|
||||||
|
state.hurry = function() {
|
||||||
|
clearTimeout(state.timeout)
|
||||||
|
state.timeout = setTimeout(check, 50)
|
||||||
|
}
|
||||||
|
CodeMirror.on(window, "mouseup", state.hurry)
|
||||||
|
CodeMirror.on(window, "keyup", state.hurry)
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopListening(_cm, state) {
|
||||||
|
clearTimeout(state.timeout)
|
||||||
|
CodeMirror.off(window, "mouseup", state.hurry)
|
||||||
|
CodeMirror.off(window, "keyup", state.hurry)
|
||||||
|
}
|
||||||
|
});
|
||||||
6
web/public/codemirror/addon/display/fullscreen.css
vendored
Normal file
6
web/public/codemirror/addon/display/fullscreen.css
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
.CodeMirror-fullscreen {
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 0; right: 0; bottom: 0;
|
||||||
|
height: auto;
|
||||||
|
z-index: 9;
|
||||||
|
}
|
||||||
41
web/public/codemirror/addon/display/fullscreen.js
vendored
Normal file
41
web/public/codemirror/addon/display/fullscreen.js
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: https://codemirror.net/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
CodeMirror.defineOption("fullScreen", false, function(cm, val, old) {
|
||||||
|
if (old == CodeMirror.Init) old = false;
|
||||||
|
if (!old == !val) return;
|
||||||
|
if (val) setFullscreen(cm);
|
||||||
|
else setNormal(cm);
|
||||||
|
});
|
||||||
|
|
||||||
|
function setFullscreen(cm) {
|
||||||
|
var wrap = cm.getWrapperElement();
|
||||||
|
cm.state.fullScreenRestore = {scrollTop: window.pageYOffset, scrollLeft: window.pageXOffset,
|
||||||
|
width: wrap.style.width, height: wrap.style.height};
|
||||||
|
wrap.style.width = "";
|
||||||
|
wrap.style.height = "auto";
|
||||||
|
wrap.className += " CodeMirror-fullscreen";
|
||||||
|
document.documentElement.style.overflow = "hidden";
|
||||||
|
cm.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setNormal(cm) {
|
||||||
|
var wrap = cm.getWrapperElement();
|
||||||
|
wrap.className = wrap.className.replace(/\s*CodeMirror-fullscreen\b/, "");
|
||||||
|
document.documentElement.style.overflow = "";
|
||||||
|
var info = cm.state.fullScreenRestore;
|
||||||
|
wrap.style.width = info.width; wrap.style.height = info.height;
|
||||||
|
window.scrollTo(info.scrollLeft, info.scrollTop);
|
||||||
|
cm.refresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
127
web/public/codemirror/addon/display/panel.js
vendored
Normal file
127
web/public/codemirror/addon/display/panel.js
vendored
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: https://codemirror.net/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
CodeMirror.defineExtension("addPanel", function(node, options) {
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
if (!this.state.panels) initPanels(this);
|
||||||
|
|
||||||
|
var info = this.state.panels;
|
||||||
|
var wrapper = info.wrapper;
|
||||||
|
var cmWrapper = this.getWrapperElement();
|
||||||
|
var replace = options.replace instanceof Panel && !options.replace.cleared;
|
||||||
|
|
||||||
|
if (options.after instanceof Panel && !options.after.cleared) {
|
||||||
|
wrapper.insertBefore(node, options.before.node.nextSibling);
|
||||||
|
} else if (options.before instanceof Panel && !options.before.cleared) {
|
||||||
|
wrapper.insertBefore(node, options.before.node);
|
||||||
|
} else if (replace) {
|
||||||
|
wrapper.insertBefore(node, options.replace.node);
|
||||||
|
info.panels++;
|
||||||
|
options.replace.clear();
|
||||||
|
} else if (options.position == "bottom") {
|
||||||
|
wrapper.appendChild(node);
|
||||||
|
} else if (options.position == "before-bottom") {
|
||||||
|
wrapper.insertBefore(node, cmWrapper.nextSibling);
|
||||||
|
} else if (options.position == "after-top") {
|
||||||
|
wrapper.insertBefore(node, cmWrapper);
|
||||||
|
} else {
|
||||||
|
wrapper.insertBefore(node, wrapper.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
var height = (options && options.height) || node.offsetHeight;
|
||||||
|
this._setSize(null, info.heightLeft -= height);
|
||||||
|
if (!replace) {
|
||||||
|
info.panels++;
|
||||||
|
}
|
||||||
|
if (options.stable && isAtTop(this, node))
|
||||||
|
this.scrollTo(null, this.getScrollInfo().top + height)
|
||||||
|
|
||||||
|
return new Panel(this, node, options, height);
|
||||||
|
});
|
||||||
|
|
||||||
|
function Panel(cm, node, options, height) {
|
||||||
|
this.cm = cm;
|
||||||
|
this.node = node;
|
||||||
|
this.options = options;
|
||||||
|
this.height = height;
|
||||||
|
this.cleared = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Panel.prototype.clear = function() {
|
||||||
|
if (this.cleared) return;
|
||||||
|
this.cleared = true;
|
||||||
|
var info = this.cm.state.panels;
|
||||||
|
this.cm._setSize(null, info.heightLeft += this.height);
|
||||||
|
if (this.options.stable && isAtTop(this.cm, this.node))
|
||||||
|
this.cm.scrollTo(null, this.cm.getScrollInfo().top - this.height)
|
||||||
|
info.wrapper.removeChild(this.node);
|
||||||
|
if (--info.panels == 0) removePanels(this.cm);
|
||||||
|
};
|
||||||
|
|
||||||
|
Panel.prototype.changed = function(height) {
|
||||||
|
var newHeight = height == null ? this.node.offsetHeight : height;
|
||||||
|
var info = this.cm.state.panels;
|
||||||
|
this.cm._setSize(null, info.heightLeft -= (newHeight - this.height));
|
||||||
|
this.height = newHeight;
|
||||||
|
};
|
||||||
|
|
||||||
|
function initPanels(cm) {
|
||||||
|
var wrap = cm.getWrapperElement();
|
||||||
|
var style = window.getComputedStyle ? window.getComputedStyle(wrap) : wrap.currentStyle;
|
||||||
|
var height = parseInt(style.height);
|
||||||
|
var info = cm.state.panels = {
|
||||||
|
setHeight: wrap.style.height,
|
||||||
|
heightLeft: height,
|
||||||
|
panels: 0,
|
||||||
|
wrapper: document.createElement("div")
|
||||||
|
};
|
||||||
|
wrap.parentNode.insertBefore(info.wrapper, wrap);
|
||||||
|
var hasFocus = cm.hasFocus();
|
||||||
|
info.wrapper.appendChild(wrap);
|
||||||
|
if (hasFocus) cm.focus();
|
||||||
|
|
||||||
|
cm._setSize = cm.setSize;
|
||||||
|
if (height != null) cm.setSize = function(width, newHeight) {
|
||||||
|
if (newHeight == null) return this._setSize(width, newHeight);
|
||||||
|
info.setHeight = newHeight;
|
||||||
|
if (typeof newHeight != "number") {
|
||||||
|
var px = /^(\d+\.?\d*)px$/.exec(newHeight);
|
||||||
|
if (px) {
|
||||||
|
newHeight = Number(px[1]);
|
||||||
|
} else {
|
||||||
|
info.wrapper.style.height = newHeight;
|
||||||
|
newHeight = info.wrapper.offsetHeight;
|
||||||
|
info.wrapper.style.height = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cm._setSize(width, info.heightLeft += (newHeight - height));
|
||||||
|
height = newHeight;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function removePanels(cm) {
|
||||||
|
var info = cm.state.panels;
|
||||||
|
cm.state.panels = null;
|
||||||
|
|
||||||
|
var wrap = cm.getWrapperElement();
|
||||||
|
info.wrapper.parentNode.replaceChild(wrap, info.wrapper);
|
||||||
|
wrap.style.height = info.setHeight;
|
||||||
|
cm.setSize = cm._setSize;
|
||||||
|
cm.setSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAtTop(cm, dom) {
|
||||||
|
for (var sibling = dom.nextSibling; sibling; sibling = sibling.nextSibling)
|
||||||
|
if (sibling == cm.getWrapperElement()) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
});
|
||||||
63
web/public/codemirror/addon/display/placeholder.js
vendored
Normal file
63
web/public/codemirror/addon/display/placeholder.js
vendored
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: https://codemirror.net/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
CodeMirror.defineOption("placeholder", "", function(cm, val, old) {
|
||||||
|
var prev = old && old != CodeMirror.Init;
|
||||||
|
if (val && !prev) {
|
||||||
|
cm.on("blur", onBlur);
|
||||||
|
cm.on("change", onChange);
|
||||||
|
cm.on("swapDoc", onChange);
|
||||||
|
onChange(cm);
|
||||||
|
} else if (!val && prev) {
|
||||||
|
cm.off("blur", onBlur);
|
||||||
|
cm.off("change", onChange);
|
||||||
|
cm.off("swapDoc", onChange);
|
||||||
|
clearPlaceholder(cm);
|
||||||
|
var wrapper = cm.getWrapperElement();
|
||||||
|
wrapper.className = wrapper.className.replace(" CodeMirror-empty", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (val && !cm.hasFocus()) onBlur(cm);
|
||||||
|
});
|
||||||
|
|
||||||
|
function clearPlaceholder(cm) {
|
||||||
|
if (cm.state.placeholder) {
|
||||||
|
cm.state.placeholder.parentNode.removeChild(cm.state.placeholder);
|
||||||
|
cm.state.placeholder = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function setPlaceholder(cm) {
|
||||||
|
clearPlaceholder(cm);
|
||||||
|
var elt = cm.state.placeholder = document.createElement("pre");
|
||||||
|
elt.style.cssText = "height: 0; overflow: visible";
|
||||||
|
elt.style.direction = cm.getOption("direction");
|
||||||
|
elt.className = "CodeMirror-placeholder";
|
||||||
|
var placeHolder = cm.getOption("placeholder")
|
||||||
|
if (typeof placeHolder == "string") placeHolder = document.createTextNode(placeHolder)
|
||||||
|
elt.appendChild(placeHolder)
|
||||||
|
cm.display.lineSpace.insertBefore(elt, cm.display.lineSpace.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onBlur(cm) {
|
||||||
|
if (isEmpty(cm)) setPlaceholder(cm);
|
||||||
|
}
|
||||||
|
function onChange(cm) {
|
||||||
|
var wrapper = cm.getWrapperElement(), empty = isEmpty(cm);
|
||||||
|
wrapper.className = wrapper.className.replace(" CodeMirror-empty", "") + (empty ? " CodeMirror-empty" : "");
|
||||||
|
|
||||||
|
if (empty) setPlaceholder(cm);
|
||||||
|
else clearPlaceholder(cm);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmpty(cm) {
|
||||||
|
return (cm.lineCount() === 1) && (cm.getLine(0) === "");
|
||||||
|
}
|
||||||
|
});
|
||||||
51
web/public/codemirror/addon/display/rulers.js
vendored
Normal file
51
web/public/codemirror/addon/display/rulers.js
vendored
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: https://codemirror.net/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
CodeMirror.defineOption("rulers", false, function(cm, val) {
|
||||||
|
if (cm.state.rulerDiv) {
|
||||||
|
cm.state.rulerDiv.parentElement.removeChild(cm.state.rulerDiv)
|
||||||
|
cm.state.rulerDiv = null
|
||||||
|
cm.off("refresh", drawRulers)
|
||||||
|
}
|
||||||
|
if (val && val.length) {
|
||||||
|
cm.state.rulerDiv = cm.display.lineSpace.parentElement.insertBefore(document.createElement("div"), cm.display.lineSpace)
|
||||||
|
cm.state.rulerDiv.className = "CodeMirror-rulers"
|
||||||
|
drawRulers(cm)
|
||||||
|
cm.on("refresh", drawRulers)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function drawRulers(cm) {
|
||||||
|
cm.state.rulerDiv.textContent = ""
|
||||||
|
var val = cm.getOption("rulers");
|
||||||
|
var cw = cm.defaultCharWidth();
|
||||||
|
var left = cm.charCoords(CodeMirror.Pos(cm.firstLine(), 0), "div").left;
|
||||||
|
cm.state.rulerDiv.style.minHeight = (cm.display.scroller.offsetHeight + 30) + "px";
|
||||||
|
for (var i = 0; i < val.length; i++) {
|
||||||
|
var elt = document.createElement("div");
|
||||||
|
elt.className = "CodeMirror-ruler";
|
||||||
|
var col, conf = val[i];
|
||||||
|
if (typeof conf == "number") {
|
||||||
|
col = conf;
|
||||||
|
} else {
|
||||||
|
col = conf.column;
|
||||||
|
if (conf.className) elt.className += " " + conf.className;
|
||||||
|
if (conf.color) elt.style.borderColor = conf.color;
|
||||||
|
if (conf.lineStyle) elt.style.borderLeftStyle = conf.lineStyle;
|
||||||
|
if (conf.width) elt.style.borderLeftWidth = conf.width;
|
||||||
|
}
|
||||||
|
elt.style.left = (left + col * cw) + "px";
|
||||||
|
cm.state.rulerDiv.appendChild(elt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user