mirror of
https://github.com/prometheus/alertmanager
synced 2025-01-03 20:42:07 +00:00
Silence preview (#7)
* Wip * Add placeholder preview html * Show affected alerts on silence show page
This commit is contained in:
parent
080f850270
commit
d229abac97
@ -3,18 +3,29 @@ module Alerts.Api exposing (..)
|
||||
import Json.Decode as Json exposing (..)
|
||||
import Utils.Api exposing (baseUrl, iso8601Time)
|
||||
import Alerts.Types exposing (..)
|
||||
import Utils.Types exposing (Filter)
|
||||
import Utils.Types exposing (ApiData, Filter)
|
||||
import Utils.Filter exposing (generateQueryString)
|
||||
|
||||
|
||||
getAlertGroups : Filter -> Cmd Msg
|
||||
getAlertGroups filter =
|
||||
alertGroups filter
|
||||
|> Cmd.map (AlertGroupsFetch >> ForSelf)
|
||||
|
||||
|
||||
alertPreview : Filter -> Cmd Msg
|
||||
alertPreview filter =
|
||||
alertGroups filter
|
||||
|> Cmd.map (AlertGroupsPreview >> ForParent)
|
||||
|
||||
|
||||
alertGroups : Filter -> Cmd (ApiData (List AlertGroup))
|
||||
alertGroups filter =
|
||||
let
|
||||
url =
|
||||
String.join "/" [ baseUrl, "alerts", "groups" ++ (generateQueryString filter) ]
|
||||
in
|
||||
Utils.Api.send (Utils.Api.get url alertGroupsDecoder)
|
||||
|> Cmd.map (AlertGroupsFetch >> ForSelf)
|
||||
|
||||
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
module Alerts.Translator exposing (translator)
|
||||
|
||||
import Alerts.Types exposing (Msg(..), Alert, AlertsMsg, OutMsg(..))
|
||||
import Utils.Types exposing (Filter)
|
||||
import Alerts.Types exposing (Msg(..), AlertGroup, Alert, AlertsMsg, OutMsg(..))
|
||||
import Utils.Types exposing (ApiData, Filter)
|
||||
|
||||
|
||||
type alias TranslationDictionary msg =
|
||||
@ -9,11 +9,12 @@ type alias TranslationDictionary msg =
|
||||
, onSilenceFromAlert : Alert -> msg
|
||||
, onUpdateFilter : Filter -> String -> msg
|
||||
, onNewUrl : String -> msg
|
||||
, onAlertGroupsPreview : ApiData (List AlertGroup) -> msg
|
||||
}
|
||||
|
||||
|
||||
translator : TranslationDictionary parentMsg -> Msg -> parentMsg
|
||||
translator { onInternalMessage, onSilenceFromAlert, onUpdateFilter, onNewUrl } msg =
|
||||
translator { onInternalMessage, onSilenceFromAlert, onUpdateFilter, onNewUrl, onAlertGroupsPreview } msg =
|
||||
case msg of
|
||||
ForSelf internal ->
|
||||
onInternalMessage internal
|
||||
@ -26,3 +27,6 @@ translator { onInternalMessage, onSilenceFromAlert, onUpdateFilter, onNewUrl } m
|
||||
|
||||
ForParent (NewUrl string) ->
|
||||
onNewUrl string
|
||||
|
||||
ForParent (AlertGroupsPreview groups) ->
|
||||
onAlertGroupsPreview groups
|
||||
|
@ -16,6 +16,7 @@ type OutMsg
|
||||
= SilenceFromAlert Alert
|
||||
| UpdateFilter Filter String
|
||||
| NewUrl String
|
||||
| AlertGroupsPreview (ApiData (List AlertGroup))
|
||||
|
||||
|
||||
type AlertsMsg
|
||||
|
@ -1,4 +1,4 @@
|
||||
module Alerts.Views exposing (view)
|
||||
module Alerts.Views exposing (view, compact)
|
||||
|
||||
import Alerts.Types exposing (Alert, AlertGroup, Block, Route(..))
|
||||
import Alerts.Types exposing (AlertsMsg(..), Msg(..), OutMsg(..))
|
||||
@ -43,6 +43,30 @@ view route alertGroups filter errorHtml =
|
||||
]
|
||||
|
||||
|
||||
compact : AlertGroup -> Html msg
|
||||
compact ag =
|
||||
let
|
||||
alerts =
|
||||
List.concatMap
|
||||
(\b ->
|
||||
b.alerts
|
||||
)
|
||||
ag.blocks
|
||||
in
|
||||
ul
|
||||
[ class "list pa0"
|
||||
]
|
||||
(List.indexedMap alertCompact alerts)
|
||||
|
||||
|
||||
alertCompact : Int -> Alert -> Html msg
|
||||
alertCompact idx alert =
|
||||
li [ class "mb2 w-80-l w-100-m" ]
|
||||
[ span [] [ text <| toString (idx + 1) ++ ". " ]
|
||||
, div [] (List.map labelButton alert.labels)
|
||||
]
|
||||
|
||||
|
||||
alertGroupView : AlertGroup -> Html Msg
|
||||
alertGroupView alertGroup =
|
||||
li [ class "pa3 pa4-ns bb b--black-10" ]
|
||||
|
29
src/Main.elm
29
src/Main.elm
@ -6,8 +6,10 @@ import Time
|
||||
import Parsing
|
||||
import Views
|
||||
import Alerts.Update
|
||||
import Alerts.Api
|
||||
import Types exposing (..)
|
||||
import Utils.Types exposing (..)
|
||||
import Utils.List
|
||||
import Silences.Types exposing (Silence, nullTime, nullSilence)
|
||||
import Silences.Update
|
||||
import Translators exposing (alertTranslator, silenceTranslator)
|
||||
@ -41,7 +43,7 @@ init location =
|
||||
Silences.Update.updateFilter silencesRoute
|
||||
|
||||
_ ->
|
||||
{ text = Nothing, receiver = Nothing, showSilenced = Nothing }
|
||||
nullFilter
|
||||
|
||||
( model, msg ) =
|
||||
update (urlUpdate location) (Model Loading Loading Loading route filter 0 (StatusModel Nothing))
|
||||
@ -59,6 +61,31 @@ update msg model =
|
||||
in
|
||||
( { model | silence = Success silence }, Cmd.none )
|
||||
|
||||
PreviewSilence silence ->
|
||||
let
|
||||
s =
|
||||
{ silence | silencedAlertGroups = Loading }
|
||||
|
||||
filter =
|
||||
{ nullFilter | text = Just <| Utils.List.mjoin s.matchers }
|
||||
in
|
||||
( { model | silence = Success silence }, Cmd.map alertTranslator (Alerts.Api.alertPreview filter) )
|
||||
|
||||
AlertGroupsPreview alertGroups ->
|
||||
let
|
||||
silence =
|
||||
case model.silence of
|
||||
Success sil ->
|
||||
Success { sil | silencedAlertGroups = alertGroups }
|
||||
|
||||
Failure e ->
|
||||
Failure e
|
||||
|
||||
Loading ->
|
||||
Loading
|
||||
in
|
||||
( { model | silence = silence }, Cmd.none )
|
||||
|
||||
NavigateToAlerts alertsRoute ->
|
||||
let
|
||||
( alertsMsg, filter ) =
|
||||
|
@ -1,9 +1,9 @@
|
||||
module Silences.Decoders exposing (..)
|
||||
|
||||
import Json.Decode as Json exposing (field)
|
||||
import Utils.Api exposing (iso8601Time, duration)
|
||||
import Utils.Api exposing (iso8601Time, duration, (|:))
|
||||
import Silences.Types exposing (Silence)
|
||||
import Utils.Types exposing (Matcher, Time)
|
||||
import Utils.Types exposing (Matcher, Time, ApiResponse(Success))
|
||||
|
||||
|
||||
show : Json.Decoder Silence
|
||||
@ -32,19 +32,21 @@ destroy =
|
||||
|
||||
silence : Json.Decoder Silence
|
||||
silence =
|
||||
Json.map8 Silence
|
||||
(field "id" Json.string)
|
||||
(field "createdBy" Json.string)
|
||||
Json.succeed Silence
|
||||
|: (field "id" Json.string)
|
||||
|: (field "createdBy" Json.string)
|
||||
-- Remove this maybe once the api either disallows empty comments on
|
||||
-- creation, or returns an empty string.
|
||||
((Json.maybe (field "comment" Json.string))
|
||||
|> Json.andThen (\x -> Json.succeed <| Maybe.withDefault "" x)
|
||||
)
|
||||
(field "startsAt" iso8601Time)
|
||||
(field "endsAt" iso8601Time)
|
||||
(duration "startsAt" "endsAt")
|
||||
(field "updatedAt" iso8601Time)
|
||||
(field "matchers" (Json.list matcher))
|
||||
|:
|
||||
((Json.maybe (field "comment" Json.string))
|
||||
|> Json.andThen (\x -> Json.succeed <| Maybe.withDefault "" x)
|
||||
)
|
||||
|: (field "startsAt" iso8601Time)
|
||||
|: (field "endsAt" iso8601Time)
|
||||
|: (duration "startsAt" "endsAt")
|
||||
|: (field "updatedAt" iso8601Time)
|
||||
|: (field "matchers" (Json.list matcher))
|
||||
|: (Json.succeed <| Success [])
|
||||
|
||||
|
||||
matcher : Json.Decoder Matcher
|
||||
|
@ -1,6 +1,6 @@
|
||||
module Silences.Translator exposing (translator)
|
||||
|
||||
import Silences.Types exposing (Msg(..), SilencesMsg, OutMsg(NewUrl, UpdateFilter, UpdateCurrentTime))
|
||||
import Silences.Types exposing (Silence, Msg(..), SilencesMsg, OutMsg(..))
|
||||
import Utils.Types exposing (Filter)
|
||||
import Time
|
||||
|
||||
@ -10,11 +10,12 @@ type alias TranslationDictionary msg =
|
||||
, onNewUrl : String -> msg
|
||||
, onUpdateFilter : Filter -> String -> msg
|
||||
, onUpdateCurrentTime : Time.Time -> msg
|
||||
, onPreviewSilence : Silence -> msg
|
||||
}
|
||||
|
||||
|
||||
translator : TranslationDictionary parentMsg -> Msg -> parentMsg
|
||||
translator { onInternalMessage, onNewUrl, onUpdateFilter, onUpdateCurrentTime } msg =
|
||||
translator { onInternalMessage, onNewUrl, onUpdateFilter, onUpdateCurrentTime, onPreviewSilence } msg =
|
||||
case msg of
|
||||
ForSelf internal ->
|
||||
onInternalMessage internal
|
||||
@ -27,3 +28,6 @@ translator { onInternalMessage, onNewUrl, onUpdateFilter, onUpdateCurrentTime }
|
||||
|
||||
ForParent (UpdateCurrentTime time) ->
|
||||
onUpdateCurrentTime time
|
||||
|
||||
ForParent (PreviewSilence silence) ->
|
||||
onPreviewSilence silence
|
||||
|
@ -1,8 +1,9 @@
|
||||
module Silences.Types exposing (..)
|
||||
|
||||
import Utils.Types exposing (Time, Duration, Matcher, Filter, ApiData)
|
||||
import Utils.Types exposing (Time, Duration, Matcher, Filter, ApiData, ApiResponse(..))
|
||||
import Utils.Date
|
||||
import Time
|
||||
import Alerts.Types exposing (AlertGroup)
|
||||
|
||||
|
||||
type alias Silence =
|
||||
@ -14,6 +15,7 @@ type alias Silence =
|
||||
, duration : Duration
|
||||
, updatedAt : Time
|
||||
, matchers : List Matcher
|
||||
, silencedAlertGroups : ApiData (List AlertGroup)
|
||||
}
|
||||
|
||||
|
||||
@ -33,6 +35,7 @@ type OutMsg
|
||||
= NewUrl String
|
||||
| UpdateFilter Filter String
|
||||
| UpdateCurrentTime Time.Time
|
||||
| PreviewSilence Silence
|
||||
|
||||
|
||||
type SilencesMsg
|
||||
@ -62,7 +65,16 @@ type SilencesMsg
|
||||
|
||||
nullSilence : Silence
|
||||
nullSilence =
|
||||
Silence "" "" "" nullTime nullTime nullDuration nullTime [ nullMatcher ]
|
||||
{ id = ""
|
||||
, createdBy = ""
|
||||
, comment = ""
|
||||
, startsAt = nullTime
|
||||
, endsAt = nullTime
|
||||
, duration = nullDuration
|
||||
, updatedAt = nullTime
|
||||
, matchers = [ nullMatcher ]
|
||||
, silencedAlertGroups = Success []
|
||||
}
|
||||
|
||||
|
||||
nullMatcher : Matcher
|
||||
|
@ -18,7 +18,16 @@ update msg silences silence filter =
|
||||
( sils, silence, Cmd.map ForParent (Task.perform UpdateCurrentTime Time.now) )
|
||||
|
||||
SilenceFetch sil ->
|
||||
( silences, sil, Cmd.none )
|
||||
let
|
||||
cmd =
|
||||
case sil of
|
||||
Success sil ->
|
||||
Task.perform ForParent <| Task.succeed (PreviewSilence sil)
|
||||
|
||||
_ ->
|
||||
Cmd.none
|
||||
in
|
||||
( silences, sil, cmd )
|
||||
|
||||
FetchSilences ->
|
||||
( silences, silence, Api.getSilences filter )
|
||||
|
@ -5,11 +5,13 @@ module Silences.Views exposing (..)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (onClick, onInput)
|
||||
import Silences.Types exposing (Silence, SilencesMsg(..), Msg(..), OutMsg(UpdateFilter), Route(..))
|
||||
import Silences.Types exposing (Silence, SilencesMsg(..), Msg(..), OutMsg(UpdateFilter, PreviewSilence), Route(..))
|
||||
import Utils.Types exposing (Matcher, ApiResponse(..), Filter, ApiData)
|
||||
import Utils.Views exposing (iconButtonMsg, checkbox, textField, formInput, formField, buttonLink, error, loading)
|
||||
import Utils.Date
|
||||
import Utils.List
|
||||
import Time
|
||||
import Alerts.Views
|
||||
|
||||
|
||||
view : Route -> ApiData (List Silence) -> ApiData Silence -> Time.Time -> Filter -> Html Msg
|
||||
@ -101,6 +103,8 @@ silence silence currentTime =
|
||||
div []
|
||||
[ silenceBase silence
|
||||
, silenceExtra silence currentTime
|
||||
, h2 [ class "h6 dark-red" ] [ text "Affected alerts" ]
|
||||
, preview silence
|
||||
]
|
||||
|
||||
|
||||
@ -158,6 +162,20 @@ silenceExtra silence currentTime =
|
||||
]
|
||||
|
||||
|
||||
preview : Silence -> Html msg
|
||||
preview s =
|
||||
case s.silencedAlertGroups of
|
||||
Success alertGroups ->
|
||||
div []
|
||||
(List.map Alerts.Views.compact alertGroups)
|
||||
|
||||
Loading ->
|
||||
loading
|
||||
|
||||
Failure e ->
|
||||
error e
|
||||
|
||||
|
||||
status : Silence -> Time.Time -> String
|
||||
status silence currentTime =
|
||||
let
|
||||
@ -196,32 +214,38 @@ silenceForm kind silence =
|
||||
_ ->
|
||||
"/#/silences"
|
||||
in
|
||||
div [ class "pa4 black-80" ]
|
||||
[ fieldset [ class "ba b--transparent ph0 mh0" ]
|
||||
[ legend [ class "ph0 mh0 fw6" ] [ text <| kind ++ " Silence" ]
|
||||
, (formField "Start" silence.startsAt.s (UpdateStartsAt silence))
|
||||
, div [ class "dib mb2 mr2 w-40" ] [ formField "End" silence.endsAt.s (UpdateEndsAt silence) ]
|
||||
, div [ class "dib mb2 mr2 w-40" ] [ formField "Duration" silence.duration.s (UpdateDuration silence) ]
|
||||
, div [ class "mt3" ]
|
||||
[ label [ class "f6 b db mb2" ]
|
||||
[ text "Matchers "
|
||||
, span [ class "normal black-60" ] [ text "Alerts affected by this silence. Format: name=value" ]
|
||||
div []
|
||||
[ div [ class "pa4 black-80" ]
|
||||
[ fieldset [ class "ba b--transparent ph0 mh0" ]
|
||||
[ legend [ class "ph0 mh0 fw6" ] [ text <| kind ++ " Silence" ]
|
||||
, (formField "Start" silence.startsAt.s (UpdateStartsAt silence))
|
||||
, div [ class "dib mb2 mr2 w-40" ] [ formField "End" silence.endsAt.s (UpdateEndsAt silence) ]
|
||||
, div [ class "dib mb2 mr2 w-40" ] [ formField "Duration" silence.duration.s (UpdateDuration silence) ]
|
||||
, div [ class "mt3" ]
|
||||
[ label [ class "f6 b db mb2" ]
|
||||
[ text "Matchers "
|
||||
, span [ class "normal black-60" ] [ text "Alerts affected by this silence. Format: name=value" ]
|
||||
]
|
||||
, label [ class "f6 dib mb2 mr2 w-40" ] [ text "Name" ]
|
||||
, label [ class "f6 dib mb2 mr2 w-40" ] [ text "Value" ]
|
||||
]
|
||||
, div [] (List.map boundMatcherForm silence.matchers)
|
||||
, iconButtonMsg "blue" "fa-plus" (AddMatcher silence)
|
||||
, formField "Creator" silence.createdBy (UpdateCreatedBy silence)
|
||||
, textField "Comment" silence.comment (UpdateComment silence)
|
||||
, div [ class "mt3" ]
|
||||
[ a [ class "f6 link br2 ba ph3 pv2 mr2 dib blue", onClick (CreateSilence silence) ] [ text "Create" ]
|
||||
-- Reset isn't working for "New" -- just updates time.
|
||||
, a [ class "f6 link br2 ba ph3 pv2 mr2 dib dark-red", href url ] [ text "Reset" ]
|
||||
]
|
||||
, label [ class "f6 dib mb2 mr2 w-40" ] [ text "Name" ]
|
||||
, label [ class "f6 dib mb2 mr2 w-40" ] [ text "Value" ]
|
||||
]
|
||||
, div [] (List.map boundMatcherForm silence.matchers)
|
||||
, iconButtonMsg "blue" "fa-plus" (AddMatcher silence)
|
||||
, formField "Creator" silence.createdBy (UpdateCreatedBy silence)
|
||||
, textField "Comment" silence.comment (UpdateComment silence)
|
||||
|> (Html.map ForSelf)
|
||||
, div [ class "mt3" ]
|
||||
[ a [ class "f6 link br2 ba ph3 pv2 mr2 dib blue", onClick (CreateSilence silence) ] [ text "Create" ]
|
||||
-- Reset isn't working for "New" -- just updates time.
|
||||
, a [ class "f6 link br2 ba ph3 pv2 mb2 dib dark-red", href url ] [ text "Reset" ]
|
||||
[ a [ class "f6 link br2 ba ph3 pv2 mr2 dib dark-green", onClick (ForParent (PreviewSilence silence)) ] [ text "Show Affected Alerts" ]
|
||||
]
|
||||
, preview silence
|
||||
]
|
||||
]
|
||||
|> (Html.map ForSelf)
|
||||
|
||||
|
||||
matcherForm : Silence -> Matcher -> Html SilencesMsg
|
||||
@ -236,11 +260,4 @@ matcherForm silence matcher =
|
||||
|
||||
matcherButton : Matcher -> Html msg
|
||||
matcherButton matcher =
|
||||
let
|
||||
join =
|
||||
if matcher.isRegex then
|
||||
"=~"
|
||||
else
|
||||
"="
|
||||
in
|
||||
Utils.Views.button "light-silver hover-black ph3 pv2" <| String.join join [ matcher.name, matcher.value ]
|
||||
Utils.Views.button "light-silver hover-black ph3 pv2" <| Utils.List.mstring matcher
|
||||
|
@ -4,7 +4,7 @@ import Alerts.Types
|
||||
import Alerts.Translator
|
||||
import Silences.Types
|
||||
import Silences.Translator
|
||||
import Types exposing (Msg(Alerts, CreateSilenceFromAlert, Silences, UpdateFilter, NewUrl, UpdateCurrentTime))
|
||||
import Types exposing (Msg(Alerts, CreateSilenceFromAlert, Silences, UpdateFilter, NewUrl, UpdateCurrentTime, PreviewSilence, AlertGroupsPreview))
|
||||
|
||||
|
||||
alertTranslator : Alerts.Types.Msg -> Msg
|
||||
@ -14,6 +14,7 @@ alertTranslator =
|
||||
, onSilenceFromAlert = CreateSilenceFromAlert
|
||||
, onUpdateFilter = UpdateFilter
|
||||
, onNewUrl = NewUrl
|
||||
, onAlertGroupsPreview = AlertGroupsPreview
|
||||
}
|
||||
|
||||
|
||||
@ -24,4 +25,5 @@ silenceTranslator =
|
||||
, onNewUrl = NewUrl
|
||||
, onUpdateFilter = UpdateFilter
|
||||
, onUpdateCurrentTime = UpdateCurrentTime
|
||||
, onPreviewSilence = PreviewSilence
|
||||
}
|
||||
|
@ -20,6 +20,8 @@ type alias Model =
|
||||
|
||||
type Msg
|
||||
= CreateSilenceFromAlert Alert
|
||||
| PreviewSilence Silence
|
||||
| AlertGroupsPreview (ApiData (List AlertGroup))
|
||||
| UpdateFilter Filter String
|
||||
| NavigateToAlerts Alerts.Types.Route
|
||||
| NavigateToSilences Silences.Types.Route
|
||||
|
@ -72,7 +72,7 @@ iso8601Time =
|
||||
|
||||
baseUrl : String
|
||||
baseUrl =
|
||||
"http://alertmanager-experimental.int.s-cloud.net/api/v1"
|
||||
"http://alertmanager.int.s-cloud.net/api/v1"
|
||||
|
||||
|
||||
defaultTimeout : Time.Time
|
||||
@ -80,5 +80,11 @@ defaultTimeout =
|
||||
1000 * Time.millisecond
|
||||
|
||||
|
||||
(|:) : Json.Decoder (a -> b) -> Json.Decoder a -> Json.Decoder b
|
||||
(|:) =
|
||||
-- Taken from elm-community/json-extra
|
||||
flip (Json.map2 (|>))
|
||||
|
||||
|
||||
|
||||
-- "http://localhost:9093/api/v1"
|
||||
|
@ -1,5 +1,7 @@
|
||||
module Utils.List exposing (..)
|
||||
|
||||
import Utils.Types exposing (Matchers, Matcher)
|
||||
|
||||
|
||||
replaceIf : (a -> Bool) -> a -> List a -> List a
|
||||
replaceIf predicate replacement list =
|
||||
@ -11,3 +13,20 @@ replaceIf predicate replacement list =
|
||||
item
|
||||
)
|
||||
list
|
||||
|
||||
|
||||
mjoin : Matchers -> String
|
||||
mjoin m =
|
||||
String.join "," (List.map mstring m)
|
||||
|
||||
|
||||
mstring : Matcher -> String
|
||||
mstring m =
|
||||
let
|
||||
sep =
|
||||
if m.isRegex then
|
||||
"=~"
|
||||
else
|
||||
"="
|
||||
in
|
||||
String.join sep [ m.name, toString m.value ]
|
||||
|
@ -17,6 +17,14 @@ type alias Filter =
|
||||
}
|
||||
|
||||
|
||||
nullFilter : Filter
|
||||
nullFilter =
|
||||
{ text = Nothing
|
||||
, receiver = Nothing
|
||||
, showSilenced = Nothing
|
||||
}
|
||||
|
||||
|
||||
type alias Matcher =
|
||||
{ name : String
|
||||
, value : String
|
||||
|
Loading…
Reference in New Issue
Block a user