Compare commits

..
5 Commits
Author SHA1 Message Date
UUBulb 0b7f43b149 fix dashboard custom theme, expose HideForGuest for api (#434)
* fix: dashboard custom theme

* api: expose HideForGuest
2024-10-10 13:38:09 +08:00
UUBulb 55f5c89c1c feat: description file for custom theme; use gjson (#433)
* feat: description file for custom theme; use gjson

* fix gosec

* remove outdated stuff
2024-10-10 00:08:16 +08:00
nap0o 937696c26d improve: status-server主题日常优化 (#432)
* improve: status-server主题日常优化
1. 首页账单信息:价格设置成“-1”前台展示为”PAYG“,自动续期(autoRenewal)功能增加支持付费周期yr,mo;
2. 当未获取到国旗图标时,默认彩虹旗;
3. 当未获取到os(操作系统)信息时,默认linux
4. 一些其他优化

* 修改表达不准确的语言文件
2024-10-08 11:39:44 +08:00
github-actions[bot] 08aaea8244 update contributors[no ci] 2024-10-06 12:54:13 +00:00
UUBulbandnaiba b886f70e48 chore: l10n: improve translation (#431)
* l10n: improve translation

* refine

---------

Co-authored-by: naiba <[email protected]>
2024-10-06 20:53:55 +08:00
29 changed files with 377 additions and 301 deletions
+2 -2
View File
@@ -59,6 +59,7 @@ You can change the dashboard language in the settings page (`/setting`) after th
<a href="https://github.com/1ridic" title="1ridic"><img src="https://avatars.githubusercontent.com/u/88495501?v=4" width="50;" alt="1ridic"/></a> <a href="https://github.com/1ridic" title="1ridic"><img src="https://avatars.githubusercontent.com/u/88495501?v=4" width="50;" alt="1ridic"/></a>
<a href="https://github.com/Mmx233" title="Mmx"><img src="https://avatars.githubusercontent.com/u/36563672?v=4" width="50;" alt="Mmx"/></a> <a href="https://github.com/Mmx233" title="Mmx"><img src="https://avatars.githubusercontent.com/u/36563672?v=4" width="50;" alt="Mmx"/></a>
<a href="https://github.com/rootmelo92118" title="rootmelo92118"><img src="https://avatars.githubusercontent.com/u/32770959?v=4" width="50;" alt="rootmelo92118"/></a> <a href="https://github.com/rootmelo92118" title="rootmelo92118"><img src="https://avatars.githubusercontent.com/u/32770959?v=4" width="50;" alt="rootmelo92118"/></a>
<a href="https://github.com/zhucaidan" title="zhucaidan"><img src="https://avatars.githubusercontent.com/u/47970938?v=4" width="50;" alt="zhucaidan"/></a>
<a href="https://github.com/iilemon" title="Sean"><img src="https://avatars.githubusercontent.com/u/33201711?v=4" width="50;" alt="Sean"/></a> <a href="https://github.com/iilemon" title="Sean"><img src="https://avatars.githubusercontent.com/u/33201711?v=4" width="50;" alt="Sean"/></a>
<a href="https://github.com/fscarmen" title="fscarmen"><img src="https://avatars.githubusercontent.com/u/62703343?v=4" width="50;" alt="fscarmen"/></a> <a href="https://github.com/fscarmen" title="fscarmen"><img src="https://avatars.githubusercontent.com/u/62703343?v=4" width="50;" alt="fscarmen"/></a>
<a href="https://github.com/ch8o" title="no-name-now"><img src="https://avatars.githubusercontent.com/u/9103372?v=4" width="50;" alt="no-name-now"/></a> <a href="https://github.com/ch8o" title="no-name-now"><img src="https://avatars.githubusercontent.com/u/9103372?v=4" width="50;" alt="no-name-now"/></a>
@@ -66,6 +67,7 @@ You can change the dashboard language in the settings page (`/setting`) after th
<a href="https://github.com/DarcJC" title="Darc Z."><img src="https://avatars.githubusercontent.com/u/53445798?v=4" width="50;" alt="Darc Z."/></a> <a href="https://github.com/DarcJC" title="Darc Z."><img src="https://avatars.githubusercontent.com/u/53445798?v=4" width="50;" alt="Darc Z."/></a>
<a href="https://github.com/Creling" title="Creling"><img src="https://avatars.githubusercontent.com/u/43109504?v=4" width="50;" alt="Creling"/></a> <a href="https://github.com/Creling" title="Creling"><img src="https://avatars.githubusercontent.com/u/43109504?v=4" width="50;" alt="Creling"/></a>
<a href="https://github.com/coreff" title="Core F"><img src="https://avatars.githubusercontent.com/u/38347122?v=4" width="50;" alt="Core F"/></a> <a href="https://github.com/coreff" title="Core F"><img src="https://avatars.githubusercontent.com/u/38347122?v=4" width="50;" alt="Core F"/></a>
<a href="https://github.com/nickfox-taterli" title="Tater Li"><img src="https://avatars.githubusercontent.com/u/19658596?v=4" width="50;" alt="Tater Li"/></a>
<a href="https://github.com/hmsjy2017" title="Tony"><img src="https://avatars.githubusercontent.com/u/42692274?v=4" width="50;" alt="Tony"/></a> <a href="https://github.com/hmsjy2017" title="Tony"><img src="https://avatars.githubusercontent.com/u/42692274?v=4" width="50;" alt="Tony"/></a>
<a href="https://github.com/adminsama" title="adminsama"><img src="https://avatars.githubusercontent.com/u/60880076?v=4" width="50;" alt="adminsama"/></a> <a href="https://github.com/adminsama" title="adminsama"><img src="https://avatars.githubusercontent.com/u/60880076?v=4" width="50;" alt="adminsama"/></a>
<a href="https://github.com/acgpiano" title="Acgpiano"><img src="https://avatars.githubusercontent.com/u/15900800?v=4" width="50;" alt="Acgpiano"/></a> <a href="https://github.com/acgpiano" title="Acgpiano"><img src="https://avatars.githubusercontent.com/u/15900800?v=4" width="50;" alt="Acgpiano"/></a>
@@ -77,10 +79,8 @@ You can change the dashboard language in the settings page (`/setting`) after th
<a href="https://github.com/lyj0309" title="lyj"><img src="https://avatars.githubusercontent.com/u/50474995?v=4" width="50;" alt="lyj"/></a> <a href="https://github.com/lyj0309" title="lyj"><img src="https://avatars.githubusercontent.com/u/50474995?v=4" width="50;" alt="lyj"/></a>
<a href="https://github.com/unclezs" title="unclezs"><img src="https://avatars.githubusercontent.com/u/42318775?v=4" width="50;" alt="unclezs"/></a> <a href="https://github.com/unclezs" title="unclezs"><img src="https://avatars.githubusercontent.com/u/42318775?v=4" width="50;" alt="unclezs"/></a>
<a href="https://github.com/ysicing" title="缘生"><img src="https://avatars.githubusercontent.com/u/8605565?v=4" width="50;" alt="缘生"/></a> <a href="https://github.com/ysicing" title="缘生"><img src="https://avatars.githubusercontent.com/u/8605565?v=4" width="50;" alt="缘生"/></a>
<a href="https://github.com/zhucaidan" title="zhucaidan"><img src="https://avatars.githubusercontent.com/u/47970938?v=4" width="50;" alt="zhucaidan"/></a>
<a href="https://github.com/arkylin" title="凌"><img src="https://avatars.githubusercontent.com/u/35104502?v=4" width="50;" alt="凌"/></a> <a href="https://github.com/arkylin" title="凌"><img src="https://avatars.githubusercontent.com/u/35104502?v=4" width="50;" alt="凌"/></a>
<a href="https://github.com/colour93" title="玖叁"><img src="https://avatars.githubusercontent.com/u/64313711?v=4" width="50;" alt="玖叁"/></a> <a href="https://github.com/colour93" title="玖叁"><img src="https://avatars.githubusercontent.com/u/64313711?v=4" width="50;" alt="玖叁"/></a>
<a href="https://github.com/nickfox-taterli" title="Tater Li"><img src="https://avatars.githubusercontent.com/u/19658596?v=4" width="50;" alt="Tater Li"/></a>
<a href="https://github.com/IamTaoChen" title="Tao Chen"><img src="https://avatars.githubusercontent.com/u/42793494?v=4" width="50;" alt="Tao Chen"/></a> <a href="https://github.com/IamTaoChen" title="Tao Chen"><img src="https://avatars.githubusercontent.com/u/42793494?v=4" width="50;" alt="Tao Chen"/></a>
<a href="https://github.com/Septrum101" title="Spetrum"><img src="https://avatars.githubusercontent.com/u/11692994?v=4" width="50;" alt="Spetrum"/></a> <a href="https://github.com/Septrum101" title="Spetrum"><img src="https://avatars.githubusercontent.com/u/11692994?v=4" width="50;" alt="Spetrum"/></a>
<a href="https://github.com/dreamingsleeping" title="Nanjing Hopefun Network Technology Co. Ltd."><img src="https://avatars.githubusercontent.com/u/13828658?v=4" width="50;" alt="Nanjing Hopefun Network Technology Co. Ltd."/></a> <a href="https://github.com/dreamingsleeping" title="Nanjing Hopefun Network Technology Co. Ltd."><img src="https://avatars.githubusercontent.com/u/13828658?v=4" width="50;" alt="Nanjing Hopefun Network Technology Co. Ltd."/></a>
+45 -20
View File
@@ -3,10 +3,10 @@ package controller
import ( import (
"fmt" "fmt"
"html/template" "html/template"
"io/fs"
"log" "log"
"net/http" "net/http"
"os" "os"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@@ -34,12 +34,6 @@ func ServeWeb(port uint) *http.Server {
pprof.Register(r) pprof.Register(r)
} }
r.Use(natGateway) r.Use(natGateway)
if os.Getenv("NZ_LOCAL_TEMPLATE") == "true" {
r.SetFuncMap(funcMap)
r.Use(mygin.RecordPath)
r.Static("/static", "resource/static")
r.LoadHTMLGlob("resource/template/**/*.html")
} else {
tmpl := template.New("").Funcs(funcMap) tmpl := template.New("").Funcs(funcMap)
var err error var err error
tmpl, err = tmpl.ParseFS(resource.TemplateFS, "template/**/*.html") tmpl, err = tmpl.ParseFS(resource.TemplateFS, "template/**/*.html")
@@ -49,13 +43,7 @@ func ServeWeb(port uint) *http.Server {
tmpl = loadThirdPartyTemplates(tmpl) tmpl = loadThirdPartyTemplates(tmpl)
r.SetHTMLTemplate(tmpl) r.SetHTMLTemplate(tmpl)
r.Use(mygin.RecordPath) r.Use(mygin.RecordPath)
staticFs, err := fs.Sub(resource.StaticFS, "static") r.StaticFS("/static", http.FS(resource.StaticFS))
if err != nil {
panic(err)
}
r.StaticFS("/static", http.FS(staticFs))
}
r.Static("/static-custom", "resource/static/custom")
routers(r) routers(r)
page404 := func(c *gin.Context) { page404 := func(c *gin.Context) {
mygin.ShowErrorPage(c, mygin.ErrInfo{ mygin.ShowErrorPage(c, mygin.ErrInfo{
@@ -96,7 +84,7 @@ func routers(r *gin.Engine) {
} }
func loadThirdPartyTemplates(tmpl *template.Template) *template.Template { func loadThirdPartyTemplates(tmpl *template.Template) *template.Template {
var ret = tmpl ret := tmpl
themes, err := os.ReadDir("resource/template") themes, err := os.ReadDir("resource/template")
if err != nil { if err != nil {
log.Printf("NEZHA>> Error reading themes folder: %v", err) log.Printf("NEZHA>> Error reading themes folder: %v", err)
@@ -106,17 +94,54 @@ func loadThirdPartyTemplates(tmpl *template.Template) *template.Template {
if !theme.IsDir() { if !theme.IsDir() {
continue continue
} }
// load templates
t, err := ret.ParseGlob(fmt.Sprintf("resource/template/%s/*.html", theme.Name())) themeDir := theme.Name()
if err != nil { if strings.HasPrefix(themeDir, "dashboard-") {
log.Printf("NEZHA>> Error parsing templates %s error: %v", theme.Name(), err) // load dashboard templates, ignore desc file
ret = loadTemplates(ret, themeDir)
continue continue
} }
ret = t
if !strings.HasPrefix(themeDir, "theme-") {
log.Printf("NEZHA>> Invalid theme name: %s", themeDir)
continue
} }
descPath := filepath.Join("resource", "template", themeDir, "theme.json")
desc, err := os.ReadFile(filepath.Clean(descPath))
if err != nil {
log.Printf("NEZHA>> Error opening %s config: %v", themeDir, err)
continue
}
themeName, err := utils.GjsonGet(desc, "name")
if err != nil {
log.Printf("NEZHA>> Error opening %s config: not a valid description file", theme.Name())
continue
}
// load templates
ret = loadTemplates(ret, themeDir)
themeKey := strings.TrimPrefix(themeDir, "theme-")
model.Themes[themeKey] = themeName.String()
}
return ret return ret
} }
func loadTemplates(tmpl *template.Template, themeDir string) *template.Template {
// load templates
templatePath := filepath.Join("resource", "template", themeDir, "*.html")
t, err := tmpl.ParseGlob(templatePath)
if err != nil {
log.Printf("NEZHA>> Error parsing templates %s: %v", themeDir, err)
return tmpl
}
return t
}
var funcMap = template.FuncMap{ var funcMap = template.FuncMap{
"tr": func(id string, dataAndCount ...interface{}) string { "tr": func(id string, dataAndCount ...interface{}) string {
conf := i18n.LocalizeConfig{ conf := i18n.LocalizeConfig{
+1 -1
View File
@@ -940,7 +940,7 @@ func (ma *memberAPI) updateSetting(c *gin.Context) {
return return
} }
if _, yes := model.Themes[sf.DashboardTheme]; !yes { if _, yes := model.DashboardThemes[sf.DashboardTheme]; !yes {
c.JSON(http.StatusOK, model.Response{ c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
Message: fmt.Sprintf("后台主题不存在:%s", sf.DashboardTheme), Message: fmt.Sprintf("后台主题不存在:%s", sf.DashboardTheme),
+3
View File
@@ -21,6 +21,7 @@ require (
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.18.2 github.com/spf13/viper v1.18.2
github.com/tidwall/gjson v1.18.0
github.com/xanzy/go-gitlab v0.103.0 github.com/xanzy/go-gitlab v0.103.0
golang.org/x/crypto v0.25.0 golang.org/x/crypto v0.25.0
golang.org/x/net v0.27.0 golang.org/x/net v0.27.0
@@ -70,6 +71,8 @@ require (
github.com/spf13/afero v1.11.0 // indirect github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect github.com/spf13/cast v1.6.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect github.com/ugorji/go/codec v1.2.11 // indirect
go.uber.org/atomic v1.9.0 // indirect go.uber.org/atomic v1.9.0 // indirect
+6
View File
@@ -180,6 +180,12 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
-1
View File
@@ -23,7 +23,6 @@ var Themes = map[string]string{
"hotaru": "Hotaru", "hotaru": "Hotaru",
"angel-kanade": "AngelKanade", "angel-kanade": "AngelKanade",
"server-status": "ServerStatus", "server-status": "ServerStatus",
"custom": "Custom(local)",
} }
var DashboardThemes = map[string]string{ var DashboardThemes = map[string]string{
+4 -4
View File
@@ -71,8 +71,8 @@ func (ns *NotificationServerBundle) reqBody(message string) (string, error) {
return string(msgBytes)[1 : len(msgBytes)-1] return string(msgBytes)[1 : len(msgBytes)-1]
}), nil }), nil
case NotificationRequestTypeForm: case NotificationRequestTypeForm:
var data map[string]string data, err := utils.GjsonParseStringMap(n.RequestBody)
if err := utils.Json.Unmarshal([]byte(n.RequestBody), &data); err != nil { if err != nil {
return "", err return "", err
} }
params := url.Values{} params := url.Values{}
@@ -99,8 +99,8 @@ func (n *Notification) setRequestHeader(req *http.Request) error {
if n.RequestHeader == "" { if n.RequestHeader == "" {
return nil return nil
} }
var m map[string]string m, err := utils.GjsonParseStringMap(n.RequestHeader)
if err := utils.Json.Unmarshal([]byte(n.RequestHeader), &m); err != nil { if err != nil {
return err return err
} }
for k, v := range m { for k, v := range m {
+32 -67
View File
@@ -2,6 +2,7 @@ package ddns
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"io" "io"
"log" "log"
@@ -14,10 +15,13 @@ import (
const baseEndpoint = "https://api.cloudflare.com/client/v4/zones" const baseEndpoint = "https://api.cloudflare.com/client/v4/zones"
type ProviderCloudflare struct { type ProviderCloudflare struct {
isIpv4 bool
domainConfig *DomainConfig
secret string secret string
zoneId string zoneId string
ipAddr string
recordId string recordId string
domainConfig *DomainConfig recordType string
} }
type cfReq struct { type cfReq struct {
@@ -28,13 +32,6 @@ type cfReq struct {
Proxied bool `json:"proxied"` Proxied bool `json:"proxied"`
} }
type cfResp struct {
Result []struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"result"`
}
func NewProviderCloudflare(s string) *ProviderCloudflare { func NewProviderCloudflare(s string) *ProviderCloudflare {
return &ProviderCloudflare{ return &ProviderCloudflare{
secret: s, secret: s,
@@ -54,13 +51,19 @@ func (provider *ProviderCloudflare) UpdateDomain(domainConfig *DomainConfig) err
// 当IPv4和IPv6同时成功才算作成功 // 当IPv4和IPv6同时成功才算作成功
if provider.domainConfig.EnableIPv4 { if provider.domainConfig.EnableIPv4 {
if err = provider.addDomainRecord(true); err != nil { provider.isIpv4 = true
provider.recordType = getRecordString(provider.isIpv4)
provider.ipAddr = provider.domainConfig.Ipv4Addr
if err = provider.addDomainRecord(); err != nil {
return err return err
} }
} }
if provider.domainConfig.EnableIpv6 { if provider.domainConfig.EnableIpv6 {
if err = provider.addDomainRecord(false); err != nil { provider.isIpv4 = false
provider.recordType = getRecordString(provider.isIpv4)
provider.ipAddr = provider.domainConfig.Ipv6Addr
if err = provider.addDomainRecord(); err != nil {
return err return err
} }
} }
@@ -68,19 +71,18 @@ func (provider *ProviderCloudflare) UpdateDomain(domainConfig *DomainConfig) err
return nil return nil
} }
func (provider *ProviderCloudflare) addDomainRecord(isIpv4 bool) error { func (provider *ProviderCloudflare) addDomainRecord() error {
err := provider.findDNSRecord(isIpv4) err := provider.findDNSRecord()
if err != nil { if err != nil {
if errors.Is(err, utils.ErrGjsonNotFound) {
// 添加 DNS 记录
return provider.createDNSRecord()
}
return fmt.Errorf("查找 DNS 记录时出错: %s", err) return fmt.Errorf("查找 DNS 记录时出错: %s", err)
} }
if provider.recordId == "" {
// 添加 DNS 记录
return provider.createDNSRecord(isIpv4)
} else {
// 更新 DNS 记录 // 更新 DNS 记录
return provider.updateDNSRecord(isIpv4) return provider.updateDNSRecord()
}
} }
func (provider *ProviderCloudflare) getZoneID() error { func (provider *ProviderCloudflare) getZoneID() error {
@@ -96,35 +98,22 @@ func (provider *ProviderCloudflare) getZoneID() error {
return err return err
} }
res := &cfResp{} result, err := utils.GjsonGet(body, "result.0.id")
err = utils.Json.Unmarshal(body, res)
if err != nil { if err != nil {
return err return err
} }
result := res.Result provider.zoneId = result.String()
if len(result) > 0 {
provider.zoneId = result[0].ID
return nil return nil
}
return fmt.Errorf("找不到 Zone ID")
} }
func (provider *ProviderCloudflare) findDNSRecord(isIPv4 bool) error { func (provider *ProviderCloudflare) findDNSRecord() error {
var ipType string
if isIPv4 {
ipType = "A"
} else {
ipType = "AAAA"
}
de, _ := url.JoinPath(baseEndpoint, provider.zoneId, "dns_records") de, _ := url.JoinPath(baseEndpoint, provider.zoneId, "dns_records")
du, _ := url.Parse(de) du, _ := url.Parse(de)
q := du.Query() q := du.Query()
q.Set("name", provider.domainConfig.FullDomain) q.Set("name", provider.domainConfig.FullDomain)
q.Set("type", ipType) q.Set("type", provider.recordType)
du.RawQuery = q.Encode() du.RawQuery = q.Encode()
body, err := provider.sendRequest("GET", du.String(), nil) body, err := provider.sendRequest("GET", du.String(), nil)
@@ -132,36 +121,21 @@ func (provider *ProviderCloudflare) findDNSRecord(isIPv4 bool) error {
return err return err
} }
res := &cfResp{} result, err := utils.GjsonGet(body, "result.0.id")
err = utils.Json.Unmarshal(body, res)
if err != nil { if err != nil {
return err return err
} }
result := res.Result provider.recordId = result.String()
if len(result) > 0 {
provider.recordId = result[0].ID
return nil
}
return nil return nil
} }
func (provider *ProviderCloudflare) createDNSRecord(isIPv4 bool) error { func (provider *ProviderCloudflare) createDNSRecord() error {
var ipType, ipAddr string
if isIPv4 {
ipType = "A"
ipAddr = provider.domainConfig.Ipv4Addr
} else {
ipType = "AAAA"
ipAddr = provider.domainConfig.Ipv6Addr
}
de, _ := url.JoinPath(baseEndpoint, provider.zoneId, "dns_records") de, _ := url.JoinPath(baseEndpoint, provider.zoneId, "dns_records")
data := &cfReq{ data := &cfReq{
Name: provider.domainConfig.FullDomain, Name: provider.domainConfig.FullDomain,
Type: ipType, Type: provider.recordType,
Content: ipAddr, Content: provider.ipAddr,
TTL: 60, TTL: 60,
Proxied: false, Proxied: false,
} }
@@ -171,21 +145,12 @@ func (provider *ProviderCloudflare) createDNSRecord(isIPv4 bool) error {
return err return err
} }
func (provider *ProviderCloudflare) updateDNSRecord(isIPv4 bool) error { func (provider *ProviderCloudflare) updateDNSRecord() error {
var ipType, ipAddr string
if isIPv4 {
ipType = "A"
ipAddr = provider.domainConfig.Ipv4Addr
} else {
ipType = "AAAA"
ipAddr = provider.domainConfig.Ipv6Addr
}
de, _ := url.JoinPath(baseEndpoint, provider.zoneId, "dns_records", provider.recordId) de, _ := url.JoinPath(baseEndpoint, provider.zoneId, "dns_records", provider.recordId)
data := &cfReq{ data := &cfReq{
Name: provider.domainConfig.FullDomain, Name: provider.domainConfig.FullDomain,
Type: ipType, Type: provider.recordType,
Content: ipAddr, Content: provider.ipAddr,
TTL: 60, TTL: 60,
Proxied: false, Proxied: false,
} }
+7
View File
@@ -20,3 +20,10 @@ func splitDomain(domain string) (prefix string, realDomain string) {
prefix = domain[:len(domain)-len(realDomain)-1] prefix = domain[:len(domain)-len(realDomain)-1]
return prefix, realDomain return prefix, realDomain
} }
func getRecordString(isIpv4 bool) string {
if isIpv4 {
return "A"
}
return "AAAA"
}
+39 -59
View File
@@ -5,6 +5,7 @@ import (
"crypto/hmac" "crypto/hmac"
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"errors"
"fmt" "fmt"
"io" "io"
"log" "log"
@@ -19,10 +20,14 @@ import (
const te = "https://dnspod.tencentcloudapi.com" const te = "https://dnspod.tencentcloudapi.com"
type ProviderTencentCloud struct { type ProviderTencentCloud struct {
isIpv4 bool
domainConfig *DomainConfig
recordID uint64
recordType string
secretID string secretID string
secretKey string secretKey string
domainConfig *DomainConfig errCode string
resp *tcResp ipAddr string
} }
type tcReq struct { type tcReq struct {
@@ -36,18 +41,6 @@ type tcReq struct {
RecordId uint64 `json:"RecordId,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 { func NewProviderTencentCloud(id, key string) *ProviderTencentCloud {
return &ProviderTencentCloud{ return &ProviderTencentCloud{
secretID: id, secretID: id,
@@ -64,13 +57,19 @@ func (provider *ProviderTencentCloud) UpdateDomain(domainConfig *DomainConfig) e
// 当IPv4和IPv6同时成功才算作成功 // 当IPv4和IPv6同时成功才算作成功
var err error var err error
if provider.domainConfig.EnableIPv4 { if provider.domainConfig.EnableIPv4 {
if err = provider.addDomainRecord(true); err != nil { provider.isIpv4 = true
provider.recordType = getRecordString(provider.isIpv4)
provider.ipAddr = provider.domainConfig.Ipv4Addr
if err = provider.addDomainRecord(); err != nil {
return err return err
} }
} }
if provider.domainConfig.EnableIpv6 { if provider.domainConfig.EnableIpv6 {
if err = provider.addDomainRecord(false); err != nil { provider.isIpv4 = false
provider.recordType = getRecordString(provider.isIpv4)
provider.ipAddr = provider.domainConfig.Ipv6Addr
if err = provider.addDomainRecord(); err != nil {
return err return err
} }
} }
@@ -78,33 +77,26 @@ func (provider *ProviderTencentCloud) UpdateDomain(domainConfig *DomainConfig) e
return err return err
} }
func (provider *ProviderTencentCloud) addDomainRecord(isIpv4 bool) error { func (provider *ProviderTencentCloud) addDomainRecord() error {
err := provider.findDNSRecord(isIpv4) err := provider.findDNSRecord()
if err != nil { if err != nil {
return fmt.Errorf("查找 DNS 记录时出错: %s", err) return fmt.Errorf("查找 DNS 记录时出错: %s", err)
} }
if provider.resp.Response.Error.Code == "ResourceNotFound.NoDataOfRecord" { // 没有找到 DNS 记录 if provider.errCode == "ResourceNotFound.NoDataOfRecord" { // 没有找到 DNS 记录
return provider.createDNSRecord(isIpv4) return provider.createDNSRecord()
} else if provider.resp.Response.Error.Code != "" { } else if provider.errCode != "" {
return fmt.Errorf("查询 DNS 记录时出错,错误代码为: %s", provider.resp.Response.Error.Code) return fmt.Errorf("查询 DNS 记录时出错,错误代码为: %s", provider.errCode)
} }
// 默认情况下更新 DNS 记录 // 默认情况下更新 DNS 记录
return provider.updateDNSRecord(isIpv4) return provider.updateDNSRecord()
} }
func (provider *ProviderTencentCloud) findDNSRecord(isIPv4 bool) error { func (provider *ProviderTencentCloud) findDNSRecord() error {
var ipType string
if isIPv4 {
ipType = "A"
} else {
ipType = "AAAA"
}
prefix, realDomain := splitDomain(provider.domainConfig.FullDomain) prefix, realDomain := splitDomain(provider.domainConfig.FullDomain)
data := &tcReq{ data := &tcReq{
RecordType: ipType, RecordType: provider.recordType,
Domain: realDomain, Domain: realDomain,
RecordLine: "默认", RecordLine: "默认",
Subdomain: prefix, Subdomain: prefix,
@@ -116,32 +108,29 @@ func (provider *ProviderTencentCloud) findDNSRecord(isIPv4 bool) error {
return err return err
} }
provider.resp = &tcResp{} result, err := utils.GjsonGet(body, "Response.RecordList.0.RecordId")
err = utils.Json.Unmarshal(body, provider.resp)
if err != nil { if err != nil {
if errors.Is(err, utils.ErrGjsonNotFound) {
if errCode, err := utils.GjsonGet(body, "Response.Error.Code"); err == nil {
provider.errCode = errCode.String()
return nil
}
}
return err return err
} }
provider.recordID = result.Uint()
return nil return nil
} }
func (provider *ProviderTencentCloud) createDNSRecord(isIPv4 bool) error { func (provider *ProviderTencentCloud) createDNSRecord() error {
var ipType, ipAddr string
if isIPv4 {
ipType = "A"
ipAddr = provider.domainConfig.Ipv4Addr
} else {
ipType = "AAAA"
ipAddr = provider.domainConfig.Ipv6Addr
}
prefix, realDomain := splitDomain(provider.domainConfig.FullDomain) prefix, realDomain := splitDomain(provider.domainConfig.FullDomain)
data := &tcReq{ data := &tcReq{
RecordType: ipType, RecordType: provider.recordType,
RecordLine: "默认", RecordLine: "默认",
Domain: realDomain, Domain: realDomain,
SubDomain: prefix, SubDomain: prefix,
Value: ipAddr, Value: provider.ipAddr,
TTL: 600, TTL: 600,
} }
@@ -150,25 +139,16 @@ func (provider *ProviderTencentCloud) createDNSRecord(isIPv4 bool) error {
return err return err
} }
func (provider *ProviderTencentCloud) updateDNSRecord(isIPv4 bool) error { func (provider *ProviderTencentCloud) updateDNSRecord() error {
var ipType, ipAddr string
if isIPv4 {
ipType = "A"
ipAddr = provider.domainConfig.Ipv4Addr
} else {
ipType = "AAAA"
ipAddr = provider.domainConfig.Ipv6Addr
}
prefix, realDomain := splitDomain(provider.domainConfig.FullDomain) prefix, realDomain := splitDomain(provider.domainConfig.FullDomain)
data := &tcReq{ data := &tcReq{
RecordType: ipType, RecordType: provider.recordType,
RecordLine: "默认", RecordLine: "默认",
Domain: realDomain, Domain: realDomain,
SubDomain: prefix, SubDomain: prefix,
Value: ipAddr, Value: provider.ipAddr,
TTL: 600, TTL: 600,
RecordId: provider.resp.Response.RecordList[0].RecordId, RecordId: provider.recordID,
} }
jsonData, _ := utils.Json.Marshal(data) jsonData, _ := utils.Json.Marshal(data)
+36
View File
@@ -0,0 +1,36 @@
package utils
import (
"errors"
"github.com/tidwall/gjson"
)
var (
ErrGjsonNotFound = errors.New("specified path does not exist")
ErrGjsonWrongType = errors.New("wrong type")
)
func GjsonGet(json []byte, path string) (gjson.Result, error) {
result := gjson.GetBytes(json, path)
if !result.Exists() {
return result, ErrGjsonNotFound
}
return result, nil
}
func GjsonParseStringMap(jsonObject string) (map[string]string, error) {
result := gjson.Parse(jsonObject)
if !result.IsObject() {
return nil, ErrGjsonWrongType
}
ret := make(map[string]string)
result.ForEach(func(key, value gjson.Result) bool {
ret[key.String()] = value.String()
return true
})
return ret, nil
}
+33
View File
@@ -0,0 +1,33 @@
package utils
import (
"embed"
"io/fs"
"os"
)
// HybridFS combines embed.FS and os.DirFS.
type HybridFS struct {
embedFS, dir fs.FS
}
func NewHybridFS(embed embed.FS, subDir string, localDir string) (*HybridFS, error) {
subFS, err := fs.Sub(embed, subDir)
if err != nil {
return nil, err
}
return &HybridFS{
embedFS: subFS,
dir: os.DirFS(localDir),
}, nil
}
func (hfs *HybridFS) Open(name string) (fs.File, error) {
// Ensure embed files are not replaced
if file, err := hfs.embedFS.Open(name); err == nil {
return file, nil
}
return hfs.dir.Open(name)
}
+30 -30
View File
@@ -23,19 +23,19 @@ other = "Home"
other = "Back to Homepage" other = "Back to Homepage"
[AdminPanel] [AdminPanel]
other = "Admin Panel" other = "Dashboard"
[Logout] [Logout]
other = "Logout" other = "Log out"
[Login] [Login]
other = "Login" other = "Log in"
[ConfirmLogout] [ConfirmLogout]
other = "Do you want to Logout?" other = "Confirm Logging Out?"
[AfterLoggingOutYouHaveToLoginAgain] [AfterLoggingOutYouHaveToLoginAgain]
other = "You need to Login again to continue" other = "You will need to log in again to continue"
[Cancel] [Cancel]
other = "Cancel" other = "Cancel"
@@ -56,7 +56,7 @@ other = "Cron Expression"
other = "Backup" other = "Backup"
[3amDaily] [3amDaily]
other = "(Daily at 3 AM)" other = "(At 3 AM)"
[Command] [Command]
other = "Command" other = "Command"
@@ -71,7 +71,7 @@ other = "Ignore All, Execute Only on Specific Servers"
other = "Cover All, Except Specific Servers" other = "Cover All, Except Specific Servers"
[ExecuteByTriggerServer] [ExecuteByTriggerServer]
other = "Execute on Trigger Server" other = "Execute on Alarmed Servers"
[SpecificServers] [SpecificServers]
other = "Specific Servers" other = "Specific Servers"
@@ -104,7 +104,7 @@ other = "Sec Min Hour Day Month Week, see details in "
other = "CRON Expression Format" other = "CRON Expression Format"
[IntroductionOfCommands] [IntroductionOfCommands]
other = "Introduction of Commands: Writing commands is similar to shell/bat scripts. It's advised not to break lines, and multiple commands can be connected with <code>&&</code> or <code>&</code>. If a command cannot be found, it might be due to <code>PATH</code> environment variable configuration issues. On <code>Linux</code> servers, you can add <code>source ~/.bashrc</code> at the beginning of the command, or use the command's absolute path." other = "Note on writing commands: It is similar to shell/bat scripts. but it's advised not to start a newline, and connect multiple commands with <code>&&</code> or <code>;</code>. If a command cannot be found, it is possibly due to <code>PATH</code> environment variable issues. On <code>Linux</code> servers, you can add <code>source ~/.bashrc</code> at the beginning of the command, or just use the absolute path."
[AddMonitor] [AddMonitor]
other = "Add Service Monitor" other = "Add Service Monitor"
@@ -119,7 +119,7 @@ other = "Target"
other = "Type" other = "Type"
[SslExpirationOrChange] [SslExpirationOrChange]
other = "(SSL Expiration or Change)" other = "(Certificate Expiration and Changes)"
[Duration] [Duration]
other = "Interval" other = "Interval"
@@ -134,10 +134,10 @@ other = "Enable Failure Notification"
other = "Failure Notification" other = "Failure Notification"
[MaxLatency] [MaxLatency]
other = "Max Latency (ms)" other = "Maximum Latency (ms)"
[MinLatency] [MinLatency]
other = "Min Latency (ms)" other = "Minimum Latency (ms)"
[EnableLatencyNotification] [EnableLatencyNotification]
other = "Enable Latency Notification" other = "Enable Latency Notification"
@@ -147,7 +147,7 @@ other = "Latency Notification"
[IntroductionOfMonitor] [IntroductionOfMonitor]
other = """ other = """
For type <b>HTTP-GET</b>, enter URL (with http/https, HTTPS protocol will also monitor SSL certificate);<br> For type <b>HTTP-GET</b>, enter URL (with http/https scheme, HTTPS protocol will also monitor SSL certificate);<br>
For type <b>ICMP-Ping</b>, enter domain/IP without port: example.com;<br> For type <b>ICMP-Ping</b>, enter domain/IP without port: example.com;<br>
For type <b>TCP-Ping</b>, enter domain/IP + port number: example.com:22""" For type <b>TCP-Ping</b>, enter domain/IP + port number: example.com:22"""
@@ -182,16 +182,16 @@ other = "Notification Trigger Mode"
other = "Always Trigger" other = "Always Trigger"
[ModeOnetimeTrigger] [ModeOnetimeTrigger]
other = "Single Trigger" other = "Trigger Once"
[EnableTriggerTask] [EnableTriggerTask]
other = "Enable Trigger Task" other = "Enable Trigger Task"
[FailTriggerTasks] [FailTriggerTasks]
other = "Tasks triggered during an alarm" other = "Tasks to trigger on an alarm"
[RecoverTriggerTasks] [RecoverTriggerTasks]
other = "Tasks triggered during recovery" other = "Tasks to trigger after recovery"
[Enable] [Enable]
other = "Enable" other = "Enable"
@@ -218,7 +218,7 @@ other = "Einstein Lightspeed 1"
other = "Display Index" other = "Display Index"
[TheLargerTheNumberTheHigherThePriority] [TheLargerTheNumberTheHigherThePriority]
other = "Higher Number, Higher Priority" other = "Greater Number, Higher Priority"
[Secret] [Secret]
other = "Secret" other = "Secret"
@@ -230,10 +230,10 @@ other = "Note"
other = "Public Note" other = "Public Note"
[LinuxOneKeyInstall] [LinuxOneKeyInstall]
other = "Linux One-Command Install" other = "Linux Installation Command"
[NoDomainAlert] [NoDomainAlert]
other = "Set CDN bypassed Domain/IP in setting before using this function" other = "Please set the IP (or a domain pointed to it) of the original server in settings"
[PushSuccessfully] [PushSuccessfully]
other = "Send Success Notification" other = "Send Success Notification"
@@ -254,13 +254,13 @@ other = "Cover All"
other = "Ignore All" other = "Ignore All"
[ByTrigger] [ByTrigger]
other = "Execute by Trigger" other = "Alarmed Servers"
[DeleteScheduledTask] [DeleteScheduledTask]
other = "Delete Scheduled Task" other = "Delete Scheduled Task"
[ConfirmToDeleteThisScheduledTask] [ConfirmToDeleteThisScheduledTask]
other = "Are you sure you want to Delete?" other = "Confirm deletion?"
[AccessDenied] [AccessDenied]
other = "Access Denied" other = "Access Denied"
@@ -281,22 +281,22 @@ other = "Don't Have an Account?"
other = "HTTP(S)/SSL Certificate" other = "HTTP(S)/SSL Certificate"
[TCPPort] [TCPPort]
other = "TCP Port" other = "TCPing"
[DeleteService] [DeleteService]
other = "Remove Service Monitor" other = "Remove Service Monitor"
[ConfirmToDeleteThisService] [ConfirmToDeleteThisService]
other = "Confirm to Delete This Service?" other = "Confirm Deleting This Service?"
[DeleteNotificationMethod] [DeleteNotificationMethod]
other = "Delete Notification Method" other = "Delete Notification Method"
[ConfirmToDeleteThisNotificationMethod] [ConfirmToDeleteThisNotificationMethod]
other = "Confirm to Delete This Notification Method?" other = "Confirm Deleting This Notification Method?"
[ForceUpdate] [ForceUpdate]
other = "Force Update Agent" other = "Trigger Agent Update"
[SelectAll] [SelectAll]
other = "Select All" other = "Select All"
@@ -305,7 +305,7 @@ other = "Select All"
other = "Version" other = "Version"
[OneKeyInstall] [OneKeyInstall]
other = "One-Command Install" other = "Installation commands"
[ClickToCopy] [ClickToCopy]
other = "Click To Copy" other = "Click To Copy"
@@ -314,7 +314,7 @@ other = "Click To Copy"
other = "Delete Server" other = "Delete Server"
[ConfirmToDeleteServer] [ConfirmToDeleteServer]
other = "Confirm to Delete Server?" other = "Confirm Deleting This Server?"
[NoServerSelected] [NoServerSelected]
other = "No Server Selected" other = "No Server Selected"
@@ -545,7 +545,7 @@ other = "Scheduled Tasks"
other = "API Management" other = "API Management"
[IssueNewApiToken] [IssueNewApiToken]
other = "Issue New API Token" other = "Generate New API Token"
[Token] [Token]
other = "Token" other = "Token"
@@ -554,10 +554,10 @@ other = "Token"
other = "Delete Token" other = "Delete Token"
[ConfirmToDeleteThisToken] [ConfirmToDeleteThisToken]
other = "Confirm to Delete This Token?" other = "Confirm Deleting This Token?"
[YouAreNotAuthorized] [YouAreNotAuthorized]
other = "Not Authorized for This Page" other = "This Page needs Authorization"
[WrongAccessPassword] [WrongAccessPassword]
other = "Wrong Access Password" other = "Wrong Access Password"
@@ -605,7 +605,7 @@ other = "IP Changed"
other = "Remaining Data" other = "Remaining Data"
[DashboardTheme] [DashboardTheme]
other = "Admin Panel Theme" other = "Dashboard Theme"
[Info] [Info]
other = "Info" other = "Info"
+4 -4
View File
@@ -71,7 +71,7 @@ other = "Ignorar todo, ejecutar solo a través de servidores específicos"
other = "Incluir todo, excepto servidores específicos" other = "Incluir todo, excepto servidores específicos"
[ExecuteByTriggerServer] [ExecuteByTriggerServer]
other = "Ejecutar por servidor disparador" other = "Ejecutar en servidores con alarma"
[SpecificServers] [SpecificServers]
other = "Servidores Específicos" other = "Servidores Específicos"
@@ -104,7 +104,7 @@ other = "Segundos, Minutos, Horas, Días, Meses, Semanas - Ver detalles a"
other = "Formato de Expresión de Cron" other = "Formato de Expresión de Cron"
[IntroductionOfCommands] [IntroductionOfCommands]
other = "Introducción de Comandos: Escriba comandos como en scripts shell/bat. Evite saltos de línea; conecte múltiples comandos con <code>&&</code> o <code>&</code>. Si no se encuentra un comando, verifique la variable de entorno <code>PATH</code>. En servidores <code>Linux</code>, antepóngale <code>source ~/.bashrc</code> a los comandos, o use rutas absolutas." other = "Introducción de Comandos: Escriba comandos como en scripts shell/bat. Evite saltos de línea; conecte múltiples comandos con <code>&&</code> o <code>;</code>. Si no se encuentra un comando, verifique la variable de entorno <code>PATH</code>. En servidores <code>Linux</code>, antepóngale <code>source ~/.bashrc</code> a los comandos, o use rutas absolutas."
[AddMonitor] [AddMonitor]
other = "Agregar Monitor de Servicio" other = "Agregar Monitor de Servicio"
@@ -254,7 +254,7 @@ other = "Cubrir Todo"
other = "Ignorar Todo" other = "Ignorar Todo"
[ByTrigger] [ByTrigger]
other = "Ejecutar por Disparador" other = "Servidores con alarma"
[DeleteScheduledTask] [DeleteScheduledTask]
other = "Eliminar Tarea Programada" other = "Eliminar Tarea Programada"
@@ -281,7 +281,7 @@ other = "¿No tiene una cuenta?"
other = "Certificados HTTP(S)/SSL" other = "Certificados HTTP(S)/SSL"
[TCPPort] [TCPPort]
other = "Puerto TCP" other = "TCPing"
[DeleteService] [DeleteService]
other = "Eliminar Servicio" other = "Eliminar Servicio"
+5 -5
View File
@@ -71,7 +71,7 @@ other = "忽略所有,仅通过特定服务器执行"
other = "覆盖所有,仅特定服务器不执行" other = "覆盖所有,仅特定服务器不执行"
[ExecuteByTriggerServer] [ExecuteByTriggerServer]
other = "由触发的服务器执行" other = "由触发告警的服务器执行"
[SpecificServers] [SpecificServers]
other = "特定服务器" other = "特定服务器"
@@ -104,7 +104,7 @@ other = "秒 分 时 天 月 星期,详情见"
other = "计划表达式格式" other = "计划表达式格式"
[IntroductionOfCommands] [IntroductionOfCommands]
other = "命令说明:编写命令时类似于 shell/bat 脚本。建议不要换行,多个命令可用 <code>&&</code> 或 <code>&</code> 连接,若出现命令无法找到的情况,可能是由于 <code>PATH</code> 环境变量配置问题。在 <code>Linux</code> 服务器上,可在命令开头加入 <code>source ~/.bashrc</code>,或使用命令的绝对路径执行。" other = "命令说明:编写命令时类似于 shell/bat 脚本。建议不要换行,多个命令可用 <code>&&</code> 或 <code>;</code> 连接,若出现命令无法找到的情况,可能是由于 <code>PATH</code> 环境变量配置问题。在 <code>Linux</code> 服务器上,可在命令开头加入 <code>source ~/.bashrc</code>,或使用命令的绝对路径执行。"
[AddMonitor] [AddMonitor]
other = "新增监控" other = "新增监控"
@@ -254,7 +254,7 @@ other = "覆盖所有"
other = "忽略所有" other = "忽略所有"
[ByTrigger] [ByTrigger]
other = "触发执行" other = "触发告警的服务器"
[DeleteScheduledTask] [DeleteScheduledTask]
other = "删除计划任务" other = "删除计划任务"
@@ -281,7 +281,7 @@ other = "没有账号?"
other = "HTTP(S)/SSL证书" other = "HTTP(S)/SSL证书"
[TCPPort] [TCPPort]
other = "TCP 端口" other = "TCPing"
[DeleteService] [DeleteService]
other = "移除服务" other = "移除服务"
@@ -425,7 +425,7 @@ other = "活动"
other = "版本" other = "版本"
[NetSpeed] [NetSpeed]
other = "网络" other = "网速"
[Uptime] [Uptime]
other = "在线" other = "在线"
+5 -5
View File
@@ -71,7 +71,7 @@ other = "忽略所有,僅通過特定伺服器執行"
other = "覆蓋所有,僅特定伺服器不執行" other = "覆蓋所有,僅特定伺服器不執行"
[ExecuteByTriggerServer] [ExecuteByTriggerServer]
other = "由觸發的伺服器執行" other = "由觸發告警的伺服器執行"
[SpecificServers] [SpecificServers]
other = "特定伺服器" other = "特定伺服器"
@@ -104,7 +104,7 @@ other = "秒 分 時 天 月 星期,詳情見"
other = "排程表達式格式" other = "排程表達式格式"
[IntroductionOfCommands] [IntroductionOfCommands]
other = "命令說明:編寫命令時類似於 shell/bat 腳本。建議不要換行,多個命令可用 <code>&&</code> 或 <code>&</code> 連接,若出現命令無法找到的情況,可能是由於 <code>PATH</code> 環境變數配置問題。在 <code>Linux</code> 伺服器上,可在命令開頭加入 <code>source ~/.bashrc</code>,或使用命令的絕對路徑執行。" other = "命令說明:編寫命令時類似於 shell/bat 腳本。建議不要換行,多個命令可用 <code>&&</code> 或 <code>;</code> 連接,若出現命令無法找到的情況,可能是由於 <code>PATH</code> 環境變數配置問題。在 <code>Linux</code> 伺服器上,可在命令開頭加入 <code>source ~/.bashrc</code>,或使用命令的絕對路徑執行。"
[AddMonitor] [AddMonitor]
other = "新增監控" other = "新增監控"
@@ -254,7 +254,7 @@ other = "覆蓋所有"
other = "忽略所有" other = "忽略所有"
[ByTrigger] [ByTrigger]
other = "觸發執行" other = "觸發告警的伺服器"
[DeleteScheduledTask] [DeleteScheduledTask]
other = "刪除排程任務" other = "刪除排程任務"
@@ -281,7 +281,7 @@ other = "沒有帳號?"
other = "HTTP(S)/SSL證書" other = "HTTP(S)/SSL證書"
[TCPPort] [TCPPort]
other = "TCP 端口" other = "TCPing"
[DeleteService] [DeleteService]
other = "移除服務" other = "移除服務"
@@ -425,7 +425,7 @@ other = "活動"
other = "版本" other = "版本"
[NetSpeed] [NetSpeed]
other = "網路" other = "網速"
[Uptime] [Uptime]
other = "在線" other = "在線"
+13 -1
View File
@@ -2,10 +2,14 @@ package resource
import ( import (
"embed" "embed"
"github.com/naiba/nezha/pkg/utils"
) )
var StaticFS *utils.HybridFS
//go:embed static //go:embed static
var StaticFS embed.FS var staticFS embed.FS
//go:embed template //go:embed template
var TemplateFS embed.FS var TemplateFS embed.FS
@@ -13,6 +17,14 @@ var TemplateFS embed.FS
//go:embed l10n //go:embed l10n
var I18nFS embed.FS var I18nFS embed.FS
func init() {
var err error
StaticFS, err = utils.NewHybridFS(staticFS, "static", "resource/static/custom")
if err != nil {
panic(err)
}
}
func IsTemplateFileExist(name string) bool { func IsTemplateFileExist(name string) bool {
_, err := TemplateFS.Open(name) _, err := TemplateFS.Open(name)
return err == nil return err == nil
+12 -12
View File
@@ -9,7 +9,7 @@ body[theme="dark"] .navbar .navbar-brand {
} }
body[theme="dark"] .navbar .navbar-nav li.pc-active a, body[theme="dark"] .navbar .navbar-nav li.pc-active a,
body[theme="dark"] .navbar .navbar-nav li.m-active a{ body[theme="dark"] .navbar .navbar-nav li.m-active a {
color: rgba(73, 146, 255, 1); color: rgba(73, 146, 255, 1);
} }
@@ -32,7 +32,7 @@ body[theme="dark"] .navbar .dropdown-menu > li > a:hover {
body[theme="dark"] .navbar .navbar-nav > .open > a:focus, body[theme="dark"] .navbar .navbar-nav > .open > a:focus,
body[theme="dark"] .navbar .navbar-nav > .open > a:hover, body[theme="dark"] .navbar .navbar-nav > .open > a:hover,
body[theme="dark"] .navbar .navbar-nav > .active > a, body[theme="dark"] .navbar .navbar-nav > .active > a,
body[theme="dark"] .navbar .navbar-nav > .open > a{ body[theme="dark"] .navbar .navbar-nav > .open > a {
background-image: none; background-image: none;
box-shadow: none; box-shadow: none;
background-color: transparent; background-color: transparent;
@@ -58,7 +58,7 @@ body[theme="dark"] .table {
body[theme="dark"] .table > tbody > tr > td:before, body[theme="dark"] .table > tbody > tr > td:before,
body[theme="dark"] .table > tfoot > tr > td:before, body[theme="dark"] .table > tfoot > tr > td:before,
body[theme="dark"] .table > thead > tr > td:before, body[theme="dark"] .table > thead > tr > td:before,
body[theme="dark"] .table > thead > tr.node-group-cell > th:before{ body[theme="dark"] .table > thead > tr.node-group-cell > th:before {
background-color: rgba(58, 62, 65, 1); background-color: rgba(58, 62, 65, 1);
} }
@@ -74,7 +74,7 @@ body[theme="dark"] .table-striped tbody > tr.odd > th {
background-color: rgba(34, 35, 46, 1); background-color: rgba(34, 35, 46, 1);
} }
body[theme="dark"] .table-hover > tbody > tr:hover{ body[theme="dark"] .table-hover > tbody > tr:hover {
background-color: unset; background-color: unset;
} }
@@ -83,11 +83,11 @@ body[theme="dark"] .table-hover > tbody > tr:not(.expandRow):hover > td {
} }
/* expandRow展开部分样式 */ /* expandRow展开部分样式 */
body[theme="dark"] .table > tbody > tr.expandRow.odd > td:before{ body[theme="dark"] .table > tbody > tr.expandRow.odd > td:before {
background-color: rgba(34, 35, 46, 1); background-color: rgba(34, 35, 46, 1);
} }
body[theme="dark"] .table > tbody > tr.expandRow.even > td:before{ body[theme="dark"] .table > tbody > tr.expandRow.even > td:before {
background-color: rgba(28, 29, 38, 1); background-color: rgba(28, 29, 38, 1);
} }
/* expandRow展开部分样式结束 */ /* expandRow展开部分样式结束 */
@@ -155,29 +155,29 @@ body[theme="dark"] footer p a:hover {
text-decoration: none; text-decoration: none;
} }
body[theme="dark"] .modal-content{ body[theme="dark"] .modal-content {
background-color: rgba(28, 29, 38, 1); background-color: rgba(28, 29, 38, 1);
color: #f1f1f1; color: #f1f1f1;
} }
body[theme="dark"] #earthChartBox .modal-content{ body[theme="dark"] #earthChartBox .modal-content {
background-color: rgba(0, 0, 0, 1); background-color: rgba(0, 0, 0, 1);
color: #f1f1f1; color: #f1f1f1;
} }
body[theme="dark"] .modal-header{ body[theme="dark"] .modal-header {
border-bottom-color:#080808; border-bottom-color:#080808;
} }
body[theme="dark"] .modal-header i.xclose{ body[theme="dark"] .modal-header i.xclose {
color: #f1f1f1; color: #f1f1f1;
} }
body[theme="dark"] .toolbox i{ body[theme="dark"] .toolbox i {
color: rgba(241, 241, 241, 1); color: rgba(241, 241, 241, 1);
background-color: rgba(0, 0, 0, 0.5); background-color: rgba(0, 0, 0, 0.5);
} }
body[theme="dark"] .network-box .network-box-header{ body[theme="dark"] .network-box .network-box-header {
border-bottom-color: rgba(110, 112, 121, 0.25); border-bottom-color: rgba(110, 112, 121, 0.25);
} }
+3 -3
View File
@@ -86,7 +86,7 @@ body[theme="dark"] .table > thead > tr.node-group-tag > th:before {
body[theme="dark"] .table > tbody > tr > td:before, body[theme="dark"] .table > tbody > tr > td:before,
body[theme="dark"] .table > tfoot > tr > td:before, body[theme="dark"] .table > tfoot > tr > td:before,
body[theme="dark"] .table > thead > tr > td:before, body[theme="dark"] .table > thead > tr > td:before,
body[theme="dark"] .table > thead > tr.node-group-cell > th:before{ body[theme="dark"] .table > thead > tr.node-group-cell > th:before {
/** border-bottom 颜色 **/ /** border-bottom 颜色 **/
background-color: rgba(155, 155, 155, 0.1); background-color: rgba(155, 155, 155, 0.1);
} }
@@ -96,11 +96,11 @@ body[theme="dark"] .table-hover > tbody > tr:not(.expandRow):hover > td {
} }
/* expandRow展开部分样式 */ /* expandRow展开部分样式 */
body[theme="dark"] .table > tbody > tr.expandRow.odd > td:before{ body[theme="dark"] .table > tbody > tr.expandRow.odd > td:before {
background-color: unset; background-color: unset;
} }
body[theme="dark"] .table > tbody > tr.expandRow.even > td:before{ body[theme="dark"] .table > tbody > tr.expandRow.even > td:before {
background-color: unset; background-color: unset;
} }
/* expandRow展开部分样式结束 */ /* expandRow展开部分样式结束 */
+12 -12
View File
@@ -47,16 +47,16 @@ body[theme="light"] .navbar .navbar-nav .open .dropdown-menu > li > a {
color: rgba(0, 0, 0, 0.87); color: rgba(0, 0, 0, 0.87);
} }
body[theme="light"] .navbar .navbar-toggle{ body[theme="light"] .navbar .navbar-toggle {
border:none; border:none;
padding:9px 12px; padding:9px 12px;
} }
body[theme="light"] .navbar .navbar-collapse{ body[theme="light"] .navbar .navbar-collapse {
border-color:rgba(0, 0, 0, 0.085); border-color:rgba(0, 0, 0, 0.085);
} }
body[theme="light"] .navbar .navbar-toggle .icon-bar{ body[theme="light"] .navbar .navbar-toggle .icon-bar {
background-color: rgba(0, 0, 0, 0.87); background-color: rgba(0, 0, 0, 0.87);
} }
@@ -95,12 +95,12 @@ body[theme="light"] .table-striped tbody > tr.odd > th {
body[theme="light"] .table > tbody > tr > td:before, body[theme="light"] .table > tbody > tr > td:before,
body[theme="light"] .table > tfoot > tr > td:before, body[theme="light"] .table > tfoot > tr > td:before,
body[theme="light"] .table > thead > tr > td:before, body[theme="light"] .table > thead > tr > td:before,
body[theme="light"] .table > thead > tr.node-group-cell > th:before{ body[theme="light"] .table > thead > tr.node-group-cell > th:before {
background-color: rgba(221, 221, 221, 1); background-color: rgba(221, 221, 221, 1);
} }
body[theme="light"] .table > tbody > tr.expandRow > td:before, body[theme="light"] .table > tbody > tr.expandRow > td:before,
body[theme="light"] .table > thead > tr.node-group-tag > th:before{ body[theme="light"] .table > thead > tr.node-group-tag > th:before {
background-color: rgba(255, 255, 255, 1); background-color: rgba(255, 255, 255, 1);
} }
@@ -174,35 +174,35 @@ body[theme="light"] footer p a:hover {
text-decoration: none; text-decoration: none;
} }
body[theme="light"] .toolbox i{ body[theme="light"] .toolbox i {
color: rgba(94, 114, 228, .9); color: rgba(94, 114, 228, .9);
background-color: rgba(255, 255, 255, .5); background-color: rgba(255, 255, 255, .5);
box-shadow:0 .125rem .25rem rgba(0, 0, 0, .15) !important; box-shadow:0 .125rem .25rem rgba(0, 0, 0, .15) !important;
} }
body[theme="light"] .toolbox .mytoggle .tooltip-inner{ body[theme="light"] .toolbox .mytoggle .tooltip-inner {
background: #ffffff; background: #ffffff;
box-shadow: 0 .125rem .25rem rgba(0, 0, 0, .15) !important; box-shadow: 0 .125rem .25rem rgba(0, 0, 0, .15) !important;
} }
body[theme="light"] .toolbox .mytoggle li:hover{ body[theme="light"] .toolbox .mytoggle li:hover {
background-color:#e6e6e6; background-color:#e6e6e6;
} }
body[theme="light"] .modal-content{ body[theme="light"] .modal-content {
background-color: rgba(249, 249, 249, 1); background-color: rgba(249, 249, 249, 1);
color: rgba(0, 0, 0, 0.87); color: rgba(0, 0, 0, 0.87);
} }
body[theme="light"] .modal-header{ body[theme="light"] .modal-header {
border-bottom-color:#f1f1f1; border-bottom-color:#f1f1f1;
} }
body[theme="light"] .modal-header i.xclose{ body[theme="light"] .modal-header i.xclose {
color: rgba(0, 0, 0, 0.87); color: rgba(0, 0, 0, 0.87);
} }
body[theme="light"] .network-box .network-box-header{ body[theme="light"] .network-box .network-box-header {
border-bottom-color: rgba(224, 230, 241, 0.6); border-bottom-color: rgba(224, 230, 241, 0.6);
} }
+7 -7
View File
@@ -36,16 +36,16 @@ body[theme="light"] .navbar .dropdown-menu {
body[theme="light"] .navbar .dropdown-menu > li > a:focus, body[theme="light"] .navbar .dropdown-menu > li > a:focus,
body[theme="light"] .navbar .dropdown-menu > li > a:hover{ body[theme="light"] .navbar .dropdown-menu > li > a:hover {
/** 二级导航鼠标悬停选中背景 **/ /** 二级导航鼠标悬停选中背景 **/
background-color: rgba(0, 0, 0, .05); background-color: rgba(0, 0, 0, .05);
} }
body[theme="light"] .navbar .navbar-collapse{ body[theme="light"] .navbar .navbar-collapse {
border-color:rgba(0, 0, 0, 0.085); border-color:rgba(0, 0, 0, 0.085);
} }
body[theme="light"] .navbar .navbar-toggle .icon-bar{ body[theme="light"] .navbar .navbar-toggle .icon-bar {
background-color: rgba(0, 0, 0, 0.9); background-color: rgba(0, 0, 0, 0.9);
} }
@@ -87,7 +87,7 @@ body[theme="light"] .table > thead > tr.node-group-tag > th:before {
body[theme="light"] .table > tbody > tr > td:before, body[theme="light"] .table > tbody > tr > td:before,
body[theme="light"] .table > tfoot > tr > td:before, body[theme="light"] .table > tfoot > tr > td:before,
body[theme="light"] .table > thead > tr > td:before, body[theme="light"] .table > thead > tr > td:before,
body[theme="light"] .table > thead > tr.node-group-cell > th:before{ body[theme="light"] .table > thead > tr.node-group-cell > th:before {
/** border-bottom 颜色 **/ /** border-bottom 颜色 **/
background-color: rgba(0, 0, 0, 0.075); background-color: rgba(0, 0, 0, 0.075);
} }
@@ -97,11 +97,11 @@ body[theme="light"] .table-hover > tbody > tr:not(.expandRow):hover > td {
} }
/* expandRow展开部分样式 */ /* expandRow展开部分样式 */
body[theme="light"] .table > tbody > tr.expandRow.odd > td:before{ body[theme="light"] .table > tbody > tr.expandRow.odd > td:before {
background-color: unset; background-color: unset;
} }
body[theme="light"] .table > tbody > tr.expandRow.even > td:before{ body[theme="light"] .table > tbody > tr.expandRow.even > td:before {
background-color: unset; background-color: unset;
} }
/* expandRow展开部分样式结束 */ /* expandRow展开部分样式结束 */
@@ -123,7 +123,7 @@ body[theme="light"] .progress-bar-danger {
background-image: linear-gradient(to right, rgba(255, 70, 0, 0.6) 0, rgba(201, 48, 44,0.6) 100%); background-image: linear-gradient(to right, rgba(255, 70, 0, 0.6) 0, rgba(201, 48, 44,0.6) 100%);
} }
body[theme="light"] .toolbox i{ body[theme="light"] .toolbox i {
background-color: rgba(255, 255, 255, .55); background-color: rgba(255, 255, 255, .55);
} }
+42 -38
View File
@@ -237,8 +237,8 @@ tr.accordion-toggle{
} }
.node-cell.network { .node-cell.network {
min-width: 110px; min-width: 100px;
max-width: 110px; max-width: 100px;
} }
.node-cell.traffic { .node-cell.traffic {
@@ -340,11 +340,11 @@ td.ping-network-quality {
/* 服务页 正文结束 */ /* 服务页 正文结束 */
/* 网络页 正文*/ /* 网络页 正文*/
.network-box .btn-group.open .dropdown-toggle{ .network-box .btn-group.open .dropdown-toggle {
box-shadow:unset; box-shadow:unset;
} }
.network-box .network-box-header{ .network-box .network-box-header {
font-size: 18px; font-size: 18px;
padding: 5px 0px 15px 5px; padding: 5px 0px 15px 5px;
cursor: pointer; cursor: pointer;
@@ -365,12 +365,12 @@ td.ping-network-quality {
display: none; display: none;
} }
.network-box .network-box-header .dropdown-menu li{ .network-box .network-box-header .dropdown-menu li {
height: 2em; height: 2em;
cursor: pointer; cursor: pointer;
} }
.network-box .network-box-header .dropdown-menu li.input-group{ .network-box .network-box-header .dropdown-menu li.input-group {
margin: 0 auto; margin: 0 auto;
display: flex; display: flex;
position: sticky; position: sticky;
@@ -378,7 +378,7 @@ td.ping-network-quality {
z-index: 99999997; z-index: 99999997;
} }
.network-box .network-box-header .dropdown-menu li.input-group input{ .network-box .network-box-header .dropdown-menu li.input-group input {
width: 100%; width: 100%;
height: 2em; height: 2em;
border-top: none; border-top: none;
@@ -407,7 +407,7 @@ td.ping-network-quality {
margin: 18px 0px 15px 0px; margin: 18px 0px 15px 0px;
} }
.network-box .chartTitle i.chartCountryCode{ .network-box .chartTitle i.chartCountryCode {
font-size: 16px; font-size: 16px;
border-radius: 12.5%; border-radius: 12.5%;
} }
@@ -415,17 +415,17 @@ td.ping-network-quality {
/* 网络页 正文结束 */ /* 网络页 正文结束 */
/* 地图版服务器分布图 */ /* 地图版服务器分布图 */
#mapChartBox{ #mapChartBox {
z-index: 999999999; z-index: 999999999;
padding-left: 0 !important; padding-left: 0 !important;
} }
.modal-open .modal{ .modal-open .modal {
overflow-x: hidden; overflow-x: hidden;
overflow-y: hidden; overflow-y: hidden;
} }
.modal-dialog-centered{ .modal-dialog-centered {
margin: 0 auto; margin: 0 auto;
width: 100%; width: 100%;
max-width: 100%; max-width: 100%;
@@ -435,7 +435,7 @@ td.ping-network-quality {
min-height: calc(100%) /* 确保模态框占据整个视口高度 */ min-height: calc(100%) /* 确保模态框占据整个视口高度 */
} }
.modal-content{ .modal-content {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
pointer-events: auto; pointer-events: auto;
@@ -443,15 +443,15 @@ td.ping-network-quality {
border: none; border: none;
} }
.modal-header{ .modal-header {
position: relative; position: relative;
} }
.modal-header h4{ .modal-header h4 {
cursor: pointer; cursor: pointer;
} }
.modal-header i.xclose{ .modal-header i.xclose {
cursor: pointer; cursor: pointer;
position: absolute; position: absolute;
font-size: 30px; font-size: 30px;
@@ -459,32 +459,32 @@ td.ping-network-quality {
right: 10px; right: 10px;
} }
.modal-body{ .modal-body {
padding: 0px; padding: 0px;
} }
a.template-item, a.cdn-item{ a.template-item, a.cdn-item {
display: block; display: block;
} }
footer{ footer {
padding-bottom: 20px; padding-bottom: 20px;
} }
footer p{ footer p {
text-align: center; text-align: center;
font-size: 10px; font-size: 10px;
} }
@media only screen and (max-width: 1200px) { @media only screen and (max-width: 1200px) {
.accordian-body{ .accordian-body {
margin: 5px 0px 5px 10px; margin: 5px 0px 5px 10px;
} }
table.monitor-detail{ table.monitor-detail {
margin:0px 0px 2px 0px; margin:0px 0px 2px 0px;
} }
.table .network-thead-tr th, .table .network-thead-tr th,
.table .node-group-tag th{ .table .node-group-tag th {
font-size:16px; font-size:16px;
padding-bottom:6px; padding-bottom:6px;
} }
@@ -512,10 +512,10 @@ footer p{
padding: 0; padding: 0;
margin-bottom: 10px; margin-bottom: 10px;
} }
footer{ footer {
padding-bottom: 10px; padding-bottom: 10px;
} }
.cycle-transfer-stats .dataTables_wrapper{ .cycle-transfer-stats .dataTables_wrapper {
margin-bottom: -10px; margin-bottom: -10px;
} }
.navbar .navbar-nav .open .dropdown-menu>li>a { .navbar .navbar-nav .open .dropdown-menu>li>a {
@@ -537,54 +537,58 @@ footer p{
} }
.node-cell.os, .node-cell.os,
.node-cell.uptime, .node-cell.uptime,
.node-cell.traffic{ .node-cell.traffic {
display: none; display: none;
visibility: hidden; visibility: hidden;
} }
.node-cell.location{ .node-cell.location {
display: table-cell; display: table-cell;
visibility: visible; visibility: visible;
} }
.node-cell-os-text,.node-cell-location-text{ .node-cell-os-text,.node-cell-location-text {
display: none; display: none;
visibility: hidden; visibility: hidden;
} }
.node-cell.network {
min-width: 95px;
max-width: 95px;
}
.node-cell.remaining { .node-cell.remaining {
min-width: 75px; min-width: 75px;
max-width: 75px; max-width: 75px;
} }
.accordian-body{ .accordian-body {
margin: 5px 0px 5px 10px; margin: 5px 0px 5px 10px;
} }
table.monitor-detail{ table.monitor-detail {
margin:0px 0px 2px 0px; margin:0px 0px 2px 0px;
} }
.table .network-thead-tr th, .table .network-thead-tr th,
.table .node-group-tag th{ .table .node-group-tag th {
font-size:14px; font-size:14px;
padding-bottom:6px; padding-bottom:6px;
} }
.table tr.network td{ .table tr.network td {
font-size: 12px; font-size: 12px;
} }
.service-status .service-status-th{ .service-status .service-status-th {
min-width:30px; min-width:30px;
} }
.service-status .delay-today{ .service-status .delay-today {
margin-top:4px; margin-top:4px;
justify-content: center; justify-content: center;
} }
.service-status .delay-today i{ .service-status .delay-today i {
margin-right:0px; margin-right:0px;
} }
.service-status .delay-today-text{ .service-status .delay-today-text {
display: none; display: none;
visibility: hidden; visibility: hidden;
} }
.service-status .service-averagelatency-th{ .service-status .service-averagelatency-th {
min-width:70px; min-width:70px;
} }
.service-status .service-30daysonline-th{ .service-status .service-30daysonline-th {
min-width:75px; min-width:75px;
} }
.toolbox { .toolbox {
@@ -602,7 +606,7 @@ footer p{
font-size: 16px; font-size: 16px;
margin: 10px 0px 10px 0px; margin: 10px 0px 10px 0px;
} }
.network-box .chartTitle i.chartCountryCode{ .network-box .chartTitle i.chartCountryCode {
font-size: 15px; font-size: 15px;
border-radius: 12.5%; border-radius: 12.5%;
} }
+2 -2
View File
@@ -28,8 +28,8 @@
</span> </span>
</aside> </aside>
<template v-if="semiTransparent"> <template v-if="semiTransparent">
<link rel="stylesheet" href="/static/theme-server-status/css/light.plus.css?v20240807"> <link rel="stylesheet" href="/static/theme-server-status/css/light.plus.css?v20241008">
<link rel="stylesheet" href="/static/theme-server-status/css/dark.plus.css?v20240807"> <link rel="stylesheet" href="/static/theme-server-status/css/dark.plus.css?v20241008">
</template> </template>
</div> </div>
{{if ts .CustomCode}}{{.CustomCode|safe}}{{end}} {{if ts .CustomCode}}{{.CustomCode|safe}}{{end}}
+3 -3
View File
@@ -26,9 +26,9 @@
<script src="https://unpkg.com/[email protected]/dist/js/bootstrap.min.js"></script> <script src="https://unpkg.com/[email protected]/dist/js/bootstrap.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script> <script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/echarts.min.js"></script> <script src="https://unpkg.com/[email protected]/dist/echarts.min.js"></script>
<link rel="stylesheet" href="/static/theme-server-status/css/main.css?v20240929"> <link rel="stylesheet" href="/static/theme-server-status/css/main.css?v20241008">
<link rel="stylesheet" href="/static/theme-server-status/css/dark.css?v202409029"> <link rel="stylesheet" href="/static/theme-server-status/css/dark.css?v20241008">
<link rel="stylesheet" href="/static/theme-server-status/css/light.css?v20240916"> <link rel="stylesheet" href="/static/theme-server-status/css/light.css?v20241008">
<script src="/static/theme-server-status/js/mixin.js?v20240915"></script> <script src="/static/theme-server-status/js/mixin.js?v20240915"></script>
</head> </head>
<body> <body>
@@ -32,15 +32,16 @@
<i v-if='isWindowsPlatform(node.host.Platform)' class="windows icon"></i> <i v-if='isWindowsPlatform(node.host.Platform)' class="windows icon"></i>
<i v-else-if='getFontLogoClass(node.host.Platform) == "" && node.stateuptime > 0' class="fl-tux"></i> <i v-else-if='getFontLogoClass(node.host.Platform) == "" && node.stateuptime > 0' class="fl-tux"></i>
<i v-else :class="'fl-' + getFontLogoClass(node.host.Platform)"></i> <i v-else :class="'fl-' + getFontLogoClass(node.host.Platform)"></i>
<span class="node-cell-os-text">@#getPlatformName(node.os)#@</span> <span class="node-cell-os-text">@#getPlatformName(node.os) === '' && node.stateuptime > 0 ? 'linux' : getPlatformName(node.os)#@</span>
</td> </td>
<td style="text-align: center;" class="node-cell location"> <td class="node-cell location center">
<i :class="'fi fi-' + node.location"></i> <i :class="'fi fi-' + (node.stateuptime > 0 ? (node.location || 'rb') : '')"></i>
<span class="node-cell-location-text text-uppercase">@#node.location#@</span> <span class="node-cell-location-text text-uppercase">@#node.stateuptime > 0 ? (node.location || 'RB') : ''#@</span>
</td> </td>
<td v-if="nodesNoTag.some(item => item.additional && item.additional.price && Object.keys(item.additional.price).length > 0)" class="node-cell price center"> <td v-if="nodesNoTag.some(item => item.additional && item.additional.price && Object.keys(item.additional.price).length > 0)" class="node-cell price center">
<template v-if="node.additional && node.additional.price"> <template v-if="node.additional && node.additional.price">
<span v-if="node.additional.price.amount == 0" class="node-cell-price-text">Free</span> <span v-if="node.additional.price.amount == 0" class="node-cell-price-text">FREE</span>
<span v-else-if="node.additional.price.amount == -1" class="node-cell-price-text">PAYG</span>
<span v-else class="node-cell-price-text">@#node.additional.price.amount#@@#(node.additional.price.cycle ? '/' + node.additional.price.cycle : '')#@</span> <span v-else class="node-cell-price-text">@#node.additional.price.amount#@@#(node.additional.price.cycle ? '/' + node.additional.price.cycle : '')#@</span>
</template> </template>
</td> </td>
@@ -89,8 +90,8 @@
<div style="display: flex;left-items: center;justify-content: center;flex-direction: column; max-width: 89vw"> <div style="display: flex;left-items: center;justify-content: center;flex-direction: column; max-width: 89vw">
<span class="node-cell-expand"> <span class="node-cell-expand">
<span class="node-cell-expand-label">{{tr "Platform"}}:</span> <span class="node-cell-expand-label">{{tr "Platform"}}:</span>
@#node.host.Platform#@-@#node.host.PlatformVersion#@ <span v-if="node.host.Platform">@#node.host.Platform#@@#node.host.PlatformVersion ? '-' + node.host.PlatformVersion : ''#@</span>
[<span v-if="node.host.Virtualization">@#node.host.Virtualization#@:</span>@#node.host.Arch#@] <span v-if="node.host.Arch">[@#node.host.Virtualization ? node.host.Virtualization + ':' : ''#@@#node.host.Arch#@]</span>
</span> </span>
<span class="node-cell-expand" v-if="node.host.CPU"> <span class="node-cell-expand" v-if="node.host.CPU">
<span class="node-cell-expand-label">CPU:</span> <span class="node-cell-expand-label">CPU:</span>
+8 -7
View File
@@ -35,15 +35,16 @@
<i v-if='isWindowsPlatform(node.host.Platform)' class="windows icon"></i> <i v-if='isWindowsPlatform(node.host.Platform)' class="windows icon"></i>
<i v-else-if='getFontLogoClass(node.host.Platform) == "" && node.stateuptime > 0' class="fl-tux"></i> <i v-else-if='getFontLogoClass(node.host.Platform) == "" && node.stateuptime > 0' class="fl-tux"></i>
<i v-else :class="'fl-' + getFontLogoClass(node.host.Platform)"></i> <i v-else :class="'fl-' + getFontLogoClass(node.host.Platform)"></i>
<span class="node-cell-os-text">@#getPlatformName(node.os)#@</span> <span class="node-cell-os-text">@#getPlatformName(node.os) === '' && node.stateuptime > 0 ? 'linux' : getPlatformName(node.os)#@</span>
</td> </td>
<td style="text-align: center;" class="node-cell location"> <td class="node-cell location center">
<i :class="'fi fi-' + node.location"></i> <i :class="'fi fi-' + (node.stateuptime > 0 ? (node.location || 'rb') : '')"></i>
<span class="node-cell-location-text text-uppercase">&nbsp;@#node.location#@</span> <span class="node-cell-location-text text-uppercase">@#node.stateuptime > 0 ? (node.location || 'RB') : ''#@</span>
</td> </td>
<td v-if="group.data.some(item => item.additional && item.additional.price && Object.keys(item.additional.price).length > 0)" class="node-cell price center"> <td v-if="group.data.some(item => item.additional && item.additional.price && Object.keys(item.additional.price).length > 0)" class="node-cell price center">
<template v-if="node.additional && node.additional.price"> <template v-if="node.additional && node.additional.price">
<span v-if="node.additional.price.amount == 0" class="node-cell-price-text">Free</span> <span v-if="node.additional.price.amount == 0" class="node-cell-price-text">FREE</span>
<span v-else-if="node.additional.price.amount == -1" class="node-cell-price-text">PAYG</span>
<span v-else class="node-cell-price-text">@#node.additional.price.amount#@@#(node.additional.price.cycle ? '/' + node.additional.price.cycle : '')#@</span> <span v-else class="node-cell-price-text">@#node.additional.price.amount#@@#(node.additional.price.cycle ? '/' + node.additional.price.cycle : '')#@</span>
</template> </template>
</td> </td>
@@ -92,8 +93,8 @@
<div style="display: flex;left-items: center;justify-content: center;flex-direction: column; max-width: 89vw"> <div style="display: flex;left-items: center;justify-content: center;flex-direction: column; max-width: 89vw">
<span class="node-cell-expand"> <span class="node-cell-expand">
<span class="node-cell-expand-label">{{tr "Platform"}}:</span> <span class="node-cell-expand-label">{{tr "Platform"}}:</span>
@#node.host.Platform#@-@#node.host.PlatformVersion#@ <span v-if="node.host.Platform">@#node.host.Platform#@@#node.host.PlatformVersion ? '-' + node.host.PlatformVersion : ''#@</span>
[<span v-if="node.host.Virtualization">@#node.host.Virtualization#@:</span>@#node.host.Arch#@] <span v-if="node.host.Arch">[@#node.host.Virtualization ? node.host.Virtualization + ':' : ''#@@#node.host.Arch#@]</span>
</span> </span>
<span class="node-cell-expand" v-if="node.host.CPU"> <span class="node-cell-expand" v-if="node.host.CPU">
<span class="node-cell-expand-label">CPU:</span> <span class="node-cell-expand-label">CPU:</span>
+2
View File
@@ -819,6 +819,7 @@
switch (cycle) { switch (cycle) {
case '月': case '月':
case 'mo':
case 'month': case 'month':
case 'monthly': case 'monthly':
case 'm': case 'm':
@@ -834,6 +835,7 @@
case 'h': case 'h':
return "H"; return "H";
case '年': case '年':
case 'yr':
case 'year': case 'year':
case 'annually': case 'annually':
case 'y': case 'y':
+1 -1
View File
@@ -155,8 +155,8 @@ func (s *NezhaHandler) ReportSystemInfo(c context.Context, r *pb.Host) (*pb.Rece
Ipv4Addr: ipv4, Ipv4Addr: ipv4,
Ipv6Addr: ipv6, Ipv6Addr: ipv6,
} }
go singleton.RetryableUpdateDomain(provider, config, maxRetries)
go singleton.RetryableUpdateDomain(provider, config, maxRetries)
} else { } else {
// 虽然会在启动时panic, 可以断言不会走这个分支, 但是考虑到动态加载配置或者其它情况, 这里输出一下方便检查奇奇怪怪的BUG // 虽然会在启动时panic, 可以断言不会走这个分支, 但是考虑到动态加载配置或者其它情况, 这里输出一下方便检查奇奇怪怪的BUG
log.Printf("NEZHA>> 未找到对应的DDNS配置(%s), 或者是provider填写不正确, 请前往config.yml检查你的设置", singleton.ServerList[clientID].DDNSProfile) log.Printf("NEZHA>> 未找到对应的DDNS配置(%s), 或者是provider填写不正确, 请前往config.yml检查你的设置", singleton.ServerList[clientID].DDNSProfile)
+2
View File
@@ -34,6 +34,7 @@ type CommonServerInfo struct {
IPV6 string `json:"ipv6"` IPV6 string `json:"ipv6"`
ValidIP string `json:"valid_ip"` ValidIP string `json:"valid_ip"`
DisplayIndex int `json:"display_index"` DisplayIndex int `json:"display_index"`
HideForGuest bool `json:"hide_for_guest"`
} }
// StatusResponse 服务器状态子结构 包含服务器信息与状态信息 // StatusResponse 服务器状态子结构 包含服务器信息与状态信息
@@ -150,6 +151,7 @@ func (s *ServerAPIService) GetAllStatus() *ServerStatusResponse {
IPV6: ipv6, IPV6: ipv6,
ValidIP: validIP, ValidIP: validIP,
DisplayIndex: v.DisplayIndex, DisplayIndex: v.DisplayIndex,
HideForGuest: v.HideForGuest,
} }
res.Result = append(res.Result, &StatusResponse{ res.Result = append(res.Result, &StatusResponse{
CommonServerInfo: info, CommonServerInfo: info,