From f47e2cfcc2a6c3e8e6595007d583a3a502ef66e2 Mon Sep 17 00:00:00 2001 From: James Hillyerd Date: Tue, 1 Jan 2019 10:58:14 -0800 Subject: [PATCH] ui: Reimplement message monitor as web component, closes #128 --- ui/src/Main.elm | 96 +++++++++++++++++---------------------- ui/src/Page/Monitor.elm | 75 +++++++++++++----------------- ui/src/Ports.elm | 10 +--- ui/src/index.js | 5 +- ui/src/monitorMessages.js | 38 ++++++++++++++++ ui/src/registerMonitor.js | 46 ------------------- 6 files changed, 114 insertions(+), 156 deletions(-) create mode 100644 ui/src/monitorMessages.js delete mode 100644 ui/src/registerMonitor.js diff --git a/ui/src/Main.elm b/ui/src/Main.elm index 5e5092c..cebecaf 100644 --- a/ui/src/Main.elm +++ b/ui/src/Main.elm @@ -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 diff --git a/ui/src/Page/Monitor.elm b/ui/src/Page/Monitor.elm index 58b935a..7410766 100644 --- a/ui/src/Page/Monitor.elm +++ b/ui/src/Page/Monitor.elm @@ -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 ] diff --git a/ui/src/Ports.elm b/ui/src/Ports.elm index f3506c1..6e9a32e 100644 --- a/ui/src/Ports.elm +++ b/ui/src/Ports.elm @@ -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 diff --git a/ui/src/index.js b/ui/src/index.js index 85ded58..6cc7225 100644 --- a/ui/src/index.js +++ b/ui/src/index.js @@ -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) diff --git a/ui/src/monitorMessages.js b/ui/src/monitorMessages.js new file mode 100644 index 0000000..c606b6d --- /dev/null +++ b/ui/src/monitorMessages.js @@ -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() + } + } + } +) diff --git a/ui/src/registerMonitor.js b/ui/src/registerMonitor.js deleted file mode 100644 index 5bf1a50..0000000 --- a/ui/src/registerMonitor.js +++ /dev/null @@ -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() - } - } -}