mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-02 14:56:38 +08:00
[新增] 对接 Uptime Kuma
This commit is contained in:
@@ -100,6 +100,56 @@ func validateAgentOption(key string, value string) error {
|
||||
}
|
||||
}
|
||||
|
||||
func validateUptimeKumaOption(key string, value string, state map[string]string) error {
|
||||
trimmed := strings.TrimSpace(value)
|
||||
switch key {
|
||||
case "UptimeKumaEnabled":
|
||||
if err := validateBooleanOption(key, trimmed); err != nil {
|
||||
return err
|
||||
}
|
||||
if trimmed == "true" {
|
||||
url := strings.TrimSpace(state["UptimeKumaUrl"])
|
||||
username := strings.TrimSpace(state["UptimeKumaUsername"])
|
||||
password := strings.TrimSpace(state["UptimeKumaPassword"])
|
||||
if url == "" {
|
||||
return fmt.Errorf("启用 Uptime Kuma 时地址不能为空")
|
||||
}
|
||||
if username == "" {
|
||||
return fmt.Errorf("启用 Uptime Kuma 时用户名不能为空")
|
||||
}
|
||||
if password == "" && common.UptimeKumaPassword == "" {
|
||||
return fmt.Errorf("启用 Uptime Kuma 时密码不能为空")
|
||||
}
|
||||
}
|
||||
case "UptimeKumaUsername":
|
||||
if trimmed == "" && state["UptimeKumaEnabled"] == "true" {
|
||||
return fmt.Errorf("启用 Uptime Kuma 时用户名不能为空")
|
||||
}
|
||||
case "UptimeKumaPassword":
|
||||
// No specific format checks needed
|
||||
case "UptimeKumaUrl":
|
||||
if trimmed != "" {
|
||||
if !strings.HasPrefix(trimmed, "http://") && !strings.HasPrefix(trimmed, "https://") {
|
||||
return fmt.Errorf("Uptime Kuma 地址必须以 http:// 或 https:// 开头")
|
||||
}
|
||||
}
|
||||
case "UptimeKumaMonitorScope":
|
||||
if trimmed != "all" && trimmed != "selected" {
|
||||
return fmt.Errorf("监控范围必须为全部站点 (all) 或选择站点 (selected)")
|
||||
}
|
||||
case "UptimeKumaSyncInterval", "UptimeKumaInterval", "UptimeKumaRetryInterval", "UptimeKumaTimeout":
|
||||
if err := validatePositiveIntegerOption(key, trimmed); err != nil {
|
||||
return err
|
||||
}
|
||||
case "UptimeKumaRetry":
|
||||
intValue, err := strconv.Atoi(trimmed)
|
||||
if err != nil || intValue < 0 {
|
||||
return fmt.Errorf("%s 必须为大于等于 0 的整数", key)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateOpenRestyOption(key string, value string) error {
|
||||
trimmed := strings.TrimSpace(value)
|
||||
|
||||
@@ -263,6 +313,9 @@ func validateOptionWithState(option model.Option, state map[string]string) error
|
||||
if err := validateAgentOption(option.Key, option.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateUptimeKumaOption(option.Key, option.Value, state); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -292,9 +345,9 @@ func updateOptions(options []model.Option) error {
|
||||
// @Router /api/option/ [get]
|
||||
func GetOptions(c *gin.Context) {
|
||||
var options []*model.Option
|
||||
common.OptionMapRWMutex.Lock()
|
||||
common.OptionMapRWMutex.RLock()
|
||||
for k, v := range common.OptionMap {
|
||||
if strings.Contains(k, "Token") || strings.Contains(k, "Secret") {
|
||||
if strings.Contains(k, "Token") || strings.Contains(k, "Secret") || strings.Contains(k, "Password") {
|
||||
continue
|
||||
}
|
||||
options = append(options, &model.Option{
|
||||
@@ -302,7 +355,7 @@ func GetOptions(c *gin.Context) {
|
||||
Value: utils.Interface2String(v),
|
||||
})
|
||||
}
|
||||
common.OptionMapRWMutex.Unlock()
|
||||
common.OptionMapRWMutex.RUnlock()
|
||||
respondSuccess(c, options)
|
||||
}
|
||||
|
||||
@@ -320,35 +373,8 @@ func UpdateOption(c *gin.Context) {
|
||||
if !bindJSON(c, &option) {
|
||||
return
|
||||
}
|
||||
switch option.Key {
|
||||
case "GitHubOAuthEnabled":
|
||||
if option.Value == "true" && common.GitHubClientId == "" {
|
||||
respondFailure(c, "无法启用 GitHub OAuth,请先填入 GitHub Client ID 以及 GitHub Client Secret!")
|
||||
return
|
||||
}
|
||||
case "WeChatAuthEnabled":
|
||||
if option.Value == "true" && common.WeChatServerAddress == "" {
|
||||
respondFailure(c, "无法启用微信登录,请先填入微信登录相关配置信息!")
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := validateRateLimitOption(option.Key, option.Value); err != nil {
|
||||
respondFailure(c, err.Error())
|
||||
return
|
||||
}
|
||||
if err := validateOpenRestyOption(option.Key, option.Value); err != nil {
|
||||
respondFailure(c, err.Error())
|
||||
return
|
||||
}
|
||||
if err := validateGeoIPOption(option.Key, option.Value); err != nil {
|
||||
respondFailure(c, err.Error())
|
||||
return
|
||||
}
|
||||
if err := validateDatabaseCleanupOption(option.Key, option.Value); err != nil {
|
||||
respondFailure(c, err.Error())
|
||||
return
|
||||
}
|
||||
if err := validateAgentOption(option.Key, option.Value); err != nil {
|
||||
state := buildOptionValidationState([]model.Option{option})
|
||||
if err := validateOptionWithState(option, state); err != nil {
|
||||
respondFailure(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package controller
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValidateOpenRestyOption(t *testing.T) {
|
||||
testCases := []struct {
|
||||
@@ -63,3 +65,55 @@ func TestValidateAgentOption(t *testing.T) {
|
||||
t.Fatal("expected websocket upgrade option to reject non-boolean value")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateUptimeKumaOption(t *testing.T) {
|
||||
state := map[string]string{
|
||||
"UptimeKumaUrl": "http://localhost:3001",
|
||||
"UptimeKumaUsername": "admin",
|
||||
"UptimeKumaPassword": "password",
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
key string
|
||||
value string
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "enabled true", key: "UptimeKumaEnabled", value: "true"},
|
||||
{name: "enabled false", key: "UptimeKumaEnabled", value: "false"},
|
||||
{name: "enabled invalid", key: "UptimeKumaEnabled", value: "on", wantErr: true},
|
||||
{name: "url http valid", key: "UptimeKumaUrl", value: "http://192.168.1.100:3001"},
|
||||
{name: "url https valid", key: "UptimeKumaUrl", value: "https://kuma.example.com"},
|
||||
{name: "url invalid", key: "UptimeKumaUrl", value: "kuma.example.com", wantErr: true},
|
||||
{name: "scope all", key: "UptimeKumaMonitorScope", value: "all"},
|
||||
{name: "scope selected", key: "UptimeKumaMonitorScope", value: "selected"},
|
||||
{name: "scope invalid", key: "UptimeKumaMonitorScope", value: "none", wantErr: true},
|
||||
{name: "sync interval valid", key: "UptimeKumaSyncInterval", value: "5"},
|
||||
{name: "sync interval invalid", key: "UptimeKumaSyncInterval", value: "0", wantErr: true},
|
||||
{name: "interval valid", key: "UptimeKumaInterval", value: "60"},
|
||||
{name: "interval invalid", key: "UptimeKumaInterval", value: "-60", wantErr: true},
|
||||
{name: "retry valid", key: "UptimeKumaRetry", value: "0"},
|
||||
{name: "retry positive valid", key: "UptimeKumaRetry", value: "3"},
|
||||
{name: "retry invalid", key: "UptimeKumaRetry", value: "-1", wantErr: true},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
err := validateUptimeKumaOption(tc.key, tc.value, state)
|
||||
if tc.wantErr && err == nil {
|
||||
t.Fatalf("%s: expected error", tc.name)
|
||||
}
|
||||
if !tc.wantErr && err != nil {
|
||||
t.Fatalf("%s: unexpected error: %v", tc.name, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test enabling Uptime Kuma when URL or credentials are empty in state
|
||||
stateEmpty := map[string]string{
|
||||
"UptimeKumaUrl": "",
|
||||
"UptimeKumaUsername": "",
|
||||
"UptimeKumaPassword": "",
|
||||
}
|
||||
if err := validateUptimeKumaOption("UptimeKumaEnabled", "true", stateEmpty); err == nil {
|
||||
t.Fatal("expected error when enabling Uptime Kuma with empty URL/credentials in state")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"openflare/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// SyncUptimeKuma godoc
|
||||
// @Summary Manually trigger Uptime Kuma sync
|
||||
// @Tags UptimeKuma
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Router /api/uptimekuma/sync [post]
|
||||
func SyncUptimeKuma(c *gin.Context) {
|
||||
err := service.SyncToUptimeKuma()
|
||||
if err != nil {
|
||||
respondFailure(c, err.Error())
|
||||
return
|
||||
}
|
||||
respondSuccessMessage(c, "同步成功")
|
||||
}
|
||||
Reference in New Issue
Block a user