Compare commits

..

30 Commits

Author SHA1 Message Date
刘祥超
f7a1e60735 更新架构图 2023-10-15 20:32:57 +08:00
刘祥超
f22651821c 更新架构图 2023-10-15 20:19:59 +08:00
刘祥超
b2a5a9ac9b 更新架构图片 2023-10-15 20:13:52 +08:00
刘祥超
245af2bd77 修改Dockfile中的版本号为1.2.10 2023-10-15 14:42:04 +08:00
刘祥超
9dd2b1d35c 删除不需要的代码 2023-10-15 14:34:37 +08:00
刘祥超
bdbfd3e055 提交components.js 2023-10-15 14:30:27 +08:00
刘祥超
1518c03d2b 将版本号修改为1.2.10 2023-10-15 13:34:44 +08:00
刘祥超
45920d4df2 WAF记录IP动作中IP名单允许留空 2023-10-15 09:34:07 +08:00
刘祥超
1c106ac6eb 优化消息通知相关代码 2023-10-14 17:15:45 +08:00
刘祥超
9f4a32b4ac macOS上检测磁盘是否不足时忽略/Developer/相关分区 2023-10-13 09:22:45 +08:00
刘祥超
2565b10491 优化集群设置DNS TTL设置文字提示 2023-10-12 10:29:48 +08:00
刘祥超
34d632a401 删除看板中不需要的版本判断 2023-10-11 17:52:02 +08:00
刘祥超
a9df27c0e1 删除不需要的文件 2023-10-11 17:49:59 +08:00
刘祥超
313011a168 支持批量复制WAF设置 2023-10-09 19:52:39 +08:00
刘祥超
5c595aad83 提交components.js 2023-10-09 17:30:23 +08:00
刘祥超
2c67eac63c 升级时如果unzip命令不存在,则使用自定义解压程序 2023-10-09 17:30:10 +08:00
刘祥超
d20c20cb33 申请证书任务列表区分管理员和用户 2023-10-09 16:18:27 +08:00
刘祥超
fa76f032be 证书列表区分管理员和用户证书 2023-10-09 15:53:02 +08:00
刘祥超
3f74910640 访问日志列表搜索增加请求来源查询语法:referer:example.com 2023-10-08 17:52:48 +08:00
刘祥超
22bd2a57e9 WAF规则对比值长度限制为4096个字符 2023-10-08 16:14:50 +08:00
刘祥超
e45a1d0367 集群设置中增加“自动调节系统参数”选项 2023-10-08 15:08:11 +08:00
刘祥超
ee7acdc6a0 优化<url-pattern-box>组件对输入问号的提示 2023-09-27 15:24:05 +08:00
刘祥超
32264f2700 优化重写规则中目标URL选项说明 2023-09-25 18:02:35 +08:00
刘祥超
249ea32973 网站访问日志未开启时,在访问日志列表显示提醒文字 2023-09-22 16:26:15 +08:00
刘祥超
9c31b69a58 优化“集群设置--网站设置”页面 2023-09-18 17:58:16 +08:00
刘祥超
6e985cf6cc 将全局设置的TCP相关设置移到“集群设置--网站设置”中 2023-09-18 16:55:40 +08:00
刘祥超
0f35b45704 将全局的通用设置--域名审核设置移到“集群设置--网站设置”中 2023-09-18 16:07:57 +08:00
刘祥超
ba9245e724 优化集群设置--网站设置界面 2023-09-18 10:43:56 +08:00
刘祥超
6e806f2822 优化缓存磁盘用量选项提示文字 2023-09-18 10:35:22 +08:00
刘祥超
7c6842a859 修改Dockfile的版本号 2023-09-18 07:40:17 +08:00
89 changed files with 855 additions and 2910 deletions

View File

@@ -46,7 +46,7 @@
* [开发者指南](https://goedge.cn/docs/Developer/Build.md)
## 架构
![架构](doc/architect-zh.jpg)
![架构](doc/architect-zh.png)
其中的组件源码地址如下:
* [边缘节点](https://github.com/TeaOSLab/EdgeNode)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

BIN
doc/architect-zh.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

View File

@@ -1,7 +1,7 @@
FROM --platform=linux/amd64 alpine:latest
LABEL maintainer="goedge.cdn@gmail.com"
ENV TZ "Asia/Shanghai"
ENV VERSION 1.2.8
ENV VERSION 1.2.10
ENV ROOT_DIR /usr/local/goedge
ENV TAR_FILE edge-admin-linux-amd64-plus-v${VERSION}.zip

View File

@@ -1,9 +1,9 @@
package teaconst
const (
Version = "1.2.9"
Version = "1.2.10"
APINodeVersion = "1.2.9"
APINodeVersion = "1.2.10"
ProductName = "Edge Admin"
ProcessName = "edge-admin"

View File

@@ -280,34 +280,6 @@ func (this *RPCClient) MessageRPC() pb.MessageServiceClient {
return pb.NewMessageServiceClient(this.pickConn())
}
func (this *RPCClient) MessageRecipientGroupRPC() pb.MessageRecipientGroupServiceClient {
return pb.NewMessageRecipientGroupServiceClient(this.pickConn())
}
func (this *RPCClient) MessageRecipientRPC() pb.MessageRecipientServiceClient {
return pb.NewMessageRecipientServiceClient(this.pickConn())
}
func (this *RPCClient) MessageMediaRPC() pb.MessageMediaServiceClient {
return pb.NewMessageMediaServiceClient(this.pickConn())
}
func (this *RPCClient) MessageMediaInstanceRPC() pb.MessageMediaInstanceServiceClient {
return pb.NewMessageMediaInstanceServiceClient(this.pickConn())
}
func (this *RPCClient) MessageTaskRPC() pb.MessageTaskServiceClient {
return pb.NewMessageTaskServiceClient(this.pickConn())
}
func (this *RPCClient) MessageTaskLogRPC() pb.MessageTaskLogServiceClient {
return pb.NewMessageTaskLogServiceClient(this.pickConn())
}
func (this *RPCClient) MessageReceiverRPC() pb.MessageReceiverServiceClient {
return pb.NewMessageReceiverServiceClient(this.pickConn())
}
func (this *RPCClient) IPLibraryRPC() pb.IPLibraryServiceClient {
return pb.NewIPLibraryServiceClient(this.pickConn())
}
@@ -416,14 +388,6 @@ func (this *RPCClient) NodeTaskRPC() pb.NodeTaskServiceClient {
return pb.NewNodeTaskServiceClient(this.pickConn())
}
func (this *RPCClient) AuthorityKeyRPC() pb.AuthorityKeyServiceClient {
return pb.NewAuthorityKeyServiceClient(this.pickConn())
}
func (this *RPCClient) AuthorityNodeRPC() pb.AuthorityNodeServiceClient {
return pb.NewAuthorityNodeServiceClient(this.pickConn())
}
func (this *RPCClient) LatestItemRPC() pb.LatestItemServiceClient {
return pb.NewLatestItemServiceClient(this.pickConn())
}

95
internal/utils/unzip.go Normal file
View File

@@ -0,0 +1,95 @@
package utils
import (
"archive/zip"
"errors"
"io"
"os"
)
type Unzip struct {
zipFile string
targetDir string
}
func NewUnzip(zipFile string, targetDir string) *Unzip {
return &Unzip{
zipFile: zipFile,
targetDir: targetDir,
}
}
func (this *Unzip) Run() error {
if len(this.zipFile) == 0 {
return errors.New("zip file should not be empty")
}
if len(this.targetDir) == 0 {
return errors.New("target dir should not be empty")
}
reader, err := zip.OpenReader(this.zipFile)
if err != nil {
return err
}
defer func() {
_ = reader.Close()
}()
for _, file := range reader.File {
var info = file.FileInfo()
var target = this.targetDir + "/" + file.Name
// 目录
if info.IsDir() {
stat, err := os.Stat(target)
if err != nil {
if !os.IsNotExist(err) {
return err
} else {
err = os.MkdirAll(target, info.Mode())
if err != nil {
return err
}
}
} else if !stat.IsDir() {
err = os.MkdirAll(target, info.Mode())
if err != nil {
return err
}
}
continue
}
// 文件
err = func(file *zip.File, target string) error {
fileReader, err := file.Open()
if err != nil {
return err
}
defer func() {
_ = fileReader.Close()
}()
// remove old
_ = os.Remove(target)
// create new
fileWriter, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, file.FileInfo().Mode())
if err != nil {
return err
}
defer func() {
_ = fileWriter.Close()
}()
_, err = io.Copy(fileWriter, fileReader)
return err
}(file, target)
if err != nil {
return err
}
}
return nil
}

View File

@@ -88,10 +88,6 @@ func (this *UpgradeManager) Start() error {
// 检查unzip
unzipExe, _ := exec.LookPath("unzip")
if len(unzipExe) == 0 {
// TODO install unzip automatically or pack with a static 'unzip' file
return errors.New("can not find 'unzip' command")
}
// 检查cp
cpExe, _ := exec.LookPath("cp")
@@ -232,12 +228,24 @@ func (this *UpgradeManager) Start() error {
return fmt.Errorf("remove old dir '%s' failed: %w", unzipDir, err)
}
}
var unzipCmd = exec.Command(unzipExe, "-q", "-o", destFile, "-d", unzipDir)
var unzipStderr = &bytes.Buffer{}
unzipCmd.Stderr = unzipStderr
err = unzipCmd.Run()
if err != nil {
return fmt.Errorf("unzip installation file failed: %w: %s", err, unzipStderr.String())
if len(unzipExe) > 0 {
var unzipCmd = exec.Command(unzipExe, "-q", "-o", destFile, "-d", unzipDir)
var unzipStderr = &bytes.Buffer{}
unzipCmd.Stderr = unzipStderr
err = unzipCmd.Run()
if err != nil {
return fmt.Errorf("unzip installation file failed: %w: %s", err, unzipStderr.String())
}
} else {
var unzipCmd = &Unzip{
zipFile: destFile,
targetDir: unzipDir,
}
err = unzipCmd.Run()
if err != nil {
return fmt.Errorf("unzip installation file failed: %w", err)
}
}
installationFiles, err := filepath.Glob(unzipDir + "/edge-" + this.component + "/*")

View File

@@ -1,82 +0,0 @@
package recipients
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"regexp"
)
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
TimeFromHour string
TimeFromMinute string
TimeFromSecond string
TimeToHour string
TimeToMinute string
TimeToSecond 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)
var digitReg = regexp.MustCompile(`^\d+$`)
var timeFrom = ""
if digitReg.MatchString(params.TimeFromHour) && digitReg.MatchString(params.TimeFromMinute) && digitReg.MatchString(params.TimeFromSecond) {
timeFrom = params.TimeFromHour + ":" + params.TimeFromMinute + ":" + params.TimeFromSecond
}
var timeTo = ""
if digitReg.MatchString(params.TimeToHour) && digitReg.MatchString(params.TimeToMinute) && digitReg.MatchString(params.TimeToSecond) {
timeTo = params.TimeToHour + ":" + params.TimeToMinute + ":" + params.TimeToSecond
}
resp, err := this.RPC().MessageRecipientRPC().CreateMessageRecipient(this.AdminContext(), &pb.CreateMessageRecipientRequest{
AdminId: params.AdminId,
MessageMediaInstanceId: params.InstanceId,
User: params.User,
MessageRecipientGroupIds: groupIds,
Description: params.Description,
TimeFrom: timeFrom,
TimeTo: timeTo,
})
if err != nil {
this.ErrorPage(err)
return
}
defer this.CreateLogInfo(codes.MessageRecipient_LogCreateMessageRecipient, resp.MessageRecipientId)
this.Success()
}

View File

@@ -1,25 +0,0 @@
package recipients
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteAction struct {
actionutils.ParentAction
}
func (this *DeleteAction) RunPost(params struct {
RecipientId int64
}) {
defer this.CreateLogInfo(codes.MessageRecipient_LogDeleteMessageRecipient, params.RecipientId)
_, err := this.RPC().MessageRecipientRPC().DeleteMessageRecipient(this.AdminContext(), &pb.DeleteMessageRecipientRequest{MessageRecipientId: params.RecipientId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -1,38 +0,0 @@
package groups
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
)
type CreatePopupAction struct {
actionutils.ParentAction
}
func (this *CreatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreatePopupAction) RunGet(params struct{}) {
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
Name string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.
Field("name", params.Name).
Require("请输入分组名称")
_, err := this.RPC().MessageRecipientGroupRPC().CreateMessageRecipientGroup(this.AdminContext(), &pb.CreateMessageRecipientGroupRequest{Name: params.Name})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -1,22 +0,0 @@
package groups
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteAction struct {
actionutils.ParentAction
}
func (this *DeleteAction) RunPost(params struct {
GroupId int64
}) {
_, err := this.RPC().MessageRecipientGroupRPC().DeleteMessageRecipientGroup(this.AdminContext(), &pb.DeleteMessageRecipientGroupRequest{MessageRecipientGroupId: params.GroupId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

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

View File

@@ -1,23 +0,0 @@
package groups
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeAdmin)).
Data("teaMenu", "admins").
Data("teaSubMenu", "recipients").
Prefix("/admins/recipients/groups").
Get("", new(IndexAction)).
GetPost("/createPopup", new(CreatePopupAction)).
GetPost("/updatePopup", new(UpdatePopupAction)).
Post("/delete", new(DeleteAction)).
Get("/selectPopup", new(SelectPopupAction)).
EndAll()
})
}

View File

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

View File

@@ -1,64 +0,0 @@
package groups
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type UpdatePopupAction struct {
actionutils.ParentAction
}
func (this *UpdatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdatePopupAction) RunGet(params struct {
GroupId int64
}) {
groupResp, err := this.RPC().MessageRecipientGroupRPC().FindEnabledMessageRecipientGroup(this.AdminContext(), &pb.FindEnabledMessageRecipientGroupRequest{MessageRecipientGroupId: params.GroupId})
if err != nil {
this.ErrorPage(err)
return
}
group := groupResp.MessageRecipientGroup
if group == nil {
this.NotFound("messageRecipientGroup", params.GroupId)
return
}
this.Data["group"] = maps.Map{
"id": group.Id,
"name": group.Name,
"isOn": group.IsOn,
}
this.Show()
}
func (this *UpdatePopupAction) RunPost(params struct {
GroupId int64
Name string
IsOn bool
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.
Field("name", params.Name).
Require("请输入分组名称")
_, err := this.RPC().MessageRecipientGroupRPC().UpdateMessageRecipientGroup(this.AdminContext(), &pb.UpdateMessageRecipientGroupRequest{
MessageRecipientGroupId: params.GroupId,
Name: params.Name,
IsOn: params.IsOn,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -1,74 +0,0 @@
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,
})
if err != nil {
this.ErrorPage(err)
return
}
recipientMaps := []maps.Map{}
for _, recipient := range recipientsResp.MessageRecipients {
if recipient.Admin == nil {
continue
}
if recipient.MessageMediaInstance == nil {
continue
}
recipientMaps = append(recipientMaps, maps.Map{
"id": recipient.Id,
"admin": maps.Map{
"id": recipient.Admin.Id,
"fullname": recipient.Admin.Fullname,
"username": recipient.Admin.Username,
},
"groups": recipient.MessageRecipientGroups,
"isOn": recipient.IsOn,
"instance": maps.Map{
"name": recipient.MessageMediaInstance.Name,
},
"user": recipient.User,
"description": recipient.Description,
})
}
this.Data["recipients"] = recipientMaps
this.Show()
}

View File

@@ -1,25 +0,0 @@
package recipients
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeAdmin)).
Data("teaMenu", "admins").
Data("teaSubMenu", "recipients").
Prefix("/admins/recipients").
Get("", new(IndexAction)).
GetPost("/createPopup", new(CreatePopupAction)).
GetPost("/update", new(UpdateAction)).
Post("/delete", new(DeleteAction)).
Post("/mediaOptions", new(MediaOptionsAction)).
Get("/recipient", new(RecipientAction)).
GetPost("/test", new(TestAction)).
EndAll()
})
}

View File

@@ -1,267 +0,0 @@
package instances
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/monitorconfigs"
"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
RateMinutes int32
RateCount int32
HashLife int32
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
}
var rateConfig = &monitorconfigs.RateConfig{
Minutes: params.RateMinutes,
Count: params.RateCount,
}
rateJSON, err := json.Marshal(rateConfig)
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,
RateJSON: rateJSON,
HashLife: params.HashLife,
})
if err != nil {
this.ErrorPage(err)
return
}
defer this.CreateLogInfo(codes.MessageMediaInstance_LogCreateMessageMediaInstance, resp.MessageMediaInstanceId)
this.Success()
}

View File

@@ -1,25 +0,0 @@
package instances
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteAction struct {
actionutils.ParentAction
}
func (this *DeleteAction) RunPost(params struct {
InstanceId int64
}) {
defer this.CreateLogInfo(codes.MessageMediaInstance_LogDeleteMessageMediaInstance, params.InstanceId)
_, err := this.RPC().MessageMediaInstanceRPC().DeleteMessageMediaInstance(this.AdminContext(), &pb.DeleteMessageMediaInstanceRequest{MessageMediaInstanceId: params.InstanceId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -1,61 +0,0 @@
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,
})
if err != nil {
this.ErrorPage(err)
return
}
instanceMaps := []maps.Map{}
for _, instance := range instancesResp.MessageMediaInstances {
if instance.MessageMedia == nil {
continue
}
instanceMaps = append(instanceMaps, maps.Map{
"id": instance.Id,
"name": instance.Name,
"isOn": instance.IsOn,
"media": maps.Map{
"name": instance.MessageMedia.Name,
},
"description": instance.Description,
})
}
this.Data["instances"] = instanceMaps
this.Show()
}

View File

@@ -1,25 +0,0 @@
package instances
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeAdmin)).
Data("teaMenu", "admins").
Data("teaSubMenu", "instances").
Prefix("/admins/recipients/instances").
Get("", new(IndexAction)).
GetPost("/createPopup", new(CreatePopupAction)).
GetPost("/update", new(UpdateAction)).
Post("/delete", new(DeleteAction)).
Get("/instance", new(InstanceAction)).
GetPost("/test", new(TestAction)).
Post("/options", new(OptionsAction)).
EndAll()
})
}

View File

@@ -1,67 +0,0 @@
package instances
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/monitorconfigs"
"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
}
}
// 频率
var rateConfig = &monitorconfigs.RateConfig{}
if len(instance.RateJSON) > 0 {
err = json.Unmarshal(instance.RateJSON, rateConfig)
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,
"rate": rateConfig,
"hashLife": instance.HashLife,
}
this.Show()
}

View File

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

View File

@@ -1,88 +0,0 @@
package instances
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"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(codes.MessageTask_LogCreateTestingMessageTask, resp.MessageTaskId)
this.Success()
}

View File

@@ -1,317 +0,0 @@
package instances
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/monitorconfigs"
"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
}
}
var rateConfig = &monitorconfigs.RateConfig{}
if len(instance.RateJSON) > 0 {
err = json.Unmarshal(instance.RateJSON, rateConfig)
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,
"rate": rateConfig,
"hashLife": instance.HashLife,
}
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
RateMinutes int32
RateCount int32
HashLife int32
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.MessageMediaInstance_LogUpdateMessageMediaInstance, 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
}
var rateConfig = &monitorconfigs.RateConfig{
Minutes: params.RateMinutes,
Count: params.RateCount,
}
rateJSON, err := json.Marshal(rateConfig)
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,
RateJSON: rateJSON,
HashLife: params.HashLife,
IsOn: params.IsOn,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

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

View File

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

View File

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

View File

@@ -1,52 +0,0 @@
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,
"timeFrom": recipient.TimeFrom,
"timeTo": recipient.TimeTo,
}
this.Show()
}

View File

@@ -1,27 +0,0 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package tasks
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteAction struct {
actionutils.ParentAction
}
func (this *DeleteAction) RunPost(params struct {
TaskId int64
}) {
defer this.CreateLogInfo(codes.MessageTask_LogDeleteMessageTask, params.TaskId)
_, err := this.RPC().MessageTaskRPC().DeleteMessageTask(this.AdminContext(), &pb.DeleteMessageTaskRequest{MessageTaskId: params.TaskId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -1,103 +0,0 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package tasks
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("", "", "task")
}
func (this *IndexAction) RunGet(params struct {
Status int32
}) {
this.Data["status"] = params.Status
if params.Status > 3 {
params.Status = 0
}
countWaitingResp, err := this.RPC().MessageTaskRPC().CountMessageTasksWithStatus(this.AdminContext(), &pb.CountMessageTasksWithStatusRequest{Status: pb.CountMessageTasksWithStatusRequest_MessageTaskStatusNone})
if err != nil {
this.ErrorPage(err)
return
}
var countWaiting = countWaitingResp.Count
this.Data["countWaiting"] = countWaiting
countFailedResp, err := this.RPC().MessageTaskRPC().CountMessageTasksWithStatus(this.AdminContext(), &pb.CountMessageTasksWithStatusRequest{Status: pb.CountMessageTasksWithStatusRequest_MessageTaskStatusFailed})
if err != nil {
this.ErrorPage(err)
return
}
var countFailed = countFailedResp.Count
this.Data["countFailed"] = countFailed
// 列表
var total = int64(0)
switch params.Status {
case 0:
total = countWaiting
case 3:
total = countFailed
}
page := this.NewPage(total)
this.Data["page"] = page.AsHTML()
var taskMaps = []maps.Map{}
tasksResp, err := this.RPC().MessageTaskRPC().ListMessageTasksWithStatus(this.AdminContext(), &pb.ListMessageTasksWithStatusRequest{
Status: pb.ListMessageTasksWithStatusRequest_Status(params.Status),
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
for _, task := range tasksResp.MessageTasks {
var resultMap = maps.Map{}
var result = task.Result
if result != nil {
resultMap = maps.Map{
"isOk": result.IsOk,
"error": result.Error,
"response": result.Response,
}
}
//var recipients = []string{}
var user = ""
var instanceMap maps.Map
if task.MessageRecipient != nil {
user = task.MessageRecipient.User
if task.MessageRecipient.MessageMediaInstance != nil {
instanceMap = maps.Map{
"id": task.MessageRecipient.MessageMediaInstance.Id,
"name": task.MessageRecipient.MessageMediaInstance.Name,
}
}
}
taskMaps = append(taskMaps, maps.Map{
"id": task.Id,
"subject": task.Subject,
"body": task.Body,
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", task.CreatedAt),
"result": resultMap,
"status": task.Status,
"user": user,
"instance": instanceMap,
})
}
this.Data["tasks"] = taskMaps
this.Show()
}

View File

@@ -1,21 +0,0 @@
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").
Get("", new(IndexAction)).
Post("/taskInfo", new(TaskInfoAction)).
Post("/delete", new(DeleteAction)).
EndAll()
})
}

View File

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

View File

@@ -1,114 +0,0 @@
package recipients
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"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(codes.MessageTask_LogCreateTestingMessageTask, resp.MessageTaskId)
this.Success()
}

View File

@@ -1,144 +0,0 @@
package recipients
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"regexp"
"strings"
)
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
}
var timeFromHour = ""
var timeFromMinute = ""
var timeFromSecond = ""
if len(recipient.TimeFrom) > 0 {
var pieces = strings.Split(recipient.TimeFrom, ":")
timeFromHour = pieces[0]
timeFromMinute = pieces[1]
timeFromSecond = pieces[2]
}
var timeToHour = ""
var timeToMinute = ""
var timeToSecond = ""
if len(recipient.TimeTo) > 0 {
var pieces = strings.Split(recipient.TimeTo, ":")
timeToHour = pieces[0]
timeToMinute = pieces[1]
timeToSecond = pieces[2]
}
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,
"timeFromHour": timeFromHour,
"timeFromMinute": timeFromMinute,
"timeFromSecond": timeFromSecond,
"timeToHour": timeToHour,
"timeToMinute": timeToMinute,
"timeToSecond": timeToSecond,
}
this.Show()
}
func (this *UpdateAction) RunPost(params struct {
RecipientId int64
AdminId int64
User string
InstanceId int64
GroupIds string
Description string
IsOn bool
TimeFromHour string
TimeFromMinute string
TimeFromSecond string
TimeToHour string
TimeToMinute string
TimeToSecond string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.MessageRecipient_LogUpdateMessageRecipient, params.RecipientId)
params.Must.
Field("adminId", params.AdminId).
Gt(0, "请选择系统用户").
Field("instanceId", params.InstanceId).
Gt(0, "请选择媒介")
groupIds := utils.SplitNumbers(params.GroupIds)
var digitReg = regexp.MustCompile(`^\d+$`)
var timeFrom = ""
if digitReg.MatchString(params.TimeFromHour) && digitReg.MatchString(params.TimeFromMinute) && digitReg.MatchString(params.TimeFromSecond) {
timeFrom = params.TimeFromHour + ":" + params.TimeFromMinute + ":" + params.TimeFromSecond
}
var timeTo = ""
if digitReg.MatchString(params.TimeToHour) && digitReg.MatchString(params.TimeToMinute) && digitReg.MatchString(params.TimeToSecond) {
timeTo = params.TimeToHour + ":" + params.TimeToMinute + ":" + params.TimeToSecond
}
_, 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,
TimeFrom: timeFrom,
TimeTo: timeTo,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -100,6 +100,9 @@ func (this *IndexAction) RunPost(params struct {
HttpAllNodeIPShowPage bool
HttpAllEnableServerAddrVariable bool
HttpAllDomainAuditingIsOn bool
HttpAllDomainAuditingPrompt string
HttpAllServerName string
HttpAllSupportsLowVersionHTTP bool
HttpAllMatchCertFromAllServers bool
@@ -119,6 +122,11 @@ func (this *IndexAction) RunPost(params struct {
PerformanceAutoWriteTimeout bool
PerformanceDebug bool
// TCP端口设置
TcpAllPortRangeMin int
TcpAllPortRangeMax int
TcpAllDenyPorts []int
Must *actions.Must
CSRF *actionutils.CSRF
}) {
@@ -172,6 +180,9 @@ func (this *IndexAction) RunPost(params struct {
config.HTTPAll.NodeIPShowPage = params.HttpAllNodeIPShowPage
config.HTTPAll.NodeIPPageHTML = params.HttpAllNodeIPPageHTML
config.HTTPAll.DomainAuditingIsOn = params.HttpAllDomainAuditingIsOn
config.HTTPAll.DomainAuditingPrompt = params.HttpAllDomainAuditingPrompt
// HTTP All
config.HTTPAll.ServerName = params.HttpAllServerName
config.HTTPAll.SupportsLowVersionHTTP = params.HttpAllSupportsLowVersionHTTP
@@ -191,6 +202,23 @@ func (this *IndexAction) RunPost(params struct {
// 日志
config.Log.RecordServerError = params.LogRecordServerError
// TCP
if params.TcpAllPortRangeMin < 1024 {
params.TcpAllPortRangeMin = 1024
}
if params.TcpAllPortRangeMax > 65534 {
params.TcpAllPortRangeMax = 65534
} else if params.TcpAllPortRangeMax < 1024 {
params.TcpAllPortRangeMax = 1024
}
if params.TcpAllPortRangeMin > params.TcpAllPortRangeMax {
params.TcpAllPortRangeMin, params.TcpAllPortRangeMax = params.TcpAllPortRangeMax, params.TcpAllPortRangeMin
}
config.TCPAll.DenyPorts = params.TcpAllDenyPorts
config.TCPAll.PortRangeMin = params.TcpAllPortRangeMin
config.TCPAll.PortRangeMax = params.TcpAllPortRangeMax
// 性能
config.Performance.AutoReadTimeout = params.PerformanceAutoReadTimeout
config.Performance.AutoWriteTimeout = params.PerformanceAutoWriteTimeout

View File

@@ -112,6 +112,7 @@ func (this *IndexAction) RunGet(params struct {
"clock": clockConfig,
"autoRemoteStart": cluster.AutoRemoteStart,
"autoInstallNftables": cluster.AutoInstallNftables,
"autoSystemTuning": cluster.AutoSystemTuning,
"sshParams": sshParams,
"domainName": fullDomainName,
}
@@ -139,6 +140,7 @@ func (this *IndexAction) RunPost(params struct {
ClockCheckChrony bool
AutoRemoteStart bool
AutoInstallNftables bool
AutoSystemTuning bool
Must *actions.Must
}) {
@@ -193,6 +195,7 @@ func (this *IndexAction) RunPost(params struct {
ClockJSON: clockConfigJSON,
AutoRemoteStart: params.AutoRemoteStart,
AutoInstallNftables: params.AutoInstallNftables,
AutoSystemTuning: params.AutoSystemTuning,
SshParamsJSON: sshParamsJSON,
})
if err != nil {

View File

@@ -64,6 +64,7 @@ func (this *CreateAction) RunPost(params struct {
InstallDir string
SystemdServiceIsOn bool
AutoInstallNftables bool
AutoSystemTuning bool
// DNS相关
DnsDomainId int64
@@ -132,6 +133,7 @@ func (this *CreateAction) RunPost(params struct {
SystemServicesJSON: systemServicesJSON,
GlobalServerConfigJSON: globalServerConfigJSON,
AutoInstallNftables: params.AutoInstallNftables,
AutoSystemTuning: params.AutoSystemTuning,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -19,6 +19,7 @@ import (
"os/exec"
"regexp"
"runtime"
"strings"
)
// CheckDiskPartitions 检查服务器磁盘空间
@@ -47,6 +48,14 @@ func CheckDiskPartitions(thresholdPercent float64) (path string, usage uint64, u
if p.Fstype != rootFS {
continue
}
// skip some specified partitions on macOS
if runtime.GOOS == "darwin" {
if strings.Contains(p.Mountpoint, "/Developer/") {
continue
}
}
stat, _ := disk.Usage(p.Mountpoint)
if stat != nil {
if stat.Used < 2*uint64(sizes.G) {

View File

@@ -154,17 +154,6 @@ func (this *IndexAction) RunPost(params struct{}) {
"version": "",
}
}
if resp.MonitorNodeUpgradeInfo != nil {
this.Data["monitorNodeUpgradeInfo"] = maps.Map{
"count": resp.MonitorNodeUpgradeInfo.CountNodes,
"version": resp.MonitorNodeUpgradeInfo.NewVersion,
}
} else {
this.Data["monitorNodeUpgradeInfo"] = maps.Map{
"count": 0,
"version": "",
}
}
if resp.ApiNodeUpgradeInfo != nil {
this.Data["apiNodeUpgradeInfo"] = maps.Map{
"count": resp.ApiNodeUpgradeInfo.CountNodes,
@@ -176,39 +165,6 @@ func (this *IndexAction) RunPost(params struct{}) {
"version": "",
}
}
if resp.UserNodeUpgradeInfo != nil {
this.Data["userNodeUpgradeInfo"] = maps.Map{
"count": resp.UserNodeUpgradeInfo.CountNodes,
"version": resp.UserNodeUpgradeInfo.NewVersion,
}
} else {
this.Data["userNodeUpgradeInfo"] = maps.Map{
"count": 0,
"version": 0,
}
}
if resp.AuthorityNodeUpgradeInfo != nil {
this.Data["authorityNodeUpgradeInfo"] = maps.Map{
"count": resp.AuthorityNodeUpgradeInfo.CountNodes,
"version": resp.AuthorityNodeUpgradeInfo.NewVersion,
}
} else {
this.Data["authorityNodeUpgradeInfo"] = maps.Map{
"count": 0,
"version": "",
}
}
if resp.NsNodeUpgradeInfo != nil {
this.Data["nsNodeUpgradeInfo"] = maps.Map{
"count": resp.NsNodeUpgradeInfo.CountNodes,
"version": resp.NsNodeUpgradeInfo.NewVersion,
}
} else {
this.Data["nsNodeUpgradeInfo"] = maps.Map{
"count": 0,
"version": "",
}
}
// 域名排行
{

View File

@@ -17,12 +17,16 @@ func (this *IndexAction) Init() {
}
func (this *IndexAction) RunGet(params struct {
UserId int64
Type string
Keyword string
UserId int64
Type string
Keyword string
UserType string
}) {
this.Data["type"] = params.Type
this.Data["keyword"] = params.Keyword
this.Data["userType"] = params.UserType
var userOnly = params.UserId > 0 || params.UserType == "user"
// 当前用户
this.Data["searchingUserId"] = params.UserId
@@ -58,8 +62,9 @@ func (this *IndexAction) RunGet(params struct {
{
// all
resp, err := this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
UserId: params.UserId,
Keyword: params.Keyword,
UserId: params.UserId,
Keyword: params.Keyword,
UserOnly: userOnly,
})
if err != nil {
this.ErrorPage(err)
@@ -72,6 +77,7 @@ func (this *IndexAction) RunGet(params struct {
UserId: params.UserId,
IsAvailable: true,
Keyword: params.Keyword,
UserOnly: userOnly,
})
if err != nil {
this.ErrorPage(err)
@@ -84,6 +90,7 @@ func (this *IndexAction) RunGet(params struct {
UserId: params.UserId,
IsExpired: true,
Keyword: params.Keyword,
UserOnly: userOnly,
})
if err != nil {
this.ErrorPage(err)
@@ -96,6 +103,7 @@ func (this *IndexAction) RunGet(params struct {
UserId: params.UserId,
ExpiringDays: 7,
Keyword: params.Keyword,
UserOnly: userOnly,
})
if err != nil {
this.ErrorPage(err)
@@ -108,6 +116,7 @@ func (this *IndexAction) RunGet(params struct {
UserId: params.UserId,
ExpiringDays: 30,
Keyword: params.Keyword,
UserOnly: userOnly,
})
if err != nil {
this.ErrorPage(err)
@@ -130,10 +139,11 @@ func (this *IndexAction) RunGet(params struct {
case "":
page = this.NewPage(countAll)
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{
UserId: params.UserId,
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
UserId: params.UserId,
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
UserOnly: userOnly,
})
case "available":
page = this.NewPage(countAvailable)
@@ -143,6 +153,7 @@ func (this *IndexAction) RunGet(params struct {
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
UserOnly: userOnly,
})
case "expired":
page = this.NewPage(countExpired)
@@ -152,6 +163,7 @@ func (this *IndexAction) RunGet(params struct {
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
UserOnly: userOnly,
})
case "7days":
page = this.NewPage(count7Days)
@@ -161,6 +173,7 @@ func (this *IndexAction) RunGet(params struct {
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
UserOnly: userOnly,
})
case "30days":
page = this.NewPage(count30Days)
@@ -170,14 +183,16 @@ func (this *IndexAction) RunGet(params struct {
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
UserOnly: userOnly,
})
default:
page = this.NewPage(countAll)
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{
UserId: params.UserId,
Keyword: params.Keyword,
Offset: page.Offset,
Size: page.Size,
UserId: params.UserId,
Keyword: params.Keyword,
UserOnly: userOnly,
Offset: page.Offset,
Size: page.Size,
})
}
if err != nil {
@@ -242,6 +257,23 @@ func (this *IndexAction) RunGet(params struct {
}
}
// user
userResp, err := this.RPC().ACMETaskRPC().FindACMETaskUser(this.AdminContext(), &pb.FindACMETaskUserRequest{AcmeTaskId: task.Id})
if err != nil {
this.ErrorPage(err)
return
}
var taskUserMap = maps.Map{
"id": 0,
}
if userResp.User != nil {
taskUserMap = maps.Map{
"id": userResp.User.Id,
"username": userResp.User.Username,
"fullname": userResp.User.Fullname,
}
}
taskMaps = append(taskMaps, maps.Map{
"id": task.Id,
"authType": task.AuthType,
@@ -257,6 +289,7 @@ func (this *IndexAction) RunGet(params struct {
"autoRenew": task.AutoRenew,
"cert": certMap,
"log": logMap,
"user": taskUserMap,
})
}
this.Data["tasks"] = taskMaps

View File

@@ -19,13 +19,19 @@ func (this *IndexAction) Init() {
}
func (this *IndexAction) RunGet(params struct {
UserId int64
Type string
Keyword string
UserId int64
Type string // [empty] | ca | 7days | ...
Keyword string
UserType string
}) {
this.Data["type"] = params.Type
this.Data["keyword"] = params.Keyword
if params.UserId > 0 {
params.UserType = "user"
}
this.Data["userType"] = params.UserType
// 当前用户
this.Data["searchingUserId"] = params.UserId
var userMap = maps.Map{
@@ -57,12 +63,15 @@ func (this *IndexAction) RunGet(params struct {
var count7Days int64
var count30Days int64
var userOnly = params.UserType == "user" || params.UserId > 0
// 计算数量
{
// all
resp, err := this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
UserId: params.UserId,
Keyword: params.Keyword,
UserId: params.UserId,
Keyword: params.Keyword,
UserOnly: userOnly,
})
if err != nil {
this.ErrorPage(err)
@@ -72,9 +81,10 @@ func (this *IndexAction) RunGet(params struct {
// CA
resp, err = this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
UserId: params.UserId,
IsCA: true,
Keyword: params.Keyword,
UserId: params.UserId,
IsCA: true,
Keyword: params.Keyword,
UserOnly: userOnly,
})
if err != nil {
this.ErrorPage(err)
@@ -87,6 +97,7 @@ func (this *IndexAction) RunGet(params struct {
UserId: params.UserId,
IsAvailable: true,
Keyword: params.Keyword,
UserOnly: userOnly,
})
if err != nil {
this.ErrorPage(err)
@@ -99,6 +110,7 @@ func (this *IndexAction) RunGet(params struct {
UserId: params.UserId,
IsExpired: true,
Keyword: params.Keyword,
UserOnly: userOnly,
})
if err != nil {
this.ErrorPage(err)
@@ -111,6 +123,7 @@ func (this *IndexAction) RunGet(params struct {
UserId: params.UserId,
ExpiringDays: 7,
Keyword: params.Keyword,
UserOnly: userOnly,
})
if err != nil {
this.ErrorPage(err)
@@ -123,6 +136,7 @@ func (this *IndexAction) RunGet(params struct {
UserId: params.UserId,
ExpiringDays: 30,
Keyword: params.Keyword,
UserOnly: userOnly,
})
if err != nil {
this.ErrorPage(err)
@@ -146,19 +160,21 @@ func (this *IndexAction) RunGet(params struct {
case "":
page = this.NewPage(countAll)
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
UserId: params.UserId,
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
UserId: params.UserId,
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
UserOnly: userOnly,
})
case "ca":
page = this.NewPage(countCA)
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
UserId: params.UserId,
IsCA: true,
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
UserId: params.UserId,
IsCA: true,
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
UserOnly: userOnly,
})
case "available":
page = this.NewPage(countAvailable)
@@ -168,6 +184,7 @@ func (this *IndexAction) RunGet(params struct {
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
UserOnly: userOnly,
})
case "expired":
page = this.NewPage(countExpired)
@@ -177,6 +194,7 @@ func (this *IndexAction) RunGet(params struct {
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
UserOnly: userOnly,
})
case "7days":
page = this.NewPage(count7Days)
@@ -195,14 +213,16 @@ func (this *IndexAction) RunGet(params struct {
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
UserOnly: userOnly,
})
default:
page = this.NewPage(countAll)
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
UserId: params.UserId,
Keyword: params.Keyword,
Offset: page.Offset,
Size: page.Size,
UserId: params.UserId,
Keyword: params.Keyword,
UserOnly: userOnly,
Offset: page.Offset,
Size: page.Size,
})
}
if err != nil {
@@ -221,6 +241,7 @@ func (this *IndexAction) RunGet(params struct {
var certMaps = []maps.Map{}
var nowTime = time.Now().Unix()
for _, certConfig := range certConfigs {
// count servers
countServersResp, err := this.RPC().ServerRPC().CountAllEnabledServersWithSSLCertId(this.AdminContext(), &pb.CountAllEnabledServersWithSSLCertIdRequest{
SslCertId: certConfig.Id,
})
@@ -229,6 +250,23 @@ func (this *IndexAction) RunGet(params struct {
return
}
// user
userResp, err := this.RPC().SSLCertRPC().FindSSLCertUser(this.AdminContext(), &pb.FindSSLCertUserRequest{SslCertId: certConfig.Id})
if err != nil {
this.ErrorPage(err)
return
}
var certUserMap = maps.Map{
"id": 0,
}
if userResp.User != nil {
certUserMap = maps.Map{
"id": userResp.User.Id,
"username": userResp.User.Username,
"fullname": userResp.User.Fullname,
}
}
certMaps = append(certMaps, maps.Map{
"isOn": certConfig.IsOn,
"beginDay": timeutil.FormatTime("Y-m-d", certConfig.TimeBeginAt),
@@ -236,6 +274,7 @@ func (this *IndexAction) RunGet(params struct {
"isExpired": nowTime > certConfig.TimeEndAt,
"isAvailable": nowTime <= certConfig.TimeEndAt,
"countServers": countServersResp.Count,
"user": certUserMap,
})
}
this.Data["certInfos"] = certMaps

View File

@@ -1,16 +0,0 @@
package components
import (
"github.com/iwind/TeaGo/actions"
)
type Helper struct {
}
func NewHelper() *Helper {
return &Helper{}
}
func (this *Helper) BeforeAction(action *actions.ActionObject) {
action.Data["teaMenu"] = "servers"
}

View File

@@ -1,118 +0,0 @@
package components
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions"
)
const (
SettingCodeServerGlobalConfig = "serverGlobalConfig"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "component", "index")
this.SecondMenu("global")
}
func (this *IndexAction) RunGet(params struct{}) {
valueJSONResp, err := this.RPC().SysSettingRPC().ReadSysSetting(this.AdminContext(), &pb.ReadSysSettingRequest{Code: SettingCodeServerGlobalConfig})
if err != nil {
this.ErrorPage(err)
return
}
valueJSON := valueJSONResp.ValueJSON
globalConfig := &serverconfigs.GlobalConfig{}
// 默认值
globalConfig.HTTPAll.DomainAuditingIsOn = false
if len(valueJSON) > 0 {
err = json.Unmarshal(valueJSON, globalConfig)
if err != nil {
this.ErrorPage(err)
return
}
}
this.Data["globalConfig"] = globalConfig
this.Show()
}
func (this *IndexAction) RunPost(params struct {
GlobalConfigJSON []byte
Must *actions.Must
// 不匹配域名相关
AllowMismatchDomains []string
DomainMismatchAction string
DomainMismatchActionPageStatusCode int
DomainMismatchActionPageContentHTML string
// TCP端口设置
TcpAllPortRangeMin int
TcpAllPortRangeMax int
TcpAllDenyPorts []int
DefaultDomain string
}) {
// 创建日志
defer this.CreateLogInfo(codes.Server_LogUpdateGlobalSettings)
if len(params.GlobalConfigJSON) == 0 {
this.Fail("错误的配置信息,请刷新当前页面后重试")
}
globalConfig := &serverconfigs.GlobalConfig{}
err := json.Unmarshal(params.GlobalConfigJSON, globalConfig)
if err != nil {
this.Fail("配置校验失败:" + err.Error())
}
// TCP端口范围
if params.TcpAllPortRangeMin < 1024 {
params.TcpAllPortRangeMin = 1024
}
if params.TcpAllPortRangeMax > 65534 {
params.TcpAllPortRangeMax = 65534
} else if params.TcpAllPortRangeMax < 1024 {
params.TcpAllPortRangeMax = 1024
}
if params.TcpAllPortRangeMin > params.TcpAllPortRangeMax {
params.TcpAllPortRangeMin, params.TcpAllPortRangeMax = params.TcpAllPortRangeMax, params.TcpAllPortRangeMin
}
globalConfig.TCPAll.DenyPorts = params.TcpAllDenyPorts
globalConfig.TCPAll.PortRangeMin = params.TcpAllPortRangeMin
globalConfig.TCPAll.PortRangeMax = params.TcpAllPortRangeMax
// 修改配置
globalConfigJSON, err := json.Marshal(globalConfig)
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().SysSettingRPC().UpdateSysSetting(this.AdminContext(), &pb.UpdateSysSettingRequest{
Code: SettingCodeServerGlobalConfig,
ValueJSON: globalConfigJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
// 通知更新
_, err = this.RPC().ServerRPC().NotifyServersChange(this.AdminContext(), &pb.NotifyServersChangeRequest{})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -1,20 +0,0 @@
package components
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeServer)).
Data("teaMenu", "servers").
Data("teaSubMenu", "global").
Helper(NewHelper()).
Prefix("/servers/components").
GetPost("", new(IndexAction)).
EndAll()
})
}

View File

@@ -71,7 +71,13 @@ func (this *CreateRulePopupAction) RunPost(params struct {
Field("prefix", params.Prefix).
Require("请选择参数")
rule := &firewallconfigs.HTTPFirewallRule{
if len(params.Value) > 4096 {
this.FailField("value", "对比值内容长度不能超过4096个字符")
return
}
var rule = &firewallconfigs.HTTPFirewallRule{
Id: params.RuleId,
IsOn: true,
}
@@ -81,7 +87,7 @@ func (this *CreateRulePopupAction) RunPost(params struct {
rule.Param = "${" + params.Prefix + "}"
}
paramFilters := []*firewallconfigs.ParamFilter{}
var paramFilters = []*firewallconfigs.ParamFilter{}
if len(params.ParamFiltersJSON) > 0 {
err := json.Unmarshal(params.ParamFiltersJSON, &paramFilters)
if err != nil {

View File

@@ -2,6 +2,7 @@ package log
import (
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
@@ -61,6 +62,27 @@ func (this *HistoryAction) RunGet(params struct {
return
}
// 检查当前网站有无开启访问日志
this.Data["serverAccessLogIsOn"] = true
groupResp, err := this.RPC().ServerGroupRPC().FindEnabledServerGroupConfigInfo(this.AdminContext(), &pb.FindEnabledServerGroupConfigInfoRequest{
ServerId: params.ServerId,
})
if err != nil {
this.ErrorPage(err)
return
}
if !groupResp.HasAccessLogConfig {
webConfig, err := dao.SharedHTTPWebDAO.FindWebConfigWithServerId(this.AdminContext(), params.ServerId)
if err != nil {
this.ErrorPage(err)
return
}
if webConfig != nil && webConfig.AccessLogRef != nil && !webConfig.AccessLogRef.IsOn {
this.Data["serverAccessLogIsOn"] = false
}
}
var day = params.Day
var ipList = []string{}
var wafMaps = []maps.Map{}

View File

@@ -2,6 +2,7 @@ package log
import (
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists"
@@ -41,8 +42,29 @@ func (this *IndexAction) RunGet(params struct {
return
}
// 检查当前网站有无开启访问日志
this.Data["serverAccessLogIsOn"] = true
groupResp, err := this.RPC().ServerGroupRPC().FindEnabledServerGroupConfigInfo(this.AdminContext(), &pb.FindEnabledServerGroupConfigInfoRequest{
ServerId: params.ServerId,
})
if err != nil {
this.ErrorPage(err)
return
}
if !groupResp.HasAccessLogConfig {
webConfig, err := dao.SharedHTTPWebDAO.FindWebConfigWithServerId(this.AdminContext(), params.ServerId)
if err != nil {
this.ErrorPage(err)
return
}
if webConfig != nil && webConfig.AccessLogRef != nil && !webConfig.AccessLogRef.IsOn {
this.Data["serverAccessLogIsOn"] = false
}
}
// 记录最近使用
_, err := this.RPC().LatestItemRPC().IncreaseLatestItem(this.AdminContext(), &pb.IncreaseLatestItemRequest{
_, err = this.RPC().LatestItemRPC().IncreaseLatestItem(this.AdminContext(), &pb.IncreaseLatestItemRequest{
ItemType: "server",
ItemId: params.ServerId,
})

View File

@@ -2,6 +2,7 @@ package log
import (
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
@@ -55,6 +56,27 @@ func (this *TodayAction) RunGet(params struct {
return
}
// 检查当前网站有无开启访问日志
this.Data["serverAccessLogIsOn"] = true
groupResp, err := this.RPC().ServerGroupRPC().FindEnabledServerGroupConfigInfo(this.AdminContext(), &pb.FindEnabledServerGroupConfigInfoRequest{
ServerId: params.ServerId,
})
if err != nil {
this.ErrorPage(err)
return
}
if !groupResp.HasAccessLogConfig {
webConfig, err := dao.SharedHTTPWebDAO.FindWebConfigWithServerId(this.AdminContext(), params.ServerId)
if err != nil {
this.ErrorPage(err)
return
}
if webConfig != nil && webConfig.AccessLogRef != nil && !webConfig.AccessLogRef.IsOn {
this.Data["serverAccessLogIsOn"] = false
}
}
resp, err := this.RPC().HTTPAccessLogRPC().ListHTTPAccessLogs(this.AdminContext(), &pb.ListHTTPAccessLogsRequest{
Partition: params.Partition,
RequestId: params.RequestId,

View File

@@ -21,7 +21,7 @@ func (this *IndexAction) Init() {
func (this *IndexAction) RunGet(params struct {
ServerId int64
}) {
// 服务分组设置
// 网站分组设置
groupResp, err := this.RPC().ServerGroupRPC().FindEnabledServerGroupConfigInfo(this.AdminContext(), &pb.FindEnabledServerGroupConfigInfoRequest{
ServerId: params.ServerId,
})

View File

@@ -291,10 +291,11 @@ func (this *ServerHelper) createSettingsMenu(secondMenuItem string, serverIdStri
"isOn": serverConfig.Web != nil && len(serverConfig.Web.RewriteRefs) > 0,
})
menuItems = append(menuItems, maps.Map{
"name": this.Lang(actionPtr, codes.Server_MenuSettingWAF),
"url": "/servers/server/settings/waf?serverId=" + serverIdString,
"isActive": secondMenuItem == "waf",
"isOn": serverConfig.Web != nil && serverConfig.Web.FirewallRef != nil && serverConfig.Web.FirewallRef.IsOn,
"name": this.Lang(actionPtr, codes.Server_MenuSettingWAF),
"url": "/servers/server/settings/waf?serverId=" + serverIdString,
"isActive": secondMenuItem == "waf",
"isOn": serverConfig.Web != nil && serverConfig.Web.FirewallRef != nil && serverConfig.Web.FirewallRef.IsOn,
"configCode": serverconfigs.ConfigCodeWAF,
})
menuItems = append(menuItems, maps.Map{
"name": this.Lang(actionPtr, codes.Server_MenuSettingCache),

View File

@@ -5,7 +5,6 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/maps"
@@ -28,17 +27,7 @@ func (this *StatusAction) RunPost(params struct {
}
// 读取全局配置
globalConfig, err := dao.SharedSysSettingDAO.ReadGlobalConfig(this.AdminContext())
if err != nil {
this.ErrorPage(err)
return
}
auditingPrompt := ""
if globalConfig != nil {
auditingPrompt = globalConfig.HTTPAll.DomainAuditingPrompt
}
wg := sync.WaitGroup{}
var wg = sync.WaitGroup{}
wg.Add(len(params.ServerIds))
for _, serverId := range params.ServerIds {
@@ -98,6 +87,17 @@ func (this *StatusAction) RunPost(params struct {
if serverNamesResp.IsAuditing {
m["type"] = "auditing"
m["message"] = "审核中"
auditingPromptResp, err := this.RPC().ServerRPC().FindServerAuditingPrompt(this.AdminContext(), &pb.FindServerAuditingPromptRequest{ServerId: serverId})
if err != nil {
this.ErrorPage(err)
m["type"] = "serverErr"
m["message"] = "服务器错误"
m["todo"] = "错误信息FindServerNames(): " + err.Error() + ",请联系管理员修复此问题"
return
}
var auditingPrompt = auditingPromptResp.PromptText
if len(auditingPrompt) > 0 {
m["todo"] = auditingPrompt
} else {

View File

@@ -37,7 +37,6 @@ import (
// 服务相关
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/certs"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/components"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/components/cache"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/components/cache/batch"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/components/log"

File diff suppressed because one or more lines are too long

View File

@@ -9060,9 +9060,12 @@ Vue.component("http-firewall-actions-box", {
if (isNaN(timeout)) {
timeout = 0
}
if (this.recordIPListId <= 0) {
if (this.recordIPListId < 0) {
return
}
// recordIPListId can be 0
this.actionOptions = {
type: this.recordIPType,
level: this.recordIPLevel,
@@ -9410,7 +9413,7 @@ Vue.component("http-firewall-actions-box", {
</td>
</tr>
<tr v-if="actionCode == 'record_ip'">
<td>选择IP名单 *</td>
<td>选择IP名单</td>
<td>
<div v-if="recordIPListId > 0" class="ui label basic small">{{recordIPListName}} <a href="" @click.prevent="removeRecordIPList"><i class="icon remove small"></i></a></div>
<button type="button" class="ui button tiny" @click.prevent="selectRecordIPList">+</button>
@@ -10670,16 +10673,6 @@ Vue.component("http-cc-config-box", {
</tr>
</tbody>
<tbody v-show="moreOptionsVisible && config.isOn">
<tr>
<td>单IP最低QPS</td>
<td>
<div class="ui input right labeled">
<input type="text" name="minQPSPerIP" maxlength="6" style="width: 6em" v-model="minQPSPerIP"/>
<span class="ui label">请求数/秒</span>
</div>
<p class="comment">当某个IP在1分钟内平均QPS达到此值时才会触发CC防护如果设置为0表示任何访问都会触发。</p>
</td>
</tr>
<tr>
<td>例外URL</td>
<td>
@@ -10709,13 +10702,23 @@ Vue.component("http-cc-config-box", {
</td>
</tr>
<tr>
<td class="color-border">使用自定义阈值</td>
<td>单IP最低QPS</td>
<td>
<div class="ui input right labeled">
<input type="text" name="minQPSPerIP" maxlength="6" style="width: 6em" v-model="minQPSPerIP"/>
<span class="ui label">请求数/秒</span>
</div>
<p class="comment">当某个IP在1分钟内平均QPS达到此值时才会开始检测如果设置为0表示任何访问都会检测。注意这里设置的是检测开启阈值不是拦截阈值拦截阈值在当前表单下方可以设置</p>
</td>
</tr>
<tr>
<td class="color-border">使用自定义拦截阈值</td>
<td>
<checkbox v-model="useCustomThresholds"></checkbox>
</td>
</tr>
<tr v-show="!config.useDefaultThresholds">
<td class="color-border">自定义阈值设置</td>
<td class="color-border">自定义拦截阈值设置</td>
<td>
<div>
<div class="ui input left right labeled">
@@ -12666,7 +12669,7 @@ Vue.component("http-access-log-search-box", {
<a class="ui label basic" :class="{disabled: keyword.length == 0}" @click.prevent="cleanKeyword"><i class="icon remove small"></i></a>
</div>
</div>
<div class="ui field"><tip-icon content="一些特殊的关键词:<br/>单个状态码status:200<br/>状态码范围status:500-504<br/>查询IPip:192.168.1.100<br/>查询URLhttps://goedge.cn/docs<br/>查询路径部分requestPath:/hello/world<br/>查询协议版本proto:HTTP/1.1<br/>协议scheme:http<br/>请求方法method:POST"></tip-icon></div>
<div class="ui field"><tip-icon content="一些特殊的关键词:<br/>单个状态码status:200<br/>状态码范围status:500-504<br/>查询IPip:192.168.1.100<br/>查询URLhttps://goedge.cn/docs<br/>查询路径部分requestPath:/hello/world<br/>查询协议版本proto:HTTP/1.1<br/>协议scheme:http<br/>请求方法method:POST<br/>请求来源referer:example.com"></tip-icon></div>
</div>
<div class="ui fields inline" style="margin-top: 0.5em">
<div class="ui field">
@@ -16409,13 +16412,19 @@ Vue.component("menu-item", {
// 使用Icon的链接方式
Vue.component("link-icon", {
props: ["href", "title", "target"],
props: ["href", "title", "target", "size"],
data: function () {
let realSize = this.size
if (realSize == null || realSize.length == 0) {
realSize = "small"
}
return {
vTitle: (this.title == null) ? "打开链接" : this.title
vTitle: (this.title == null) ? "打开链接" : this.title,
realSize: realSize
}
},
template: `<span><slot></slot>&nbsp;<a :href="href" :title="vTitle" class="link grey" :target="target"><i class="icon linkify small"></i></a></span>`
template: `<span><slot></slot>&nbsp;<a :href="href" :title="vTitle" class="link grey" :target="target"><i class="icon linkify" :class="realSize"></i></a></span>`
})
// 带有下划虚线的连接
@@ -18122,7 +18131,9 @@ Vue.component("url-patterns-box", {
isAdding: false,
addingPattern: {"type": "wildcard", "pattern": ""},
editingIndex: -1
editingIndex: -1,
patternIsInvalid: false
}
},
methods: {
@@ -18183,6 +18194,27 @@ Vue.component("url-patterns-box", {
},
notifyChange: function () {
this.$emit("input", this.patterns)
},
changePattern: function () {
this.patternIsInvalid = false
let pattern = this.addingPattern.pattern
switch (this.addingPattern.type) {
case "wildcard":
if (pattern.indexOf("?") >= 0) {
this.patternIsInvalid = true
}
break
case "regexp":
if (pattern.indexOf("?") >= 0) {
let pieces = pattern.split("?")
for (let i = 0; i < pieces.length - 1; i++) {
if (pieces[i].length == 0 || pieces[i][pieces[i].length - 1] != "\\") {
this.patternIsInvalid = true
}
}
}
break
}
}
},
template: `<div>
@@ -18202,14 +18234,15 @@ Vue.component("url-patterns-box", {
</select>
</div>
<div class="ui field">
<input type="text" :placeholder="(addingPattern.type == 'wildcard') ? '可以使用星号(*)通配符,不区分大小写' : '可以使用正则表达式,不区分大小写'" v-model="addingPattern.pattern" size="36" ref="patternInput" @keyup.enter="confirm()" @keypress.enter.prevent="1" spellcheck="false"/>
<input type="text" :placeholder="(addingPattern.type == 'wildcard') ? '可以使用星号(*)通配符,不区分大小写' : '可以使用正则表达式,不区分大小写'" v-model="addingPattern.pattern" @input="changePattern" size="36" ref="patternInput" @keyup.enter="confirm()" @keypress.enter.prevent="1" spellcheck="false"/>
<p class="comment" v-if="patternIsInvalid"><span class="red" style="font-weight: normal"><span v-if="addingPattern.type == 'wildcard'">通配符</span><span v-if="addingPattern.type == 'regexp'">正则表达式</span>中不能包含问号(?)及问号以后的内容。</span></p>
</div>
<div class="ui field" style="padding-left: 0">
<tip-icon content="通配符示例:<br/>单个路径开头:/hello/world/*<br/>单个路径结尾:*/hello/world<br/>包含某个路径:*/article/*<br/>某个域名下的所有URL*example.com/*" v-if="addingPattern.type == 'wildcard'"></tip-icon>
<tip-icon content="正则表达式示例:<br/>单个路径开头:^/hello/world<br/>单个路径结尾:/hello/world$<br/>包含某个路径:/article/<br/>匹配某个数字路径:/article/(\\d+)<br/>某个域名下的所有URL^(http|https)://example.com/" v-if="addingPattern.type == 'regexp'"></tip-icon>
</div>
<div class="ui field">
<button class="ui button tiny" type="button" @click.prevent="confirm">确定</button><a href="" title="取消" @click.prevent="cancel"><i class="icon remove small"></i></a>
<button class="ui button tiny" :class="{disabled:this.patternIsInvalid}" type="button" @click.prevent="confirm">确定</button><a href="" title="取消" @click.prevent="cancel"><i class="icon remove small"></i></a>
</div>
</div>
</div>

View File

@@ -1,12 +1,18 @@
// 使用Icon的链接方式
Vue.component("link-icon", {
props: ["href", "title", "target"],
props: ["href", "title", "target", "size"],
data: function () {
let realSize = this.size
if (realSize == null || realSize.length == 0) {
realSize = "small"
}
return {
vTitle: (this.title == null) ? "打开链接" : this.title
vTitle: (this.title == null) ? "打开链接" : this.title,
realSize: realSize
}
},
template: `<span><slot></slot>&nbsp;<a :href="href" :title="vTitle" class="link grey" :target="target"><i class="icon linkify small"></i></a></span>`
template: `<span><slot></slot>&nbsp;<a :href="href" :title="vTitle" class="link grey" :target="target"><i class="icon linkify" :class="realSize"></i></a></span>`
})
// 带有下划虚线的连接

View File

@@ -10,7 +10,9 @@ Vue.component("url-patterns-box", {
isAdding: false,
addingPattern: {"type": "wildcard", "pattern": ""},
editingIndex: -1
editingIndex: -1,
patternIsInvalid: false
}
},
methods: {
@@ -71,6 +73,27 @@ Vue.component("url-patterns-box", {
},
notifyChange: function () {
this.$emit("input", this.patterns)
},
changePattern: function () {
this.patternIsInvalid = false
let pattern = this.addingPattern.pattern
switch (this.addingPattern.type) {
case "wildcard":
if (pattern.indexOf("?") >= 0) {
this.patternIsInvalid = true
}
break
case "regexp":
if (pattern.indexOf("?") >= 0) {
let pieces = pattern.split("?")
for (let i = 0; i < pieces.length - 1; i++) {
if (pieces[i].length == 0 || pieces[i][pieces[i].length - 1] != "\\") {
this.patternIsInvalid = true
}
}
}
break
}
}
},
template: `<div>
@@ -90,14 +113,15 @@ Vue.component("url-patterns-box", {
</select>
</div>
<div class="ui field">
<input type="text" :placeholder="(addingPattern.type == 'wildcard') ? '可以使用星号(*)通配符,不区分大小写' : '可以使用正则表达式,不区分大小写'" v-model="addingPattern.pattern" size="36" ref="patternInput" @keyup.enter="confirm()" @keypress.enter.prevent="1" spellcheck="false"/>
<input type="text" :placeholder="(addingPattern.type == 'wildcard') ? '可以使用星号(*)通配符,不区分大小写' : '可以使用正则表达式,不区分大小写'" v-model="addingPattern.pattern" @input="changePattern" size="36" ref="patternInput" @keyup.enter="confirm()" @keypress.enter.prevent="1" spellcheck="false"/>
<p class="comment" v-if="patternIsInvalid"><span class="red" style="font-weight: normal"><span v-if="addingPattern.type == 'wildcard'">通配符</span><span v-if="addingPattern.type == 'regexp'">正则表达式</span>中不能包含问号(?)及问号以后的内容。</span></p>
</div>
<div class="ui field" style="padding-left: 0">
<tip-icon content="通配符示例:<br/>单个路径开头:/hello/world/*<br/>单个路径结尾:*/hello/world<br/>包含某个路径:*/article/*<br/>某个域名下的所有URL*example.com/*" v-if="addingPattern.type == 'wildcard'"></tip-icon>
<tip-icon content="正则表达式示例:<br/>单个路径开头:^/hello/world<br/>单个路径结尾:/hello/world$<br/>包含某个路径:/article/<br/>匹配某个数字路径:/article/(\\d+)<br/>某个域名下的所有URL^(http|https)://example.com/" v-if="addingPattern.type == 'regexp'"></tip-icon>
</div>
<div class="ui field">
<button class="ui button tiny" type="button" @click.prevent="confirm">确定</button><a href="" title="取消" @click.prevent="cancel"><i class="icon remove small"></i></a>
<button class="ui button tiny" :class="{disabled:this.patternIsInvalid}" type="button" @click.prevent="confirm">确定</button><a href="" title="取消" @click.prevent="cancel"><i class="icon remove small"></i></a>
</div>
</div>
</div>

View File

@@ -82,7 +82,7 @@ Vue.component("http-access-log-search-box", {
<a class="ui label basic" :class="{disabled: keyword.length == 0}" @click.prevent="cleanKeyword"><i class="icon remove small"></i></a>
</div>
</div>
<div class="ui field"><tip-icon content="一些特殊的关键词:<br/>单个状态码status:200<br/>状态码范围status:500-504<br/>查询IPip:192.168.1.100<br/>查询URLhttps://goedge.cn/docs<br/>查询路径部分requestPath:/hello/world<br/>查询协议版本proto:HTTP/1.1<br/>协议scheme:http<br/>请求方法method:POST"></tip-icon></div>
<div class="ui field"><tip-icon content="一些特殊的关键词:<br/>单个状态码status:200<br/>状态码范围status:500-504<br/>查询IPip:192.168.1.100<br/>查询URLhttps://goedge.cn/docs<br/>查询路径部分requestPath:/hello/world<br/>查询协议版本proto:HTTP/1.1<br/>协议scheme:http<br/>请求方法method:POST<br/>请求来源referer:example.com"></tip-icon></div>
</div>
<div class="ui fields inline" style="margin-top: 0.5em">
<div class="ui field">

View File

@@ -499,9 +499,12 @@ Vue.component("http-firewall-actions-box", {
if (isNaN(timeout)) {
timeout = 0
}
if (this.recordIPListId <= 0) {
if (this.recordIPListId < 0) {
return
}
// recordIPListId can be 0
this.actionOptions = {
type: this.recordIPType,
level: this.recordIPLevel,
@@ -849,7 +852,7 @@ Vue.component("http-firewall-actions-box", {
</td>
</tr>
<tr v-if="actionCode == 'record_ip'">
<td>选择IP名单 *</td>
<td>选择IP名单</td>
<td>
<div v-if="recordIPListId > 0" class="ui label basic small">{{recordIPListName}} <a href="" @click.prevent="removeRecordIPList"><i class="icon remove small"></i></a></div>
<button type="button" class="ui button tiny" @click.prevent="selectRecordIPList">+</button>

View File

@@ -71,7 +71,7 @@
<input type="text" name="ttl" maxlength="6" style="width: 6em" v-model="ttl"/>
<span class="ui label"></span>
</div>
<p class="comment">每个DNS服务商或者账号的TTL限制各有不同请注意取值范围。0表示使用默认。</p>
<p class="comment">每个DNS服务商或者账号的TTL限制各有不同请注意取值范围;修改后,只对新的解析记录生效。0表示使用默认。</p>
</td>
</tr>
<tr>

View File

@@ -3,216 +3,286 @@
{$template "/left_menu_with_menu"}
<div class="right-box with-menu">
<form class="ui form" data-tea-action="$" data-tea-success="success">
<second-menu>
<a v-for="item in titleMenus" :class="{active: item.id == currentItem}" :href="'#' + item.id" class="item" @click.prevent="selectItem(item)">{{item.name}}</a>
</second-menu>
<form class="ui form" data-tea-action="$" data-tea-success="success" id="global-config-form">
<csrf-token></csrf-token>
<input type="hidden" name="clusterId" :value="clusterId"/>
<h4>域名</h4>
<div v-show="currentItem == 'title-server-name-bind'">
<h4 id="title-server-name-bind">域名绑定</h4>
<table class="ui table definition selectable">
<tr>
<td class="title color-border">禁止未绑定域名访问</td>
<td>
<checkbox name="httpAllMatchDomainStrictly" v-model="config.httpAll.matchDomainStrictly"></checkbox>
<p class="comment">选中后表示禁止未在网站绑定的域名和IP访问。</p>
</td>
</tr>
<table class="ui table definition selectable">
<tr>
<td class="title color-border">禁止未绑定域名访问</td>
<td>
<checkbox name="httpAllMatchDomainStrictly" v-model="config.httpAll.matchDomainStrictly"></checkbox>
<p class="comment">选中后表示禁止未在网站绑定的域名和IP访问。</p>
</td>
</tr>
<tr>
<td class="color-border">处理未绑定域名方式</td>
<td>
<radio name="httpAllDomainMismatchActionCode" :v-value="'page'" v-model="httpAllDomainMismatchActionCode">显示提示页面</radio> &nbsp;
&nbsp; <radio name="httpAllDomainMismatchActionCode" :v-value="'close'" v-model="httpAllDomainMismatchActionCode">关闭连接</radio>
<p class="comment" v-if="httpAllDomainMismatchActionCode == 'page'">显示提示内容。</p>
<p class="comment" v-if="httpAllDomainMismatchActionCode == 'close'">直接关闭网络连接,不提示任何内容。</p>
</td>
</tr>
<tr>
<td class="color-border">处理未绑定域名方式</td>
<td>
<radio name="httpAllDomainMismatchActionCode" :v-value="'page'" v-model="httpAllDomainMismatchActionCode">显示提示页面</radio> &nbsp;
&nbsp; <radio name="httpAllDomainMismatchActionCode" :v-value="'close'" v-model="httpAllDomainMismatchActionCode">关闭连接</radio>
<p class="comment" v-if="httpAllDomainMismatchActionCode == 'page'">显示提示内容。</p>
<p class="comment" v-if="httpAllDomainMismatchActionCode == 'close'">直接关闭网络连接,不提示任何内容。</p>
</td>
</tr>
<tr v-show="config.httpAll.matchDomainStrictly && httpAllDomainMismatchActionCode == 'page'">
<td class="color-border">提示页面状态码</td>
<td>
<input type="text" name="httpAllDomainMismatchActionStatusCode" v-model="httpAllDomainMismatchActionStatusCode" style="width: 4em" maxlength="3"/>
<p class="comment">访问未绑定域名时的提示页面状态码默认404。</p>
</td>
</tr>
<tr v-show="config.httpAll.matchDomainStrictly && httpAllDomainMismatchActionCode == 'page'">
<td class="color-border">提示页面内容</td>
<td>
<textarea rows="3" name="httpAllDomainMismatchActionContentHTML" v-model="httpAllDomainMismatchActionContentHTML"></textarea>
<p class="comment">访问未绑定的域名时提示页面内容可以使用HTML仅限于HTTP请求不适于用HTTPSHTTPS会提示证书错误</p>
</td>
</tr>
<tr v-show="config.httpAll.matchDomainStrictly && httpAllDomainMismatchActionCode == 'page'">
<td class="color-border">提示页面状态码</td>
<td>
<input type="text" name="httpAllDomainMismatchActionStatusCode" v-model="httpAllDomainMismatchActionStatusCode" style="width: 4em" maxlength="3"/>
<p class="comment">访问未绑定域名时的提示页面状态码默认404。</p>
</td>
</tr>
<tr v-show="config.httpAll.matchDomainStrictly && httpAllDomainMismatchActionCode == 'page'">
<td class="color-border">提示页面内容</td>
<td>
<textarea rows="3" name="httpAllDomainMismatchActionContentHTML" v-model="httpAllDomainMismatchActionContentHTML"></textarea>
<p class="comment">访问未绑定的域名时提示页面内容可以使用HTML仅限于HTTP请求不适于用HTTPSHTTPS会提示证书错误</p>
</td>
</tr>
<tr v-show="config.httpAll.matchDomainStrictly">
<td class="color-border">允许例外的域名</td>
<td>
<domains-box name="httpAllAllowMismatchDomainsJSON" :v-domains="config.httpAll.allowMismatchDomains"></domains-box>
<p class="comment">允许这些域名不经过绑定就可以直接访问网站。</p>
</td>
</tr>
<tr v-show="config.httpAll.matchDomainStrictly">
<td class="color-border">默认域名</td>
<td>
<input type="text" name="httpAllDefaultDomain" v-model="config.httpAll.defaultDomain"/>
<p class="comment">例外域名或使用节点IP访问时使用的默认域名如果指定的域名在集群里已经绑定到某个网站则相当于直接访问该网站。</p>
</td>
</tr>
<tr v-show="config.httpAll.matchDomainStrictly">
<td class="color-border">允许例外的域名</td>
<td>
<domains-box name="httpAllAllowMismatchDomainsJSON" :v-domains="config.httpAll.allowMismatchDomains"></domains-box>
<p class="comment">允许这些域名不经过绑定就可以直接访问网站。</p>
</td>
</tr>
<tr v-show="config.httpAll.matchDomainStrictly">
<td class="color-border">默认域名</td>
<td>
<input type="text" name="httpAllDefaultDomain" v-model="config.httpAll.defaultDomain"/>
<p class="comment">例外域名或使用节点IP访问时使用的默认域名如果指定的域名在集群里已经绑定到某个网站则相当于直接访问该网站。</p>
</td>
</tr>
<tr v-show="config.httpAll.matchDomainStrictly">
<td class="color-border">允许使用节点IP访问</td>
<td>
<checkbox name="httpAllAllowNodeIP" v-model="config.httpAll.allowNodeIP"></checkbox>
<p class="comment">选中后表示允许直接使用节点IP访问网站。</p>
</td>
</tr>
<tr v-show="config.httpAll.matchDomainStrictly">
<td class="color-border">访问节点IP显示自定义内容</td>
<td>
<checkbox name="httpAllNodeIPShowPage" v-model="config.httpAll.nodeIPShowPage"></checkbox>
<p class="comment">选中后表示用户访问节点IP时显示自定义内容。</p>
</td>
</tr>
<tr v-show="config.httpAll.matchDomainStrictly && config.httpAll.nodeIPShowPage">
<td class="color-border">访问节点IP自定义内容</td>
<td>
<textarea name="httpAllNodeIPPageHTML" v-model="config.httpAll.nodeIPPageHTML" rows="3" placeholder="访问节点IP时要显示的自定义内容"></textarea>
<p class="comment">访问节点IP时要显示的自定义内容支持HTML。</p>
</td>
</tr>
</table>
<tr v-show="config.httpAll.matchDomainStrictly">
<td class="color-border">允许使用节点IP访问</td>
<td>
<checkbox name="httpAllAllowNodeIP" v-model="config.httpAll.allowNodeIP"></checkbox>
<p class="comment">选中后表示允许直接使用节点IP访问网站。</p>
</td>
</tr>
<tr v-show="config.httpAll.matchDomainStrictly">
<td class="color-border">访问节点IP显示自定义内容</td>
<td>
<checkbox name="httpAllNodeIPShowPage" v-model="config.httpAll.nodeIPShowPage"></checkbox>
<p class="comment">选中后表示用户访问节点IP时显示自定义内容。</p>
</td>
</tr>
<tr v-show="config.httpAll.matchDomainStrictly && config.httpAll.nodeIPShowPage">
<td class="color-border">访问节点IP自定义内容</td>
<td>
<textarea name="httpAllNodeIPPageHTML" v-model="config.httpAll.nodeIPPageHTML" rows="3" placeholder="访问节点IP时要显示的自定义内容"></textarea>
<p class="comment">访问节点IP时要显示的自定义内容支持HTML。</p>
</td>
</tr>
</table>
</div>
<h4>访问日志</h4>
<table class="ui table definition selectable">
<tr>
<td>允许记录访问日志</td>
<td>
<checkbox name="httpAccessLogIsOn" v-model="config.httpAccessLog.isOn"></checkbox>
<p class="comment">选中后,表示允许当前集群下的网站记录访问日志,否则当前集群下的所有网站都不会记录访问日志。</p>
</td>
</tr>
<tr>
<td>记录请求报头</td>
<td>
<checkbox name="httpAccessLogEnableRequestHeaders" v-model="config.httpAccessLog.enableRequestHeaders"></checkbox>
<p class="comment">选中后,表示在访问日志中记录请求报头。</p>
</td>
</tr>
<tr>
<td class="title">只记录通用请求报头</td>
<td>
<checkbox name="httpAccessLogCommonRequestHeadersOnly" v-model="config.httpAccessLog.commonRequestHeadersOnly"></checkbox>
<p class="comment">选中后表示访问日志中只记录通用的HTTP请求报头比如<code-label>User-Agent</code-label>),其他自定义或非标准的(比如<code-label>Test-Header</code-label>)将不记录。</p>
</td>
</tr>
<tr>
<td>记录响应报头</td>
<td>
<checkbox name="httpAccessLogEnableResponseHeaders" v-model="config.httpAccessLog.enableResponseHeaders"></checkbox>
<p class="comment">选中后,表示在访问日志中记录响应报头。</p>
</td>
</tr>
<tr>
<td>记录Cookie</td>
<td>
<checkbox name="httpAccessLogEnableCookies" v-model="config.httpAccessLog.enableCookies"></checkbox>
<p class="comment">选中后表示访问日志中记录Cookie内容。</p>
</td>
</tr>
<tr>
<td>记录找不到网站日志</td>
<td>
<checkbox name="httpAccessLogEnableServerNotFound" v-model="config.httpAccessLog.enableServerNotFound"></checkbox>
<p class="comment">选中后,表示如果访客访问的域名对应的网站不存在也会记录日志。</p>
</td>
</tr>
</table>
<div v-show="teaIsPlus && currentItem == 'title-server-name-auditing'">
<h4 id="title-server-name-auditing" v-if="teaIsPlus">域名审核</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">域名变更时需要审核</td>
<td>
<checkbox name="httpAllDomainAuditingIsOn" v-model="config.httpAll.domainAuditingIsOn"></checkbox>
<p class="comment">选中后,用户在创建和修改域名时需要管理员审核通过才能生效。</p>
</td>
</tr>
<tr v-show="config.httpAll.domainAuditingIsOn">
<td>审核提示</td>
<td>
<input name="httpAllDomainAuditingPrompt" type="text" v-model="config.httpAll.domainAuditingPrompt" maxlength="200"/>
<p class="comment">提示用户需要审核的文字说明。</p>
</td>
</tr>
</table>
</div>
<h4>运行日志</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">记录服务错误</td>
<td>
<checkbox name="logRecordServerError" v-model="config.log.recordServerError"></checkbox>
<p class="comment">在节点运行日志中记录网站相关错误,比如无法连接源站等。</p>
</td>
</tr>
</table>
<div v-show="currentItem == 'title-access-log'">
<h4 id="title-access-log">访问日志</h4>
<table class="ui table definition selectable">
<tr>
<td>允许记录访问日志</td>
<td>
<checkbox name="httpAccessLogIsOn" v-model="config.httpAccessLog.isOn"></checkbox>
<p class="comment">选中后,表示允许当前集群下的网站记录访问日志,否则当前集群下的所有网站都不会记录访问日志。</p>
</td>
</tr>
<tr>
<td>记录请求报头</td>
<td>
<checkbox name="httpAccessLogEnableRequestHeaders" v-model="config.httpAccessLog.enableRequestHeaders"></checkbox>
<p class="comment">选中后,表示在访问日志中记录请求报头。</p>
</td>
</tr>
<tr>
<td class="title">只记录通用请求报头</td>
<td>
<checkbox name="httpAccessLogCommonRequestHeadersOnly" v-model="config.httpAccessLog.commonRequestHeadersOnly"></checkbox>
<p class="comment">选中后表示访问日志中只记录通用的HTTP请求报头比如<code-label>User-Agent</code-label>),其他自定义或非标准的(比如<code-label>Test-Header</code-label>)将不记录。</p>
</td>
</tr>
<tr>
<td>记录响应报头</td>
<td>
<checkbox name="httpAccessLogEnableResponseHeaders" v-model="config.httpAccessLog.enableResponseHeaders"></checkbox>
<p class="comment">选中后,表示在访问日志中记录响应报头。</p>
</td>
</tr>
<tr>
<td>记录Cookie</td>
<td>
<checkbox name="httpAccessLogEnableCookies" v-model="config.httpAccessLog.enableCookies"></checkbox>
<p class="comment">选中后表示访问日志中记录Cookie内容。</p>
</td>
</tr>
<tr>
<td>记录找不到网站日志</td>
<td>
<checkbox name="httpAccessLogEnableServerNotFound" v-model="config.httpAccessLog.enableServerNotFound"></checkbox>
<p class="comment">选中后,表示如果访客访问的域名对应的网站不存在也会记录日志。</p>
</td>
</tr>
</table>
</div>
<h4>性能</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">自动读数据超时</td>
<td>
<checkbox name="performanceAutoReadTimeout" v-model="config.performance.autoReadTimeout"></checkbox>
<p class="comment">从客户端读取数据时自动设置超时时间,如果超时,则自动视为慢连接,并关闭网络连接;此为专业选项,请在专家指导下进行修改。</p>
</td>
</tr>
<tr>
<td class="title">自动写数据超时</td>
<td>
<checkbox name="performanceAutoWriteTimeout" v-model="config.performance.autoWriteTimeout"></checkbox>
<p class="comment">向客户端发送数据时自动设置超时时间,如果超时,则自动视为慢连接,并关闭网络连接;此为专业选项,请在专家指导下进行修改。</p>
</td>
</tr>
<tr>
<td>调试模式</td>
<td>
<checkbox name="performanceDebug" v-model="config.performance.debug"></checkbox>
<p class="comment">开启调试模式后,将在某些信息中包含调试信息。</p>
</td>
</tr>
</table>
<div v-show="currentItem == 'title-server-log'">
<h4 id="title-server-log">运行日志</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">记录网站错误</td>
<td>
<checkbox name="logRecordServerError" v-model="config.log.recordServerError"></checkbox>
<p class="comment">在节点运行日志中记录网站相关错误详细信息,比如无法连接源站等,建议仅用于调试。</p>
</td>
</tr>
</table>
</div>
<h4>其他</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">服务器旗标</td>
<td>
<input type="text" name="httpAllServerName" v-model="config.httpAll.serverName" maxlength="60"/>
<p class="comment">服务器旗标会以<code-label>Server: 旗标</code-label>的形式出现在响应报头中。</p>
</td>
</tr>
<tr>
<td>支持低版本HTTP</td>
<td>
<checkbox name="httpAllSupportsLowVersionHTTP" v-model="config.httpAll.supportsLowVersionHTTP"></checkbox>
<p class="comment">选中后表示支持HTTP/1.0、HTTP/0.9等低于HTTP/1.1版本的HTTP协议。低版本HTTP协议不支持分段传输内容且无法保持连接对系统性能有严重的负面影响。建议只有在你的用户正在使用非常老旧的设备时才启用此选项。</p>
</td>
</tr>
<tr>
<td>自动匹配证书</td>
<td>
<checkbox name="httpAllMatchCertFromAllServers" v-model="config.httpAll.matchCertFromAllServers"></checkbox>
<p class="comment">选中后,表示找不到证书时自动查找其他网站设置的证书。此功能仅仅为了兼容以往系统版本,可能会导致用户访问的网站混乱,所以请不要轻易启用。</p>
</td>
</tr>
<tr>
<td>强制Ln请求</td>
<td>
<checkbox name="httpAllForceLnRequest" v-model="config.httpAll.forceLnRequest"></checkbox>
<p class="comment">选中后所有请求优先发送到L2或者更高级别节点。默认不开启的情况下只有可以缓存的内容请求才会发送到L2或者更高级别节点。</p>
</td>
</tr>
<tr>
<td>Ln请求负载均衡方法</td>
<td>
<select class="ui dropdown auto-width" name="httpAllLnRequestSchedulingMethod" v-model="config.httpAll.lnRequestSchedulingMethod">
<option value="urlMapping">URL映射</option>
<option value="random">随机</option>
</select>
<p class="comment" v-if="config.httpAll.lnRequestSchedulingMethod == 'urlMapping'">当存在多个Ln节点时将请求根据URL自动映射到某个固定的Ln节点。</p>
<p class="comment" v-if="config.httpAll.lnRequestSchedulingMethod == 'random'">当存在多个Ln节点时将请求随机发送到某个Ln节点。</p>
</td>
</tr>
<tr>
<td>支持${serverAddr}变量</td>
<td>
<checkbox name="httpAllEnableServerAddrVariable" v-model="config.httpAll.enableServerAddrVariable"></checkbox>
<p class="comment">支持在自定义页面中使用<code-label>${serverAddr}</code-label>变量,表示访客当前正在访问的服务器地址。</p>
</td>
</tr>
</table>
<div v-show="currentItem == 'title-performance'">
<h4 id="title-performance">性能</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">自动读数据超时</td>
<td>
<checkbox name="performanceAutoReadTimeout" v-model="config.performance.autoReadTimeout"></checkbox>
<p class="comment">从客户端读取数据时自动设置超时时间,如果超时,则自动视为慢连接,并关闭网络连接;此为专业选项,请在专家指导下进行修改。</p>
</td>
</tr>
<tr>
<td class="title">自动写数据超时</td>
<td>
<checkbox name="performanceAutoWriteTimeout" v-model="config.performance.autoWriteTimeout"></checkbox>
<p class="comment">向客户端发送数据时自动设置超时时间,如果超时,则自动视为慢连接,并关闭网络连接;此为专业选项,请在专家指导下进行修改。</p>
</td>
</tr>
<tr>
<td>调试模式</td>
<td>
<checkbox name="performanceDebug" v-model="config.performance.debug"></checkbox>
<p class="comment">开启调试模式后,将在某些信息中包含调试信息。</p>
</td>
</tr>
</table>
</div>
<div v-show="teaIsPlus && currentItem == 'title-ln'">
<h4 id="title-ln" v-if="teaIsPlus">Ln节点</h4>
<table class="ui table definition selectable">
<tr>
<td>强制Ln请求</td>
<td>
<checkbox name="httpAllForceLnRequest" v-model="config.httpAll.forceLnRequest"></checkbox>
<p class="comment">选中后所有请求优先发送到L2或者更高级别节点。默认不开启的情况下只有可以缓存的内容请求才会发送到L2或者更高级别节点。</p>
</td>
</tr>
<tr>
<td>Ln请求负载均衡方法</td>
<td>
<select class="ui dropdown auto-width" name="httpAllLnRequestSchedulingMethod" v-model="config.httpAll.lnRequestSchedulingMethod">
<option value="urlMapping">URL映射</option>
<option value="random">随机</option>
</select>
<p class="comment" v-if="config.httpAll.lnRequestSchedulingMethod == 'urlMapping'">当存在多个Ln节点时将请求根据URL自动映射到某个固定的Ln节点。</p>
<p class="comment" v-if="config.httpAll.lnRequestSchedulingMethod == 'random'">当存在多个Ln节点时将请求随机发送到某个Ln节点。</p>
</td>
</tr>
</table>
</div>
<div v-show="currentItem == 'title-tcp-udp'">
<h4 id="title-tcp-udp">TCP/UDP相关</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">允许的端口范围</td>
<td>
<div class="ui fields inline">
<div class="ui field">
<input type="text" name="tcpAllPortRangeMin" maxlength="5" size="6" v-model="tcpAllPortRangeMin"/>
</div>
<div class="ui field">
-
</div>
<div class="ui field">
<input type="text" name="tcpAllPortRangeMax" maxlength="5" size="6" v-model="tcpAllPortRangeMax"/>
</div>
</div>
<p class="comment">用户创建TCP/TLS负载均衡服务时可以随机选择的端口范围最小不能小于1024最大不能大于65534。</p>
</td>
</tr>
<tr>
<td>排除的端口</td>
<td>
<values-box placeholder="端口" size="6" name="tcpAllDenyPorts" :values="tcpAllDenyPorts"></values-box>
<p class="comment">当为用户随机分配端口时要排除的端口。</p>
</td>
</tr>
</table>
</div>
<div v-show="currentItem == 'title-others'">
<h4 id="title-others">其他</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">服务器旗标</td>
<td>
<input type="text" name="httpAllServerName" v-model="config.httpAll.serverName" maxlength="60"/>
<p class="comment">服务器旗标会以<code-label>Server: 旗标</code-label>的形式出现在响应报头中。</p>
</td>
</tr>
<tr>
<td>支持低版本HTTP</td>
<td>
<checkbox name="httpAllSupportsLowVersionHTTP" v-model="config.httpAll.supportsLowVersionHTTP"></checkbox>
<p class="comment">选中后表示支持HTTP/1.0、HTTP/0.9等低于HTTP/1.1版本的HTTP协议。低版本HTTP协议不支持分段传输内容且无法保持连接对系统性能有严重的负面影响。建议只有在你的用户正在使用非常老旧的设备时才启用此选项。</p>
</td>
</tr>
<tr>
<td>自动匹配证书</td>
<td>
<checkbox name="httpAllMatchCertFromAllServers" v-model="config.httpAll.matchCertFromAllServers"></checkbox>
<p class="comment">选中后,表示找不到证书时自动查找其他网站设置的证书。此功能仅仅为了兼容以往系统版本,可能会导致用户访问的网站混乱,所以请不要轻易启用。</p>
</td>
</tr>
<tr>
<td>支持${serverAddr}变量</td>
<td>
<checkbox name="httpAllEnableServerAddrVariable" v-model="config.httpAll.enableServerAddrVariable"></checkbox>
<p class="comment">支持在自定义页面中使用<code-label>${serverAddr}</code-label>变量,表示访客当前正在访问的服务器地址。</p>
</td>
</tr>
</table>
</div>
<div class="margin"></div>
<submit-btn></submit-btn>
</form>
</div>

View File

@@ -1,3 +1,43 @@
Tea.context(function () {
this.success = NotifyReloadSuccess("保存成功")
// Menu
this.currentItem = ""
this.titleMenus = []
this.$delay(function () {
let elements = document.querySelectorAll("#global-config-form h4")
this.currentItem = elements[0].getAttribute("id")
for (let i = 0; i < elements.length; i++) {
let textContent = elements[i].textContent
if (textContent == null || textContent.length == 0) {
textContent = elements[i].innerText
}
this.titleMenus.push({
name: textContent,
id: elements[i].getAttribute("id")
})
}
})
this.selectItem = function (item) {
this.currentItem = item.id
}
/**
* TCP端口
*/
this.tcpAllPortRangeMin = 10000
this.tcpAllPortRangeMax = 40000
if (this.config.tcpAll.portRangeMin > 0) {
this.tcpAllPortRangeMin = this.config.tcpAll.portRangeMin
}
if (this.config.tcpAll.portRangeMax > 0) {
this.tcpAllPortRangeMax = this.config.tcpAll.portRangeMax
}
this.tcpAllDenyPorts = []
if (this.config.tcpAll.denyPorts != null) {
this.tcpAllDenyPorts = this.config.tcpAll.denyPorts
}
})

View File

@@ -109,7 +109,14 @@
<td>自动安装nftables</td>
<td>
<checkbox name="autoInstallNftables" v-model="cluster.autoInstallNftables"></checkbox>
<p class="comment">在Linux系统中自动尝试安装<code-label>nftables</code-label>用于安全防御此功能需要联网从Ubuntu、CentOS等软件库中下载安装包。</p>
<p class="comment">选中后,表示在Linux系统中自动尝试安装<code-label>nftables</code-label>用于安全防御此功能需要联网从Ubuntu、CentOS等软件库中下载安装包。</p>
</td>
</tr>
<tr>
<td>自动调节系统参数</td>
<td>
<checkbox name="autoSystemTuning" v-model="cluster.autoSystemTuning"></checkbox>
<p class="comment">选中后表示自动调整Linux内核等参数以便提升性能启用后以往安装的边缘节点需要重启进程后才能生效。</p>
</td>
</tr>
</tbody>

View File

@@ -60,7 +60,14 @@
<td>自动安装nftables</td>
<td>
<checkbox name="autoInstallNftables" value="1"></checkbox>
<p class="comment">在Linux系统中自动尝试安装<code-label>nftables</code-label>用于安全防御此功能需要联网从Ubuntu、CentOS等软件库中下载安装包。</p>
<p class="comment">选中后,表示在Linux系统中自动尝试安装<code-label>nftables</code-label>用于安全防御此功能需要联网从Ubuntu、CentOS等软件库中下载安装包。</p>
</td>
</tr>
<tr>
<td>自动调节系统参数</td>
<td>
<checkbox name="autoSystemTuning" value="1"></checkbox>
<p class="comment">选中后表示自动调整Linux内核等参数以便提升性能。</p>
</td>
</tr>
</table>

View File

@@ -76,7 +76,7 @@
<input type="text" name="ttl" maxlength="6" style="width: 6em" v-model="ttl"/>
<span class="ui label"></span>
</div>
<p class="comment">每个DNS服务商或者账号的TTL限制各有不同请注意取值范围。0表示使用默认。</p>
<p class="comment">每个DNS服务商或者账号的TTL限制各有不同请注意取值范围;修改后,只对新的解析记录生效。0表示使用默认。</p>
</td>
</tr>
<tr>

View File

@@ -5,14 +5,15 @@
{$template "menu"}
<second-menu>
<menu-item :href="'/servers/certs/acme?keyword=' + keyword + '&userId=' + searchingUserId" :active="type == ''">所有任务({{countAll}})</menu-item>
<menu-item :href="'/servers/certs/acme?type=available&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == 'available'">有效证书({{countAvailable}})</menu-item>
<menu-item :href="'/servers/certs/acme?type=expired&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == 'expired'">过期证书<span :class="{red: countExpired > 0}">({{countExpired}})</span></menu-item>
<menu-item :href="'/servers/certs/acme?type=7days&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == '7days'">7天内过期<span :class="{red: count7Days > 0}">({{count7Days}})</span></menu-item>
<menu-item :href="'/servers/certs/acme?type=30days&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == '30days'">30天内过期({{count30Days}})</menu-item>
<menu-item :href="'/servers/certs/acme?userType=' + userType + '&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == ''">所有任务({{countAll}})</menu-item>
<menu-item :href="'/servers/certs/acme?userType=' + userType + '&type=available&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == 'available'">有效证书({{countAvailable}})</menu-item>
<menu-item :href="'/servers/certs/acme?userType=' + userType + '&type=expired&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == 'expired'">过期证书<span :class="{red: countExpired > 0}">({{countExpired}})</span></menu-item>
<menu-item :href="'/servers/certs/acme?userType=' + userType + '&type=7days&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == '7days'">7天内过期<span :class="{red: count7Days > 0}">({{count7Days}})</span></menu-item>
<menu-item :href="'/servers/certs/acme?userType=' + userType + '&type=30days&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == '30days'">30天内过期({{count30Days}})</menu-item>
</second-menu>
<form class="ui form">
<input type="hidden" name="userType" :value="userType"/>
<div class="ui fields inline">
<div class="ui field">
<input type="text" name="keyword" placeholder="域名等关键词" style="width:12em" v-model="keyword"/>
@@ -24,11 +25,19 @@
<div class="ui field">
<button type="submit" class="ui button">搜索</button>
<a :href="Tea.url('.')" v-if="keyword.length > 0 || searchingUserId > 0">[清除条件]</a>
<a :href="Tea.url('.', {userType:userType})" v-if="keyword.length > 0 || searchingUserId > 0">[清除条件]</a>
</div>
</div>
</form>
<div v-if="searchingUserId == 0">
<div class="ui menu text basic tiny blue" style="margin-bottom:0">
<a :href="'/servers/certs/acme?userType=&type=' + type + '&keyword=' + keyword + '&userId=' + searchingUserId" class="item" :class="{active: userType == ''}">管理员证书</a>
<a :href="'/servers/certs/acme?userType=user&type=' + type + '&keyword=' + keyword + '&userId=' + searchingUserId" class="item" :class="{active: userType == 'user'}">用户证书</a>
</div>
<div class="ui divider" style="margin-top:0"></div>
</div>
<p class="comment" v-if="tasks.length == 0"><span v-if="searchingUserId > 0">当前用户下</span>暂时还没有证书申请任务。</p>
<div class="ui message blue" v-if="isRunning">有任务在执行中,可能需要的时间较长,请耐心等待。</div>
@@ -84,7 +93,7 @@
<span class="disabled" v-else="">-</span>
</td>
<td>
<span v-if="user != null && user.id > 0"><user-link :v-user="user"></user-link></span>
<span v-if="task.user != null && task.user.id > 0"><user-link :v-user="task.user"></user-link></span>
<span v-else class="disabled">管理员</span>
</td>
<td>

View File

@@ -3,12 +3,12 @@
<div class="right-box without-tabbar">
<second-menu>
<menu-item :href="'/servers/certs?keyword=' + keyword + '&userId=' + searchingUserId" :active="type == ''">所有证书({{countAll}})</menu-item>
<menu-item :href="'/servers/certs?type=ca&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == 'ca'">CA证书({{countCA}})</menu-item>
<menu-item :href="'/servers/certs?type=available&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == 'available'">有效证书({{countAvailable}})</menu-item>
<menu-item :href="'/servers/certs?type=expired&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == 'expired'">过期证书<span :class="{red: countExpired > 0}">({{countExpired}})</span></menu-item>
<menu-item :href="'/servers/certs?type=7days&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == '7days'">7天内过期<span :class="{red: count7Days > 0}">({{count7Days}})</span></menu-item>
<menu-item :href="'/servers/certs?type=30days&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == '30days'">30天内过期({{count30Days}})</menu-item>
<menu-item :href="'/servers/certs?userType=' + userType + '&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == ''">所有证书({{countAll}})</menu-item>
<menu-item :href="'/servers/certs?userType=' + userType + '&type=ca&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == 'ca'">CA证书({{countCA}})</menu-item>
<menu-item :href="'/servers/certs?userType=' + userType + '&type=available&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == 'available'">有效证书({{countAvailable}})</menu-item>
<menu-item :href="'/servers/certs?userType=' + userType + '&type=expired&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == 'expired'">过期证书<span :class="{red: countExpired > 0}">({{countExpired}})</span></menu-item>
<menu-item :href="'/servers/certs?userType=' + userType + '&type=7days&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == '7days'">7天内过期<span :class="{red: count7Days > 0}">({{count7Days}})</span></menu-item>
<menu-item :href="'/servers/certs?userType=' + userType + '&type=30days&keyword=' + keyword + '&userId=' + searchingUserId" :active="type == '30days'">30天内过期({{count30Days}})</menu-item>
<span class="item disabled">|</span>
<a href="" class="item" @click.prevent="uploadCert">[上传证书]</a>
<a href="" class="item" @click.prevent="uploadBatch">[批量上传]</a>
@@ -16,6 +16,7 @@
<form class="ui form">
<input type="hidden" name="type" :value="type"/>
<input type="hidden" name="userType" :value="userType"/>
<div class="ui fields inline">
<div class="ui field">
<input type="text" name="keyword" placeholder="域名、说明等关键词" style="width:12em" v-model="keyword"/>
@@ -26,17 +27,23 @@
<div class="ui field">
<button type="submit" class="ui button">搜索</button>
&nbsp;
<a :href="Tea.url('.', { 'type':type })" v-if="keyword.length > 0 || searchingUserId > 0">[清除条件]</a>
<a :href="Tea.url('.', { 'userType':userType, 'type':type })" v-if="keyword.length > 0 || searchingUserId > 0">[清除条件]</a>
</div>
</div>
</form>
<div v-if="searchingUserId == 0">
<div class="ui divider"></div>
<p class="comment" style="padding-top: 0">默认只显示管理员所属的证书,用户所属证书请在上面表单中选择对应用户后搜索。</p>
<div class="ui menu text basic tiny blue" style="margin-bottom:0">
<a :href="'/servers/certs?userType=&type=' + type + '&keyword=' + keyword + '&userId=' + searchingUserId" class="item" :class="{active: userType == ''}">管理员证书</a>
<a :href="'/servers/certs?userType=user&type=' + type + '&keyword=' + keyword + '&userId=' + searchingUserId" class="item" :class="{active: userType == 'user'}">用户证书</a>
</div>
<div class="ui divider" style="margin-top:0"></div>
</div>
<p class="comment" v-if="certs.length == 0"><span v-if="searchingUserId > 0">当前用户下</span>暂时还没有相关的证书。</p>
<table class="ui table selectable celled" v-if="certs.length > 0">
<thead>
<tr>
@@ -72,7 +79,7 @@
<td>{{certInfos[index].endDay}}</td>
<td class="center">{{certInfos[index].countServers}}</td>
<td>
<span v-if="user != null && user.id > 0"><user-link :v-user="user"></user-link></span>
<span v-if="certInfos[index].user != null && certInfos[index].user.id > 0"><user-link :v-user="certInfos[index].user"></user-link></span>
<span v-else class="disabled">管理员</span>
</td>
<td nowrap="" class="center">

View File

@@ -29,7 +29,7 @@
<td class="color-border">缓存磁盘最大用量 *</td>
<td>
<size-capacity-box :v-name="'capacityJSON'" :v-count="128" :v-unit="'gb'" :key="'capacityJSON1'"></size-capacity-box>
<p class="comment">缓存所在磁盘的最大用量超出此用量或磁盘接近用尽时将会自动尝试清理旧数据如果为0表示没有限制。</p>
<p class="comment">单个节点上缓存所在磁盘的最大用量超出此用量或磁盘接近用尽时将会自动尝试清理旧数据如果为0表示没有限制。</p>
</td>
</tr>
<tr>

View File

@@ -30,7 +30,7 @@
<td class="color-border">缓存磁盘最大用量</td>
<td>
<size-capacity-view :v-value="cachePolicy.capacity" :v-default-text="'不限'"></size-capacity-view>
<p class="comment">单个节点上缓存目录所在磁盘允许的最大如果为0表示没有限制。</p>
<p class="comment">单个节点上缓存所在磁盘的最大如果为0表示没有限制。</p>
</td>
</tr>
<tr v-if="cachePolicy.options.memoryPolicy != null && cachePolicy.options.memoryPolicy.capacity != null && cachePolicy.options.memoryPolicy.capacity.count > 0">

View File

@@ -31,7 +31,7 @@
<td class="color-border">缓存磁盘最大用量</td>
<td>
<size-capacity-box :v-name="'capacityJSON'" :v-value="cachePolicy.capacity" :v-count="0" :v-unit="'gb'"></size-capacity-box>
<p class="comment">缓存所在磁盘的最大用量超出此用量或磁盘接近用尽时将会自动尝试清理旧数据如果为0表示没有限制。</p>
<p class="comment">单个节点上缓存所在磁盘的最大用量超出此用量或磁盘接近用尽时将会自动尝试清理旧数据如果为0表示没有限制。</p>
</td>
</tr>
<tr v-if="cachePolicy.options.memoryPolicy != null && cachePolicy.options.memoryPolicy.capacity != null">

View File

@@ -1,63 +0,0 @@
{$layout}
<div class="ui tabular menu tiny">
<a href="" class="item" :class="{active: tab == 'domainAuditing'}" @click.prevent="selectTab('domainAuditing')">域名审核配置</a>
<a href="" class="item" :class="{active: tab == 'tcpPorts'}" @click.prevent="selectTab('tcpPorts')">TCP/TLS端口</a>
</div>
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="globalConfigJSON" :value="JSON.stringify(globalConfig)"/>
<!-- 域名审核相关配置 -->
<div v-show="tab == 'domainAuditing'">
<table class="ui table definition selectable">
<tr>
<td class="title">域名变更时是否需要审核</td>
<td>
<checkbox v-model="globalConfig.httpAll.domainAuditingIsOn"></checkbox>
<p class="comment">选中后,用户在修改域名时需要管理员审核通过才能生效。</p>
</td>
</tr>
<tr v-show="globalConfig.httpAll.domainAuditingIsOn">
<td>审核提示</td>
<td>
<input type="text" v-model="globalConfig.httpAll.domainAuditingPrompt" maxlength="200"/>
<p class="comment">提示用户需要审核的文字说明。</p>
</td>
</tr>
</table>
</div>
<!-- TCP 相关 -->
<div v-show="tab == 'tcpPorts'">
<table class="ui table definition selectable">
<tr>
<td class="title">允许的端口范围</td>
<td>
<div class="ui fields inline">
<div class="ui field">
<input type="text" name="tcpAllPortRangeMin" maxlength="5" size="6" v-model="tcpAllPortRangeMin"/>
</div>
<div class="ui field">
-
</div>
<div class="ui field">
<input type="text" name="tcpAllPortRangeMax" maxlength="5" size="6" v-model="tcpAllPortRangeMax"/>
</div>
</div>
<p class="comment">用户创建TCP/TLS负载均衡服务时可以随机选择的端口范围最小不能小于1024最大不能大于65534。</p>
</td>
</tr>
<tr>
<td>排除的端口</td>
<td>
<values-box placeholder="端口" size="6" name="tcpAllDenyPorts" :values="tcpAllDenyPorts"></values-box>
<p class="comment">当为用户随机分配端口时要排除的端口。</p>
</td>
</tr>
</table>
</div>
<div class="margin"></div>
<submit-btn v-show="tab != 'domainMatch'">保存</submit-btn>
</form>

View File

@@ -1,72 +0,0 @@
Tea.context(function () {
this.tab = "domainAuditing"
this.$delay(function () {
if (window.location.hash != null && window.location.hash.length > 1) {
this.selectTab(window.location.hash.substring(1))
}
})
this.selectTab = function (tab) {
this.tab = tab
window.location.hash = "#" + tab
}
this.success = function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
/**
* 域名不匹配动作
*/
this.domainMismatchAction = "page"
this.domainMismatchActionPageOptions = {
statusCode: 404,
contentHTML: `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>404 not found</title>
<style type="text/css">
* { font-family: Roboto, system-ui, sans-serif; }
h3, p { text-align: center; }
p { color: grey; }
</style>
</head>
<body>
<h3>Error: 404 Page Not Found</h3>
<h3>找不到您要访问的页面。</h3>
<p>原因:找不到当前访问域名对应的网站,请联系网站管理员。</p>
</body>
</html>`
}
if (this.globalConfig.httpAll.domainMismatchAction != null) {
this.domainMismatchAction = this.globalConfig.httpAll.domainMismatchAction.code
if (this.domainMismatchAction == "page") {
this.domainMismatchActionPageOptions = this.globalConfig.httpAll.domainMismatchAction.options;
}
}
/**
* TCP端口
*/
this.tcpAllPortRangeMin = 10000
this.tcpAllPortRangeMax = 40000
if (this.globalConfig.tcpAll.portRangeMin > 0) {
this.tcpAllPortRangeMin = this.globalConfig.tcpAll.portRangeMin
}
if (this.globalConfig.tcpAll.portRangeMax > 0) {
this.tcpAllPortRangeMax = this.globalConfig.tcpAll.portRangeMax
}
this.tcpAllDenyPorts = []
if (this.globalConfig.tcpAll.denyPorts != null) {
this.tcpAllDenyPorts = this.globalConfig.tcpAll.denyPorts
}
})

View File

@@ -20,6 +20,12 @@
<warning-message>当前集群已经设置不允许网站记录访问日志,可以在"集群设置" -- "网站设置"中修改此选项。</warning-message>
</div>
<!-- 网站设置提醒 -->
<div v-if="!serverAccessLogIsOn">
<div class="margin"></div>
<warning-message>当前网站尚未启用访问日志,可以在 <a :href="'/servers/server/settings/accessLog?serverId=' + serverId">[这里]</a> 修改。</warning-message>
</div>
<first-menu>
<menu-item :href="path + '?' + query()" :active="hasError == 0 && hasWAF == 0">所有日志</menu-item>
<menu-item :href="path + '?' + query('hasError=1')" :active="hasError > 0">错误日志</menu-item>

View File

@@ -18,6 +18,12 @@
<warning-message>当前集群已经设置不允许网站记录访问日志,可以在"集群设置" -- "网站设置"中修改此选项。</warning-message>
</div>
<!-- 网站设置提醒 -->
<div v-if="!serverAccessLogIsOn">
<div class="margin"></div>
<warning-message>当前网站尚未启用访问日志,可以在 <a :href="'/servers/server/settings/accessLog?serverId=' + serverId">[这里]</a> 修改。</warning-message>
</div>
<form method="get" class="ui form small" :action="path" autocomplete="off">
<input type="hidden" name="serverId" :value="serverId"/>
<http-access-log-search-box :v-ip="ip" :v-domain="domain" :v-keyword="keyword" :v-cluster-id="clusterId" :v-node-id="nodeId"></http-access-log-search-box>

View File

@@ -18,6 +18,12 @@
<warning-message>当前集群已经设置不允许网站记录访问日志,可以在"集群设置" -- "网站设置"中修改此选项。</warning-message>
</div>
<!-- 网站设置提醒 -->
<div v-if="!serverAccessLogIsOn">
<div class="margin"></div>
<warning-message>当前网站尚未启用访问日志,可以在 <a :href="'/servers/server/settings/accessLog?serverId=' + serverId">[这里]</a> 修改。</warning-message>
</div>
<first-menu>
<menu-item :href="path + '?' + query()" :active="hasError == 0 && hasWAF == 0">所有日志</menu-item>
<menu-item :href="path + '?' + query('hasError=1')" :active="hasError > 0">错误日志</menu-item>

View File

@@ -15,7 +15,7 @@
<td>目标URL *</td>
<td>
<input type="text" name="replace" placeholder="比如 /post/${1}.html" maxlength="500"/>
<p class="comment">URL中可以包含一些<a href="https://goedge.cn/docs/Server/Variables.md" target="_blank">内置的变量</a>也可以是一个完整的URL比如http://example.com/hello。</p>
<p class="comment">URL中可以包含一些<a href="https://goedge.cn/docs/Server/Variables.md" target="_blank">内置的变量</a>并且可以用<code-label>${1}</code-label><code-label>${2}</code-label>等变量形式表示在匹配规则中匹配的第N个括号里的内容也可以是一个完整的URL比如http://example.com/hello。</p>
</td>
</tr>
<tr>

View File

@@ -16,7 +16,7 @@
<td>目标URL *</td>
<td>
<input type="text" name="replace" placeholder="比如 /post/${1}.html" maxlength="500" v-model="rewriteRule.replace"/>
<p class="comment">URL中可以包含一些<a href="https://goedge.cn/docs/Server/Variables.md" target="_blank">内置的变量</a>也可以是一个完整的URL比如http://example.com/hello。</p>
<p class="comment">URL中可以包含一些<a href="https://goedge.cn/docs/Server/Variables.md" target="_blank">内置的变量</a>并且可以用<code-label>${1}</code-label><code-label>${2}</code-label>等变量形式表示在匹配规则中匹配的第N个括号里的内容也可以是一个完整的URL比如http://example.com/hello。</p>
</td>
</tr>
<tr>

View File

@@ -1,49 +0,0 @@
{$layout}
<first-menu>
<a href="" class="item" @click.prevent="createNode()">[添加节点]</a>
</first-menu>
<p class="comment" v-if="nodes.length == 0">暂时还没有节点。</p>
<table class="ui table selectable celled" v-if="nodes.length > 0">
<thead>
<tr>
<th>节点名称</th>
<th class="width6 center">版本号</th>
<th class="width5 center">CPU</th>
<th class="width5 center">内存</th>
<th class="center width10">状态</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="node in nodes">
<td><a :href="'/settings/monitorNodes/node?nodeId=' + node.id">{{node.name}}</a></td>
<td class="center">
<span v-if="node.status.buildVersion.length > 0">v{{node.status.buildVersion}}</span>
<span v-else class="disabled">-</span>
</td>
<td class="center">
<span v-if="node.status.isActive" :class="{red:node.status.cpuUsage > 0.80}">{{node.status.cpuUsageText}}</span>
<span v-else class="disabled">-</span>
</td>
<td class="center">
<span v-if="node.status.isActive" :class="{red:node.status.memUsage > 0.80}">{{node.status.memUsageText}}</span>
<span v-else class="disabled">-</span>
</td>
<td class="center">
<span v-if="!node.isOn"><label-on :v-is-on="node.isOn"></label-on></span>
<div v-else-if="node.status.isActive">
<span class="green">运行中</span>
</div>
<span v-else-if="node.status.updatedAt > 0" class="red">已断开</span>
<span v-else-if="node.status.updatedAt == 0" class="red">未连接</span>
</td>
<td>
<a :href="'/settings/monitorNodes/node?nodeId=' + node.id">详情</a> &nbsp;
<a href="" @click.prevent="deleteNode(node.id)">删除</a>
</td>
</tr>
</table>
<div class="page" v-html="page"></div>

View File

@@ -1,26 +0,0 @@
Tea.context(function () {
// 创建节点
this.createNode = function () {
teaweb.popup("/settings/monitorNodes/node/createPopup", {
width: "50em",
height: "30em",
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
// 删除节点
this.deleteNode = function (nodeId) {
let that = this
teaweb.confirm("确定要删除此节点吗?", function () {
that.$post("/settings/monitorNodes/delete")
.params({
nodeId: nodeId
})
.refresh()
})
}
})

View File

@@ -1,8 +0,0 @@
<first-menu>
<menu-item href="/settings/monitorNodes">节点列表</menu-item>
<span class="item">|</span>
<menu-item :href="'/settings/monitorNodes/node?nodeId=' + node.id" code="index">"{{node.name}}"详情</menu-item>
<menu-item :href="'/settings/monitorNodes/node/logs?nodeId=' + node.id" code="log">运行日志</menu-item>
<menu-item :href="'/settings/monitorNodes/node/install?nodeId=' + node.id" code="install">安装节点</menu-item>
<menu-item :href="'/settings/monitorNodes/node/update?nodeId=' + node.id" code="update">修改节点</menu-item>
</first-menu>

View File

@@ -1,34 +0,0 @@
{$layout "layout_popup"}
<h3>添加监控节点</h3>
<form class="ui form" method="post" data-tea-action="$" data-tea-success="success">
<table class="ui table selectable definition">
<tr>
<td class="title">节点名称 *</td>
<td>
<input type="text" name="name" maxlength="100" ref="focus"/>
</td>
</tr>
<tr>
<td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr>
<tbody v-show="moreOptionsVisible">
<tr>
<td>描述</td>
<td>
<textarea name="description" maxlength="200" rows="3"></textarea>
</td>
</tr>
<tr>
<td>是否启用</td>
<td>
<div class="ui checkbox">
<input type="checkbox" name="isOn" value="1" checked="checked"/>
<label></label>
</div>
</td>
</tr>
</tbody>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -1,3 +0,0 @@
Tea.context(function () {
})

View File

@@ -1,24 +0,0 @@
{$layout}
{$template "menu"}
<table class="ui table selectable definition">
<tr>
<td class="title">节点名称</td>
<td>
{{node.name}}
</td>
</tr>
<tr>
<td>状态</td>
<td>
<label-on :v-is-on="node.isOn"></label-on>
</td>
</tr>
<tr>
<td>描述</td>
<td>
<span v-if="node.description.length > 0">{{node.description}}</span>
<span v-else class="disabled">暂时还没有描述。</span>
</td>
</tr>
</table>

View File

@@ -1,31 +0,0 @@
{$layout}
{$template "menu"}
{$template "/code_editor"}
<h3>安装步骤</h3>
<ol class="ui list">
<li>按照下面的配置信息替换<code-label>configs/api.yaml</code-label>内容;如果文件不存在,请先创建</li>
<li>使用<code-label>bin/edge-monitor start</code-label>启动节点</li>
<li>可以在<code-label>logs/run.log</code-label>中查看启动是否有异常</li>
<li>配置修改后,使用<code-label>bin/edge-monitor restart</code-label>重启节点</li>
</ol>
<div class="ui divider"></div>
<h3>配置信息</h3>
<table class="ui table definition selectable">
<tr>
<td class="title">配置文件</td>
<td>
configs/api.yaml &nbsp; <download-link :v-element="'api-code'" :v-file="'api.yaml'">[下载]</download-link>
</td>
</tr>
<tr>
<td class="title">配置内容</td>
<td>
<source-code-box id="api-code" type="text/yaml">rpc:
endpoints: [ {{apiEndpoints}} ]
nodeId: "{{node.uniqueId}}"
secret: "{{node.secret}}"</source-code-box>
</td>
</tr>
</table>

View File

@@ -1,51 +0,0 @@
{$layout}
{$template "menu"}
{$template "/datepicker"}
<div class="margin"></div>
<form method="get" action="/settings/monitorNodes/node/logs" class="ui form" autocomplete="off">
<input type="hidden" name="nodeId" :value="nodeId"/>
<div class="ui fields inline">
<div class="ui field">
<input type="text" name="dayFrom" placeholder="开始日期" v-model="dayFrom" value="" style="width:8em" id="day-from-picker"/>
</div>
<div class="ui field">
<input type="text" name="dayTo" placeholder="结束日期" v-model="dayTo" value="" style="width:8em" id="day-to-picker"/>
</div>
<div class="ui field">
<select class="ui dropdown" name="level" v-model="level">
<option value="">[级别]</option>
<option value="error">错误</option>
<option value="warning">警告</option>
<option value="info">信息</option>
<option value="success">成功</option>
</select>
</div>
<div class="ui field">
<input type="text" name="keyword" style="width:10em" v-model="keyword" placeholder="关键词"/>
</div>
<div class="ui field">
<button type="submit" class="ui button">查询</button>
</div>
<div class="ui field" v-if="dayFrom.length > 0 || dayTo.length > 0 || keyword.length > 0 || level.length > 0">
<a :href="'/settings/monitorNodes/node/logs?nodeId=' + nodeId">[清除条件]</a>
</div>
</div>
</form>
<p class="comment" v-if="logs.length == 0">暂时还没有日志。</p>
<table class="ui table selectable" v-if="logs.length > 0">
<thead>
<tr>
</tr>
</thead>
<tr v-for="log in logs">
<td>
<node-log-row :v-log="log" :v-keyword="keyword"></node-log-row>
</td>
</tr>
</table>
<div class="page" v-html="page"></div>

View File

@@ -1,6 +0,0 @@
Tea.context(function () {
this.$delay(function () {
teaweb.datepicker("day-from-picker")
teaweb.datepicker("day-to-picker")
})
})

View File

@@ -1,37 +0,0 @@
{$layout}
{$template "menu"}
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="nodeId" :value="node.id"/>
<input type="hidden" name="sslPolicyId" :value="node.sslPolicyId"/>
<table class="ui table selectable definition">
<tr>
<td class="title">节点名称 *</td>
<td>
<input type="text" name="name" maxlength="100" ref="focus" v-model="node.name"/>
</td>
</tr>
<tr>
<td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr>
<tbody v-show="moreOptionsVisible">
<tr>
<td>描述</td>
<td>
<textarea name="description" maxlength="200" rows="3" v-model="node.description"></textarea>
</td>
</tr>
<tr>
<td>是否启用</td>
<td>
<div class="ui checkbox">
<input type="checkbox" name="isOn" value="1" v-model="node.isOn"/>
<label></label>
</div>
</td>
</tr>
</tbody>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -1,3 +0,0 @@
Tea.context(function () {
this.success = NotifySuccess("保存成功", "/settings/monitorNodes/node?nodeId=" + this.node.id)
})