diff --git a/README.md b/README.md
index 94e2521..ab195f8 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,12 @@
-
-

+
+
-

+
-
:trollface: 哪吒监控 一站式轻监控轻运维系统。支持系统状态、HTTP(SSL 证书变更、即将到期、到期)、TCP、Ping 监控报警,命令批量执行和计划任务。
+
+
:trollface: 哪吒监控 一站式轻监控轻运维系统。支持系统状态、HTTP(SSL 证书变更、即将到期、到期)、TCP、Ping 监控报警,命令批量执行和计划任务。
-\>> 交流论坛:[打杂社区](https://daza.net/c/nezha) (Lemmy)
-
\>> QQ 交流群:872069346 **加群要求:已搭建好哪吒监控 & 有 2+ 服务器**
\>> [我们的用户](https://www.google.com/search?q="powered+by+哪吒监控%7C哪吒面板"&filter=0) (Google)
@@ -102,6 +101,9 @@ URL 里面也可放置占位符,请求时会进行简单的字符串替换。
- net_in_speed(入站网速)、net_out_speed(出站网速)、net_all_speed(双向网速)、transfer_in(入站流量)、transfer_out(出站流量)、transfer_all(双向流量):Min/Max 数值为字节(1kb=1024,1mb = 1024\*1024)
- offline:不支持 Min/Max 参数
- Duration:持续秒数,监控比较简陋,取持续时间内的 70% 采样结果
+- Cover
+ - `0` 监控所有,通过 `Ignore` 忽略特定服务器
+ - `1` 忽略所有,通过 `Ignore` 监控特定服务器
- Ignore: `{"1": true, "2":false}` 忽略此规则的服务器 ID 列表,比如忽略服务器 ID 5 的离线通知 `[{"Type":"offline","Duration":10, "Ignore":{"5": true}}]`
diff --git a/cmd/agent/monitor/myip.go b/cmd/agent/monitor/myip.go
index 4da05e5..cdf8962 100644
--- a/cmd/agent/monitor/myip.go
+++ b/cmd/agent/monitor/myip.go
@@ -1,18 +1,13 @@
package monitor
import (
- "context"
"encoding/json"
- "errors"
"fmt"
"io/ioutil"
- "net"
"net/http"
- "strings"
- "sync"
"time"
- "github.com/miekg/dns"
+ "github.com/naiba/nezha/pkg/utils"
)
type geoIP struct {
@@ -24,14 +19,16 @@ var (
ipv4Servers = []string{
"https://api-ipv4.ip.sb/geoip",
"https://ip4.seeip.org/geoip",
+ "https://ipapi.co/json",
}
ipv6Servers = []string{
"https://ip6.seeip.org/geoip",
"https://api-ipv6.ip.sb/geoip",
+ "https://ipapi.co/json",
}
cachedIP, cachedCountry string
- httpClientV4 = newHTTPClient(time.Second*20, time.Second*5, time.Second*10, false)
- httpClientV6 = newHTTPClient(time.Second*20, time.Second*5, time.Second*10, true)
+ httpClientV4 = utils.NewSingleStackHTTPClient(time.Second*20, time.Second*5, time.Second*10, false)
+ httpClientV6 = utils.NewSingleStackHTTPClient(time.Second*20, time.Second*5, time.Second*10, true)
)
func UpdateIP() {
@@ -73,93 +70,3 @@ func fetchGeoIP(servers []string, isV6 bool) geoIP {
}
return ip
}
-
-func newHTTPClient(httpTimeout, dialTimeout, keepAliveTimeout time.Duration, ipv6 bool) *http.Client {
- dialer := &net.Dialer{
- Timeout: dialTimeout,
- KeepAlive: keepAliveTimeout,
- }
-
- transport := &http.Transport{
- Proxy: http.ProxyFromEnvironment,
- ForceAttemptHTTP2: false,
- DialContext: func(ctx context.Context, network string, addr string) (net.Conn, error) {
- ip, err := resolveIP(addr, ipv6)
- if err != nil {
- return nil, err
- }
- return dialer.DialContext(ctx, network, ip)
- },
- }
-
- return &http.Client{
- Transport: transport,
- Timeout: httpTimeout,
- }
-}
-
-func resolveIP(addr string, ipv6 bool) (string, error) {
- url := strings.Split(addr, ":")
-
- m := new(dns.Msg)
- if ipv6 {
- m.SetQuestion(dns.Fqdn(url[0]), dns.TypeAAAA)
- } else {
- m.SetQuestion(dns.Fqdn(url[0]), dns.TypeA)
- }
- m.RecursionDesired = true
-
- dnsServers := []string{"2606:4700:4700::1001", "2001:4860:4860::8844"}
- if !ipv6 {
- dnsServers = []string{"1.0.0.1", "8.8.4.4"}
- }
-
- var wg sync.WaitGroup
- var resolveLock sync.RWMutex
- var ipv4Resolved, ipv6Resolved bool
-
- wg.Add(len(dnsServers))
- for i := 0; i < len(dnsServers); i++ {
- go func(i int) {
- defer wg.Done()
- c := new(dns.Client)
- c.Timeout = time.Second * 3
- r, _, err := c.Exchange(m, net.JoinHostPort(dnsServers[i], "53"))
- if err != nil {
- return
- }
- resolveLock.Lock()
- defer resolveLock.Unlock()
- if ipv6 && ipv6Resolved {
- return
- }
- if !ipv6 && ipv4Resolved {
- return
- }
- for _, ans := range r.Answer {
- if ipv6 {
- if aaaa, ok := ans.(*dns.AAAA); ok {
- url[0] = "[" + aaaa.AAAA.String() + "]"
- ipv6Resolved = true
- }
- } else {
- if a, ok := ans.(*dns.A); ok {
- url[0] = a.A.String()
- ipv4Resolved = true
- }
- }
- }
- }(i)
- }
- wg.Wait()
-
- if ipv6 && !ipv6Resolved {
- return "", errors.New("the AAAA record not resolved")
- }
-
- if !ipv6 && !ipv4Resolved {
- return "", errors.New("the A record not resolved")
- }
-
- return strings.Join(url, ":"), nil
-}
diff --git a/cmd/dashboard/controller/member_api.go b/cmd/dashboard/controller/member_api.go
index 09b5c50..c39ef07 100644
--- a/cmd/dashboard/controller/member_api.go
+++ b/cmd/dashboard/controller/member_api.go
@@ -15,7 +15,6 @@ import (
"github.com/naiba/nezha/model"
"github.com/naiba/nezha/pkg/mygin"
"github.com/naiba/nezha/pkg/utils"
- pb "github.com/naiba/nezha/proto"
"github.com/naiba/nezha/service/dao"
)
@@ -196,6 +195,7 @@ type monitorForm struct {
Name string
Target string
Type uint8
+ Cover uint8
Notify string
SkipServersRaw string
}
@@ -210,6 +210,7 @@ func (ma *memberAPI) addOrEditMonitor(c *gin.Context) {
m.Type = mf.Type
m.ID = mf.ID
m.SkipServersRaw = mf.SkipServersRaw
+ m.Cover = mf.Cover
m.Notify = mf.Notify == "on"
}
if err == nil {
@@ -239,6 +240,7 @@ type cronForm struct {
Scheduler string
Command string
ServersRaw string
+ Cover uint8
PushSuccessful string
}
@@ -253,6 +255,7 @@ func (ma *memberAPI) addOrEditCron(c *gin.Context) {
cr.ServersRaw = cf.ServersRaw
cr.PushSuccessful = cf.PushSuccessful == "on"
cr.ID = cf.ID
+ cr.Cover = cf.Cover
err = json.Unmarshal([]byte(cf.ServersRaw), &cr.Servers)
}
if err == nil {
@@ -281,21 +284,7 @@ func (ma *memberAPI) addOrEditCron(c *gin.Context) {
dao.Cron.Remove(crOld.CronID)
}
- cr.CronID, err = dao.Cron.AddFunc(cr.Scheduler, func() {
- dao.ServerLock.RLock()
- defer dao.ServerLock.RUnlock()
- for j := 0; j < len(cr.Servers); j++ {
- if dao.ServerList[cr.Servers[j]].TaskStream != nil {
- dao.ServerList[cr.Servers[j]].TaskStream.Send(&pb.Task{
- Id: cr.ID,
- Data: cr.Command,
- Type: model.TaskTypeCommand,
- })
- } else {
- dao.SendNotification(fmt.Sprintf("计划任务:%s,服务器:%s 离线,无法执行。", cr.Name, dao.ServerList[cr.Servers[j]].Name), false)
- }
- }
- })
+ cr.CronID, err = dao.Cron.AddFunc(cr.Scheduler, dao.CronTrigger(cr))
if err != nil {
panic(err)
}
@@ -318,7 +307,7 @@ func (ma *memberAPI) manualTrigger(c *gin.Context) {
return
}
- dao.CronTrigger(&cr)
+ dao.ManualTrigger(&cr)
c.JSON(http.StatusOK, model.Response{
Code: http.StatusOK,
diff --git a/cmd/dashboard/main.go b/cmd/dashboard/main.go
index 6680f56..ce3ae2a 100644
--- a/cmd/dashboard/main.go
+++ b/cmd/dashboard/main.go
@@ -1,7 +1,6 @@
package main
import (
- "fmt"
"time"
"github.com/patrickmn/go-cache"
@@ -12,7 +11,6 @@ import (
"github.com/naiba/nezha/cmd/dashboard/controller"
"github.com/naiba/nezha/cmd/dashboard/rpc"
"github.com/naiba/nezha/model"
- pb "github.com/naiba/nezha/proto"
"github.com/naiba/nezha/service/dao"
)
@@ -84,21 +82,13 @@ func loadCrons() {
var err error
for i := 0; i < len(crons); i++ {
cr := crons[i]
- cr.CronID, err = dao.Cron.AddFunc(cr.Scheduler, func() {
- dao.ServerLock.RLock()
- defer dao.ServerLock.RUnlock()
- for j := 0; j < len(cr.Servers); j++ {
- if dao.ServerList[cr.Servers[j]].TaskStream != nil {
- dao.ServerList[cr.Servers[j]].TaskStream.Send(&pb.Task{
- Id: cr.ID,
- Data: cr.Command,
- Type: model.TaskTypeCommand,
- })
- } else {
- dao.SendNotification(fmt.Sprintf("计划任务:%s,服务器:%s 离线,无法执行。", cr.Name, dao.ServerList[cr.Servers[j]].Name), false)
- }
- }
- })
+
+ crIgnoreMap := make(map[uint64]bool)
+ for j := 0; j < len(cr.Servers); j++ {
+ crIgnoreMap[cr.Servers[j]] = true
+ }
+
+ cr.CronID, err = dao.Cron.AddFunc(cr.Scheduler, dao.CronTrigger(cr))
if err != nil {
panic(err)
}
diff --git a/cmd/dashboard/rpc/rpc.go b/cmd/dashboard/rpc/rpc.go
index 19a3fb4..2a3bfd0 100644
--- a/cmd/dashboard/rpc/rpc.go
+++ b/cmd/dashboard/rpc/rpc.go
@@ -7,6 +7,7 @@ import (
"google.golang.org/grpc"
+ "github.com/naiba/nezha/model"
pb "github.com/naiba/nezha/proto"
"github.com/naiba/nezha/service/dao"
rpcService "github.com/naiba/nezha/service/rpc"
@@ -39,13 +40,21 @@ func DispatchTask(duration time.Duration) {
}
hasAliveAgent = false
}
- // 1. 如果此任务不可使用此服务器请求,跳过这个服务器(有些 IPv6 only 开了 NAT64 的机器请求 IPv4 总会出问题)
- // 2. 如果服务器不在线,跳过这个服务器
- if tasks[i].SkipServers[dao.SortedServerList[index].ID] || dao.SortedServerList[index].TaskStream == nil {
+
+ // 1. 如果服务器不在线,跳过这个服务器
+ if dao.SortedServerList[index].TaskStream == nil {
i--
index++
continue
}
+ // 2. 如果此任务不可使用此服务器请求,跳过这个服务器(有些 IPv6 only 开了 NAT64 的机器请求 IPv4 总会出问题)
+ if (tasks[i].Cover == model.MonitorCoverAll && tasks[i].SkipServers[dao.SortedServerList[index].ID]) ||
+ (tasks[i].Cover == model.MonitorCoverIgnoreAll && !tasks[i].SkipServers[dao.SortedServerList[index].ID]) {
+ i--
+ index++
+ continue
+ }
+
hasAliveAgent = true
dao.SortedServerList[index].TaskStream.Send(tasks[i].PB())
index++
diff --git a/model/alertrule.go b/model/alertrule.go
index 82bd686..61e0919 100644
--- a/model/alertrule.go
+++ b/model/alertrule.go
@@ -1,79 +1,11 @@
package model
import (
- "bytes"
"encoding/json"
- "fmt"
- "time"
"gorm.io/gorm"
)
-const (
- RuleCheckPass = 1
- RuleCheckFail = 0
-)
-
-type Rule struct {
- // 指标类型,cpu、memory、swap、disk、net_in_speed、net_out_speed
- // net_all_speed、transfer_in、transfer_out、transfer_all、offline
- Type string `json:"type,omitempty"`
- Min uint64 `json:"min,omitempty"` // 最小阈值 (百分比、字节 kb ÷ 1024)
- Max uint64 `json:"max,omitempty"` // 最大阈值 (百分比、字节 kb ÷ 1024)
- Duration uint64 `json:"duration,omitempty"` // 持续时间 (秒)
- Ignore map[uint64]bool `json:"ignore,omitempty"` //忽略此规则的ID列表
-}
-
-func percentage(used, total uint64) uint64 {
- if total == 0 {
- return 0
- }
- return used * 100 / total
-}
-
-// Snapshot 未通过规则返回 struct{}{}, 通过返回 nil
-func (u *Rule) Snapshot(server *Server) interface{} {
- if u.Ignore[server.ID] {
- return nil
- }
- var src uint64
- switch u.Type {
- case "cpu":
- src = uint64(server.State.CPU)
- case "memory":
- src = percentage(server.State.MemUsed, server.Host.MemTotal)
- case "swap":
- src = percentage(server.State.SwapUsed, server.Host.SwapTotal)
- case "disk":
- src = percentage(server.State.DiskUsed, server.Host.DiskTotal)
- case "net_in_speed":
- src = server.State.NetInSpeed
- case "net_out_speed":
- src = server.State.NetOutSpeed
- case "net_all_speed":
- src = server.State.NetOutSpeed + server.State.NetOutSpeed
- case "transfer_in":
- src = server.State.NetInTransfer
- case "transfer_out":
- src = server.State.NetOutTransfer
- case "transfer_all":
- src = server.State.NetOutTransfer + server.State.NetInTransfer
- case "offline":
- if server.LastActive.IsZero() {
- src = 0
- } else {
- src = uint64(server.LastActive.Unix())
- }
- }
-
- if u.Type == "offline" && uint64(time.Now().Unix())-src > 6 {
- return struct{}{}
- } else if (u.Max > 0 && src > u.Max) || (u.Min > 0 && src < u.Min) {
- return struct{}{}
- }
- return nil
-}
-
type AlertRule struct {
Common
Name string
@@ -103,8 +35,7 @@ func (r *AlertRule) Snapshot(server *Server) []interface{} {
return point
}
-func (r *AlertRule) Check(points [][]interface{}) (int, string) {
- var dist bytes.Buffer
+func (r *AlertRule) Check(points [][]interface{}) (int, bool) {
var max int
var count int
for i := 0; i < len(r.Rules); i++ {
@@ -125,11 +56,11 @@ func (r *AlertRule) Check(points [][]interface{}) (int, string) {
}
if fail/total > 0.7 {
count++
- dist.WriteString(fmt.Sprintf("%+v\n", r.Rules[i]))
+ break
}
}
if count == len(r.Rules) {
- return max, dist.String()
+ return max, false
}
- return max, ""
+ return max, true
}
diff --git a/model/cron.go b/model/cron.go
index d8ef23f..c03b7f0 100644
--- a/model/cron.go
+++ b/model/cron.go
@@ -8,6 +8,11 @@ import (
"gorm.io/gorm"
)
+const (
+ CronCoverIgnoreAll = iota
+ CronCoverAll
+)
+
type Cron struct {
Common
Name string
@@ -17,6 +22,7 @@ type Cron struct {
PushSuccessful bool // 推送成功的通知
LastExecutedAt time.Time // 最后一次执行时间
LastResult bool // 最后一次执行结果
+ Cover uint8
CronID cron.EntryID `gorn:"-"`
ServersRaw string
diff --git a/model/monitor.go b/model/monitor.go
index 49ceb3d..4003332 100644
--- a/model/monitor.go
+++ b/model/monitor.go
@@ -15,6 +15,11 @@ const (
TaskTypeCommand
)
+const (
+ MonitorCoverAll = iota
+ MonitorCoverIgnoreAll
+)
+
type Monitor struct {
Common
Name string
@@ -22,8 +27,8 @@ type Monitor struct {
Target string
SkipServersRaw string
Notify bool
-
- SkipServers map[uint64]bool `gorm:"-" json:"-"`
+ Cover uint8
+ SkipServers map[uint64]bool `gorm:"-" json:"-"`
}
func (m *Monitor) PB() *pb.Task {
diff --git a/model/rule.go b/model/rule.go
new file mode 100644
index 0000000..9731fae
--- /dev/null
+++ b/model/rule.go
@@ -0,0 +1,77 @@
+package model
+
+import "time"
+
+const (
+ RuleCoverAll = iota
+ RuleCoverIgnoreAll
+)
+
+type Rule struct {
+ // 指标类型,cpu、memory、swap、disk、net_in_speed、net_out_speed
+ // net_all_speed、transfer_in、transfer_out、transfer_all、offline
+ Type string `json:"type,omitempty"`
+ Min uint64 `json:"min,omitempty"` // 最小阈值 (百分比、字节 kb ÷ 1024)
+ Max uint64 `json:"max,omitempty"` // 最大阈值 (百分比、字节 kb ÷ 1024)
+ Duration uint64 `json:"duration,omitempty"` // 持续时间 (秒)
+ Cover uint64 `json:"cover,omitempty"` // 覆盖范围 RuleCoverAll/IgnoreAll
+ Ignore map[uint64]bool `json:"ignore,omitempty"` // 覆盖范围的排除
+}
+
+func percentage(used, total uint64) uint64 {
+ if total == 0 {
+ return 0
+ }
+ return used * 100 / total
+}
+
+// Snapshot 未通过规则返回 struct{}{}, 通过返回 nil
+func (u *Rule) Snapshot(server *Server) interface{} {
+ // 监控全部但是排除了此服务器
+ if u.Cover == RuleCoverAll && u.Ignore[server.ID] {
+ return nil
+ }
+ // 忽略全部但是指定监控了此服务器
+ if u.Cover == RuleCoverIgnoreAll && !u.Ignore[server.ID] {
+ return nil
+ }
+
+ var src uint64
+
+ switch u.Type {
+ case "cpu":
+ src = uint64(server.State.CPU)
+ case "memory":
+ src = percentage(server.State.MemUsed, server.Host.MemTotal)
+ case "swap":
+ src = percentage(server.State.SwapUsed, server.Host.SwapTotal)
+ case "disk":
+ src = percentage(server.State.DiskUsed, server.Host.DiskTotal)
+ case "net_in_speed":
+ src = server.State.NetInSpeed
+ case "net_out_speed":
+ src = server.State.NetOutSpeed
+ case "net_all_speed":
+ src = server.State.NetOutSpeed + server.State.NetOutSpeed
+ case "transfer_in":
+ src = server.State.NetInTransfer
+ case "transfer_out":
+ src = server.State.NetOutTransfer
+ case "transfer_all":
+ src = server.State.NetOutTransfer + server.State.NetInTransfer
+ case "offline":
+ if server.LastActive.IsZero() {
+ src = 0
+ } else {
+ src = uint64(server.LastActive.Unix())
+ }
+ }
+
+ if u.Type == "offline" && uint64(time.Now().Unix())-src > 6 {
+ return struct{}{}
+ } else if (u.Max > 0 && src > u.Max) || (u.Min > 0 && src < u.Min) {
+ return struct{}{}
+ }
+
+ return nil
+}
diff --git a/pkg/utils/http.go b/pkg/utils/http.go
new file mode 100644
index 0000000..3ae3a4c
--- /dev/null
+++ b/pkg/utils/http.go
@@ -0,0 +1,106 @@
+package utils
+
+import (
+ "context"
+ "errors"
+ "net"
+ "net/http"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/miekg/dns"
+)
+
+func NewSingleStackHTTPClient(httpTimeout, dialTimeout, keepAliveTimeout time.Duration, ipv6 bool) *http.Client {
+ dialer := &net.Dialer{
+ Timeout: dialTimeout,
+ KeepAlive: keepAliveTimeout,
+ }
+
+ transport := &http.Transport{
+ Proxy: http.ProxyFromEnvironment,
+ ForceAttemptHTTP2: false,
+ DialContext: func(ctx context.Context, network string, addr string) (net.Conn, error) {
+ ip, err := resolveIP(addr, ipv6)
+ if err != nil {
+ return nil, err
+ }
+ return dialer.DialContext(ctx, network, ip)
+ },
+ }
+
+ return &http.Client{
+ Transport: transport,
+ Timeout: httpTimeout,
+ }
+}
+
+func resolveIP(addr string, ipv6 bool) (string, error) {
+ url := strings.Split(addr, ":")
+
+ m := new(dns.Msg)
+ if ipv6 {
+ m.SetQuestion(dns.Fqdn(url[0]), dns.TypeAAAA)
+ } else {
+ m.SetQuestion(dns.Fqdn(url[0]), dns.TypeA)
+ }
+ m.RecursionDesired = true
+
+ dnsServers := []string{"2606:4700:4700::1001", "2001:4860:4860::8844", "2400:3200::1", "2400:3200:baba::1"}
+ if !ipv6 {
+ dnsServers = []string{"1.0.0.1", "8.8.4.4", "223.5.5.5", "223.6.6.6"}
+ }
+
+ var wg sync.WaitGroup
+ var resolveLock sync.RWMutex
+ var ipv4Resolved, ipv6Resolved bool
+
+ wg.Add(len(dnsServers) + 1)
+ go func() {
+
+ }()
+ for i := 0; i < len(dnsServers); i++ {
+ go func(i int) {
+ defer wg.Done()
+ c := new(dns.Client)
+ c.Timeout = time.Second * 3
+ r, _, err := c.Exchange(m, net.JoinHostPort(dnsServers[i], "53"))
+ if err != nil {
+ return
+ }
+ resolveLock.Lock()
+ defer resolveLock.Unlock()
+ if ipv6 && ipv6Resolved {
+ return
+ }
+ if !ipv6 && ipv4Resolved {
+ return
+ }
+ for _, ans := range r.Answer {
+ if ipv6 {
+ if aaaa, ok := ans.(*dns.AAAA); ok {
+ url[0] = "[" + aaaa.AAAA.String() + "]"
+ ipv6Resolved = true
+ }
+ } else {
+ if a, ok := ans.(*dns.A); ok {
+ url[0] = a.A.String()
+ ipv4Resolved = true
+ }
+ }
+ }
+ }(i)
+ }
+ wg.Wait()
+
+ if ipv6 && !ipv6Resolved {
+ return "", errors.New("the AAAA record not resolved")
+ }
+
+ if !ipv6 && !ipv4Resolved {
+ return "", errors.New("the A record not resolved")
+ }
+
+ return strings.Join(url, ":"), nil
+}
diff --git a/resource/static/brand.png b/resource/static/brand.png
index 5eaea20..7e760ea 100644
Binary files a/resource/static/brand.png and b/resource/static/brand.png differ
diff --git a/resource/static/main.js b/resource/static/main.js
index 30343fd..356142c 100644
--- a/resource/static/main.js
+++ b/resource/static/main.js
@@ -41,61 +41,53 @@ function showFormModal(modelSelector, formID, URL, getData) {
const data = getData
? getData()
: $(formID)
- .serializeArray()
- .reduce(function (obj, item) {
- // ID 类的数据
- if (
- item.name.endsWith("_id") ||
- item.name === "id" ||
- item.name === "ID" ||
- item.name === "RequestType" ||
- item.name === "RequestMethod" ||
- item.name === "DisplayIndex" ||
- item.name === "Type"
- ) {
- obj[item.name] = parseInt(item.value);
- } else {
- obj[item.name] = item.value;
- }
+ .serializeArray()
+ .reduce(function (obj, item) {
+ // ID 类的数据
+ if (
+ item.name.endsWith("_id") ||
+ item.name === "id" ||
+ item.name === "ID" ||
+ item.name === "RequestType" ||
+ item.name === "RequestMethod" ||
+ item.name === "DisplayIndex" ||
+ item.name === "Type" ||
+ item.name === "Cover"
+ ) {
+ obj[item.name] = parseInt(item.value);
+ } else {
+ obj[item.name] = item.value;
+ }
- if (item.name.endsWith("ServersRaw")) {
- if (item.value.length > 2) {
- obj[item.name] = JSON.stringify(
- [...item.value.matchAll(/\d+/gm)].map((k) =>
- parseInt(k[0])
- )
- );
- }
+ if (item.name.endsWith("ServersRaw")) {
+ if (item.value.length > 2) {
+ obj[item.name] = JSON.stringify(
+ [...item.value.matchAll(/\d+/gm)].map((k) =>
+ parseInt(k[0])
+ )
+ );
}
+ }
- return obj;
- }, {});
+ return obj;
+ }, {});
$.post(URL, JSON.stringify(data))
.done(function (resp) {
if (resp.code == 200) {
- if (resp.message) {
- $.suiAlert({
- title: "操作成功",
- type: "success",
- description: resp.message,
- time: "3",
- position: "top-center",
- });
- }
- window.location.reload();
+ window.location.reload()
} else {
form.append(
`
`
+ resp.message +
+ `
`
);
}
})
.fail(function (err) {
form.append(
`