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:
@@ -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"
|
||||||
|
|||||||
5
pkg/server/web/app_json.go
Normal file
5
pkg/server/web/app_json.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package web
|
||||||
|
|
||||||
|
type jsonAppConfig struct {
|
||||||
|
MonitorVisible bool `json:"monitor-visible"`
|
||||||
|
}
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
20
ui/src/Data/AppConfig.elm
Normal 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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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" ]
|
||||||
|
|||||||
@@ -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=/;"
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user