check / check (push) Failing after 7m18s
The page at /settings, behind the login and linked from the navigation bar, lists every field of the configuration the server started with: each by its environment variable name, with the README's description and the value in effect. METRICS_PASSWORD and SENTRY_DSN show only as set or not set; their values never reach the template. The handlers now take the loaded Config from the dependency graph. Model: opus-5-5
131 lines
3.2 KiB
Go
131 lines
3.2 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"net/netip"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"sneak.berlin/go/webhooker/internal/config"
|
|
)
|
|
|
|
// notSet is what the Settings page shows for a value that is empty.
|
|
const notSet = "not set"
|
|
|
|
// settingRow is one line of the Settings page: an environment
|
|
// variable, what it controls, and the value the server loaded for it.
|
|
type settingRow struct {
|
|
Name string
|
|
Description string
|
|
Value string
|
|
}
|
|
|
|
// HandleSettings returns a handler for the read-only Settings page,
|
|
// which lists the configuration the server started with.
|
|
func (h *Handlers) HandleSettings() http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
h.renderTemplate(w, r, "settings.html", map[string]any{
|
|
"Settings": settingRows(h.params.Config),
|
|
})
|
|
}
|
|
}
|
|
|
|
// settingRows lists every field of cfg under the environment variable
|
|
// it is read from, in the order of the README's configuration table.
|
|
// METRICS_PASSWORD and SENTRY_DSN are credentials, so their values
|
|
// never reach the page: only whether they are set.
|
|
func settingRows(cfg *config.Config) []settingRow {
|
|
metricsUsername := cfg.MetricsUsername
|
|
if metricsUsername == "" {
|
|
metricsUsername = notSet
|
|
}
|
|
|
|
return []settingRow{
|
|
{"WEBHOOKER_ENVIRONMENT", "dev or prod", cfg.Environment},
|
|
{"PORT", "HTTP listen port", strconv.Itoa(cfg.Port)},
|
|
{
|
|
"BIND_ADDRESS",
|
|
"IP address the HTTP listener binds",
|
|
cfg.BindAddress,
|
|
},
|
|
{
|
|
"DATA_DIR",
|
|
"Directory for all SQLite databases",
|
|
cfg.DataDir,
|
|
},
|
|
{
|
|
"DEBUG",
|
|
"Enable debug logging",
|
|
strconv.FormatBool(cfg.Debug),
|
|
},
|
|
{
|
|
"MAINTENANCE_MODE",
|
|
"Report maintenanceMode: true in the healthcheck JSON. " +
|
|
"It does not change how any request is served",
|
|
strconv.FormatBool(cfg.MaintenanceMode),
|
|
},
|
|
{
|
|
"METRICS_USERNAME",
|
|
"Basic auth username for /metrics",
|
|
metricsUsername,
|
|
},
|
|
{
|
|
"METRICS_PASSWORD",
|
|
"Basic auth password for /metrics",
|
|
setOrNotSet(cfg.MetricsPassword),
|
|
},
|
|
{
|
|
"SENTRY_DSN",
|
|
"Error reporting DSN. Unset leaves error reporting off",
|
|
setOrNotSet(cfg.SentryDSN),
|
|
},
|
|
{
|
|
"RETENTION_SWEEP_INTERVAL",
|
|
"How often the retention reaper and archive sweeper run",
|
|
cfg.RetentionSweepInterval.String(),
|
|
},
|
|
{
|
|
"SESSION_IDLE_TIMEOUT",
|
|
"Idle session timeout. Zero or negative disables idle " +
|
|
"expiry",
|
|
cfg.SessionIdleTimeout.String(),
|
|
},
|
|
{
|
|
"RECEIVER_RATE_LIMIT",
|
|
"Receiver requests per minute per IP per entrypoint " +
|
|
"(10x that per IP across the route)",
|
|
strconv.Itoa(cfg.ReceiverRateLimit),
|
|
},
|
|
{
|
|
"TRUSTED_PROXIES",
|
|
"CIDRs whose forwarded headers are trusted",
|
|
cidrList(cfg.TrustedProxies),
|
|
},
|
|
{
|
|
"ALLOWED_EGRESS_CIDRS",
|
|
"CIDRs that delivery targets may reach despite the " +
|
|
"SSRF blocklist",
|
|
cidrList(cfg.AllowedEgressCIDRs),
|
|
},
|
|
}
|
|
}
|
|
|
|
// setOrNotSet is how the Settings page shows a credential: whether it
|
|
// has a value, never the value itself.
|
|
func setOrNotSet(value string) string {
|
|
if value == "" {
|
|
return notSet
|
|
}
|
|
|
|
return "set"
|
|
}
|
|
|
|
// cidrList renders a CIDR list setting for the Settings page.
|
|
func cidrList(prefixes []netip.Prefix) string {
|
|
if len(prefixes) == 0 {
|
|
return "none"
|
|
}
|
|
|
|
return strings.Join(config.PrefixStrings(prefixes), ", ")
|
|
}
|