1
0
mirror of https://github.com/jhillyerd/inbucket.git synced 2025-12-17 09:37:02 +00:00

ui: Reimplement message monitor as web component, closes #128

This commit is contained in:
James Hillyerd
2019-01-01 10:58:14 -08:00
parent dbdc60a0fb
commit f47e2cfcc2
6 changed files with 114 additions and 156 deletions

View File

@@ -111,9 +111,6 @@ pageSubscriptions page =
Mailbox subModel ->
Sub.map MailboxMsg (Mailbox.subscriptions subModel)
Monitor subModel ->
Sub.map MonitorMsg (Monitor.subscriptions subModel)
Status subModel ->
Sub.map StatusMsg (Status.subscriptions subModel)
@@ -251,59 +248,50 @@ changeRouteTo route model =
let
session =
getSession model
( newModel, newCmd ) =
case route of
Route.Unknown path ->
let
flash =
{ title = "Unknown route requested"
, table = [ ( "Path", path ) ]
}
in
( applyToModelSession (Session.showFlash flash) model
, Cmd.none
)
Route.Home ->
Home.init session
|> updateWith Home HomeMsg model
Route.Mailbox name ->
Mailbox.init session name Nothing
|> updateWith Mailbox MailboxMsg model
Route.Message mailbox id ->
Mailbox.init session mailbox (Just id)
|> updateWith Mailbox MailboxMsg model
Route.Monitor ->
if session.config.monitorVisible then
Monitor.init session
|> updateWith Monitor MonitorMsg model
else
let
flash =
{ title = "Unknown route requested"
, table = [ ( "Error", "Monitor disabled by configuration." ) ]
}
in
( applyToModelSession (Session.showFlash flash) model
, Cmd.none
)
Route.Status ->
Status.init session
|> updateWith Status StatusMsg model
in
case model.page of
Monitor _ ->
-- Leaving Monitor page, shut down the web socket.
( newModel, Cmd.batch [ Ports.monitorCommand False, newCmd ] )
case route of
Route.Unknown path ->
let
flash =
{ title = "Unknown route requested"
, table = [ ( "Path", path ) ]
}
in
( applyToModelSession (Session.showFlash flash) model
, Cmd.none
)
_ ->
( newModel, newCmd )
Route.Home ->
Home.init session
|> updateWith Home HomeMsg model
Route.Mailbox name ->
Mailbox.init session name Nothing
|> updateWith Mailbox MailboxMsg model
Route.Message mailbox id ->
Mailbox.init session mailbox (Just id)
|> updateWith Mailbox MailboxMsg model
Route.Monitor ->
if session.config.monitorVisible then
Monitor.init session
|> updateWith Monitor MonitorMsg model
else
let
flash =
{ title = "Unknown route requested"
, table = [ ( "Error", "Monitor disabled by configuration." ) ]
}
in
( applyToModelSession (Session.showFlash flash) model
, Cmd.none
)
Route.Status ->
Status.init session
|> updateWith Status StatusMsg model
getSession : Model -> Session

View File

@@ -1,4 +1,4 @@
module Page.Monitor exposing (Model, Msg, init, subscriptions, update, view)
module Page.Monitor exposing (Model, Msg, init, update, view)
import Data.MessageHeader as MessageHeader exposing (MessageHeader)
import Data.Session as Session exposing (Session)
@@ -7,7 +7,6 @@ import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events as Events
import Json.Decode as D
import Ports
import Route
import Time exposing (Posix)
@@ -23,34 +22,9 @@ type alias Model =
}
type MonitorMessage
= Connected Bool
| Message MessageHeader
init : Session -> ( Model, Cmd Msg )
init session =
( Model session False []
, Ports.monitorCommand True
)
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model =
let
monitorMessage =
D.oneOf
[ D.map Message MessageHeader.decoder
, D.map Connected D.bool
]
|> D.decodeValue
|> Ports.monitorMessage
in
Sub.map MessageReceived monitorMessage
( Model session False [], Cmd.none )
@@ -58,29 +32,37 @@ subscriptions model =
type Msg
= MessageReceived (Result D.Error MonitorMessage)
= Connected Bool
| MessageReceived D.Value
| OpenMessage MessageHeader
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
MessageReceived (Ok (Connected status)) ->
( { model | connected = status }, Cmd.none )
Connected True ->
( { model | connected = True, messages = [] }, Cmd.none )
MessageReceived (Ok (Message header)) ->
( { model | messages = header :: model.messages }, Cmd.none )
Connected False ->
( { model | connected = False }, Cmd.none )
MessageReceived (Err err) ->
let
flash =
{ title = "Decoding failed"
, table = [ ( "Error", D.errorToString err ) ]
}
in
( { model | session = Session.showFlash flash model.session }
, Cmd.none
)
MessageReceived value ->
case D.decodeValue (MessageHeader.decoder |> D.at [ "detail" ]) value of
Ok header ->
( { model | messages = header :: model.messages }
, Cmd.none
)
Err err ->
let
flash =
{ title = "Message decoding failed"
, table = [ ( "Error", D.errorToString err ) ]
}
in
( { model | session = Session.showFlash flash model.session }
, Cmd.none
)
OpenMessage header ->
( model
@@ -110,6 +92,11 @@ view model =
)
]
]
, node "monitor-messages"
[ Events.on "connected" (D.map Connected <| D.at [ "detail" ] <| D.bool)
, Events.on "message" (D.map MessageReceived D.value)
]
[]
, table [ class "monitor" ]
[ thead []
[ th [] [ text "Date" ]
@@ -143,6 +130,8 @@ shortDate zone date =
, DF.hourNumber
, DF.text ":"
, DF.minuteFixed
, DF.text ":"
, DF.secondFixed
, DF.text " "
, DF.amPmUppercase
]

View File

@@ -1,19 +1,11 @@
port module Ports exposing
( monitorCommand
, monitorMessage
, onSessionChange
( onSessionChange
, storeSession
)
import Json.Encode exposing (Value)
port monitorCommand : Bool -> Cmd msg
port monitorMessage : (Value -> msg) -> Sub msg
port onSessionChange : (Value -> msg) -> Sub msg

View File

@@ -1,7 +1,7 @@
import './main.css'
import '@fortawesome/fontawesome-free/css/all.css'
import { Elm } from './Main.elm'
import registerMonitorPorts from './registerMonitor'
import './monitorMessages'
import './renderedHtml'
// Initial configuration from Inbucket server to Elm App.
@@ -16,9 +16,6 @@ var app = Elm.Main.init({
flags: flags,
})
// Message monitor.
registerMonitorPorts(app)
// Session storage.
app.ports.storeSession.subscribe(function (session) {
localStorage.session = JSON.stringify(session)

38
ui/src/monitorMessages.js Normal file
View File

@@ -0,0 +1,38 @@
// monitor-messages connects to the Inbucket backend via WebSocket to monitor
// incoming messages.
customElements.define(
'monitor-messages',
class MonitorMessages extends HTMLElement {
constructor() {
const self = super()
// TODO make URI/URL configurable.
var uri = '/api/v1/monitor/messages'
self._url = ((window.location.protocol === 'https:') ? 'wss://' : 'ws://') + window.location.host + uri
self._socket = null
}
connectedCallback() {
const self = this
self._socket = new WebSocket(self._url)
var ws = self._socket
ws.addEventListener('open', function (e) {
self.dispatchEvent(new CustomEvent('connected', { detail: true }))
})
ws.addEventListener('close', function (e) {
self.dispatchEvent(new CustomEvent('connected', { detail: false }))
})
ws.addEventListener('message', function (e) {
self.dispatchEvent(new CustomEvent('message', {
detail: JSON.parse(e.data),
}))
})
}
disconnectedCallback() {
var ws = this._socket
if (ws) {
ws.close()
}
}
}
)

View File

@@ -1,46 +0,0 @@
// Register the websocket listeners for the monitor API.
export default function registerMonitorPorts(app) {
var uri = '/api/v1/monitor/messages'
var url = ((window.location.protocol === "https:") ? "wss://" : "ws://") + window.location.host + uri
// Current handler.
var handler = null
app.ports.monitorCommand.subscribe(function (cmd) {
if (handler != null) {
handler.down()
handler = null
}
if (cmd) {
// Command is up.
handler = websocketHandler(url, app.ports.monitorMessage)
handler.up()
}
})
}
// Creates a handler responsible for connecting, disconnecting from web socket.
function websocketHandler(url, port) {
var ws = null
return {
up: () => {
ws = new WebSocket(url)
ws.addEventListener('open', function (e) {
port.send(true)
})
ws.addEventListener('close', function (e) {
port.send(false)
})
ws.addEventListener('message', function (e) {
var msg = JSON.parse(e.data)
port.send(msg)
})
},
down: () => {
ws.close()
}
}
}