mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-22 02:18:26 +00:00
Manage notification channels separately and migrate
It's more convenient to manage notification channels separately, as it is done with email settings. Notification hook and other forms are adopted to only select channels. Hooks can now use more than one channel.
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)
|
||||||
})
|
})
|
||||||
@ -264,84 +257,30 @@ object ONotification {
|
|||||||
Channel.Gotify(r.id, gotify.name, gotify.url, gotify.appKey, gotify.priority),
|
Channel.Gotify(r.id, gotify.name, gotify.url, gotify.appKey, gotify.priority),
|
||||||
matrix =>
|
matrix =>
|
||||||
Channel
|
Channel
|
||||||
.Matrix(r.id, matrix.name, matrix.homeServer, matrix.roomId, matrix.accessToken),
|
.Matrix(
|
||||||
|
r.id,
|
||||||
|
matrix.name,
|
||||||
|
matrix.homeServer,
|
||||||
|
matrix.roomId,
|
||||||
|
matrix.accessToken
|
||||||
|
),
|
||||||
http => Channel.Http(r.id, http.name, http.url)
|
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(_, name, 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,
|
|
||||||
name,
|
|
||||||
mailConn.id,
|
|
||||||
recipients.toList,
|
|
||||||
time
|
|
||||||
).vary
|
|
||||||
} yield rec
|
|
||||||
case Channel.Gotify(_, name, url, appKey, prio) =>
|
|
||||||
OptionT.pure[F](
|
|
||||||
RNotificationChannelGotify(
|
|
||||||
id,
|
|
||||||
userId,
|
|
||||||
name,
|
|
||||||
url,
|
|
||||||
appKey,
|
|
||||||
prio,
|
|
||||||
time
|
|
||||||
).vary
|
|
||||||
)
|
|
||||||
case Channel.Matrix(_, name, homeServer, roomId, accessToken) =>
|
|
||||||
OptionT.pure[F](
|
|
||||||
RNotificationChannelMatrix(
|
|
||||||
id,
|
|
||||||
userId,
|
|
||||||
name,
|
|
||||||
homeServer,
|
|
||||||
roomId,
|
|
||||||
accessToken,
|
|
||||||
"m.text",
|
|
||||||
time
|
|
||||||
).vary
|
|
||||||
)
|
|
||||||
case Channel.Http(_, name, url) =>
|
|
||||||
OptionT.pure[F](
|
|
||||||
RNotificationChannelHttp(id, userId, name, 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]
|
||||||
@ -354,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] =
|
||||||
@ -372,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 {
|
||||||
@ -98,7 +99,8 @@ object Channel {
|
|||||||
implicit val jsonEncoder: Encoder[Matrix] = deriveConfiguredEncoder
|
implicit val jsonEncoder: Encoder[Matrix] = deriveConfiguredEncoder
|
||||||
}
|
}
|
||||||
|
|
||||||
final case class Http(id: Ident, name: Option[String], 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.
|
||||||
@ -5576,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
|
||||||
@ -8009,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"
|
|
||||||
|
@ -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,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,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,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(""), None, 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,10 +7,10 @@
|
|||||||
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._
|
||||||
|
|
||||||
@ -20,6 +20,17 @@ sealed trait RNotificationChannel {
|
|||||||
|
|
||||||
def name: Option[String] = fold(_.name, _.name, _.name, _.name)
|
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,
|
||||||
f2: RNotificationChannelGotify => A,
|
f2: RNotificationChannelGotify => A,
|
||||||
@ -93,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] =
|
||||||
@ -138,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
|
||||||
}
|
}
|
||||||
|
@ -49,8 +49,12 @@ object RNotificationChannelGotify {
|
|||||||
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(
|
||||||
|
@ -45,8 +45,10 @@ object RNotificationChannelHttp {
|
|||||||
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.name},${r.url},${r.created}")
|
DML.insert(T, T.all, sql"${r.id},${r.uid},${r.name},${r.url},${r.created}")
|
||||||
|
@ -65,8 +65,10 @@ object RNotificationChannelMail {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
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")
|
||||||
|
@ -77,8 +77,12 @@ object RNotificationChannelMatrix {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
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