-
-
- Version: {{.Version}} -
- GitBranch: {{.GitBranch}} -
- GitRevision: {{.GitRevision}} -
- BuildTime: {{.BuildTime}} -
- StartTime: {{.StartTime}} -
diff --git a/.gitignore b/.gitignore index 1b1813923..6d398564a 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,4 @@ cmd/test/ localdev/ .vscode/settings.json +.zed/ diff --git a/go.mod b/go.mod index b762ac7c5..2ff9d23c9 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/hashicorp/go-retryablehttp v0.7.7 github.com/juju/ratelimit v1.0.2 github.com/labstack/echo/v4 v4.12.0 + github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.20.0 github.com/prometheus/client_model v0.6.1 github.com/prometheus/common v0.55.0 diff --git a/internal/web/server.go b/internal/web/server.go index 18673a1e9..4efca1eab 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -12,6 +12,7 @@ import ( "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" + "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus/promhttp" "go.uber.org/zap" @@ -24,6 +25,14 @@ import ( //go:embed templates/*.html var templatesFS embed.FS +const ( + metricsPath = "/metrics/" + indexPath = "/" + connectionsPath = "/connections/" + rulesPath = "/rules/" + apiPrefix = "/api/v1" +) + type Server struct { glue.Reloader glue.HealthChecker @@ -50,15 +59,53 @@ func NewServer( healthChecker glue.HealthChecker, connMgr cmgr.Cmgr, ) (*Server, error) { + if err := validateConfig(cfg); err != nil { + return nil, errors.Wrap(err, "invalid configuration") + } + l := zap.S().Named("web") - templates := template.Must(template.ParseFS(templatesFS, "templates/*.html")) - for _, temp := range templates.Templates() { - l.Debug("template name: ", temp.Name()) - } e := NewEchoServer() + if err := setupMiddleware(e, cfg, l); err != nil { + return nil, errors.Wrap(err, "failed to setup middleware") + } + + if err := setupTemplates(e, l); err != nil { + return nil, errors.Wrap(err, "failed to setup templates") + } + + if err := setupMetrics(cfg); err != nil { + return nil, errors.Wrap(err, "failed to setup metrics") + } + + s := &Server{ + Reloader: relayReloader, + HealthChecker: healthChecker, + + e: e, + l: l, + cfg: cfg, + connMgr: connMgr, + addr: net.JoinHostPort(cfg.WebHost, fmt.Sprintf("%d", cfg.WebPort)), + } + + setupRoutes(s) + + return s, nil +} + +func validateConfig(cfg *config.Config) error { + // Add validation logic here + if cfg.WebPort <= 0 || cfg.WebPort > 65535 { + return errors.New("invalid web port") + } + // Add more validations as needed + return nil +} + +func setupMiddleware(e *echo.Echo, cfg *config.Config, l *zap.SugaredLogger) error { e.Use(NginxLogMiddleware(l)) - e.Renderer = &echoTemplate{templates: templates} + if cfg.WebToken != "" { e.Use(middleware.KeyAuthWithConfig(middleware.KeyAuthConfig{ KeyLookup: "query:token", @@ -70,7 +117,6 @@ func NewServer( if cfg.WebAuthUser != "" && cfg.WebAuthPass != "" { e.Use(middleware.BasicAuth(func(username, password string, c echo.Context) (bool, error) { - // Be careful to use constant time comparison to prevent timing attacks if subtle.ConstantTimeCompare([]byte(username), []byte(cfg.WebAuthUser)) == 1 && subtle.ConstantTimeCompare([]byte(password), []byte(cfg.WebAuthPass)) == 1 { return true, nil @@ -79,38 +125,51 @@ func NewServer( })) } + return nil +} + +func setupTemplates(e *echo.Echo, l *zap.SugaredLogger) error { + funcMap := template.FuncMap{ + "sub": func(a, b int) int { return a - b }, + "add": func(a, b int) int { return a + b }, + } + tmpl, err := template.New("").Funcs(funcMap).ParseFS(templatesFS, "templates/*.html") + if err != nil { + return errors.Wrap(err, "failed to parse templates") + } + templates := template.Must(tmpl, nil) + for _, temp := range templates.Templates() { + l.Debug("template name: ", temp.Name()) + } + e.Renderer = &echoTemplate{templates: templates} + return nil +} + +func setupMetrics(cfg *config.Config) error { if err := metrics.RegisterEhcoMetrics(cfg); err != nil { - return nil, err + return errors.Wrap(err, "failed to register Ehco metrics") } if err := metrics.RegisterNodeExporterMetrics(cfg); err != nil { - return nil, err + return errors.Wrap(err, "failed to register Node Exporter metrics") } - s := &Server{ - Reloader: relayReloader, - HealthChecker: healthChecker, + return nil +} - e: e, - l: l, - cfg: cfg, - connMgr: connMgr, - addr: net.JoinHostPort(cfg.WebHost, fmt.Sprintf("%d", cfg.WebPort)), - } +func setupRoutes(s *Server) { + e := s.e - // register handler - e.GET("/metrics/", echo.WrapHandler(promhttp.Handler())) + e.GET(metricsPath, echo.WrapHandler(promhttp.Handler())) e.GET("/debug/pprof/*", echo.WrapHandler(http.DefaultServeMux)) - e.GET("/", s.index) - e.GET("/connections/", s.ListConnections) - e.GET("/rules/", s.ListRules) + e.GET(indexPath, s.index) + e.GET(connectionsPath, s.ListConnections) + e.GET(rulesPath, s.ListRules) - // api group - api := e.Group("/api/v1") + api := e.Group(apiPrefix) api.GET("/config/", s.CurrentConfig) api.POST("/config/reload/", s.HandleReload) api.GET("/health_check/", s.HandleHealthCheck) api.GET("/node_metrics/", s.GetNodeMetrics) - return s, nil } func (s *Server) Start() error { diff --git a/internal/web/templates/_head.html b/internal/web/templates/_head.html new file mode 100644 index 000000000..0e30406b1 --- /dev/null +++ b/internal/web/templates/_head.html @@ -0,0 +1,13 @@ +
+Label | @@ -37,39 +34,70 @@|||||||||
---|---|---|---|---|---|---|---|---|---|
{{.RelayLabel}} | -{{.ConnType}} | -{{.GetFlow}} | -{{.Stats}} | -{{.GetTime}} | +{{.RelayLabel}} | +{{.ConnType}} | +{{.GetFlow}} | +{{.Stats}} | +{{.GetTime}} |
No connections available.
+No {{.ConnType}} connections available.
Build Info
-Quick Links({{ .Cfg.NodeLabel }})
-Build Info
+