Files
agent-dashboard/internal/server/http.go
T
2026-06-02 14:32:14 +08:00

109 lines
2.9 KiB
Go

package server
import (
"crypto/subtle"
"encoding/json"
"net/http"
"strings"
"github.com/longbin/agent-notify/internal/remote"
)
func NewHandler(store *Store, token string) http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/healthz", handleHealthz)
mux.Handle("/api/v1/status", bearerAuth(token, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodPost:
handleStatusPost(store, w, r)
case http.MethodGet:
handleStatusList(store, w, r)
default:
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
}
})))
mux.Handle("/api/v1/meta", bearerAuth(token, http.HandlerFunc(handleMeta(store))))
return mux
}
func handleHealthz(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
w.WriteHeader(http.StatusOK)
}
func bearerAuth(token string, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !checkBearer(r, token) {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
func checkBearer(r *http.Request, token string) bool {
if token == "" {
return false
}
h := r.Header.Get("Authorization")
const prefix = "Bearer "
if len(h) < len(prefix) || !strings.HasPrefix(h, prefix) {
return false
}
got := h[len(prefix):]
return subtle.ConstantTimeCompare([]byte(got), []byte(token)) == 1
}
func handleStatusPost(store *Store, w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
var report remote.StatusReport
if err := json.NewDecoder(r.Body).Decode(&report); err != nil {
http.Error(w, "bad json", http.StatusBadRequest)
return
}
if err := store.Upsert(report); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]bool{"ok": true})
}
func handleStatusList(store *Store, w http.ResponseWriter, r *http.Request) {
filters := ListFilters{
Host: r.URL.Query().Get("host"),
CWD: r.URL.Query().Get("cwd"),
Agent: r.URL.Query().Get("agent"),
Status: r.URL.Query().Get("status"),
}
rows, err := store.List(filters)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if rows == nil {
rows = []SessionRow{}
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(rows)
}
func handleMeta(store *Store) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
meta, err := store.Meta()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(meta)
}
}