package web import ( "context" "encoding/json" "fmt" "html/template" "log" "net" "net/http" "sync" "time" "ananta-ps/internal/config" "ananta-ps/internal/gameserver" ) type AdminServer struct { cfg *config.Config gameServer *gameserver.Server httpSrv *http.Server cancelFunc context.CancelFunc mu sync.RWMutex } func NewAdmin(cfg *config.Config, gameServer *gameserver.Server) *AdminServer { return &AdminServer{ cfg: cfg, gameServer: gameServer, } } func (a *AdminServer) Start() error { if !a.cfg.Admin.Enabled { return nil } ctx, cancel := context.WithCancel(context.Background()) a.cancelFunc = cancel mux := http.NewServeMux() mux.HandleFunc("/", a.handleDashboard) mux.HandleFunc("/api/teleport", a.handleTeleport) mux.HandleFunc("/api/command", a.handleCommand) mux.HandleFunc("/api/sessions", a.handleSessions) host := a.cfg.Admin.Host if host == "" { host = "127.0.0.1" } addr := net.JoinHostPort(host, fmt.Sprint(a.cfg.Admin.Port)) a.httpSrv = &http.Server{ Addr: addr, Handler: mux, } ln, err := net.Listen("tcp", addr) if err != nil { cancel() return err } go func() { log.Printf("[ADMIN-GM] Admin Web Panel running at http://%s:%d/", a.cfg.Network.AdvertisedHost, a.cfg.Admin.Port) if err := a.httpSrv.Serve(ln); err != nil && err != http.ErrServerClosed { log.Printf("[ADMIN-GM] Web server error: %v", err) } }() _ = ctx return nil } func (a *AdminServer) Stop() error { if a.cancelFunc != nil { a.cancelFunc() } if a.httpSrv != nil { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() _ = a.httpSrv.Shutdown(ctx) } return nil } func (a *AdminServer) handleDashboard(w http.ResponseWriter, r *http.Request) { tmpl := ` Ananta GM Admin Dashboard

Ananta GM Admin Dashboard (Go Native)

Project Mugen CBT 4229938 Sandbox Controller

ONLINE

Server Status

Active Sessions
{{.ActiveSessions}}
Player UID / Account
{{.PlayerPID}} ({{.AccountId}})
Active Character Template
{{.InitialSpirit}}

Instant Teleportation

Free-Roam & Abilities

` sessions := a.gameServer.GetActiveSessions() data := map[string]interface{}{ "ActiveSessions": len(sessions), "PlayerPID": a.cfg.Player.Pid, "AccountId": a.cfg.Player.AccountId, "InitialSpirit": a.cfg.Player.InitialSpiritTemplateId, } t := template.Must(template.New("dashboard").Parse(tmpl)) w.Header().Set("Content-Type", "text/html; charset=utf-8") _ = t.Execute(w, data) } func (a *AdminServer) handleTeleport(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var req struct { X float32 `json:"x"` Y float32 `json:"y"` Z float32 `json:"z"` Facing float32 `json:"facing"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{"ok": false, "msg": err.Error()}) return } sessions := a.gameServer.GetActiveSessions() if len(sessions) == 0 { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{"ok": false, "msg": "No active client connected in game"}) return } for _, sess := range sessions { _ = sess.Teleport(req.X, req.Y, req.Z, req.Facing) } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{"ok": true, "count": len(sessions)}) } func (a *AdminServer) handleSessions(w http.ResponseWriter, r *http.Request) { sessions := a.gameServer.GetActiveSessions() w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "count": len(sessions), "host": a.cfg.Network.AdvertisedHost, "port": a.cfg.Network.GamePort, }) } func (a *AdminServer) handleCommand(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var req struct { Command string `json:"command"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{"ok": false, "msg": err.Error()}) return } bridge := a.gameServer.GetAdminBridge() if bridge == nil { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{"ok": false, "msg": "Admin bridge not initialized"}) return } res, err := bridge.ExecuteCommand(req.Command) w.Header().Set("Content-Type", "application/json") if err != nil { json.NewEncoder(w).Encode(map[string]interface{}{"ok": false, "msg": err.Error()}) return } json.NewEncoder(w).Encode(map[string]interface{}{"ok": true, "result": res}) }