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

ui: Display server configuration on status page

This commit is contained in:
James Hillyerd
2018-11-26 22:19:37 -08:00
parent 7a16f64ff0
commit 9b3049562d
7 changed files with 326 additions and 50 deletions

View File

@@ -3,13 +3,19 @@
# description: Developer friendly Inbucket configuration # description: Developer friendly Inbucket configuration
export INBUCKET_LOGLEVEL="debug" 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_DISCARDDOMAINS="bitbucket.local"
#export INBUCKET_SMTP_DEFAULTSTORE="false"
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_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"
export INBUCKET_STORAGE_MAILBOXMSGCAP="300"
if ! test -x ./inbucket; then if ! test -x ./inbucket; then
echo "$PWD/inbucket not found/executable!" >&2 echo "$PWD/inbucket not found/executable!" >&2

View File

@@ -71,21 +71,31 @@ func RootMonitorMailbox(w http.ResponseWriter, req *http.Request, ctx *web.Conte
// RootStatus serves the Inbucket status page // RootStatus serves the Inbucket status page
func RootStatus(w http.ResponseWriter, req *http.Request, ctx *web.Context) (err error) { func RootStatus(w http.ResponseWriter, req *http.Request, ctx *web.Context) (err error) {
// Get flash messages, save session root := ctx.RootConfig
errorFlash := ctx.Session.Flashes("errors") retPeriod := ""
if err = ctx.Session.Save(req, w); err != nil { if root.Storage.RetentionPeriod > 0 {
return err retPeriod = root.Storage.RetentionPeriod.String()
} }
// Render template
return web.RenderTemplate("root/status.html", w, map[string]interface{}{ return web.RenderJSON(w,
"ctx": ctx, &jsonServerConfig{
"errorFlash": errorFlash, Version: config.Version,
"version": config.Version, BuildDate: config.BuildDate,
"buildDate": config.BuildDate, POP3Listener: root.POP3.Addr,
"smtpListener": ctx.RootConfig.SMTP.Addr, WebListener: root.Web.Addr,
"pop3Listener": ctx.RootConfig.POP3.Addr, SMTPConfig: jsonSMTPConfig{
"webListener": ctx.RootConfig.Web.Addr, Addr: root.SMTP.Addr,
"smtpConfig": ctx.RootConfig.SMTP, DefaultAccept: root.SMTP.DefaultAccept,
"storageConfig": ctx.RootConfig.Storage, 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
View 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"`
}

File diff suppressed because one or more lines are too long

View 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

View File

@@ -1,6 +1,7 @@
module Page.Status exposing (Model, Msg, init, subscriptions, update, view) module Page.Status exposing (Model, Msg, init, subscriptions, update, view)
import Data.Metrics as Metrics exposing (Metrics) import Data.Metrics as Metrics exposing (Metrics)
import Data.ServerConfig as ServerConfig exposing (ServerConfig)
import Data.Session as Session exposing (Session) import Data.Session as Session exposing (Session)
import Filesize import Filesize
import Html exposing (..) import Html exposing (..)
@@ -17,7 +18,8 @@ import Time exposing (Posix)
type alias Model = type alias Model =
{ metrics : Maybe Metrics { config : Maybe ServerConfig
, metrics : Maybe Metrics
, xCounter : Float , xCounter : Float
, sysMem : Metric , sysMem : Metric
, heapSize : Metric , heapSize : Metric
@@ -48,7 +50,8 @@ type alias Metric =
init : ( Model, Cmd Msg, Session.Msg ) init : ( Model, Cmd Msg, Session.Msg )
init = init =
( { metrics = Nothing ( { config = Nothing
, metrics = Nothing
, xCounter = 60 , xCounter = 60
, sysMem = Metric "System Memory" 0 Filesize.format graphZero initDataSet 10 , sysMem = Metric "System Memory" 0 Filesize.format graphZero initDataSet 10
, heapSize = Metric "Heap Size" 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 , retainedCount = Metric "Stored Messages" 0 fmtInt graphZero initDataSet 60
, retainedSize = Metric "Store Size" 0 Filesize.format graphZero initDataSet 60 , retainedSize = Metric "Store Size" 0 Filesize.format graphZero initDataSet 60
} }
, getMetrics , Cmd.batch [ loadServerConfig, loadMetrics ]
, Session.none , Session.none
) )
@@ -91,6 +94,7 @@ subscriptions model =
type Msg type Msg
= MetricsReceived (Result Http.Error Metrics) = MetricsReceived (Result Http.Error Metrics)
| ServerConfigLoaded (Result Http.Error ServerConfig)
| Tick Posix | Tick Posix
@@ -103,8 +107,14 @@ update session msg model =
MetricsReceived (Err err) -> MetricsReceived (Err err) ->
( model, Cmd.none, Session.SetFlash (HttpUtil.errorString 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 -> Tick time ->
( model, getMetrics, Session.ClearFlash ) ( model, loadMetrics, Session.none )
{-| Update all metrics in Model; increment xCounter. {-| Update all metrics in Model; increment xCounter.
@@ -203,14 +213,22 @@ updateRemoteTotal metric value history =
} }
getMetrics : Cmd Msg loadMetrics : Cmd Msg
getMetrics = loadMetrics =
Http.get Http.get
{ url = "/debug/vars" { url = "/debug/vars"
, expect = Http.expectJson MetricsReceived Metrics.decoder , expect = Http.expectJson MetricsReceived Metrics.decoder
} }
loadServerConfig : Cmd Msg
loadServerConfig =
Http.get
{ url = "/serve/status"
, expect = Http.expectJson ServerConfigLoaded ServerConfig.decoder
}
-- VIEW -- -- VIEW --
@@ -221,37 +239,142 @@ view session model =
, content = , content =
div [ class "page" ] div [ class "page" ]
[ h1 [] [ text "Status" ] [ h1 [] [ text "Status" ]
, case model.metrics of , div [] (configPanel model.config :: metricPanels model)
Nothing ->
div [] [ text "Loading metrics..." ]
Just metrics ->
div []
[ framePanel "General Metrics"
[ viewMetric model.sysMem
, viewMetric model.heapSize
, viewMetric model.heapUsed
, viewMetric model.heapObjects
, viewMetric model.goRoutines
, viewMetric model.webSockets
]
, framePanel "SMTP Metrics"
[ viewMetric model.smtpConnOpen
, viewMetric model.smtpConnTotal
, viewMetric model.smtpReceivedTotal
, viewMetric model.smtpErrorsTotal
, viewMetric model.smtpWarnsTotal
]
, framePanel "Storage Metrics"
[ viewMetric model.retentionDeletesTotal
, viewMetric model.retainedCount
, viewMetric model.retainedSize
]
]
] ]
} }
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
, viewMetric model.heapUsed
, viewMetric model.heapObjects
, viewMetric model.goRoutines
, viewMetric model.webSockets
]
, framePanel "SMTP Metrics"
[ viewMetric model.smtpConnOpen
, viewMetric model.smtpConnTotal
, viewMetric model.smtpReceivedTotal
, viewMetric model.smtpErrorsTotal
, viewMetric model.smtpWarnsTotal
]
, framePanel "Storage Metrics"
[ viewMetric model.retentionDeletesTotal
, viewMetric model.retainedCount
, 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 viewMetric : Metric -> Html Msg
viewMetric metric = viewMetric metric =
div [ class "metric" ] div [ class "metric" ]

View File

@@ -462,6 +462,10 @@ nav.tab-bar a.active:hover {
flex-basis: 15em; flex-basis: 15em;
} }
.metric .text-value {
flex-basis: 40em;
}
.metric .graph { .metric .graph {
flex-basis: 25em; flex-basis: 25em;
} }