From 8ff699e776d1148b24fc5731014fec26d58f0835 Mon Sep 17 00:00:00 2001
From: johndoe6345789
Date: Wed, 18 Mar 2026 22:17:42 +0000
Subject: [PATCH] fix: resolve DBAL frontend client-side errors and deployment
issues
- Fix DBAL overview page: basePath doubled in NavTabs and Link hrefs
- Fix client-side fetch URLs: prepend basePath for /api/status and /api/query
- Remove unused workspace deps (api-clients, core-hooks, redux) from DBAL frontend
- Simplify DBAL Dockerfile to standalone build (no monorepo workspace deps needed)
- Add null guards for health array in ServerStatusPanel
- Fix Prometheus nginx proxy: don't strip prefix (web.external-url handles it)
- Fix caproverforge portal: remove onMouse handlers from Server Component
Co-Authored-By: Claude Opus 4.6 (1M context)
---
deployment/config/nginx/production.conf | 2 +-
deployment/docker-compose.stack.yml | 4 +-
frontends/dbal/Dockerfile | 57 +++++++++---------------
frontends/dbal/next.config.ts | 6 ---
frontends/dbal/package.json | 6 +--
frontends/dbal/src/DaemonPage.tsx | 4 +-
frontends/dbal/src/NavTabs.tsx | 4 +-
frontends/dbal/src/QueryConsole.tsx | 3 +-
frontends/dbal/src/ServerStatusPanel.tsx | 13 +++---
9 files changed, 38 insertions(+), 61 deletions(-)
diff --git a/deployment/config/nginx/production.conf b/deployment/config/nginx/production.conf
index 771641ab6..b40f3e11e 100644
--- a/deployment/config/nginx/production.conf
+++ b/deployment/config/nginx/production.conf
@@ -345,9 +345,9 @@ http {
}
# Prometheus - Metrics scraping
+ # --web.external-url=/prometheus/ means it expects the prefix in requests
location /prometheus/ {
set $upstream_prometheus prometheus;
- rewrite ^/prometheus/(.*)$ /$1 break;
proxy_pass http://$upstream_prometheus:9090;
proxy_http_version 1.1;
proxy_set_header Host $host;
diff --git a/deployment/docker-compose.stack.yml b/deployment/docker-compose.stack.yml
index dd6042e55..83f44ef71 100644
--- a/deployment/docker-compose.stack.yml
+++ b/deployment/docker-compose.stack.yml
@@ -749,8 +749,8 @@ services:
# DBAL Frontend - Daemon overview + query console
dbal-frontend:
build:
- context: ..
- dockerfile: frontends/dbal/Dockerfile
+ context: ../frontends/dbal
+ dockerfile: Dockerfile
args:
DBAL_DAEMON_URL: http://dbal:8080
container_name: metabuilder-dbal-frontend
diff --git a/frontends/dbal/Dockerfile b/frontends/dbal/Dockerfile
index defbe7b51..008c297ca 100644
--- a/frontends/dbal/Dockerfile
+++ b/frontends/dbal/Dockerfile
@@ -1,50 +1,35 @@
-# Multi-stage build: node_modules from base image, source built fresh
-# Context: monorepo root (..)
-# Requires: docker build -f deployment/base-images/Dockerfile.node-deps \
-# -t metabuilder/base-node-deps:latest .
+FROM node:24-alpine AS base
-# --- Build stage ---
-ARG BASE_REGISTRY=metabuilder
-FROM ${BASE_REGISTRY}/base-node-deps:latest AS builder
+FROM base AS deps
+RUN apk add --no-cache libc6-compat
+WORKDIR /app
+COPY package.json ./
+RUN npm install --legacy-peer-deps
+
+FROM base AS builder
+WORKDIR /app
+COPY --from=deps /app/node_modules ./node_modules
+COPY . .
ARG DBAL_DAEMON_URL=http://dbal:8080
ENV DBAL_DAEMON_URL=$DBAL_DAEMON_URL
-# ── Shared packages (resolved by transpilePackages) ─────────────────────────
-# Only the workspace deps that dbal-ui actually imports.
-#
-# redux/api-clients → hooks-async → redux-slices (transitive chain)
-COPY redux/api-clients/ ./redux/api-clients/
-COPY redux/hooks-async/ ./redux/hooks-async/
-COPY redux/slices/ ./redux/slices/
-COPY redux/core-hooks/ ./redux/core-hooks/
+RUN mkdir -p public && npm run build
-# ── DBAL frontend (the app itself) ──────────────────────────────────────────
-COPY frontends/dbal/ ./frontends/dbal/
-
-# Build workspace library packages
-RUN for ws in redux/slices redux/hooks-async redux/api-clients redux/core-hooks; do \
- npm run build -w "$ws" --if-present 2>&1 || echo "WARN: $ws build failed (non-critical)"; \
-done
-
-# Build the app
-RUN cd frontends/dbal && npx next build
-
-# --- Runtime stage ---
-FROM node:24-alpine
+FROM base AS runner
WORKDIR /app
-
-COPY --from=builder /app/frontends/dbal/.next/standalone ./
-COPY --from=builder /app/frontends/dbal/.next/static ./frontends/dbal/.next/static
-
ENV NODE_ENV=production
-ENV PORT=3000
-ENV HOSTNAME="0.0.0.0"
-
+RUN addgroup --system --gid 1001 nodejs
+RUN adduser --system --uid 1001 nextjs
+COPY --from=builder /app/public ./public
+RUN mkdir .next && chown nextjs:nodejs .next
+COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
+COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
+USER nextjs
EXPOSE 3000
+ENV PORT=3000 HOSTNAME="0.0.0.0"
HEALTHCHECK --interval=15s --timeout=5s --start-period=30s --retries=3 \
CMD wget --quiet --tries=1 --spider http://127.0.0.1:3000/dbal || exit 1
-WORKDIR /app/frontends/dbal
CMD ["node", "server.js"]
diff --git a/frontends/dbal/next.config.ts b/frontends/dbal/next.config.ts
index 52ab32a20..9a1e917b1 100644
--- a/frontends/dbal/next.config.ts
+++ b/frontends/dbal/next.config.ts
@@ -4,12 +4,6 @@ const nextConfig: NextConfig = {
reactStrictMode: true,
output: 'standalone',
basePath: '/dbal',
- transpilePackages: [
- '@metabuilder/api-clients',
- '@metabuilder/core-hooks',
- '@metabuilder/hooks-async',
- '@metabuilder/redux-slices',
- ],
}
export default nextConfig
diff --git a/frontends/dbal/package.json b/frontends/dbal/package.json
index 0ca6d515c..3c0fa41b8 100644
--- a/frontends/dbal/package.json
+++ b/frontends/dbal/package.json
@@ -14,13 +14,9 @@
"test:e2e:ui": "playwright test --ui"
},
"dependencies": {
- "@metabuilder/api-clients": "*",
- "@metabuilder/core-hooks": "*",
"next": "^16.1.6",
"react": "^19.2.4",
- "react-dom": "^19.2.4",
- "react-redux": "^9.2.0",
- "redux": "^5.0.1"
+ "react-dom": "^19.2.4"
},
"overrides": {
"react": "^19.2.4",
diff --git a/frontends/dbal/src/DaemonPage.tsx b/frontends/dbal/src/DaemonPage.tsx
index dd2a339d4..1151ae21d 100644
--- a/frontends/dbal/src/DaemonPage.tsx
+++ b/frontends/dbal/src/DaemonPage.tsx
@@ -44,10 +44,10 @@ export function DBALDaemonPage() {
A hardened, sandboxed C++ daemon serves all database operations via REST API. It validates every request, enforces ACLs, and executes SQL through safe adapters.
-
+
Visit daemon docs
-
+
Check status
diff --git a/frontends/dbal/src/NavTabs.tsx b/frontends/dbal/src/NavTabs.tsx
index b36be5949..edbed0dea 100644
--- a/frontends/dbal/src/NavTabs.tsx
+++ b/frontends/dbal/src/NavTabs.tsx
@@ -4,8 +4,8 @@ import { usePathname } from 'next/navigation'
import styles from './NavTabs.module.scss'
const tabs = [
- { href: '/dbal', label: 'Overview' },
- { href: '/dbal/query', label: 'Query Console' },
+ { href: '/', label: 'Overview' },
+ { href: '/query', label: 'Query Console' },
]
export function NavTabs() {
diff --git a/frontends/dbal/src/QueryConsole.tsx b/frontends/dbal/src/QueryConsole.tsx
index c3290d5b2..66e134495 100644
--- a/frontends/dbal/src/QueryConsole.tsx
+++ b/frontends/dbal/src/QueryConsole.tsx
@@ -119,7 +119,8 @@ export function QueryConsole() {
}
try {
- const res = await fetch('/api/query', {
+ const basePath = process.env.__NEXT_ROUTER_BASEPATH || '/dbal'
+ const res = await fetch(`${basePath}/api/query`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ method, path, body: parsedBody }),
diff --git a/frontends/dbal/src/ServerStatusPanel.tsx b/frontends/dbal/src/ServerStatusPanel.tsx
index 9f65b1b25..4365772f3 100644
--- a/frontends/dbal/src/ServerStatusPanel.tsx
+++ b/frontends/dbal/src/ServerStatusPanel.tsx
@@ -5,7 +5,7 @@ import type { ServerHealth, StatusResponse } from './status'
import styles from './ServerStatusPanel.module.scss'
export function ServerStatusPanel() {
- const [health, setHealth] = useState([])
+ const [health, setHealth] = useState(() => [])
const [lastUpdated, setLastUpdated] = useState('')
const [error, setError] = useState(null)
const [loading, setLoading] = useState(true)
@@ -15,7 +15,8 @@ export function ServerStatusPanel() {
const fetchStatus = async () => {
try {
- const response = await fetch('/api/status', { cache: 'no-store' })
+ const basePath = process.env.__NEXT_ROUTER_BASEPATH || '/dbal'
+ const response = await fetch(`${basePath}/api/status`, { cache: 'no-store' })
if (!response.ok) {
throw new Error('Status endpoint failed')
}
@@ -48,11 +49,11 @@ export function ServerStatusPanel() {
return 'Status unavailable right now'
}
- if (health.length === 0) {
+ if (!health || health.length === 0) {
return 'Initializing status feed...'
}
- const degraded = health.some(item => item.status !== 'online')
+ const degraded = health?.some(item => item.status !== 'online')
return degraded ? 'Some systems need attention' : 'All systems nominal'
}, [health, error])
@@ -65,7 +66,7 @@ export function ServerStatusPanel() {
- {loading && health.length === 0 ? (
+ {loading && (!health || health.length === 0) ? (
Loading status...
) : error ? (
@@ -73,7 +74,7 @@ export function ServerStatusPanel() {
Try refreshing the page in a few moments.
) : (
- health.map(item => (
+ (health ?? []).map(item => (