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:
@@ -106,7 +106,7 @@ func registerHandlers(mux *http.ServeMux, clients []*maglevClient, broker *Broke
|
||||
adminAPI := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
handleAdminAPI(w, r, byName)
|
||||
})
|
||||
realm := "maglev-frontend admin"
|
||||
realm := "maglevd-frontend admin"
|
||||
// Register /admin/api/ before /admin/ so the more specific
|
||||
// pattern wins in net/http's ServeMux.
|
||||
mux.Handle("/admin/api/", basicAuth(realm, admin.User, admin.Password, adminAPI))
|
||||
|
||||
@@ -26,14 +26,18 @@ func main() {
|
||||
}
|
||||
|
||||
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")
|
||||
servers := stringFlag("server", "", "MAGLEV_SERVERS", "comma-separated maglevd gRPC addresses (required)")
|
||||
listen := stringFlag("listen", ":8080", "MAGLEV_LISTEN", "HTTP listen address")
|
||||
logLevel := stringFlag("log-level", "info", "MAGLEV_LOG_LEVEL", "log verbosity (debug|info|warn|error)")
|
||||
servers := stringFlag("server", "", "MAGLEV_FRONTEND_SERVERS", "comma-separated maglevd gRPC addresses (required)")
|
||||
listen := stringFlag("listen", ":8080", "MAGLEV_FRONTEND_LISTEN", "HTTP listen address")
|
||||
logLevel := stringFlag("log-level", "info", "MAGLEV_FRONTEND_LOG_LEVEL", "log verbosity (debug|info|warn|error)")
|
||||
flag.Parse()
|
||||
|
||||
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())
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ type MaglevdInfo struct {
|
||||
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.
|
||||
type VersionInfo struct {
|
||||
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 name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>maglev</title>
|
||||
<script type="module" crossorigin src="/view/assets/index-C-XMkBf5.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/view/assets/index-CxDuAfMR.css">
|
||||
<script type="module" crossorigin src="/view/assets/index-DjixLt11.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/view/assets/index-CExoCDXh.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -676,6 +676,45 @@
|
||||
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 {
|
||||
display: grid;
|
||||
grid-template-columns: max-content 1fr;
|
||||
|
||||
@@ -24,7 +24,7 @@ const Overview: Component = () => {
|
||||
<div class="frontend-list">
|
||||
<For each={s().frontends}>{(fe) => <FrontendCard snap={s()} frontend={fe} />}</For>
|
||||
</div>
|
||||
<VPPInfoPanel info={s().vpp_info} state={s().vpp_state} />
|
||||
<VPPInfoPanel name={s().maglevd.name} info={s().vpp_info} state={s().vpp_state} />
|
||||
</>
|
||||
)}
|
||||
</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 Flash from "../components/Flash";
|
||||
import type { VPPInfoSnapshot } from "../types";
|
||||
import { events } from "../stores/events";
|
||||
import type { LogEventPayload, VPPInfoSnapshot } from "../types";
|
||||
|
||||
type Props = {
|
||||
name: string;
|
||||
info?: VPPInfoSnapshot;
|
||||
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() : "";
|
||||
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 = (
|
||||
<span class="zippy-title">
|
||||
VPP
|
||||
@@ -23,6 +64,15 @@ const VPPInfoPanel: Component<Props> = (props) => {
|
||||
{label()}
|
||||
</span>
|
||||
</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>
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user