diff --git a/.gitea/workflows/server-deploy.yml b/.gitea/workflows/server-deploy.yml index d94c61c..56a3c9e 100644 --- a/.gitea/workflows/server-deploy.yml +++ b/.gitea/workflows/server-deploy.yml @@ -60,3 +60,13 @@ jobs: # Root context: admin depends on the shared module. docker build -t "$IMAGE" -f admin/Dockerfile . docker push "$IMAGE" + + - name: Build and push adminsite image + run: | + IMAGE="${{ vars.DOCKER_HOST }}/${{ github.repository_owner }}/vantage/adminsite:latest" + docker build \ + --build-arg NEXT_PUBLIC_ADMIN_API_URL="${{ vars.ADMIN_API_URL }}" \ + --build-arg NEXT_PUBLIC_ADMIN_ENV="${{ vars.ADMIN_ENV }}" \ + -t "$IMAGE" \ + -f adminsite/Dockerfile adminsite/ + docker push "$IMAGE" diff --git a/adminsite/Dockerfile b/adminsite/Dockerfile new file mode 100644 index 0000000..a11705a --- /dev/null +++ b/adminsite/Dockerfile @@ -0,0 +1,43 @@ +FROM node:26-alpine AS deps + +WORKDIR /app + +COPY package.json package-lock.json* ./ +RUN npm install + +FROM node:26-alpine AS builder + +WORKDIR /app + +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +# Baked in at build time and must be reachable from the BROWSER, and present in +# admin's ADMIN_ORIGIN. Wrong here means every request fails at runtime. +ARG NEXT_PUBLIC_ADMIN_API_URL=http://localhost:8083 +ENV NEXT_PUBLIC_ADMIN_API_URL=$NEXT_PUBLIC_ADMIN_API_URL +ARG NEXT_PUBLIC_ADMIN_ENV=production +ENV NEXT_PUBLIC_ADMIN_ENV=$NEXT_PUBLIC_ADMIN_ENV + +RUN npm run build + +FROM node:26-alpine AS runner + +WORKDIR /app + +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 + +RUN addgroup --system --gid 1001 nodejs && \ + adduser --system --uid 1001 nextjs + +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 +ENV HOSTNAME="0.0.0.0" + +CMD ["node", "server.js"] diff --git a/deploy/docker-compose.site.yml b/deploy/docker-compose.site.yml index 31df76b..04f699f 100644 --- a/deploy/docker-compose.site.yml +++ b/deploy/docker-compose.site.yml @@ -3,7 +3,7 @@ services: image: gitea.hostxtra.co.uk/mrhid6/vantage/site:latest restart: unless-stopped ports: - - 3002:3000 + - 3003:3000 depends_on: - sitesvc sitesvc: @@ -33,9 +33,9 @@ services: PORT: "8083" ADMIN_MONGO_URI: ${ADMIN_MONGO_URI:-} CONTROL_MONGO_URI: ${MONGO_URI:-} - REDIS_ADDR: ${ADMIN_REDIS_ADDR:-10.10.10.2:6379} - REDIS_USERNAME: ${ADMIN_REDIS_USERNAME:-} - REDIS_PASSWORD: ${ADMIN_REDIS_PASSWORD:-} + REDIS_ADDR: ${REDIS_ADDR:-10.10.10.2:6379} + REDIS_USERNAME: ${REDIS_USERNAME:-} + REDIS_PASSWORD: ${REDIS_PASSWORD:-} LICENSE_SIGNING_KEY: ${LICENSE_SIGNING_KEY:-} PUBLIC_URL: ${ADMIN_PUBLIC_URL:-} ADMIN_ORIGIN: ${ADMIN_ORIGIN:-} @@ -45,4 +45,17 @@ services: SMTP_USERNAME: ${SMTP_USERNAME:-} SMTP_PASSWORD: ${SMTP_PASSWORD:-} SMTP_FROM: ${SMTP_FROM:-} + + # The staff and customer console, served at vantage-hq.hostxtra.co.uk. + # ADMIN_API_URL is baked into the image at build time, not read here, so + # changing it needs a rebuild rather than a restart — and it must appear in + # admin's ADMIN_ORIGIN above or the browser blocks every request. + adminsite: + image: gitea.hostxtra.co.uk/mrhid6/vantage/adminsite:latest + restart: unless-stopped + ports: + # 3000 is web, 3003 is the marketing site; this takes 3004. + - 3004:3000 + depends_on: + - admin networks: {} diff --git a/docs/superpowers/plans/2026-07-25-admin-site.md b/docs/superpowers/plans/2026-07-25-admin-site.md index 347b976..d143bbf 100644 --- a/docs/superpowers/plans/2026-07-25-admin-site.md +++ b/docs/superpowers/plans/2026-07-25-admin-site.md @@ -527,7 +527,7 @@ git commit -m "feat(admin): staff instance detail, subscriptions and richer sear **Interfaces:** - Produces: Tailwind classes `bg-ground bg-panel bg-panel-2 text-ink text-ink-2 text-ink-3 border-rule border-rule-soft text-accent bg-accent text-valid text-warn text-expired`, fonts `font-sans font-mono` (no display face — headings are the sans at weight 800) -- [ ] **Step 1: package.json** +- [x] **Step 1: package.json** ```json { @@ -569,7 +569,7 @@ git commit -m "feat(admin): staff instance detail, subscriptions and richer sear } ``` -- [ ] **Step 2: tsconfig.json** +- [x] **Step 2: tsconfig.json** ```json { @@ -596,7 +596,7 @@ git commit -m "feat(admin): staff instance detail, subscriptions and richer sear } ``` -- [ ] **Step 3: next.config.ts** +- [x] **Step 3: next.config.ts** ```ts import type { NextConfig } from "next"; @@ -614,7 +614,7 @@ const nextConfig: NextConfig = { export default nextConfig; ``` -- [ ] **Step 4: postcss.config.js and tailwind.config.ts** +- [x] **Step 4: postcss.config.js and tailwind.config.ts** `postcss.config.js`: @@ -678,7 +678,7 @@ const config: Config = { export default config; ``` -- [ ] **Step 5: app/globals.css** +- [x] **Step 5: app/globals.css** Copy the token block out of `site/app/globals.css` unchanged — same names, same values, all three theme selectors. The only addition is `--accent-wash`, which @@ -857,7 +857,7 @@ classes on headings in later tasks (`text-3xl font-extrabold tracking-[-0.03em]` are belt-and-braces for elements that are not `h1`–`h3`. Leave them; they cost nothing and keep a `

` used as a title looking right. -- [ ] **Step 6: app/layout.tsx** +- [x] **Step 6: app/layout.tsx** ```tsx import type { Metadata } from "next"; @@ -892,7 +892,7 @@ export default function RootLayout({ children }: { children: React.ReactNode }) } ``` -- [ ] **Step 7: .gitignore** +- [x] **Step 7: .gitignore** ``` node_modules @@ -902,7 +902,7 @@ next-env.d.ts *.lic ``` -- [ ] **Step 8: Install and confirm it builds later** +- [x] **Step 8: Install and confirm it builds later** ```bash sh /tmp/noderun.sh adminsite npm install @@ -910,7 +910,7 @@ sh /tmp/noderun.sh adminsite npm install Expected: a lockfile appears. `npm run build` cannot pass until Task 5 creates the pages it imports; that is Task 5's verification. -- [ ] **Step 9: Commit** +- [x] **Step 9: Commit** ```bash git add adminsite/ @@ -935,7 +935,7 @@ The repo has no frontend test setup. This task creates it and proves it on spec - `` - `licenceState(expiresAt: string | undefined, hasLicence: boolean): "valid" | "warn" | "expired" | "none"`, `daysRemaining(iso: string): number`, `formatDate(iso: string): string` -- [ ] **Step 1: vitest.config.ts and test/setup.ts** +- [x] **Step 1: vitest.config.ts and test/setup.ts** ```ts import { defineConfig } from "vitest/config"; @@ -967,7 +967,7 @@ afterEach(() => { }); ``` -- [ ] **Step 2: Write the failing test for the not-connected state** +- [x] **Step 2: Write the failing test for the not-connected state** `adminsite/components/NotConnected.test.tsx`: @@ -997,7 +997,7 @@ describe("NotConnectedPanel", () => { }); ``` -- [ ] **Step 3: Run it and watch it fail** +- [x] **Step 3: Run it and watch it fail** ```bash sh /tmp/noderun.sh adminsite npx vitest run components/NotConnected.test.tsx @@ -1005,7 +1005,7 @@ sh /tmp/noderun.sh adminsite npx vitest run components/NotConnected.test.tsx Expected: FAIL — `Failed to resolve import "./NotConnected"`. -- [ ] **Step 4: Write the component** +- [x] **Step 4: Write the component** `adminsite/components/NotConnected.tsx`: @@ -1043,7 +1043,7 @@ export function NotConnectedPanel({ url }: { url: string }) { } ``` -- [ ] **Step 5: Run it and watch it pass** +- [x] **Step 5: Run it and watch it pass** ```bash sh /tmp/noderun.sh adminsite npx vitest run components/NotConnected.test.tsx @@ -1051,7 +1051,7 @@ sh /tmp/noderun.sh adminsite npx vitest run components/NotConnected.test.tsx Expected: `2 passed`. -- [ ] **Step 6: Write the API client** +- [x] **Step 6: Write the API client** `adminsite/lib/api.ts`: @@ -1279,7 +1279,7 @@ export const api = { }; ``` -- [ ] **Step 7: Write format helpers and providers** +- [x] **Step 7: Write format helpers and providers** `adminsite/lib/format.ts`: @@ -1353,7 +1353,7 @@ export function Providers({ children }: { children: React.ReactNode }) { } ``` -- [ ] **Step 8: Run the whole suite** +- [x] **Step 8: Run the whole suite** ```bash sh /tmp/noderun.sh adminsite npx vitest run @@ -1361,7 +1361,7 @@ sh /tmp/noderun.sh adminsite npx vitest run Expected: `2 passed`. -- [ ] **Step 9: Commit** +- [x] **Step 9: Commit** ```bash git add adminsite/ @@ -1381,7 +1381,7 @@ Covers spec test 1. - Consumes: `api`, `NotConnected`, `ApiError` - Produces: `useSession()`, ``, ``, `