Skip to content
Open
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
19 changes: 13 additions & 6 deletions apps/web/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,27 @@ VITE_API_BASE_URL=http://localhost:8080
# Example values: dealbot.filoz.org or staging.dealbot.filoz.org
VITE_PLAUSIBLE_DATA_DOMAIN=

# Dashboard URL linked from the landing page
# Metrics dashboard URL linked from the per-SP rows on the landing page.
# One dashboard per environment; the app adds a vs[network] filter per row so it
# serves every network the deployment monitors (a no-op until the dashboard
# defines a {{network}} variable).
# Example: https://your-team.betteruptime.com
VITE_DASHBOARD_URL=

# Dashboard URL to embed as an iframe on the landing page (optional)
# Example: https://your-team.betteruptime.com/embed/...
VITE_DASHBOARD_EMBED_URL=

# Combined approved-SP dashboard URL linked from the landing page, per network (optional)
# The frontend picks the URL matching the backend's network (mainnet vs calibration).
# Example mainnet: https://telemetry.betterstack.com/dashboards/Zz8k7L?rf=now-72h&rt=now
# Example calibration: https://telemetry.betterstack.com/dashboards/wt6QCw?rf=now-3d&rt=now
# Combined approved-SP dashboard URL linked from the landing page (optional).
# Preferred: one per-environment dashboard; the app adds a vs[network] filter.
# When set, it wins over the legacy per-network vars below.
# Example: https://telemetry.betterstack.com/dashboards/Zz8k7L?rf=now-72h&rt=now
VITE_APPROVED_SP_DASHBOARD_URL=
# Legacy per-network approved-SP dashboards (fallback used when the combined var
# above is unset). Each deployment sets the one matching its network.
VITE_APPROVED_SP_DASHBOARD_URL_MAINNET=
VITE_APPROVED_SP_DASHBOARD_URL_CALIBRATION=

# Logs URL linked from the landing page (optional)
# Logs URL linked from the per-SP rows on the landing page (optional).
# One dashboard per environment; the app adds a vs[network] filter per row.
VITE_LOGS_URL=
3 changes: 3 additions & 0 deletions apps/web/docker-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ API_BASE_URL="${DEALBOT_API_BASE_URL:-${VITE_API_BASE_URL:-}}"
PLAUSIBLE_DATA_DOMAIN="${VITE_PLAUSIBLE_DATA_DOMAIN:-}"
DASHBOARD_URL="${DASHBOARD_URL:-${VITE_DASHBOARD_URL:-}}"
DASHBOARD_EMBED_URL="${DASHBOARD_EMBED_URL:-${VITE_DASHBOARD_EMBED_URL:-}}"
APPROVED_SP_DASHBOARD_URL="${APPROVED_SP_DASHBOARD_URL:-${VITE_APPROVED_SP_DASHBOARD_URL:-}}"
APPROVED_SP_DASHBOARD_URL_MAINNET="${APPROVED_SP_DASHBOARD_URL_MAINNET:-${VITE_APPROVED_SP_DASHBOARD_URL_MAINNET:-}}"
APPROVED_SP_DASHBOARD_URL_CALIBRATION="${APPROVED_SP_DASHBOARD_URL_CALIBRATION:-${VITE_APPROVED_SP_DASHBOARD_URL_CALIBRATION:-}}"
LOGS_URL="${LOGS_URL:-${VITE_LOGS_URL:-}}"
Expand All @@ -19,6 +20,7 @@ API_BASE_URL_ESCAPED="$(escape_js "$API_BASE_URL")"
PLAUSIBLE_DATA_DOMAIN_ESCAPED="$(escape_js "$PLAUSIBLE_DATA_DOMAIN")"
DASHBOARD_URL_ESCAPED="$(escape_js "$DASHBOARD_URL")"
DASHBOARD_EMBED_URL_ESCAPED="$(escape_js "$DASHBOARD_EMBED_URL")"
APPROVED_SP_DASHBOARD_URL_ESCAPED="$(escape_js "$APPROVED_SP_DASHBOARD_URL")"
APPROVED_SP_DASHBOARD_URL_MAINNET_ESCAPED="$(escape_js "$APPROVED_SP_DASHBOARD_URL_MAINNET")"
APPROVED_SP_DASHBOARD_URL_CALIBRATION_ESCAPED="$(escape_js "$APPROVED_SP_DASHBOARD_URL_CALIBRATION")"
LOGS_URL_ESCAPED="$(escape_js "$LOGS_URL")"
Expand All @@ -29,6 +31,7 @@ window.__DEALBOT_CONFIG__ = {
PLAUSIBLE_DATA_DOMAIN: "${PLAUSIBLE_DATA_DOMAIN_ESCAPED}",
DASHBOARD_URL: "${DASHBOARD_URL_ESCAPED}",
DASHBOARD_EMBED_URL: "${DASHBOARD_EMBED_URL_ESCAPED}",
APPROVED_SP_DASHBOARD_URL: "${APPROVED_SP_DASHBOARD_URL_ESCAPED}",
APPROVED_SP_DASHBOARD_URL_MAINNET: "${APPROVED_SP_DASHBOARD_URL_MAINNET_ESCAPED}",
APPROVED_SP_DASHBOARD_URL_CALIBRATION: "${APPROVED_SP_DASHBOARD_URL_CALIBRATION_ESCAPED}",
LOGS_URL: "${LOGS_URL_ESCAPED}"
Expand Down
39 changes: 39 additions & 0 deletions apps/web/src/pages/Landing.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ function renderLanding() {
}

describe("Landing", () => {
afterEach(() => {
window.__DEALBOT_CONFIG__ = undefined;
});
beforeEach(() => {
mockUseActiveNetworks.mockReturnValue({ activeNetworks: ["mainnet"], loading: false, error: null });
mockUseSelectedNetwork.mockReturnValue(["mainnet", vi.fn()]);
Expand All @@ -98,6 +101,42 @@ describe("Landing", () => {
expect(cells[1].textContent).toBe("—");
});

it("scopes per-SP metrics and logs links with vs[network]", () => {
window.__DEALBOT_CONFIG__ = {
DASHBOARD_URL: "https://telemetry.betterstack.com/dashboards/metrics",
LOGS_URL: "https://telemetry.betterstack.com/dashboards/logs",
};
setupMock([makeProvider({ providerId: "42" })]);
renderLanding();
const metrics = screen.getByRole("link", { name: /Metrics/ });
const logs = screen.getByRole("link", { name: /Logs/ });
expect(metrics.getAttribute("href")).toContain("vs%5Bnetwork%5D=mainnet");
expect(logs.getAttribute("href")).toContain("vs%5Bnetwork%5D=mainnet");
});

it("prefers the combined approved-SP dashboard and scopes it with vs[network]", () => {
window.__DEALBOT_CONFIG__ = {
APPROVED_SP_DASHBOARD_URL: "https://telemetry.betterstack.com/dashboards/combined",
APPROVED_SP_DASHBOARD_URL_MAINNET: "https://telemetry.betterstack.com/dashboards/legacy-mainnet",
};
setupMock();
renderLanding();
const href = screen.getByRole("link", { name: /View dashboard/ }).getAttribute("href") ?? "";
expect(href).toContain("/dashboards/combined");
expect(href).toContain("vs%5Bnetwork%5D=mainnet");
});

it("falls back to the legacy per-network approved-SP dashboard when the combined var is unset", () => {
window.__DEALBOT_CONFIG__ = {
APPROVED_SP_DASHBOARD_URL_MAINNET: "https://telemetry.betterstack.com/dashboards/legacy-mainnet",
};
setupMock();
renderLanding();
const href = screen.getByRole("link", { name: /View dashboard/ }).getAttribute("href") ?? "";
expect(href).toContain("/dashboards/legacy-mainnet");
expect(href).not.toContain("vs%5Bnetwork%5D");
});

it("shows config errors instead of leaving providers loading", () => {
mockUseActiveNetworks.mockReturnValue({
activeNetworks: [],
Expand Down
65 changes: 54 additions & 11 deletions apps/web/src/pages/Landing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import type { Network } from "@/types/config";

/**
* Builds a BetterStack dashboard or logs URL scoped to a single provider.
* Appends time range (now-24h to now) and provider filter.
* Metrics dashboard uses vs[provider_id]; logs dashboard uses vs[providerId].
* Appends time range and provider filter. Metrics dashboard uses vs[provider_id];
* logs dashboard uses vs[providerId]. Network scoping is layered on separately
* via withNetworkParam.
*/
function buildBetterStackUrlWithProvider(
baseUrl: string,
Expand Down Expand Up @@ -51,11 +52,42 @@ const getConfigUrl = (runtimeValue?: string, buildValue?: string) => {
return { safe, isInvalid: Boolean(raw) && !safe };
};

type NetworkUrlSource = { runtime?: string; build?: string };

/**
* Adds the vs[network] dashboard filter to a URL, preserving existing params.
* Empty/invalid input yields "", and a null network returns the URL unchanged.
* A no-op until the BetterStack dashboard defines a {{network}} variable, so it
* is safe to emit ahead of that dashboard change.
*/
const withNetworkParam = (baseUrl: string, network: Network | null): string => {
if (!baseUrl || !network) return baseUrl;
try {
const url = new URL(baseUrl);
url.searchParams.set("vs[network]", network);
return url.toString();
} catch {
return "";
}
};

const getConfig = (network: Network | null) => {
const runtimeConfig = typeof window === "undefined" ? undefined : window.__DEALBOT_CONFIG__;

// Metrics dashboard and SP logs are a single per-environment dashboard; the
// per-SP rows add a vs[network] filter (see withNetworkParam) so one dashboard
// serves every network the deployment monitors.
const dashboardUrl = getConfigUrl(runtimeConfig?.DASHBOARD_URL, import.meta.env.VITE_DASHBOARD_URL);
const approvedSpSources: Record<Network, { runtime?: string; build?: string }> = {
const logsUrl = getConfigUrl(runtimeConfig?.LOGS_URL, import.meta.env.VITE_LOGS_URL);

// Approved-SP dashboard: prefer the combined per-environment dashboard scoped
// with vs[network]; fall back to the legacy per-network dashboards so existing
// deployments keep working until infra sets APPROVED_SP_DASHBOARD_URL.
const approvedSpGlobal = getConfigUrl(
runtimeConfig?.APPROVED_SP_DASHBOARD_URL,
import.meta.env.VITE_APPROVED_SP_DASHBOARD_URL,
);
const approvedSpPerNetworkSources: Record<Network, NetworkUrlSource> = {
mainnet: {
runtime: runtimeConfig?.APPROVED_SP_DASHBOARD_URL_MAINNET,
build: import.meta.env.VITE_APPROVED_SP_DASHBOARD_URL_MAINNET,
Expand All @@ -65,10 +97,15 @@ const getConfig = (network: Network | null) => {
build: import.meta.env.VITE_APPROVED_SP_DASHBOARD_URL_CALIBRATION,
},
};
const approvedSpDashboardUrl = network
? getConfigUrl(approvedSpSources[network].runtime, approvedSpSources[network].build)
const approvedSpPerNetwork = network
? getConfigUrl(approvedSpPerNetworkSources[network].runtime, approvedSpPerNetworkSources[network].build)
: { safe: "", isInvalid: false };
const logsUrl = getConfigUrl(runtimeConfig?.LOGS_URL, import.meta.env.VITE_LOGS_URL);
let approvedSpDashboardUrl = approvedSpPerNetwork;
if (approvedSpGlobal.safe) {
approvedSpDashboardUrl = { safe: withNetworkParam(approvedSpGlobal.safe, network), isInvalid: false };
} else if (approvedSpGlobal.isInvalid) {
approvedSpDashboardUrl = approvedSpGlobal;
}

return {
dashboardUrl: dashboardUrl.safe,
Expand Down Expand Up @@ -142,9 +179,9 @@ export default function Landing() {
</p>
{approvedSpDashboardUrlInvalid && selectedNetwork && (
<p className="text-sm text-yellow-600">
Warning: <code>APPROVED_SP_DASHBOARD_URL_{selectedNetwork.toUpperCase()}</code> (or{" "}
<code>VITE_APPROVED_SP_DASHBOARD_URL_{selectedNetwork.toUpperCase()}</code>) configured but invalid. Link
unavailable.
Warning: approved-SP dashboard URL (<code>APPROVED_SP_DASHBOARD_URL</code> or{" "}
<code>APPROVED_SP_DASHBOARD_URL_{selectedNetwork.toUpperCase()}</code>, or their <code>VITE_</code>-prefixed
build-time equivalents) configured but invalid. Link unavailable.
</p>
)}
<p className="text-sm text-muted-foreground">
Expand Down Expand Up @@ -247,11 +284,17 @@ export default function Landing() {
const providerId = provider.providerId;
const metricsHref =
dashboardUrl && providerId != null
? buildBetterStackUrlWithProvider(dashboardUrl, providerId, "vs[provider_id]")
? withNetworkParam(
buildBetterStackUrlWithProvider(dashboardUrl, providerId, "vs[provider_id]"),
selectedNetwork,
)
: "";
const logsHref =
logsUrl && providerId != null
? buildBetterStackUrlWithProvider(logsUrl, providerId, "vs[providerId]")
? withNetworkParam(
buildBetterStackUrlWithProvider(logsUrl, providerId, "vs[providerId]"),
selectedNetwork,
)
: "";
return (
<tr key={provider.address} className="border-b last:border-b-0">
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/vite-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ interface ImportMeta {
readonly VITE_PLAUSIBLE_DATA_DOMAIN?: string;
readonly VITE_DASHBOARD_URL?: string;
readonly VITE_DASHBOARD_EMBED_URL?: string;
readonly VITE_APPROVED_SP_DASHBOARD_URL?: string;
readonly VITE_APPROVED_SP_DASHBOARD_URL_MAINNET?: string;
readonly VITE_APPROVED_SP_DASHBOARD_URL_CALIBRATION?: string;
readonly VITE_LOGS_URL?: string;
Expand All @@ -20,6 +21,7 @@ interface Window {
PLAUSIBLE_DATA_DOMAIN?: string;
DASHBOARD_URL?: string;
DASHBOARD_EMBED_URL?: string;
APPROVED_SP_DASHBOARD_URL?: string;
APPROVED_SP_DASHBOARD_URL_MAINNET?: string;
APPROVED_SP_DASHBOARD_URL_CALIBRATION?: string;
LOGS_URL?: string;
Expand Down