mirror of
https://github.com/hanzoai/bootnode.git
synced 2026-09-30 20:46:32 +00:00
fix(console): one exported apiBase, and no relative /api/ paths
Three dashboard surfaces fetched a RELATIVE /api/v1/... path. That cannot resolve in a static export — there is no server at that origin — so the observability panel, the services grid and the AI chat were dead on every deployed host. It also violates the v1-only rule: the prefix is /v1, never /api. They now call the host-derived origin at /v1/... Deleted app/api/health/route.ts, which emitted out/api/health — an /api/ path SHIPPED INSIDE the artifact. The deeper fix is the duplication. lib/fleet.ts kept a PRIVATE copy of the "brand apiUrl, else build-time default" resolver. A second private copy of exactly that logic, in lib/auth.tsx, shadowed its own import and called itself — infinite recursion that killed every checkAuth/login/register on any host classified as local, i.e. all of local development. That copy is already gone; this removes the other one. apiBase now lives once, exported from lib/api.ts beside getApiUrl, and fleet.ts imports it. Left alone deliberately: the /api/v1 strings in app/products/mpc are curl examples for a CUSTOMER's own host, not calls this app makes. Console compiles clean. Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
@@ -1,5 +0,0 @@
|
||||
export const dynamic = "force-static"
|
||||
|
||||
export function GET() {
|
||||
return Response.json({ status: "ok" })
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
"use client"
|
||||
import { apiBase } from "@/lib/api"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import {
|
||||
@@ -115,10 +116,10 @@ export default function ObservabilityPage() {
|
||||
setLoading(true)
|
||||
try {
|
||||
const [healthRes, metricsRes, eventsRes, alertsRes] = await Promise.allSettled([
|
||||
fetch("/api/v1/o11y/health").then((r) => r.ok ? r.json() : null),
|
||||
fetch("/api/v1/o11y/metrics").then((r) => r.ok ? r.json() : null),
|
||||
fetch("/api/v1/o11y/events?limit=20").then((r) => r.ok ? r.json() : []),
|
||||
fetch("/api/v1/o11y/alerts").then((r) => r.ok ? r.json() : []),
|
||||
fetch(`${apiBase()}/v1/o11y/health`).then((r) => r.ok ? r.json() : null),
|
||||
fetch(`${apiBase()}/v1/o11y/metrics`).then((r) => r.ok ? r.json() : null),
|
||||
fetch(`${apiBase()}/v1/o11y/events?limit=20`).then((r) => r.ok ? r.json() : []),
|
||||
fetch(`${apiBase()}/v1/o11y/alerts`).then((r) => r.ok ? r.json() : []),
|
||||
])
|
||||
|
||||
if (healthRes.status === "fulfilled" && healthRes.value) setHealth(healthRes.value)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"use client"
|
||||
import { apiBase } from "@/lib/api"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import {
|
||||
@@ -109,7 +110,7 @@ export default function ServicesPage() {
|
||||
const fetchServices = async () => {
|
||||
try {
|
||||
setLoading(true)
|
||||
const res = await fetch("/api/v1/infra/services")
|
||||
const res = await fetch(`${apiBase()}/v1/infra/services`)
|
||||
if (res.ok) {
|
||||
const data = await res.json()
|
||||
setServices(data.services || [])
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"use client"
|
||||
import { apiBase } from "@/lib/api"
|
||||
|
||||
import { useState, useRef, useEffect, useCallback } from "react"
|
||||
import {
|
||||
@@ -83,7 +84,7 @@ export function AiChat({
|
||||
|
||||
const tokenKey = `${getBrandKey()}_token`
|
||||
const apiKeyKey = `${getBrandKey()}_api_key`
|
||||
const res = await fetch("/api/v1/chat/completions", {
|
||||
const res = await fetch(`${apiBase()}/v1/chat/completions`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
|
||||
@@ -36,6 +36,16 @@ const getApiKey = () => {
|
||||
}
|
||||
|
||||
/** Base API URL (no trailing slash, no /v1) */
|
||||
// apiBase is THE host-derived API origin: the brand's own apiUrl when the
|
||||
// catalog gives one, else the build-time default. Exported so there is exactly
|
||||
// ONE copy — a second, private copy of this logic is what made lib/auth.tsx
|
||||
// infinitely recursive and killed all local development.
|
||||
export function apiBase(): string {
|
||||
const brandUrl = getBrand().apiUrl
|
||||
if (brandUrl) return brandUrl.replace(/\/$/, "")
|
||||
return getApiUrl()
|
||||
}
|
||||
|
||||
export function getApiUrl(): string {
|
||||
return getApiConfig().API_URL
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// read-only fleet API. The endpoint exposes only public chain data — no auth,
|
||||
// no secrets — so the lux.cloud landing + fleet dashboard can render it freely.
|
||||
|
||||
import { getApiUrl } from "@/lib/api"
|
||||
import { apiBase, getApiUrl } from "@/lib/api"
|
||||
import { getBrand } from "@/lib/brand"
|
||||
|
||||
/**
|
||||
@@ -15,11 +15,6 @@ import { getBrand } from "@/lib/brand"
|
||||
* hostname) is the canonical per-brand API URL — e.g. lux.cloud → api.cloud.lux.network.
|
||||
* Fall back to getApiUrl() (build-time env) when no brand URL is present.
|
||||
*/
|
||||
function apiBase(): string {
|
||||
const brandUrl = getBrand().apiUrl
|
||||
if (brandUrl) return brandUrl.replace(/\/$/, "")
|
||||
return getApiUrl()
|
||||
}
|
||||
|
||||
export interface ChainHeight {
|
||||
chain: string
|
||||
|
||||
Reference in New Issue
Block a user