HEX
Server: LiteSpeed
System: Linux houston.panomity.com 6.8.0-100-generic #100-Ubuntu SMP PREEMPT_DYNAMIC Tue Jan 13 16:40:06 UTC 2026 x86_64
User: nudepix (1011)
PHP: 7.4.33
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: //workspace/rollout/mission-control-frontend.patch
diff --git a/frontend/next.config.ts b/frontend/next.config.ts
index 8c034bc..e54d28c 100644
--- a/frontend/next.config.ts
+++ b/frontend/next.config.ts
@@ -1,11 +1,24 @@
 import type { NextConfig } from "next";
 
+function normalizeBasePath(raw: string | undefined): string {
+  const trimmed = (raw ?? "").trim();
+  if (!trimmed || trimmed === "/") return "";
+  const withLeadingSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
+  return withLeadingSlash.replace(/\/+$/, "");
+}
+
+const basePath = normalizeBasePath(process.env.NEXT_PUBLIC_BASE_PATH);
+const internalApiUrl = (process.env.INTERNAL_API_URL ?? "")
+  .trim()
+  .replace(/\/+$/, "");
+
 const nextConfig: NextConfig = {
   // In dev, Next may proxy requests based on the request origin/host.
   // Allow common local origins so `next dev --hostname 127.0.0.1` works
   // when users access via http://localhost:3000 or http://127.0.0.1:3000.
   // Keep the LAN IP as well for dev on the local network.
   allowedDevOrigins: ["192.168.1.101", "localhost", "127.0.0.1"],
+  ...(basePath ? { basePath } : {}),
   images: {
     remotePatterns: [
       {
@@ -14,6 +27,36 @@ const nextConfig: NextConfig = {
       },
     ],
   },
+  async headers() {
+    return [
+      {
+        source: "/((?!_next/static|_next/image|favicon.ico).*)",
+        headers: [
+          {
+            key: "Cache-Control",
+            value: "no-store, no-cache, must-revalidate, max-age=0",
+          },
+        ],
+      },
+    ];
+  },
+  async rewrites() {
+    if (!internalApiUrl) return [];
+    return [
+      {
+        source: "/healthz",
+        destination: `${internalApiUrl}/healthz`,
+      },
+      {
+        source: "/readyz",
+        destination: `${internalApiUrl}/readyz`,
+      },
+      {
+        source: "/openapi.json",
+        destination: `${internalApiUrl}/openapi.json`,
+      },
+    ];
+  },
 };
 
 export default nextConfig;
diff --git a/frontend/src/app/api/[...path]/route.ts b/frontend/src/app/api/[...path]/route.ts
new file mode 100644
index 0000000..12d6b8d
--- /dev/null
+++ b/frontend/src/app/api/[...path]/route.ts
@@ -0,0 +1,139 @@
+import { NextRequest } from "next/server";
+
+const RAW_INTERNAL_API_URL = process.env.INTERNAL_API_URL ?? "";
+const INTERNAL_API_URL =
+  RAW_INTERNAL_API_URL.trim().replace(/\/+$/, "") || "http://127.0.0.1:3411";
+const LOCAL_MODE = (process.env.NEXT_PUBLIC_AUTH_MODE ?? "").trim() === "local";
+const CONFIGURED_LOCAL_TOKEN = (
+  process.env.NEXT_PUBLIC_LOCAL_AUTH_TOKEN ?? process.env.LOCAL_AUTH_TOKEN ?? ""
+)
+  .trim();
+
+const HOP_BY_HOP_REQUEST_HEADERS = new Set([
+  "connection",
+  "keep-alive",
+  "proxy-authenticate",
+  "proxy-authorization",
+  "te",
+  "trailer",
+  "transfer-encoding",
+  "upgrade",
+  "host",
+]);
+
+const HOP_BY_HOP_RESPONSE_HEADERS = new Set([
+  "connection",
+  "keep-alive",
+  "proxy-authenticate",
+  "proxy-authorization",
+  "te",
+  "trailer",
+  "transfer-encoding",
+  "upgrade",
+]);
+
+function targetUrlFrom(req: NextRequest, path: string[] | undefined): string {
+  const joined = (path ?? []).join("/");
+  const suffix = joined ? `/${joined}` : "";
+  const search = req.nextUrl.search || "";
+  return `${INTERNAL_API_URL}/api${suffix}${search}`;
+}
+
+function prepareRequestHeaders(req: NextRequest): Headers {
+  const headers = new Headers(req.headers);
+
+  for (const name of HOP_BY_HOP_REQUEST_HEADERS) {
+    headers.delete(name);
+  }
+
+  const overrideToken = (headers.get("x-mc-local-token") ?? "").trim();
+  headers.delete("x-mc-local-token");
+
+  if (LOCAL_MODE) {
+    if (overrideToken) {
+      headers.set("authorization", `Bearer ${overrideToken}`);
+    } else if (!headers.has("authorization")) {
+      const effective = CONFIGURED_LOCAL_TOKEN;
+      if (!effective) {
+        throw new Error(
+          "Local auth mode is enabled but no token is configured in frontend environment.",
+        );
+      }
+      headers.set("authorization", `Bearer ${effective}`);
+    }
+  }
+
+  return headers;
+}
+
+function prepareResponseHeaders(upstream: Headers): Headers {
+  const headers = new Headers(upstream);
+  for (const name of HOP_BY_HOP_RESPONSE_HEADERS) {
+    headers.delete(name);
+  }
+  return headers;
+}
+
+async function proxy(req: NextRequest, path: string[] | undefined): Promise<Response> {
+  let requestHeaders: Headers;
+  try {
+    requestHeaders = prepareRequestHeaders(req);
+  } catch (error) {
+    const message = error instanceof Error ? error.message : "Proxy auth setup failed";
+    return Response.json({ detail: message }, { status: 500 });
+  }
+
+  const method = req.method.toUpperCase();
+  const hasBody = method !== "GET" && method !== "HEAD";
+  const body = hasBody ? await req.arrayBuffer() : undefined;
+
+  const upstream = await fetch(targetUrlFrom(req, path), {
+    method,
+    headers: requestHeaders,
+    body,
+    redirect: "manual",
+    cache: "no-store",
+  });
+
+  return new Response(upstream.body, {
+    status: upstream.status,
+    statusText: upstream.statusText,
+    headers: prepareResponseHeaders(upstream.headers),
+  });
+}
+
+type RouteContext = {
+  params: Promise<{
+    path: string[];
+  }>;
+};
+
+export async function GET(req: NextRequest, ctx: RouteContext): Promise<Response> {
+  const { path } = await ctx.params;
+  return proxy(req, path);
+}
+
+export async function POST(req: NextRequest, ctx: RouteContext): Promise<Response> {
+  const { path } = await ctx.params;
+  return proxy(req, path);
+}
+
+export async function PUT(req: NextRequest, ctx: RouteContext): Promise<Response> {
+  const { path } = await ctx.params;
+  return proxy(req, path);
+}
+
+export async function PATCH(req: NextRequest, ctx: RouteContext): Promise<Response> {
+  const { path } = await ctx.params;
+  return proxy(req, path);
+}
+
+export async function DELETE(req: NextRequest, ctx: RouteContext): Promise<Response> {
+  const { path } = await ctx.params;
+  return proxy(req, path);
+}
+
+export async function OPTIONS(req: NextRequest, ctx: RouteContext): Promise<Response> {
+  const { path } = await ctx.params;
+  return proxy(req, path);
+}