package server import ( "crypto/rand" "encoding/hex" "encoding/json" "net/http" "strings" "time" ) func (s *Server) handleAdminLogin(w http.ResponseWriter, r *http.Request) { switch r.Method { case "GET": w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Write([]byte(`Admin Login

Admin Login

`)) case "POST": if err := r.ParseForm(); err != nil { http.Error(w, "bad form", 400) return } user := r.FormValue("username") pass := r.FormValue("password") if !s.cfg.CheckAdmin(user, pass) { http.Error(w, "401 Unauthorized", 401) return } tok := s.tokens.Create() http.SetCookie(w, &http.Cookie{ Name: "admin_session", Value: tok, Path: "/admin", HttpOnly: true, SameSite: http.SameSiteLaxMode, MaxAge: 86400, }) http.Redirect(w, r, "/admin/dashboard", http.StatusFound) default: http.Error(w, "method not allowed", 405) } } func (s *Server) handleAdminDashboard(w http.ResponseWriter, r *http.Request) { if !s.requireAdminCookie(w, r) { return } var userCount, deviceCount, opsCount int s.db.QueryRow("SELECT COUNT(*) FROM server_users").Scan(&userCount) s.db.QueryRow("SELECT COUNT(*) FROM server_devices").Scan(&deviceCount) s.db.QueryRow("SELECT COUNT(*) FROM server_ops").Scan(&opsCount) w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Write([]byte(`Admin Dashboard

Verstak Sync Server — Admin

` + intToStr(userCount) + `
Users
` + intToStr(deviceCount) + `
Devices
` + intToStr(opsCount) + `
Sync Ops

Users | Devices | Health

`)) } func (s *Server) handleAdminUsers(w http.ResponseWriter, r *http.Request) { if !s.requireAdminCookie(w, r) { return } rows, err := s.db.Query("SELECT id, username, email, confirmed, blocked, created_at FROM server_users ORDER BY created_at DESC") if err != nil { http.Error(w, err.Error(), 500) return } defer rows.Close() var users []map[string]interface{} for rows.Next() { var id, username, email, createdAt string var confirmed, blocked int rows.Scan(&id, &username, &email, &confirmed, &blocked, &createdAt) users = append(users, map[string]interface{}{ "id": id, "username": username, "email": email, "confirmed": confirmed, "blocked": blocked, "created_at": createdAt, }) } w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Write([]byte(`Users

Users ← Dashboard

`)) for _, u := range users { confirmed := "✅" if u["confirmed"].(int) == 0 { confirmed = "❌" } blocked := "" if u["blocked"].(int) != 0 { blocked = "🚫" } w.Write([]byte(``)) } w.Write([]byte(`
UsernameEmailConfirmedBlockedCreated
` + u["username"].(string) + `` + u["email"].(string) + `` + confirmed + `` + blocked + `` + u["created_at"].(string) + `
`)) } func (s *Server) handleAdminDevices(w http.ResponseWriter, r *http.Request) { if !s.requireAdminCookie(w, r) { return } rows, err := s.db.Query(`SELECT d.id, d.name, d.client_version, COALESCE(d.last_seen,''), COALESCE(d.revoked_at,''), d.created_at FROM server_devices d ORDER BY d.created_at DESC`) if err != nil { http.Error(w, err.Error(), 500) return } defer rows.Close() w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Write([]byte(`Devices

Devices ← Dashboard

`)) for rows.Next() { var id, name, clientVer, lastSeen, revokedAt, createdAt string rows.Scan(&id, &name, &clientVer, &lastSeen, &revokedAt, &createdAt) if lastSeen == "" { lastSeen = "never" } if revokedAt == "" { revokedAt = "-" } w.Write([]byte(``)) } w.Write([]byte(`
NameIDVersionLast SeenRevokedCreated
` + name + `` + id + `` + clientVer + `` + lastSeen + `` + revokedAt + `` + createdAt + `
`)) } func (s *Server) requireAdminCookie(w http.ResponseWriter, r *http.Request) bool { cookie, err := r.Cookie("admin_session") if err != nil || cookie.Value == "" { http.Redirect(w, r, "/admin/login", http.StatusFound) return false } if !s.tokens.Check(cookie.Value) { http.Redirect(w, r, "/admin/login", http.StatusFound) return false } return true } func intToStr(n int) string { b, _ := json.Marshal(n) return strings.Trim(string(b), "\"") } var _ = time.Now var _ = rand.Read var _ = hex.EncodeToString