Files
karma/views.go
Łukasz Mierzwa 129ffb7e17 fix(test): use httptest.newRequest instead of (incorrect) http.newRequest
httptest.newRequest is suppose to be used for testing request handing, http.newRequest is for preparing outgoing requests, this allows to fix cache key selection and increase test coverage
2018-08-21 09:35:49 +01:00

268 lines
7.2 KiB
Go

package main
import (
"encoding/base64"
"encoding/json"
"net/http"
"sort"
"strings"
"time"
"github.com/prymitive/unsee/internal/alertmanager"
"github.com/prymitive/unsee/internal/config"
"github.com/prymitive/unsee/internal/models"
"github.com/prymitive/unsee/internal/slices"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
func notFound(c *gin.Context) {
c.String(404, "404 page not found")
}
func noCache(c *gin.Context) {
c.Header("Cache-Control", "no-cache, no-store, must-revalidate")
}
func index(c *gin.Context) {
start := time.Now()
noCache(c)
filtersJSON, err := json.Marshal(config.Config.Filters.Default)
if err != nil {
panic(err)
}
filtersB64 := base64.StdEncoding.EncodeToString(filtersJSON)
c.HTML(http.StatusOK, "ui/build/index.html", gin.H{
"Version": version,
"SentryDSN": config.Config.Sentry.Public,
"DefaultFilter": filtersB64,
})
log.Infof("[%s] %s %s took %s", c.ClientIP(), c.Request.Method, c.Request.RequestURI, time.Since(start))
}
func logAlertsView(c *gin.Context, cacheStatus string, duration time.Duration) {
log.Infof("[%s %s] <%d> %s %s took %s", c.ClientIP(), cacheStatus, http.StatusOK, c.Request.Method, c.Request.RequestURI, duration)
}
// alerts endpoint, json, JS will query this via AJAX call
func alerts(c *gin.Context) {
noCache(c)
start := time.Now()
ts, _ := start.UTC().MarshalText()
// intialize response object, set fields that don't require any locking
resp := models.AlertsResponse{}
resp.Status = "success"
resp.Timestamp = string(ts)
resp.Version = version
resp.Upstreams = getUpstreams()
resp.Settings = models.Settings{
StaticColorLabels: config.Config.Labels.Color.Static,
AnnotationsDefaultHidden: config.Config.Annotations.Default.Hidden,
AnnotationsHidden: config.Config.Annotations.Hidden,
AnnotationsVisible: config.Config.Annotations.Visible,
}
// use full URI (including query args) as cache key
cacheKey := c.Request.RequestURI
data, found := apiCache.Get(cacheKey)
if found {
c.Data(http.StatusOK, gin.MIMEJSON, data.([]byte))
logAlertsView(c, "HIT", time.Since(start))
return
}
// get filters
apiFilters := []models.Filter{}
matchFilters, validFilters := getFiltersFromQuery(c.QueryArray("q"))
// set pointers for data store objects, need a lock until end of view is reached
alerts := map[string]models.APIAlertGroup{}
colors := models.LabelsColorMap{}
// used for top labels dropdown
counters := models.LabelsCountMap{}
dedupedAlerts := alertmanager.DedupAlerts()
dedupedColors := alertmanager.DedupColors()
silences := map[string]map[string]models.Silence{}
for _, am := range alertmanager.GetAlertmanagers() {
silences[am.Name] = map[string]models.Silence{}
}
var matches int
for _, ag := range dedupedAlerts {
agCopy := models.AlertGroup{
ID: ag.ID,
Receiver: ag.Receiver,
Labels: ag.Labels,
Alerts: []models.Alert{},
AlertmanagerCount: map[string]int{},
StateCount: map[string]int{},
}
for _, s := range models.AlertStateList {
agCopy.StateCount[s] = 0
}
for _, alert := range ag.Alerts {
results := []bool{}
if validFilters {
for _, filter := range matchFilters {
if filter.GetIsValid() {
match := filter.Match(&alert, matches)
results = append(results, match)
}
}
}
if !validFilters || (slices.BoolInSlice(results, true) && !slices.BoolInSlice(results, false)) {
matches++
// we need to update fingerprints since we've modified some fields in dedup
// and agCopy.ContentFingerprint() depends on per alert fingerprint
// we update it here rather than in dedup since here we can apply it
// only for alerts left after filtering
alert.UpdateFingerprints()
agCopy.Alerts = append(agCopy.Alerts, alert)
countLabel(counters, "@state", alert.State)
countLabel(counters, "@receiver", alert.Receiver)
if ck, foundKey := dedupedColors["@receiver"]; foundKey {
if cv, foundVal := ck[alert.Receiver]; foundVal {
if _, found := colors["@receiver"]; !found {
colors["@receiver"] = map[string]models.LabelColors{}
}
colors["@receiver"][alert.Receiver] = cv
}
}
agCopy.StateCount[alert.State]++
for _, am := range alert.Alertmanager {
if _, found := agCopy.AlertmanagerCount[am.Name]; !found {
agCopy.AlertmanagerCount[am.Name] = 1
} else {
agCopy.AlertmanagerCount[am.Name]++
}
}
for key, value := range alert.Labels {
if keyMap, foundKey := dedupedColors[key]; foundKey {
if color, foundColor := keyMap[value]; foundColor {
if _, found := colors[key]; !found {
colors[key] = map[string]models.LabelColors{}
}
colors[key][value] = color
}
}
countLabel(counters, key, value)
}
}
}
if len(agCopy.Alerts) > 0 {
for _, alert := range agCopy.Alerts {
if alert.IsSilenced() {
for _, am := range alert.Alertmanager {
for _, silence := range am.Silences {
silences[am.Name][silence.ID] = *silence
}
}
}
}
agCopy.Hash = agCopy.ContentFingerprint()
apiAG := models.APIAlertGroup{AlertGroup: agCopy}
apiAG.DedupSharedMaps()
alerts[agCopy.ID] = apiAG
resp.TotalAlerts += len(agCopy.Alerts)
}
}
resp.AlertGroups = alerts
resp.Silences = silences
resp.Colors = colors
resp.Counters = counters
for _, filter := range matchFilters {
af := models.Filter{
Text: filter.GetRawText(),
Name: filter.GetName(),
Matcher: filter.GetMatcher(),
Value: filter.GetValue(),
Hits: filter.GetHits(),
IsValid: filter.GetIsValid(),
}
if af.Text != "" {
apiFilters = append(apiFilters, af)
}
}
resp.Filters = apiFilters
data, err := json.Marshal(resp)
if err != nil {
log.Error(err.Error())
panic(err)
}
apiCache.Set(cacheKey, data, -1)
c.Data(http.StatusOK, gin.MIMEJSON, data.([]byte))
logAlertsView(c, "MIS", time.Since(start))
}
// autocomplete endpoint, json, used for filter autocomplete hints
func autocomplete(c *gin.Context) {
noCache(c)
start := time.Now()
cacheKey := c.Request.RequestURI
data, found := apiCache.Get(cacheKey)
if found {
c.Data(http.StatusOK, gin.MIMEJSON, data.([]byte))
logAlertsView(c, "HIT", time.Since(start))
return
}
term, found := c.GetQuery("term")
if !found || term == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "missing term=<token> parameter"})
log.Infof("[%s] <%d> %s %s took %s", c.ClientIP(), http.StatusBadRequest, c.Request.Method, c.Request.RequestURI, time.Since(start))
return
}
acData := sort.StringSlice{}
dedupedAutocomplete := alertmanager.DedupAutocomplete()
for _, hint := range dedupedAutocomplete {
if strings.HasPrefix(strings.ToLower(hint.Value), strings.ToLower(term)) {
acData = append(acData, hint.Value)
} else {
for _, token := range hint.Tokens {
if strings.HasPrefix(strings.ToLower(token), strings.ToLower(term)) {
acData = append(acData, hint.Value)
}
}
}
}
sort.Sort(sort.Reverse(acData))
data, err := json.Marshal(acData)
if err != nil {
log.Error(err.Error())
panic(err)
}
apiCache.Set(cacheKey, data, time.Second*15)
c.Data(http.StatusOK, gin.MIMEJSON, data.([]byte))
logAlertsView(c, "MIS", time.Since(start))
}