Add support for more generic notification

This is a start to have different kinds of notifications. It is
possible to be notified via e-mail, matrix or gotify. It also extends
the current "periodic query" for due items by allowing notification
over different channels. A "generic periodic query" variant is added
as well.
This commit is contained in:
eikek
2021-11-22 00:22:51 +01:00
parent 93a828720c
commit 4ffc8d1f14
175 changed files with 13041 additions and 599 deletions

View File

@ -0,0 +1,33 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Data.ChannelRef exposing (..)
import Data.ChannelType exposing (ChannelType)
import Json.Decode as D
import Json.Encode as E
type alias ChannelRef =
{ id : String
, channelType : ChannelType
}
decoder : D.Decoder ChannelRef
decoder =
D.map2 ChannelRef
(D.field "id" D.string)
(D.field "channelType" Data.ChannelType.decoder)
encode : ChannelRef -> E.Value
encode cref =
E.object
[ ( "id", E.string cref.id )
, ( "channelType", Data.ChannelType.encode cref.channelType )
]

View File

@ -0,0 +1,117 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Data.ChannelType exposing
( ChannelType(..)
, all
, asString
, decoder
, encode
, fromString
, icon
)
import Data.Icons as Icons
import Html exposing (Html, i)
import Html.Attributes exposing (class)
import Json.Decode as D
import Json.Encode as E
type ChannelType
= Mail
| Gotify
| Matrix
| Http
all : List ChannelType
all =
[ Matrix
, Gotify
, Mail
, Http
]
fromString : String -> Maybe ChannelType
fromString str =
case String.toLower str of
"mail" ->
Just Mail
"matrix" ->
Just Matrix
"gotify" ->
Just Gotify
"http" ->
Just Http
_ ->
Nothing
asString : ChannelType -> String
asString et =
case et of
Mail ->
"Mail"
Matrix ->
"Matrix"
Gotify ->
"Gotify"
Http ->
"Http"
decoder : D.Decoder ChannelType
decoder =
let
unwrap me =
case me of
Just et ->
D.succeed et
Nothing ->
D.fail "Unknown event type!"
in
D.map fromString D.string
|> D.andThen unwrap
encode : ChannelType -> E.Value
encode et =
E.string (asString et)
icon : ChannelType -> String -> Html msg
icon ct classes =
case ct of
Matrix ->
Icons.matrixIcon classes
Mail ->
i
[ class "fa fa-envelope"
, class classes
]
[]
Gotify ->
Icons.gotifyIcon classes
Http ->
i
[ class "fa fa-ethernet"
, class classes
]
[]

View File

@ -0,0 +1,90 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Data.EventType exposing (..)
import Json.Decode as D
import Json.Encode as E
type EventType
= TagsChanged
| SetFieldValue
| DeleteFieldValue
| JobSubmitted
| JobDone
all : List EventType
all =
[ TagsChanged
, SetFieldValue
, DeleteFieldValue
, JobSubmitted
, JobDone
]
fromString : String -> Maybe EventType
fromString str =
case String.toLower str of
"tagschanged" ->
Just TagsChanged
"setfieldvalue" ->
Just SetFieldValue
"deletefieldvalue" ->
Just DeleteFieldValue
"jobsubmitted" ->
Just JobSubmitted
"jobdone" ->
Just JobDone
_ ->
Nothing
asString : EventType -> String
asString et =
case et of
TagsChanged ->
"TagsChanged"
SetFieldValue ->
"SetFieldValue"
DeleteFieldValue ->
"DeleteFieldValue"
JobSubmitted ->
"JobSubmitted"
JobDone ->
"JobDone"
decoder : D.Decoder EventType
decoder =
let
unwrap me =
case me of
Just et ->
D.succeed et
Nothing ->
D.fail "Unknown event type!"
in
D.map fromString D.string
|> D.andThen unwrap
encode : EventType -> E.Value
encode et =
E.string (asString et)

View File

@ -47,7 +47,9 @@ module Data.Icons exposing
, folder2
, folderIcon
, folderIcon2
, gotifyIcon
, itemDatesIcon
, matrixIcon
, organization
, organization2
, organizationIcon
@ -73,8 +75,10 @@ module Data.Icons exposing
)
import Data.CustomFieldType exposing (CustomFieldType)
import Html exposing (Html, i)
import Html.Attributes exposing (class)
import Html exposing (Html, i, img)
import Html.Attributes exposing (class, src)
import Svg
import Svg.Attributes as SA
share : String
@ -447,3 +451,32 @@ equipmentIcon classes =
equipmentIcon2 : String -> Html msg
equipmentIcon2 classes =
i [ class (equipment2 ++ " " ++ classes) ] []
matrixIcon : String -> Html msg
matrixIcon classes =
Svg.svg
[ SA.width "520"
, SA.height "520"
, SA.viewBox "0 0 520 520"
, SA.class classes
]
[ Svg.path
[ SA.d "M13.7,11.9v496.2h35.7V520H0V0h49.4v11.9H13.7z" ]
[]
, Svg.path
[ SA.d "M166.3,169.2v25.1h0.7c6.7-9.6,14.8-17,24.2-22.2c9.4-5.3,20.3-7.9,32.5-7.9c11.7,0,22.4,2.3,32.1,6.8\n\tc9.7,4.5,17,12.6,22.1,24c5.5-8.1,13-15.3,22.4-21.5c9.4-6.2,20.6-9.3,33.5-9.3c9.8,0,18.9,1.2,27.3,3.6c8.4,2.4,15.5,6.2,21.5,11.5\n\tc6,5.3,10.6,12.1,14,20.6c3.3,8.5,5,18.7,5,30.7v124.1h-50.9V249.6c0-6.2-0.2-12.1-0.7-17.6c-0.5-5.5-1.8-10.3-3.9-14.3\n\tc-2.2-4.1-5.3-7.3-9.5-9.7c-4.2-2.4-9.9-3.6-17-3.6c-7.2,0-13,1.4-17.4,4.1c-4.4,2.8-7.9,6.3-10.4,10.8c-2.5,4.4-4.2,9.4-5,15.1\n\tc-0.8,5.6-1.3,11.3-1.3,17v103.3h-50.9v-104c0-5.5-0.1-10.9-0.4-16.3c-0.2-5.4-1.3-10.3-3.1-14.9c-1.8-4.5-4.8-8.2-9-10.9\n\tc-4.2-2.7-10.3-4.1-18.5-4.1c-2.4,0-5.6,0.5-9.5,1.6c-3.9,1.1-7.8,3.1-11.5,6.1c-3.7,3-6.9,7.3-9.5,12.9c-2.6,5.6-3.9,13-3.9,22.1\n\tv107.6h-50.9V169.2H166.3z" ]
[]
, Svg.path
[ SA.d "M506.3,508.1V11.9h-35.7V0H520v520h-49.4v-11.9H506.3z" ]
[]
]
gotifyIcon : String -> Html msg
gotifyIcon classes =
img
[ class classes
, src "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC80lEQVQ4y32SW0iTYRjHh7WL6K4gCKILbwQzJeomQcKLKEtJvAo0M8vDPG+WFlagaKgdzYgiUYRSUuzCwMN0NtOJ29xJN+fUj3nKedhe9+mOmvv3ft+FZlkPPHwvfO//x/P8379AQOtUXOL70/HJjcFRl4tDr14vC7mY8PDEmchbh48eO0t/HxT8ry6IK9pEvQwKtRsoGvOj2LjJ9z29G9nyOcS/aB49HnYu+Z+AYtOWlxOL1WsQqwj/LRhZR46aRb7WhfsTARSOsAhPSKndF0BFDl7INb2Y8d2Gwi4jnkh1KO3WI0s6BbHOTaEsYlJzW6Iiz6fsD6AXRDIrWgd1WHPYgZ+b2PZ5oDNPIr/TiDydBw8G5+Ega4iLjb37FyBTsYJ6uQZAAH+WTGNEunwBaQPL+GZZgLxX6qfSkF3AiBOiHgYW69yOKBAIQKlUYnFxERssCwldK0vrQc3ABOaYSa9QKDy5A+D2y5Va8GNpeQfg8/kQHR2N+vp6StvGI/kUbraPQ1z+ElVV1eagoKBDuxOoncjotUJnYfaMbrVaQQjB8qodonY9nja8Q93rUlQ+zrQJhQeO7PEgW7mG8i4NNXB1D8TvdqFGpkdquwHyr69gGapD3p1rTX+ZKKHvnzVkR1GHHh8HDejUmvF52IiS7jGkKegEKhbJzQMof/Y8EBEekR4cHBzPp/T3HEiomflaN24PEST1zOFGxyRyuswoo5N9kKnRqRqFyTyB+fl59Pf3Iyw0RCQQKx0O2vwE2YpV5HZbUCnV41P/CIb0RszOWMHaV+DbYPl1OHM1Gg3vzZWYS+WCfMWSgwtRroxBNWfSmzJ0fWmEZ53Flt8Pl8sFlj6j0+mE2+2GyWTC+Pg4709iYlIFB2C5/OcN2yFp60NTSwMM2mGoVGoYDAbYbDZ4PB54vV7MzMxAoVDwZ65KSkpaBQXDdobuTyiEZCg3yNs+Pdn2esj6OktmZ2eJWq0mMpmMMAxDqJjv6elpQoNGamtr+34BzIywNQI18UAAAAAASUVORK5CYII="
]
[]

View File

@ -0,0 +1,148 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Data.NotificationChannel exposing
( NotificationChannel(..)
, asString
, channelType
, decoder
, empty
, encode
, setTypeGotify
, setTypeHttp
, setTypeMail
, setTypeMatrix
)
import Api.Model.NotificationGotify exposing (NotificationGotify)
import Api.Model.NotificationHttp exposing (NotificationHttp)
import Api.Model.NotificationMail exposing (NotificationMail)
import Api.Model.NotificationMatrix exposing (NotificationMatrix)
import Data.ChannelRef exposing (ChannelRef)
import Data.ChannelType exposing (ChannelType)
import Json.Decode as D
import Json.Encode as E
type NotificationChannel
= Matrix NotificationMatrix
| Mail NotificationMail
| Gotify NotificationGotify
| Http NotificationHttp
| Ref ChannelRef
empty : ChannelType -> NotificationChannel
empty ct =
let
set =
setType ct
in
case ct of
Data.ChannelType.Mail ->
Mail <| set Api.Model.NotificationMail.empty
Data.ChannelType.Matrix ->
Matrix <| set Api.Model.NotificationMatrix.empty
Data.ChannelType.Gotify ->
Gotify <| set Api.Model.NotificationGotify.empty
Data.ChannelType.Http ->
Http <| set Api.Model.NotificationHttp.empty
setType ct rec =
{ rec | channelType = Data.ChannelType.asString ct }
setTypeHttp : NotificationHttp -> NotificationHttp
setTypeHttp h =
setType Data.ChannelType.Http h
setTypeMail : NotificationMail -> NotificationMail
setTypeMail h =
setType Data.ChannelType.Mail h
setTypeMatrix : NotificationMatrix -> NotificationMatrix
setTypeMatrix h =
setType Data.ChannelType.Matrix h
setTypeGotify : NotificationGotify -> NotificationGotify
setTypeGotify h =
setType Data.ChannelType.Gotify h
decoder : D.Decoder NotificationChannel
decoder =
D.oneOf
[ D.map Gotify Api.Model.NotificationGotify.decoder
, D.map Mail Api.Model.NotificationMail.decoder
, D.map Matrix Api.Model.NotificationMatrix.decoder
, D.map Http Api.Model.NotificationHttp.decoder
, D.map Ref Data.ChannelRef.decoder
]
encode : NotificationChannel -> E.Value
encode channel =
case channel of
Matrix ch ->
Api.Model.NotificationMatrix.encode ch
Mail ch ->
Api.Model.NotificationMail.encode ch
Gotify ch ->
Api.Model.NotificationGotify.encode ch
Http ch ->
Api.Model.NotificationHttp.encode ch
Ref ch ->
Data.ChannelRef.encode ch
channelType : NotificationChannel -> Maybe ChannelType
channelType ch =
case ch of
Matrix m ->
Data.ChannelType.fromString m.channelType
Mail m ->
Data.ChannelType.fromString m.channelType
Gotify m ->
Data.ChannelType.fromString m.channelType
Http m ->
Data.ChannelType.fromString m.channelType
Ref m ->
Just m.channelType
asString : NotificationChannel -> String
asString channel =
case channel of
Matrix ch ->
"Matrix @ " ++ ch.homeServer ++ "(" ++ ch.roomId ++ ")"
Mail ch ->
"Mail @ " ++ ch.connection ++ " (" ++ String.join ", " ch.recipients ++ ")"
Gotify ch ->
"Gotify @ " ++ ch.url
Http ch ->
"Http @ " ++ ch.url
Ref ch ->
"Ref(" ++ Data.ChannelType.asString ch.channelType ++ "/" ++ ch.id ++ ")"

View File

@ -0,0 +1,62 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Data.NotificationHook exposing (NotificationHook, decoder, empty, encode)
import Data.ChannelType exposing (ChannelType)
import Data.EventType exposing (EventType)
import Data.NotificationChannel exposing (NotificationChannel)
import Json.Decode as D
import Json.Encode as E
type alias NotificationHook =
{ id : String
, enabled : Bool
, channel : NotificationChannel
, allEvents : Bool
, eventFilter : Maybe String
, events : List EventType
}
empty : ChannelType -> NotificationHook
empty ct =
{ id = ""
, enabled = True
, channel = Data.NotificationChannel.empty ct
, allEvents = False
, eventFilter = Nothing
, events = []
}
decoder : D.Decoder NotificationHook
decoder =
D.map6 NotificationHook
(D.field "id" D.string)
(D.field "enabled" D.bool)
(D.field "channel" Data.NotificationChannel.decoder)
(D.field "allEvents" D.bool)
(D.field "eventFilter" (D.maybe D.string))
(D.field "events" (D.list Data.EventType.decoder))
encode : NotificationHook -> E.Value
encode hook =
E.object
[ ( "id", E.string hook.id )
, ( "enabled", E.bool hook.enabled )
, ( "channel", Data.NotificationChannel.encode hook.channel )
, ( "allEvents", E.bool hook.allEvents )
, ( "eventFilter", Maybe.map E.string hook.eventFilter |> Maybe.withDefault E.null )
, ( "events", E.list Data.EventType.encode hook.events )
]
--- private

View File

@ -0,0 +1,77 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Data.PeriodicDueItemsSettings exposing (..)
import Api.Model.Tag exposing (Tag)
import Data.ChannelType exposing (ChannelType)
import Data.NotificationChannel exposing (NotificationChannel)
import Json.Decode as Decode
import Json.Decode.Pipeline as P
import Json.Encode as Encode
{--
- Settings for notifying about due items.
--}
type alias PeriodicDueItemsSettings =
{ id : String
, enabled : Bool
, summary : Maybe String
, channel : NotificationChannel
, schedule : String
, remindDays : Int
, capOverdue : Bool
, tagsInclude : List Tag
, tagsExclude : List Tag
}
empty : ChannelType -> PeriodicDueItemsSettings
empty ct =
{ id = ""
, enabled = False
, summary = Nothing
, channel = Data.NotificationChannel.empty ct
, schedule = ""
, remindDays = 0
, capOverdue = False
, tagsInclude = []
, tagsExclude = []
}
decoder : Decode.Decoder PeriodicDueItemsSettings
decoder =
Decode.succeed PeriodicDueItemsSettings
|> P.required "id" Decode.string
|> P.required "enabled" Decode.bool
|> P.optional "summary" (Decode.maybe Decode.string) Nothing
|> P.required "channel" Data.NotificationChannel.decoder
|> P.required "schedule" Decode.string
|> P.required "remindDays" Decode.int
|> P.required "capOverdue" Decode.bool
|> P.required "tagsInclude" (Decode.list Api.Model.Tag.decoder)
|> P.required "tagsExclude" (Decode.list Api.Model.Tag.decoder)
encode : PeriodicDueItemsSettings -> Encode.Value
encode value =
Encode.object
[ ( "id", Encode.string value.id )
, ( "enabled", Encode.bool value.enabled )
, ( "summary", (Maybe.map Encode.string >> Maybe.withDefault Encode.null) value.summary )
, ( "channel", Data.NotificationChannel.encode value.channel )
, ( "schedule", Encode.string value.schedule )
, ( "remindDays", Encode.int value.remindDays )
, ( "capOverdue", Encode.bool value.capOverdue )
, ( "tagsInclude", Encode.list Api.Model.Tag.encode value.tagsInclude )
, ( "tagsExclude", Encode.list Api.Model.Tag.encode value.tagsExclude )
]

View File

@ -0,0 +1,57 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Data.PeriodicQuerySettings exposing (PeriodicQuerySettings, decoder, empty, encode)
import Data.ChannelType exposing (ChannelType)
import Data.NotificationChannel exposing (NotificationChannel)
import Json.Decode as D
import Json.Encode as E
type alias PeriodicQuerySettings =
{ id : String
, enabled : Bool
, summary : Maybe String
, channel : NotificationChannel
, query : String
, schedule : String
}
empty : ChannelType -> PeriodicQuerySettings
empty ct =
{ id = ""
, enabled = False
, summary = Nothing
, channel = Data.NotificationChannel.empty ct
, query = ""
, schedule = ""
}
decoder : D.Decoder PeriodicQuerySettings
decoder =
D.map6 PeriodicQuerySettings
(D.field "id" D.string)
(D.field "enabled" D.bool)
(D.field "summary" (D.maybe D.string))
(D.field "channel" Data.NotificationChannel.decoder)
(D.field "query" D.string)
(D.field "schedule" D.string)
encode : PeriodicQuerySettings -> E.Value
encode s =
E.object
[ ( "id", E.string s.id )
, ( "enabled", E.bool s.enabled )
, ( "summary", Maybe.map E.string s.summary |> Maybe.withDefault E.null )
, ( "channel", Data.NotificationChannel.encode s.channel )
, ( "query", E.string s.query )
, ( "schedule", E.string s.schedule )
]