mirror of
https://github.com/jhillyerd/inbucket.git
synced 2025-12-17 09:37:02 +00:00
ui: Display server configuration on status page
This commit is contained in:
@@ -3,13 +3,19 @@
|
||||
# description: Developer friendly Inbucket configuration
|
||||
|
||||
export INBUCKET_LOGLEVEL="debug"
|
||||
export INBUCKET_SMTP_REJECTDOMAINS="bad-actors.local"
|
||||
#export INBUCKET_SMTP_DEFAULTACCEPT="false"
|
||||
export INBUCKET_SMTP_ACCEPTDOMAINS="good-actors.local"
|
||||
export INBUCKET_SMTP_DISCARDDOMAINS="bitbucket.local"
|
||||
#export INBUCKET_SMTP_DEFAULTSTORE="false"
|
||||
export INBUCKET_SMTP_STOREDOMAINS="important.local"
|
||||
export INBUCKET_WEB_TEMPLATECACHE="false"
|
||||
export INBUCKET_WEB_COOKIEAUTHKEY="not-secret"
|
||||
export INBUCKET_WEB_UIDIR="ui/dist"
|
||||
export INBUCKET_STORAGE_TYPE="file"
|
||||
export INBUCKET_STORAGE_PARAMS="path:/tmp/inbucket"
|
||||
export INBUCKET_STORAGE_RETENTIONPERIOD="3h"
|
||||
export INBUCKET_STORAGE_MAILBOXMSGCAP="300"
|
||||
|
||||
if ! test -x ./inbucket; then
|
||||
echo "$PWD/inbucket not found/executable!" >&2
|
||||
|
||||
@@ -71,21 +71,31 @@ func RootMonitorMailbox(w http.ResponseWriter, req *http.Request, ctx *web.Conte
|
||||
|
||||
// RootStatus serves the Inbucket status page
|
||||
func RootStatus(w http.ResponseWriter, req *http.Request, ctx *web.Context) (err error) {
|
||||
// Get flash messages, save session
|
||||
errorFlash := ctx.Session.Flashes("errors")
|
||||
if err = ctx.Session.Save(req, w); err != nil {
|
||||
return err
|
||||
root := ctx.RootConfig
|
||||
retPeriod := ""
|
||||
if root.Storage.RetentionPeriod > 0 {
|
||||
retPeriod = root.Storage.RetentionPeriod.String()
|
||||
}
|
||||
// Render template
|
||||
return web.RenderTemplate("root/status.html", w, map[string]interface{}{
|
||||
"ctx": ctx,
|
||||
"errorFlash": errorFlash,
|
||||
"version": config.Version,
|
||||
"buildDate": config.BuildDate,
|
||||
"smtpListener": ctx.RootConfig.SMTP.Addr,
|
||||
"pop3Listener": ctx.RootConfig.POP3.Addr,
|
||||
"webListener": ctx.RootConfig.Web.Addr,
|
||||
"smtpConfig": ctx.RootConfig.SMTP,
|
||||
"storageConfig": ctx.RootConfig.Storage,
|
||||
|
||||
return web.RenderJSON(w,
|
||||
&jsonServerConfig{
|
||||
Version: config.Version,
|
||||
BuildDate: config.BuildDate,
|
||||
POP3Listener: root.POP3.Addr,
|
||||
WebListener: root.Web.Addr,
|
||||
SMTPConfig: jsonSMTPConfig{
|
||||
Addr: root.SMTP.Addr,
|
||||
DefaultAccept: root.SMTP.DefaultAccept,
|
||||
AcceptDomains: root.SMTP.AcceptDomains,
|
||||
RejectDomains: root.SMTP.RejectDomains,
|
||||
DefaultStore: root.SMTP.DefaultStore,
|
||||
StoreDomains: root.SMTP.StoreDomains,
|
||||
DiscardDomains: root.SMTP.DiscardDomains,
|
||||
},
|
||||
StorageConfig: jsonStorageConfig{
|
||||
MailboxMsgCap: root.Storage.MailboxMsgCap,
|
||||
StoreType: root.Storage.Type,
|
||||
RetentionPeriod: retPeriod,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
26
pkg/webui/status_json.go
Normal file
26
pkg/webui/status_json.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package webui
|
||||
|
||||
type jsonServerConfig struct {
|
||||
Version string `json:"version"`
|
||||
BuildDate string `json:"build-date"`
|
||||
POP3Listener string `json:"pop3-listener"`
|
||||
WebListener string `json:"web-listener"`
|
||||
SMTPConfig jsonSMTPConfig `json:"smtp-config"`
|
||||
StorageConfig jsonStorageConfig `json:"storage-config"`
|
||||
}
|
||||
|
||||
type jsonSMTPConfig struct {
|
||||
Addr string `json:"addr"`
|
||||
DefaultAccept bool `json:"default-accept"`
|
||||
AcceptDomains []string `json:"accept-domains"`
|
||||
RejectDomains []string `json:"reject-domains"`
|
||||
DefaultStore bool `json:"default-store"`
|
||||
StoreDomains []string `json:"store-domains"`
|
||||
DiscardDomains []string `json:"discard-domains"`
|
||||
}
|
||||
|
||||
type jsonStorageConfig struct {
|
||||
MailboxMsgCap int `json:"mailbox-msg-cap"`
|
||||
StoreType string `json:"store-type"`
|
||||
RetentionPeriod string `json:"retention-period"`
|
||||
}
|
||||
2
ui/dist/static/main.js
vendored
2
ui/dist/static/main.js
vendored
File diff suppressed because one or more lines are too long
107
ui/src/Data/ServerConfig.elm
Normal file
107
ui/src/Data/ServerConfig.elm
Normal file
@@ -0,0 +1,107 @@
|
||||
module Data.ServerConfig exposing (ServerConfig, decoder, encode)
|
||||
|
||||
import Json.Decode as D
|
||||
import Json.Decode.Pipeline as P
|
||||
import Json.Encode as E
|
||||
|
||||
|
||||
|
||||
-- Generated by https://github.com/jhillyerd/go-to-elm-json
|
||||
|
||||
|
||||
type alias ServerConfig =
|
||||
{ version : String
|
||||
, buildDate : String
|
||||
, pop3Listener : String
|
||||
, webListener : String
|
||||
, smtpConfig : SmtpConfig
|
||||
, storageConfig : StorageConfig
|
||||
}
|
||||
|
||||
|
||||
type alias SmtpConfig =
|
||||
{ addr : String
|
||||
, defaultAccept : Bool
|
||||
, acceptDomains : Maybe (List String)
|
||||
, rejectDomains : Maybe (List String)
|
||||
, defaultStore : Bool
|
||||
, storeDomains : Maybe (List String)
|
||||
, discardDomains : Maybe (List String)
|
||||
}
|
||||
|
||||
|
||||
type alias StorageConfig =
|
||||
{ mailboxMsgCap : Int
|
||||
, storeType : String
|
||||
, retentionPeriod : String
|
||||
}
|
||||
|
||||
|
||||
decoder : D.Decoder ServerConfig
|
||||
decoder =
|
||||
D.succeed ServerConfig
|
||||
|> P.required "version" D.string
|
||||
|> P.required "build-date" D.string
|
||||
|> P.required "pop3-listener" D.string
|
||||
|> P.required "web-listener" D.string
|
||||
|> P.required "smtp-config" smtpConfigDecoder
|
||||
|> P.required "storage-config" storageConfigDecoder
|
||||
|
||||
|
||||
encode : ServerConfig -> E.Value
|
||||
encode r =
|
||||
E.object
|
||||
[ ( "version", E.string r.version )
|
||||
, ( "build-date", E.string r.buildDate )
|
||||
, ( "pop3-listener", E.string r.pop3Listener )
|
||||
, ( "web-listener", E.string r.webListener )
|
||||
, ( "smtp-config", encodeSmtpConfig r.smtpConfig )
|
||||
, ( "storage-config", encodeStorageConfig r.storageConfig )
|
||||
]
|
||||
|
||||
|
||||
smtpConfigDecoder : D.Decoder SmtpConfig
|
||||
smtpConfigDecoder =
|
||||
D.succeed SmtpConfig
|
||||
|> P.required "addr" D.string
|
||||
|> P.required "default-accept" D.bool
|
||||
|> P.required "accept-domains" (D.nullable (D.list D.string))
|
||||
|> P.required "reject-domains" (D.nullable (D.list D.string))
|
||||
|> P.required "default-store" D.bool
|
||||
|> P.required "store-domains" (D.nullable (D.list D.string))
|
||||
|> P.required "discard-domains" (D.nullable (D.list D.string))
|
||||
|
||||
|
||||
encodeSmtpConfig : SmtpConfig -> E.Value
|
||||
encodeSmtpConfig r =
|
||||
E.object
|
||||
[ ( "addr", E.string r.addr )
|
||||
, ( "default-accept", E.bool r.defaultAccept )
|
||||
, ( "accept-domains", maybe (E.list E.string) r.acceptDomains )
|
||||
, ( "reject-domains", maybe (E.list E.string) r.rejectDomains )
|
||||
, ( "default-store", E.bool r.defaultStore )
|
||||
, ( "store-domains", maybe (E.list E.string) r.storeDomains )
|
||||
, ( "discard-domains", maybe (E.list E.string) r.discardDomains )
|
||||
]
|
||||
|
||||
|
||||
storageConfigDecoder : D.Decoder StorageConfig
|
||||
storageConfigDecoder =
|
||||
D.succeed StorageConfig
|
||||
|> P.required "mailbox-msg-cap" D.int
|
||||
|> P.required "store-type" D.string
|
||||
|> P.required "retention-period" D.string
|
||||
|
||||
|
||||
encodeStorageConfig : StorageConfig -> E.Value
|
||||
encodeStorageConfig r =
|
||||
E.object
|
||||
[ ( "mailbox-msg-cap", E.int r.mailboxMsgCap )
|
||||
, ( "store-type", E.string r.storeType )
|
||||
, ( "retention-period", E.string r.retentionPeriod )
|
||||
]
|
||||
|
||||
|
||||
maybe : (a -> E.Value) -> Maybe a -> E.Value
|
||||
maybe encoder =
|
||||
Maybe.map encoder >> Maybe.withDefault E.null
|
||||
@@ -1,6 +1,7 @@
|
||||
module Page.Status exposing (Model, Msg, init, subscriptions, update, view)
|
||||
|
||||
import Data.Metrics as Metrics exposing (Metrics)
|
||||
import Data.ServerConfig as ServerConfig exposing (ServerConfig)
|
||||
import Data.Session as Session exposing (Session)
|
||||
import Filesize
|
||||
import Html exposing (..)
|
||||
@@ -17,7 +18,8 @@ import Time exposing (Posix)
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ metrics : Maybe Metrics
|
||||
{ config : Maybe ServerConfig
|
||||
, metrics : Maybe Metrics
|
||||
, xCounter : Float
|
||||
, sysMem : Metric
|
||||
, heapSize : Metric
|
||||
@@ -48,7 +50,8 @@ type alias Metric =
|
||||
|
||||
init : ( Model, Cmd Msg, Session.Msg )
|
||||
init =
|
||||
( { metrics = Nothing
|
||||
( { config = Nothing
|
||||
, metrics = Nothing
|
||||
, xCounter = 60
|
||||
, sysMem = Metric "System Memory" 0 Filesize.format graphZero initDataSet 10
|
||||
, heapSize = Metric "Heap Size" 0 Filesize.format graphZero initDataSet 10
|
||||
@@ -65,7 +68,7 @@ init =
|
||||
, retainedCount = Metric "Stored Messages" 0 fmtInt graphZero initDataSet 60
|
||||
, retainedSize = Metric "Store Size" 0 Filesize.format graphZero initDataSet 60
|
||||
}
|
||||
, getMetrics
|
||||
, Cmd.batch [ loadServerConfig, loadMetrics ]
|
||||
, Session.none
|
||||
)
|
||||
|
||||
@@ -91,6 +94,7 @@ subscriptions model =
|
||||
|
||||
type Msg
|
||||
= MetricsReceived (Result Http.Error Metrics)
|
||||
| ServerConfigLoaded (Result Http.Error ServerConfig)
|
||||
| Tick Posix
|
||||
|
||||
|
||||
@@ -103,8 +107,14 @@ update session msg model =
|
||||
MetricsReceived (Err err) ->
|
||||
( model, Cmd.none, Session.SetFlash (HttpUtil.errorString err) )
|
||||
|
||||
ServerConfigLoaded (Ok config) ->
|
||||
( { model | config = Just config }, Cmd.none, Session.none )
|
||||
|
||||
ServerConfigLoaded (Err err) ->
|
||||
( model, Cmd.none, Session.SetFlash (HttpUtil.errorString err) )
|
||||
|
||||
Tick time ->
|
||||
( model, getMetrics, Session.ClearFlash )
|
||||
( model, loadMetrics, Session.none )
|
||||
|
||||
|
||||
{-| Update all metrics in Model; increment xCounter.
|
||||
@@ -203,14 +213,22 @@ updateRemoteTotal metric value history =
|
||||
}
|
||||
|
||||
|
||||
getMetrics : Cmd Msg
|
||||
getMetrics =
|
||||
loadMetrics : Cmd Msg
|
||||
loadMetrics =
|
||||
Http.get
|
||||
{ url = "/debug/vars"
|
||||
, expect = Http.expectJson MetricsReceived Metrics.decoder
|
||||
}
|
||||
|
||||
|
||||
loadServerConfig : Cmd Msg
|
||||
loadServerConfig =
|
||||
Http.get
|
||||
{ url = "/serve/status"
|
||||
, expect = Http.expectJson ServerConfigLoaded ServerConfig.decoder
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- VIEW --
|
||||
|
||||
@@ -221,12 +239,111 @@ view session model =
|
||||
, content =
|
||||
div [ class "page" ]
|
||||
[ h1 [] [ text "Status" ]
|
||||
, case model.metrics of
|
||||
Nothing ->
|
||||
div [] [ text "Loading metrics..." ]
|
||||
, div [] (configPanel model.config :: metricPanels model)
|
||||
]
|
||||
}
|
||||
|
||||
Just metrics ->
|
||||
div []
|
||||
|
||||
configPanel : Maybe ServerConfig -> Html Msg
|
||||
configPanel maybeConfig =
|
||||
let
|
||||
mailboxCap config =
|
||||
case config.storageConfig.mailboxMsgCap of
|
||||
0 ->
|
||||
"Unlimited"
|
||||
|
||||
cap ->
|
||||
String.fromInt cap ++ " messages per mailbox"
|
||||
|
||||
retentionPeriod config =
|
||||
case config.storageConfig.retentionPeriod of
|
||||
"" ->
|
||||
"Forever"
|
||||
|
||||
period ->
|
||||
period
|
||||
in
|
||||
case maybeConfig of
|
||||
Nothing ->
|
||||
text "Loading server config..."
|
||||
|
||||
Just config ->
|
||||
framePanel "Configuration"
|
||||
[ textEntry "Version" (config.version ++ ", built on " ++ config.buildDate)
|
||||
, textEntry "SMTP Listener" config.smtpConfig.addr
|
||||
, textEntry "POP3 Listener" config.pop3Listener
|
||||
, textEntry "HTTP Listener" config.webListener
|
||||
, textEntry "Accept Policy" (acceptPolicy config.smtpConfig)
|
||||
, textEntry "Store Policy" (storePolicy config.smtpConfig)
|
||||
, textEntry "Store Type" config.storageConfig.storeType
|
||||
, textEntry "Message Cap" (mailboxCap config)
|
||||
, textEntry "Retention Period" (retentionPeriod config)
|
||||
]
|
||||
|
||||
|
||||
acceptPolicy config =
|
||||
if config.defaultAccept then
|
||||
"All domains"
|
||||
++ (case config.rejectDomains of
|
||||
Nothing ->
|
||||
""
|
||||
|
||||
Just [] ->
|
||||
""
|
||||
|
||||
Just domains ->
|
||||
", except: " ++ String.join ", " domains
|
||||
)
|
||||
|
||||
else
|
||||
"No domains"
|
||||
++ (case config.acceptDomains of
|
||||
Nothing ->
|
||||
""
|
||||
|
||||
Just [] ->
|
||||
""
|
||||
|
||||
Just domains ->
|
||||
", except: " ++ String.join ", " domains
|
||||
)
|
||||
|
||||
|
||||
storePolicy config =
|
||||
if config.defaultStore then
|
||||
"All domains"
|
||||
++ (case config.discardDomains of
|
||||
Nothing ->
|
||||
""
|
||||
|
||||
Just [] ->
|
||||
""
|
||||
|
||||
Just domains ->
|
||||
", except: " ++ String.join ", " domains
|
||||
)
|
||||
|
||||
else
|
||||
"No domains"
|
||||
++ (case config.storeDomains of
|
||||
Nothing ->
|
||||
""
|
||||
|
||||
Just [] ->
|
||||
""
|
||||
|
||||
Just domains ->
|
||||
", except: " ++ String.join ", " domains
|
||||
)
|
||||
|
||||
|
||||
metricPanels : Model -> List (Html Msg)
|
||||
metricPanels model =
|
||||
case model.metrics of
|
||||
Nothing ->
|
||||
[ text "Loading metrics..." ]
|
||||
|
||||
Just _ ->
|
||||
[ framePanel "General Metrics"
|
||||
[ viewMetric model.sysMem
|
||||
, viewMetric model.heapSize
|
||||
@@ -248,8 +365,14 @@ view session model =
|
||||
, viewMetric model.retainedSize
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
textEntry : String -> String -> Html Msg
|
||||
textEntry name value =
|
||||
div [ class "metric" ]
|
||||
[ div [ class "label" ] [ text name ]
|
||||
, div [ class "text-value" ] [ text value ]
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
viewMetric : Metric -> Html Msg
|
||||
|
||||
@@ -462,6 +462,10 @@ nav.tab-bar a.active:hover {
|
||||
flex-basis: 15em;
|
||||
}
|
||||
|
||||
.metric .text-value {
|
||||
flex-basis: 40em;
|
||||
}
|
||||
|
||||
.metric .graph {
|
||||
flex-basis: 25em;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user