diff --git a/backend/api/api.go b/backend/api/api.go
index dd596ed..f562b08 100644
--- a/backend/api/api.go
+++ b/backend/api/api.go
@@ -1,9 +1,11 @@
package api
import (
+ "encoding/json"
"s-ui/logger"
"s-ui/service"
"s-ui/util"
+ "s-ui/util/common"
"strconv"
"strings"
@@ -17,6 +19,8 @@ type APIHandler struct {
service.ClientService
service.TlsService
service.InboundService
+ service.OutboundService
+ service.EndpointService
service.PanelService
service.StatsService
service.ServerService
@@ -43,6 +47,7 @@ func (a *APIHandler) postHandler(c *gin.Context) {
var err error
action := c.Param("postAction")
remoteIP := getRemoteIp(c)
+ loginUser := GetLoginUser(c)
switch action {
case "login":
@@ -79,13 +84,21 @@ func (a *APIHandler) postHandler(c *gin.Context) {
jsonMsg(c, "", err)
}
case "save":
- loginUser := GetLoginUser(c)
- data := map[string]string{}
- err = c.ShouldBind(&data)
- if err == nil {
- err = a.ConfigService.SaveChanges(data, loginUser)
+ obj := c.Request.FormValue("object")
+ act := c.Request.FormValue("action")
+ data := c.Request.FormValue("data")
+ userLinks := c.Request.FormValue("userLinks")
+ outJsons := c.Request.FormValue("outJsons")
+ err = a.ConfigService.Save(obj, act, json.RawMessage(data), json.RawMessage(userLinks), json.RawMessage(outJsons), loginUser)
+ if err != nil {
+ jsonMsg(c, "save", err)
+ return
}
- jsonMsg(c, "save", err)
+ err = a.loadPartialData(c, obj, len(outJsons) > 5, len(userLinks) > 5)
+ if err != nil {
+ jsonMsg(c, obj, err)
+ }
+ return
case "restartApp":
err = a.PanelService.RestartPanel(3)
jsonMsg(c, "restartApp", err)
@@ -97,7 +110,7 @@ func (a *APIHandler) postHandler(c *gin.Context) {
result, _, err := util.GetOutbound(link, 0)
jsonObj(c, result, err)
default:
- jsonMsg(c, "API call", nil)
+ jsonMsg(c, "failed", common.NewError("unknown action: ", action))
}
}
@@ -119,6 +132,12 @@ func (a *APIHandler) getHandler(c *gin.Context) {
return
}
jsonObj(c, data, nil)
+ case "inbounds", "outbounds", "endpoints", "tls", "clients", "config":
+ err := a.loadPartialData(c, action, false, false)
+ if err != nil {
+ jsonMsg(c, action, err)
+ }
+ return
case "users":
users, err := a.UserService.GetUsers()
if err != nil {
@@ -170,7 +189,7 @@ func (a *APIHandler) getHandler(c *gin.Context) {
keypair := a.ServerService.GenKeypair(kType, options)
jsonObj(c, keypair, nil)
default:
- jsonMsg(c, "API call", nil)
+ jsonMsg(c, "failed", common.NewError("unknown action: ", action))
}
}
@@ -195,7 +214,7 @@ func (a *APIHandler) loadData(c *gin.Context) (interface{}, error) {
return "", err
}
if isUpdated {
- config, err := a.ConfigService.GetConfig()
+ config, err := a.SettingService.GetConfig()
if err != nil {
return "", err
}
@@ -211,14 +230,24 @@ func (a *APIHandler) loadData(c *gin.Context) (interface{}, error) {
if err != nil {
return "", err
}
+ outbounds, err := a.OutboundService.GetAll()
+ if err != nil {
+ return "", err
+ }
+ endpoints, err := a.EndpointService.GetAll()
+ if err != nil {
+ return "", err
+ }
subURI, err := a.SettingService.GetFinalSubURI(strings.Split(c.Request.Host, ":")[0])
if err != nil {
return "", err
}
- data["config"] = *config
+ data["config"] = json.RawMessage(config)
data["clients"] = clients
data["tls"] = tlsConfigs
data["inbounds"] = inbounds
+ data["outbounds"] = outbounds
+ data["endpoints"] = endpoints
data["subURI"] = subURI
data["onlines"] = onlines
} else {
@@ -227,3 +256,72 @@ func (a *APIHandler) loadData(c *gin.Context) (interface{}, error) {
return data, nil
}
+
+func (a *APIHandler) loadPartialData(c *gin.Context, obj string, plusInbounds bool, plusClients bool) error {
+ data := make(map[string]interface{}, 0)
+ switch obj {
+ case "inbounds":
+ id := c.Query("id")
+ inbounds, err := a.InboundService.Get(id)
+ if err != nil {
+ return err
+ }
+ data[obj] = inbounds
+ case "outbounds":
+ outbounds, err := a.OutboundService.GetAll()
+ if err != nil {
+ return err
+ }
+ data[obj] = outbounds
+ case "endpoints":
+ endpoints, err := a.EndpointService.GetAll()
+ if err != nil {
+ return err
+ }
+ data[obj] = endpoints
+ case "tls":
+ tlsConfigs, err := a.TlsService.GetAll()
+ if err != nil {
+ return err
+ }
+ data[obj] = tlsConfigs
+ case "clients":
+ clients, err := a.ClientService.GetAll()
+ if err != nil {
+ return err
+ }
+ data[obj] = clients
+ case "config":
+ config, err := a.SettingService.GetConfig()
+ if err != nil {
+ return err
+ }
+ data[obj] = json.RawMessage(config)
+ }
+
+ if plusInbounds {
+ inbounds, err := a.InboundService.GetAll()
+ if err != nil {
+ return err
+ }
+ data["inbounds"] = inbounds
+ }
+ if plusClients {
+ clients, err := a.ClientService.GetAll()
+ if err != nil {
+ return err
+ }
+ data["clients"] = clients
+ }
+ jsonObj(c, data, nil)
+ return nil
+}
+
+func (a *APIHandler) postActions(c *gin.Context) (string, json.RawMessage, error) {
+ var data map[string]json.RawMessage
+ err := c.ShouldBind(&data)
+ if err != nil {
+ return "", nil, err
+ }
+ return string(data["action"]), data["data"], nil
+}
diff --git a/backend/api/utils.go b/backend/api/utils.go
index 07aa2a7..ab19cb4 100644
--- a/backend/api/utils.go
+++ b/backend/api/utils.go
@@ -46,7 +46,7 @@ func jsonMsgObj(c *gin.Context, msg string, obj interface{}, err error) {
}
} else {
m.Success = false
- m.Msg = msg + err.Error()
+ m.Msg = msg + ": " + err.Error()
logger.Warning("failed :", err)
}
c.JSON(http.StatusOK, m)
diff --git a/backend/app/app.go b/backend/app/app.go
index c9762a0..04f1e30 100644
--- a/backend/app/app.go
+++ b/backend/app/app.go
@@ -43,7 +43,7 @@ func (a *APP) Init() error {
a.core = core.NewCore()
- a.cronJob = cronjob.NewCronJob(a.core)
+ a.cronJob = cronjob.NewCronJob()
a.webServer = web.NewServer()
a.subServer = sub.NewServer()
@@ -81,7 +81,7 @@ func (a *APP) Start() error {
return err
}
- err = a.configService.StartCore()
+ err = a.configService.StartCore("")
if err != nil {
logger.Error(err)
}
diff --git a/backend/cmd/migration/1_2.go b/backend/cmd/migration/1_2.go
index 47720ad..7d16d3f 100644
--- a/backend/cmd/migration/1_2.go
+++ b/backend/cmd/migration/1_2.go
@@ -60,9 +60,22 @@ func moveJsonToDb(db *gorm.DB) error {
} else {
tls_server, _ := json.MarshalIndent(tlsObj, "", " ")
if len(tls_server) > 5 {
+ tlsObject := tlsObj.(map[string]interface{})
+ tlsClientObj := map[string]interface{}{}
+ if enabled, ok := tlsObject["enabled"]; ok {
+ tlsClientObj["enabled"] = enabled
+ }
+ if alpn, ok := tlsObject["alpn"]; ok {
+ tlsClientObj["alpn"] = alpn
+ }
+ if sni, ok := tlsObject["server_name"]; ok {
+ tlsClientObj["server_name"] = sni
+ }
+ tls_client, _ := json.MarshalIndent(tlsClientObj, "", " ")
newTls := &model.Tls{
Name: tag,
Server: tls_server,
+ Client: tls_client,
}
err = db.Create(newTls).Error
if err != nil {
@@ -76,10 +89,10 @@ func moveJsonToDb(db *gorm.DB) error {
var inbData InboundData
db.Raw("select id,addrs,out_json from inbound_data where tag = ?", tag).Find(&inbData)
if inbData.Id > 0 {
- inbObj["outJson"] = inbData.OutJson
+ inbObj["out_json"] = inbData.OutJson
inbObj["addrs"] = inbData.Addrs
} else {
- inbObj["outJson"] = json.RawMessage("{}")
+ inbObj["out_json"] = json.RawMessage("{}")
inbObj["addrs"] = json.RawMessage("[]")
}
inbJson, _ := json.Marshal(inbObj)
diff --git a/backend/core/box.go b/backend/core/box.go
index 3287e73..da269ce 100644
--- a/backend/core/box.go
+++ b/backend/core/box.go
@@ -109,20 +109,16 @@ func NewBox(options Options) (*Box, error) {
defaultLogWriter = io.Discard
}
var logFactory log.Factory
- if factory == nil {
- logFactory, err = NewFactory(log.Options{
- Context: ctx,
- Options: sbCommon.PtrValueOrDefault(options.Log),
- DefaultWriter: defaultLogWriter,
- BaseTime: createdAt,
- })
- if err != nil {
- return nil, common.NewError("create log factory", err)
- }
- factory = logFactory
- } else {
- logFactory = factory
+ logFactory, err = NewFactory(log.Options{
+ Context: ctx,
+ Options: sbCommon.PtrValueOrDefault(options.Log),
+ DefaultWriter: defaultLogWriter,
+ BaseTime: createdAt,
+ })
+ if err != nil {
+ return nil, common.NewError("create log factory", err)
}
+ factory = logFactory
routeOptions := sbCommon.PtrValueOrDefault(options.Route)
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry)
@@ -158,7 +154,7 @@ func NewBox(options Options) (*Box, error) {
endpointOptions.Options,
)
if err != nil {
- return nil, common.NewError("initialize endpoint["+string(i)+"] "+tag, err)
+ return nil, common.NewError("initialize endpoint["+F.ToString(i)+"] "+tag, err)
}
}
for i, inboundOptions := range options.Inbounds {
@@ -202,7 +198,7 @@ func NewBox(options Options) (*Box, error) {
outboundOptions.Options,
)
if err != nil {
- return nil, common.NewError("initialize outbound["+string(i)+"] "+tag, err)
+ return nil, common.NewError("initialize outbound["+F.ToString(i)+"] "+tag, err)
}
}
outboundManager.Initialize(sbCommon.Must1(
@@ -368,7 +364,7 @@ func (s *Box) Close() error {
close(s.done)
}
err := sbCommon.Close(
- s.inbound, s.outbound, s.router, s.connection, s.network,
+ s.endpoint, s.inbound, s.outbound, s.router, s.connection, s.network,
)
for _, lifecycleService := range s.services {
err1 := lifecycleService.Close()
diff --git a/backend/core/endpoint.go b/backend/core/endpoint.go
index 4f0be03..d3c3a3c 100644
--- a/backend/core/endpoint.go
+++ b/backend/core/endpoint.go
@@ -58,7 +58,7 @@ func (c *Core) AddOutbound(config []byte) error {
factory.NewLogger("outbound/"+outbound_config.Type+"["+outbound_config.Tag+"]"),
outbound_config.Tag,
outbound_config.Type,
- outbound_config)
+ outbound_config.Options)
if err != nil {
return err
}
@@ -92,7 +92,7 @@ func (c *Core) AddEndpoint(config []byte) error {
factory.NewLogger("endpoint/"+endpoint_config.Type+"["+endpoint_config.Tag+"]"),
endpoint_config.Tag,
endpoint_config.Type,
- endpoint_config)
+ endpoint_config.Options)
if err != nil {
return err
}
diff --git a/backend/cronjob/checkCoreJob.go b/backend/cronjob/checkCoreJob.go
new file mode 100644
index 0000000..0f90613
--- /dev/null
+++ b/backend/cronjob/checkCoreJob.go
@@ -0,0 +1,17 @@
+package cronjob
+
+import (
+ "s-ui/service"
+)
+
+type CheckCoreJob struct {
+ service.ConfigService
+}
+
+func NewCheckCoreJob() *CheckCoreJob {
+ return &CheckCoreJob{}
+}
+
+func (s *CheckCoreJob) Run() {
+ s.ConfigService.StartCore("")
+}
diff --git a/backend/cronjob/cronJob.go b/backend/cronjob/cronJob.go
index 0a73f44..4bf3eff 100644
--- a/backend/cronjob/cronJob.go
+++ b/backend/cronjob/cronJob.go
@@ -1,7 +1,6 @@
package cronjob
import (
- "s-ui/core"
"time"
"github.com/robfig/cron/v3"
@@ -9,13 +8,10 @@ import (
type CronJob struct {
cron *cron.Cron
- Core *core.Core
}
-func NewCronJob(c *core.Core) *CronJob {
- return &CronJob{
- Core: c,
- }
+func NewCronJob() *CronJob {
+ return &CronJob{}
}
func (c *CronJob) Start(loc *time.Location, trafficAge int) error {
@@ -29,6 +25,8 @@ func (c *CronJob) Start(loc *time.Location, trafficAge int) error {
c.cron.AddJob("@every 1m", NewDepleteJob())
// Start deleting old stats
c.cron.AddJob("@daily", NewDelStatsJob(trafficAge))
+ // Start core if it is not running
+ c.cron.AddJob("@every 5s", NewCheckCoreJob())
}()
return nil
diff --git a/backend/cronjob/depleteJob.go b/backend/cronjob/depleteJob.go
index 0b0eb9b..d7adcffa 100644
--- a/backend/cronjob/depleteJob.go
+++ b/backend/cronjob/depleteJob.go
@@ -6,7 +6,7 @@ import (
)
type DepleteJob struct {
- service.ConfigService
+ service.ClientService
}
func NewDepleteJob() *DepleteJob {
@@ -14,7 +14,7 @@ func NewDepleteJob() *DepleteJob {
}
func (s *DepleteJob) Run() {
- err := s.ConfigService.DepleteClients()
+ err := s.ClientService.DepleteClients()
if err != nil {
logger.Warning("Disable depleted users failed: ", err)
return
diff --git a/backend/database/model/endpoints.go b/backend/database/model/endpoints.go
index d6eae27..43de727 100644
--- a/backend/database/model/endpoints.go
+++ b/backend/database/model/endpoints.go
@@ -1,11 +1,13 @@
package model
-import "encoding/json"
+import (
+ "encoding/json"
+)
type Endpoint struct {
Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
Type string `json:"type" form:"type"`
- Tag string `json:"tag" form:"tag"`
+ Tag string `json:"tag" form:"tag" gorm:"unique"`
Options json.RawMessage `json:"-" form:"-"`
}
@@ -17,10 +19,10 @@ func (o *Endpoint) UnmarshalJSON(data []byte) error {
}
// Extract fixed fields and store the rest in Options
- if val, exists := raw["id"]; exists {
- o.Id = val.(uint)
- delete(raw, "id")
+ if val, exists := raw["id"].(float64); exists {
+ o.Id = uint(val)
}
+ delete(raw, "id")
o.Type, _ = raw["type"].(string)
delete(raw, "type")
o.Tag = raw["tag"].(string)
diff --git a/backend/database/model/inbounds.go b/backend/database/model/inbounds.go
index 56c0c7c..4b3229b 100644
--- a/backend/database/model/inbounds.go
+++ b/backend/database/model/inbounds.go
@@ -7,14 +7,14 @@ import (
type Inbound struct {
Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
Type string `json:"type" form:"type"`
- Tag string `json:"tag" form:"tag"`
+ Tag string `json:"tag" form:"tag" gorm:"unique"`
// Foreign key to tls table
TlsId uint `json:"tls_id" form:"tls_id"`
Tls *Tls `json:"tls" form:"tls" gorm:"foreignKey:TlsId;references:Id"`
Addrs json.RawMessage `json:"addrs" form:"addrs"`
- OutJson json.RawMessage `json:"outJson" form:"outJson"`
+ OutJson json.RawMessage `json:"out_json" form:"out_json"`
Options json.RawMessage `json:"-" form:"-"`
}
@@ -26,10 +26,10 @@ func (i *Inbound) UnmarshalJSON(data []byte) error {
}
// Extract fixed fields and store the rest in Options
- if val, exists := raw["id"].(uint); exists {
- i.Id = val
- delete(raw, "id")
+ if val, exists := raw["id"].(float64); exists {
+ i.Id = uint(val)
}
+ delete(raw, "id")
i.Type, _ = raw["type"].(string)
delete(raw, "type")
i.Tag, _ = raw["tag"].(string)
@@ -48,8 +48,8 @@ func (i *Inbound) UnmarshalJSON(data []byte) error {
delete(raw, "addrs")
// OutJson
- i.OutJson, _ = json.MarshalIndent(raw["outJson"], "", " ")
- delete(raw, "outJson")
+ i.OutJson, _ = json.MarshalIndent(raw["out_json"], "", " ")
+ delete(raw, "out_json")
// Remaining fields
i.Options, err = json.MarshalIndent(raw, "", " ")
@@ -79,3 +79,25 @@ func (i Inbound) MarshalJSON() ([]byte, error) {
return json.Marshal(combined)
}
+
+func (i Inbound) MarshalFull() (*map[string]interface{}, error) {
+ combined := make(map[string]interface{})
+ combined["id"] = i.Id
+ combined["type"] = i.Type
+ combined["tag"] = i.Tag
+ combined["tls_id"] = i.TlsId
+ combined["addrs"] = i.Addrs
+ combined["out_json"] = i.OutJson
+
+ if i.Options != nil {
+ var restFields map[string]json.RawMessage
+ if err := json.Unmarshal(i.Options, &restFields); err != nil {
+ return nil, err
+ }
+
+ for k, v := range restFields {
+ combined[k] = v
+ }
+ }
+ return &combined, nil
+}
diff --git a/backend/database/model/outbounds.go b/backend/database/model/outbounds.go
index 0ae84ab..75b6fc2 100644
--- a/backend/database/model/outbounds.go
+++ b/backend/database/model/outbounds.go
@@ -5,7 +5,7 @@ import "encoding/json"
type Outbound struct {
Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
Type string `json:"type" form:"type"`
- Tag string `json:"tag" form:"tag"`
+ Tag string `json:"tag" form:"tag" gorm:"unique"`
Options json.RawMessage `json:"-" form:"-"`
}
@@ -17,10 +17,10 @@ func (o *Outbound) UnmarshalJSON(data []byte) error {
}
// Extract fixed fields and store the rest in Options
- if val, exists := raw["id"]; exists {
- o.Id = val.(uint)
- delete(raw, "id")
+ if val, exists := raw["id"].(float64); exists {
+ o.Id = uint(val)
}
+ delete(raw, "id")
o.Type, _ = raw["type"].(string)
delete(raw, "type")
o.Tag = raw["tag"].(string)
diff --git a/backend/go.mod b/backend/go.mod
index ad867e6..221d5d7 100644
--- a/backend/go.mod
+++ b/backend/go.mod
@@ -7,8 +7,8 @@ require (
github.com/gin-gonic/gin v1.10.0
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/robfig/cron/v3 v3.0.1
- github.com/sagernet/sing v0.6.0-beta.8
- github.com/sagernet/sing-box v1.11.0-beta.11
+ github.com/sagernet/sing v0.6.0-beta.9
+ github.com/sagernet/sing-box v1.11.0-beta.19
github.com/sagernet/sing-dns v0.4.0-beta.1
github.com/shirou/gopsutil/v3 v3.24.5
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
@@ -88,11 +88,11 @@ require (
github.com/sagernet/quic-go v0.48.2-beta.1 // indirect
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect
github.com/sagernet/sing-mux v0.3.0-alpha.1 // indirect
- github.com/sagernet/sing-quic v0.4.0-alpha.4 // indirect
+ github.com/sagernet/sing-quic v0.4.0-beta.2 // indirect
github.com/sagernet/sing-shadowsocks v0.2.7 // indirect
github.com/sagernet/sing-shadowsocks2 v0.2.0 // indirect
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 // indirect
- github.com/sagernet/sing-tun v0.6.0-beta.6 // indirect
+ github.com/sagernet/sing-tun v0.6.0-beta.7.0.20241229131914-aa9d9c62966f // indirect
github.com/sagernet/sing-vmess v0.2.0-beta.1 // indirect
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
github.com/sagernet/utls v1.6.7 // indirect
@@ -110,13 +110,13 @@ require (
go.uber.org/zap v1.27.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/arch v0.11.0 // indirect
- golang.org/x/crypto v0.29.0 // indirect
+ golang.org/x/crypto v0.31.0 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/mod v0.20.0 // indirect
golang.org/x/net v0.31.0 // indirect
- golang.org/x/sync v0.9.0 // indirect
- golang.org/x/sys v0.27.0 // indirect
- golang.org/x/text v0.20.0 // indirect
+ golang.org/x/sync v0.10.0 // indirect
+ golang.org/x/sys v0.28.0 // indirect
+ golang.org/x/text v0.21.0 // indirect
golang.org/x/time v0.7.0 // indirect
golang.org/x/tools v0.24.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
diff --git a/backend/go.sum b/backend/go.sum
index 2bc2b2a..7082940 100644
--- a/backend/go.sum
+++ b/backend/go.sum
@@ -174,16 +174,24 @@ github.com/sagernet/sing v0.6.0-beta.5 h1:RD2j8WmJsvAbbBkAlJWaiYmnd+v/JohBiweoew
github.com/sagernet/sing v0.6.0-beta.5/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing v0.6.0-beta.8 h1:PoxDdN7y8D4oImT3cQ05Sq1ZYnYsJberkUkIEHIGwWE=
github.com/sagernet/sing v0.6.0-beta.8/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
+github.com/sagernet/sing v0.6.0-beta.9 h1:P8lKa5hN53fRNAVCIKy5cWd6/kLO5c4slhdsfehSmHs=
+github.com/sagernet/sing v0.6.0-beta.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-box v1.11.0-beta.6 h1:MPdL2Yem/xM0RhejCO7krYvl1Zbd1zkSjKluKpHnHPQ=
github.com/sagernet/sing-box v1.11.0-beta.6/go.mod h1:6dO5V0A37cLlhvKnxCmZinSpZXz7ZSk11x3rgI+xH1I=
github.com/sagernet/sing-box v1.11.0-beta.11 h1:bVR0n3oQ3hGcuc/CSS7axsOeRNCRlCGkYVOKl0wxbsw=
github.com/sagernet/sing-box v1.11.0-beta.11/go.mod h1:GZnZUzUHZ6Bgm7D/i8unNORv3537u1s03tLXFdxCRpg=
+github.com/sagernet/sing-box v1.11.0-beta.15 h1:oWcs/PHgKaeWKbTfgz/020KEVvDqQv/tQWe7zpyktkc=
+github.com/sagernet/sing-box v1.11.0-beta.15/go.mod h1:+QZDsF4HkdiGcMfz+JNOfONLh9CnZjIwJJQNWEzhiaQ=
+github.com/sagernet/sing-box v1.11.0-beta.19 h1:uL2xlXpz4t7BduLbXiLe5QqpyiMhvNNRThBzhTJ4p00=
+github.com/sagernet/sing-box v1.11.0-beta.19/go.mod h1:UXUN/lwRT9mAM8PK7upPOwgqooOV2vU+CcjBfwT1rYg=
github.com/sagernet/sing-dns v0.4.0-beta.1 h1:W1XkdhigwxDOMgMDVB+9kdomCpb7ExsZfB4acPcTZFY=
github.com/sagernet/sing-dns v0.4.0-beta.1/go.mod h1:8wuFcoFkWM4vJuQyg8e97LyvDwe0/Vl7G839WLcKDs8=
github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg=
github.com/sagernet/sing-mux v0.3.0-alpha.1/go.mod h1:FTcImmdfW38Lz7b+HQ+mxxOth1lz4ao8uEnz+MwIJQE=
github.com/sagernet/sing-quic v0.4.0-alpha.4 h1:P9xAx3nIfcqb9M8jfgs0uLm+VxCcaY++FCqaBfHY3dQ=
github.com/sagernet/sing-quic v0.4.0-alpha.4/go.mod h1:h5RkKTmUhudJKzK7c87FPXD5w1bJjVyxMN9+opZcctA=
+github.com/sagernet/sing-quic v0.4.0-beta.2 h1:ikoQ7zTR0o/2rlI5H5FeNC0j5bQJJHb1uoyXFRu3yGk=
+github.com/sagernet/sing-quic v0.4.0-beta.2/go.mod h1:1UNObFodd8CnS3aCT53x9cigjPSCl3P//8dfBMCwBDM=
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg=
@@ -194,6 +202,10 @@ github.com/sagernet/sing-tun v0.6.0-beta.2 h1:GK7r2jWKm7RhlJGTq4QadgFcebQia1c3BO
github.com/sagernet/sing-tun v0.6.0-beta.2/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
github.com/sagernet/sing-tun v0.6.0-beta.6 h1:xaIHoH78MqTSvZqQ4SQto8pC1A+X4qXReDRNaC8DQeI=
github.com/sagernet/sing-tun v0.6.0-beta.6/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
+github.com/sagernet/sing-tun v0.6.0-beta.7 h1:FCSX8oGBqb0H57AAvfGeeH/jMGYWCOg6XWkN/oeES+0=
+github.com/sagernet/sing-tun v0.6.0-beta.7/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
+github.com/sagernet/sing-tun v0.6.0-beta.7.0.20241229131914-aa9d9c62966f h1:dTnXP0e3LbSa4EpUmuOGhllanKPei4vPKfzlLvk76Pc=
+github.com/sagernet/sing-tun v0.6.0-beta.7.0.20241229131914-aa9d9c62966f/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
github.com/sagernet/sing-vmess v0.2.0-beta.1 h1:5sXQ23uwNlZuDvygzi0dFtnG0Csm/SNqTjAHXJkpuj4=
github.com/sagernet/sing-vmess v0.2.0-beta.1/go.mod h1:fLyE1emIcvQ5DV8reFWnufquZ7MkCSYM5ThodsR9NrQ=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
@@ -254,6 +266,8 @@ golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
+golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
+golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
@@ -264,6 +278,8 @@ golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
+golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -274,13 +290,18 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
+golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
+golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
+golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
+golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
diff --git a/backend/logger/logger.go b/backend/logger/logger.go
index 6e30b4b..49f2ac0 100644
--- a/backend/logger/logger.go
+++ b/backend/logger/logger.go
@@ -26,7 +26,7 @@ func InitLogger(level logging.Level) {
backend, err = logging.NewSyslogBackend("")
if err != nil {
- println("Unable to use syslog: " + err.Error())
+ fmt.Println("Unable to use syslog: " + err.Error())
backend = logging.NewLogBackend(os.Stderr, "", 0)
}
if config.IsSystemd() && err != nil {
diff --git a/backend/service/client.go b/backend/service/client.go
index f66835d..d3798c4 100644
--- a/backend/service/client.go
+++ b/backend/service/client.go
@@ -11,6 +11,7 @@ import (
)
type ClientService struct {
+ InboundService
}
func (s *ClientService) GetAll() ([]model.Client, error) {
@@ -23,48 +24,117 @@ func (s *ClientService) GetAll() ([]model.Client, error) {
return clients, nil
}
-func (s *ClientService) Save(tx *gorm.DB, changes []model.Changes) error {
+func (s *ClientService) Save(tx *gorm.DB, act string, data json.RawMessage) ([]uint, error) {
var err error
- for _, change := range changes {
- client := model.Client{}
- err = json.Unmarshal(change.Obj, &client)
+ var inboundIds []uint
+
+ switch act {
+ case "new", "edit":
+ var client model.Client
+ err = json.Unmarshal(data, &client)
+ if err != nil {
+ return nil, err
+ }
+ err = json.Unmarshal(client.Inbounds, &inboundIds)
+ if err != nil {
+ return nil, err
+ }
+ err = tx.Save(&client).Error
+ if err != nil {
+ return nil, err
+ }
+ case "del":
+ var id uint
+ err = json.Unmarshal(data, &id)
+ if err != nil {
+ return nil, err
+ }
+ var client model.Client
+ err = tx.Where("id = ?", id).First(&client).Error
+ if err != nil {
+ return nil, err
+ }
+ err = json.Unmarshal(client.Inbounds, &inboundIds)
+ if err != nil {
+ return nil, err
+ }
+ err = tx.Where("id = ?", id).Delete(model.Client{}).Error
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return inboundIds, nil
+}
+
+func (s *ClientService) UpdateLinks(tx *gorm.DB, links json.RawMessage) error {
+ var userLinks []interface{}
+ err := json.Unmarshal(links, &userLinks)
+ if err != nil {
+ return err
+ }
+ for _, userLink := range userLinks {
+ userLinkData, _ := userLink.(map[string]interface{})
+ userId, _ := userLinkData["id"].(float64)
+ links, err := json.MarshalIndent(userLinkData["links"], "", " ")
if err != nil {
return err
}
- switch change.Action {
- case "new":
- err = tx.Create(&client).Error
- case "del":
- err = tx.Where("id = ?", change.Index).Delete(model.Client{}).Error
- default:
- err = tx.Save(client).Error
+ if inbounds, ok := userLinkData["inbounds"]; ok {
+ inbounds, err := json.MarshalIndent(inbounds, "", " ")
+ if err != nil {
+ return err
+ }
+ err = tx.Model(model.Client{}).Where("id = ?", uint(userId)).Update("inbounds", inbounds).Error
+ if err != nil {
+ return err
+ }
}
+ err = tx.Model(model.Client{}).Where("id = ?", uint(userId)).Update("links", links).Error
if err != nil {
return err
}
}
- return err
+ return nil
}
-func (s *ClientService) DepleteClients() ([]string, []string, error) {
+func (s *ClientService) DepleteClients() error {
var err error
var clients []model.Client
var changes []model.Changes
+ var users []string
+ var inboundIds []uint
+
now := time.Now().Unix()
db := database.GetDB()
- err = db.Model(model.Client{}).Where("enable = true AND ((volume >0 AND up+down > volume) OR (expiry > 0 AND expiry < ?))", now).Scan(&clients).Error
+
+ tx := db.Begin()
+ defer func() {
+ if err == nil {
+ tx.Commit()
+ if len(inboundIds) > 0 && corePtr.IsRunning() {
+ err1 := s.InboundService.RestartInbounds(tx, inboundIds)
+ if err1 != nil {
+ logger.Error("unable to restart inbounds: ", err1)
+ }
+ }
+ } else {
+ tx.Rollback()
+ }
+ }()
+
+ err = tx.Model(model.Client{}).Where("enable = true AND ((volume >0 AND up+down > volume) OR (expiry > 0 AND expiry < ?))", now).Scan(&clients).Error
if err != nil {
- return nil, nil, err
+ return err
}
dt := time.Now().Unix()
- var users, inbounds []string
for _, client := range clients {
logger.Debug("Client ", client.Name, " is going to be disabled")
users = append(users, client.Name)
- var userInbounds []string
+ var userInbounds []uint
json.Unmarshal(client.Inbounds, &userInbounds)
- inbounds = append(inbounds, userInbounds...)
+ inboundIds = s.uniqueAppendInboundIds(inboundIds, userInbounds)
changes = append(changes, model.Changes{
DateTime: dt,
Actor: "DepleteJob",
@@ -76,16 +146,32 @@ func (s *ClientService) DepleteClients() ([]string, []string, error) {
// Save changes
if len(changes) > 0 {
- err = db.Model(model.Client{}).Where("enable = true AND ((volume >0 AND up+down > volume) OR (expiry > 0 AND expiry < ?))", now).Update("enable", false).Error
+ err = tx.Model(model.Client{}).Where("enable = true AND ((volume >0 AND up+down > volume) OR (expiry > 0 AND expiry < ?))", now).Update("enable", false).Error
if err != nil {
- return nil, nil, err
+ return err
}
- err = db.Model(model.Changes{}).Create(&changes).Error
+ err = tx.Model(model.Changes{}).Create(&changes).Error
if err != nil {
- return nil, nil, err
+ return err
}
LastUpdate = dt
}
- return users, inbounds, nil
+ return nil
+}
+
+// avoid duplicate inboundIds
+func (s *ClientService) uniqueAppendInboundIds(a []uint, b []uint) []uint {
+ m := make(map[uint]bool)
+ for _, v := range a {
+ m[v] = true
+ }
+ for _, v := range b {
+ m[v] = true
+ }
+ var res []uint
+ for k := range m {
+ res = append(res, k)
+ }
+ return res
}
diff --git a/backend/service/config.go b/backend/service/config.go
index 754de3d..78d1c4a 100644
--- a/backend/service/config.go
+++ b/backend/service/config.go
@@ -2,12 +2,12 @@ package service
import (
"encoding/json"
- "os"
"s-ui/config"
"s-ui/core"
"s-ui/database"
"s-ui/database/model"
"s-ui/logger"
+ "s-ui/util/common"
"strconv"
"time"
)
@@ -48,10 +48,13 @@ func (s *ConfigService) InitConfig() error {
return nil
}
-func (s *ConfigService) GetConfig() (*SingBoxConfig, error) {
- data, err := s.SettingService.GetConfig()
- if err != nil {
- return nil, err
+func (s *ConfigService) GetConfig(data string) (*SingBoxConfig, error) {
+ var err error
+ if len(data) == 0 {
+ data, err = s.SettingService.GetConfig()
+ if err != nil {
+ return nil, err
+ }
}
singboxConfig := SingBoxConfig{}
err = json.Unmarshal([]byte(data), &singboxConfig)
@@ -74,8 +77,11 @@ func (s *ConfigService) GetConfig() (*SingBoxConfig, error) {
return &singboxConfig, nil
}
-func (s *ConfigService) StartCore() error {
- singboxConfig, err := s.GetConfig()
+func (s *ConfigService) StartCore(defaultConfig string) error {
+ if corePtr.IsRunning() {
+ return nil
+ }
+ singboxConfig, err := s.GetConfig(defaultConfig)
if err != nil {
return err
}
@@ -93,11 +99,19 @@ func (s *ConfigService) StartCore() error {
}
func (s *ConfigService) RestartCore() error {
- err := s.StartCore()
+ err := s.StopCore()
if err != nil {
return err
}
- return s.StartCore()
+ return s.StartCore("")
+}
+
+func (s *ConfigService) restartCoreWithConfig(config json.RawMessage) error {
+ err := s.StopCore()
+ if err != nil {
+ return err
+ }
+ return s.StartCore(string(config))
}
func (s *ConfigService) StopCore() error {
@@ -109,220 +123,80 @@ func (s *ConfigService) StopCore() error {
return nil
}
-func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string) error {
+func (s *ConfigService) Save(obj string, act string, data json.RawMessage, userLinks json.RawMessage, outJsons json.RawMessage, loginUser string) error {
var err error
- var clientChanges, tlsChanges, inChanges, settingChanges, configChanges []model.Changes
- if _, ok := changes["clients"]; ok {
- err = json.Unmarshal([]byte(changes["clients"]), &clientChanges)
- if err != nil {
- return err
- }
- }
- if _, ok := changes["tls"]; ok {
- err = json.Unmarshal([]byte(changes["tls"]), &tlsChanges)
- if err != nil {
- return err
- }
- }
- if _, ok := changes["inData"]; ok {
- err = json.Unmarshal([]byte(changes["inData"]), &inChanges)
- if err != nil {
- return err
- }
- }
- if _, ok := changes["settings"]; ok {
- err = json.Unmarshal([]byte(changes["settings"]), &settingChanges)
- if err != nil {
- return err
- }
- }
- if _, ok := changes["config"]; ok {
- err = json.Unmarshal([]byte(changes["config"]), &configChanges)
- if err != nil {
- return err
- }
- }
+ var inboundIds []uint
db := database.GetDB()
tx := db.Begin()
defer func() {
if err == nil {
tx.Commit()
+ if len(inboundIds) > 0 && corePtr.IsRunning() {
+ err1 := s.InboundService.RestartInbounds(tx, inboundIds)
+ if err1 != nil {
+ logger.Error("unable to restart inbounds: ", err1)
+ }
+ }
+ // Try to start core if it is not running
+ if !corePtr.IsRunning() {
+ s.StartCore("")
+ }
} else {
tx.Rollback()
}
}()
- if len(clientChanges) > 0 {
- err = s.ClientService.Save(tx, clientChanges)
+ switch obj {
+ case "clients":
+ inboundIds, err = s.ClientService.Save(tx, act, data)
+ case "tls":
+ inboundIds, err = s.TlsService.Save(tx, act, data)
+ case "inbounds":
+ err = s.InboundService.Save(tx, act, data)
+ case "outbounds":
+ err = s.OutboundService.Save(tx, act, data)
+ case "endpoints":
+ err = s.EndpointService.Save(tx, act, data)
+ case "config":
+ err = s.SettingService.SaveConfig(tx, data)
if err != nil {
return err
}
+ err = s.restartCoreWithConfig(data)
+ default:
+ return common.NewError("unknown object: ", obj)
}
- if len(tlsChanges) > 0 {
- err = s.TlsService.Save(tx, tlsChanges)
- if err != nil {
- return err
- }
+ if err != nil {
+ return err
}
- // if len(inChanges) > 0 {
- // err = s.InDataService.Save(tx, inChanges)
- // if err != nil {
- // return err
- // }
- // }
- if len(settingChanges) > 0 {
- err = s.SettingService.Save(tx, settingChanges)
- if err != nil {
- return err
- }
- }
- needRestart := false
- if len(configChanges) > 0 {
- singboxConfig, err := s.GetConfig()
- if err != nil {
- return err
- }
- newConfig := *singboxConfig
- for _, change := range configChanges {
- rawObject := change.Obj
- switch change.Key {
- case "all":
- err = json.Unmarshal(rawObject, &newConfig)
- if err != nil {
- return err
- }
- needRestart = true
- case "log":
- newConfig.Log = rawObject
- needRestart = true
- case "dns":
- newConfig.Dns = rawObject
- needRestart = true
- case "ntp":
- newConfig.Ntp = rawObject
- needRestart = true
- case "route":
- newConfig.Route = rawObject
- needRestart = true
- case "experimental":
- newConfig.Experimental = rawObject
- needRestart = true
- case "inbounds":
- if change.Action == "edit" {
- var object map[string]interface{}
- err = json.Unmarshal(newConfig.Inbounds[change.Index], &object)
- if err != nil {
- return err
- }
- if tag, ok := object["tag"].(string); ok {
- err = corePtr.RemoveInbound(tag)
- if err == nil {
- err = corePtr.AddInbound(rawObject)
- if err != nil {
- needRestart = true
- }
- } else {
- needRestart = true
- }
- } else {
- needRestart = true
- }
- newConfig.Inbounds[change.Index] = rawObject
- } else if change.Action == "del" {
- var object map[string]interface{}
- err = json.Unmarshal(newConfig.Inbounds[change.Index], &object)
- if err != nil {
- return err
- }
- if tag, ok := object["tag"].(string); ok {
- err = corePtr.RemoveInbound(tag)
- if err != nil {
- needRestart = true
- }
- } else {
- needRestart = true
- }
- newConfig.Inbounds = append(newConfig.Inbounds[:change.Index], newConfig.Inbounds[change.Index+1:]...)
- } else {
- newConfig.Inbounds = append(newConfig.Inbounds, rawObject)
- err = corePtr.AddInbound(rawObject)
- if err != nil {
- logger.Debug(err)
- needRestart = true
- }
- }
- case "outbounds":
- if change.Action == "edit" {
- var object map[string]interface{}
- err = json.Unmarshal(newConfig.Outbounds[change.Index], &object)
- if err != nil {
- return err
- }
- if tag, ok := object["tag"].(string); ok {
- err = corePtr.RemoveOutbound(tag)
- if err == nil {
- err = corePtr.AddOutbound(rawObject)
- if err != nil {
- needRestart = true
- }
- } else {
- needRestart = true
- }
- } else {
- needRestart = true
- }
- newConfig.Outbounds[change.Index] = rawObject
- } else if change.Action == "del" {
- var object map[string]interface{}
- err = json.Unmarshal(newConfig.Outbounds[change.Index], &object)
- if err != nil {
- return err
- }
- if tag, ok := object["tag"].(string); ok {
- err = corePtr.RemoveOutbound(tag)
- if err != nil {
- needRestart = true
- }
- } else {
- needRestart = true
- }
- newConfig.Outbounds = append(newConfig.Outbounds[:change.Index], newConfig.Outbounds[change.Index+1:]...)
- } else {
- err = corePtr.AddOutbound(rawObject)
- if err != nil {
- logger.Debug(err)
- needRestart = true
- }
- newConfig.Outbounds = append(newConfig.Outbounds, rawObject)
- }
- }
- }
- err = s.Save(&newConfig, needRestart)
+ if len(userLinks) > 0 {
+ err = s.ClientService.UpdateLinks(tx, userLinks)
+ if err != nil {
+ return err
+ }
+ }
+
+ if len(outJsons) > 0 {
+ err = s.InboundService.UpdateOutJsons(tx, outJsons)
if err != nil {
return err
}
}
- // Log changes
dt := time.Now().Unix()
- allChanges := append(clientChanges, settingChanges...)
- allChanges = append(allChanges, configChanges...)
- allChanges = append(allChanges, tlsChanges...)
- allChanges = append(allChanges, inChanges...)
- if len(allChanges) > 0 {
- for index := range allChanges {
- allChanges[index].DateTime = dt
- allChanges[index].Actor = loginUser
- }
- err = tx.Model(model.Changes{}).Create(&allChanges).Error
- if err != nil {
- return err
- }
+ err = tx.Create(&model.Changes{
+ DateTime: dt,
+ Actor: loginUser,
+ Key: obj,
+ Action: act,
+ Obj: data,
+ }).Error
+ if err != nil {
+ return err
}
-
- LastUpdate = dt
+ LastUpdate = time.Now().Unix()
return nil
}
@@ -345,105 +219,6 @@ func (s *ConfigService) CheckChanges(lu string) (bool, error) {
}
}
-func (s *ConfigService) Save(singboxConfig *SingBoxConfig, needRestart bool) error {
- configPath := config.GetBinFolderPath()
- _, err := os.Stat(configPath + "/config.json")
- if os.IsNotExist(err) {
- err = os.MkdirAll(configPath, 01764)
- if err != nil {
- return err
- }
- } else if err != nil {
- return err
- }
-
- data, err := json.MarshalIndent(singboxConfig, "", " ")
- if err != nil {
- return err
- }
-
- err = os.WriteFile(configPath+"/config.json", data, 0764)
- if err != nil {
- return err
- }
-
- if needRestart {
- err = s.RestartCore()
- if err != nil {
- return err
- }
- }
- // s.Controller.Restart()
-
- return nil
-}
-
-func (s *ConfigService) DepleteClients() error {
- users, inboundIds, err := s.ClientService.DepleteClients()
- if err != nil || len(users) == 0 || len(inboundIds) == 0 {
- return err
- }
-
- // inbounds, err := s.InboundService.FromIds(inboundIds)
- // if err != nil {
- // return err
- // }
- // for inbound_index, inbound := range inbounds {
- // var inboundJson map[string]interface{}
- // json.Unmarshal(inbound.Options, &inboundJson)
- // inbound_users, ok := inboundJson["users"].([]interface{})
- // if ok {
- // var updatedUsers []interface{}
- // for _, user := range inbound_users {
- // userMap, ok := user.(map[string]interface{})
- // if ok {
- // name, exists := userMap["name"].(string)
- // if exists && s.contains(users, name) {
- // // Skip the user exists
- // continue
- // }
- // username, exists := userMap["username"].(string)
- // if exists && s.contains(users, username) {
- // // Skip the username exists
- // continue
- // }
- // }
- // updatedUsers = append(updatedUsers, user)
- // }
- // // Exception for Naive and ShadowTLSv3
- // if len(updatedUsers) == 0 {
- // if inboundJson["type"].(string) == "naive" ||
- // (inboundJson["type"].(string) == "shadowtls" &&
- // inboundJson["version"].(float64) == 3) {
- // updatedUsers = append(updatedUsers, make(map[string]interface{}))
- // }
- // }
-
- // inboundJson["users"] = updatedUsers
- // }
- // modifiedInbound, err := json.MarshalIndent(inboundJson, "", " ")
- // if err != nil {
- // return err
- // }
- // inbounds[inbound_index] = modifiedInbound
- // }
-
- // err = s.Save(singboxConfig, true)
- // if err != nil {
- // return err
- // }
- return nil
-}
-
-func (s *ConfigService) contains(slice []string, item string) bool {
- for _, str := range slice {
- if str == item {
- return true
- }
- }
- return false
-}
-
func (s *ConfigService) GetChanges(actor string, chngKey string, count string) []model.Changes {
c, _ := strconv.Atoi(count)
whereString := "`id`>0"
diff --git a/backend/service/endpoints.go b/backend/service/endpoints.go
index 704b48b..db52f14 100644
--- a/backend/service/endpoints.go
+++ b/backend/service/endpoints.go
@@ -2,6 +2,7 @@ package service
import (
"encoding/json"
+ "os"
"s-ui/database"
"s-ui/database/model"
@@ -10,24 +11,32 @@ import (
type EndpointService struct{}
-func (o *EndpointService) GetAll() ([]*model.Endpoint, error) {
+func (o *EndpointService) GetAll() (*[]map[string]interface{}, error) {
db := database.GetDB()
endpoints := []*model.Endpoint{}
err := db.Model(model.Endpoint{}).Scan(&endpoints).Error
if err != nil {
return nil, err
}
- return endpoints, nil
-}
-
-func (o *EndpointService) Get(id uint) (*model.Endpoint, error) {
- db := database.GetDB()
- endpoint := &model.Endpoint{}
- err := db.First(endpoint, id).Error
- if err != nil {
- return nil, err
+ var data []map[string]interface{}
+ for _, endpoint := range endpoints {
+ epData := map[string]interface{}{
+ "id": endpoint.Id,
+ "type": endpoint.Type,
+ "tag": endpoint.Tag,
+ }
+ if endpoint.Options != nil {
+ var restFields map[string]json.RawMessage
+ if err := json.Unmarshal(endpoint.Options, &restFields); err != nil {
+ return nil, err
+ }
+ for k, v := range restFields {
+ epData[k] = v
+ }
+ }
+ data = append(data, epData)
}
- return endpoint, nil
+ return &data, nil
}
func (o *EndpointService) GetAllConfig(db *gorm.DB) ([]json.RawMessage, error) {
@@ -46,3 +55,55 @@ func (o *EndpointService) GetAllConfig(db *gorm.DB) ([]json.RawMessage, error) {
}
return endpointsJson, nil
}
+
+func (s *EndpointService) Save(tx *gorm.DB, action string, data json.RawMessage) error {
+ var err error
+
+ switch action {
+ case "new", "edit":
+ var endpoint model.Endpoint
+ err = endpoint.UnmarshalJSON(data)
+ if err != nil {
+ return err
+ }
+
+ if corePtr.IsRunning() {
+ configData, err := endpoint.MarshalJSON()
+ if err != nil {
+ return err
+ }
+ if action == "edit" {
+ err = corePtr.RemoveEndpoint(endpoint.Tag)
+ if err != nil && err != os.ErrInvalid {
+ return err
+ }
+ }
+ err = corePtr.AddEndpoint(configData)
+ if err != nil {
+ return err
+ }
+ }
+
+ err = tx.Save(&endpoint).Error
+ if err != nil {
+ return err
+ }
+ case "del":
+ var tag string
+ err = json.Unmarshal(data, &tag)
+ if err != nil {
+ return err
+ }
+ if corePtr.IsRunning() {
+ err = corePtr.RemoveEndpoint(tag)
+ if err != nil && err != os.ErrInvalid {
+ return err
+ }
+ }
+ err = tx.Where("tag = ?", tag).Delete(model.Endpoint{}).Error
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/backend/service/inbounds.go b/backend/service/inbounds.go
index de4c706..a16775e 100644
--- a/backend/service/inbounds.go
+++ b/backend/service/inbounds.go
@@ -2,22 +2,67 @@ package service
import (
"encoding/json"
+ "os"
"s-ui/database"
"s-ui/database/model"
+ "strings"
"gorm.io/gorm"
)
type InboundService struct{}
-func (s *InboundService) GetAll() (*[]map[string]interface{}, error) {
+func (s *InboundService) Get(ids string) (*[]map[string]interface{}, error) {
+ if ids == "" {
+ return s.GetAll()
+ }
+ return s.getById(ids)
+}
+
+func (s *InboundService) getById(ids string) (*[]map[string]interface{}, error) {
+ var inbound []model.Inbound
+ var result []map[string]interface{}
db := database.GetDB()
- inbounds := []map[string]interface{}{}
- err := db.Model(model.Inbound{}).Select("id, tag, type, address, port, tls_id , count(users) as ucount").Scan(&inbounds).Error
+ err := db.Model(model.Inbound{}).Where("id in ?", strings.Split(ids, ",")).Scan(&inbound).Error
if err != nil {
return nil, err
}
- return &inbounds, nil
+ for _, inb := range inbound {
+ inbData, err := inb.MarshalFull()
+ if err != nil {
+ return nil, err
+ }
+ result = append(result, *inbData)
+ }
+ return &result, nil
+}
+
+func (s *InboundService) GetAll() (*[]map[string]interface{}, error) {
+ db := database.GetDB()
+ inbounds := []model.Inbound{}
+ err := db.Model(model.Inbound{}).Scan(&inbounds).Error
+ if err != nil {
+ return nil, err
+ }
+ var data []map[string]interface{}
+ for _, inbound := range inbounds {
+ inbData := map[string]interface{}{
+ "id": inbound.Id,
+ "type": inbound.Type,
+ "tag": inbound.Tag,
+ "tls_id": inbound.TlsId,
+ }
+ if inbound.Options != nil {
+ var restFields map[string]json.RawMessage
+ if err := json.Unmarshal(inbound.Options, &restFields); err != nil {
+ return nil, err
+ }
+ inbData["listen"] = restFields["listen"]
+ inbData["listen_port"] = restFields["listen_port"]
+ }
+ data = append(data, inbData)
+ }
+ return &data, nil
}
func (s *InboundService) FromIds(ids []uint) ([]*model.Inbound, error) {
@@ -30,8 +75,91 @@ func (s *InboundService) FromIds(ids []uint) ([]*model.Inbound, error) {
return inbounds, nil
}
-func (s *InboundService) Save(db *gorm.DB, inbounds []*model.Inbound) error {
- return db.Save(inbounds).Error
+func (s *InboundService) Save(tx *gorm.DB, act string, data json.RawMessage) error {
+ var err error
+
+ switch act {
+ case "new", "edit":
+ var inbound model.Inbound
+ err = inbound.UnmarshalJSON(data)
+ if err != nil {
+ return err
+ }
+ if corePtr.IsRunning() {
+ if act == "edit" {
+ err = corePtr.RemoveInbound(inbound.Tag)
+ if err != nil && err != os.ErrInvalid {
+ return err
+ }
+ }
+
+ if inbound.TlsId > 0 {
+ err = tx.Model(model.Tls{}).Where("id = ?", inbound.TlsId).Find(&inbound.Tls).Error
+ if err != nil {
+ return err
+ }
+ }
+
+ inboundConfig, err := inbound.MarshalJSON()
+ if err != nil {
+ return err
+ }
+
+ inboundConfig, err = s.addUsers(tx, inboundConfig, inbound.Id, inbound.Type)
+ if err != nil {
+ return err
+ }
+
+ err = corePtr.AddInbound(inboundConfig)
+ if err != nil {
+ return err
+ }
+ }
+
+ err = tx.Save(&inbound).Error
+ if err != nil {
+ return err
+ }
+ case "del":
+ var tag string
+ err = json.Unmarshal(data, &tag)
+ if err != nil {
+ return err
+ }
+ if corePtr.IsRunning() {
+ err = corePtr.RemoveInbound(tag)
+ if err != nil && err != os.ErrInvalid {
+ return err
+ }
+ }
+ err = tx.Where("tag = ?", tag).Delete(model.Inbound{}).Error
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (s *InboundService) UpdateOutJsons(tx *gorm.DB, data json.RawMessage) error {
+ var outJsons []interface{}
+ err := json.Unmarshal(data, &outJsons)
+ if err != nil {
+ return err
+ }
+ for _, outJson := range outJsons {
+ outJsonData := outJson.(map[string]interface{})
+ tag := outJsonData["tag"].(string)
+ outJson, err := json.MarshalIndent(outJsonData["out_json"], "", " ")
+ if err != nil {
+ return err
+ }
+ err = tx.Model(model.Inbound{}).Where("tag = ?", tag).Update("out_json", outJson).Error
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
}
func (s *InboundService) GetAllConfig(db *gorm.DB) ([]json.RawMessage, error) {
@@ -46,12 +174,9 @@ func (s *InboundService) GetAllConfig(db *gorm.DB) ([]json.RawMessage, error) {
if err != nil {
return nil, err
}
- switch inbound.Type {
- case "mixed", "socks", "http", "shadowsocks", "vmess", "trojan", "naive", "hysteria", "shadowtls", "tuic", "hysteria2", "vless":
- inboundJson, err = s.addUsers(db, inboundJson, inbound.Id, inbound.Type)
- if err != nil {
- return nil, err
- }
+ inboundJson, err = s.addUsers(db, inboundJson, inbound.Id, inbound.Type)
+ if err != nil {
+ return nil, err
}
inboundsJson = append(inboundsJson, inboundJson)
}
@@ -59,11 +184,24 @@ func (s *InboundService) GetAllConfig(db *gorm.DB) ([]json.RawMessage, error) {
}
func (s *InboundService) addUsers(db *gorm.DB, inboundJson []byte, inboundId uint, inboundType string) ([]byte, error) {
+ switch inboundType {
+ case "mixed", "socks", "http", "shadowsocks", "vmess", "trojan", "naive", "hysteria", "shadowtls", "tuic", "hysteria2", "vless":
+ break
+ default:
+ return inboundJson, nil
+ }
+
var inbound map[string]interface{}
err := json.Unmarshal(inboundJson, &inbound)
if err != nil {
return nil, err
}
+ if inboundType == "shadowsocks" {
+ method, _ := inbound["method"].(string)
+ if method == "2022-blake3-aes-128-gcm" {
+ inboundType = "shadowsocks16"
+ }
+ }
var users []string
err = db.Raw(`SELECT json_extract(clients.config, ?)
FROM clients, json_each(clients.inbounds) as je
@@ -80,3 +218,27 @@ func (s *InboundService) addUsers(db *gorm.DB, inboundJson []byte, inboundId uin
inbound["users"] = usersJson
return json.Marshal(inbound)
}
+
+func (s *InboundService) RestartInbounds(tx *gorm.DB, ids []uint) error {
+ var inbounds []*model.Inbound
+ err := tx.Model(model.Inbound{}).Preload("Tls").Where("id in ?", ids).Find(&inbounds).Error
+ if err != nil {
+ return err
+ }
+ for _, inbound := range inbounds {
+ err = corePtr.RemoveInbound(inbound.Tag)
+ if err != nil && err != os.ErrInvalid {
+ return err
+ }
+ inboundConfig, err := inbound.MarshalJSON()
+ if err != nil {
+ return err
+ }
+ inboundConfig, err = s.addUsers(tx, inboundConfig, inbound.Id, inbound.Type)
+ err = corePtr.AddInbound(inboundConfig)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/backend/service/outbounds.go b/backend/service/outbounds.go
index 00419da..f8eb2f2 100644
--- a/backend/service/outbounds.go
+++ b/backend/service/outbounds.go
@@ -2,6 +2,7 @@ package service
import (
"encoding/json"
+ "os"
"s-ui/database"
"s-ui/database/model"
@@ -10,24 +11,32 @@ import (
type OutboundService struct{}
-func (o *OutboundService) GetAll() ([]*model.Outbound, error) {
+func (o *OutboundService) GetAll() (*[]map[string]interface{}, error) {
db := database.GetDB()
outbounds := []*model.Outbound{}
err := db.Model(model.Outbound{}).Scan(&outbounds).Error
if err != nil {
return nil, err
}
- return outbounds, nil
-}
-
-func (o *OutboundService) Get(id uint) (*model.Outbound, error) {
- db := database.GetDB()
- outbound := &model.Outbound{}
- err := db.First(outbound, id).Error
- if err != nil {
- return nil, err
+ var data []map[string]interface{}
+ for _, outbound := range outbounds {
+ outData := map[string]interface{}{
+ "id": outbound.Id,
+ "type": outbound.Type,
+ "tag": outbound.Tag,
+ }
+ if outbound.Options != nil {
+ var restFields map[string]json.RawMessage
+ if err := json.Unmarshal(outbound.Options, &restFields); err != nil {
+ return nil, err
+ }
+ for k, v := range restFields {
+ outData[k] = v
+ }
+ }
+ data = append(data, outData)
}
- return outbound, nil
+ return &data, nil
}
func (o *OutboundService) GetAllConfig(db *gorm.DB) ([]json.RawMessage, error) {
@@ -46,3 +55,55 @@ func (o *OutboundService) GetAllConfig(db *gorm.DB) ([]json.RawMessage, error) {
}
return outboundsJson, nil
}
+
+func (s *OutboundService) Save(tx *gorm.DB, action string, data json.RawMessage) error {
+ var err error
+
+ switch action {
+ case "new", "edit":
+ var outbound model.Outbound
+ err = outbound.UnmarshalJSON(data)
+ if err != nil {
+ return err
+ }
+
+ if corePtr.IsRunning() {
+ configData, err := outbound.MarshalJSON()
+ if err != nil {
+ return err
+ }
+ if action == "edit" {
+ err = corePtr.RemoveOutbound(outbound.Tag)
+ if err != nil && err != os.ErrInvalid {
+ return err
+ }
+ }
+ err = corePtr.AddOutbound(configData)
+ if err != nil {
+ return err
+ }
+ }
+
+ err = tx.Save(&outbound).Error
+ if err != nil {
+ return err
+ }
+ case "del":
+ var tag string
+ err = json.Unmarshal(data, &tag)
+ if err != nil {
+ return err
+ }
+ if corePtr.IsRunning() {
+ err = corePtr.RemoveOutbound(tag)
+ if err != nil && err != os.ErrInvalid {
+ return err
+ }
+ }
+ err = tx.Where("tag = ?", tag).Delete(model.Outbound{}).Error
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/backend/service/setting.go b/backend/service/setting.go
index 4941c9c..3ca4f0d 100644
--- a/backend/service/setting.go
+++ b/backend/service/setting.go
@@ -343,6 +343,14 @@ func (s *SettingService) SetConfig(config string) error {
return s.setString("config", config)
}
+func (s *SettingService) SaveConfig(tx *gorm.DB, config json.RawMessage) error {
+ configs, err := json.MarshalIndent(config, "", " ")
+ if err != nil {
+ return err
+ }
+ return tx.Model(model.Setting{}).Where("key = ?", "config").Update("value", string(configs)).Error
+}
+
func (s *SettingService) Save(tx *gorm.DB, changes []model.Changes) error {
var err error
for _, change := range changes {
diff --git a/backend/service/stats.go b/backend/service/stats.go
index 2ff547c..876b1e1 100644
--- a/backend/service/stats.go
+++ b/backend/service/stats.go
@@ -74,7 +74,7 @@ func (s *StatsService) SaveStats() error {
return err
}
-func (s *StatsService) GetStats(resorce string, tag string, limit int) ([]model.Stats, error) {
+func (s *StatsService) GetStats(resource string, tag string, limit int) ([]model.Stats, error) {
var err error
var result []model.Stats
@@ -82,7 +82,11 @@ func (s *StatsService) GetStats(resorce string, tag string, limit int) ([]model.
timeDiff := currentTime - (int64(limit) * 3600)
db := database.GetDB()
- err = db.Model(model.Stats{}).Where("resource = ? AND tag = ? AND date_time > ?", resorce, tag, timeDiff).Scan(&result).Error
+ resources := []string{resource}
+ if resource == "endpoint" {
+ resources = []string{"inbound", "outbound"}
+ }
+ err = db.Model(model.Stats{}).Where("resource in ? AND tag = ? AND date_time > ?", resources, tag, timeDiff).Scan(&result).Error
if err != nil {
return nil, err
}
diff --git a/backend/service/tls.go b/backend/service/tls.go
index ada8cb2..11d23ab 100644
--- a/backend/service/tls.go
+++ b/backend/service/tls.go
@@ -4,11 +4,13 @@ import (
"encoding/json"
"s-ui/database"
"s-ui/database/model"
+ "s-ui/util/common"
"gorm.io/gorm"
)
type TlsService struct {
+ InboundService
}
func (s *TlsService) GetAll() ([]model.Tls, error) {
@@ -22,25 +24,45 @@ func (s *TlsService) GetAll() ([]model.Tls, error) {
return tlsConfig, nil
}
-func (s *TlsService) Save(tx *gorm.DB, changes []model.Changes) error {
+func (s *TlsService) Save(tx *gorm.DB, action string, data json.RawMessage) ([]uint, error) {
var err error
- for _, change := range changes {
- tlsConfig := model.Tls{}
- err = json.Unmarshal(change.Obj, &tlsConfig)
+ var inboundIds []uint
+
+ switch action {
+ case "new", "edit":
+ var tls model.Tls
+ err = json.Unmarshal(data, &tls)
if err != nil {
- return err
- }
- switch change.Action {
- case "new":
- err = tx.Create(&tlsConfig).Error
- case "del":
- err = tx.Where("id = ?", change.Index).Delete(model.Tls{}).Error
- default:
- err = tx.Save(tlsConfig).Error
+ return nil, err
}
+ err = tx.Save(&tls).Error
if err != nil {
- return err
+ return nil, err
+ }
+ err = tx.Model(model.Inbound{}).Select("id").Where("tls_id = ?", tls.Id).Scan(&inboundIds).Error
+ if err != nil {
+ return nil, err
+ }
+ return inboundIds, nil
+ case "del":
+ var id uint
+ err = json.Unmarshal(data, &id)
+ if err != nil {
+ return nil, err
+ }
+ var inboundCount int64
+ err = tx.Model(model.Inbound{}).Where("tls_id = ?", id).Count(&inboundCount).Error
+ if err != nil {
+ return nil, err
+ }
+ if inboundCount > 0 {
+ return nil, common.NewError("tls in use")
+ }
+ err = tx.Where("id = ?", id).Delete(model.Tls{}).Error
+ if err != nil {
+ return nil, err
}
}
- return err
+
+ return nil, nil
}
diff --git a/backend/sub/jsonService.go b/backend/sub/jsonService.go
index 48c5c93..33db779 100644
--- a/backend/sub/jsonService.go
+++ b/backend/sub/jsonService.go
@@ -87,27 +87,22 @@ func (j *JsonService) GetJson(subId string, format string) (*string, error) {
return &resultStr, nil
}
-func (j *JsonService) getData(subId string) (*model.Client, *[]model.InboundData, error) {
+func (j *JsonService) getData(subId string) (*model.Client, []*model.Inbound, error) {
db := database.GetDB()
client := &model.Client{}
err := db.Model(model.Client{}).Where("enable = true and name = ?", subId).First(client).Error
if err != nil {
return nil, nil, err
}
- var inbounds []string
- err = json.Unmarshal(client.Inbounds, &inbounds)
+ var inbounds []*model.Inbound
+ err = db.Model(model.Inbound{}).Where("tag in ?", client.Inbounds).Find(&inbounds).Error
if err != nil {
return nil, nil, err
}
- inDatas := &[]model.InboundData{}
- err = db.Model(model.InboundData{}).Where("tag in ?", inbounds).Find(&inDatas).Error
- if err != nil {
- return nil, nil, err
- }
- return client, inDatas, nil
+ return client, inbounds, nil
}
-func (j *JsonService) getOutbounds(clientConfig json.RawMessage, inDatas *[]model.InboundData) (*[]map[string]interface{}, *[]string, error) {
+func (j *JsonService) getOutbounds(clientConfig json.RawMessage, inbounds []*model.Inbound) (*[]map[string]interface{}, *[]string, error) {
var outbounds []map[string]interface{}
var configs map[string]interface{}
var outTags []string
@@ -116,7 +111,7 @@ func (j *JsonService) getOutbounds(clientConfig json.RawMessage, inDatas *[]mode
if err != nil {
return nil, nil, err
}
- for _, inData := range *inDatas {
+ for _, inData := range inbounds {
if len(inData.OutJson) < 5 {
continue
}
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index cf837f0..9376f66 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -58,12 +58,12 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.26.1",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.1.tgz",
- "integrity": "sha512-reoQYNiAJreZNsJzyrDNzFQ+IQ5JFiIzAHJg9bn94S3l+4++J7RsIhNMoB+lgP/9tpmiAQqspv+xfdxTSzREOw==",
+ "version": "7.26.3",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz",
+ "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==",
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.26.0"
+ "@babel/types": "^7.26.3"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -73,9 +73,9 @@
}
},
"node_modules/@babel/types": {
- "version": "7.26.0",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz",
- "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==",
+ "version": "7.26.3",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz",
+ "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==",
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.25.9",
@@ -497,14 +497,14 @@
}
},
"node_modules/@eslint/config-array": {
- "version": "0.18.0",
- "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz",
- "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==",
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz",
+ "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
- "@eslint/object-schema": "^2.1.4",
+ "@eslint/object-schema": "^2.1.5",
"debug": "^4.3.1",
"minimatch": "^3.1.2"
},
@@ -513,20 +513,23 @@
}
},
"node_modules/@eslint/core": {
- "version": "0.7.0",
- "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz",
- "integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==",
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.1.tgz",
+ "integrity": "sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/eslintrc": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz",
- "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==",
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz",
+ "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==",
"dev": true,
"license": "MIT",
"peer": true,
@@ -549,9 +552,9 @@
}
},
"node_modules/@eslint/js": {
- "version": "9.13.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.13.0.tgz",
- "integrity": "sha512-IFLyoY4d72Z5y/6o/BazFBezupzI/taV8sGumxTAVw3lXG9A6md1Dc34T9s1FoD/an9pJH8RHbAxsaEbBed9lA==",
+ "version": "9.17.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz",
+ "integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==",
"dev": true,
"license": "MIT",
"peer": true,
@@ -560,9 +563,9 @@
}
},
"node_modules/@eslint/object-schema": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz",
- "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==",
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz",
+ "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
@@ -571,9 +574,9 @@
}
},
"node_modules/@eslint/plugin-kit": {
- "version": "0.2.2",
- "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.2.tgz",
- "integrity": "sha512-CXtq5nR4Su+2I47WPOlWud98Y5Lv8Kyxp2ukhgFx/eW6Blm18VXJO5WuQylPugRo8nbluoi6GvvxBLqHcvqUUw==",
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.4.tgz",
+ "integrity": "sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
@@ -610,6 +613,21 @@
"node": ">=18.18.0"
}
},
+ "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
+ "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
"node_modules/@humanwhocodes/module-importer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
@@ -626,9 +644,9 @@
}
},
"node_modules/@humanwhocodes/retry": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
- "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz",
+ "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
@@ -641,13 +659,13 @@
}
},
"node_modules/@intlify/core-base": {
- "version": "9.14.1",
- "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.14.1.tgz",
- "integrity": "sha512-rG5/hlNW6Qfve41go37szEf0mVLcfhYuOu83JcY0jZKasnwsrcZYYWDzebCcuO5I/6Sy1JFWo9p+nvkQS1Dy+w==",
+ "version": "9.14.2",
+ "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.14.2.tgz",
+ "integrity": "sha512-DZyQ4Hk22sC81MP4qiCDuU+LdaYW91A6lCjq8AWPvY3+mGMzhGDfOCzvyR6YBQxtlPjFqMoFk9ylnNYRAQwXtQ==",
"license": "MIT",
"dependencies": {
- "@intlify/message-compiler": "9.14.1",
- "@intlify/shared": "9.14.1"
+ "@intlify/message-compiler": "9.14.2",
+ "@intlify/shared": "9.14.2"
},
"engines": {
"node": ">= 16"
@@ -657,12 +675,12 @@
}
},
"node_modules/@intlify/message-compiler": {
- "version": "9.14.1",
- "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.14.1.tgz",
- "integrity": "sha512-MY8hwukJBnXvGAncVKlHsqKDQ5ZcQx4peqEmI8wBUTXn4pezrtTGYXNoz81cLyEEHB+L/zlKWVBSh5TiX4gYoQ==",
+ "version": "9.14.2",
+ "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.14.2.tgz",
+ "integrity": "sha512-YsKKuV4Qv4wrLNsvgWbTf0E40uRv+Qiw1BeLQ0LAxifQuhiMe+hfTIzOMdWj/ZpnTDj4RSZtkXjJM7JDiiB5LQ==",
"license": "MIT",
"dependencies": {
- "@intlify/shared": "9.14.1",
+ "@intlify/shared": "9.14.2",
"source-map-js": "^1.0.2"
},
"engines": {
@@ -673,9 +691,9 @@
}
},
"node_modules/@intlify/shared": {
- "version": "9.14.1",
- "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.14.1.tgz",
- "integrity": "sha512-XjHu6PEQup9MnP1x0W9y0nXXfq9jFftAYSfV11hryjtH4XqXP8HrzMvXI+ZVifF+jZLszaTzIhvukllplxTQTg==",
+ "version": "9.14.2",
+ "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.14.2.tgz",
+ "integrity": "sha512-uRAHAxYPeF+G5DBIboKpPgC/Waecd4Jz8ihtkpJQD5ycb5PwXp0k/+hBGl5dAjwF7w+l74kz/PKA8r8OK//RUw==",
"license": "MIT",
"engines": {
"node": ">= 16"
@@ -691,9 +709,9 @@
"license": "MIT"
},
"node_modules/@kurkle/color": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz",
- "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==",
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
+ "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
"license": "MIT"
},
"node_modules/@mdi/font": {
@@ -741,9 +759,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.24.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.2.tgz",
- "integrity": "sha512-ufoveNTKDg9t/b7nqI3lwbCG/9IJMhADBNjjz/Jn6LxIZxD7T5L8l2uO/wD99945F1Oo8FvgbbZJRguyk/BdzA==",
+ "version": "4.29.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.29.1.tgz",
+ "integrity": "sha512-ssKhA8RNltTZLpG6/QNkCSge+7mBQGUqJRisZ2MDQcEGaK93QESEgWK2iOpIDZ7k9zPVkG5AS3ksvD5ZWxmItw==",
"cpu": [
"arm"
],
@@ -754,9 +772,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
- "version": "4.24.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.2.tgz",
- "integrity": "sha512-iZoYCiJz3Uek4NI0J06/ZxUgwAfNzqltK0MptPDO4OR0a88R4h0DSELMsflS6ibMCJ4PnLvq8f7O1d7WexUvIA==",
+ "version": "4.29.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.29.1.tgz",
+ "integrity": "sha512-CaRfrV0cd+NIIcVVN/jx+hVLN+VRqnuzLRmfmlzpOzB87ajixsN/+9L5xNmkaUUvEbI5BmIKS+XTwXsHEb65Ew==",
"cpu": [
"arm64"
],
@@ -767,9 +785,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.24.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.2.tgz",
- "integrity": "sha512-/UhrIxobHYCBfhi5paTkUDQ0w+jckjRZDZ1kcBL132WeHZQ6+S5v9jQPVGLVrLbNUebdIRpIt00lQ+4Z7ys4Rg==",
+ "version": "4.29.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.29.1.tgz",
+ "integrity": "sha512-2ORr7T31Y0Mnk6qNuwtyNmy14MunTAMx06VAPI6/Ju52W10zk1i7i5U3vlDRWjhOI5quBcrvhkCHyF76bI7kEw==",
"cpu": [
"arm64"
],
@@ -780,9 +798,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.24.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.2.tgz",
- "integrity": "sha512-1F/jrfhxJtWILusgx63WeTvGTwE4vmsT9+e/z7cZLKU8sBMddwqw3UV5ERfOV+H1FuRK3YREZ46J4Gy0aP3qDA==",
+ "version": "4.29.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.29.1.tgz",
+ "integrity": "sha512-j/Ej1oanzPjmN0tirRd5K2/nncAhS9W6ICzgxV+9Y5ZsP0hiGhHJXZ2JQ53iSSjj8m6cRY6oB1GMzNn2EUt6Ng==",
"cpu": [
"x64"
],
@@ -793,9 +811,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.24.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.24.2.tgz",
- "integrity": "sha512-1YWOpFcGuC6iGAS4EI+o3BV2/6S0H+m9kFOIlyFtp4xIX5rjSnL3AwbTBxROX0c8yWtiWM7ZI6mEPTI7VkSpZw==",
+ "version": "4.29.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.29.1.tgz",
+ "integrity": "sha512-91C//G6Dm/cv724tpt7nTyP+JdN12iqeXGFM1SqnljCmi5yTXriH7B1r8AD9dAZByHpKAumqP1Qy2vVNIdLZqw==",
"cpu": [
"arm64"
],
@@ -806,9 +824,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.24.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.24.2.tgz",
- "integrity": "sha512-3qAqTewYrCdnOD9Gl9yvPoAoFAVmPJsBvleabvx4bnu1Kt6DrB2OALeRVag7BdWGWLhP1yooeMLEi6r2nYSOjg==",
+ "version": "4.29.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.29.1.tgz",
+ "integrity": "sha512-hEioiEQ9Dec2nIRoeHUP6hr1PSkXzQaCUyqBDQ9I9ik4gCXQZjJMIVzoNLBRGet+hIUb3CISMh9KXuCcWVW/8w==",
"cpu": [
"x64"
],
@@ -819,9 +837,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.24.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.2.tgz",
- "integrity": "sha512-ArdGtPHjLqWkqQuoVQ6a5UC5ebdX8INPuJuJNWRe0RGa/YNhVvxeWmCTFQ7LdmNCSUzVZzxAvUznKaYx645Rig==",
+ "version": "4.29.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.29.1.tgz",
+ "integrity": "sha512-Py5vFd5HWYN9zxBv3WMrLAXY3yYJ6Q/aVERoeUFwiDGiMOWsMs7FokXihSOaT/PMWUty/Pj60XDQndK3eAfE6A==",
"cpu": [
"arm"
],
@@ -832,9 +850,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.24.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.2.tgz",
- "integrity": "sha512-B6UHHeNnnih8xH6wRKB0mOcJGvjZTww1FV59HqJoTJ5da9LCG6R4SEBt6uPqzlawv1LoEXSS0d4fBlHNWl6iYw==",
+ "version": "4.29.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.29.1.tgz",
+ "integrity": "sha512-RiWpGgbayf7LUcuSNIbahr0ys2YnEERD4gYdISA06wa0i8RALrnzflh9Wxii7zQJEB2/Eh74dX4y/sHKLWp5uQ==",
"cpu": [
"arm"
],
@@ -845,9 +863,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.24.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.2.tgz",
- "integrity": "sha512-kr3gqzczJjSAncwOS6i7fpb4dlqcvLidqrX5hpGBIM1wtt0QEVtf4wFaAwVv8QygFU8iWUMYEoJZWuWxyua4GQ==",
+ "version": "4.29.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.29.1.tgz",
+ "integrity": "sha512-Z80O+taYxTQITWMjm/YqNoe9d10OX6kDh8X5/rFCMuPqsKsSyDilvfg+vd3iXIqtfmp+cnfL1UrYirkaF8SBZA==",
"cpu": [
"arm64"
],
@@ -858,9 +876,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.24.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.2.tgz",
- "integrity": "sha512-TDdHLKCWgPuq9vQcmyLrhg/bgbOvIQ8rtWQK7MRxJ9nvaxKx38NvY7/Lo6cYuEnNHqf6rMqnivOIPIQt6H2AoA==",
+ "version": "4.29.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.29.1.tgz",
+ "integrity": "sha512-fOHRtF9gahwJk3QVp01a/GqS4hBEZCV1oKglVVq13kcK3NeVlS4BwIFzOHDbmKzt3i0OuHG4zfRP0YoG5OF/rA==",
"cpu": [
"arm64"
],
@@ -870,10 +888,23 @@
"linux"
]
},
+ "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
+ "version": "4.29.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.29.1.tgz",
+ "integrity": "sha512-5a7q3tnlbcg0OodyxcAdrrCxFi0DgXJSoOuidFUzHZ2GixZXQs6Tc3CHmlvqKAmOs5eRde+JJxeIf9DonkmYkw==",
+ "cpu": [
+ "loong64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
- "version": "4.24.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.2.tgz",
- "integrity": "sha512-xv9vS648T3X4AxFFZGWeB5Dou8ilsv4VVqJ0+loOIgDO20zIhYfDLkk5xoQiej2RiSQkld9ijF/fhLeonrz2mw==",
+ "version": "4.29.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.29.1.tgz",
+ "integrity": "sha512-9b4Mg5Yfz6mRnlSPIdROcfw1BU22FQxmfjlp/CShWwO3LilKQuMISMTtAu/bxmmrE6A902W2cZJuzx8+gJ8e9w==",
"cpu": [
"ppc64"
],
@@ -884,9 +915,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.24.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.2.tgz",
- "integrity": "sha512-tbtXwnofRoTt223WUZYiUnbxhGAOVul/3StZ947U4A5NNjnQJV5irKMm76G0LGItWs6y+SCjUn/Q0WaMLkEskg==",
+ "version": "4.29.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.29.1.tgz",
+ "integrity": "sha512-G5pn0NChlbRM8OJWpJFMX4/i8OEU538uiSv0P6roZcbpe/WfhEO+AT8SHVKfp8qhDQzaz7Q+1/ixMy7hBRidnQ==",
"cpu": [
"riscv64"
],
@@ -897,9 +928,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.24.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.2.tgz",
- "integrity": "sha512-gc97UebApwdsSNT3q79glOSPdfwgwj5ELuiyuiMY3pEWMxeVqLGKfpDFoum4ujivzxn6veUPzkGuSYoh5deQ2Q==",
+ "version": "4.29.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.29.1.tgz",
+ "integrity": "sha512-WM9lIkNdkhVwiArmLxFXpWndFGuOka4oJOZh8EP3Vb8q5lzdSCBuhjavJsw68Q9AKDGeOOIHYzYm4ZFvmWez5g==",
"cpu": [
"s390x"
],
@@ -910,9 +941,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.24.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.2.tgz",
- "integrity": "sha512-jOG/0nXb3z+EM6SioY8RofqqmZ+9NKYvJ6QQaa9Mvd3RQxlH68/jcB/lpyVt4lCiqr04IyaC34NzhUqcXbB5FQ==",
+ "version": "4.29.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.29.1.tgz",
+ "integrity": "sha512-87xYCwb0cPGZFoGiErT1eDcssByaLX4fc0z2nRM6eMtV9njAfEE6OW3UniAoDhX4Iq5xQVpE6qO9aJbCFumKYQ==",
"cpu": [
"x64"
],
@@ -923,9 +954,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.24.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.2.tgz",
- "integrity": "sha512-XAo7cJec80NWx9LlZFEJQxqKOMz/lX3geWs2iNT5CHIERLFfd90f3RYLLjiCBm1IMaQ4VOX/lTC9lWfzzQm14Q==",
+ "version": "4.29.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.29.1.tgz",
+ "integrity": "sha512-xufkSNppNOdVRCEC4WKvlR1FBDyqCSCpQeMMgv9ZyXqqtKBfkw1yfGMTUTs9Qsl6WQbJnsGboWCp7pJGkeMhKA==",
"cpu": [
"x64"
],
@@ -936,9 +967,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.24.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.2.tgz",
- "integrity": "sha512-A+JAs4+EhsTjnPQvo9XY/DC0ztaws3vfqzrMNMKlwQXuniBKOIIvAAI8M0fBYiTCxQnElYu7mLk7JrhlQ+HeOw==",
+ "version": "4.29.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.29.1.tgz",
+ "integrity": "sha512-F2OiJ42m77lSkizZQLuC+jiZ2cgueWQL5YC9tjo3AgaEw+KJmVxHGSyQfDUoYR9cci0lAywv2Clmckzulcq6ig==",
"cpu": [
"arm64"
],
@@ -949,9 +980,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.24.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.2.tgz",
- "integrity": "sha512-ZhcrakbqA1SCiJRMKSU64AZcYzlZ/9M5LaYil9QWxx9vLnkQ9Vnkve17Qn4SjlipqIIBFKjBES6Zxhnvh0EAEw==",
+ "version": "4.29.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.29.1.tgz",
+ "integrity": "sha512-rYRe5S0FcjlOBZQHgbTKNrqxCBUmgDJem/VQTCcTnA2KCabYSWQDrytOzX7avb79cAAweNmMUb/Zw18RNd4mng==",
"cpu": [
"ia32"
],
@@ -962,9 +993,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.24.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.2.tgz",
- "integrity": "sha512-2mLH46K1u3r6uwc95hU+OR9q/ggYMpnS7pSp83Ece1HUQgF9Nh/QwTK5rcgbFnV9j+08yBrU5sA/P0RK2MSBNA==",
+ "version": "4.29.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.29.1.tgz",
+ "integrity": "sha512-+10CMg9vt1MoHj6x1pxyjPSMjHTIlqs8/tBztXvPAx24SKs9jwVnKqHJumlH/IzhaPUaj3T6T6wfZr8okdXaIg==",
"cpu": [
"x64"
],
@@ -990,9 +1021,9 @@
"peer": true
},
"node_modules/@types/node": {
- "version": "20.17.2",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.2.tgz",
- "integrity": "sha512-OOHK4sjXqkL7yQ7VEEHcf6+0jSvKjWqwnaCtY7AKD/VLEvRHMsxxu7eI8ErnjxHS8VwmekD4PeVCpu4qZEZSxg==",
+ "version": "20.17.10",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.10.tgz",
+ "integrity": "sha512-/jrvh5h6NXhEauFFexRin69nA0uHJ5gwk4iDivp/DeoEua3uwCUto6PC86IpRITBOs4+6i2I56K5x5b6WYGXHA==",
"devOptional": true,
"license": "MIT",
"dependencies": {
@@ -1000,96 +1031,96 @@
}
},
"node_modules/@vitejs/plugin-vue": {
- "version": "5.1.4",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.1.4.tgz",
- "integrity": "sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==",
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.1.tgz",
+ "integrity": "sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.0.0 || >=20.0.0"
},
"peerDependencies": {
- "vite": "^5.0.0",
+ "vite": "^5.0.0 || ^6.0.0",
"vue": "^3.2.25"
}
},
"node_modules/@volar/language-core": {
- "version": "2.4.8",
- "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.8.tgz",
- "integrity": "sha512-K/GxMOXGq997bO00cdFhTNuR85xPxj0BEEAy+BaqqayTmy9Tmhfgmq2wpJcVspRhcwfgPoE2/mEJa26emUhG/g==",
+ "version": "2.4.11",
+ "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.11.tgz",
+ "integrity": "sha512-lN2C1+ByfW9/JRPpqScuZt/4OrUUse57GLI6TbLgTIqBVemdl1wNcZ1qYGEo2+Gw8coYLgCy7SuKqn6IrQcQgg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@volar/source-map": "2.4.8"
+ "@volar/source-map": "2.4.11"
}
},
"node_modules/@volar/source-map": {
- "version": "2.4.8",
- "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.8.tgz",
- "integrity": "sha512-jeWJBkC/WivdelMwxKkpFL811uH/jJ1kVxa+c7OvG48DXc3VrP7pplSWPP2W1dLMqBxD+awRlg55FQQfiup4cA==",
+ "version": "2.4.11",
+ "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.11.tgz",
+ "integrity": "sha512-ZQpmafIGvaZMn/8iuvCFGrW3smeqkq/IIh9F1SdSx9aUl0J4Iurzd6/FhmjNO5g2ejF3rT45dKskgXWiofqlZQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@volar/typescript": {
- "version": "2.4.8",
- "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.8.tgz",
- "integrity": "sha512-6xkIYJ5xxghVBhVywMoPMidDDAFT1OoQeXwa27HSgJ6AiIKRe61RXLoik+14Z7r0JvnblXVsjsRLmCr42SGzqg==",
+ "version": "2.4.11",
+ "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.11.tgz",
+ "integrity": "sha512-2DT+Tdh88Spp5PyPbqhyoYavYCPDsqbHLFwcUI9K1NlY1YgUJvujGdrqUp0zWxnW7KWNTr3xSpMuv2WnaTKDAw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@volar/language-core": "2.4.8",
+ "@volar/language-core": "2.4.11",
"path-browserify": "^1.0.1",
"vscode-uri": "^3.0.8"
}
},
"node_modules/@vue/compiler-core": {
- "version": "3.5.12",
- "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.12.tgz",
- "integrity": "sha512-ISyBTRMmMYagUxhcpyEH0hpXRd/KqDU4ymofPgl2XAkY9ZhQ+h0ovEZJIiPop13UmR/54oA2cgMDjgroRelaEw==",
+ "version": "3.5.13",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz",
+ "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.25.3",
- "@vue/shared": "3.5.12",
+ "@vue/shared": "3.5.13",
"entities": "^4.5.0",
"estree-walker": "^2.0.2",
"source-map-js": "^1.2.0"
}
},
"node_modules/@vue/compiler-dom": {
- "version": "3.5.12",
- "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.12.tgz",
- "integrity": "sha512-9G6PbJ03uwxLHKQ3P42cMTi85lDRvGLB2rSGOiQqtXELat6uI4n8cNz9yjfVHRPIu+MsK6TE418Giruvgptckg==",
+ "version": "3.5.13",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz",
+ "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==",
"license": "MIT",
"dependencies": {
- "@vue/compiler-core": "3.5.12",
- "@vue/shared": "3.5.12"
+ "@vue/compiler-core": "3.5.13",
+ "@vue/shared": "3.5.13"
}
},
"node_modules/@vue/compiler-sfc": {
- "version": "3.5.12",
- "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.12.tgz",
- "integrity": "sha512-2k973OGo2JuAa5+ZlekuQJtitI5CgLMOwgl94BzMCsKZCX/xiqzJYzapl4opFogKHqwJk34vfsaKpfEhd1k5nw==",
+ "version": "3.5.13",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz",
+ "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.25.3",
- "@vue/compiler-core": "3.5.12",
- "@vue/compiler-dom": "3.5.12",
- "@vue/compiler-ssr": "3.5.12",
- "@vue/shared": "3.5.12",
+ "@vue/compiler-core": "3.5.13",
+ "@vue/compiler-dom": "3.5.13",
+ "@vue/compiler-ssr": "3.5.13",
+ "@vue/shared": "3.5.13",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.11",
- "postcss": "^8.4.47",
+ "postcss": "^8.4.48",
"source-map-js": "^1.2.0"
}
},
"node_modules/@vue/compiler-ssr": {
- "version": "3.5.12",
- "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.12.tgz",
- "integrity": "sha512-eLwc7v6bfGBSM7wZOGPmRavSWzNFF6+PdRhE+VFJhNCgHiF8AM7ccoqcv5kBXA2eWUfigD7byekvf/JsOfKvPA==",
+ "version": "3.5.13",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz",
+ "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==",
"license": "MIT",
"dependencies": {
- "@vue/compiler-dom": "3.5.12",
- "@vue/shared": "3.5.12"
+ "@vue/compiler-dom": "3.5.13",
+ "@vue/shared": "3.5.13"
}
},
"node_modules/@vue/compiler-vue2": {
@@ -1110,17 +1141,17 @@
"license": "MIT"
},
"node_modules/@vue/language-core": {
- "version": "2.1.8",
- "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.1.8.tgz",
- "integrity": "sha512-DtPUKrIRqqzY1joGfVHxHWZoxXZbCQLmVtW+QTifuPInfcs1R/3UAdlJXDp+lpSpP9lI5m+jMYYlwDXXu3KSTg==",
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.0.tgz",
+ "integrity": "sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@volar/language-core": "~2.4.8",
+ "@volar/language-core": "~2.4.11",
"@vue/compiler-dom": "^3.5.0",
"@vue/compiler-vue2": "^2.7.16",
"@vue/shared": "^3.5.0",
- "alien-signals": "^0.2.0",
+ "alien-signals": "^0.4.9",
"minimatch": "^9.0.3",
"muggle-string": "^0.4.1",
"path-browserify": "^1.0.1"
@@ -1161,53 +1192,53 @@
}
},
"node_modules/@vue/reactivity": {
- "version": "3.5.12",
- "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.12.tgz",
- "integrity": "sha512-UzaN3Da7xnJXdz4Okb/BGbAaomRHc3RdoWqTzlvd9+WBR5m3J39J1fGcHes7U3za0ruYn/iYy/a1euhMEHvTAg==",
+ "version": "3.5.13",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz",
+ "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==",
"license": "MIT",
"dependencies": {
- "@vue/shared": "3.5.12"
+ "@vue/shared": "3.5.13"
}
},
"node_modules/@vue/runtime-core": {
- "version": "3.5.12",
- "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.12.tgz",
- "integrity": "sha512-hrMUYV6tpocr3TL3Ad8DqxOdpDe4zuQY4HPY3X/VRh+L2myQO8MFXPAMarIOSGNu0bFAjh1yBkMPXZBqCk62Uw==",
+ "version": "3.5.13",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz",
+ "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==",
"license": "MIT",
"dependencies": {
- "@vue/reactivity": "3.5.12",
- "@vue/shared": "3.5.12"
+ "@vue/reactivity": "3.5.13",
+ "@vue/shared": "3.5.13"
}
},
"node_modules/@vue/runtime-dom": {
- "version": "3.5.12",
- "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.12.tgz",
- "integrity": "sha512-q8VFxR9A2MRfBr6/55Q3umyoN7ya836FzRXajPB6/Vvuv0zOPL+qltd9rIMzG/DbRLAIlREmnLsplEF/kotXKA==",
+ "version": "3.5.13",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz",
+ "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==",
"license": "MIT",
"dependencies": {
- "@vue/reactivity": "3.5.12",
- "@vue/runtime-core": "3.5.12",
- "@vue/shared": "3.5.12",
+ "@vue/reactivity": "3.5.13",
+ "@vue/runtime-core": "3.5.13",
+ "@vue/shared": "3.5.13",
"csstype": "^3.1.3"
}
},
"node_modules/@vue/server-renderer": {
- "version": "3.5.12",
- "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.12.tgz",
- "integrity": "sha512-I3QoeDDeEPZm8yR28JtY+rk880Oqmj43hreIBVTicisFTx/Dl7JpG72g/X7YF8hnQD3IFhkky5i2bPonwrTVPg==",
+ "version": "3.5.13",
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz",
+ "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==",
"license": "MIT",
"dependencies": {
- "@vue/compiler-ssr": "3.5.12",
- "@vue/shared": "3.5.12"
+ "@vue/compiler-ssr": "3.5.13",
+ "@vue/shared": "3.5.13"
},
"peerDependencies": {
- "vue": "3.5.12"
+ "vue": "3.5.13"
}
},
"node_modules/@vue/shared": {
- "version": "3.5.12",
- "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.12.tgz",
- "integrity": "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==",
+ "version": "3.5.13",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz",
+ "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==",
"license": "MIT"
},
"node_modules/@vuetify/loader-shared": {
@@ -1266,9 +1297,9 @@
}
},
"node_modules/alien-signals": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-0.2.0.tgz",
- "integrity": "sha512-StlonZhBBrsPPwrDjiPAiVTf/rolxffLxVPT60Qv/t88BZ81BvUVzHgGqEFvJ1ii8HXtm1+zU2Icr59tfWEcag==",
+ "version": "0.4.10",
+ "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-0.4.10.tgz",
+ "integrity": "sha512-7S60rz/mMjz0Djq1VI9rd4bGqKNgxTUGE6k7kwrRO6tF95qt1S3ohz1qaQisvUsfbGh7yXnm6DPRrOhOl1ho1A==",
"dev": true,
"license": "MIT"
},
@@ -1318,9 +1349,9 @@
"license": "MIT"
},
"node_modules/axios": {
- "version": "1.7.7",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
- "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
+ "version": "1.7.9",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
+ "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
@@ -1407,9 +1438,9 @@
}
},
"node_modules/chart.js": {
- "version": "4.4.6",
- "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.6.tgz",
- "integrity": "sha512-8Y406zevUPbbIBA/HRk33khEmQPk5+cxeflWE/2rx1NJsjVWMPw/9mSP9rxHP5eqi6LNoPBVMfZHxbwLSgldYA==",
+ "version": "4.4.7",
+ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.7.tgz",
+ "integrity": "sha512-pwkcKfdzTMAU/+jNosKhNL2bHtJc/sSmYgVbuGTEDhzkrhmyihmP7vUc/5ZK9WopidMDHNe3Wm7jOd/WhuHWuw==",
"license": "MIT",
"dependencies": {
"@kurkle/color": "^0.3.0"
@@ -1508,9 +1539,9 @@
"license": "MIT"
},
"node_modules/core-js": {
- "version": "3.38.1",
- "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.38.1.tgz",
- "integrity": "sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==",
+ "version": "3.39.0",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.39.0.tgz",
+ "integrity": "sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==",
"hasInstallScript": true,
"license": "MIT",
"funding": {
@@ -1519,9 +1550,9 @@
}
},
"node_modules/cross-spawn": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
- "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"license": "MIT",
"peer": true,
@@ -1561,9 +1592,9 @@
"license": "MIT"
},
"node_modules/debug": {
- "version": "4.3.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
- "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
+ "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"devOptional": true,
"license": "MIT",
"dependencies": {
@@ -1667,33 +1698,33 @@
}
},
"node_modules/eslint": {
- "version": "9.13.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.13.0.tgz",
- "integrity": "sha512-EYZK6SX6zjFHST/HRytOdA/zE72Cq/bfw45LSyuwrdvcclb/gqV8RRQxywOBEWO2+WDpva6UZa4CcDeJKzUCFA==",
+ "version": "9.17.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz",
+ "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
- "@eslint-community/regexpp": "^4.11.0",
- "@eslint/config-array": "^0.18.0",
- "@eslint/core": "^0.7.0",
- "@eslint/eslintrc": "^3.1.0",
- "@eslint/js": "9.13.0",
- "@eslint/plugin-kit": "^0.2.0",
- "@humanfs/node": "^0.16.5",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.19.0",
+ "@eslint/core": "^0.9.0",
+ "@eslint/eslintrc": "^3.2.0",
+ "@eslint/js": "9.17.0",
+ "@eslint/plugin-kit": "^0.2.3",
+ "@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
- "@humanwhocodes/retry": "^0.3.1",
+ "@humanwhocodes/retry": "^0.4.1",
"@types/estree": "^1.0.6",
"@types/json-schema": "^7.0.15",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
- "cross-spawn": "^7.0.2",
+ "cross-spawn": "^7.0.6",
"debug": "^4.3.2",
"escape-string-regexp": "^4.0.0",
- "eslint-scope": "^8.1.0",
- "eslint-visitor-keys": "^4.1.0",
- "espree": "^10.2.0",
+ "eslint-scope": "^8.2.0",
+ "eslint-visitor-keys": "^4.2.0",
+ "espree": "^10.3.0",
"esquery": "^1.5.0",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
@@ -1707,8 +1738,7 @@
"lodash.merge": "^4.6.2",
"minimatch": "^3.1.2",
"natural-compare": "^1.4.0",
- "optionator": "^0.9.3",
- "text-table": "^0.2.0"
+ "optionator": "^0.9.3"
},
"bin": {
"eslint": "bin/eslint.js"
@@ -1729,9 +1759,9 @@
}
},
"node_modules/eslint-plugin-vue": {
- "version": "9.30.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.30.0.tgz",
- "integrity": "sha512-CyqlRgShvljFkOeYK8wN5frh/OGTvkj1S7wlr2Q2pUvwq+X5VYiLd6ZjujpgSgLnys2W8qrBLkXQ41SUYaoPIQ==",
+ "version": "9.32.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.32.0.tgz",
+ "integrity": "sha512-b/Y05HYmnB/32wqVcjxjHZzNpwxj1onBOvqW89W+V+XNG1dRuaFbNd3vT9CLbr2LXjEoq+3vn8DanWf7XU22Ug==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1768,9 +1798,9 @@
}
},
"node_modules/eslint-scope": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.1.0.tgz",
- "integrity": "sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==",
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz",
+ "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==",
"dev": true,
"license": "BSD-2-Clause",
"peer": true,
@@ -1786,9 +1816,9 @@
}
},
"node_modules/eslint-visitor-keys": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz",
- "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
+ "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
@@ -1800,16 +1830,16 @@
}
},
"node_modules/espree": {
- "version": "10.2.0",
- "resolved": "https://registry.npmjs.org/espree/-/espree-10.2.0.tgz",
- "integrity": "sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==",
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
+ "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
"dev": true,
"license": "BSD-2-Clause",
"peer": true,
"dependencies": {
- "acorn": "^8.12.0",
+ "acorn": "^8.14.0",
"acorn-jsx": "^5.3.2",
- "eslint-visitor-keys": "^4.1.0"
+ "eslint-visitor-keys": "^4.2.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1926,9 +1956,9 @@
"peer": true
},
"node_modules/fastq": {
- "version": "1.17.1",
- "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
- "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
+ "version": "1.18.0",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz",
+ "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==",
"dev": true,
"license": "ISC",
"dependencies": {
@@ -1996,9 +2026,9 @@
}
},
"node_modules/flatted": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
- "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz",
+ "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==",
"dev": true,
"license": "ISC",
"peer": true
@@ -2357,9 +2387,9 @@
"peer": true
},
"node_modules/magic-string": {
- "version": "0.30.12",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz",
- "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==",
+ "version": "0.30.17",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
+ "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0"
@@ -2477,9 +2507,9 @@
"license": "MIT"
},
"node_modules/nanoid": {
- "version": "3.3.7",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
- "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
+ "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
"funding": [
{
"type": "github",
@@ -2680,9 +2710,9 @@
}
},
"node_modules/pinia": {
- "version": "2.2.4",
- "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.2.4.tgz",
- "integrity": "sha512-K7ZhpMY9iJ9ShTC0cR2+PnxdQRuwVIsXDO/WIEV/RnMC/vmSoKDTKW/exNQYPI+4ij10UjXqdNiEHwn47McANQ==",
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.0.tgz",
+ "integrity": "sha512-ohZj3jla0LL0OH5PlLTDMzqKiVw2XARmC1XYLdLWIPBMdhDW/123ZWr4zVAhtJm+aoSkFa13pYXskAvAscIkhQ==",
"license": "MIT",
"dependencies": {
"@vue/devtools-api": "^6.6.3",
@@ -2692,49 +2722,19 @@
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
- "@vue/composition-api": "^1.4.0",
"typescript": ">=4.4.4",
- "vue": "^2.6.14 || ^3.3.0"
+ "vue": "^2.7.0 || ^3.5.11"
},
"peerDependenciesMeta": {
- "@vue/composition-api": {
- "optional": true
- },
"typescript": {
"optional": true
}
}
},
- "node_modules/pinia/node_modules/vue-demi": {
- "version": "0.14.10",
- "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
- "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
- "hasInstallScript": true,
- "license": "MIT",
- "bin": {
- "vue-demi-fix": "bin/vue-demi-fix.js",
- "vue-demi-switch": "bin/vue-demi-switch.js"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/antfu"
- },
- "peerDependencies": {
- "@vue/composition-api": "^1.0.0-rc.1",
- "vue": "^3.0.0-0 || ^2.6.0"
- },
- "peerDependenciesMeta": {
- "@vue/composition-api": {
- "optional": true
- }
- }
- },
"node_modules/postcss": {
- "version": "8.4.47",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
- "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
+ "version": "8.4.49",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
+ "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
"funding": [
{
"type": "opencollective",
@@ -2752,7 +2752,7 @@
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.7",
- "picocolors": "^1.1.0",
+ "picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
"engines": {
@@ -2802,9 +2802,9 @@
}
},
"node_modules/qrcode.vue": {
- "version": "3.5.1",
- "resolved": "https://registry.npmjs.org/qrcode.vue/-/qrcode.vue-3.5.1.tgz",
- "integrity": "sha512-Mek5hpUgYP2KsRW4mnyPMUttknuXSe37UorUzymYi3rr/74rV0aTvejl2gF2phrxwAEm6zhpSvkGzIxftxj5Tg==",
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/qrcode.vue/-/qrcode.vue-3.6.0.tgz",
+ "integrity": "sha512-vQcl2fyHYHMjDO1GguCldJxepq2izQjBkDEEu9NENgfVKP6mv/e2SU62WbqYHGwTgWXLhxZ1NCD1dAZKHQq1fg==",
"license": "MIT",
"peerDependencies": {
"vue": "^3.0.0"
@@ -2889,9 +2889,9 @@
"license": "Apache-2.0"
},
"node_modules/rollup": {
- "version": "4.24.2",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.2.tgz",
- "integrity": "sha512-do/DFGq5g6rdDhdpPq5qb2ecoczeK6y+2UAjdJ5trjQJj5f1AiVdLRWRc9A9/fFukfvJRgM0UXzxBIYMovm5ww==",
+ "version": "4.29.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.29.1.tgz",
+ "integrity": "sha512-RaJ45M/kmJUzSWDs1Nnd5DdV4eerC98idtUOVr6FfKcgxqvjwHmxc5upLF9qZU9EpsVzzhleFahrT3shLuJzIw==",
"devOptional": true,
"license": "MIT",
"dependencies": {
@@ -2905,24 +2905,25 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.24.2",
- "@rollup/rollup-android-arm64": "4.24.2",
- "@rollup/rollup-darwin-arm64": "4.24.2",
- "@rollup/rollup-darwin-x64": "4.24.2",
- "@rollup/rollup-freebsd-arm64": "4.24.2",
- "@rollup/rollup-freebsd-x64": "4.24.2",
- "@rollup/rollup-linux-arm-gnueabihf": "4.24.2",
- "@rollup/rollup-linux-arm-musleabihf": "4.24.2",
- "@rollup/rollup-linux-arm64-gnu": "4.24.2",
- "@rollup/rollup-linux-arm64-musl": "4.24.2",
- "@rollup/rollup-linux-powerpc64le-gnu": "4.24.2",
- "@rollup/rollup-linux-riscv64-gnu": "4.24.2",
- "@rollup/rollup-linux-s390x-gnu": "4.24.2",
- "@rollup/rollup-linux-x64-gnu": "4.24.2",
- "@rollup/rollup-linux-x64-musl": "4.24.2",
- "@rollup/rollup-win32-arm64-msvc": "4.24.2",
- "@rollup/rollup-win32-ia32-msvc": "4.24.2",
- "@rollup/rollup-win32-x64-msvc": "4.24.2",
+ "@rollup/rollup-android-arm-eabi": "4.29.1",
+ "@rollup/rollup-android-arm64": "4.29.1",
+ "@rollup/rollup-darwin-arm64": "4.29.1",
+ "@rollup/rollup-darwin-x64": "4.29.1",
+ "@rollup/rollup-freebsd-arm64": "4.29.1",
+ "@rollup/rollup-freebsd-x64": "4.29.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.29.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.29.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.29.1",
+ "@rollup/rollup-linux-arm64-musl": "4.29.1",
+ "@rollup/rollup-linux-loongarch64-gnu": "4.29.1",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.29.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.29.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.29.1",
+ "@rollup/rollup-linux-x64-gnu": "4.29.1",
+ "@rollup/rollup-linux-x64-musl": "4.29.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.29.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.29.1",
+ "@rollup/rollup-win32-x64-msvc": "4.29.1",
"fsevents": "~2.3.2"
}
},
@@ -3049,14 +3050,6 @@
"node": ">=8"
}
},
- "node_modules/text-table": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
- "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
- "dev": true,
- "license": "MIT",
- "peer": true
- },
"node_modules/tiny-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
@@ -3104,9 +3097,9 @@
}
},
"node_modules/typescript": {
- "version": "5.6.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
- "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz",
+ "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==",
"devOptional": true,
"license": "Apache-2.0",
"bin": {
@@ -3125,9 +3118,9 @@
"license": "MIT"
},
"node_modules/unplugin": {
- "version": "1.15.0",
- "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.15.0.tgz",
- "integrity": "sha512-jTPIs63W+DUEDW207ztbaoO7cQ4p5aVaB823LSlxpsFEU3Mykwxf3ZGC/wzxFJeZlASZYgVrWeo7LgOrqJZ8RA==",
+ "version": "2.0.0-beta.1",
+ "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.0.0-beta.1.tgz",
+ "integrity": "sha512-2qzQo5LN2DmUZXkWDHvGKLF5BP0WN+KthD6aPnPJ8plRBIjv4lh5O07eYcSxgO2znNw9s4MNhEO1sB+JDllDbQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3135,30 +3128,22 @@
"webpack-virtual-modules": "^0.6.2"
},
"engines": {
- "node": ">=14.0.0"
- },
- "peerDependencies": {
- "webpack-sources": "^3"
- },
- "peerDependenciesMeta": {
- "webpack-sources": {
- "optional": true
- }
+ "node": ">=18.12.0"
}
},
"node_modules/unplugin-fonts": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/unplugin-fonts/-/unplugin-fonts-1.1.1.tgz",
- "integrity": "sha512-/Aw/rL9D2aslGGM0vi+2R2aG508RSwawLnnBuo+JDSqYc4cHJO1R1phllhN6GysEhBp/6a4B6+vSFPVapWyAAw==",
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/unplugin-fonts/-/unplugin-fonts-1.3.1.tgz",
+ "integrity": "sha512-GmaJWPAWH6lBI4fP8xKdbMZJwTgsnr8PGJOfQE52jlod8QkqSO4M529Nox2L8zYapjB5hox2wCu4N3c/LOal/A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "fast-glob": "^3.2.12",
- "unplugin": "^1.3.1"
+ "fast-glob": "^3.3.2",
+ "unplugin": "2.0.0-beta.1"
},
"peerDependencies": {
"@nuxt/kit": "^3.0.0",
- "vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0"
+ "vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0"
},
"peerDependenciesMeta": {
"@nuxt/kit": {
@@ -3196,9 +3181,9 @@
"license": "MIT"
},
"node_modules/vite": {
- "version": "5.4.10",
- "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz",
- "integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==",
+ "version": "5.4.11",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz",
+ "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==",
"devOptional": true,
"license": "MIT",
"dependencies": {
@@ -3283,16 +3268,16 @@
"license": "MIT"
},
"node_modules/vue": {
- "version": "3.5.12",
- "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.12.tgz",
- "integrity": "sha512-CLVZtXtn2ItBIi/zHZ0Sg1Xkb7+PU32bJJ8Bmy7ts3jxXTcbfsEfBivFYYWz1Hur+lalqGAh65Coin0r+HRUfg==",
+ "version": "3.5.13",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz",
+ "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==",
"license": "MIT",
"dependencies": {
- "@vue/compiler-dom": "3.5.12",
- "@vue/compiler-sfc": "3.5.12",
- "@vue/runtime-dom": "3.5.12",
- "@vue/server-renderer": "3.5.12",
- "@vue/shared": "3.5.12"
+ "@vue/compiler-dom": "3.5.13",
+ "@vue/compiler-sfc": "3.5.13",
+ "@vue/runtime-dom": "3.5.13",
+ "@vue/server-renderer": "3.5.13",
+ "@vue/shared": "3.5.13"
},
"peerDependencies": {
"typescript": "*"
@@ -3304,15 +3289,41 @@
}
},
"node_modules/vue-chartjs": {
- "version": "5.3.1",
- "resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.3.1.tgz",
- "integrity": "sha512-rZjqcHBxKiHrBl0CIvcOlVEBwRhpWAVf6rDU3vUfa7HuSRmGtCslc0Oc8m16oAVuk0erzc1FCtH1VCriHsrz+A==",
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.3.2.tgz",
+ "integrity": "sha512-NrkbRRoYshbXbWqJkTN6InoDVwVb90C0R7eAVgMWcB9dPikbruaOoTFjFYHE/+tNPdIe6qdLCDjfjPHQ0fw4jw==",
"license": "MIT",
"peerDependencies": {
"chart.js": "^4.1.1",
"vue": "^3.0.0-0 || ^2.7.0"
}
},
+ "node_modules/vue-demi": {
+ "version": "0.14.10",
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
+ "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "vue-demi-fix": "bin/vue-demi-fix.js",
+ "vue-demi-switch": "bin/vue-demi-switch.js"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@vue/composition-api": "^1.0.0-rc.1",
+ "vue": "^3.0.0-0 || ^2.6.0"
+ },
+ "peerDependenciesMeta": {
+ "@vue/composition-api": {
+ "optional": true
+ }
+ }
+ },
"node_modules/vue-eslint-parser": {
"version": "9.4.3",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz",
@@ -3387,13 +3398,13 @@
}
},
"node_modules/vue-i18n": {
- "version": "9.14.1",
- "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.14.1.tgz",
- "integrity": "sha512-xjxV0LYc1xQ8TbAVfIyZiOSS8qoU1R0YwV7V5I8I6Fd64+zvsTsdPgtylPsie3Vdt9wekeYhr+smKDeaK6RBuA==",
+ "version": "9.14.2",
+ "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.14.2.tgz",
+ "integrity": "sha512-JK9Pm80OqssGJU2Y6F7DcM8RFHqVG4WkuCqOZTVsXkEzZME7ABejAUqUdA931zEBedc4thBgSUWxeQh4uocJAQ==",
"license": "MIT",
"dependencies": {
- "@intlify/core-base": "9.14.1",
- "@intlify/shared": "9.14.1",
+ "@intlify/core-base": "9.14.2",
+ "@intlify/shared": "9.14.2",
"@vue/devtools-api": "^6.5.0"
},
"engines": {
@@ -3407,9 +3418,9 @@
}
},
"node_modules/vue-router": {
- "version": "4.4.5",
- "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.4.5.tgz",
- "integrity": "sha512-4fKZygS8cH1yCyuabAXGUAsyi1b2/o/OKgu/RUb+znIYOxPRxdkytJEx+0wGcpBE1pX6vUgh5jwWOKRGvuA/7Q==",
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.0.tgz",
+ "integrity": "sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==",
"license": "MIT",
"dependencies": {
"@vue/devtools-api": "^6.6.4"
@@ -3422,15 +3433,14 @@
}
},
"node_modules/vue-tsc": {
- "version": "2.1.8",
- "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.1.8.tgz",
- "integrity": "sha512-6+vjb7JLxKIzeD/1ktoUBZGAr+148FQoEFl8Lv5EpDJLO2PrUalhp7atMEuzEkLnoooM5bg3pJqjZI+oobxIaQ==",
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.0.tgz",
+ "integrity": "sha512-gtmM1sUuJ8aSb0KoAFmK9yMxb8TxjewmxqTJ1aKphD5Cbu0rULFY6+UQT51zW7SpUcenfPUuflKyVwyx9Qdnxg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@volar/typescript": "~2.4.8",
- "@vue/language-core": "2.1.8",
- "semver": "^7.5.4"
+ "@volar/typescript": "~2.4.11",
+ "@vue/language-core": "2.2.0"
},
"bin": {
"vue-tsc": "bin/vue-tsc.js"
@@ -3449,9 +3459,9 @@
}
},
"node_modules/vuetify": {
- "version": "3.7.3",
- "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.7.3.tgz",
- "integrity": "sha512-bpuvBpZl1/+nLlXDgdVXekvMNR6W/ciaoa8CYlpeAzAARbY8zUFSoBq05JlLhkIHI58AnzKVy4c09d0OtfYAPg==",
+ "version": "3.7.6",
+ "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.7.6.tgz",
+ "integrity": "sha512-lol0Va5HtMIqZfjccSD5DLv5v31R/asJXzc6s7ULy51PHr1DjXxWylZejhq0kVpMGW64MiV1FmA/p8eYQfOWfQ==",
"license": "MIT",
"engines": {
"node": "^12.20 || >=14.13"
diff --git a/frontend/src/components/Listen.vue b/frontend/src/components/Listen.vue
index 2b29110..8ea85dc 100644
--- a/frontend/src/components/Listen.vue
+++ b/frontend/src/components/Listen.vue
@@ -14,6 +14,8 @@
:label="$t('in.port')"
hide-details
type="number"
+ min="1"
+ max="65535"
required
v-model.number="inbound.listen_port">
diff --git a/frontend/src/components/OutJson.vue b/frontend/src/components/OutJson.vue
index 35dbf74..afbc382 100644
--- a/frontend/src/components/OutJson.vue
+++ b/frontend/src/components/OutJson.vue
@@ -6,20 +6,20 @@
hide-details
:items="['4','4a','5']"
:label="$t('version')"
- v-model="inData.outJson.version">
+ v-model="inData.out_json.version">
-
+
-
+
+ v-model="inData.out_json.path">
@@ -36,14 +36,14 @@
hide-details
:label="$t('types.vmess.security')"
:items="vmessSecurities"
- v-model="inData.outJson.security">
+ v-model="inData.out_json.security">
-
+
-
+
@@ -52,7 +52,7 @@
hide-details
type="number"
min="0"
- v-model.number="inData.outJson.recv_window">
+ v-model.number="inData.out_json.recv_window">
@@ -62,16 +62,16 @@
label="UDP Relay Mode"
:items="['native', 'quic']"
clearable
- @click:clear="delete inData.outJson.udp_relay_mode"
- v-model="inData.outJson.udp_relay_mode">
+ @click:clear="delete inData.out_json.udp_relay_mode"
+ v-model="inData.out_json.udp_relay_mode">
-
+
-
+
@@ -114,8 +114,8 @@ export default {
needNetwork():boolean { return this.haveNetwork.includes(this.$props.type) },
needUot():boolean { return this.havUoT.includes(this.$props.type) },
packet_encoding: {
- get() { return this.$props.inData.outJson.packet_encoding != undefined ? this.$props.inData.outJson.packet_encoding : 'none'; },
- set(v:string) { this.$props.inData.outJson.packet_encoding = v != "none" ? v : undefined }
+ get() { return this.$props.inData.out_json.packet_encoding != undefined ? this.$props.inData.out_json.packet_encoding : 'none'; },
+ set(v:string) { this.$props.inData.out_json.packet_encoding = v != "none" ? v : undefined }
},
},
components: { Network, UoT, Headers }
diff --git a/frontend/src/components/WgPeer.vue b/frontend/src/components/WgPeer.vue
index 6dffcc1..695fe3f 100644
--- a/frontend/src/components/WgPeer.vue
+++ b/frontend/src/components/WgPeer.vue
@@ -4,7 +4,7 @@
+ v-model="address">
@@ -13,7 +13,16 @@
type="number"
min="0"
hide-details
- v-model="data.server_port">
+ v-model="port">
+
+
+
+
@@ -36,6 +45,8 @@
\ No newline at end of file
diff --git a/frontend/src/components/protocols/Wireguard.vue b/frontend/src/components/protocols/Wireguard.vue
index 4c2b7c2..d589474 100644
--- a/frontend/src/components/protocols/Wireguard.vue
+++ b/frontend/src/components/protocols/Wireguard.vue
@@ -2,22 +2,40 @@
-
+
+
-
-
-
-
-
-
-
+
-
-
+
+
+
+
+
+
+
+
+
-
+
-
+
+ v-model="data.name">
-
-
-
-
-
-
-
-
@@ -66,10 +76,7 @@
-
-
-
-
+
@@ -80,9 +87,6 @@
-
-
-
@@ -95,7 +99,7 @@
- {{ $t('types.wg.peer') + ' ' + (index+1) }}
+ {{ $t('types.wg.peer') + ' ' + (index+1) }}
@@ -104,9 +108,8 @@
\ No newline at end of file
diff --git a/frontend/src/components/tls/InTLS.vue b/frontend/src/components/tls/InTLS.vue
index 9a54cf5..5763141 100644
--- a/frontend/src/components/tls/InTLS.vue
+++ b/frontend/src/components/tls/InTLS.vue
@@ -1,242 +1,25 @@
-
-
-
-
+
+ v-model="inbound.tls_id">
-
-
-
-
- {{ $t('tls.usePath') }}
- {{ $t('tls.useText') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ $t('tls.options') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/frontend/src/layouts/default/Drawer.vue b/frontend/src/layouts/default/Drawer.vue
index 4701039..13e8943 100644
--- a/frontend/src/layouts/default/Drawer.vue
+++ b/frontend/src/layouts/default/Drawer.vue
@@ -53,6 +53,7 @@ const menu = [
{ title: 'pages.inbounds', icon: 'mdi-cloud-download', path: '/inbounds' },
{ title: 'pages.clients', icon: 'mdi-account-multiple', path: '/clients' },
{ title: 'pages.outbounds', icon: 'mdi-cloud-upload', path: '/outbounds' },
+ { title: 'pages.endpoints', icon: 'mdi-cloud-tags', path: '/endpoints' },
{ title: 'pages.rules', icon: 'mdi-routes', path: '/rules' },
{ title: 'pages.tls', icon: 'mdi-certificate', path: '/tls' },
{ title: 'pages.basics', icon: 'mdi-application-cog', path: '/basics' },
diff --git a/frontend/src/layouts/modals/Client.vue b/frontend/src/layouts/modals/Client.vue
index cae827a..f9143cd 100644
--- a/frontend/src/layouts/modals/Client.vue
+++ b/frontend/src/layouts/modals/Client.vue
@@ -41,7 +41,7 @@
-
+
@@ -80,11 +80,6 @@
>
-
-
-
-
-
@@ -189,7 +184,7 @@ import DatePick from '@/components/DateTime.vue'
import { HumanReadable } from '@/plugins/utils'
export default {
- props: ['visible', 'data', 'index', 'inboundTags', 'groups', 'stats'],
+ props: ['visible', 'data', 'id', 'inboundTags', 'groups'],
emits: ['close', 'save'],
data() {
return {
@@ -206,7 +201,7 @@ export default {
},
methods: {
updateData() {
- if (this.$props.index != -1) {
+ if (this.$props.id > 0) {
const newData = JSON.parse(this.$props.data)
this.client = createClient(newData)
this.title = "edit"
@@ -217,7 +212,6 @@ export default {
this.title = "add"
this.clientConfig = randomConfigs('client')
}
- this.clientStats = this.$props.stats
this.links = this.client.links.filter(l => l.type == 'local')
this.extLinks = this.client.links.filter(l => l.type == 'external')
this.subLinks = this.client.links.filter(l => l.type == 'sub')
@@ -243,8 +237,8 @@ export default {
},
computed: {
clientInbounds: {
- get() { return this.client.inbounds.length>0 ? this.client.inbounds.filter(i => this.inboundTags.includes(i)) : [] },
- set(newValue:string[]) { this.client.inbounds = newValue.length == 0 ? [] : newValue }
+ get() { return this.client.inbounds.length>0 ? this.client.inbounds : [] },
+ set(v:any[]) { this.client.inbounds = v.length == 0 ? [] : v.map(i => i.value) }
},
expDate: {
get() { return this.client.expiry},
diff --git a/frontend/src/layouts/modals/ClientBulk.vue b/frontend/src/layouts/modals/ClientBulk.vue
index aaa3971..4f305dc 100644
--- a/frontend/src/layouts/modals/ClientBulk.vue
+++ b/frontend/src/layouts/modals/ClientBulk.vue
@@ -59,11 +59,6 @@
>
-
-
-
-
-
@@ -109,7 +104,6 @@ export default {
clientInbounds: [],
expiry: 0,
Volume: 0,
- clientStats: false,
},
patterns: [
{ title: i18n.global.t("bulk.random"), value: "random" },
@@ -129,7 +123,6 @@ export default {
clientInbounds: [],
expiry: 0,
Volume: 0,
- clientStats: false,
}
},
closeModal() {
@@ -157,7 +150,7 @@ export default {
group: this.bulkData.group
}))
}
- this.$emit('save', this.clients, this.bulkData.clientInbounds, this.bulkData.clientStats)
+ this.$emit('save', this.clients, this.bulkData.clientInbounds)
this.resetData() // reset to default
this.loading = false
},
diff --git a/frontend/src/layouts/modals/Endpoint.vue b/frontend/src/layouts/modals/Endpoint.vue
new file mode 100644
index 0000000..ef958d4
--- /dev/null
+++ b/frontend/src/layouts/modals/Endpoint.vue
@@ -0,0 +1,153 @@
+
+
+
+
+ {{ $t('actions.' + title) + " " + $t('objects.endpoint') }}
+
+
+
+
+
+ {{ $t('client.basics') }}
+ {{ $t('client.external') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('submit') }}
+
+
+
+
+
+
+
+
+
+ {{ $t('actions.close') }}
+
+
+ {{ $t('actions.save') }}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/layouts/modals/Inbound.vue b/frontend/src/layouts/modals/Inbound.vue
index cfd8cdf..026e885 100644
--- a/frontend/src/layouts/modals/Inbound.vue
+++ b/frontend/src/layouts/modals/Inbound.vue
@@ -1,12 +1,18 @@
-
-
+
+
{{ $t('actions.' + title) + " " + $t('objects.inbound') }}
+
-
+
-
+
-
-
-
+
+
{{ $t('in.multiDomain') }}
-
- {{ $t('in.addr') }} #{{ (index+1) }}
+
+ {{ $t('in.addr') }} #{{ (index+1) }}
@@ -79,6 +84,7 @@
color="blue-darken-1"
variant="text"
:loading="loading"
+ :disabled="!validate"
@click="saveChanges"
>
{{ $t('actions.save') }}
@@ -89,8 +95,7 @@
\ No newline at end of file
diff --git a/frontend/src/views/Clients.vue b/frontend/src/views/Clients.vue
index f2542fc..0812a5b 100644
--- a/frontend/src/views/Clients.vue
+++ b/frontend/src/views/Clients.vue
@@ -3,10 +3,9 @@
- {{ $t('actions.add') }}
+ {{ $t('actions.add') }}
@@ -147,7 +146,6 @@
@@ -162,7 +160,7 @@
{{ $t('pages.inbounds') }}
- {{ i }}
+ {{ inbounds.find(inb => inb.id == i)?.tag }}
{{ item.inbounds.length }}
@@ -230,7 +228,7 @@
-
+
@@ -324,7 +322,7 @@
>
mdi-qrcode
-
+
@@ -349,8 +347,7 @@ import QrCode from '@/layouts/modals/QrCode.vue'
import Stats from '@/layouts/modals/Stats.vue'
import { Client, createClient } from '@/types/clients'
import { computed, ref } from 'vue'
-import { Config, V2rayApiStats } from '@/types/config'
-import { InTypes, Inbound,InboundWithUser, ShadowTLS, VLESS } from '@/types/inbounds'
+import { Inbound, inboundWithUsers } from '@/types/inbounds'
import { Link, LinkUtil } from '@/plugins/link'
import { HumanReadable } from '@/plugins/utils'
import { i18n } from '@/locales'
@@ -367,21 +364,13 @@ const isOnline = (cname: string) => computed(() => {
return Data().onlines?.user ? Data().onlines.user.includes(cname) : false
})
-const appConfig = computed((): Config => {
- return Data().config
-})
-
-const v2rayStats = computed((): V2rayApiStats => {
- return appConfig.value.experimental.v2ray_api.stats
-})
-
const inbounds = computed((): Inbound[] => {
- return appConfig.value?.inbounds
+ return Data().inbounds?? []
})
-const inboundTags = computed((): string[] => {
+const inboundTags = computed((): any[] => {
if (!inbounds.value) return []
- return inbounds.value?.filter(i => i.tag != "" && Object.hasOwn(i,'users')).map(i => i.tag)
+ return inbounds.value?.filter(i => i.tag != "" && inboundWithUsers.includes(i.type)).map(i => { return { title: i.tag, value: i.id } })
})
const groups = computed((): string[] => {
@@ -430,102 +419,44 @@ const groupBy = [
const modal = ref({
visible: false,
- index: -1,
+ id: 0,
data: "",
- stats: false,
})
const delOverlay = ref(new Array(clients.value.length).fill(false))
-const showModal = (id: number) => {
- const index = id == -1 ? -1 : clients.value.findIndex(c => c.id == id)
- modal.value.index = index
- modal.value.data = index == -1 ? '' : JSON.stringify(clients.value[index])
- modal.value.stats = index == -1 ? false : v2rayStats.value.users.includes(clients.value[index].name)
+const showModal = async (id: number) => {
+ modal.value.id = id
+ modal.value.data = id == 0 ? '' : JSON.stringify(clients.value.findLast(o => o.id == id))
modal.value.visible = true
}
const closeModal = () => {
modal.value.visible = false
}
-const saveModal = (data:any, stats:boolean) => {
+const saveModal = async (data:any) => {
// Check duplicate name
- const oldName = modal.value.index != -1 ? clients.value[modal.value.index].name : null
+ const oldName = modal.value.id > 0 ? clients.value.findLast(i => i.id == modal.value.id)?.name : null
if (data.name != oldName && clients.value.findIndex(c => c.name == data.name) != -1) {
push.error({
message: i18n.global.t('error.dplData') + ": " + i18n.global.t('client.name')
})
return
}
- if(modal.value.index == -1) {
- clients.value.push(data)
- } else {
- clients.value[modal.value.index] = data
- }
-
- // Rebuild affected inbounds
- buildInboundsUsers(data.inbounds)
// Rebuild links
- data.links = updateLinks(data)
+ const clientInbounds = data.inbounds.length == 0 ? [] : await Data().loadInbounds(data.inbounds)
+ data.links = updateLinks(data, clientInbounds)
- // Set Client Stats
- const sIndex = v2rayStats.value.users.findIndex(i => i == data.name) // Find if new user exists
-
- if (oldName != data.name) {
- v2rayStats.value.users = v2rayStats.value.users.filter(item => item != oldName)
- }
-
- if (stats) {
- // Add if dos not exist
- if (data.name.length>0 && sIndex == -1) v2rayStats.value.users.push(data.name)
- } else {
- // Delete if exists
- if (sIndex != -1) v2rayStats.value.users.splice(sIndex,1)
- }
-
- modal.value.visible = false
+ // save data
+ const success = await Data().save("clients", modal.value.id == 0 ? "new" : "edit", data)
+ if (success) modal.value.visible = false
}
-const buildInboundsUsers = (inboundTags:string[]) => {
- inboundTags.forEach(tag => {
- const inbound_index = inbounds.value.findIndex(i => i.tag == tag)
- if (inbound_index != -1){
- const users = []
- const newInbound = inbounds.value[inbound_index]
- const inboundClients = clients.value.filter(c => c.enable && c.inbounds.includes(tag))
- inboundClients.forEach(c => {
- // Remove flow in non tls VLESS
- if (newInbound.type == InTypes.VLESS) {
- const vlessInbound = newInbound
- if (!vlessInbound.tls?.enabled || vlessInbound.transport?.type) delete(c.config?.vless?.flow)
- }
- users.push(c.config[newInbound.type])
- })
- newInbound.users = users
- // Exceptions for Naive and ShadowTLSv3
- if (users.length == 0){
- if (newInbound.type == InTypes.Naive) {
- newInbound.users = [{}]
- } else {
- if (newInbound.type == InTypes.ShadowTLS){
- const ssTls = newInbound
- if (ssTls.version == 3) newInbound.users = [{}]
- }
- }
- }
-
- inbounds.value[inbound_index] = newInbound
- }
- })
-}
-const updateLinks = (c:Client):Link[] => {
- const clientInbounds = inbounds.value.filter(i => c.inbounds.includes(i.tag))
+const updateLinks = (c:Client, clientInbounds:Inbound[]):Link[] => {
const newLinks = []
clientInbounds.forEach(i =>{
- const tlsConfig = Data().tlsConfigs?.findLast((t:any) => t.inbounds.includes(i.tag))
- const cData = Data().inData?.findLast((d:any) => d.tag == i.tag)
- const addrs = cData ? cData.addrs : []
- const uris = LinkUtil.linkGenerator(c,i, tlsConfig?.client?? {}, addrs)
+ const tls = i.tls_id && i.tls_id>0 ? Data().tlsConfigs?.findLast((t:any) => t.id == i.tls_id) : undefined
+ const uris = LinkUtil.linkGenerator(c,i, tls, i.addrs)
if (uris.length>0){
uris.forEach(uri => {
newLinks.push({ type: 'local', remark: i.tag, uri: uri })
@@ -537,21 +468,10 @@ const updateLinks = (c:Client):Link[] => {
return links
}
-const delClient = (id: number) => {
- const clientIndex = clients.value.findIndex(c => c.id === id)
- const oldData = createClient(clients.value[clientIndex])
-
- // Delete stats if exists and will be orphaned
- const tagCounts = clients.value.filter(i => i.name == oldData.name).length
- const sIndex = v2rayStats.value.users.findIndex(i => i == oldData.name)
- if (tagCounts == 1 && sIndex != -1){
- v2rayStats.value.users.splice(sIndex,1)
- }
-
- clients.value.splice(clientIndex,1)
- buildInboundsUsers(oldData.inbounds)
- if (id>0) Data().delClient(id)
- delOverlay.value[clientIndex] = false
+const delClient = async (id: number) => {
+ const index = clients.value.findIndex(c => c.id === id)
+ const success = await Data().save("clients", "del", id)
+ if (success) delOverlay.value[index] = false
}
const qrcode = ref({
@@ -636,14 +556,12 @@ const closeBulk = () => {
addBulkModal.value = false
}
-const saveBulk = (bulkClients: Client[], clientInbounds: string[], clientStats: boolean) => {
+const saveBulk = async (bulkClients: Client[], clientInbounds: number[]) => {
+ const inboundData = clientInbounds.length == 0 ? [] : await Data().loadInbounds(clientInbounds)
bulkClients.forEach((c,c_index) => {
- bulkClients[c_index].links = updateLinks(c)
+ bulkClients[c_index].links = updateLinks(c, inboundData)
})
clients.value.push(...bulkClients)
- buildInboundsUsers(clientInbounds)
- // Stats
- if (clientStats) v2rayStats.value.users.push(...bulkClients.map(bc => bc.name))
closeBulk()
}
\ No newline at end of file
diff --git a/frontend/src/views/Endpoints.vue b/frontend/src/views/Endpoints.vue
new file mode 100644
index 0000000..2258da0
--- /dev/null
+++ b/frontend/src/views/Endpoints.vue
@@ -0,0 +1,166 @@
+
+
+
+
+
+ {{ $t('actions.add') }}
+
+
+
+
+
+
+
+ {{ item.type }}
+
+
+
+
+ {{ $t('in.addr') }}
+
+ {{ item.address?.length>0 ? item.address[0] : '-' }}
+
+
+
+ {{ $t('in.port') }}
+
+ {{ item.listen_port?? '-' }}
+
+
+
+ {{ $t('types.wg.peers') }}
+
+ {{ item.peers.length?? '-' }}
+
+
+
+ {{ $t('online') }}
+
+
+ {{ $t('online') }}
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('confirm') }}
+
+ {{ $t('yes') }}
+ {{ $t('no') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/views/Inbounds.vue b/frontend/src/views/Inbounds.vue
index 17c647d..9da8321 100644
--- a/frontend/src/views/Inbounds.vue
+++ b/frontend/src/views/Inbounds.vue
@@ -2,10 +2,7 @@
- {{ $t('actions.add') }}
+ {{ $t('actions.add') }}
@@ -48,22 +45,25 @@
{{ $t('objects.tls') }}
- {{ Object.hasOwn(item,'tls') ? $t(item.tls?.enabled ? 'enable' : 'disable') : '-' }}
+ {{ item.tls_id > 0 ? $t('enable') : $t('disable') }}
{{ $t('pages.clients') }}
-
- {{ u }}
-
- {{ Array.isArray(item.users) ? item.users.length : '-' }}
+
+
+ {{ u }}
+
+ {{ findInboundUsers(item.tag).length }}
+
+ -
{{ $t('online') }}
-
+
{{ $t('online') }}
-
@@ -72,7 +72,7 @@
-
+
@@ -89,12 +89,12 @@
{{ $t('confirm') }}
- {{ $t('yes') }}
+ {{ $t('yes') }}
{{ $t('no') }}
-
+
@@ -108,9 +108,9 @@
import Data from '@/store/modules/data'
import InboundVue from '@/layouts/modals/Inbound.vue'
import Stats from '@/layouts/modals/Stats.vue'
-import { Config, V2rayApiStats } from '@/types/config'
-import { computed, ref } from 'vue'
-import { InTypes, Inbound, InboundWithUser, ShadowTLS, VLESS } from '@/types/inbounds'
+import { Config } from '@/types/config'
+import { computed, onMounted, ref } from 'vue'
+import { Inbound, inboundWithUsers } from '@/types/inbounds'
import { Client } from '@/types/clients'
import { Link, LinkUtil } from '@/plugins/link'
import { i18n } from '@/locales'
@@ -122,17 +122,13 @@ const appConfig = computed((): Config => {
})
const inbounds = computed((): Inbound[] => {
- return appConfig.value.inbounds
+ return Data().inbounds
})
const tlsConfigs = computed((): any[] => {
return Data().tlsConfigs
})
-const inData = computed((): any[] => {
- return Data().inData
-})
-
const inTags = computed((): string[] => {
return inbounds.value?.map(i => i.tag)
})
@@ -149,216 +145,101 @@ const onlines = computed(() => {
return Data().onlines.inbound ? inbounds.value.map(i => Data().onlines.inbound.includes(i.tag)) : []
})
-const v2rayStats = computed((): V2rayApiStats => {
- return appConfig.value.experimental?.v2ray_api.stats
-})
-
const modal = ref({
visible: false,
- index: -1,
- data: "",
- cData: "",
- stats: false,
+ id: 0,
})
let delOverlay = ref(new Array)
-const showModal = (index: number) => {
- modal.value.index = index
- if (index == -1){
- modal.value.data = ''
- modal.value.cData = ''
- modal.value.stats = false
- } else {
- modal.value.data = JSON.stringify(inbounds.value[index])
- modal.value.stats = v2rayStats.value.inbounds.includes(inbounds.value[index].tag)
- const inDataIndex = inData.value.findIndex(d => d.tag == inbounds.value[index].tag)
- modal.value.cData = inDataIndex == -1 ? '' : JSON.stringify(inData.value[inDataIndex])
- }
+const showModal = (id: number) => {
+ modal.value.id = id
modal.value.visible = true
}
const closeModal = () => {
modal.value.visible = false
}
-const saveModal = (data:Inbound, stats: boolean, tls_id: number, cData: any) => {
+const saveModal = async (data:Inbound) => {
// Check duplicate tag
- const oldTag = modal.value.index != -1 ? inbounds.value[modal.value.index].tag : null
- if (data.tag != oldTag && inTags.value.includes(data.tag)) {
+ const oldInbound = modal.value.id > 0 ? inbounds.value.findLast(i => i.id == modal.value.id) : null
+ if (data.tag != oldInbound?.tag && inTags.value.includes(data.tag)) {
push.error({
message: i18n.global.t('error.dplData') + ": " + i18n.global.t('objects.tag')
})
return
}
- if (cData.id != -1) {
- cData.tag = data.tag
- fillData(cData.outJson, data,tls_id>0 ? tlsConfigs.value.findLast(t => t.id == tls_id).client : {})
+
+ // Fill outjson
+ if (data.out_json){
+ fillData(data, data.tls_id > 0 ? tlsConfigs?.value.findLast((t:any) => t.id == data.tls_id) : null)
+ }
+
+ let userLinkDiff = []
+ // Update links
+ if (data.id > 0 && oldInbound != null) {
+ userLinkDiff = updateLinks(data,oldInbound)
}
- // New or Edit
- if (modal.value.index == -1) {
- inbounds.value.push(data)
- if (stats && data.tag.length>0) {
- v2rayStats.value.inbounds.push(data.tag)
- }
- if (cData.id != -1){
- inData.value.push(cData)
- }
- } else {
- const oldTag = inbounds.value[modal.value.index].tag
- const sIndex = v2rayStats.value.inbounds.findIndex(i => i == data.tag) // Find if new tag exists
-
- // Update tls preset
- const oldTlsConfigIndex = tlsConfigs?.value.findIndex(t => t.inbounds?.includes(oldTag))
- if (oldTlsConfigIndex != -1){
- tlsConfigs.value[oldTlsConfigIndex].inbounds = tlsConfigs?.value[oldTlsConfigIndex].inbounds.filter((i:string) => i != oldTag)
- }
-
- if (oldTag != data.tag) {
- v2rayStats.value.inbounds = v2rayStats.value.inbounds.filter(item => item != oldTag)
- changeClientInboundsTag(oldTag,data.tag)
- }
-
- if (stats) {
- // Add if dos not exist
- if (data.tag.length>0 && sIndex == -1) v2rayStats.value.inbounds.push(data.tag)
- } else {
- // Delete if exists
- if (sIndex != -1) v2rayStats.value.inbounds.splice(sIndex,1)
- }
-
- inbounds.value[modal.value.index] = data
- const inDataIndex = inData.value.findIndex(indata => indata.tag == oldTag)
- if (cData.id != -1) {
- if (inDataIndex == -1){
- inData.value.push(cData)
- } else {
- inData.value[inDataIndex] = cData
- }
- } else if (inDataIndex != -1) {
- Data().delInData(inData.value[inDataIndex].id)
- inData.value.splice(inDataIndex,1)
- }
- }
- // Update tls preset
- if (tls_id>0) {
- tlsConfigs.value.findLast(t => t.id == tls_id).inbounds.push(data.tag)
- tlsConfigs.value.sort()
- }
-
- if (Object.hasOwn(data,'users')) {
- // Set users
- data = buildInboundsUsers(data)
- // Update links
- updateLinks(data)
- }
- modal.value.visible = false
+ // save data
+ const success = await Data().save("inbounds", modal.value.id == 0 ? "new" : "edit", data, userLinkDiff)
+ if (success) modal.value.visible = false
}
-const updateLinks = (i: any) => {
- if(i.users){
- const uClients = clients.value.filter(c => c.inbounds.includes(i.tag))
- uClients.forEach((u:Client) => {
- const clientInbounds = inbounds.value.filter(inb => u.inbounds.includes(inb.tag))
- const newLinks = []
- clientInbounds.forEach(i =>{
- const tlsClient = tlsConfigs?.value.findLast((t:any) => t.inbounds.includes(i.tag))?.client?? {}
- const cData = Data().inData?.findLast((d:any) => d.tag == i.tag)
- const addrs = cData ? cData.addrs : []
- const uris = LinkUtil.linkGenerator(u,i, tlsClient, addrs)
- if (uris.length>0){
- uris.forEach(uri => {
- newLinks.push({ type: 'local', remark: i.tag, uri: uri })
- })
- }
- })
- let links = u.links && u.links.length>0? u.links : []
- links = [...newLinks, ...links.filter(l => l.type != 'local')]
+const updateLinks = (i: Inbound, o: Inbound): any[] => {
+ let diff = []
+ const uClients = clients.value.filter(c => c.inbounds.includes(i.id))
+ if (uClients.length == 0) return diff
- u.links = links
+ if (inboundWithUsers.includes(o.type) && !inboundWithUsers.includes(i.type)){
+ // Remove old inbound links if new type does not support users
+ uClients.forEach((u:Client) => {
+ u.inbounds = u.inbounds.filter(i => i != o.id)
+ const otherLocalLinks = u.links.filter(l => l.type == 'local' && l.remark != o.tag)
+ let links = u.links && u.links.length>0? u.links : []
+ links = [...otherLocalLinks, ...links.filter(l => l.type != 'local')]
+
+ diff.push({ id: u.id, links: links, inbounds: u.inbounds })
+ })
+ } else if(inboundWithUsers.includes(i.type)){
+ // Add new inbound links if new type supports users
+ const tls = tlsConfigs?.value.findLast((t:any) => t.id == i.tls_id)
+ uClients.forEach((u:Client) => {
+ const otherLocalLinks = u.links.filter(l => l.type == 'local' && l.remark != i.tag)
+ const uris = LinkUtil.linkGenerator(u,i, tls, i.addrs)
+ let newLinks = []
+ if (uris.length>0){
+ uris.forEach(uri => {
+ newLinks.push({ type: 'local', remark: i.tag, uri: uri })
+ })
+ }
+ let links = u.links && u.links.length>0? u.links : []
+ links = [...otherLocalLinks, ...newLinks, ...links.filter(l => l.type != 'local')]
+
+ diff.push({ id: u.id, links: links, inbounds: u.inbounds })
})
}
+
+ return diff
}
-const delInbound = (index: number) => {
+const delInbound = async (id: number) => {
+ const index = inbounds.value.findIndex(i => i.id == id)
const inb = inbounds.value[index]
- inbounds.value.splice(index,1)
const tag = inb.tag
- if (Object.hasOwn(inb,'users')) {
- const inbU = inb
- if (inbU.users && inbU.users.length>0){
- inbU.users.forEach((u:any) => {
- const c_index = clients.value.findIndex(c => u.username? u.username == c.name : u.name == c.name)
- if (c_index != -1) {
- clients.value[c_index].inbounds = clients.value[c_index].inbounds.filter((x:string) => x!=tag)
- clients.value[c_index].links = clients.value[c_index].links.filter((x:any) => x.remark!=tag)
- }
- })
- }
- }
-
- // Delete binded tls if exists
- if (Object.hasOwn(inb,'tls')) {
- const oldTlsConfigIndex = tlsConfigs?.value.findIndex(t => t.inbounds?.includes(inb.tag))
- if (oldTlsConfigIndex != -1){
- tlsConfigs.value[oldTlsConfigIndex].inbounds = tlsConfigs?.value[oldTlsConfigIndex].inbounds.filter((i:string) => i != inb.tag)
- }
- }
-
- // Delete stats if exists and will be orphaned
- const tagCounts = inbounds.value.filter(i => i.tag == inb.tag).length
- const sIndex = v2rayStats.value.inbounds.findIndex(i => i == inb.tag)
- if (tagCounts == 1 && sIndex != -1){
- v2rayStats.value.inbounds.splice(sIndex,1)
- }
- if (index < Data().oldData.config.inbounds.length){
- Data().delInbound(index)
- } else {
- // Delete new inbound's inData if exists
- const inDataIndex = Data().inData.findIndex((d:any) => d.tag == tag)
- if (inDataIndex != -1) Data().inData.splice(inDataIndex, 1)
- }
- delOverlay.value[index] = false
-}
-const buildInboundsUsers = (inbound:any):Inbound => {
- const users = []
- const inboundClients = clients.value.filter(c => c.enable && c.inbounds.includes(inbound.tag))
- inboundClients.forEach(c => {
- // Remove flow in non tls VLESS
- if (inbound.type == InTypes.VLESS) {
- const vlessInbound = inbound
- if (!vlessInbound.tls?.enabled || vlessInbound.transport?.type) delete(c.config?.vless?.flow)
- }
- users.push(c.config[inbound.type])
- })
- inbound.users = users
-
- // Exceptions for Naive and ShadowTLSv3
- if (users.length == 0){
- if (inbound.type == InTypes.Naive){
- inbound.users = [{}]
- } else {
- if (inbound.type == InTypes.ShadowTLS){
- const ssTls = inbound
- if (ssTls.version == 3) inbound.users = [{}]
- }
- }
- }
-
- return inbound
-}
-const changeClientInboundsTag = (oldtag: string, newTag:string) => {
- clients.value.forEach((c, c_index) => {
- const inbound_index = c.inbounds.findIndex(i => i == oldtag)
- if (inbound_index != -1) {
- c.inbounds[inbound_index] = newTag
- clients.value[c_index].inbounds = c.inbounds
- }
+ let diff = []
+ // delete inbound in client table
+ const inboundClients = clients.value.filter(c => c.inbounds.includes(id))
+ inboundClients.forEach((c:Client) => {
+ c.inbounds = c.inbounds.filter((x:number) => x!=id)
+ c.links = c.links.filter((x:any) => x.remark!=tag)
+ diff.push({ id: c.id, links: c.links, inbounds: c.inbounds })
})
-}
-const findInbounsUsers = (inbound: InboundWithUser): string[] => {
- if (inbound.users === null || !Array.isArray(inbound.users) || inbound.users.length == 0) return []
- const users = inbound.users.map(user => "username" in user ? user.username : user.name)
- return users
+ const success = await Data().save("inbounds", "del", tag, diff)
+ if (success) delOverlay.value[index] = false
+}
+
+const findInboundUsers = (i: Inbound): string[] => {
+ return clients.value.filter(c => c.inbounds.includes(i.id)).map(c => c.name)
}
const stats = ref({
diff --git a/frontend/src/views/Outbounds.vue b/frontend/src/views/Outbounds.vue
index 7ace1d9..c11a866 100644
--- a/frontend/src/views/Outbounds.vue
+++ b/frontend/src/views/Outbounds.vue
@@ -3,7 +3,6 @@
v-model="modal.visible"
:visible="modal.visible"
:id="modal.id"
- :stats="modal.stats"
:data="modal.data"
:tags="outboundTags"
@close="closeModal"
@@ -18,7 +17,7 @@
/>
- {{ $t('actions.add') }}
+ {{ $t('actions.add') }}
@@ -51,7 +50,7 @@
{{ $t('online') }}
-
+
{{ $t('online') }}
-
@@ -60,7 +59,7 @@
-
+
@@ -77,12 +76,12 @@
{{ $t('confirm') }}
- {{ $t('yes') }}
+ {{ $t('yes') }}
{{ $t('no') }}
-
+
@@ -96,79 +95,56 @@
import Data from '@/store/modules/data'
import OutboundVue from '@/layouts/modals/Outbound.vue'
import Stats from '@/layouts/modals/Stats.vue'
-import { Config, V2rayApiStats } from '@/types/config';
import { Outbound } from '@/types/outbounds';
import { computed, ref } from 'vue'
import { i18n } from '@/locales';
import { push } from 'notivue';
-const appConfig = computed((): Config => {
- return Data().config
-})
-
const outbounds = computed((): Outbound[] => {
- return appConfig.value.outbounds
+ return Data().outbounds
})
-const outboundTags = computed((): string[] => {
+const outboundTags = computed((): any[] => {
return outbounds.value?.map((o:Outbound) => o.tag)
})
const onlines = computed(() => {
- return Data().onlines.outbound ? outbounds.value.map(i => Data().onlines.outbound.includes(i.tag)) : []
-})
-
-const v2rayStats = computed((): V2rayApiStats => {
- return appConfig.value.experimental?.v2ray_api.stats
+ return Data().onlines.outbound?? []
})
const modal = ref({
visible: false,
- id: -1,
+ id: 0,
data: "",
- stats: false,
})
let delOverlay = ref(new Array)
const showModal = (id: number) => {
modal.value.id = id
- modal.value.data = id == -1 ? '' : JSON.stringify(outbounds.value[id])
- modal.value.stats = id == -1 ? false : v2rayStats.value.outbounds.includes(outbounds.value[id].tag)
+ modal.value.data = id == 0 ? '' : JSON.stringify(outbounds.value.findLast(o => o.id == id))
modal.value.visible = true
}
const closeModal = () => {
modal.value.visible = false
}
-const saveModal = (data:Outbound, stats: boolean) => {
+const saveModal = async (data:Outbound) => {
// Check duplicate tag
- const oldTag = modal.value.id != -1 ? outbounds.value[modal.value.id].tag : null
+ const oldTag = modal.value.id > 0 ? outbounds.value.findLast(i => i.id == modal.value.id)?.tag : null
if (data.tag != oldTag && outboundTags.value.includes(data.tag)) {
push.error({
message: i18n.global.t('error.dplData') + ": " + i18n.global.t('objects.tag')
})
return
}
- // New or Edit
- if (modal.value.id == -1) {
- outbounds.value.push(data)
- if (stats && data.tag.length>0) {
- v2rayStats.value.outbounds.push(data.tag)
- }
- } else {
- const sIndex = v2rayStats.value.outbounds.findIndex(i => i == data.tag) // Find if new tag exists
- if (stats) {
- // Add if dos not exist
- if (data.tag.length>0 && sIndex == -1) v2rayStats.value.outbounds.push(data.tag)
- } else {
- // Delete if exists
- if (sIndex != -1) v2rayStats.value.outbounds.splice(sIndex,1)
- }
-
- outbounds.value[modal.value.id] = data
+ // save data
+ const success = await Data().save("outbounds", modal.value.id == 0 ? "new" : "edit", data)
+ if (!success) {
+ return
}
+
modal.value.visible = false
}
@@ -178,21 +154,10 @@ const stats = ref({
tag: "",
})
-const delOutbound = (index: number) => {
- const inb = outbounds.value[index]
- outbounds.value.splice(index,1)
- const tag = inb.tag
-
- // Delete stats if exists and will be orphaned
- const tagCounts = outbounds.value.filter(i => i.tag == inb.tag).length
- const sIndex = v2rayStats.value.outbounds.findIndex(i => i == inb.tag)
- if (tagCounts == 1 && sIndex != -1){
- v2rayStats.value.outbounds.splice(sIndex,1)
- }
- if (index < Data().oldData.config.outbounds.length){
- Data().delOutbound(index)
- }
- delOverlay.value[index] = false
+const delOutbound = async (tag: string) => {
+ const index = outbounds.value.findIndex(i => i.tag == tag)
+ const success = await Data().save("outbounds", "del", tag)
+ if (success) delOverlay.value[index] = false
}
const showStats = (tag: string) => {
@@ -202,4 +167,8 @@ const showStats = (tag: string) => {
const closeStats = () => {
stats.value.visible = false
}
+
+function awaitData() {
+ throw new Error('Function not implemented.');
+}
\ No newline at end of file
diff --git a/frontend/src/views/Rules.vue b/frontend/src/views/Rules.vue
index 1b9ef8f..1fa3944 100644
--- a/frontend/src/views/Rules.vue
+++ b/frontend/src/views/Rules.vue
@@ -24,6 +24,9 @@
{{ $t('rule.add') }}
{{ $t('ruleset.add') }}
+
+ {{ $t('actions.save') }}
+
@@ -144,16 +147,37 @@
\ No newline at end of file
diff --git a/frontend/src/views/Tls.vue b/frontend/src/views/Tls.vue
index f857227..ac427d7 100644
--- a/frontend/src/views/Tls.vue
+++ b/frontend/src/views/Tls.vue
@@ -2,19 +2,19 @@
- {{ $t('actions.add') }}
+ {{ $t('actions.add') }}
-
+
{{ item.server?.server_name?.length>0 ? item.server.server_name : "-" }}
@@ -22,10 +22,13 @@
{{ $t('pages.inbounds') }}
-
- {{ i }}
-
- {{ item.inbounds?.length }}
+
+
+ {{ i }}
+
+ {{ tlsInbounds.length }}
+
+ -
@@ -49,11 +52,11 @@
-
+
-
+
@@ -66,12 +69,12 @@
{{ $t('confirm') }}
- {{ $t('yes') }}
+ {{ $t('yes') }}
{{ $t('no') }}
-
+
@@ -85,8 +88,7 @@
import TlsVue from '@/layouts/modals/Tls.vue'
import Data from '@/store/modules/data'
import { computed, ref } from 'vue'
-import { Config } from '@/types/config'
-import { Inbound } from '@/types/inbounds'
+import { Inbound, inboundWithUsers } from '@/types/inbounds'
import { Client } from '@/types/clients'
import { Link, LinkUtil } from '@/plugins/link'
import { fillData } from '@/plugins/outJson'
@@ -95,13 +97,13 @@ const tlsConfigs = computed((): any[] => {
return Data().tlsConfigs
})
-const inbounds = computed((): any[] => {
- return (Data().config)?.inbounds
+const inbounds = computed((): Inbound[] => {
+ return Data().inbounds
})
-const inData = computed((): any[] => {
- return Data().inData
-})
+const tlsInbounds = (id: number): string[] => {
+ return inbounds.value.filter(i => i.tls_id == id).map(i => i.tag)
+}
const clients = computed((): any[] => {
return Data().clients
@@ -109,21 +111,20 @@ const clients = computed((): any[] => {
const modal = ref({
visible: false,
- index: -1,
+ id: 0,
data: "",
})
const delOverlay = ref(new Array(tlsConfigs.value.length).fill(false))
-const showModal = (index: number) => {
- modal.value.index = index
- modal.value.data = index == -1 ? '{}' : JSON.stringify(tlsConfigs.value[index])
+const showModal = (id: number) => {
+ modal.value.id = id
+ modal.value.data = id == 0 ? '{}' : JSON.stringify(tlsConfigs.value.findLast(t => t.id == id))
modal.value.visible = true
}
-const clone = (index: number) => {
- let data = JSON.parse(JSON.stringify(tlsConfigs.value[index]))
+const clone = (obj: any) => {
+ let data = JSON.parse(JSON.stringify(obj))
data.id = 0
- data.inbounds = []
while (tlsConfigs.value.findIndex(t => t.name == data.name) != -1){
data.name += "-copy"
}
@@ -132,57 +133,67 @@ const clone = (index: number) => {
const closeModal = () => {
modal.value.visible = false
}
-const saveModal = (data:any) => {
+const saveModal = async (data:any) => {
+ let outJsons = []
+ let userLinks = []
// New or Edit
- if (modal.value.index == -1) {
- tlsConfigs.value.push(data)
- } else {
- tlsConfigs.value[modal.value.index] = data
- inbounds?.value.filter(i => tlsConfigs.value[modal.value.index].inbounds.includes(i.tag)).forEach(i =>{
- if (i.tls != undefined) i.tls = data.server
- updateInData(i,data.client)
- updateLinks(i,data.client)
- })
- }
- modal.value.visible = false
-}
-
-const delTls = (index: number) => {
- if (index < Data().oldData.tlsConfigs.length){
- Data().delTls(tlsConfigs.value[index].id)
- }
- tlsConfigs.value.splice(index,1)
- delOverlay.value[index] = false
-}
-
-const updateLinks = (i:any,tlsClient:any) => {
- if(i.users){
- const uClients = clients.value.filter(c => c.inbounds.includes(i.tag))
- uClients.forEach((client:any) => {
- const clientInbounds = inbounds.value.filter(inb => client?.inbounds.includes(inb.tag))
- const newLinks = []
- clientInbounds.forEach(i =>{
- const cData = Data().inData?.findLast((d:any) => d.tag == i.tag)
- const addrs = cData ? cData.addrs : []
- const uris = LinkUtil.linkGenerator(client,i, tlsClient, addrs)
- if (uris.length>0){
- uris.forEach(uri => {
- newLinks.push({ type: 'local', remark: i.tag, uri: uri })
- })
+ if (modal.value.id > 0) {
+ const inboundIds = inbounds.value.filter(i => i.tls_id == modal.value.id).map(i => i.id)
+ if (inboundIds.length > 0) {
+ const tlsInbounds = inboundIds.length == 0 ? [] : await Data().loadInbounds(inboundIds)
+ for (const inbound of tlsInbounds) {
+ // Fill outjson
+ if (inbound.out_json) {
+ fillData(inbound, data)
}
- })
- let links = client.links && client.links.length>0? client.links : []
- links = [...newLinks, ...links.filter((l:Link) => l.type != 'local')]
+ outJsons.push({tag: inbound.tag,out_jsons: inbound.out_json})
+ // Update links
+ const diff = updateLinks(inbound)
+ diff.forEach((d: any) => {
+ if (userLinks.findIndex(l => l.id == d.id) == -1) {
+ userLinks.push(d)
+ } else {
+ const index = userLinks.findIndex(l => l.id == d.id)
+ userLinks[index].links = d.links
+ }
+ })
+ }
+ }
- client.links = links
+ }
+ const success = await Data().save("tls", data.id == 0 ? "new" : "edit", data, userLinks.length > 0 ? null: userLinks, outJsons.length > 0 ? null: outJsons)
+ if (success) modal.value.visible = false
+}
+
+const delTls = async (id: number) => {
+ const index = tlsConfigs.value.findIndex(t => t.id == id)
+ const success = await Data().save("tls", "del", id)
+ if (success) delOverlay.value[index] = false
+}
+
+const updateLinks = (i: Inbound): any[] => {
+ let diff = []
+ if(inboundWithUsers.includes(i.type) && i.id != 0){
+ const uClients = clients.value.filter(c => c.inbounds.includes(i.id))
+ const tlsClient = tlsConfigs?.value.findLast((t:any) => t.id == i.tls_id)
+ uClients.forEach((u:Client) => {
+ const otherLocalLinks = u.links.filter(l => l.type == 'local' && l.remark != i.tag)
+ const uris = LinkUtil.linkGenerator(u,i, tlsClient, i.addrs)
+ let newLinks = []
+ if (uris.length>0){
+ uris.forEach(uri => {
+ newLinks.push({ type: 'local', remark: i.tag, uri: uri })
+ })
+ }
+ let links = u.links && u.links.length>0? u.links : []
+ links = [...otherLocalLinks, ...newLinks, ...links.filter(l => l.type != 'local')]
+
+ u.links = links
+ diff.push({ id: u.id, links: links })
})
}
+
+ return diff
}
-const updateInData = (i:any, c:any) => {
- const inDataIndex = inData.value.findIndex(d => d.tag == i.tag)
- if (inDataIndex != -1) {
- fillData(inData.value[inDataIndex].outJson, i, c)
- }
-}