package adminapi import ( "net/http" "github.com/dariuszjaniak/warp/server/internal/appservice" "github.com/dariuszjaniak/warp/server/internal/httputil" "github.com/dariuszjaniak/warp/server/internal/userapi" ) // appServiceNamespace is one namespace in the status view. type appServiceNamespace struct { Regex string `json:"regex"` Exclusive bool `json:"exclusive"` } // appServiceDelivery is a service's delivery health. It is absent for a // service without a URL, which is sent nothing. type appServiceDelivery struct { StreamPos int64 `json:"stream_pos"` Behind int64 `json:"behind"` TxnID int64 `json:"txn_id"` EventsDelivered int64 `json:"events_delivered"` Failures int `json:"failures"` LastError string `json:"last_error,omitempty"` LastSuccessMS int64 `json:"last_success_ms"` LastFailureMS int64 `json:"last_failure_ms"` RetryAtMS int64 `json:"retry_at_ms"` } // appServiceResponse is one service in GET /appservices. type appServiceResponse struct { ID string `json:"id"` URL string `json:"url,omitempty"` Sender string `json:"sender"` RateLimited bool `json:"rate_limited"` Protocols []string `json:"protocols"` Namespaces map[string][]appServiceNamespace `json:"namespaces"` Delivery *appServiceDelivery `json:"delivery,omitempty"` } // handleGetAppServices implements GET /_warp/admin/v1/appservices: what is // registered, and how delivery to each is going. Registrations are read // from files at startup, so this is a view, not an editor; the tokens are // never shown. func (a *AdminAPI) handleGetAppServices(w http.ResponseWriter, r *http.Request, _ userapi.Authenticated) error { var top int64 if a.rooms != nil { var err error if top, err = a.rooms.StreamTop(r.Context()); err != nil { return err } } statuses := map[string]appServiceDelivery{} for _, s := range a.appservices.Statuses() { statuses[s.ID] = appServiceDelivery{ StreamPos: s.StreamPos, Behind: max(top-s.StreamPos, 0), TxnID: s.TxnID, EventsDelivered: s.EventsDelivered, Failures: s.Failures, LastError: s.LastError, LastSuccessMS: s.LastSuccessMS, LastFailureMS: s.LastFailureMS, RetryAtMS: s.RetryAtMS, } } out := []appServiceResponse{} for _, svc := range a.appservices.Registry().Services() { resp := appServiceResponse{ ID: svc.ID, URL: svc.URL, Sender: svc.SenderUserID(), RateLimited: svc.RateLimitedUsers(), Protocols: svc.Protocols, Namespaces: map[string][]appServiceNamespace{ "users": namespacesOf(svc.Namespaces.Users), "aliases": namespacesOf(svc.Namespaces.Aliases), "rooms": namespacesOf(svc.Namespaces.Rooms), }, } if resp.Protocols == nil { resp.Protocols = []string{} } if d, ok := statuses[svc.ID]; ok { resp.Delivery = &d } out = append(out, resp) } httputil.NoCache(w) httputil.WriteJSON(w, http.StatusOK, map[string]any{"appservices": out}) return nil } func namespacesOf(list []appservice.Namespace) []appServiceNamespace { out := make([]appServiceNamespace, 0, len(list)) for _, ns := range list { out = append(out, appServiceNamespace{Regex: ns.Regex, Exclusive: ns.Exclusive}) } return out }