From eb6dd2855e6cdd3ac5f85ab3543310726a11129f Mon Sep 17 00:00:00 2001
From: UUBulb <35923940+uubulb@users.noreply.github.com>
Date: Sat, 24 Aug 2024 11:11:06 +0800
Subject: [PATCH] refactor: ddns (#414)
* refactor ddns
* update webhook
---
cmd/dashboard/controller/controller.go | 3 +-
cmd/dashboard/controller/oauth2.go | 3 +-
model/rule.go | 16 +-
pkg/ddns/cloudflare.go | 214 +++++++++++-------
pkg/ddns/ddns.go | 10 +-
pkg/ddns/dummy.go | 4 +-
pkg/ddns/helper.go | 40 ----
pkg/ddns/tencentcloud.go | 203 ++++++++++-------
pkg/ddns/webhook.go | 131 +++++++----
pkg/utils/utils.go | 13 ++
resource/l10n/en-US.toml | 25 +-
resource/l10n/es-ES.toml | 25 +-
resource/l10n/zh-CN.toml | 21 ++
resource/l10n/zh-TW.toml | 59 +++--
resource/template/dashboard-default/file.html | 14 +-
service/rpc/nezha.go | 2 +-
service/singleton/ddns.go | 92 ++++----
17 files changed, 523 insertions(+), 352 deletions(-)
delete mode 100644 pkg/ddns/helper.go
diff --git a/cmd/dashboard/controller/controller.go b/cmd/dashboard/controller/controller.go
index 6628781..50d4745 100644
--- a/cmd/dashboard/controller/controller.go
+++ b/cmd/dashboard/controller/controller.go
@@ -1,7 +1,6 @@
package controller
import (
- "encoding/json"
"fmt"
"html/template"
"io/fs"
@@ -277,7 +276,7 @@ func natGateway(c *gin.Context) {
rpc.NezhaHandlerSingleton.CreateStream(streamId)
defer rpc.NezhaHandlerSingleton.CloseStream(streamId)
- taskData, err := json.Marshal(model.TaskNAT{
+ taskData, err := utils.Json.Marshal(model.TaskNAT{
StreamID: streamId,
Host: natConfig.Host,
})
diff --git a/cmd/dashboard/controller/oauth2.go b/cmd/dashboard/controller/oauth2.go
index 1866108..3b057ed 100644
--- a/cmd/dashboard/controller/oauth2.go
+++ b/cmd/dashboard/controller/oauth2.go
@@ -2,7 +2,6 @@ package controller
import (
"context"
- "encoding/json"
"errors"
"fmt"
"net/http"
@@ -199,7 +198,7 @@ func (oa *oauth2controller) callback(c *gin.Context) {
if err == nil {
defer resp.Body.Close()
var cloudflareUserInfo *cloudflare.UserInfo
- if err := json.NewDecoder(resp.Body).Decode(&cloudflareUserInfo); err == nil {
+ if err := utils.Json.NewDecoder(resp.Body).Decode(&cloudflareUserInfo); err == nil {
user = cloudflareUserInfo.MapToNezhaUser()
}
}
diff --git a/model/rule.go b/model/rule.go
index f00c136..3cc2b9d 100644
--- a/model/rule.go
+++ b/model/rule.go
@@ -1,6 +1,7 @@
package model
import (
+ "slices"
"strings"
"time"
@@ -44,19 +45,6 @@ func percentage(used, total uint64) float64 {
return float64(used) * 100 / float64(total)
}
-func maxSliceValue(slice []float64) float64 {
- if len(slice) != 0 {
- max := slice[0]
- for _, val := range slice {
- if max < val {
- max = val
- }
- }
- return max
- }
- return 0
-}
-
// Snapshot 未通过规则返回 struct{}{}, 通过返回 nil
func (u *Rule) Snapshot(cycleTransferStats *CycleTransferStats, server *Server, db *gorm.DB) interface{} {
// 监控全部但是排除了此服务器
@@ -145,7 +133,7 @@ func (u *Rule) Snapshot(cycleTransferStats *CycleTransferStats, server *Server,
temp = append(temp, tempStat.Temperature)
}
}
- src = maxSliceValue(temp)
+ src = slices.Max(temp)
}
}
diff --git a/pkg/ddns/cloudflare.go b/pkg/ddns/cloudflare.go
index 3651211..e19ab79 100644
--- a/pkg/ddns/cloudflare.go
+++ b/pkg/ddns/cloudflare.go
@@ -2,169 +2,217 @@ package ddns
import (
"bytes"
- "encoding/json"
"fmt"
"io"
"log"
"net/http"
+ "net/url"
+
+ "github.com/naiba/nezha/pkg/utils"
)
+const baseEndpoint = "https://api.cloudflare.com/client/v4/zones"
+
type ProviderCloudflare struct {
- Secret string
+ secret string
+ zoneId string
+ recordId string
+ domainConfig *DomainConfig
}
-func (provider *ProviderCloudflare) UpdateDomain(domainConfig *DomainConfig) bool {
- if domainConfig == nil {
- return false
- }
+type cfReq struct {
+ Name string `json:"name"`
+ Type string `json:"type"`
+ Content string `json:"content"`
+ TTL uint32 `json:"ttl"`
+ Proxied bool `json:"proxied"`
+}
- zoneID, err := provider.getZoneID(domainConfig.FullDomain)
+type cfResp struct {
+ Result []struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+ } `json:"result"`
+}
+
+func NewProviderCloudflare(s string) *ProviderCloudflare {
+ return &ProviderCloudflare{
+ secret: s,
+ }
+}
+
+func (provider *ProviderCloudflare) UpdateDomain(domainConfig *DomainConfig) error {
+ if domainConfig == nil {
+ return fmt.Errorf("获取 DDNS 配置失败")
+ }
+ provider.domainConfig = domainConfig
+
+ err := provider.getZoneID()
if err != nil {
- log.Printf("无法获取 zone ID: %s\n", err)
- return false
+ return fmt.Errorf("无法获取 zone ID: %s", err)
}
// 当IPv4和IPv6同时成功才算作成功
- var resultV4 = true
- var resultV6 = true
- if domainConfig.EnableIPv4 {
- if !provider.addDomainRecord(zoneID, domainConfig, true) {
- resultV4 = false
+ if provider.domainConfig.EnableIPv4 {
+ if err = provider.addDomainRecord(true); err != nil {
+ return err
}
}
- if domainConfig.EnableIpv6 {
- if !provider.addDomainRecord(zoneID, domainConfig, false) {
- resultV6 = false
+ if provider.domainConfig.EnableIpv6 {
+ if err = provider.addDomainRecord(false); err != nil {
+ return err
}
}
- return resultV4 && resultV6
+ return nil
}
-func (provider *ProviderCloudflare) addDomainRecord(zoneID string, domainConfig *DomainConfig, isIpv4 bool) bool {
- record, err := provider.findDNSRecord(zoneID, domainConfig.FullDomain, isIpv4)
+func (provider *ProviderCloudflare) addDomainRecord(isIpv4 bool) error {
+ err := provider.findDNSRecord(isIpv4)
if err != nil {
- log.Printf("查找 DNS 记录时出错: %s\n", err)
- return false
+ return fmt.Errorf("查找 DNS 记录时出错: %s", err)
}
- if record == nil {
+ if provider.recordId == "" {
// 添加 DNS 记录
- return provider.createDNSRecord(zoneID, domainConfig, isIpv4)
+ return provider.createDNSRecord(isIpv4)
} else {
// 更新 DNS 记录
- return provider.updateDNSRecord(zoneID, record["id"].(string), domainConfig, isIpv4)
+ return provider.updateDNSRecord(isIpv4)
}
}
-func (provider *ProviderCloudflare) getZoneID(domain string) (string, error) {
- _, realDomain := SplitDomain(domain)
- url := fmt.Sprintf("https://api.cloudflare.com/client/v4/zones?name=%s", realDomain)
- body, err := provider.sendRequest("GET", url, nil)
+func (provider *ProviderCloudflare) getZoneID() error {
+ _, realDomain := splitDomain(provider.domainConfig.FullDomain)
+ zu, _ := url.Parse(baseEndpoint)
+
+ q := zu.Query()
+ q.Set("name", realDomain)
+ zu.RawQuery = q.Encode()
+
+ body, err := provider.sendRequest("GET", zu.String(), nil)
if err != nil {
- return "", err
+ return err
}
- var res map[string]interface{}
- err = json.Unmarshal(body, &res)
+ res := &cfResp{}
+ err = utils.Json.Unmarshal(body, res)
if err != nil {
- return "", err
+ return err
}
- result := res["result"].([]interface{})
+ result := res.Result
if len(result) > 0 {
- zoneID := result[0].(map[string]interface{})["id"].(string)
- return zoneID, nil
+ provider.zoneId = result[0].ID
+ return nil
}
- return "", fmt.Errorf("找不到 Zone ID")
+ return fmt.Errorf("找不到 Zone ID")
}
-func (provider *ProviderCloudflare) findDNSRecord(zoneID string, domain string, isIPv4 bool) (map[string]interface{}, error) {
- var ipType = "A"
- if !isIPv4 {
+func (provider *ProviderCloudflare) findDNSRecord(isIPv4 bool) error {
+ var ipType string
+ if isIPv4 {
+ ipType = "A"
+ } else {
ipType = "AAAA"
}
- url := fmt.Sprintf("https://api.cloudflare.com/client/v4/zones/%s/dns_records?type=%s&name=%s", zoneID, ipType, domain)
- body, err := provider.sendRequest("GET", url, nil)
+
+ de, _ := url.JoinPath(baseEndpoint, provider.zoneId, "dns_records")
+ du, _ := url.Parse(de)
+
+ q := du.Query()
+ q.Set("name", provider.domainConfig.FullDomain)
+ q.Set("type", ipType)
+ du.RawQuery = q.Encode()
+
+ body, err := provider.sendRequest("GET", du.String(), nil)
if err != nil {
- return nil, err
+ return err
}
- var res map[string]interface{}
- err = json.Unmarshal(body, &res)
+ res := &cfResp{}
+ err = utils.Json.Unmarshal(body, res)
if err != nil {
- return nil, err
+ return err
}
- result := res["result"].([]interface{})
+ result := res.Result
if len(result) > 0 {
- return result[0].(map[string]interface{}), nil
+ provider.recordId = result[0].ID
+ return nil
}
- return nil, nil // 没有找到 DNS 记录
+ return nil
}
-func (provider *ProviderCloudflare) createDNSRecord(zoneID string, domainConfig *DomainConfig, isIPv4 bool) bool {
- var ipType = "A"
- var ipAddr = domainConfig.Ipv4Addr
- if !isIPv4 {
+func (provider *ProviderCloudflare) createDNSRecord(isIPv4 bool) error {
+ var ipType, ipAddr string
+ if isIPv4 {
+ ipType = "A"
+ ipAddr = provider.domainConfig.Ipv4Addr
+ } else {
ipType = "AAAA"
- ipAddr = domainConfig.Ipv6Addr
+ ipAddr = provider.domainConfig.Ipv6Addr
}
- url := fmt.Sprintf("https://api.cloudflare.com/client/v4/zones/%s/dns_records", zoneID)
- data := map[string]interface{}{
- "type": ipType,
- "name": domainConfig.FullDomain,
- "content": ipAddr,
- "ttl": 60,
- "proxied": false,
+
+ de, _ := url.JoinPath(baseEndpoint, provider.zoneId, "dns_records")
+ data := &cfReq{
+ Name: provider.domainConfig.FullDomain,
+ Type: ipType,
+ Content: ipAddr,
+ TTL: 60,
+ Proxied: false,
}
- jsonData, _ := json.Marshal(data)
- _, err := provider.sendRequest("POST", url, jsonData)
- return err == nil
+
+ jsonData, _ := utils.Json.Marshal(data)
+ _, err := provider.sendRequest("POST", de, jsonData)
+ return err
}
-func (provider *ProviderCloudflare) updateDNSRecord(zoneID string, recordID string, domainConfig *DomainConfig, isIPv4 bool) bool {
- var ipType = "A"
- var ipAddr = domainConfig.Ipv4Addr
- if !isIPv4 {
+func (provider *ProviderCloudflare) updateDNSRecord(isIPv4 bool) error {
+ var ipType, ipAddr string
+ if isIPv4 {
+ ipType = "A"
+ ipAddr = provider.domainConfig.Ipv4Addr
+ } else {
ipType = "AAAA"
- ipAddr = domainConfig.Ipv6Addr
+ ipAddr = provider.domainConfig.Ipv6Addr
}
- url := fmt.Sprintf("https://api.cloudflare.com/client/v4/zones/%s/dns_records/%s", zoneID, recordID)
- data := map[string]interface{}{
- "type": ipType,
- "name": domainConfig.FullDomain,
- "content": ipAddr,
- "ttl": 60,
- "proxied": false,
+
+ de, _ := url.JoinPath(baseEndpoint, provider.zoneId, "dns_records", provider.recordId)
+ data := &cfReq{
+ Name: provider.domainConfig.FullDomain,
+ Type: ipType,
+ Content: ipAddr,
+ TTL: 60,
+ Proxied: false,
}
- jsonData, _ := json.Marshal(data)
- _, err := provider.sendRequest("PATCH", url, jsonData)
- return err == nil
+
+ jsonData, _ := utils.Json.Marshal(data)
+ _, err := provider.sendRequest("PATCH", de, jsonData)
+ return err
}
// 以下为辅助方法,如发送 HTTP 请求等
func (provider *ProviderCloudflare) sendRequest(method string, url string, data []byte) ([]byte, error) {
- client := &http.Client{}
req, err := http.NewRequest(method, url, bytes.NewBuffer(data))
if err != nil {
return nil, err
}
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", provider.Secret))
+ req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", provider.secret))
req.Header.Add("Content-Type", "application/json")
- resp, err := client.Do(req)
+ resp, err := utils.HttpClient.Do(req)
if err != nil {
return nil, err
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
- log.Printf("NEZHA>> 无法关闭HTTP响应体流: %s\n", err.Error())
+ log.Printf("NEZHA>> 无法关闭HTTP响应体流: %s", err.Error())
}
}(resp.Body)
diff --git a/pkg/ddns/ddns.go b/pkg/ddns/ddns.go
index 3e80d6e..2bf9251 100644
--- a/pkg/ddns/ddns.go
+++ b/pkg/ddns/ddns.go
@@ -1,5 +1,7 @@
package ddns
+import "golang.org/x/net/publicsuffix"
+
type DomainConfig struct {
EnableIPv4 bool
EnableIpv6 bool
@@ -10,5 +12,11 @@ type DomainConfig struct {
type Provider interface {
// UpdateDomain Return is updated
- UpdateDomain(domainConfig *DomainConfig) bool
+ UpdateDomain(*DomainConfig) error
+}
+
+func splitDomain(domain string) (prefix string, realDomain string) {
+ realDomain, _ = publicsuffix.EffectiveTLDPlusOne(domain)
+ prefix = domain[:len(domain)-len(realDomain)-1]
+ return prefix, realDomain
}
diff --git a/pkg/ddns/dummy.go b/pkg/ddns/dummy.go
index 3dd83e2..0216871 100644
--- a/pkg/ddns/dummy.go
+++ b/pkg/ddns/dummy.go
@@ -2,6 +2,6 @@ package ddns
type ProviderDummy struct{}
-func (provider *ProviderDummy) UpdateDomain(domainConfig *DomainConfig) bool {
- return false
+func (provider *ProviderDummy) UpdateDomain(domainConfig *DomainConfig) error {
+ return nil
}
diff --git a/pkg/ddns/helper.go b/pkg/ddns/helper.go
deleted file mode 100644
index a9e909e..0000000
--- a/pkg/ddns/helper.go
+++ /dev/null
@@ -1,40 +0,0 @@
-package ddns
-
-import (
- "golang.org/x/net/publicsuffix"
- "net/http"
- "strings"
-)
-
-func (provider ProviderWebHook) FormatWebhookString(s string, config *DomainConfig, ipType string) string {
- if config == nil {
- return s
- }
-
- result := strings.TrimSpace(s)
- result = strings.Replace(s, "{ip}", config.Ipv4Addr, -1)
- result = strings.Replace(result, "{domain}", config.FullDomain, -1)
- result = strings.Replace(result, "{type}", ipType, -1)
- // remove \r
- result = strings.Replace(result, "\r", "", -1)
- return result
-}
-
-func SetStringHeadersToRequest(req *http.Request, headers []string) {
- if req == nil {
- return
- }
- for _, element := range headers {
- kv := strings.SplitN(element, ":", 2)
- if len(kv) == 2 {
- req.Header.Add(kv[0], kv[1])
- }
- }
-}
-
-// SplitDomain 分割域名为前缀和一级域名
-func SplitDomain(domain string) (prefix string, realDomain string) {
- realDomain, _ = publicsuffix.EffectiveTLDPlusOne(domain)
- prefix = domain[:len(domain)-len(realDomain)-1]
- return prefix, realDomain
-}
diff --git a/pkg/ddns/tencentcloud.go b/pkg/ddns/tencentcloud.go
index 9c9e56d..fb75d79 100644
--- a/pkg/ddns/tencentcloud.go
+++ b/pkg/ddns/tencentcloud.go
@@ -5,145 +5,180 @@ import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
- "encoding/json"
+ "fmt"
"io"
"log"
"net/http"
"strconv"
"strings"
"time"
+
+ "github.com/naiba/nezha/pkg/utils"
)
-const (
- url = "https://dnspod.tencentcloudapi.com"
-)
+const te = "https://dnspod.tencentcloudapi.com"
type ProviderTencentCloud struct {
- SecretID string
- SecretKey string
+ secretID string
+ secretKey string
+ domainConfig *DomainConfig
+ resp *tcResp
}
-func (provider *ProviderTencentCloud) UpdateDomain(domainConfig *DomainConfig) bool {
- if domainConfig == nil {
- return false
+type tcReq struct {
+ RecordType string `json:"RecordType"`
+ Domain string `json:"Domain"`
+ RecordLine string `json:"RecordLine"`
+ Subdomain string `json:"Subdomain,omitempty"`
+ SubDomain string `json:"SubDomain,omitempty"` // As is
+ Value string `json:"Value,omitempty"`
+ TTL uint32 `json:"TTL,omitempty"`
+ RecordId uint64 `json:"RecordId,omitempty"`
+}
+
+type tcResp struct {
+ Response struct {
+ RecordList []struct {
+ RecordId uint64
+ Value string
+ }
+ Error struct {
+ Code string
+ }
}
+}
+
+func NewProviderTencentCloud(id, key string) *ProviderTencentCloud {
+ return &ProviderTencentCloud{
+ secretID: id,
+ secretKey: key,
+ }
+}
+
+func (provider *ProviderTencentCloud) UpdateDomain(domainConfig *DomainConfig) error {
+ if domainConfig == nil {
+ return fmt.Errorf("获取 DDNS 配置失败")
+ }
+ provider.domainConfig = domainConfig
// 当IPv4和IPv6同时成功才算作成功
- var resultV4 = true
- var resultV6 = true
- if domainConfig.EnableIPv4 {
- if !provider.addDomainRecord(domainConfig, true) {
- resultV4 = false
+ var err error
+ if provider.domainConfig.EnableIPv4 {
+ if err = provider.addDomainRecord(true); err != nil {
+ return err
}
}
- if domainConfig.EnableIpv6 {
- if !provider.addDomainRecord(domainConfig, false) {
- resultV6 = false
+ if provider.domainConfig.EnableIpv6 {
+ if err = provider.addDomainRecord(false); err != nil {
+ return err
}
}
- return resultV4 && resultV6
+ return err
}
-func (provider *ProviderTencentCloud) addDomainRecord(domainConfig *DomainConfig, isIpv4 bool) bool {
- record, err := provider.findDNSRecord(domainConfig.FullDomain, isIpv4)
+func (provider *ProviderTencentCloud) addDomainRecord(isIpv4 bool) error {
+ err := provider.findDNSRecord(isIpv4)
if err != nil {
- log.Printf("查找 DNS 记录时出错: %s\n", err)
- return false
+ return fmt.Errorf("查找 DNS 记录时出错: %s", err)
}
- if errResponse, ok := record["Error"].(map[string]interface{}); ok {
- if errCode, ok := errResponse["Code"].(string); ok && errCode == "ResourceNotFound.NoDataOfRecord" { // 没有找到 DNS 记录
- // 添加 DNS 记录
- return provider.createDNSRecord(domainConfig.FullDomain, domainConfig, isIpv4)
- } else {
- log.Printf("查询 DNS 记录时出错,错误代码为: %s\n", errCode)
- }
+ if provider.resp.Response.Error.Code == "ResourceNotFound.NoDataOfRecord" { // 没有找到 DNS 记录
+ return provider.createDNSRecord(isIpv4)
+ } else if provider.resp.Response.Error.Code != "" {
+ return fmt.Errorf("查询 DNS 记录时出错,错误代码为: %s", provider.resp.Response.Error.Code)
}
// 默认情况下更新 DNS 记录
- return provider.updateDNSRecord(domainConfig.FullDomain, record["RecordList"].([]interface{})[0].(map[string]interface{})["RecordId"].(float64), domainConfig, isIpv4)
+ return provider.updateDNSRecord(isIpv4)
}
-func (provider *ProviderTencentCloud) findDNSRecord(domain string, isIPv4 bool) (map[string]interface{}, error) {
- var ipType = "A"
- if !isIPv4 {
+func (provider *ProviderTencentCloud) findDNSRecord(isIPv4 bool) error {
+ var ipType string
+ if isIPv4 {
+ ipType = "A"
+ } else {
ipType = "AAAA"
}
- _, realDomain := SplitDomain(domain)
- prefix, _ := SplitDomain(domain)
- data := map[string]interface{}{
- "RecordType": ipType,
- "Domain": realDomain,
- "RecordLine": "默认",
- "Subdomain": prefix,
+
+ prefix, realDomain := splitDomain(provider.domainConfig.FullDomain)
+ data := &tcReq{
+ RecordType: ipType,
+ Domain: realDomain,
+ RecordLine: "默认",
+ Subdomain: prefix,
}
- jsonData, _ := json.Marshal(data)
+
+ jsonData, _ := utils.Json.Marshal(data)
body, err := provider.sendRequest("DescribeRecordList", jsonData)
if err != nil {
- return nil, err
+ return err
}
- var res map[string]interface{}
- err = json.Unmarshal(body, &res)
+ provider.resp = &tcResp{}
+ err = utils.Json.Unmarshal(body, provider.resp)
if err != nil {
- return nil, err
+ return err
}
- result := res["Response"].(map[string]interface{})
- return result, nil
+ return nil
}
-func (provider *ProviderTencentCloud) createDNSRecord(domain string, domainConfig *DomainConfig, isIPv4 bool) bool {
- var ipType = "A"
- var ipAddr = domainConfig.Ipv4Addr
- if !isIPv4 {
+func (provider *ProviderTencentCloud) createDNSRecord(isIPv4 bool) error {
+ var ipType, ipAddr string
+ if isIPv4 {
+ ipType = "A"
+ ipAddr = provider.domainConfig.Ipv4Addr
+ } else {
ipType = "AAAA"
- ipAddr = domainConfig.Ipv6Addr
+ ipAddr = provider.domainConfig.Ipv6Addr
}
- _, realDomain := SplitDomain(domain)
- prefix, _ := SplitDomain(domain)
- data := map[string]interface{}{
- "RecordType": ipType,
- "RecordLine": "默认",
- "Domain": realDomain,
- "SubDomain": prefix,
- "Value": ipAddr,
- "TTL": 600,
+
+ prefix, realDomain := splitDomain(provider.domainConfig.FullDomain)
+ data := &tcReq{
+ RecordType: ipType,
+ RecordLine: "默认",
+ Domain: realDomain,
+ SubDomain: prefix,
+ Value: ipAddr,
+ TTL: 600,
}
- jsonData, _ := json.Marshal(data)
+
+ jsonData, _ := utils.Json.Marshal(data)
_, err := provider.sendRequest("CreateRecord", jsonData)
- return err == nil
+ return err
}
-func (provider *ProviderTencentCloud) updateDNSRecord(domain string, recordID float64, domainConfig *DomainConfig, isIPv4 bool) bool {
- var ipType = "A"
- var ipAddr = domainConfig.Ipv4Addr
- if !isIPv4 {
+func (provider *ProviderTencentCloud) updateDNSRecord(isIPv4 bool) error {
+ var ipType, ipAddr string
+ if isIPv4 {
+ ipType = "A"
+ ipAddr = provider.domainConfig.Ipv4Addr
+ } else {
ipType = "AAAA"
- ipAddr = domainConfig.Ipv6Addr
+ ipAddr = provider.domainConfig.Ipv6Addr
}
- _, realDomain := SplitDomain(domain)
- prefix, _ := SplitDomain(domain)
- data := map[string]interface{}{
- "RecordType": ipType,
- "RecordLine": "默认",
- "Domain": realDomain,
- "SubDomain": prefix,
- "Value": ipAddr,
- "TTL": 600,
- "RecordId": recordID,
+
+ prefix, realDomain := splitDomain(provider.domainConfig.FullDomain)
+ data := &tcReq{
+ RecordType: ipType,
+ RecordLine: "默认",
+ Domain: realDomain,
+ SubDomain: prefix,
+ Value: ipAddr,
+ TTL: 600,
+ RecordId: provider.resp.Response.RecordList[0].RecordId,
}
- jsonData, _ := json.Marshal(data)
+
+ jsonData, _ := utils.Json.Marshal(data)
_, err := provider.sendRequest("ModifyRecord", jsonData)
- return err == nil
+ return err
}
// 以下为辅助方法,如发送 HTTP 请求等
func (provider *ProviderTencentCloud) sendRequest(action string, data []byte) ([]byte, error) {
- client := &http.Client{}
- req, err := http.NewRequest("POST", url, bytes.NewBuffer(data))
+ req, err := http.NewRequest("POST", te, bytes.NewBuffer(data))
if err != nil {
return nil, err
}
@@ -151,8 +186,8 @@ func (provider *ProviderTencentCloud) sendRequest(action string, data []byte) ([
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-TC-Version", "2021-03-23")
- provider.signRequest(provider.SecretID, provider.SecretKey, req, action, string(data))
- resp, err := client.Do(req)
+ provider.signRequest(provider.secretID, provider.secretKey, req, action, string(data))
+ resp, err := utils.HttpClient.Do(req)
if err != nil {
return nil, err
}
diff --git a/pkg/ddns/webhook.go b/pkg/ddns/webhook.go
index 488bbb3..74f0570 100644
--- a/pkg/ddns/webhook.go
+++ b/pkg/ddns/webhook.go
@@ -2,58 +2,109 @@ package ddns
import (
"bytes"
- "log"
+ "fmt"
"net/http"
+ "net/url"
"strings"
+
+ "github.com/naiba/nezha/pkg/utils"
)
type ProviderWebHook struct {
- URL string
- RequestMethod string
- RequestBody string
- RequestHeader string
+ url string
+ requestMethod string
+ requestBody string
+ requestHeader string
+ domainConfig *DomainConfig
}
-func (provider *ProviderWebHook) UpdateDomain(domainConfig *DomainConfig) bool {
+func NewProviderWebHook(s, rm, rb, rh string) *ProviderWebHook {
+ return &ProviderWebHook{
+ url: s,
+ requestMethod: rm,
+ requestBody: rb,
+ requestHeader: rh,
+ }
+}
+
+func (provider *ProviderWebHook) UpdateDomain(domainConfig *DomainConfig) error {
if domainConfig == nil {
- return false
+ return fmt.Errorf("获取 DDNS 配置失败")
+ }
+ provider.domainConfig = domainConfig
+
+ if provider.domainConfig.FullDomain == "" {
+ return fmt.Errorf("failed to update an empty domain")
}
- if domainConfig.FullDomain == "" {
- log.Println("NEZHA>> Failed to update an empty domain")
- return false
- }
- updated := false
- client := &http.Client{}
- if domainConfig.EnableIPv4 && domainConfig.Ipv4Addr != "" {
- url := provider.FormatWebhookString(provider.URL, domainConfig, "ipv4")
- body := provider.FormatWebhookString(provider.RequestBody, domainConfig, "ipv4")
- header := provider.FormatWebhookString(provider.RequestHeader, domainConfig, "ipv4")
- headers := strings.Split(header, "\n")
- req, err := http.NewRequest(provider.RequestMethod, url, bytes.NewBufferString(body))
- if err == nil && req != nil {
- SetStringHeadersToRequest(req, headers)
- if _, err := client.Do(req); err != nil {
- log.Printf("NEZHA>> Failed to update a domain: %s. Cause by: %s\n", domainConfig.FullDomain, err.Error())
- } else {
- updated = true
- }
+ if provider.domainConfig.EnableIPv4 && provider.domainConfig.Ipv4Addr != "" {
+ req, err := provider.prepareRequest(true)
+ if err != nil {
+ return fmt.Errorf("failed to update a domain: %s. Cause by: %v", provider.domainConfig.FullDomain, err)
+ }
+ if _, err := utils.HttpClient.Do(req); err != nil {
+ return fmt.Errorf("failed to update a domain: %s. Cause by: %v", provider.domainConfig.FullDomain, err)
}
}
- if domainConfig.EnableIpv6 && domainConfig.Ipv6Addr != "" {
- url := provider.FormatWebhookString(provider.URL, domainConfig, "ipv6")
- body := provider.FormatWebhookString(provider.RequestBody, domainConfig, "ipv6")
- header := provider.FormatWebhookString(provider.RequestHeader, domainConfig, "ipv6")
- headers := strings.Split(header, "\n")
- req, err := http.NewRequest(provider.RequestMethod, url, bytes.NewBufferString(body))
- if err == nil && req != nil {
- SetStringHeadersToRequest(req, headers)
- if _, err := client.Do(req); err != nil {
- log.Printf("NEZHA>> Failed to update a domain: %s. Cause by: %s\n", domainConfig.FullDomain, err.Error())
- } else {
- updated = true
- }
+
+ if provider.domainConfig.EnableIpv6 && provider.domainConfig.Ipv6Addr != "" {
+ req, err := provider.prepareRequest(false)
+ if err != nil {
+ return fmt.Errorf("failed to update a domain: %s. Cause by: %v", provider.domainConfig.FullDomain, err)
+ }
+ if _, err := utils.HttpClient.Do(req); err != nil {
+ return fmt.Errorf("failed to update a domain: %s. Cause by: %v", provider.domainConfig.FullDomain, err)
}
}
- return updated
+ return nil
+}
+
+func (provider *ProviderWebHook) prepareRequest(isIPv4 bool) (*http.Request, error) {
+ u, err := url.Parse(provider.url)
+ if err != nil {
+ return nil, fmt.Errorf("failed parsing url: %v", err)
+ }
+
+ // Only handle queries here
+ q := u.Query()
+ for p, vals := range q {
+ for n, v := range vals {
+ vals[n] = provider.formatWebhookString(v, isIPv4)
+ }
+ q[p] = vals
+ }
+
+ u.RawQuery = q.Encode()
+ body := provider.formatWebhookString(provider.requestBody, isIPv4)
+ header := provider.formatWebhookString(provider.requestHeader, isIPv4)
+ headers := strings.Split(header, "\n")
+
+ req, err := http.NewRequest(provider.requestMethod, u.String(), bytes.NewBufferString(body))
+ if err != nil {
+ return nil, fmt.Errorf("failed creating new request: %v", err)
+ }
+
+ utils.SetStringHeadersToRequest(req, headers)
+ return req, nil
+}
+
+func (provider *ProviderWebHook) formatWebhookString(s string, isIPv4 bool) string {
+ var ipAddr, ipType string
+ if isIPv4 {
+ ipAddr = provider.domainConfig.Ipv4Addr
+ ipType = "ipv4"
+ } else {
+ ipAddr = provider.domainConfig.Ipv6Addr
+ ipType = "ipv6"
+ }
+
+ r := strings.NewReplacer(
+ "{ip}", ipAddr,
+ "{domain}", provider.domainConfig.FullDomain,
+ "{type}", ipType,
+ "\r", "",
+ )
+
+ result := r.Replace(strings.TrimSpace(s))
+ return result
}
diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go
index 6b0ed0f..a5caec7 100644
--- a/pkg/utils/utils.go
+++ b/pkg/utils/utils.go
@@ -3,6 +3,7 @@ package utils
import (
"crypto/rand"
"math/big"
+ "net/http"
"os"
"regexp"
"strings"
@@ -86,3 +87,15 @@ func Uint64SubInt64(a uint64, b int64) uint64 {
}
return a - uint64(b)
}
+
+func SetStringHeadersToRequest(req *http.Request, headers []string) {
+ if req == nil {
+ return
+ }
+ for _, element := range headers {
+ kv := strings.SplitN(element, ":", 2)
+ if len(kv) == 2 {
+ req.Header.Add(kv[0], kv[1])
+ }
+ }
+}
diff --git a/resource/l10n/en-US.toml b/resource/l10n/en-US.toml
index 4a33281..28ff53c 100644
--- a/resource/l10n/en-US.toml
+++ b/resource/l10n/en-US.toml
@@ -653,7 +653,28 @@ other = "Disable Switch Template in Frontend"
other = "Servers On World Map"
[NAT]
-other = "NAT"
+other = "NAT Traversal"
[NetworkSpiterList]
-other = "Network Monitor"
\ No newline at end of file
+other = "Network Monitor"
+
+[Refresh]
+other = "Refresh"
+
+[CopyPath]
+other = "Copy Path"
+
+[Goto]
+other = "Go to"
+
+[GotoHeadline]
+other = "Go to a Folder"
+
+[GotoGo]
+other = "Go"
+
+[GotoClose]
+other = "Cancel"
+
+[FMError]
+other = "Agent returned an error, please view the console for details. To open a new connection, reopen the FM again."
diff --git a/resource/l10n/es-ES.toml b/resource/l10n/es-ES.toml
index accc1ba..fa21889 100644
--- a/resource/l10n/es-ES.toml
+++ b/resource/l10n/es-ES.toml
@@ -653,7 +653,28 @@ other = "Deshabilitar Cambio de Plantilla en Frontend"
other = "Servidores en el mapa mundial"
[NAT]
-other = "NAT"
+other = "NAT traversal"
[NetworkSpiterList]
-other = "Red Monitor"
\ No newline at end of file
+other = "Monitor de red"
+
+[Refresh]
+other = "Actualizar"
+
+[CopyPath]
+other = "Copiar ruta"
+
+[Goto]
+other = "Ir a"
+
+[GotoHeadline]
+other = "Ir a una carpeta"
+
+[GotoGo]
+other = "Ir"
+
+[GotoClose]
+other = "Cancelar"
+
+[FMError]
+other = "Agent devolvió un error, consulte la consola para obtener más detalles. Para abrir una nueva conexión, vuelva a abrir el FM."
diff --git a/resource/l10n/zh-CN.toml b/resource/l10n/zh-CN.toml
index 192efa7..61e81f8 100644
--- a/resource/l10n/zh-CN.toml
+++ b/resource/l10n/zh-CN.toml
@@ -657,3 +657,24 @@ other = "内网穿透"
[NetworkSpiterList]
other = "网络监控"
+
+[Refresh]
+other = "刷新"
+
+[CopyPath]
+other = "复制路径"
+
+[Goto]
+other = "跳往"
+
+[GotoHeadline]
+other = "跳往文件夹"
+
+[GotoGo]
+other = "确认"
+
+[GotoClose]
+other = "取消"
+
+[FMError]
+other = "Agent 返回了错误,请查看控制台获取详细信息。要建立新连接,请重新打开 FM。"
diff --git a/resource/l10n/zh-TW.toml b/resource/l10n/zh-TW.toml
index 9a7cf51..e9fcf92 100644
--- a/resource/l10n/zh-TW.toml
+++ b/resource/l10n/zh-TW.toml
@@ -50,7 +50,7 @@ other = "新增計劃任務"
other = "名稱"
[Scheduler]
-other = "計劃"
+other = "排程"
[BackUp]
other = "備份"
@@ -80,37 +80,37 @@ other = "特定伺服器"
other = "輸入ID/名稱以搜尋"
[NotificationMethodGroup]
-other = "通知方式組"
+other = "通知群組"
[PushSuccessMessages]
-other = "推送成功的消息"
+other = "推送成功的訊息"
[TaskType]
other = "任務類型"
[CronTask]
-other = "計劃任務"
+other = "排程任務"
[TriggerTask]
other = "觸發任務"
[TheFormaOfTheScheduleIs]
-other = "計劃的格式為:"
+other = "排程的格式為:"
[SecondsMinutesHoursDaysMonthsWeeksSeeDetails]
other = "秒 分 時 天 月 星期,詳情見"
[ScheduleExpressionFormat]
-other = "計劃表達式格式"
+other = "排程表達式格式"
[IntroductionOfCommands]
-other = "命令說明:編寫命令時類似於 shell/bat 腳本。建議不要換行,多個命令可用 &&
或 &
連接,若出現命令無法找到的情況,可能是由於 PATH
環境變量配置問題。在 Linux
伺服器上,可在命令開頭加入 source ~/.bashrc
,或使用命令的絕對路徑執行。"
+other = "命令說明:編寫命令時類似於 shell/bat 腳本。建議不要換行,多個命令可用 &&
或 &
連接,若出現命令無法找到的情況,可能是由於 PATH
環境變數配置問題。在 Linux
伺服器上,可在命令開頭加入 source ~/.bashrc
,或使用命令的絕對路徑執行。"
[AddMonitor]
other = "新增監控"
[Blog]
-other = "博客"
+other = "部落格"
[Target]
other = "目標"
@@ -158,7 +158,7 @@ other = "新增通知方式"
other = "分組"
[DoNotSendTestMessages]
-other = "不發送測試信息"
+other = "不發送測試訊息"
[RequestMethod]
other = "請求方式"
@@ -221,7 +221,7 @@ other = "排序"
other = "越大越靠前"
[Secret]
-other = "密鑰"
+other = "金鑰"
[Note]
other = "備註"
@@ -254,10 +254,10 @@ other = "忽略所有"
other = "觸發執行"
[DeleteScheduledTask]
-other = "刪除計劃任務"
+other = "刪除排程任務"
[ConfirmToDeleteThisScheduledTask]
-other = "確認刪除此計劃任務?"
+other = "確認刪除此排程任務?"
[AccessDenied]
other = "訪問被拒絕"
@@ -407,7 +407,7 @@ other = "流量"
other = "負載"
[ProcessCount]
-other = "進程數"
+other = "行程數"
[ConnCount]
other = "連接數"
@@ -422,7 +422,7 @@ other = "活動"
other = "版本"
[NetSpeed]
-other = "網絡"
+other = "網路"
[Uptime]
other = "在線"
@@ -458,7 +458,7 @@ other = "狀態"
other = "可用性"
[AverageLatency]
-other = "平均響應時間"
+other = "平均回應時間"
[CycleTransferStats]
other = "周期性流量統計"
@@ -524,7 +524,7 @@ other = "發生錯誤"
other = "系統錯誤"
[NetworkError]
-other = "網絡錯誤"
+other = "網路錯誤"
[ServicesStatus]
other = "服務狀態"
@@ -536,7 +536,7 @@ other = "伺服器管理"
other = "服務監控"
[ScheduledTasks]
-other = "計劃任務"
+other = "排程任務"
[ApiManagement]
other = "API 管理"
@@ -614,7 +614,7 @@ other = "對遊客隱藏"
other = "菜單"
[NetworkSpiter]
-other = "網絡"
+other = "網路"
[EnableShowInService]
other = "在服務中顯示"
@@ -656,4 +656,25 @@ other = "伺服器世界分布圖"
other = "NAT"
[NetworkSpiterList]
-other = "網絡監控"
\ No newline at end of file
+other = "網路監控"
+
+[Refresh]
+other = "重新整理"
+
+[CopyPath]
+other = "複製路徑"
+
+[Goto]
+other = "跳至"
+
+[GotoHeadline]
+other = "跳至資料夾"
+
+[GotoGo]
+other = "確定"
+
+[GotoClose]
+other = "取消"
+
+[FMError]
+other = "Agent 回傳了錯誤,請查看主控台獲取詳細資訊。要建立新連線,請重新開啟 FM。"
diff --git a/resource/template/dashboard-default/file.html b/resource/template/dashboard-default/file.html
index 401482b..8f76369 100644
--- a/resource/template/dashboard-default/file.html
+++ b/resource/template/dashboard-default/file.html
@@ -58,9 +58,9 @@
- Refresh
- Copy path
- Go to
+ {{tr "Refresh"}}
+ {{tr "CopyPath"}}
+ {{tr "Goto"}}
@@ -70,16 +70,16 @@
+ description="{{tr "FMError"}}">
-
+
- Go
- Close
+ {{tr "GotoGo"}}
+ {{tr "GotoClose"}}