Rename maglev-frontend → maglevd-frontend; v0.9.1; API RX/TX pulse
Rename the web dashboard binary to maglevd-frontend and move it to /usr/sbin (it's a daemon and belongs with maglevd). The systemd unit name stays vpp-maglev-frontend.service since that prefix is the package name. Manpage, README, user-guide, and debian packaging all updated in lockstep; bump to 0.9.1 for the first real release. All frontend env vars are now prefixed MAGLEV_FRONTEND_ so a single /etc/default/vpp-maglev can be shared with maglevd without collisions. Every flag has an env equivalent for Docker use. MAGLEV_FRONTEND_USER and MAGLEV_FRONTEND_PASSWORD still gate the /admin surface. VPPInfoPanel now pulses "API: ↑↓" indicators in the zippy title whenever a vpp-api-send / vpp-api-recv log event arrives on the SSE stream for the scoped maglevd — 250ms blue flash, re-triggerable, with the two arrows tightly kerned via negative letter-spacing.
This commit is contained in:
16
Makefile
16
Makefile
@@ -1,4 +1,4 @@
|
|||||||
BINARIES := maglevd maglevc maglev-frontend
|
BINARIES := maglevd maglevc maglevd-frontend
|
||||||
MODULE := git.ipng.ch/ipng/vpp-maglev
|
MODULE := git.ipng.ch/ipng/vpp-maglev
|
||||||
PROTO_DIR := proto
|
PROTO_DIR := proto
|
||||||
PROTO_FILE := $(PROTO_DIR)/maglev.proto
|
PROTO_FILE := $(PROTO_DIR)/maglev.proto
|
||||||
@@ -15,7 +15,7 @@ FRONTEND_WEB_SRC := $(shell find cmd/frontend/web/src -type f 2>/dev/null) \
|
|||||||
FRONTEND_WEB_DIST := cmd/frontend/web/dist/index.html
|
FRONTEND_WEB_DIST := cmd/frontend/web/dist/index.html
|
||||||
|
|
||||||
NATIVE_ARCH := $(shell go env GOARCH)
|
NATIVE_ARCH := $(shell go env GOARCH)
|
||||||
VERSION := 0.1.1
|
VERSION := 0.9.1
|
||||||
COMMIT_HASH := $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
|
COMMIT_HASH := $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
|
||||||
DATE := $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
|
DATE := $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||||
LDFLAGS := -X '$(MODULE)/cmd.version=$(VERSION)' \
|
LDFLAGS := -X '$(MODULE)/cmd.version=$(VERSION)' \
|
||||||
@@ -26,7 +26,7 @@ TEST ?= tests/
|
|||||||
|
|
||||||
VPP_API_DIR ?= $(HOME)/src/vpp/build-root/install-vpp_debug-native/vpp/share/vpp/api
|
VPP_API_DIR ?= $(HOME)/src/vpp/build-root/install-vpp_debug-native/vpp/share/vpp/api
|
||||||
|
|
||||||
.PHONY: all build build-amd64 build-arm64 test proto vpp-binapi lint fixstyle fixstyle-web pkg-deb robot-test clean maglev-frontend-web
|
.PHONY: all build build-amd64 build-arm64 test proto vpp-binapi lint fixstyle fixstyle-web pkg-deb robot-test clean maglevd-frontend-web
|
||||||
|
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
@@ -34,24 +34,24 @@ build: $(GEN_FILES) $(FRONTEND_WEB_DIST)
|
|||||||
mkdir -p build/$(NATIVE_ARCH)
|
mkdir -p build/$(NATIVE_ARCH)
|
||||||
go build -ldflags "$(LDFLAGS)" -o build/$(NATIVE_ARCH)/maglevd ./cmd/maglevd/
|
go build -ldflags "$(LDFLAGS)" -o build/$(NATIVE_ARCH)/maglevd ./cmd/maglevd/
|
||||||
go build -ldflags "$(LDFLAGS)" -o build/$(NATIVE_ARCH)/maglevc ./cmd/maglevc/
|
go build -ldflags "$(LDFLAGS)" -o build/$(NATIVE_ARCH)/maglevc ./cmd/maglevc/
|
||||||
go build -ldflags "$(LDFLAGS)" -o build/$(NATIVE_ARCH)/maglev-frontend ./cmd/frontend/
|
go build -ldflags "$(LDFLAGS)" -o build/$(NATIVE_ARCH)/maglevd-frontend ./cmd/frontend/
|
||||||
|
|
||||||
build-amd64: $(GEN_FILES) $(FRONTEND_WEB_DIST)
|
build-amd64: $(GEN_FILES) $(FRONTEND_WEB_DIST)
|
||||||
mkdir -p build/amd64
|
mkdir -p build/amd64
|
||||||
GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o build/amd64/maglevd ./cmd/maglevd/
|
GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o build/amd64/maglevd ./cmd/maglevd/
|
||||||
GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o build/amd64/maglevc ./cmd/maglevc/
|
GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o build/amd64/maglevc ./cmd/maglevc/
|
||||||
GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o build/amd64/maglev-frontend ./cmd/frontend/
|
GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o build/amd64/maglevd-frontend ./cmd/frontend/
|
||||||
|
|
||||||
build-arm64: $(GEN_FILES) $(FRONTEND_WEB_DIST)
|
build-arm64: $(GEN_FILES) $(FRONTEND_WEB_DIST)
|
||||||
mkdir -p build/arm64
|
mkdir -p build/arm64
|
||||||
GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o build/arm64/maglevd ./cmd/maglevd/
|
GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o build/arm64/maglevd ./cmd/maglevd/
|
||||||
GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o build/arm64/maglevc ./cmd/maglevc/
|
GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o build/arm64/maglevc ./cmd/maglevc/
|
||||||
GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o build/arm64/maglev-frontend ./cmd/frontend/
|
GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o build/arm64/maglevd-frontend ./cmd/frontend/
|
||||||
|
|
||||||
# maglev-frontend-web rebuilds the SolidJS bundle. The Go binary embeds the
|
# maglevd-frontend-web rebuilds the SolidJS bundle. The Go binary embeds the
|
||||||
# resulting cmd/frontend/web/dist/ via //go:embed, so a `go build` after
|
# resulting cmd/frontend/web/dist/ via //go:embed, so a `go build` after
|
||||||
# this target picks up any asset changes automatically.
|
# this target picks up any asset changes automatically.
|
||||||
maglev-frontend-web: $(FRONTEND_WEB_DIST)
|
maglevd-frontend-web: $(FRONTEND_WEB_DIST)
|
||||||
|
|
||||||
$(FRONTEND_WEB_DIST): $(FRONTEND_WEB_SRC)
|
$(FRONTEND_WEB_DIST): $(FRONTEND_WEB_SRC)
|
||||||
cd cmd/frontend/web && npm install && npm run build
|
cd cmd/frontend/web && npm install && npm run build
|
||||||
|
|||||||
81
README.md
81
README.md
@@ -1,52 +1,89 @@
|
|||||||
# maglevd
|
# vpp-maglev
|
||||||
|
|
||||||
Health checker and gRPC control plane for VPP Maglev load balancing.
|
Health checker, gRPC control plane, CLI, and web dashboard for the VPP
|
||||||
|
`lb` (load-balancer) plugin. Runs as a set of three binaries under one
|
||||||
|
Debian package:
|
||||||
|
|
||||||
## Build and Install
|
- **`maglevd`** — the long-running health-checker daemon. Probes backends
|
||||||
|
(HTTP, TCP, ICMP), tracks their aggregate state, programs the VPP
|
||||||
|
dataplane via the `lb` plugin binary API, and exposes everything
|
||||||
|
over a gRPC API + Prometheus `/metrics` endpoint.
|
||||||
|
- **`maglevc`** — the interactive CLI client. Tab-completing shell with
|
||||||
|
inline help; also runs one-shot commands for scripting.
|
||||||
|
- **`maglevd-frontend`** — optional web dashboard. One binary with the
|
||||||
|
SolidJS SPA embedded via `//go:embed`; connects to one or more
|
||||||
|
maglevds over gRPC and serves a live HTTP view (read-only `/view/`
|
||||||
|
and optional basic-auth `/admin/`).
|
||||||
|
|
||||||
|
## Build and install
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
make # builds build/<arch>/maglevd and build/<arch>/maglevc
|
make # builds build/<arch>/{maglevd,maglevc,maglevd-frontend}
|
||||||
make test # runs all tests
|
make test # runs all tests
|
||||||
make pkg-deb # Creates a debian package for arm64 and amd64
|
make pkg-deb # creates a Debian package for amd64 and arm64
|
||||||
```
|
```
|
||||||
|
|
||||||
Requires Go 1.25+ and (for `make proto`) `protoc` with `protoc-gen-go` and
|
Requires Go 1.25+ and (for `make proto`) `protoc` with `protoc-gen-go`
|
||||||
`protoc-gen-go-grpc`.
|
and `protoc-gen-go-grpc`. The SolidJS bundle under
|
||||||
|
`cmd/frontend/web/` is built automatically via `make` through the
|
||||||
|
`maglevd-frontend-web` target, which needs `npm`.
|
||||||
|
|
||||||
Produces `vpp-maglev_<version>_amd64.deb` and `vpp-maglev_<version>_arm64.deb`
|
Produces `vpp-maglev_<version>_amd64.deb` and
|
||||||
in the `build/` directory by cross-compiling with `GOOS=linux GOARCH=<arch>`.
|
`vpp-maglev_<version>_arm64.deb` in the `build/` directory by
|
||||||
Requires `dpkg-deb` (available on any Debian/Ubuntu host). The installed
|
cross-compiling with `GOOS=linux GOARCH=<arch>`. Requires `dpkg-deb`
|
||||||
binaries report the exact git commit via `maglevd --version` (and
|
(available on any Debian/Ubuntu host). The installed binaries report
|
||||||
similarly for `maglevc` / `maglev-frontend`).
|
the exact git commit via `maglevd --version` (and similarly for
|
||||||
|
`maglevc` / `maglevd-frontend`).
|
||||||
|
|
||||||
## Running
|
## Running
|
||||||
|
|
||||||
After installing, the unit is enabled but not started automatically:
|
After installing, `maglevd` is enabled automatically but
|
||||||
|
`maglevd-frontend` is **not** — it's opt-in, so the web dashboard
|
||||||
|
doesn't surprise anyone who just wanted the daemon:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# edit /etc/vpp-maglev/maglev.yaml, then:
|
# edit /etc/vpp-maglev/maglev.yaml, then:
|
||||||
systemctl enable --now vpp-maglev
|
systemctl enable --now vpp-maglev
|
||||||
|
|
||||||
|
# optional: web dashboard. Edit /etc/default/vpp-maglev to set
|
||||||
|
# MAGLEV_FRONTEND_ARGS and (optionally) MAGLEV_FRONTEND_USER /
|
||||||
|
# MAGLEV_FRONTEND_PASSWORD for /admin/ access, then:
|
||||||
|
systemctl enable --now vpp-maglev-frontend
|
||||||
```
|
```
|
||||||
|
|
||||||
Or run the server and client by hand:
|
Or run the components by hand:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
maglevd --config /etc/vpp-maglev/maglev.yaml --grpc-addr :9090
|
maglevd --config /etc/vpp-maglev/maglev.yaml --grpc-addr :9090
|
||||||
maglevd --version # print version and exit
|
maglevd --version # print version and exit
|
||||||
|
|
||||||
maglevc --server localhost:9090 # interactive shell
|
maglevc --server localhost:9090 # interactive shell
|
||||||
maglevc show frontends # one-shot
|
maglevc show frontends # one-shot
|
||||||
maglevc -color=false show backends # one-shot, no ANSI color
|
maglevc -color=false show backends # one-shot, no ANSI color
|
||||||
maglevc set backend nginx0-ams pause
|
maglevc set backend nginx0-ams pause
|
||||||
|
|
||||||
|
maglevd-frontend -server localhost:9090 -listen :8080
|
||||||
```
|
```
|
||||||
|
|
||||||
Send `SIGHUP` to `maglevd` to reload config without restarting.
|
Send `SIGHUP` to `maglevd` to reload config without restarting.
|
||||||
`maglevd` requires `CAP_NET_RAW` for ICMP health checks.
|
`maglevd` requires `CAP_NET_RAW` for ICMP health checks.
|
||||||
|
|
||||||
Check out a minimal configuration file in [[debian/maglev.yaml](debian/maglev.yaml)].
|
Every flag on every binary also has an environment-variable
|
||||||
See [docs/user-guide.md](docs/user-guide.md) for flags, signals, and `maglevc` usage.
|
equivalent (e.g. `MAGLEV_CONFIG`, `MAGLEV_GRPC_ADDR`,
|
||||||
See [docs/config-guide.md](docs/config-guide.md) for the full configuration reference.
|
`MAGLEV_SERVERS`, `MAGLEV_LISTEN`, `MAGLEV_LOG_LEVEL`) so all three
|
||||||
See [docs/healthchecks.md](docs/healthchecks.md) for health state machine details.
|
programs can be driven entirely via env in containerized
|
||||||
|
deployments.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- A minimal configuration file in
|
||||||
|
[debian/maglev.yaml](debian/maglev.yaml) shows every knob.
|
||||||
|
- [docs/user-guide.md](docs/user-guide.md) — flags, signals, and
|
||||||
|
`maglevc` command reference.
|
||||||
|
- [docs/config-guide.md](docs/config-guide.md) — full YAML reference.
|
||||||
|
- [docs/healthchecks.md](docs/healthchecks.md) — health state
|
||||||
|
machine, probe scheduling, rise/fall semantics.
|
||||||
|
- Manpages: `maglevd(8)`, `maglevc(1)`, `maglevd-frontend(8)`.
|
||||||
|
|
||||||
## Docker
|
## Docker
|
||||||
|
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ func registerHandlers(mux *http.ServeMux, clients []*maglevClient, broker *Broke
|
|||||||
adminAPI := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
adminAPI := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
handleAdminAPI(w, r, byName)
|
handleAdminAPI(w, r, byName)
|
||||||
})
|
})
|
||||||
realm := "maglev-frontend admin"
|
realm := "maglevd-frontend admin"
|
||||||
// Register /admin/api/ before /admin/ so the more specific
|
// Register /admin/api/ before /admin/ so the more specific
|
||||||
// pattern wins in net/http's ServeMux.
|
// pattern wins in net/http's ServeMux.
|
||||||
mux.Handle("/admin/api/", basicAuth(realm, admin.User, admin.Password, adminAPI))
|
mux.Handle("/admin/api/", basicAuth(realm, admin.User, admin.Password, adminAPI))
|
||||||
|
|||||||
@@ -26,14 +26,18 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func run() error {
|
func run() error {
|
||||||
|
// All env vars are prefixed with MAGLEV_FRONTEND_ so a single
|
||||||
|
// /etc/default/vpp-maglev (or a Docker env file) can be shared
|
||||||
|
// with maglevd without its MAGLEV_LOG_LEVEL / MAGLEV_GRPC_ADDR /
|
||||||
|
// etc. leaking into this process's config.
|
||||||
printVersion := flag.Bool("version", false, "print version and exit")
|
printVersion := flag.Bool("version", false, "print version and exit")
|
||||||
servers := stringFlag("server", "", "MAGLEV_SERVERS", "comma-separated maglevd gRPC addresses (required)")
|
servers := stringFlag("server", "", "MAGLEV_FRONTEND_SERVERS", "comma-separated maglevd gRPC addresses (required)")
|
||||||
listen := stringFlag("listen", ":8080", "MAGLEV_LISTEN", "HTTP listen address")
|
listen := stringFlag("listen", ":8080", "MAGLEV_FRONTEND_LISTEN", "HTTP listen address")
|
||||||
logLevel := stringFlag("log-level", "info", "MAGLEV_LOG_LEVEL", "log verbosity (debug|info|warn|error)")
|
logLevel := stringFlag("log-level", "info", "MAGLEV_FRONTEND_LOG_LEVEL", "log verbosity (debug|info|warn|error)")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if *printVersion {
|
if *printVersion {
|
||||||
fmt.Printf("maglev-frontend %s (commit %s, built %s)\n",
|
fmt.Printf("maglevd-frontend %s (commit %s, built %s)\n",
|
||||||
buildinfo.Version(), buildinfo.Commit(), buildinfo.Date())
|
buildinfo.Version(), buildinfo.Commit(), buildinfo.Date())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ type MaglevdInfo struct {
|
|||||||
LastError string `json:"last_error,omitempty"`
|
LastError string `json:"last_error,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// VersionInfo is the build metadata of this maglev-frontend binary
|
// VersionInfo is the build metadata of this maglevd-frontend binary
|
||||||
// plus runtime capability flags the SPA needs to know at mount time.
|
// plus runtime capability flags the SPA needs to know at mount time.
|
||||||
type VersionInfo struct {
|
type VersionInfo struct {
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
cmd/frontend/web/dist/assets/index-DjixLt11.js
vendored
Normal file
1
cmd/frontend/web/dist/assets/index-DjixLt11.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
cmd/frontend/web/dist/index.html
vendored
4
cmd/frontend/web/dist/index.html
vendored
@@ -4,8 +4,8 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>maglev</title>
|
<title>maglev</title>
|
||||||
<script type="module" crossorigin src="/view/assets/index-C-XMkBf5.js"></script>
|
<script type="module" crossorigin src="/view/assets/index-DjixLt11.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/view/assets/index-CxDuAfMR.css">
|
<link rel="stylesheet" crossorigin href="/view/assets/index-CExoCDXh.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -676,6 +676,45 @@
|
|||||||
background: var(--state-down);
|
background: var(--state-down);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vpp-io {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0;
|
||||||
|
margin-left: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1;
|
||||||
|
letter-spacing: -0.15em;
|
||||||
|
padding-right: 0.15em;
|
||||||
|
}
|
||||||
|
.vpp-io-label {
|
||||||
|
color: var(--fg-muted);
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: normal;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
.vpp-tx,
|
||||||
|
.vpp-rx {
|
||||||
|
display: inline-block;
|
||||||
|
color: var(--fg-muted);
|
||||||
|
opacity: 0.25;
|
||||||
|
transition:
|
||||||
|
color 120ms ease-out,
|
||||||
|
opacity 120ms ease-out,
|
||||||
|
transform 120ms ease-out;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
.vpp-tx.lit {
|
||||||
|
color: var(--accent);
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
.vpp-rx.lit {
|
||||||
|
color: var(--accent);
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(1px);
|
||||||
|
}
|
||||||
|
|
||||||
.kv {
|
.kv {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: max-content 1fr;
|
grid-template-columns: max-content 1fr;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const Overview: Component = () => {
|
|||||||
<div class="frontend-list">
|
<div class="frontend-list">
|
||||||
<For each={s().frontends}>{(fe) => <FrontendCard snap={s()} frontend={fe} />}</For>
|
<For each={s().frontends}>{(fe) => <FrontendCard snap={s()} frontend={fe} />}</For>
|
||||||
</div>
|
</div>
|
||||||
<VPPInfoPanel info={s().vpp_info} state={s().vpp_state} />
|
<VPPInfoPanel name={s().maglevd.name} info={s().vpp_info} state={s().vpp_state} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { Show, type Component } from "solid-js";
|
import { Show, createEffect, createSignal, onCleanup, type Component } from "solid-js";
|
||||||
import Zippy from "../components/Zippy";
|
import Zippy from "../components/Zippy";
|
||||||
import Flash from "../components/Flash";
|
import Flash from "../components/Flash";
|
||||||
import type { VPPInfoSnapshot } from "../types";
|
import { events } from "../stores/events";
|
||||||
|
import type { LogEventPayload, VPPInfoSnapshot } from "../types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
name: string;
|
||||||
info?: VPPInfoSnapshot;
|
info?: VPPInfoSnapshot;
|
||||||
state?: string; // "connected" | "disconnected" | ""
|
state?: string; // "connected" | "disconnected" | ""
|
||||||
};
|
};
|
||||||
@@ -15,6 +17,45 @@ const VPPInfoPanel: Component<Props> = (props) => {
|
|||||||
props.info?.connecttime_ns ? new Date(props.info.connecttime_ns / 1e6).toISOString() : "";
|
props.info?.connecttime_ns ? new Date(props.info.connecttime_ns / 1e6).toISOString() : "";
|
||||||
const label = () => (props.state === "connected" ? "connected" : "disconnected");
|
const label = () => (props.state === "connected" ? "connected" : "disconnected");
|
||||||
|
|
||||||
|
// RX/TX indicators: pulse for 100ms whenever a vpp-api-send / vpp-api-recv
|
||||||
|
// debug log arrives for this maglevd. The upstream events() signal is
|
||||||
|
// updated one push at a time, so looking at the newest entry in an effect
|
||||||
|
// sees every event.
|
||||||
|
const [tx, setTx] = createSignal(false);
|
||||||
|
const [rx, setRx] = createSignal(false);
|
||||||
|
let txTimer: number | undefined;
|
||||||
|
let rxTimer: number | undefined;
|
||||||
|
const flashTx = () => {
|
||||||
|
setTx(true);
|
||||||
|
if (txTimer) clearTimeout(txTimer);
|
||||||
|
txTimer = window.setTimeout(() => {
|
||||||
|
setTx(false);
|
||||||
|
txTimer = undefined;
|
||||||
|
}, 250);
|
||||||
|
};
|
||||||
|
const flashRx = () => {
|
||||||
|
setRx(true);
|
||||||
|
if (rxTimer) clearTimeout(rxTimer);
|
||||||
|
rxTimer = window.setTimeout(() => {
|
||||||
|
setRx(false);
|
||||||
|
rxTimer = undefined;
|
||||||
|
}, 250);
|
||||||
|
};
|
||||||
|
createEffect(() => {
|
||||||
|
const evs = events();
|
||||||
|
if (!evs.length) return;
|
||||||
|
const ev = evs[evs.length - 1];
|
||||||
|
if (ev.maglevd !== props.name || ev.type !== "log") return;
|
||||||
|
const msg = (ev.payload as LogEventPayload | undefined)?.msg;
|
||||||
|
if (!msg) return;
|
||||||
|
if (msg.startsWith("vpp-api-send")) flashTx();
|
||||||
|
else if (msg.startsWith("vpp-api-recv")) flashRx();
|
||||||
|
});
|
||||||
|
onCleanup(() => {
|
||||||
|
if (txTimer) clearTimeout(txTimer);
|
||||||
|
if (rxTimer) clearTimeout(rxTimer);
|
||||||
|
});
|
||||||
|
|
||||||
const title = (
|
const title = (
|
||||||
<span class="zippy-title">
|
<span class="zippy-title">
|
||||||
VPP
|
VPP
|
||||||
@@ -23,6 +64,15 @@ const VPPInfoPanel: Component<Props> = (props) => {
|
|||||||
{label()}
|
{label()}
|
||||||
</span>
|
</span>
|
||||||
</Flash>
|
</Flash>
|
||||||
|
<span class="vpp-io" aria-hidden="true">
|
||||||
|
<span class="vpp-io-label">API:</span>
|
||||||
|
<span class="vpp-tx" classList={{ lit: tx() }}>
|
||||||
|
↑
|
||||||
|
</span>
|
||||||
|
<span class="vpp-rx" classList={{ lit: rx() }}>
|
||||||
|
↓
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
18
debian/build-deb.sh
vendored
18
debian/build-deb.sh
vendored
@@ -4,7 +4,7 @@
|
|||||||
#
|
#
|
||||||
# The commit hash is baked into the binaries at link time via -ldflags
|
# The commit hash is baked into the binaries at link time via -ldflags
|
||||||
# in the Makefile, so `maglevd --version` / `maglevc --version` /
|
# in the Makefile, so `maglevd --version` / `maglevc --version` /
|
||||||
# `maglev-frontend --version` are the source of truth for "which
|
# `maglevd-frontend --version` are the source of truth for "which
|
||||||
# build". The .deb itself carries only the release version.
|
# build". The .deb itself carries only the release version.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
@@ -28,15 +28,17 @@ install -d "$STAGING/etc/default"
|
|||||||
install -d "$STAGING/etc/vpp-maglev"
|
install -d "$STAGING/etc/vpp-maglev"
|
||||||
install -d "$STAGING/DEBIAN"
|
install -d "$STAGING/DEBIAN"
|
||||||
|
|
||||||
# Binaries
|
# Binaries. maglevd and maglevd-frontend are daemons and live under
|
||||||
install -m 755 "$REPO_ROOT/build/${ARCH}/maglevd" "$STAGING/usr/sbin/maglevd"
|
# /usr/sbin; maglevc is the interactive CLI client and lives under
|
||||||
install -m 755 "$REPO_ROOT/build/${ARCH}/maglevc" "$STAGING/usr/bin/maglevc"
|
# /usr/bin so it's on every login shell's PATH.
|
||||||
install -m 755 "$REPO_ROOT/build/${ARCH}/maglev-frontend" "$STAGING/usr/bin/maglev-frontend"
|
install -m 755 "$REPO_ROOT/build/${ARCH}/maglevd" "$STAGING/usr/sbin/maglevd"
|
||||||
|
install -m 755 "$REPO_ROOT/build/${ARCH}/maglevc" "$STAGING/usr/bin/maglevc"
|
||||||
|
install -m 755 "$REPO_ROOT/build/${ARCH}/maglevd-frontend" "$STAGING/usr/sbin/maglevd-frontend"
|
||||||
|
|
||||||
# Man pages
|
# Man pages
|
||||||
gzip -9 -c "$REPO_ROOT/docs/maglevd.8" > "$STAGING/usr/share/man/man8/maglevd.8.gz"
|
gzip -9 -c "$REPO_ROOT/docs/maglevd.8" > "$STAGING/usr/share/man/man8/maglevd.8.gz"
|
||||||
gzip -9 -c "$REPO_ROOT/docs/maglevc.1" > "$STAGING/usr/share/man/man1/maglevc.1.gz"
|
gzip -9 -c "$REPO_ROOT/docs/maglevc.1" > "$STAGING/usr/share/man/man1/maglevc.1.gz"
|
||||||
gzip -9 -c "$REPO_ROOT/docs/maglev-frontend.8" > "$STAGING/usr/share/man/man8/maglev-frontend.8.gz"
|
gzip -9 -c "$REPO_ROOT/docs/maglevd-frontend.8" > "$STAGING/usr/share/man/man8/maglevd-frontend.8.gz"
|
||||||
|
|
||||||
# Systemd units
|
# Systemd units
|
||||||
install -m 644 "$REPO_ROOT/debian/vpp-maglev.service" "$STAGING/lib/systemd/system/vpp-maglev.service"
|
install -m 644 "$REPO_ROOT/debian/vpp-maglev.service" "$STAGING/lib/systemd/system/vpp-maglev.service"
|
||||||
|
|||||||
2
debian/control.in
vendored
2
debian/control.in
vendored
@@ -13,7 +13,7 @@ Description: Maglev health-checker daemon, CLI client, and web frontend
|
|||||||
maglevc is an interactive CLI client for maglevd with tab completion,
|
maglevc is an interactive CLI client for maglevd with tab completion,
|
||||||
inline help, and one-shot mode for scripting.
|
inline help, and one-shot mode for scripting.
|
||||||
.
|
.
|
||||||
maglev-frontend is an optional web dashboard that fans one or more
|
maglevd-frontend is an optional web dashboard that fans one or more
|
||||||
maglevd gRPC streams out to browsers over Server-Sent Events. It is
|
maglevd gRPC streams out to browsers over Server-Sent Events. It is
|
||||||
installed but not enabled by default; enable with:
|
installed but not enabled by default; enable with:
|
||||||
systemctl enable --now vpp-maglev-frontend
|
systemctl enable --now vpp-maglev-frontend
|
||||||
|
|||||||
15
debian/default.vpp-maglev
vendored
15
debian/default.vpp-maglev
vendored
@@ -17,12 +17,21 @@ MAGLEV_CONFIG=/etc/vpp-maglev/maglev.yaml
|
|||||||
# Log level: debug, info, warn, error (default: info)
|
# Log level: debug, info, warn, error (default: info)
|
||||||
#MAGLEV_LOG_LEVEL=info
|
#MAGLEV_LOG_LEVEL=info
|
||||||
|
|
||||||
# ---- maglev-frontend -------------------------------------------------------
|
# ---- maglevd-frontend ------------------------------------------------------
|
||||||
# The web dashboard is installed but not enabled by default. Enable with
|
# The web dashboard is installed but not enabled by default. Enable with
|
||||||
# systemctl enable --now vpp-maglev-frontend
|
# systemctl enable --now vpp-maglev-frontend
|
||||||
# after reviewing the arguments below.
|
# after reviewing the arguments below.
|
||||||
|
|
||||||
# Command-line arguments passed to /usr/bin/maglev-frontend. At minimum
|
# Command-line arguments passed to /usr/sbin/maglevd-frontend. At minimum
|
||||||
# -server is required (comma-separated list of maglevd gRPC addresses).
|
# -server is required (comma-separated list of maglevd gRPC addresses).
|
||||||
# -listen controls the HTTP bind address. See maglev-frontend(8).
|
# -listen controls the HTTP bind address. See maglevd-frontend(8).
|
||||||
MAGLEV_FRONTEND_ARGS="-server localhost:9090 -listen=:8080"
|
MAGLEV_FRONTEND_ARGS="-server localhost:9090 -listen=:8080"
|
||||||
|
|
||||||
|
# Basic-auth credentials for the /admin/ surface. When both are set to
|
||||||
|
# non-empty values, /admin/ is reachable and the SPA exposes backend
|
||||||
|
# lifecycle mutations (pause/resume/enable/disable/set-weight). When
|
||||||
|
# either is missing or empty, /admin/ returns 404 and the SPA hides
|
||||||
|
# the admin toggle entirely. Leave commented out for a read-only
|
||||||
|
# deployment.
|
||||||
|
#MAGLEV_FRONTEND_USER=admin
|
||||||
|
#MAGLEV_FRONTEND_PASSWORD=changeme
|
||||||
|
|||||||
7
debian/vpp-maglev-frontend.service
vendored
7
debian/vpp-maglev-frontend.service
vendored
@@ -1,6 +1,6 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=Maglev web frontend dashboard
|
Description=Maglev web frontend dashboard
|
||||||
Documentation=man:maglev-frontend(8)
|
Documentation=man:maglevd-frontend(8)
|
||||||
After=network-online.target
|
After=network-online.target
|
||||||
Wants=network-online.target
|
Wants=network-online.target
|
||||||
|
|
||||||
@@ -8,12 +8,13 @@ Wants=network-online.target
|
|||||||
User=maglevd
|
User=maglevd
|
||||||
Group=maglevd
|
Group=maglevd
|
||||||
EnvironmentFile=/etc/default/vpp-maglev
|
EnvironmentFile=/etc/default/vpp-maglev
|
||||||
ExecStart=/usr/bin/maglev-frontend $MAGLEV_FRONTEND_ARGS
|
ExecStart=/usr/sbin/maglevd-frontend $MAGLEV_FRONTEND_ARGS
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
RestartSec=5s
|
RestartSec=5s
|
||||||
Type=simple
|
Type=simple
|
||||||
|
|
||||||
# Read-only presentation layer — needs no capabilities.
|
# Presentation layer — needs no capabilities. /admin/ mutations go
|
||||||
|
# through the gRPC client to maglevd, which does the privileged work.
|
||||||
NoNewPrivileges=yes
|
NoNewPrivileges=yes
|
||||||
ProtectSystem=strict
|
ProtectSystem=strict
|
||||||
ProtectHome=yes
|
ProtectHome=yes
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ command, with examples and operational notes — see the user guide at:
|
|||||||
https://git.ipng.ch/ipng/vpp-maglev/docs/user-guide.md
|
https://git.ipng.ch/ipng/vpp-maglev/docs/user-guide.md
|
||||||
.RE
|
.RE
|
||||||
.SH SEE ALSO
|
.SH SEE ALSO
|
||||||
.BR maglevd (8)
|
.BR maglevd (8),
|
||||||
|
.BR maglevd\-frontend (8)
|
||||||
.SH AUTHOR
|
.SH AUTHOR
|
||||||
Pim van Pelt <pim@ipng.ch>
|
Pim van Pelt <pim@ipng.ch>
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
.TH MAGLEV\-FRONTEND 8 "April 2026" "vpp\-maglev" "System Administration"
|
.TH MAGLEVD\-FRONTEND 8 "April 2026" "vpp\-maglev" "System Administration"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
maglev\-frontend \- web dashboard for one or more running maglevd instances
|
maglevd\-frontend \- web dashboard for one or more running maglevd instances
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
.B maglev\-frontend
|
.B maglevd\-frontend
|
||||||
\fB\-server\fR \fIaddr\fR[,\fIaddr\fR...]
|
\fB\-server\fR \fIaddr\fR[,\fIaddr\fR...]
|
||||||
[\fB\-listen\fR \fIaddr\fR]
|
[\fB\-listen\fR \fIaddr\fR]
|
||||||
[\fB\-log\-level\fR \fIlevel\fR]
|
[\fB\-log\-level\fR \fIlevel\fR]
|
||||||
[\fB\-version\fR]
|
[\fB\-version\fR]
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
.B maglev\-frontend
|
.B maglevd\-frontend
|
||||||
is a single\-binary web dashboard that connects to one or more running
|
is a single\-binary web dashboard that connects to one or more running
|
||||||
.BR maglevd (8)
|
.BR maglevd (8)
|
||||||
instances over gRPC and renders a live view of frontends, backends,
|
instances over gRPC and renders a live view of frontends, backends,
|
||||||
@@ -21,7 +21,7 @@ one or more maglevds with
|
|||||||
is enough to serve the dashboard.
|
is enough to serve the dashboard.
|
||||||
.PP
|
.PP
|
||||||
For each configured maglevd,
|
For each configured maglevd,
|
||||||
.B maglev\-frontend
|
.B maglevd\-frontend
|
||||||
maintains:
|
maintains:
|
||||||
.IP \(bu 2
|
.IP \(bu 2
|
||||||
A long\-lived
|
A long\-lived
|
||||||
@@ -45,16 +45,36 @@ A 5\-second health probe that surfaces maglevd connection drops
|
|||||||
quickly and flips the scope\-selector indicator dot red.
|
quickly and flips the scope\-selector indicator dot red.
|
||||||
.PP
|
.PP
|
||||||
Browsers connect to
|
Browsers connect to
|
||||||
.B maglev\-frontend
|
.B maglevd\-frontend
|
||||||
over HTTP. State is hydrated once via REST and then kept live via a
|
over HTTP. State is hydrated once via REST and then kept live via a
|
||||||
Server\-Sent Events stream. Short SSE disconnects (nginx idle timeout,
|
Server\-Sent Events stream. Short SSE disconnects (nginx idle timeout,
|
||||||
wifi flap, laptop wake) are handled silently via a 30\-second replay
|
wifi flap, laptop wake) are handled silently via a 30\-second replay
|
||||||
ring buffer; longer outages fall through to a full refetch. The SPA
|
ring buffer; longer outages fall through to a full refetch. The SPA
|
||||||
is stateless on reload so refreshing the page at any time returns a
|
is stateless on reload so refreshing the page at any time returns a
|
||||||
consistent view.
|
consistent view.
|
||||||
|
.PP
|
||||||
|
The frontend exposes two base paths:
|
||||||
|
.B /view/
|
||||||
|
is the read\-only dashboard and serves without authentication;
|
||||||
|
.B /admin/
|
||||||
|
is a basic\-auth\-protected variant of the same SPA that exposes
|
||||||
|
lifecycle mutations (pause / resume / enable / disable a backend,
|
||||||
|
set configured weight within a pool). The admin surface is only
|
||||||
|
mounted when both
|
||||||
|
.B MAGLEV_FRONTEND_USER
|
||||||
|
and
|
||||||
|
.B MAGLEV_FRONTEND_PASSWORD
|
||||||
|
are set to non\-empty values at startup; otherwise
|
||||||
|
.B /admin/
|
||||||
|
returns 404 and the SPA hides the admin\-toggle button entirely.
|
||||||
.SH OPTIONS
|
.SH OPTIONS
|
||||||
Each flag may also be supplied via an environment variable (shown in
|
Each flag may also be supplied via an environment variable (shown in
|
||||||
parentheses); the flag takes precedence.
|
parentheses); the flag takes precedence when both are set. All env
|
||||||
|
vars are prefixed with
|
||||||
|
.B MAGLEV_FRONTEND_
|
||||||
|
so a single env file can be shared with
|
||||||
|
.BR maglevd (8)
|
||||||
|
without variables leaking across processes.
|
||||||
.TP
|
.TP
|
||||||
.BI \-server " addr[,addr...]"
|
.BI \-server " addr[,addr...]"
|
||||||
Comma\-separated list of maglevd gRPC addresses. Required. Each
|
Comma\-separated list of maglevd gRPC addresses. Required. Each
|
||||||
@@ -62,11 +82,11 @@ entry is in
|
|||||||
.I host:port
|
.I host:port
|
||||||
form; a short display name is derived from the hostname label (for
|
form; a short display name is derived from the hostname label (for
|
||||||
IP literals the full address is used).
|
IP literals the full address is used).
|
||||||
.RI "(env: " MAGLEV_SERVERS )
|
.RI "(env: " MAGLEV_FRONTEND_SERVERS )
|
||||||
.TP
|
.TP
|
||||||
.BI \-listen " addr"
|
.BI \-listen " addr"
|
||||||
HTTP bind address for the dashboard.
|
HTTP bind address for the dashboard.
|
||||||
.RI "(default: " :8080 "; env: " MAGLEV_LISTEN )
|
.RI "(default: " :8080 "; env: " MAGLEV_FRONTEND_LISTEN )
|
||||||
.TP
|
.TP
|
||||||
.BI \-log\-level " level"
|
.BI \-log\-level " level"
|
||||||
Structured\-log verbosity:
|
Structured\-log verbosity:
|
||||||
@@ -76,19 +96,19 @@ Structured\-log verbosity:
|
|||||||
or
|
or
|
||||||
.BR error .
|
.BR error .
|
||||||
Affects
|
Affects
|
||||||
.B maglev\-frontend 's
|
.B maglevd\-frontend 's
|
||||||
own logs, not the log level it subscribes to on the upstream maglevd
|
own logs, not the log level it subscribes to on the upstream maglevd
|
||||||
(which is always
|
(which is always
|
||||||
.BR debug
|
.BR debug
|
||||||
so the probe heartbeat can animate).
|
so the probe heartbeat can animate).
|
||||||
.RI "(default: " info "; env: " MAGLEV_LOG_LEVEL )
|
.RI "(default: " info "; env: " MAGLEV_FRONTEND_LOG_LEVEL )
|
||||||
.TP
|
.TP
|
||||||
.B \-version
|
.B \-version
|
||||||
Print version, commit hash, and build date, then exit.
|
Print version, commit hash, and build date, then exit.
|
||||||
.SH HTTP ENDPOINTS
|
.SH HTTP ENDPOINTS
|
||||||
.TP
|
.TP
|
||||||
.I /view/
|
.I /view/
|
||||||
Static SPA (HTML, JS, CSS, assets).
|
Static SPA (HTML, JS, CSS, assets). Read\-only.
|
||||||
.TP
|
.TP
|
||||||
.I /view/api/maglevds
|
.I /view/api/maglevds
|
||||||
JSON array describing the configured maglevds and their current
|
JSON array describing the configured maglevds and their current
|
||||||
@@ -101,22 +121,39 @@ Full JSON state snapshot for every maglevd.
|
|||||||
Full JSON state snapshot for a single maglevd.
|
Full JSON state snapshot for a single maglevd.
|
||||||
.TP
|
.TP
|
||||||
.I /view/api/version
|
.I /view/api/version
|
||||||
Build version, commit hash, and build date.
|
Build version, commit hash, and build date, plus an
|
||||||
|
.B admin_enabled
|
||||||
|
flag the SPA uses to decide whether to show the admin toggle.
|
||||||
.TP
|
.TP
|
||||||
.I /view/api/events
|
.I /view/api/events
|
||||||
Server\-Sent Events stream. Long\-lived HTTP/1.1 chunked response
|
Server\-Sent Events stream. Long\-lived HTTP/1.1 chunked response
|
||||||
fanning out log, backend, frontend, maglevd\-status, and vpp\-status
|
fanning out log, backend, frontend, maglevd\-status, and vpp\-status
|
||||||
events to every connected browser. Supports
|
events to every connected browser. Supports
|
||||||
.B Last\-Event\-ID
|
.B Last\-Event\-ID
|
||||||
replay from a 30\-second / 2000\-event ring buffer.
|
replay from a 30\-second / 2000\-event ring buffer, plus a
|
||||||
|
.B resync
|
||||||
|
control event emitted after every maglevd config reload so the SPA
|
||||||
|
re\-hydrates from the now\-fresh server cache.
|
||||||
.TP
|
.TP
|
||||||
.I /healthz
|
.I /healthz
|
||||||
Liveness endpoint; returns 200 if the HTTP server is up.
|
Liveness endpoint; returns 200 if the HTTP server is up.
|
||||||
.TP
|
.TP
|
||||||
.I /admin/
|
.I /admin/
|
||||||
Placeholder for a future basic\-auth mutation surface. Currently
|
SPA shell served behind basic auth when
|
||||||
returns
|
.B MAGLEV_FRONTEND_USER
|
||||||
.B 501 Not Implemented .
|
and
|
||||||
|
.B MAGLEV_FRONTEND_PASSWORD
|
||||||
|
are configured. Returns 404 when they're not.
|
||||||
|
.TP
|
||||||
|
.I "/admin/api/{maglevd}/backend/{name}/{action}"
|
||||||
|
Backend lifecycle POST. Action is
|
||||||
|
.BR pause ", " resume ", " enable ", or " disable .
|
||||||
|
Returns the fresh backend snapshot as JSON.
|
||||||
|
.TP
|
||||||
|
.I "/admin/api/{maglevd}/frontend/{fe}/pool/{pool}/backend/{name}/weight"
|
||||||
|
Weight change POST. Body is
|
||||||
|
.B {"weight": 0\-100, "flush": bool} .
|
||||||
|
Returns the fresh frontend snapshot as JSON.
|
||||||
.SH REVERSE PROXY NOTES
|
.SH REVERSE PROXY NOTES
|
||||||
The SSE stream has a handful of operational requirements that every
|
The SSE stream has a handful of operational requirements that every
|
||||||
reverse proxy must satisfy:
|
reverse proxy must satisfy:
|
||||||
@@ -124,7 +161,7 @@ reverse proxy must satisfy:
|
|||||||
Disable buffering on the events endpoint. Nginx honours
|
Disable buffering on the events endpoint. Nginx honours
|
||||||
.B X\-Accel\-Buffering: no
|
.B X\-Accel\-Buffering: no
|
||||||
(sent by
|
(sent by
|
||||||
.BR maglev\-frontend )
|
.BR maglevd\-frontend )
|
||||||
but a global
|
but a global
|
||||||
.B proxy_buffering off;
|
.B proxy_buffering off;
|
||||||
in the server block is the more robust answer.
|
in the server block is the more robust answer.
|
||||||
@@ -136,36 +173,72 @@ to at least
|
|||||||
so the stream isn't torn down between the 15\-second
|
so the stream isn't torn down between the 15\-second
|
||||||
.B :\ ping
|
.B :\ ping
|
||||||
heartbeats that
|
heartbeats that
|
||||||
.B maglev\-frontend
|
.B maglevd\-frontend
|
||||||
sends.
|
sends.
|
||||||
.IP \(bu 2
|
.IP \(bu 2
|
||||||
Do not wrap the events endpoint in a gzip/brotli middleware — response
|
Do not wrap the events endpoint in a gzip/brotli middleware — response
|
||||||
compression buffers until its window fills and destroys the live\-stream
|
compression buffers until its window fills and destroys the live\-stream
|
||||||
property.
|
property.
|
||||||
.SH ENVIRONMENT
|
.SH ENVIRONMENT
|
||||||
|
All environment variables are prefixed with
|
||||||
|
.B MAGLEV_FRONTEND_
|
||||||
|
so this daemon can share
|
||||||
|
.I /etc/default/vpp-maglev
|
||||||
|
(or a container env file) with
|
||||||
|
.BR maglevd (8)
|
||||||
|
— whose env vars use only the shorter
|
||||||
|
.B MAGLEV_
|
||||||
|
prefix — without cross\-contamination.
|
||||||
.TP
|
.TP
|
||||||
.B MAGLEV_SERVERS
|
.B MAGLEV_FRONTEND_SERVERS
|
||||||
Default value of
|
Default value of
|
||||||
.BR \-server .
|
.BR \-server .
|
||||||
.TP
|
.TP
|
||||||
.B MAGLEV_LISTEN
|
.B MAGLEV_FRONTEND_LISTEN
|
||||||
Default value of
|
Default value of
|
||||||
.BR \-listen .
|
.BR \-listen .
|
||||||
.TP
|
.TP
|
||||||
.B MAGLEV_LOG_LEVEL
|
.B MAGLEV_FRONTEND_LOG_LEVEL
|
||||||
Default value of
|
Default value of
|
||||||
.BR \-log\-level .
|
.BR \-log\-level .
|
||||||
|
.TP
|
||||||
|
.B MAGLEV_FRONTEND_USER
|
||||||
|
HTTP basic\-auth username for
|
||||||
|
.BR /admin/ .
|
||||||
|
When set together with
|
||||||
|
.B MAGLEV_FRONTEND_PASSWORD
|
||||||
|
the admin surface is enabled; when either is missing or empty the
|
||||||
|
admin surface is hidden entirely (the SPA doesn't render the admin
|
||||||
|
toggle button and
|
||||||
|
.B /admin/
|
||||||
|
itself returns 404).
|
||||||
|
.TP
|
||||||
|
.B MAGLEV_FRONTEND_PASSWORD
|
||||||
|
HTTP basic\-auth password for
|
||||||
|
.BR /admin/ .
|
||||||
|
See
|
||||||
|
.B MAGLEV_FRONTEND_USER
|
||||||
|
above.
|
||||||
|
.TP
|
||||||
|
.B MAGLEV_FRONTEND_ARGS
|
||||||
|
Extra command\-line arguments picked up by the systemd unit's
|
||||||
|
.B ExecStart
|
||||||
|
line. Not read directly by the process — the unit expands it before
|
||||||
|
exec\-ing the binary.
|
||||||
.SH FILES
|
.SH FILES
|
||||||
.TP
|
.TP
|
||||||
.I /etc/default/vpp-maglev
|
.I /etc/default/vpp-maglev
|
||||||
Environment file sourced by the systemd unit before starting
|
Environment file sourced by the systemd unit before starting
|
||||||
.BR maglev\-frontend .
|
.BR maglevd\-frontend .
|
||||||
The same file is shared with
|
The same file is shared with
|
||||||
.BR maglevd (8);
|
.BR maglevd (8);
|
||||||
the
|
the
|
||||||
.B MAGLEV_FRONTEND_ARGS
|
.B MAGLEV_FRONTEND_ARGS
|
||||||
variable there is passed on the command line to
|
variable there is passed on the command line to
|
||||||
.B maglev\-frontend .
|
.BR maglevd\-frontend ,
|
||||||
|
and
|
||||||
|
.B MAGLEV_FRONTEND_USER / MAGLEV_FRONTEND_PASSWORD
|
||||||
|
are read from the process environment.
|
||||||
.SH SEE ALSO
|
.SH SEE ALSO
|
||||||
.BR maglevd (8),
|
.BR maglevd (8),
|
||||||
.BR maglevc (1)
|
.BR maglevc (1)
|
||||||
@@ -130,6 +130,7 @@ https://git.ipng.ch/ipng/vpp-maglev/docs/user-guide.md
|
|||||||
.RE
|
.RE
|
||||||
.SH SEE ALSO
|
.SH SEE ALSO
|
||||||
.BR maglevc (1),
|
.BR maglevc (1),
|
||||||
|
.BR maglevd\-frontend (8),
|
||||||
.BR vpp (8)
|
.BR vpp (8)
|
||||||
.SH AUTHOR
|
.SH AUTHOR
|
||||||
Pim van Pelt <pim@ipng.ch>
|
Pim van Pelt <pim@ipng.ch>
|
||||||
|
|||||||
@@ -241,3 +241,83 @@ keyword. The `?` character is not added to the input line.
|
|||||||
|
|
||||||
Commands and keywords support **prefix matching**: typing `sh ba` is equivalent
|
Commands and keywords support **prefix matching**: typing `sh ba` is equivalent
|
||||||
to `show backends`, and `sh ba nginx0` is equivalent to `show backends nginx0`.
|
to `show backends`, and `sh ba nginx0` is equivalent to `show backends nginx0`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## maglevd-frontend
|
||||||
|
|
||||||
|
`maglevd-frontend` is an optional web dashboard that connects to one or
|
||||||
|
more running `maglevd` instances over gRPC and renders a live view of
|
||||||
|
frontends, backends, health checks, and VPP load-balancer state. It is
|
||||||
|
a single Go binary with the SolidJS SPA embedded via `//go:embed`; no
|
||||||
|
runtime file dependencies.
|
||||||
|
|
||||||
|
Installed by the Debian package to `/usr/sbin/maglevd-frontend` but
|
||||||
|
**not** enabled by default — the operator opts in via:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
systemctl enable --now vpp-maglev-frontend
|
||||||
|
```
|
||||||
|
|
||||||
|
The systemd unit (`vpp-maglev-frontend.service`) reads its arguments
|
||||||
|
from `/etc/default/vpp-maglev` via `MAGLEV_FRONTEND_ARGS`. The same
|
||||||
|
env file is shared with `maglevd`; all `maglevd-frontend`-specific
|
||||||
|
variables are prefixed with `MAGLEV_FRONTEND_` so there's no overlap.
|
||||||
|
|
||||||
|
### Flags
|
||||||
|
|
||||||
|
| Flag | Environment variable | Default | Description |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `--server` | `MAGLEV_FRONTEND_SERVERS` | *(required)* | Comma-separated list of `host:port` maglevd addresses. |
|
||||||
|
| `--listen` | `MAGLEV_FRONTEND_LISTEN` | `:8080` | HTTP bind address. |
|
||||||
|
| `--log-level` | `MAGLEV_FRONTEND_LOG_LEVEL` | `info` | Structured-log verbosity for `maglevd-frontend`'s own logs. |
|
||||||
|
| `--version` | — | — | Print version, commit hash, and build date, then exit. |
|
||||||
|
|
||||||
|
In addition to flags, two env-only variables control the admin surface:
|
||||||
|
|
||||||
|
| Environment variable | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `MAGLEV_FRONTEND_USER` | HTTP basic-auth username for `/admin/`. |
|
||||||
|
| `MAGLEV_FRONTEND_PASSWORD` | HTTP basic-auth password for `/admin/`. |
|
||||||
|
|
||||||
|
When **both** are set and non-empty the admin surface is mounted and
|
||||||
|
the SPA's "admin…" toggle becomes visible. When either is missing or
|
||||||
|
empty the `/admin/` route returns 404 and the SPA hides the toggle —
|
||||||
|
`/view/` is always reachable read-only.
|
||||||
|
|
||||||
|
### HTTP surface
|
||||||
|
|
||||||
|
- **`/view/`** — static SPA (dashboard). No authentication.
|
||||||
|
- **`/view/api/state`**, **`/view/api/state/{name}`** — full JSON
|
||||||
|
snapshot for every maglevd, or one maglevd.
|
||||||
|
- **`/view/api/maglevds`** — configured maglevds and connection status.
|
||||||
|
- **`/view/api/version`** — build info + `admin_enabled` flag.
|
||||||
|
- **`/view/api/events`** — Server-Sent Events stream; log, backend,
|
||||||
|
frontend, maglevd-status, vpp-status events with
|
||||||
|
`Last-Event-ID` replay from a 30-second / 2000-event ring buffer.
|
||||||
|
- **`/healthz`** — liveness; returns 200 if the HTTP server is up.
|
||||||
|
- **`/admin/`** — SPA shell behind basic auth (when configured).
|
||||||
|
- **`POST /admin/api/{maglevd}/backend/{name}/{action}`** — backend
|
||||||
|
lifecycle action. `action` is `pause`, `resume`, `enable`, or
|
||||||
|
`disable`. Returns the fresh backend snapshot as JSON.
|
||||||
|
- **`POST /admin/api/{maglevd}/frontend/{fe}/pool/{pool}/backend/{name}/weight`**
|
||||||
|
— weight change. Body: `{"weight": 0-100, "flush": bool}`. When
|
||||||
|
`flush=true`, VPP's flow table for the backend is cleared;
|
||||||
|
otherwise only the new-buckets map is updated and existing
|
||||||
|
sessions keep reaching the backend until they finish.
|
||||||
|
|
||||||
|
### Reverse-proxy requirements (SSE)
|
||||||
|
|
||||||
|
Nginx, HAProxy, or any proxy in front of `maglevd-frontend` must:
|
||||||
|
|
||||||
|
- Disable buffering on the events endpoint. `X-Accel-Buffering: no`
|
||||||
|
is sent by the server; a global `proxy_buffering off;` in the
|
||||||
|
nginx server block is the more robust answer.
|
||||||
|
- Raise `proxy_read_timeout` to at least 300s so the stream isn't
|
||||||
|
torn down between the 15-second `: ping` heartbeats the server
|
||||||
|
sends.
|
||||||
|
- Not wrap the events endpoint in any gzip/brotli middleware —
|
||||||
|
response compression buffers until its window fills and destroys
|
||||||
|
the live-stream property.
|
||||||
|
|
||||||
|
See `maglevd-frontend(8)` for the full reference.
|
||||||
|
|||||||
Reference in New Issue
Block a user