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"}}