mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-22 10:28:27 +00:00
Merge pull request #1301 from eikek/notification-improvements
Notification improvements
This commit is contained in:
@ -11,7 +11,6 @@ import cats.implicits._
|
|||||||
|
|
||||||
import docspell.backend.MailAddressCodec
|
import docspell.backend.MailAddressCodec
|
||||||
import docspell.common._
|
import docspell.common._
|
||||||
import docspell.notification.api.ChannelOrRef._
|
|
||||||
import docspell.notification.api.PeriodicQueryArgs
|
import docspell.notification.api.PeriodicQueryArgs
|
||||||
import docspell.store.records.RJob
|
import docspell.store.records.RJob
|
||||||
|
|
||||||
@ -25,7 +24,7 @@ object JobFactory extends MailAddressCodec {
|
|||||||
PeriodicQueryArgs.taskName,
|
PeriodicQueryArgs.taskName,
|
||||||
submitter.collective,
|
submitter.collective,
|
||||||
args,
|
args,
|
||||||
s"Running periodic query, notify via ${args.channel.channelType}",
|
s"Running periodic query, notify via ${args.channels.map(_.channelType)}",
|
||||||
now,
|
now,
|
||||||
submitter.user,
|
submitter.user,
|
||||||
Priority.Low,
|
Priority.Low,
|
||||||
|
@ -39,7 +39,10 @@ trait ONotification[F[_]] {
|
|||||||
userId: Ident
|
userId: Ident
|
||||||
): F[Vector[NotificationChannel]]
|
): F[Vector[NotificationChannel]]
|
||||||
|
|
||||||
def findNotificationChannel(ref: ChannelRef): F[Vector[NotificationChannel]]
|
def findNotificationChannel(
|
||||||
|
ref: ChannelRef,
|
||||||
|
account: AccountId
|
||||||
|
): F[Vector[NotificationChannel]]
|
||||||
|
|
||||||
def listChannels(account: AccountId): F[Vector[Channel]]
|
def listChannels(account: AccountId): F[Vector[Channel]]
|
||||||
|
|
||||||
@ -65,7 +68,7 @@ trait ONotification[F[_]] {
|
|||||||
|
|
||||||
def sendSampleEvent(
|
def sendSampleEvent(
|
||||||
evt: EventType,
|
evt: EventType,
|
||||||
channel: Channel,
|
channel: Nel[ChannelRef],
|
||||||
account: AccountId,
|
account: AccountId,
|
||||||
baseUrl: Option[LenientUri]
|
baseUrl: Option[LenientUri]
|
||||||
): F[ONotification.SendTestResult]
|
): F[ONotification.SendTestResult]
|
||||||
@ -89,7 +92,7 @@ object ONotification {
|
|||||||
.getOrElse(UpdateResult.notFound)
|
.getOrElse(UpdateResult.notFound)
|
||||||
|
|
||||||
def offerEvents(ev: Iterable[Event]): F[Unit] =
|
def offerEvents(ev: Iterable[Event]): F[Unit] =
|
||||||
ev.toList.traverse(notMod.offer(_)).as(())
|
ev.toList.traverse(notMod.offer).as(())
|
||||||
|
|
||||||
def sendMessage(
|
def sendMessage(
|
||||||
logger: Logger[F],
|
logger: Logger[F],
|
||||||
@ -109,15 +112,16 @@ object ONotification {
|
|||||||
|
|
||||||
def sendSampleEvent(
|
def sendSampleEvent(
|
||||||
evt: EventType,
|
evt: EventType,
|
||||||
channel: Channel,
|
channels: Nel[ChannelRef],
|
||||||
account: AccountId,
|
account: AccountId,
|
||||||
baseUrl: Option[LenientUri]
|
baseUrl: Option[LenientUri]
|
||||||
): F[SendTestResult] = {
|
): F[SendTestResult] =
|
||||||
def doCreate(userId: Ident) =
|
|
||||||
(for {
|
(for {
|
||||||
ev <- sampleEvent(evt, account, baseUrl)
|
ev <- sampleEvent(evt, account, baseUrl)
|
||||||
logbuf <- Logger.buffer()
|
logbuf <- Logger.buffer()
|
||||||
ch <- mkNotificationChannel(channel, userId)
|
ch <- channels.toList.toVector.flatTraverse(
|
||||||
|
findNotificationChannel(_, account)
|
||||||
|
)
|
||||||
_ <- notMod.send(logbuf._2.andThen(log), ev, ch)
|
_ <- notMod.send(logbuf._2.andThen(log), ev, ch)
|
||||||
logs <- logbuf._1.get
|
logs <- logbuf._1.get
|
||||||
res = SendTestResult(true, logs)
|
res = SendTestResult(true, logs)
|
||||||
@ -130,13 +134,6 @@ object ONotification {
|
|||||||
SendTestResult(false, Vector(s"${ex.getMessage}\n$ps"))
|
SendTestResult(false, Vector(s"${ex.getMessage}\n$ps"))
|
||||||
}
|
}
|
||||||
|
|
||||||
OptionT(store.transact(RUser.findIdByAccount(account)))
|
|
||||||
.semiflatMap(doCreate)
|
|
||||||
.getOrElse(
|
|
||||||
SendTestResult(false, Vector(s"No user found in db for: ${account.asString}"))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
def listChannels(account: AccountId): F[Vector[Channel]] =
|
def listChannels(account: AccountId): F[Vector[Channel]] =
|
||||||
store
|
store
|
||||||
.transact(RNotificationChannel.getByAccount(account))
|
.transact(RNotificationChannel.getByAccount(account))
|
||||||
@ -153,7 +150,7 @@ object ONotification {
|
|||||||
(for {
|
(for {
|
||||||
newId <- OptionT.liftF(Ident.randomId[F])
|
newId <- OptionT.liftF(Ident.randomId[F])
|
||||||
userId <- OptionT(store.transact(RUser.findIdByAccount(account)))
|
userId <- OptionT(store.transact(RUser.findIdByAccount(account)))
|
||||||
r <- ChannelConv.makeRecord[F](store, log, Right(channel), newId, userId)
|
r <- ChannelConv.makeRecord[F](store, channel, newId, userId)
|
||||||
_ <- OptionT.liftF(store.transact(RNotificationChannel.insert(r)))
|
_ <- OptionT.liftF(store.transact(RNotificationChannel.insert(r)))
|
||||||
_ <- OptionT.liftF(log.debug(s"Created channel $r for $account"))
|
_ <- OptionT.liftF(log.debug(s"Created channel $r for $account"))
|
||||||
} yield AddResult.Success)
|
} yield AddResult.Success)
|
||||||
@ -162,7 +159,7 @@ object ONotification {
|
|||||||
def updateChannel(channel: Channel, account: AccountId): F[UpdateResult] =
|
def updateChannel(channel: Channel, account: AccountId): F[UpdateResult] =
|
||||||
(for {
|
(for {
|
||||||
userId <- OptionT(store.transact(RUser.findIdByAccount(account)))
|
userId <- OptionT(store.transact(RUser.findIdByAccount(account)))
|
||||||
r <- ChannelConv.makeRecord[F](store, log, Right(channel), channel.id, userId)
|
r <- ChannelConv.makeRecord[F](store, channel, channel.id, userId)
|
||||||
n <- OptionT.liftF(store.transact(RNotificationChannel.update(r)))
|
n <- OptionT.liftF(store.transact(RNotificationChannel.update(r)))
|
||||||
} yield UpdateResult.fromUpdateRows(n)).getOrElse(UpdateResult.notFound)
|
} yield UpdateResult.fromUpdateRows(n)).getOrElse(UpdateResult.notFound)
|
||||||
|
|
||||||
@ -179,16 +176,14 @@ object ONotification {
|
|||||||
def createHook(hook: Hook, account: AccountId): F[AddResult] =
|
def createHook(hook: Hook, account: AccountId): F[AddResult] =
|
||||||
(for {
|
(for {
|
||||||
_ <- OptionT.liftF(log.debug(s"Creating new notification hook: $hook"))
|
_ <- OptionT.liftF(log.debug(s"Creating new notification hook: $hook"))
|
||||||
channelId <- OptionT.liftF(Ident.randomId[F])
|
|
||||||
userId <- OptionT(store.transact(RUser.findIdByAccount(account)))
|
userId <- OptionT(store.transact(RUser.findIdByAccount(account)))
|
||||||
r <- ChannelConv.makeRecord[F](store, log, hook.channel, channelId, userId)
|
hr <- OptionT.liftF(Hook.makeRecord(userId, hook))
|
||||||
_ <- OptionT.liftF(
|
_ <- OptionT.liftF(
|
||||||
if (channelId == r.id) store.transact(RNotificationChannel.insert(r))
|
store.transact(
|
||||||
else ().pure[F]
|
RNotificationHook.insert(hr) *> RNotificationHookChannel
|
||||||
|
.updateAll(hr.id, hook.channels.toList)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
_ <- OptionT.liftF(log.debug(s"Created channel $r for $account"))
|
|
||||||
hr <- OptionT.liftF(Hook.makeRecord(r, userId, hook))
|
|
||||||
_ <- OptionT.liftF(store.transact(RNotificationHook.insert(hr)))
|
|
||||||
_ <- OptionT.liftF(
|
_ <- OptionT.liftF(
|
||||||
store.transact(RNotificationHookEvent.insertAll(hr.id, hook.events))
|
store.transact(RNotificationHookEvent.insertAll(hr.id, hook.events))
|
||||||
)
|
)
|
||||||
@ -203,22 +198,16 @@ object ONotification {
|
|||||||
.getOrElse(UpdateResult.notFound)
|
.getOrElse(UpdateResult.notFound)
|
||||||
)
|
)
|
||||||
|
|
||||||
def withChannel(
|
|
||||||
r: RNotificationHook
|
|
||||||
)(f: RNotificationChannel => F[UpdateResult]): F[UpdateResult] =
|
|
||||||
ChannelConv
|
|
||||||
.makeRecord(store, log, hook.channel, r.channelId, r.uid)
|
|
||||||
.semiflatMap(f)
|
|
||||||
.getOrElse(UpdateResult.notFound)
|
|
||||||
|
|
||||||
def doUpdate(r: RNotificationHook): F[UpdateResult] =
|
def doUpdate(r: RNotificationHook): F[UpdateResult] =
|
||||||
withChannel(r) { ch =>
|
|
||||||
UpdateResult.fromUpdate(store.transact(for {
|
UpdateResult.fromUpdate(store.transact(for {
|
||||||
nc <- RNotificationChannel.update(ch)
|
|
||||||
ne <- RNotificationHookEvent.updateAll(
|
ne <- RNotificationHookEvent.updateAll(
|
||||||
r.id,
|
r.id,
|
||||||
if (hook.allEvents) Nil else hook.events
|
if (hook.allEvents) Nil else hook.events
|
||||||
)
|
)
|
||||||
|
nc <- RNotificationHookChannel.updateAll(
|
||||||
|
r.id,
|
||||||
|
hook.channels.toList
|
||||||
|
)
|
||||||
nr <- RNotificationHook.update(
|
nr <- RNotificationHook.update(
|
||||||
r.copy(
|
r.copy(
|
||||||
enabled = hook.enabled,
|
enabled = hook.enabled,
|
||||||
@ -226,8 +215,8 @@ object ONotification {
|
|||||||
eventFilter = hook.eventFilter
|
eventFilter = hook.eventFilter
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
} yield nc + ne + nr))
|
} yield nc + ne + nr))
|
||||||
}
|
|
||||||
|
|
||||||
withHook(doUpdate)
|
withHook(doUpdate)
|
||||||
}
|
}
|
||||||
@ -238,13 +227,17 @@ object ONotification {
|
|||||||
): F[Vector[NotificationChannel]] =
|
): F[Vector[NotificationChannel]] =
|
||||||
(for {
|
(for {
|
||||||
rec <- ChannelConv
|
rec <- ChannelConv
|
||||||
.makeRecord(store, log, Right(channel), channel.id, userId)
|
.makeRecord(store, channel, channel.id, userId)
|
||||||
ch <- OptionT.liftF(store.transact(QNotification.readChannel(rec)))
|
ch <- OptionT.liftF(store.transact(QNotification.readChannel(rec)))
|
||||||
} yield ch).getOrElse(Vector.empty)
|
} yield ch).getOrElse(Vector.empty)
|
||||||
|
|
||||||
def findNotificationChannel(ref: ChannelRef): F[Vector[NotificationChannel]] =
|
def findNotificationChannel(
|
||||||
|
ref: ChannelRef,
|
||||||
|
accountId: AccountId
|
||||||
|
): F[Vector[NotificationChannel]] =
|
||||||
(for {
|
(for {
|
||||||
rec <- OptionT(store.transact(RNotificationChannel.getByRef(ref)))
|
userId <- OptionT(store.transact(RUser.findIdByAccount(accountId)))
|
||||||
|
rec <- OptionT(store.transact(RNotificationChannel.getByRef(ref, userId)))
|
||||||
ch <- OptionT.liftF(store.transact(QNotification.readChannel(rec)))
|
ch <- OptionT.liftF(store.transact(QNotification.readChannel(rec)))
|
||||||
} yield ch).getOrElse(Vector.empty)
|
} yield ch).getOrElse(Vector.empty)
|
||||||
})
|
})
|
||||||
@ -254,75 +247,40 @@ object ONotification {
|
|||||||
private[ops] def makeChannel(r: RNotificationChannel): Channel =
|
private[ops] def makeChannel(r: RNotificationChannel): Channel =
|
||||||
r.fold(
|
r.fold(
|
||||||
mail =>
|
mail =>
|
||||||
Channel.Mail(mail.id, mail.connection, Nel.fromListUnsafe(mail.recipients)),
|
Channel.Mail(
|
||||||
gotify => Channel.Gotify(r.id, gotify.url, gotify.appKey, gotify.priority),
|
mail.id,
|
||||||
|
mail.name,
|
||||||
|
mail.connection,
|
||||||
|
Nel.fromListUnsafe(mail.recipients)
|
||||||
|
),
|
||||||
|
gotify =>
|
||||||
|
Channel.Gotify(r.id, gotify.name, gotify.url, gotify.appKey, gotify.priority),
|
||||||
matrix =>
|
matrix =>
|
||||||
Channel.Matrix(r.id, matrix.homeServer, matrix.roomId, matrix.accessToken),
|
Channel
|
||||||
http => Channel.Http(r.id, http.url)
|
.Matrix(
|
||||||
|
r.id,
|
||||||
|
matrix.name,
|
||||||
|
matrix.homeServer,
|
||||||
|
matrix.roomId,
|
||||||
|
matrix.accessToken
|
||||||
|
),
|
||||||
|
http => Channel.Http(r.id, http.name, http.url)
|
||||||
)
|
)
|
||||||
|
|
||||||
private[ops] def makeRecord[F[_]: Sync](
|
private[ops] def makeRecord[F[_]](
|
||||||
store: Store[F],
|
store: Store[F],
|
||||||
logger: Logger[F],
|
channel: Channel,
|
||||||
channelIn: Either[ChannelRef, Channel],
|
|
||||||
id: Ident,
|
id: Ident,
|
||||||
userId: Ident
|
userId: Ident
|
||||||
): OptionT[F, RNotificationChannel] =
|
): OptionT[F, RNotificationChannel] =
|
||||||
channelIn match {
|
RNotificationChannel.fromChannel(channel, id, userId).mapK(store.transform)
|
||||||
case Left(ref) =>
|
|
||||||
OptionT.liftF(logger.debug(s"Loading channel for ref: ${ref}")) *>
|
|
||||||
OptionT(store.transact(RNotificationChannel.getByRef(ref)))
|
|
||||||
|
|
||||||
case Right(channel) =>
|
|
||||||
for {
|
|
||||||
time <- OptionT.liftF(Timestamp.current[F])
|
|
||||||
r <-
|
|
||||||
channel match {
|
|
||||||
case Channel.Mail(_, conn, recipients) =>
|
|
||||||
for {
|
|
||||||
_ <- OptionT.liftF(
|
|
||||||
logger.debug(
|
|
||||||
s"Looking up user smtp for ${userId.id} and ${conn.id}"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
mailConn <- OptionT(
|
|
||||||
store.transact(RUserEmail.getByUser(userId, conn))
|
|
||||||
)
|
|
||||||
rec = RNotificationChannelMail(
|
|
||||||
id,
|
|
||||||
userId,
|
|
||||||
mailConn.id,
|
|
||||||
recipients.toList,
|
|
||||||
time
|
|
||||||
).vary
|
|
||||||
} yield rec
|
|
||||||
case Channel.Gotify(_, url, appKey, prio) =>
|
|
||||||
OptionT.pure[F](
|
|
||||||
RNotificationChannelGotify(id, userId, url, appKey, prio, time).vary
|
|
||||||
)
|
|
||||||
case Channel.Matrix(_, homeServer, roomId, accessToken) =>
|
|
||||||
OptionT.pure[F](
|
|
||||||
RNotificationChannelMatrix(
|
|
||||||
id,
|
|
||||||
userId,
|
|
||||||
homeServer,
|
|
||||||
roomId,
|
|
||||||
accessToken,
|
|
||||||
"m.text",
|
|
||||||
time
|
|
||||||
).vary
|
|
||||||
)
|
|
||||||
case Channel.Http(_, url) =>
|
|
||||||
OptionT.pure[F](RNotificationChannelHttp(id, userId, url, time).vary)
|
|
||||||
}
|
|
||||||
} yield r
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final case class Hook(
|
final case class Hook(
|
||||||
id: Ident,
|
id: Ident,
|
||||||
enabled: Boolean,
|
enabled: Boolean,
|
||||||
channel: Either[ChannelRef, Channel],
|
channels: List[ChannelRef],
|
||||||
allEvents: Boolean,
|
allEvents: Boolean,
|
||||||
eventFilter: Option[JsonMiniQuery],
|
eventFilter: Option[JsonMiniQuery],
|
||||||
events: List[EventType]
|
events: List[EventType]
|
||||||
@ -335,14 +293,12 @@ object ONotification {
|
|||||||
r: RNotificationHook,
|
r: RNotificationHook,
|
||||||
events: List[EventType]
|
events: List[EventType]
|
||||||
): ConnectionIO[Hook] =
|
): ConnectionIO[Hook] =
|
||||||
RNotificationChannel
|
RNotificationHookChannel
|
||||||
.getByHook(r)
|
.allOfNel(r.id)
|
||||||
.map(_.head)
|
.flatMap(rhcs => RNotificationHookChannel.resolveRefs(rhcs))
|
||||||
.map(ChannelConv.makeChannel)
|
.map(refs => Hook(r.id, r.enabled, refs, r.allEvents, r.eventFilter, events))
|
||||||
.map(ch => Hook(r.id, r.enabled, Right(ch), r.allEvents, r.eventFilter, events))
|
|
||||||
|
|
||||||
private[ops] def makeRecord[F[_]: Sync](
|
private[ops] def makeRecord[F[_]: Sync](
|
||||||
ch: RNotificationChannel,
|
|
||||||
userId: Ident,
|
userId: Ident,
|
||||||
hook: Hook
|
hook: Hook
|
||||||
): F[RNotificationHook] =
|
): F[RNotificationHook] =
|
||||||
@ -353,10 +309,6 @@ object ONotification {
|
|||||||
id,
|
id,
|
||||||
userId,
|
userId,
|
||||||
hook.enabled,
|
hook.enabled,
|
||||||
ch.fold(_.id.some, _ => None, _ => None, _ => None),
|
|
||||||
ch.fold(_ => None, _.id.some, _ => None, _ => None),
|
|
||||||
ch.fold(_ => None, _ => None, _.id.some, _ => None),
|
|
||||||
ch.fold(_ => None, _ => None, _ => None, _.id.some),
|
|
||||||
hook.allEvents,
|
hook.allEvents,
|
||||||
hook.eventFilter,
|
hook.eventFilter,
|
||||||
time
|
time
|
||||||
|
@ -11,7 +11,6 @@ import cats.effect._
|
|||||||
import cats.implicits._
|
import cats.implicits._
|
||||||
import fs2.Stream
|
import fs2.Stream
|
||||||
|
|
||||||
import docspell.backend.MailAddressCodec._
|
|
||||||
import docspell.common._
|
import docspell.common._
|
||||||
import docspell.notification.api.PeriodicDueItemsArgs
|
import docspell.notification.api.PeriodicDueItemsArgs
|
||||||
import docspell.notification.api.PeriodicQueryArgs
|
import docspell.notification.api.PeriodicQueryArgs
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
package docspell.joex.notify
|
package docspell.joex.notify
|
||||||
|
|
||||||
import cats.data.NonEmptyList
|
import cats.data.NonEmptyList
|
||||||
import cats.data.OptionT
|
|
||||||
import cats.effect._
|
import cats.effect._
|
||||||
import cats.implicits._
|
import cats.implicits._
|
||||||
|
|
||||||
@ -24,7 +23,6 @@ import docspell.query.ItemQueryDsl._
|
|||||||
import docspell.store.qb.Batch
|
import docspell.store.qb.Batch
|
||||||
import docspell.store.queries.ListItem
|
import docspell.store.queries.ListItem
|
||||||
import docspell.store.queries.{QItem, Query}
|
import docspell.store.queries.{QItem, Query}
|
||||||
import docspell.store.records.RUser
|
|
||||||
|
|
||||||
object PeriodicDueItemsTask {
|
object PeriodicDueItemsTask {
|
||||||
val taskName = PeriodicDueItemsArgs.taskName
|
val taskName = PeriodicDueItemsArgs.taskName
|
||||||
@ -51,11 +49,7 @@ object PeriodicDueItemsTask {
|
|||||||
def withChannel[F[_]: Sync](ctx: Context[F, Args], ops: ONotification[F])(
|
def withChannel[F[_]: Sync](ctx: Context[F, Args], ops: ONotification[F])(
|
||||||
cont: Vector[NotificationChannel] => F[Unit]
|
cont: Vector[NotificationChannel] => F[Unit]
|
||||||
): F[Unit] =
|
): F[Unit] =
|
||||||
OptionT(ctx.store.transact(RUser.findIdByAccount(ctx.args.account)))
|
TaskOperations.withChannel(ctx.logger, ctx.args.channels, ctx.args.account, ops)(cont)
|
||||||
.semiflatMap(userId =>
|
|
||||||
TaskOperations.withChannel(ctx.logger, ctx.args.channel, userId, ops)(cont)
|
|
||||||
)
|
|
||||||
.getOrElse(())
|
|
||||||
|
|
||||||
def withItems[F[_]: Sync](ctx: Context[F, Args], limit: Int, now: Timestamp)(
|
def withItems[F[_]: Sync](ctx: Context[F, Args], limit: Int, now: Timestamp)(
|
||||||
cont: Vector[ListItem] => F[Unit]
|
cont: Vector[ListItem] => F[Unit]
|
||||||
|
@ -26,7 +26,6 @@ import docspell.store.queries.ListItem
|
|||||||
import docspell.store.queries.{QItem, Query}
|
import docspell.store.queries.{QItem, Query}
|
||||||
import docspell.store.records.RQueryBookmark
|
import docspell.store.records.RQueryBookmark
|
||||||
import docspell.store.records.RShare
|
import docspell.store.records.RShare
|
||||||
import docspell.store.records.RUser
|
|
||||||
|
|
||||||
object PeriodicQueryTask {
|
object PeriodicQueryTask {
|
||||||
val taskName = PeriodicQueryArgs.taskName
|
val taskName = PeriodicQueryArgs.taskName
|
||||||
@ -53,11 +52,7 @@ object PeriodicQueryTask {
|
|||||||
def withChannel[F[_]: Sync](ctx: Context[F, Args], ops: ONotification[F])(
|
def withChannel[F[_]: Sync](ctx: Context[F, Args], ops: ONotification[F])(
|
||||||
cont: Vector[NotificationChannel] => F[Unit]
|
cont: Vector[NotificationChannel] => F[Unit]
|
||||||
): F[Unit] =
|
): F[Unit] =
|
||||||
OptionT(ctx.store.transact(RUser.findIdByAccount(ctx.args.account)))
|
TaskOperations.withChannel(ctx.logger, ctx.args.channels, ctx.args.account, ops)(cont)
|
||||||
.semiflatMap(userId =>
|
|
||||||
TaskOperations.withChannel(ctx.logger, ctx.args.channel, userId, ops)(cont)
|
|
||||||
)
|
|
||||||
.getOrElse(())
|
|
||||||
|
|
||||||
private def queryString(q: ItemQuery.Expr) =
|
private def queryString(q: ItemQuery.Expr) =
|
||||||
ItemQueryParser.asString(q)
|
ItemQueryParser.asString(q)
|
||||||
|
@ -12,7 +12,7 @@ import cats.implicits._
|
|||||||
|
|
||||||
import docspell.backend.ops.ONotification
|
import docspell.backend.ops.ONotification
|
||||||
import docspell.common._
|
import docspell.common._
|
||||||
import docspell.notification.api.ChannelOrRef
|
import docspell.notification.api.ChannelRef
|
||||||
import docspell.notification.api.Event
|
import docspell.notification.api.Event
|
||||||
import docspell.notification.api.EventContext
|
import docspell.notification.api.EventContext
|
||||||
import docspell.notification.api.NotificationChannel
|
import docspell.notification.api.NotificationChannel
|
||||||
@ -23,19 +23,18 @@ trait TaskOperations {
|
|||||||
|
|
||||||
def withChannel[F[_]: Sync](
|
def withChannel[F[_]: Sync](
|
||||||
logger: Logger[F],
|
logger: Logger[F],
|
||||||
channel: ChannelOrRef,
|
channelsIn: NonEmptyList[ChannelRef],
|
||||||
userId: Ident,
|
accountId: AccountId,
|
||||||
ops: ONotification[F]
|
ops: ONotification[F]
|
||||||
)(
|
)(
|
||||||
cont: Vector[NotificationChannel] => F[Unit]
|
cont: Vector[NotificationChannel] => F[Unit]
|
||||||
): F[Unit] = {
|
): F[Unit] = {
|
||||||
val channels = channel match {
|
val channels =
|
||||||
case Right(ch) => ops.mkNotificationChannel(ch, userId)
|
channelsIn.toList.toVector.flatTraverse(ops.findNotificationChannel(_, accountId))
|
||||||
case Left(ref) => ops.findNotificationChannel(ref)
|
|
||||||
}
|
|
||||||
channels.flatMap { ch =>
|
channels.flatMap { ch =>
|
||||||
if (ch.isEmpty)
|
if (ch.isEmpty)
|
||||||
logger.error(s"No channels found for the given data: ${channel}")
|
logger.error(s"No channels found for the given data: ${channelsIn}")
|
||||||
else cont(ch)
|
else cont(ch)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,13 +19,14 @@ import io.circe.{Decoder, Encoder}
|
|||||||
sealed trait Channel {
|
sealed trait Channel {
|
||||||
def id: Ident
|
def id: Ident
|
||||||
def channelType: ChannelType
|
def channelType: ChannelType
|
||||||
|
def name: Option[String]
|
||||||
def fold[A](
|
def fold[A](
|
||||||
f1: Channel.Mail => A,
|
f1: Channel.Mail => A,
|
||||||
f2: Channel.Gotify => A,
|
f2: Channel.Gotify => A,
|
||||||
f3: Channel.Matrix => A,
|
f3: Channel.Matrix => A,
|
||||||
f4: Channel.Http => A
|
f4: Channel.Http => A
|
||||||
): A
|
): A
|
||||||
def asRef: ChannelRef = ChannelRef(id, channelType)
|
def asRef: ChannelRef = ChannelRef(id, channelType, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
object Channel {
|
object Channel {
|
||||||
@ -33,6 +34,7 @@ object Channel {
|
|||||||
|
|
||||||
final case class Mail(
|
final case class Mail(
|
||||||
id: Ident,
|
id: Ident,
|
||||||
|
name: Option[String],
|
||||||
connection: Ident,
|
connection: Ident,
|
||||||
recipients: Nel[MailAddress]
|
recipients: Nel[MailAddress]
|
||||||
) extends Channel {
|
) extends Channel {
|
||||||
@ -55,6 +57,7 @@ object Channel {
|
|||||||
|
|
||||||
final case class Gotify(
|
final case class Gotify(
|
||||||
id: Ident,
|
id: Ident,
|
||||||
|
name: Option[String],
|
||||||
url: LenientUri,
|
url: LenientUri,
|
||||||
appKey: Password,
|
appKey: Password,
|
||||||
priority: Option[Int]
|
priority: Option[Int]
|
||||||
@ -77,6 +80,7 @@ object Channel {
|
|||||||
|
|
||||||
final case class Matrix(
|
final case class Matrix(
|
||||||
id: Ident,
|
id: Ident,
|
||||||
|
name: Option[String],
|
||||||
homeServer: LenientUri,
|
homeServer: LenientUri,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
accessToken: Password
|
accessToken: Password
|
||||||
@ -95,7 +99,8 @@ object Channel {
|
|||||||
implicit val jsonEncoder: Encoder[Matrix] = deriveConfiguredEncoder
|
implicit val jsonEncoder: Encoder[Matrix] = deriveConfiguredEncoder
|
||||||
}
|
}
|
||||||
|
|
||||||
final case class Http(id: Ident, url: LenientUri) extends Channel {
|
final case class Http(id: Ident, name: Option[String], url: LenientUri)
|
||||||
|
extends Channel {
|
||||||
val channelType = ChannelType.Http
|
val channelType = ChannelType.Http
|
||||||
def fold[A](
|
def fold[A](
|
||||||
f1: Mail => A,
|
f1: Mail => A,
|
||||||
|
@ -12,7 +12,7 @@ import io.circe.Decoder
|
|||||||
import io.circe.Encoder
|
import io.circe.Encoder
|
||||||
import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
|
import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
|
||||||
|
|
||||||
final case class ChannelRef(id: Ident, channelType: ChannelType)
|
final case class ChannelRef(id: Ident, channelType: ChannelType, name: Option[String])
|
||||||
|
|
||||||
object ChannelRef {
|
object ChannelRef {
|
||||||
|
|
||||||
|
@ -6,9 +6,10 @@
|
|||||||
|
|
||||||
package docspell.notification.api
|
package docspell.notification.api
|
||||||
|
|
||||||
|
import cats.data.NonEmptyList
|
||||||
|
|
||||||
import docspell.common._
|
import docspell.common._
|
||||||
|
|
||||||
import emil.MailAddress
|
|
||||||
import io.circe.generic.semiauto
|
import io.circe.generic.semiauto
|
||||||
import io.circe.{Decoder, Encoder}
|
import io.circe.{Decoder, Encoder}
|
||||||
|
|
||||||
@ -21,7 +22,7 @@ import io.circe.{Decoder, Encoder}
|
|||||||
*/
|
*/
|
||||||
final case class PeriodicDueItemsArgs(
|
final case class PeriodicDueItemsArgs(
|
||||||
account: AccountId,
|
account: AccountId,
|
||||||
channel: ChannelOrRef,
|
channels: NonEmptyList[ChannelRef],
|
||||||
remindDays: Int,
|
remindDays: Int,
|
||||||
daysBack: Option[Int],
|
daysBack: Option[Int],
|
||||||
tagsInclude: List[Ident],
|
tagsInclude: List[Ident],
|
||||||
@ -30,19 +31,11 @@ final case class PeriodicDueItemsArgs(
|
|||||||
)
|
)
|
||||||
|
|
||||||
object PeriodicDueItemsArgs {
|
object PeriodicDueItemsArgs {
|
||||||
val taskName = Ident.unsafe("periodic-due-items-notify")
|
val taskName = Ident.unsafe("periodic-due-items-notify2")
|
||||||
|
|
||||||
implicit def jsonDecoder(implicit
|
implicit val jsonDecoder: Decoder[PeriodicDueItemsArgs] =
|
||||||
mc: Decoder[MailAddress]
|
|
||||||
): Decoder[PeriodicDueItemsArgs] = {
|
|
||||||
implicit val x = ChannelOrRef.jsonDecoder
|
|
||||||
semiauto.deriveDecoder
|
semiauto.deriveDecoder
|
||||||
}
|
|
||||||
|
|
||||||
implicit def jsonEncoder(implicit
|
implicit val jsonEncoder: Encoder[PeriodicDueItemsArgs] =
|
||||||
mc: Encoder[MailAddress]
|
|
||||||
): Encoder[PeriodicDueItemsArgs] = {
|
|
||||||
implicit val x = ChannelOrRef.jsonEncoder
|
|
||||||
semiauto.deriveEncoder
|
semiauto.deriveEncoder
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -6,15 +6,16 @@
|
|||||||
|
|
||||||
package docspell.notification.api
|
package docspell.notification.api
|
||||||
|
|
||||||
|
import cats.data.NonEmptyList
|
||||||
|
|
||||||
import docspell.common._
|
import docspell.common._
|
||||||
|
|
||||||
import emil.MailAddress
|
|
||||||
import io.circe.generic.semiauto
|
import io.circe.generic.semiauto
|
||||||
import io.circe.{Decoder, Encoder}
|
import io.circe.{Decoder, Encoder}
|
||||||
|
|
||||||
final case class PeriodicQueryArgs(
|
final case class PeriodicQueryArgs(
|
||||||
account: AccountId,
|
account: AccountId,
|
||||||
channel: ChannelOrRef,
|
channels: NonEmptyList[ChannelRef],
|
||||||
query: Option[ItemQueryString],
|
query: Option[ItemQueryString],
|
||||||
bookmark: Option[String],
|
bookmark: Option[String],
|
||||||
baseUrl: Option[LenientUri],
|
baseUrl: Option[LenientUri],
|
||||||
@ -22,19 +23,11 @@ final case class PeriodicQueryArgs(
|
|||||||
)
|
)
|
||||||
|
|
||||||
object PeriodicQueryArgs {
|
object PeriodicQueryArgs {
|
||||||
val taskName = Ident.unsafe("periodic-query-notify")
|
val taskName = Ident.unsafe("periodic-query-notify2")
|
||||||
|
|
||||||
implicit def jsonDecoder(implicit
|
implicit val jsonDecoder: Decoder[PeriodicQueryArgs] =
|
||||||
mc: Decoder[MailAddress]
|
|
||||||
): Decoder[PeriodicQueryArgs] = {
|
|
||||||
implicit val x = ChannelOrRef.jsonDecoder
|
|
||||||
semiauto.deriveDecoder
|
semiauto.deriveDecoder
|
||||||
}
|
|
||||||
|
|
||||||
implicit def jsonEncoder(implicit
|
implicit def jsonEncoder: Encoder[PeriodicQueryArgs] =
|
||||||
mc: Encoder[MailAddress]
|
|
||||||
): Encoder[PeriodicQueryArgs] = {
|
|
||||||
implicit val x = ChannelOrRef.jsonEncoder
|
|
||||||
semiauto.deriveEncoder
|
semiauto.deriveEncoder
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1739,7 +1739,7 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: "#/extraSchemas/NotificationHook"
|
$ref: "#/components/schemas/NotificationHook"
|
||||||
post:
|
post:
|
||||||
operationId: "sec-notification-hook-post"
|
operationId: "sec-notification-hook-post"
|
||||||
tags: [ Notification ]
|
tags: [ Notification ]
|
||||||
@ -1753,7 +1753,7 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/extraSchemas/NotificationHook"
|
$ref: "#/components/schemas/NotificationHook"
|
||||||
responses:
|
responses:
|
||||||
422:
|
422:
|
||||||
description: BadRequest
|
description: BadRequest
|
||||||
@ -1775,7 +1775,7 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/extraSchemas/NotificationHook"
|
$ref: "#/components/schemas/NotificationHook"
|
||||||
responses:
|
responses:
|
||||||
422:
|
422:
|
||||||
description: BadRequest
|
description: BadRequest
|
||||||
@ -1821,7 +1821,7 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/extraSchemas/NotificationHook"
|
$ref: "#/components/schemas/NotificationHook"
|
||||||
responses:
|
responses:
|
||||||
422:
|
422:
|
||||||
description: BadRequest
|
description: BadRequest
|
||||||
@ -4917,7 +4917,7 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: "#/extraSchemas/PeriodicDueItemsSettings"
|
$ref: "#/components/schemas/PeriodicDueItemsSettings"
|
||||||
post:
|
post:
|
||||||
operationId: "sec-usertask-notify-new"
|
operationId: "sec-usertask-notify-new"
|
||||||
tags: [ User Tasks ]
|
tags: [ User Tasks ]
|
||||||
@ -4931,7 +4931,7 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/extraSchemas/PeriodicDueItemsSettings"
|
$ref: "#/components/schemas/PeriodicDueItemsSettings"
|
||||||
responses:
|
responses:
|
||||||
422:
|
422:
|
||||||
description: BadRequest
|
description: BadRequest
|
||||||
@ -4954,7 +4954,7 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/extraSchemas/PeriodicDueItemsSettings"
|
$ref: "#/components/schemas/PeriodicDueItemsSettings"
|
||||||
responses:
|
responses:
|
||||||
422:
|
422:
|
||||||
description: BadRequest
|
description: BadRequest
|
||||||
@ -4984,7 +4984,7 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/extraSchemas/PeriodicDueItemsSettings"
|
$ref: "#/components/schemas/PeriodicDueItemsSettings"
|
||||||
delete:
|
delete:
|
||||||
operationId: "sec-usertask-notify-delete"
|
operationId: "sec-usertask-notify-delete"
|
||||||
tags: [ User Tasks ]
|
tags: [ User Tasks ]
|
||||||
@ -5018,7 +5018,7 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/extraSchemas/PeriodicDueItemsSettings"
|
$ref: "#/components/schemas/PeriodicDueItemsSettings"
|
||||||
responses:
|
responses:
|
||||||
422:
|
422:
|
||||||
description: BadRequest
|
description: BadRequest
|
||||||
@ -5048,7 +5048,7 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: "#/extraSchemas/PeriodicQuerySettings"
|
$ref: "#/components/schemas/PeriodicQuerySettings"
|
||||||
post:
|
post:
|
||||||
operationId: "sec-usertask-periodic-query-new"
|
operationId: "sec-usertask-periodic-query-new"
|
||||||
tags: [ User Tasks ]
|
tags: [ User Tasks ]
|
||||||
@ -5062,7 +5062,7 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/extraSchemas/PeriodicQuerySettings"
|
$ref: "#/components/schemas/PeriodicQuerySettings"
|
||||||
responses:
|
responses:
|
||||||
422:
|
422:
|
||||||
description: BadRequest
|
description: BadRequest
|
||||||
@ -5085,7 +5085,7 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/extraSchemas/PeriodicQuerySettings"
|
$ref: "#/components/schemas/PeriodicQuerySettings"
|
||||||
responses:
|
responses:
|
||||||
422:
|
422:
|
||||||
description: BadRequest
|
description: BadRequest
|
||||||
@ -5115,7 +5115,7 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/extraSchemas/PeriodicQuerySettings"
|
$ref: "#/components/schemas/PeriodicQuerySettings"
|
||||||
delete:
|
delete:
|
||||||
operationId: "sec-usertask-periodic-query-delete"
|
operationId: "sec-usertask-periodic-query-delete"
|
||||||
tags: [ User Tasks ]
|
tags: [ User Tasks ]
|
||||||
@ -5149,7 +5149,7 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/extraSchemas/PeriodicQuerySettings"
|
$ref: "#/components/schemas/PeriodicQuerySettings"
|
||||||
responses:
|
responses:
|
||||||
422:
|
422:
|
||||||
description: BadRequest
|
description: BadRequest
|
||||||
@ -5467,7 +5467,10 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
NotificationChannelRef:
|
NotificationChannelRef:
|
||||||
description: |
|
description: |
|
||||||
A reference to a channel.
|
A reference to a channel. The `id` and `channelType` are
|
||||||
|
required to identify a channel. The `name` attribute is as a
|
||||||
|
descriptive name and is returned by the server if it is
|
||||||
|
specified for the corresponding channel.
|
||||||
required:
|
required:
|
||||||
- id
|
- id
|
||||||
- channelType
|
- channelType
|
||||||
@ -5478,6 +5481,8 @@ components:
|
|||||||
channelType:
|
channelType:
|
||||||
type: string
|
type: string
|
||||||
format: channeltype
|
format: channeltype
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
NotificationMatrix:
|
NotificationMatrix:
|
||||||
description: |
|
description: |
|
||||||
A notification channel for matrix.
|
A notification channel for matrix.
|
||||||
@ -5491,6 +5496,8 @@ components:
|
|||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
format: ident
|
format: ident
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
channelType:
|
channelType:
|
||||||
type: string
|
type: string
|
||||||
format: channeltype
|
format: channeltype
|
||||||
@ -5514,6 +5521,8 @@ components:
|
|||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
format: ident
|
format: ident
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
channelType:
|
channelType:
|
||||||
type: string
|
type: string
|
||||||
format: channeltype
|
format: channeltype
|
||||||
@ -5539,6 +5548,8 @@ components:
|
|||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
format: ident
|
format: ident
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
channelType:
|
channelType:
|
||||||
type: string
|
type: string
|
||||||
format: channeltype
|
format: channeltype
|
||||||
@ -5557,6 +5568,8 @@ components:
|
|||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
format: ident
|
format: ident
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
channelType:
|
channelType:
|
||||||
type: string
|
type: string
|
||||||
format: channeltype
|
format: channeltype
|
||||||
@ -5568,6 +5581,136 @@ components:
|
|||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
|
|
||||||
|
NotificationHook:
|
||||||
|
description: |
|
||||||
|
Describes a notifcation hook. There must be at least one
|
||||||
|
channel specified. When creating hooks, the channels must
|
||||||
|
provide the `ìd` and the `channelType` while their `name`
|
||||||
|
attribute is optional.
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- enabled
|
||||||
|
- channel
|
||||||
|
- events
|
||||||
|
- allEvents
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
format: ident
|
||||||
|
enabled:
|
||||||
|
type: boolean
|
||||||
|
channels:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/NotificationChannelRef"
|
||||||
|
allEvents:
|
||||||
|
type: boolean
|
||||||
|
eventFilter:
|
||||||
|
type: string
|
||||||
|
format: jsonminiq
|
||||||
|
description: |
|
||||||
|
A filter expression that is applied to the event to be able
|
||||||
|
to ignore a subset of them. See its
|
||||||
|
[documentation](https://docspell.org/docs/jsonminiquery/).
|
||||||
|
events:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
format: eventtype
|
||||||
|
enum:
|
||||||
|
- tagsAdded
|
||||||
|
- tagsSet
|
||||||
|
|
||||||
|
PeriodicQuerySettings:
|
||||||
|
description: |
|
||||||
|
Settings for the periodc-query task. At least one of `query`
|
||||||
|
and `bookmark` is required! There must be at least one channel
|
||||||
|
specified when creating settings. A channel must provide its
|
||||||
|
`id` and `channelType`, while its `name` is optional.
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- enabled
|
||||||
|
- channel
|
||||||
|
- schedule
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
format: ident
|
||||||
|
enabled:
|
||||||
|
type: boolean
|
||||||
|
summary:
|
||||||
|
type: string
|
||||||
|
channels:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/NotificationChannelRef"
|
||||||
|
schedule:
|
||||||
|
type: string
|
||||||
|
format: calevent
|
||||||
|
query:
|
||||||
|
type: string
|
||||||
|
format: itemquery
|
||||||
|
bookmark:
|
||||||
|
type: string
|
||||||
|
description: |
|
||||||
|
Name or ID of bookmark to use.
|
||||||
|
contentStart:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
PeriodicDueItemsSettings:
|
||||||
|
description: |
|
||||||
|
Settings for notifying about due items. At least one of
|
||||||
|
`query` and `bookmark` is required! There must be at least one
|
||||||
|
channel specified when creating settings. A channel must
|
||||||
|
provide its `id` and `channelType`, while its `name` is
|
||||||
|
optional.
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- enabled
|
||||||
|
- channel
|
||||||
|
- schedule
|
||||||
|
- remindDays
|
||||||
|
- capOverdue
|
||||||
|
- tagsInclude
|
||||||
|
- tagsExclude
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
format: ident
|
||||||
|
enabled:
|
||||||
|
type: boolean
|
||||||
|
summary:
|
||||||
|
type: string
|
||||||
|
channels:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/NotificationChannelRef"
|
||||||
|
schedule:
|
||||||
|
type: string
|
||||||
|
format: calevent
|
||||||
|
remindDays:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
description: |
|
||||||
|
Used to restrict items by their due dates. All items with
|
||||||
|
a due date lower than (now + remindDays) are searched.
|
||||||
|
capOverdue:
|
||||||
|
type: boolean
|
||||||
|
description: |
|
||||||
|
If this is true, the search is also restricted to due
|
||||||
|
dates greater than `now - remindDays'. Otherwise, due date
|
||||||
|
are not restricted in that direction (only lower than `now
|
||||||
|
+ remindDays' applies) and it is expected to restrict it
|
||||||
|
more using custom tags.
|
||||||
|
tagsInclude:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Tag"
|
||||||
|
tagsExclude:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Tag"
|
||||||
|
|
||||||
ShareSecret:
|
ShareSecret:
|
||||||
description: |
|
description: |
|
||||||
The secret (the share id + optional password) to access a
|
The secret (the share id + optional password) to access a
|
||||||
@ -8001,137 +8144,3 @@ components:
|
|||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
format: ident
|
format: ident
|
||||||
|
|
||||||
# sadly no generator support for these.
|
|
||||||
# Changes here requires corresponding changes in:
|
|
||||||
# - NotificationHook.elm
|
|
||||||
# - routes.model.*
|
|
||||||
extraSchemas:
|
|
||||||
NotificationHook:
|
|
||||||
description: |
|
|
||||||
Describes a notifcation hook. There must be exactly one channel
|
|
||||||
specified, so either use a `channelRef` or one `channel`.
|
|
||||||
required:
|
|
||||||
- id
|
|
||||||
- enabled
|
|
||||||
- channel
|
|
||||||
- events
|
|
||||||
- allEvents
|
|
||||||
properties:
|
|
||||||
id:
|
|
||||||
type: string
|
|
||||||
format: ident
|
|
||||||
enabled:
|
|
||||||
type: boolean
|
|
||||||
channel:
|
|
||||||
oneOf:
|
|
||||||
- $ref: "#/components/schemas/NotificationMail"
|
|
||||||
- $ref: "#/components/schemas/NotificationGotify"
|
|
||||||
- $ref: "#/components/schemas/NotificationMatrix"
|
|
||||||
- $ref: "#/components/schemas/NotificationHttp"
|
|
||||||
- $ref: "#/components/schemas/NotificationChannelRef"
|
|
||||||
allEvents:
|
|
||||||
type: boolean
|
|
||||||
eventFilter:
|
|
||||||
type: string
|
|
||||||
format: jsonminiq
|
|
||||||
description: |
|
|
||||||
A filter expression that is applied to the event to be able
|
|
||||||
to ignore a subset of them. See its
|
|
||||||
[documentation](https://docspell.org/docs/jsonminiquery/).
|
|
||||||
events:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
format: eventtype
|
|
||||||
enum:
|
|
||||||
- tagsAdded
|
|
||||||
- tagsSet
|
|
||||||
|
|
||||||
PeriodicQuerySettings:
|
|
||||||
description: |
|
|
||||||
Settings for the periodc-query task. At least one of `query` and
|
|
||||||
`bookmark` is required!
|
|
||||||
required:
|
|
||||||
- id
|
|
||||||
- enabled
|
|
||||||
- channel
|
|
||||||
- schedule
|
|
||||||
properties:
|
|
||||||
id:
|
|
||||||
type: string
|
|
||||||
format: ident
|
|
||||||
enabled:
|
|
||||||
type: boolean
|
|
||||||
summary:
|
|
||||||
type: string
|
|
||||||
channel:
|
|
||||||
oneOf:
|
|
||||||
- $ref: "#/components/schemas/NotificationMail"
|
|
||||||
- $ref: "#/components/schemas/NotificationGotify"
|
|
||||||
- $ref: "#/components/schemas/NotificationMatrix"
|
|
||||||
- $ref: "#/components/schemas/NotificationHttp"
|
|
||||||
- $ref: "#/components/schemas/NotificationChannelRef"
|
|
||||||
schedule:
|
|
||||||
type: string
|
|
||||||
format: calevent
|
|
||||||
query:
|
|
||||||
type: string
|
|
||||||
format: itemquery
|
|
||||||
bookmark:
|
|
||||||
type: string
|
|
||||||
description: |
|
|
||||||
Name or ID of bookmark to use.
|
|
||||||
|
|
||||||
PeriodicDueItemsSettings:
|
|
||||||
description: |
|
|
||||||
Settings for notifying about due items.
|
|
||||||
required:
|
|
||||||
- id
|
|
||||||
- enabled
|
|
||||||
- channel
|
|
||||||
- schedule
|
|
||||||
- remindDays
|
|
||||||
- capOverdue
|
|
||||||
- tagsInclude
|
|
||||||
- tagsExclude
|
|
||||||
properties:
|
|
||||||
id:
|
|
||||||
type: string
|
|
||||||
format: ident
|
|
||||||
enabled:
|
|
||||||
type: boolean
|
|
||||||
summary:
|
|
||||||
type: string
|
|
||||||
channel:
|
|
||||||
oneOf:
|
|
||||||
- $ref: "#/components/schemas/NotificationMail"
|
|
||||||
- $ref: "#/components/schemas/NotificationGotify"
|
|
||||||
- $ref: "#/components/schemas/NotificationMatrix"
|
|
||||||
- $ref: "#/components/schemas/NotificationHttp"
|
|
||||||
- $ref: "#/components/schemas/NotificationChannelRef"
|
|
||||||
schedule:
|
|
||||||
type: string
|
|
||||||
format: calevent
|
|
||||||
remindDays:
|
|
||||||
type: integer
|
|
||||||
format: int32
|
|
||||||
description: |
|
|
||||||
Used to restrict items by their due dates. All items with
|
|
||||||
a due date lower than (now + remindDays) are searched.
|
|
||||||
capOverdue:
|
|
||||||
type: boolean
|
|
||||||
description: |
|
|
||||||
If this is true, the search is also restricted to due
|
|
||||||
dates greater than `now - remindDays'. Otherwise, due date
|
|
||||||
are not restricted in that direction (only lower than `now
|
|
||||||
+ remindDays' applies) and it is expected to restrict it
|
|
||||||
more using custom tags.
|
|
||||||
tagsInclude:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
$ref: "#/components/schemas/Tag"
|
|
||||||
tagsExclude:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
$ref: "#/components/schemas/Tag"
|
|
||||||
|
@ -73,15 +73,24 @@ object NotificationChannel {
|
|||||||
.map(NonEmptyList.fromList)
|
.map(NonEmptyList.fromList)
|
||||||
.flatMap(_.toRight("No recipients given!"))
|
.flatMap(_.toRight("No recipients given!"))
|
||||||
.leftMap(new IllegalArgumentException(_))
|
.leftMap(new IllegalArgumentException(_))
|
||||||
.map(rec => Channel.Mail(mail.id, mail.connection, rec)),
|
.map(rec => Channel.Mail(mail.id, mail.name, mail.connection, rec)),
|
||||||
gotify =>
|
gotify =>
|
||||||
Right(Channel.Gotify(gotify.id, gotify.url, gotify.appKey, gotify.priority)),
|
Right(
|
||||||
|
Channel
|
||||||
|
.Gotify(gotify.id, gotify.name, gotify.url, gotify.appKey, gotify.priority)
|
||||||
|
),
|
||||||
matrix =>
|
matrix =>
|
||||||
Right(
|
Right(
|
||||||
Channel
|
Channel
|
||||||
.Matrix(matrix.id, matrix.homeServer, matrix.roomId, matrix.accessToken)
|
.Matrix(
|
||||||
|
matrix.id,
|
||||||
|
matrix.name,
|
||||||
|
matrix.homeServer,
|
||||||
|
matrix.roomId,
|
||||||
|
matrix.accessToken
|
||||||
|
)
|
||||||
),
|
),
|
||||||
http => Right(Channel.Http(http.id, http.url))
|
http => Right(Channel.Http(http.id, http.name, http.url))
|
||||||
)
|
)
|
||||||
|
|
||||||
def convert(c: Channel): NotificationChannel =
|
def convert(c: Channel): NotificationChannel =
|
||||||
@ -90,24 +99,35 @@ object NotificationChannel {
|
|||||||
mail {
|
mail {
|
||||||
NotificationMail(
|
NotificationMail(
|
||||||
m.id,
|
m.id,
|
||||||
|
m.name,
|
||||||
ChannelType.Mail,
|
ChannelType.Mail,
|
||||||
m.connection,
|
m.connection,
|
||||||
m.recipients.toList.map(_.displayString)
|
m.recipients.toList.map(_.displayString)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
g =>
|
g =>
|
||||||
gotify(NotificationGotify(g.id, ChannelType.Gotify, g.url, g.appKey, g.priority)),
|
gotify(
|
||||||
|
NotificationGotify(
|
||||||
|
g.id,
|
||||||
|
g.name,
|
||||||
|
ChannelType.Gotify,
|
||||||
|
g.url,
|
||||||
|
g.appKey,
|
||||||
|
g.priority
|
||||||
|
)
|
||||||
|
),
|
||||||
m =>
|
m =>
|
||||||
matrix(
|
matrix(
|
||||||
NotificationMatrix(
|
NotificationMatrix(
|
||||||
m.id,
|
m.id,
|
||||||
|
m.name,
|
||||||
ChannelType.Matrix,
|
ChannelType.Matrix,
|
||||||
m.homeServer,
|
m.homeServer,
|
||||||
m.roomId,
|
m.roomId,
|
||||||
m.accessToken
|
m.accessToken
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
h => http(NotificationHttp(h.id, ChannelType.Http, h.url))
|
h => http(NotificationHttp(h.id, h.name, ChannelType.Http, h.url))
|
||||||
)
|
)
|
||||||
|
|
||||||
implicit val jsonDecoder: Decoder[NotificationChannel] =
|
implicit val jsonDecoder: Decoder[NotificationChannel] =
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2020 Eike K. & Contributors
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
package docspell.restapi.model
|
|
||||||
|
|
||||||
import docspell.common._
|
|
||||||
import docspell.jsonminiq.JsonMiniQuery
|
|
||||||
import docspell.notification.api.{ChannelRef, EventType}
|
|
||||||
import docspell.restapi.codec.ChannelEitherCodec
|
|
||||||
|
|
||||||
import io.circe.{Decoder, Encoder}
|
|
||||||
|
|
||||||
// this must comply to the definition in openapi.yml in `extraSchemas`
|
|
||||||
final case class NotificationHook(
|
|
||||||
id: Ident,
|
|
||||||
enabled: Boolean,
|
|
||||||
channel: Either[ChannelRef, NotificationChannel],
|
|
||||||
allEvents: Boolean,
|
|
||||||
eventFilter: Option[JsonMiniQuery],
|
|
||||||
events: List[EventType]
|
|
||||||
)
|
|
||||||
|
|
||||||
object NotificationHook {
|
|
||||||
import ChannelEitherCodec._
|
|
||||||
|
|
||||||
implicit val jsonDecoder: Decoder[NotificationHook] =
|
|
||||||
io.circe.generic.semiauto.deriveDecoder
|
|
||||||
implicit val jsonEncoder: Encoder[NotificationHook] =
|
|
||||||
io.circe.generic.semiauto.deriveEncoder
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2020 Eike K. & Contributors
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
package docspell.restapi.model
|
|
||||||
|
|
||||||
import docspell.common._
|
|
||||||
import docspell.restapi.model._
|
|
||||||
|
|
||||||
import com.github.eikek.calev.CalEvent
|
|
||||||
import com.github.eikek.calev.circe.CalevCirceCodec._
|
|
||||||
import io.circe.generic.semiauto
|
|
||||||
import io.circe.{Decoder, Encoder}
|
|
||||||
|
|
||||||
// this must comply to the definition in openapi.yml in `extraSchemas`
|
|
||||||
final case class PeriodicDueItemsSettings(
|
|
||||||
id: Ident,
|
|
||||||
enabled: Boolean,
|
|
||||||
summary: Option[String],
|
|
||||||
channel: NotificationChannel,
|
|
||||||
schedule: CalEvent,
|
|
||||||
remindDays: Int,
|
|
||||||
capOverdue: Boolean,
|
|
||||||
tagsInclude: List[Tag],
|
|
||||||
tagsExclude: List[Tag]
|
|
||||||
)
|
|
||||||
object PeriodicDueItemsSettings {
|
|
||||||
|
|
||||||
implicit val jsonDecoder: Decoder[PeriodicDueItemsSettings] =
|
|
||||||
semiauto.deriveDecoder[PeriodicDueItemsSettings]
|
|
||||||
implicit val jsonEncoder: Encoder[PeriodicDueItemsSettings] =
|
|
||||||
semiauto.deriveEncoder[PeriodicDueItemsSettings]
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2020 Eike K. & Contributors
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
package docspell.restapi.model
|
|
||||||
|
|
||||||
import docspell.common._
|
|
||||||
import docspell.query.ItemQuery
|
|
||||||
import docspell.restapi.codec.ItemQueryJson._
|
|
||||||
|
|
||||||
import com.github.eikek.calev.CalEvent
|
|
||||||
import com.github.eikek.calev.circe.CalevCirceCodec._
|
|
||||||
import io.circe.generic.semiauto
|
|
||||||
import io.circe.{Decoder, Encoder}
|
|
||||||
|
|
||||||
// this must comply to the definition in openapi.yml in `extraSchemas`
|
|
||||||
final case class PeriodicQuerySettings(
|
|
||||||
id: Ident,
|
|
||||||
summary: Option[String],
|
|
||||||
enabled: Boolean,
|
|
||||||
channel: NotificationChannel,
|
|
||||||
query: Option[ItemQuery],
|
|
||||||
bookmark: Option[String],
|
|
||||||
contentStart: Option[String],
|
|
||||||
schedule: CalEvent
|
|
||||||
) {}
|
|
||||||
|
|
||||||
object PeriodicQuerySettings {
|
|
||||||
|
|
||||||
implicit val jsonDecoder: Decoder[PeriodicQuerySettings] =
|
|
||||||
semiauto.deriveDecoder
|
|
||||||
|
|
||||||
implicit val jsonEncoder: Encoder[PeriodicQuerySettings] =
|
|
||||||
semiauto.deriveEncoder
|
|
||||||
}
|
|
@ -1,89 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2020 Eike K. & Contributors
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
package docspell.restapi.model
|
|
||||||
|
|
||||||
import docspell.common._
|
|
||||||
import docspell.notification.api.ChannelRef
|
|
||||||
import docspell.notification.api.ChannelType
|
|
||||||
|
|
||||||
import io.circe.Decoder
|
|
||||||
import io.circe.parser
|
|
||||||
import munit._
|
|
||||||
|
|
||||||
class NotificationCodecTest extends FunSuite {
|
|
||||||
|
|
||||||
def parse[A: Decoder](str: String): A =
|
|
||||||
parser.parse(str).fold(throw _, identity).as[A].fold(throw _, identity)
|
|
||||||
|
|
||||||
def id(str: String): Ident =
|
|
||||||
Ident.unsafe(str)
|
|
||||||
|
|
||||||
test("decode with channelref") {
|
|
||||||
val json = """{"id":"",
|
|
||||||
"enabled": true,
|
|
||||||
"channel": {"id":"abcde", "channelType":"matrix"},
|
|
||||||
"allEvents": false,
|
|
||||||
"events": ["TagsChanged", "SetFieldValue"]
|
|
||||||
}"""
|
|
||||||
|
|
||||||
val hook = parse[NotificationHook](json)
|
|
||||||
assertEquals(hook.enabled, true)
|
|
||||||
assertEquals(hook.channel, Left(ChannelRef(id("abcde"), ChannelType.Matrix)))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("decode with gotify data") {
|
|
||||||
val json = """{"id":"",
|
|
||||||
"enabled": true,
|
|
||||||
"channel": {"id":"", "channelType":"gotify", "url":"http://test.gotify.com", "appKey": "abcde"},
|
|
||||||
"allEvents": false,
|
|
||||||
"eventFilter": null,
|
|
||||||
"events": ["TagsChanged", "SetFieldValue"]
|
|
||||||
}"""
|
|
||||||
val hook = parse[NotificationHook](json)
|
|
||||||
assertEquals(hook.enabled, true)
|
|
||||||
assertEquals(
|
|
||||||
hook.channel,
|
|
||||||
Right(
|
|
||||||
NotificationChannel.Gotify(
|
|
||||||
NotificationGotify(
|
|
||||||
id(""),
|
|
||||||
ChannelType.Gotify,
|
|
||||||
LenientUri.unsafe("http://test.gotify.com"),
|
|
||||||
Password("abcde"),
|
|
||||||
None
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("decode with gotify data with prio") {
|
|
||||||
val json = """{"id":"",
|
|
||||||
"enabled": true,
|
|
||||||
"channel": {"id":"", "channelType":"gotify", "url":"http://test.gotify.com", "appKey": "abcde", "priority":9},
|
|
||||||
"allEvents": false,
|
|
||||||
"eventFilter": null,
|
|
||||||
"events": ["TagsChanged", "SetFieldValue"]
|
|
||||||
}"""
|
|
||||||
val hook = parse[NotificationHook](json)
|
|
||||||
assertEquals(hook.enabled, true)
|
|
||||||
assertEquals(
|
|
||||||
hook.channel,
|
|
||||||
Right(
|
|
||||||
NotificationChannel.Gotify(
|
|
||||||
NotificationGotify(
|
|
||||||
id(""),
|
|
||||||
ChannelType.Gotify,
|
|
||||||
LenientUri.unsafe("http://test.gotify.com"),
|
|
||||||
Password("abcde"),
|
|
||||||
Some(9)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
package docspell.restserver.routes
|
package docspell.restserver.routes
|
||||||
|
|
||||||
|
import cats.data.NonEmptyList
|
||||||
import cats.effect._
|
import cats.effect._
|
||||||
import cats.implicits._
|
import cats.implicits._
|
||||||
|
|
||||||
@ -14,10 +15,10 @@ import docspell.backend.auth.AuthToken
|
|||||||
import docspell.common._
|
import docspell.common._
|
||||||
import docspell.joexapi.model.BasicResult
|
import docspell.joexapi.model.BasicResult
|
||||||
import docspell.jsonminiq.JsonMiniQuery
|
import docspell.jsonminiq.JsonMiniQuery
|
||||||
import docspell.notification.api.EventType
|
import docspell.notification.api.{ChannelRef, EventType}
|
||||||
import docspell.restapi.model._
|
import docspell.restapi.model._
|
||||||
import docspell.restserver.Config
|
import docspell.restserver.Config
|
||||||
import docspell.restserver.conv.Conversions
|
import docspell.restserver.conv.{Conversions, NonEmptyListSupport}
|
||||||
import docspell.restserver.http4s.ClientRequestInfo
|
import docspell.restserver.http4s.ClientRequestInfo
|
||||||
|
|
||||||
import org.http4s._
|
import org.http4s._
|
||||||
@ -26,7 +27,7 @@ import org.http4s.circe.CirceEntityEncoder._
|
|||||||
import org.http4s.dsl.Http4sDsl
|
import org.http4s.dsl.Http4sDsl
|
||||||
import org.http4s.server.Router
|
import org.http4s.server.Router
|
||||||
|
|
||||||
object NotificationRoutes {
|
object NotificationRoutes extends NonEmptyListSupport {
|
||||||
|
|
||||||
def apply[F[_]: Async](
|
def apply[F[_]: Async](
|
||||||
cfg: Config,
|
cfg: Config,
|
||||||
@ -126,17 +127,11 @@ object NotificationRoutes {
|
|||||||
case req @ POST -> Root / "sendTestEvent" =>
|
case req @ POST -> Root / "sendTestEvent" =>
|
||||||
for {
|
for {
|
||||||
input <- req.as[NotificationHook]
|
input <- req.as[NotificationHook]
|
||||||
ch <- Sync[F]
|
ch <- requireNonEmpty(input.channels)
|
||||||
.pure(
|
|
||||||
input.channel.left
|
|
||||||
.map(_ => new Exception(s"ChannelRefs not allowed for testing"))
|
|
||||||
.flatMap(NotificationChannel.convert)
|
|
||||||
)
|
|
||||||
.rethrow
|
|
||||||
baseUrl = ClientRequestInfo.getBaseUrl(cfg, req)
|
baseUrl = ClientRequestInfo.getBaseUrl(cfg, req)
|
||||||
res <- backend.notification.sendSampleEvent(
|
res <- backend.notification.sendSampleEvent(
|
||||||
input.events.headOption.getOrElse(EventType.all.head),
|
input.events.headOption.getOrElse(EventType.all.head),
|
||||||
ch,
|
ch.map(r => ChannelRef(r.id, r.channelType, r.name)),
|
||||||
user.account,
|
user.account,
|
||||||
baseUrl.some
|
baseUrl.some
|
||||||
)
|
)
|
||||||
@ -179,33 +174,26 @@ object NotificationRoutes {
|
|||||||
NotificationHook(
|
NotificationHook(
|
||||||
h.id,
|
h.id,
|
||||||
h.enabled,
|
h.enabled,
|
||||||
h.channel.map(NotificationChannel.convert),
|
h.channels.map(c => NotificationChannelRef(c.id, c.channelType, c.name)).toList,
|
||||||
h.allEvents,
|
h.allEvents,
|
||||||
h.eventFilter,
|
h.eventFilter,
|
||||||
h.events
|
h.events
|
||||||
)
|
)
|
||||||
|
|
||||||
def convertHook(h: NotificationHook): Either[Throwable, ONotification.Hook] =
|
def convertHook(h: NotificationHook): Either[Throwable, ONotification.Hook] =
|
||||||
h.channel match {
|
NonEmptyList
|
||||||
case Left(cref) =>
|
.fromList(h.channels)
|
||||||
Right(
|
.toRight(new IllegalArgumentException(s"Empty channels not allowed!"))
|
||||||
|
.map(_ =>
|
||||||
ONotification.Hook(
|
ONotification.Hook(
|
||||||
h.id,
|
h.id,
|
||||||
h.enabled,
|
h.enabled,
|
||||||
Left(cref),
|
h.channels.map(c => ChannelRef(c.id, c.channelType, c.name)),
|
||||||
h.allEvents,
|
h.allEvents,
|
||||||
h.eventFilter,
|
h.eventFilter,
|
||||||
h.events
|
h.events
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
case Right(channel) =>
|
|
||||||
NotificationChannel
|
|
||||||
.convert(channel)
|
|
||||||
.map(ch =>
|
|
||||||
ONotification
|
|
||||||
.Hook(h.id, h.enabled, Right(ch), h.allEvents, h.eventFilter, h.events)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,10 +14,10 @@ import docspell.backend.BackendApp
|
|||||||
import docspell.backend.MailAddressCodec
|
import docspell.backend.MailAddressCodec
|
||||||
import docspell.backend.auth.AuthToken
|
import docspell.backend.auth.AuthToken
|
||||||
import docspell.common._
|
import docspell.common._
|
||||||
import docspell.notification.api.PeriodicDueItemsArgs
|
import docspell.notification.api.{ChannelRef, PeriodicDueItemsArgs}
|
||||||
import docspell.restapi.model._
|
import docspell.restapi.model._
|
||||||
import docspell.restserver.Config
|
import docspell.restserver.Config
|
||||||
import docspell.restserver.conv.Conversions
|
import docspell.restserver.conv.{Conversions, NonEmptyListSupport}
|
||||||
import docspell.restserver.http4s.ClientRequestInfo
|
import docspell.restserver.http4s.ClientRequestInfo
|
||||||
import docspell.store.usertask._
|
import docspell.store.usertask._
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ import org.http4s.circe.CirceEntityDecoder._
|
|||||||
import org.http4s.circe.CirceEntityEncoder._
|
import org.http4s.circe.CirceEntityEncoder._
|
||||||
import org.http4s.dsl.Http4sDsl
|
import org.http4s.dsl.Http4sDsl
|
||||||
|
|
||||||
object NotifyDueItemsRoutes extends MailAddressCodec {
|
object NotifyDueItemsRoutes extends MailAddressCodec with NonEmptyListSupport {
|
||||||
|
|
||||||
def apply[F[_]: Async](
|
def apply[F[_]: Async](
|
||||||
cfg: Config,
|
cfg: Config,
|
||||||
@ -113,7 +113,7 @@ object NotifyDueItemsRoutes extends MailAddressCodec {
|
|||||||
user: AccountId,
|
user: AccountId,
|
||||||
settings: PeriodicDueItemsSettings
|
settings: PeriodicDueItemsSettings
|
||||||
): F[UserTask[PeriodicDueItemsArgs]] =
|
): F[UserTask[PeriodicDueItemsArgs]] =
|
||||||
Sync[F].pure(NotificationChannel.convert(settings.channel)).rethrow.map { channel =>
|
requireNonEmpty(settings.channels).map { ch =>
|
||||||
UserTask(
|
UserTask(
|
||||||
id,
|
id,
|
||||||
PeriodicDueItemsArgs.taskName,
|
PeriodicDueItemsArgs.taskName,
|
||||||
@ -122,7 +122,7 @@ object NotifyDueItemsRoutes extends MailAddressCodec {
|
|||||||
settings.summary,
|
settings.summary,
|
||||||
PeriodicDueItemsArgs(
|
PeriodicDueItemsArgs(
|
||||||
user,
|
user,
|
||||||
Right(channel),
|
ch.map(c => ChannelRef(c.id, c.channelType, c.name)),
|
||||||
settings.remindDays,
|
settings.remindDays,
|
||||||
if (settings.capOverdue) Some(settings.remindDays)
|
if (settings.capOverdue) Some(settings.remindDays)
|
||||||
else None,
|
else None,
|
||||||
@ -140,20 +140,13 @@ object NotifyDueItemsRoutes extends MailAddressCodec {
|
|||||||
for {
|
for {
|
||||||
tinc <- backend.tag.loadAll(task.args.tagsInclude)
|
tinc <- backend.tag.loadAll(task.args.tagsInclude)
|
||||||
texc <- backend.tag.loadAll(task.args.tagsExclude)
|
texc <- backend.tag.loadAll(task.args.tagsExclude)
|
||||||
|
|
||||||
ch <- task.args.channel match {
|
|
||||||
case Right(c) => NotificationChannel.convert(c).pure[F]
|
|
||||||
case Left(ref) =>
|
|
||||||
Sync[F].raiseError(
|
|
||||||
new IllegalStateException(s"ChannelRefs are not supported: $ref")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
} yield PeriodicDueItemsSettings(
|
} yield PeriodicDueItemsSettings(
|
||||||
task.id,
|
task.id,
|
||||||
task.enabled,
|
task.enabled,
|
||||||
task.summary,
|
task.summary,
|
||||||
ch,
|
task.args.channels
|
||||||
|
.map(c => NotificationChannelRef(c.id, c.channelType, c.name))
|
||||||
|
.toList,
|
||||||
task.timer,
|
task.timer,
|
||||||
task.args.remindDays,
|
task.args.remindDays,
|
||||||
task.args.daysBack.isDefined,
|
task.args.daysBack.isDefined,
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
package docspell.restserver.routes
|
package docspell.restserver.routes
|
||||||
|
|
||||||
import cats.data.OptionT
|
import cats.data.{NonEmptyList, OptionT}
|
||||||
import cats.effect._
|
import cats.effect._
|
||||||
import cats.implicits._
|
import cats.implicits._
|
||||||
|
|
||||||
@ -14,11 +14,11 @@ import docspell.backend.BackendApp
|
|||||||
import docspell.backend.MailAddressCodec
|
import docspell.backend.MailAddressCodec
|
||||||
import docspell.backend.auth.AuthToken
|
import docspell.backend.auth.AuthToken
|
||||||
import docspell.common._
|
import docspell.common._
|
||||||
import docspell.notification.api.PeriodicQueryArgs
|
import docspell.notification.api.{ChannelRef, PeriodicQueryArgs}
|
||||||
import docspell.query.ItemQueryParser
|
import docspell.query.ItemQueryParser
|
||||||
import docspell.restapi.model._
|
import docspell.restapi.model._
|
||||||
import docspell.restserver.Config
|
import docspell.restserver.Config
|
||||||
import docspell.restserver.conv.Conversions
|
import docspell.restserver.conv.{Conversions, NonEmptyListSupport}
|
||||||
import docspell.restserver.http4s.ClientRequestInfo
|
import docspell.restserver.http4s.ClientRequestInfo
|
||||||
import docspell.store.usertask._
|
import docspell.store.usertask._
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ import org.http4s.circe.CirceEntityDecoder._
|
|||||||
import org.http4s.circe.CirceEntityEncoder._
|
import org.http4s.circe.CirceEntityEncoder._
|
||||||
import org.http4s.dsl.Http4sDsl
|
import org.http4s.dsl.Http4sDsl
|
||||||
|
|
||||||
object PeriodicQueryRoutes extends MailAddressCodec {
|
object PeriodicQueryRoutes extends MailAddressCodec with NonEmptyListSupport {
|
||||||
|
|
||||||
def apply[F[_]: Async](
|
def apply[F[_]: Async](
|
||||||
cfg: Config,
|
cfg: Config,
|
||||||
@ -116,7 +116,9 @@ object PeriodicQueryRoutes extends MailAddressCodec {
|
|||||||
): F[UserTask[PeriodicQueryArgs]] =
|
): F[UserTask[PeriodicQueryArgs]] =
|
||||||
Sync[F]
|
Sync[F]
|
||||||
.pure(for {
|
.pure(for {
|
||||||
ch <- NotificationChannel.convert(settings.channel)
|
ch <- NonEmptyList
|
||||||
|
.fromList(settings.channels)
|
||||||
|
.toRight(new Exception(s"No channels found for: ${settings.channels}"))
|
||||||
qstr <- settings.query match {
|
qstr <- settings.query match {
|
||||||
case Some(q) =>
|
case Some(q) =>
|
||||||
ItemQueryParser
|
ItemQueryParser
|
||||||
@ -132,7 +134,7 @@ object PeriodicQueryRoutes extends MailAddressCodec {
|
|||||||
else Left(new IllegalArgumentException("No query or bookmark provided"))
|
else Left(new IllegalArgumentException("No query or bookmark provided"))
|
||||||
} yield (ch, qstr.map(ItemQueryString.apply)))
|
} yield (ch, qstr.map(ItemQueryString.apply)))
|
||||||
.rethrow
|
.rethrow
|
||||||
.map { case (channel, qstr) =>
|
.map { case (channels, qstr) =>
|
||||||
UserTask(
|
UserTask(
|
||||||
id,
|
id,
|
||||||
PeriodicQueryArgs.taskName,
|
PeriodicQueryArgs.taskName,
|
||||||
@ -141,7 +143,7 @@ object PeriodicQueryRoutes extends MailAddressCodec {
|
|||||||
settings.summary,
|
settings.summary,
|
||||||
PeriodicQueryArgs(
|
PeriodicQueryArgs(
|
||||||
user,
|
user,
|
||||||
Right(channel),
|
channels.map(r => ChannelRef(r.id, r.channelType, r.name)),
|
||||||
qstr,
|
qstr,
|
||||||
settings.bookmark,
|
settings.bookmark,
|
||||||
Some(baseUrl / "app" / "item"),
|
Some(baseUrl / "app" / "item"),
|
||||||
@ -153,22 +155,18 @@ object PeriodicQueryRoutes extends MailAddressCodec {
|
|||||||
def taskToSettings[F[_]: Sync](
|
def taskToSettings[F[_]: Sync](
|
||||||
task: UserTask[PeriodicQueryArgs]
|
task: UserTask[PeriodicQueryArgs]
|
||||||
): F[PeriodicQuerySettings] =
|
): F[PeriodicQuerySettings] =
|
||||||
for {
|
Sync[F].pure(
|
||||||
ch <- task.args.channel match {
|
PeriodicQuerySettings(
|
||||||
case Right(c) => NotificationChannel.convert(c).pure[F]
|
|
||||||
case Left(ref) =>
|
|
||||||
Sync[F].raiseError(
|
|
||||||
new IllegalStateException(s"ChannelRefs are not supported: $ref")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} yield PeriodicQuerySettings(
|
|
||||||
task.id,
|
task.id,
|
||||||
task.summary,
|
|
||||||
task.enabled,
|
task.enabled,
|
||||||
ch,
|
task.summary,
|
||||||
|
task.args.channels
|
||||||
|
.map(c => NotificationChannelRef(c.id, c.channelType, c.name))
|
||||||
|
.toList,
|
||||||
|
task.timer,
|
||||||
task.args.query.map(_.query).map(ItemQueryParser.parseUnsafe),
|
task.args.query.map(_.query).map(ItemQueryParser.parseUnsafe),
|
||||||
task.args.bookmark,
|
task.args.bookmark,
|
||||||
task.args.contentStart,
|
task.args.contentStart
|
||||||
task.timer
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
alter table "notification_channel_mail"
|
||||||
|
add column "name" varchar(254);
|
||||||
|
|
||||||
|
alter table "notification_channel_gotify"
|
||||||
|
add column "name" varchar(254);
|
||||||
|
|
||||||
|
alter table "notification_channel_matrix"
|
||||||
|
add column "name" varchar(254);
|
||||||
|
|
||||||
|
alter table "notification_channel_http"
|
||||||
|
add column "name" varchar(254);
|
@ -0,0 +1,33 @@
|
|||||||
|
CREATE TABLE "notification_hook_channel" (
|
||||||
|
"id" varchar(254) not null primary key,
|
||||||
|
"hook_id" varchar(254) not null,
|
||||||
|
"channel_mail" varchar(254),
|
||||||
|
"channel_gotify" varchar(254),
|
||||||
|
"channel_matrix" varchar(254),
|
||||||
|
"channel_http" varchar(254),
|
||||||
|
foreign key ("hook_id") references "notification_hook"("id") on delete cascade,
|
||||||
|
foreign key ("channel_mail") references "notification_channel_mail"("id") on delete cascade,
|
||||||
|
foreign key ("channel_gotify") references "notification_channel_gotify"("id") on delete cascade,
|
||||||
|
foreign key ("channel_matrix") references "notification_channel_matrix"("id") on delete cascade,
|
||||||
|
foreign key ("channel_http") references "notification_channel_http"("id") on delete cascade,
|
||||||
|
unique("hook_id", "channel_mail"),
|
||||||
|
unique("hook_id", "channel_gotify"),
|
||||||
|
unique("hook_id", "channel_matrix"),
|
||||||
|
unique("hook_id", "channel_http")
|
||||||
|
);
|
||||||
|
|
||||||
|
insert into "notification_hook_channel" ("id", "hook_id", "channel_mail", "channel_gotify", "channel_matrix", "channel_http")
|
||||||
|
select random_uuid(), id, channel_mail, channel_gotify, channel_matrix, channel_http
|
||||||
|
from "notification_hook";
|
||||||
|
|
||||||
|
alter table "notification_hook"
|
||||||
|
drop column "channel_mail";
|
||||||
|
|
||||||
|
alter table "notification_hook"
|
||||||
|
drop column "channel_gotify";
|
||||||
|
|
||||||
|
alter table "notification_hook"
|
||||||
|
drop column "channel_matrix";
|
||||||
|
|
||||||
|
alter table "notification_hook"
|
||||||
|
drop column "channel_http";
|
@ -0,0 +1,11 @@
|
|||||||
|
alter table `notification_channel_mail`
|
||||||
|
add column `name` varchar(254);
|
||||||
|
|
||||||
|
alter table `notification_channel_gotify`
|
||||||
|
add column `name` varchar(254);
|
||||||
|
|
||||||
|
alter table `notification_channel_matrix`
|
||||||
|
add column `name` varchar(254);
|
||||||
|
|
||||||
|
alter table `notification_channel_http`
|
||||||
|
add column `name` varchar(254);
|
@ -0,0 +1,42 @@
|
|||||||
|
CREATE TABLE `notification_hook_channel` (
|
||||||
|
`id` varchar(254) not null primary key,
|
||||||
|
`hook_id` varchar(254) not null,
|
||||||
|
`channel_mail` varchar(254),
|
||||||
|
`channel_gotify` varchar(254),
|
||||||
|
`channel_matrix` varchar(254),
|
||||||
|
`channel_http` varchar(254),
|
||||||
|
foreign key (`hook_id`) references `notification_hook`(`id`) on delete cascade,
|
||||||
|
foreign key (`channel_mail`) references `notification_channel_mail`(`id`) on delete cascade,
|
||||||
|
foreign key (`channel_gotify`) references `notification_channel_gotify`(`id`) on delete cascade,
|
||||||
|
foreign key (`channel_matrix`) references `notification_channel_matrix`(`id`) on delete cascade,
|
||||||
|
foreign key (`channel_http`) references `notification_channel_http`(`id`) on delete cascade,
|
||||||
|
unique(`hook_id`, `channel_mail`),
|
||||||
|
unique(`hook_id`, `channel_gotify`),
|
||||||
|
unique(`hook_id`, `channel_matrix`),
|
||||||
|
unique(`hook_id`, `channel_http`)
|
||||||
|
);
|
||||||
|
|
||||||
|
insert into `notification_hook_channel`
|
||||||
|
select md5(rand()), id, channel_mail, channel_gotify, channel_matrix, channel_http
|
||||||
|
from `notification_hook`;
|
||||||
|
|
||||||
|
alter table `notification_hook`
|
||||||
|
drop constraint `notification_hook_ibfk_2`;
|
||||||
|
alter table `notification_hook`
|
||||||
|
drop constraint `notification_hook_ibfk_3`;
|
||||||
|
alter table `notification_hook`
|
||||||
|
drop constraint `notification_hook_ibfk_4`;
|
||||||
|
alter table `notification_hook`
|
||||||
|
drop constraint `notification_hook_ibfk_5`;
|
||||||
|
|
||||||
|
alter table `notification_hook`
|
||||||
|
drop column `channel_mail`;
|
||||||
|
|
||||||
|
alter table `notification_hook`
|
||||||
|
drop column `channel_gotify`;
|
||||||
|
|
||||||
|
alter table `notification_hook`
|
||||||
|
drop column `channel_matrix`;
|
||||||
|
|
||||||
|
alter table `notification_hook`
|
||||||
|
drop column `channel_http`;
|
@ -0,0 +1,11 @@
|
|||||||
|
alter table "notification_channel_mail"
|
||||||
|
add column "name" varchar(254);
|
||||||
|
|
||||||
|
alter table "notification_channel_gotify"
|
||||||
|
add column "name" varchar(254);
|
||||||
|
|
||||||
|
alter table "notification_channel_matrix"
|
||||||
|
add column "name" varchar(254);
|
||||||
|
|
||||||
|
alter table "notification_channel_http"
|
||||||
|
add column "name" varchar(254);
|
@ -0,0 +1,33 @@
|
|||||||
|
CREATE TABLE "notification_hook_channel" (
|
||||||
|
"id" varchar(254) not null primary key,
|
||||||
|
"hook_id" varchar(254) not null,
|
||||||
|
"channel_mail" varchar(254),
|
||||||
|
"channel_gotify" varchar(254),
|
||||||
|
"channel_matrix" varchar(254),
|
||||||
|
"channel_http" varchar(254),
|
||||||
|
foreign key ("hook_id") references "notification_hook"("id") on delete cascade,
|
||||||
|
foreign key ("channel_mail") references "notification_channel_mail"("id") on delete cascade,
|
||||||
|
foreign key ("channel_gotify") references "notification_channel_gotify"("id") on delete cascade,
|
||||||
|
foreign key ("channel_matrix") references "notification_channel_matrix"("id") on delete cascade,
|
||||||
|
foreign key ("channel_http") references "notification_channel_http"("id") on delete cascade,
|
||||||
|
unique("hook_id", "channel_mail"),
|
||||||
|
unique("hook_id", "channel_gotify"),
|
||||||
|
unique("hook_id", "channel_matrix"),
|
||||||
|
unique("hook_id", "channel_http")
|
||||||
|
);
|
||||||
|
|
||||||
|
insert into "notification_hook_channel" ("id", "hook_id", "channel_mail", "channel_gotify", "channel_matrix", "channel_http")
|
||||||
|
select md5(random()::text), id, channel_mail, channel_gotify, channel_matrix, channel_http
|
||||||
|
from "notification_hook";
|
||||||
|
|
||||||
|
alter table "notification_hook"
|
||||||
|
drop column "channel_mail";
|
||||||
|
|
||||||
|
alter table "notification_hook"
|
||||||
|
drop column "channel_gotify";
|
||||||
|
|
||||||
|
alter table "notification_hook"
|
||||||
|
drop column "channel_matrix";
|
||||||
|
|
||||||
|
alter table "notification_hook"
|
||||||
|
drop column "channel_http";
|
@ -6,23 +6,23 @@
|
|||||||
|
|
||||||
package db.migration
|
package db.migration
|
||||||
|
|
||||||
import cats.data.NonEmptyList
|
import cats.data.{NonEmptyList, OptionT}
|
||||||
import cats.effect.{IO, Sync}
|
import cats.effect.{IO, Sync}
|
||||||
import cats.implicits._
|
import cats.implicits._
|
||||||
|
|
||||||
import docspell.common._
|
import docspell.common._
|
||||||
import docspell.common.syntax.StringSyntax._
|
import docspell.common.syntax.StringSyntax._
|
||||||
import docspell.notification.api.Channel
|
import docspell.notification.api._
|
||||||
import docspell.notification.api.PeriodicDueItemsArgs
|
import docspell.store.records._
|
||||||
import docspell.store.records.RPeriodicTask
|
|
||||||
|
|
||||||
|
import db.migration.data.{PeriodicDueItemsArgsOld, PeriodicQueryArgsOld}
|
||||||
import doobie._
|
import doobie._
|
||||||
import doobie.implicits._
|
import doobie.implicits._
|
||||||
import doobie.util.transactor.Strategy
|
import doobie.util.transactor.Strategy
|
||||||
import emil.MailAddress
|
import emil.MailAddress
|
||||||
import emil.javamail.syntax._
|
import emil.javamail.syntax._
|
||||||
import io.circe.Encoder
|
|
||||||
import io.circe.syntax._
|
import io.circe.syntax._
|
||||||
|
import io.circe.{Decoder, Encoder}
|
||||||
import org.flywaydb.core.api.migration.Context
|
import org.flywaydb.core.api.migration.Context
|
||||||
|
|
||||||
trait MigrationTasks {
|
trait MigrationTasks {
|
||||||
@ -31,6 +31,8 @@ trait MigrationTasks {
|
|||||||
|
|
||||||
implicit val jsonEncoder: Encoder[MailAddress] =
|
implicit val jsonEncoder: Encoder[MailAddress] =
|
||||||
Encoder.encodeString.contramap(_.asUnicodeString)
|
Encoder.encodeString.contramap(_.asUnicodeString)
|
||||||
|
implicit val jsonDecoder: Decoder[MailAddress] =
|
||||||
|
Decoder.decodeString.emap(MailAddress.parse)
|
||||||
|
|
||||||
def migrateDueItemTasks: ConnectionIO[Unit] =
|
def migrateDueItemTasks: ConnectionIO[Unit] =
|
||||||
for {
|
for {
|
||||||
@ -42,20 +44,114 @@ trait MigrationTasks {
|
|||||||
_ <- RPeriodicTask.setEnabledByTask(NotifyDueItemsArgs.taskName, false)
|
_ <- RPeriodicTask.setEnabledByTask(NotifyDueItemsArgs.taskName, false)
|
||||||
} yield ()
|
} yield ()
|
||||||
|
|
||||||
def migrateDueItemTask1(old: RPeriodicTask): ConnectionIO[Int] = {
|
def migratePeriodicItemTasks: ConnectionIO[Unit] =
|
||||||
val converted = old.args
|
for {
|
||||||
.parseJsonAs[NotifyDueItemsArgs]
|
tasks2 <- RPeriodicTask.findByTask(PeriodicDueItemsArgsOld.taskName)
|
||||||
.leftMap(_.getMessage())
|
tasks3 <- RPeriodicTask.findByTask(PeriodicQueryArgsOld.taskName)
|
||||||
.flatMap(convertArgs)
|
size = tasks2.size + tasks3.size
|
||||||
|
_ <- Sync[ConnectionIO].delay(
|
||||||
|
logger.info(s"Starting to migrate $size user tasks")
|
||||||
|
)
|
||||||
|
_ <- tasks2.traverse(migratePeriodicDueItemsTask)
|
||||||
|
_ <- tasks3.traverse(migratePeriodicQueryTask)
|
||||||
|
_ <- RPeriodicTask.setEnabledByTask(PeriodicQueryArgsOld.taskName, false)
|
||||||
|
_ <- RPeriodicTask.setEnabledByTask(PeriodicDueItemsArgsOld.taskName, false)
|
||||||
|
} yield ()
|
||||||
|
|
||||||
converted match {
|
private def migratePeriodicQueryTask(old: RPeriodicTask): ConnectionIO[Int] =
|
||||||
case Right(args) =>
|
old.args
|
||||||
Sync[ConnectionIO].delay(logger.info(s"Converting user task: $old")) *>
|
.parseJsonAs[PeriodicQueryArgsOld]
|
||||||
|
.leftMap { ex =>
|
||||||
|
logger.error(ex)(s"Error migrating tasks")
|
||||||
|
0.pure[ConnectionIO]
|
||||||
|
}
|
||||||
|
.map { oldArgs =>
|
||||||
|
val ref = oldArgs.channel match {
|
||||||
|
case Right(c) => saveChannel(c, oldArgs.account)
|
||||||
|
case Left(ref) => ref.pure[ConnectionIO]
|
||||||
|
}
|
||||||
|
|
||||||
|
ref.flatMap(channelRef =>
|
||||||
|
RPeriodicTask.updateTask(
|
||||||
|
old.id,
|
||||||
|
PeriodicQueryArgs.taskName,
|
||||||
|
PeriodicQueryArgs(
|
||||||
|
oldArgs.account,
|
||||||
|
NonEmptyList.of(channelRef),
|
||||||
|
oldArgs.query,
|
||||||
|
oldArgs.bookmark,
|
||||||
|
oldArgs.baseUrl,
|
||||||
|
oldArgs.contentStart
|
||||||
|
).asJson.noSpaces
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.fold(identity, identity)
|
||||||
|
|
||||||
|
private def migratePeriodicDueItemsTask(old: RPeriodicTask): ConnectionIO[Int] =
|
||||||
|
old.args
|
||||||
|
.parseJsonAs[PeriodicDueItemsArgsOld]
|
||||||
|
.leftMap { ex =>
|
||||||
|
logger.error(ex)(s"Error migrating tasks")
|
||||||
|
0.pure[ConnectionIO]
|
||||||
|
}
|
||||||
|
.map { oldArgs =>
|
||||||
|
val ref = oldArgs.channel match {
|
||||||
|
case Right(c) => saveChannel(c, oldArgs.account)
|
||||||
|
case Left(ref) => ref.pure[ConnectionIO]
|
||||||
|
}
|
||||||
|
|
||||||
|
ref.flatMap(channelRef =>
|
||||||
RPeriodicTask.updateTask(
|
RPeriodicTask.updateTask(
|
||||||
old.id,
|
old.id,
|
||||||
PeriodicDueItemsArgs.taskName,
|
PeriodicDueItemsArgs.taskName,
|
||||||
args.asJson.noSpaces
|
PeriodicDueItemsArgs(
|
||||||
|
oldArgs.account,
|
||||||
|
NonEmptyList.of(channelRef),
|
||||||
|
oldArgs.remindDays,
|
||||||
|
oldArgs.daysBack,
|
||||||
|
oldArgs.tagsInclude,
|
||||||
|
oldArgs.tagsExclude,
|
||||||
|
oldArgs.baseUrl
|
||||||
|
).asJson.noSpaces
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.fold(identity, identity)
|
||||||
|
|
||||||
|
private def saveChannel(ch: Channel, account: AccountId): ConnectionIO[ChannelRef] =
|
||||||
|
(for {
|
||||||
|
newId <- OptionT.liftF(Ident.randomId[ConnectionIO])
|
||||||
|
userId <- OptionT(RUser.findIdByAccount(account))
|
||||||
|
r <- RNotificationChannel.fromChannel(ch, newId, userId)
|
||||||
|
_ <- OptionT.liftF(RNotificationChannel.insert(r))
|
||||||
|
_ <- OptionT.liftF(
|
||||||
|
Sync[ConnectionIO].delay(logger.debug(s"Created channel $r for $account"))
|
||||||
|
)
|
||||||
|
ref = r.asRef
|
||||||
|
} yield ref)
|
||||||
|
.getOrElseF(Sync[ConnectionIO].raiseError(new Exception("User not found!")))
|
||||||
|
|
||||||
|
private def migrateDueItemTask1(old: RPeriodicTask): ConnectionIO[Int] = {
|
||||||
|
val converted = old.args
|
||||||
|
.parseJsonAs[NotifyDueItemsArgs]
|
||||||
|
.leftMap(_.getMessage())
|
||||||
|
.map(convertArgs)
|
||||||
|
|
||||||
|
converted match {
|
||||||
|
case Right(args) =>
|
||||||
|
val task = args
|
||||||
|
.semiflatMap(a =>
|
||||||
|
RPeriodicTask
|
||||||
|
.updateTask(
|
||||||
|
old.id,
|
||||||
|
PeriodicDueItemsArgs.taskName,
|
||||||
|
a.asJson.noSpaces
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.getOrElse(0)
|
||||||
|
|
||||||
|
Sync[ConnectionIO].delay(logger.info(s"Converting user task: $old")) *> task
|
||||||
|
|
||||||
case Left(err) =>
|
case Left(err) =>
|
||||||
logger.error(s"Error converting user task: $old. $err")
|
logger.error(s"Error converting user task: $old. $err")
|
||||||
@ -63,20 +159,42 @@ trait MigrationTasks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def convertArgs(old: NotifyDueItemsArgs): Either[String, PeriodicDueItemsArgs] =
|
private def convertArgs(
|
||||||
old.recipients
|
old: NotifyDueItemsArgs
|
||||||
.traverse(MailAddress.parse)
|
): OptionT[ConnectionIO, PeriodicDueItemsArgs] = {
|
||||||
.flatMap(l => NonEmptyList.fromList(l).toRight("No recipients provided"))
|
val recs = old.recipients
|
||||||
.map { rec =>
|
.map(MailAddress.parse)
|
||||||
PeriodicDueItemsArgs(
|
.flatMap {
|
||||||
|
case Right(m) => Some(m)
|
||||||
|
case Left(err) =>
|
||||||
|
logger.warn(s"Cannot read mail address: $err. Skip this while migrating.")
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
userId <- OptionT(RUser.findIdByAccount(old.account))
|
||||||
|
id <- OptionT.liftF(Ident.randomId[ConnectionIO])
|
||||||
|
now <- OptionT.liftF(Timestamp.current[ConnectionIO])
|
||||||
|
chName = Some("migrate notify items")
|
||||||
|
ch = RNotificationChannelMail(
|
||||||
|
id,
|
||||||
|
userId,
|
||||||
|
chName,
|
||||||
|
old.smtpConnection,
|
||||||
|
recs,
|
||||||
|
now
|
||||||
|
)
|
||||||
|
_ <- OptionT.liftF(RNotificationChannelMail.insert(ch))
|
||||||
|
args = PeriodicDueItemsArgs(
|
||||||
old.account,
|
old.account,
|
||||||
Right(Channel.Mail(Ident.unsafe(""), old.smtpConnection, rec)),
|
NonEmptyList.of(ChannelRef(ch.id, ChannelType.Mail, chName)),
|
||||||
old.remindDays,
|
old.remindDays,
|
||||||
old.daysBack,
|
old.daysBack,
|
||||||
old.tagsInclude,
|
old.tagsInclude,
|
||||||
old.tagsExclude,
|
old.tagsExclude,
|
||||||
old.itemDetailUrl
|
old.itemDetailUrl
|
||||||
)
|
)
|
||||||
|
} yield args
|
||||||
}
|
}
|
||||||
|
|
||||||
def mkTransactor(ctx: Context): Transactor[IO] = {
|
def mkTransactor(ctx: Context): Transactor[IO] = {
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 Eike K. & Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package db.migration.data
|
||||||
|
|
||||||
|
import docspell.common._
|
||||||
|
|
||||||
|
import emil.MailAddress
|
||||||
|
import io.circe.generic.semiauto
|
||||||
|
import io.circe.{Decoder, Encoder}
|
||||||
|
|
||||||
|
/** Arguments to the notification task.
|
||||||
|
*
|
||||||
|
* This tasks queries items with a due date and informs the user via mail.
|
||||||
|
*
|
||||||
|
* If the structure changes, there must be some database migration to update or remove
|
||||||
|
* the json data of the corresponding task.
|
||||||
|
*/
|
||||||
|
final case class PeriodicDueItemsArgsOld(
|
||||||
|
account: AccountId,
|
||||||
|
channel: ChannelOrRef,
|
||||||
|
remindDays: Int,
|
||||||
|
daysBack: Option[Int],
|
||||||
|
tagsInclude: List[Ident],
|
||||||
|
tagsExclude: List[Ident],
|
||||||
|
baseUrl: Option[LenientUri]
|
||||||
|
)
|
||||||
|
|
||||||
|
object PeriodicDueItemsArgsOld {
|
||||||
|
val taskName = Ident.unsafe("periodic-due-items-notify")
|
||||||
|
|
||||||
|
implicit def jsonDecoder(implicit
|
||||||
|
mc: Decoder[MailAddress]
|
||||||
|
): Decoder[PeriodicDueItemsArgsOld] = {
|
||||||
|
implicit val x = ChannelOrRef.jsonDecoder
|
||||||
|
semiauto.deriveDecoder
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit def jsonEncoder(implicit
|
||||||
|
mc: Encoder[MailAddress]
|
||||||
|
): Encoder[PeriodicDueItemsArgsOld] = {
|
||||||
|
implicit val x = ChannelOrRef.jsonEncoder
|
||||||
|
semiauto.deriveEncoder
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 Eike K. & Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package db.migration.data
|
||||||
|
|
||||||
|
import docspell.common._
|
||||||
|
|
||||||
|
import emil.MailAddress
|
||||||
|
import io.circe.generic.semiauto
|
||||||
|
import io.circe.{Decoder, Encoder}
|
||||||
|
|
||||||
|
final case class PeriodicQueryArgsOld(
|
||||||
|
account: AccountId,
|
||||||
|
channel: ChannelOrRef,
|
||||||
|
query: Option[ItemQueryString],
|
||||||
|
bookmark: Option[String],
|
||||||
|
baseUrl: Option[LenientUri],
|
||||||
|
contentStart: Option[String]
|
||||||
|
)
|
||||||
|
|
||||||
|
object PeriodicQueryArgsOld {
|
||||||
|
val taskName = Ident.unsafe("periodic-query-notify")
|
||||||
|
|
||||||
|
implicit def jsonDecoder(implicit
|
||||||
|
mc: Decoder[MailAddress]
|
||||||
|
): Decoder[PeriodicQueryArgsOld] = {
|
||||||
|
implicit val x = ChannelOrRef.jsonDecoder
|
||||||
|
semiauto.deriveDecoder
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit def jsonEncoder(implicit
|
||||||
|
mc: Encoder[MailAddress]
|
||||||
|
): Encoder[PeriodicQueryArgsOld] = {
|
||||||
|
implicit val x = ChannelOrRef.jsonEncoder
|
||||||
|
semiauto.deriveEncoder
|
||||||
|
}
|
||||||
|
}
|
@ -4,13 +4,14 @@
|
|||||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package docspell.notification
|
package db.migration
|
||||||
|
|
||||||
|
import docspell.notification.api._
|
||||||
|
|
||||||
import emil.MailAddress
|
import emil.MailAddress
|
||||||
import io.circe.{Decoder, Encoder}
|
import io.circe.{Decoder, Encoder}
|
||||||
|
|
||||||
package object api {
|
package object data {
|
||||||
|
|
||||||
type ChannelOrRef = Either[ChannelRef, Channel]
|
type ChannelOrRef = Either[ChannelRef, Channel]
|
||||||
|
|
||||||
object ChannelOrRef {
|
object ChannelOrRef {
|
||||||
@ -25,5 +26,4 @@ package object api {
|
|||||||
cr.fold(_.channelType, _.channelType)
|
cr.fold(_.channelType, _.channelType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 Eike K. & Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package db.migration.h2
|
||||||
|
|
||||||
|
import cats.effect.unsafe.implicits._
|
||||||
|
|
||||||
|
import db.migration.MigrationTasks
|
||||||
|
import doobie.implicits._
|
||||||
|
import org.flywaydb.core.api.migration.{BaseJavaMigration, Context}
|
||||||
|
|
||||||
|
class V1_32_2__MigrateChannels extends BaseJavaMigration with MigrationTasks {
|
||||||
|
val logger = org.log4s.getLogger
|
||||||
|
|
||||||
|
override def migrate(ctx: Context): Unit = {
|
||||||
|
val xa = mkTransactor(ctx)
|
||||||
|
migratePeriodicItemTasks.transact(xa).unsafeRunSync()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 Eike K. & Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package db.migration.mariadb
|
||||||
|
|
||||||
|
import cats.effect.unsafe.implicits._
|
||||||
|
|
||||||
|
import db.migration.MigrationTasks
|
||||||
|
import doobie.implicits._
|
||||||
|
import org.flywaydb.core.api.migration.{BaseJavaMigration, Context}
|
||||||
|
|
||||||
|
class V1_32_2__MigrateChannels extends BaseJavaMigration with MigrationTasks {
|
||||||
|
val logger = org.log4s.getLogger
|
||||||
|
|
||||||
|
override def migrate(ctx: Context): Unit = {
|
||||||
|
val xa = mkTransactor(ctx)
|
||||||
|
migratePeriodicItemTasks.transact(xa).unsafeRunSync()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 Eike K. & Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package db.migration.postgresql
|
||||||
|
|
||||||
|
import cats.effect.unsafe.implicits._
|
||||||
|
|
||||||
|
import db.migration.MigrationTasks
|
||||||
|
import doobie.implicits._
|
||||||
|
import org.flywaydb.core.api.migration.{BaseJavaMigration, Context}
|
||||||
|
|
||||||
|
class V1_32_2__MigrateChannels extends BaseJavaMigration with MigrationTasks {
|
||||||
|
val logger = org.log4s.getLogger
|
||||||
|
|
||||||
|
override def migrate(ctx: Context): Unit = {
|
||||||
|
val xa = mkTransactor(ctx)
|
||||||
|
migratePeriodicItemTasks.transact(xa).unsafeRunSync()
|
||||||
|
}
|
||||||
|
}
|
@ -12,7 +12,7 @@ import java.time.{Instant, LocalDate}
|
|||||||
import docspell.common._
|
import docspell.common._
|
||||||
import docspell.common.syntax.all._
|
import docspell.common.syntax.all._
|
||||||
import docspell.jsonminiq.JsonMiniQuery
|
import docspell.jsonminiq.JsonMiniQuery
|
||||||
import docspell.notification.api.EventType
|
import docspell.notification.api.{ChannelType, EventType}
|
||||||
import docspell.query.{ItemQuery, ItemQueryParser}
|
import docspell.query.{ItemQuery, ItemQueryParser}
|
||||||
import docspell.totp.Key
|
import docspell.totp.Key
|
||||||
|
|
||||||
@ -156,6 +156,9 @@ trait DoobieMeta extends EmilDoobieMeta {
|
|||||||
|
|
||||||
implicit val metaJsonMiniQuery: Meta[JsonMiniQuery] =
|
implicit val metaJsonMiniQuery: Meta[JsonMiniQuery] =
|
||||||
Meta[String].timap(JsonMiniQuery.unsafeParse)(_.unsafeAsString)
|
Meta[String].timap(JsonMiniQuery.unsafeParse)(_.unsafeAsString)
|
||||||
|
|
||||||
|
implicit val channelTypeRead: Read[ChannelType] =
|
||||||
|
Read[String].map(ChannelType.unsafeFromString)
|
||||||
}
|
}
|
||||||
|
|
||||||
object DoobieMeta extends DoobieMeta {
|
object DoobieMeta extends DoobieMeta {
|
||||||
|
@ -27,7 +27,11 @@ object QNotification {
|
|||||||
def findChannelsForEvent(event: Event): ConnectionIO[Vector[HookChannel]] =
|
def findChannelsForEvent(event: Event): ConnectionIO[Vector[HookChannel]] =
|
||||||
for {
|
for {
|
||||||
hooks <- listHooks(event.account.collective, event.eventType)
|
hooks <- listHooks(event.account.collective, event.eventType)
|
||||||
chs <- hooks.traverse(readHookChannel)
|
chs <- hooks.traverse(h =>
|
||||||
|
listChannels(h.id)
|
||||||
|
.flatMap(_.flatTraverse(hc => readHookChannel(h.uid, hc)))
|
||||||
|
.map(c => HookChannel(h, c))
|
||||||
|
)
|
||||||
} yield chs
|
} yield chs
|
||||||
|
|
||||||
// --
|
// --
|
||||||
@ -50,21 +54,27 @@ object QNotification {
|
|||||||
)
|
)
|
||||||
).query[RNotificationHook].to[Vector]
|
).query[RNotificationHook].to[Vector]
|
||||||
|
|
||||||
|
def listChannels(hookId: Ident): ConnectionIO[Vector[RNotificationHookChannel]] =
|
||||||
|
RNotificationHookChannel.allOf(hookId)
|
||||||
|
|
||||||
def readHookChannel(
|
def readHookChannel(
|
||||||
hook: RNotificationHook
|
userId: Ident,
|
||||||
): ConnectionIO[HookChannel] =
|
hook: RNotificationHookChannel
|
||||||
|
): ConnectionIO[Vector[NotificationChannel]] =
|
||||||
for {
|
for {
|
||||||
c1 <- read(hook.channelMail)(RNotificationChannelMail.getById)(
|
c1 <- read(hook.channelMail)(RNotificationChannelMail.getById(userId))(
|
||||||
ChannelMap.readMail
|
ChannelMap.readMail
|
||||||
)
|
)
|
||||||
c2 <- read(hook.channelGotify)(RNotificationChannelGotify.getById)(
|
c2 <- read(hook.channelGotify)(RNotificationChannelGotify.getById(userId))(
|
||||||
ChannelMap.readGotify
|
ChannelMap.readGotify
|
||||||
)
|
)
|
||||||
c3 <- read(hook.channelMatrix)(RNotificationChannelMatrix.getById)(
|
c3 <- read(hook.channelMatrix)(RNotificationChannelMatrix.getById(userId))(
|
||||||
ChannelMap.readMatrix
|
ChannelMap.readMatrix
|
||||||
)
|
)
|
||||||
c4 <- read(hook.channelHttp)(RNotificationChannelHttp.getById)(ChannelMap.readHttp)
|
c4 <- read(hook.channelHttp)(RNotificationChannelHttp.getById(userId))(
|
||||||
} yield HookChannel(hook, c1 ++ c2 ++ c3 ++ c4)
|
ChannelMap.readHttp
|
||||||
|
)
|
||||||
|
} yield c1 ++ c2 ++ c3 ++ c4
|
||||||
|
|
||||||
def readChannel(ch: RNotificationChannel): ConnectionIO[Vector[NotificationChannel]] =
|
def readChannel(ch: RNotificationChannel): ConnectionIO[Vector[NotificationChannel]] =
|
||||||
ch.fold(
|
ch.fold(
|
||||||
|
@ -7,16 +7,29 @@
|
|||||||
package docspell.store.records
|
package docspell.store.records
|
||||||
|
|
||||||
import cats.data.OptionT
|
import cats.data.OptionT
|
||||||
|
import cats.implicits._
|
||||||
|
|
||||||
import docspell.common._
|
import docspell.common._
|
||||||
import docspell.notification.api.ChannelRef
|
import docspell.notification.api.{Channel, ChannelRef, ChannelType}
|
||||||
import docspell.notification.api.ChannelType
|
|
||||||
|
|
||||||
import doobie._
|
import doobie._
|
||||||
|
|
||||||
sealed trait RNotificationChannel {
|
sealed trait RNotificationChannel {
|
||||||
|
|
||||||
def id: Ident
|
def id: Ident = fold(_.id, _.id, _.id, _.id)
|
||||||
|
|
||||||
|
def name: Option[String] = fold(_.name, _.name, _.name, _.name)
|
||||||
|
|
||||||
|
def channelType: ChannelType =
|
||||||
|
fold(
|
||||||
|
_ => ChannelType.Mail,
|
||||||
|
_ => ChannelType.Gotify,
|
||||||
|
_ => ChannelType.Matrix,
|
||||||
|
_ => ChannelType.Http
|
||||||
|
)
|
||||||
|
|
||||||
|
def asRef: ChannelRef =
|
||||||
|
ChannelRef(id, channelType, name)
|
||||||
|
|
||||||
def fold[A](
|
def fold[A](
|
||||||
f1: RNotificationChannelMail => A,
|
f1: RNotificationChannelMail => A,
|
||||||
@ -24,7 +37,6 @@ sealed trait RNotificationChannel {
|
|||||||
f3: RNotificationChannelMatrix => A,
|
f3: RNotificationChannelMatrix => A,
|
||||||
f4: RNotificationChannelHttp => A
|
f4: RNotificationChannelHttp => A
|
||||||
): A
|
): A
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object RNotificationChannel {
|
object RNotificationChannel {
|
||||||
@ -37,8 +49,6 @@ object RNotificationChannel {
|
|||||||
f3: RNotificationChannelMatrix => A,
|
f3: RNotificationChannelMatrix => A,
|
||||||
f4: RNotificationChannelHttp => A
|
f4: RNotificationChannelHttp => A
|
||||||
): A = f1(r)
|
): A = f1(r)
|
||||||
|
|
||||||
val id = r.id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final case class Gotify(r: RNotificationChannelGotify) extends RNotificationChannel {
|
final case class Gotify(r: RNotificationChannelGotify) extends RNotificationChannel {
|
||||||
@ -48,8 +58,6 @@ object RNotificationChannel {
|
|||||||
f3: RNotificationChannelMatrix => A,
|
f3: RNotificationChannelMatrix => A,
|
||||||
f4: RNotificationChannelHttp => A
|
f4: RNotificationChannelHttp => A
|
||||||
): A = f2(r)
|
): A = f2(r)
|
||||||
|
|
||||||
val id = r.id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final case class Matrix(r: RNotificationChannelMatrix) extends RNotificationChannel {
|
final case class Matrix(r: RNotificationChannelMatrix) extends RNotificationChannel {
|
||||||
@ -59,8 +67,6 @@ object RNotificationChannel {
|
|||||||
f3: RNotificationChannelMatrix => A,
|
f3: RNotificationChannelMatrix => A,
|
||||||
f4: RNotificationChannelHttp => A
|
f4: RNotificationChannelHttp => A
|
||||||
): A = f3(r)
|
): A = f3(r)
|
||||||
|
|
||||||
val id = r.id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final case class Http(r: RNotificationChannelHttp) extends RNotificationChannel {
|
final case class Http(r: RNotificationChannelHttp) extends RNotificationChannel {
|
||||||
@ -70,8 +76,6 @@ object RNotificationChannel {
|
|||||||
f3: RNotificationChannelMatrix => A,
|
f3: RNotificationChannelMatrix => A,
|
||||||
f4: RNotificationChannelHttp => A
|
f4: RNotificationChannelHttp => A
|
||||||
): A = f4(r)
|
): A = f4(r)
|
||||||
|
|
||||||
val id = r.id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def insert(r: RNotificationChannel): ConnectionIO[Int] =
|
def insert(r: RNotificationChannel): ConnectionIO[Int] =
|
||||||
@ -100,42 +104,60 @@ object RNotificationChannel {
|
|||||||
Matrix.apply
|
Matrix.apply
|
||||||
) ++ http.map(Http.apply)
|
) ++ http.map(Http.apply)
|
||||||
|
|
||||||
def getById(id: Ident): ConnectionIO[Vector[RNotificationChannel]] =
|
def getById(id: Ident, userId: Ident): ConnectionIO[Vector[RNotificationChannel]] =
|
||||||
for {
|
for {
|
||||||
mail <- RNotificationChannelMail.getById(id)
|
mail <- RNotificationChannelMail.getById(userId)(id)
|
||||||
gotify <- RNotificationChannelGotify.getById(id)
|
gotify <- RNotificationChannelGotify.getById(userId)(id)
|
||||||
matrix <- RNotificationChannelMatrix.getById(id)
|
matrix <- RNotificationChannelMatrix.getById(userId)(id)
|
||||||
http <- RNotificationChannelHttp.getById(id)
|
http <- RNotificationChannelHttp.getById(userId)(id)
|
||||||
} yield mail.map(Email.apply).toVector ++
|
} yield mail.map(Email.apply).toVector ++
|
||||||
gotify.map(Gotify.apply).toVector ++
|
gotify.map(Gotify.apply).toVector ++
|
||||||
matrix.map(Matrix.apply).toVector ++
|
matrix.map(Matrix.apply).toVector ++
|
||||||
http.map(Http.apply).toVector
|
http.map(Http.apply).toVector
|
||||||
|
|
||||||
def getByRef(ref: ChannelRef): ConnectionIO[Option[RNotificationChannel]] =
|
def getByRef(
|
||||||
|
ref: ChannelRef,
|
||||||
|
userId: Ident
|
||||||
|
): ConnectionIO[Option[RNotificationChannel]] =
|
||||||
ref.channelType match {
|
ref.channelType match {
|
||||||
case ChannelType.Mail =>
|
case ChannelType.Mail =>
|
||||||
RNotificationChannelMail.getById(ref.id).map(_.map(Email.apply))
|
RNotificationChannelMail.getById(userId)(ref.id).map(_.map(Email.apply))
|
||||||
case ChannelType.Matrix =>
|
case ChannelType.Matrix =>
|
||||||
RNotificationChannelMatrix.getById(ref.id).map(_.map(Matrix.apply))
|
RNotificationChannelMatrix.getById(userId)(ref.id).map(_.map(Matrix.apply))
|
||||||
case ChannelType.Gotify =>
|
case ChannelType.Gotify =>
|
||||||
RNotificationChannelGotify.getById(ref.id).map(_.map(Gotify.apply))
|
RNotificationChannelGotify.getById(userId)(ref.id).map(_.map(Gotify.apply))
|
||||||
case ChannelType.Http =>
|
case ChannelType.Http =>
|
||||||
RNotificationChannelHttp.getById(ref.id).map(_.map(Http.apply))
|
RNotificationChannelHttp.getById(userId)(ref.id).map(_.map(Http.apply))
|
||||||
}
|
}
|
||||||
|
|
||||||
def getByHook(r: RNotificationHook): ConnectionIO[Vector[RNotificationChannel]] = {
|
def getByHook(hook: RNotificationHook): ConnectionIO[Vector[RNotificationChannel]] = {
|
||||||
def opt(id: Option[Ident]): OptionT[ConnectionIO, Ident] =
|
def opt(id: Option[Ident]): OptionT[ConnectionIO, Ident] =
|
||||||
OptionT.fromOption(id)
|
OptionT.fromOption(id)
|
||||||
|
|
||||||
|
def find(
|
||||||
|
r: RNotificationHookChannel
|
||||||
|
): ConnectionIO[Vector[RNotificationChannel]] =
|
||||||
for {
|
for {
|
||||||
mail <- opt(r.channelMail).flatMapF(RNotificationChannelMail.getById).value
|
mail <- opt(r.channelMail)
|
||||||
gotify <- opt(r.channelGotify).flatMapF(RNotificationChannelGotify.getById).value
|
.flatMapF(RNotificationChannelMail.getById(hook.uid))
|
||||||
matrix <- opt(r.channelMatrix).flatMapF(RNotificationChannelMatrix.getById).value
|
.value
|
||||||
http <- opt(r.channelHttp).flatMapF(RNotificationChannelHttp.getById).value
|
gotify <- opt(r.channelGotify)
|
||||||
|
.flatMapF(RNotificationChannelGotify.getById(hook.uid))
|
||||||
|
.value
|
||||||
|
matrix <- opt(r.channelMatrix)
|
||||||
|
.flatMapF(RNotificationChannelMatrix.getById(hook.uid))
|
||||||
|
.value
|
||||||
|
http <- opt(r.channelHttp)
|
||||||
|
.flatMapF(RNotificationChannelHttp.getById(hook.uid))
|
||||||
|
.value
|
||||||
} yield mail.map(Email.apply).toVector ++
|
} yield mail.map(Email.apply).toVector ++
|
||||||
gotify.map(Gotify.apply).toVector ++
|
gotify.map(Gotify.apply).toVector ++
|
||||||
matrix.map(Matrix.apply).toVector ++
|
matrix.map(Matrix.apply).toVector ++
|
||||||
http.map(Http.apply).toVector
|
http.map(Http.apply).toVector
|
||||||
|
|
||||||
|
RNotificationHookChannel
|
||||||
|
.allOf(hook.id)
|
||||||
|
.flatMap(_.flatTraverse(find))
|
||||||
}
|
}
|
||||||
|
|
||||||
def deleteByAccount(id: Ident, account: AccountId): ConnectionIO[Int] =
|
def deleteByAccount(id: Ident, account: AccountId): ConnectionIO[Int] =
|
||||||
@ -145,4 +167,63 @@ object RNotificationChannel {
|
|||||||
n3 <- RNotificationChannelMatrix.deleteByAccount(id, account)
|
n3 <- RNotificationChannelMatrix.deleteByAccount(id, account)
|
||||||
n4 <- RNotificationChannelHttp.deleteByAccount(id, account)
|
n4 <- RNotificationChannelHttp.deleteByAccount(id, account)
|
||||||
} yield n1 + n2 + n3 + n4
|
} yield n1 + n2 + n3 + n4
|
||||||
|
|
||||||
|
def fromChannel(
|
||||||
|
channel: Channel,
|
||||||
|
id: Ident,
|
||||||
|
userId: Ident
|
||||||
|
): OptionT[ConnectionIO, RNotificationChannel] =
|
||||||
|
for {
|
||||||
|
time <- OptionT.liftF(Timestamp.current[ConnectionIO])
|
||||||
|
logger = Logger.log4s[ConnectionIO](org.log4s.getLogger)
|
||||||
|
r <-
|
||||||
|
channel match {
|
||||||
|
case Channel.Mail(_, name, conn, recipients) =>
|
||||||
|
for {
|
||||||
|
_ <- OptionT.liftF(
|
||||||
|
logger.debug(
|
||||||
|
s"Looking up user smtp for ${userId.id} and ${conn.id}"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
mailConn <- OptionT(RUserEmail.getByUser(userId, conn))
|
||||||
|
rec = RNotificationChannelMail(
|
||||||
|
id,
|
||||||
|
userId,
|
||||||
|
name,
|
||||||
|
mailConn.id,
|
||||||
|
recipients.toList,
|
||||||
|
time
|
||||||
|
).vary
|
||||||
|
} yield rec
|
||||||
|
case Channel.Gotify(_, name, url, appKey, prio) =>
|
||||||
|
OptionT.pure[ConnectionIO](
|
||||||
|
RNotificationChannelGotify(
|
||||||
|
id,
|
||||||
|
userId,
|
||||||
|
name,
|
||||||
|
url,
|
||||||
|
appKey,
|
||||||
|
prio,
|
||||||
|
time
|
||||||
|
).vary
|
||||||
|
)
|
||||||
|
case Channel.Matrix(_, name, homeServer, roomId, accessToken) =>
|
||||||
|
OptionT.pure[ConnectionIO](
|
||||||
|
RNotificationChannelMatrix(
|
||||||
|
id,
|
||||||
|
userId,
|
||||||
|
name,
|
||||||
|
homeServer,
|
||||||
|
roomId,
|
||||||
|
accessToken,
|
||||||
|
"m.text",
|
||||||
|
time
|
||||||
|
).vary
|
||||||
|
)
|
||||||
|
case Channel.Http(_, name, url) =>
|
||||||
|
OptionT.pure[ConnectionIO](
|
||||||
|
RNotificationChannelHttp(id, userId, name, url, time).vary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} yield r
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ import doobie.implicits._
|
|||||||
final case class RNotificationChannelGotify(
|
final case class RNotificationChannelGotify(
|
||||||
id: Ident,
|
id: Ident,
|
||||||
uid: Ident,
|
uid: Ident,
|
||||||
|
name: Option[String],
|
||||||
url: LenientUri,
|
url: LenientUri,
|
||||||
appKey: Password,
|
appKey: Password,
|
||||||
priority: Option[Int],
|
priority: Option[Int],
|
||||||
@ -34,27 +35,32 @@ object RNotificationChannelGotify {
|
|||||||
|
|
||||||
val id = Column[Ident]("id", this)
|
val id = Column[Ident]("id", this)
|
||||||
val uid = Column[Ident]("uid", this)
|
val uid = Column[Ident]("uid", this)
|
||||||
|
val name = Column[String]("name", this)
|
||||||
val url = Column[LenientUri]("url", this)
|
val url = Column[LenientUri]("url", this)
|
||||||
val appKey = Column[Password]("app_key", this)
|
val appKey = Column[Password]("app_key", this)
|
||||||
val priority = Column[Int]("priority", this)
|
val priority = Column[Int]("priority", this)
|
||||||
val created = Column[Timestamp]("created", this)
|
val created = Column[Timestamp]("created", this)
|
||||||
|
|
||||||
val all: NonEmptyList[Column[_]] =
|
val all: NonEmptyList[Column[_]] =
|
||||||
NonEmptyList.of(id, uid, url, appKey, priority, created)
|
NonEmptyList.of(id, uid, name, url, appKey, priority, created)
|
||||||
}
|
}
|
||||||
|
|
||||||
val T: Table = Table(None)
|
val T: Table = Table(None)
|
||||||
def as(alias: String): Table =
|
def as(alias: String): Table =
|
||||||
Table(Some(alias))
|
Table(Some(alias))
|
||||||
|
|
||||||
def getById(id: Ident): ConnectionIO[Option[RNotificationChannelGotify]] =
|
def getById(
|
||||||
run(select(T.all), from(T), T.id === id).query[RNotificationChannelGotify].option
|
userId: Ident
|
||||||
|
)(id: Ident): ConnectionIO[Option[RNotificationChannelGotify]] =
|
||||||
|
run(select(T.all), from(T), T.id === id && T.uid === userId)
|
||||||
|
.query[RNotificationChannelGotify]
|
||||||
|
.option
|
||||||
|
|
||||||
def insert(r: RNotificationChannelGotify): ConnectionIO[Int] =
|
def insert(r: RNotificationChannelGotify): ConnectionIO[Int] =
|
||||||
DML.insert(
|
DML.insert(
|
||||||
T,
|
T,
|
||||||
T.all,
|
T.all,
|
||||||
sql"${r.id},${r.uid},${r.url},${r.appKey},${r.priority},${r.created}"
|
sql"${r.id},${r.uid},${r.name},${r.url},${r.appKey},${r.priority},${r.created}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def update(r: RNotificationChannelGotify): ConnectionIO[Int] =
|
def update(r: RNotificationChannelGotify): ConnectionIO[Int] =
|
||||||
@ -64,7 +70,8 @@ object RNotificationChannelGotify {
|
|||||||
DML.set(
|
DML.set(
|
||||||
T.url.setTo(r.url),
|
T.url.setTo(r.url),
|
||||||
T.appKey.setTo(r.appKey),
|
T.appKey.setTo(r.appKey),
|
||||||
T.priority.setTo(r.priority)
|
T.priority.setTo(r.priority),
|
||||||
|
T.name.setTo(r.name)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ import doobie.implicits._
|
|||||||
final case class RNotificationChannelHttp(
|
final case class RNotificationChannelHttp(
|
||||||
id: Ident,
|
id: Ident,
|
||||||
uid: Ident,
|
uid: Ident,
|
||||||
|
name: Option[String],
|
||||||
url: LenientUri,
|
url: LenientUri,
|
||||||
created: Timestamp
|
created: Timestamp
|
||||||
) {
|
) {
|
||||||
@ -32,25 +33,32 @@ object RNotificationChannelHttp {
|
|||||||
|
|
||||||
val id = Column[Ident]("id", this)
|
val id = Column[Ident]("id", this)
|
||||||
val uid = Column[Ident]("uid", this)
|
val uid = Column[Ident]("uid", this)
|
||||||
|
val name = Column[String]("name", this)
|
||||||
val url = Column[LenientUri]("url", this)
|
val url = Column[LenientUri]("url", this)
|
||||||
val created = Column[Timestamp]("created", this)
|
val created = Column[Timestamp]("created", this)
|
||||||
|
|
||||||
val all: NonEmptyList[Column[_]] =
|
val all: NonEmptyList[Column[_]] =
|
||||||
NonEmptyList.of(id, uid, url, created)
|
NonEmptyList.of(id, uid, name, url, created)
|
||||||
}
|
}
|
||||||
|
|
||||||
val T: Table = Table(None)
|
val T: Table = Table(None)
|
||||||
def as(alias: String): Table =
|
def as(alias: String): Table =
|
||||||
Table(Some(alias))
|
Table(Some(alias))
|
||||||
|
|
||||||
def getById(id: Ident): ConnectionIO[Option[RNotificationChannelHttp]] =
|
def getById(userId: Ident)(id: Ident): ConnectionIO[Option[RNotificationChannelHttp]] =
|
||||||
run(select(T.all), from(T), T.id === id).query[RNotificationChannelHttp].option
|
run(select(T.all), from(T), T.id === id && T.uid === userId)
|
||||||
|
.query[RNotificationChannelHttp]
|
||||||
|
.option
|
||||||
|
|
||||||
def insert(r: RNotificationChannelHttp): ConnectionIO[Int] =
|
def insert(r: RNotificationChannelHttp): ConnectionIO[Int] =
|
||||||
DML.insert(T, T.all, sql"${r.id},${r.uid},${r.url},${r.created}")
|
DML.insert(T, T.all, sql"${r.id},${r.uid},${r.name},${r.url},${r.created}")
|
||||||
|
|
||||||
def update(r: RNotificationChannelHttp): ConnectionIO[Int] =
|
def update(r: RNotificationChannelHttp): ConnectionIO[Int] =
|
||||||
DML.update(T, T.id === r.id && T.uid === r.uid, DML.set(T.url.setTo(r.url)))
|
DML.update(
|
||||||
|
T,
|
||||||
|
T.id === r.id && T.uid === r.uid,
|
||||||
|
DML.set(T.url.setTo(r.url), T.name.setTo(r.name))
|
||||||
|
)
|
||||||
|
|
||||||
def getByAccount(account: AccountId): ConnectionIO[Vector[RNotificationChannelHttp]] = {
|
def getByAccount(account: AccountId): ConnectionIO[Vector[RNotificationChannelHttp]] = {
|
||||||
val user = RUser.as("u")
|
val user = RUser.as("u")
|
||||||
|
@ -19,6 +19,7 @@ import emil.MailAddress
|
|||||||
final case class RNotificationChannelMail(
|
final case class RNotificationChannelMail(
|
||||||
id: Ident,
|
id: Ident,
|
||||||
uid: Ident,
|
uid: Ident,
|
||||||
|
name: Option[String],
|
||||||
connection: Ident,
|
connection: Ident,
|
||||||
recipients: List[MailAddress],
|
recipients: List[MailAddress],
|
||||||
created: Timestamp
|
created: Timestamp
|
||||||
@ -34,12 +35,13 @@ object RNotificationChannelMail {
|
|||||||
|
|
||||||
val id = Column[Ident]("id", this)
|
val id = Column[Ident]("id", this)
|
||||||
val uid = Column[Ident]("uid", this)
|
val uid = Column[Ident]("uid", this)
|
||||||
|
val name = Column[String]("name", this)
|
||||||
val connection = Column[Ident]("conn_id", this)
|
val connection = Column[Ident]("conn_id", this)
|
||||||
val recipients = Column[List[MailAddress]]("recipients", this)
|
val recipients = Column[List[MailAddress]]("recipients", this)
|
||||||
val created = Column[Timestamp]("created", this)
|
val created = Column[Timestamp]("created", this)
|
||||||
|
|
||||||
val all: NonEmptyList[Column[_]] =
|
val all: NonEmptyList[Column[_]] =
|
||||||
NonEmptyList.of(id, uid, connection, recipients, created)
|
NonEmptyList.of(id, uid, name, connection, recipients, created)
|
||||||
}
|
}
|
||||||
|
|
||||||
val T: Table = Table(None)
|
val T: Table = Table(None)
|
||||||
@ -49,7 +51,7 @@ object RNotificationChannelMail {
|
|||||||
DML.insert(
|
DML.insert(
|
||||||
T,
|
T,
|
||||||
T.all,
|
T.all,
|
||||||
sql"${r.id},${r.uid},${r.connection},${r.recipients},${r.created}"
|
sql"${r.id},${r.uid},${r.name},${r.connection},${r.recipients},${r.created}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def update(r: RNotificationChannelMail): ConnectionIO[Int] =
|
def update(r: RNotificationChannelMail): ConnectionIO[Int] =
|
||||||
@ -58,12 +60,15 @@ object RNotificationChannelMail {
|
|||||||
T.id === r.id && T.uid === r.uid,
|
T.id === r.id && T.uid === r.uid,
|
||||||
DML.set(
|
DML.set(
|
||||||
T.connection.setTo(r.connection),
|
T.connection.setTo(r.connection),
|
||||||
T.recipients.setTo(r.recipients.toList)
|
T.recipients.setTo(r.recipients.toList),
|
||||||
|
T.name.setTo(r.name)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def getById(id: Ident): ConnectionIO[Option[RNotificationChannelMail]] =
|
def getById(userId: Ident)(id: Ident): ConnectionIO[Option[RNotificationChannelMail]] =
|
||||||
run(select(T.all), from(T), T.id === id).query[RNotificationChannelMail].option
|
run(select(T.all), from(T), T.id === id && T.uid === userId)
|
||||||
|
.query[RNotificationChannelMail]
|
||||||
|
.option
|
||||||
|
|
||||||
def getByAccount(account: AccountId): ConnectionIO[Vector[RNotificationChannelMail]] = {
|
def getByAccount(account: AccountId): ConnectionIO[Vector[RNotificationChannelMail]] = {
|
||||||
val user = RUser.as("u")
|
val user = RUser.as("u")
|
||||||
|
@ -18,6 +18,7 @@ import doobie.implicits._
|
|||||||
final case class RNotificationChannelMatrix(
|
final case class RNotificationChannelMatrix(
|
||||||
id: Ident,
|
id: Ident,
|
||||||
uid: Ident,
|
uid: Ident,
|
||||||
|
name: Option[String],
|
||||||
homeServer: LenientUri,
|
homeServer: LenientUri,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
accessToken: Password,
|
accessToken: Password,
|
||||||
@ -34,6 +35,7 @@ object RNotificationChannelMatrix {
|
|||||||
|
|
||||||
val id = Column[Ident]("id", this)
|
val id = Column[Ident]("id", this)
|
||||||
val uid = Column[Ident]("uid", this)
|
val uid = Column[Ident]("uid", this)
|
||||||
|
val name = Column[String]("name", this)
|
||||||
val homeServer = Column[LenientUri]("home_server", this)
|
val homeServer = Column[LenientUri]("home_server", this)
|
||||||
val roomId = Column[String]("room_id", this)
|
val roomId = Column[String]("room_id", this)
|
||||||
val accessToken = Column[Password]("access_token", this)
|
val accessToken = Column[Password]("access_token", this)
|
||||||
@ -41,7 +43,16 @@ object RNotificationChannelMatrix {
|
|||||||
val created = Column[Timestamp]("created", this)
|
val created = Column[Timestamp]("created", this)
|
||||||
|
|
||||||
val all: NonEmptyList[Column[_]] =
|
val all: NonEmptyList[Column[_]] =
|
||||||
NonEmptyList.of(id, uid, homeServer, roomId, accessToken, messageType, created)
|
NonEmptyList.of(
|
||||||
|
id,
|
||||||
|
uid,
|
||||||
|
name,
|
||||||
|
homeServer,
|
||||||
|
roomId,
|
||||||
|
accessToken,
|
||||||
|
messageType,
|
||||||
|
created
|
||||||
|
)
|
||||||
}
|
}
|
||||||
val T: Table = Table(None)
|
val T: Table = Table(None)
|
||||||
def as(alias: String): Table = Table(Some(alias))
|
def as(alias: String): Table = Table(Some(alias))
|
||||||
@ -50,7 +61,7 @@ object RNotificationChannelMatrix {
|
|||||||
DML.insert(
|
DML.insert(
|
||||||
T,
|
T,
|
||||||
T.all,
|
T.all,
|
||||||
sql"${r.id},${r.uid},${r.homeServer},${r.roomId},${r.accessToken},${r.messageType},${r.created}"
|
sql"${r.id},${r.uid},${r.name},${r.homeServer},${r.roomId},${r.accessToken},${r.messageType},${r.created}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def update(r: RNotificationChannelMatrix): ConnectionIO[Int] =
|
def update(r: RNotificationChannelMatrix): ConnectionIO[Int] =
|
||||||
@ -61,12 +72,17 @@ object RNotificationChannelMatrix {
|
|||||||
T.homeServer.setTo(r.homeServer),
|
T.homeServer.setTo(r.homeServer),
|
||||||
T.roomId.setTo(r.roomId),
|
T.roomId.setTo(r.roomId),
|
||||||
T.accessToken.setTo(r.accessToken),
|
T.accessToken.setTo(r.accessToken),
|
||||||
T.messageType.setTo(r.messageType)
|
T.messageType.setTo(r.messageType),
|
||||||
|
T.name.setTo(r.name)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def getById(id: Ident): ConnectionIO[Option[RNotificationChannelMatrix]] =
|
def getById(userId: Ident)(
|
||||||
run(select(T.all), from(T), T.id === id).query[RNotificationChannelMatrix].option
|
id: Ident
|
||||||
|
): ConnectionIO[Option[RNotificationChannelMatrix]] =
|
||||||
|
run(select(T.all), from(T), T.id === id && T.uid === userId)
|
||||||
|
.query[RNotificationChannelMatrix]
|
||||||
|
.option
|
||||||
|
|
||||||
def getByAccount(
|
def getByAccount(
|
||||||
account: AccountId
|
account: AccountId
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
package docspell.store.records
|
package docspell.store.records
|
||||||
|
|
||||||
import cats.data.NonEmptyList
|
import cats.data.NonEmptyList
|
||||||
import cats.implicits._
|
|
||||||
|
|
||||||
import docspell.common._
|
import docspell.common._
|
||||||
import docspell.jsonminiq.JsonMiniQuery
|
import docspell.jsonminiq.JsonMiniQuery
|
||||||
@ -22,115 +21,18 @@ final case class RNotificationHook(
|
|||||||
id: Ident,
|
id: Ident,
|
||||||
uid: Ident,
|
uid: Ident,
|
||||||
enabled: Boolean,
|
enabled: Boolean,
|
||||||
channelMail: Option[Ident],
|
|
||||||
channelGotify: Option[Ident],
|
|
||||||
channelMatrix: Option[Ident],
|
|
||||||
channelHttp: Option[Ident],
|
|
||||||
allEvents: Boolean,
|
allEvents: Boolean,
|
||||||
eventFilter: Option[JsonMiniQuery],
|
eventFilter: Option[JsonMiniQuery],
|
||||||
created: Timestamp
|
created: Timestamp
|
||||||
) {
|
) {}
|
||||||
def channelId: Ident =
|
|
||||||
channelMail
|
|
||||||
.orElse(channelGotify)
|
|
||||||
.orElse(channelMatrix)
|
|
||||||
.orElse(channelHttp)
|
|
||||||
.getOrElse(
|
|
||||||
sys.error(s"Illegal internal state: notification hook has no channel: ${id.id}")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
object RNotificationHook {
|
object RNotificationHook {
|
||||||
def mail(
|
|
||||||
id: Ident,
|
|
||||||
uid: Ident,
|
|
||||||
enabled: Boolean,
|
|
||||||
channelMail: Ident,
|
|
||||||
created: Timestamp
|
|
||||||
): RNotificationHook =
|
|
||||||
RNotificationHook(
|
|
||||||
id,
|
|
||||||
uid,
|
|
||||||
enabled,
|
|
||||||
channelMail.some,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
false,
|
|
||||||
None,
|
|
||||||
created
|
|
||||||
)
|
|
||||||
|
|
||||||
def gotify(
|
|
||||||
id: Ident,
|
|
||||||
uid: Ident,
|
|
||||||
enabled: Boolean,
|
|
||||||
channelGotify: Ident,
|
|
||||||
created: Timestamp
|
|
||||||
): RNotificationHook =
|
|
||||||
RNotificationHook(
|
|
||||||
id,
|
|
||||||
uid,
|
|
||||||
enabled,
|
|
||||||
None,
|
|
||||||
channelGotify.some,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
false,
|
|
||||||
None,
|
|
||||||
created
|
|
||||||
)
|
|
||||||
|
|
||||||
def matrix(
|
|
||||||
id: Ident,
|
|
||||||
uid: Ident,
|
|
||||||
enabled: Boolean,
|
|
||||||
channelMatrix: Ident,
|
|
||||||
created: Timestamp
|
|
||||||
): RNotificationHook =
|
|
||||||
RNotificationHook(
|
|
||||||
id,
|
|
||||||
uid,
|
|
||||||
enabled,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
channelMatrix.some,
|
|
||||||
None,
|
|
||||||
false,
|
|
||||||
None,
|
|
||||||
created
|
|
||||||
)
|
|
||||||
|
|
||||||
def http(
|
|
||||||
id: Ident,
|
|
||||||
uid: Ident,
|
|
||||||
enabled: Boolean,
|
|
||||||
channelHttp: Ident,
|
|
||||||
created: Timestamp
|
|
||||||
): RNotificationHook =
|
|
||||||
RNotificationHook(
|
|
||||||
id,
|
|
||||||
uid,
|
|
||||||
enabled,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
channelHttp.some,
|
|
||||||
false,
|
|
||||||
None,
|
|
||||||
created
|
|
||||||
)
|
|
||||||
|
|
||||||
final case class Table(alias: Option[String]) extends TableDef {
|
final case class Table(alias: Option[String]) extends TableDef {
|
||||||
val tableName = "notification_hook"
|
val tableName = "notification_hook"
|
||||||
|
|
||||||
val id = Column[Ident]("id", this)
|
val id = Column[Ident]("id", this)
|
||||||
val uid = Column[Ident]("uid", this)
|
val uid = Column[Ident]("uid", this)
|
||||||
val enabled = Column[Boolean]("enabled", this)
|
val enabled = Column[Boolean]("enabled", this)
|
||||||
val channelMail = Column[Ident]("channel_mail", this)
|
|
||||||
val channelGotify = Column[Ident]("channel_gotify", this)
|
|
||||||
val channelMatrix = Column[Ident]("channel_matrix", this)
|
|
||||||
val channelHttp = Column[Ident]("channel_http", this)
|
|
||||||
val allEvents = Column[Boolean]("all_events", this)
|
val allEvents = Column[Boolean]("all_events", this)
|
||||||
val eventFilter = Column[JsonMiniQuery]("event_filter", this)
|
val eventFilter = Column[JsonMiniQuery]("event_filter", this)
|
||||||
val created = Column[Timestamp]("created", this)
|
val created = Column[Timestamp]("created", this)
|
||||||
@ -140,10 +42,6 @@ object RNotificationHook {
|
|||||||
id,
|
id,
|
||||||
uid,
|
uid,
|
||||||
enabled,
|
enabled,
|
||||||
channelMail,
|
|
||||||
channelGotify,
|
|
||||||
channelMatrix,
|
|
||||||
channelHttp,
|
|
||||||
allEvents,
|
allEvents,
|
||||||
eventFilter,
|
eventFilter,
|
||||||
created
|
created
|
||||||
@ -157,7 +55,7 @@ object RNotificationHook {
|
|||||||
DML.insert(
|
DML.insert(
|
||||||
T,
|
T,
|
||||||
T.all,
|
T.all,
|
||||||
sql"${r.id},${r.uid},${r.enabled},${r.channelMail},${r.channelGotify},${r.channelMatrix},${r.channelHttp},${r.allEvents},${r.eventFilter},${r.created}"
|
sql"${r.id},${r.uid},${r.enabled},${r.allEvents},${r.eventFilter},${r.created}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def deleteByAccount(id: Ident, account: AccountId): ConnectionIO[Int] = {
|
def deleteByAccount(id: Ident, account: AccountId): ConnectionIO[Int] = {
|
||||||
@ -174,10 +72,6 @@ object RNotificationHook {
|
|||||||
T.id === r.id && T.uid === r.uid,
|
T.id === r.id && T.uid === r.uid,
|
||||||
DML.set(
|
DML.set(
|
||||||
T.enabled.setTo(r.enabled),
|
T.enabled.setTo(r.enabled),
|
||||||
T.channelMail.setTo(r.channelMail),
|
|
||||||
T.channelGotify.setTo(r.channelGotify),
|
|
||||||
T.channelMatrix.setTo(r.channelMatrix),
|
|
||||||
T.channelHttp.setTo(r.channelHttp),
|
|
||||||
T.allEvents.setTo(r.allEvents),
|
T.allEvents.setTo(r.allEvents),
|
||||||
T.eventFilter.setTo(r.eventFilter)
|
T.eventFilter.setTo(r.eventFilter)
|
||||||
)
|
)
|
||||||
|
@ -0,0 +1,236 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 Eike K. & Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package docspell.store.records
|
||||||
|
|
||||||
|
import cats.data.{NonEmptyList => Nel}
|
||||||
|
import cats.effect.Sync
|
||||||
|
import cats.implicits._
|
||||||
|
|
||||||
|
import docspell.common._
|
||||||
|
import docspell.notification.api.{ChannelRef, ChannelType}
|
||||||
|
import docspell.store.qb.DSL._
|
||||||
|
import docspell.store.qb._
|
||||||
|
|
||||||
|
import doobie._
|
||||||
|
import doobie.implicits._
|
||||||
|
|
||||||
|
final case class RNotificationHookChannel(
|
||||||
|
id: Ident,
|
||||||
|
hookId: Ident,
|
||||||
|
channelMail: Option[Ident],
|
||||||
|
channelGotify: Option[Ident],
|
||||||
|
channelMatrix: Option[Ident],
|
||||||
|
channelHttp: Option[Ident]
|
||||||
|
) {
|
||||||
|
|
||||||
|
def channelId: Ident =
|
||||||
|
channelMail
|
||||||
|
.orElse(channelGotify)
|
||||||
|
.orElse(channelMatrix)
|
||||||
|
.orElse(channelHttp)
|
||||||
|
.getOrElse(
|
||||||
|
sys.error(s"Illegal internal state: notification hook has no channel: $this")
|
||||||
|
)
|
||||||
|
|
||||||
|
def channelType: ChannelType =
|
||||||
|
channelMail
|
||||||
|
.map(_ => ChannelType.Mail)
|
||||||
|
.orElse(channelGotify.map(_ => ChannelType.Gotify))
|
||||||
|
.orElse(channelMatrix.map(_ => ChannelType.Matrix))
|
||||||
|
.orElse(channelHttp.map(_ => ChannelType.Http))
|
||||||
|
.getOrElse(
|
||||||
|
sys.error(s"Illegal internal state: notification hook has no channel: $this")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
object RNotificationHookChannel {
|
||||||
|
def fromRef(id: Ident, hookId: Ident, ref: ChannelRef): RNotificationHookChannel =
|
||||||
|
ref.channelType match {
|
||||||
|
case ChannelType.Mail => mail(id, hookId, ref.id)
|
||||||
|
case ChannelType.Gotify => gotify(id, hookId, ref.id)
|
||||||
|
case ChannelType.Matrix => matrix(id, hookId, ref.id)
|
||||||
|
case ChannelType.Http => http(id, hookId, ref.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
def mail(
|
||||||
|
id: Ident,
|
||||||
|
hookId: Ident,
|
||||||
|
channelMail: Ident
|
||||||
|
): RNotificationHookChannel =
|
||||||
|
RNotificationHookChannel(
|
||||||
|
id,
|
||||||
|
hookId,
|
||||||
|
channelMail.some,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
|
def gotify(
|
||||||
|
id: Ident,
|
||||||
|
hookId: Ident,
|
||||||
|
channelGotify: Ident
|
||||||
|
): RNotificationHookChannel =
|
||||||
|
RNotificationHookChannel(
|
||||||
|
id,
|
||||||
|
hookId,
|
||||||
|
None,
|
||||||
|
channelGotify.some,
|
||||||
|
None,
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
|
def matrix(
|
||||||
|
id: Ident,
|
||||||
|
hookId: Ident,
|
||||||
|
channelMatrix: Ident
|
||||||
|
): RNotificationHookChannel =
|
||||||
|
RNotificationHookChannel(
|
||||||
|
id,
|
||||||
|
hookId,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
channelMatrix.some,
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
|
def http(
|
||||||
|
id: Ident,
|
||||||
|
hookId: Ident,
|
||||||
|
channelHttp: Ident
|
||||||
|
): RNotificationHookChannel =
|
||||||
|
RNotificationHookChannel(
|
||||||
|
id,
|
||||||
|
hookId,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
channelHttp.some
|
||||||
|
)
|
||||||
|
|
||||||
|
final case class Table(alias: Option[String]) extends TableDef {
|
||||||
|
val tableName = "notification_hook_channel"
|
||||||
|
|
||||||
|
val id = Column[Ident]("id", this)
|
||||||
|
val hookId = Column[Ident]("hook_id", this)
|
||||||
|
val channelMail = Column[Ident]("channel_mail", this)
|
||||||
|
val channelGotify = Column[Ident]("channel_gotify", this)
|
||||||
|
val channelMatrix = Column[Ident]("channel_matrix", this)
|
||||||
|
val channelHttp = Column[Ident]("channel_http", this)
|
||||||
|
|
||||||
|
val all: Nel[Column[_]] =
|
||||||
|
Nel.of(id, hookId, channelMail, channelGotify, channelMatrix, channelHttp)
|
||||||
|
}
|
||||||
|
|
||||||
|
def as(alias: String): Table =
|
||||||
|
Table(Some(alias))
|
||||||
|
|
||||||
|
val T: Table = Table(None)
|
||||||
|
|
||||||
|
def insert(r: RNotificationHookChannel): ConnectionIO[Int] =
|
||||||
|
DML.insert(
|
||||||
|
T,
|
||||||
|
T.all,
|
||||||
|
sql"${r.id},${r.hookId},${r.channelMail},${r.channelGotify},${r.channelMatrix},${r.channelHttp}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def update(r: RNotificationHookChannel): ConnectionIO[Int] =
|
||||||
|
DML.update(
|
||||||
|
T,
|
||||||
|
T.id === r.id && T.hookId === r.hookId,
|
||||||
|
DML.set(
|
||||||
|
T.channelMail.setTo(r.channelMail),
|
||||||
|
T.channelGotify.setTo(r.channelGotify),
|
||||||
|
T.channelMatrix.setTo(r.channelMatrix),
|
||||||
|
T.channelHttp.setTo(r.channelHttp)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def deleteByHook(hookId: Ident): ConnectionIO[Int] =
|
||||||
|
DML.delete(T, T.hookId === hookId)
|
||||||
|
|
||||||
|
def insertAll(rs: List[RNotificationHookChannel]): ConnectionIO[Int] =
|
||||||
|
rs.traverse(insert).map(_.sum)
|
||||||
|
|
||||||
|
def updateAll(hookId: Ident, channels: List[ChannelRef]): ConnectionIO[Int] =
|
||||||
|
channels
|
||||||
|
.traverse(ref => Ident.randomId[ConnectionIO].map(id => fromRef(id, hookId, ref)))
|
||||||
|
.flatMap(all => deleteByHook(hookId) *> insertAll(all))
|
||||||
|
|
||||||
|
def allOf(hookId: Ident): ConnectionIO[Vector[RNotificationHookChannel]] =
|
||||||
|
Select(select(T.all), from(T), T.hookId === hookId).build
|
||||||
|
.query[RNotificationHookChannel]
|
||||||
|
.to[Vector]
|
||||||
|
|
||||||
|
def allOfNel(hookId: Ident): ConnectionIO[Nel[RNotificationHookChannel]] =
|
||||||
|
allOf(hookId)
|
||||||
|
.map(Nel.fromFoldable[Vector, RNotificationHookChannel])
|
||||||
|
.flatMap(
|
||||||
|
_.map(_.pure[ConnectionIO]).getOrElse(
|
||||||
|
Sync[ConnectionIO]
|
||||||
|
.raiseError(new Exception(s"Hook '${hookId.id}' has no associated channels!"))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def resolveRefs(rs: Nel[RNotificationHookChannel]): ConnectionIO[List[ChannelRef]] = {
|
||||||
|
val cmail = RNotificationChannelMail.as("cmail")
|
||||||
|
val cgotify = RNotificationChannelGotify.as("cgotify")
|
||||||
|
val cmatrix = RNotificationChannelMatrix.as("cmatrix")
|
||||||
|
val chttp = RNotificationChannelHttp.as("chttp")
|
||||||
|
|
||||||
|
def selectRef(
|
||||||
|
idList: List[Ident],
|
||||||
|
idCol: Column[Ident],
|
||||||
|
nameCol: Column[String],
|
||||||
|
ctype: ChannelType,
|
||||||
|
table: TableDef
|
||||||
|
) =
|
||||||
|
Nel
|
||||||
|
.fromList(idList)
|
||||||
|
.map(ids =>
|
||||||
|
Select(
|
||||||
|
select(idCol.s, const(ctype.name), nameCol.s),
|
||||||
|
from(table),
|
||||||
|
idCol.in(ids)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val mailRefs = selectRef(
|
||||||
|
rs.toList.flatMap(_.channelMail),
|
||||||
|
cmail.id,
|
||||||
|
cmail.name,
|
||||||
|
ChannelType.Mail,
|
||||||
|
cmail
|
||||||
|
)
|
||||||
|
val gotifyRefs = selectRef(
|
||||||
|
rs.toList.flatMap(_.channelGotify),
|
||||||
|
cgotify.id,
|
||||||
|
cgotify.name,
|
||||||
|
ChannelType.Gotify,
|
||||||
|
cgotify
|
||||||
|
)
|
||||||
|
val matrixRefs = selectRef(
|
||||||
|
rs.toList.flatMap(_.channelMatrix),
|
||||||
|
cmatrix.id,
|
||||||
|
cmatrix.name,
|
||||||
|
ChannelType.Matrix,
|
||||||
|
cmatrix
|
||||||
|
)
|
||||||
|
val httpRefs = selectRef(
|
||||||
|
rs.toList.flatMap(_.channelHttp),
|
||||||
|
chttp.id,
|
||||||
|
chttp.name,
|
||||||
|
ChannelType.Http,
|
||||||
|
chttp
|
||||||
|
)
|
||||||
|
|
||||||
|
val queries = List(mailRefs, gotifyRefs, matrixRefs, httpRefs).flatten
|
||||||
|
Nel.fromList(queries) match {
|
||||||
|
case Some(nel) => union(nel.head, nel.tail: _*).build.query[ChannelRef].to[List]
|
||||||
|
case None => List.empty[ChannelRef].pure[ConnectionIO]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -23,6 +23,7 @@ module Api exposing
|
|||||||
, checkCalEvent
|
, checkCalEvent
|
||||||
, confirmMultiple
|
, confirmMultiple
|
||||||
, confirmOtp
|
, confirmOtp
|
||||||
|
, createChannel
|
||||||
, createHook
|
, createHook
|
||||||
, createImapSettings
|
, createImapSettings
|
||||||
, createMailSettings
|
, createMailSettings
|
||||||
@ -34,6 +35,7 @@ module Api exposing
|
|||||||
, deleteAttachment
|
, deleteAttachment
|
||||||
, deleteAttachments
|
, deleteAttachments
|
||||||
, deleteBookmark
|
, deleteBookmark
|
||||||
|
, deleteChannel
|
||||||
, deleteCustomField
|
, deleteCustomField
|
||||||
, deleteCustomValue
|
, deleteCustomValue
|
||||||
, deleteCustomValueMultiple
|
, deleteCustomValueMultiple
|
||||||
@ -56,6 +58,8 @@ module Api exposing
|
|||||||
, fileURL
|
, fileURL
|
||||||
, getAttachmentMeta
|
, getAttachmentMeta
|
||||||
, getBookmarks
|
, getBookmarks
|
||||||
|
, getChannels
|
||||||
|
, getChannelsIgnoreError
|
||||||
, getClientSettings
|
, getClientSettings
|
||||||
, getCollective
|
, getCollective
|
||||||
, getCollectiveSettings
|
, getCollectiveSettings
|
||||||
@ -172,6 +176,7 @@ module Api exposing
|
|||||||
, twoFactor
|
, twoFactor
|
||||||
, unconfirmMultiple
|
, unconfirmMultiple
|
||||||
, updateBookmark
|
, updateBookmark
|
||||||
|
, updateChannel
|
||||||
, updateHook
|
, updateHook
|
||||||
, updateNotifyDueItems
|
, updateNotifyDueItems
|
||||||
, updatePeriodicQuery
|
, updatePeriodicQuery
|
||||||
@ -229,6 +234,7 @@ import Api.Model.MoveAttachment exposing (MoveAttachment)
|
|||||||
import Api.Model.NewCustomField exposing (NewCustomField)
|
import Api.Model.NewCustomField exposing (NewCustomField)
|
||||||
import Api.Model.NewFolder exposing (NewFolder)
|
import Api.Model.NewFolder exposing (NewFolder)
|
||||||
import Api.Model.NotificationChannelTestResult exposing (NotificationChannelTestResult)
|
import Api.Model.NotificationChannelTestResult exposing (NotificationChannelTestResult)
|
||||||
|
import Api.Model.NotificationHook exposing (NotificationHook)
|
||||||
import Api.Model.NotificationSampleEventReq exposing (NotificationSampleEventReq)
|
import Api.Model.NotificationSampleEventReq exposing (NotificationSampleEventReq)
|
||||||
import Api.Model.OptionalDate exposing (OptionalDate)
|
import Api.Model.OptionalDate exposing (OptionalDate)
|
||||||
import Api.Model.OptionalId exposing (OptionalId)
|
import Api.Model.OptionalId exposing (OptionalId)
|
||||||
@ -239,6 +245,8 @@ import Api.Model.OtpConfirm exposing (OtpConfirm)
|
|||||||
import Api.Model.OtpResult exposing (OtpResult)
|
import Api.Model.OtpResult exposing (OtpResult)
|
||||||
import Api.Model.OtpState exposing (OtpState)
|
import Api.Model.OtpState exposing (OtpState)
|
||||||
import Api.Model.PasswordChange exposing (PasswordChange)
|
import Api.Model.PasswordChange exposing (PasswordChange)
|
||||||
|
import Api.Model.PeriodicDueItemsSettings exposing (PeriodicDueItemsSettings)
|
||||||
|
import Api.Model.PeriodicQuerySettings exposing (PeriodicQuerySettings)
|
||||||
import Api.Model.Person exposing (Person)
|
import Api.Model.Person exposing (Person)
|
||||||
import Api.Model.PersonList exposing (PersonList)
|
import Api.Model.PersonList exposing (PersonList)
|
||||||
import Api.Model.ReferenceList exposing (ReferenceList)
|
import Api.Model.ReferenceList exposing (ReferenceList)
|
||||||
@ -274,10 +282,8 @@ import Data.EquipmentOrder exposing (EquipmentOrder)
|
|||||||
import Data.EventType exposing (EventType)
|
import Data.EventType exposing (EventType)
|
||||||
import Data.Flags exposing (Flags)
|
import Data.Flags exposing (Flags)
|
||||||
import Data.FolderOrder exposing (FolderOrder)
|
import Data.FolderOrder exposing (FolderOrder)
|
||||||
import Data.NotificationHook exposing (NotificationHook)
|
import Data.NotificationChannel exposing (NotificationChannel)
|
||||||
import Data.OrganizationOrder exposing (OrganizationOrder)
|
import Data.OrganizationOrder exposing (OrganizationOrder)
|
||||||
import Data.PeriodicDueItemsSettings exposing (PeriodicDueItemsSettings)
|
|
||||||
import Data.PeriodicQuerySettings exposing (PeriodicQuerySettings)
|
|
||||||
import Data.PersonOrder exposing (PersonOrder)
|
import Data.PersonOrder exposing (PersonOrder)
|
||||||
import Data.Priority exposing (Priority)
|
import Data.Priority exposing (Priority)
|
||||||
import Data.TagOrder exposing (TagOrder)
|
import Data.TagOrder exposing (TagOrder)
|
||||||
@ -604,7 +610,7 @@ startOnceNotifyDueItems flags settings receive =
|
|||||||
Http2.authPost
|
Http2.authPost
|
||||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/notifydueitems/startonce"
|
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/notifydueitems/startonce"
|
||||||
, account = getAccount flags
|
, account = getAccount flags
|
||||||
, body = Http.jsonBody (Data.PeriodicDueItemsSettings.encode settings)
|
, body = Http.jsonBody (Api.Model.PeriodicDueItemsSettings.encode settings)
|
||||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -618,7 +624,7 @@ updateNotifyDueItems flags settings receive =
|
|||||||
Http2.authPut
|
Http2.authPut
|
||||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/notifydueitems"
|
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/notifydueitems"
|
||||||
, account = getAccount flags
|
, account = getAccount flags
|
||||||
, body = Http.jsonBody (Data.PeriodicDueItemsSettings.encode settings)
|
, body = Http.jsonBody (Api.Model.PeriodicDueItemsSettings.encode settings)
|
||||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -632,7 +638,7 @@ createNotifyDueItems flags settings receive =
|
|||||||
Http2.authPost
|
Http2.authPost
|
||||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/notifydueitems"
|
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/notifydueitems"
|
||||||
, account = getAccount flags
|
, account = getAccount flags
|
||||||
, body = Http.jsonBody (Data.PeriodicDueItemsSettings.encode settings)
|
, body = Http.jsonBody (Api.Model.PeriodicDueItemsSettings.encode settings)
|
||||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -645,7 +651,7 @@ getNotifyDueItems flags receive =
|
|||||||
Http2.authGet
|
Http2.authGet
|
||||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/notifydueitems"
|
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/notifydueitems"
|
||||||
, account = getAccount flags
|
, account = getAccount flags
|
||||||
, expect = Http.expectJson receive (JsonDecode.list Data.PeriodicDueItemsSettings.decoder)
|
, expect = Http.expectJson receive (JsonDecode.list Api.Model.PeriodicDueItemsSettings.decoder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -658,7 +664,7 @@ submitNotifyDueItems flags settings receive =
|
|||||||
Http2.authPost
|
Http2.authPost
|
||||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/notifydueitems"
|
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/notifydueitems"
|
||||||
, account = getAccount flags
|
, account = getAccount flags
|
||||||
, body = Http.jsonBody (Data.PeriodicDueItemsSettings.encode settings)
|
, body = Http.jsonBody (Api.Model.PeriodicDueItemsSettings.encode settings)
|
||||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -689,7 +695,7 @@ startOncePeriodicQuery flags settings receive =
|
|||||||
Http2.authPost
|
Http2.authPost
|
||||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/periodicquery/startonce"
|
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/periodicquery/startonce"
|
||||||
, account = getAccount flags
|
, account = getAccount flags
|
||||||
, body = Http.jsonBody (Data.PeriodicQuerySettings.encode settings)
|
, body = Http.jsonBody (Api.Model.PeriodicQuerySettings.encode settings)
|
||||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -703,7 +709,7 @@ updatePeriodicQuery flags settings receive =
|
|||||||
Http2.authPut
|
Http2.authPut
|
||||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/periodicquery"
|
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/periodicquery"
|
||||||
, account = getAccount flags
|
, account = getAccount flags
|
||||||
, body = Http.jsonBody (Data.PeriodicQuerySettings.encode settings)
|
, body = Http.jsonBody (Api.Model.PeriodicQuerySettings.encode settings)
|
||||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -717,7 +723,7 @@ createPeriodicQuery flags settings receive =
|
|||||||
Http2.authPost
|
Http2.authPost
|
||||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/periodicquery"
|
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/periodicquery"
|
||||||
, account = getAccount flags
|
, account = getAccount flags
|
||||||
, body = Http.jsonBody (Data.PeriodicQuerySettings.encode settings)
|
, body = Http.jsonBody (Api.Model.PeriodicQuerySettings.encode settings)
|
||||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -730,7 +736,7 @@ getPeriodicQuery flags receive =
|
|||||||
Http2.authGet
|
Http2.authGet
|
||||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/periodicquery"
|
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/periodicquery"
|
||||||
, account = getAccount flags
|
, account = getAccount flags
|
||||||
, expect = Http.expectJson receive (JsonDecode.list Data.PeriodicQuerySettings.decoder)
|
, expect = Http.expectJson receive (JsonDecode.list Api.Model.PeriodicQuerySettings.decoder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -743,7 +749,7 @@ submitPeriodicQuery flags settings receive =
|
|||||||
Http2.authPost
|
Http2.authPost
|
||||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/periodicquery"
|
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/periodicquery"
|
||||||
, account = getAccount flags
|
, account = getAccount flags
|
||||||
, body = Http.jsonBody (Data.PeriodicQuerySettings.encode settings)
|
, body = Http.jsonBody (Api.Model.PeriodicQuerySettings.encode settings)
|
||||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2576,6 +2582,63 @@ shareFileURL attachId =
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--- NotificationChannel
|
||||||
|
|
||||||
|
|
||||||
|
getChannelsTask : Flags -> Task.Task Http.Error (List NotificationChannel)
|
||||||
|
getChannelsTask flags =
|
||||||
|
Http2.authTask
|
||||||
|
{ method = "GET"
|
||||||
|
, url = flags.config.baseUrl ++ "/api/v1/sec/notification/channel"
|
||||||
|
, account = getAccount flags
|
||||||
|
, body = Http.emptyBody
|
||||||
|
, resolver = Http2.jsonResolver (JsonDecode.list Data.NotificationChannel.decoder)
|
||||||
|
, headers = []
|
||||||
|
, timeout = Nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
getChannelsIgnoreError : Flags -> (List NotificationChannel -> msg) -> Cmd msg
|
||||||
|
getChannelsIgnoreError flags tagger =
|
||||||
|
getChannelsTask flags
|
||||||
|
|> Task.attempt (Result.map tagger >> Result.withDefault (tagger []))
|
||||||
|
|
||||||
|
|
||||||
|
getChannels : Flags -> (Result Http.Error (List NotificationChannel) -> msg) -> Cmd msg
|
||||||
|
getChannels flags receive =
|
||||||
|
getChannelsTask flags |> Task.attempt receive
|
||||||
|
|
||||||
|
|
||||||
|
deleteChannel : Flags -> String -> (Result Http.Error BasicResult -> msg) -> Cmd msg
|
||||||
|
deleteChannel flags id receive =
|
||||||
|
Http2.authDelete
|
||||||
|
{ url = flags.config.baseUrl ++ "/api/v1/sec/notification/channel/" ++ id
|
||||||
|
, account = getAccount flags
|
||||||
|
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
createChannel : Flags -> NotificationChannel -> (Result Http.Error BasicResult -> msg) -> Cmd msg
|
||||||
|
createChannel flags hook receive =
|
||||||
|
Http2.authPost
|
||||||
|
{ url = flags.config.baseUrl ++ "/api/v1/sec/notification/channel"
|
||||||
|
, account = getAccount flags
|
||||||
|
, body = Http.jsonBody (Data.NotificationChannel.encode hook)
|
||||||
|
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
updateChannel : Flags -> NotificationChannel -> (Result Http.Error BasicResult -> msg) -> Cmd msg
|
||||||
|
updateChannel flags hook receive =
|
||||||
|
Http2.authPut
|
||||||
|
{ url = flags.config.baseUrl ++ "/api/v1/sec/notification/channel"
|
||||||
|
, account = getAccount flags
|
||||||
|
, body = Http.jsonBody (Data.NotificationChannel.encode hook)
|
||||||
|
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- NotificationHook
|
--- NotificationHook
|
||||||
|
|
||||||
|
|
||||||
@ -2584,7 +2647,7 @@ getHooks flags receive =
|
|||||||
Http2.authGet
|
Http2.authGet
|
||||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/notification/hook"
|
{ url = flags.config.baseUrl ++ "/api/v1/sec/notification/hook"
|
||||||
, account = getAccount flags
|
, account = getAccount flags
|
||||||
, expect = Http.expectJson receive (JsonDecode.list Data.NotificationHook.decoder)
|
, expect = Http.expectJson receive (JsonDecode.list Api.Model.NotificationHook.decoder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -2602,7 +2665,7 @@ createHook flags hook receive =
|
|||||||
Http2.authPost
|
Http2.authPost
|
||||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/notification/hook"
|
{ url = flags.config.baseUrl ++ "/api/v1/sec/notification/hook"
|
||||||
, account = getAccount flags
|
, account = getAccount flags
|
||||||
, body = Http.jsonBody (Data.NotificationHook.encode hook)
|
, body = Http.jsonBody (Api.Model.NotificationHook.encode hook)
|
||||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2612,7 +2675,7 @@ updateHook flags hook receive =
|
|||||||
Http2.authPut
|
Http2.authPut
|
||||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/notification/hook"
|
{ url = flags.config.baseUrl ++ "/api/v1/sec/notification/hook"
|
||||||
, account = getAccount flags
|
, account = getAccount flags
|
||||||
, body = Http.jsonBody (Data.NotificationHook.encode hook)
|
, body = Http.jsonBody (Api.Model.NotificationHook.encode hook)
|
||||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2642,7 +2705,7 @@ testHook flags hook receive =
|
|||||||
Http2.authPost
|
Http2.authPost
|
||||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/notification/hook/sendTestEvent"
|
{ url = flags.config.baseUrl ++ "/api/v1/sec/notification/hook/sendTestEvent"
|
||||||
, account = getAccount flags
|
, account = getAccount flags
|
||||||
, body = Http.jsonBody (Data.NotificationHook.encode hook)
|
, body = Http.jsonBody (Api.Model.NotificationHook.encode hook)
|
||||||
, expect = Http.expectJson receive Api.Model.NotificationChannelTestResult.decoder
|
, expect = Http.expectJson receive Api.Model.NotificationChannelTestResult.decoder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,17 +48,11 @@ type alias HttpModel =
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type alias RefModel =
|
|
||||||
{ channelType : ChannelType
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
type Model
|
type Model
|
||||||
= Matrix MatrixModel
|
= Matrix MatrixModel
|
||||||
| Gotify GotifyModel
|
| Gotify GotifyModel
|
||||||
| Mail MailModel
|
| Mail MailModel
|
||||||
| Http HttpModel
|
| Http HttpModel
|
||||||
| Ref RefModel
|
|
||||||
|
|
||||||
|
|
||||||
type Msg
|
type Msg
|
||||||
@ -147,11 +141,6 @@ initWith flags channel =
|
|||||||
, Cmd.none
|
, Cmd.none
|
||||||
)
|
)
|
||||||
|
|
||||||
Data.NotificationChannel.Ref m ->
|
|
||||||
( Ref { channelType = m.channelType }
|
|
||||||
, Cmd.none
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
channelType : Model -> ChannelType
|
channelType : Model -> ChannelType
|
||||||
channelType model =
|
channelType model =
|
||||||
@ -168,9 +157,6 @@ channelType model =
|
|||||||
Http _ ->
|
Http _ ->
|
||||||
Data.ChannelType.Http
|
Data.ChannelType.Http
|
||||||
|
|
||||||
Ref ref ->
|
|
||||||
ref.channelType
|
|
||||||
|
|
||||||
|
|
||||||
getChannel : Model -> Maybe NotificationChannel
|
getChannel : Model -> Maybe NotificationChannel
|
||||||
getChannel model =
|
getChannel model =
|
||||||
@ -187,9 +173,6 @@ getChannel model =
|
|||||||
Http mm ->
|
Http mm ->
|
||||||
Maybe.map Data.NotificationChannel.Http mm.value
|
Maybe.map Data.NotificationChannel.Http mm.value
|
||||||
|
|
||||||
Ref _ ->
|
|
||||||
Nothing
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Update
|
--- Update
|
||||||
@ -269,12 +252,3 @@ view texts settings model =
|
|||||||
Http m ->
|
Http m ->
|
||||||
Html.map HttpMsg
|
Html.map HttpMsg
|
||||||
(Comp.NotificationHttpForm.view texts.httpForm m.form)
|
(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" ] []
|
|
||||||
|
154
modules/webapp/src/main/elm/Comp/ChannelRefInput.elm
Normal file
154
modules/webapp/src/main/elm/Comp/ChannelRefInput.elm
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
{-
|
||||||
|
Copyright 2020 Eike K. & Contributors
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
-}
|
||||||
|
|
||||||
|
|
||||||
|
module Comp.ChannelRefInput exposing (Model, Msg, getSelected, init, initSelected, initWith, setOptions, setSelected, update, view)
|
||||||
|
|
||||||
|
import Api
|
||||||
|
import Api.Model.NotificationChannelRef exposing (NotificationChannelRef)
|
||||||
|
import Comp.Dropdown exposing (Option)
|
||||||
|
import Data.ChannelType
|
||||||
|
import Data.DropdownStyle
|
||||||
|
import Data.Flags exposing (Flags)
|
||||||
|
import Data.NotificationChannel
|
||||||
|
import Data.UiSettings exposing (UiSettings)
|
||||||
|
import Html exposing (Html)
|
||||||
|
import Messages.Comp.ChannelRefInput exposing (Texts)
|
||||||
|
import Util.String
|
||||||
|
|
||||||
|
|
||||||
|
type alias Model =
|
||||||
|
{ ddm : Comp.Dropdown.Model NotificationChannelRef
|
||||||
|
, all : List NotificationChannelRef
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type Msg
|
||||||
|
= DropdownMsg (Comp.Dropdown.Msg NotificationChannelRef)
|
||||||
|
| LoadChannelsResp (List NotificationChannelRef)
|
||||||
|
|
||||||
|
|
||||||
|
emptyModel : Model
|
||||||
|
emptyModel =
|
||||||
|
{ ddm = makeDropdownModel
|
||||||
|
, all = []
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
init : Flags -> ( Model, Cmd Msg )
|
||||||
|
init flags =
|
||||||
|
( emptyModel, getOptions flags )
|
||||||
|
|
||||||
|
|
||||||
|
getOptions : Flags -> Cmd Msg
|
||||||
|
getOptions flags =
|
||||||
|
Api.getChannelsIgnoreError flags (List.map Data.NotificationChannel.getRef >> LoadChannelsResp)
|
||||||
|
|
||||||
|
|
||||||
|
setOptions : List NotificationChannelRef -> Msg
|
||||||
|
setOptions refs =
|
||||||
|
LoadChannelsResp refs
|
||||||
|
|
||||||
|
|
||||||
|
initSelected : Flags -> List NotificationChannelRef -> ( Model, Cmd Msg )
|
||||||
|
initSelected flags selected =
|
||||||
|
( update (setSelected selected) emptyModel
|
||||||
|
|> Tuple.first
|
||||||
|
, getOptions flags
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
initWith : List NotificationChannelRef -> List NotificationChannelRef -> Model
|
||||||
|
initWith options selected =
|
||||||
|
update (setSelected selected) emptyModel
|
||||||
|
|> Tuple.first
|
||||||
|
|> update (setOptions options)
|
||||||
|
|> Tuple.first
|
||||||
|
|
||||||
|
|
||||||
|
getSelected : Model -> List NotificationChannelRef
|
||||||
|
getSelected model =
|
||||||
|
Comp.Dropdown.getSelected model.ddm
|
||||||
|
|
||||||
|
|
||||||
|
setSelected : List NotificationChannelRef -> Msg
|
||||||
|
setSelected refs =
|
||||||
|
DropdownMsg (Comp.Dropdown.SetSelection refs)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--- Update
|
||||||
|
|
||||||
|
|
||||||
|
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||||
|
update msg model =
|
||||||
|
case msg of
|
||||||
|
DropdownMsg lm ->
|
||||||
|
let
|
||||||
|
( dm, dc ) =
|
||||||
|
Comp.Dropdown.update lm model.ddm
|
||||||
|
in
|
||||||
|
( { model | ddm = dm }
|
||||||
|
, Cmd.map DropdownMsg dc
|
||||||
|
)
|
||||||
|
|
||||||
|
LoadChannelsResp refs ->
|
||||||
|
let
|
||||||
|
( dm, dc ) =
|
||||||
|
Comp.Dropdown.update (Comp.Dropdown.SetOptions refs) model.ddm
|
||||||
|
in
|
||||||
|
( { model
|
||||||
|
| all = refs
|
||||||
|
, ddm = dm
|
||||||
|
}
|
||||||
|
, Cmd.map DropdownMsg dc
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--- View
|
||||||
|
|
||||||
|
|
||||||
|
view : Texts -> UiSettings -> Model -> Html Msg
|
||||||
|
view texts settings model =
|
||||||
|
let
|
||||||
|
idShort id =
|
||||||
|
String.slice 0 6 id
|
||||||
|
|
||||||
|
joinName name ct =
|
||||||
|
Option (ct ++ " (" ++ name ++ ")") ""
|
||||||
|
|
||||||
|
mkName ref =
|
||||||
|
Data.ChannelType.fromString ref.channelType
|
||||||
|
|> Maybe.map texts.channelType
|
||||||
|
|> Maybe.withDefault ref.channelType
|
||||||
|
|> joinName (Maybe.withDefault (idShort ref.id) ref.name)
|
||||||
|
|
||||||
|
viewCfg =
|
||||||
|
{ makeOption = mkName
|
||||||
|
, placeholder = texts.placeholder
|
||||||
|
, labelColor = \_ -> \_ -> ""
|
||||||
|
, style = Data.DropdownStyle.mainStyle
|
||||||
|
}
|
||||||
|
in
|
||||||
|
Html.map DropdownMsg
|
||||||
|
(Comp.Dropdown.view2 viewCfg settings model.ddm)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--- Helpers
|
||||||
|
|
||||||
|
|
||||||
|
makeDropdownModel : Comp.Dropdown.Model NotificationChannelRef
|
||||||
|
makeDropdownModel =
|
||||||
|
let
|
||||||
|
m =
|
||||||
|
Comp.Dropdown.makeModel
|
||||||
|
{ multiple = True
|
||||||
|
, searchable = \n -> n > 0
|
||||||
|
}
|
||||||
|
in
|
||||||
|
{ m | searchWithAdditional = True }
|
@ -16,22 +16,18 @@ module Comp.DueItemsTaskForm exposing
|
|||||||
)
|
)
|
||||||
|
|
||||||
import Api
|
import Api
|
||||||
import Api.Model.EmailSettingsList exposing (EmailSettingsList)
|
import Api.Model.PeriodicDueItemsSettings exposing (PeriodicDueItemsSettings)
|
||||||
import Api.Model.Tag exposing (Tag)
|
|
||||||
import Api.Model.TagList exposing (TagList)
|
import Api.Model.TagList exposing (TagList)
|
||||||
import Comp.Basic as B
|
import Comp.Basic as B
|
||||||
import Comp.CalEventInput
|
import Comp.CalEventInput
|
||||||
import Comp.ChannelForm
|
import Comp.ChannelRefInput
|
||||||
import Comp.IntField
|
import Comp.IntField
|
||||||
import Comp.MenuBar as MB
|
import Comp.MenuBar as MB
|
||||||
import Comp.TagDropdown
|
import Comp.TagDropdown
|
||||||
import Comp.YesNoDimmer
|
import Comp.YesNoDimmer
|
||||||
import Data.CalEvent exposing (CalEvent)
|
import Data.CalEvent exposing (CalEvent)
|
||||||
import Data.ChannelType exposing (ChannelType)
|
|
||||||
import Data.DropdownStyle as DS
|
import Data.DropdownStyle as DS
|
||||||
import Data.Flags exposing (Flags)
|
import Data.Flags exposing (Flags)
|
||||||
import Data.NotificationChannel
|
|
||||||
import Data.PeriodicDueItemsSettings exposing (PeriodicDueItemsSettings)
|
|
||||||
import Data.TagOrder
|
import Data.TagOrder
|
||||||
import Data.UiSettings exposing (UiSettings)
|
import Data.UiSettings exposing (UiSettings)
|
||||||
import Data.Validated exposing (Validated(..))
|
import Data.Validated exposing (Validated(..))
|
||||||
@ -43,13 +39,11 @@ import Markdown
|
|||||||
import Messages.Comp.DueItemsTaskForm exposing (Texts)
|
import Messages.Comp.DueItemsTaskForm exposing (Texts)
|
||||||
import Styles as S
|
import Styles as S
|
||||||
import Util.Maybe
|
import Util.Maybe
|
||||||
import Util.Tag
|
|
||||||
import Util.Update
|
|
||||||
|
|
||||||
|
|
||||||
type alias Model =
|
type alias Model =
|
||||||
{ settings : PeriodicDueItemsSettings
|
{ settings : PeriodicDueItemsSettings
|
||||||
, channelModel : Comp.ChannelForm.Model
|
, channelModel : Comp.ChannelRefInput.Model
|
||||||
, tagInclModel : Comp.TagDropdown.Model
|
, tagInclModel : Comp.TagDropdown.Model
|
||||||
, tagExclModel : Comp.TagDropdown.Model
|
, tagExclModel : Comp.TagDropdown.Model
|
||||||
, remindDays : Maybe Int
|
, remindDays : Maybe Int
|
||||||
@ -99,18 +93,14 @@ type Msg
|
|||||||
| RequestDelete
|
| RequestDelete
|
||||||
| YesNoDeleteMsg Comp.YesNoDimmer.Msg
|
| YesNoDeleteMsg Comp.YesNoDimmer.Msg
|
||||||
| SetSummary String
|
| SetSummary String
|
||||||
| ChannelMsg Comp.ChannelForm.Msg
|
| ChannelMsg Comp.ChannelRefInput.Msg
|
||||||
|
|
||||||
|
|
||||||
initWith : Flags -> PeriodicDueItemsSettings -> ( Model, Cmd Msg )
|
initWith : Flags -> PeriodicDueItemsSettings -> ( Model, Cmd Msg )
|
||||||
initWith flags s =
|
initWith flags s =
|
||||||
let
|
let
|
||||||
ct =
|
|
||||||
Data.NotificationChannel.channelType s.channel
|
|
||||||
|> Maybe.withDefault Data.ChannelType.Matrix
|
|
||||||
|
|
||||||
( im, ic ) =
|
( im, ic ) =
|
||||||
init flags ct
|
init flags
|
||||||
|
|
||||||
newSchedule =
|
newSchedule =
|
||||||
Data.CalEvent.fromEvent s.schedule
|
Data.CalEvent.fromEvent s.schedule
|
||||||
@ -120,7 +110,7 @@ initWith flags s =
|
|||||||
Comp.CalEventInput.init flags newSchedule
|
Comp.CalEventInput.init flags newSchedule
|
||||||
|
|
||||||
( cfm, cfc ) =
|
( cfm, cfc ) =
|
||||||
Comp.ChannelForm.initWith flags s.channel
|
Comp.ChannelRefInput.initSelected flags s.channels
|
||||||
in
|
in
|
||||||
( { im
|
( { im
|
||||||
| settings = s
|
| settings = s
|
||||||
@ -145,8 +135,8 @@ initWith flags s =
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
init : Flags -> ChannelType -> ( Model, Cmd Msg )
|
init : Flags -> ( Model, Cmd Msg )
|
||||||
init flags ct =
|
init flags =
|
||||||
let
|
let
|
||||||
initialSchedule =
|
initialSchedule =
|
||||||
Data.CalEvent.everyMonth
|
Data.CalEvent.everyMonth
|
||||||
@ -155,9 +145,9 @@ init flags ct =
|
|||||||
Comp.CalEventInput.init flags initialSchedule
|
Comp.CalEventInput.init flags initialSchedule
|
||||||
|
|
||||||
( cfm, cfc ) =
|
( cfm, cfc ) =
|
||||||
Comp.ChannelForm.init flags ct
|
Comp.ChannelRefInput.init flags
|
||||||
in
|
in
|
||||||
( { settings = Data.PeriodicDueItemsSettings.empty ct
|
( { settings = Api.Model.PeriodicDueItemsSettings.empty
|
||||||
, channelModel = cfm
|
, channelModel = cfm
|
||||||
, tagInclModel = Comp.TagDropdown.initWith [] []
|
, tagInclModel = Comp.TagDropdown.initWith [] []
|
||||||
, tagExclModel = Comp.TagDropdown.initWith [] []
|
, tagExclModel = Comp.TagDropdown.initWith [] []
|
||||||
@ -203,11 +193,17 @@ makeSettings model =
|
|||||||
Err ValidateCalEventInvalid
|
Err ValidateCalEventInvalid
|
||||||
|
|
||||||
channelM =
|
channelM =
|
||||||
Result.fromMaybe
|
let
|
||||||
ValidateChannelRequired
|
list =
|
||||||
(Comp.ChannelForm.getChannel model.channelModel)
|
Comp.ChannelRefInput.getSelected model.channelModel
|
||||||
|
in
|
||||||
|
if list == [] then
|
||||||
|
Err ValidateChannelRequired
|
||||||
|
|
||||||
make days timer channel =
|
else
|
||||||
|
Ok list
|
||||||
|
|
||||||
|
make days timer channels =
|
||||||
{ prev
|
{ prev
|
||||||
| tagsInclude = Comp.TagDropdown.getSelected model.tagInclModel
|
| tagsInclude = Comp.TagDropdown.getSelected model.tagInclModel
|
||||||
, tagsExclude = Comp.TagDropdown.getSelected model.tagExclModel
|
, tagsExclude = Comp.TagDropdown.getSelected model.tagExclModel
|
||||||
@ -216,7 +212,7 @@ makeSettings model =
|
|||||||
, enabled = model.enabled
|
, enabled = model.enabled
|
||||||
, schedule = Data.CalEvent.makeEvent timer
|
, schedule = Data.CalEvent.makeEvent timer
|
||||||
, summary = model.summary
|
, summary = model.summary
|
||||||
, channel = channel
|
, channels = channels
|
||||||
}
|
}
|
||||||
in
|
in
|
||||||
Result.map3 make
|
Result.map3 make
|
||||||
@ -247,7 +243,7 @@ update flags msg model =
|
|||||||
ChannelMsg lm ->
|
ChannelMsg lm ->
|
||||||
let
|
let
|
||||||
( cfm, cfc ) =
|
( cfm, cfc ) =
|
||||||
Comp.ChannelForm.update flags lm model.channelModel
|
Comp.ChannelRefInput.update lm model.channelModel
|
||||||
in
|
in
|
||||||
( { model | channelModel = cfm }
|
( { model | channelModel = cfm }
|
||||||
, NoAction
|
, NoAction
|
||||||
@ -538,9 +534,9 @@ view2 texts extraClasses settings model =
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
, div [ class "mb-4" ]
|
, div [ class "mb-4" ]
|
||||||
[ formHeader (texts.channelHeader (Comp.ChannelForm.channelType model.channelModel))
|
[ formHeader texts.channelHeader
|
||||||
, Html.map ChannelMsg
|
, Html.map ChannelMsg
|
||||||
(Comp.ChannelForm.view texts.channelForm settings model.channelModel)
|
(Comp.ChannelRefInput.view texts.channelRef settings model.channelModel)
|
||||||
]
|
]
|
||||||
, formHeader texts.queryLabel
|
, formHeader texts.queryLabel
|
||||||
, div [ class "mb-4" ]
|
, div [ class "mb-4" ]
|
||||||
|
@ -14,15 +14,16 @@ module Comp.DueItemsTaskList exposing
|
|||||||
, view2
|
, view2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
import Api.Model.PeriodicDueItemsSettings exposing (PeriodicDueItemsSettings)
|
||||||
import Comp.Basic as B
|
import Comp.Basic as B
|
||||||
|
import Data.ChannelRef
|
||||||
import Data.ChannelType
|
import Data.ChannelType
|
||||||
import Data.NotificationChannel
|
|
||||||
import Data.PeriodicDueItemsSettings exposing (PeriodicDueItemsSettings)
|
|
||||||
import Html exposing (..)
|
import Html exposing (..)
|
||||||
import Html.Attributes exposing (..)
|
import Html.Attributes exposing (..)
|
||||||
import Messages.Comp.DueItemsTaskList exposing (Texts)
|
import Messages.Comp.DueItemsTaskList exposing (Texts)
|
||||||
import Styles as S
|
import Styles as S
|
||||||
import Util.Html
|
import Util.Html
|
||||||
|
import Util.List
|
||||||
|
|
||||||
|
|
||||||
type alias Model =
|
type alias Model =
|
||||||
@ -94,9 +95,7 @@ viewItem2 texts item =
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
, td [ class "text-left mr-2" ]
|
, td [ class "text-left mr-2" ]
|
||||||
[ Data.NotificationChannel.channelType item.channel
|
[ div [ class " space-x-1" ]
|
||||||
|> Maybe.map Data.ChannelType.asString
|
(Data.ChannelRef.asDivs texts.channelType [ class "inline" ] item.channels)
|
||||||
|> Maybe.withDefault "-"
|
|
||||||
|> text
|
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
@ -15,13 +15,11 @@ module Comp.DueItemsTaskManage exposing
|
|||||||
|
|
||||||
import Api
|
import Api
|
||||||
import Api.Model.BasicResult exposing (BasicResult)
|
import Api.Model.BasicResult exposing (BasicResult)
|
||||||
import Comp.ChannelMenu
|
import Api.Model.PeriodicDueItemsSettings exposing (PeriodicDueItemsSettings)
|
||||||
import Comp.DueItemsTaskForm
|
import Comp.DueItemsTaskForm
|
||||||
import Comp.DueItemsTaskList
|
import Comp.DueItemsTaskList
|
||||||
import Comp.MenuBar as MB
|
import Comp.MenuBar as MB
|
||||||
import Data.ChannelType exposing (ChannelType)
|
|
||||||
import Data.Flags exposing (Flags)
|
import Data.Flags exposing (Flags)
|
||||||
import Data.PeriodicDueItemsSettings exposing (PeriodicDueItemsSettings)
|
|
||||||
import Data.UiSettings exposing (UiSettings)
|
import Data.UiSettings exposing (UiSettings)
|
||||||
import Html exposing (..)
|
import Html exposing (..)
|
||||||
import Html.Attributes exposing (..)
|
import Html.Attributes exposing (..)
|
||||||
@ -35,7 +33,6 @@ type alias Model =
|
|||||||
, detailModel : Maybe Comp.DueItemsTaskForm.Model
|
, detailModel : Maybe Comp.DueItemsTaskForm.Model
|
||||||
, items : List PeriodicDueItemsSettings
|
, items : List PeriodicDueItemsSettings
|
||||||
, formState : FormState
|
, formState : FormState
|
||||||
, channelMenuOpen : Bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -57,9 +54,8 @@ type Msg
|
|||||||
= ListMsg Comp.DueItemsTaskList.Msg
|
= ListMsg Comp.DueItemsTaskList.Msg
|
||||||
| DetailMsg Comp.DueItemsTaskForm.Msg
|
| DetailMsg Comp.DueItemsTaskForm.Msg
|
||||||
| GetDataResp (Result Http.Error (List PeriodicDueItemsSettings))
|
| GetDataResp (Result Http.Error (List PeriodicDueItemsSettings))
|
||||||
| NewTaskInit ChannelType
|
| NewTaskInit
|
||||||
| SubmitResp SubmitType (Result Http.Error BasicResult)
|
| SubmitResp SubmitType (Result Http.Error BasicResult)
|
||||||
| ToggleChannelMenu
|
|
||||||
|
|
||||||
|
|
||||||
initModel : Model
|
initModel : Model
|
||||||
@ -68,7 +64,6 @@ initModel =
|
|||||||
, detailModel = Nothing
|
, detailModel = Nothing
|
||||||
, items = []
|
, items = []
|
||||||
, formState = FormStateInitial
|
, formState = FormStateInitial
|
||||||
, channelMenuOpen = False
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -89,11 +84,6 @@ init flags =
|
|||||||
update : Flags -> Msg -> Model -> ( Model, Cmd Msg )
|
update : Flags -> Msg -> Model -> ( Model, Cmd Msg )
|
||||||
update flags msg model =
|
update flags msg model =
|
||||||
case msg of
|
case msg of
|
||||||
ToggleChannelMenu ->
|
|
||||||
( { model | channelMenuOpen = not model.channelMenuOpen }
|
|
||||||
, Cmd.none
|
|
||||||
)
|
|
||||||
|
|
||||||
GetDataResp (Ok items) ->
|
GetDataResp (Ok items) ->
|
||||||
( { model
|
( { model
|
||||||
| items = items
|
| items = items
|
||||||
@ -194,12 +184,12 @@ update flags msg model =
|
|||||||
Nothing ->
|
Nothing ->
|
||||||
( model, Cmd.none )
|
( model, Cmd.none )
|
||||||
|
|
||||||
NewTaskInit ct ->
|
NewTaskInit ->
|
||||||
let
|
let
|
||||||
( mm, mc ) =
|
( mm, mc ) =
|
||||||
Comp.DueItemsTaskForm.init flags ct
|
Comp.DueItemsTaskForm.init flags
|
||||||
in
|
in
|
||||||
( { model | detailModel = Just mm, channelMenuOpen = False }, Cmd.map DetailMsg mc )
|
( { model | detailModel = Just mm }, Cmd.map DetailMsg mc )
|
||||||
|
|
||||||
SubmitResp submitType (Ok res) ->
|
SubmitResp submitType (Ok res) ->
|
||||||
( { model
|
( { model
|
||||||
@ -295,18 +285,15 @@ viewForm2 texts settings model =
|
|||||||
|
|
||||||
viewList2 : Texts -> Model -> List (Html Msg)
|
viewList2 : Texts -> Model -> List (Html Msg)
|
||||||
viewList2 texts model =
|
viewList2 texts model =
|
||||||
let
|
|
||||||
menuModel =
|
|
||||||
{ menuOpen = model.channelMenuOpen
|
|
||||||
, toggleMenu = ToggleChannelMenu
|
|
||||||
, menuLabel = texts.newTask
|
|
||||||
, onItem = NewTaskInit
|
|
||||||
}
|
|
||||||
in
|
|
||||||
[ MB.view
|
[ MB.view
|
||||||
{ start = []
|
{ start = []
|
||||||
, end =
|
, end =
|
||||||
[ Comp.ChannelMenu.channelMenu texts.channelType menuModel
|
[ MB.PrimaryButton
|
||||||
|
{ tagger = NewTaskInit
|
||||||
|
, title = texts.newTask
|
||||||
|
, icon = Just "fa fa-plus"
|
||||||
|
, label = texts.newTask
|
||||||
|
}
|
||||||
]
|
]
|
||||||
, rootClasses = "mb-4"
|
, rootClasses = "mb-4"
|
||||||
}
|
}
|
||||||
|
@ -115,8 +115,8 @@ dropdownCfg texts =
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
viewJson : Texts -> Model -> Html Msg
|
viewJson : Texts -> Bool -> Model -> Html Msg
|
||||||
viewJson texts model =
|
viewJson texts enableEventChooser model =
|
||||||
let
|
let
|
||||||
json =
|
json =
|
||||||
Result.withDefault ""
|
Result.withDefault ""
|
||||||
@ -125,7 +125,10 @@ viewJson texts model =
|
|||||||
div
|
div
|
||||||
[ class "flex flex-col w-full relative"
|
[ class "flex flex-col w-full relative"
|
||||||
]
|
]
|
||||||
[ div [ class "flex inline-flex items-center absolute top-2 right-4" ]
|
[ div
|
||||||
|
[ class "flex inline-flex items-center absolute top-2 right-4"
|
||||||
|
, classList [ ( "hidden", not enableEventChooser ) ]
|
||||||
|
]
|
||||||
[ Html.map EventTypeMsg
|
[ Html.map EventTypeMsg
|
||||||
(Comp.FixedDropdown.viewStyled2 (dropdownCfg texts)
|
(Comp.FixedDropdown.viewStyled2 (dropdownCfg texts)
|
||||||
False
|
False
|
||||||
@ -144,8 +147,8 @@ viewJson texts model =
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
viewMessage : Texts -> Model -> Html Msg
|
viewMessage : Texts -> Bool -> Model -> Html Msg
|
||||||
viewMessage texts model =
|
viewMessage texts enableEventChooser model =
|
||||||
let
|
let
|
||||||
titleDecoder =
|
titleDecoder =
|
||||||
D.at [ "message", "title" ] D.string
|
D.at [ "message", "title" ] D.string
|
||||||
@ -162,7 +165,10 @@ viewMessage texts model =
|
|||||||
div
|
div
|
||||||
[ class "flex flex-col w-full relative"
|
[ class "flex flex-col w-full relative"
|
||||||
]
|
]
|
||||||
[ div [ class "flex inline-flex items-center absolute top-2 right-4" ]
|
[ div
|
||||||
|
[ class "flex inline-flex items-center absolute top-2 right-4"
|
||||||
|
, classList [ ( "hidden", not enableEventChooser ) ]
|
||||||
|
]
|
||||||
[ Html.map EventTypeMsg
|
[ Html.map EventTypeMsg
|
||||||
(Comp.FixedDropdown.viewStyled2 (dropdownCfg texts)
|
(Comp.FixedDropdown.viewStyled2 (dropdownCfg texts)
|
||||||
False
|
False
|
||||||
|
454
modules/webapp/src/main/elm/Comp/NotificationChannelManage.elm
Normal file
454
modules/webapp/src/main/elm/Comp/NotificationChannelManage.elm
Normal file
@ -0,0 +1,454 @@
|
|||||||
|
{-
|
||||||
|
Copyright 2020 Eike K. & Contributors
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
-}
|
||||||
|
|
||||||
|
|
||||||
|
module Comp.NotificationChannelManage exposing
|
||||||
|
( Model
|
||||||
|
, Msg
|
||||||
|
, init
|
||||||
|
, update
|
||||||
|
, view
|
||||||
|
)
|
||||||
|
|
||||||
|
import Api
|
||||||
|
import Api.Model.BasicResult exposing (BasicResult)
|
||||||
|
import Comp.Basic as B
|
||||||
|
import Comp.ChannelForm
|
||||||
|
import Comp.ChannelMenu
|
||||||
|
import Comp.MenuBar as MB
|
||||||
|
import Comp.NotificationChannelTable
|
||||||
|
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 Html.Events exposing (onClick)
|
||||||
|
import Http
|
||||||
|
import Messages.Comp.NotificationChannelManage exposing (Texts)
|
||||||
|
import Styles as S
|
||||||
|
|
||||||
|
|
||||||
|
type alias Model =
|
||||||
|
{ listModel : Comp.NotificationChannelTable.Model
|
||||||
|
, detailModel : Maybe Comp.ChannelForm.Model
|
||||||
|
, items : List NotificationChannel
|
||||||
|
, deleteConfirm : DeleteConfirm
|
||||||
|
, loading : Bool
|
||||||
|
, formState : FormState
|
||||||
|
, newChannelMenuOpen : 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.NotificationChannelTable.Msg
|
||||||
|
| DetailMsg Comp.ChannelForm.Msg
|
||||||
|
| GetDataResp (Result Http.Error (List NotificationChannel))
|
||||||
|
| ToggleNewChannelMenu
|
||||||
|
| SubmitResp SubmitType (Result Http.Error BasicResult)
|
||||||
|
| NewChannelInit ChannelType
|
||||||
|
| BackToTable
|
||||||
|
| Submit
|
||||||
|
| RequestDelete
|
||||||
|
| CancelDelete
|
||||||
|
| DeleteChannelNow String
|
||||||
|
|
||||||
|
|
||||||
|
initModel : Model
|
||||||
|
initModel =
|
||||||
|
{ listModel = Comp.NotificationChannelTable.init
|
||||||
|
, detailModel = Nothing
|
||||||
|
, items = []
|
||||||
|
, loading = False
|
||||||
|
, formState = FormStateInitial
|
||||||
|
, newChannelMenuOpen = False
|
||||||
|
, deleteConfirm = DeleteConfirmOff
|
||||||
|
, jsonFilterError = Nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
initCmd : Flags -> Cmd Msg
|
||||||
|
initCmd flags =
|
||||||
|
Api.getChannels 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
|
||||||
|
, formState = FormStateInitial
|
||||||
|
}
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
GetDataResp (Err err) ->
|
||||||
|
( { model | formState = FormErrorHttp err }
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
TableMsg lm ->
|
||||||
|
let
|
||||||
|
( mm, action ) =
|
||||||
|
Comp.NotificationChannelTable.update flags lm model.listModel
|
||||||
|
|
||||||
|
( detail, cmd ) =
|
||||||
|
case action of
|
||||||
|
Comp.NotificationChannelTable.NoAction ->
|
||||||
|
( Nothing, Cmd.none )
|
||||||
|
|
||||||
|
Comp.NotificationChannelTable.EditAction channel ->
|
||||||
|
let
|
||||||
|
( dm, dc ) =
|
||||||
|
Comp.ChannelForm.initWith flags channel
|
||||||
|
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.ChannelForm.update flags lm dm
|
||||||
|
in
|
||||||
|
( { model | detailModel = Just mm }
|
||||||
|
, Cmd.map DetailMsg mc
|
||||||
|
)
|
||||||
|
|
||||||
|
Nothing ->
|
||||||
|
( model, Cmd.none )
|
||||||
|
|
||||||
|
ToggleNewChannelMenu ->
|
||||||
|
( { model | newChannelMenuOpen = not model.newChannelMenuOpen }, 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
|
||||||
|
)
|
||||||
|
|
||||||
|
NewChannelInit ct ->
|
||||||
|
let
|
||||||
|
( mm, mc ) =
|
||||||
|
Comp.ChannelForm.init flags ct
|
||||||
|
in
|
||||||
|
( { model | detailModel = Just mm, newChannelMenuOpen = False }, Cmd.map DetailMsg mc )
|
||||||
|
|
||||||
|
BackToTable ->
|
||||||
|
( { model | detailModel = Nothing }, initCmd flags )
|
||||||
|
|
||||||
|
Submit ->
|
||||||
|
case model.detailModel of
|
||||||
|
Just dm ->
|
||||||
|
case Comp.ChannelForm.getChannel dm of
|
||||||
|
Just data ->
|
||||||
|
postChannel flags data model
|
||||||
|
|
||||||
|
Nothing ->
|
||||||
|
( { model | formState = FormErrorInvalid }, Cmd.none )
|
||||||
|
|
||||||
|
Nothing ->
|
||||||
|
( model, Cmd.none )
|
||||||
|
|
||||||
|
RequestDelete ->
|
||||||
|
( { model | deleteConfirm = DeleteConfirmOn }, Cmd.none )
|
||||||
|
|
||||||
|
CancelDelete ->
|
||||||
|
( { model | deleteConfirm = DeleteConfirmOff }, Cmd.none )
|
||||||
|
|
||||||
|
DeleteChannelNow id ->
|
||||||
|
( { model | deleteConfirm = DeleteConfirmOff, loading = True }
|
||||||
|
, Api.deleteChannel flags id (SubmitResp SubmitDelete)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
postChannel : Flags -> NotificationChannel -> Model -> ( Model, Cmd Msg )
|
||||||
|
postChannel flags channel model =
|
||||||
|
if (Data.NotificationChannel.getRef channel |> .id) == "" then
|
||||||
|
( { model | loading = True }, Api.createChannel flags channel (SubmitResp SubmitCreate) )
|
||||||
|
|
||||||
|
else
|
||||||
|
( { model | loading = True }, Api.updateChannel flags channel (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, not (isSuccess model.formState) )
|
||||||
|
, ( S.successMessage, isSuccess model.formState )
|
||||||
|
, ( "hidden", model.formState == FormStateInitial )
|
||||||
|
]
|
||||||
|
, class "mb-2"
|
||||||
|
]
|
||||||
|
[ case model.formState of
|
||||||
|
FormStateInitial ->
|
||||||
|
text ""
|
||||||
|
|
||||||
|
FormSubmitSuccessful SubmitCreate ->
|
||||||
|
text texts.channelCreated
|
||||||
|
|
||||||
|
FormSubmitSuccessful SubmitUpdate ->
|
||||||
|
text texts.channelUpdated
|
||||||
|
|
||||||
|
FormSubmitSuccessful SubmitDelete ->
|
||||||
|
text texts.channelDeleted
|
||||||
|
|
||||||
|
FormErrorSubmit m ->
|
||||||
|
text m
|
||||||
|
|
||||||
|
FormErrorHttp err ->
|
||||||
|
text (texts.httpError err)
|
||||||
|
|
||||||
|
FormErrorInvalid ->
|
||||||
|
text texts.formInvalid
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
isSuccess : FormState -> Bool
|
||||||
|
isSuccess state =
|
||||||
|
case state of
|
||||||
|
FormSubmitSuccessful _ ->
|
||||||
|
True
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
False
|
||||||
|
|
||||||
|
|
||||||
|
viewForm : Texts -> UiSettings -> Model -> Comp.ChannelForm.Model -> List (Html Msg)
|
||||||
|
viewForm texts settings outerModel model =
|
||||||
|
let
|
||||||
|
channelId =
|
||||||
|
Comp.ChannelForm.getChannel model
|
||||||
|
|> Maybe.map Data.NotificationChannel.getRef
|
||||||
|
|> Maybe.map .id
|
||||||
|
|
||||||
|
newChannel =
|
||||||
|
channelId |> (==) (Just "")
|
||||||
|
|
||||||
|
headline =
|
||||||
|
case Comp.ChannelForm.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.ChannelForm.channelType model) "w-8 h-8 inline-block mr-2"
|
||||||
|
, if newChannel then
|
||||||
|
text texts.addChannel
|
||||||
|
|
||||||
|
else
|
||||||
|
text texts.updateChannel
|
||||||
|
, div [ class "text-xs opacity-50 font-mono" ]
|
||||||
|
[ Maybe.withDefault "" channelId |> text
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, 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 newChannel then
|
||||||
|
[ MB.DeleteButton
|
||||||
|
{ tagger = RequestDelete
|
||||||
|
, title = texts.deleteThisChannel
|
||||||
|
, icon = Just "fa fa-trash"
|
||||||
|
, label = texts.basics.delete
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
, rootClasses = "mb-4"
|
||||||
|
}
|
||||||
|
, div [ class "mt-2" ]
|
||||||
|
[ viewState texts outerModel
|
||||||
|
]
|
||||||
|
, Html.map DetailMsg
|
||||||
|
(Comp.ChannelForm.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.reallyDeleteChannel
|
||||||
|
]
|
||||||
|
, div [ class "mt-4 flex flex-row items-center" ]
|
||||||
|
[ B.deleteButton
|
||||||
|
{ label = texts.basics.yes
|
||||||
|
, icon = "fa fa-check"
|
||||||
|
, disabled = False
|
||||||
|
, handler = onClick (DeleteChannelNow (Maybe.withDefault "" channelId))
|
||||||
|
, 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.newChannelMenuOpen
|
||||||
|
, toggleMenu = ToggleNewChannelMenu
|
||||||
|
, menuLabel = texts.newChannel
|
||||||
|
, onItem = NewChannelInit
|
||||||
|
}
|
||||||
|
in
|
||||||
|
[ MB.view
|
||||||
|
{ start = []
|
||||||
|
, end =
|
||||||
|
[ Comp.ChannelMenu.channelMenu texts.channelType menuModel
|
||||||
|
]
|
||||||
|
, rootClasses = "mb-4"
|
||||||
|
}
|
||||||
|
, Html.map TableMsg
|
||||||
|
(Comp.NotificationChannelTable.view texts.notificationTable
|
||||||
|
model.listModel
|
||||||
|
model.items
|
||||||
|
)
|
||||||
|
]
|
@ -0,0 +1,87 @@
|
|||||||
|
{-
|
||||||
|
Copyright 2020 Eike K. & Contributors
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
-}
|
||||||
|
|
||||||
|
|
||||||
|
module Comp.NotificationChannelTable exposing (..)
|
||||||
|
|
||||||
|
import Comp.Basic as B
|
||||||
|
import Data.ChannelType
|
||||||
|
import Data.Flags exposing (Flags)
|
||||||
|
import Data.NotificationChannel exposing (NotificationChannel)
|
||||||
|
import Html exposing (..)
|
||||||
|
import Html.Attributes exposing (..)
|
||||||
|
import Messages.Comp.NotificationChannelTable exposing (Texts)
|
||||||
|
import Styles as S
|
||||||
|
|
||||||
|
|
||||||
|
type alias Model =
|
||||||
|
{}
|
||||||
|
|
||||||
|
|
||||||
|
type Action
|
||||||
|
= NoAction
|
||||||
|
| EditAction NotificationChannel
|
||||||
|
|
||||||
|
|
||||||
|
init : Model
|
||||||
|
init =
|
||||||
|
{}
|
||||||
|
|
||||||
|
|
||||||
|
type Msg
|
||||||
|
= Select NotificationChannel
|
||||||
|
|
||||||
|
|
||||||
|
update : Flags -> Msg -> Model -> ( Model, Action )
|
||||||
|
update _ msg model =
|
||||||
|
case msg of
|
||||||
|
Select channel ->
|
||||||
|
( model, EditAction channel )
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--- View
|
||||||
|
|
||||||
|
|
||||||
|
view : Texts -> Model -> List NotificationChannel -> Html Msg
|
||||||
|
view texts model channels =
|
||||||
|
table [ class S.tableMain ]
|
||||||
|
[ thead []
|
||||||
|
[ tr []
|
||||||
|
[ th [ class "" ] []
|
||||||
|
, th [ class "text-left" ]
|
||||||
|
[ text texts.basics.name
|
||||||
|
]
|
||||||
|
, th [ class "text-left" ]
|
||||||
|
[ text texts.channelType
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, tbody []
|
||||||
|
(List.map (renderNotificationChannelLine texts model) channels)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
renderNotificationChannelLine : Texts -> Model -> NotificationChannel -> Html Msg
|
||||||
|
renderNotificationChannelLine texts _ channel =
|
||||||
|
let
|
||||||
|
ref =
|
||||||
|
Data.NotificationChannel.getRef channel
|
||||||
|
in
|
||||||
|
tr
|
||||||
|
[ class S.tableRow
|
||||||
|
]
|
||||||
|
[ B.editLinkTableCell texts.basics.edit (Select channel)
|
||||||
|
, td
|
||||||
|
[ class "text-left "
|
||||||
|
, classList [ ( "font-mono", ref.name == Nothing ) ]
|
||||||
|
]
|
||||||
|
[ Maybe.withDefault (String.slice 0 10 ref.id) ref.name |> text
|
||||||
|
]
|
||||||
|
, td [ class "text-left py-4 md:py-2" ]
|
||||||
|
[ text ref.channelType
|
||||||
|
]
|
||||||
|
]
|
@ -17,24 +17,25 @@ import Html.Attributes exposing (..)
|
|||||||
import Html.Events exposing (onInput)
|
import Html.Events exposing (onInput)
|
||||||
import Messages.Comp.NotificationGotifyForm exposing (Texts)
|
import Messages.Comp.NotificationGotifyForm exposing (Texts)
|
||||||
import Styles as S
|
import Styles as S
|
||||||
|
import Util.Maybe
|
||||||
|
|
||||||
|
|
||||||
type alias Model =
|
type alias Model =
|
||||||
{ hook : NotificationGotify
|
{ channel : NotificationGotify
|
||||||
, prioModel : Comp.FixedDropdown.Model Int
|
, prioModel : Comp.FixedDropdown.Model Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
init : Model
|
init : Model
|
||||||
init =
|
init =
|
||||||
{ hook = Data.NotificationChannel.setTypeGotify Api.Model.NotificationGotify.empty
|
{ channel = Data.NotificationChannel.setTypeGotify Api.Model.NotificationGotify.empty
|
||||||
, prioModel = Comp.FixedDropdown.init (List.range 0 10)
|
, prioModel = Comp.FixedDropdown.init (List.range 0 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
initWith : NotificationGotify -> Model
|
initWith : NotificationGotify -> Model
|
||||||
initWith hook =
|
initWith channel =
|
||||||
{ hook = Data.NotificationChannel.setTypeGotify hook
|
{ channel = Data.NotificationChannel.setTypeGotify channel
|
||||||
, prioModel = Comp.FixedDropdown.init (List.range 0 10)
|
, prioModel = Comp.FixedDropdown.init (List.range 0 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,6 +43,7 @@ initWith hook =
|
|||||||
type Msg
|
type Msg
|
||||||
= SetUrl String
|
= SetUrl String
|
||||||
| SetAppKey String
|
| SetAppKey String
|
||||||
|
| SetName String
|
||||||
| PrioMsg (Comp.FixedDropdown.Msg Int)
|
| PrioMsg (Comp.FixedDropdown.Msg Int)
|
||||||
|
|
||||||
|
|
||||||
@ -52,30 +54,33 @@ type Msg
|
|||||||
update : Msg -> Model -> ( Model, Maybe NotificationGotify )
|
update : Msg -> Model -> ( Model, Maybe NotificationGotify )
|
||||||
update msg model =
|
update msg model =
|
||||||
let
|
let
|
||||||
hook =
|
channel =
|
||||||
model.hook
|
model.channel
|
||||||
|
|
||||||
newModel =
|
newModel =
|
||||||
case msg of
|
case msg of
|
||||||
SetUrl s ->
|
SetUrl s ->
|
||||||
{ model | hook = { hook | url = s } }
|
{ model | channel = { channel | url = s } }
|
||||||
|
|
||||||
SetAppKey s ->
|
SetAppKey s ->
|
||||||
{ model | hook = { hook | appKey = s } }
|
{ model | channel = { channel | appKey = s } }
|
||||||
|
|
||||||
|
SetName s ->
|
||||||
|
{ model | channel = { channel | name = Util.Maybe.fromString s } }
|
||||||
|
|
||||||
PrioMsg lm ->
|
PrioMsg lm ->
|
||||||
let
|
let
|
||||||
( m, sel ) =
|
( m, sel ) =
|
||||||
Comp.FixedDropdown.update lm model.prioModel
|
Comp.FixedDropdown.update lm model.prioModel
|
||||||
in
|
in
|
||||||
{ model | hook = { hook | priority = sel }, prioModel = m }
|
{ model | channel = { channel | priority = sel }, prioModel = m }
|
||||||
in
|
in
|
||||||
( newModel, check newModel.hook )
|
( newModel, check newModel.channel )
|
||||||
|
|
||||||
|
|
||||||
check : NotificationGotify -> Maybe NotificationGotify
|
check : NotificationGotify -> Maybe NotificationGotify
|
||||||
check hook =
|
check channel =
|
||||||
Just hook
|
Just channel
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -94,6 +99,25 @@ view texts model =
|
|||||||
in
|
in
|
||||||
div []
|
div []
|
||||||
[ div
|
[ div
|
||||||
|
[ class "mb-2"
|
||||||
|
]
|
||||||
|
[ label
|
||||||
|
[ for "name"
|
||||||
|
, class S.inputLabel
|
||||||
|
]
|
||||||
|
[ text texts.basics.name
|
||||||
|
]
|
||||||
|
, input
|
||||||
|
[ type_ "text"
|
||||||
|
, onInput SetName
|
||||||
|
, placeholder texts.basics.name
|
||||||
|
, value (Maybe.withDefault "" model.channel.name)
|
||||||
|
, name "name"
|
||||||
|
, class S.textInput
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
, div
|
||||||
[ class "mb-2"
|
[ class "mb-2"
|
||||||
]
|
]
|
||||||
[ label
|
[ label
|
||||||
@ -107,7 +131,7 @@ view texts model =
|
|||||||
[ type_ "text"
|
[ type_ "text"
|
||||||
, onInput SetUrl
|
, onInput SetUrl
|
||||||
, placeholder texts.gotifyUrl
|
, placeholder texts.gotifyUrl
|
||||||
, value model.hook.url
|
, value model.channel.url
|
||||||
, name "gotifyurl"
|
, name "gotifyurl"
|
||||||
, class S.textInput
|
, class S.textInput
|
||||||
]
|
]
|
||||||
@ -127,7 +151,7 @@ view texts model =
|
|||||||
[ type_ "text"
|
[ type_ "text"
|
||||||
, onInput SetAppKey
|
, onInput SetAppKey
|
||||||
, placeholder texts.appKey
|
, placeholder texts.appKey
|
||||||
, value model.hook.appKey
|
, value model.channel.appKey
|
||||||
, name "appkey"
|
, name "appkey"
|
||||||
, class S.textInput
|
, class S.textInput
|
||||||
]
|
]
|
||||||
@ -142,7 +166,7 @@ view texts model =
|
|||||||
]
|
]
|
||||||
[ text texts.priority
|
[ text texts.priority
|
||||||
]
|
]
|
||||||
, Html.map PrioMsg (Comp.FixedDropdown.viewStyled2 cfg False model.hook.priority model.prioModel)
|
, Html.map PrioMsg (Comp.FixedDropdown.viewStyled2 cfg False model.channel.priority model.prioModel)
|
||||||
, span [ class "text-sm opacity-75" ]
|
, span [ class "text-sm opacity-75" ]
|
||||||
[ text texts.priorityInfo
|
[ text texts.priorityInfo
|
||||||
]
|
]
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
module Comp.NotificationHookForm exposing
|
module Comp.NotificationHookForm exposing
|
||||||
( Model
|
( Model
|
||||||
, Msg(..)
|
, Msg(..)
|
||||||
, channelType
|
|
||||||
, getHook
|
, getHook
|
||||||
, init
|
, init
|
||||||
, initWith
|
, initWith
|
||||||
@ -16,17 +15,16 @@ module Comp.NotificationHookForm exposing
|
|||||||
, view
|
, view
|
||||||
)
|
)
|
||||||
|
|
||||||
|
import Api.Model.NotificationHook exposing (NotificationHook)
|
||||||
import Comp.Basic as B
|
import Comp.Basic as B
|
||||||
import Comp.ChannelForm
|
import Comp.ChannelRefInput
|
||||||
import Comp.Dropdown
|
import Comp.Dropdown
|
||||||
import Comp.EventSample
|
import Comp.EventSample
|
||||||
import Comp.MenuBar as MB
|
import Comp.MenuBar as MB
|
||||||
import Comp.NotificationTest
|
import Comp.NotificationTest
|
||||||
import Data.ChannelType exposing (ChannelType)
|
|
||||||
import Data.DropdownStyle as DS
|
import Data.DropdownStyle as DS
|
||||||
import Data.EventType exposing (EventType)
|
import Data.EventType exposing (EventType)
|
||||||
import Data.Flags exposing (Flags)
|
import Data.Flags exposing (Flags)
|
||||||
import Data.NotificationHook exposing (NotificationHook)
|
|
||||||
import Data.UiSettings exposing (UiSettings)
|
import Data.UiSettings exposing (UiSettings)
|
||||||
import Html exposing (..)
|
import Html exposing (..)
|
||||||
import Html.Attributes exposing (..)
|
import Html.Attributes exposing (..)
|
||||||
@ -39,7 +37,7 @@ import Util.Maybe
|
|||||||
type alias Model =
|
type alias Model =
|
||||||
{ hook : NotificationHook
|
{ hook : NotificationHook
|
||||||
, enabled : Bool
|
, enabled : Bool
|
||||||
, channelModel : Comp.ChannelForm.Model
|
, channelModel : Comp.ChannelRefInput.Model
|
||||||
, eventsDropdown : Comp.Dropdown.Model EventType
|
, eventsDropdown : Comp.Dropdown.Model EventType
|
||||||
, eventSampleModel : Comp.EventSample.Model
|
, eventSampleModel : Comp.EventSample.Model
|
||||||
, testDeliveryModel : Comp.NotificationTest.Model
|
, testDeliveryModel : Comp.NotificationTest.Model
|
||||||
@ -48,16 +46,16 @@ type alias Model =
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
init : Flags -> ChannelType -> ( Model, Cmd Msg )
|
init : Flags -> ( Model, Cmd Msg )
|
||||||
init flags ct =
|
init flags =
|
||||||
let
|
let
|
||||||
( cm, cc ) =
|
( cm, cc ) =
|
||||||
Comp.ChannelForm.init flags ct
|
Comp.ChannelRefInput.init flags
|
||||||
|
|
||||||
( esm, esc ) =
|
( esm, esc ) =
|
||||||
Comp.EventSample.initWith flags Data.EventType.TagsChanged
|
Comp.EventSample.initWith flags Data.EventType.TagsChanged
|
||||||
in
|
in
|
||||||
( { hook = Data.NotificationHook.empty ct
|
( { hook = Api.Model.NotificationHook.empty
|
||||||
, enabled = True
|
, enabled = True
|
||||||
, channelModel = cm
|
, channelModel = cm
|
||||||
, eventsDropdown =
|
, eventsDropdown =
|
||||||
@ -81,7 +79,7 @@ initWith : Flags -> NotificationHook -> ( Model, Cmd Msg )
|
|||||||
initWith flags h =
|
initWith flags h =
|
||||||
let
|
let
|
||||||
( cm, cc ) =
|
( cm, cc ) =
|
||||||
Comp.ChannelForm.initWith flags h.channel
|
Comp.ChannelRefInput.initSelected flags h.channels
|
||||||
|
|
||||||
( esm, esc ) =
|
( esm, esc ) =
|
||||||
Comp.EventSample.initWith flags Data.EventType.TagsChanged
|
Comp.EventSample.initWith flags Data.EventType.TagsChanged
|
||||||
@ -92,7 +90,7 @@ initWith flags h =
|
|||||||
, eventsDropdown =
|
, eventsDropdown =
|
||||||
Comp.Dropdown.makeMultipleList
|
Comp.Dropdown.makeMultipleList
|
||||||
{ options = Data.EventType.all
|
{ options = Data.EventType.all
|
||||||
, selected = h.events
|
, selected = List.filterMap Data.EventType.fromString h.events
|
||||||
}
|
}
|
||||||
, eventSampleModel = esm
|
, eventSampleModel = esm
|
||||||
, testDeliveryModel = Comp.NotificationTest.init
|
, testDeliveryModel = Comp.NotificationTest.init
|
||||||
@ -106,11 +104,6 @@ initWith flags h =
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
channelType : Model -> ChannelType
|
|
||||||
channelType model =
|
|
||||||
Comp.ChannelForm.channelType model.channelModel
|
|
||||||
|
|
||||||
|
|
||||||
getHook : Model -> Maybe NotificationHook
|
getHook : Model -> Maybe NotificationHook
|
||||||
getHook model =
|
getHook model =
|
||||||
let
|
let
|
||||||
@ -123,20 +116,28 @@ getHook model =
|
|||||||
Nothing
|
Nothing
|
||||||
|
|
||||||
else
|
else
|
||||||
Just ev
|
Just (List.map Data.EventType.asString ev)
|
||||||
|
|
||||||
channel =
|
channels =
|
||||||
Comp.ChannelForm.getChannel model.channelModel
|
let
|
||||||
|
list =
|
||||||
|
Comp.ChannelRefInput.getSelected model.channelModel
|
||||||
|
in
|
||||||
|
if list == [] then
|
||||||
|
Nothing
|
||||||
|
|
||||||
|
else
|
||||||
|
Just list
|
||||||
|
|
||||||
mkHook ev ch =
|
mkHook ev ch =
|
||||||
NotificationHook model.hook.id model.enabled ch model.allEvents model.eventFilter ev
|
NotificationHook model.hook.id model.enabled ch model.allEvents model.eventFilter ev
|
||||||
in
|
in
|
||||||
Maybe.map2 mkHook events channel
|
Maybe.map2 mkHook events channels
|
||||||
|
|
||||||
|
|
||||||
type Msg
|
type Msg
|
||||||
= ToggleEnabled
|
= ToggleEnabled
|
||||||
| ChannelFormMsg Comp.ChannelForm.Msg
|
| ChannelFormMsg Comp.ChannelRefInput.Msg
|
||||||
| EventMsg (Comp.Dropdown.Msg EventType)
|
| EventMsg (Comp.Dropdown.Msg EventType)
|
||||||
| EventSampleMsg Comp.EventSample.Msg
|
| EventSampleMsg Comp.EventSample.Msg
|
||||||
| DeliveryTestMsg Comp.NotificationTest.Msg
|
| DeliveryTestMsg Comp.NotificationTest.Msg
|
||||||
@ -163,7 +164,7 @@ update flags msg model =
|
|||||||
ChannelFormMsg lm ->
|
ChannelFormMsg lm ->
|
||||||
let
|
let
|
||||||
( cm, cc ) =
|
( cm, cc ) =
|
||||||
Comp.ChannelForm.update flags lm model.channelModel
|
Comp.ChannelRefInput.update lm model.channelModel
|
||||||
in
|
in
|
||||||
( { model | channelModel = cm }, Cmd.map ChannelFormMsg cc )
|
( { model | channelModel = cm }, Cmd.map ChannelFormMsg cc )
|
||||||
|
|
||||||
@ -229,9 +230,9 @@ view texts settings model =
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
, div [ class "mb-4" ]
|
, div [ class "mb-4" ]
|
||||||
[ formHeader (texts.channelHeader (Comp.ChannelForm.channelType model.channelModel))
|
[ formHeader texts.channelHeader
|
||||||
, Html.map ChannelFormMsg
|
, Html.map ChannelFormMsg
|
||||||
(Comp.ChannelForm.view texts.channelForm settings model.channelModel)
|
(Comp.ChannelRefInput.view texts.channelRef settings model.channelModel)
|
||||||
]
|
]
|
||||||
, div [ class "mb-4" ]
|
, div [ class "mb-4" ]
|
||||||
[ formHeader texts.events
|
[ formHeader texts.events
|
||||||
@ -290,21 +291,21 @@ view texts settings model =
|
|||||||
]
|
]
|
||||||
, div
|
, div
|
||||||
[ class "mt-4"
|
[ 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
|
[ formHeader texts.samplePayload
|
||||||
|
, div [ class "opacity-80 mb-1" ]
|
||||||
|
[ text texts.payloadInfo
|
||||||
|
]
|
||||||
, Html.map EventSampleMsg
|
, Html.map EventSampleMsg
|
||||||
(Comp.EventSample.viewMessage texts.eventSample model.eventSampleModel)
|
(Comp.EventSample.viewMessage texts.eventSample True model.eventSampleModel)
|
||||||
|
, div [ class "py-2 text-center text-sm" ]
|
||||||
|
[ text texts.jsonPayload
|
||||||
|
, i [ class "fa fa-arrow-down ml-1 mr-3" ] []
|
||||||
|
, i [ class "fa fa-arrow-up mr-1" ] []
|
||||||
|
, text texts.messagePayload
|
||||||
|
]
|
||||||
|
, Html.map EventSampleMsg
|
||||||
|
(Comp.EventSample.viewJson texts.eventSample False model.eventSampleModel)
|
||||||
]
|
]
|
||||||
, div [ class "mt-4" ]
|
, div [ class "mt-4" ]
|
||||||
[ formHeader "Test Delviery"
|
[ formHeader "Test Delviery"
|
||||||
|
@ -15,14 +15,12 @@ module Comp.NotificationHookManage exposing
|
|||||||
|
|
||||||
import Api
|
import Api
|
||||||
import Api.Model.BasicResult exposing (BasicResult)
|
import Api.Model.BasicResult exposing (BasicResult)
|
||||||
|
import Api.Model.NotificationHook exposing (NotificationHook)
|
||||||
import Comp.Basic as B
|
import Comp.Basic as B
|
||||||
import Comp.ChannelMenu
|
|
||||||
import Comp.MenuBar as MB
|
import Comp.MenuBar as MB
|
||||||
import Comp.NotificationHookForm
|
import Comp.NotificationHookForm
|
||||||
import Comp.NotificationHookTable
|
import Comp.NotificationHookTable
|
||||||
import Data.ChannelType exposing (ChannelType)
|
|
||||||
import Data.Flags exposing (Flags)
|
import Data.Flags exposing (Flags)
|
||||||
import Data.NotificationHook exposing (NotificationHook)
|
|
||||||
import Data.UiSettings exposing (UiSettings)
|
import Data.UiSettings exposing (UiSettings)
|
||||||
import Html exposing (..)
|
import Html exposing (..)
|
||||||
import Html.Attributes exposing (..)
|
import Html.Attributes exposing (..)
|
||||||
@ -39,7 +37,6 @@ type alias Model =
|
|||||||
, deleteConfirm : DeleteConfirm
|
, deleteConfirm : DeleteConfirm
|
||||||
, loading : Bool
|
, loading : Bool
|
||||||
, formState : FormState
|
, formState : FormState
|
||||||
, newHookMenuOpen : Bool
|
|
||||||
, jsonFilterError : Maybe String
|
, jsonFilterError : Maybe String
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,9 +64,8 @@ type Msg
|
|||||||
= TableMsg Comp.NotificationHookTable.Msg
|
= TableMsg Comp.NotificationHookTable.Msg
|
||||||
| DetailMsg Comp.NotificationHookForm.Msg
|
| DetailMsg Comp.NotificationHookForm.Msg
|
||||||
| GetDataResp (Result Http.Error (List NotificationHook))
|
| GetDataResp (Result Http.Error (List NotificationHook))
|
||||||
| ToggleNewHookMenu
|
|
||||||
| SubmitResp SubmitType (Result Http.Error BasicResult)
|
| SubmitResp SubmitType (Result Http.Error BasicResult)
|
||||||
| NewHookInit ChannelType
|
| NewHookInit
|
||||||
| BackToTable
|
| BackToTable
|
||||||
| Submit
|
| Submit
|
||||||
| RequestDelete
|
| RequestDelete
|
||||||
@ -85,7 +81,6 @@ initModel =
|
|||||||
, items = []
|
, items = []
|
||||||
, loading = False
|
, loading = False
|
||||||
, formState = FormStateInitial
|
, formState = FormStateInitial
|
||||||
, newHookMenuOpen = False
|
|
||||||
, deleteConfirm = DeleteConfirmOff
|
, deleteConfirm = DeleteConfirmOff
|
||||||
, jsonFilterError = Nothing
|
, jsonFilterError = Nothing
|
||||||
}
|
}
|
||||||
@ -177,9 +172,6 @@ update flags msg model =
|
|||||||
Nothing ->
|
Nothing ->
|
||||||
( model, Cmd.none )
|
( model, Cmd.none )
|
||||||
|
|
||||||
ToggleNewHookMenu ->
|
|
||||||
( { model | newHookMenuOpen = not model.newHookMenuOpen }, Cmd.none )
|
|
||||||
|
|
||||||
SubmitResp submitType (Ok res) ->
|
SubmitResp submitType (Ok res) ->
|
||||||
( { model
|
( { model
|
||||||
| formState =
|
| formState =
|
||||||
@ -208,12 +200,12 @@ update flags msg model =
|
|||||||
, Cmd.none
|
, Cmd.none
|
||||||
)
|
)
|
||||||
|
|
||||||
NewHookInit ct ->
|
NewHookInit ->
|
||||||
let
|
let
|
||||||
( mm, mc ) =
|
( mm, mc ) =
|
||||||
Comp.NotificationHookForm.init flags ct
|
Comp.NotificationHookForm.init flags
|
||||||
in
|
in
|
||||||
( { model | detailModel = Just mm, newHookMenuOpen = False }, Cmd.map DetailMsg mc )
|
( { model | detailModel = Just mm }, Cmd.map DetailMsg mc )
|
||||||
|
|
||||||
BackToTable ->
|
BackToTable ->
|
||||||
( { model | detailModel = Nothing }, initCmd flags )
|
( { model | detailModel = Nothing }, initCmd flags )
|
||||||
@ -327,60 +319,14 @@ viewForm texts settings outerModel model =
|
|||||||
let
|
let
|
||||||
newHook =
|
newHook =
|
||||||
model.hook.id == ""
|
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
|
in
|
||||||
[ h1 [ class S.header2 ]
|
[ h1 [ class S.header2 ]
|
||||||
[ Data.ChannelType.icon (Comp.NotificationHookForm.channelType model) "w-8 h-8 inline-block mr-4"
|
[ if newHook then
|
||||||
, if newHook then
|
|
||||||
text texts.addWebhook
|
text texts.addWebhook
|
||||||
|
|
||||||
else
|
else
|
||||||
text texts.updateWebhook
|
text texts.updateWebhook
|
||||||
]
|
]
|
||||||
, div [ class "pt-2 pb-4 font-medium" ]
|
|
||||||
[ headline
|
|
||||||
]
|
|
||||||
, MB.view
|
, MB.view
|
||||||
{ start =
|
{ start =
|
||||||
[ MB.CustomElement <|
|
[ MB.CustomElement <|
|
||||||
@ -452,18 +398,15 @@ viewForm texts settings outerModel model =
|
|||||||
|
|
||||||
viewList : Texts -> Model -> List (Html Msg)
|
viewList : Texts -> Model -> List (Html Msg)
|
||||||
viewList texts model =
|
viewList texts model =
|
||||||
let
|
|
||||||
menuModel =
|
|
||||||
{ menuOpen = model.newHookMenuOpen
|
|
||||||
, toggleMenu = ToggleNewHookMenu
|
|
||||||
, menuLabel = texts.newHook
|
|
||||||
, onItem = NewHookInit
|
|
||||||
}
|
|
||||||
in
|
|
||||||
[ MB.view
|
[ MB.view
|
||||||
{ start = []
|
{ start = []
|
||||||
, end =
|
, end =
|
||||||
[ Comp.ChannelMenu.channelMenu texts.channelType menuModel
|
[ MB.PrimaryButton
|
||||||
|
{ tagger = NewHookInit
|
||||||
|
, title = texts.newHook
|
||||||
|
, icon = Just "fa fa-plus"
|
||||||
|
, label = texts.newHook
|
||||||
|
}
|
||||||
]
|
]
|
||||||
, rootClasses = "mb-4"
|
, rootClasses = "mb-4"
|
||||||
}
|
}
|
||||||
|
@ -14,15 +14,13 @@ module Comp.NotificationHookTable exposing
|
|||||||
, view
|
, view
|
||||||
)
|
)
|
||||||
|
|
||||||
|
import Api.Model.NotificationHook exposing (NotificationHook)
|
||||||
import Comp.Basic as B
|
import Comp.Basic as B
|
||||||
import Data.ChannelType
|
import Data.ChannelRef
|
||||||
import Data.EventType
|
import Data.EventType
|
||||||
import Data.Flags exposing (Flags)
|
import Data.Flags exposing (Flags)
|
||||||
import Data.NotificationChannel
|
|
||||||
import Data.NotificationHook exposing (NotificationHook)
|
|
||||||
import Html exposing (..)
|
import Html exposing (..)
|
||||||
import Html.Attributes exposing (..)
|
import Html.Attributes exposing (..)
|
||||||
import Html.Events exposing (onClick)
|
|
||||||
import Messages.Comp.NotificationHookTable exposing (Texts)
|
import Messages.Comp.NotificationHookTable exposing (Texts)
|
||||||
import Styles as S
|
import Styles as S
|
||||||
import Util.Html
|
import Util.Html
|
||||||
@ -80,7 +78,7 @@ view texts model hooks =
|
|||||||
|
|
||||||
|
|
||||||
renderNotificationHookLine : Texts -> Model -> NotificationHook -> Html Msg
|
renderNotificationHookLine : Texts -> Model -> NotificationHook -> Html Msg
|
||||||
renderNotificationHookLine texts model hook =
|
renderNotificationHookLine texts _ hook =
|
||||||
let
|
let
|
||||||
eventName =
|
eventName =
|
||||||
texts.eventType >> .name
|
texts.eventType >> .name
|
||||||
@ -93,13 +91,16 @@ renderNotificationHookLine texts model hook =
|
|||||||
[ Util.Html.checkbox2 hook.enabled
|
[ Util.Html.checkbox2 hook.enabled
|
||||||
]
|
]
|
||||||
, td [ class "text-left py-4 md:py-2" ]
|
, td [ class "text-left py-4 md:py-2" ]
|
||||||
[ Data.NotificationChannel.channelType hook.channel
|
[ div [ class "space-x-1" ]
|
||||||
|> Maybe.map Data.ChannelType.asString
|
(Data.ChannelRef.asDivs texts.channelType [ class "inline" ] hook.channels)
|
||||||
|> Maybe.withDefault "-"
|
|
||||||
|> text
|
|
||||||
]
|
]
|
||||||
, td [ class "text-left hidden sm:table-cell" ]
|
, td [ class "text-left hidden sm:table-cell" ]
|
||||||
[ List.map eventName hook.events
|
[ if hook.allEvents then
|
||||||
|
text texts.allEvents
|
||||||
|
|
||||||
|
else
|
||||||
|
List.filterMap Data.EventType.fromString hook.events
|
||||||
|
|> List.map eventName
|
||||||
|> String.join ", "
|
|> String.join ", "
|
||||||
|> text
|
|> text
|
||||||
]
|
]
|
||||||
|
@ -15,29 +15,31 @@ import Html.Attributes exposing (..)
|
|||||||
import Html.Events exposing (onInput)
|
import Html.Events exposing (onInput)
|
||||||
import Messages.Comp.NotificationHttpForm exposing (Texts)
|
import Messages.Comp.NotificationHttpForm exposing (Texts)
|
||||||
import Styles as S
|
import Styles as S
|
||||||
|
import Util.Maybe
|
||||||
|
|
||||||
|
|
||||||
type alias Model =
|
type alias Model =
|
||||||
{ hook : NotificationHttp
|
{ channel : NotificationHttp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
init : Model
|
init : Model
|
||||||
init =
|
init =
|
||||||
{ hook =
|
{ channel =
|
||||||
Data.NotificationChannel.setTypeHttp
|
Data.NotificationChannel.setTypeHttp
|
||||||
Api.Model.NotificationHttp.empty
|
Api.Model.NotificationHttp.empty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
initWith : NotificationHttp -> Model
|
initWith : NotificationHttp -> Model
|
||||||
initWith hook =
|
initWith channel =
|
||||||
{ hook = Data.NotificationChannel.setTypeHttp hook
|
{ channel = Data.NotificationChannel.setTypeHttp channel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type Msg
|
type Msg
|
||||||
= SetUrl String
|
= SetUrl String
|
||||||
|
| SetName String
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -47,26 +49,29 @@ type Msg
|
|||||||
update : Msg -> Model -> ( Model, Maybe NotificationHttp )
|
update : Msg -> Model -> ( Model, Maybe NotificationHttp )
|
||||||
update msg model =
|
update msg model =
|
||||||
let
|
let
|
||||||
newHook =
|
newChannel =
|
||||||
updateHook msg model.hook
|
updateChannel msg model.channel
|
||||||
in
|
in
|
||||||
( { model | hook = newHook }, check newHook )
|
( { model | channel = newChannel }, check newChannel )
|
||||||
|
|
||||||
|
|
||||||
check : NotificationHttp -> Maybe NotificationHttp
|
check : NotificationHttp -> Maybe NotificationHttp
|
||||||
check hook =
|
check channel =
|
||||||
if hook.url == "" then
|
if channel.url == "" then
|
||||||
Nothing
|
Nothing
|
||||||
|
|
||||||
else
|
else
|
||||||
Just hook
|
Just channel
|
||||||
|
|
||||||
|
|
||||||
updateHook : Msg -> NotificationHttp -> NotificationHttp
|
updateChannel : Msg -> NotificationHttp -> NotificationHttp
|
||||||
updateHook msg hook =
|
updateChannel msg channel =
|
||||||
case msg of
|
case msg of
|
||||||
SetUrl s ->
|
SetUrl s ->
|
||||||
{ hook | url = s }
|
{ channel | url = s }
|
||||||
|
|
||||||
|
SetName s ->
|
||||||
|
{ channel | name = Util.Maybe.fromString s }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -77,6 +82,25 @@ view : Texts -> Model -> Html Msg
|
|||||||
view texts model =
|
view texts model =
|
||||||
div []
|
div []
|
||||||
[ div
|
[ div
|
||||||
|
[ class "mb-2"
|
||||||
|
]
|
||||||
|
[ label
|
||||||
|
[ for "name"
|
||||||
|
, class S.inputLabel
|
||||||
|
]
|
||||||
|
[ text texts.basics.name
|
||||||
|
]
|
||||||
|
, input
|
||||||
|
[ type_ "text"
|
||||||
|
, onInput SetName
|
||||||
|
, placeholder texts.basics.name
|
||||||
|
, value (Maybe.withDefault "" model.channel.name)
|
||||||
|
, name "name"
|
||||||
|
, class S.textInput
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
, div
|
||||||
[ class "mb-2"
|
[ class "mb-2"
|
||||||
]
|
]
|
||||||
[ label
|
[ label
|
||||||
@ -90,7 +114,7 @@ view texts model =
|
|||||||
[ type_ "text"
|
[ type_ "text"
|
||||||
, onInput SetUrl
|
, onInput SetUrl
|
||||||
, placeholder texts.httpUrl
|
, placeholder texts.httpUrl
|
||||||
, value model.hook.url
|
, value model.channel.url
|
||||||
, name "httpurl"
|
, name "httpurl"
|
||||||
, class S.textInput
|
, class S.textInput
|
||||||
]
|
]
|
||||||
|
@ -23,13 +23,15 @@ import Html.Events exposing (onInput)
|
|||||||
import Http
|
import Http
|
||||||
import Messages.Comp.NotificationMailForm exposing (Texts)
|
import Messages.Comp.NotificationMailForm exposing (Texts)
|
||||||
import Styles as S
|
import Styles as S
|
||||||
|
import Util.Maybe
|
||||||
|
|
||||||
|
|
||||||
type alias Model =
|
type alias Model =
|
||||||
{ hook : NotificationMail
|
{ channel : NotificationMail
|
||||||
, connectionModel : Comp.Dropdown.Model String
|
, connectionModel : Comp.Dropdown.Model String
|
||||||
, recipients : List String
|
, recipients : List String
|
||||||
, recipientsModel : Comp.EmailInput.Model
|
, recipientsModel : Comp.EmailInput.Model
|
||||||
|
, name : Maybe String
|
||||||
, formState : FormState
|
, formState : FormState
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,10 +48,11 @@ type ValidateError
|
|||||||
|
|
||||||
init : Flags -> ( Model, Cmd Msg )
|
init : Flags -> ( Model, Cmd Msg )
|
||||||
init flags =
|
init flags =
|
||||||
( { hook = Data.NotificationChannel.setTypeMail Api.Model.NotificationMail.empty
|
( { channel = Data.NotificationChannel.setTypeMail Api.Model.NotificationMail.empty
|
||||||
, connectionModel = Comp.Dropdown.makeSingle
|
, connectionModel = Comp.Dropdown.makeSingle
|
||||||
, recipients = []
|
, recipients = []
|
||||||
, recipientsModel = Comp.EmailInput.init
|
, recipientsModel = Comp.EmailInput.init
|
||||||
|
, name = Nothing
|
||||||
, formState = FormStateInitial
|
, formState = FormStateInitial
|
||||||
}
|
}
|
||||||
, Cmd.batch
|
, Cmd.batch
|
||||||
@ -59,17 +62,17 @@ init flags =
|
|||||||
|
|
||||||
|
|
||||||
initWith : Flags -> NotificationMail -> ( Model, Cmd Msg )
|
initWith : Flags -> NotificationMail -> ( Model, Cmd Msg )
|
||||||
initWith flags hook =
|
initWith flags channel =
|
||||||
let
|
let
|
||||||
( mm, mc ) =
|
( mm, mc ) =
|
||||||
init flags
|
init flags
|
||||||
|
|
||||||
( cm, _ ) =
|
( cm, _ ) =
|
||||||
Comp.Dropdown.update (Comp.Dropdown.SetSelection [ hook.connection ]) mm.connectionModel
|
Comp.Dropdown.update (Comp.Dropdown.SetSelection [ channel.connection ]) mm.connectionModel
|
||||||
in
|
in
|
||||||
( { mm
|
( { mm
|
||||||
| hook = Data.NotificationChannel.setTypeMail hook
|
| channel = Data.NotificationChannel.setTypeMail channel
|
||||||
, recipients = hook.recipients
|
, recipients = channel.recipients
|
||||||
, connectionModel = cm
|
, connectionModel = cm
|
||||||
}
|
}
|
||||||
, mc
|
, mc
|
||||||
@ -80,6 +83,7 @@ type Msg
|
|||||||
= ConnResp (Result Http.Error EmailSettingsList)
|
= ConnResp (Result Http.Error EmailSettingsList)
|
||||||
| ConnMsg (Comp.Dropdown.Msg String)
|
| ConnMsg (Comp.Dropdown.Msg String)
|
||||||
| RecipientMsg Comp.EmailInput.Msg
|
| RecipientMsg Comp.EmailInput.Msg
|
||||||
|
| SetName String
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -108,12 +112,12 @@ check model =
|
|||||||
|> List.head
|
|> List.head
|
||||||
|
|
||||||
h =
|
h =
|
||||||
model.hook
|
model.channel
|
||||||
|
|
||||||
makeHook _ rec conn =
|
makeChannel _ rec conn =
|
||||||
{ h | connection = conn, recipients = rec }
|
{ h | connection = conn, recipients = rec, name = model.name }
|
||||||
in
|
in
|
||||||
Maybe.map3 makeHook formState recipients connection
|
Maybe.map3 makeChannel formState recipients connection
|
||||||
|
|
||||||
|
|
||||||
update : Flags -> Msg -> Model -> ( Model, Cmd Msg, Maybe NotificationMail )
|
update : Flags -> Msg -> Model -> ( Model, Cmd Msg, Maybe NotificationMail )
|
||||||
@ -152,6 +156,16 @@ update flags msg model =
|
|||||||
, Nothing
|
, Nothing
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SetName s ->
|
||||||
|
let
|
||||||
|
model_ =
|
||||||
|
{ model | name = Util.Maybe.fromString s }
|
||||||
|
in
|
||||||
|
( model_
|
||||||
|
, Cmd.none
|
||||||
|
, check model_
|
||||||
|
)
|
||||||
|
|
||||||
ConnMsg lm ->
|
ConnMsg lm ->
|
||||||
let
|
let
|
||||||
( cm, cc ) =
|
( cm, cc ) =
|
||||||
@ -201,7 +215,26 @@ view texts settings model =
|
|||||||
}
|
}
|
||||||
in
|
in
|
||||||
div []
|
div []
|
||||||
[ div [ class "mb-4" ]
|
[ div
|
||||||
|
[ class "mb-2"
|
||||||
|
]
|
||||||
|
[ label
|
||||||
|
[ for "name"
|
||||||
|
, class S.inputLabel
|
||||||
|
]
|
||||||
|
[ text texts.basics.name
|
||||||
|
]
|
||||||
|
, input
|
||||||
|
[ type_ "text"
|
||||||
|
, onInput SetName
|
||||||
|
, placeholder texts.basics.name
|
||||||
|
, value (Maybe.withDefault "" model.name)
|
||||||
|
, name "name"
|
||||||
|
, class S.textInput
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
, div [ class "mb-4" ]
|
||||||
[ label [ class S.inputLabel ]
|
[ label [ class S.inputLabel ]
|
||||||
[ text texts.sendVia
|
[ text texts.sendVia
|
||||||
, B.inputRequired
|
, B.inputRequired
|
||||||
|
@ -15,22 +15,23 @@ import Html.Attributes exposing (..)
|
|||||||
import Html.Events exposing (onInput)
|
import Html.Events exposing (onInput)
|
||||||
import Messages.Comp.NotificationMatrixForm exposing (Texts)
|
import Messages.Comp.NotificationMatrixForm exposing (Texts)
|
||||||
import Styles as S
|
import Styles as S
|
||||||
|
import Util.Maybe
|
||||||
|
|
||||||
|
|
||||||
type alias Model =
|
type alias Model =
|
||||||
{ hook : NotificationMatrix
|
{ channel : NotificationMatrix
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
init : Model
|
init : Model
|
||||||
init =
|
init =
|
||||||
{ hook = Data.NotificationChannel.setTypeMatrix Api.Model.NotificationMatrix.empty
|
{ channel = Data.NotificationChannel.setTypeMatrix Api.Model.NotificationMatrix.empty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
initWith : NotificationMatrix -> Model
|
initWith : NotificationMatrix -> Model
|
||||||
initWith hook =
|
initWith channel =
|
||||||
{ hook = Data.NotificationChannel.setTypeMatrix hook
|
{ channel = Data.NotificationChannel.setTypeMatrix channel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -38,6 +39,7 @@ type Msg
|
|||||||
= SetHomeServer String
|
= SetHomeServer String
|
||||||
| SetRoomId String
|
| SetRoomId String
|
||||||
| SetAccessKey String
|
| SetAccessKey String
|
||||||
|
| SetName String
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -47,28 +49,31 @@ type Msg
|
|||||||
update : Msg -> Model -> ( Model, Maybe NotificationMatrix )
|
update : Msg -> Model -> ( Model, Maybe NotificationMatrix )
|
||||||
update msg model =
|
update msg model =
|
||||||
let
|
let
|
||||||
newHook =
|
newChannel =
|
||||||
updateHook msg model.hook
|
updateChannel msg model.channel
|
||||||
in
|
in
|
||||||
( { model | hook = newHook }, check newHook )
|
( { model | channel = newChannel }, check newChannel )
|
||||||
|
|
||||||
|
|
||||||
check : NotificationMatrix -> Maybe NotificationMatrix
|
check : NotificationMatrix -> Maybe NotificationMatrix
|
||||||
check hook =
|
check channel =
|
||||||
Just hook
|
Just channel
|
||||||
|
|
||||||
|
|
||||||
updateHook : Msg -> NotificationMatrix -> NotificationMatrix
|
updateChannel : Msg -> NotificationMatrix -> NotificationMatrix
|
||||||
updateHook msg hook =
|
updateChannel msg channel =
|
||||||
case msg of
|
case msg of
|
||||||
SetHomeServer s ->
|
SetHomeServer s ->
|
||||||
{ hook | homeServer = s }
|
{ channel | homeServer = s }
|
||||||
|
|
||||||
SetRoomId s ->
|
SetRoomId s ->
|
||||||
{ hook | roomId = s }
|
{ channel | roomId = s }
|
||||||
|
|
||||||
SetAccessKey s ->
|
SetAccessKey s ->
|
||||||
{ hook | accessToken = s }
|
{ channel | accessToken = s }
|
||||||
|
|
||||||
|
SetName s ->
|
||||||
|
{ channel | name = Util.Maybe.fromString s }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -79,6 +84,25 @@ view : Texts -> Model -> Html Msg
|
|||||||
view texts model =
|
view texts model =
|
||||||
div []
|
div []
|
||||||
[ div
|
[ div
|
||||||
|
[ class "mb-2"
|
||||||
|
]
|
||||||
|
[ label
|
||||||
|
[ for "name"
|
||||||
|
, class S.inputLabel
|
||||||
|
]
|
||||||
|
[ text texts.basics.name
|
||||||
|
]
|
||||||
|
, input
|
||||||
|
[ type_ "text"
|
||||||
|
, onInput SetName
|
||||||
|
, placeholder texts.basics.name
|
||||||
|
, value (Maybe.withDefault "" model.channel.name)
|
||||||
|
, name "name"
|
||||||
|
, class S.textInput
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
, div
|
||||||
[ class "mb-2"
|
[ class "mb-2"
|
||||||
]
|
]
|
||||||
[ label
|
[ label
|
||||||
@ -92,7 +116,7 @@ view texts model =
|
|||||||
[ type_ "text"
|
[ type_ "text"
|
||||||
, onInput SetHomeServer
|
, onInput SetHomeServer
|
||||||
, placeholder texts.homeServer
|
, placeholder texts.homeServer
|
||||||
, value model.hook.homeServer
|
, value model.channel.homeServer
|
||||||
, name "homeserver"
|
, name "homeserver"
|
||||||
, class S.textInput
|
, class S.textInput
|
||||||
]
|
]
|
||||||
@ -112,7 +136,7 @@ view texts model =
|
|||||||
[ type_ "text"
|
[ type_ "text"
|
||||||
, onInput SetRoomId
|
, onInput SetRoomId
|
||||||
, placeholder texts.roomId
|
, placeholder texts.roomId
|
||||||
, value model.hook.roomId
|
, value model.channel.roomId
|
||||||
, name "roomid"
|
, name "roomid"
|
||||||
, class S.textInput
|
, class S.textInput
|
||||||
]
|
]
|
||||||
@ -131,7 +155,7 @@ view texts model =
|
|||||||
, textarea
|
, textarea
|
||||||
[ onInput SetAccessKey
|
[ onInput SetAccessKey
|
||||||
, placeholder texts.accessKey
|
, placeholder texts.accessKey
|
||||||
, value model.hook.accessToken
|
, value model.channel.accessToken
|
||||||
, name "accesskey"
|
, name "accesskey"
|
||||||
, class S.textAreaInput
|
, class S.textAreaInput
|
||||||
]
|
]
|
||||||
|
@ -9,10 +9,10 @@ module Comp.NotificationTest exposing (Model, Msg, ViewConfig, init, update, vie
|
|||||||
|
|
||||||
import Api
|
import Api
|
||||||
import Api.Model.NotificationChannelTestResult exposing (NotificationChannelTestResult)
|
import Api.Model.NotificationChannelTestResult exposing (NotificationChannelTestResult)
|
||||||
|
import Api.Model.NotificationHook exposing (NotificationHook)
|
||||||
import Comp.Basic as B
|
import Comp.Basic as B
|
||||||
import Comp.MenuBar as MB
|
import Comp.MenuBar as MB
|
||||||
import Data.Flags exposing (Flags)
|
import Data.Flags exposing (Flags)
|
||||||
import Data.NotificationHook exposing (NotificationHook)
|
|
||||||
import Html exposing (..)
|
import Html exposing (..)
|
||||||
import Html.Attributes exposing (..)
|
import Html.Attributes exposing (..)
|
||||||
import Html.Events exposing (onClick)
|
import Html.Events exposing (onClick)
|
||||||
|
@ -16,16 +16,15 @@ module Comp.PeriodicQueryTaskForm exposing
|
|||||||
, view
|
, view
|
||||||
)
|
)
|
||||||
|
|
||||||
|
import Api.Model.PeriodicQuerySettings exposing (PeriodicQuerySettings)
|
||||||
import Comp.Basic as B
|
import Comp.Basic as B
|
||||||
import Comp.BookmarkDropdown
|
import Comp.BookmarkDropdown
|
||||||
import Comp.CalEventInput
|
import Comp.CalEventInput
|
||||||
import Comp.ChannelForm
|
import Comp.ChannelRefInput
|
||||||
import Comp.MenuBar as MB
|
import Comp.MenuBar as MB
|
||||||
import Comp.PowerSearchInput
|
import Comp.PowerSearchInput
|
||||||
import Data.CalEvent exposing (CalEvent)
|
import Data.CalEvent exposing (CalEvent)
|
||||||
import Data.ChannelType exposing (ChannelType)
|
|
||||||
import Data.Flags exposing (Flags)
|
import Data.Flags exposing (Flags)
|
||||||
import Data.PeriodicQuerySettings exposing (PeriodicQuerySettings)
|
|
||||||
import Data.UiSettings exposing (UiSettings)
|
import Data.UiSettings exposing (UiSettings)
|
||||||
import Data.Validated exposing (Validated(..))
|
import Data.Validated exposing (Validated(..))
|
||||||
import Html exposing (..)
|
import Html exposing (..)
|
||||||
@ -44,7 +43,7 @@ type alias Model =
|
|||||||
, schedule : Maybe CalEvent
|
, schedule : Maybe CalEvent
|
||||||
, scheduleModel : Comp.CalEventInput.Model
|
, scheduleModel : Comp.CalEventInput.Model
|
||||||
, queryModel : Comp.PowerSearchInput.Model
|
, queryModel : Comp.PowerSearchInput.Model
|
||||||
, channelModel : Comp.ChannelForm.Model
|
, channelModel : Comp.ChannelRefInput.Model
|
||||||
, bookmarkDropdown : Comp.BookmarkDropdown.Model
|
, bookmarkDropdown : Comp.BookmarkDropdown.Model
|
||||||
, contentStart : Maybe String
|
, contentStart : Maybe String
|
||||||
, formState : FormState
|
, formState : FormState
|
||||||
@ -78,7 +77,7 @@ type Msg
|
|||||||
| ToggleEnabled
|
| ToggleEnabled
|
||||||
| CalEventMsg Comp.CalEventInput.Msg
|
| CalEventMsg Comp.CalEventInput.Msg
|
||||||
| QueryMsg Comp.PowerSearchInput.Msg
|
| QueryMsg Comp.PowerSearchInput.Msg
|
||||||
| ChannelMsg Comp.ChannelForm.Msg
|
| ChannelMsg Comp.ChannelRefInput.Msg
|
||||||
| BookmarkMsg Comp.BookmarkDropdown.Msg
|
| BookmarkMsg Comp.BookmarkDropdown.Msg
|
||||||
| SetContentStart String
|
| SetContentStart String
|
||||||
| StartOnce
|
| StartOnce
|
||||||
@ -105,7 +104,7 @@ initWith flags s =
|
|||||||
Comp.PowerSearchInput.init
|
Comp.PowerSearchInput.init
|
||||||
|
|
||||||
( cfm, cfc ) =
|
( cfm, cfc ) =
|
||||||
Comp.ChannelForm.initWith flags s.channel
|
Comp.ChannelRefInput.initSelected flags s.channels
|
||||||
|
|
||||||
( bm, bc ) =
|
( bm, bc ) =
|
||||||
Comp.BookmarkDropdown.init flags s.bookmark
|
Comp.BookmarkDropdown.init flags s.bookmark
|
||||||
@ -132,8 +131,8 @@ initWith flags s =
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
init : Flags -> ChannelType -> ( Model, Cmd Msg )
|
init : Flags -> ( Model, Cmd Msg )
|
||||||
init flags ct =
|
init flags =
|
||||||
let
|
let
|
||||||
initialSchedule =
|
initialSchedule =
|
||||||
Data.CalEvent.everyMonth
|
Data.CalEvent.everyMonth
|
||||||
@ -142,12 +141,12 @@ init flags ct =
|
|||||||
Comp.CalEventInput.init flags initialSchedule
|
Comp.CalEventInput.init flags initialSchedule
|
||||||
|
|
||||||
( cfm, cfc ) =
|
( cfm, cfc ) =
|
||||||
Comp.ChannelForm.init flags ct
|
Comp.ChannelRefInput.init flags
|
||||||
|
|
||||||
( bm, bc ) =
|
( bm, bc ) =
|
||||||
Comp.BookmarkDropdown.init flags Nothing
|
Comp.BookmarkDropdown.init flags Nothing
|
||||||
in
|
in
|
||||||
( { settings = Data.PeriodicQuerySettings.empty ct
|
( { settings = Api.Model.PeriodicQuerySettings.empty
|
||||||
, enabled = False
|
, enabled = False
|
||||||
, schedule = Just initialSchedule
|
, schedule = Just initialSchedule
|
||||||
, scheduleModel = sm
|
, scheduleModel = sm
|
||||||
@ -210,16 +209,22 @@ makeSettings model =
|
|||||||
Result.Ok ( qstr, bm )
|
Result.Ok ( qstr, bm )
|
||||||
|
|
||||||
channelM =
|
channelM =
|
||||||
Result.fromMaybe
|
let
|
||||||
ValidateChannelRequired
|
list =
|
||||||
(Comp.ChannelForm.getChannel model.channelModel)
|
Comp.ChannelRefInput.getSelected model.channelModel
|
||||||
|
in
|
||||||
|
if list == [] then
|
||||||
|
Err ValidateChannelRequired
|
||||||
|
|
||||||
make timer channel q =
|
else
|
||||||
|
Ok list
|
||||||
|
|
||||||
|
make timer channels q =
|
||||||
{ prev
|
{ prev
|
||||||
| enabled = model.enabled
|
| enabled = model.enabled
|
||||||
, schedule = Data.CalEvent.makeEvent timer
|
, schedule = Data.CalEvent.makeEvent timer
|
||||||
, summary = model.summary
|
, summary = model.summary
|
||||||
, channel = channel
|
, channels = channels
|
||||||
, query = Tuple.first q
|
, query = Tuple.first q
|
||||||
, bookmark = Tuple.second q
|
, bookmark = Tuple.second q
|
||||||
, contentStart = model.contentStart
|
, contentStart = model.contentStart
|
||||||
@ -285,7 +290,7 @@ update flags msg model =
|
|||||||
ChannelMsg lm ->
|
ChannelMsg lm ->
|
||||||
let
|
let
|
||||||
( cfm, cfc ) =
|
( cfm, cfc ) =
|
||||||
Comp.ChannelForm.update flags lm model.channelModel
|
Comp.ChannelRefInput.update lm model.channelModel
|
||||||
in
|
in
|
||||||
{ model = { model | channelModel = cfm }
|
{ model = { model | channelModel = cfm }
|
||||||
, action = NoAction
|
, action = NoAction
|
||||||
@ -536,9 +541,9 @@ view texts extraClasses settings model =
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
, div [ class "mb-4" ]
|
, div [ class "mb-4" ]
|
||||||
[ formHeader (texts.channelHeader (Comp.ChannelForm.channelType model.channelModel)) False
|
[ formHeader texts.channelHeader True
|
||||||
, Html.map ChannelMsg
|
, Html.map ChannelMsg
|
||||||
(Comp.ChannelForm.view texts.channelForm settings model.channelModel)
|
(Comp.ChannelRefInput.view texts.channelRef settings model.channelModel)
|
||||||
]
|
]
|
||||||
, div [ class "mb-4" ]
|
, div [ class "mb-4" ]
|
||||||
[ formHeader texts.queryLabel True
|
[ formHeader texts.queryLabel True
|
||||||
|
@ -14,15 +14,17 @@ module Comp.PeriodicQueryTaskList exposing
|
|||||||
, view2
|
, view2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
import Api.Model.PeriodicQuerySettings exposing (PeriodicQuerySettings)
|
||||||
import Comp.Basic as B
|
import Comp.Basic as B
|
||||||
|
import Data.ChannelRef
|
||||||
import Data.ChannelType
|
import Data.ChannelType
|
||||||
import Data.NotificationChannel
|
import Data.NotificationChannel
|
||||||
import Data.PeriodicQuerySettings exposing (PeriodicQuerySettings)
|
|
||||||
import Html exposing (..)
|
import Html exposing (..)
|
||||||
import Html.Attributes exposing (..)
|
import Html.Attributes exposing (..)
|
||||||
import Messages.Comp.PeriodicQueryTaskList exposing (Texts)
|
import Messages.Comp.PeriodicQueryTaskList exposing (Texts)
|
||||||
import Styles as S
|
import Styles as S
|
||||||
import Util.Html
|
import Util.Html
|
||||||
|
import Util.List
|
||||||
|
|
||||||
|
|
||||||
type alias Model =
|
type alias Model =
|
||||||
@ -94,9 +96,7 @@ viewItem2 texts item =
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
, td [ class "text-left py-4 md:py-2" ]
|
, td [ class "text-left py-4 md:py-2" ]
|
||||||
[ Data.NotificationChannel.channelType item.channel
|
[ div [ class " space-x-1" ]
|
||||||
|> Maybe.map Data.ChannelType.asString
|
(Data.ChannelRef.asDivs texts.channelType [ class "inline" ] item.channels)
|
||||||
|> Maybe.withDefault "-"
|
|
||||||
|> text
|
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
@ -15,17 +15,16 @@ module Comp.PeriodicQueryTaskManage exposing
|
|||||||
|
|
||||||
import Api
|
import Api
|
||||||
import Api.Model.BasicResult exposing (BasicResult)
|
import Api.Model.BasicResult exposing (BasicResult)
|
||||||
|
import Api.Model.PeriodicQuerySettings exposing (PeriodicQuerySettings)
|
||||||
import Comp.ChannelMenu
|
import Comp.ChannelMenu
|
||||||
import Comp.MenuBar as MB
|
import Comp.MenuBar as MB
|
||||||
import Comp.PeriodicQueryTaskForm
|
import Comp.PeriodicQueryTaskForm
|
||||||
import Comp.PeriodicQueryTaskList
|
import Comp.PeriodicQueryTaskList
|
||||||
import Data.ChannelType exposing (ChannelType)
|
import Data.ChannelType exposing (ChannelType)
|
||||||
import Data.Flags exposing (Flags)
|
import Data.Flags exposing (Flags)
|
||||||
import Data.PeriodicQuerySettings exposing (PeriodicQuerySettings)
|
|
||||||
import Data.UiSettings exposing (UiSettings)
|
import Data.UiSettings exposing (UiSettings)
|
||||||
import Html exposing (..)
|
import Html exposing (..)
|
||||||
import Html.Attributes exposing (..)
|
import Html.Attributes exposing (..)
|
||||||
import Html.Events exposing (onClick)
|
|
||||||
import Http
|
import Http
|
||||||
import Messages.Comp.PeriodicQueryTaskManage exposing (Texts)
|
import Messages.Comp.PeriodicQueryTaskManage exposing (Texts)
|
||||||
import Styles as S
|
import Styles as S
|
||||||
@ -36,7 +35,6 @@ type alias Model =
|
|||||||
, detailModel : Maybe Comp.PeriodicQueryTaskForm.Model
|
, detailModel : Maybe Comp.PeriodicQueryTaskForm.Model
|
||||||
, items : List PeriodicQuerySettings
|
, items : List PeriodicQuerySettings
|
||||||
, formState : FormState
|
, formState : FormState
|
||||||
, channelMenuOpen : Bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -58,9 +56,8 @@ type Msg
|
|||||||
= ListMsg Comp.PeriodicQueryTaskList.Msg
|
= ListMsg Comp.PeriodicQueryTaskList.Msg
|
||||||
| DetailMsg Comp.PeriodicQueryTaskForm.Msg
|
| DetailMsg Comp.PeriodicQueryTaskForm.Msg
|
||||||
| GetDataResp (Result Http.Error (List PeriodicQuerySettings))
|
| GetDataResp (Result Http.Error (List PeriodicQuerySettings))
|
||||||
| NewTaskInit ChannelType
|
| NewTaskInit
|
||||||
| SubmitResp SubmitType (Result Http.Error BasicResult)
|
| SubmitResp SubmitType (Result Http.Error BasicResult)
|
||||||
| ToggleChannelMenu
|
|
||||||
|
|
||||||
|
|
||||||
initModel : Model
|
initModel : Model
|
||||||
@ -69,7 +66,6 @@ initModel =
|
|||||||
, detailModel = Nothing
|
, detailModel = Nothing
|
||||||
, items = []
|
, items = []
|
||||||
, formState = FormStateInitial
|
, formState = FormStateInitial
|
||||||
, channelMenuOpen = False
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -195,12 +191,12 @@ update flags msg model =
|
|||||||
Nothing ->
|
Nothing ->
|
||||||
( model, Cmd.none, Sub.none )
|
( model, Cmd.none, Sub.none )
|
||||||
|
|
||||||
NewTaskInit ct ->
|
NewTaskInit ->
|
||||||
let
|
let
|
||||||
( mm, mc ) =
|
( mm, mc ) =
|
||||||
Comp.PeriodicQueryTaskForm.init flags ct
|
Comp.PeriodicQueryTaskForm.init flags
|
||||||
in
|
in
|
||||||
( { model | detailModel = Just mm, channelMenuOpen = False }, Cmd.map DetailMsg mc, Sub.none )
|
( { model | detailModel = Just mm }, Cmd.map DetailMsg mc, Sub.none )
|
||||||
|
|
||||||
SubmitResp submitType (Ok res) ->
|
SubmitResp submitType (Ok res) ->
|
||||||
( { model
|
( { model
|
||||||
@ -231,9 +227,6 @@ update flags msg model =
|
|||||||
, Sub.none
|
, Sub.none
|
||||||
)
|
)
|
||||||
|
|
||||||
ToggleChannelMenu ->
|
|
||||||
( { model | channelMenuOpen = not model.channelMenuOpen }, Cmd.none, Sub.none )
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- View2
|
--- View2
|
||||||
@ -301,18 +294,15 @@ viewForm2 texts settings model =
|
|||||||
|
|
||||||
viewList2 : Texts -> Model -> List (Html Msg)
|
viewList2 : Texts -> Model -> List (Html Msg)
|
||||||
viewList2 texts model =
|
viewList2 texts model =
|
||||||
let
|
|
||||||
menuModel =
|
|
||||||
{ menuOpen = model.channelMenuOpen
|
|
||||||
, toggleMenu = ToggleChannelMenu
|
|
||||||
, menuLabel = texts.newTask
|
|
||||||
, onItem = NewTaskInit
|
|
||||||
}
|
|
||||||
in
|
|
||||||
[ MB.view
|
[ MB.view
|
||||||
{ start = []
|
{ start = []
|
||||||
, end =
|
, end =
|
||||||
[ Comp.ChannelMenu.channelMenu texts.channelType menuModel
|
[ MB.PrimaryButton
|
||||||
|
{ tagger = NewTaskInit
|
||||||
|
, title = texts.newTask
|
||||||
|
, icon = Just "fa fa-plus"
|
||||||
|
, label = texts.newTask
|
||||||
|
}
|
||||||
]
|
]
|
||||||
, rootClasses = "mb-4"
|
, rootClasses = "mb-4"
|
||||||
}
|
}
|
||||||
|
@ -7,27 +7,63 @@
|
|||||||
|
|
||||||
module Data.ChannelRef exposing (..)
|
module Data.ChannelRef exposing (..)
|
||||||
|
|
||||||
|
import Api.Model.NotificationChannelRef exposing (NotificationChannelRef)
|
||||||
import Data.ChannelType exposing (ChannelType)
|
import Data.ChannelType exposing (ChannelType)
|
||||||
import Json.Decode as D
|
import Html exposing (Attribute, Html, div, span, text)
|
||||||
import Json.Encode as E
|
import Html.Attributes exposing (class)
|
||||||
|
import Messages.Data.ChannelType as M
|
||||||
|
import Util.List
|
||||||
|
|
||||||
|
|
||||||
type alias ChannelRef =
|
channelType : NotificationChannelRef -> Maybe ChannelType
|
||||||
{ id : String
|
channelType ref =
|
||||||
, channelType : ChannelType
|
Data.ChannelType.fromString ref.channelType
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
decoder : D.Decoder ChannelRef
|
split : M.Texts -> NotificationChannelRef -> ( String, String )
|
||||||
decoder =
|
split texts ref =
|
||||||
D.map2 ChannelRef
|
let
|
||||||
(D.field "id" D.string)
|
chStr =
|
||||||
(D.field "channelType" Data.ChannelType.decoder)
|
channelType ref
|
||||||
|
|> Maybe.map texts
|
||||||
|
|> Maybe.withDefault ref.channelType
|
||||||
|
|
||||||
|
name =
|
||||||
|
Maybe.withDefault (String.slice 0 6 ref.id) ref.name
|
||||||
|
in
|
||||||
|
( chStr, name )
|
||||||
|
|
||||||
|
|
||||||
encode : ChannelRef -> E.Value
|
asString : M.Texts -> NotificationChannelRef -> String
|
||||||
encode cref =
|
asString texts ref =
|
||||||
E.object
|
let
|
||||||
[ ( "id", E.string cref.id )
|
( chStr, name ) =
|
||||||
, ( "channelType", Data.ChannelType.encode cref.channelType )
|
split texts ref
|
||||||
|
in
|
||||||
|
chStr ++ " (" ++ name ++ ")"
|
||||||
|
|
||||||
|
|
||||||
|
asDiv : List (Attribute msg) -> M.Texts -> NotificationChannelRef -> Html msg
|
||||||
|
asDiv attrs texts ref =
|
||||||
|
let
|
||||||
|
( chStr, name ) =
|
||||||
|
split texts ref
|
||||||
|
in
|
||||||
|
div attrs
|
||||||
|
[ text chStr
|
||||||
|
, span [ class "ml-1 text-xs opacity-75" ]
|
||||||
|
[ text ("(" ++ name ++ ")")
|
||||||
]
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
asStringJoined : M.Texts -> List NotificationChannelRef -> String
|
||||||
|
asStringJoined texts refs =
|
||||||
|
List.map (asString texts) refs
|
||||||
|
|> Util.List.distinct
|
||||||
|
|> String.join ", "
|
||||||
|
|
||||||
|
|
||||||
|
asDivs : M.Texts -> List (Attribute msg) -> List NotificationChannelRef -> List (Html msg)
|
||||||
|
asDivs texts inner refs =
|
||||||
|
List.map (asDiv inner texts) refs
|
||||||
|
@ -12,17 +12,18 @@ module Data.NotificationChannel exposing
|
|||||||
, decoder
|
, decoder
|
||||||
, empty
|
, empty
|
||||||
, encode
|
, encode
|
||||||
|
, getRef
|
||||||
, setTypeGotify
|
, setTypeGotify
|
||||||
, setTypeHttp
|
, setTypeHttp
|
||||||
, setTypeMail
|
, setTypeMail
|
||||||
, setTypeMatrix
|
, setTypeMatrix
|
||||||
)
|
)
|
||||||
|
|
||||||
|
import Api.Model.NotificationChannelRef exposing (NotificationChannelRef)
|
||||||
import Api.Model.NotificationGotify exposing (NotificationGotify)
|
import Api.Model.NotificationGotify exposing (NotificationGotify)
|
||||||
import Api.Model.NotificationHttp exposing (NotificationHttp)
|
import Api.Model.NotificationHttp exposing (NotificationHttp)
|
||||||
import Api.Model.NotificationMail exposing (NotificationMail)
|
import Api.Model.NotificationMail exposing (NotificationMail)
|
||||||
import Api.Model.NotificationMatrix exposing (NotificationMatrix)
|
import Api.Model.NotificationMatrix exposing (NotificationMatrix)
|
||||||
import Data.ChannelRef exposing (ChannelRef)
|
|
||||||
import Data.ChannelType exposing (ChannelType)
|
import Data.ChannelType exposing (ChannelType)
|
||||||
import Json.Decode as D
|
import Json.Decode as D
|
||||||
import Json.Encode as E
|
import Json.Encode as E
|
||||||
@ -33,7 +34,6 @@ type NotificationChannel
|
|||||||
| Mail NotificationMail
|
| Mail NotificationMail
|
||||||
| Gotify NotificationGotify
|
| Gotify NotificationGotify
|
||||||
| Http NotificationHttp
|
| Http NotificationHttp
|
||||||
| Ref ChannelRef
|
|
||||||
|
|
||||||
|
|
||||||
empty : ChannelType -> NotificationChannel
|
empty : ChannelType -> NotificationChannel
|
||||||
@ -87,46 +87,49 @@ decoder =
|
|||||||
, D.map Mail Api.Model.NotificationMail.decoder
|
, D.map Mail Api.Model.NotificationMail.decoder
|
||||||
, D.map Matrix Api.Model.NotificationMatrix.decoder
|
, D.map Matrix Api.Model.NotificationMatrix.decoder
|
||||||
, D.map Http Api.Model.NotificationHttp.decoder
|
, D.map Http Api.Model.NotificationHttp.decoder
|
||||||
, D.map Ref Data.ChannelRef.decoder
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
fold :
|
||||||
|
(NotificationMail -> a)
|
||||||
|
-> (NotificationGotify -> a)
|
||||||
|
-> (NotificationMatrix -> a)
|
||||||
|
-> (NotificationHttp -> a)
|
||||||
|
-> NotificationChannel
|
||||||
|
-> a
|
||||||
|
fold fa fb fc fd channel =
|
||||||
|
case channel of
|
||||||
|
Mail ch ->
|
||||||
|
fa ch
|
||||||
|
|
||||||
|
Gotify ch ->
|
||||||
|
fb ch
|
||||||
|
|
||||||
|
Matrix ch ->
|
||||||
|
fc ch
|
||||||
|
|
||||||
|
Http ch ->
|
||||||
|
fd ch
|
||||||
|
|
||||||
|
|
||||||
encode : NotificationChannel -> E.Value
|
encode : NotificationChannel -> E.Value
|
||||||
encode channel =
|
encode channel =
|
||||||
case channel of
|
fold
|
||||||
Matrix ch ->
|
Api.Model.NotificationMail.encode
|
||||||
Api.Model.NotificationMatrix.encode ch
|
Api.Model.NotificationGotify.encode
|
||||||
|
Api.Model.NotificationMatrix.encode
|
||||||
Mail ch ->
|
Api.Model.NotificationHttp.encode
|
||||||
Api.Model.NotificationMail.encode ch
|
channel
|
||||||
|
|
||||||
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 : NotificationChannel -> Maybe ChannelType
|
||||||
channelType ch =
|
channelType ch =
|
||||||
case ch of
|
fold
|
||||||
Matrix m ->
|
(.channelType >> Data.ChannelType.fromString)
|
||||||
Data.ChannelType.fromString m.channelType
|
(.channelType >> Data.ChannelType.fromString)
|
||||||
|
(.channelType >> Data.ChannelType.fromString)
|
||||||
Mail m ->
|
(.channelType >> Data.ChannelType.fromString)
|
||||||
Data.ChannelType.fromString m.channelType
|
ch
|
||||||
|
|
||||||
Gotify m ->
|
|
||||||
Data.ChannelType.fromString m.channelType
|
|
||||||
|
|
||||||
Http m ->
|
|
||||||
Data.ChannelType.fromString m.channelType
|
|
||||||
|
|
||||||
Ref m ->
|
|
||||||
Just m.channelType
|
|
||||||
|
|
||||||
|
|
||||||
asString : NotificationChannel -> String
|
asString : NotificationChannel -> String
|
||||||
@ -144,5 +147,12 @@ asString channel =
|
|||||||
Http ch ->
|
Http ch ->
|
||||||
"Http @ " ++ ch.url
|
"Http @ " ++ ch.url
|
||||||
|
|
||||||
Ref ch ->
|
|
||||||
"Ref(" ++ Data.ChannelType.asString ch.channelType ++ "/" ++ ch.id ++ ")"
|
getRef : NotificationChannel -> NotificationChannelRef
|
||||||
|
getRef channel =
|
||||||
|
fold
|
||||||
|
(\c -> NotificationChannelRef c.id c.channelType c.name)
|
||||||
|
(\c -> NotificationChannelRef c.id c.channelType c.name)
|
||||||
|
(\c -> NotificationChannelRef c.id c.channelType c.name)
|
||||||
|
(\c -> NotificationChannelRef c.id c.channelType c.name)
|
||||||
|
channel
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
{-
|
|
||||||
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
|
|
@ -1,77 +0,0 @@
|
|||||||
{-
|
|
||||||
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 )
|
|
||||||
]
|
|
@ -1,65 +0,0 @@
|
|||||||
{-
|
|
||||||
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 : Maybe String
|
|
||||||
, bookmark : Maybe String
|
|
||||||
, contentStart : Maybe String
|
|
||||||
, schedule : String
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
empty : ChannelType -> PeriodicQuerySettings
|
|
||||||
empty ct =
|
|
||||||
{ id = ""
|
|
||||||
, enabled = False
|
|
||||||
, summary = Nothing
|
|
||||||
, channel = Data.NotificationChannel.empty ct
|
|
||||||
, query = Nothing
|
|
||||||
, bookmark = Nothing
|
|
||||||
, contentStart = Nothing
|
|
||||||
, schedule = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
decoder : D.Decoder PeriodicQuerySettings
|
|
||||||
decoder =
|
|
||||||
D.map8 PeriodicQuerySettings
|
|
||||||
(D.field "id" D.string)
|
|
||||||
(D.field "enabled" D.bool)
|
|
||||||
(D.maybe (D.field "summary" D.string))
|
|
||||||
(D.field "channel" Data.NotificationChannel.decoder)
|
|
||||||
(D.maybe (D.field "query" D.string))
|
|
||||||
(D.maybe (D.field "bookmark" D.string))
|
|
||||||
(D.maybe (D.field "contentStart" 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", Maybe.map E.string s.query |> Maybe.withDefault E.null )
|
|
||||||
, ( "bookmark", Maybe.map E.string s.bookmark |> Maybe.withDefault E.null )
|
|
||||||
, ( "contentStart", Maybe.map E.string s.contentStart |> Maybe.withDefault E.null )
|
|
||||||
, ( "schedule", E.string s.schedule )
|
|
||||||
]
|
|
@ -0,0 +1,41 @@
|
|||||||
|
{-
|
||||||
|
Copyright 2020 Eike K. & Contributors
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
-}
|
||||||
|
|
||||||
|
|
||||||
|
module Messages.Comp.ChannelRefInput exposing
|
||||||
|
( Texts
|
||||||
|
, de
|
||||||
|
, gb
|
||||||
|
)
|
||||||
|
|
||||||
|
import Messages.Basics
|
||||||
|
import Messages.Data.ChannelType
|
||||||
|
|
||||||
|
|
||||||
|
type alias Texts =
|
||||||
|
{ basics : Messages.Basics.Texts
|
||||||
|
, channelType : Messages.Data.ChannelType.Texts
|
||||||
|
, placeholder : String
|
||||||
|
, noCategory : String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
gb : Texts
|
||||||
|
gb =
|
||||||
|
{ basics = Messages.Basics.gb
|
||||||
|
, channelType = Messages.Data.ChannelType.gb
|
||||||
|
, placeholder = "Choose…"
|
||||||
|
, noCategory = "No channel"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
de : Texts
|
||||||
|
de =
|
||||||
|
{ basics = Messages.Basics.de
|
||||||
|
, channelType = Messages.Data.ChannelType.de
|
||||||
|
, placeholder = "Wähle…"
|
||||||
|
, noCategory = "Kein Kanal"
|
||||||
|
}
|
@ -15,6 +15,7 @@ import Http
|
|||||||
import Messages.Basics
|
import Messages.Basics
|
||||||
import Messages.Comp.CalEventInput
|
import Messages.Comp.CalEventInput
|
||||||
import Messages.Comp.ChannelForm
|
import Messages.Comp.ChannelForm
|
||||||
|
import Messages.Comp.ChannelRefInput
|
||||||
import Messages.Comp.HttpError
|
import Messages.Comp.HttpError
|
||||||
import Messages.Comp.TagDropdown
|
import Messages.Comp.TagDropdown
|
||||||
import Messages.Data.ChannelType
|
import Messages.Data.ChannelType
|
||||||
@ -26,6 +27,8 @@ type alias Texts =
|
|||||||
, httpError : Http.Error -> String
|
, httpError : Http.Error -> String
|
||||||
, channelForm : Messages.Comp.ChannelForm.Texts
|
, channelForm : Messages.Comp.ChannelForm.Texts
|
||||||
, tagDropdown : Messages.Comp.TagDropdown.Texts
|
, tagDropdown : Messages.Comp.TagDropdown.Texts
|
||||||
|
, channelType : Messages.Data.ChannelType.Texts
|
||||||
|
, channelRef : Messages.Comp.ChannelRefInput.Texts
|
||||||
, reallyDeleteTask : String
|
, reallyDeleteTask : String
|
||||||
, startOnce : String
|
, startOnce : String
|
||||||
, startTaskNow : String
|
, startTaskNow : String
|
||||||
@ -50,7 +53,7 @@ type alias Texts =
|
|||||||
, recipientsRequired : String
|
, recipientsRequired : String
|
||||||
, queryLabel : String
|
, queryLabel : String
|
||||||
, channelRequired : String
|
, channelRequired : String
|
||||||
, channelHeader : Messages.Data.ChannelType.Texts
|
, channelHeader : String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -61,6 +64,8 @@ gb =
|
|||||||
, httpError = Messages.Comp.HttpError.gb
|
, httpError = Messages.Comp.HttpError.gb
|
||||||
, channelForm = Messages.Comp.ChannelForm.gb
|
, channelForm = Messages.Comp.ChannelForm.gb
|
||||||
, tagDropdown = Messages.Comp.TagDropdown.gb
|
, tagDropdown = Messages.Comp.TagDropdown.gb
|
||||||
|
, channelType = Messages.Data.ChannelType.gb
|
||||||
|
, channelRef = Messages.Comp.ChannelRefInput.gb
|
||||||
, reallyDeleteTask = "Really delete this notification task?"
|
, reallyDeleteTask = "Really delete this notification task?"
|
||||||
, startOnce = "Start Once"
|
, startOnce = "Start Once"
|
||||||
, startTaskNow = "Start this task now"
|
, startTaskNow = "Start this task now"
|
||||||
@ -89,7 +94,7 @@ gb =
|
|||||||
, recipientsRequired = "At least one recipient is required."
|
, recipientsRequired = "At least one recipient is required."
|
||||||
, queryLabel = "Query"
|
, queryLabel = "Query"
|
||||||
, channelRequired = "A valid channel must be given."
|
, channelRequired = "A valid channel must be given."
|
||||||
, channelHeader = \ct -> "Connection details for " ++ Messages.Data.ChannelType.gb ct
|
, channelHeader = "Channels"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -98,8 +103,10 @@ de =
|
|||||||
{ basics = Messages.Basics.de
|
{ basics = Messages.Basics.de
|
||||||
, calEventInput = Messages.Comp.CalEventInput.de
|
, calEventInput = Messages.Comp.CalEventInput.de
|
||||||
, httpError = Messages.Comp.HttpError.de
|
, httpError = Messages.Comp.HttpError.de
|
||||||
, channelForm = Messages.Comp.ChannelForm.gb
|
, channelForm = Messages.Comp.ChannelForm.de
|
||||||
, tagDropdown = Messages.Comp.TagDropdown.gb
|
, tagDropdown = Messages.Comp.TagDropdown.de
|
||||||
|
, channelType = Messages.Data.ChannelType.de
|
||||||
|
, channelRef = Messages.Comp.ChannelRefInput.de
|
||||||
, reallyDeleteTask = "Diesen Benachrichtigungsauftrag wirklich löschen?"
|
, reallyDeleteTask = "Diesen Benachrichtigungsauftrag wirklich löschen?"
|
||||||
, startOnce = "Jetzt starten"
|
, startOnce = "Jetzt starten"
|
||||||
, startTaskNow = "Starte den Auftrag sofort"
|
, startTaskNow = "Starte den Auftrag sofort"
|
||||||
@ -128,5 +135,5 @@ de =
|
|||||||
, recipientsRequired = "Mindestens ein Empfänger muss angegeben werden."
|
, recipientsRequired = "Mindestens ein Empfänger muss angegeben werden."
|
||||||
, queryLabel = "Abfrage"
|
, queryLabel = "Abfrage"
|
||||||
, channelRequired = "Ein Versandkanal muss angegeben werden."
|
, channelRequired = "Ein Versandkanal muss angegeben werden."
|
||||||
, channelHeader = \ct -> "Details für " ++ Messages.Data.ChannelType.de ct
|
, channelHeader = "Kanäle"
|
||||||
}
|
}
|
||||||
|
@ -12,32 +12,33 @@ module Messages.Comp.DueItemsTaskList exposing
|
|||||||
)
|
)
|
||||||
|
|
||||||
import Messages.Basics
|
import Messages.Basics
|
||||||
|
import Messages.Data.ChannelType
|
||||||
|
|
||||||
|
|
||||||
type alias Texts =
|
type alias Texts =
|
||||||
{ basics : Messages.Basics.Texts
|
{ basics : Messages.Basics.Texts
|
||||||
|
, channelType : Messages.Data.ChannelType.Texts
|
||||||
, summary : String
|
, summary : String
|
||||||
, schedule : String
|
, schedule : String
|
||||||
, connection : String
|
, connection : String
|
||||||
, recipients : String
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
gb : Texts
|
gb : Texts
|
||||||
gb =
|
gb =
|
||||||
{ basics = Messages.Basics.gb
|
{ basics = Messages.Basics.gb
|
||||||
|
, channelType = Messages.Data.ChannelType.gb
|
||||||
, summary = "Summary"
|
, summary = "Summary"
|
||||||
, schedule = "Schedule"
|
, schedule = "Schedule"
|
||||||
, connection = "Connection"
|
, connection = "Channel"
|
||||||
, recipients = "Recipients"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
de : Texts
|
de : Texts
|
||||||
de =
|
de =
|
||||||
{ basics = Messages.Basics.de
|
{ basics = Messages.Basics.de
|
||||||
|
, channelType = Messages.Data.ChannelType.de
|
||||||
, summary = "Kurzbeschreibung"
|
, summary = "Kurzbeschreibung"
|
||||||
, schedule = "Zeitplan"
|
, schedule = "Zeitplan"
|
||||||
, connection = "Verbindung"
|
, connection = "Kanal"
|
||||||
, recipients = "Empfänger"
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,83 @@
|
|||||||
|
{-
|
||||||
|
Copyright 2020 Eike K. & Contributors
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
-}
|
||||||
|
|
||||||
|
|
||||||
|
module Messages.Comp.NotificationChannelManage exposing (Texts, de, gb)
|
||||||
|
|
||||||
|
import Http
|
||||||
|
import Messages.Basics
|
||||||
|
import Messages.Comp.ChannelForm
|
||||||
|
import Messages.Comp.HttpError
|
||||||
|
import Messages.Comp.NotificationChannelTable
|
||||||
|
import Messages.Data.ChannelType
|
||||||
|
|
||||||
|
|
||||||
|
type alias Texts =
|
||||||
|
{ basics : Messages.Basics.Texts
|
||||||
|
, notificationForm : Messages.Comp.ChannelForm.Texts
|
||||||
|
, notificationTable : Messages.Comp.NotificationChannelTable.Texts
|
||||||
|
, httpError : Http.Error -> String
|
||||||
|
, channelType : Messages.Data.ChannelType.Texts
|
||||||
|
, newChannel : String
|
||||||
|
, channelCreated : String
|
||||||
|
, channelUpdated : String
|
||||||
|
, channelDeleted : String
|
||||||
|
, formInvalid : String
|
||||||
|
, integrate : String
|
||||||
|
, intoDocspell : String
|
||||||
|
, postRequestInfo : String
|
||||||
|
, notifyEmailInfo : String
|
||||||
|
, addChannel : String
|
||||||
|
, updateChannel : String
|
||||||
|
, deleteThisChannel : String
|
||||||
|
, reallyDeleteChannel : String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
gb : Texts
|
||||||
|
gb =
|
||||||
|
{ basics = Messages.Basics.gb
|
||||||
|
, notificationForm = Messages.Comp.ChannelForm.gb
|
||||||
|
, notificationTable = Messages.Comp.NotificationChannelTable.gb
|
||||||
|
, httpError = Messages.Comp.HttpError.gb
|
||||||
|
, channelType = Messages.Data.ChannelType.gb
|
||||||
|
, newChannel = "New Channel"
|
||||||
|
, channelCreated = "Channel created"
|
||||||
|
, channelUpdated = "Channel updated"
|
||||||
|
, channelDeleted = "Channel deleted"
|
||||||
|
, formInvalid = "Please fill in all required fields"
|
||||||
|
, integrate = "Integrate"
|
||||||
|
, intoDocspell = "into Docspell"
|
||||||
|
, postRequestInfo = "Docspell will send POST requests with JSON payload."
|
||||||
|
, notifyEmailInfo = "Get notified via e-mail."
|
||||||
|
, addChannel = "Add new channel"
|
||||||
|
, updateChannel = "Update channel"
|
||||||
|
, deleteThisChannel = "Kanal löschen"
|
||||||
|
, reallyDeleteChannel = "Really delete this channel?"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
de : Texts
|
||||||
|
de =
|
||||||
|
{ basics = Messages.Basics.de
|
||||||
|
, notificationForm = Messages.Comp.ChannelForm.de
|
||||||
|
, notificationTable = Messages.Comp.NotificationChannelTable.de
|
||||||
|
, httpError = Messages.Comp.HttpError.de
|
||||||
|
, channelType = Messages.Data.ChannelType.de
|
||||||
|
, newChannel = "Neuer Kanal"
|
||||||
|
, channelCreated = "Kanal wurde angelegt."
|
||||||
|
, channelUpdated = "Kanal wurde aktualisiert."
|
||||||
|
, channelDeleted = "Kanal wurde entfernt."
|
||||||
|
, formInvalid = "Bitte alle erforderlichen Felder ausfüllen"
|
||||||
|
, integrate = "Integriere"
|
||||||
|
, intoDocspell = "in Docspell"
|
||||||
|
, postRequestInfo = "Docspell wird JSON POST requests senden."
|
||||||
|
, notifyEmailInfo = "Werde per E-Mail benachrichtigt."
|
||||||
|
, addChannel = "Neuen Kanal hinzufügen"
|
||||||
|
, updateChannel = "Kanal aktualisieren"
|
||||||
|
, deleteThisChannel = "Kanal löschen"
|
||||||
|
, reallyDeleteChannel = "Den Kanal wirklich löschen?"
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
{-
|
||||||
|
Copyright 2020 Eike K. & Contributors
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
-}
|
||||||
|
|
||||||
|
|
||||||
|
module Messages.Comp.NotificationChannelTable exposing (..)
|
||||||
|
|
||||||
|
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
|
||||||
|
, channelType : String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
gb : Texts
|
||||||
|
gb =
|
||||||
|
{ basics = Messages.Basics.gb
|
||||||
|
, eventType = Messages.Data.EventType.gb
|
||||||
|
, channelType = "Channel type"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
de : Texts
|
||||||
|
de =
|
||||||
|
{ basics = Messages.Basics.de
|
||||||
|
, eventType = Messages.Data.EventType.de
|
||||||
|
, channelType = "Kanaltyp"
|
||||||
|
}
|
@ -13,18 +13,17 @@ module Messages.Comp.NotificationHookForm exposing
|
|||||||
|
|
||||||
import Data.EventType exposing (EventType)
|
import Data.EventType exposing (EventType)
|
||||||
import Messages.Basics
|
import Messages.Basics
|
||||||
import Messages.Comp.ChannelForm
|
import Messages.Comp.ChannelRefInput
|
||||||
import Messages.Comp.EventSample
|
import Messages.Comp.EventSample
|
||||||
import Messages.Data.ChannelType
|
|
||||||
import Messages.Data.EventType
|
import Messages.Data.EventType
|
||||||
|
|
||||||
|
|
||||||
type alias Texts =
|
type alias Texts =
|
||||||
{ basics : Messages.Basics.Texts
|
{ basics : Messages.Basics.Texts
|
||||||
, channelForm : Messages.Comp.ChannelForm.Texts
|
, channelRef : Messages.Comp.ChannelRefInput.Texts
|
||||||
, eventType : EventType -> Messages.Data.EventType.Texts
|
, eventType : EventType -> Messages.Data.EventType.Texts
|
||||||
, eventSample : Messages.Comp.EventSample.Texts
|
, eventSample : Messages.Comp.EventSample.Texts
|
||||||
, channelHeader : Messages.Data.ChannelType.Texts
|
, channelHeader : String
|
||||||
, enableDisable : String
|
, enableDisable : String
|
||||||
, eventsInfo : String
|
, eventsInfo : String
|
||||||
, selectEvents : String
|
, selectEvents : String
|
||||||
@ -34,16 +33,19 @@ type alias Texts =
|
|||||||
, eventFilter : String
|
, eventFilter : String
|
||||||
, eventFilterInfo : String
|
, eventFilterInfo : String
|
||||||
, eventFilterClickForHelp : String
|
, eventFilterClickForHelp : String
|
||||||
|
, jsonPayload : String
|
||||||
|
, messagePayload : String
|
||||||
|
, payloadInfo : String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
gb : Texts
|
gb : Texts
|
||||||
gb =
|
gb =
|
||||||
{ basics = Messages.Basics.gb
|
{ basics = Messages.Basics.gb
|
||||||
, channelForm = Messages.Comp.ChannelForm.gb
|
, channelRef = Messages.Comp.ChannelRefInput.gb
|
||||||
, eventType = Messages.Data.EventType.gb
|
, eventType = Messages.Data.EventType.gb
|
||||||
, eventSample = Messages.Comp.EventSample.gb
|
, eventSample = Messages.Comp.EventSample.gb
|
||||||
, channelHeader = Messages.Data.ChannelType.gb
|
, channelHeader = "Select channels"
|
||||||
, enableDisable = "Enabled / Disabled"
|
, enableDisable = "Enabled / Disabled"
|
||||||
, eventsInfo = "Select events that trigger this webhook"
|
, eventsInfo = "Select events that trigger this webhook"
|
||||||
, selectEvents = "Select…"
|
, selectEvents = "Select…"
|
||||||
@ -53,16 +55,19 @@ gb =
|
|||||||
, eventFilter = "Event Filter Expression"
|
, eventFilter = "Event Filter Expression"
|
||||||
, eventFilterInfo = "Optional specify an expression to filter events based on their JSON structure."
|
, eventFilterInfo = "Optional specify an expression to filter events based on their JSON structure."
|
||||||
, eventFilterClickForHelp = "Click here for help"
|
, eventFilterClickForHelp = "Click here for help"
|
||||||
|
, jsonPayload = "JSON"
|
||||||
|
, messagePayload = "Message"
|
||||||
|
, payloadInfo = "Message payloads are sent to gotify, email and matrix. The JSON is sent to http channel."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
de : Texts
|
de : Texts
|
||||||
de =
|
de =
|
||||||
{ basics = Messages.Basics.de
|
{ basics = Messages.Basics.de
|
||||||
, channelForm = Messages.Comp.ChannelForm.de
|
, channelRef = Messages.Comp.ChannelRefInput.de
|
||||||
, eventType = Messages.Data.EventType.de
|
, eventType = Messages.Data.EventType.de
|
||||||
, eventSample = Messages.Comp.EventSample.de
|
, eventSample = Messages.Comp.EventSample.de
|
||||||
, channelHeader = Messages.Data.ChannelType.de
|
, channelHeader = "Kanäle"
|
||||||
, enableDisable = "Aktiviert / Deaktivert"
|
, enableDisable = "Aktiviert / Deaktivert"
|
||||||
, eventsInfo = "Wähle die Ereignisse, die diesen webhook auslösen"
|
, eventsInfo = "Wähle die Ereignisse, die diesen webhook auslösen"
|
||||||
, selectEvents = "Wähle…"
|
, selectEvents = "Wähle…"
|
||||||
@ -72,4 +77,7 @@ de =
|
|||||||
, eventFilter = "Ereignisfilter"
|
, eventFilter = "Ereignisfilter"
|
||||||
, eventFilterInfo = "Optionaler Ausdruck zum filtern von Ereignissen auf Basis ihrer JSON Struktur."
|
, eventFilterInfo = "Optionaler Ausdruck zum filtern von Ereignissen auf Basis ihrer JSON Struktur."
|
||||||
, eventFilterClickForHelp = "Klicke für Hilfe"
|
, eventFilterClickForHelp = "Klicke für Hilfe"
|
||||||
|
, jsonPayload = "JSON"
|
||||||
|
, messagePayload = "Nachricht"
|
||||||
|
, payloadInfo = "Es werden abhängig vom Kanal JSON oder Nachricht-Formate versendet. Der HTTP Kanal empfängt nur JSON, an die anderen wird das Nachrichtformat gesendet."
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ module Messages.Comp.NotificationHookManage exposing
|
|||||||
, gb
|
, gb
|
||||||
)
|
)
|
||||||
|
|
||||||
import Html exposing (Html, text)
|
|
||||||
import Http
|
import Http
|
||||||
import Messages.Basics
|
import Messages.Basics
|
||||||
import Messages.Comp.HttpError
|
import Messages.Comp.HttpError
|
||||||
@ -27,9 +26,6 @@ type alias Texts =
|
|||||||
, httpError : Http.Error -> String
|
, httpError : Http.Error -> String
|
||||||
, channelType : Messages.Data.ChannelType.Texts
|
, channelType : Messages.Data.ChannelType.Texts
|
||||||
, newHook : String
|
, newHook : String
|
||||||
, matrix : String
|
|
||||||
, gotify : String
|
|
||||||
, email : String
|
|
||||||
, httpRequest : String
|
, httpRequest : String
|
||||||
, hookCreated : String
|
, hookCreated : String
|
||||||
, hookUpdated : String
|
, hookUpdated : String
|
||||||
@ -39,12 +35,8 @@ type alias Texts =
|
|||||||
, reallyDeleteHook : String
|
, reallyDeleteHook : String
|
||||||
, formInvalid : String
|
, formInvalid : String
|
||||||
, invalidJsonFilter : String -> String
|
, invalidJsonFilter : String -> String
|
||||||
, integrate : String
|
|
||||||
, intoDocspell : String
|
|
||||||
, postRequestInfo : String
|
|
||||||
, updateWebhook : String
|
, updateWebhook : String
|
||||||
, addWebhook : String
|
, addWebhook : String
|
||||||
, notifyEmailInfo : String
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -56,9 +48,6 @@ gb =
|
|||||||
, httpError = Messages.Comp.HttpError.gb
|
, httpError = Messages.Comp.HttpError.gb
|
||||||
, channelType = Messages.Data.ChannelType.gb
|
, channelType = Messages.Data.ChannelType.gb
|
||||||
, newHook = "New Webhook"
|
, newHook = "New Webhook"
|
||||||
, matrix = "Matrix"
|
|
||||||
, gotify = "Gotify"
|
|
||||||
, email = "E-Mail"
|
|
||||||
, httpRequest = "HTTP Request"
|
, httpRequest = "HTTP Request"
|
||||||
, hookCreated = "Webhook created"
|
, hookCreated = "Webhook created"
|
||||||
, hookUpdated = "Webhook updated"
|
, hookUpdated = "Webhook updated"
|
||||||
@ -68,12 +57,8 @@ gb =
|
|||||||
, reallyDeleteHook = "Really delete this webhook?"
|
, reallyDeleteHook = "Really delete this webhook?"
|
||||||
, formInvalid = "Please fill in all required fields"
|
, formInvalid = "Please fill in all required fields"
|
||||||
, invalidJsonFilter = \m -> "Event filter invalid: " ++ m
|
, invalidJsonFilter = \m -> "Event filter invalid: " ++ m
|
||||||
, integrate = "Integrate"
|
|
||||||
, intoDocspell = "into Docspell"
|
|
||||||
, postRequestInfo = "Docspell will send POST requests with JSON payload."
|
|
||||||
, updateWebhook = "Update webhook"
|
, updateWebhook = "Update webhook"
|
||||||
, addWebhook = "Add new webhook"
|
, addWebhook = "Add new webhook"
|
||||||
, notifyEmailInfo = "Get notified via e-mail."
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -85,9 +70,6 @@ de =
|
|||||||
, httpError = Messages.Comp.HttpError.de
|
, httpError = Messages.Comp.HttpError.de
|
||||||
, channelType = Messages.Data.ChannelType.de
|
, channelType = Messages.Data.ChannelType.de
|
||||||
, newHook = "Neuer Webhook"
|
, newHook = "Neuer Webhook"
|
||||||
, matrix = "Matrix"
|
|
||||||
, gotify = "Gotify"
|
|
||||||
, email = "E-Mail"
|
|
||||||
, httpRequest = "HTTP Request"
|
, httpRequest = "HTTP Request"
|
||||||
, hookCreated = "Webhook erstellt"
|
, hookCreated = "Webhook erstellt"
|
||||||
, hookUpdated = "Webhook aktualisiert"
|
, hookUpdated = "Webhook aktualisiert"
|
||||||
@ -97,10 +79,6 @@ de =
|
|||||||
, reallyDeleteHook = "Den webhook wirklich löschen?"
|
, reallyDeleteHook = "Den webhook wirklich löschen?"
|
||||||
, formInvalid = "Bitte alle erforderlichen Felder ausfüllen"
|
, formInvalid = "Bitte alle erforderlichen Felder ausfüllen"
|
||||||
, invalidJsonFilter = \m -> "Ereignisfilter ist falsch: " ++ m
|
, invalidJsonFilter = \m -> "Ereignisfilter ist falsch: " ++ m
|
||||||
, integrate = "Integriere"
|
|
||||||
, intoDocspell = "in Docspell"
|
|
||||||
, postRequestInfo = "Docspell wird JSON POST requests senden."
|
|
||||||
, updateWebhook = "Webhook aktualisieren"
|
, updateWebhook = "Webhook aktualisieren"
|
||||||
, addWebhook = "Neuen Webhook hinzufügen"
|
, addWebhook = "Neuen Webhook hinzufügen"
|
||||||
, notifyEmailInfo = "Werde per E-Mail benachrichtigt."
|
|
||||||
}
|
}
|
||||||
|
@ -13,15 +13,18 @@ module Messages.Comp.NotificationHookTable exposing
|
|||||||
|
|
||||||
import Data.EventType exposing (EventType)
|
import Data.EventType exposing (EventType)
|
||||||
import Messages.Basics
|
import Messages.Basics
|
||||||
|
import Messages.Data.ChannelType
|
||||||
import Messages.Data.EventType
|
import Messages.Data.EventType
|
||||||
|
|
||||||
|
|
||||||
type alias Texts =
|
type alias Texts =
|
||||||
{ basics : Messages.Basics.Texts
|
{ basics : Messages.Basics.Texts
|
||||||
, eventType : EventType -> Messages.Data.EventType.Texts
|
, eventType : EventType -> Messages.Data.EventType.Texts
|
||||||
|
, channelType : Messages.Data.ChannelType.Texts
|
||||||
, enabled : String
|
, enabled : String
|
||||||
, channel : String
|
, channel : String
|
||||||
, events : String
|
, events : String
|
||||||
|
, allEvents : String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -29,9 +32,11 @@ gb : Texts
|
|||||||
gb =
|
gb =
|
||||||
{ basics = Messages.Basics.gb
|
{ basics = Messages.Basics.gb
|
||||||
, eventType = Messages.Data.EventType.gb
|
, eventType = Messages.Data.EventType.gb
|
||||||
|
, channelType = Messages.Data.ChannelType.gb
|
||||||
, enabled = "Enabled"
|
, enabled = "Enabled"
|
||||||
, channel = "Channel"
|
, channel = "Channel"
|
||||||
, events = "Events"
|
, events = "Events"
|
||||||
|
, allEvents = "All"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -39,7 +44,9 @@ de : Texts
|
|||||||
de =
|
de =
|
||||||
{ basics = Messages.Basics.de
|
{ basics = Messages.Basics.de
|
||||||
, eventType = Messages.Data.EventType.de
|
, eventType = Messages.Data.EventType.de
|
||||||
|
, channelType = Messages.Data.ChannelType.de
|
||||||
, enabled = "Aktiv"
|
, enabled = "Aktiv"
|
||||||
, channel = "Kanal"
|
, channel = "Kanal"
|
||||||
, events = "Ereignisse"
|
, events = "Ereignisse"
|
||||||
|
, allEvents = "Alle"
|
||||||
}
|
}
|
||||||
|
@ -11,14 +11,13 @@ module Messages.Comp.PeriodicQueryTaskForm exposing
|
|||||||
, gb
|
, gb
|
||||||
)
|
)
|
||||||
|
|
||||||
import Data.ChannelType exposing (ChannelType)
|
|
||||||
import Http
|
import Http
|
||||||
import Messages.Basics
|
import Messages.Basics
|
||||||
import Messages.Comp.BookmarkDropdown
|
import Messages.Comp.BookmarkDropdown
|
||||||
import Messages.Comp.CalEventInput
|
import Messages.Comp.CalEventInput
|
||||||
import Messages.Comp.ChannelForm
|
import Messages.Comp.ChannelForm
|
||||||
|
import Messages.Comp.ChannelRefInput
|
||||||
import Messages.Comp.HttpError
|
import Messages.Comp.HttpError
|
||||||
import Messages.Data.ChannelType
|
|
||||||
|
|
||||||
|
|
||||||
type alias Texts =
|
type alias Texts =
|
||||||
@ -26,6 +25,7 @@ type alias Texts =
|
|||||||
, calEventInput : Messages.Comp.CalEventInput.Texts
|
, calEventInput : Messages.Comp.CalEventInput.Texts
|
||||||
, channelForm : Messages.Comp.ChannelForm.Texts
|
, channelForm : Messages.Comp.ChannelForm.Texts
|
||||||
, bookmarkDropdown : Messages.Comp.BookmarkDropdown.Texts
|
, bookmarkDropdown : Messages.Comp.BookmarkDropdown.Texts
|
||||||
|
, channelRef : Messages.Comp.ChannelRefInput.Texts
|
||||||
, httpError : Http.Error -> String
|
, httpError : Http.Error -> String
|
||||||
, reallyDeleteTask : String
|
, reallyDeleteTask : String
|
||||||
, startOnce : String
|
, startOnce : String
|
||||||
@ -41,7 +41,7 @@ type alias Texts =
|
|||||||
, invalidCalEvent : String
|
, invalidCalEvent : String
|
||||||
, channelRequired : String
|
, channelRequired : String
|
||||||
, queryStringRequired : String
|
, queryStringRequired : String
|
||||||
, channelHeader : ChannelType -> String
|
, channelHeader : String
|
||||||
, messageContentTitle : String
|
, messageContentTitle : String
|
||||||
, messageContentLabel : String
|
, messageContentLabel : String
|
||||||
, messageContentInfo : String
|
, messageContentInfo : String
|
||||||
@ -56,6 +56,7 @@ gb =
|
|||||||
, channelForm = Messages.Comp.ChannelForm.gb
|
, channelForm = Messages.Comp.ChannelForm.gb
|
||||||
, httpError = Messages.Comp.HttpError.gb
|
, httpError = Messages.Comp.HttpError.gb
|
||||||
, bookmarkDropdown = Messages.Comp.BookmarkDropdown.gb
|
, bookmarkDropdown = Messages.Comp.BookmarkDropdown.gb
|
||||||
|
, channelRef = Messages.Comp.ChannelRefInput.gb
|
||||||
, reallyDeleteTask = "Really delete this notification task?"
|
, reallyDeleteTask = "Really delete this notification task?"
|
||||||
, startOnce = "Start Once"
|
, startOnce = "Start Once"
|
||||||
, startTaskNow = "Start this task now"
|
, startTaskNow = "Start this task now"
|
||||||
@ -74,7 +75,7 @@ gb =
|
|||||||
, queryLabel = "Query"
|
, queryLabel = "Query"
|
||||||
, channelRequired = "A valid channel must be given."
|
, channelRequired = "A valid channel must be given."
|
||||||
, queryStringRequired = "A query string and/or bookmark must be supplied"
|
, queryStringRequired = "A query string and/or bookmark must be supplied"
|
||||||
, channelHeader = \ct -> "Connection details for " ++ Messages.Data.ChannelType.gb ct
|
, channelHeader = "Channels"
|
||||||
, messageContentTitle = "Customize message"
|
, messageContentTitle = "Customize message"
|
||||||
, messageContentLabel = "Beginning of message"
|
, messageContentLabel = "Beginning of message"
|
||||||
, messageContentInfo = "Insert text that is prependend to the generated message."
|
, messageContentInfo = "Insert text that is prependend to the generated message."
|
||||||
@ -89,6 +90,7 @@ de =
|
|||||||
, channelForm = Messages.Comp.ChannelForm.de
|
, channelForm = Messages.Comp.ChannelForm.de
|
||||||
, httpError = Messages.Comp.HttpError.de
|
, httpError = Messages.Comp.HttpError.de
|
||||||
, bookmarkDropdown = Messages.Comp.BookmarkDropdown.de
|
, bookmarkDropdown = Messages.Comp.BookmarkDropdown.de
|
||||||
|
, channelRef = Messages.Comp.ChannelRefInput.de
|
||||||
, reallyDeleteTask = "Diesen Benachrichtigungsauftrag wirklich löschen?"
|
, reallyDeleteTask = "Diesen Benachrichtigungsauftrag wirklich löschen?"
|
||||||
, startOnce = "Jetzt starten"
|
, startOnce = "Jetzt starten"
|
||||||
, startTaskNow = "Starte den Auftrag sofort"
|
, startTaskNow = "Starte den Auftrag sofort"
|
||||||
@ -107,7 +109,7 @@ de =
|
|||||||
, queryLabel = "Abfrage"
|
, queryLabel = "Abfrage"
|
||||||
, channelRequired = "Ein Versandkanal muss angegeben werden."
|
, channelRequired = "Ein Versandkanal muss angegeben werden."
|
||||||
, queryStringRequired = "Eine Suchabfrage und/oder ein Bookmark muss angegeben werden."
|
, queryStringRequired = "Eine Suchabfrage und/oder ein Bookmark muss angegeben werden."
|
||||||
, channelHeader = \ct -> "Details für " ++ Messages.Data.ChannelType.de ct
|
, channelHeader = "Kanäle"
|
||||||
, messageContentTitle = "Nachricht anpassen"
|
, messageContentTitle = "Nachricht anpassen"
|
||||||
, messageContentLabel = "Anfang der Nachricht"
|
, messageContentLabel = "Anfang der Nachricht"
|
||||||
, messageContentInfo = "Dieser Text wird an den Anfang der generierten Nachricht angefügt."
|
, messageContentInfo = "Dieser Text wird an den Anfang der generierten Nachricht angefügt."
|
||||||
|
@ -12,32 +12,33 @@ module Messages.Comp.PeriodicQueryTaskList exposing
|
|||||||
)
|
)
|
||||||
|
|
||||||
import Messages.Basics
|
import Messages.Basics
|
||||||
|
import Messages.Data.ChannelType
|
||||||
|
|
||||||
|
|
||||||
type alias Texts =
|
type alias Texts =
|
||||||
{ basics : Messages.Basics.Texts
|
{ basics : Messages.Basics.Texts
|
||||||
|
, channelType : Messages.Data.ChannelType.Texts
|
||||||
, summary : String
|
, summary : String
|
||||||
, schedule : String
|
, schedule : String
|
||||||
, connection : String
|
, connection : String
|
||||||
, recipients : String
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
gb : Texts
|
gb : Texts
|
||||||
gb =
|
gb =
|
||||||
{ basics = Messages.Basics.gb
|
{ basics = Messages.Basics.gb
|
||||||
|
, channelType = Messages.Data.ChannelType.gb
|
||||||
, summary = "Summary"
|
, summary = "Summary"
|
||||||
, schedule = "Schedule"
|
, schedule = "Schedule"
|
||||||
, connection = "Connection"
|
, connection = "Channel"
|
||||||
, recipients = "Recipients"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
de : Texts
|
de : Texts
|
||||||
de =
|
de =
|
||||||
{ basics = Messages.Basics.de
|
{ basics = Messages.Basics.de
|
||||||
|
, channelType = Messages.Data.ChannelType.de
|
||||||
, summary = "Kurzbeschreibung"
|
, summary = "Kurzbeschreibung"
|
||||||
, schedule = "Zeitplan"
|
, schedule = "Zeitplan"
|
||||||
, connection = "Verbindung"
|
, connection = "Kanal"
|
||||||
, recipients = "Empfänger"
|
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ gb ct =
|
|||||||
"E-Mail"
|
"E-Mail"
|
||||||
|
|
||||||
Data.ChannelType.Http ->
|
Data.ChannelType.Http ->
|
||||||
"HTTP request"
|
"JSON"
|
||||||
|
|
||||||
|
|
||||||
de : Texts
|
de : Texts
|
||||||
@ -43,4 +43,4 @@ de ct =
|
|||||||
"E-Mail"
|
"E-Mail"
|
||||||
|
|
||||||
Data.ChannelType.Http ->
|
Data.ChannelType.Http ->
|
||||||
"HTTP Request"
|
"JSON"
|
||||||
|
@ -15,6 +15,7 @@ import Messages.Comp.ChangePasswordForm
|
|||||||
import Messages.Comp.DueItemsTaskManage
|
import Messages.Comp.DueItemsTaskManage
|
||||||
import Messages.Comp.EmailSettingsManage
|
import Messages.Comp.EmailSettingsManage
|
||||||
import Messages.Comp.ImapSettingsManage
|
import Messages.Comp.ImapSettingsManage
|
||||||
|
import Messages.Comp.NotificationChannelManage
|
||||||
import Messages.Comp.NotificationHookManage
|
import Messages.Comp.NotificationHookManage
|
||||||
import Messages.Comp.OtpSetup
|
import Messages.Comp.OtpSetup
|
||||||
import Messages.Comp.PeriodicQueryTaskManage
|
import Messages.Comp.PeriodicQueryTaskManage
|
||||||
@ -31,6 +32,7 @@ type alias Texts =
|
|||||||
, scanMailboxManage : Messages.Comp.ScanMailboxManage.Texts
|
, scanMailboxManage : Messages.Comp.ScanMailboxManage.Texts
|
||||||
, notificationHookManage : Messages.Comp.NotificationHookManage.Texts
|
, notificationHookManage : Messages.Comp.NotificationHookManage.Texts
|
||||||
, periodicQueryTask : Messages.Comp.PeriodicQueryTaskManage.Texts
|
, periodicQueryTask : Messages.Comp.PeriodicQueryTaskManage.Texts
|
||||||
|
, channelManage : Messages.Comp.NotificationChannelManage.Texts
|
||||||
, otpSetup : Messages.Comp.OtpSetup.Texts
|
, otpSetup : Messages.Comp.OtpSetup.Texts
|
||||||
, userSettings : String
|
, userSettings : String
|
||||||
, uiSettings : String
|
, uiSettings : String
|
||||||
@ -38,6 +40,7 @@ type alias Texts =
|
|||||||
, scanMailbox : String
|
, scanMailbox : String
|
||||||
, emailSettingSmtp : String
|
, emailSettingSmtp : String
|
||||||
, emailSettingImap : String
|
, emailSettingImap : String
|
||||||
|
, channelSettings : String
|
||||||
, changePassword : String
|
, changePassword : String
|
||||||
, uiSettingsInfo : String
|
, uiSettingsInfo : String
|
||||||
, scanMailboxInfo1 : String
|
, scanMailboxInfo1 : String
|
||||||
@ -50,6 +53,8 @@ type alias Texts =
|
|||||||
, webhookInfoText : String
|
, webhookInfoText : String
|
||||||
, dueItemsInfoText : String
|
, dueItemsInfoText : String
|
||||||
, periodicQueryInfoText : String
|
, periodicQueryInfoText : String
|
||||||
|
, channels : String
|
||||||
|
, channelInfoText : String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -63,6 +68,7 @@ gb =
|
|||||||
, scanMailboxManage = Messages.Comp.ScanMailboxManage.gb
|
, scanMailboxManage = Messages.Comp.ScanMailboxManage.gb
|
||||||
, notificationHookManage = Messages.Comp.NotificationHookManage.gb
|
, notificationHookManage = Messages.Comp.NotificationHookManage.gb
|
||||||
, periodicQueryTask = Messages.Comp.PeriodicQueryTaskManage.gb
|
, periodicQueryTask = Messages.Comp.PeriodicQueryTaskManage.gb
|
||||||
|
, channelManage = Messages.Comp.NotificationChannelManage.gb
|
||||||
, otpSetup = Messages.Comp.OtpSetup.gb
|
, otpSetup = Messages.Comp.OtpSetup.gb
|
||||||
, userSettings = "User Settings"
|
, userSettings = "User Settings"
|
||||||
, uiSettings = "UI Settings"
|
, uiSettings = "UI Settings"
|
||||||
@ -71,6 +77,7 @@ gb =
|
|||||||
, emailSettingSmtp = "E-Mail Settings (SMTP)"
|
, emailSettingSmtp = "E-Mail Settings (SMTP)"
|
||||||
, emailSettingImap = "E-Mail Settings (IMAP)"
|
, emailSettingImap = "E-Mail Settings (IMAP)"
|
||||||
, changePassword = "Change Password"
|
, changePassword = "Change Password"
|
||||||
|
, channelSettings = "Notification Channels"
|
||||||
, uiSettingsInfo =
|
, uiSettingsInfo =
|
||||||
"These settings only affect the web ui. They are stored in the browser, "
|
"These settings only affect the web ui. They are stored in the browser, "
|
||||||
++ "so they are separated between browsers and devices."
|
++ "so they are separated between browsers and devices."
|
||||||
@ -103,14 +110,16 @@ its payload.
|
|||||||
Additionally, you can setup queries that are executed periodically.
|
Additionally, you can setup queries that are executed periodically.
|
||||||
The results are send as a notification message.
|
The results are send as a notification message.
|
||||||
|
|
||||||
When creating a new notification task, choose first the communication
|
A notification setting needs at least one communication channel, which
|
||||||
channel.
|
must be created before.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
, webhookInfoText = """Webhooks execute http request upon certain events in docspell.
|
, webhookInfoText = """Webhooks execute http request upon certain events in docspell.
|
||||||
"""
|
"""
|
||||||
, dueItemsInfoText = """Docspell can notify you once the due dates of your items come closer. """
|
, 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."
|
, periodicQueryInfoText = "You can define a custom query that gets executed periodically."
|
||||||
|
, channels = "Notification Channels"
|
||||||
|
, channelInfoText = "Channels are used to send notification messages."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -124,6 +133,7 @@ de =
|
|||||||
, scanMailboxManage = Messages.Comp.ScanMailboxManage.de
|
, scanMailboxManage = Messages.Comp.ScanMailboxManage.de
|
||||||
, notificationHookManage = Messages.Comp.NotificationHookManage.de
|
, notificationHookManage = Messages.Comp.NotificationHookManage.de
|
||||||
, periodicQueryTask = Messages.Comp.PeriodicQueryTaskManage.de
|
, periodicQueryTask = Messages.Comp.PeriodicQueryTaskManage.de
|
||||||
|
, channelManage = Messages.Comp.NotificationChannelManage.de
|
||||||
, otpSetup = Messages.Comp.OtpSetup.de
|
, otpSetup = Messages.Comp.OtpSetup.de
|
||||||
, userSettings = "Benutzereinstellung"
|
, userSettings = "Benutzereinstellung"
|
||||||
, uiSettings = "Oberfläche"
|
, uiSettings = "Oberfläche"
|
||||||
@ -131,6 +141,7 @@ de =
|
|||||||
, scanMailbox = "E-Mail-Import"
|
, scanMailbox = "E-Mail-Import"
|
||||||
, emailSettingSmtp = "E-Mail-Einstellungen (SMTP)"
|
, emailSettingSmtp = "E-Mail-Einstellungen (SMTP)"
|
||||||
, emailSettingImap = "E-Mail-Einstellungen (IMAP)"
|
, emailSettingImap = "E-Mail-Einstellungen (IMAP)"
|
||||||
|
, channelSettings = "Benachrichtigungskanäle"
|
||||||
, changePassword = "Passwort ändern"
|
, changePassword = "Passwort ändern"
|
||||||
, uiSettingsInfo =
|
, uiSettingsInfo =
|
||||||
"Diese Einstellungen sind für die Web-Oberfläche."
|
"Diese Einstellungen sind für die Web-Oberfläche."
|
||||||
@ -161,15 +172,16 @@ Es kann aus diesen Versandkanälen gewählt werden:
|
|||||||
E-Mail. Zusätzlich kann das HTTP request direkt empfangen werden, was
|
E-Mail. Zusätzlich kann das HTTP request direkt empfangen werden, was
|
||||||
alle Details zu einem Ereignis enthält.
|
alle Details zu einem Ereignis enthält.
|
||||||
|
|
||||||
|
|
||||||
Ausserdem können periodische Suchabfragen erstellt werden, dessen
|
Ausserdem können periodische Suchabfragen erstellt werden, dessen
|
||||||
Ergebnis dann als Benachrichtigung versendet wird.
|
Ergebnis dann als Benachrichtigung versendet wird.
|
||||||
|
|
||||||
Beim Erstellen eines neuen Auftrags muss zunächst der gewünschte
|
Für eine Notifikation ist ein Kommunikationskanal notwendig, der zuvor
|
||||||
Versandkanal gewählt werden.
|
erstellt werden muss.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
, webhookInfoText = """Webhooks versenden HTTP Requests wenn bestimmte Ereignisse in Docspell auftreten."""
|
, 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. """
|
, 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."
|
, periodicQueryInfoText = "Hier können beliebige Abfragen definiert werden, welche regelmäßig ausgeführt werden."
|
||||||
|
, channels = "Benachrichtigungskanäle"
|
||||||
|
, channelInfoText = "Über Kanäle werden Notifizierungen versendet."
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import Comp.ChangePasswordForm
|
|||||||
import Comp.DueItemsTaskManage
|
import Comp.DueItemsTaskManage
|
||||||
import Comp.EmailSettingsManage
|
import Comp.EmailSettingsManage
|
||||||
import Comp.ImapSettingsManage
|
import Comp.ImapSettingsManage
|
||||||
|
import Comp.NotificationChannelManage
|
||||||
import Comp.NotificationHookManage
|
import Comp.NotificationHookManage
|
||||||
import Comp.OtpSetup
|
import Comp.OtpSetup
|
||||||
import Comp.PeriodicQueryTaskManage
|
import Comp.PeriodicQueryTaskManage
|
||||||
@ -35,6 +36,7 @@ type alias Model =
|
|||||||
, uiSettingsModel : Comp.UiSettingsManage.Model
|
, uiSettingsModel : Comp.UiSettingsManage.Model
|
||||||
, otpSetupModel : Comp.OtpSetup.Model
|
, otpSetupModel : Comp.OtpSetup.Model
|
||||||
, notificationHookModel : Comp.NotificationHookManage.Model
|
, notificationHookModel : Comp.NotificationHookManage.Model
|
||||||
|
, channelModel : Comp.NotificationChannelManage.Model
|
||||||
, periodicQueryModel : Comp.PeriodicQueryTaskManage.Model
|
, periodicQueryModel : Comp.PeriodicQueryTaskManage.Model
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,6 +55,9 @@ init flags settings =
|
|||||||
|
|
||||||
( pqm, pqc ) =
|
( pqm, pqc ) =
|
||||||
Comp.PeriodicQueryTaskManage.init flags
|
Comp.PeriodicQueryTaskManage.init flags
|
||||||
|
|
||||||
|
( ncm, ncc ) =
|
||||||
|
Comp.NotificationChannelManage.init flags
|
||||||
in
|
in
|
||||||
( { currentTab = Just UiSettingsTab
|
( { currentTab = Just UiSettingsTab
|
||||||
, changePassModel = Comp.ChangePasswordForm.emptyModel
|
, changePassModel = Comp.ChangePasswordForm.emptyModel
|
||||||
@ -64,12 +69,14 @@ init flags settings =
|
|||||||
, otpSetupModel = otpm
|
, otpSetupModel = otpm
|
||||||
, notificationHookModel = nhm
|
, notificationHookModel = nhm
|
||||||
, periodicQueryModel = pqm
|
, periodicQueryModel = pqm
|
||||||
|
, channelModel = ncm
|
||||||
}
|
}
|
||||||
, Cmd.batch
|
, Cmd.batch
|
||||||
[ Cmd.map UiSettingsMsg uc
|
[ Cmd.map UiSettingsMsg uc
|
||||||
, Cmd.map OtpSetupMsg otpc
|
, Cmd.map OtpSetupMsg otpc
|
||||||
, Cmd.map NotificationHookMsg nhc
|
, Cmd.map NotificationHookMsg nhc
|
||||||
, Cmd.map PeriodicQueryMsg pqc
|
, Cmd.map PeriodicQueryMsg pqc
|
||||||
|
, Cmd.map ChannelMsg ncc
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -85,6 +92,7 @@ type Tab
|
|||||||
| ScanMailboxTab
|
| ScanMailboxTab
|
||||||
| UiSettingsTab
|
| UiSettingsTab
|
||||||
| OtpTab
|
| OtpTab
|
||||||
|
| ChannelTab
|
||||||
|
|
||||||
|
|
||||||
type Msg
|
type Msg
|
||||||
@ -98,5 +106,6 @@ type Msg
|
|||||||
| OtpSetupMsg Comp.OtpSetup.Msg
|
| OtpSetupMsg Comp.OtpSetup.Msg
|
||||||
| NotificationHookMsg Comp.NotificationHookManage.Msg
|
| NotificationHookMsg Comp.NotificationHookManage.Msg
|
||||||
| PeriodicQueryMsg Comp.PeriodicQueryTaskManage.Msg
|
| PeriodicQueryMsg Comp.PeriodicQueryTaskManage.Msg
|
||||||
|
| ChannelMsg Comp.NotificationChannelManage.Msg
|
||||||
| UpdateSettings
|
| UpdateSettings
|
||||||
| ReceiveBrowserSettings StoredUiSettings
|
| ReceiveBrowserSettings StoredUiSettings
|
||||||
|
@ -11,6 +11,7 @@ import Comp.ChangePasswordForm
|
|||||||
import Comp.DueItemsTaskManage
|
import Comp.DueItemsTaskManage
|
||||||
import Comp.EmailSettingsManage
|
import Comp.EmailSettingsManage
|
||||||
import Comp.ImapSettingsManage
|
import Comp.ImapSettingsManage
|
||||||
|
import Comp.NotificationChannelManage
|
||||||
import Comp.NotificationHookManage
|
import Comp.NotificationHookManage
|
||||||
import Comp.OtpSetup
|
import Comp.OtpSetup
|
||||||
import Comp.PeriodicQueryTaskManage
|
import Comp.PeriodicQueryTaskManage
|
||||||
@ -71,8 +72,12 @@ update flags settings msg model =
|
|||||||
}
|
}
|
||||||
|
|
||||||
NotificationWebhookTab ->
|
NotificationWebhookTab ->
|
||||||
|
let
|
||||||
|
( _, nc ) =
|
||||||
|
Comp.NotificationHookManage.init flags
|
||||||
|
in
|
||||||
{ model = m
|
{ model = m
|
||||||
, cmd = Cmd.none
|
, cmd = Cmd.map NotificationHookMsg nc
|
||||||
, sub = Sub.none
|
, sub = Sub.none
|
||||||
, newSettings = Nothing
|
, newSettings = Nothing
|
||||||
}
|
}
|
||||||
@ -107,6 +112,9 @@ update flags settings msg model =
|
|||||||
OtpTab ->
|
OtpTab ->
|
||||||
UpdateResult m Cmd.none Sub.none Nothing
|
UpdateResult m Cmd.none Sub.none Nothing
|
||||||
|
|
||||||
|
ChannelTab ->
|
||||||
|
UpdateResult m Cmd.none Sub.none Nothing
|
||||||
|
|
||||||
ChangePassMsg m ->
|
ChangePassMsg m ->
|
||||||
let
|
let
|
||||||
( m2, c2 ) =
|
( m2, c2 ) =
|
||||||
@ -195,6 +203,17 @@ update flags settings msg model =
|
|||||||
, newSettings = Nothing
|
, newSettings = Nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ChannelMsg lm ->
|
||||||
|
let
|
||||||
|
( cm, cc ) =
|
||||||
|
Comp.NotificationChannelManage.update flags lm model.channelModel
|
||||||
|
in
|
||||||
|
{ model = { model | channelModel = cm }
|
||||||
|
, cmd = Cmd.map ChannelMsg cc
|
||||||
|
, sub = Sub.none
|
||||||
|
, newSettings = Nothing
|
||||||
|
}
|
||||||
|
|
||||||
UpdateSettings ->
|
UpdateSettings ->
|
||||||
update flags
|
update flags
|
||||||
settings
|
settings
|
||||||
|
@ -11,6 +11,7 @@ import Comp.ChangePasswordForm
|
|||||||
import Comp.DueItemsTaskManage
|
import Comp.DueItemsTaskManage
|
||||||
import Comp.EmailSettingsManage
|
import Comp.EmailSettingsManage
|
||||||
import Comp.ImapSettingsManage
|
import Comp.ImapSettingsManage
|
||||||
|
import Comp.NotificationChannelManage
|
||||||
import Comp.NotificationHookManage
|
import Comp.NotificationHookManage
|
||||||
import Comp.OtpSetup
|
import Comp.OtpSetup
|
||||||
import Comp.PeriodicQueryTaskManage
|
import Comp.PeriodicQueryTaskManage
|
||||||
@ -77,7 +78,7 @@ viewSidebar texts visible _ _ model =
|
|||||||
, menuEntryActive model NotificationTab
|
, menuEntryActive model NotificationTab
|
||||||
, class S.sidebarLink
|
, class S.sidebarLink
|
||||||
]
|
]
|
||||||
[ i [ class "fa fa-bullhorn" ] []
|
[ i [ class "fa fa-comment font-thin" ] []
|
||||||
, span
|
, span
|
||||||
[ class "ml-3" ]
|
[ class "ml-3" ]
|
||||||
[ text texts.notifications ]
|
[ text texts.notifications ]
|
||||||
@ -121,6 +122,17 @@ viewSidebar texts visible _ _ model =
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
, a
|
||||||
|
[ href "#"
|
||||||
|
, onClick (SetTab ChannelTab)
|
||||||
|
, menuEntryActive model ChannelTab
|
||||||
|
, class S.sidebarLink
|
||||||
|
]
|
||||||
|
[ i [ class "fa fa-bullhorn" ] []
|
||||||
|
, span
|
||||||
|
[ class "ml-3" ]
|
||||||
|
[ text texts.channelSettings ]
|
||||||
|
]
|
||||||
, a
|
, a
|
||||||
[ href "#"
|
[ href "#"
|
||||||
, onClick (SetTab ScanMailboxTab)
|
, onClick (SetTab ScanMailboxTab)
|
||||||
@ -217,6 +229,9 @@ viewContent texts flags settings model =
|
|||||||
Just OtpTab ->
|
Just OtpTab ->
|
||||||
viewOtpSetup texts settings model
|
viewOtpSetup texts settings model
|
||||||
|
|
||||||
|
Just ChannelTab ->
|
||||||
|
viewChannels texts settings model
|
||||||
|
|
||||||
Nothing ->
|
Nothing ->
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
@ -235,6 +250,26 @@ menuEntryActive model tab =
|
|||||||
class ""
|
class ""
|
||||||
|
|
||||||
|
|
||||||
|
viewChannels : Texts -> UiSettings -> Model -> List (Html Msg)
|
||||||
|
viewChannels texts settings model =
|
||||||
|
[ h2
|
||||||
|
[ class S.header1
|
||||||
|
, class "inline-flex items-center"
|
||||||
|
]
|
||||||
|
[ i [ class "fa fa-bell" ] []
|
||||||
|
, div [ class "ml-3" ]
|
||||||
|
[ text texts.channels
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, Markdown.toHtml [ class "opacity-80 text-lg mb-3 markdown-preview" ] texts.channelInfoText
|
||||||
|
, Html.map ChannelMsg
|
||||||
|
(Comp.NotificationChannelManage.view texts.channelManage
|
||||||
|
settings
|
||||||
|
model.channelModel
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
viewOtpSetup : Texts -> UiSettings -> Model -> List (Html Msg)
|
viewOtpSetup : Texts -> UiSettings -> Model -> List (Html Msg)
|
||||||
viewOtpSetup texts _ model =
|
viewOtpSetup texts _ model =
|
||||||
[ h2
|
[ h2
|
||||||
|
Reference in New Issue
Block a user