mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-23 10:58:26 +00:00
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:
280
modules/webapp/src/main/elm/Comp/ChannelForm.elm
Normal file
280
modules/webapp/src/main/elm/Comp/ChannelForm.elm
Normal file
@ -0,0 +1,280 @@
|
||||
{-
|
||||
Copyright 2020 Eike K. & Contributors
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-}
|
||||
|
||||
|
||||
module Comp.ChannelForm exposing (..)
|
||||
|
||||
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 Comp.NotificationGotifyForm
|
||||
import Comp.NotificationHttpForm
|
||||
import Comp.NotificationMailForm
|
||||
import Comp.NotificationMatrixForm
|
||||
import Data.ChannelType exposing (ChannelType)
|
||||
import Data.Flags exposing (Flags)
|
||||
import Data.NotificationChannel exposing (NotificationChannel)
|
||||
import Data.UiSettings exposing (UiSettings)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Messages.Comp.ChannelForm exposing (Texts)
|
||||
|
||||
|
||||
type alias MatrixModel =
|
||||
{ form : Comp.NotificationMatrixForm.Model
|
||||
, value : Maybe NotificationMatrix
|
||||
}
|
||||
|
||||
|
||||
type alias GotifyModel =
|
||||
{ form : Comp.NotificationGotifyForm.Model
|
||||
, value : Maybe NotificationGotify
|
||||
}
|
||||
|
||||
|
||||
type alias MailModel =
|
||||
{ form : Comp.NotificationMailForm.Model
|
||||
, value : Maybe NotificationMail
|
||||
}
|
||||
|
||||
|
||||
type alias HttpModel =
|
||||
{ form : Comp.NotificationHttpForm.Model
|
||||
, value : Maybe NotificationHttp
|
||||
}
|
||||
|
||||
|
||||
type alias RefModel =
|
||||
{ channelType : ChannelType
|
||||
}
|
||||
|
||||
|
||||
type Model
|
||||
= Matrix MatrixModel
|
||||
| Gotify GotifyModel
|
||||
| Mail MailModel
|
||||
| Http HttpModel
|
||||
| Ref RefModel
|
||||
|
||||
|
||||
type Msg
|
||||
= MatrixMsg Comp.NotificationMatrixForm.Msg
|
||||
| GotifyMsg Comp.NotificationGotifyForm.Msg
|
||||
| MailMsg Comp.NotificationMailForm.Msg
|
||||
| HttpMsg Comp.NotificationHttpForm.Msg
|
||||
|
||||
|
||||
init : Flags -> ChannelType -> ( Model, Cmd Msg )
|
||||
init flags ct =
|
||||
case ct of
|
||||
Data.ChannelType.Matrix ->
|
||||
( Matrix
|
||||
{ form = Comp.NotificationMatrixForm.init
|
||||
, value = Nothing
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
Data.ChannelType.Gotify ->
|
||||
( Gotify
|
||||
{ form = Comp.NotificationGotifyForm.init
|
||||
, value = Nothing
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
Data.ChannelType.Mail ->
|
||||
let
|
||||
( mm, mc ) =
|
||||
Comp.NotificationMailForm.init flags
|
||||
in
|
||||
( Mail
|
||||
{ form = mm
|
||||
, value = Nothing
|
||||
}
|
||||
, Cmd.map MailMsg mc
|
||||
)
|
||||
|
||||
Data.ChannelType.Http ->
|
||||
( Http
|
||||
{ form = Comp.NotificationHttpForm.init
|
||||
, value = Nothing
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
initWith : Flags -> NotificationChannel -> ( Model, Cmd Msg )
|
||||
initWith flags channel =
|
||||
case channel of
|
||||
Data.NotificationChannel.Matrix m ->
|
||||
( Matrix
|
||||
{ form = Comp.NotificationMatrixForm.initWith m
|
||||
, value = Just m
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
Data.NotificationChannel.Gotify m ->
|
||||
( Gotify
|
||||
{ form = Comp.NotificationGotifyForm.initWith m
|
||||
, value = Just m
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
Data.NotificationChannel.Mail m ->
|
||||
let
|
||||
( mm, mc ) =
|
||||
Comp.NotificationMailForm.initWith flags m
|
||||
in
|
||||
( Mail
|
||||
{ form = mm
|
||||
, value = Just m
|
||||
}
|
||||
, Cmd.map MailMsg mc
|
||||
)
|
||||
|
||||
Data.NotificationChannel.Http m ->
|
||||
( Http
|
||||
{ form = Comp.NotificationHttpForm.initWith m
|
||||
, value = Just m
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
Data.NotificationChannel.Ref m ->
|
||||
( Ref { channelType = m.channelType }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
channelType : Model -> ChannelType
|
||||
channelType model =
|
||||
case model of
|
||||
Matrix _ ->
|
||||
Data.ChannelType.Matrix
|
||||
|
||||
Gotify _ ->
|
||||
Data.ChannelType.Gotify
|
||||
|
||||
Mail _ ->
|
||||
Data.ChannelType.Mail
|
||||
|
||||
Http _ ->
|
||||
Data.ChannelType.Http
|
||||
|
||||
Ref ref ->
|
||||
ref.channelType
|
||||
|
||||
|
||||
getChannel : Model -> Maybe NotificationChannel
|
||||
getChannel model =
|
||||
case model of
|
||||
Matrix mm ->
|
||||
Maybe.map Data.NotificationChannel.Matrix mm.value
|
||||
|
||||
Gotify mm ->
|
||||
Maybe.map Data.NotificationChannel.Gotify mm.value
|
||||
|
||||
Mail mm ->
|
||||
Maybe.map Data.NotificationChannel.Mail mm.value
|
||||
|
||||
Http mm ->
|
||||
Maybe.map Data.NotificationChannel.Http mm.value
|
||||
|
||||
Ref _ ->
|
||||
Nothing
|
||||
|
||||
|
||||
|
||||
--- Update
|
||||
|
||||
|
||||
update : Flags -> Msg -> Model -> ( Model, Cmd Msg )
|
||||
update flags msg model =
|
||||
case msg of
|
||||
MatrixMsg lm ->
|
||||
case model of
|
||||
Matrix matrix ->
|
||||
let
|
||||
( mm, mv ) =
|
||||
Comp.NotificationMatrixForm.update lm matrix.form
|
||||
in
|
||||
( Matrix { form = mm, value = mv }, Cmd.none )
|
||||
|
||||
_ ->
|
||||
( model, Cmd.none )
|
||||
|
||||
GotifyMsg lm ->
|
||||
case model of
|
||||
Gotify gotify ->
|
||||
let
|
||||
( mm, mv ) =
|
||||
Comp.NotificationGotifyForm.update lm gotify.form
|
||||
in
|
||||
( Gotify { form = mm, value = mv }, Cmd.none )
|
||||
|
||||
_ ->
|
||||
( model, Cmd.none )
|
||||
|
||||
MailMsg lm ->
|
||||
case model of
|
||||
Mail mail ->
|
||||
let
|
||||
( mm, mc, mv ) =
|
||||
Comp.NotificationMailForm.update flags lm mail.form
|
||||
in
|
||||
( Mail { form = mm, value = mv }, Cmd.map MailMsg mc )
|
||||
|
||||
_ ->
|
||||
( model, Cmd.none )
|
||||
|
||||
HttpMsg lm ->
|
||||
case model of
|
||||
Http http ->
|
||||
let
|
||||
( mm, mv ) =
|
||||
Comp.NotificationHttpForm.update lm http.form
|
||||
in
|
||||
( Http { form = mm, value = mv }, Cmd.none )
|
||||
|
||||
_ ->
|
||||
( model, Cmd.none )
|
||||
|
||||
|
||||
|
||||
--- View
|
||||
|
||||
|
||||
view : Texts -> UiSettings -> Model -> Html Msg
|
||||
view texts settings model =
|
||||
case model of
|
||||
Matrix m ->
|
||||
Html.map MatrixMsg
|
||||
(Comp.NotificationMatrixForm.view texts.matrixForm m.form)
|
||||
|
||||
Gotify m ->
|
||||
Html.map GotifyMsg
|
||||
(Comp.NotificationGotifyForm.view texts.gotifyForm m.form)
|
||||
|
||||
Mail m ->
|
||||
Html.map MailMsg
|
||||
(Comp.NotificationMailForm.view texts.mailForm settings m.form)
|
||||
|
||||
Http m ->
|
||||
Html.map HttpMsg
|
||||
(Comp.NotificationHttpForm.view texts.httpForm m.form)
|
||||
|
||||
-- Note: currently when retrieving hooks, this is not
|
||||
-- send from the server. The server always sends
|
||||
-- concrete channel details. However, it is possible
|
||||
-- to create hooks with a reference to an existing
|
||||
-- channel, but this is not supported in this client.
|
||||
-- So this channel is ignored here.
|
||||
Ref _ ->
|
||||
span [ class "hidden" ] []
|
48
modules/webapp/src/main/elm/Comp/ChannelMenu.elm
Normal file
48
modules/webapp/src/main/elm/Comp/ChannelMenu.elm
Normal file
@ -0,0 +1,48 @@
|
||||
{-
|
||||
Copyright 2020 Eike K. & Contributors
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-}
|
||||
|
||||
|
||||
module Comp.ChannelMenu exposing (..)
|
||||
|
||||
import Comp.MenuBar as MB
|
||||
import Data.ChannelType exposing (ChannelType)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (onClick)
|
||||
import Messages.Data.ChannelType exposing (Texts)
|
||||
import Styles as S
|
||||
|
||||
|
||||
type alias Model msg =
|
||||
{ menuOpen : Bool
|
||||
, toggleMenu : msg
|
||||
, menuLabel : String
|
||||
, onItem : ChannelType -> msg
|
||||
}
|
||||
|
||||
|
||||
channelMenu : Texts -> Model msg -> MB.Item msg
|
||||
channelMenu texts model =
|
||||
MB.Dropdown
|
||||
{ linkIcon = "fa fa-plus"
|
||||
, label = model.menuLabel
|
||||
, linkClass = [ ( S.primaryButton, True ) ]
|
||||
, toggleMenu = model.toggleMenu
|
||||
, menuOpen = model.menuOpen
|
||||
, items =
|
||||
List.map (menuItem texts model) Data.ChannelType.all
|
||||
}
|
||||
|
||||
|
||||
menuItem : Texts -> Model msg -> ChannelType -> MB.DropdownMenu msg
|
||||
menuItem texts model ct =
|
||||
{ icon = Data.ChannelType.icon ct "w-6 h-6 text-center inline-block"
|
||||
, label = texts ct
|
||||
, attrs =
|
||||
[ href ""
|
||||
, onClick (model.onItem ct)
|
||||
]
|
||||
}
|
@ -14,6 +14,7 @@ module Comp.Dropdown exposing
|
||||
, isDropdownChangeMsg
|
||||
, makeModel
|
||||
, makeMultiple
|
||||
, makeMultipleList
|
||||
, makeSingle
|
||||
, makeSingleList
|
||||
, mkOption
|
||||
@ -116,6 +117,26 @@ makeMultiple =
|
||||
}
|
||||
|
||||
|
||||
makeMultipleList :
|
||||
{ options : List a
|
||||
, selected : List a
|
||||
}
|
||||
-> Model a
|
||||
makeMultipleList opts =
|
||||
let
|
||||
m =
|
||||
makeMultiple
|
||||
|
||||
m2 =
|
||||
{ m | available = List.map (makeItem m) opts.options }
|
||||
|
||||
m3 =
|
||||
List.map (makeItem m2) opts.selected
|
||||
|> List.foldl (\el -> \model -> selectItem model el) m2
|
||||
in
|
||||
m3
|
||||
|
||||
|
||||
getSelected : Model a -> List a
|
||||
getSelected model =
|
||||
List.map .value model.selected
|
||||
|
@ -5,7 +5,7 @@
|
||||
-}
|
||||
|
||||
|
||||
module Comp.NotificationForm exposing
|
||||
module Comp.DueItemsTaskForm exposing
|
||||
( Action(..)
|
||||
, Model
|
||||
, Msg
|
||||
@ -17,19 +17,21 @@ module Comp.NotificationForm exposing
|
||||
|
||||
import Api
|
||||
import Api.Model.EmailSettingsList exposing (EmailSettingsList)
|
||||
import Api.Model.NotificationSettings exposing (NotificationSettings)
|
||||
import Api.Model.Tag exposing (Tag)
|
||||
import Api.Model.TagList exposing (TagList)
|
||||
import Comp.Basic as B
|
||||
import Comp.CalEventInput
|
||||
import Comp.ChannelForm
|
||||
import Comp.Dropdown
|
||||
import Comp.EmailInput
|
||||
import Comp.IntField
|
||||
import Comp.MenuBar as MB
|
||||
import Comp.YesNoDimmer
|
||||
import Data.CalEvent exposing (CalEvent)
|
||||
import Data.ChannelType exposing (ChannelType)
|
||||
import Data.DropdownStyle as DS
|
||||
import Data.Flags exposing (Flags)
|
||||
import Data.NotificationChannel
|
||||
import Data.PeriodicDueItemsSettings exposing (PeriodicDueItemsSettings)
|
||||
import Data.TagOrder
|
||||
import Data.UiSettings exposing (UiSettings)
|
||||
import Data.Validated exposing (Validated(..))
|
||||
@ -38,7 +40,7 @@ import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (onInput)
|
||||
import Http
|
||||
import Markdown
|
||||
import Messages.Comp.NotificationForm exposing (Texts)
|
||||
import Messages.Comp.DueItemsTaskForm exposing (Texts)
|
||||
import Styles as S
|
||||
import Util.Maybe
|
||||
import Util.Tag
|
||||
@ -46,12 +48,10 @@ import Util.Update
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ settings : NotificationSettings
|
||||
, connectionModel : Comp.Dropdown.Model String
|
||||
{ settings : PeriodicDueItemsSettings
|
||||
, channelModel : Comp.ChannelForm.Model
|
||||
, tagInclModel : Comp.Dropdown.Model Tag
|
||||
, tagExclModel : Comp.Dropdown.Model Tag
|
||||
, recipients : List String
|
||||
, recipientsModel : Comp.EmailInput.Model
|
||||
, remindDays : Maybe Int
|
||||
, remindDaysModel : Comp.IntField.Model
|
||||
, capOverdue : Bool
|
||||
@ -72,15 +72,14 @@ type FormState
|
||||
|
||||
|
||||
type ValidateError
|
||||
= ValidateConnectionMissing
|
||||
| ValidateRemindDaysRequired
|
||||
| ValidateRecipientsRequired
|
||||
= ValidateRemindDaysRequired
|
||||
| ValidateCalEventInvalid
|
||||
| ValidateChannelRequired
|
||||
|
||||
|
||||
type Action
|
||||
= SubmitAction NotificationSettings
|
||||
| StartOnceAction NotificationSettings
|
||||
= SubmitAction PeriodicDueItemsSettings
|
||||
| StartOnceAction PeriodicDueItemsSettings
|
||||
| CancelAction
|
||||
| DeleteAction String
|
||||
| NoAction
|
||||
@ -90,9 +89,6 @@ type Msg
|
||||
= Submit
|
||||
| TagIncMsg (Comp.Dropdown.Msg Tag)
|
||||
| TagExcMsg (Comp.Dropdown.Msg Tag)
|
||||
| ConnMsg (Comp.Dropdown.Msg String)
|
||||
| ConnResp (Result Http.Error EmailSettingsList)
|
||||
| RecipientMsg Comp.EmailInput.Msg
|
||||
| GetTagsResp (Result Http.Error TagList)
|
||||
| RemindDaysMsg Comp.IntField.Msg
|
||||
| ToggleEnabled
|
||||
@ -103,26 +99,25 @@ type Msg
|
||||
| RequestDelete
|
||||
| YesNoDeleteMsg Comp.YesNoDimmer.Msg
|
||||
| SetSummary String
|
||||
| ChannelMsg Comp.ChannelForm.Msg
|
||||
|
||||
|
||||
initWith : Flags -> NotificationSettings -> ( Model, Cmd Msg )
|
||||
initWith : Flags -> PeriodicDueItemsSettings -> ( Model, Cmd Msg )
|
||||
initWith flags s =
|
||||
let
|
||||
( im, ic ) =
|
||||
init flags
|
||||
ct =
|
||||
Data.NotificationChannel.channelType s.channel
|
||||
|> Maybe.withDefault Data.ChannelType.Matrix
|
||||
|
||||
smtp =
|
||||
Util.Maybe.fromString s.smtpConnection
|
||||
|> Maybe.map List.singleton
|
||||
|> Maybe.withDefault []
|
||||
( im, ic ) =
|
||||
init flags ct
|
||||
|
||||
removeAction ( tm, _, tc ) =
|
||||
( tm, tc )
|
||||
|
||||
( nm, nc ) =
|
||||
Util.Update.andThen1
|
||||
[ update flags (ConnMsg (Comp.Dropdown.SetSelection smtp)) >> removeAction
|
||||
, update flags (TagIncMsg (Comp.Dropdown.SetSelection s.tagsInclude)) >> removeAction
|
||||
[ update flags (TagIncMsg (Comp.Dropdown.SetSelection s.tagsInclude)) >> removeAction
|
||||
, update flags (TagExcMsg (Comp.Dropdown.SetSelection s.tagsExclude)) >> removeAction
|
||||
]
|
||||
im
|
||||
@ -133,10 +128,13 @@ initWith flags s =
|
||||
|
||||
( sm, sc ) =
|
||||
Comp.CalEventInput.init flags newSchedule
|
||||
|
||||
( cfm, cfc ) =
|
||||
Comp.ChannelForm.initWith flags s.channel
|
||||
in
|
||||
( { nm
|
||||
| settings = s
|
||||
, recipients = s.recipients
|
||||
, channelModel = cfm
|
||||
, remindDays = Just s.remindDays
|
||||
, enabled = s.enabled
|
||||
, capOverdue = s.capOverdue
|
||||
@ -151,25 +149,27 @@ initWith flags s =
|
||||
[ nc
|
||||
, ic
|
||||
, Cmd.map CalEventMsg sc
|
||||
, Cmd.map ChannelMsg cfc
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
init : Flags -> ( Model, Cmd Msg )
|
||||
init flags =
|
||||
init : Flags -> ChannelType -> ( Model, Cmd Msg )
|
||||
init flags ct =
|
||||
let
|
||||
initialSchedule =
|
||||
Data.CalEvent.everyMonth
|
||||
|
||||
( sm, scmd ) =
|
||||
Comp.CalEventInput.init flags initialSchedule
|
||||
|
||||
( cfm, cfc ) =
|
||||
Comp.ChannelForm.init flags ct
|
||||
in
|
||||
( { settings = Api.Model.NotificationSettings.empty
|
||||
, connectionModel = Comp.Dropdown.makeSingle
|
||||
( { settings = Data.PeriodicDueItemsSettings.empty ct
|
||||
, channelModel = cfm
|
||||
, tagInclModel = Util.Tag.makeDropdownModel
|
||||
, tagExclModel = Util.Tag.makeDropdownModel
|
||||
, recipients = []
|
||||
, recipientsModel = Comp.EmailInput.init
|
||||
, remindDays = Just 1
|
||||
, remindDaysModel = Comp.IntField.init (Just 1) Nothing True
|
||||
, enabled = False
|
||||
@ -177,14 +177,14 @@ init flags =
|
||||
, schedule = Just initialSchedule
|
||||
, scheduleModel = sm
|
||||
, formState = FormStateInitial
|
||||
, loading = 2
|
||||
, loading = 1
|
||||
, yesNoDelete = Comp.YesNoDimmer.emptyModel
|
||||
, summary = Nothing
|
||||
}
|
||||
, Cmd.batch
|
||||
[ Api.getMailSettings flags "" ConnResp
|
||||
, Api.getTags flags "" Data.TagOrder.NameAsc GetTagsResp
|
||||
[ Api.getTags flags "" Data.TagOrder.NameAsc GetTagsResp
|
||||
, Cmd.map CalEventMsg scmd
|
||||
, Cmd.map ChannelMsg cfc
|
||||
]
|
||||
)
|
||||
|
||||
@ -193,25 +193,12 @@ init flags =
|
||||
--- Update
|
||||
|
||||
|
||||
makeSettings : Model -> Result ValidateError NotificationSettings
|
||||
makeSettings : Model -> Result ValidateError PeriodicDueItemsSettings
|
||||
makeSettings model =
|
||||
let
|
||||
prev =
|
||||
model.settings
|
||||
|
||||
conn =
|
||||
Comp.Dropdown.getSelected model.connectionModel
|
||||
|> List.head
|
||||
|> Maybe.map Ok
|
||||
|> Maybe.withDefault (Err ValidateConnectionMissing)
|
||||
|
||||
recp =
|
||||
if List.isEmpty model.recipients then
|
||||
Err ValidateRecipientsRequired
|
||||
|
||||
else
|
||||
Ok model.recipients
|
||||
|
||||
rmdays =
|
||||
Maybe.map Ok model.remindDays
|
||||
|> Maybe.withDefault (Err ValidateRemindDaysRequired)
|
||||
@ -224,27 +211,30 @@ makeSettings model =
|
||||
Nothing ->
|
||||
Err ValidateCalEventInvalid
|
||||
|
||||
make smtp rec days timer =
|
||||
channelM =
|
||||
Result.fromMaybe
|
||||
ValidateChannelRequired
|
||||
(Comp.ChannelForm.getChannel model.channelModel)
|
||||
|
||||
make days timer channel =
|
||||
{ prev
|
||||
| smtpConnection = smtp
|
||||
, tagsInclude = Comp.Dropdown.getSelected model.tagInclModel
|
||||
| tagsInclude = Comp.Dropdown.getSelected model.tagInclModel
|
||||
, tagsExclude = Comp.Dropdown.getSelected model.tagExclModel
|
||||
, recipients = rec
|
||||
, remindDays = days
|
||||
, capOverdue = model.capOverdue
|
||||
, enabled = model.enabled
|
||||
, schedule = Data.CalEvent.makeEvent timer
|
||||
, summary = model.summary
|
||||
, channel = channel
|
||||
}
|
||||
in
|
||||
Result.map4 make
|
||||
conn
|
||||
recp
|
||||
Result.map3 make
|
||||
rmdays
|
||||
schedule_
|
||||
channelM
|
||||
|
||||
|
||||
withValidSettings : (NotificationSettings -> Action) -> Model -> ( Model, Action, Cmd Msg )
|
||||
withValidSettings : (PeriodicDueItemsSettings -> Action) -> Model -> ( Model, Action, Cmd Msg )
|
||||
withValidSettings mkcmd model =
|
||||
case makeSettings model of
|
||||
Ok set ->
|
||||
@ -263,6 +253,16 @@ withValidSettings mkcmd model =
|
||||
update : Flags -> Msg -> Model -> ( Model, Action, Cmd Msg )
|
||||
update flags msg model =
|
||||
case msg of
|
||||
ChannelMsg lm ->
|
||||
let
|
||||
( cfm, cfc ) =
|
||||
Comp.ChannelForm.update flags lm model.channelModel
|
||||
in
|
||||
( { model | channelModel = cfm }
|
||||
, NoAction
|
||||
, Cmd.map ChannelMsg cfc
|
||||
)
|
||||
|
||||
CalEventMsg lmsg ->
|
||||
let
|
||||
( cm, cc, cs ) =
|
||||
@ -280,67 +280,6 @@ update flags msg model =
|
||||
, Cmd.map CalEventMsg cc
|
||||
)
|
||||
|
||||
RecipientMsg m ->
|
||||
let
|
||||
( em, ec, rec ) =
|
||||
Comp.EmailInput.update flags model.recipients m model.recipientsModel
|
||||
in
|
||||
( { model
|
||||
| recipients = rec
|
||||
, recipientsModel = em
|
||||
, formState = FormStateInitial
|
||||
}
|
||||
, NoAction
|
||||
, Cmd.map RecipientMsg ec
|
||||
)
|
||||
|
||||
ConnMsg m ->
|
||||
let
|
||||
( cm, cc ) =
|
||||
Comp.Dropdown.update m model.connectionModel
|
||||
in
|
||||
( { model
|
||||
| connectionModel = cm
|
||||
, formState = FormStateInitial
|
||||
}
|
||||
, NoAction
|
||||
, Cmd.map ConnMsg cc
|
||||
)
|
||||
|
||||
ConnResp (Ok list) ->
|
||||
let
|
||||
names =
|
||||
List.map .name list.items
|
||||
|
||||
cm =
|
||||
Comp.Dropdown.makeSingleList
|
||||
{ options = names
|
||||
, selected = List.head names
|
||||
}
|
||||
in
|
||||
( { model
|
||||
| connectionModel = cm
|
||||
, loading = model.loading - 1
|
||||
, formState =
|
||||
if names == [] then
|
||||
FormStateInvalid ValidateConnectionMissing
|
||||
|
||||
else
|
||||
FormStateInitial
|
||||
}
|
||||
, NoAction
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
ConnResp (Err err) ->
|
||||
( { model
|
||||
| formState = FormStateHttpError err
|
||||
, loading = model.loading - 1
|
||||
}
|
||||
, NoAction
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
TagIncMsg m ->
|
||||
let
|
||||
( m2, c2 ) =
|
||||
@ -509,12 +448,10 @@ view2 texts extraClasses settings model =
|
||||
, icon = Just "fa fa-play"
|
||||
}
|
||||
|
||||
connectionCfg =
|
||||
{ makeOption = \a -> { text = a, additional = "" }
|
||||
, placeholder = texts.selectConnection
|
||||
, labelColor = \_ -> \_ -> ""
|
||||
, style = DS.mainStyle
|
||||
}
|
||||
formHeader txt =
|
||||
h2 [ class S.formHeader, class "mt-2" ]
|
||||
[ text txt
|
||||
]
|
||||
in
|
||||
div
|
||||
[ class "flex flex-col md:relative"
|
||||
@ -539,7 +476,7 @@ view2 texts extraClasses settings model =
|
||||
}
|
||||
, MB.SecondaryButton
|
||||
{ tagger = Cancel
|
||||
, label = texts.basics.cancel
|
||||
, label = texts.basics.backToList
|
||||
, title = texts.basics.backToList
|
||||
, icon = Just "fa fa-arrow-left"
|
||||
}
|
||||
@ -575,17 +512,14 @@ view2 texts extraClasses settings model =
|
||||
FormStateHttpError err ->
|
||||
text (texts.httpError err)
|
||||
|
||||
FormStateInvalid ValidateConnectionMissing ->
|
||||
text texts.connectionMissing
|
||||
|
||||
FormStateInvalid ValidateCalEventInvalid ->
|
||||
text texts.invalidCalEvent
|
||||
|
||||
FormStateInvalid ValidateRemindDaysRequired ->
|
||||
text texts.remindDaysRequired
|
||||
|
||||
FormStateInvalid ValidateRecipientsRequired ->
|
||||
text texts.recipientsRequired
|
||||
FormStateInvalid ValidateChannelRequired ->
|
||||
text texts.channelRequired
|
||||
]
|
||||
, div [ class "mb-4" ]
|
||||
[ MB.viewItem <|
|
||||
@ -613,37 +547,11 @@ view2 texts extraClasses settings model =
|
||||
]
|
||||
]
|
||||
, div [ class "mb-4" ]
|
||||
[ label [ class S.inputLabel ]
|
||||
[ text texts.sendVia
|
||||
, B.inputRequired
|
||||
]
|
||||
, Html.map ConnMsg
|
||||
(Comp.Dropdown.view2
|
||||
connectionCfg
|
||||
settings
|
||||
model.connectionModel
|
||||
)
|
||||
, span [ class "opacity-50 text-sm" ]
|
||||
[ text texts.sendViaInfo
|
||||
]
|
||||
]
|
||||
, div [ class "mb-4" ]
|
||||
[ label
|
||||
[ class S.inputLabel
|
||||
]
|
||||
[ text texts.recipients
|
||||
, B.inputRequired
|
||||
]
|
||||
, Html.map RecipientMsg
|
||||
(Comp.EmailInput.view2
|
||||
{ style = DS.mainStyle, placeholder = texts.recipients }
|
||||
model.recipients
|
||||
model.recipientsModel
|
||||
)
|
||||
, span [ class "opacity-50 text-sm" ]
|
||||
[ text texts.recipientsInfo
|
||||
]
|
||||
[ formHeader (texts.channelHeader (Comp.ChannelForm.channelType model.channelModel))
|
||||
, Html.map ChannelMsg
|
||||
(Comp.ChannelForm.view texts.channelForm settings model.channelModel)
|
||||
]
|
||||
, formHeader texts.queryLabel
|
||||
, div [ class "mb-4" ]
|
||||
[ label [ class S.inputLabel ]
|
||||
[ text texts.tagsInclude ]
|
||||
@ -666,7 +574,7 @@ view2 texts extraClasses settings model =
|
||||
settings
|
||||
model.tagExclModel
|
||||
)
|
||||
, span [ class "small-info" ]
|
||||
, span [ class "opacity-50 text-sm" ]
|
||||
[ text texts.tagsExcludeInfo
|
||||
]
|
||||
]
|
||||
@ -692,7 +600,8 @@ view2 texts extraClasses settings model =
|
||||
]
|
||||
]
|
||||
, div [ class "mb-4" ]
|
||||
[ label [ class S.inputLabel ]
|
||||
[ formHeader texts.schedule
|
||||
, label [ class S.inputLabel ]
|
||||
[ text texts.schedule
|
||||
, a
|
||||
[ class "float-right"
|
@ -5,7 +5,7 @@
|
||||
-}
|
||||
|
||||
|
||||
module Comp.NotificationList exposing
|
||||
module Comp.DueItemsTaskList exposing
|
||||
( Action(..)
|
||||
, Model
|
||||
, Msg
|
||||
@ -14,11 +14,13 @@ module Comp.NotificationList exposing
|
||||
, view2
|
||||
)
|
||||
|
||||
import Api.Model.NotificationSettings exposing (NotificationSettings)
|
||||
import Comp.Basic as B
|
||||
import Data.ChannelType
|
||||
import Data.NotificationChannel
|
||||
import Data.PeriodicDueItemsSettings exposing (PeriodicDueItemsSettings)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Messages.Comp.NotificationTable exposing (Texts)
|
||||
import Messages.Comp.DueItemsTaskList exposing (Texts)
|
||||
import Styles as S
|
||||
import Util.Html
|
||||
|
||||
@ -28,12 +30,12 @@ type alias Model =
|
||||
|
||||
|
||||
type Msg
|
||||
= EditSettings NotificationSettings
|
||||
= EditSettings PeriodicDueItemsSettings
|
||||
|
||||
|
||||
type Action
|
||||
= NoAction
|
||||
| EditAction NotificationSettings
|
||||
| EditAction PeriodicDueItemsSettings
|
||||
|
||||
|
||||
init : Model
|
||||
@ -52,7 +54,7 @@ update msg model =
|
||||
--- View2
|
||||
|
||||
|
||||
view2 : Texts -> Model -> List NotificationSettings -> Html Msg
|
||||
view2 : Texts -> Model -> List PeriodicDueItemsSettings -> Html Msg
|
||||
view2 texts _ items =
|
||||
div []
|
||||
[ table [ class S.tableMain ]
|
||||
@ -67,8 +69,6 @@ view2 texts _ items =
|
||||
[ text texts.schedule ]
|
||||
, th [ class "text-left mr-2" ]
|
||||
[ text texts.connection ]
|
||||
, th [ class "text-left hidden sm:table-cell mr-2" ]
|
||||
[ text texts.recipients ]
|
||||
]
|
||||
]
|
||||
, tbody []
|
||||
@ -77,7 +77,7 @@ view2 texts _ items =
|
||||
]
|
||||
|
||||
|
||||
viewItem2 : Texts -> NotificationSettings -> Html Msg
|
||||
viewItem2 : Texts -> PeriodicDueItemsSettings -> Html Msg
|
||||
viewItem2 texts item =
|
||||
tr []
|
||||
[ B.editLinkTableCell texts.basics.edit (EditSettings item)
|
||||
@ -94,9 +94,9 @@ viewItem2 texts item =
|
||||
]
|
||||
]
|
||||
, td [ class "text-left mr-2" ]
|
||||
[ text item.smtpConnection
|
||||
]
|
||||
, td [ class "text-left hidden sm:table-cell mr-2" ]
|
||||
[ String.join ", " item.recipients |> text
|
||||
[ Data.NotificationChannel.channelType item.channel
|
||||
|> Maybe.map Data.ChannelType.asString
|
||||
|> Maybe.withDefault "-"
|
||||
|> text
|
||||
]
|
||||
]
|
@ -5,7 +5,7 @@
|
||||
-}
|
||||
|
||||
|
||||
module Comp.NotificationManage exposing
|
||||
module Comp.DueItemsTaskManage exposing
|
||||
( Model
|
||||
, Msg
|
||||
, init
|
||||
@ -15,25 +15,27 @@ module Comp.NotificationManage exposing
|
||||
|
||||
import Api
|
||||
import Api.Model.BasicResult exposing (BasicResult)
|
||||
import Api.Model.NotificationSettings exposing (NotificationSettings)
|
||||
import Api.Model.NotificationSettingsList exposing (NotificationSettingsList)
|
||||
import Comp.ChannelMenu
|
||||
import Comp.DueItemsTaskForm
|
||||
import Comp.DueItemsTaskList
|
||||
import Comp.MenuBar as MB
|
||||
import Comp.NotificationForm
|
||||
import Comp.NotificationList
|
||||
import Data.ChannelType exposing (ChannelType)
|
||||
import Data.Flags exposing (Flags)
|
||||
import Data.PeriodicDueItemsSettings exposing (PeriodicDueItemsSettings)
|
||||
import Data.UiSettings exposing (UiSettings)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Http
|
||||
import Messages.Comp.NotificationManage exposing (Texts)
|
||||
import Messages.Comp.DueItemsTaskManage exposing (Texts)
|
||||
import Styles as S
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ listModel : Comp.NotificationList.Model
|
||||
, detailModel : Maybe Comp.NotificationForm.Model
|
||||
, items : List NotificationSettings
|
||||
{ listModel : Comp.DueItemsTaskList.Model
|
||||
, detailModel : Maybe Comp.DueItemsTaskForm.Model
|
||||
, items : List PeriodicDueItemsSettings
|
||||
, formState : FormState
|
||||
, channelMenuOpen : Bool
|
||||
}
|
||||
|
||||
|
||||
@ -52,19 +54,21 @@ type FormState
|
||||
|
||||
|
||||
type Msg
|
||||
= ListMsg Comp.NotificationList.Msg
|
||||
| DetailMsg Comp.NotificationForm.Msg
|
||||
| GetDataResp (Result Http.Error NotificationSettingsList)
|
||||
| NewTask
|
||||
= ListMsg Comp.DueItemsTaskList.Msg
|
||||
| DetailMsg Comp.DueItemsTaskForm.Msg
|
||||
| GetDataResp (Result Http.Error (List PeriodicDueItemsSettings))
|
||||
| NewTaskInit ChannelType
|
||||
| SubmitResp SubmitType (Result Http.Error BasicResult)
|
||||
| ToggleChannelMenu
|
||||
|
||||
|
||||
initModel : Model
|
||||
initModel =
|
||||
{ listModel = Comp.NotificationList.init
|
||||
{ listModel = Comp.DueItemsTaskList.init
|
||||
, detailModel = Nothing
|
||||
, items = []
|
||||
, formState = FormStateInitial
|
||||
, channelMenuOpen = False
|
||||
}
|
||||
|
||||
|
||||
@ -85,9 +89,14 @@ init flags =
|
||||
update : Flags -> Msg -> Model -> ( Model, Cmd Msg )
|
||||
update flags msg model =
|
||||
case msg of
|
||||
GetDataResp (Ok res) ->
|
||||
ToggleChannelMenu ->
|
||||
( { model | channelMenuOpen = not model.channelMenuOpen }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
GetDataResp (Ok items) ->
|
||||
( { model
|
||||
| items = res.items
|
||||
| items = items
|
||||
, formState = FormStateInitial
|
||||
}
|
||||
, Cmd.none
|
||||
@ -101,17 +110,17 @@ update flags msg model =
|
||||
ListMsg lm ->
|
||||
let
|
||||
( mm, action ) =
|
||||
Comp.NotificationList.update lm model.listModel
|
||||
Comp.DueItemsTaskList.update lm model.listModel
|
||||
|
||||
( detail, cmd ) =
|
||||
case action of
|
||||
Comp.NotificationList.NoAction ->
|
||||
Comp.DueItemsTaskList.NoAction ->
|
||||
( Nothing, Cmd.none )
|
||||
|
||||
Comp.NotificationList.EditAction settings ->
|
||||
Comp.DueItemsTaskList.EditAction settings ->
|
||||
let
|
||||
( dm, dc ) =
|
||||
Comp.NotificationForm.initWith flags settings
|
||||
Comp.DueItemsTaskForm.initWith flags settings
|
||||
in
|
||||
( Just dm, Cmd.map DetailMsg dc )
|
||||
in
|
||||
@ -127,11 +136,11 @@ update flags msg model =
|
||||
Just dm ->
|
||||
let
|
||||
( mm, action, mc ) =
|
||||
Comp.NotificationForm.update flags lm dm
|
||||
Comp.DueItemsTaskForm.update flags lm dm
|
||||
|
||||
( model_, cmd_ ) =
|
||||
case action of
|
||||
Comp.NotificationForm.NoAction ->
|
||||
Comp.DueItemsTaskForm.NoAction ->
|
||||
( { model
|
||||
| detailModel = Just mm
|
||||
, formState = FormStateInitial
|
||||
@ -139,7 +148,7 @@ update flags msg model =
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
Comp.NotificationForm.SubmitAction settings ->
|
||||
Comp.DueItemsTaskForm.SubmitAction settings ->
|
||||
( { model
|
||||
| detailModel = Just mm
|
||||
, formState = FormStateInitial
|
||||
@ -151,7 +160,7 @@ update flags msg model =
|
||||
Api.updateNotifyDueItems flags settings (SubmitResp SubmitUpdate)
|
||||
)
|
||||
|
||||
Comp.NotificationForm.CancelAction ->
|
||||
Comp.DueItemsTaskForm.CancelAction ->
|
||||
( { model
|
||||
| detailModel = Nothing
|
||||
, formState = FormStateInitial
|
||||
@ -159,7 +168,7 @@ update flags msg model =
|
||||
, initCmd flags
|
||||
)
|
||||
|
||||
Comp.NotificationForm.StartOnceAction settings ->
|
||||
Comp.DueItemsTaskForm.StartOnceAction settings ->
|
||||
( { model
|
||||
| detailModel = Just mm
|
||||
, formState = FormStateInitial
|
||||
@ -167,7 +176,7 @@ update flags msg model =
|
||||
, Api.startOnceNotifyDueItems flags settings (SubmitResp SubmitStartOnce)
|
||||
)
|
||||
|
||||
Comp.NotificationForm.DeleteAction id ->
|
||||
Comp.DueItemsTaskForm.DeleteAction id ->
|
||||
( { model
|
||||
| detailModel = Just mm
|
||||
, formState = FormStateInitial
|
||||
@ -185,12 +194,12 @@ update flags msg model =
|
||||
Nothing ->
|
||||
( model, Cmd.none )
|
||||
|
||||
NewTask ->
|
||||
NewTaskInit ct ->
|
||||
let
|
||||
( mm, mc ) =
|
||||
Comp.NotificationForm.init flags
|
||||
Comp.DueItemsTaskForm.init flags ct
|
||||
in
|
||||
( { model | detailModel = Just mm }, Cmd.map DetailMsg mc )
|
||||
( { model | detailModel = Just mm, channelMenuOpen = False }, Cmd.map DetailMsg mc )
|
||||
|
||||
SubmitResp submitType (Ok res) ->
|
||||
( { model
|
||||
@ -277,29 +286,32 @@ isSuccess state =
|
||||
False
|
||||
|
||||
|
||||
viewForm2 : Texts -> UiSettings -> Comp.NotificationForm.Model -> List (Html Msg)
|
||||
viewForm2 : Texts -> UiSettings -> Comp.DueItemsTaskForm.Model -> List (Html Msg)
|
||||
viewForm2 texts settings model =
|
||||
[ Html.map DetailMsg
|
||||
(Comp.NotificationForm.view2 texts.notificationForm "flex flex-col" settings model)
|
||||
(Comp.DueItemsTaskForm.view2 texts.notificationForm "flex flex-col" settings model)
|
||||
]
|
||||
|
||||
|
||||
viewList2 : Texts -> Model -> List (Html Msg)
|
||||
viewList2 texts model =
|
||||
let
|
||||
menuModel =
|
||||
{ menuOpen = model.channelMenuOpen
|
||||
, toggleMenu = ToggleChannelMenu
|
||||
, menuLabel = texts.newTask
|
||||
, onItem = NewTaskInit
|
||||
}
|
||||
in
|
||||
[ MB.view
|
||||
{ start =
|
||||
[ MB.PrimaryButton
|
||||
{ tagger = NewTask
|
||||
, label = texts.newTask
|
||||
, icon = Just "fa fa-plus"
|
||||
, title = texts.createNewTask
|
||||
}
|
||||
{ start = []
|
||||
, end =
|
||||
[ Comp.ChannelMenu.channelMenu texts.channelType menuModel
|
||||
]
|
||||
, end = []
|
||||
, rootClasses = "mb-4"
|
||||
}
|
||||
, Html.map ListMsg
|
||||
(Comp.NotificationList.view2 texts.notificationTable
|
||||
(Comp.DueItemsTaskList.view2 texts.notificationTable
|
||||
model.listModel
|
||||
model.items
|
||||
)
|
182
modules/webapp/src/main/elm/Comp/EventSample.elm
Normal file
182
modules/webapp/src/main/elm/Comp/EventSample.elm
Normal file
@ -0,0 +1,182 @@
|
||||
{-
|
||||
Copyright 2020 Eike K. & Contributors
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-}
|
||||
|
||||
|
||||
module Comp.EventSample exposing (Model, Msg, init, initWith, update, viewJson, viewMessage)
|
||||
|
||||
import Api
|
||||
import Comp.FixedDropdown
|
||||
import Data.DropdownStyle as DS
|
||||
import Data.EventType exposing (EventType)
|
||||
import Data.Flags exposing (Flags)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Http
|
||||
import Json.Decode as D
|
||||
import Json.Print
|
||||
import Markdown
|
||||
import Messages.Comp.EventSample exposing (Texts)
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ eventTypeDropdown : Comp.FixedDropdown.Model EventType
|
||||
, selectedEventType : Maybe EventType
|
||||
, content : String
|
||||
}
|
||||
|
||||
|
||||
init : Model
|
||||
init =
|
||||
{ eventTypeDropdown = Comp.FixedDropdown.init Data.EventType.all
|
||||
, selectedEventType = Nothing
|
||||
, content = ""
|
||||
}
|
||||
|
||||
|
||||
initWith : Flags -> EventType -> ( Model, Cmd Msg )
|
||||
initWith flags evt =
|
||||
( { init | selectedEventType = Just evt }
|
||||
, Api.sampleEvent flags evt SampleEvent
|
||||
)
|
||||
|
||||
|
||||
type Msg
|
||||
= EventTypeMsg (Comp.FixedDropdown.Msg EventType)
|
||||
| SampleEvent (Result Http.Error String)
|
||||
|
||||
|
||||
|
||||
--- Update
|
||||
|
||||
|
||||
update : Flags -> Msg -> Model -> ( Model, Cmd Msg )
|
||||
update flags msg model =
|
||||
case msg of
|
||||
EventTypeMsg lm ->
|
||||
let
|
||||
( evm, evt ) =
|
||||
Comp.FixedDropdown.update lm model.eventTypeDropdown
|
||||
|
||||
sampleCmd =
|
||||
case evt of
|
||||
Just ev ->
|
||||
Api.sampleEvent flags ev SampleEvent
|
||||
|
||||
Nothing ->
|
||||
Cmd.none
|
||||
in
|
||||
( { model
|
||||
| eventTypeDropdown = evm
|
||||
, selectedEventType = evt
|
||||
}
|
||||
, sampleCmd
|
||||
)
|
||||
|
||||
SampleEvent (Ok str) ->
|
||||
( { model | content = str }, Cmd.none )
|
||||
|
||||
SampleEvent (Err err) ->
|
||||
( model, Cmd.none )
|
||||
|
||||
|
||||
|
||||
--- View
|
||||
|
||||
|
||||
styleBase : String
|
||||
styleBase =
|
||||
"bg-gray-100 dark:bg-bluegray-900 text-gray-900 dark:text-gray-100 text-sm leading-5"
|
||||
|
||||
|
||||
stylePayload : String
|
||||
stylePayload =
|
||||
"px-2 font-mono overflow-auto max-h-96 h-full whitespace-pre"
|
||||
|
||||
|
||||
styleMessage : String
|
||||
styleMessage =
|
||||
"-my-2 "
|
||||
|
||||
|
||||
jsonPrettyCfg =
|
||||
{ indent = 2
|
||||
, columns = 80
|
||||
}
|
||||
|
||||
|
||||
dropdownCfg texts =
|
||||
{ display = texts.eventType >> .name
|
||||
, icon = \_ -> Nothing
|
||||
, selectPlaceholder = texts.selectEvent
|
||||
, style = DS.mainStyleWith "w-48"
|
||||
}
|
||||
|
||||
|
||||
viewJson : Texts -> Model -> Html Msg
|
||||
viewJson texts model =
|
||||
let
|
||||
json =
|
||||
Result.withDefault ""
|
||||
(Json.Print.prettyString jsonPrettyCfg model.content)
|
||||
in
|
||||
div
|
||||
[ class "flex flex-col w-full relative"
|
||||
]
|
||||
[ div [ class "flex inline-flex items-center absolute top-2 right-4" ]
|
||||
[ Html.map EventTypeMsg
|
||||
(Comp.FixedDropdown.viewStyled2 (dropdownCfg texts)
|
||||
False
|
||||
model.selectedEventType
|
||||
model.eventTypeDropdown
|
||||
)
|
||||
]
|
||||
, div
|
||||
[ class "flex pt-5"
|
||||
, class styleBase
|
||||
, class stylePayload
|
||||
, classList [ ( "hidden", json == "" ) ]
|
||||
]
|
||||
[ text json
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
viewMessage : Texts -> Model -> Html Msg
|
||||
viewMessage texts model =
|
||||
let
|
||||
titleDecoder =
|
||||
D.at [ "message", "title" ] D.string
|
||||
|
||||
bodyDecoder =
|
||||
D.at [ "message", "body" ] D.string
|
||||
|
||||
title =
|
||||
D.decodeString titleDecoder model.content
|
||||
|
||||
body =
|
||||
D.decodeString bodyDecoder model.content
|
||||
in
|
||||
div
|
||||
[ class "flex flex-col w-full relative"
|
||||
]
|
||||
[ div [ class "flex inline-flex items-center absolute top-2 right-4" ]
|
||||
[ Html.map EventTypeMsg
|
||||
(Comp.FixedDropdown.viewStyled2 (dropdownCfg texts)
|
||||
False
|
||||
model.selectedEventType
|
||||
model.eventTypeDropdown
|
||||
)
|
||||
]
|
||||
, div
|
||||
[ class "flex flex-col py-5 px-2 markdown-preview"
|
||||
, class styleBase
|
||||
]
|
||||
[ Markdown.toHtml [ class styleMessage ]
|
||||
(Result.withDefault "" title)
|
||||
, Markdown.toHtml [ class styleMessage ]
|
||||
(Result.withDefault "" body)
|
||||
]
|
||||
]
|
@ -217,6 +217,7 @@ attachHeader texts settings model _ attach =
|
||||
, MB.viewItem <|
|
||||
MB.Dropdown
|
||||
{ linkIcon = "fa fa-bars"
|
||||
, label = ""
|
||||
, linkClass =
|
||||
[ ( "ml-2", True )
|
||||
, ( S.secondaryBasicButton, True )
|
||||
@ -225,21 +226,21 @@ attachHeader texts settings model _ attach =
|
||||
, toggleMenu = ToggleAttachmentDropdown
|
||||
, menuOpen = model.attachmentDropdownOpen
|
||||
, items =
|
||||
[ { icon = "fa fa-download"
|
||||
[ { icon = i [ class "fa fa-download" ] []
|
||||
, label = texts.downloadFile
|
||||
, attrs =
|
||||
[ download attachName
|
||||
, href fileUrl
|
||||
]
|
||||
}
|
||||
, { icon = "fa fa-file"
|
||||
, { icon = i [ class "fa fa-file" ] []
|
||||
, label = texts.renameFile
|
||||
, attrs =
|
||||
[ href "#"
|
||||
, onClick (EditAttachNameStart attach.id)
|
||||
]
|
||||
}
|
||||
, { icon = "fa fa-file-archive"
|
||||
, { icon = i [ class "fa fa-file-archive" ] []
|
||||
, label = texts.downloadOriginalArchiveFile
|
||||
, attrs =
|
||||
[ href (fileUrl ++ "/archive")
|
||||
@ -247,7 +248,7 @@ attachHeader texts settings model _ attach =
|
||||
, classList [ ( "hidden", not hasArchive ) ]
|
||||
]
|
||||
}
|
||||
, { icon = "fa fa-external-link-alt"
|
||||
, { icon = i [ class "fa fa-external-link-alt" ] []
|
||||
, label = texts.originalFile
|
||||
, attrs =
|
||||
[ href (fileUrl ++ "/original")
|
||||
@ -257,31 +258,31 @@ attachHeader texts settings model _ attach =
|
||||
}
|
||||
, { icon =
|
||||
if isAttachMetaOpen model attach.id then
|
||||
"fa fa-toggle-on"
|
||||
i [ class "fa fa-toggle-on" ] []
|
||||
|
||||
else
|
||||
"fa fa-toggle-off"
|
||||
i [ class "fa fa-toggle-off" ] []
|
||||
, label = texts.viewExtractedData
|
||||
, attrs =
|
||||
[ onClick (AttachMetaClick attach.id)
|
||||
, href "#"
|
||||
]
|
||||
}
|
||||
, { icon = "fa fa-redo-alt"
|
||||
, { icon = i [ class "fa fa-redo-alt" ] []
|
||||
, label = texts.reprocessFile
|
||||
, attrs =
|
||||
[ onClick (RequestReprocessFile attach.id)
|
||||
, href "#"
|
||||
]
|
||||
}
|
||||
, { icon = Icons.showQr
|
||||
, { icon = i [ class Icons.showQr ] []
|
||||
, label = texts.showQrCode
|
||||
, attrs =
|
||||
[ onClick (ToggleShowQrAttach attach.id)
|
||||
, href "#"
|
||||
]
|
||||
}
|
||||
, { icon = "fa fa-trash"
|
||||
, { icon = i [ class "fa fa-trash" ] []
|
||||
, label = texts.deleteThisFile
|
||||
, attrs =
|
||||
[ onClick (RequestDeleteAttachment attach.id)
|
||||
|
@ -8,6 +8,7 @@
|
||||
module Comp.MenuBar exposing
|
||||
( ButtonData
|
||||
, CheckboxData
|
||||
, DropdownMenu
|
||||
, Item(..)
|
||||
, MenuBar
|
||||
, TextInputData
|
||||
@ -85,6 +86,7 @@ type alias LabelData =
|
||||
type alias DropdownData msg =
|
||||
{ linkIcon : String
|
||||
, linkClass : List ( String, Bool )
|
||||
, label : String
|
||||
, toggleMenu : msg
|
||||
, menuOpen : Bool
|
||||
, items : List (DropdownMenu msg)
|
||||
@ -92,7 +94,7 @@ type alias DropdownData msg =
|
||||
|
||||
|
||||
type alias DropdownMenu msg =
|
||||
{ icon : String
|
||||
{ icon : Html msg
|
||||
, label : String
|
||||
, attrs : List (Attribute msg)
|
||||
}
|
||||
@ -175,11 +177,7 @@ makeDropdown model =
|
||||
menuItem m =
|
||||
a
|
||||
(class itemStyle :: m.attrs)
|
||||
[ i
|
||||
[ class m.icon
|
||||
, classList [ ( "hidden", m.icon == "" ) ]
|
||||
]
|
||||
[]
|
||||
[ m.icon
|
||||
, span
|
||||
[ class "ml-2"
|
||||
, classList [ ( "hidden", m.label == "" ) ]
|
||||
@ -196,6 +194,13 @@ makeDropdown model =
|
||||
, onClick model.toggleMenu
|
||||
]
|
||||
[ i [ class model.linkIcon ] []
|
||||
, if model.label == "" then
|
||||
span [ class "hidden" ] []
|
||||
|
||||
else
|
||||
span [ class "ml-2" ]
|
||||
[ text model.label
|
||||
]
|
||||
]
|
||||
, div
|
||||
[ class menuStyle
|
||||
|
117
modules/webapp/src/main/elm/Comp/NotificationGotifyForm.elm
Normal file
117
modules/webapp/src/main/elm/Comp/NotificationGotifyForm.elm
Normal file
@ -0,0 +1,117 @@
|
||||
{-
|
||||
Copyright 2020 Eike K. & Contributors
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-}
|
||||
|
||||
|
||||
module Comp.NotificationGotifyForm exposing (Model, Msg, init, initWith, update, view)
|
||||
|
||||
import Api.Model.NotificationGotify exposing (NotificationGotify)
|
||||
import Comp.Basic as B
|
||||
import Data.NotificationChannel
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (onInput)
|
||||
import Messages.Comp.NotificationGotifyForm exposing (Texts)
|
||||
import Styles as S
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ hook : NotificationGotify
|
||||
}
|
||||
|
||||
|
||||
init : Model
|
||||
init =
|
||||
{ hook = Data.NotificationChannel.setTypeGotify Api.Model.NotificationGotify.empty
|
||||
}
|
||||
|
||||
|
||||
initWith : NotificationGotify -> Model
|
||||
initWith hook =
|
||||
{ hook = Data.NotificationChannel.setTypeGotify hook
|
||||
}
|
||||
|
||||
|
||||
type Msg
|
||||
= SetUrl String
|
||||
| SetAppKey String
|
||||
|
||||
|
||||
|
||||
--- Update
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Maybe NotificationGotify )
|
||||
update msg model =
|
||||
let
|
||||
newHook =
|
||||
updateHook msg model.hook
|
||||
in
|
||||
( { model | hook = newHook }, check newHook )
|
||||
|
||||
|
||||
check : NotificationGotify -> Maybe NotificationGotify
|
||||
check hook =
|
||||
Just hook
|
||||
|
||||
|
||||
updateHook : Msg -> NotificationGotify -> NotificationGotify
|
||||
updateHook msg hook =
|
||||
case msg of
|
||||
SetUrl s ->
|
||||
{ hook | url = s }
|
||||
|
||||
SetAppKey s ->
|
||||
{ hook | appKey = s }
|
||||
|
||||
|
||||
|
||||
--- View
|
||||
|
||||
|
||||
view : Texts -> Model -> Html Msg
|
||||
view texts model =
|
||||
div []
|
||||
[ div
|
||||
[ class "mb-2"
|
||||
]
|
||||
[ label
|
||||
[ for "gotifyurl"
|
||||
, class S.inputLabel
|
||||
]
|
||||
[ text texts.gotifyUrl
|
||||
, B.inputRequired
|
||||
]
|
||||
, input
|
||||
[ type_ "text"
|
||||
, onInput SetUrl
|
||||
, placeholder texts.gotifyUrl
|
||||
, value model.hook.url
|
||||
, name "gotifyurl"
|
||||
, class S.textInput
|
||||
]
|
||||
[]
|
||||
]
|
||||
, div
|
||||
[ class "mb-2"
|
||||
]
|
||||
[ label
|
||||
[ for "appkey"
|
||||
, class S.inputLabel
|
||||
]
|
||||
[ text texts.appKey
|
||||
, B.inputRequired
|
||||
]
|
||||
, input
|
||||
[ type_ "text"
|
||||
, onInput SetAppKey
|
||||
, placeholder texts.appKey
|
||||
, value model.hook.appKey
|
||||
, name "appkey"
|
||||
, class S.textInput
|
||||
]
|
||||
[]
|
||||
]
|
||||
]
|
317
modules/webapp/src/main/elm/Comp/NotificationHookForm.elm
Normal file
317
modules/webapp/src/main/elm/Comp/NotificationHookForm.elm
Normal file
@ -0,0 +1,317 @@
|
||||
{-
|
||||
Copyright 2020 Eike K. & Contributors
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-}
|
||||
|
||||
|
||||
module Comp.NotificationHookForm exposing
|
||||
( Model
|
||||
, Msg(..)
|
||||
, channelType
|
||||
, getHook
|
||||
, init
|
||||
, initWith
|
||||
, update
|
||||
, view
|
||||
)
|
||||
|
||||
import Comp.Basic as B
|
||||
import Comp.ChannelForm
|
||||
import Comp.Dropdown
|
||||
import Comp.EventSample
|
||||
import Comp.MenuBar as MB
|
||||
import Comp.NotificationTest
|
||||
import Data.ChannelType exposing (ChannelType)
|
||||
import Data.DropdownStyle as DS
|
||||
import Data.EventType exposing (EventType)
|
||||
import Data.Flags exposing (Flags)
|
||||
import Data.NotificationHook exposing (NotificationHook)
|
||||
import Data.UiSettings exposing (UiSettings)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (onInput)
|
||||
import Messages.Comp.NotificationHookForm exposing (Texts)
|
||||
import Styles as S
|
||||
import Util.Maybe
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ hook : NotificationHook
|
||||
, enabled : Bool
|
||||
, channelModel : Comp.ChannelForm.Model
|
||||
, eventsDropdown : Comp.Dropdown.Model EventType
|
||||
, eventSampleModel : Comp.EventSample.Model
|
||||
, testDeliveryModel : Comp.NotificationTest.Model
|
||||
, allEvents : Bool
|
||||
, eventFilter : Maybe String
|
||||
}
|
||||
|
||||
|
||||
init : Flags -> ChannelType -> ( Model, Cmd Msg )
|
||||
init flags ct =
|
||||
let
|
||||
( cm, cc ) =
|
||||
Comp.ChannelForm.init flags ct
|
||||
|
||||
( esm, esc ) =
|
||||
Comp.EventSample.initWith flags Data.EventType.TagsChanged
|
||||
in
|
||||
( { hook = Data.NotificationHook.empty ct
|
||||
, enabled = True
|
||||
, channelModel = cm
|
||||
, eventsDropdown =
|
||||
Comp.Dropdown.makeMultipleList
|
||||
{ options = Data.EventType.all
|
||||
, selected = []
|
||||
}
|
||||
, eventSampleModel = esm
|
||||
, testDeliveryModel = Comp.NotificationTest.init
|
||||
, allEvents = False
|
||||
, eventFilter = Nothing
|
||||
}
|
||||
, Cmd.batch
|
||||
[ Cmd.map ChannelFormMsg cc
|
||||
, Cmd.map EventSampleMsg esc
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
initWith : Flags -> NotificationHook -> ( Model, Cmd Msg )
|
||||
initWith flags h =
|
||||
let
|
||||
( cm, cc ) =
|
||||
Comp.ChannelForm.initWith flags h.channel
|
||||
|
||||
( esm, esc ) =
|
||||
Comp.EventSample.initWith flags Data.EventType.TagsChanged
|
||||
in
|
||||
( { hook = h
|
||||
, enabled = h.enabled
|
||||
, channelModel = cm
|
||||
, eventsDropdown =
|
||||
Comp.Dropdown.makeMultipleList
|
||||
{ options = Data.EventType.all
|
||||
, selected = h.events
|
||||
}
|
||||
, eventSampleModel = esm
|
||||
, testDeliveryModel = Comp.NotificationTest.init
|
||||
, allEvents = h.allEvents
|
||||
, eventFilter = h.eventFilter
|
||||
}
|
||||
, Cmd.batch
|
||||
[ Cmd.map ChannelFormMsg cc
|
||||
, Cmd.map EventSampleMsg esc
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
channelType : Model -> ChannelType
|
||||
channelType model =
|
||||
Comp.ChannelForm.channelType model.channelModel
|
||||
|
||||
|
||||
getHook : Model -> Maybe NotificationHook
|
||||
getHook model =
|
||||
let
|
||||
events =
|
||||
let
|
||||
ev =
|
||||
Comp.Dropdown.getSelected model.eventsDropdown
|
||||
in
|
||||
if List.isEmpty ev && not model.allEvents then
|
||||
Nothing
|
||||
|
||||
else
|
||||
Just ev
|
||||
|
||||
channel =
|
||||
Comp.ChannelForm.getChannel model.channelModel
|
||||
|
||||
mkHook ev ch =
|
||||
NotificationHook model.hook.id model.enabled ch model.allEvents model.eventFilter ev
|
||||
in
|
||||
Maybe.map2 mkHook events channel
|
||||
|
||||
|
||||
type Msg
|
||||
= ToggleEnabled
|
||||
| ChannelFormMsg Comp.ChannelForm.Msg
|
||||
| EventMsg (Comp.Dropdown.Msg EventType)
|
||||
| EventSampleMsg Comp.EventSample.Msg
|
||||
| DeliveryTestMsg Comp.NotificationTest.Msg
|
||||
| ToggleAllEvents
|
||||
| SetEventFilter String
|
||||
|
||||
|
||||
update : Flags -> Msg -> Model -> ( Model, Cmd Msg )
|
||||
update flags msg model =
|
||||
case msg of
|
||||
SetEventFilter str ->
|
||||
( { model | eventFilter = Util.Maybe.fromString str }, Cmd.none )
|
||||
|
||||
ToggleAllEvents ->
|
||||
( { model | allEvents = not model.allEvents }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
ToggleEnabled ->
|
||||
( { model | enabled = not model.enabled }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
ChannelFormMsg lm ->
|
||||
let
|
||||
( cm, cc ) =
|
||||
Comp.ChannelForm.update flags lm model.channelModel
|
||||
in
|
||||
( { model | channelModel = cm }, Cmd.map ChannelFormMsg cc )
|
||||
|
||||
EventMsg lm ->
|
||||
if model.allEvents then
|
||||
( model, Cmd.none )
|
||||
|
||||
else
|
||||
let
|
||||
( em, ec ) =
|
||||
Comp.Dropdown.update lm model.eventsDropdown
|
||||
in
|
||||
( { model | eventsDropdown = em }, Cmd.map EventMsg ec )
|
||||
|
||||
EventSampleMsg lm ->
|
||||
let
|
||||
( esm, esc ) =
|
||||
Comp.EventSample.update flags lm model.eventSampleModel
|
||||
in
|
||||
( { model | eventSampleModel = esm }, Cmd.map EventSampleMsg esc )
|
||||
|
||||
DeliveryTestMsg lm ->
|
||||
case getHook model of
|
||||
Just hook ->
|
||||
let
|
||||
( ntm, ntc ) =
|
||||
Comp.NotificationTest.update flags hook lm model.testDeliveryModel
|
||||
in
|
||||
( { model | testDeliveryModel = ntm }, Cmd.map DeliveryTestMsg ntc )
|
||||
|
||||
Nothing ->
|
||||
( model, Cmd.none )
|
||||
|
||||
|
||||
|
||||
--- View
|
||||
|
||||
|
||||
view : Texts -> UiSettings -> Model -> Html Msg
|
||||
view texts settings model =
|
||||
let
|
||||
connectionCfg =
|
||||
{ makeOption = \a -> { text = (texts.eventType a).name, additional = (texts.eventType a).info }
|
||||
, placeholder = texts.selectEvents
|
||||
, labelColor = \_ -> \_ -> ""
|
||||
, style = DS.mainStyle
|
||||
}
|
||||
|
||||
formHeader txt =
|
||||
h2 [ class S.formHeader, class "mt-2" ]
|
||||
[ text txt
|
||||
]
|
||||
in
|
||||
div
|
||||
[ class "flex flex-col" ]
|
||||
[ div [ class "mb-4" ]
|
||||
[ MB.viewItem <|
|
||||
MB.Checkbox
|
||||
{ tagger = \_ -> ToggleEnabled
|
||||
, label = texts.enableDisable
|
||||
, value = model.enabled
|
||||
, id = "notify-enabled"
|
||||
}
|
||||
]
|
||||
, div [ class "mb-4" ]
|
||||
[ formHeader (texts.channelHeader (Comp.ChannelForm.channelType model.channelModel))
|
||||
, Html.map ChannelFormMsg
|
||||
(Comp.ChannelForm.view texts.channelForm settings model.channelModel)
|
||||
]
|
||||
, div [ class "mb-4" ]
|
||||
[ formHeader texts.events
|
||||
, MB.viewItem <|
|
||||
MB.Checkbox
|
||||
{ tagger = \_ -> ToggleAllEvents
|
||||
, label = texts.toggleAllEvents
|
||||
, value = model.allEvents
|
||||
, id = "notify-on-all-events"
|
||||
}
|
||||
]
|
||||
, div
|
||||
[ class "mb-4"
|
||||
, classList [ ( "disabled", model.allEvents ) ]
|
||||
]
|
||||
[ label [ class S.inputLabel ]
|
||||
[ text texts.events
|
||||
, B.inputRequired
|
||||
]
|
||||
, Html.map EventMsg
|
||||
(Comp.Dropdown.view2
|
||||
connectionCfg
|
||||
settings
|
||||
model.eventsDropdown
|
||||
)
|
||||
, span [ class "opacity-50 text-sm" ]
|
||||
[ text texts.eventsInfo
|
||||
]
|
||||
]
|
||||
, div [ class "mb-4" ]
|
||||
[ label [ class S.inputLabel ]
|
||||
[ text texts.eventFilter
|
||||
, a
|
||||
[ class "float-right"
|
||||
, class S.link
|
||||
, href "https://docspell.org/docs/jsonminiquery/"
|
||||
, target "_blank"
|
||||
]
|
||||
[ i [ class "fa fa-question" ] []
|
||||
, span [ class "pl-2" ]
|
||||
[ text texts.eventFilterClickForHelp
|
||||
]
|
||||
]
|
||||
]
|
||||
, input
|
||||
[ type_ "text"
|
||||
, onInput SetEventFilter
|
||||
, class S.textInput
|
||||
, Maybe.withDefault "" model.eventFilter
|
||||
|> value
|
||||
]
|
||||
[]
|
||||
, span [ class "opacity-50 text-sm" ]
|
||||
[ text texts.eventFilterInfo
|
||||
]
|
||||
]
|
||||
, div
|
||||
[ class "mt-4"
|
||||
, classList [ ( "hidden", channelType model /= Data.ChannelType.Http ) ]
|
||||
]
|
||||
[ h3 [ class S.header3 ]
|
||||
[ text texts.samplePayload
|
||||
]
|
||||
, Html.map EventSampleMsg
|
||||
(Comp.EventSample.viewJson texts.eventSample model.eventSampleModel)
|
||||
]
|
||||
, div
|
||||
[ class "mt-4"
|
||||
, classList [ ( "hidden", channelType model == Data.ChannelType.Http ) ]
|
||||
]
|
||||
[ formHeader texts.samplePayload
|
||||
, Html.map EventSampleMsg
|
||||
(Comp.EventSample.viewMessage texts.eventSample model.eventSampleModel)
|
||||
]
|
||||
, div [ class "mt-4" ]
|
||||
[ formHeader "Test Delviery"
|
||||
, Html.map DeliveryTestMsg
|
||||
(Comp.NotificationTest.view
|
||||
{ runDisabled = getHook model == Nothing }
|
||||
model.testDeliveryModel
|
||||
)
|
||||
]
|
||||
]
|
475
modules/webapp/src/main/elm/Comp/NotificationHookManage.elm
Normal file
475
modules/webapp/src/main/elm/Comp/NotificationHookManage.elm
Normal file
@ -0,0 +1,475 @@
|
||||
{-
|
||||
Copyright 2020 Eike K. & Contributors
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-}
|
||||
|
||||
|
||||
module Comp.NotificationHookManage exposing
|
||||
( Model
|
||||
, Msg
|
||||
, init
|
||||
, update
|
||||
, view
|
||||
)
|
||||
|
||||
import Api
|
||||
import Api.Model.BasicResult exposing (BasicResult)
|
||||
import Comp.Basic as B
|
||||
import Comp.ChannelMenu
|
||||
import Comp.MenuBar as MB
|
||||
import Comp.NotificationHookForm
|
||||
import Comp.NotificationHookTable
|
||||
import Data.ChannelType exposing (ChannelType)
|
||||
import Data.Flags exposing (Flags)
|
||||
import Data.NotificationHook exposing (NotificationHook)
|
||||
import Data.UiSettings exposing (UiSettings)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (onClick)
|
||||
import Http
|
||||
import Messages.Comp.NotificationHookManage exposing (Texts)
|
||||
import Styles as S
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ listModel : Comp.NotificationHookTable.Model
|
||||
, detailModel : Maybe Comp.NotificationHookForm.Model
|
||||
, items : List NotificationHook
|
||||
, deleteConfirm : DeleteConfirm
|
||||
, loading : Bool
|
||||
, formState : FormState
|
||||
, newHookMenuOpen : Bool
|
||||
, jsonFilterError : Maybe String
|
||||
}
|
||||
|
||||
|
||||
type DeleteConfirm
|
||||
= DeleteConfirmOff
|
||||
| DeleteConfirmOn
|
||||
|
||||
|
||||
type SubmitType
|
||||
= SubmitDelete
|
||||
| SubmitUpdate
|
||||
| SubmitCreate
|
||||
|
||||
|
||||
type FormState
|
||||
= FormStateInitial
|
||||
| FormErrorHttp Http.Error
|
||||
| FormSubmitSuccessful SubmitType
|
||||
| FormErrorSubmit String
|
||||
| FormErrorInvalid
|
||||
|
||||
|
||||
type Msg
|
||||
= TableMsg Comp.NotificationHookTable.Msg
|
||||
| DetailMsg Comp.NotificationHookForm.Msg
|
||||
| GetDataResp (Result Http.Error (List NotificationHook))
|
||||
| ToggleNewHookMenu
|
||||
| SubmitResp SubmitType (Result Http.Error BasicResult)
|
||||
| NewHookInit ChannelType
|
||||
| BackToTable
|
||||
| Submit
|
||||
| RequestDelete
|
||||
| CancelDelete
|
||||
| DeleteHookNow String
|
||||
| VerifyFilterResp NotificationHook (Result Http.Error BasicResult)
|
||||
|
||||
|
||||
initModel : Model
|
||||
initModel =
|
||||
{ listModel = Comp.NotificationHookTable.init
|
||||
, detailModel = Nothing
|
||||
, items = []
|
||||
, loading = False
|
||||
, formState = FormStateInitial
|
||||
, newHookMenuOpen = False
|
||||
, deleteConfirm = DeleteConfirmOff
|
||||
, jsonFilterError = Nothing
|
||||
}
|
||||
|
||||
|
||||
initCmd : Flags -> Cmd Msg
|
||||
initCmd flags =
|
||||
Api.getHooks flags GetDataResp
|
||||
|
||||
|
||||
init : Flags -> ( Model, Cmd Msg )
|
||||
init flags =
|
||||
( initModel, initCmd flags )
|
||||
|
||||
|
||||
|
||||
--- Update
|
||||
|
||||
|
||||
update : Flags -> Msg -> Model -> ( Model, Cmd Msg )
|
||||
update flags msg model =
|
||||
case msg of
|
||||
VerifyFilterResp hook (Ok res) ->
|
||||
if res.success then
|
||||
postHook flags hook model
|
||||
|
||||
else
|
||||
( { model
|
||||
| loading = False
|
||||
, formState = FormErrorInvalid
|
||||
, jsonFilterError = Just res.message
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
VerifyFilterResp _ (Err err) ->
|
||||
( { model | formState = FormErrorHttp err }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
GetDataResp (Ok res) ->
|
||||
( { model
|
||||
| items = res
|
||||
, formState = FormStateInitial
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
GetDataResp (Err err) ->
|
||||
( { model | formState = FormErrorHttp err }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
TableMsg lm ->
|
||||
let
|
||||
( mm, action ) =
|
||||
Comp.NotificationHookTable.update flags lm model.listModel
|
||||
|
||||
( detail, cmd ) =
|
||||
case action of
|
||||
Comp.NotificationHookTable.NoAction ->
|
||||
( Nothing, Cmd.none )
|
||||
|
||||
Comp.NotificationHookTable.EditAction hook ->
|
||||
let
|
||||
( dm, dc ) =
|
||||
Comp.NotificationHookForm.initWith flags hook
|
||||
in
|
||||
( Just dm, Cmd.map DetailMsg dc )
|
||||
in
|
||||
( { model
|
||||
| listModel = mm
|
||||
, detailModel = detail
|
||||
}
|
||||
, cmd
|
||||
)
|
||||
|
||||
DetailMsg lm ->
|
||||
case model.detailModel of
|
||||
Just dm ->
|
||||
let
|
||||
( mm, mc ) =
|
||||
Comp.NotificationHookForm.update flags lm dm
|
||||
in
|
||||
( { model | detailModel = Just mm }
|
||||
, Cmd.map DetailMsg mc
|
||||
)
|
||||
|
||||
Nothing ->
|
||||
( model, Cmd.none )
|
||||
|
||||
ToggleNewHookMenu ->
|
||||
( { model | newHookMenuOpen = not model.newHookMenuOpen }, Cmd.none )
|
||||
|
||||
SubmitResp submitType (Ok res) ->
|
||||
( { model
|
||||
| formState =
|
||||
if res.success then
|
||||
FormSubmitSuccessful submitType
|
||||
|
||||
else
|
||||
FormErrorSubmit res.message
|
||||
, detailModel =
|
||||
if submitType == SubmitDelete then
|
||||
Nothing
|
||||
|
||||
else
|
||||
model.detailModel
|
||||
, loading = False
|
||||
}
|
||||
, if submitType == SubmitDelete then
|
||||
initCmd flags
|
||||
|
||||
else
|
||||
Cmd.none
|
||||
)
|
||||
|
||||
SubmitResp _ (Err err) ->
|
||||
( { model | formState = FormErrorHttp err, loading = False }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
NewHookInit ct ->
|
||||
let
|
||||
( mm, mc ) =
|
||||
Comp.NotificationHookForm.init flags ct
|
||||
in
|
||||
( { model | detailModel = Just mm, newHookMenuOpen = False }, Cmd.map DetailMsg mc )
|
||||
|
||||
BackToTable ->
|
||||
( { model | detailModel = Nothing }, initCmd flags )
|
||||
|
||||
Submit ->
|
||||
case model.detailModel of
|
||||
Just dm ->
|
||||
case Comp.NotificationHookForm.getHook dm of
|
||||
Just data ->
|
||||
case data.eventFilter of
|
||||
Nothing ->
|
||||
postHook flags data model
|
||||
|
||||
Just jf ->
|
||||
( { model | loading = True }, Api.verifyJsonFilter flags jf (VerifyFilterResp data) )
|
||||
|
||||
Nothing ->
|
||||
( { model | formState = FormErrorInvalid }, Cmd.none )
|
||||
|
||||
Nothing ->
|
||||
( model, Cmd.none )
|
||||
|
||||
RequestDelete ->
|
||||
( { model | deleteConfirm = DeleteConfirmOn }, Cmd.none )
|
||||
|
||||
CancelDelete ->
|
||||
( { model | deleteConfirm = DeleteConfirmOff }, Cmd.none )
|
||||
|
||||
DeleteHookNow id ->
|
||||
( { model | deleteConfirm = DeleteConfirmOff, loading = True }
|
||||
, Api.deleteHook flags id (SubmitResp SubmitDelete)
|
||||
)
|
||||
|
||||
|
||||
postHook : Flags -> NotificationHook -> Model -> ( Model, Cmd Msg )
|
||||
postHook flags hook model =
|
||||
if hook.id == "" then
|
||||
( { model | loading = True }, Api.createHook flags hook (SubmitResp SubmitCreate) )
|
||||
|
||||
else
|
||||
( { model | loading = True }, Api.updateHook flags hook (SubmitResp SubmitUpdate) )
|
||||
|
||||
|
||||
|
||||
--- View2
|
||||
|
||||
|
||||
view : Texts -> UiSettings -> Model -> Html Msg
|
||||
view texts settings model =
|
||||
div [ class "flex flex-col" ]
|
||||
(case model.detailModel of
|
||||
Just msett ->
|
||||
viewForm texts settings model msett
|
||||
|
||||
Nothing ->
|
||||
viewList texts model
|
||||
)
|
||||
|
||||
|
||||
viewState : Texts -> Model -> Html Msg
|
||||
viewState texts model =
|
||||
div
|
||||
[ classList
|
||||
[ ( S.errorMessage, model.formState /= FormStateInitial )
|
||||
, ( S.successMessage, isSuccess model.formState )
|
||||
, ( "hidden", model.formState == FormStateInitial )
|
||||
]
|
||||
, class "mb-2"
|
||||
]
|
||||
[ case model.formState of
|
||||
FormStateInitial ->
|
||||
text ""
|
||||
|
||||
FormSubmitSuccessful SubmitCreate ->
|
||||
text texts.hookCreated
|
||||
|
||||
FormSubmitSuccessful SubmitUpdate ->
|
||||
text texts.hookUpdated
|
||||
|
||||
FormSubmitSuccessful SubmitDelete ->
|
||||
text texts.hookDeleted
|
||||
|
||||
FormErrorSubmit m ->
|
||||
text m
|
||||
|
||||
FormErrorHttp err ->
|
||||
text (texts.httpError err)
|
||||
|
||||
FormErrorInvalid ->
|
||||
case model.jsonFilterError of
|
||||
Just m ->
|
||||
text (texts.invalidJsonFilter m)
|
||||
|
||||
Nothing ->
|
||||
text texts.formInvalid
|
||||
]
|
||||
|
||||
|
||||
isSuccess : FormState -> Bool
|
||||
isSuccess state =
|
||||
case state of
|
||||
FormSubmitSuccessful _ ->
|
||||
True
|
||||
|
||||
_ ->
|
||||
False
|
||||
|
||||
|
||||
viewForm : Texts -> UiSettings -> Model -> Comp.NotificationHookForm.Model -> List (Html Msg)
|
||||
viewForm texts settings outerModel model =
|
||||
let
|
||||
newHook =
|
||||
model.hook.id == ""
|
||||
|
||||
headline =
|
||||
case Comp.NotificationHookForm.channelType model of
|
||||
Data.ChannelType.Matrix ->
|
||||
span []
|
||||
[ text texts.integrate
|
||||
, a
|
||||
[ href "https://matrix.org"
|
||||
, target "_blank"
|
||||
, class S.link
|
||||
, class "mx-3"
|
||||
]
|
||||
[ i [ class "fa fa-external-link-alt mr-1" ] []
|
||||
, text "Matrix"
|
||||
]
|
||||
, text texts.intoDocspell
|
||||
]
|
||||
|
||||
Data.ChannelType.Mail ->
|
||||
span []
|
||||
[ text texts.notifyEmailInfo
|
||||
]
|
||||
|
||||
Data.ChannelType.Gotify ->
|
||||
span []
|
||||
[ text texts.integrate
|
||||
, a
|
||||
[ href "https://gotify.net"
|
||||
, target "_blank"
|
||||
, class S.link
|
||||
, class "mx-3"
|
||||
]
|
||||
[ i [ class "fa fa-external-link-alt mr-1" ] []
|
||||
, text "Gotify"
|
||||
]
|
||||
, text texts.intoDocspell
|
||||
]
|
||||
|
||||
Data.ChannelType.Http ->
|
||||
span []
|
||||
[ text texts.postRequestInfo
|
||||
]
|
||||
in
|
||||
[ h1 [ class S.header2 ]
|
||||
[ Data.ChannelType.icon (Comp.NotificationHookForm.channelType model) "w-8 h-8 inline-block mr-4"
|
||||
, if newHook then
|
||||
text texts.addWebhook
|
||||
|
||||
else
|
||||
text texts.updateWebhook
|
||||
]
|
||||
, div [ class "pt-2 pb-4 font-medium" ]
|
||||
[ headline
|
||||
]
|
||||
, MB.view
|
||||
{ start =
|
||||
[ MB.CustomElement <|
|
||||
B.primaryButton
|
||||
{ handler = onClick Submit
|
||||
, title = texts.basics.submitThisForm
|
||||
, icon = "fa fa-save"
|
||||
, label = texts.basics.submit
|
||||
, disabled = False
|
||||
, attrs = [ href "#" ]
|
||||
}
|
||||
, MB.SecondaryButton
|
||||
{ tagger = BackToTable
|
||||
, title = texts.basics.backToList
|
||||
, icon = Just "fa fa-arrow-left"
|
||||
, label = texts.basics.backToList
|
||||
}
|
||||
]
|
||||
, end =
|
||||
if not newHook then
|
||||
[ MB.DeleteButton
|
||||
{ tagger = RequestDelete
|
||||
, title = texts.deleteThisHook
|
||||
, icon = Just "fa fa-trash"
|
||||
, label = texts.basics.delete
|
||||
}
|
||||
]
|
||||
|
||||
else
|
||||
[]
|
||||
, rootClasses = "mb-4"
|
||||
}
|
||||
, div [ class "mt-2" ]
|
||||
[ viewState texts outerModel
|
||||
]
|
||||
, Html.map DetailMsg
|
||||
(Comp.NotificationHookForm.view texts.notificationForm settings model)
|
||||
, B.loadingDimmer
|
||||
{ active = outerModel.loading
|
||||
, label = texts.basics.loading
|
||||
}
|
||||
, B.contentDimmer
|
||||
(outerModel.deleteConfirm == DeleteConfirmOn)
|
||||
(div [ class "flex flex-col" ]
|
||||
[ div [ class "text-lg" ]
|
||||
[ i [ class "fa fa-info-circle mr-2" ] []
|
||||
, text texts.reallyDeleteHook
|
||||
]
|
||||
, div [ class "mt-4 flex flex-row items-center" ]
|
||||
[ B.deleteButton
|
||||
{ label = texts.basics.yes
|
||||
, icon = "fa fa-check"
|
||||
, disabled = False
|
||||
, handler = onClick (DeleteHookNow model.hook.id)
|
||||
, attrs = [ href "#" ]
|
||||
}
|
||||
, B.secondaryButton
|
||||
{ label = texts.basics.no
|
||||
, icon = "fa fa-times"
|
||||
, disabled = False
|
||||
, handler = onClick CancelDelete
|
||||
, attrs = [ href "#", class "ml-2" ]
|
||||
}
|
||||
]
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
viewList : Texts -> Model -> List (Html Msg)
|
||||
viewList texts model =
|
||||
let
|
||||
menuModel =
|
||||
{ menuOpen = model.newHookMenuOpen
|
||||
, toggleMenu = ToggleNewHookMenu
|
||||
, menuLabel = texts.newHook
|
||||
, onItem = NewHookInit
|
||||
}
|
||||
in
|
||||
[ MB.view
|
||||
{ start = []
|
||||
, end =
|
||||
[ Comp.ChannelMenu.channelMenu texts.channelType menuModel
|
||||
]
|
||||
, rootClasses = "mb-4"
|
||||
}
|
||||
, Html.map TableMsg
|
||||
(Comp.NotificationHookTable.view texts.notificationTable
|
||||
model.listModel
|
||||
model.items
|
||||
)
|
||||
]
|
106
modules/webapp/src/main/elm/Comp/NotificationHookTable.elm
Normal file
106
modules/webapp/src/main/elm/Comp/NotificationHookTable.elm
Normal file
@ -0,0 +1,106 @@
|
||||
{-
|
||||
Copyright 2020 Eike K. & Contributors
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-}
|
||||
|
||||
|
||||
module Comp.NotificationHookTable exposing
|
||||
( Action(..)
|
||||
, Model
|
||||
, Msg(..)
|
||||
, init
|
||||
, update
|
||||
, view
|
||||
)
|
||||
|
||||
import Comp.Basic as B
|
||||
import Data.ChannelType
|
||||
import Data.EventType
|
||||
import Data.Flags exposing (Flags)
|
||||
import Data.NotificationChannel
|
||||
import Data.NotificationHook exposing (NotificationHook)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (onClick)
|
||||
import Messages.Comp.NotificationHookTable exposing (Texts)
|
||||
import Styles as S
|
||||
import Util.Html
|
||||
|
||||
|
||||
type alias Model =
|
||||
{}
|
||||
|
||||
|
||||
type Action
|
||||
= NoAction
|
||||
| EditAction NotificationHook
|
||||
|
||||
|
||||
init : Model
|
||||
init =
|
||||
{}
|
||||
|
||||
|
||||
type Msg
|
||||
= Select NotificationHook
|
||||
|
||||
|
||||
update : Flags -> Msg -> Model -> ( Model, Action )
|
||||
update _ msg model =
|
||||
case msg of
|
||||
Select hook ->
|
||||
( model, EditAction hook )
|
||||
|
||||
|
||||
|
||||
--- View
|
||||
|
||||
|
||||
view : Texts -> Model -> List NotificationHook -> Html Msg
|
||||
view texts model hooks =
|
||||
table [ class S.tableMain ]
|
||||
[ thead []
|
||||
[ tr []
|
||||
[ th [ class "" ] []
|
||||
, th [ class "text-center mr-2" ]
|
||||
[ i [ class "fa fa-check" ] []
|
||||
]
|
||||
, th [ class "text-left" ]
|
||||
[ text texts.channel
|
||||
]
|
||||
, th [ class "text-left hidden sm:table-cell" ]
|
||||
[ text texts.events
|
||||
]
|
||||
]
|
||||
]
|
||||
, tbody []
|
||||
(List.map (renderNotificationHookLine texts model) hooks)
|
||||
]
|
||||
|
||||
|
||||
renderNotificationHookLine : Texts -> Model -> NotificationHook -> Html Msg
|
||||
renderNotificationHookLine texts model hook =
|
||||
let
|
||||
eventName =
|
||||
texts.eventType >> .name
|
||||
in
|
||||
tr
|
||||
[ class S.tableRow
|
||||
]
|
||||
[ B.editLinkTableCell texts.basics.edit (Select hook)
|
||||
, td [ class "w-px whitespace-nowrap px-2 text-center" ]
|
||||
[ Util.Html.checkbox2 hook.enabled
|
||||
]
|
||||
, td [ class "text-left py-4 md:py-2" ]
|
||||
[ Data.NotificationChannel.channelType hook.channel
|
||||
|> Maybe.map Data.ChannelType.asString
|
||||
|> Maybe.withDefault "-"
|
||||
|> text
|
||||
]
|
||||
, td [ class "text-left hidden sm:table-cell" ]
|
||||
[ List.map eventName hook.events
|
||||
|> String.join ", "
|
||||
|> text
|
||||
]
|
||||
]
|
99
modules/webapp/src/main/elm/Comp/NotificationHttpForm.elm
Normal file
99
modules/webapp/src/main/elm/Comp/NotificationHttpForm.elm
Normal file
@ -0,0 +1,99 @@
|
||||
{-
|
||||
Copyright 2020 Eike K. & Contributors
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-}
|
||||
|
||||
|
||||
module Comp.NotificationHttpForm exposing (Model, Msg, init, initWith, update, view)
|
||||
|
||||
import Api.Model.NotificationHttp exposing (NotificationHttp)
|
||||
import Comp.Basic as B
|
||||
import Data.NotificationChannel
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (onInput)
|
||||
import Messages.Comp.NotificationHttpForm exposing (Texts)
|
||||
import Styles as S
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ hook : NotificationHttp
|
||||
}
|
||||
|
||||
|
||||
init : Model
|
||||
init =
|
||||
{ hook =
|
||||
Data.NotificationChannel.setTypeHttp
|
||||
Api.Model.NotificationHttp.empty
|
||||
}
|
||||
|
||||
|
||||
initWith : NotificationHttp -> Model
|
||||
initWith hook =
|
||||
{ hook = Data.NotificationChannel.setTypeHttp hook
|
||||
}
|
||||
|
||||
|
||||
type Msg
|
||||
= SetUrl String
|
||||
|
||||
|
||||
|
||||
--- Update
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Maybe NotificationHttp )
|
||||
update msg model =
|
||||
let
|
||||
newHook =
|
||||
updateHook msg model.hook
|
||||
in
|
||||
( { model | hook = newHook }, check newHook )
|
||||
|
||||
|
||||
check : NotificationHttp -> Maybe NotificationHttp
|
||||
check hook =
|
||||
if hook.url == "" then
|
||||
Nothing
|
||||
|
||||
else
|
||||
Just hook
|
||||
|
||||
|
||||
updateHook : Msg -> NotificationHttp -> NotificationHttp
|
||||
updateHook msg hook =
|
||||
case msg of
|
||||
SetUrl s ->
|
||||
{ hook | url = s }
|
||||
|
||||
|
||||
|
||||
--- View
|
||||
|
||||
|
||||
view : Texts -> Model -> Html Msg
|
||||
view texts model =
|
||||
div []
|
||||
[ div
|
||||
[ class "mb-2"
|
||||
]
|
||||
[ label
|
||||
[ for "httpurl"
|
||||
, class S.inputLabel
|
||||
]
|
||||
[ text texts.httpUrl
|
||||
, B.inputRequired
|
||||
]
|
||||
, input
|
||||
[ type_ "text"
|
||||
, onInput SetUrl
|
||||
, placeholder texts.httpUrl
|
||||
, value model.hook.url
|
||||
, name "httpurl"
|
||||
, class S.textInput
|
||||
]
|
||||
[]
|
||||
]
|
||||
]
|
236
modules/webapp/src/main/elm/Comp/NotificationMailForm.elm
Normal file
236
modules/webapp/src/main/elm/Comp/NotificationMailForm.elm
Normal file
@ -0,0 +1,236 @@
|
||||
{-
|
||||
Copyright 2020 Eike K. & Contributors
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-}
|
||||
|
||||
|
||||
module Comp.NotificationMailForm exposing (Model, Msg, init, initWith, update, view)
|
||||
|
||||
import Api
|
||||
import Api.Model.EmailSettingsList exposing (EmailSettingsList)
|
||||
import Api.Model.NotificationMail exposing (NotificationMail)
|
||||
import Comp.Basic as B
|
||||
import Comp.Dropdown
|
||||
import Comp.EmailInput
|
||||
import Data.DropdownStyle as DS
|
||||
import Data.Flags exposing (Flags)
|
||||
import Data.NotificationChannel
|
||||
import Data.UiSettings exposing (UiSettings)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (onInput)
|
||||
import Http
|
||||
import Messages.Comp.NotificationMailForm exposing (Texts)
|
||||
import Styles as S
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ hook : NotificationMail
|
||||
, connectionModel : Comp.Dropdown.Model String
|
||||
, recipients : List String
|
||||
, recipientsModel : Comp.EmailInput.Model
|
||||
, formState : FormState
|
||||
}
|
||||
|
||||
|
||||
type FormState
|
||||
= FormStateInitial
|
||||
| FormStateHttpError Http.Error
|
||||
| FormStateInvalid ValidateError
|
||||
|
||||
|
||||
type ValidateError
|
||||
= ValidateConnectionMissing
|
||||
|
||||
|
||||
init : Flags -> ( Model, Cmd Msg )
|
||||
init flags =
|
||||
( { hook = Data.NotificationChannel.setTypeMail Api.Model.NotificationMail.empty
|
||||
, connectionModel = Comp.Dropdown.makeSingle
|
||||
, recipients = []
|
||||
, recipientsModel = Comp.EmailInput.init
|
||||
, formState = FormStateInitial
|
||||
}
|
||||
, Cmd.batch
|
||||
[ Api.getMailSettings flags "" ConnResp
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
initWith : Flags -> NotificationMail -> ( Model, Cmd Msg )
|
||||
initWith flags hook =
|
||||
let
|
||||
( mm, mc ) =
|
||||
init flags
|
||||
|
||||
( cm, _ ) =
|
||||
Comp.Dropdown.update (Comp.Dropdown.SetSelection [ hook.connection ]) mm.connectionModel
|
||||
in
|
||||
( { mm
|
||||
| hook = Data.NotificationChannel.setTypeMail hook
|
||||
, recipients = hook.recipients
|
||||
, connectionModel = cm
|
||||
}
|
||||
, mc
|
||||
)
|
||||
|
||||
|
||||
type Msg
|
||||
= ConnResp (Result Http.Error EmailSettingsList)
|
||||
| ConnMsg (Comp.Dropdown.Msg String)
|
||||
| RecipientMsg Comp.EmailInput.Msg
|
||||
|
||||
|
||||
|
||||
--- Update
|
||||
|
||||
|
||||
check : Model -> Maybe NotificationMail
|
||||
check model =
|
||||
let
|
||||
formState =
|
||||
if model.formState == FormStateInitial then
|
||||
Just ()
|
||||
|
||||
else
|
||||
Nothing
|
||||
|
||||
recipients =
|
||||
if List.isEmpty model.recipients then
|
||||
Nothing
|
||||
|
||||
else
|
||||
Just model.recipients
|
||||
|
||||
connection =
|
||||
Comp.Dropdown.getSelected model.connectionModel
|
||||
|> List.head
|
||||
|
||||
h =
|
||||
model.hook
|
||||
|
||||
makeHook _ rec conn =
|
||||
{ h | connection = conn, recipients = rec }
|
||||
in
|
||||
Maybe.map3 makeHook formState recipients connection
|
||||
|
||||
|
||||
update : Flags -> Msg -> Model -> ( Model, Cmd Msg, Maybe NotificationMail )
|
||||
update flags msg model =
|
||||
case msg of
|
||||
ConnResp (Ok list) ->
|
||||
let
|
||||
names =
|
||||
List.map .name list.items
|
||||
|
||||
cm =
|
||||
Comp.Dropdown.makeSingleList
|
||||
{ options = names
|
||||
, selected = List.head names
|
||||
}
|
||||
|
||||
model_ =
|
||||
{ model
|
||||
| connectionModel = cm
|
||||
, formState =
|
||||
if names == [] then
|
||||
FormStateInvalid ValidateConnectionMissing
|
||||
|
||||
else
|
||||
FormStateInitial
|
||||
}
|
||||
in
|
||||
( model_
|
||||
, Cmd.none
|
||||
, check model_
|
||||
)
|
||||
|
||||
ConnResp (Err err) ->
|
||||
( { model | formState = FormStateHttpError err }
|
||||
, Cmd.none
|
||||
, Nothing
|
||||
)
|
||||
|
||||
ConnMsg lm ->
|
||||
let
|
||||
( cm, cc ) =
|
||||
Comp.Dropdown.update lm model.connectionModel
|
||||
|
||||
model_ =
|
||||
{ model
|
||||
| connectionModel = cm
|
||||
, formState = FormStateInitial
|
||||
}
|
||||
in
|
||||
( model_
|
||||
, Cmd.map ConnMsg cc
|
||||
, check model_
|
||||
)
|
||||
|
||||
RecipientMsg lm ->
|
||||
let
|
||||
( em, ec, rec ) =
|
||||
Comp.EmailInput.update flags model.recipients lm model.recipientsModel
|
||||
|
||||
model_ =
|
||||
{ model
|
||||
| recipients = rec
|
||||
, recipientsModel = em
|
||||
, formState = FormStateInitial
|
||||
}
|
||||
in
|
||||
( model_
|
||||
, Cmd.map RecipientMsg ec
|
||||
, check model_
|
||||
)
|
||||
|
||||
|
||||
|
||||
--- View
|
||||
|
||||
|
||||
view : Texts -> UiSettings -> Model -> Html Msg
|
||||
view texts settings model =
|
||||
let
|
||||
connectionCfg =
|
||||
{ makeOption = \a -> { text = a, additional = "" }
|
||||
, placeholder = texts.selectConnection
|
||||
, labelColor = \_ -> \_ -> ""
|
||||
, style = DS.mainStyle
|
||||
}
|
||||
in
|
||||
div []
|
||||
[ div [ class "mb-4" ]
|
||||
[ label [ class S.inputLabel ]
|
||||
[ text texts.sendVia
|
||||
, B.inputRequired
|
||||
]
|
||||
, Html.map ConnMsg
|
||||
(Comp.Dropdown.view2
|
||||
connectionCfg
|
||||
settings
|
||||
model.connectionModel
|
||||
)
|
||||
, span [ class "opacity-50 text-sm" ]
|
||||
[ text texts.sendViaInfo
|
||||
]
|
||||
]
|
||||
, div [ class "" ]
|
||||
[ label
|
||||
[ class S.inputLabel
|
||||
]
|
||||
[ text texts.recipients
|
||||
, B.inputRequired
|
||||
]
|
||||
, Html.map RecipientMsg
|
||||
(Comp.EmailInput.view2
|
||||
{ style = DS.mainStyle, placeholder = texts.recipients }
|
||||
model.recipients
|
||||
model.recipientsModel
|
||||
)
|
||||
, span [ class "opacity-50 text-sm" ]
|
||||
[ text texts.recipientsInfo
|
||||
]
|
||||
]
|
||||
]
|
140
modules/webapp/src/main/elm/Comp/NotificationMatrixForm.elm
Normal file
140
modules/webapp/src/main/elm/Comp/NotificationMatrixForm.elm
Normal file
@ -0,0 +1,140 @@
|
||||
{-
|
||||
Copyright 2020 Eike K. & Contributors
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-}
|
||||
|
||||
|
||||
module Comp.NotificationMatrixForm exposing (Model, Msg, init, initWith, update, view)
|
||||
|
||||
import Api.Model.NotificationMatrix exposing (NotificationMatrix)
|
||||
import Comp.Basic as B
|
||||
import Data.NotificationChannel
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (onInput)
|
||||
import Messages.Comp.NotificationMatrixForm exposing (Texts)
|
||||
import Styles as S
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ hook : NotificationMatrix
|
||||
}
|
||||
|
||||
|
||||
init : Model
|
||||
init =
|
||||
{ hook = Data.NotificationChannel.setTypeMatrix Api.Model.NotificationMatrix.empty
|
||||
}
|
||||
|
||||
|
||||
initWith : NotificationMatrix -> Model
|
||||
initWith hook =
|
||||
{ hook = Data.NotificationChannel.setTypeMatrix hook
|
||||
}
|
||||
|
||||
|
||||
type Msg
|
||||
= SetHomeServer String
|
||||
| SetRoomId String
|
||||
| SetAccessKey String
|
||||
|
||||
|
||||
|
||||
--- Update
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Maybe NotificationMatrix )
|
||||
update msg model =
|
||||
let
|
||||
newHook =
|
||||
updateHook msg model.hook
|
||||
in
|
||||
( { model | hook = newHook }, check newHook )
|
||||
|
||||
|
||||
check : NotificationMatrix -> Maybe NotificationMatrix
|
||||
check hook =
|
||||
Just hook
|
||||
|
||||
|
||||
updateHook : Msg -> NotificationMatrix -> NotificationMatrix
|
||||
updateHook msg hook =
|
||||
case msg of
|
||||
SetHomeServer s ->
|
||||
{ hook | homeServer = s }
|
||||
|
||||
SetRoomId s ->
|
||||
{ hook | roomId = s }
|
||||
|
||||
SetAccessKey s ->
|
||||
{ hook | accessToken = s }
|
||||
|
||||
|
||||
|
||||
--- View
|
||||
|
||||
|
||||
view : Texts -> Model -> Html Msg
|
||||
view texts model =
|
||||
div []
|
||||
[ div
|
||||
[ class "mb-2"
|
||||
]
|
||||
[ label
|
||||
[ for "homeserver"
|
||||
, class S.inputLabel
|
||||
]
|
||||
[ text texts.homeServer
|
||||
, B.inputRequired
|
||||
]
|
||||
, input
|
||||
[ type_ "text"
|
||||
, onInput SetHomeServer
|
||||
, placeholder texts.homeServer
|
||||
, value model.hook.homeServer
|
||||
, name "homeserver"
|
||||
, class S.textInput
|
||||
]
|
||||
[]
|
||||
]
|
||||
, div
|
||||
[ class "mb-2"
|
||||
]
|
||||
[ label
|
||||
[ for "roomid"
|
||||
, class S.inputLabel
|
||||
]
|
||||
[ text texts.roomId
|
||||
, B.inputRequired
|
||||
]
|
||||
, input
|
||||
[ type_ "text"
|
||||
, onInput SetRoomId
|
||||
, placeholder texts.roomId
|
||||
, value model.hook.roomId
|
||||
, name "roomid"
|
||||
, class S.textInput
|
||||
]
|
||||
[]
|
||||
]
|
||||
, div
|
||||
[ class "mb-2"
|
||||
]
|
||||
[ label
|
||||
[ for "accesskey"
|
||||
, class S.inputLabel
|
||||
]
|
||||
[ text texts.accessKey
|
||||
, B.inputRequired
|
||||
]
|
||||
, textarea
|
||||
[ onInput SetAccessKey
|
||||
, placeholder texts.accessKey
|
||||
, value model.hook.accessToken
|
||||
, name "accesskey"
|
||||
, class S.textAreaInput
|
||||
]
|
||||
[]
|
||||
]
|
||||
]
|
146
modules/webapp/src/main/elm/Comp/NotificationTest.elm
Normal file
146
modules/webapp/src/main/elm/Comp/NotificationTest.elm
Normal file
@ -0,0 +1,146 @@
|
||||
{-
|
||||
Copyright 2020 Eike K. & Contributors
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-}
|
||||
|
||||
|
||||
module Comp.NotificationTest exposing (Model, Msg, ViewConfig, init, update, view)
|
||||
|
||||
import Api
|
||||
import Api.Model.NotificationChannelTestResult exposing (NotificationChannelTestResult)
|
||||
import Comp.Basic as B
|
||||
import Comp.MenuBar as MB
|
||||
import Data.Flags exposing (Flags)
|
||||
import Data.NotificationHook exposing (NotificationHook)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (onClick)
|
||||
import Http
|
||||
|
||||
|
||||
type Model
|
||||
= ModelInit
|
||||
| ModelResp NotificationChannelTestResult
|
||||
| ModelHttpError Http.Error
|
||||
| ModelLoading
|
||||
|
||||
|
||||
init : Model
|
||||
init =
|
||||
ModelInit
|
||||
|
||||
|
||||
type Msg
|
||||
= RunTest
|
||||
| TestResp (Result Http.Error NotificationChannelTestResult)
|
||||
|
||||
|
||||
hasResponse : Model -> Bool
|
||||
hasResponse model =
|
||||
case model of
|
||||
ModelResp _ ->
|
||||
True
|
||||
|
||||
_ ->
|
||||
False
|
||||
|
||||
|
||||
|
||||
--- Update
|
||||
|
||||
|
||||
update : Flags -> NotificationHook -> Msg -> Model -> ( Model, Cmd Msg )
|
||||
update flags hook msg model =
|
||||
case msg of
|
||||
RunTest ->
|
||||
case model of
|
||||
ModelLoading ->
|
||||
( model, Cmd.none )
|
||||
|
||||
_ ->
|
||||
( ModelLoading, Api.testHook flags hook TestResp )
|
||||
|
||||
TestResp (Ok res) ->
|
||||
( ModelResp res, Cmd.none )
|
||||
|
||||
TestResp (Err err) ->
|
||||
( ModelHttpError err, Cmd.none )
|
||||
|
||||
|
||||
|
||||
--- View
|
||||
|
||||
|
||||
type alias ViewConfig =
|
||||
{ runDisabled : Bool
|
||||
}
|
||||
|
||||
|
||||
styleBase : String
|
||||
styleBase =
|
||||
"bg-gray-100 dark:bg-bluegray-900 text-gray-900 dark:text-gray-100 text-sm leading-5"
|
||||
|
||||
|
||||
stylePayload : String
|
||||
stylePayload =
|
||||
"px-2 font-mono overflow-auto h-full whitespace-pre "
|
||||
|
||||
|
||||
view : ViewConfig -> Model -> Html Msg
|
||||
view cfg model =
|
||||
div
|
||||
[ class "flex flex-col w-full"
|
||||
]
|
||||
[ MB.view
|
||||
{ start =
|
||||
case model of
|
||||
ModelResp res ->
|
||||
[ MB.CustomElement <|
|
||||
if res.success then
|
||||
div [ class "text-3xl text-green-500" ]
|
||||
[ i [ class "fa fa-check" ] []
|
||||
]
|
||||
|
||||
else
|
||||
div [ class "text-3xl text-red-500" ]
|
||||
[ i [ class "fa fa-times" ] []
|
||||
]
|
||||
]
|
||||
|
||||
_ ->
|
||||
[]
|
||||
, end =
|
||||
[ MB.CustomElement <|
|
||||
B.primaryButton
|
||||
{ label = "Test Delivery"
|
||||
, disabled = cfg.runDisabled || model == ModelLoading
|
||||
, icon =
|
||||
if model == ModelLoading then
|
||||
"fa fa-cog animate-spin"
|
||||
|
||||
else
|
||||
"fa fa-cog"
|
||||
, handler = onClick RunTest
|
||||
, attrs = [ href "#" ]
|
||||
}
|
||||
]
|
||||
, rootClasses = "mb-1"
|
||||
}
|
||||
, case model of
|
||||
ModelResp res ->
|
||||
div
|
||||
[ class "flex flex-col py-5 px-2"
|
||||
, class styleBase
|
||||
, class stylePayload
|
||||
]
|
||||
[ text (String.join "\n" res.messages)
|
||||
]
|
||||
|
||||
ModelHttpError err ->
|
||||
div [ class "" ]
|
||||
[]
|
||||
|
||||
_ ->
|
||||
span [ class "hidden" ] []
|
||||
]
|
484
modules/webapp/src/main/elm/Comp/PeriodicQueryTaskForm.elm
Normal file
484
modules/webapp/src/main/elm/Comp/PeriodicQueryTaskForm.elm
Normal file
@ -0,0 +1,484 @@
|
||||
{-
|
||||
Copyright 2020 Eike K. & Contributors
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-}
|
||||
|
||||
|
||||
module Comp.PeriodicQueryTaskForm exposing
|
||||
( Action(..)
|
||||
, Model
|
||||
, Msg
|
||||
, UpdateResult
|
||||
, init
|
||||
, initWith
|
||||
, update
|
||||
, view
|
||||
)
|
||||
|
||||
import Comp.Basic as B
|
||||
import Comp.CalEventInput
|
||||
import Comp.ChannelForm
|
||||
import Comp.MenuBar as MB
|
||||
import Comp.PowerSearchInput
|
||||
import Data.CalEvent exposing (CalEvent)
|
||||
import Data.ChannelType exposing (ChannelType)
|
||||
import Data.Flags exposing (Flags)
|
||||
import Data.PeriodicQuerySettings exposing (PeriodicQuerySettings)
|
||||
import Data.UiSettings exposing (UiSettings)
|
||||
import Data.Validated exposing (Validated(..))
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (onInput)
|
||||
import Http
|
||||
import Messages.Comp.PeriodicQueryTaskForm exposing (Texts)
|
||||
import Styles as S
|
||||
import Util.Maybe
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ settings : PeriodicQuerySettings
|
||||
, enabled : Bool
|
||||
, summary : Maybe String
|
||||
, schedule : Maybe CalEvent
|
||||
, scheduleModel : Comp.CalEventInput.Model
|
||||
, queryModel : Comp.PowerSearchInput.Model
|
||||
, channelModel : Comp.ChannelForm.Model
|
||||
, formState : FormState
|
||||
, loading : Int
|
||||
}
|
||||
|
||||
|
||||
type FormState
|
||||
= FormStateInitial
|
||||
| FormStateHttpError Http.Error
|
||||
| FormStateInvalid ValidateError
|
||||
|
||||
|
||||
type ValidateError
|
||||
= ValidateCalEventInvalid
|
||||
| ValidateQueryStringRequired
|
||||
| ValidateChannelRequired
|
||||
|
||||
|
||||
type Action
|
||||
= SubmitAction PeriodicQuerySettings
|
||||
| StartOnceAction PeriodicQuerySettings
|
||||
| CancelAction
|
||||
| DeleteAction String
|
||||
| NoAction
|
||||
|
||||
|
||||
type Msg
|
||||
= Submit
|
||||
| ToggleEnabled
|
||||
| CalEventMsg Comp.CalEventInput.Msg
|
||||
| QueryMsg Comp.PowerSearchInput.Msg
|
||||
| ChannelMsg Comp.ChannelForm.Msg
|
||||
| StartOnce
|
||||
| Cancel
|
||||
| RequestDelete
|
||||
| SetSummary String
|
||||
|
||||
|
||||
initWith : Flags -> PeriodicQuerySettings -> ( Model, Cmd Msg )
|
||||
initWith flags s =
|
||||
let
|
||||
newSchedule =
|
||||
Data.CalEvent.fromEvent s.schedule
|
||||
|> Maybe.withDefault Data.CalEvent.everyMonth
|
||||
|
||||
( sm, sc ) =
|
||||
Comp.CalEventInput.init flags newSchedule
|
||||
|
||||
res =
|
||||
Comp.PowerSearchInput.update
|
||||
(Comp.PowerSearchInput.setSearchString s.query)
|
||||
Comp.PowerSearchInput.init
|
||||
|
||||
( cfm, cfc ) =
|
||||
Comp.ChannelForm.initWith flags s.channel
|
||||
in
|
||||
( { settings = s
|
||||
, enabled = s.enabled
|
||||
, schedule = Just newSchedule
|
||||
, scheduleModel = sm
|
||||
, queryModel = res.model
|
||||
, channelModel = cfm
|
||||
, formState = FormStateInitial
|
||||
, loading = 0
|
||||
, summary = s.summary
|
||||
}
|
||||
, Cmd.batch
|
||||
[ Cmd.map CalEventMsg sc
|
||||
, Cmd.map QueryMsg res.cmd
|
||||
, Cmd.map ChannelMsg cfc
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
init : Flags -> ChannelType -> ( Model, Cmd Msg )
|
||||
init flags ct =
|
||||
let
|
||||
initialSchedule =
|
||||
Data.CalEvent.everyMonth
|
||||
|
||||
( sm, scmd ) =
|
||||
Comp.CalEventInput.init flags initialSchedule
|
||||
|
||||
( cfm, cfc ) =
|
||||
Comp.ChannelForm.init flags ct
|
||||
in
|
||||
( { settings = Data.PeriodicQuerySettings.empty ct
|
||||
, enabled = False
|
||||
, schedule = Just initialSchedule
|
||||
, scheduleModel = sm
|
||||
, queryModel = Comp.PowerSearchInput.init
|
||||
, channelModel = cfm
|
||||
, formState = FormStateInitial
|
||||
, loading = 0
|
||||
, summary = Nothing
|
||||
}
|
||||
, Cmd.batch
|
||||
[ Cmd.map CalEventMsg scmd
|
||||
, Cmd.map ChannelMsg cfc
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
|
||||
--- Update
|
||||
|
||||
|
||||
type alias UpdateResult =
|
||||
{ model : Model
|
||||
, action : Action
|
||||
, cmd : Cmd Msg
|
||||
, sub : Sub Msg
|
||||
}
|
||||
|
||||
|
||||
makeSettings : Model -> Result ValidateError PeriodicQuerySettings
|
||||
makeSettings model =
|
||||
let
|
||||
prev =
|
||||
model.settings
|
||||
|
||||
schedule_ =
|
||||
case model.schedule of
|
||||
Just s ->
|
||||
Ok s
|
||||
|
||||
Nothing ->
|
||||
Err ValidateCalEventInvalid
|
||||
|
||||
queryString =
|
||||
Result.fromMaybe ValidateQueryStringRequired model.queryModel.input
|
||||
|
||||
channelM =
|
||||
Result.fromMaybe
|
||||
ValidateChannelRequired
|
||||
(Comp.ChannelForm.getChannel model.channelModel)
|
||||
|
||||
make timer channel query =
|
||||
{ prev
|
||||
| enabled = model.enabled
|
||||
, schedule = Data.CalEvent.makeEvent timer
|
||||
, summary = model.summary
|
||||
, channel = channel
|
||||
, query = query
|
||||
}
|
||||
in
|
||||
Result.map3 make
|
||||
schedule_
|
||||
channelM
|
||||
queryString
|
||||
|
||||
|
||||
withValidSettings : (PeriodicQuerySettings -> Action) -> Model -> UpdateResult
|
||||
withValidSettings mkcmd model =
|
||||
case makeSettings model of
|
||||
Ok set ->
|
||||
{ model = { model | formState = FormStateInitial }
|
||||
, action = mkcmd set
|
||||
, cmd = Cmd.none
|
||||
, sub = Sub.none
|
||||
}
|
||||
|
||||
Err errs ->
|
||||
{ model = { model | formState = FormStateInvalid errs }
|
||||
, action = NoAction
|
||||
, cmd = Cmd.none
|
||||
, sub = Sub.none
|
||||
}
|
||||
|
||||
|
||||
update : Flags -> Msg -> Model -> UpdateResult
|
||||
update flags msg model =
|
||||
case msg of
|
||||
CalEventMsg lmsg ->
|
||||
let
|
||||
( cm, cc, cs ) =
|
||||
Comp.CalEventInput.update flags
|
||||
model.schedule
|
||||
lmsg
|
||||
model.scheduleModel
|
||||
in
|
||||
{ model =
|
||||
{ model
|
||||
| schedule = cs
|
||||
, scheduleModel = cm
|
||||
, formState = FormStateInitial
|
||||
}
|
||||
, action = NoAction
|
||||
, cmd = Cmd.map CalEventMsg cc
|
||||
, sub = Sub.none
|
||||
}
|
||||
|
||||
QueryMsg lm ->
|
||||
let
|
||||
res =
|
||||
Comp.PowerSearchInput.update lm model.queryModel
|
||||
in
|
||||
{ model = { model | queryModel = res.model }
|
||||
, action = NoAction
|
||||
, cmd = Cmd.map QueryMsg res.cmd
|
||||
, sub = Sub.map QueryMsg res.subs
|
||||
}
|
||||
|
||||
ChannelMsg lm ->
|
||||
let
|
||||
( cfm, cfc ) =
|
||||
Comp.ChannelForm.update flags lm model.channelModel
|
||||
in
|
||||
{ model = { model | channelModel = cfm }
|
||||
, action = NoAction
|
||||
, cmd = Cmd.map ChannelMsg cfc
|
||||
, sub = Sub.none
|
||||
}
|
||||
|
||||
ToggleEnabled ->
|
||||
{ model =
|
||||
{ model
|
||||
| enabled = not model.enabled
|
||||
, formState = FormStateInitial
|
||||
}
|
||||
, action = NoAction
|
||||
, cmd = Cmd.none
|
||||
, sub = Sub.none
|
||||
}
|
||||
|
||||
Submit ->
|
||||
withValidSettings
|
||||
SubmitAction
|
||||
model
|
||||
|
||||
StartOnce ->
|
||||
withValidSettings
|
||||
StartOnceAction
|
||||
model
|
||||
|
||||
Cancel ->
|
||||
{ model = model
|
||||
, action = CancelAction
|
||||
, cmd = Cmd.none
|
||||
, sub = Sub.none
|
||||
}
|
||||
|
||||
RequestDelete ->
|
||||
{ model = model
|
||||
, action = NoAction
|
||||
, cmd = Cmd.none
|
||||
, sub = Sub.none
|
||||
}
|
||||
|
||||
SetSummary str ->
|
||||
{ model = { model | summary = Util.Maybe.fromString str }
|
||||
, action = NoAction
|
||||
, cmd = Cmd.none
|
||||
, sub = Sub.none
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- View2
|
||||
|
||||
|
||||
isFormError : Model -> Bool
|
||||
isFormError model =
|
||||
case model.formState of
|
||||
FormStateInitial ->
|
||||
False
|
||||
|
||||
_ ->
|
||||
True
|
||||
|
||||
|
||||
isFormSuccess : Model -> Bool
|
||||
isFormSuccess model =
|
||||
not (isFormError model)
|
||||
|
||||
|
||||
view : Texts -> String -> UiSettings -> Model -> Html Msg
|
||||
view texts extraClasses settings model =
|
||||
let
|
||||
startOnceBtn =
|
||||
MB.SecondaryButton
|
||||
{ tagger = StartOnce
|
||||
, label = texts.startOnce
|
||||
, title = texts.startTaskNow
|
||||
, icon = Just "fa fa-play"
|
||||
}
|
||||
|
||||
queryInput =
|
||||
div
|
||||
[ class "relative flex flex-grow flex-row" ]
|
||||
[ Html.map QueryMsg
|
||||
(Comp.PowerSearchInput.viewInput
|
||||
{ placeholder = texts.queryLabel
|
||||
, extraAttrs = []
|
||||
}
|
||||
model.queryModel
|
||||
)
|
||||
, Html.map QueryMsg
|
||||
(Comp.PowerSearchInput.viewResult [] model.queryModel)
|
||||
]
|
||||
|
||||
formHeader txt =
|
||||
h2 [ class S.formHeader, class "mt-2" ]
|
||||
[ text txt
|
||||
]
|
||||
in
|
||||
div
|
||||
[ class "flex flex-col md:relative"
|
||||
, class extraClasses
|
||||
]
|
||||
[ B.loadingDimmer
|
||||
{ active = model.loading > 0
|
||||
, label = texts.basics.loading
|
||||
}
|
||||
, MB.view
|
||||
{ start =
|
||||
[ MB.PrimaryButton
|
||||
{ tagger = Submit
|
||||
, label = texts.basics.submit
|
||||
, title = texts.basics.submitThisForm
|
||||
, icon = Just "fa fa-save"
|
||||
}
|
||||
, MB.SecondaryButton
|
||||
{ tagger = Cancel
|
||||
, label = texts.basics.backToList
|
||||
, title = texts.basics.backToList
|
||||
, icon = Just "fa fa-arrow-left"
|
||||
}
|
||||
]
|
||||
, end =
|
||||
if model.settings.id /= "" then
|
||||
[ startOnceBtn
|
||||
, MB.DeleteButton
|
||||
{ tagger = RequestDelete
|
||||
, label = texts.basics.delete
|
||||
, title = texts.deleteThisTask
|
||||
, icon = Just "fa fa-trash"
|
||||
}
|
||||
]
|
||||
|
||||
else
|
||||
[ startOnceBtn
|
||||
]
|
||||
, rootClasses = "mb-4"
|
||||
}
|
||||
, div
|
||||
[ classList
|
||||
[ ( S.successMessage, isFormSuccess model )
|
||||
, ( S.errorMessage, isFormError model )
|
||||
, ( "hidden", model.formState == FormStateInitial )
|
||||
]
|
||||
, class "mb-4"
|
||||
]
|
||||
[ case model.formState of
|
||||
FormStateInitial ->
|
||||
text ""
|
||||
|
||||
FormStateHttpError err ->
|
||||
text (texts.httpError err)
|
||||
|
||||
FormStateInvalid ValidateCalEventInvalid ->
|
||||
text texts.invalidCalEvent
|
||||
|
||||
FormStateInvalid ValidateChannelRequired ->
|
||||
text texts.channelRequired
|
||||
|
||||
FormStateInvalid ValidateQueryStringRequired ->
|
||||
text texts.queryStringRequired
|
||||
]
|
||||
, div [ class "mb-4" ]
|
||||
[ MB.viewItem <|
|
||||
MB.Checkbox
|
||||
{ tagger = \_ -> ToggleEnabled
|
||||
, label = texts.enableDisable
|
||||
, value = model.enabled
|
||||
, id = "notify-enabled"
|
||||
}
|
||||
]
|
||||
, div [ class "mb-4" ]
|
||||
[ label [ class S.inputLabel ]
|
||||
[ text texts.summary
|
||||
]
|
||||
, input
|
||||
[ type_ "text"
|
||||
, onInput SetSummary
|
||||
, class S.textInput
|
||||
, Maybe.withDefault "" model.summary
|
||||
|> value
|
||||
]
|
||||
[]
|
||||
, span [ class "opacity-50 text-sm" ]
|
||||
[ text texts.summaryInfo
|
||||
]
|
||||
]
|
||||
, div [ class "mb-4" ]
|
||||
[ formHeader (texts.channelHeader (Comp.ChannelForm.channelType model.channelModel))
|
||||
, Html.map ChannelMsg
|
||||
(Comp.ChannelForm.view texts.channelForm settings model.channelModel)
|
||||
]
|
||||
, div [ class "mb-4" ]
|
||||
[ formHeader texts.queryLabel
|
||||
, label
|
||||
[ for "sharequery"
|
||||
, class S.inputLabel
|
||||
]
|
||||
[ text texts.queryLabel
|
||||
, B.inputRequired
|
||||
]
|
||||
, queryInput
|
||||
]
|
||||
, div [ class "mb-4" ]
|
||||
[ formHeader texts.schedule
|
||||
, label [ class S.inputLabel ]
|
||||
[ text texts.schedule
|
||||
, B.inputRequired
|
||||
, a
|
||||
[ class "float-right"
|
||||
, class S.link
|
||||
, href "https://github.com/eikek/calev#what-are-calendar-events"
|
||||
, target "_blank"
|
||||
]
|
||||
[ i [ class "fa fa-question" ] []
|
||||
, span [ class "pl-2" ]
|
||||
[ text texts.scheduleClickForHelp
|
||||
]
|
||||
]
|
||||
]
|
||||
, Html.map CalEventMsg
|
||||
(Comp.CalEventInput.view2
|
||||
texts.calEventInput
|
||||
""
|
||||
model.schedule
|
||||
model.scheduleModel
|
||||
)
|
||||
, span [ class "opacity-50 text-sm" ]
|
||||
[ text texts.scheduleInfo
|
||||
]
|
||||
]
|
||||
]
|
102
modules/webapp/src/main/elm/Comp/PeriodicQueryTaskList.elm
Normal file
102
modules/webapp/src/main/elm/Comp/PeriodicQueryTaskList.elm
Normal file
@ -0,0 +1,102 @@
|
||||
{-
|
||||
Copyright 2020 Eike K. & Contributors
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-}
|
||||
|
||||
|
||||
module Comp.PeriodicQueryTaskList exposing
|
||||
( Action(..)
|
||||
, Model
|
||||
, Msg
|
||||
, init
|
||||
, update
|
||||
, view2
|
||||
)
|
||||
|
||||
import Comp.Basic as B
|
||||
import Data.ChannelType
|
||||
import Data.NotificationChannel
|
||||
import Data.PeriodicQuerySettings exposing (PeriodicQuerySettings)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Messages.Comp.PeriodicQueryTaskList exposing (Texts)
|
||||
import Styles as S
|
||||
import Util.Html
|
||||
|
||||
|
||||
type alias Model =
|
||||
{}
|
||||
|
||||
|
||||
type Msg
|
||||
= EditSettings PeriodicQuerySettings
|
||||
|
||||
|
||||
type Action
|
||||
= NoAction
|
||||
| EditAction PeriodicQuerySettings
|
||||
|
||||
|
||||
init : Model
|
||||
init =
|
||||
{}
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Action )
|
||||
update msg model =
|
||||
case msg of
|
||||
EditSettings settings ->
|
||||
( model, EditAction settings )
|
||||
|
||||
|
||||
|
||||
--- View2
|
||||
|
||||
|
||||
view2 : Texts -> Model -> List PeriodicQuerySettings -> Html Msg
|
||||
view2 texts _ items =
|
||||
div []
|
||||
[ table [ class S.tableMain ]
|
||||
[ thead []
|
||||
[ tr []
|
||||
[ th [ class "" ] []
|
||||
, th [ class "text-center mr-2" ]
|
||||
[ i [ class "fa fa-check" ] []
|
||||
]
|
||||
, th [ class "text-left " ] [ text texts.summary ]
|
||||
, th [ class "text-left hidden sm:table-cell mr-2" ]
|
||||
[ text texts.schedule ]
|
||||
, th [ class "text-left mr-2" ]
|
||||
[ text texts.connection ]
|
||||
]
|
||||
]
|
||||
, tbody []
|
||||
(List.map (viewItem2 texts) items)
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
viewItem2 : Texts -> PeriodicQuerySettings -> Html Msg
|
||||
viewItem2 texts item =
|
||||
tr []
|
||||
[ B.editLinkTableCell texts.basics.edit (EditSettings item)
|
||||
, td [ class "w-px whitespace-nowrap px-2 text-center" ]
|
||||
[ Util.Html.checkbox2 item.enabled
|
||||
]
|
||||
, td [ class "text-left" ]
|
||||
[ Maybe.withDefault "" item.summary
|
||||
|> text
|
||||
]
|
||||
, td [ class "text-left hidden sm:table-cell mr-2" ]
|
||||
[ code [ class "font-mono text-sm" ]
|
||||
[ text item.schedule
|
||||
]
|
||||
]
|
||||
, td [ class "text-left py-4 md:py-2" ]
|
||||
[ Data.NotificationChannel.channelType item.channel
|
||||
|> Maybe.map Data.ChannelType.asString
|
||||
|> Maybe.withDefault "-"
|
||||
|> text
|
||||
]
|
||||
]
|
324
modules/webapp/src/main/elm/Comp/PeriodicQueryTaskManage.elm
Normal file
324
modules/webapp/src/main/elm/Comp/PeriodicQueryTaskManage.elm
Normal file
@ -0,0 +1,324 @@
|
||||
{-
|
||||
Copyright 2020 Eike K. & Contributors
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-}
|
||||
|
||||
|
||||
module Comp.PeriodicQueryTaskManage exposing
|
||||
( Model
|
||||
, Msg
|
||||
, init
|
||||
, update
|
||||
, view
|
||||
)
|
||||
|
||||
import Api
|
||||
import Api.Model.BasicResult exposing (BasicResult)
|
||||
import Comp.ChannelMenu
|
||||
import Comp.MenuBar as MB
|
||||
import Comp.PeriodicQueryTaskForm
|
||||
import Comp.PeriodicQueryTaskList
|
||||
import Data.ChannelType exposing (ChannelType)
|
||||
import Data.Flags exposing (Flags)
|
||||
import Data.PeriodicQuerySettings exposing (PeriodicQuerySettings)
|
||||
import Data.UiSettings exposing (UiSettings)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (onClick)
|
||||
import Http
|
||||
import Messages.Comp.PeriodicQueryTaskManage exposing (Texts)
|
||||
import Styles as S
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ listModel : Comp.PeriodicQueryTaskList.Model
|
||||
, detailModel : Maybe Comp.PeriodicQueryTaskForm.Model
|
||||
, items : List PeriodicQuerySettings
|
||||
, formState : FormState
|
||||
, channelMenuOpen : Bool
|
||||
}
|
||||
|
||||
|
||||
type SubmitType
|
||||
= SubmitDelete
|
||||
| SubmitUpdate
|
||||
| SubmitCreate
|
||||
| SubmitStartOnce
|
||||
|
||||
|
||||
type FormState
|
||||
= FormStateInitial
|
||||
| FormHttpError Http.Error
|
||||
| FormSubmitSuccessful SubmitType
|
||||
| FormSubmitFailed String
|
||||
|
||||
|
||||
type Msg
|
||||
= ListMsg Comp.PeriodicQueryTaskList.Msg
|
||||
| DetailMsg Comp.PeriodicQueryTaskForm.Msg
|
||||
| GetDataResp (Result Http.Error (List PeriodicQuerySettings))
|
||||
| NewTaskInit ChannelType
|
||||
| SubmitResp SubmitType (Result Http.Error BasicResult)
|
||||
| ToggleChannelMenu
|
||||
|
||||
|
||||
initModel : Model
|
||||
initModel =
|
||||
{ listModel = Comp.PeriodicQueryTaskList.init
|
||||
, detailModel = Nothing
|
||||
, items = []
|
||||
, formState = FormStateInitial
|
||||
, channelMenuOpen = False
|
||||
}
|
||||
|
||||
|
||||
initCmd : Flags -> Cmd Msg
|
||||
initCmd flags =
|
||||
Api.getPeriodicQuery flags GetDataResp
|
||||
|
||||
|
||||
init : Flags -> ( Model, Cmd Msg )
|
||||
init flags =
|
||||
( initModel, initCmd flags )
|
||||
|
||||
|
||||
|
||||
--- Update
|
||||
|
||||
|
||||
update : Flags -> Msg -> Model -> ( Model, Cmd Msg, Sub Msg )
|
||||
update flags msg model =
|
||||
case msg of
|
||||
GetDataResp (Ok items) ->
|
||||
( { model
|
||||
| items = items
|
||||
, formState = FormStateInitial
|
||||
}
|
||||
, Cmd.none
|
||||
, Sub.none
|
||||
)
|
||||
|
||||
GetDataResp (Err err) ->
|
||||
( { model | formState = FormHttpError err }
|
||||
, Cmd.none
|
||||
, Sub.none
|
||||
)
|
||||
|
||||
ListMsg lm ->
|
||||
let
|
||||
( mm, action ) =
|
||||
Comp.PeriodicQueryTaskList.update lm model.listModel
|
||||
|
||||
( detail, cmd ) =
|
||||
case action of
|
||||
Comp.PeriodicQueryTaskList.NoAction ->
|
||||
( Nothing, Cmd.none )
|
||||
|
||||
Comp.PeriodicQueryTaskList.EditAction settings ->
|
||||
let
|
||||
( dm, dc ) =
|
||||
Comp.PeriodicQueryTaskForm.initWith flags settings
|
||||
in
|
||||
( Just dm, Cmd.map DetailMsg dc )
|
||||
in
|
||||
( { model
|
||||
| listModel = mm
|
||||
, detailModel = detail
|
||||
}
|
||||
, cmd
|
||||
, Sub.none
|
||||
)
|
||||
|
||||
DetailMsg lm ->
|
||||
case model.detailModel of
|
||||
Just dm ->
|
||||
let
|
||||
--( mm, action, mc ) =
|
||||
result =
|
||||
Comp.PeriodicQueryTaskForm.update flags lm dm
|
||||
|
||||
( model_, cmd_ ) =
|
||||
case result.action of
|
||||
Comp.PeriodicQueryTaskForm.NoAction ->
|
||||
( { model
|
||||
| detailModel = Just result.model
|
||||
, formState = FormStateInitial
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
Comp.PeriodicQueryTaskForm.SubmitAction settings ->
|
||||
( { model
|
||||
| detailModel = Just result.model
|
||||
, formState = FormStateInitial
|
||||
}
|
||||
, if settings.id == "" then
|
||||
Api.createPeriodicQuery flags settings (SubmitResp SubmitCreate)
|
||||
|
||||
else
|
||||
Api.updatePeriodicQuery flags settings (SubmitResp SubmitUpdate)
|
||||
)
|
||||
|
||||
Comp.PeriodicQueryTaskForm.CancelAction ->
|
||||
( { model
|
||||
| detailModel = Nothing
|
||||
, formState = FormStateInitial
|
||||
}
|
||||
, initCmd flags
|
||||
)
|
||||
|
||||
Comp.PeriodicQueryTaskForm.StartOnceAction settings ->
|
||||
( { model
|
||||
| detailModel = Just result.model
|
||||
, formState = FormStateInitial
|
||||
}
|
||||
, Api.startOncePeriodicQuery flags settings (SubmitResp SubmitStartOnce)
|
||||
)
|
||||
|
||||
Comp.PeriodicQueryTaskForm.DeleteAction id ->
|
||||
( { model
|
||||
| detailModel = Just result.model
|
||||
, formState = FormStateInitial
|
||||
}
|
||||
, Api.deletePeriodicQueryTask flags id (SubmitResp SubmitDelete)
|
||||
)
|
||||
in
|
||||
( model_
|
||||
, Cmd.batch
|
||||
[ Cmd.map DetailMsg result.cmd
|
||||
, cmd_
|
||||
]
|
||||
, Sub.map DetailMsg result.sub
|
||||
)
|
||||
|
||||
Nothing ->
|
||||
( model, Cmd.none, Sub.none )
|
||||
|
||||
NewTaskInit ct ->
|
||||
let
|
||||
( mm, mc ) =
|
||||
Comp.PeriodicQueryTaskForm.init flags ct
|
||||
in
|
||||
( { model | detailModel = Just mm, channelMenuOpen = False }, Cmd.map DetailMsg mc, Sub.none )
|
||||
|
||||
SubmitResp submitType (Ok res) ->
|
||||
( { model
|
||||
| formState =
|
||||
if res.success then
|
||||
FormSubmitSuccessful submitType
|
||||
|
||||
else
|
||||
FormSubmitFailed res.message
|
||||
, detailModel =
|
||||
if submitType == SubmitDelete then
|
||||
Nothing
|
||||
|
||||
else
|
||||
model.detailModel
|
||||
}
|
||||
, if submitType == SubmitDelete then
|
||||
initCmd flags
|
||||
|
||||
else
|
||||
Cmd.none
|
||||
, Sub.none
|
||||
)
|
||||
|
||||
SubmitResp _ (Err err) ->
|
||||
( { model | formState = FormHttpError err }
|
||||
, Cmd.none
|
||||
, Sub.none
|
||||
)
|
||||
|
||||
ToggleChannelMenu ->
|
||||
( { model | channelMenuOpen = not model.channelMenuOpen }, Cmd.none, Sub.none )
|
||||
|
||||
|
||||
|
||||
--- View2
|
||||
|
||||
|
||||
view : Texts -> UiSettings -> Model -> Html Msg
|
||||
view texts settings model =
|
||||
div [ class "flex flex-col" ]
|
||||
(div
|
||||
[ classList
|
||||
[ ( S.errorMessage, model.formState /= FormStateInitial )
|
||||
, ( S.successMessage, isSuccess model.formState )
|
||||
, ( "hidden", model.formState == FormStateInitial )
|
||||
]
|
||||
, class "mb-2"
|
||||
]
|
||||
[ case model.formState of
|
||||
FormStateInitial ->
|
||||
text ""
|
||||
|
||||
FormSubmitSuccessful SubmitCreate ->
|
||||
text texts.taskCreated
|
||||
|
||||
FormSubmitSuccessful SubmitUpdate ->
|
||||
text texts.taskUpdated
|
||||
|
||||
FormSubmitSuccessful SubmitStartOnce ->
|
||||
text texts.taskStarted
|
||||
|
||||
FormSubmitSuccessful SubmitDelete ->
|
||||
text texts.taskDeleted
|
||||
|
||||
FormSubmitFailed m ->
|
||||
text m
|
||||
|
||||
FormHttpError err ->
|
||||
text (texts.httpError err)
|
||||
]
|
||||
:: (case model.detailModel of
|
||||
Just msett ->
|
||||
viewForm2 texts settings msett
|
||||
|
||||
Nothing ->
|
||||
viewList2 texts model
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
isSuccess : FormState -> Bool
|
||||
isSuccess state =
|
||||
case state of
|
||||
FormSubmitSuccessful _ ->
|
||||
True
|
||||
|
||||
_ ->
|
||||
False
|
||||
|
||||
|
||||
viewForm2 : Texts -> UiSettings -> Comp.PeriodicQueryTaskForm.Model -> List (Html Msg)
|
||||
viewForm2 texts settings model =
|
||||
[ Html.map DetailMsg
|
||||
(Comp.PeriodicQueryTaskForm.view texts.notificationForm "flex flex-col" settings model)
|
||||
]
|
||||
|
||||
|
||||
viewList2 : Texts -> Model -> List (Html Msg)
|
||||
viewList2 texts model =
|
||||
let
|
||||
menuModel =
|
||||
{ menuOpen = model.channelMenuOpen
|
||||
, toggleMenu = ToggleChannelMenu
|
||||
, menuLabel = texts.newTask
|
||||
, onItem = NewTaskInit
|
||||
}
|
||||
in
|
||||
[ MB.view
|
||||
{ start = []
|
||||
, end =
|
||||
[ Comp.ChannelMenu.channelMenu texts.channelType menuModel
|
||||
]
|
||||
, rootClasses = "mb-4"
|
||||
}
|
||||
, Html.map ListMsg
|
||||
(Comp.PeriodicQueryTaskList.view2 texts.notificationTable
|
||||
model.listModel
|
||||
model.items
|
||||
)
|
||||
]
|
@ -287,7 +287,8 @@ viewForm2 texts flags settings model =
|
||||
viewList2 : Texts -> Model -> List (Html Msg)
|
||||
viewList2 texts model =
|
||||
[ MB.view
|
||||
{ start =
|
||||
{ start = []
|
||||
, end =
|
||||
[ MB.PrimaryButton
|
||||
{ tagger = NewTask
|
||||
, label = texts.newTask
|
||||
@ -295,7 +296,6 @@ viewList2 texts model =
|
||||
, title = texts.createNewTask
|
||||
}
|
||||
]
|
||||
, end = []
|
||||
, rootClasses = "mb-4"
|
||||
}
|
||||
, Html.map ListMsg
|
||||
|
Reference in New Issue
Block a user