Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions apps/gittensory-miner-ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,9 @@ The miner package invariant is client-side only with no required phone-home to b
local miner CLI can serve later — not a Wrangler deploy target.

Phase 6 data views (run history, portfolio cards) land in follow-up issues after this empty shell.

## Configuration

| Env var | Required | Description |
| --------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `VITE_MINER_UI_GRAFANA_URL` | No | If set (and non-empty), renders a footer link to your ORB/Grafana dashboard at this URL. Unset ⇒ no link. Must be `VITE_`-prefixed so Vite exposes it to the client bundle. It is a plain navigational link — no token or credential is ever appended. |
23 changes: 23 additions & 0 deletions apps/gittensory-miner-ui/src/components/grafana-footer-link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Optional footer link to an operator's ORB/Grafana dashboard (#5194). Rendered only when the operator sets
// VITE_MINER_UI_GRAFANA_URL (Vite exposes only VITE_-prefixed vars to the client bundle; a bare name would be
// undefined at build time). The URL is a plain navigational link — no auth/session token or credential is ever
// appended, and it is rendered as a normal href attribute (React escapes it, so a misconfigured value cannot
// inject markup). Renders nothing when the var is unset or empty.
export function GrafanaFooterLink() {
const url = import.meta.env.VITE_MINER_UI_GRAFANA_URL;
// Own the whole <footer> wrapper and return null for ALL of it when unset — otherwise a wrapper rendered by
// the layout would leave an empty, padded footer visible even though there is no link to show.
if (!url) return null;
return (
<footer className="mx-auto max-w-5xl px-6 py-4 text-token-sm text-muted-foreground">
<a
href={url}
target="_blank"
rel="noopener noreferrer"
className="hover-surface rounded-token-sm px-2 py-1 hover:text-foreground"
>
ORB / Grafana dashboard ↗
</a>
</footer>
);
}
34 changes: 34 additions & 0 deletions apps/gittensory-miner-ui/src/grafana-footer-link.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { render, screen } from "@testing-library/react";
import { afterEach, describe, expect, it, vi } from "vitest";
import { GrafanaFooterLink } from "./components/grafana-footer-link";

afterEach(() => vi.unstubAllEnvs());

describe("GrafanaFooterLink (#5194)", () => {
it("renders a footer with a new-tab link to the configured dashboard URL", () => {
vi.stubEnv("VITE_MINER_UI_GRAFANA_URL", "https://grafana.example.internal/d/ams");
render(<GrafanaFooterLink />);
expect(screen.getByRole("contentinfo")).toBeTruthy(); // the <footer> itself
const link = screen.getByRole("link", { name: /Grafana dashboard/i });
expect(link.getAttribute("href")).toBe("https://grafana.example.internal/d/ams");
expect(link.getAttribute("target")).toBe("_blank");
expect(link.getAttribute("rel")).toBe("noopener noreferrer");
});

it("renders NOTHING when unset/empty — no footer wrapper, not just no link", () => {
vi.stubEnv("VITE_MINER_UI_GRAFANA_URL", "");
const { container } = render(<GrafanaFooterLink />);
expect(container.firstChild).toBeNull();
expect(screen.queryByRole("contentinfo")).toBeNull(); // no empty padded <footer> either
expect(screen.queryByRole("link")).toBeNull();
});

it("uses the URL verbatim as the href — never appends a token or credential", () => {
const url = "https://dash.example.internal/grafana";
vi.stubEnv("VITE_MINER_UI_GRAFANA_URL", url);
render(<GrafanaFooterLink />);
const href = screen.getByRole("link").getAttribute("href") ?? "";
expect(href).toBe(url); // exact — no `?token=`, no appended session/credential
expect(href).not.toMatch(/token|api[_-]?key|secret|session|auth=/i);
});
});
2 changes: 2 additions & 0 deletions apps/gittensory-miner-ui/src/routes/__root.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Outlet, createRootRoute, Link } from "@tanstack/react-router";
import { GrafanaFooterLink } from "@/components/grafana-footer-link";

export const Route = createRootRoute({
component: RootLayout,
Expand Down Expand Up @@ -29,6 +30,7 @@ function RootLayout() {
<main className="mx-auto max-w-5xl px-6 py-8">
<Outlet />
</main>
<GrafanaFooterLink />
</div>
);
}
Loading