From 78872de89703566ed7ede51f10dc7a13d2086415 Mon Sep 17 00:00:00 2001 From: Achmad Date: Wed, 24 Jun 2026 03:59:13 +0000 Subject: [PATCH] =?UTF-8?q?Slice=202:=20dashboard=20=E2=80=94=20nav,=20san?= =?UTF-8?q?dboxes/templates/environments/history=20pages,=20basePath?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New /dashboard layout with a top nav (Quick Deploy / Sandboxes / Templates / Environments / History) and a Logout button that invalidates the session. - Quick Deploy: stage list switches per repo (Go vs PHP, so the composer-install stage is shown for the gateway), env-var textarea, host-port input. - Sandboxes: list, create, clone-from-template, delete. - Sandbox detail: live _url map from the gateway's config.php, per-route toggle (OCP / sandbox override with a URL input), microservice deploys with per-service host port and env, branch picker. - Templates / Environments: list + create + delete. - History: filterable deployment list with state badges. - Sandbox detail page is a server component with generateStaticParams that delegates to a client component; required for the static export. - API client: prefix all /api and /ws URLs with NEXT_PUBLIC_BASE_PATH (set in next.config.js) so the dashboard works under a non-root basePath. - next.config.js: basePath and assetPrefix set to /sandbox/credit-card so asset URLs and internal Link hrefs resolve under the sub-path. NEXT_PUBLIC_BASE_PATH env is exposed to the browser bundle for the fetch() prefix. --- .../src/app/dashboard/environments/page.tsx | 93 ++++ dashboard/src/app/dashboard/history/page.tsx | 79 ++++ dashboard/src/app/dashboard/layout.tsx | 10 + dashboard/src/app/dashboard/page.tsx | 6 +- .../app/dashboard/sandboxes/[id]/client.tsx | 438 ++++++++++++++++++ .../src/app/dashboard/sandboxes/[id]/page.tsx | 14 + .../src/app/dashboard/sandboxes/page.tsx | 175 +++++++ .../src/app/dashboard/templates/page.tsx | 86 ++++ dashboard/src/components/dashboard-nav.tsx | 63 +++ dashboard/src/components/dashboard.tsx | 82 +++- dashboard/src/components/login-form.tsx | 3 +- dashboard/src/lib/api.ts | 263 ++++++++++- dashboard/src/lib/use-deployment-ws.ts | 5 +- 13 files changed, 1270 insertions(+), 47 deletions(-) create mode 100644 dashboard/src/app/dashboard/environments/page.tsx create mode 100644 dashboard/src/app/dashboard/history/page.tsx create mode 100644 dashboard/src/app/dashboard/layout.tsx create mode 100644 dashboard/src/app/dashboard/sandboxes/[id]/client.tsx create mode 100644 dashboard/src/app/dashboard/sandboxes/[id]/page.tsx create mode 100644 dashboard/src/app/dashboard/sandboxes/page.tsx create mode 100644 dashboard/src/app/dashboard/templates/page.tsx create mode 100644 dashboard/src/components/dashboard-nav.tsx diff --git a/dashboard/src/app/dashboard/environments/page.tsx b/dashboard/src/app/dashboard/environments/page.tsx new file mode 100644 index 0000000..16ab3aa --- /dev/null +++ b/dashboard/src/app/dashboard/environments/page.tsx @@ -0,0 +1,93 @@ +'use client' +import { useEffect, useState } from 'react' +import { Button } from '@/components/ui/button' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { listEnvironments, createEnvironment, deleteEnvironment, type Environment } from '@/lib/api' + +export default function EnvironmentsPage() { + const [envs, setEnvs] = useState([]) + const [error, setError] = useState(null) + const [name, setName] = useState('') + const [body, setBody] = useState('KEY=value') + const [busy, setBusy] = useState(false) + + async function refresh() { + try { setEnvs(await listEnvironments()) } catch (e) { setError(String(e)) } + } + useEffect(() => { refresh() }, []) + + async function create() { + if (!name) return + const values: Record = {} + for (const line of body.split('\n')) { + const t = line.trim() + if (!t || !t.includes('=')) continue + const i = t.indexOf('=') + values[t.slice(0, i).trim()] = t.slice(i + 1).trim() + } + setBusy(true); setError(null) + try { + await createEnvironment(name, values) + setName('') + await refresh() + } catch (e) { setError(String(e)) } finally { setBusy(false) } + } + + async function del(id: string) { + if (!confirm('Delete this environment?')) return + setBusy(true) + try { await deleteEnvironment(id); await refresh() } catch (e) { setError(String(e)) } finally { setBusy(false) } + } + + return ( +
+
+

Environments

+

Named sets of env vars. Attach to a sandbox, a service, or the gateway.

+
+ {error &&

{error}

} + + + New environment + KEY=value per line. + + +
+ + setName(e.target.value)} placeholder="dev-creds" /> +
+
+ +