status updates
This commit is contained in:
@@ -69,7 +69,7 @@ func (ac *ApiController) getStatus(c *fiber.Ctx) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(400).SendString(strings.ReplaceAll(err.Error(), "\x00", ""))
|
return c.Status(400).SendString(strings.ReplaceAll(err.Error(), "\x00", ""))
|
||||||
}
|
}
|
||||||
return c.SendString(apiModel)
|
return c.SendString(string(apiModel))
|
||||||
}
|
}
|
||||||
|
|
||||||
// startServer starts service
|
// startServer starts service
|
||||||
|
|||||||
@@ -58,11 +58,7 @@ func (ac *ConfigController) updateConfig(c *fiber.Ctx) error {
|
|||||||
return c.Status(400).SendString(err.Error())
|
return c.Status(400).SendString(err.Error())
|
||||||
}
|
}
|
||||||
if restart {
|
if restart {
|
||||||
serviceName, err := ac.apiService.GetServiceName(c)
|
ac.apiService.ApiRestartServer(c)
|
||||||
if err != nil {
|
|
||||||
return c.Status(400).JSON(fiber.Map{"error": "Unable to restart service"})
|
|
||||||
}
|
|
||||||
ac.apiService.RestartServer(serviceName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.JSON(ConfigModel)
|
return c.JSON(ConfigModel)
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
|
type ServiceStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
StatusRunning ServiceStatus = "SERVICE_RUNNING\r\n"
|
||||||
|
StatusStopped ServiceStatus = "SERVICE_STOPPED\r\n"
|
||||||
|
StatusRestarting ServiceStatus = "SERVICE_RESTARTING\r\n"
|
||||||
|
)
|
||||||
|
|
||||||
type ApiModel struct {
|
type ApiModel struct {
|
||||||
Api string `json:"api"`
|
Api string `json:"api"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServiceStatus represents a Windows service state
|
|
||||||
type ServiceStatus struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Status string `json:"status"` // "running", "stopped", "pending"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ type Config struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Configurations struct {
|
type Configurations struct {
|
||||||
Configuration map[string]interface{} `json:"configuration"`
|
Configuration Configuration `json:"configuration"`
|
||||||
Entrylist map[string]interface{} `json:"entrylist"`
|
AssistRules AssistRules `json:"assistRules"`
|
||||||
Event map[string]interface{} `json:"event"`
|
Event EventConfig `json:"event"`
|
||||||
EventRules map[string]interface{} `json:"eventRules"`
|
EventRules EventRules `json:"eventRules"`
|
||||||
Settings map[string]interface{} `json:"settings"`
|
Settings ServerSettings `json:"settings"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServerSettings struct {
|
type ServerSettings struct {
|
||||||
@@ -64,6 +64,18 @@ type Session struct {
|
|||||||
SessionDurationMinutes int `json:"sessionDurationMinutes"`
|
SessionDurationMinutes int `json:"sessionDurationMinutes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AssistRules struct {
|
||||||
|
StabilityControlLevelMax int `json:"stabilityControlLevelMax"`
|
||||||
|
DisableAutosteer int `json:"disableAutosteer"`
|
||||||
|
DisableAutoLights int `json:"disableAutoLights"`
|
||||||
|
DisableAutoWiper int `json:"disableAutoWiper"`
|
||||||
|
DisableAutoEngineStart int `json:"disableAutoEngineStart"`
|
||||||
|
DisableAutoPitLimiter int `json:"disableAutoPitLimiter"`
|
||||||
|
DisableAutoGear int `json:"disableAutoGear"`
|
||||||
|
DisableAutoClutch int `json:"disableAutoClutch"`
|
||||||
|
DisableIdealLine int `json:"disableIdealLine"`
|
||||||
|
}
|
||||||
|
|
||||||
type EventRules struct {
|
type EventRules struct {
|
||||||
QualifyStandingType int `json:"qualifyStandingType"`
|
QualifyStandingType int `json:"qualifyStandingType"`
|
||||||
PitWindowLengthSec int `json:"pitWindowLengthSec"`
|
PitWindowLengthSec int `json:"pitWindowLengthSec"`
|
||||||
@@ -77,3 +89,12 @@ type EventRules struct {
|
|||||||
IsMandatoryPitstopSwapDriverRequired bool `json:"isMandatoryPitstopSwapDriverRequired"`
|
IsMandatoryPitstopSwapDriverRequired bool `json:"isMandatoryPitstopSwapDriverRequired"`
|
||||||
TyreSetCount int `json:"tyreSetCount"`
|
TyreSetCount int `json:"tyreSetCount"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Configuration struct {
|
||||||
|
UdpPort int `json:"udpPort"`
|
||||||
|
TcpPort int `json:"tcpPort"`
|
||||||
|
MaxConnections int `json:"maxConnections"`
|
||||||
|
LanDiscovery int `json:"lanDiscovery"`
|
||||||
|
RegisterToLobby int `json:"registerToLobby"`
|
||||||
|
ConfigVersion int `json:"configVersion"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,42 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
// Server represents an ACC server instance
|
// Server represents an ACC server instance
|
||||||
type Server struct {
|
type Server struct {
|
||||||
ID uint `gorm:"primaryKey" json:"id"`
|
ID uint `gorm:"primaryKey" json:"id"`
|
||||||
Name string `gorm:"not null" json:"name"`
|
Name string `gorm:"not null" json:"name"`
|
||||||
Status string `json:"status"`
|
Status ServiceStatus `json:"status"`
|
||||||
IP string `gorm:"not null" json:"-"`
|
IP string `gorm:"not null" json:"-"`
|
||||||
Port int `gorm:"not null" json:"-"`
|
Port int `gorm:"not null" json:"-"`
|
||||||
ConfigPath string `gorm:"not null" json:"-"` // e.g. "/acc/servers/server1/"
|
ConfigPath string `gorm:"not null" json:"-"` // e.g. "/acc/servers/server1/"
|
||||||
ServiceName string `gorm:"not null" json:"-"` // Windows service name
|
ServiceName string `gorm:"not null" json:"-"` // Windows service name
|
||||||
|
BroadcastIP string `json:"-"`
|
||||||
|
BroadcastPort int `json:"-"`
|
||||||
|
BroadcastPassword string `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PlayerState struct {
|
||||||
|
CarID int // Car ID in broadcast packets
|
||||||
|
DriverName string // Optional: pulled from registration packet
|
||||||
|
TeamName string
|
||||||
|
CarModel string
|
||||||
|
CurrentLap int
|
||||||
|
LastLapTime int // in milliseconds
|
||||||
|
BestLapTime int // in milliseconds
|
||||||
|
Position int
|
||||||
|
ConnectedAt time.Time
|
||||||
|
DisconnectedAt *time.Time
|
||||||
|
IsConnected bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerState struct {
|
||||||
|
sync.RWMutex
|
||||||
|
Session string
|
||||||
|
PlayerCount int
|
||||||
|
Players map[int]*PlayerState
|
||||||
|
// etc.
|
||||||
}
|
}
|
||||||
@@ -62,3 +62,22 @@ func (as ServerRepository) GetAll(ctx context.Context) *[]model.Server {
|
|||||||
db.Find(&ServerModel)
|
db.Find(&ServerModel)
|
||||||
return ServerModel
|
return ServerModel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateServer
|
||||||
|
// Updates Server row from Server table.
|
||||||
|
//
|
||||||
|
// Args:
|
||||||
|
// context.Context: Application context
|
||||||
|
// Returns:
|
||||||
|
// model.Server: Server object from database.
|
||||||
|
func (as ServerRepository) UpdateServer(ctx context.Context, body *model.Server) *model.Server {
|
||||||
|
db := as.db.WithContext(ctx)
|
||||||
|
|
||||||
|
existingServer := new(model.Server)
|
||||||
|
result := db.Where("id=?", body.ID).First(existingServer)
|
||||||
|
if !errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||||
|
body.ID = existingServer.ID
|
||||||
|
}
|
||||||
|
db.Save(body)
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,6 +17,47 @@ import (
|
|||||||
"golang.org/x/text/encoding/unicode"
|
"golang.org/x/text/encoding/unicode"
|
||||||
"golang.org/x/text/transform"
|
"golang.org/x/text/transform"
|
||||||
)
|
)
|
||||||
|
const (
|
||||||
|
ConfigurationJson = "configuration.json"
|
||||||
|
AssistRulesJson = "assistRules.json"
|
||||||
|
EventJson = "event.json"
|
||||||
|
EventRulesJson = "eventRules.json"
|
||||||
|
SettingsJson = "settings.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
var decodeMap = map[string]func(string) (interface{}, error){
|
||||||
|
ConfigurationJson: func(f string) (interface{}, error) {
|
||||||
|
return readAndDecode[model.Configuration](f, ConfigurationJson)
|
||||||
|
},
|
||||||
|
AssistRulesJson: func(f string) (interface{}, error) {
|
||||||
|
return readAndDecode[model.AssistRules](f, AssistRulesJson)
|
||||||
|
},
|
||||||
|
EventJson: func(f string) (interface{}, error) {
|
||||||
|
return readAndDecode[model.EventConfig](f, EventJson)
|
||||||
|
},
|
||||||
|
EventRulesJson: func(f string) (interface{}, error) {
|
||||||
|
return readAndDecode[model.EventRules](f, EventRulesJson)
|
||||||
|
},
|
||||||
|
SettingsJson: func(f string) (interface{}, error) {
|
||||||
|
return readAndDecode[model.ServerSettings](f, SettingsJson)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodeFileName(fileName string) func(path string) (interface{}, error) {
|
||||||
|
if decoder, ok := decodeMap[fileName]; ok {
|
||||||
|
return decoder
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustDecode[T any](fileName, path string) (T, error) {
|
||||||
|
result, err := DecodeFileName(fileName)(path)
|
||||||
|
if err != nil {
|
||||||
|
var zero T
|
||||||
|
return zero, err
|
||||||
|
}
|
||||||
|
return result.(T), nil
|
||||||
|
}
|
||||||
|
|
||||||
type ConfigService struct {
|
type ConfigService struct {
|
||||||
repository *repository.ConfigRepository
|
repository *repository.ConfigRepository
|
||||||
@@ -77,17 +118,32 @@ func (as ConfigService) UpdateConfig(ctx *fiber.Ctx, body *map[string]interface{
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
newDataUTF16, err := EncodeUTF16LEBOM(newData)
|
newDataUTF16, err := EncodeUTF16LEBOM(newData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context := ctx.UserContext()
|
||||||
|
|
||||||
|
if (configFile == ConfigurationJson) {
|
||||||
|
config, err := DecodeToMap[model.Configuration](newDataUTF16)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if (server.BroadcastPort != config.UdpPort || server.Port != config.TcpPort) {
|
||||||
|
server.BroadcastPort = config.UdpPort
|
||||||
|
server.Port = config.TcpPort
|
||||||
|
as.serverRepository.UpdateServer(context, server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := os.WriteFile(configPath, newDataUTF16, 0644); err != nil {
|
if err := os.WriteFile(configPath, newDataUTF16, 0644); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log change
|
// Log change
|
||||||
return as.repository.UpdateConfig(ctx.UserContext(), &model.Config{
|
return as.repository.UpdateConfig(context, &model.Config{
|
||||||
ServerID: uint(serverID),
|
ServerID: uint(serverID),
|
||||||
ConfigFile: configFile,
|
ConfigFile: configFile,
|
||||||
OldConfig: string(oldDataUTF8),
|
OldConfig: string(oldDataUTF8),
|
||||||
@@ -103,7 +159,7 @@ func (as ConfigService) UpdateConfig(ctx *fiber.Ctx, body *map[string]interface{
|
|||||||
// context.Context: Application context
|
// context.Context: Application context
|
||||||
// Returns:
|
// Returns:
|
||||||
// string: Application version
|
// string: Application version
|
||||||
func (as ConfigService) GetConfig(ctx *fiber.Ctx) (map[string]interface{}, error) {
|
func (as ConfigService) GetConfig(ctx *fiber.Ctx) (interface{}, error) {
|
||||||
serverID, _ := ctx.ParamsInt("id")
|
serverID, _ := ctx.ParamsInt("id")
|
||||||
configFile := ctx.Params("file")
|
configFile := ctx.Params("file")
|
||||||
|
|
||||||
@@ -113,13 +169,7 @@ func (as ConfigService) GetConfig(ctx *fiber.Ctx) (map[string]interface{}, error
|
|||||||
return nil, fiber.NewError(404, "Server not found")
|
return nil, fiber.NewError(404, "Server not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
config, err := readFile(server.ConfigPath, configFile)
|
decoded, err := DecodeFileName(configFile)(server.ConfigPath)
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
decoded, err := DecodeToMap(config)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -143,47 +193,27 @@ func (as ConfigService) GetConfigs(ctx *fiber.Ctx) (*model.Configurations, error
|
|||||||
return nil, fiber.NewError(404, "Server not found")
|
return nil, fiber.NewError(404, "Server not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
configuration, err := readFile(server.ConfigPath, "configuration.json")
|
decodedconfiguration, err := mustDecode[model.Configuration](ConfigurationJson, server.ConfigPath)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
decodedconfiguration, err := DecodeToMap(configuration)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
entrylist, err := readFile(server.ConfigPath, "entrylist.json")
|
decodedAssistRules, err := mustDecode[model.AssistRules](AssistRulesJson, server.ConfigPath)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
decodedentrylist, err := DecodeToMap(entrylist)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
event, err := readFile(server.ConfigPath, "event.json")
|
decodedevent, err := mustDecode[model.EventConfig](EventJson, server.ConfigPath)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
decodedevent, err := DecodeToMap(event)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
eventRules, err := readFile(server.ConfigPath, "eventRules.json")
|
decodedeventRules, err := mustDecode[model.EventRules](EventRulesJson, server.ConfigPath)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
decodedeventRules, err := DecodeToMap(eventRules)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
settings, err := readFile(server.ConfigPath, "settings.json")
|
decodedsettings, err := mustDecode[model.ServerSettings](SettingsJson, server.ConfigPath)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
decodedsettings, err := DecodeToMap(settings)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -193,10 +223,24 @@ func (as ConfigService) GetConfigs(ctx *fiber.Ctx) (*model.Configurations, error
|
|||||||
Event: decodedevent,
|
Event: decodedevent,
|
||||||
EventRules: decodedeventRules,
|
EventRules: decodedeventRules,
|
||||||
Settings: decodedsettings,
|
Settings: decodedsettings,
|
||||||
Entrylist: decodedentrylist,
|
AssistRules: decodedAssistRules,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func readAndDecode[T interface{}](path string, configFile string) (T, error) {
|
||||||
|
settings, err := readFile(path, configFile)
|
||||||
|
var zero T
|
||||||
|
if err != nil {
|
||||||
|
return zero, err
|
||||||
|
}
|
||||||
|
decodedsettings, err := DecodeToMap[T](settings)
|
||||||
|
if err != nil {
|
||||||
|
return zero, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return decodedsettings, nil
|
||||||
|
}
|
||||||
|
|
||||||
func readFile(path string, configFile string) ([]byte, error) {
|
func readFile(path string, configFile string) ([]byte, error) {
|
||||||
configPath := filepath.Join(path, "\\server\\cfg", configFile)
|
configPath := filepath.Join(path, "\\server\\cfg", configFile)
|
||||||
oldData, err := os.ReadFile(configPath)
|
oldData, err := os.ReadFile(configPath)
|
||||||
@@ -219,19 +263,20 @@ func DecodeUTF16LEBOM(input []byte) ([]byte, error) {
|
|||||||
return transformBytes(decoder.NewDecoder(), input)
|
return transformBytes(decoder.NewDecoder(), input)
|
||||||
}
|
}
|
||||||
|
|
||||||
func DecodeToMap(input []byte) (map[string]interface{}, error) {
|
func DecodeToMap[T interface{}](input []byte) (T, error) {
|
||||||
|
var zero T
|
||||||
if input == nil {
|
if input == nil {
|
||||||
return nil, nil
|
return zero, nil
|
||||||
}
|
}
|
||||||
configUTF8 := new(map[string]interface{})
|
configUTF8 := new(T)
|
||||||
decoded, err := DecodeUTF16LEBOM(input)
|
decoded, err := DecodeUTF16LEBOM(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return zero, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.Unmarshal(decoded, configUTF8)
|
err = json.Unmarshal(decoded, configUTF8)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return zero, err
|
||||||
}
|
}
|
||||||
return *configUTF8, nil
|
return *configUTF8, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ func (as ServerService) GetAll(ctx *fiber.Ctx) *[]model.Server {
|
|||||||
|
|
||||||
for i, server := range *servers {
|
for i, server := range *servers {
|
||||||
status, _ := as.apiService.StatusServer(server.ServiceName)
|
status, _ := as.apiService.StatusServer(server.ServiceName)
|
||||||
(*servers)[i].Status = status
|
(*servers)[i].Status = model.ServiceStatus(status)
|
||||||
}
|
}
|
||||||
|
|
||||||
return servers
|
return servers
|
||||||
@@ -46,7 +46,8 @@ func (as ServerService) GetAll(ctx *fiber.Ctx) *[]model.Server {
|
|||||||
// string: Application version
|
// string: Application version
|
||||||
func (as ServerService) GetById(ctx *fiber.Ctx, serverID int) *model.Server {
|
func (as ServerService) GetById(ctx *fiber.Ctx, serverID int) *model.Server {
|
||||||
server := as.repository.GetFirst(ctx.UserContext(), serverID)
|
server := as.repository.GetFirst(ctx.UserContext(), serverID)
|
||||||
server.Status, _ = as.apiService.StatusServer(server.ServiceName);
|
status, _ := as.apiService.StatusServer(server.ServiceName)
|
||||||
|
server.Status = model.ServiceStatus(status)
|
||||||
|
|
||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ import (
|
|||||||
func InitializeServices(c *dig.Container) {
|
func InitializeServices(c *dig.Container) {
|
||||||
repository.InitializeRepositories(c)
|
repository.InitializeRepositories(c)
|
||||||
|
|
||||||
|
c.Provide(NewServerService)
|
||||||
c.Provide(NewApiService)
|
c.Provide(NewApiService)
|
||||||
c.Provide(NewConfigService)
|
c.Provide(NewConfigService)
|
||||||
c.Provide(NewServerService)
|
|
||||||
c.Provide(NewLookupService)
|
c.Provide(NewLookupService)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user