diff --git a/backend/api/api.go b/backend/api/api.go index a883947..5ba3b33 100644 --- a/backend/api/api.go +++ b/backend/api/api.go @@ -1,7 +1,6 @@ package api import ( - "fmt" "s-ui/logger" "s-ui/service" "strconv" @@ -157,8 +156,8 @@ func (a *APIHandler) getHandler(c *gin.Context) { } } -func (a *APIHandler) loadData(c *gin.Context) (string, error) { - var data string +func (a *APIHandler) loadData(c *gin.Context) (interface{}, error) { + data := make(map[string]interface{}, 0) lu := c.Query("lu") isUpdated, err := a.ConfigService.CheckChanges(lu) if err != nil { @@ -185,9 +184,13 @@ func (a *APIHandler) loadData(c *gin.Context) (string, error) { if err != nil { return "", err } - data = fmt.Sprintf(`{"config": %s, "clients": %s, "tls": %s, "subURI": "%s", "onlines": %s}`, string(*config), clients, tlsConfigs, subURI, onlines) + data["config"] = *config + data["clients"] = clients + data["tls"] = tlsConfigs + data["subURI"] = subURI + data["onlines"] = onlines } else { - data = fmt.Sprintf(`{"onlines": %s}`, onlines) + data["onlines"] = onlines } return data, nil diff --git a/backend/database/model/model.go b/backend/database/model/model.go index 9f06842..eca10f9 100644 --- a/backend/database/model/model.go +++ b/backend/database/model/model.go @@ -24,17 +24,17 @@ type User struct { } type Client struct { - Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"` - Enable bool `json:"enable" form:"enable"` - Name string `json:"name" form:"name"` - Config string `json:"config" form:"config"` - Inbounds string `json:"inbounds" form:"inbounds"` - Links string `json:"links" form:"links"` - Volume int64 `json:"volume" form:"volume"` - Expiry int64 `json:"expiry" form:"expiry"` - Down int64 `json:"down" form:"down"` - Up int64 `json:"up" form:"up"` - Desc string `json:"desc" from:"desc"` + Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"` + Enable bool `json:"enable" form:"enable"` + Name string `json:"name" form:"name"` + Config json.RawMessage `json:"config" form:"config"` + Inbounds json.RawMessage `json:"inbounds" form:"inbounds"` + Links json.RawMessage `json:"links" form:"links"` + Volume int64 `json:"volume" form:"volume"` + Expiry int64 `json:"expiry" form:"expiry"` + Down int64 `json:"down" form:"down"` + Up int64 `json:"up" form:"up"` + Desc string `json:"desc" from:"desc"` } type Stats struct { diff --git a/backend/service/client.go b/backend/service/client.go index 44558cc..ffa86d1 100644 --- a/backend/service/client.go +++ b/backend/service/client.go @@ -5,7 +5,6 @@ import ( "s-ui/database" "s-ui/database/model" "s-ui/logger" - "strings" "time" "gorm.io/gorm" @@ -14,18 +13,14 @@ import ( type ClientService struct { } -func (s *ClientService) GetAll() (string, error) { +func (s *ClientService) GetAll() ([]model.Client, error) { db := database.GetDB() clients := []model.Client{} err := db.Model(model.Client{}).Scan(&clients).Error if err != nil { - return "", err + return nil, err } - data, err := json.Marshal(clients) - if err != nil { - return "", err - } - return string(data), nil + return clients, nil } func (s *ClientService) Save(tx *gorm.DB, changes []model.Changes) error { @@ -62,14 +57,16 @@ func (s *ClientService) DepleteClients() ([]string, []string, error) { return nil, nil, 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) - userInbounds := strings.Split(client.Inbounds, ",") + var userInbounds []string + json.Unmarshal(client.Inbounds, &userInbounds) inbounds = append(inbounds, userInbounds...) changes = append(changes, model.Changes{ - DateTime: time.Now().Unix(), + DateTime: dt, Actor: "DepleteJob", Key: "clients", Action: "disable", @@ -87,6 +84,7 @@ func (s *ClientService) DepleteClients() ([]string, []string, error) { if err != nil { return nil, nil, err } + LastUpdate = dt } return users, inbounds, nil diff --git a/backend/service/config.go b/backend/service/config.go index d30c0cf..2927d4f 100644 --- a/backend/service/config.go +++ b/backend/service/config.go @@ -54,16 +54,27 @@ func (s *ConfigService) InitConfig() error { return err } } - return s.RefreshApiAddr(&data) + var singboxConfig SingBoxConfig + err = json.Unmarshal(data, &singboxConfig) + if err != nil { + return err + } + + return s.RefreshApiAddr(&singboxConfig) } -func (s *ConfigService) GetConfig() (*[]byte, error) { +func (s *ConfigService) GetConfig() (*SingBoxConfig, error) { configPath := config.GetBinFolderPath() data, err := os.ReadFile(configPath + "/config.json") if err != nil { return nil, err } - return &data, nil + singboxConfig := SingBoxConfig{} + err = json.Unmarshal(data, &singboxConfig) + if err != nil { + return nil, err + } + return &singboxConfig, nil } func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string) error { @@ -127,11 +138,7 @@ func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string) if err != nil { return err } - newConfig := SingBoxConfig{} - err = json.Unmarshal(*singboxConfig, &newConfig) - if err != nil { - return err - } + newConfig := *singboxConfig for _, change := range configChanges { rawObject := change.Obj switch change.Key { @@ -169,12 +176,7 @@ func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string) } } - // Save to config.json - data, err := json.MarshalIndent(newConfig, "", " ") - if err != nil { - return err - } - err = s.Save(&data) + err = s.Save(&newConfig) if err != nil { return err } @@ -215,7 +217,7 @@ func (s *ConfigService) CheckChanges(lu string) (bool, error) { } } -func (s *ConfigService) Save(data *[]byte) error { +func (s *ConfigService) Save(singboxConfig *SingBoxConfig) error { configPath := config.GetBinFolderPath() _, err := os.Stat(configPath + "/config.json") if os.IsNotExist(err) { @@ -227,35 +229,36 @@ func (s *ConfigService) Save(data *[]byte) error { return err } - err = os.WriteFile(configPath+"/config.json", *data, 0764) + data, err := json.MarshalIndent(singboxConfig, " ", " ") if err != nil { return err } - s.RefreshApiAddr(data) + err = os.WriteFile(configPath+"/config.json", data, 0764) + if err != nil { + return err + } + + s.RefreshApiAddr(singboxConfig) s.Controller.Restart() return nil } -func (s *ConfigService) RefreshApiAddr(data *[]byte) error { +func (s *ConfigService) RefreshApiAddr(singboxConfig *SingBoxConfig) error { Env_API := config.GetEnvApi() if len(Env_API) > 0 { ApiAddr = Env_API } else { var err error - if data == nil { - data, err = s.GetConfig() + if singboxConfig == nil { + singboxConfig, err = s.GetConfig() if err != nil { return err } + } - singboxConfig := SingBoxConfig{} - err = json.Unmarshal(*data, &singboxConfig) - if err != nil { - return err - } var experimental struct { V2rayApi struct { Listen string `json:"listen"` @@ -282,12 +285,7 @@ func (s *ConfigService) DepleteClients() error { if err != nil { return err } - newConfig := SingBoxConfig{} - err = json.Unmarshal(*singboxConfig, &newConfig) - if err != nil { - return err - } - for inbound_index, inbound := range newConfig.Inbounds { + for inbound_index, inbound := range singboxConfig.Inbounds { var inboundJson map[string]interface{} json.Unmarshal(inbound, &inboundJson) if s.contains(inbounds, inboundJson["tag"].(string)) { @@ -326,13 +324,10 @@ func (s *ConfigService) DepleteClients() error { if err != nil { return err } - newConfig.Inbounds[inbound_index] = modifiedInbound + singboxConfig.Inbounds[inbound_index] = modifiedInbound } - modifiedConfig, err := json.MarshalIndent(newConfig, "", " ") - if err != nil { - return err - } - err = s.Save(&modifiedConfig) + + err = s.Save(singboxConfig) if err != nil { return err } diff --git a/backend/service/stats.go b/backend/service/stats.go index 7394321..fd7bf36 100644 --- a/backend/service/stats.go +++ b/backend/service/stats.go @@ -1,7 +1,6 @@ package service import ( - "encoding/json" "s-ui/database" "s-ui/database/model" "time" @@ -86,12 +85,8 @@ func (s *StatsService) GetStats(resorce string, tag string, limit int) ([]model. return result, nil } -func (s *StatsService) GetOnlines() (string, error) { - onlines, err := json.Marshal(onlineResources) - if err != nil { - return "", err - } - return string(onlines), nil +func (s *StatsService) GetOnlines() (onlines, error) { + return *onlineResources, nil } func (s *StatsService) DelOldStats(days int) error { oldTime := time.Now().AddDate(0, 0, -(days)).Unix() diff --git a/backend/service/tls.go b/backend/service/tls.go index 578aef1..ada8cb2 100644 --- a/backend/service/tls.go +++ b/backend/service/tls.go @@ -11,18 +11,15 @@ import ( type TlsService struct { } -func (s *TlsService) GetAll() (string, error) { +func (s *TlsService) GetAll() ([]model.Tls, error) { db := database.GetDB() tlsConfig := []model.Tls{} err := db.Model(model.Tls{}).Scan(&tlsConfig).Error if err != nil { - return "", err + return nil, err } - data, err := json.Marshal(tlsConfig) - if err != nil { - return "", err - } - return string(data), nil + + return tlsConfig, nil } func (s *TlsService) Save(tx *gorm.DB, changes []model.Changes) error { diff --git a/frontend/src/layouts/modals/Client.vue b/frontend/src/layouts/modals/Client.vue index 256d0f4..ba98039 100644 --- a/frontend/src/layouts/modals/Client.vue +++ b/frontend/src/layouts/modals/Client.vue @@ -179,7 +179,7 @@ export default { const newData = JSON.parse(this.$props.data) this.client = createClient(newData) this.title = "edit" - this.clientConfig = JSON.parse(this.client.config) + this.clientConfig = this.client.config } else { this.client = createClient() @@ -187,10 +187,9 @@ export default { this.clientConfig = randomConfigs('client') } this.clientStats = this.$props.stats - const allLinks = JSON.parse(this.client.links) - this.links = allLinks.filter(l => l.type == 'local') - this.extLinks = allLinks.filter(l => l.type == 'external') - this.subLinks = allLinks.filter(l => l.type == 'sub') + 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') this.tab = "t1" }, closeModal() { @@ -199,11 +198,11 @@ export default { }, saveChanges() { this.loading = true - this.client.config = updateConfigs(JSON.stringify(this.clientConfig), this.client.name) - this.client.links = JSON.stringify([ - ...this.links, - ...this.extLinks.filter(l => l.uri != ''), - ...this.subLinks.filter(l => l.uri != '')]) + this.client.config = updateConfigs(this.clientConfig, this.client.name) + this.client.links = [ + ...this.links, + ...this.extLinks.filter(l => l.uri != ''), + ...this.subLinks.filter(l => l.uri != '')] this.$emit('save', this.client, this.clientStats) this.loading = false }, @@ -213,8 +212,8 @@ export default { }, computed: { clientInbounds: { - get() { return this.client.inbounds == "" ? [] : this.client.inbounds.split(',').filter(i => this.inboundTags.includes(i)) }, - set(newValue:string[]) { this.client.inbounds = newValue.length == 0 ? "" : newValue.join(',') } + 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 } }, expDate: { get() { return this.client.expiry}, diff --git a/frontend/src/store/modules/data.ts b/frontend/src/store/modules/data.ts index 3a39b4e..b725dcb 100644 --- a/frontend/src/store/modules/data.ts +++ b/frontend/src/store/modules/data.ts @@ -20,7 +20,7 @@ const Data = defineStore('Data', { this.lastLoad = Math.floor((new Date()).getTime()/1000) // Set new data - const data = JSON.parse(msg.obj) + const data = JSON.parse(JSON.stringify(msg.obj)) if (data.subURI) this.subURI = data.subURI if (data.config) this.config = data.config if (data.clients) this.clients = data.clients @@ -28,9 +28,9 @@ const Data = defineStore('Data', { this.onlines = data.onlines // To avoid ref copy - if (data.config) this.oldData.config = { ...JSON.parse(msg.obj).config } - if (data.clients) this.oldData.clients = [ ...JSON.parse(msg.obj).clients ] - if (data.tls) this.oldData.tlsConfigs = [ ...JSON.parse(msg.obj).tls ] + if (data.config) this.oldData.config = { ...msg.obj }.config + if (data.clients) this.oldData.clients = { ...msg.obj }.clients + if (data.tls) this.oldData.tlsConfigs = { ...msg.obj }.tls } }, async pushData() { diff --git a/frontend/src/types/clients.ts b/frontend/src/types/clients.ts index 946e681..02fca3e 100644 --- a/frontend/src/types/clients.ts +++ b/frontend/src/types/clients.ts @@ -1,12 +1,13 @@ +import { Link } from "@/plugins/link" import RandomUtil from "@/plugins/randomUtil" export interface Client { id?: number enable: boolean name: string - config: string - inbounds: string - links: string + config: Config + inbounds: string[] + links: Link[] volume: number expiry: number up: number @@ -17,9 +18,9 @@ export interface Client { const defaultClient: Client = { enable: true, name: "", - config: "[]", - inbounds: "", - links: "[]", + config: {}, + inbounds: [], + links: [], volume: 0, expiry: 0, up: 0, @@ -35,12 +36,11 @@ type Config = { } } -export function updateConfigs(configs: string, newUserName: string): string { - const updatedConfigs: Config = JSON.parse(configs) +export function updateConfigs(configs: Config, newUserName: string): Config { - for (const key in updatedConfigs) { - if (updatedConfigs.hasOwnProperty(key)) { - const config = updatedConfigs[key] + for (const key in configs) { + if (configs.hasOwnProperty(key)) { + const config = configs[key] if (config.hasOwnProperty("name")) { config.name = newUserName } else if (config.hasOwnProperty("username")) { @@ -49,7 +49,7 @@ export function updateConfigs(configs: string, newUserName: string): string { } } - return JSON.stringify(updatedConfigs) + return configs } export function randomConfigs(user: string): Config { diff --git a/frontend/src/views/Clients.vue b/frontend/src/views/Clients.vue index 5abd3ea..f687f0e 100644 --- a/frontend/src/views/Clients.vue +++ b/frontend/src/views/Clients.vue @@ -38,7 +38,7 @@ @@ -53,9 +53,9 @@ {{ $t('pages.inbounds') }} - {{ i }}
+ {{ i }}
- {{ item.inbounds != '' ? item.inbounds.split(',').length : 0 }} + {{ item.inbounds.length }}
@@ -189,19 +189,18 @@ const saveModal = (data:any, stats:boolean) => { sb.showMessage(i18n.global.t('error.dplData') + ': ' + i18n.global.t('client.name') ,'error', 5000) return } - const inboundTags: string[] = data.inbounds.split(',')?? [] if(modal.value.index == -1) { clients.value.push(data) } else { const oldData = createClient(clients.value[modal.value.index]) - oldData.inbounds.split(',').forEach((i:string) => { - if (!inboundTags.includes(i)) inboundTags.push(i) + oldData.inbounds.forEach((i:string) => { + if (!data.inbounds.includes(i)) data.inbounds.push(i) }) clients.value[modal.value.index] = data } // Rebuild affected inbounds - buildInboundsUsers(inboundTags) + buildInboundsUsers(data.inbounds) // Rebuild links data.links = updateLinks(data) @@ -229,15 +228,14 @@ const buildInboundsUsers = (inboundTags:string[]) => { if (inbound_index != -1){ const users = [] const newInbound = inbounds.value[inbound_index] - const inboundClients = clients.value.filter(c => c.enable && c.inbounds.split(',').includes(tag)) + const inboundClients = clients.value.filter(c => c.enable && c.inbounds.includes(tag)) inboundClients.forEach(c => { - const clientConfig = JSON.parse(c.config) // Remove flow in non tls VLESS if (newInbound.type == InTypes.VLESS) { const vlessInbound = newInbound - if (!vlessInbound.tls?.enabled || vlessInbound.transport?.type) delete(clientConfig["vless"].flow) + if (!vlessInbound.tls?.enabled || vlessInbound.transport?.type) delete(c.config?.vless?.flow) } - users.push(clientConfig[newInbound.type]) + users.push(c.config[newInbound.type]) }) newInbound.users = users @@ -257,8 +255,8 @@ const buildInboundsUsers = (inboundTags:string[]) => { } }) } -const updateLinks = (c:Client):string => { - const clientInbounds = inbounds.value.filter(i => c.inbounds.split(',').includes(i.tag)) +const updateLinks = (c:Client):Link[] => { + const clientInbounds = inbounds.value.filter(i => c.inbounds.includes(i.tag)) const newLinks = [] clientInbounds.forEach(i =>{ const tlsConfig = Data().tlsConfigs?.findLast((t:any) => t.inbounds.includes(i.tag)) @@ -267,10 +265,10 @@ const updateLinks = (c:Client):string => { newLinks.push({ type: 'local', remark: i.tag, uri: uri }) } }) - let links = c.links && c.links.length>0? JSON.parse(c.links) : [] + let links = c.links && c.links.length>0? c.links : [] links = [...newLinks, ...links.filter(l => l.type != 'local')] - return JSON.stringify(links) + return links } const delClient = (clientIndex: number) => { const id = clients.value[clientIndex].id @@ -284,7 +282,7 @@ const delClient = (clientIndex: number) => { } clients.value.splice(clientIndex,1) - buildInboundsUsers(oldData.inbounds.split(',')) + buildInboundsUsers(oldData.inbounds) if (id>0) Data().delClient(id) delOverlay.value[clientIndex] = false } diff --git a/frontend/src/views/Inbounds.vue b/frontend/src/views/Inbounds.vue index 088770f..a0f5c40 100644 --- a/frontend/src/views/Inbounds.vue +++ b/frontend/src/views/Inbounds.vue @@ -222,7 +222,7 @@ const updateLinks = (i: InboundWithUser) => { i.users.forEach((u:any) => { const client = clients.value.find(c => u.username? c.name == u.username : c.name == u.name) if (client){ - const clientInbounds = inbounds.value.filter(inb => client?.inbounds.split(',').includes(inb.tag)) + const clientInbounds = inbounds.value.filter(inb => client?.inbounds.includes(inb.tag)) const newLinks = [] clientInbounds.forEach(i =>{ const tlsClient = tlsConfigs?.value.findLast((t:any) => t.inbounds.includes(i.tag))?.client?? null @@ -231,10 +231,10 @@ const updateLinks = (i: InboundWithUser) => { newLinks.push({ type: 'local', remark: i.tag, uri: uri }) } }) - let links = client.links && client.links.length>0? JSON.parse(client.links) : [] + let links = client.links && client.links.length>0? client.links : [] links = [...newLinks, ...links.filter(l => l.type != 'local')] - client.links = JSON.stringify(links) + client.links = links } }) } @@ -250,8 +250,8 @@ const delInbound = (index: number) => { inbU.users.forEach((u:any) => { const c_index = clients.value.findIndex(c => u.username? u.username == c.name : u.user == c.name) if (c_index != -1) { - const clientInbounds = clients.value[c_index].inbounds.split(',').filter((x:string) => x!=tag) - clients.value[c_index].inbounds = clientInbounds.join(',') + const clientInbounds = clients.value[c_index].inbounds.filter((x:string) => x!=tag) + clients.value[c_index].inbounds = clientInbounds } }) } @@ -278,15 +278,14 @@ const delInbound = (index: number) => { } const buildInboundsUsers = (inbound:InboundWithUser):Inbound => { const users = [] - const inboundClients = clients.value.filter(c => c.enable && c.inbounds.split(',').includes(inbound.tag)) + const inboundClients = clients.value.filter(c => c.enable && c.inbounds.includes(inbound.tag)) inboundClients.forEach(c => { - const clientConfig = JSON.parse(c.config) // Remove flow in non tls VLESS if (inbound.type == InTypes.VLESS) { const vlessInbound = inbound - if (!vlessInbound.tls?.enabled || vlessInbound.transport?.type) delete(clientConfig["vless"].flow) + if (!vlessInbound.tls?.enabled || vlessInbound.transport?.type) delete(c.config?.vless?.flow) } - users.push(clientConfig[inbound.type]) + users.push(c.config[inbound.type]) }) inbound.users = users @@ -306,11 +305,10 @@ const buildInboundsUsers = (inbound:InboundWithUser):Inbound => { } const changeClientInboundsTag = (oldtag: string, newTag:string) => { clients.value.forEach((c, c_index) => { - const inboundsArray = c.inbounds.split(',') - const inbound_index = inboundsArray.findIndex(i => i == oldtag) + const inbound_index = c.inbounds.findIndex(i => i == oldtag) if (inbound_index != -1) { - inboundsArray[inbound_index] = newTag - clients.value[c_index].inbounds = inboundsArray.join(',') + c.inbounds[inbound_index] = newTag + clients.value[c_index].inbounds = c.inbounds } }) } diff --git a/frontend/src/views/Tls.vue b/frontend/src/views/Tls.vue index 030648e..77c3dac 100644 --- a/frontend/src/views/Tls.vue +++ b/frontend/src/views/Tls.vue @@ -141,7 +141,7 @@ const updateLinks = (i:any,tlsClient:any) => { i.users.forEach((u:any) => { const client = clients.value.find(c => u.username? c.name == u.username : c.name == u.name) if (client){ - const clientInbounds = inbounds.value.filter(inb => client?.inbounds.split(',').includes(inb.tag)) + const clientInbounds = inbounds.value.filter(inb => client?.inbounds.includes(inb.tag)) const newLinks = [] clientInbounds.forEach(i =>{ const uri = LinkUtil.linkGenerator(client.name,i,tlsClient) @@ -149,10 +149,10 @@ const updateLinks = (i:any,tlsClient:any) => { newLinks.push({ type: 'local', remark: i.tag, uri: uri }) } }) - let links = client.links && client.links.length>0? JSON.parse(client.links) : [] - links = [...newLinks, ...links.filter(l => l.type != 'local')] + let links = client.links && client.links.length>0? client.links : [] + links = [...newLinks, ...links.filter((l:Link) => l.type != 'local')] - client.links = JSON.stringify(links) + client.links = links } }) }