diff --git a/modules/restapi/src/main/resources/docspell-openapi.yml b/modules/restapi/src/main/resources/docspell-openapi.yml index 642a8bba..090a80d7 100644 --- a/modules/restapi/src/main/resources/docspell-openapi.yml +++ b/modules/restapi/src/main/resources/docspell-openapi.yml @@ -1755,8 +1755,113 @@ paths: schema: $ref: "#/components/schemas/BasicResult" + /sec/usertask/scanmailbox: + get: + tags: [ User Tasks ] + summary: Get settings for "Scan Mailbox" task + description: | + Return the current settings for the scan mailbox task of the + authenticated user. Users can periodically fetch mails to be + imported into docspell. + security: + - authTokenHeader: [] + responses: + 200: + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/ScanMailboxSettings" + post: + tags: [ User Tasks ] + summary: Change current settings for "Scan Mailbox" task + description: | + Change the current settings for the scan-mailbox task of the + authenticated user. + security: + - authTokenHeader: [] + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/ScanMailboxSettings" + responses: + 200: + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/BasicResult" + /sec/usertask/scanmailbox/startonce: + post: + tags: [ User Tasks ] + summary: Start the "Scan Mailbox" task once + description: | + Starts the scan-mailbox task just once, discarding the + schedule and not updating the periodic task. + security: + - authTokenHeader: [] + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/ScanMailboxSettings" + responses: + 200: + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/BasicResult" + components: schemas: + ScanMailboxSettings: + description: | + Settings for the scan mailbox task. + required: + - id + - enabled + - imapConnection + - schedule + - folders + properties: + id: + type: string + format: ident + enabled: + type: boolean + imapConnection: + type: string + format: ident + folders: + type: array + items: + type: string + schedule: + type: string + format: calevent + receivedSinceHours: + type: integer + description: | + Look only for mails newer than `receivedSinceHours' hours. + targetFolder: + type: string + description: | + The folder to move all mails into that have been + successfully submitted to docspell. + deleteMail: + type: boolean + description: | + Whether to delete all successfully imported mails. This + only applies, if `targetFolder' is not set. + direction: + type: string + format: direction + description: | + The direction to apply to items resulting from importing + mails. If not set, the value is guessed based on the from + and to mail headers and your address book. ImapSettingsList: description: | A list of user email settings. diff --git a/modules/webapp/src/main/elm/Api.elm b/modules/webapp/src/main/elm/Api.elm index e1e74060..f56cb7d7 100644 --- a/modules/webapp/src/main/elm/Api.elm +++ b/modules/webapp/src/main/elm/Api.elm @@ -30,6 +30,7 @@ module Api exposing , getOrganizations , getPersons , getPersonsLight + , getScanMailbox , getSentMails , getSources , getTags @@ -64,7 +65,9 @@ module Api exposing , setTags , setUnconfirmed , startOnceNotifyDueItems + , startOnceScanMailbox , submitNotifyDueItems + , submitScanMailbox , upload , uploadSingle , versionInfo @@ -105,6 +108,7 @@ import Api.Model.Person exposing (Person) import Api.Model.PersonList exposing (PersonList) import Api.Model.ReferenceList exposing (ReferenceList) import Api.Model.Registration exposing (Registration) +import Api.Model.ScanMailboxSettings exposing (ScanMailboxSettings) import Api.Model.SentMails exposing (SentMails) import Api.Model.SimpleMail exposing (SimpleMail) import Api.Model.Source exposing (Source) @@ -127,6 +131,50 @@ import Util.Http as Http2 +--- Scan Mailboxes + + +startOnceScanMailbox : + Flags + -> ScanMailboxSettings + -> (Result Http.Error BasicResult -> msg) + -> Cmd msg +startOnceScanMailbox flags settings receive = + Http2.authPost + { url = flags.config.baseUrl ++ "/api/v1/sec/usertask/scanmailbox/startonce" + , account = getAccount flags + , body = Http.jsonBody (Api.Model.ScanMailboxSettings.encode settings) + , expect = Http.expectJson receive Api.Model.BasicResult.decoder + } + + +submitScanMailbox : + Flags + -> ScanMailboxSettings + -> (Result Http.Error BasicResult -> msg) + -> Cmd msg +submitScanMailbox flags settings receive = + Http2.authPost + { url = flags.config.baseUrl ++ "/api/v1/sec/usertask/scanmailbox" + , account = getAccount flags + , body = Http.jsonBody (Api.Model.ScanMailboxSettings.encode settings) + , expect = Http.expectJson receive Api.Model.BasicResult.decoder + } + + +getScanMailbox : + Flags + -> (Result Http.Error ScanMailboxSettings -> msg) + -> Cmd msg +getScanMailbox flags receive = + Http2.authGet + { url = flags.config.baseUrl ++ "/api/v1/sec/usertask/scanmailbox" + , account = getAccount flags + , expect = Http.expectJson receive Api.Model.ScanMailboxSettings.decoder + } + + + --- NotifyDueItems diff --git a/modules/webapp/src/main/elm/Comp/ScanMailboxForm.elm b/modules/webapp/src/main/elm/Comp/ScanMailboxForm.elm new file mode 100644 index 00000000..e1628039 --- /dev/null +++ b/modules/webapp/src/main/elm/Comp/ScanMailboxForm.elm @@ -0,0 +1,372 @@ +module Comp.ScanMailboxForm exposing + ( Model + , Msg + , init + , update + , view + ) + +import Api +import Api.Model.BasicResult exposing (BasicResult) +import Api.Model.ImapSettingsList exposing (ImapSettingsList) +import Api.Model.ScanMailboxSettings exposing (ScanMailboxSettings) +import Api.Model.Tag exposing (Tag) +import Api.Model.TagList exposing (TagList) +import Comp.CalEventInput +import Comp.Dropdown +import Comp.EmailInput +import Comp.IntField +import Data.CalEvent exposing (CalEvent) +import Data.Flags exposing (Flags) +import Data.Validated exposing (Validated(..)) +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (onCheck, onClick) +import Http +import Util.Http +import Util.Maybe +import Util.Update + + +type alias Model = + { settings : ScanMailboxSettings + , connectionModel : Comp.Dropdown.Model String + , enabled : Bool + , schedule : Validated CalEvent + , scheduleModel : Comp.CalEventInput.Model + , formMsg : Maybe BasicResult + , loading : Int + } + + +type Msg + = Submit + | ConnMsg (Comp.Dropdown.Msg String) + | ConnResp (Result Http.Error ImapSettingsList) + | ToggleEnabled + | CalEventMsg Comp.CalEventInput.Msg + | SetScanMailboxSettings (Result Http.Error ScanMailboxSettings) + | SubmitResp (Result Http.Error BasicResult) + | StartOnce + + +initCmd : Flags -> Cmd Msg +initCmd flags = + Cmd.batch + [ Api.getImapSettings flags "" ConnResp + , Api.getScanMailbox flags SetScanMailboxSettings + ] + + +init : Flags -> ( Model, Cmd Msg ) +init flags = + let + initialSchedule = + Data.Validated.Unknown Data.CalEvent.everyMonth + + ( sm, sc ) = + Comp.CalEventInput.init flags Data.CalEvent.everyMonth + in + ( { settings = Api.Model.ScanMailboxSettings.empty + , connectionModel = + Comp.Dropdown.makeSingle + { makeOption = \a -> { value = a, text = a } + , placeholder = "Select connection..." + } + , enabled = False + , schedule = initialSchedule + , scheduleModel = sm + , formMsg = Nothing + , loading = 3 + } + , Cmd.batch + [ initCmd flags + , Cmd.map CalEventMsg sc + ] + ) + + + +--- Update + + +makeSettings : Model -> Validated ScanMailboxSettings +makeSettings model = + let + prev = + model.settings + + conn = + Comp.Dropdown.getSelected model.connectionModel + |> List.head + |> Maybe.map Valid + |> Maybe.withDefault (Invalid [ "Connection missing" ] "") + + make smtp timer = + { prev + | imapConnection = smtp + , enabled = model.enabled + , schedule = Data.CalEvent.makeEvent timer + } + in + Data.Validated.map2 make + conn + model.schedule + + +withValidSettings : (ScanMailboxSettings -> Cmd Msg) -> Model -> ( Model, Cmd Msg ) +withValidSettings mkcmd model = + case makeSettings model of + Valid set -> + ( { model | formMsg = Nothing } + , mkcmd set + ) + + Invalid errs _ -> + let + errMsg = + String.join ", " errs + in + ( { model | formMsg = Just (BasicResult False errMsg) }, Cmd.none ) + + Unknown _ -> + ( { model | formMsg = Just (BasicResult False "An unknown error occured") } + , Cmd.none + ) + + +update : Flags -> Msg -> Model -> ( Model, Cmd Msg ) +update flags msg model = + case msg of + CalEventMsg lmsg -> + let + ( cm, cc, cs ) = + Comp.CalEventInput.update flags + (Data.Validated.value model.schedule) + lmsg + model.scheduleModel + in + ( { model + | schedule = cs + , scheduleModel = cm + , formMsg = Nothing + } + , Cmd.map CalEventMsg cc + ) + + ConnMsg m -> + let + ( cm, cc ) = + Comp.Dropdown.update m model.connectionModel + in + ( { model + | connectionModel = cm + , formMsg = Nothing + } + , Cmd.map ConnMsg cc + ) + + ConnResp (Ok list) -> + let + names = + List.map .name list.items + + cm = + Comp.Dropdown.makeSingleList + { makeOption = \a -> { value = a, text = a } + , placeholder = "Select Connection..." + , options = names + , selected = List.head names + } + in + ( { model + | connectionModel = cm + , loading = model.loading - 1 + , formMsg = + if names == [] then + Just + (BasicResult False + "No E-Mail connections configured. Goto E-Mail Settings to add one." + ) + + else + Nothing + } + , Cmd.none + ) + + ConnResp (Err err) -> + ( { model + | formMsg = Just (BasicResult False (Util.Http.errorToString err)) + , loading = model.loading - 1 + } + , Cmd.none + ) + + ToggleEnabled -> + ( { model + | enabled = not model.enabled + , formMsg = Nothing + } + , Cmd.none + ) + + SetScanMailboxSettings (Ok s) -> + let + imap = + Util.Maybe.fromString s.imapConnection + |> Maybe.map List.singleton + |> Maybe.withDefault [] + + ( nm, nc ) = + Util.Update.andThen1 + [ update flags (ConnMsg (Comp.Dropdown.SetSelection imap)) + ] + model + + newSchedule = + Data.CalEvent.fromEvent s.schedule + |> Maybe.withDefault Data.CalEvent.everyMonth + + ( sm, sc ) = + Comp.CalEventInput.init flags newSchedule + in + ( { nm + | settings = s + , enabled = s.enabled + , schedule = Data.Validated.Unknown newSchedule + , scheduleModel = sm + , formMsg = Nothing + , loading = model.loading - 1 + } + , Cmd.batch + [ nc + , Cmd.map CalEventMsg sc + ] + ) + + SetScanMailboxSettings (Err err) -> + ( { model + | formMsg = Just (BasicResult False (Util.Http.errorToString err)) + , loading = model.loading - 1 + } + , Cmd.none + ) + + Submit -> + withValidSettings + (\set -> Api.submitScanMailbox flags set SubmitResp) + model + + StartOnce -> + withValidSettings + (\set -> Api.startOnceScanMailbox flags set SubmitResp) + model + + SubmitResp (Ok res) -> + ( { model | formMsg = Just res } + , Cmd.none + ) + + SubmitResp (Err err) -> + ( { model + | formMsg = Just (BasicResult False (Util.Http.errorToString err)) + } + , Cmd.none + ) + + + +--- View + + +isFormError : Model -> Bool +isFormError model = + Maybe.map .success model.formMsg + |> Maybe.map not + |> Maybe.withDefault False + + +isFormSuccess : Model -> Bool +isFormSuccess model = + Maybe.map .success model.formMsg + |> Maybe.withDefault False + + +view : String -> Model -> Html Msg +view extraClasses model = + div + [ classList + [ ( "ui form", True ) + , ( extraClasses, True ) + , ( "error", isFormError model ) + , ( "success", isFormSuccess model ) + ] + ] + [ div + [ classList + [ ( "ui dimmer", True ) + , ( "active", model.loading > 0 ) + ] + ] + [ div [ class "ui text loader" ] + [ text "Loading..." + ] + ] + , div [ class "required field" ] + [ label [] [ text "Send via" ] + , Html.map ConnMsg (Comp.Dropdown.view model.connectionModel) + , span [ class "small-info" ] + [ text "The IMAP connection to use when sending notification mails." + ] + ] + , div [ class "required field" ] + [ label [] + [ text "Schedule" + , a + [ class "right-float" + , href "https://github.com/eikek/calev#what-are-calendar-events" + , target "_blank" + ] + [ i [ class "help icon" ] [] + , text "Click here for help" + ] + ] + , Html.map CalEventMsg + (Comp.CalEventInput.view "" + (Data.Validated.value model.schedule) + model.scheduleModel + ) + , span [ class "small-info" ] + [ text "Specify how often and when this task should run. " + , text "Use English 3-letter weekdays. Either a single value, " + , text "a list (ex. 1,2,3), a range (ex. 1..3) or a '*' (meaning all) " + , text "is allowed for each part." + ] + ] + , div [ class "ui divider" ] [] + , div + [ classList + [ ( "ui message", True ) + , ( "success", isFormSuccess model ) + , ( "error", isFormError model ) + , ( "hidden", model.formMsg == Nothing ) + ] + ] + [ Maybe.map .message model.formMsg + |> Maybe.withDefault "" + |> text + ] + , button + [ class "ui primary button" + , onClick Submit + ] + [ text "Submit" + ] + , button + [ class "ui right floated button" + , onClick StartOnce + ] + [ text "Start Once" + ] + ] diff --git a/modules/webapp/src/main/elm/Page/UserSettings/Data.elm b/modules/webapp/src/main/elm/Page/UserSettings/Data.elm index 56276a09..292fc5b0 100644 --- a/modules/webapp/src/main/elm/Page/UserSettings/Data.elm +++ b/modules/webapp/src/main/elm/Page/UserSettings/Data.elm @@ -9,6 +9,7 @@ import Comp.ChangePasswordForm import Comp.EmailSettingsManage import Comp.ImapSettingsManage import Comp.NotificationForm +import Comp.ScanMailboxForm import Data.Flags exposing (Flags) @@ -18,6 +19,7 @@ type alias Model = , emailSettingsModel : Comp.EmailSettingsManage.Model , imapSettingsModel : Comp.ImapSettingsManage.Model , notificationModel : Comp.NotificationForm.Model + , scanMailboxModel : Comp.ScanMailboxForm.Model } @@ -28,6 +30,7 @@ emptyModel flags = , emailSettingsModel = Comp.EmailSettingsManage.emptyModel , imapSettingsModel = Comp.ImapSettingsManage.emptyModel , notificationModel = Tuple.first (Comp.NotificationForm.init flags) + , scanMailboxModel = Tuple.first (Comp.ScanMailboxForm.init flags) } @@ -36,6 +39,7 @@ type Tab | EmailSettingsTab | ImapSettingsTab | NotificationTab + | ScanMailboxTab type Msg @@ -44,3 +48,4 @@ type Msg | EmailSettingsMsg Comp.EmailSettingsManage.Msg | NotificationMsg Comp.NotificationForm.Msg | ImapSettingsMsg Comp.ImapSettingsManage.Msg + | ScanMailboxMsg Comp.ScanMailboxForm.Msg diff --git a/modules/webapp/src/main/elm/Page/UserSettings/Update.elm b/modules/webapp/src/main/elm/Page/UserSettings/Update.elm index 1a56cac1..fffa6b8c 100644 --- a/modules/webapp/src/main/elm/Page/UserSettings/Update.elm +++ b/modules/webapp/src/main/elm/Page/UserSettings/Update.elm @@ -4,6 +4,7 @@ import Comp.ChangePasswordForm import Comp.EmailSettingsManage import Comp.ImapSettingsManage import Comp.NotificationForm +import Comp.ScanMailboxForm import Data.Flags exposing (Flags) import Page.UserSettings.Data exposing (..) @@ -42,6 +43,14 @@ update flags msg model = (Tuple.second (Comp.NotificationForm.init flags)) in ( m, initCmd ) + + ScanMailboxTab -> + let + initCmd = + Cmd.map ScanMailboxMsg + (Tuple.second (Comp.ScanMailboxForm.init flags)) + in + ( m, initCmd ) in ( m2, cmd ) @@ -74,3 +83,12 @@ update flags msg model = ( { model | notificationModel = m2 } , Cmd.map NotificationMsg c2 ) + + ScanMailboxMsg lm -> + let + ( m2, c2 ) = + Comp.ScanMailboxForm.update flags lm model.scanMailboxModel + in + ( { model | scanMailboxModel = m2 } + , Cmd.map ScanMailboxMsg c2 + ) diff --git a/modules/webapp/src/main/elm/Page/UserSettings/View.elm b/modules/webapp/src/main/elm/Page/UserSettings/View.elm index 4997c5ee..2fad7e0b 100644 --- a/modules/webapp/src/main/elm/Page/UserSettings/View.elm +++ b/modules/webapp/src/main/elm/Page/UserSettings/View.elm @@ -4,6 +4,7 @@ import Comp.ChangePasswordForm import Comp.EmailSettingsManage import Comp.ImapSettingsManage import Comp.NotificationForm +import Comp.ScanMailboxForm import Html exposing (..) import Html.Attributes exposing (..) import Html.Events exposing (onClick) @@ -24,6 +25,7 @@ view model = , makeTab model EmailSettingsTab "E-Mail Settings (SMTP)" "mail icon" , makeTab model ImapSettingsTab "E-Mail Settings (IMAP)" "mail icon" , makeTab model NotificationTab "Notification Task" "bullhorn icon" + , makeTab model ScanMailboxTab "Scan Mailbox Task" "envelope open outline icon" ] ] ] @@ -42,6 +44,9 @@ view model = Just ImapSettingsTab -> viewImapSettings model + Just ScanMailboxTab -> + viewScanMailboxForm model + Nothing -> [] ) @@ -118,3 +123,26 @@ viewNotificationForm model = , Html.map NotificationMsg (Comp.NotificationForm.view "segment" model.notificationModel) ] + + +viewScanMailboxForm : Model -> List (Html Msg) +viewScanMailboxForm model = + [ h2 [ class "ui header" ] + [ i [ class "ui bullhorn icon" ] [] + , div [ class "content" ] + [ text "Scan Mailbox" + ] + ] + , p [] + [ text "Docspell can scan folders of your mailbox for mails to import. " + , text "You need to provide a connection in " + , text "your e-mail (imap) settings." + ] + , p [] + [ text "Each time this is executed, docspell goes through all configured folders " + , text "and imports mails matching the search criteria. The number of mails to import " + , text "at one task run is limited. Mails already read in are skipped." + ] + , Html.map ScanMailboxMsg + (Comp.ScanMailboxForm.view "segment" model.scanMailboxModel) + ]