Merge pull request #128 from eikek/multiple-scan-mailbox

Multiple scan mailbox
This commit is contained in:
eikek 2020-05-21 22:37:30 +02:00 committed by GitHub
commit 72f0897a3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 756 additions and 155 deletions

View File

@ -2,8 +2,10 @@ package docspell.backend.ops
import cats.implicits._ import cats.implicits._
import cats.effect._ import cats.effect._
import cats.data.OptionT
import com.github.eikek.calev.CalEvent import com.github.eikek.calev.CalEvent
import io.circe.Encoder import io.circe.Encoder
import fs2.Stream
import docspell.store.queue.JobQueue import docspell.store.queue.JobQueue
import docspell.store.usertask._ import docspell.store.usertask._
@ -11,10 +13,15 @@ import docspell.common._
trait OUserTask[F[_]] { trait OUserTask[F[_]] {
/** Return the settings for the scan-mailbox task of the current user. /** Return the settings for all scan-mailbox tasks of the current user.
* There is at most one such task per user.
*/ */
def getScanMailbox(account: AccountId): F[UserTask[ScanMailboxArgs]] def getScanMailbox(account: AccountId): Stream[F, UserTask[ScanMailboxArgs]]
/** Find a scan-mailbox task by the given id. */
def findScanMailbox(
id: Ident,
account: AccountId
): OptionT[F, UserTask[ScanMailboxArgs]]
/** Updates the scan-mailbox tasks and notifies the joex nodes. /** Updates the scan-mailbox tasks and notifies the joex nodes.
*/ */
@ -24,7 +31,9 @@ trait OUserTask[F[_]] {
): F[Unit] ): F[Unit]
/** Return the settings for the notify-due-items task of the current /** Return the settings for the notify-due-items task of the current
* user. There is at most one such task per user. * user. There is at most one such task per user. If no task has
* been created/submitted a new one with default values is
* returned.
*/ */
def getNotifyDueItems(account: AccountId): F[UserTask[NotifyDueItemsArgs]] def getNotifyDueItems(account: AccountId): F[UserTask[NotifyDueItemsArgs]]
@ -35,6 +44,9 @@ trait OUserTask[F[_]] {
task: UserTask[NotifyDueItemsArgs] task: UserTask[NotifyDueItemsArgs]
): F[Unit] ): F[Unit]
/** Removes a user task with the given id. */
def deleteTask(account: AccountId, id: Ident): F[Unit]
/** Discards the schedule and immediately submits the task to the job /** Discards the schedule and immediately submits the task to the job
* executor's queue. It will not update the corresponding periodic * executor's queue. It will not update the corresponding periodic
* task. * task.
@ -63,17 +75,28 @@ object OUserTask {
_ <- joex.notifyAllNodes _ <- joex.notifyAllNodes
} yield () } yield ()
def getScanMailbox(account: AccountId): F[UserTask[ScanMailboxArgs]] = def getScanMailbox(account: AccountId): Stream[F, UserTask[ScanMailboxArgs]] =
store store
.getOneByName[ScanMailboxArgs](account, ScanMailboxArgs.taskName) .getByName[ScanMailboxArgs](account, ScanMailboxArgs.taskName)
.getOrElseF(scanMailboxDefault(account))
def findScanMailbox(
id: Ident,
account: AccountId
): OptionT[F, UserTask[ScanMailboxArgs]] =
OptionT(getScanMailbox(account).find(_.id == id).compile.last)
def deleteTask(account: AccountId, id: Ident): F[Unit] =
(for {
_ <- store.getByIdRaw(account, id)
_ <- OptionT.liftF(store.deleteTask(account, id))
} yield ()).getOrElse(())
def submitScanMailbox( def submitScanMailbox(
account: AccountId, account: AccountId,
task: UserTask[ScanMailboxArgs] task: UserTask[ScanMailboxArgs]
): F[Unit] = ): F[Unit] =
for { for {
_ <- store.updateOneTask[ScanMailboxArgs](account, task) _ <- store.updateTask[ScanMailboxArgs](account, task)
_ <- joex.notifyAllNodes _ <- joex.notifyAllNodes
} yield () } yield ()
@ -113,26 +136,26 @@ object OUserTask {
) )
) )
private def scanMailboxDefault( // private def scanMailboxDefault(
account: AccountId // account: AccountId
): F[UserTask[ScanMailboxArgs]] = // ): F[UserTask[ScanMailboxArgs]] =
for { // for {
id <- Ident.randomId[F] // id <- Ident.randomId[F]
} yield UserTask( // } yield UserTask(
id, // id,
ScanMailboxArgs.taskName, // ScanMailboxArgs.taskName,
false, // false,
CalEvent.unsafe("*-*-* 0,12:00"), // CalEvent.unsafe("*-*-* 0,12:00"),
ScanMailboxArgs( // ScanMailboxArgs(
account, // account,
Ident.unsafe(""), // Ident.unsafe(""),
Nil, // Nil,
Some(Duration.hours(12)), // Some(Duration.hours(12)),
None, // None,
false, // false,
None // None
) // )
) // )
}) })
} }

View File

@ -9,7 +9,13 @@ import cats.effect.Sync
import io.circe.{Decoder, Encoder} import io.circe.{Decoder, Encoder}
import scodec.bits.ByteVector import scodec.bits.ByteVector
case class Ident(id: String) {} case class Ident(id: String) {
def isEmpty: Boolean =
id.trim.isEmpty
def nonEmpty: Boolean =
!isEmpty
}
object Ident { object Ident {
implicit val identEq: Eq[Ident] = implicit val identEq: Eq[Ident] =

View File

@ -1760,9 +1760,10 @@ paths:
tags: [ User Tasks ] tags: [ User Tasks ]
summary: Get settings for "Scan Mailbox" task summary: Get settings for "Scan Mailbox" task
description: | description: |
Return the current settings for the scan mailbox task of the Return the current settings for the scan-mailbox tasks of the
authenticated user. Users can periodically fetch mails to be authenticated user. Users can periodically fetch mails to be
imported into docspell. imported into docspell. It is possible to have multiple of
these tasks.
security: security:
- authTokenHeader: [] - authTokenHeader: []
responses: responses:
@ -1771,13 +1772,13 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/ScanMailboxSettings" $ref: "#/components/schemas/ScanMailboxSettingsList"
post: post:
tags: [ User Tasks ] tags: [ User Tasks ]
summary: Change current settings for "Scan Mailbox" task summary: Create settings for "Scan Mailbox" task
description: | description: |
Change the current settings for the scan-mailbox task of the Create new settings for a scan-mailbox task. The id field in
authenticated user. the input data is ignored.
security: security:
- authTokenHeader: [] - authTokenHeader: []
requestBody: requestBody:
@ -1792,6 +1793,61 @@ paths:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/BasicResult" $ref: "#/components/schemas/BasicResult"
put:
tags: [ User Tasks ]
summary: Change current settings for "Scan Mailbox" task
description: |
Change the settings for a scan-mailbox task. The task is
looked up by its id.
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/{id}:
parameters:
- $ref: "#/components/parameters/id"
get:
tags: [ User Tasks ]
summary: Get settings for "Scan Mailbox" task
description: |
Return the current settings for a single 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"
delete:
tags: [ User Tasks ]
summary: Delete a scan-mailbox task.
description: |
Deletes the settings to a scan-mailbox task of the
authenticated user.
security:
- authTokenHeader: []
responses:
200:
description: Ok
content:
application/json:
schema:
$ref: "#/components/schemas/BasicResult"
/sec/usertask/scanmailbox/startonce: /sec/usertask/scanmailbox/startonce:
post: post:
tags: [ User Tasks ] tags: [ User Tasks ]
@ -1816,6 +1872,16 @@ paths:
components: components:
schemas: schemas:
ScanMailboxSettingsList:
description: |
A list of scan-mailbox tasks.
required:
- items
properties:
items:
type: array
items:
$ref: "#/components/schemas/ScanMailboxSettings"
ScanMailboxSettings: ScanMailboxSettings:
description: | description: |
Settings for the scan mailbox task. Settings for the scan mailbox task.

View File

@ -2,6 +2,7 @@ package docspell.restserver.routes
import cats.effect._ import cats.effect._
import cats.implicits._ import cats.implicits._
import cats.data.OptionT
import org.http4s._ import org.http4s._
import org.http4s.dsl.Http4sDsl import org.http4s.dsl.Http4sDsl
import org.http4s.circe.CirceEntityEncoder._ import org.http4s.circe.CirceEntityEncoder._
@ -25,10 +26,18 @@ object ScanMailboxRoutes {
import dsl._ import dsl._
HttpRoutes.of { HttpRoutes.of {
case GET -> Root / Ident(id) =>
(for {
task <- ut.findScanMailbox(id, user.account)
res <- OptionT.liftF(taskToSettings(user.account, backend, task))
resp <- OptionT.liftF(Ok(res))
} yield resp).getOrElseF(NotFound())
case req @ POST -> Root / "startonce" => case req @ POST -> Root / "startonce" =>
for { for {
data <- req.as[ScanMailboxSettings] data <- req.as[ScanMailboxSettings]
task = makeTask(user.account, data) newId <- Ident.randomId[F]
task <- makeTask(newId, user.account, data)
res <- res <-
ut.executeNow(user.account, task) ut.executeNow(user.account, task)
.attempt .attempt
@ -36,43 +45,74 @@ object ScanMailboxRoutes {
resp <- Ok(res) resp <- Ok(res)
} yield resp } yield resp
case GET -> Root => case DELETE -> Root / Ident(id) =>
for { for {
task <- ut.getScanMailbox(user.account) res <-
res <- taskToSettings(user.account, backend, task) ut.deleteTask(user.account, id)
.attempt
.map(Conversions.basicResult(_, "Deleted successfully."))
resp <- Ok(res) resp <- Ok(res)
} yield resp } yield resp
case req @ PUT -> Root =>
def run(data: ScanMailboxSettings) =
for {
task <- makeTask(data.id, user.account, data)
res <-
ut.submitScanMailbox(user.account, task)
.attempt
.map(Conversions.basicResult(_, "Saved successfully."))
resp <- Ok(res)
} yield resp
for {
data <- req.as[ScanMailboxSettings]
resp <-
if (data.id.isEmpty) Ok(BasicResult(false, "Empty id is not allowed"))
else run(data)
} yield resp
case req @ POST -> Root => case req @ POST -> Root =>
for { for {
data <- req.as[ScanMailboxSettings] data <- req.as[ScanMailboxSettings]
task = makeTask(user.account, data) newId <- Ident.randomId[F]
task <- makeTask(newId, user.account, data)
res <- res <-
ut.submitScanMailbox(user.account, task) ut.submitScanMailbox(user.account, task)
.attempt .attempt
.map(Conversions.basicResult(_, "Saved successfully.")) .map(Conversions.basicResult(_, "Saved successfully."))
resp <- Ok(res) resp <- Ok(res)
} yield resp } yield resp
case GET -> Root =>
ut.getScanMailbox(user.account)
.evalMap(task => taskToSettings(user.account, backend, task))
.compile
.toVector
.map(v => ScanMailboxSettingsList(v.toList))
.flatMap(Ok(_))
} }
} }
def makeTask( def makeTask[F[_]: Sync](
id: Ident,
user: AccountId, user: AccountId,
settings: ScanMailboxSettings settings: ScanMailboxSettings
): UserTask[ScanMailboxArgs] = ): F[UserTask[ScanMailboxArgs]] =
UserTask( Sync[F].pure(
settings.id, UserTask(
ScanMailboxArgs.taskName, id,
settings.enabled, ScanMailboxArgs.taskName,
settings.schedule, settings.enabled,
ScanMailboxArgs( settings.schedule,
user, ScanMailboxArgs(
settings.imapConnection, user,
settings.folders, settings.imapConnection,
settings.receivedSinceHours.map(_.toLong).map(Duration.hours), settings.folders,
settings.targetFolder, settings.receivedSinceHours.map(_.toLong).map(Duration.hours),
settings.deleteMail, settings.targetFolder,
settings.direction settings.deleteMail,
settings.direction
)
) )
) )

View File

@ -31,6 +31,20 @@ object QUserTask {
) )
).query[RPeriodicTask].stream.map(makeUserTask) ).query[RPeriodicTask].stream.map(makeUserTask)
def findById(
account: AccountId,
id: Ident
): ConnectionIO[Option[UserTask[String]]] =
selectSimple(
RPeriodicTask.Columns.all,
RPeriodicTask.table,
and(
cols.group.is(account.collective),
cols.submitter.is(account.user),
cols.id.is(id)
)
).query[RPeriodicTask].option.map(_.map(makeUserTask))
def insert(account: AccountId, task: UserTask[String]): ConnectionIO[Int] = def insert(account: AccountId, task: UserTask[String]): ConnectionIO[Int] =
for { for {
r <- task.toPeriodicTask[ConnectionIO](account) r <- task.toPeriodicTask[ConnectionIO](account)

View File

@ -42,12 +42,14 @@ trait UserTaskStore[F[_]] {
D: Decoder[A] D: Decoder[A]
): Stream[F, UserTask[A]] ): Stream[F, UserTask[A]]
/** Return a user-task with the given id. */
def getByIdRaw(account: AccountId, id: Ident): OptionT[F, UserTask[String]]
/** Updates or inserts the given task. /** Updates or inserts the given task.
* *
* The task is identified by its id. If no task with this id * The task is identified by its id. If no task with this id
* exists, a new one is created. Otherwise the existing task is * exists, a new one is created. Otherwise the existing task is
* updated. The job executors are notified if a task has been * updated.
* enabled.
*/ */
def updateTask[A](account: AccountId, ut: UserTask[A])(implicit E: Encoder[A]): F[Int] def updateTask[A](account: AccountId, ut: UserTask[A])(implicit E: Encoder[A]): F[Int]
@ -100,6 +102,9 @@ object UserTaskStore {
def getByNameRaw(account: AccountId, name: Ident): Stream[F, UserTask[String]] = def getByNameRaw(account: AccountId, name: Ident): Stream[F, UserTask[String]] =
store.transact(QUserTask.findByName(account, name)) store.transact(QUserTask.findByName(account, name))
def getByIdRaw(account: AccountId, id: Ident): OptionT[F, UserTask[String]] =
OptionT(store.transact(QUserTask.findById(account, id)))
def getByName[A](account: AccountId, name: Ident)(implicit def getByName[A](account: AccountId, name: Ident)(implicit
D: Decoder[A] D: Decoder[A]
): Stream[F, UserTask[A]] = ): Stream[F, UserTask[A]] =

View File

@ -4,6 +4,7 @@ module Api exposing
, checkCalEvent , checkCalEvent
, createImapSettings , createImapSettings
, createMailSettings , createMailSettings
, createScanMailbox
, deleteAttachment , deleteAttachment
, deleteEquip , deleteEquip
, deleteImapSettings , deleteImapSettings
@ -11,6 +12,7 @@ module Api exposing
, deleteMailSettings , deleteMailSettings
, deleteOrg , deleteOrg
, deletePerson , deletePerson
, deleteScanMailbox
, deleteSource , deleteSource
, deleteTag , deleteTag
, deleteUser , deleteUser
@ -67,7 +69,7 @@ module Api exposing
, startOnceNotifyDueItems , startOnceNotifyDueItems
, startOnceScanMailbox , startOnceScanMailbox
, submitNotifyDueItems , submitNotifyDueItems
, submitScanMailbox , updateScanMailbox
, upload , upload
, uploadSingle , uploadSingle
, versionInfo , versionInfo
@ -109,6 +111,7 @@ import Api.Model.PersonList exposing (PersonList)
import Api.Model.ReferenceList exposing (ReferenceList) import Api.Model.ReferenceList exposing (ReferenceList)
import Api.Model.Registration exposing (Registration) import Api.Model.Registration exposing (Registration)
import Api.Model.ScanMailboxSettings exposing (ScanMailboxSettings) import Api.Model.ScanMailboxSettings exposing (ScanMailboxSettings)
import Api.Model.ScanMailboxSettingsList exposing (ScanMailboxSettingsList)
import Api.Model.SentMails exposing (SentMails) import Api.Model.SentMails exposing (SentMails)
import Api.Model.SimpleMail exposing (SimpleMail) import Api.Model.SimpleMail exposing (SimpleMail)
import Api.Model.Source exposing (Source) import Api.Model.Source exposing (Source)
@ -134,6 +137,19 @@ import Util.Http as Http2
--- Scan Mailboxes --- Scan Mailboxes
deleteScanMailbox :
Flags
-> String
-> (Result Http.Error BasicResult -> msg)
-> Cmd msg
deleteScanMailbox flags id receive =
Http2.authDelete
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/scanmailbox/" ++ id
, account = getAccount flags
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
}
startOnceScanMailbox : startOnceScanMailbox :
Flags Flags
-> ScanMailboxSettings -> ScanMailboxSettings
@ -148,12 +164,26 @@ startOnceScanMailbox flags settings receive =
} }
submitScanMailbox : updateScanMailbox :
Flags Flags
-> ScanMailboxSettings -> ScanMailboxSettings
-> (Result Http.Error BasicResult -> msg) -> (Result Http.Error BasicResult -> msg)
-> Cmd msg -> Cmd msg
submitScanMailbox flags settings receive = updateScanMailbox flags settings receive =
Http2.authPut
{ 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
}
createScanMailbox :
Flags
-> ScanMailboxSettings
-> (Result Http.Error BasicResult -> msg)
-> Cmd msg
createScanMailbox flags settings receive =
Http2.authPost Http2.authPost
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/scanmailbox" { url = flags.config.baseUrl ++ "/api/v1/sec/usertask/scanmailbox"
, account = getAccount flags , account = getAccount flags
@ -164,13 +194,13 @@ submitScanMailbox flags settings receive =
getScanMailbox : getScanMailbox :
Flags Flags
-> (Result Http.Error ScanMailboxSettings -> msg) -> (Result Http.Error ScanMailboxSettingsList -> msg)
-> Cmd msg -> Cmd msg
getScanMailbox flags receive = getScanMailbox flags receive =
Http2.authGet Http2.authGet
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/scanmailbox" { url = flags.config.baseUrl ++ "/api/v1/sec/usertask/scanmailbox"
, account = getAccount flags , account = getAccount flags
, expect = Http.expectJson receive Api.Model.ScanMailboxSettings.decoder , expect = Http.expectJson receive Api.Model.ScanMailboxSettingsList.decoder
} }

View File

@ -180,14 +180,14 @@ viewItem item =
[ text item.source [ text item.source
] ]
, div , div
[ class "item" [ classList
[ ( "item", True )
, ( "invisible hidden", item.dueDate == Nothing )
]
, title ("Due on " ++ dueDate) , title ("Due on " ++ dueDate)
] ]
[ div [ div
[ classList [ class "ui basic grey label"
[ ( "ui basic grey label", True )
, ( "invisible hidden", item.dueDate == Nothing )
]
] ]
[ Icons.dueDateIcon [ Icons.dueDateIcon
, text (" " ++ dueDate) , text (" " ++ dueDate)

View File

@ -1,7 +1,9 @@
module Comp.ScanMailboxForm exposing module Comp.ScanMailboxForm exposing
( Model ( Action(..)
, Model
, Msg , Msg
, init , init
, initWith
, update , update
, view , view
) )
@ -10,12 +12,11 @@ import Api
import Api.Model.BasicResult exposing (BasicResult) import Api.Model.BasicResult exposing (BasicResult)
import Api.Model.ImapSettingsList exposing (ImapSettingsList) import Api.Model.ImapSettingsList exposing (ImapSettingsList)
import Api.Model.ScanMailboxSettings exposing (ScanMailboxSettings) import Api.Model.ScanMailboxSettings exposing (ScanMailboxSettings)
import Api.Model.Tag exposing (Tag)
import Api.Model.TagList exposing (TagList)
import Comp.CalEventInput import Comp.CalEventInput
import Comp.Dropdown import Comp.Dropdown
import Comp.IntField import Comp.IntField
import Comp.StringListInput import Comp.StringListInput
import Comp.YesNoDimmer
import Data.CalEvent exposing (CalEvent) import Data.CalEvent exposing (CalEvent)
import Data.Direction exposing (Direction(..)) import Data.Direction exposing (Direction(..))
import Data.Flags exposing (Flags) import Data.Flags exposing (Flags)
@ -45,31 +46,75 @@ type alias Model =
, scheduleModel : Comp.CalEventInput.Model , scheduleModel : Comp.CalEventInput.Model
, formMsg : Maybe BasicResult , formMsg : Maybe BasicResult
, loading : Int , loading : Int
, yesNoDelete : Comp.YesNoDimmer.Model
} }
type Action
= SubmitAction ScanMailboxSettings
| StartOnceAction ScanMailboxSettings
| CancelAction
| DeleteAction String
| NoAction
type Msg type Msg
= Submit = Submit
| Cancel
| RequestDelete
| ConnMsg (Comp.Dropdown.Msg String) | ConnMsg (Comp.Dropdown.Msg String)
| ConnResp (Result Http.Error ImapSettingsList) | ConnResp (Result Http.Error ImapSettingsList)
| ToggleEnabled | ToggleEnabled
| ToggleDeleteMail | ToggleDeleteMail
| CalEventMsg Comp.CalEventInput.Msg | CalEventMsg Comp.CalEventInput.Msg
| SetScanMailboxSettings (Result Http.Error ScanMailboxSettings)
| SubmitResp (Result Http.Error BasicResult)
| StartOnce | StartOnce
| ReceivedHoursMsg Comp.IntField.Msg | ReceivedHoursMsg Comp.IntField.Msg
| SetTargetFolder String | SetTargetFolder String
| FoldersMsg Comp.StringListInput.Msg | FoldersMsg Comp.StringListInput.Msg
| DirectionMsg (Maybe Direction) | DirectionMsg (Maybe Direction)
| YesNoDeleteMsg Comp.YesNoDimmer.Msg
initCmd : Flags -> Cmd Msg initWith : Flags -> ScanMailboxSettings -> ( Model, Cmd Msg )
initCmd flags = initWith flags s =
Cmd.batch let
( im, _ ) =
init flags
imap =
Util.Maybe.fromString s.imapConnection
|> Maybe.map List.singleton
|> Maybe.withDefault []
( nm, _, nc ) =
update flags (ConnMsg (Comp.Dropdown.SetSelection imap)) im
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
, deleteMail = s.deleteMail
, receivedHours = s.receivedSinceHours
, targetFolder = s.targetFolder
, folders = s.folders
, schedule = Data.Validated.Unknown newSchedule
, direction = Maybe.andThen Data.Direction.fromString s.direction
, scheduleModel = sm
, formMsg = Nothing
, yesNoDelete = Comp.YesNoDimmer.emptyModel
}
, Cmd.batch
[ Api.getImapSettings flags "" ConnResp [ Api.getImapSettings flags "" ConnResp
, Api.getScanMailbox flags SetScanMailboxSettings , nc
, Cmd.map CalEventMsg sc
] ]
)
init : Flags -> ( Model, Cmd Msg ) init : Flags -> ( Model, Cmd Msg )
@ -98,10 +143,11 @@ init flags =
, schedule = initialSchedule , schedule = initialSchedule
, scheduleModel = sm , scheduleModel = sm
, formMsg = Nothing , formMsg = Nothing
, loading = 2 , loading = 1
, yesNoDelete = Comp.YesNoDimmer.emptyModel
} }
, Cmd.batch , Cmd.batch
[ initCmd flags [ Api.getImapSettings flags "" ConnResp
, Cmd.map CalEventMsg sc , Cmd.map CalEventMsg sc
] ]
) )
@ -148,12 +194,13 @@ makeSettings model =
infolders infolders
withValidSettings : (ScanMailboxSettings -> Cmd Msg) -> Model -> ( Model, Cmd Msg ) withValidSettings : (ScanMailboxSettings -> Action) -> Model -> ( Model, Action, Cmd Msg )
withValidSettings mkcmd model = withValidSettings mkAction model =
case makeSettings model of case makeSettings model of
Valid set -> Valid set ->
( { model | formMsg = Nothing } ( { model | formMsg = Nothing }
, mkcmd set , mkAction set
, Cmd.none
) )
Invalid errs _ -> Invalid errs _ ->
@ -161,15 +208,19 @@ withValidSettings mkcmd model =
errMsg = errMsg =
String.join ", " errs String.join ", " errs
in in
( { model | formMsg = Just (BasicResult False errMsg) }, Cmd.none ) ( { model | formMsg = Just (BasicResult False errMsg) }
, NoAction
, Cmd.none
)
Unknown _ -> Unknown _ ->
( { model | formMsg = Just (BasicResult False "An unknown error occured") } ( { model | formMsg = Just (BasicResult False "An unknown error occured") }
, NoAction
, Cmd.none , Cmd.none
) )
update : Flags -> Msg -> Model -> ( Model, Cmd Msg ) update : Flags -> Msg -> Model -> ( Model, Action, Cmd Msg )
update flags msg model = update flags msg model =
case msg of case msg of
CalEventMsg lmsg -> CalEventMsg lmsg ->
@ -185,6 +236,7 @@ update flags msg model =
, scheduleModel = cm , scheduleModel = cm
, formMsg = Nothing , formMsg = Nothing
} }
, NoAction
, Cmd.map CalEventMsg cc , Cmd.map CalEventMsg cc
) )
@ -197,6 +249,7 @@ update flags msg model =
| connectionModel = cm | connectionModel = cm
, formMsg = Nothing , formMsg = Nothing
} }
, NoAction
, Cmd.map ConnMsg cc , Cmd.map ConnMsg cc
) )
@ -226,6 +279,7 @@ update flags msg model =
else else
Nothing Nothing
} }
, NoAction
, Cmd.none , Cmd.none
) )
@ -234,6 +288,7 @@ update flags msg model =
| formMsg = Just (BasicResult False (Util.Http.errorToString err)) | formMsg = Just (BasicResult False (Util.Http.errorToString err))
, loading = model.loading - 1 , loading = model.loading - 1
} }
, NoAction
, Cmd.none , Cmd.none
) )
@ -242,6 +297,7 @@ update flags msg model =
| enabled = not model.enabled | enabled = not model.enabled
, formMsg = Nothing , formMsg = Nothing
} }
, NoAction
, Cmd.none , Cmd.none
) )
@ -250,6 +306,7 @@ update flags msg model =
| deleteMail = not model.deleteMail | deleteMail = not model.deleteMail
, formMsg = Nothing , formMsg = Nothing
} }
, NoAction
, Cmd.none , Cmd.none
) )
@ -263,11 +320,13 @@ update flags msg model =
, receivedHours = val , receivedHours = val
, formMsg = Nothing , formMsg = Nothing
} }
, NoAction
, Cmd.none , Cmd.none
) )
SetTargetFolder str -> SetTargetFolder str ->
( { model | targetFolder = Util.Maybe.fromString str } ( { model | targetFolder = Util.Maybe.fromString str }
, NoAction
, Cmd.none , Cmd.none
) )
@ -291,80 +350,55 @@ update flags msg model =
| foldersModel = fm | foldersModel = fm
, folders = newList , folders = newList
} }
, NoAction
, Cmd.none , Cmd.none
) )
DirectionMsg md -> DirectionMsg md ->
( { model | direction = md } ( { model | direction = md }
, Cmd.none , NoAction
)
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
, deleteMail = s.deleteMail
, receivedHours = s.receivedSinceHours
, targetFolder = s.targetFolder
, folders = s.folders
, schedule = Data.Validated.Unknown newSchedule
, direction = Maybe.andThen Data.Direction.fromString s.direction
, 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 , Cmd.none
) )
Submit -> Submit ->
withValidSettings withValidSettings
(\set -> Api.submitScanMailbox flags set SubmitResp) SubmitAction
model model
StartOnce -> StartOnce ->
withValidSettings withValidSettings
(\set -> Api.startOnceScanMailbox flags set SubmitResp) StartOnceAction
model model
SubmitResp (Ok res) -> Cancel ->
( { model | formMsg = Just res } ( model, CancelAction, Cmd.none )
RequestDelete ->
let
( ym, _ ) =
Comp.YesNoDimmer.update
Comp.YesNoDimmer.activate
model.yesNoDelete
in
( { model | yesNoDelete = ym }
, NoAction
, Cmd.none , Cmd.none
) )
SubmitResp (Err err) -> YesNoDeleteMsg lm ->
( { model let
| formMsg = Just (BasicResult False (Util.Http.errorToString err)) ( ym, flag ) =
} Comp.YesNoDimmer.update lm model.yesNoDelete
act =
if flag then
DeleteAction model.settings.id
else
NoAction
in
( { model | yesNoDelete = ym }
, act
, Cmd.none , Cmd.none
) )
@ -396,7 +430,8 @@ view extraClasses model =
, ( "success", isFormSuccess model ) , ( "success", isFormSuccess model )
] ]
] ]
[ div [ Html.map YesNoDeleteMsg (Comp.YesNoDimmer.view model.yesNoDelete)
, div
[ classList [ classList
[ ( "ui dimmer", True ) [ ( "ui dimmer", True )
, ( "active", model.loading > 0 ) , ( "active", model.loading > 0 )
@ -464,7 +499,9 @@ view extraClasses model =
, label [] [ text "Delete imported mails" ] , label [] [ text "Delete imported mails" ]
] ]
, span [ class "small-info" ] , span [ class "small-info" ]
[ text "Whether to delete all mails successfully imported into docspell." [ text "Whether to delete all mails successfully imported into docspell. This only applies if "
, em [] [ text "target folder" ]
, text " is not set."
] ]
] ]
, div [ class "required field" ] , div [ class "required field" ]
@ -553,6 +590,18 @@ view extraClasses model =
] ]
[ text "Submit" [ text "Submit"
] ]
, button
[ class "ui secondary button"
, onClick Cancel
]
[ text "Cancel"
]
, button
[ class "ui red button"
, onClick RequestDelete
]
[ text "Delete"
]
, button , button
[ class "ui right floated button" [ class "ui right floated button"
, onClick StartOnce , onClick StartOnce

View File

@ -0,0 +1,104 @@
module Comp.ScanMailboxList exposing
( Action(..)
, Model
, Msg
, init
, update
, view
)
import Api.Model.ScanMailboxSettings exposing (ScanMailboxSettings)
import Api.Model.ScanMailboxSettingsList exposing (ScanMailboxSettingsList)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick)
import Util.Html
type alias Model =
{}
type Msg
= EditSettings ScanMailboxSettings
type Action
= NoAction
| EditAction ScanMailboxSettings
init : Model
init =
{}
update : Msg -> Model -> ( Model, Action )
update msg model =
case msg of
EditSettings settings ->
( model, EditAction settings )
view : Model -> List ScanMailboxSettings -> Html Msg
view model items =
div []
[ table [ class "ui very basic table" ]
[ thead []
[ th [ class "collapsing" ] []
, th [ class "collapsing" ]
[ i [ class "check icon" ] []
]
, th [] [ text "Connection" ]
, th [] [ text "Folders" ]
, th [] [ text "Schedule" ]
, th [] [ text "Received Since" ]
, th [] [ text "Target" ]
, th [] [ text "Delete" ]
]
, tbody []
(List.map viewItem items)
]
]
viewItem : ScanMailboxSettings -> Html Msg
viewItem item =
tr []
[ td [ class "collapsing" ]
[ a
[ href "#"
, class "ui basic small blue label"
, onClick (EditSettings item)
]
[ i [ class "edit icon" ] []
, text "Edit"
]
]
, td [ class "collapsing" ]
[ Util.Html.checkbox item.enabled
]
, td []
[ text item.imapConnection
]
, td []
[ String.join ", " item.folders |> text
]
, td []
[ code []
[ text item.schedule
]
]
, td []
[ Maybe.map String.fromInt item.receivedSinceHours
|> Maybe.withDefault "-"
|> text
]
, td []
[ Maybe.withDefault "-" item.targetFolder
|> text
]
, td []
[ Util.Html.checkbox item.deleteMail
]
]

View File

@ -0,0 +1,242 @@
module Comp.ScanMailboxManage exposing
( Model
, Msg
, init
, update
, view
)
import Api
import Api.Model.BasicResult exposing (BasicResult)
import Api.Model.ScanMailboxSettings exposing (ScanMailboxSettings)
import Api.Model.ScanMailboxSettingsList exposing (ScanMailboxSettingsList)
import Comp.ScanMailboxForm
import Comp.ScanMailboxList
import Data.Flags exposing (Flags)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick)
import Http
import Util.Http
type alias Model =
{ listModel : Comp.ScanMailboxList.Model
, detailModel : Maybe Comp.ScanMailboxForm.Model
, items : List ScanMailboxSettings
, result : Maybe BasicResult
}
type Msg
= ListMsg Comp.ScanMailboxList.Msg
| DetailMsg Comp.ScanMailboxForm.Msg
| GetDataResp (Result Http.Error ScanMailboxSettingsList)
| NewTask
| SubmitResp (Result Http.Error BasicResult)
| DeleteResp (Result Http.Error BasicResult)
initModel : Model
initModel =
{ listModel = Comp.ScanMailboxList.init
, detailModel = Nothing
, items = []
, result = Nothing
}
initCmd : Flags -> Cmd Msg
initCmd flags =
Api.getScanMailbox 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
GetDataResp (Ok res) ->
( { model
| items = res.items
, result = Nothing
}
, Cmd.none
)
GetDataResp (Err err) ->
( { model | result = Just (BasicResult False (Util.Http.errorToString err)) }
, Cmd.none
)
ListMsg lm ->
let
( mm, action ) =
Comp.ScanMailboxList.update lm model.listModel
( detail, cmd ) =
case action of
Comp.ScanMailboxList.NoAction ->
( Nothing, Cmd.none )
Comp.ScanMailboxList.EditAction settings ->
let
( dm, dc ) =
Comp.ScanMailboxForm.initWith flags settings
in
( Just dm, Cmd.map DetailMsg dc )
in
( { model
| listModel = mm
, detailModel = detail
}
, cmd
)
DetailMsg lm ->
case model.detailModel of
Just dm ->
let
( mm, action, mc ) =
Comp.ScanMailboxForm.update flags lm dm
( model_, cmd_ ) =
case action of
Comp.ScanMailboxForm.NoAction ->
( { model | detailModel = Just mm }
, Cmd.none
)
Comp.ScanMailboxForm.SubmitAction settings ->
( { model
| detailModel = Just mm
, result = Nothing
}
, if settings.id == "" then
Api.createScanMailbox flags settings SubmitResp
else
Api.updateScanMailbox flags settings SubmitResp
)
Comp.ScanMailboxForm.CancelAction ->
( { model
| detailModel = Nothing
, result = Nothing
}
, initCmd flags
)
Comp.ScanMailboxForm.StartOnceAction settings ->
( { model
| detailModel = Just mm
, result = Nothing
}
, Api.startOnceScanMailbox flags settings SubmitResp
)
Comp.ScanMailboxForm.DeleteAction id ->
( { model
| detailModel = Just mm
, result = Nothing
}
, Api.deleteScanMailbox flags id DeleteResp
)
in
( model_
, Cmd.batch
[ Cmd.map DetailMsg mc
, cmd_
]
)
Nothing ->
( model, Cmd.none )
NewTask ->
let
( mm, mc ) =
Comp.ScanMailboxForm.init flags
in
( { model | detailModel = Just mm }, Cmd.map DetailMsg mc )
SubmitResp (Ok res) ->
( { model | result = Just res }
, Cmd.none
)
SubmitResp (Err err) ->
( { model | result = Just (BasicResult False (Util.Http.errorToString err)) }
, Cmd.none
)
DeleteResp (Ok res) ->
if res.success then
( { model | result = Nothing, detailModel = Nothing }
, initCmd flags
)
else
( { model | result = Just res }
, Cmd.none
)
DeleteResp (Err err) ->
( { model | result = Just (BasicResult False (Util.Http.errorToString err)) }
, Cmd.none
)
--- View
view : Model -> Html Msg
view model =
div []
[ div [ class "ui menu" ]
[ a
[ class "link item"
, href "#"
, onClick NewTask
]
[ i [ class "add icon" ] []
, text "New Task"
]
]
, div
[ classList
[ ( "ui message", True )
, ( "error", Maybe.map .success model.result == Just False )
, ( "success", Maybe.map .success model.result == Just True )
, ( "invisible hidden", model.result == Nothing )
]
]
[ Maybe.map .message model.result
|> Maybe.withDefault ""
|> text
]
, case model.detailModel of
Just settings ->
viewForm settings
Nothing ->
viewList model
]
viewForm : Comp.ScanMailboxForm.Model -> Html Msg
viewForm model =
Html.map DetailMsg (Comp.ScanMailboxForm.view "segment" model)
viewList : Model -> Html Msg
viewList model =
Html.map ListMsg (Comp.ScanMailboxList.view model.listModel model.items)

View File

@ -9,7 +9,7 @@ import Comp.ChangePasswordForm
import Comp.EmailSettingsManage import Comp.EmailSettingsManage
import Comp.ImapSettingsManage import Comp.ImapSettingsManage
import Comp.NotificationForm import Comp.NotificationForm
import Comp.ScanMailboxForm import Comp.ScanMailboxManage
import Data.Flags exposing (Flags) import Data.Flags exposing (Flags)
@ -19,7 +19,7 @@ type alias Model =
, emailSettingsModel : Comp.EmailSettingsManage.Model , emailSettingsModel : Comp.EmailSettingsManage.Model
, imapSettingsModel : Comp.ImapSettingsManage.Model , imapSettingsModel : Comp.ImapSettingsManage.Model
, notificationModel : Comp.NotificationForm.Model , notificationModel : Comp.NotificationForm.Model
, scanMailboxModel : Comp.ScanMailboxForm.Model , scanMailboxModel : Comp.ScanMailboxManage.Model
} }
@ -30,7 +30,7 @@ emptyModel flags =
, emailSettingsModel = Comp.EmailSettingsManage.emptyModel , emailSettingsModel = Comp.EmailSettingsManage.emptyModel
, imapSettingsModel = Comp.ImapSettingsManage.emptyModel , imapSettingsModel = Comp.ImapSettingsManage.emptyModel
, notificationModel = Tuple.first (Comp.NotificationForm.init flags) , notificationModel = Tuple.first (Comp.NotificationForm.init flags)
, scanMailboxModel = Tuple.first (Comp.ScanMailboxForm.init flags) , scanMailboxModel = Tuple.first (Comp.ScanMailboxManage.init flags)
} }
@ -48,4 +48,4 @@ type Msg
| EmailSettingsMsg Comp.EmailSettingsManage.Msg | EmailSettingsMsg Comp.EmailSettingsManage.Msg
| NotificationMsg Comp.NotificationForm.Msg | NotificationMsg Comp.NotificationForm.Msg
| ImapSettingsMsg Comp.ImapSettingsManage.Msg | ImapSettingsMsg Comp.ImapSettingsManage.Msg
| ScanMailboxMsg Comp.ScanMailboxForm.Msg | ScanMailboxMsg Comp.ScanMailboxManage.Msg

View File

@ -4,7 +4,7 @@ import Comp.ChangePasswordForm
import Comp.EmailSettingsManage import Comp.EmailSettingsManage
import Comp.ImapSettingsManage import Comp.ImapSettingsManage
import Comp.NotificationForm import Comp.NotificationForm
import Comp.ScanMailboxForm import Comp.ScanMailboxManage
import Data.Flags exposing (Flags) import Data.Flags exposing (Flags)
import Page.UserSettings.Data exposing (..) import Page.UserSettings.Data exposing (..)
@ -48,7 +48,7 @@ update flags msg model =
let let
initCmd = initCmd =
Cmd.map ScanMailboxMsg Cmd.map ScanMailboxMsg
(Tuple.second (Comp.ScanMailboxForm.init flags)) (Tuple.second (Comp.ScanMailboxManage.init flags))
in in
( m, initCmd ) ( m, initCmd )
in in
@ -87,7 +87,7 @@ update flags msg model =
ScanMailboxMsg lm -> ScanMailboxMsg lm ->
let let
( m2, c2 ) = ( m2, c2 ) =
Comp.ScanMailboxForm.update flags lm model.scanMailboxModel Comp.ScanMailboxManage.update flags lm model.scanMailboxModel
in in
( { model | scanMailboxModel = m2 } ( { model | scanMailboxModel = m2 }
, Cmd.map ScanMailboxMsg c2 , Cmd.map ScanMailboxMsg c2

View File

@ -4,7 +4,7 @@ import Comp.ChangePasswordForm
import Comp.EmailSettingsManage import Comp.EmailSettingsManage
import Comp.ImapSettingsManage import Comp.ImapSettingsManage
import Comp.NotificationForm import Comp.NotificationForm
import Comp.ScanMailboxForm import Comp.ScanMailboxManage
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (..) import Html.Attributes exposing (..)
import Html.Events exposing (onClick) import Html.Events exposing (onClick)
@ -45,7 +45,7 @@ view model =
viewImapSettings model viewImapSettings model
Just ScanMailboxTab -> Just ScanMailboxTab ->
viewScanMailboxForm model viewScanMailboxManage model
Nothing -> Nothing ->
[] []
@ -126,8 +126,8 @@ viewNotificationForm model =
] ]
viewScanMailboxForm : Model -> List (Html Msg) viewScanMailboxManage : Model -> List (Html Msg)
viewScanMailboxForm model = viewScanMailboxManage model =
[ h2 [ class "ui header" ] [ h2 [ class "ui header" ]
[ i [ class "ui envelope open outline icon" ] [] [ i [ class "ui envelope open outline icon" ] []
, div [ class "content" ] , div [ class "content" ]
@ -151,5 +151,7 @@ viewScanMailboxForm model =
again.""" again."""
] ]
, Html.map ScanMailboxMsg , Html.map ScanMailboxMsg
(Comp.ScanMailboxForm.view "segment" model.scanMailboxModel) (Comp.ScanMailboxManage.view
model.scanMailboxModel
)
] ]

View File

@ -1,17 +1,37 @@
module Util.Html exposing module Util.Html exposing
( KeyCode(..) ( KeyCode(..)
, checkbox
, classActive , classActive
, intToKeyCode , intToKeyCode
, onClickk , onClickk
, onKeyUp , onKeyUp
) )
import Html exposing (Attribute) import Html exposing (Attribute, Html, i)
import Html.Attributes exposing (class) import Html.Attributes exposing (class)
import Html.Events exposing (keyCode, on) import Html.Events exposing (keyCode, on)
import Json.Decode as Decode import Json.Decode as Decode
checkboxChecked : Html msg
checkboxChecked =
i [ class "ui check square outline icon" ] []
checkboxUnchecked : Html msg
checkboxUnchecked =
i [ class "ui square outline icon" ] []
checkbox : Bool -> Html msg
checkbox flag =
if flag then
checkboxChecked
else
checkboxUnchecked
type KeyCode type KeyCode
= Up = Up
| Down | Down