diff --git a/Dockerfile b/Dockerfile index 058fdde5981958046807a814245df96331200649..4ff779eb274fd97e905f623a0cba80a153fae6fb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,17 @@ +FROM golang:1.19 AS build + +ADD . /src +WORKDIR /src +RUN go build -tags netgo -o server server.go + FROM node:current-bullseye AS assets ADD . /src WORKDIR /src RUN npm install && ./node_modules/.bin/webpack -FROM registry.git.autistici.org/ai3/docker/static-content:master +FROM scratch COPY --from=assets /src/assets/ /var/www/ +COPY --from=build /src/server /server + +ENTRYPOINT ["/server"] diff --git a/README.md b/README.md index 33fd3114b1e55e33c95b4dd3f80a5e8af006a53f..99f9457766b51633b01f40e5dd08b0a44f1648b3 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,13 @@ Simple admin dashboard for A/I. Contains links to useful internal systems and account management tools. + +The HTTP server serves static files but also proxies requests to some +backends we're interested in (prometheus, alertmanager). This is +necessary because we can't do client-side requests to these other +services on other domains due to browser's CORS policies: in practice, +CORS prevents the single-sign on request flow (through the sso-server +and back) to work because redirects are [forbidden by +CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors/CORSExternalRedirectNotAllowed). + + diff --git a/server.go b/server.go new file mode 100644 index 0000000000000000000000000000000000000000..aa2b48f8400e8f63dab552cd57770f3d0c564e15 --- /dev/null +++ b/server.go @@ -0,0 +1,84 @@ +package main + +import ( + "context" + "flag" + "log" + "net/http" + "net/http/httputil" + "net/url" + "os" + "os/signal" + "syscall" + "time" +) + +var ( + addr = flag.String("addr", getenv("ADDR", ":3000"), "TCP `addr` to listen on") + karmaBackend = flag.String("karma-url", getenv("ALERTS_URL", ""), "`URL` for the Karma backend") + prometheusBackend = flag.String("prometheus-url", getenv("PROMETHEUS_URL", ""), "`URL` for the Prometheus backend") + + rootDir = "/var/www" + + alertBackendURL *url.URL + prometheusBackendURL *url.URL +) + +func getenv(k, dflt string) string { + if s := os.Getenv(k); s != "" { + return s + } + return dflt +} + +func main() { + log.SetFlags(0) + flag.Parse() + + var err error + alertBackendURL, err = url.Parse(*karmaBackend) + if err != nil { + log.Fatalf("Error parsing --karma-url: %v", err) + } + prometheusBackendURL, err = url.Parse(*prometheusBackend) + if err != nil { + log.Fatalf("Error parsing --prometheus-url: %v", err) + } + + // Proxy some specific URLs to certain backends. If we need + // more we'll add them. + http.Handle("/alerts.json", httputil.NewSingleHostReverseProxy(alertBackendURL)) + http.Handle("/api/v1/", httputil.NewSingleHostReverseProxy(prometheusBackendURL)) + + // Simple static file server for /var/www. + http.Handle("/", http.FileServer(http.Dir(rootDir))) + + srv := &http.Server{ + Addr: *addr, + ReadTimeout: 30 * time.Second, + WriteTimeout: 30 * time.Second, + IdleTimeout: 600 * time.Second, + } + + sigCh := make(chan os.Signal, 1) + go func() { + <-sigCh + // Gracefully terminate for 3 seconds max, then shut + // down remaining clients. + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + err := srv.Shutdown(ctx) + if err == context.Canceled { + err = srv.Close() + } + if err != nil { + log.Printf("error terminating server: %v", err) + } + }() + signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) + + log.Printf("starting static server on %s", *addr) + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Fatalf("error: %v", err) + } +} diff --git a/src/dashboard.js b/src/dashboard.js index 5d99f87312f7dd784aa1a53cb251d5256c03365a..8ee5a286c64e1b0efc99774015708c05665c3aa9 100644 --- a/src/dashboard.js +++ b/src/dashboard.js @@ -1,14 +1,14 @@ import { PrometheusDriver } from 'prometheus-query'; const prom = new PrometheusDriver({ - endpoint: "https://monitor.autistici.org", + endpoint: "https://admin.autistici.org", baseURL: "/api/v1", withCredentials: true }); const accountsQuery = "sum(max(accounts_count) by (shard,type,status)) by (type)"; -const alertsURL = "https://alerts.autistici.org/alerts.json"; +const alertsURL = "https://admin.autistici.org/alerts.json"; const alertsQuery = { filters: ["@state=active", "severity=page"],