You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
334 lines
7.0 KiB
334 lines
7.0 KiB
package internal
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/go-chi/chi/v5/middleware"
|
|
"github.com/go-chi/cors"
|
|
)
|
|
|
|
type adminHandler struct {
|
|
Cache
|
|
Storage
|
|
*RuleEngine
|
|
h http.Handler
|
|
}
|
|
|
|
func NewAdminHandler(c Cache, s Storage, re *RuleEngine) http.Handler {
|
|
handler := chi.NewRouter()
|
|
|
|
a := &adminHandler{
|
|
Cache: c,
|
|
Storage: s,
|
|
RuleEngine: re,
|
|
h: handler,
|
|
}
|
|
|
|
handler.Use(middleware.RequestID)
|
|
handler.Use(middleware.RealIP)
|
|
handler.Use(middleware.Logger)
|
|
handler.Use(middleware.AllowContentType("application/json; utf-8", "application/json"))
|
|
handler.Use(middleware.Timeout(time.Second * 10))
|
|
|
|
handler.Use(cors.Handler(cors.Options{
|
|
AllowedOrigins: []string{"http://*", "https://*"},
|
|
AllowedMethods: []string{"GET", "PUT", "DELETE", "POST", "OPTIONS"},
|
|
AllowedHeaders: []string{"Content-Type", "Accept"},
|
|
AllowCredentials: false,
|
|
MaxAge: 300,
|
|
}))
|
|
|
|
handler.Use(middleware.Recoverer)
|
|
|
|
handler.Route("/api/v1", func(r chi.Router) {
|
|
r.Get("/metrics/log", RestHandler(a.getLog).ToHF())
|
|
r.Get("/metrics/stats", RestHandler(a.getStats).ToHF())
|
|
r.Get("/rules", RestHandler(a.getRules).ToHF())
|
|
r.Put("/rules", RestHandler(a.createRule).ToHF())
|
|
r.Delete("/rules/{id:[0-9]+}", RestHandler(a.deleteRule).ToHF())
|
|
// r.Put("/rules/lists", a.addRulelist)
|
|
// r.Get("/rules/lists", a.getRuleLists)
|
|
// r.Delete("/rules/lists/{id}", a.deleteRuleList)
|
|
// r.Post("/rules/lists/reload/{id}", a.reloadRuleLists)
|
|
|
|
// r.Delete("/cache/purgeall", RestHandler(a.purgeAll).ToHF())
|
|
// r.Delete("/cache/purge", a.purgeKey)
|
|
// r.Get("/cache", a.getCacheContents)
|
|
})
|
|
|
|
return a
|
|
|
|
}
|
|
|
|
func (a *adminHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
a.h.ServeHTTP(w, r)
|
|
}
|
|
|
|
func (a *adminHandler) deleteRule(r *http.Request) (*RestResponse, error) {
|
|
ruleIdParam := chi.URLParam(r, "id")
|
|
ruleId, err := strconv.Atoi(ruleIdParam)
|
|
if err != nil {
|
|
return &RestResponse{
|
|
Payload: struct {
|
|
Success bool "json:\"success\""
|
|
Payload interface{} "json:\"payload\""
|
|
}{
|
|
Success: false,
|
|
Payload: "Invalid rule ID",
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
if err := a.Storage.DeleteRule(ruleId); err != nil {
|
|
return &RestResponse{
|
|
Payload: struct {
|
|
Success bool "json:\"success\""
|
|
Payload interface{} "json:\"payload\""
|
|
}{
|
|
Success: false,
|
|
Payload: err.Error(),
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
return &RestResponse{
|
|
Payload: struct {
|
|
Success bool "json:\"success\""
|
|
Payload interface{} "json:\"payload\""
|
|
}{
|
|
Success: true,
|
|
Payload: ruleId,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func (a *adminHandler) createRule(r *http.Request) (*RestResponse, error) {
|
|
var rr RuleRow
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&rr); err != nil {
|
|
return &RestResponse{
|
|
Status: http.StatusUnprocessableEntity,
|
|
Payload: struct {
|
|
Success bool "json:\"success\""
|
|
Payload interface{} "json:\"payload\""
|
|
}{
|
|
Success: false,
|
|
Payload: err.Error(),
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
if err := a.Storage.AddRule(rr); err != nil {
|
|
return &RestResponse{
|
|
Payload: struct {
|
|
Success bool "json:\"success\""
|
|
Payload interface{} "json:\"payload\""
|
|
}{
|
|
Success: false,
|
|
Payload: err.Error(),
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
return &RestResponse{
|
|
Payload: struct {
|
|
Success bool "json:\"success\""
|
|
Payload interface{} "json:\"payload\""
|
|
}{
|
|
Success: true,
|
|
},
|
|
}, nil
|
|
}
|
|
func (a *adminHandler) getRules(r *http.Request) (*RestResponse, error) {
|
|
results, err := a.Storage.GetRules()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(results) <= 0 {
|
|
results = []RuleRow{}
|
|
}
|
|
|
|
return &RestResponse{
|
|
Payload: struct {
|
|
Success bool "json:\"success\""
|
|
Payload interface{} "json:\"payload\""
|
|
}{
|
|
Success: true,
|
|
Payload: results,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func (a *adminHandler) getStats(r *http.Request) (*RestResponse, error) {
|
|
q := r.URL.Query()
|
|
startFilter := q.Get("start")
|
|
endFilter := q.Get("end")
|
|
key := q.Get("key")
|
|
intervalSecondsStr := q.Get("interval")
|
|
|
|
var err error
|
|
start := time.Now().UTC().Add(time.Hour * -86400)
|
|
end := time.Now().UTC()
|
|
|
|
if startFilter != "" {
|
|
if start, err = time.Parse(ISO8601, startFilter); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if endFilter != "" {
|
|
if end, err = time.Parse(ISO8601, endFilter); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
lai := LogAggregateInput{
|
|
Start: start,
|
|
End: end,
|
|
Column: key,
|
|
}
|
|
|
|
if intervalSecondsStr != "" {
|
|
if lai.IntervalSeconds, err = strconv.Atoi(intervalSecondsStr); err != nil {
|
|
return nil, errors.New("interval query param must be a valid whole number")
|
|
}
|
|
}
|
|
|
|
la, err := a.Storage.GetLogAggregate(lai)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &RestResponse{
|
|
Status: http.StatusOK,
|
|
Payload: struct {
|
|
Success bool `json:"success"`
|
|
Payload interface{} `json:"payload"`
|
|
}{
|
|
Success: true,
|
|
Payload: la,
|
|
},
|
|
}, nil
|
|
|
|
}
|
|
|
|
func (a *adminHandler) getLog(r *http.Request) (*RestResponse, error) {
|
|
q := r.URL.Query()
|
|
startFilter := q.Get("start")
|
|
endFilter := q.Get("end")
|
|
// filter := q.Get("filter")
|
|
pageStr := q.Get("page")
|
|
|
|
var err error
|
|
var page int
|
|
start := time.Now().UTC().Add(time.Hour * -86400)
|
|
end := time.Now().UTC()
|
|
|
|
if startFilter != "" {
|
|
if start, err = time.Parse(ISO8601, startFilter); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if endFilter != "" {
|
|
if end, err = time.Parse(ISO8601, endFilter); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if pageStr != "" {
|
|
page, _ = strconv.Atoi(pageStr)
|
|
}
|
|
|
|
ql, err := a.Storage.GetLog(GetLogInput{
|
|
Start: start,
|
|
End: end,
|
|
Limit: 250,
|
|
Page: page,
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &RestResponse{
|
|
Status: http.StatusOK,
|
|
Payload: struct {
|
|
Success bool `json:"success"`
|
|
Payload interface{} `json:"payload"`
|
|
}{
|
|
Success: true,
|
|
Payload: ql,
|
|
},
|
|
}, nil
|
|
|
|
}
|
|
|
|
type RestResponse struct {
|
|
Status int
|
|
Headers http.Header
|
|
Payload struct {
|
|
Success bool `json:"success"`
|
|
Payload interface{} `json:"payload"`
|
|
}
|
|
}
|
|
|
|
func (rr *RestResponse) Write(w http.ResponseWriter) error {
|
|
if rr.Status != 0 && rr.Status != 200 {
|
|
w.WriteHeader(rr.Status)
|
|
}
|
|
|
|
for k, v := range rr.Headers {
|
|
for _, ve := range v {
|
|
w.Header().Add(k, ve)
|
|
}
|
|
}
|
|
|
|
e := json.NewEncoder(w)
|
|
e.SetIndent("\n", "\t")
|
|
|
|
if err := e.Encode(rr.Payload); err != nil {
|
|
return fmt.Errorf("could not serialize struct for http response: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type RestHandler func(request *http.Request) (*RestResponse, error)
|
|
|
|
func (rh RestHandler) ToHF() http.HandlerFunc {
|
|
return func(rw http.ResponseWriter, r *http.Request) { rh.ServeHTTP(rw, r) }
|
|
}
|
|
|
|
func (rh RestHandler) Error(e error) *RestResponse {
|
|
return &RestResponse{
|
|
Status: http.StatusInternalServerError,
|
|
Payload: struct {
|
|
Success bool `json:"success"`
|
|
Payload interface{} `json:"payload"`
|
|
}{
|
|
Success: false,
|
|
Payload: e.Error(),
|
|
},
|
|
}
|
|
}
|
|
|
|
func (r RestHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
response, err := r(req)
|
|
if err != nil {
|
|
response = r.Error(err)
|
|
}
|
|
|
|
if err := response.Write(w); err != nil {
|
|
log.Printf("Error occurred handling rest response: %v", err)
|
|
}
|
|
}
|