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

Merge branch 'feature/config-cookie' into develop

This commit is contained in:
James Hillyerd
2018-12-31 15:29:15 -08:00
9 changed files with 153 additions and 17 deletions

View File

@@ -12,6 +12,7 @@ export INBUCKET_SMTP_STOREDOMAINS="important.local"
export INBUCKET_WEB_TEMPLATECACHE="false" export INBUCKET_WEB_TEMPLATECACHE="false"
export INBUCKET_WEB_COOKIEAUTHKEY="not-secret" export INBUCKET_WEB_COOKIEAUTHKEY="not-secret"
export INBUCKET_WEB_UIDIR="ui/dist" export INBUCKET_WEB_UIDIR="ui/dist"
#export INBUCKET_WEB_MONITORVISIBLE="false"
export INBUCKET_STORAGE_TYPE="file" export INBUCKET_STORAGE_TYPE="file"
export INBUCKET_STORAGE_PARAMS="path:/tmp/inbucket" export INBUCKET_STORAGE_PARAMS="path:/tmp/inbucket"
export INBUCKET_STORAGE_RETENTIONPERIOD="3h" export INBUCKET_STORAGE_RETENTIONPERIOD="3h"

View File

@@ -0,0 +1,5 @@
package web
type jsonAppConfig struct {
MonitorVisible bool `json:"monitor-visible"`
}

View File

@@ -31,6 +31,16 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
} }
// cookieHandler injects an HTTP cookie into the response.
func cookieHandler(cookie *http.Cookie, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
log.Debug().Str("module", "web").Str("remote", req.RemoteAddr).Str("proto", req.Proto).
Str("method", req.Method).Str("path", req.RequestURI).Msg("Injecting cookie")
http.SetCookie(w, cookie)
next.ServeHTTP(w, req)
})
}
// fileHandler creates a handler that sends the named file regardless of the requested URL. // fileHandler creates a handler that sends the named file regardless of the requested URL.
func fileHandler(name string) http.Handler { func fileHandler(name string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {

View File

@@ -3,10 +3,12 @@ package web
import ( import (
"context" "context"
"encoding/json"
"expvar" "expvar"
"net" "net"
"net/http" "net/http"
"net/http/pprof" "net/http/pprof"
"net/url"
"path/filepath" "path/filepath"
"time" "time"
@@ -75,7 +77,8 @@ func Initialize(
fileHandler(filepath.Join(conf.Web.UIDir, "favicon.png"))) fileHandler(filepath.Join(conf.Web.UIDir, "favicon.png")))
// SPA managed paths. // SPA managed paths.
spaHandler := fileHandler(filepath.Join(conf.Web.UIDir, "index.html")) spaHandler := cookieHandler(appConfigCookie(conf.Web),
fileHandler(filepath.Join(conf.Web.UIDir, "index.html")))
Router.Path("/").Handler(spaHandler) Router.Path("/").Handler(spaHandler)
Router.Path("/monitor").Handler(spaHandler) Router.Path("/monitor").Handler(spaHandler)
Router.Path("/status").Handler(spaHandler) Router.Path("/status").Handler(spaHandler)
@@ -126,6 +129,22 @@ func Start(ctx context.Context) {
} }
} }
func appConfigCookie(webConfig config.Web) *http.Cookie {
o := &jsonAppConfig{
MonitorVisible: webConfig.MonitorVisible,
}
b, err := json.Marshal(o)
if err != nil {
log.Error().Str("module", "web").Str("phase", "startup").Err(err).
Msg("Failed to convert app-config to JSON")
}
return &http.Cookie{
Name: "app-config",
Value: url.PathEscape(string(b)),
Path: "/",
}
}
// serve begins serving HTTP requests // serve begins serving HTTP requests
func serve(ctx context.Context) { func serve(ctx context.Context) {
// server.Serve blocks until we close the listener // server.Serve blocks until we close the listener

20
ui/src/Data/AppConfig.elm Normal file
View File

@@ -0,0 +1,20 @@
module Data.AppConfig exposing (AppConfig, decoder, default)
import Json.Decode as D
import Json.Decode.Pipeline as P
type alias AppConfig =
{ monitorVisible : Bool
}
decoder : D.Decoder AppConfig
decoder =
D.succeed AppConfig
|> P.required "monitor-visible" D.bool
default : AppConfig
default =
AppConfig True

View File

@@ -4,15 +4,16 @@ module Data.Session exposing
, Session , Session
, addRecent , addRecent
, clearFlash , clearFlash
, decodeValueWithDefault
, decoder , decoder
, disableRouting , disableRouting
, enableRouting , enableRouting
, init , init
, initError
, showFlash , showFlash
) )
import Browser.Navigation as Nav import Browser.Navigation as Nav
import Data.AppConfig as AppConfig exposing (AppConfig)
import Html exposing (Html) import Html exposing (Html)
import Json.Decode as D import Json.Decode as D
import Json.Decode.Pipeline exposing (..) import Json.Decode.Pipeline exposing (..)
@@ -28,6 +29,7 @@ type alias Session =
, flash : Maybe Flash , flash : Maybe Flash
, routing : Bool , routing : Bool
, zone : Time.Zone , zone : Time.Zone
, config : AppConfig
, persistent : Persistent , persistent : Persistent
} }
@@ -43,17 +45,30 @@ type alias Persistent =
} }
init : Nav.Key -> Url -> Persistent -> Session init : Nav.Key -> Url -> AppConfig -> Persistent -> Session
init key location persistent = init key location config persistent =
{ key = key { key = key
, host = location.host , host = location.host
, flash = Nothing , flash = Nothing
, routing = True , routing = True
, zone = Time.utc , zone = Time.utc
, config = config
, persistent = persistent , persistent = persistent
} }
initError : Nav.Key -> Url -> String -> Session
initError key location error =
{ key = key
, host = location.host
, flash = Just (Flash "Initialization failed" [ ( "Error", error ) ])
, routing = True
, zone = Time.utc
, config = AppConfig.default
, persistent = Persistent []
}
addRecent : String -> Session -> Session addRecent : String -> Session -> Session
addRecent mailbox session = addRecent mailbox session =
if List.head session.persistent.recentMailboxes == Just mailbox then if List.head session.persistent.recentMailboxes == Just mailbox then
@@ -99,11 +114,6 @@ decoder =
|> optional "recentMailboxes" (D.list D.string) [] |> optional "recentMailboxes" (D.list D.string) []
decodeValueWithDefault : D.Value -> Persistent
decodeValueWithDefault =
D.decodeValue decoder >> Result.withDefault { recentMailboxes = [] }
encode : Persistent -> E.Value encode : Persistent -> E.Value
encode persistent = encode persistent =
E.object E.object

View File

@@ -2,7 +2,8 @@ module Main exposing (main)
import Browser exposing (Document, UrlRequest) import Browser exposing (Document, UrlRequest)
import Browser.Navigation as Nav import Browser.Navigation as Nav
import Data.Session as Session exposing (Session, decoder) import Data.AppConfig as AppConfig exposing (AppConfig)
import Data.Session as Session exposing (Session)
import Html exposing (..) import Html exposing (..)
import Json.Decode as D exposing (Value) import Json.Decode as D exposing (Value)
import Page.Home as Home import Page.Home as Home
@@ -34,11 +35,27 @@ type alias Model =
} }
type alias InitConfig =
{ appConfig : AppConfig
, session : Session.Persistent
}
init : Value -> Url -> Nav.Key -> ( Model, Cmd Msg ) init : Value -> Url -> Nav.Key -> ( Model, Cmd Msg )
init sessionValue location key = init configValue location key =
let let
configDecoder =
D.map2 InitConfig
(D.field "app-config" AppConfig.decoder)
(D.field "session" Session.decoder)
session = session =
Session.init key location (Session.decodeValueWithDefault sessionValue) case D.decodeValue configDecoder configValue of
Ok config ->
Session.init key location config.appConfig config.session
Err error ->
Session.initError key location (D.errorToString error)
( subModel, _ ) = ( subModel, _ ) =
Home.init session Home.init session
@@ -238,8 +255,20 @@ changeRouteTo route model =
|> updateWith Mailbox MailboxMsg model |> updateWith Mailbox MailboxMsg model
Route.Monitor -> Route.Monitor ->
Monitor.init session if session.config.monitorVisible then
|> updateWith Monitor MonitorMsg model 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 -> Route.Status ->
Status.init session Status.init session

View File

@@ -44,7 +44,11 @@ frame controls session page modal content =
[ ul [ class "navbar", attribute "role" "navigation" ] [ ul [ class "navbar", attribute "role" "navigation" ]
[ li [ class "navbar-brand" ] [ li [ class "navbar-brand" ]
[ a [ Route.href Route.Home ] [ text "@ inbucket" ] ] [ a [ Route.href Route.Home ] [ text "@ inbucket" ] ]
, navbarLink page Route.Monitor [ text "Monitor" ] , if session.config.monitorVisible then
navbarLink page Route.Monitor [ text "Monitor" ]
else
text ""
, navbarLink page Route.Status [ text "Status" ] , navbarLink page Route.Status [ text "Status" ]
, navbarRecent page controls , navbarRecent page controls
, li [ class "navbar-mailbox" ] , li [ class "navbar-mailbox" ]

View File

@@ -4,10 +4,16 @@ import { Elm } from './Main.elm'
import registerMonitorPorts from './registerMonitor' import registerMonitorPorts from './registerMonitor'
import './renderedHtml' import './renderedHtml'
// Initial configuration from Inbucket server to Elm App.
var flags = {
"app-config": appConfig(),
"session": sessionObject(),
}
// App startup. // App startup.
var app = Elm.Main.init({ var app = Elm.Main.init({
node: document.getElementById('root'), node: document.getElementById('root'),
flags: sessionObject() flags: flags,
}) })
// Message monitor. // Message monitor.
@@ -24,9 +30,24 @@ window.addEventListener("storage", function (event) {
} }
}, false) }, false)
// Decode the JSON value of the app-config cookie, then delete it.
function appConfig() {
var name = "app-config"
var c = getCookie(name)
if (c) {
deleteCookie(name)
return JSON.parse(decodeURIComponent(c))
}
console.warn("Inbucket " + name + " cookie not found, running with defaults.")
return {
"monitor-visible": true,
}
}
// Grab peristent session data out of local storage.
function sessionObject() { function sessionObject() {
var s = localStorage.session
try { try {
var s = localStorage.session
if (s) { if (s) {
return JSON.parse(s) return JSON.parse(s)
} }
@@ -35,3 +56,20 @@ function sessionObject() {
} }
return null return null
} }
function getCookie(cookieName) {
var name = cookieName + "="
var cookies = decodeURIComponent(document.cookie).split(';')
for (var i=0; i<cookies.length; i++) {
var cookie = cookies[i].trim()
if (cookie.indexOf(name) == 0) {
return cookie.substring(name.length, cookie.length)
}
}
return null
}
function deleteCookie(cookieName) {
document.cookie = cookieName +
"=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"
}