Add support for more generic notification

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

View File

@ -10,6 +10,7 @@
"CurrySoftware/elm-datepicker": "4.0.0",
"NoRedInk/elm-json-decode-pipeline": "1.0.0",
"NoRedInk/elm-simple-fuzzy": "1.0.3",
"ThinkAlexandria/elm-pretty-print-json": "1.0.1",
"elm/browser": "1.0.2",
"elm/core": "1.0.5",
"elm/file": "1.0.5",
@ -35,9 +36,11 @@
"elm/parser": "1.1.0",
"elm/regex": "1.0.0",
"elm/virtual-dom": "1.0.2",
"elm-community/basics-extra": "4.1.0",
"elm-community/list-extra": "8.2.4",
"folkertdev/elm-flate": "2.0.4",
"justgook/elm-image": "4.0.0"
"justgook/elm-image": "4.0.0",
"the-sett/elm-pretty-printer": "2.2.3"
}
},
"test-dependencies": {

View File

@ -21,10 +21,12 @@ module Api exposing
, checkCalEvent
, confirmMultiple
, confirmOtp
, createHook
, createImapSettings
, createMailSettings
, createNewFolder
, createNotifyDueItems
, createPeriodicQuery
, createScanMailbox
, deleteAllItems
, deleteAttachment
@ -34,11 +36,13 @@ module Api exposing
, deleteCustomValueMultiple
, deleteEquip
, deleteFolder
, deleteHook
, deleteImapSettings
, deleteItem
, deleteMailSettings
, deleteNotifyDueItems
, deleteOrg
, deletePeriodicQueryTask
, deletePerson
, deleteScanMailbox
, deleteShare
@ -58,6 +62,7 @@ module Api exposing
, getEquipments
, getFolderDetail
, getFolders
, getHooks
, getImapSettings
, getInsights
, getItemProposals
@ -69,6 +74,7 @@ module Api exposing
, getOrgLight
, getOrganizations
, getOtpState
, getPeriodicQuery
, getPersonFull
, getPersons
, getPersonsLight
@ -113,6 +119,7 @@ module Api exposing
, reprocessMultiple
, restoreAllItems
, restoreItem
, sampleEvent
, saveClientSettings
, searchShare
, searchShareStats
@ -150,18 +157,24 @@ module Api exposing
, startClassifier
, startEmptyTrash
, startOnceNotifyDueItems
, startOncePeriodicQuery
, startOnceScanMailbox
, startReIndex
, submitNotifyDueItems
, submitPeriodicQuery
, testHook
, toggleTags
, twoFactor
, unconfirmMultiple
, updateHook
, updateNotifyDueItems
, updatePeriodicQuery
, updateScanMailbox
, updateShare
, upload
, uploadAmend
, uploadSingle
, verifyJsonFilter
, verifyShare
, versionInfo
)
@ -208,8 +221,8 @@ import Api.Model.JobQueueState exposing (JobQueueState)
import Api.Model.MoveAttachment exposing (MoveAttachment)
import Api.Model.NewCustomField exposing (NewCustomField)
import Api.Model.NewFolder exposing (NewFolder)
import Api.Model.NotificationSettings exposing (NotificationSettings)
import Api.Model.NotificationSettingsList exposing (NotificationSettingsList)
import Api.Model.NotificationChannelTestResult exposing (NotificationChannelTestResult)
import Api.Model.NotificationSampleEventReq exposing (NotificationSampleEventReq)
import Api.Model.OptionalDate exposing (OptionalDate)
import Api.Model.OptionalId exposing (OptionalId)
import Api.Model.OptionalText exposing (OptionalText)
@ -239,6 +252,7 @@ import Api.Model.SourceAndTags exposing (SourceAndTags)
import Api.Model.SourceList exposing (SourceList)
import Api.Model.SourceTagIn
import Api.Model.StringList exposing (StringList)
import Api.Model.StringValue exposing (StringValue)
import Api.Model.Tag exposing (Tag)
import Api.Model.TagCloud exposing (TagCloud)
import Api.Model.TagList exposing (TagList)
@ -249,9 +263,13 @@ import Api.Model.VersionInfo exposing (VersionInfo)
import Data.ContactType exposing (ContactType)
import Data.CustomFieldOrder exposing (CustomFieldOrder)
import Data.EquipmentOrder exposing (EquipmentOrder)
import Data.EventType exposing (EventType)
import Data.Flags exposing (Flags)
import Data.FolderOrder exposing (FolderOrder)
import Data.NotificationHook exposing (NotificationHook)
import Data.OrganizationOrder exposing (OrganizationOrder)
import Data.PeriodicDueItemsSettings exposing (PeriodicDueItemsSettings)
import Data.PeriodicQuerySettings exposing (PeriodicQuerySettings)
import Data.PersonOrder exposing (PersonOrder)
import Data.Priority exposing (Priority)
import Data.TagOrder exposing (TagOrder)
@ -571,68 +589,153 @@ deleteNotifyDueItems flags id receive =
startOnceNotifyDueItems :
Flags
-> NotificationSettings
-> PeriodicDueItemsSettings
-> (Result Http.Error BasicResult -> msg)
-> Cmd msg
startOnceNotifyDueItems flags settings receive =
Http2.authPost
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/notifydueitems/startonce"
, account = getAccount flags
, body = Http.jsonBody (Api.Model.NotificationSettings.encode settings)
, body = Http.jsonBody (Data.PeriodicDueItemsSettings.encode settings)
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
}
updateNotifyDueItems :
Flags
-> NotificationSettings
-> PeriodicDueItemsSettings
-> (Result Http.Error BasicResult -> msg)
-> Cmd msg
updateNotifyDueItems flags settings receive =
Http2.authPut
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/notifydueitems"
, account = getAccount flags
, body = Http.jsonBody (Api.Model.NotificationSettings.encode settings)
, body = Http.jsonBody (Data.PeriodicDueItemsSettings.encode settings)
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
}
createNotifyDueItems :
Flags
-> NotificationSettings
-> PeriodicDueItemsSettings
-> (Result Http.Error BasicResult -> msg)
-> Cmd msg
createNotifyDueItems flags settings receive =
Http2.authPost
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/notifydueitems"
, account = getAccount flags
, body = Http.jsonBody (Api.Model.NotificationSettings.encode settings)
, body = Http.jsonBody (Data.PeriodicDueItemsSettings.encode settings)
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
}
getNotifyDueItems :
Flags
-> (Result Http.Error NotificationSettingsList -> msg)
-> (Result Http.Error (List PeriodicDueItemsSettings) -> msg)
-> Cmd msg
getNotifyDueItems flags receive =
Http2.authGet
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/notifydueitems"
, account = getAccount flags
, expect = Http.expectJson receive Api.Model.NotificationSettingsList.decoder
, expect = Http.expectJson receive (JsonDecode.list Data.PeriodicDueItemsSettings.decoder)
}
submitNotifyDueItems :
Flags
-> NotificationSettings
-> PeriodicDueItemsSettings
-> (Result Http.Error BasicResult -> msg)
-> Cmd msg
submitNotifyDueItems flags settings receive =
Http2.authPost
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/notifydueitems"
, account = getAccount flags
, body = Http.jsonBody (Api.Model.NotificationSettings.encode settings)
, body = Http.jsonBody (Data.PeriodicDueItemsSettings.encode settings)
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
}
--- PeriodicQueryTask
deletePeriodicQueryTask :
Flags
-> String
-> (Result Http.Error BasicResult -> msg)
-> Cmd msg
deletePeriodicQueryTask flags id receive =
Http2.authDelete
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/periodicquery/" ++ id
, account = getAccount flags
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
}
startOncePeriodicQuery :
Flags
-> PeriodicQuerySettings
-> (Result Http.Error BasicResult -> msg)
-> Cmd msg
startOncePeriodicQuery flags settings receive =
Http2.authPost
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/periodicquery/startonce"
, account = getAccount flags
, body = Http.jsonBody (Data.PeriodicQuerySettings.encode settings)
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
}
updatePeriodicQuery :
Flags
-> PeriodicQuerySettings
-> (Result Http.Error BasicResult -> msg)
-> Cmd msg
updatePeriodicQuery flags settings receive =
Http2.authPut
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/periodicquery"
, account = getAccount flags
, body = Http.jsonBody (Data.PeriodicQuerySettings.encode settings)
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
}
createPeriodicQuery :
Flags
-> PeriodicQuerySettings
-> (Result Http.Error BasicResult -> msg)
-> Cmd msg
createPeriodicQuery flags settings receive =
Http2.authPost
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/periodicquery"
, account = getAccount flags
, body = Http.jsonBody (Data.PeriodicQuerySettings.encode settings)
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
}
getPeriodicQuery :
Flags
-> (Result Http.Error (List PeriodicQuerySettings) -> msg)
-> Cmd msg
getPeriodicQuery flags receive =
Http2.authGet
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/periodicquery"
, account = getAccount flags
, expect = Http.expectJson receive (JsonDecode.list Data.PeriodicQuerySettings.decoder)
}
submitPeriodicQuery :
Flags
-> PeriodicQuerySettings
-> (Result Http.Error BasicResult -> msg)
-> Cmd msg
submitPeriodicQuery flags settings receive =
Http2.authPost
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/periodicquery"
, account = getAccount flags
, body = Http.jsonBody (Data.PeriodicQuerySettings.encode settings)
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
}
@ -2353,6 +2456,88 @@ shareFileURL attachId =
--- NotificationHook
getHooks : Flags -> (Result Http.Error (List NotificationHook) -> msg) -> Cmd msg
getHooks flags receive =
Http2.authGet
{ url = flags.config.baseUrl ++ "/api/v1/sec/notification/hook"
, account = getAccount flags
, expect = Http.expectJson receive (JsonDecode.list Data.NotificationHook.decoder)
}
deleteHook : Flags -> String -> (Result Http.Error BasicResult -> msg) -> Cmd msg
deleteHook flags id receive =
Http2.authDelete
{ url = flags.config.baseUrl ++ "/api/v1/sec/notification/hook/" ++ id
, account = getAccount flags
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
}
createHook : Flags -> NotificationHook -> (Result Http.Error BasicResult -> msg) -> Cmd msg
createHook flags hook receive =
Http2.authPost
{ url = flags.config.baseUrl ++ "/api/v1/sec/notification/hook"
, account = getAccount flags
, body = Http.jsonBody (Data.NotificationHook.encode hook)
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
}
updateHook : Flags -> NotificationHook -> (Result Http.Error BasicResult -> msg) -> Cmd msg
updateHook flags hook receive =
Http2.authPut
{ url = flags.config.baseUrl ++ "/api/v1/sec/notification/hook"
, account = getAccount flags
, body = Http.jsonBody (Data.NotificationHook.encode hook)
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
}
sampleEvent : Flags -> EventType -> (Result Http.Error String -> msg) -> Cmd msg
sampleEvent flags evt receive =
Http2.authPost
{ url = flags.config.baseUrl ++ "/api/v1/sec/notification/event/sample"
, account = getAccount flags
, body =
Http.jsonBody
(Api.Model.NotificationSampleEventReq.encode
(NotificationSampleEventReq <|
Data.EventType.asString evt
)
)
, expect = Http.expectString receive
}
testHook :
Flags
-> NotificationHook
-> (Result Http.Error NotificationChannelTestResult -> msg)
-> Cmd msg
testHook flags hook receive =
Http2.authPost
{ url = flags.config.baseUrl ++ "/api/v1/sec/notification/hook/sendTestEvent"
, account = getAccount flags
, body = Http.jsonBody (Data.NotificationHook.encode hook)
, expect = Http.expectJson receive Api.Model.NotificationChannelTestResult.decoder
}
verifyJsonFilter : Flags -> String -> (Result Http.Error BasicResult -> msg) -> Cmd msg
verifyJsonFilter flags query receive =
Http2.authPost
{ url = flags.config.baseUrl ++ "/api/v1/sec/notification/hook/verifyJsonFilter"
, account = getAccount flags
, body = Http.jsonBody (Api.Model.StringValue.encode (StringValue query))
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
}
--- Helper

View 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" ] []

View 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)
]
}

View File

@ -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

View File

@ -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"

View File

@ -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
]
]

View File

@ -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
)

View 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)
]
]

View File

@ -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)

View File

@ -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

View 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
]
[]
]
]

View 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
)
]
]

View 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
)
]

View 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
]
]

View 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
]
[]
]
]

View 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
]
]
]

View 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
]
[]
]
]

View 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" ] []
]

View 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
]
]
]

View 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
]
]

View 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
)
]

View File

@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,6 +14,7 @@ import App.View2
import Browser exposing (Document)
import Browser.Navigation exposing (Key)
import Data.Flags exposing (Flags)
import Data.NotificationChannel
import Data.UiSettings
import Html exposing (..)
import Html.Attributes exposing (..)

View File

@ -0,0 +1,47 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.ChannelForm exposing
( Texts
, de
, gb
)
import Messages.Basics
import Messages.Comp.NotificationGotifyForm
import Messages.Comp.NotificationHttpForm
import Messages.Comp.NotificationMailForm
import Messages.Comp.NotificationMatrixForm
type alias Texts =
{ basics : Messages.Basics.Texts
, matrixForm : Messages.Comp.NotificationMatrixForm.Texts
, gotifyForm : Messages.Comp.NotificationGotifyForm.Texts
, mailForm : Messages.Comp.NotificationMailForm.Texts
, httpForm : Messages.Comp.NotificationHttpForm.Texts
}
gb : Texts
gb =
{ basics = Messages.Basics.gb
, matrixForm = Messages.Comp.NotificationMatrixForm.gb
, gotifyForm = Messages.Comp.NotificationGotifyForm.gb
, mailForm = Messages.Comp.NotificationMailForm.gb
, httpForm = Messages.Comp.NotificationHttpForm.gb
}
de : Texts
de =
{ basics = Messages.Basics.de
, matrixForm = Messages.Comp.NotificationMatrixForm.de
, gotifyForm = Messages.Comp.NotificationGotifyForm.de
, mailForm = Messages.Comp.NotificationMailForm.de
, httpForm = Messages.Comp.NotificationHttpForm.de
}

View File

@ -5,7 +5,7 @@
-}
module Messages.Comp.NotificationForm exposing
module Messages.Comp.DueItemsTaskForm exposing
( Texts
, de
, gb
@ -14,25 +14,23 @@ module Messages.Comp.NotificationForm exposing
import Http
import Messages.Basics
import Messages.Comp.CalEventInput
import Messages.Comp.ChannelForm
import Messages.Comp.HttpError
import Messages.Data.ChannelType
type alias Texts =
{ basics : Messages.Basics.Texts
, calEventInput : Messages.Comp.CalEventInput.Texts
, httpError : Http.Error -> String
, channelForm : Messages.Comp.ChannelForm.Texts
, reallyDeleteTask : String
, startOnce : String
, startTaskNow : String
, selectConnection : String
, deleteThisTask : String
, enableDisable : String
, summary : String
, summaryInfo : String
, sendVia : String
, sendViaInfo : String
, recipients : String
, recipientsInfo : String
, tagsInclude : String
, tagsIncludeInfo : String
, tagsExclude : String
@ -48,6 +46,9 @@ type alias Texts =
, invalidCalEvent : String
, remindDaysRequired : String
, recipientsRequired : String
, queryLabel : String
, channelRequired : String
, channelHeader : Messages.Data.ChannelType.Texts
}
@ -56,18 +57,14 @@ gb =
{ basics = Messages.Basics.gb
, calEventInput = Messages.Comp.CalEventInput.gb
, httpError = Messages.Comp.HttpError.gb
, channelForm = Messages.Comp.ChannelForm.gb
, reallyDeleteTask = "Really delete this notification task?"
, startOnce = "Start Once"
, startTaskNow = "Start this task now"
, selectConnection = "Select connection..."
, deleteThisTask = "Delete this task"
, enableDisable = "Enable or disable this task."
, summary = "Summary"
, summaryInfo = "Some human readable name, only for displaying"
, sendVia = "Send via"
, sendViaInfo = "The SMTP connection to use when sending notification mails."
, recipients = "Recipient(s)"
, recipientsInfo = "One or more mail addresses, confirm each by pressing 'Return'."
, tagsInclude = "Tags Include (and)"
, tagsIncludeInfo = "Items must have all the tags specified here."
, tagsExclude = "Tags Exclude (or)"
@ -87,6 +84,9 @@ gb =
, invalidCalEvent = "The calendar event is not valid."
, remindDaysRequired = "Remind-Days is required."
, recipientsRequired = "At least one recipient is required."
, queryLabel = "Query"
, channelRequired = "A valid channel must be given."
, channelHeader = \ct -> "Connection details for " ++ Messages.Data.ChannelType.gb ct
}
@ -95,18 +95,14 @@ de =
{ basics = Messages.Basics.de
, calEventInput = Messages.Comp.CalEventInput.de
, httpError = Messages.Comp.HttpError.de
, channelForm = Messages.Comp.ChannelForm.gb
, reallyDeleteTask = "Diesen Benachrichtigungsauftrag wirklich löschen?"
, startOnce = "Jetzt starten"
, startTaskNow = "Starte den Auftrag sofort"
, selectConnection = "Verbindung auswählen"
, deleteThisTask = "Den Auftrag löschen"
, enableDisable = "Auftrag aktivieren oder deaktivieren"
, summary = "Kurzbeschreibung"
, summaryInfo = "Eine kurze lesbare Zusammenfassung, nur für die Anzeige"
, sendVia = "Senden via"
, sendViaInfo = "Die SMTP-Verbindung, die zum Senden der Benachrichtigungs-E-Mails verwendet werden soll."
, recipients = "Empfänger"
, recipientsInfo = "Eine oder mehrere E-Mail-Adressen, jede mit 'Eingabe' bestätigen."
, tagsInclude = "Tags verknüpft (&&)"
, tagsIncludeInfo = "Dokumente müssen alle diese Tags haben."
, tagsExclude = "Tags nicht verknüpft (||)"
@ -126,4 +122,7 @@ de =
, invalidCalEvent = "Das Kalenderereignis ist nicht gültig."
, remindDaysRequired = "'Fällig in Tagen' ist erforderlich."
, recipientsRequired = "Mindestens ein Empfänger muss angegeben werden."
, queryLabel = "Abfrage"
, channelRequired = "Ein Versandkanal muss angegeben werden."
, channelHeader = \ct -> "Details für " ++ Messages.Data.ChannelType.de ct
}

View File

@ -5,7 +5,7 @@
-}
module Messages.Comp.NotificationTable exposing
module Messages.Comp.DueItemsTaskList exposing
( Texts
, de
, gb

View File

@ -5,7 +5,7 @@
-}
module Messages.Comp.NotificationManage exposing
module Messages.Comp.DueItemsTaskManage exposing
( Texts
, de
, gb
@ -13,16 +13,18 @@ module Messages.Comp.NotificationManage exposing
import Http
import Messages.Basics
import Messages.Comp.DueItemsTaskForm
import Messages.Comp.DueItemsTaskList
import Messages.Comp.HttpError
import Messages.Comp.NotificationForm
import Messages.Comp.NotificationTable
import Messages.Data.ChannelType
type alias Texts =
{ basics : Messages.Basics.Texts
, notificationForm : Messages.Comp.NotificationForm.Texts
, notificationTable : Messages.Comp.NotificationTable.Texts
, notificationForm : Messages.Comp.DueItemsTaskForm.Texts
, notificationTable : Messages.Comp.DueItemsTaskList.Texts
, httpError : Http.Error -> String
, channelType : Messages.Data.ChannelType.Texts
, newTask : String
, createNewTask : String
, taskCreated : String
@ -35,9 +37,10 @@ type alias Texts =
gb : Texts
gb =
{ basics = Messages.Basics.gb
, notificationForm = Messages.Comp.NotificationForm.gb
, notificationTable = Messages.Comp.NotificationTable.gb
, notificationForm = Messages.Comp.DueItemsTaskForm.gb
, notificationTable = Messages.Comp.DueItemsTaskList.gb
, httpError = Messages.Comp.HttpError.gb
, channelType = Messages.Data.ChannelType.gb
, newTask = "New Task"
, createNewTask = "Create a new notification task"
, taskCreated = "Task created."
@ -50,9 +53,10 @@ gb =
de : Texts
de =
{ basics = Messages.Basics.de
, notificationForm = Messages.Comp.NotificationForm.de
, notificationTable = Messages.Comp.NotificationTable.de
, notificationForm = Messages.Comp.DueItemsTaskForm.de
, notificationTable = Messages.Comp.DueItemsTaskList.de
, httpError = Messages.Comp.HttpError.de
, channelType = Messages.Data.ChannelType.de
, newTask = "Neuer Auftrag"
, createNewTask = "Erstelle einen neuen Benachrichtigungsauftrag"
, taskCreated = "Auftrag erstellt."

View File

@ -0,0 +1,40 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.EventSample exposing
( Texts
, de
, gb
)
import Data.EventType exposing (EventType)
import Http
import Messages.Comp.HttpError
import Messages.Data.EventType
type alias Texts =
{ eventType : EventType -> Messages.Data.EventType.Texts
, httpError : Http.Error -> String
, selectEvent : String
}
gb : Texts
gb =
{ eventType = Messages.Data.EventType.gb
, httpError = Messages.Comp.HttpError.gb
, selectEvent = "Select event"
}
de : Texts
de =
{ eventType = Messages.Data.EventType.de
, httpError = Messages.Comp.HttpError.de
, selectEvent = "Ereignis wählen"
}

View File

@ -0,0 +1,37 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.NotificationGotifyForm exposing
( Texts
, de
, gb
)
import Messages.Basics
type alias Texts =
{ basics : Messages.Basics.Texts
, gotifyUrl : String
, appKey : String
}
gb : Texts
gb =
{ basics = Messages.Basics.gb
, gotifyUrl = "Gotify URL"
, appKey = "App Key"
}
de : Texts
de =
{ basics = Messages.Basics.de
, gotifyUrl = "Gotify URL"
, appKey = "App Key"
}

View File

@ -0,0 +1,75 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.NotificationHookForm exposing
( Texts
, de
, gb
)
import Data.EventType exposing (EventType)
import Messages.Basics
import Messages.Comp.ChannelForm
import Messages.Comp.EventSample
import Messages.Data.ChannelType
import Messages.Data.EventType
type alias Texts =
{ basics : Messages.Basics.Texts
, channelForm : Messages.Comp.ChannelForm.Texts
, eventType : EventType -> Messages.Data.EventType.Texts
, eventSample : Messages.Comp.EventSample.Texts
, channelHeader : Messages.Data.ChannelType.Texts
, enableDisable : String
, eventsInfo : String
, selectEvents : String
, events : String
, samplePayload : String
, toggleAllEvents : String
, eventFilter : String
, eventFilterInfo : String
, eventFilterClickForHelp : String
}
gb : Texts
gb =
{ basics = Messages.Basics.gb
, channelForm = Messages.Comp.ChannelForm.gb
, eventType = Messages.Data.EventType.gb
, eventSample = Messages.Comp.EventSample.gb
, channelHeader = Messages.Data.ChannelType.gb
, enableDisable = "Enabled / Disabled"
, eventsInfo = "Select events that trigger this webhook"
, selectEvents = "Select"
, events = "Events"
, samplePayload = "Sample Payload"
, toggleAllEvents = "Notify on all events"
, eventFilter = "Event Filter Expression"
, eventFilterInfo = "Optional specify an expression to filter events based on their JSON structure."
, eventFilterClickForHelp = "Click here for help"
}
de : Texts
de =
{ basics = Messages.Basics.de
, channelForm = Messages.Comp.ChannelForm.de
, eventType = Messages.Data.EventType.de
, eventSample = Messages.Comp.EventSample.de
, channelHeader = Messages.Data.ChannelType.de
, enableDisable = "Aktiviert / Deaktivert"
, eventsInfo = "Wähle die Ereignisse, die diesen webhook auslösen"
, selectEvents = "Wähle"
, events = "Ereignisse"
, samplePayload = "Beispieldaten"
, toggleAllEvents = "Bei allen Ereignissen"
, eventFilter = "Ereignisfilter"
, eventFilterInfo = "Optionaler Ausdruck zum filtern von Ereignissen auf Basis ihrer JSON Struktur."
, eventFilterClickForHelp = "Klicke für Hilfe"
}

View File

@ -0,0 +1,106 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.NotificationHookManage exposing
( Texts
, de
, gb
)
import Html exposing (Html, text)
import Http
import Messages.Basics
import Messages.Comp.HttpError
import Messages.Comp.NotificationHookForm
import Messages.Comp.NotificationHookTable
import Messages.Data.ChannelType
type alias Texts =
{ basics : Messages.Basics.Texts
, notificationForm : Messages.Comp.NotificationHookForm.Texts
, notificationTable : Messages.Comp.NotificationHookTable.Texts
, httpError : Http.Error -> String
, channelType : Messages.Data.ChannelType.Texts
, newHook : String
, matrix : String
, gotify : String
, email : String
, httpRequest : String
, hookCreated : String
, hookUpdated : String
, hookStarted : String
, hookDeleted : String
, deleteThisHook : String
, reallyDeleteHook : String
, formInvalid : String
, invalidJsonFilter : String -> String
, integrate : String
, intoDocspell : String
, postRequestInfo : String
, updateWebhook : String
, addWebhook : String
, notifyEmailInfo : String
}
gb : Texts
gb =
{ basics = Messages.Basics.gb
, notificationForm = Messages.Comp.NotificationHookForm.gb
, notificationTable = Messages.Comp.NotificationHookTable.gb
, httpError = Messages.Comp.HttpError.gb
, channelType = Messages.Data.ChannelType.gb
, newHook = "New Webhook"
, matrix = "Matrix"
, gotify = "Gotify"
, email = "E-Mail"
, httpRequest = "HTTP Request"
, hookCreated = "Webhook created"
, hookUpdated = "Webhook updated"
, hookStarted = "Webhook executed"
, hookDeleted = "Webhook deleted"
, deleteThisHook = "Delete this webhook"
, reallyDeleteHook = "Really delete this webhook?"
, formInvalid = "Please fill in all required fields"
, invalidJsonFilter = \m -> "Event filter invalid: " ++ m
, integrate = "Integrate"
, intoDocspell = "into Docspell"
, postRequestInfo = "Docspell will send POST requests with JSON payload."
, updateWebhook = "Update webhook"
, addWebhook = "Add new webhook"
, notifyEmailInfo = "Get notified via e-mail."
}
de : Texts
de =
{ basics = Messages.Basics.de
, notificationForm = Messages.Comp.NotificationHookForm.de
, notificationTable = Messages.Comp.NotificationHookTable.de
, httpError = Messages.Comp.HttpError.de
, channelType = Messages.Data.ChannelType.de
, newHook = "Neuer Webhook"
, matrix = "Matrix"
, gotify = "Gotify"
, email = "E-Mail"
, httpRequest = "HTTP Request"
, hookCreated = "Webhook erstellt"
, hookUpdated = "Webhook aktualisiert"
, hookStarted = "Webhook ausgeführt"
, hookDeleted = "Webhook gelöscht"
, deleteThisHook = "Diesen Webhook löschen"
, reallyDeleteHook = "Den webhook wirklich löschen?"
, formInvalid = "Bitte alle erforderlichen Felder ausfüllen"
, invalidJsonFilter = \m -> "Ereignisfilter ist falsch: " ++ m
, integrate = "Integriere"
, intoDocspell = "in Docspell"
, postRequestInfo = "Docspell wird JSON POST requests senden."
, updateWebhook = "Webhook aktualisieren"
, addWebhook = "Neuen Webhook hinzufügen"
, notifyEmailInfo = "Werde per E-Mail benachrichtigt."
}

View File

@ -0,0 +1,45 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.NotificationHookTable exposing
( Texts
, de
, gb
)
import Data.EventType exposing (EventType)
import Messages.Basics
import Messages.Data.EventType
type alias Texts =
{ basics : Messages.Basics.Texts
, eventType : EventType -> Messages.Data.EventType.Texts
, enabled : String
, channel : String
, events : String
}
gb : Texts
gb =
{ basics = Messages.Basics.gb
, eventType = Messages.Data.EventType.gb
, enabled = "Enabled"
, channel = "Channel"
, events = "Events"
}
de : Texts
de =
{ basics = Messages.Basics.de
, eventType = Messages.Data.EventType.de
, enabled = "Aktiv"
, channel = "Kanal"
, events = "Ereignisse"
}

View File

@ -0,0 +1,34 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.NotificationHttpForm exposing
( Texts
, de
, gb
)
import Messages.Basics
type alias Texts =
{ basics : Messages.Basics.Texts
, httpUrl : String
}
gb : Texts
gb =
{ basics = Messages.Basics.gb
, httpUrl = "Http URL"
}
de : Texts
de =
{ basics = Messages.Basics.de
, httpUrl = "URL"
}

View File

@ -0,0 +1,49 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.NotificationMailForm exposing
( Texts
, de
, gb
)
import Messages.Basics
type alias Texts =
{ basics : Messages.Basics.Texts
, selectConnection : String
, sendVia : String
, sendViaInfo : String
, recipients : String
, recipientsInfo : String
, recipientsRequired : String
}
gb : Texts
gb =
{ basics = Messages.Basics.gb
, selectConnection = "Select connection..."
, sendVia = "Send via"
, sendViaInfo = "The SMTP connection to use when sending notification mails."
, recipients = "Recipient(s)"
, recipientsInfo = "One or more mail addresses, confirm each by pressing 'Return'."
, recipientsRequired = "At least one recipient is required."
}
de : Texts
de =
{ basics = Messages.Basics.de
, selectConnection = "Verbindung auswählen"
, sendVia = "Senden via"
, sendViaInfo = "Die SMTP-Verbindung, die zum Senden der Benachrichtigungs-E-Mails verwendet werden soll."
, recipients = "Empfänger"
, recipientsInfo = "Eine oder mehrere E-Mail-Adressen, jede mit 'Eingabe' bestätigen."
, recipientsRequired = "Mindestens ein Empfänger muss angegeben werden."
}

View File

@ -0,0 +1,40 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.NotificationMatrixForm exposing
( Texts
, de
, gb
)
import Messages.Basics
type alias Texts =
{ basics : Messages.Basics.Texts
, homeServer : String
, roomId : String
, accessKey : String
}
gb : Texts
gb =
{ basics = Messages.Basics.gb
, homeServer = "Homeserver URL"
, roomId = "Room ID"
, accessKey = "Access Token"
}
de : Texts
de =
{ basics = Messages.Basics.de
, homeServer = "Homeserver URL"
, roomId = "Room ID"
, accessKey = "Access Token"
}

View File

@ -0,0 +1,99 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.PeriodicQueryTaskForm exposing
( Texts
, de
, gb
)
import Data.ChannelType exposing (ChannelType)
import Http
import Messages.Basics
import Messages.Comp.CalEventInput
import Messages.Comp.ChannelForm
import Messages.Comp.HttpError
import Messages.Data.ChannelType
type alias Texts =
{ basics : Messages.Basics.Texts
, calEventInput : Messages.Comp.CalEventInput.Texts
, channelForm : Messages.Comp.ChannelForm.Texts
, httpError : Http.Error -> String
, reallyDeleteTask : String
, startOnce : String
, startTaskNow : String
, deleteThisTask : String
, enableDisable : String
, summary : String
, summaryInfo : String
, schedule : String
, scheduleClickForHelp : String
, scheduleInfo : String
, queryLabel : String
, invalidCalEvent : String
, channelRequired : String
, queryStringRequired : String
, channelHeader : ChannelType -> String
}
gb : Texts
gb =
{ basics = Messages.Basics.gb
, calEventInput = Messages.Comp.CalEventInput.gb
, channelForm = Messages.Comp.ChannelForm.gb
, httpError = Messages.Comp.HttpError.gb
, reallyDeleteTask = "Really delete this notification task?"
, startOnce = "Start Once"
, startTaskNow = "Start this task now"
, deleteThisTask = "Delete this task"
, enableDisable = "Enable or disable this task."
, summary = "Summary"
, summaryInfo = "Some human readable name, only for displaying"
, schedule = "Schedule"
, scheduleClickForHelp = "Click here for help"
, scheduleInfo =
"Specify how often and when this task should run. "
++ "Use English 3-letter weekdays. Either a single value, "
++ "a list (ex. 1,2,3), a range (ex. 1..3) or a '*' (meaning all) "
++ "is allowed for each part."
, invalidCalEvent = "The calendar event is not valid."
, queryLabel = "Query"
, channelRequired = "A valid channel must be given."
, queryStringRequired = "A query string must be supplied"
, channelHeader = \ct -> "Connection details for " ++ Messages.Data.ChannelType.gb ct
}
de : Texts
de =
{ basics = Messages.Basics.de
, calEventInput = Messages.Comp.CalEventInput.de
, channelForm = Messages.Comp.ChannelForm.de
, httpError = Messages.Comp.HttpError.de
, reallyDeleteTask = "Diesen Benachrichtigungsauftrag wirklich löschen?"
, startOnce = "Jetzt starten"
, startTaskNow = "Starte den Auftrag sofort"
, deleteThisTask = "Den Auftrag löschen"
, enableDisable = "Auftrag aktivieren oder deaktivieren"
, summary = "Kurzbeschreibung"
, summaryInfo = "Eine kurze lesbare Zusammenfassung, nur für die Anzeige"
, schedule = "Zeitplan"
, scheduleClickForHelp = "Klicke für Hilfe"
, scheduleInfo =
"Gib an, wie oft und wann der Auftrag laufen soll. "
++ "Verwende englische 3-Buchstaben Wochentage. Entweder ein einzelner Wert, "
++ "eine Liste (wie `1,2,3`), eine Bereich (wie `1..3`) oder ein '*' (für alle) "
++ "ist mögich für jeden Teil."
, invalidCalEvent = "Das Kalenderereignis ist nicht gültig."
, queryLabel = "Abfrage"
, channelRequired = "Ein Versandkanal muss angegeben werden."
, queryStringRequired = "Eine Suchabfrage muss angegeben werden."
, channelHeader = \ct -> "Details für " ++ Messages.Data.ChannelType.de ct
}

View File

@ -0,0 +1,43 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.PeriodicQueryTaskList exposing
( Texts
, de
, gb
)
import Messages.Basics
type alias Texts =
{ basics : Messages.Basics.Texts
, summary : String
, schedule : String
, connection : String
, recipients : String
}
gb : Texts
gb =
{ basics = Messages.Basics.gb
, summary = "Summary"
, schedule = "Schedule"
, connection = "Connection"
, recipients = "Recipients"
}
de : Texts
de =
{ basics = Messages.Basics.de
, summary = "Kurzbeschreibung"
, schedule = "Zeitplan"
, connection = "Verbindung"
, recipients = "Empfänger"
}

View File

@ -0,0 +1,78 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.PeriodicQueryTaskManage exposing
( Texts
, de
, gb
)
import Http
import Messages.Basics
import Messages.Comp.HttpError
import Messages.Comp.PeriodicQueryTaskForm
import Messages.Comp.PeriodicQueryTaskList
import Messages.Data.ChannelType
type alias Texts =
{ basics : Messages.Basics.Texts
, notificationForm : Messages.Comp.PeriodicQueryTaskForm.Texts
, notificationTable : Messages.Comp.PeriodicQueryTaskList.Texts
, httpError : Http.Error -> String
, channelType : Messages.Data.ChannelType.Texts
, newTask : String
, createNewTask : String
, taskCreated : String
, taskUpdated : String
, taskStarted : String
, taskDeleted : String
, matrix : String
, gotify : String
, email : String
, httpRequest : String
}
gb : Texts
gb =
{ basics = Messages.Basics.gb
, notificationForm = Messages.Comp.PeriodicQueryTaskForm.gb
, notificationTable = Messages.Comp.PeriodicQueryTaskList.gb
, httpError = Messages.Comp.HttpError.gb
, channelType = Messages.Data.ChannelType.gb
, newTask = "New Task"
, createNewTask = "Create a new notification task"
, taskCreated = "Task created."
, taskUpdated = "Task updated."
, taskStarted = "Task started."
, taskDeleted = "Task deleted."
, matrix = "Matrix"
, gotify = "Gotify"
, email = "E-Mail"
, httpRequest = "HTTP Request"
}
de : Texts
de =
{ basics = Messages.Basics.de
, notificationForm = Messages.Comp.PeriodicQueryTaskForm.de
, notificationTable = Messages.Comp.PeriodicQueryTaskList.de
, httpError = Messages.Comp.HttpError.de
, channelType = Messages.Data.ChannelType.de
, newTask = "Neuer Auftrag"
, createNewTask = "Erstelle einen neuen Benachrichtigungsauftrag"
, taskCreated = "Auftrag erstellt."
, taskUpdated = "Auftrag aktualisiert."
, taskStarted = "Auftrag gestartet."
, taskDeleted = "Auftrag gelöscht."
, matrix = "Matrix"
, gotify = "Gotify"
, email = "E-Mail"
, httpRequest = "HTTP Request"
}

View File

@ -0,0 +1,46 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Data.ChannelType exposing (Texts, de, gb)
import Data.ChannelType exposing (ChannelType)
type alias Texts =
ChannelType -> String
gb : Texts
gb ct =
case ct of
Data.ChannelType.Matrix ->
"Matrix"
Data.ChannelType.Gotify ->
"Gotify"
Data.ChannelType.Mail ->
"E-Mail"
Data.ChannelType.Http ->
"HTTP request"
de : Texts
de ct =
case ct of
Data.ChannelType.Matrix ->
"Matrix"
Data.ChannelType.Gotify ->
"Gotify"
Data.ChannelType.Mail ->
"E-Mail"
Data.ChannelType.Http ->
"HTTP Request"

View File

@ -0,0 +1,78 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Data.EventType exposing
( Texts
, de
, gb
)
import Data.EventType exposing (EventType(..))
type alias Texts =
{ name : String
, info : String
}
gb : EventType -> Texts
gb et =
case et of
TagsChanged ->
{ name = "Tags changed"
, info = "Whenever a tag on an item is added or removed"
}
SetFieldValue ->
{ name = "Set field value"
, info = "Whenever a custom field is set to a value"
}
DeleteFieldValue ->
{ name = "Delete field value"
, info = "Whenever a custom field is removed"
}
JobSubmitted ->
{ name = "Job submitted"
, info = "Whenever a new job is submitted"
}
JobDone ->
{ name = "Job done"
, info = "Whenever a new job finished"
}
de : EventType -> Texts
de et =
case et of
TagsChanged ->
{ name = "Tags geändert"
, info = "Wenn ein tag hinzugefügt oder entfernt wird"
}
SetFieldValue ->
{ name = "Benutzerfeldwert ändert"
, info = "Wenn für ein Benutzerfeld ein Wert gesetzt wird"
}
DeleteFieldValue ->
{ name = "Benutzerfeldwert entfernt"
, info = "Wenn der Wert für ein Benuzterfeld entfernt wird"
}
JobSubmitted ->
{ name = "Auftrag gestartet"
, info = "Wenn ein neuer Auftrag gestartet wird"
}
JobDone ->
{ name = "Auftrag beendet"
, info = "Wenn ein Auftrag beendet wurde"
}

View File

@ -12,10 +12,12 @@ module Messages.Page.UserSettings exposing
)
import Messages.Comp.ChangePasswordForm
import Messages.Comp.DueItemsTaskManage
import Messages.Comp.EmailSettingsManage
import Messages.Comp.ImapSettingsManage
import Messages.Comp.NotificationManage
import Messages.Comp.NotificationHookManage
import Messages.Comp.OtpSetup
import Messages.Comp.PeriodicQueryTaskManage
import Messages.Comp.ScanMailboxManage
import Messages.Comp.UiSettingsManage
@ -25,8 +27,10 @@ type alias Texts =
, uiSettingsManage : Messages.Comp.UiSettingsManage.Texts
, emailSettingsManage : Messages.Comp.EmailSettingsManage.Texts
, imapSettingsManage : Messages.Comp.ImapSettingsManage.Texts
, notificationManage : Messages.Comp.NotificationManage.Texts
, notificationManage : Messages.Comp.DueItemsTaskManage.Texts
, scanMailboxManage : Messages.Comp.ScanMailboxManage.Texts
, notificationHookManage : Messages.Comp.NotificationHookManage.Texts
, periodicQueryTask : Messages.Comp.PeriodicQueryTaskManage.Texts
, otpSetup : Messages.Comp.OtpSetup.Texts
, userSettings : String
, uiSettings : String
@ -36,11 +40,16 @@ type alias Texts =
, emailSettingImap : String
, changePassword : String
, uiSettingsInfo : String
, notificationInfoText : String
, notificationRemindDaysInfo : String
, scanMailboxInfo1 : String
, scanMailboxInfo2 : String
, otpMenu : String
, webhooks : String
, genericQueries : String
, dueItems : String
, notificationInfoText : String
, webhookInfoText : String
, dueItemsInfoText : String
, periodicQueryInfoText : String
}
@ -50,8 +59,10 @@ gb =
, uiSettingsManage = Messages.Comp.UiSettingsManage.gb
, emailSettingsManage = Messages.Comp.EmailSettingsManage.gb
, imapSettingsManage = Messages.Comp.ImapSettingsManage.gb
, notificationManage = Messages.Comp.NotificationManage.gb
, notificationManage = Messages.Comp.DueItemsTaskManage.gb
, scanMailboxManage = Messages.Comp.ScanMailboxManage.gb
, notificationHookManage = Messages.Comp.NotificationHookManage.gb
, periodicQueryTask = Messages.Comp.PeriodicQueryTaskManage.gb
, otpSetup = Messages.Comp.OtpSetup.gb
, userSettings = "User Settings"
, uiSettings = "UI Settings"
@ -63,13 +74,6 @@ gb =
, uiSettingsInfo =
"These settings only affect the web ui. They are stored in the browser, "
++ "so they are separated between browsers and devices."
, notificationInfoText =
"""
Docspell can notify you once the due dates of your items
come closer. Notification is done via e-mail. You need to
provide a connection in your e-mail settings."""
, notificationRemindDaysInfo =
"Docspell finds all items that are due in *Remind Days* days and sends this list via e-mail."
, scanMailboxInfo1 =
"Docspell can scan folders of your mailbox to import your mails. "
++ "You need to provide a connection in "
@ -85,6 +89,28 @@ gb =
adjust the schedule to avoid reading over the same mails
again."""
, otpMenu = "Two Factor Authentication"
, webhooks = "Webhooks"
, genericQueries = "Generic Queries"
, dueItems = "Due Items Query"
, notificationInfoText = """
Docspell can send notification messages on various events. You can
choose from these channels to send messages:
[Matrix](https://matrix.org), [Gotify](https://gotify.net) or E-Mail.
At last you can send a plain http request with the event details in
its payload.
Additionally, you can setup queries that are executed periodically.
The results are send as a notification message.
When creating a new notification task, choose first the communication
channel.
"""
, webhookInfoText = """Webhooks execute http request upon certain events in docspell.
"""
, dueItemsInfoText = """Docspell can notify you once the due dates of your items come closer. """
, periodicQueryInfoText = "You can define a custom query that gets executed periodically."
}
@ -94,8 +120,10 @@ de =
, uiSettingsManage = Messages.Comp.UiSettingsManage.de
, emailSettingsManage = Messages.Comp.EmailSettingsManage.de
, imapSettingsManage = Messages.Comp.ImapSettingsManage.de
, notificationManage = Messages.Comp.NotificationManage.de
, notificationManage = Messages.Comp.DueItemsTaskManage.de
, scanMailboxManage = Messages.Comp.ScanMailboxManage.de
, notificationHookManage = Messages.Comp.NotificationHookManage.de
, periodicQueryTask = Messages.Comp.PeriodicQueryTaskManage.de
, otpSetup = Messages.Comp.OtpSetup.de
, userSettings = "Benutzereinstellung"
, uiSettings = "Oberfläche"
@ -106,13 +134,6 @@ de =
, changePassword = "Passwort ändern"
, uiSettingsInfo =
"Diese Einstellungen sind für die Web-Oberfläche."
, notificationInfoText =
"""
Docspell kann eine E-Mail versenden, sobald das
Fälligkeitsdatum von Dokumenten näher kommt. Dafür muss eine
E-Mail-SMTP-Verbindung konfiguriert werden.."""
, notificationRemindDaysInfo =
"Docspell sucht Dokumente die in X Tagen fällig sind und sendet diese Liste als E-Mail."
, scanMailboxInfo1 =
"""Docspell kann Postfächer durchsuchen und E-Mails importieren. Dafür sind
E-Mail-Einstellungen (IMAP) notwendig."""
@ -129,4 +150,26 @@ E-Mail-Einstellungen (IMAP) notwendig."""
gleichen E-Mails möglichst nicht noch einmal eingelesen
werden."""
, otpMenu = "Zwei-Faktor-Authentifizierung"
, webhooks = "Webhooks"
, genericQueries = "Periodische Abfragen"
, dueItems = "Fällige Dokumente"
, notificationInfoText = """
Docspell kann Benachrichtigungen bei gewissen Ereignissen versenden.
Es kann aus diesen Versandkanälen gewählt werden:
[Matrix](https://matrix.org), [Gotify](https://gotify.net) oder
E-Mail. Zusätzlich kann das HTTP request direkt empfangen werden, was
alle Details zu einem Ereignis enthält.
Ausserdem können periodische Suchabfragen erstellt werden, dessen
Ergebnis dann als Benachrichtigung versendet wird.
Beim Erstellen eines neuen Auftrags muss zunächst der gewünschte
Versandkanal gewählt werden.
"""
, webhookInfoText = """Webhooks versenden HTTP Requests wenn bestimmte Ereignisse in Docspell auftreten."""
, dueItemsInfoText = """Docspell kann dich benachrichtigen, sobald das Fälligkeitsdatum von Dokumenten näher kommt. """
, periodicQueryInfoText = "Hier können beliebige Abfragen definiert werden, welche regelmäßig ausgeführt werden."
}

View File

@ -13,10 +13,12 @@ module Page.UserSettings.Data exposing
)
import Comp.ChangePasswordForm
import Comp.DueItemsTaskManage
import Comp.EmailSettingsManage
import Comp.ImapSettingsManage
import Comp.NotificationManage
import Comp.NotificationHookManage
import Comp.OtpSetup
import Comp.PeriodicQueryTaskManage
import Comp.ScanMailboxManage
import Comp.UiSettingsManage
import Data.Flags exposing (Flags)
@ -28,10 +30,12 @@ type alias Model =
, changePassModel : Comp.ChangePasswordForm.Model
, emailSettingsModel : Comp.EmailSettingsManage.Model
, imapSettingsModel : Comp.ImapSettingsManage.Model
, notificationModel : Comp.NotificationManage.Model
, notificationModel : Comp.DueItemsTaskManage.Model
, scanMailboxModel : Comp.ScanMailboxManage.Model
, uiSettingsModel : Comp.UiSettingsManage.Model
, otpSetupModel : Comp.OtpSetup.Model
, notificationHookModel : Comp.NotificationHookManage.Model
, periodicQueryModel : Comp.PeriodicQueryTaskManage.Model
}
@ -43,19 +47,29 @@ init flags settings =
( otpm, otpc ) =
Comp.OtpSetup.init flags
( nhm, nhc ) =
Comp.NotificationHookManage.init flags
( pqm, pqc ) =
Comp.PeriodicQueryTaskManage.init flags
in
( { currentTab = Just UiSettingsTab
, changePassModel = Comp.ChangePasswordForm.emptyModel
, emailSettingsModel = Comp.EmailSettingsManage.emptyModel
, imapSettingsModel = Comp.ImapSettingsManage.emptyModel
, notificationModel = Tuple.first (Comp.NotificationManage.init flags)
, notificationModel = Tuple.first (Comp.DueItemsTaskManage.init flags)
, scanMailboxModel = Tuple.first (Comp.ScanMailboxManage.init flags)
, uiSettingsModel = um
, otpSetupModel = otpm
, notificationHookModel = nhm
, periodicQueryModel = pqm
}
, Cmd.batch
[ Cmd.map UiSettingsMsg uc
, Cmd.map OtpSetupMsg otpc
, Cmd.map NotificationHookMsg nhc
, Cmd.map PeriodicQueryMsg pqc
]
)
@ -65,6 +79,9 @@ type Tab
| EmailSettingsTab
| ImapSettingsTab
| NotificationTab
| NotificationWebhookTab
| NotificationQueriesTab
| NotificationDueItemsTab
| ScanMailboxTab
| UiSettingsTab
| OtpTab
@ -74,10 +91,12 @@ type Msg
= SetTab Tab
| ChangePassMsg Comp.ChangePasswordForm.Msg
| EmailSettingsMsg Comp.EmailSettingsManage.Msg
| NotificationMsg Comp.NotificationManage.Msg
| NotificationMsg Comp.DueItemsTaskManage.Msg
| ImapSettingsMsg Comp.ImapSettingsManage.Msg
| ScanMailboxMsg Comp.ScanMailboxManage.Msg
| UiSettingsMsg Comp.UiSettingsManage.Msg
| OtpSetupMsg Comp.OtpSetup.Msg
| NotificationHookMsg Comp.NotificationHookManage.Msg
| PeriodicQueryMsg Comp.PeriodicQueryTaskManage.Msg
| UpdateSettings
| ReceiveBrowserSettings StoredUiSettings

View File

@ -8,10 +8,12 @@
module Page.UserSettings.Update exposing (UpdateResult, update)
import Comp.ChangePasswordForm
import Comp.DueItemsTaskManage
import Comp.EmailSettingsManage
import Comp.ImapSettingsManage
import Comp.NotificationManage
import Comp.NotificationHookManage
import Comp.OtpSetup
import Comp.PeriodicQueryTaskManage
import Comp.ScanMailboxManage
import Comp.UiSettingsManage
import Data.Flags exposing (Flags)
@ -62,10 +64,32 @@ update flags settings msg model =
UpdateResult m Cmd.none Sub.none Nothing
NotificationTab ->
{ model = m
, cmd = Cmd.none
, sub = Sub.none
, newSettings = Nothing
}
NotificationWebhookTab ->
{ model = m
, cmd = Cmd.none
, sub = Sub.none
, newSettings = Nothing
}
NotificationQueriesTab ->
let
initCmd =
Cmd.map NotificationMsg
(Tuple.second (Comp.NotificationManage.init flags))
(Tuple.second (Comp.DueItemsTaskManage.init flags))
in
UpdateResult m initCmd Sub.none Nothing
NotificationDueItemsTab ->
let
initCmd =
Cmd.map NotificationMsg
(Tuple.second (Comp.DueItemsTaskManage.init flags))
in
UpdateResult m initCmd Sub.none Nothing
@ -119,7 +143,7 @@ update flags settings msg model =
NotificationMsg lm ->
let
( m2, c2 ) =
Comp.NotificationManage.update flags lm model.notificationModel
Comp.DueItemsTaskManage.update flags lm model.notificationModel
in
{ model = { model | notificationModel = m2 }
, cmd = Cmd.map NotificationMsg c2
@ -160,6 +184,17 @@ update flags settings msg model =
, newSettings = Nothing
}
NotificationHookMsg lm ->
let
( hm, hc ) =
Comp.NotificationHookManage.update flags lm model.notificationHookModel
in
{ model = { model | notificationHookModel = hm }
, cmd = Cmd.map NotificationHookMsg hc
, sub = Sub.none
, newSettings = Nothing
}
UpdateSettings ->
update flags
settings
@ -172,3 +207,14 @@ update flags settings msg model =
Comp.UiSettingsManage.ReceiveBrowserSettings sett
in
update flags settings (UiSettingsMsg lm) model
PeriodicQueryMsg lm ->
let
( pqm, pqc, pqs ) =
Comp.PeriodicQueryTaskManage.update flags lm model.periodicQueryModel
in
{ model = { model | periodicQueryModel = pqm }
, cmd = Cmd.map PeriodicQueryMsg pqc
, sub = Sub.map PeriodicQueryMsg pqs
, newSettings = Nothing
}

View File

@ -8,10 +8,12 @@
module Page.UserSettings.View2 exposing (viewContent, viewSidebar)
import Comp.ChangePasswordForm
import Comp.DueItemsTaskManage
import Comp.EmailSettingsManage
import Comp.ImapSettingsManage
import Comp.NotificationManage
import Comp.NotificationHookManage
import Comp.OtpSetup
import Comp.PeriodicQueryTaskManage
import Comp.ScanMailboxManage
import Comp.UiSettingsManage
import Data.Flags exposing (Flags)
@ -27,6 +29,24 @@ import Styles as S
viewSidebar : Texts -> Bool -> Flags -> UiSettings -> Model -> Html Msg
viewSidebar texts visible _ _ model =
let
isNotificationTab =
case model.currentTab of
Just NotificationTab ->
True
Just NotificationQueriesTab ->
True
Just NotificationWebhookTab ->
True
Just NotificationDueItemsTab ->
True
_ ->
False
in
div
[ id "sidebar"
, class S.sidebar
@ -50,16 +70,56 @@ viewSidebar texts visible _ _ model =
[ class "ml-3" ]
[ text texts.uiSettings ]
]
, a
[ href "#"
, onClick (SetTab NotificationTab)
, menuEntryActive model NotificationTab
, class S.sidebarLink
]
[ i [ class "fa fa-bullhorn" ] []
, span
[ class "ml-3" ]
[ text texts.notifications ]
, div []
[ a
[ href "#"
, onClick (SetTab NotificationTab)
, menuEntryActive model NotificationTab
, class S.sidebarLink
]
[ i [ class "fa fa-bullhorn" ] []
, span
[ class "ml-3" ]
[ text texts.notifications ]
]
, div
[ class "ml-5 flex flex-col"
, classList [ ( "hidden", not isNotificationTab ) ]
]
[ a
[ href "#"
, onClick (SetTab NotificationWebhookTab)
, menuEntryActive model NotificationWebhookTab
, class S.sidebarLink
]
[ i [ class "fa fa-bell" ] []
, span
[ class "ml-3" ]
[ text texts.webhooks ]
]
, a
[ href "#"
, onClick (SetTab NotificationDueItemsTab)
, menuEntryActive model NotificationDueItemsTab
, class S.sidebarLink
]
[ i [ class "fa fa-history" ] []
, span
[ class "ml-3" ]
[ text texts.dueItems ]
]
, a
[ href "#"
, onClick (SetTab NotificationQueriesTab)
, menuEntryActive model NotificationQueriesTab
, class S.sidebarLink
]
[ i [ class "fa fa-history" ] []
, span
[ class "ml-3" ]
[ text texts.genericQueries ]
]
]
]
, a
[ href "#"
@ -134,7 +194,16 @@ viewContent texts flags settings model =
viewEmailSettings texts settings model
Just NotificationTab ->
viewNotificationManage texts settings model
viewNotificationInfo texts settings model
Just NotificationWebhookTab ->
viewNotificationHooks texts settings model
Just NotificationQueriesTab ->
viewNotificationQueries texts settings model
Just NotificationDueItemsTab ->
viewNotificationDueItems texts settings model
Just ImapSettingsTab ->
viewImapSettings texts settings model
@ -268,8 +337,8 @@ viewImapSettings texts settings model =
]
viewNotificationManage : Texts -> UiSettings -> Model -> List (Html Msg)
viewNotificationManage texts settings model =
viewNotificationInfo : Texts -> UiSettings -> Model -> List (Html Msg)
viewNotificationInfo texts settings model =
[ h2
[ class S.header1
, class "inline-flex items-center"
@ -279,20 +348,119 @@ viewNotificationManage texts settings model =
[ text texts.notifications
]
]
, p [ class "opacity-80 text-lg mb-3" ]
[ text texts.notificationInfoText
, Markdown.toHtml [ class "opacity-80 text-lg max-w-prose mb-3 markdown-preview" ] texts.notificationInfoText
, div [ class "mt-2" ]
[ ul [ class "list-none ml-8" ]
[ li [ class "py-2" ]
[ a
[ href "#"
, onClick (SetTab NotificationWebhookTab)
, class S.link
]
[ i [ class "fa fa-bell" ] []
, span
[ class "ml-3" ]
[ text texts.webhooks ]
]
, div [ class "ml-3 text-sm opacity-50" ]
[ text texts.webhookInfoText
]
]
, li [ class "py-2" ]
[ a
[ href "#"
, onClick (SetTab NotificationDueItemsTab)
, class S.link
]
[ i [ class "fa fa-history" ] []
, span
[ class "ml-3" ]
[ text texts.dueItems ]
]
, div [ class "ml-3 text-sm opacity-50" ]
[ text texts.dueItemsInfoText
]
]
, li [ class "py-2" ]
[ a
[ href "#"
, onClick (SetTab NotificationQueriesTab)
, class S.link
]
[ i [ class "fa fa-history" ] []
, span
[ class "ml-3" ]
[ text texts.genericQueries ]
]
, div [ class "ml-3 text-sm opacity-50" ]
[ text texts.periodicQueryInfoText
]
]
]
]
, p [ class "opacity-80 text-lg mb-3" ]
[ Markdown.toHtml [] texts.notificationRemindDaysInfo
]
viewNotificationDueItems : Texts -> UiSettings -> Model -> List (Html Msg)
viewNotificationDueItems texts settings model =
[ h2
[ class S.header1
, class "inline-flex items-center"
]
[ i [ class "fa fa-history" ] []
, div [ class "ml-3" ]
[ text texts.dueItems
]
]
, Markdown.toHtml [ class "opacity-80 text-lg mb-3 markdown-preview" ] texts.dueItemsInfoText
, Html.map NotificationMsg
(Comp.NotificationManage.view2 texts.notificationManage
(Comp.DueItemsTaskManage.view2 texts.notificationManage
settings
model.notificationModel
)
]
viewNotificationQueries : Texts -> UiSettings -> Model -> List (Html Msg)
viewNotificationQueries texts settings model =
[ h2
[ class S.header1
, class "inline-flex items-center"
]
[ i [ class "fa fa-history" ] []
, div [ class "ml-3" ]
[ text texts.genericQueries
]
]
, Markdown.toHtml [ class "opacity-80 text-lg mb-3 markdown-preview" ] texts.periodicQueryInfoText
, Html.map PeriodicQueryMsg
(Comp.PeriodicQueryTaskManage.view texts.periodicQueryTask
settings
model.periodicQueryModel
)
]
viewNotificationHooks : Texts -> UiSettings -> Model -> List (Html Msg)
viewNotificationHooks texts settings model =
[ h2
[ class S.header1
, class "inline-flex items-center"
]
[ i [ class "fa fa-bell" ] []
, div [ class "ml-3" ]
[ text texts.webhooks
]
]
, Markdown.toHtml [ class "opacity-80 text-lg mb-3 markdown-preview" ] texts.webhookInfoText
, Html.map NotificationHookMsg
(Comp.NotificationHookManage.view texts.notificationHookManage
settings
model.notificationHookModel
)
]
viewScanMailboxManage : Texts -> Flags -> UiSettings -> Model -> List (Html Msg)
viewScanMailboxManage texts flags settings model =
[ h2

View File

@ -7,6 +7,8 @@
module Styles exposing (..)
import Svg exposing (Svg)
sidebar : String
sidebar =
@ -348,6 +350,11 @@ header3 =
" text-xl mb-3 font-medium tracking-wide "
formHeader : String
formHeader =
header3 ++ " text-xl mb-4 font-medium tracking-wide border-b dark:border-bluegray-300 border-gray-800"
editLinkTableCellStyle : String
editLinkTableCellStyle =
"w-px whitespace-nowrap pr-2 md:pr-4 py-4 md:py-2"