mirror of
https://github.com/prometheus/alertmanager
synced 2025-03-31 23:59:03 +00:00
Improve filter (#714)
* Update elm-tools/parser * Improve filter UI * Pressing backspace edits the last matcher * Put escape char back into the output * Allow editing the matcher * Update bindata.go * Const for key codes * Use qualified imports * Update bindata.go * Commented the backspacePressed attribute
This commit is contained in:
parent
c3850708c1
commit
88ec956973
File diff suppressed because one or more lines are too long
@ -9,10 +9,11 @@
|
||||
"exposed-modules": [],
|
||||
"dependencies": {
|
||||
"elm-lang/core": "5.0.0 <= v < 6.0.0",
|
||||
"elm-lang/dom": "1.1.1 <= v < 2.0.0",
|
||||
"elm-lang/html": "2.0.0 <= v < 3.0.0",
|
||||
"elm-lang/http": "1.0.0 <= v < 2.0.0",
|
||||
"elm-lang/navigation": "2.0.1 <= v < 3.0.0",
|
||||
"elm-tools/parser": "1.0.2 <= v < 2.0.0",
|
||||
"elm-tools/parser": "2.0.0 <= v < 3.0.0",
|
||||
"evancz/url-parser": "2.0.1 <= v < 3.0.0",
|
||||
"jweir/elm-iso8601": "3.0.2 <= v < 4.0.0"
|
||||
},
|
||||
|
@ -24,6 +24,7 @@ import Types
|
||||
import Utils.Types exposing (..)
|
||||
import Views.SilenceForm.Types exposing (initSilenceForm)
|
||||
import Views.Status.Types exposing (StatusModel, initStatusModel)
|
||||
import Views.AlertList.Types exposing (initAlertList)
|
||||
import Updates exposing (update)
|
||||
|
||||
|
||||
@ -55,7 +56,7 @@ init location =
|
||||
nullFilter
|
||||
|
||||
( model, msg ) =
|
||||
update (urlUpdate location) (Model Loading Loading initSilenceForm Loading route filter 0 initStatusModel)
|
||||
update (urlUpdate location) (Model Loading Loading initSilenceForm initAlertList route filter 0 initStatusModel)
|
||||
in
|
||||
model ! [ msg, Task.perform UpdateCurrentTime Time.now ]
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
module Types exposing (Model, Msg(..), Route(..))
|
||||
|
||||
import Alerts.Types exposing (AlertGroup, Alert)
|
||||
import Views.AlertList.Types exposing (AlertListMsg)
|
||||
import Views.AlertList.Types as AlertList exposing (AlertListMsg)
|
||||
import Views.SilenceList.Types exposing (SilenceListMsg)
|
||||
import Views.Silence.Types exposing (SilenceMsg)
|
||||
import Views.SilenceForm.Types as SilenceForm exposing (SilenceFormMsg)
|
||||
@ -15,7 +15,7 @@ type alias Model =
|
||||
{ silences : ApiData (List Silence)
|
||||
, silence : ApiData Silence
|
||||
, silenceForm : SilenceForm.Model
|
||||
, alertGroups : ApiData (List AlertGroup)
|
||||
, alertList : AlertList.Model
|
||||
, route : Route
|
||||
, filter : Filter
|
||||
, currentTime : Time.Time
|
||||
@ -40,8 +40,7 @@ type Msg
|
||||
| Noop
|
||||
| RedirectAlerts
|
||||
| UpdateCurrentTime Time.Time
|
||||
| UpdateFilter Filter String
|
||||
| AddLabel Msg Label
|
||||
| UpdateFilter String
|
||||
|
||||
|
||||
type Route
|
||||
|
@ -25,7 +25,6 @@ import Views.SilenceList.Updates
|
||||
import Views.Status.Types exposing (StatusMsg(InitStatusView))
|
||||
import Views.Status.Updates
|
||||
import String exposing (trim)
|
||||
import Regex
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
@ -43,10 +42,10 @@ update msg model =
|
||||
|
||||
NavigateToAlerts filter ->
|
||||
let
|
||||
( alertGroups, cmd ) =
|
||||
Views.AlertList.Updates.update FetchAlertGroups model.alertGroups filter
|
||||
( alertList, cmd ) =
|
||||
Views.AlertList.Updates.update FetchAlertGroups model.alertList filter
|
||||
in
|
||||
( { model | alertGroups = alertGroups, route = AlertsRoute filter, filter = filter }, cmd )
|
||||
( { model | alertList = alertList, route = AlertsRoute filter, filter = filter }, cmd )
|
||||
|
||||
NavigateToSilenceList filter ->
|
||||
let
|
||||
@ -87,44 +86,18 @@ update msg model =
|
||||
RedirectAlerts ->
|
||||
( model, Navigation.newUrl "/#/alerts" )
|
||||
|
||||
UpdateFilter filter text ->
|
||||
UpdateFilter text ->
|
||||
let
|
||||
t =
|
||||
if trim text == "" then
|
||||
Nothing
|
||||
else
|
||||
Just text
|
||||
in
|
||||
( { model | filter = { filter | text = t } }, Cmd.none )
|
||||
|
||||
AddLabel msg ( key, value ) ->
|
||||
let
|
||||
filter =
|
||||
prevFilter =
|
||||
model.filter
|
||||
|
||||
label =
|
||||
key ++ "=" ++ toString value
|
||||
|
||||
labels =
|
||||
Maybe.withDefault "" filter.text
|
||||
|> Regex.replace Regex.All (Regex.regex "{|}") (\_ -> "")
|
||||
|> Regex.split Regex.All (Regex.regex "\\s*,\\s*")
|
||||
|> List.filter (String.isEmpty >> not)
|
||||
|> (\labels ->
|
||||
if List.member label labels then
|
||||
labels
|
||||
else
|
||||
labels ++ [ label ]
|
||||
)
|
||||
|> String.join ", "
|
||||
|
||||
text =
|
||||
if String.isEmpty labels then
|
||||
Nothing
|
||||
else
|
||||
Just ("{" ++ labels ++ "}")
|
||||
in
|
||||
( { model | filter = { filter | text = text } }, Task.perform identity (Task.succeed msg) )
|
||||
( { model | filter = { prevFilter | text = t } }, Cmd.none )
|
||||
|
||||
Noop ->
|
||||
( model, Cmd.none )
|
||||
@ -137,10 +110,10 @@ update msg model =
|
||||
|
||||
MsgForAlertList msg ->
|
||||
let
|
||||
( alertGroups, cmd ) =
|
||||
Views.AlertList.Updates.update msg model.alertGroups model.filter
|
||||
( alertList, cmd ) =
|
||||
Views.AlertList.Updates.update msg model.alertList model.filter
|
||||
in
|
||||
( { model | alertGroups = alertGroups }, cmd )
|
||||
( { model | alertList = alertList }, cmd )
|
||||
|
||||
MsgForSilenceList msg ->
|
||||
let
|
||||
|
@ -15,7 +15,7 @@ parseDuration =
|
||||
durationParser : Parser Time.Time
|
||||
durationParser =
|
||||
Parser.succeed (List.foldr (+) 0)
|
||||
|= Parser.zeroOrMore term
|
||||
|= Parser.repeat Parser.zeroOrMore term
|
||||
|. Parser.end
|
||||
|
||||
|
||||
@ -42,7 +42,7 @@ term =
|
||||
|> List.map (\( unit, ms ) -> Parser.succeed ms |. Parser.symbol unit)
|
||||
|> Parser.oneOf
|
||||
)
|
||||
|. Parser.ignoreWhile ((==) ' ')
|
||||
|. Parser.ignore Parser.zeroOrMore ((==) ' ')
|
||||
|
||||
|
||||
durationFormat : Time.Time -> String
|
||||
|
@ -1,7 +1,21 @@
|
||||
module Utils.Filter exposing (..)
|
||||
module Utils.Filter
|
||||
exposing
|
||||
( Matcher
|
||||
, MatchOperator(..)
|
||||
, generateQueryParam
|
||||
, generateQueryString
|
||||
, stringifyMatcher
|
||||
, stringifyFilter
|
||||
, parseFilter
|
||||
, parseMatcher
|
||||
)
|
||||
|
||||
import Utils.Types exposing (Filter)
|
||||
import Http exposing (encodeUri)
|
||||
import Parser exposing (Parser, (|.), (|=), zeroOrMore, ignore)
|
||||
import Parser.LanguageKit as Parser exposing (Trailing(..))
|
||||
import Char
|
||||
import Set
|
||||
|
||||
|
||||
generateQueryParam : String -> Maybe String -> Maybe String
|
||||
@ -18,3 +32,136 @@ generateQueryString { receiver, showSilenced, text } =
|
||||
|> List.filterMap (uncurry generateQueryParam)
|
||||
|> String.join "&"
|
||||
|> (++) "?"
|
||||
|
||||
|
||||
type alias Matcher =
|
||||
{ key : String
|
||||
, op : MatchOperator
|
||||
, value : String
|
||||
}
|
||||
|
||||
|
||||
type MatchOperator
|
||||
= Eq
|
||||
| NotEq
|
||||
| RegexMatch
|
||||
| NotRegexMatch
|
||||
|
||||
|
||||
matchers : List ( String, MatchOperator )
|
||||
matchers =
|
||||
[ ( "=~", RegexMatch )
|
||||
, ( "!~", NotRegexMatch )
|
||||
, ( "=", Eq )
|
||||
, ( "!=", NotEq )
|
||||
]
|
||||
|
||||
|
||||
parseFilter : String -> Maybe (List Matcher)
|
||||
parseFilter =
|
||||
Parser.run filter
|
||||
>> Result.toMaybe
|
||||
|
||||
|
||||
parseMatcher : String -> Maybe Matcher
|
||||
parseMatcher =
|
||||
Parser.run matcher
|
||||
>> Result.toMaybe
|
||||
|
||||
|
||||
stringifyFilter : List Matcher -> String
|
||||
stringifyFilter matchers =
|
||||
case matchers of
|
||||
[] ->
|
||||
""
|
||||
|
||||
list ->
|
||||
(list
|
||||
|> List.map stringifyMatcher
|
||||
|> String.join ", "
|
||||
|> (++) "{"
|
||||
)
|
||||
++ "}"
|
||||
|
||||
|
||||
stringifyMatcher : Matcher -> String
|
||||
stringifyMatcher { key, op, value } =
|
||||
key
|
||||
++ (matchers
|
||||
|> List.filter (Tuple.second >> (==) op)
|
||||
|> List.head
|
||||
|> Maybe.map Tuple.first
|
||||
|> Maybe.withDefault ""
|
||||
)
|
||||
++ "\""
|
||||
++ value
|
||||
++ "\""
|
||||
|
||||
|
||||
filter : Parser (List Matcher)
|
||||
filter =
|
||||
Parser.succeed identity
|
||||
|= Parser.record spaces item
|
||||
|. Parser.end
|
||||
|
||||
|
||||
matcher : Parser Matcher
|
||||
matcher =
|
||||
Parser.succeed identity
|
||||
|. spaces
|
||||
|= item
|
||||
|. spaces
|
||||
|. Parser.end
|
||||
|
||||
|
||||
item : Parser Matcher
|
||||
item =
|
||||
Parser.succeed Matcher
|
||||
|= Parser.variable isVarChar isVarChar Set.empty
|
||||
|= (matchers
|
||||
|> List.map
|
||||
(\( keyword, matcher ) ->
|
||||
Parser.succeed matcher
|
||||
|. Parser.keyword keyword
|
||||
)
|
||||
|> Parser.oneOf
|
||||
)
|
||||
|= string '"'
|
||||
|
||||
|
||||
spaces : Parser ()
|
||||
spaces =
|
||||
ignore zeroOrMore (\char -> char == ' ' || char == '\t')
|
||||
|
||||
|
||||
string : Char -> Parser String
|
||||
string separator =
|
||||
Parser.succeed identity
|
||||
|. Parser.symbol (String.fromChar separator)
|
||||
|= stringContents separator
|
||||
|. Parser.symbol (String.fromChar separator)
|
||||
|
||||
|
||||
stringContents : Char -> Parser String
|
||||
stringContents separator =
|
||||
Parser.oneOf
|
||||
[ Parser.succeed (++)
|
||||
|= keepOne (\char -> char == '\\')
|
||||
|= keepOne (\char -> True)
|
||||
, Parser.keep Parser.oneOrMore (\char -> char /= separator && char /= '\\')
|
||||
]
|
||||
|> Parser.repeat Parser.oneOrMore
|
||||
|> Parser.map (String.join "")
|
||||
|
||||
|
||||
isVarChar : Char -> Bool
|
||||
isVarChar char =
|
||||
Char.isLower char
|
||||
|| Char.isUpper char
|
||||
|| (char == '_')
|
||||
|| Char.isDigit char
|
||||
|
||||
|
||||
keepOne : (Char -> Bool) -> Parser String
|
||||
keepOne =
|
||||
Parser.keep (Parser.Exactly 1)
|
||||
|
@ -3,7 +3,6 @@ module Views exposing (..)
|
||||
import Html exposing (Html, text, div)
|
||||
import Html.Attributes exposing (class)
|
||||
import Types exposing (Msg(MsgForSilenceForm), Model, Route(..))
|
||||
import Utils.Types exposing (ApiResponse(..))
|
||||
import Utils.Views exposing (error, loading)
|
||||
import Views.SilenceList.Views as SilenceList
|
||||
import Views.SilenceForm.Views as SilenceForm
|
||||
@ -33,15 +32,7 @@ currentView model =
|
||||
Silence.view model
|
||||
|
||||
AlertsRoute filter ->
|
||||
case model.alertGroups of
|
||||
Success alertGroups ->
|
||||
AlertList.view alertGroups model.filter (text "")
|
||||
|
||||
Loading ->
|
||||
loading
|
||||
|
||||
Failure msg ->
|
||||
AlertList.view [] model.filter (error msg)
|
||||
AlertList.view model.alertList filter
|
||||
|
||||
SilenceListRoute route ->
|
||||
SilenceList.view model.silences model.silence model.currentTime model.filter
|
||||
|
@ -1,20 +1,151 @@
|
||||
module Views.AlertList.FilterBar exposing (view)
|
||||
|
||||
import Html exposing (Html, div, span, input, text, button, i)
|
||||
import Html.Attributes exposing (value, class)
|
||||
import Html.Events exposing (onClick, onInput)
|
||||
import Html exposing (Html, Attribute, div, span, input, text, button, i, small)
|
||||
import Html.Attributes exposing (value, class, style, disabled, id)
|
||||
import Html.Events exposing (onClick, onInput, on, keyCode)
|
||||
import Utils.Filter exposing (Matcher)
|
||||
import Views.AlertList.Types exposing (AlertListMsg(AddFilterMatcher, DeleteFilterMatcher, PressingBackspace, UpdateMatcherText))
|
||||
import Types exposing (Msg(MsgForAlertList, Noop))
|
||||
import Json.Decode as Json
|
||||
|
||||
|
||||
view : String -> (String -> msg) -> msg -> Html msg
|
||||
view filterText inputChangedMsg buttonClickedMsg =
|
||||
div [ class "input-group" ]
|
||||
[ span [ class "input-group-addon" ]
|
||||
[ i [ class "fa fa-filter" ] []
|
||||
keys :
|
||||
{ backspace : Int
|
||||
, enter : Int
|
||||
}
|
||||
keys =
|
||||
{ backspace = 8
|
||||
, enter = 13
|
||||
}
|
||||
|
||||
|
||||
viewMatcher : Matcher -> Html Msg
|
||||
viewMatcher matcher =
|
||||
div [ class "col col-auto", style [ ( "padding", "5px" ) ] ]
|
||||
[ div [ class "btn-group" ]
|
||||
[ button
|
||||
[ class "btn btn-outline-info"
|
||||
, onClick (DeleteFilterMatcher True matcher |> MsgForAlertList)
|
||||
]
|
||||
[ text <| Utils.Filter.stringifyMatcher matcher
|
||||
]
|
||||
, button
|
||||
[ class "btn btn-outline-danger"
|
||||
, onClick (DeleteFilterMatcher False matcher |> MsgForAlertList)
|
||||
]
|
||||
[ text "×" ]
|
||||
]
|
||||
, input
|
||||
[ class "form-control", value filterText, onInput inputChangedMsg ]
|
||||
[]
|
||||
, span
|
||||
[ class "input-group-btn" ]
|
||||
[ button [ class "btn btn-primary", onClick buttonClickedMsg ] [ text "Filter" ] ]
|
||||
]
|
||||
|
||||
|
||||
lastElem : List a -> Maybe a
|
||||
lastElem =
|
||||
List.foldl (Just >> always) Nothing
|
||||
|
||||
|
||||
viewMatchers : List Matcher -> List (Html Msg)
|
||||
viewMatchers matchers =
|
||||
matchers
|
||||
|> List.map viewMatcher
|
||||
|
||||
|
||||
onKey : String -> Int -> Msg -> Attribute Msg
|
||||
onKey event key msg =
|
||||
on event
|
||||
(Json.map
|
||||
(\k ->
|
||||
if k == key then
|
||||
msg
|
||||
else
|
||||
Noop
|
||||
)
|
||||
keyCode
|
||||
)
|
||||
|
||||
|
||||
view : List Matcher -> String -> Bool -> Html Msg
|
||||
view matchers matcherText backspacePressed =
|
||||
let
|
||||
className =
|
||||
if matcherText == "" then
|
||||
""
|
||||
else
|
||||
case maybeMatcher of
|
||||
Just _ ->
|
||||
"has-success"
|
||||
|
||||
Nothing ->
|
||||
"has-danger"
|
||||
|
||||
maybeMatcher =
|
||||
Utils.Filter.parseMatcher matcherText
|
||||
|
||||
onKeydown =
|
||||
onKey "keydown" keys.backspace <|
|
||||
case ( matcherText, backspacePressed ) of
|
||||
( "", True ) ->
|
||||
Noop
|
||||
|
||||
( "", False ) ->
|
||||
lastElem matchers
|
||||
|> Maybe.map (DeleteFilterMatcher True >> MsgForAlertList)
|
||||
|> Maybe.withDefault Noop
|
||||
|
||||
_ ->
|
||||
PressingBackspace True |> MsgForAlertList
|
||||
|
||||
onKeypress =
|
||||
maybeMatcher
|
||||
|> Maybe.map (AddFilterMatcher True >> MsgForAlertList)
|
||||
|> Maybe.withDefault Noop
|
||||
|> onKey "keypress" keys.enter
|
||||
|
||||
onKeyup =
|
||||
onKey "keyup" keys.backspace (PressingBackspace False |> MsgForAlertList)
|
||||
|
||||
onClickAttr =
|
||||
maybeMatcher
|
||||
|> Maybe.map (AddFilterMatcher True >> MsgForAlertList)
|
||||
|> Maybe.withDefault Noop
|
||||
|> onClick
|
||||
|
||||
isDisabled =
|
||||
maybeMatcher == Nothing
|
||||
in
|
||||
div
|
||||
[ class "row no-gutters align-items-start" ]
|
||||
(viewMatchers matchers
|
||||
++ [ div
|
||||
[ class ("col form-group " ++ className)
|
||||
, style
|
||||
[ ( "padding", "5px" )
|
||||
, ( "min-width", "200px" )
|
||||
, ( "max-width", "500px" )
|
||||
]
|
||||
]
|
||||
[ div [ class "input-group" ]
|
||||
[ input
|
||||
[ id "custom-matcher"
|
||||
, class "form-control"
|
||||
, value matcherText
|
||||
, onKeydown
|
||||
, onKeyup
|
||||
, onKeypress
|
||||
, onInput (UpdateMatcherText >> MsgForAlertList)
|
||||
]
|
||||
[]
|
||||
, span
|
||||
[ class "input-group-btn" ]
|
||||
[ button [ class "btn btn-primary", disabled isDisabled, onClickAttr ] [ text "Add" ] ]
|
||||
]
|
||||
, small [ class "form-text text-muted" ]
|
||||
[ text "Custom matcher, e.g."
|
||||
, button
|
||||
[ class "btn btn-link btn-sm align-baseline"
|
||||
, onClick (UpdateMatcherText "env=\"production\"" |> MsgForAlertList)
|
||||
]
|
||||
[ text "env=\"production\"" ]
|
||||
]
|
||||
]
|
||||
]
|
||||
)
|
||||
|
@ -1,10 +1,40 @@
|
||||
module Views.AlertList.Types exposing (AlertListMsg(..))
|
||||
module Views.AlertList.Types exposing (AlertListMsg(..), Model, initAlertList)
|
||||
|
||||
import Utils.Types exposing (ApiData, Filter)
|
||||
import Utils.Types exposing (ApiData, Filter, ApiResponse(Loading))
|
||||
import Alerts.Types exposing (Alert, AlertGroup)
|
||||
import Utils.Filter
|
||||
|
||||
|
||||
type AlertListMsg
|
||||
= AlertGroupsFetch (ApiData (List AlertGroup))
|
||||
| FetchAlertGroups
|
||||
| FilterAlerts
|
||||
| AddFilterMatcher Bool Utils.Filter.Matcher
|
||||
| DeleteFilterMatcher Bool Utils.Filter.Matcher
|
||||
| PressingBackspace Bool
|
||||
| UpdateMatcherText String
|
||||
|
||||
|
||||
{-| A note about the `backspacePressed` attribute:
|
||||
|
||||
Holding down the backspace removes (one by one) each last character in the input,
|
||||
and the whole time sends multiple keyDown events. This is a guard so that if a user
|
||||
holds down backspace to remove the text in the input, they won't accidentally hold
|
||||
backspace too long and then delete the preceding matcher as well. So, once a user holds
|
||||
backspace to clear an input, they have to then lift up the key and press it again to
|
||||
proceed to deleting the next matcher.
|
||||
-}
|
||||
type alias Model =
|
||||
{ alertGroups : ApiData (List AlertGroup)
|
||||
, matchers : List Utils.Filter.Matcher
|
||||
, backspacePressed : Bool
|
||||
, matcherText : String
|
||||
}
|
||||
|
||||
|
||||
initAlertList : Model
|
||||
initAlertList =
|
||||
{ alertGroups = Loading
|
||||
, matchers = []
|
||||
, backspacePressed = False
|
||||
, matcherText = ""
|
||||
}
|
||||
|
@ -1,22 +1,74 @@
|
||||
module Views.AlertList.Updates exposing (..)
|
||||
|
||||
import Alerts.Api as Api
|
||||
import Views.AlertList.Types exposing (AlertListMsg(..))
|
||||
import Alerts.Types exposing (AlertGroup)
|
||||
import Views.AlertList.Types exposing (AlertListMsg(..), Model)
|
||||
import Navigation
|
||||
import Utils.Types exposing (ApiData, ApiResponse(..), Filter)
|
||||
import Utils.Filter exposing (generateQueryString)
|
||||
import Types exposing (Msg(MsgForAlertList))
|
||||
import Utils.Types exposing (ApiData, ApiResponse(Loading), Filter)
|
||||
import Utils.Filter exposing (generateQueryString, stringifyFilter, parseFilter)
|
||||
import Types exposing (Msg(MsgForAlertList, Noop))
|
||||
import Dom
|
||||
import Task
|
||||
|
||||
|
||||
update : AlertListMsg -> ApiData (List AlertGroup) -> Filter -> ( ApiData (List AlertGroup), Cmd Types.Msg )
|
||||
update msg groups filter =
|
||||
immediatelyFilter : Filter -> Model -> ( Model, Cmd Types.Msg )
|
||||
immediatelyFilter filter model =
|
||||
let
|
||||
newFilter =
|
||||
{ filter | text = Just (stringifyFilter model.matchers) }
|
||||
in
|
||||
( model
|
||||
, Cmd.batch
|
||||
[ Navigation.newUrl ("/#/alerts" ++ generateQueryString newFilter)
|
||||
, Dom.focus "custom-matcher" |> Task.attempt (always Noop)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
update : AlertListMsg -> Model -> Filter -> ( Model, Cmd Types.Msg )
|
||||
update msg model filter =
|
||||
case msg of
|
||||
AlertGroupsFetch alertGroups ->
|
||||
( alertGroups, Cmd.none )
|
||||
( { model | alertGroups = alertGroups }, Cmd.none )
|
||||
|
||||
FetchAlertGroups ->
|
||||
( groups, Api.alertGroups filter |> Cmd.map (AlertGroupsFetch >> MsgForAlertList) )
|
||||
( { model
|
||||
| matchers =
|
||||
filter.text
|
||||
|> Maybe.andThen parseFilter
|
||||
|> Maybe.withDefault []
|
||||
, alertGroups = Loading
|
||||
}
|
||||
, Api.alertGroups filter |> Cmd.map (AlertGroupsFetch >> MsgForAlertList)
|
||||
)
|
||||
|
||||
FilterAlerts ->
|
||||
( groups, Navigation.newUrl ("/#/alerts" ++ generateQueryString filter) )
|
||||
AddFilterMatcher emptyMatcherText matcher ->
|
||||
immediatelyFilter filter
|
||||
{ model
|
||||
| matchers =
|
||||
if List.member matcher model.matchers then
|
||||
model.matchers
|
||||
else
|
||||
model.matchers ++ [ matcher ]
|
||||
, matcherText =
|
||||
if emptyMatcherText then
|
||||
""
|
||||
else
|
||||
model.matcherText
|
||||
}
|
||||
|
||||
DeleteFilterMatcher setMatcherText matcher ->
|
||||
immediatelyFilter filter
|
||||
{ model
|
||||
| matchers = List.filter ((/=) matcher) model.matchers
|
||||
, matcherText =
|
||||
if setMatcherText then
|
||||
Utils.Filter.stringifyMatcher matcher
|
||||
else
|
||||
model.matcherText
|
||||
}
|
||||
|
||||
UpdateMatcherText value ->
|
||||
( { model | matcherText = value }, Cmd.none )
|
||||
|
||||
PressingBackspace isPressed ->
|
||||
( { model | backspacePressed = isPressed }, Cmd.none )
|
||||
|
@ -1,44 +1,51 @@
|
||||
module Views.AlertList.Views exposing (view)
|
||||
|
||||
import Alerts.Types exposing (Alert, AlertGroup, Block)
|
||||
import Views.AlertList.Types exposing (AlertListMsg(FilterAlerts))
|
||||
import Views.AlertList.Filter exposing (silenced, receiver, matchers)
|
||||
import Views.AlertList.FilterBar
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Utils.Date
|
||||
import Utils.Types exposing (Filter)
|
||||
import Utils.Views exposing (..)
|
||||
import Types exposing (Msg(MsgForAlertList, Noop, CreateSilenceFromAlert, UpdateFilter, AddLabel))
|
||||
import Utils.Types exposing (ApiResponse(Success, Loading, Failure), Filter)
|
||||
import Utils.Filter
|
||||
import Utils.Views exposing (buttonLink, onClickMsgButton, listButton)
|
||||
import Views.AlertList.Types exposing (AlertListMsg(AddFilterMatcher), Model)
|
||||
import Types exposing (Msg(Noop, CreateSilenceFromAlert, MsgForAlertList))
|
||||
|
||||
|
||||
view : List AlertGroup -> Filter -> Html Msg -> Html Msg
|
||||
view alertGroups filter errorHtml =
|
||||
view : Model -> Filter -> Html Msg
|
||||
view { alertGroups, matchers, matcherText, backspacePressed } filter =
|
||||
div []
|
||||
[ Views.AlertList.FilterBar.view matchers matcherText backspacePressed
|
||||
, case alertGroups of
|
||||
Success groups ->
|
||||
viewGroups groups filter
|
||||
|
||||
Loading ->
|
||||
Utils.Views.loading
|
||||
|
||||
Failure msg ->
|
||||
Utils.Views.error msg
|
||||
]
|
||||
|
||||
|
||||
viewGroups : List AlertGroup -> Filter -> Html Msg
|
||||
viewGroups alertGroups filter =
|
||||
let
|
||||
filteredGroups =
|
||||
receiver filter.receiver alertGroups
|
||||
|> silenced filter.showSilenced
|
||||
|
||||
filterText =
|
||||
Maybe.withDefault "" filter.text
|
||||
|
||||
alertHtml =
|
||||
if List.isEmpty filteredGroups then
|
||||
div [ class "mt2" ] [ text "no alerts found found" ]
|
||||
else
|
||||
ul
|
||||
[ classList
|
||||
[ ( "list", True )
|
||||
, ( "pa0", True )
|
||||
]
|
||||
]
|
||||
(List.map alertGroupView filteredGroups)
|
||||
in
|
||||
div []
|
||||
[ Views.AlertList.FilterBar.view filterText (Types.UpdateFilter filter) (MsgForAlertList FilterAlerts)
|
||||
, errorHtml
|
||||
, alertHtml
|
||||
]
|
||||
if List.isEmpty filteredGroups then
|
||||
div [ class "mt2" ] [ text "no alerts found" ]
|
||||
else
|
||||
ul
|
||||
[ classList
|
||||
[ ( "list", True )
|
||||
, ( "pa0", True )
|
||||
]
|
||||
]
|
||||
(List.map alertGroupView filteredGroups)
|
||||
|
||||
|
||||
alertGroupView : AlertGroup -> Html Msg
|
||||
@ -80,10 +87,16 @@ alertView alert =
|
||||
|
||||
|
||||
labelButton : ( String, String ) -> Html Msg
|
||||
labelButton (( key, value ) as label) =
|
||||
labelButton ( key, value ) =
|
||||
onClickMsgButton
|
||||
(key ++ "=" ++ value)
|
||||
(AddLabel Noop label)
|
||||
(AddFilterMatcher False
|
||||
{ key = key
|
||||
, op = Utils.Filter.Eq
|
||||
, value = value
|
||||
}
|
||||
|> MsgForAlertList
|
||||
)
|
||||
|
||||
|
||||
alertHeader : ( String, String ) -> Html msg
|
||||
|
@ -47,7 +47,7 @@ silences silences filter errorHtml =
|
||||
(List.map silenceList silences)
|
||||
in
|
||||
div []
|
||||
[ textField "Filter" filterText (UpdateFilter filter)
|
||||
[ textField "Filter" filterText UpdateFilter
|
||||
, a [ class "f6 link br2 ba ph3 pv2 mr2 dib blue", onClick (MsgForSilenceList FilterSilences) ] [ text "Filter Silences" ]
|
||||
, a [ class "f6 link br2 ba ph3 pv2 mr2 dib blue", href "#/silences/new" ] [ text "New Silence" ]
|
||||
, errorHtml
|
||||
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user