initial commit
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"s-ui/database"
|
||||
"s-ui/database/model"
|
||||
"s-ui/logger"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ClientService struct {
|
||||
}
|
||||
|
||||
func (s *ClientService) GetAll() (string, error) {
|
||||
db := database.GetDB()
|
||||
clients := []model.Client{}
|
||||
err := db.Model(model.Client{}).Scan(&clients).Error
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
data, err := json.Marshal(clients)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func (s *ClientService) Save(tx *gorm.DB, changes []model.Changes) error {
|
||||
var err error
|
||||
for _, change := range changes {
|
||||
client := model.Client{}
|
||||
err = json.Unmarshal(change.Obj, &client)
|
||||
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 err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *ClientService) DepleteClients() ([]string, []string, error) {
|
||||
var err error
|
||||
var clients []model.Client
|
||||
var changes []model.Changes
|
||||
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
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
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, ",")
|
||||
inbounds = append(inbounds, userInbounds...)
|
||||
changes = append(changes, model.Changes{
|
||||
DateTime: time.Now().Unix(),
|
||||
Actor: "DepleteJob",
|
||||
Key: "clients",
|
||||
Action: "disable",
|
||||
Obj: json.RawMessage(client.Name),
|
||||
})
|
||||
}
|
||||
|
||||
// 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
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
err = db.Model(model.Changes{}).Create(&changes).Error
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return users, inbounds, nil
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"s-ui/config"
|
||||
"s-ui/database"
|
||||
"s-ui/database/model"
|
||||
"s-ui/singbox"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ApiAddr string
|
||||
|
||||
type ConfigService struct {
|
||||
ClientService
|
||||
singbox.Controller
|
||||
SettingService
|
||||
}
|
||||
|
||||
type SingBoxConfig struct {
|
||||
Log json.RawMessage `json:"log"`
|
||||
Dns json.RawMessage `json:"dns"`
|
||||
Ntp json.RawMessage `json:"ntp"`
|
||||
Inbounds []json.RawMessage `json:"inbounds"`
|
||||
Outbounds []json.RawMessage `json:"outbounds"`
|
||||
Route json.RawMessage `json:"route"`
|
||||
Experimental json.RawMessage `json:"experimental"`
|
||||
}
|
||||
|
||||
func NewConfigService() *ConfigService {
|
||||
return &ConfigService{}
|
||||
}
|
||||
|
||||
func (s *ConfigService) InitConfig() error {
|
||||
configPath := config.GetBinFolderPath()
|
||||
data, err := os.ReadFile(configPath + "/config.json")
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
defaultConfig := []byte(config.GetDefaultConfig())
|
||||
err = os.MkdirAll(configPath, 01764)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.WriteFile(configPath+"/config.json", defaultConfig, 0764)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data = defaultConfig
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return s.RefreshApiAddr(&data)
|
||||
}
|
||||
|
||||
func (s *ConfigService) GetConfig() (*[]byte, error) {
|
||||
configPath := config.GetBinFolderPath()
|
||||
data, err := os.ReadFile(configPath + "/config.json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string) error {
|
||||
var err error
|
||||
var clientChanges, settingChanges, configChanges []model.Changes
|
||||
if _, ok := changes["clients"]; ok {
|
||||
err = json.Unmarshal([]byte(changes["clients"]), &clientChanges)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
db := database.GetDB()
|
||||
tx := db.Begin()
|
||||
defer func() {
|
||||
if err == nil {
|
||||
tx.Commit()
|
||||
} else {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
if len(clientChanges) > 0 {
|
||||
err = s.ClientService.Save(tx, clientChanges)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(settingChanges) > 0 {
|
||||
err = s.SettingService.Save(tx, settingChanges)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(configChanges) > 0 {
|
||||
singboxConfig, err := s.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newConfig := SingBoxConfig{}
|
||||
err = json.Unmarshal(*singboxConfig, &newConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, change := range configChanges {
|
||||
rawObject := change.Obj
|
||||
switch change.Key {
|
||||
case "all":
|
||||
err = json.Unmarshal(rawObject, &newConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "log":
|
||||
newConfig.Log = rawObject
|
||||
case "dns":
|
||||
newConfig.Dns = rawObject
|
||||
case "ntp":
|
||||
newConfig.Ntp = rawObject
|
||||
case "route":
|
||||
newConfig.Route = rawObject
|
||||
case "experimental":
|
||||
newConfig.Experimental = rawObject
|
||||
case "inbounds":
|
||||
if change.Action == "edit" {
|
||||
newConfig.Inbounds[change.Index] = rawObject
|
||||
} else if change.Action == "del" {
|
||||
newConfig.Inbounds = append(newConfig.Inbounds[:change.Index], newConfig.Inbounds[change.Index+1:]...)
|
||||
} else {
|
||||
newConfig.Inbounds = append(newConfig.Inbounds, rawObject)
|
||||
}
|
||||
case "outbounds":
|
||||
if change.Action == "edit" {
|
||||
newConfig.Outbounds[change.Index] = rawObject
|
||||
} else {
|
||||
newConfig.Outbounds = append(newConfig.Outbounds, rawObject)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save to config.json
|
||||
data, err := json.MarshalIndent(newConfig, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.Save(&data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Log changes
|
||||
dt := time.Now().Unix()
|
||||
allChanges := append(append(clientChanges, settingChanges...), configChanges...)
|
||||
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
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ConfigService) CheckChnages(lu string) (bool, error) {
|
||||
if lu == "" {
|
||||
return true, nil
|
||||
}
|
||||
db := database.GetDB()
|
||||
var count int64
|
||||
err := db.Model(model.Changes{}).Where("date_time > " + lu).Count(&count).Error
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
func (s *ConfigService) Save(data *[]byte) 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
|
||||
}
|
||||
|
||||
err = os.WriteFile(configPath+"/config.json", *data, 0764)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.RefreshApiAddr(data)
|
||||
s.Controller.Restart()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ConfigService) RefreshApiAddr(data *[]byte) 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 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"`
|
||||
Stats interface{} `jaon:"stats"`
|
||||
} `json:"v2ray_api"`
|
||||
}
|
||||
err = json.Unmarshal(singboxConfig.Experimental, &experimental)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ApiAddr = experimental.V2rayApi.Listen
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ConfigService) DepleteClients() error {
|
||||
users, inbounds, err := s.ClientService.DepleteClients()
|
||||
if err != nil || len(users) == 0 || len(inbounds) == 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
singboxConfig, err := s.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newConfig := SingBoxConfig{}
|
||||
err = json.Unmarshal(*singboxConfig, &newConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for inbound_index, inbound := range newConfig.Inbounds {
|
||||
var inboundJson map[string]interface{}
|
||||
json.Unmarshal(inbound, &inboundJson)
|
||||
if s.contains(inbounds, inboundJson["tag"].(string)) {
|
||||
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
|
||||
}
|
||||
newConfig.Inbounds[inbound_index] = modifiedInbound
|
||||
}
|
||||
modifiedConfig, err := json.MarshalIndent(newConfig, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.Save(&modifiedConfig)
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"os"
|
||||
"s-ui/logger"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type PanelService struct {
|
||||
}
|
||||
|
||||
func (s *PanelService) RestartPanel(delay time.Duration) error {
|
||||
p, err := os.FindProcess(syscall.Getpid())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
time.Sleep(delay)
|
||||
err := p.Signal(syscall.SIGHUP)
|
||||
if err != nil {
|
||||
logger.Error("send signal SIGHUP failed:", err)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"s-ui/config"
|
||||
"s-ui/logger"
|
||||
"strings"
|
||||
|
||||
"github.com/shirou/gopsutil/v3/cpu"
|
||||
"github.com/shirou/gopsutil/v3/host"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
"github.com/shirou/gopsutil/v3/net"
|
||||
)
|
||||
|
||||
type ServerService struct {
|
||||
SingBoxService
|
||||
}
|
||||
|
||||
func (s *ServerService) GetStatus(request string) *map[string]interface{} {
|
||||
status := make(map[string]interface{}, 0)
|
||||
requests := strings.Split(request, ",")
|
||||
for _, req := range requests {
|
||||
switch req {
|
||||
case "cpu":
|
||||
status["cpu"] = s.GetCpuPercent()
|
||||
case "mem":
|
||||
status["mem"] = s.GetMemInfo()
|
||||
case "net":
|
||||
status["net"] = s.GetNetInfo()
|
||||
case "sys":
|
||||
status["uptime"] = s.GetUptime()
|
||||
status["sys"] = s.GetSystemInfo()
|
||||
case "sbd":
|
||||
status["sbd"] = s.GetSingboxInfo()
|
||||
}
|
||||
}
|
||||
return &status
|
||||
}
|
||||
|
||||
func (s *ServerService) GetCpuPercent() float64 {
|
||||
percents, err := cpu.Percent(0, false)
|
||||
if err != nil {
|
||||
logger.Warning("get cpu percent failed:", err)
|
||||
return 0
|
||||
} else {
|
||||
return percents[0]
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ServerService) GetUptime() uint64 {
|
||||
upTime, err := host.Uptime()
|
||||
if err != nil {
|
||||
logger.Warning("get uptime failed:", err)
|
||||
return 0
|
||||
} else {
|
||||
return upTime
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ServerService) GetMemInfo() map[string]interface{} {
|
||||
info := make(map[string]interface{}, 0)
|
||||
memInfo, err := mem.VirtualMemory()
|
||||
if err != nil {
|
||||
logger.Warning("get virtual memory failed:", err)
|
||||
} else {
|
||||
info["current"] = memInfo.Used
|
||||
info["total"] = memInfo.Total
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
func (s *ServerService) GetNetInfo() map[string]interface{} {
|
||||
info := make(map[string]interface{}, 0)
|
||||
ioStats, err := net.IOCounters(false)
|
||||
if err != nil {
|
||||
logger.Warning("get io counters failed:", err)
|
||||
} else if len(ioStats) > 0 {
|
||||
ioStat := ioStats[0]
|
||||
info["sent"] = ioStat.BytesSent
|
||||
info["recv"] = ioStat.BytesRecv
|
||||
info["psent"] = ioStat.PacketsSent
|
||||
info["precv"] = ioStat.PacketsRecv
|
||||
} else {
|
||||
logger.Warning("can not find io counters")
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
func (s *ServerService) GetSingboxInfo() map[string]interface{} {
|
||||
info := make(map[string]interface{}, 0)
|
||||
if s.SingBoxService.IsRunning() {
|
||||
info["running"] = true
|
||||
sysStats, _ := s.SingBoxService.GetSysStats()
|
||||
info["stats"] = sysStats
|
||||
} else {
|
||||
info["running"] = false
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
func (s *ServerService) GetSystemInfo() map[string]interface{} {
|
||||
info := make(map[string]interface{}, 0)
|
||||
var rtm runtime.MemStats
|
||||
runtime.ReadMemStats(&rtm)
|
||||
|
||||
info["appMem"] = rtm.Sys
|
||||
info["appThreads"] = uint32(runtime.NumGoroutine())
|
||||
cpuInfo, err := cpu.Info()
|
||||
if err == nil {
|
||||
info["cpuType"] = cpuInfo[0].ModelName
|
||||
}
|
||||
info["cpuCount"] = runtime.NumCPU()
|
||||
info["hostName"], _ = os.Hostname()
|
||||
info["appVersion"] = config.GetVersion()
|
||||
ipv4 := make([]string, 0)
|
||||
ipv6 := make([]string, 0)
|
||||
// get ip address
|
||||
netInterfaces, _ := net.Interfaces()
|
||||
for i := 0; i < len(netInterfaces); i++ {
|
||||
if len(netInterfaces[i].Flags) > 2 && netInterfaces[i].Flags[0] == "up" && netInterfaces[i].Flags[1] != "loopback" {
|
||||
addrs := netInterfaces[i].Addrs
|
||||
|
||||
for _, address := range addrs {
|
||||
if strings.Contains(address.Addr, ".") {
|
||||
ipv4 = append(ipv4, address.Addr)
|
||||
} else if address.Addr[0:6] != "fe80::" {
|
||||
ipv6 = append(ipv6, address.Addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
info["ipv4"] = ipv4
|
||||
info["ipv6"] = ipv6
|
||||
|
||||
return info
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"s-ui/database"
|
||||
"s-ui/database/model"
|
||||
"s-ui/logger"
|
||||
"s-ui/util/common"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var defaultValueMap = map[string]string{
|
||||
"webListen": "",
|
||||
"webDomain": "",
|
||||
"webPort": "2095",
|
||||
"webSecret": common.Random(32),
|
||||
"webCertFile": "",
|
||||
"webKeyFile": "",
|
||||
"sessionMaxAge": "0",
|
||||
"timeLocation": "Asia/Tehran",
|
||||
"subListen": "",
|
||||
"subPort": "2096",
|
||||
"subPath": "/sub/",
|
||||
"subDomain": "",
|
||||
"subCertFile": "",
|
||||
"subKeyFile": "",
|
||||
"subUpdates": "12",
|
||||
"subEncode": "true",
|
||||
"subShowInfo": "false",
|
||||
"subURI": "",
|
||||
}
|
||||
|
||||
type SettingService struct {
|
||||
}
|
||||
|
||||
func (s *SettingService) GetAllSetting() (*map[string]string, error) {
|
||||
db := database.GetDB()
|
||||
settings := make([]*model.Setting, 0)
|
||||
err := db.Model(model.Setting{}).Find(&settings).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allSetting := map[string]string{}
|
||||
|
||||
for _, setting := range settings {
|
||||
allSetting[setting.Key] = setting.Value
|
||||
}
|
||||
|
||||
for key, defaultValue := range defaultValueMap {
|
||||
if _, exists := allSetting[key]; !exists {
|
||||
err = s.saveSetting(key, defaultValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allSetting[key] = defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
// Due to security principles
|
||||
delete(allSetting, "webSecret")
|
||||
|
||||
return &allSetting, nil
|
||||
}
|
||||
|
||||
func (s *SettingService) getSetting(key string) (*model.Setting, error) {
|
||||
db := database.GetDB()
|
||||
setting := &model.Setting{}
|
||||
err := db.Model(model.Setting{}).Where("key = ?", key).First(setting).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return setting, nil
|
||||
}
|
||||
|
||||
func (s *SettingService) getString(key string) (string, error) {
|
||||
setting, err := s.getSetting(key)
|
||||
if database.IsNotFound(err) {
|
||||
value, ok := defaultValueMap[key]
|
||||
if !ok {
|
||||
return "", common.NewErrorf("key <%v> not in defaultValueMap", key)
|
||||
}
|
||||
return value, nil
|
||||
} else if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return setting.Value, nil
|
||||
}
|
||||
|
||||
func (s *SettingService) saveSetting(key string, value string) error {
|
||||
setting, err := s.getSetting(key)
|
||||
db := database.GetDB()
|
||||
if database.IsNotFound(err) {
|
||||
return db.Create(&model.Setting{
|
||||
Key: key,
|
||||
Value: value,
|
||||
}).Error
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
setting.Key = key
|
||||
setting.Value = value
|
||||
return db.Save(setting).Error
|
||||
}
|
||||
|
||||
func (s *SettingService) setString(key string, value string) error {
|
||||
return s.saveSetting(key, value)
|
||||
}
|
||||
|
||||
func (s *SettingService) getBool(key string) (bool, error) {
|
||||
str, err := s.getString(key)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return strconv.ParseBool(str)
|
||||
}
|
||||
|
||||
func (s *SettingService) setBool(key string, value bool) error {
|
||||
return s.setString(key, strconv.FormatBool(value))
|
||||
}
|
||||
|
||||
func (s *SettingService) getInt(key string) (int, error) {
|
||||
str, err := s.getString(key)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return strconv.Atoi(str)
|
||||
}
|
||||
|
||||
func (s *SettingService) setInt(key string, value int) error {
|
||||
return s.setString(key, strconv.Itoa(value))
|
||||
}
|
||||
func (s *SettingService) GetListen() (string, error) {
|
||||
return s.getString("webListen")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetWebDomain() (string, error) {
|
||||
return s.getString("webDomain")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetPort() (int, error) {
|
||||
return s.getInt("webPort")
|
||||
}
|
||||
|
||||
func (s *SettingService) SetPort(port int) error {
|
||||
return s.setInt("webPort", port)
|
||||
}
|
||||
|
||||
func (s *SettingService) GetCertFile() (string, error) {
|
||||
return s.getString("webCertFile")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetKeyFile() (string, error) {
|
||||
return s.getString("webKeyFile")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSecret() ([]byte, error) {
|
||||
secret, err := s.getString("webSecret")
|
||||
if secret == defaultValueMap["webSecret"] {
|
||||
err := s.saveSetting("webSecret", secret)
|
||||
if err != nil {
|
||||
logger.Warning("save webSecret failed:", err)
|
||||
}
|
||||
}
|
||||
return []byte(secret), err
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSessionMaxAge() (int, error) {
|
||||
return s.getInt("sessionMaxAge")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetTimeLocation() (*time.Location, error) {
|
||||
l, err := s.getString("timeLocation")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
location, err := time.LoadLocation(l)
|
||||
if err != nil {
|
||||
defaultLocation := defaultValueMap["timeLocation"]
|
||||
logger.Errorf("location <%v> not exist, using default location: %v", l, defaultLocation)
|
||||
return time.LoadLocation(defaultLocation)
|
||||
}
|
||||
return location, nil
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubListen() (string, error) {
|
||||
return s.getString("subListen")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubPort() (int, error) {
|
||||
return s.getInt("subPort")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubPath() (string, error) {
|
||||
subPath, err := s.getString("subPath")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !strings.HasPrefix(subPath, "/") {
|
||||
subPath = "/" + subPath
|
||||
}
|
||||
if !strings.HasSuffix(subPath, "/") {
|
||||
subPath += "/"
|
||||
}
|
||||
return subPath, nil
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubDomain() (string, error) {
|
||||
return s.getString("subDomain")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubCertFile() (string, error) {
|
||||
return s.getString("subCertFile")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubKeyFile() (string, error) {
|
||||
return s.getString("subKeyFile")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubUpdates() (int, error) {
|
||||
return s.getInt("subUpdates")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubEncode() (bool, error) {
|
||||
return s.getBool("subEncode")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubShowInfo() (bool, error) {
|
||||
return s.getBool("subShowInfo")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubURI() (string, error) {
|
||||
return s.getString("subURI")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetFinalSubURI(host string) (string, error) {
|
||||
allSetting, err := s.GetAllSetting()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
SubURI := (*allSetting)["subURI"]
|
||||
if SubURI != "" {
|
||||
return SubURI, nil
|
||||
}
|
||||
protocol := "http"
|
||||
if (*allSetting)["subKeyFile"] != "" && (*allSetting)["subCertFile"] != "" {
|
||||
protocol = "https"
|
||||
}
|
||||
if (*allSetting)["subDomain"] != "" {
|
||||
host = (*allSetting)["subDomain"]
|
||||
}
|
||||
port := ":" + (*allSetting)["subPort"]
|
||||
if (port == "80" && protocol == "http") || (port == "443" && protocol == "https") {
|
||||
port = ""
|
||||
}
|
||||
return protocol + "://" + host + port + (*allSetting)["subPath"], nil
|
||||
}
|
||||
|
||||
func (s *SettingService) Save(tx *gorm.DB, changes []model.Changes) error {
|
||||
var err error
|
||||
for _, change := range changes {
|
||||
key := change.Key
|
||||
var obj string
|
||||
json.Unmarshal(change.Obj, &obj)
|
||||
|
||||
// Secure file existance check
|
||||
if key == "webCertFile" ||
|
||||
key == "webKeyFile" ||
|
||||
key == "subCertFile" ||
|
||||
key == "subKeyFile" {
|
||||
err = s.fileExists(obj)
|
||||
if err != nil {
|
||||
return common.NewError(" -> ", obj, " is not exists")
|
||||
}
|
||||
}
|
||||
|
||||
err = tx.Model(model.Setting{}).Where("key = ?", key).Update("value", obj).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *SettingService) fileExists(path string) error {
|
||||
_, err := os.Stat(path)
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"s-ui/singbox"
|
||||
)
|
||||
|
||||
type SingBoxService struct {
|
||||
singbox.V2rayAPI
|
||||
singbox.Controller
|
||||
StatsService
|
||||
}
|
||||
|
||||
func (s *SingBoxService) GetStats() error {
|
||||
s.V2rayAPI.Init(ApiAddr)
|
||||
defer s.V2rayAPI.Close()
|
||||
stats, err := s.V2rayAPI.GetStats(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.StatsService.SaveStats(stats)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SingBoxService) GetSysStats() (*map[string]interface{}, error) {
|
||||
s.V2rayAPI.Init(ApiAddr)
|
||||
defer s.V2rayAPI.Close()
|
||||
resp, err := s.V2rayAPI.GetSysStats()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make(map[string]interface{})
|
||||
result["NumGoroutine"] = resp.NumGoroutine
|
||||
result["Alloc"] = resp.Alloc
|
||||
result["Uptime"] = resp.Uptime
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"s-ui/database"
|
||||
"s-ui/database/model"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type onlines struct {
|
||||
Inbound []string `json:"inbound,omitempty"`
|
||||
User []string `json:"user,omitempty"`
|
||||
Outbound []string `json:"outbound,omitempty"`
|
||||
}
|
||||
|
||||
var onlineResources = &onlines{}
|
||||
|
||||
type StatsService struct {
|
||||
}
|
||||
|
||||
func (s *StatsService) SaveStats(stats []*model.Stats) error {
|
||||
var err error
|
||||
|
||||
// Reset onlines
|
||||
onlineResources.Inbound = nil
|
||||
onlineResources.Outbound = nil
|
||||
onlineResources.User = nil
|
||||
|
||||
if len(stats) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
db := database.GetDB()
|
||||
tx := db.Begin()
|
||||
defer func() {
|
||||
if err == nil {
|
||||
tx.Commit()
|
||||
} else {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
for _, stat := range stats {
|
||||
if stat.Resource == "user" {
|
||||
if stat.Direction {
|
||||
err = tx.Model(model.Client{}).Where("name = ?", stat.Tag).
|
||||
UpdateColumn("up", gorm.Expr("up + ?", stat.Traffic)).Error
|
||||
} else {
|
||||
err = tx.Model(model.Client{}).Where("name = ?", stat.Tag).
|
||||
UpdateColumn("down", gorm.Expr("down + ?", stat.Traffic)).Error
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if stat.Direction {
|
||||
switch stat.Resource {
|
||||
case "inbound":
|
||||
onlineResources.Inbound = append(onlineResources.Inbound, stat.Tag)
|
||||
case "outbound":
|
||||
onlineResources.Outbound = append(onlineResources.Outbound, stat.Tag)
|
||||
case "user":
|
||||
onlineResources.User = append(onlineResources.User, stat.Tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = tx.Create(&stats).Error
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *StatsService) GetStats(resorce string, tag string, limit int) ([]model.Stats, error) {
|
||||
var err error
|
||||
var result []model.Stats
|
||||
|
||||
currentTime := time.Now().Unix()
|
||||
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
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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) DelOldStats(days int) error {
|
||||
oldTime := time.Now().AddDate(0, 0, -(days)).Unix()
|
||||
db := database.GetDB()
|
||||
return db.Where("date_time < ?", oldTime).Delete(model.Stats{}).Error
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"s-ui/database"
|
||||
"s-ui/database/model"
|
||||
"s-ui/logger"
|
||||
"s-ui/util/common"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type UserService struct {
|
||||
}
|
||||
|
||||
func (s *UserService) Login(username string, password string, remoteIP string) (string, error) {
|
||||
user := s.CheckUser(username, password, remoteIP)
|
||||
if user == nil {
|
||||
return "", common.NewError("wrong user or password! IP: ", remoteIP)
|
||||
}
|
||||
return user.Username, nil
|
||||
}
|
||||
|
||||
func (s *UserService) CheckUser(username string, password string, remoteIP string) *model.User {
|
||||
db := database.GetDB()
|
||||
|
||||
user := &model.User{}
|
||||
err := db.Model(model.User{}).
|
||||
Where("username = ? and password = ?", username, password).
|
||||
First(user).
|
||||
Error
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
logger.Warning("check user err:", err, " IP: ", remoteIP)
|
||||
return nil
|
||||
}
|
||||
|
||||
lastLoginTxt := time.Now().Format("2006-01-02 15:04:05") + "-" + remoteIP
|
||||
err = db.Model(model.User{}).
|
||||
Where("username = ?", username).
|
||||
Update("last_logins", &lastLoginTxt).Error
|
||||
if err != nil {
|
||||
logger.Warning("unable to log login data", err)
|
||||
}
|
||||
return user
|
||||
}
|
||||
Reference in New Issue
Block a user