252 lines
7.1 KiB
Go
252 lines
7.1 KiB
Go
package login
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
// handleWebRoot handles the root web interface page
|
|
func (s *Server) handleWebRoot(w http.ResponseWriter, r *http.Request) {
|
|
if !s.authenticateWebRequest(w, r) {
|
|
return
|
|
}
|
|
|
|
html := `<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>EQ2Go Login Server</title>
|
|
<style>
|
|
body { font-family: Arial, sans-serif; margin: 40px; }
|
|
.header { background: #2c3e50; color: white; padding: 20px; border-radius: 5px; }
|
|
.stats { display: flex; gap: 20px; margin: 20px 0; }
|
|
.stat-card { background: #f8f9fa; padding: 15px; border-radius: 5px; flex: 1; }
|
|
.stat-value { font-size: 24px; font-weight: bold; color: #2c3e50; }
|
|
.table { width: 100%; border-collapse: collapse; margin: 20px 0; }
|
|
.table th, .table td { border: 1px solid #ddd; padding: 8px; text-align: left; }
|
|
.table th { background-color: #f2f2f2; }
|
|
.status-up { color: green; font-weight: bold; }
|
|
.status-down { color: red; font-weight: bold; }
|
|
.status-locked { color: orange; font-weight: bold; }
|
|
</style>
|
|
<script>
|
|
function refreshData() {
|
|
fetch('/api/status')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
document.getElementById('clients').textContent = data.clients;
|
|
document.getElementById('worlds').textContent = data.worlds;
|
|
document.getElementById('uptime').textContent = data.uptime;
|
|
});
|
|
}
|
|
|
|
setInterval(refreshData, 5000); // Refresh every 5 seconds
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<div class="header">
|
|
<h1>EQ2Go Login Server Administration</h1>
|
|
<p>Version: 1.0.0-dev</p>
|
|
</div>
|
|
|
|
<div class="stats">
|
|
<div class="stat-card">
|
|
<div class="stat-value" id="clients">-</div>
|
|
<div>Connected Clients</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value" id="worlds">-</div>
|
|
<div>World Servers</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value" id="uptime">-</div>
|
|
<div>Uptime</div>
|
|
</div>
|
|
</div>
|
|
|
|
<h2>API Endpoints</h2>
|
|
<ul>
|
|
<li><a href="/api/status">Server Status</a></li>
|
|
<li><a href="/api/clients">Connected Clients</a></li>
|
|
<li><a href="/api/worlds">World Servers</a></li>
|
|
</ul>
|
|
|
|
<script>refreshData();</script>
|
|
</body>
|
|
</html>`
|
|
|
|
w.Header().Set("Content-Type", "text/html")
|
|
w.Write([]byte(html))
|
|
}
|
|
|
|
// handleAPIStatus handles the status API endpoint
|
|
func (s *Server) handleAPIStatus(w http.ResponseWriter, r *http.Request) {
|
|
if !s.authenticateWebRequest(w, r) {
|
|
return
|
|
}
|
|
|
|
status := map[string]interface{}{
|
|
"server_name": s.config.ServerName,
|
|
"version": "1.0.0-dev",
|
|
"running": s.IsRunning(),
|
|
"uptime": s.formatUptime(s.GetUptime()),
|
|
"clients": s.GetClientCount(),
|
|
"worlds": s.GetWorldCount(),
|
|
"timestamp": time.Now().Unix(),
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(status)
|
|
}
|
|
|
|
// handleAPIClients handles the clients API endpoint
|
|
func (s *Server) handleAPIClients(w http.ResponseWriter, r *http.Request) {
|
|
if !s.authenticateWebRequest(w, r) {
|
|
return
|
|
}
|
|
|
|
clients := s.clientList.GetClients()
|
|
clientInfo := make([]map[string]interface{}, len(clients))
|
|
|
|
for i, client := range clients {
|
|
clientInfo[i] = map[string]interface{}{
|
|
"ip_address": client.GetIPAddress(),
|
|
"account_id": client.GetAccountID(),
|
|
"account_name": client.GetAccountName(),
|
|
"state": client.GetState().String(),
|
|
"connect_time": client.GetConnectTime().Unix(),
|
|
"last_activity": client.GetLastActivity().Unix(),
|
|
}
|
|
}
|
|
|
|
response := map[string]interface{}{
|
|
"total_clients": len(clients),
|
|
"clients": clientInfo,
|
|
"stats": s.clientList.GetStats(),
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(response)
|
|
}
|
|
|
|
// handleAPIWorlds handles the worlds API endpoint
|
|
func (s *Server) handleAPIWorlds(w http.ResponseWriter, r *http.Request) {
|
|
if !s.authenticateWebRequest(w, r) {
|
|
return
|
|
}
|
|
|
|
worlds := s.worldList.GetAllWorlds()
|
|
worldInfo := make([]map[string]interface{}, len(worlds))
|
|
|
|
for i, world := range worlds {
|
|
worldInfo[i] = map[string]interface{}{
|
|
"id": world.ID,
|
|
"name": world.Name,
|
|
"address": world.Address,
|
|
"port": world.Port,
|
|
"status": world.Status,
|
|
"population": world.Population,
|
|
"max_players": world.MaxPlayers,
|
|
"population_pct": world.GetPopulationPercentage(),
|
|
"population_level": world.GetPopulationLevel(),
|
|
"locked": world.IsLocked(),
|
|
"hidden": world.IsHidden(),
|
|
"last_heartbeat": world.LastHeartbeat,
|
|
"description": world.Description,
|
|
}
|
|
}
|
|
|
|
response := map[string]interface{}{
|
|
"total_worlds": len(worlds),
|
|
"worlds": worldInfo,
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(response)
|
|
}
|
|
|
|
// authenticateWebRequest performs basic authentication for web requests
|
|
func (s *Server) authenticateWebRequest(w http.ResponseWriter, r *http.Request) bool {
|
|
// Skip authentication if no credentials are configured
|
|
if s.config.WebUser == "" || s.config.WebPassword == "" {
|
|
return true
|
|
}
|
|
|
|
username, password, ok := r.BasicAuth()
|
|
if !ok {
|
|
w.Header().Set("WWW-Authenticate", `Basic realm="EQ2Go Login Server"`)
|
|
w.WriteHeader(401)
|
|
w.Write([]byte("Unauthorized"))
|
|
return false
|
|
}
|
|
|
|
if username != s.config.WebUser || password != s.config.WebPassword {
|
|
w.Header().Set("WWW-Authenticate", `Basic realm="EQ2Go Login Server"`)
|
|
w.WriteHeader(401)
|
|
w.Write([]byte("Unauthorized"))
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// formatUptime formats uptime duration into a readable string
|
|
func (s *Server) formatUptime(duration time.Duration) string {
|
|
if duration == 0 {
|
|
return "Not running"
|
|
}
|
|
|
|
days := int(duration.Hours()) / 24
|
|
hours := int(duration.Hours()) % 24
|
|
minutes := int(duration.Minutes()) % 60
|
|
seconds := int(duration.Seconds()) % 60
|
|
|
|
if days > 0 {
|
|
return fmt.Sprintf("%dd %dh %dm %ds", days, hours, minutes, seconds)
|
|
} else if hours > 0 {
|
|
return fmt.Sprintf("%dh %dm %ds", hours, minutes, seconds)
|
|
} else if minutes > 0 {
|
|
return fmt.Sprintf("%dm %ds", minutes, seconds)
|
|
} else {
|
|
return fmt.Sprintf("%ds", seconds)
|
|
}
|
|
}
|
|
|
|
// handleKickClient handles kicking a client (admin endpoint)
|
|
func (s *Server) handleKickClient(w http.ResponseWriter, r *http.Request) {
|
|
if !s.authenticateWebRequest(w, r) {
|
|
return
|
|
}
|
|
|
|
if r.Method != "POST" {
|
|
w.WriteHeader(405)
|
|
w.Write([]byte("Method not allowed"))
|
|
return
|
|
}
|
|
|
|
accountIDStr := r.FormValue("account_id")
|
|
reason := r.FormValue("reason")
|
|
|
|
if reason == "" {
|
|
reason = "Kicked by administrator"
|
|
}
|
|
|
|
accountID, err := strconv.ParseInt(accountIDStr, 10, 32)
|
|
if err != nil {
|
|
w.WriteHeader(400)
|
|
w.Write([]byte("Invalid account ID"))
|
|
return
|
|
}
|
|
|
|
success := s.clientList.DisconnectByAccountID(int32(accountID), reason)
|
|
|
|
response := map[string]interface{}{
|
|
"success": success,
|
|
"message": "Client disconnected",
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(response)
|
|
} |