Slice 2: port 3452, nginx sandbox mount, AGENTS.md, docs, deploy script cleanup

- control-plane default listen addr is now :3452 (was :8080). An
  unusual port to avoid collisions on the VM.
- agent-micro and agent-gateway default SDP_CP_URL points at
  ws://localhost:3452/ws/agent. docker-compose.yml updates the
  control plane command, host port mapping, and agent -cp URLs.
- nginx/nginx.conf (the legacy root-mount reference) uses
  127.0.0.1:3452 for the upstream. nginx/sandbox.conf is the new
  deployment config: four location blocks for the /sandbox/credit-card
  mount — _next/static serves cached chunks, /api/ and /ws/ proxy
  to 127.0.0.1:3452, /sandbox/credit-card serves the static
  dashboard with try_files for SPA routing.
- scripts/patch-nginx.sh: deleted. The user configures nginx on 186
  by hand. scripts/deploy.sh no longer calls it.
- AGENTS.md: new file. Documents the build/lint/test commands
  (with the golang:1.24-alpine container — local Go can't fetch
  the toolchain), the wire protocol, the Slice-2 conventions
  (sdp-<repo> container naming, snapshot persistence,
  PreGitReset/AfterStart hooks), the repo-path gotcha, and the
  build-artifacts-in-git rationale.
- dashboard/out: now tracked in git, alongside bin/. The dashboard
  static export is scp'd to 186 on deploy; the VMs have no
  internet so they can't regenerate it. .gitignore comment
  explains this and warns against re-ignoring.
- README.md / REQUIREMENTS.md: status updated to 'Slice 2 done',
  per-feature checklist marked. Erangel repo path corrected to
  /var/www/html/erangel-ocean (was wrongly ~/SDP in earlier docs).
This commit is contained in:
Achmad
2026-06-24 04:00:49 +00:00
parent 78872de897
commit 4cab047432
48 changed files with 464 additions and 81 deletions
+152
View File
@@ -0,0 +1,152 @@
# AGENTS.md — Sandbox Deployment Platform
## Build, lint, test
The build script is the only way to compile — local Go can't fetch the
1.24 toolchain. Run:
```
./scripts/build.sh # cross-compiles 3 Go binaries + builds the Next.js dashboard
./scripts/deploy.sh # SSHes the artifacts to 92 and 186; needs sshpass
```
The script uses a `golang:1.24-alpine` container with a persistent
`sdp-gocache` named volume. `GO_IMAGE=...` overrides the image. Outputs:
`bin/{control-plane,agent-micro,agent-gateway}` (Linux/amd64, static) and
`dashboard/out/`.
Per-module Go work uses the same container:
```
docker run --rm -v "$PWD:/src" -w /src/<module> golang:1.24-alpine sh -c \
"apk add --no-cache git >/dev/null && git config --global --add safe.directory /src && go vet ./..."
```
For a single test:
```
docker run --rm -v "$PWD:/src" -w /src/control-plane golang:1.24-alpine sh -c \
"apk add --no-cache git >/dev/null && git config --global --add safe.directory /src && go test ./internal/store/..."
```
There is one test file today: `control-plane/internal/store/store_test.go`
(round-trips all Slice-2 CRUD).
The dashboard has no separate typecheck or lint script — `npm run build`
runs both. `cd dashboard && npm run build` locally is fine; node_modules
is gitignored.
## Layout
Five Go modules in a workspace (`go.work`):
- `protocol/` — wire types shared by CP and agents. Keep small.
- `agentlib/``gitutil` (askpass-via-stdin credential helper;
`git ls-remote`, `fetch`, `checkout`, `pull`, `for-each-ref`,
`reset --hard`) and `deployer` (per-deployment state machine; `NewGo`
for microservices, `NewPHP` for erangel).
- `control-plane/` — HTTP API + WS hub + SQLite. Routes split across
`internal/api/{login,sandboxes,templates,environments,routes,deployments,repos}.go`.
`internal/ws/hub.go` exposes `CallAgent` for sync RPCs.
- `agent-micro/` — runs on 172.18.136.92.
- `agent-gateway/` — runs on 172.18.139.186; owns erangel at
`/var/www/html/erangel-ocean` and the `<service>_url` patching.
Dashboard is a separate `next build` static export under
`dashboard/src/app/`. Static export means dynamic routes need
`generateStaticParams` (see the `sandboxes/[id]` page for the pattern).
## Wire protocol
The agent → control-plane channel is one `protocol.Event` per WS text
message. The control-plane → agent channel is an ad-hoc envelope
`{op, id, data}`. `op` values: `deploy`, `stop`, `list_repos`,
`list_branches`, `list_routes`, `probe`, `push_routes`. RPC replies have
`{op:"reply", id, ok, error?}` and a `data` field. The two shapes are
disambiguated by `kind` (event) vs `op` (rpc reply). New ops go in
`agentlib/.../main.go`'s switch and the control-plane's `repos.go` /
`sandboxes.go` / `routes.go` handlers — there is no central registry.
## Conventions
- `ponytail:` comments mark intentional shortcuts and "TODO: real
impl"-style carve-outs. They survive into main. Don't remove without
fixing the underlying limitation.
- Slice-2 stable container name: `sdp-<repo>` (no deployment id). The
next deploy force-removes the existing one. One live container per
repo at a time.
- Gateway agent persists the per-branch OCP-default snapshot to
`<repoPath>/.sdp/ocp-defaults.json`. Re-captured on every deploy so
branch switches don't break "Restore OCP" buttons.
- `NewPHP` runs `git reset --hard` before fetch (via
`Spec.PreGitReset`), and the agent passes an `AfterStart` closure
that re-applies active route overrides after the container is up.
This is what survives `git reset --hard` + checkout.
- `protocol.Event.ContainerID` is set on the deployer side; the
deployer writes it back via `Store.SetContainerID`. (Currently the
field on the event is unused; container id is recorded in SQLite.)
- Cookie auth: `sdp_session` HttpOnly cookie; the `withAuth` middleware
skips `/api/login`. WebSocket endpoints are NOT auth-gated by the
middleware — they rely on the agent being on a private network.
- Crendentials travel with each deploy/probe/push_routes frame from
control plane to agent. Never logged. Never persisted on the agent.
## Gotchas
- Host Go (`/usr/bin/go`) is older than the `go 1.24` modules require
and the toolchain download is blocked. Use the `golang:1.24-alpine`
container. Do not edit code expecting `go build` to work locally.
- The micro agent and gateway agent `main.go` files duplicate most
logic (dial / writer / readLoop / runDeploy). The shared code is in
`agentlib/`. When adding a new op, both files need a switch case.
- `moby/moby/client` v0.5.0 uses `netip.Addr` for `PortBinding.HostIP`,
not a string.
- `sdp-<repo>` containers must be in a state where `docker rm -f` works
(the `Slice-2` "one live per repo" rule). Don't manually `docker run`
a second container with the same name.
- The erangel repo path is `/var/www/html/erangel-ocean` on 186, NOT
`~/SDP` (README's earlier value is wrong; the spec was fixed in
Slice 2). `APACHE_DOCUMENT_ROOT` is set to the same path so the
gateway is served at `/erangel/`.
- `agent-gateway/.../main.go` re-imports the `routesState` type and
uses `rs` as both a value and a parameter name in some helpers.
Compiles fine; just be aware when grepping.
- Static-export dynamic routes: `generateStaticParams` must return at
least one placeholder; the actual id is read at runtime in the
client component. See `dashboard/src/app/dashboard/sandboxes/[id]/`.
## Verifying changes locally
```
# Typecheck + build everything
./scripts/build.sh
# Run the only Go test
docker run --rm -v "$PWD:/src" -w /src/control-plane golang:1.24-alpine sh -c \
"apk add --no-cache git >/dev/null && git config --global --add safe.directory /src && go test ./..."
# Smoke the control plane
./bin/control-plane -addr :3452 -data /tmp/sdp-data &
curl -i -X POST http://127.0.0.1:3452/api/login -d '{"username":"x","password":"y"}'
# Expects 401 ("login failed — git ls-remote rejected") when no gateway agent is connected.
```
## Out of scope
RBAC, suspend/resume, sandbox cloning beyond "clone template into
sandbox", per-sandbox Docker networks, per-sandbox resource limits,
health monitoring, the 172.18.136.93 infra agent, notifications.
These are listed as `later` in REQUIREMENTS.md.
## Do not
- Do not commit or push unless the user explicitly says "commit" or
"push".
- Do not change the gateway repo path back to `~/SDP` (old docs say
so; reality is `/var/www/html/erangel-ocean`).
- Do not rebuild the dashboard via `next start` for production; the
output is served by nginx on 186. Configure nginx by hand; the
reference config is in `nginx/nginx.conf` and uses
`root /home/administrator/SDP/dashboard;` (i.e. the path
`deploy.sh` scp's the static export to).
- Do not log or persist Bitbucket creds anywhere.
+60 -31
View File
@@ -5,27 +5,42 @@ branch into an isolated sandbox, with the API Gateway routing selected
services to the sandbox and the rest to OCP. See services to the sandbox and the rest to OCP. See
[REQUIREMENTS.md](REQUIREMENTS.md) for the full spec. [REQUIREMENTS.md](REQUIREMENTS.md) for the full spec.
## Status (Slice 1build green, MVP core flow) ## Status (Slice 2sandboxes, routes, real auth, all MVP features)
`./scripts/build.sh` produces three Linux/amd64 binaries and a static `./scripts/build.sh` produces three Linux/amd64 binaries and a static
dashboard. The MVP core flow — login, deploy a microservice or the PHP dashboard. The full MVP flow works end to end:
gateway, watch progress and logs in real time — works end to end. The
sandbox / template / route management described in REQUIREMENTS.md is - Real Bitbucket auth via `git ls-remote` against the api-gateway.
**deferred to Slice 2** and is not yet built. See - Real repo and branch listing via agent WS frames.
[REQUIREMENTS.md](REQUIREMENTS.md#status) for a per-feature checklist. - Sandbox / template / environment CRUD with persisted metadata in
SQLite.
- Route overrides per sandbox, with live read-back of the
`<service>_url` map from the gateway's `config.php` after every
branch switch. The agent patches the file and gracefully reloads
apache.
- Per-deploy port binding: the user picks the host port per service
(e.g. eredar at `172.18.136.92:9001`), the container's exposed port
is published to that port.
- Erangel deploy: `git reset --hard → fetch → checkout → pull →
composer install → start container → re-apply route overrides`.
Per-branch OCP-default snapshot persisted to
`<repo>/.sdp/ocp-defaults.json`.
See [REQUIREMENTS.md](REQUIREMENTS.md#status) for the per-feature
checklist.
## Layout ## Layout
``` ```
. .
├── protocol/ # shared wire types (Event, DeployRequest) ├── protocol/ # shared wire types (Event, DeployRequest, RouteOverride, ...)
├── agentlib/ # Go. Shared agent library: gitutil + deployer (Go/PHP flavours) ├── agentlib/ # Go. Shared agent library: gitutil + deployer (Go/PHP flavours)
├── control-plane/ # Go. HTTP API + WS hub + SQLite/.log persistence ├── control-plane/ # Go. HTTP API + WS hub + SQLite/.log persistence
├── agent-micro/ # Go. Runs on 172.18.136.92, deploys Go microservices ├── agent-micro/ # Go. Runs on 172.18.136.92, deploys Go microservices
├── agent-gateway/ # Go. Runs on 172.18.139.186, deploys the PHP API Gateway ├── agent-gateway/ # Go. Runs on 172.18.139.186, deploys the PHP API Gateway
├── dashboard/ # NextJS static export, served by nginx ├── dashboard/ # NextJS static export, served by nginx
├── nginx/ # reverse proxy + try_files for the dashboard ├── nginx/ # reference nginx config (manually applied on 186)
├── scripts/ # build, deploy, ssh wrappers, nginx patch ├── scripts/ # build, deploy, ssh wrappers
├── docker-compose.yml # all three services on alpine:latest ├── docker-compose.yml # all three services on alpine:latest
├── go.work # Go workspace — one build, five modules ├── go.work # Go workspace — one build, five modules
└── bin/ # build output (gitignored) └── bin/ # build output (gitignored)
@@ -39,10 +54,14 @@ for two build flavours:
`docker run alpine:3.20` with the host repo bind-mounted at `/src` and `docker run alpine:3.20` with the host repo bind-mounted at `/src` and
the binary as the container command. `alpine:3.20` must be pre-loaded the binary as the container command. `alpine:3.20` must be pre-loaded
on the host (see [Offline VMs](#offline-vms)). on the host (see [Offline VMs](#offline-vms)).
- **`NewPHP`** — for the API Gateway. Runs `composer install --no-dev` - **`NewPHP`** — for the API Gateway (erangel). Runs
on the host as a best-effort step (skipped if `composer` or `git reset --hard → fetch → checkout → pull → composer install
`composer.json` are absent), then `docker run php:8.3-apache` with the (best-effort) → docker run php:8.3-apache`, with the repo
repo bind-mounted at `/app`. `php:8.3-apache` must be pre-loaded on bind-mounted at `/var/www/html/erangel-ocean` and
`APACHE_DOCUMENT_ROOT=/var/www/html/erangel-ocean` so the gateway is
served at `/erangel/`, mirroring production. After the container is
up, the agent's `AfterStart` callback re-applies the active route
overrides and reloads apache. `php:8.3-apache` must be pre-loaded on
the host. The agent is written in Go; the thing it deploys is a the host. The agent is written in Go; the thing it deploys is a
PHP project. PHP project.
@@ -93,8 +112,10 @@ This script:
2. SSHs to **172.18.139.186** (`administrator`) and pushes 2. SSHs to **172.18.139.186** (`administrator`) and pushes
`bin/control-plane`, `bin/agent-gateway`, and `dashboard/out/` to `bin/control-plane`, `bin/agent-gateway`, and `dashboard/out/` to
`~/SDP/` `~/SDP/`
3. Idempotently splices the SDP location block into
`/etc/nginx/sites-available/default` on 186 and reloads nginx Nginx on 186 is configured by hand; the dashboard ends up at
`/home/administrator/SDP/dashboard/`. The required location block is
in [nginx/nginx.conf](nginx/nginx.conf).
Override the creds via `SDP_92_PASS` / `SDP_186_PASS` env vars. Override the creds via `SDP_92_PASS` / `SDP_186_PASS` env vars.
@@ -108,7 +129,7 @@ docker compose up -d
``` ```
Three services come up on `alpine:latest`: Three services come up on `alpine:latest`:
- `control-plane` → `:8080` - `control-plane` → `:3452` (an unusual port to avoid collisions)
- `agent-micro` (connects to control plane, has docker socket + repos mounted) - `agent-micro` (connects to control plane, has docker socket + repos mounted)
- `agent-gateway` (same shape) - `agent-gateway` (same shape)
@@ -135,24 +156,32 @@ Three services come up on `alpine:latest`:
`/ws/agent` on the control plane; the dashboard subscribes to `/ws/agent` on the control plane; the dashboard subscribes to
`/ws/deployments/{id}`. `/ws/deployments/{id}`.
## MVP stubs (intentional, not Slice 1 scope) ## MVP stubs (intentional, deferred)
These are marked with `ponytail:` comments in the code and are These are marked with `ponytail:` comments in the code and are
scheduled for later slices. They are **not** in scope for Slice 1. scheduled for later slices.
- `validateViaAgent` (login) — accepts any creds if an agent is - `CheckOrigin` in the WS upgrader — open CORS, intentional for an
connected. Real impl: a `git ls-remote` probe frame to the agent internal tool.
(`control-plane/internal/api/api.go:126`). - "Drop on backpressure" policy for slow WS subscribers — replace with
- `handleListRepos` / `handleListBranches` — hardcoded fixtures. flow control or persistent event log if the dashboard ever needs
Real impl: a `list_repos` / `list_branches` frame to the connected catch-up replay.
agent. The `gitutil.ListBranches` helper and the `agentlib` frame - O(n) log tail scan in `store.TailLogs` — fine for tail use; swap to
protocol are not yet wired up. a ring buffer if logs get huge.
- `handleListDeployments` (GET) — returns `[]`. Real impl reads SQLite
(`control-plane/internal/api/api.go:182`). ## Slice 2 dashboard
- WS auth on `/ws/deployments/*` — open. Real impl checks session token.
- Sandbox, Template, Route, Environment CRUD — entirely deferred to The dashboard has these pages:
Slice 2. The data model, REST endpoints, and dashboard pages do not
exist yet. - `/` — login (real git-ls-remote via the gateway agent).
- `/dashboard` — quick deploy (ad-hoc single-service deploy).
- `/dashboard/sandboxes` — list, create, clone-from-template.
- `/dashboard/sandboxes/{id}` — sandbox detail. Live routes from the
gateway's `config.php`, per-route toggle (OCP / sandbox override),
microservice deploys with per-service host port and env.
- `/dashboard/templates` — template CRUD.
- `/dashboard/environments` — env CRUD.
- `/dashboard/history` — deployment history (filterable by sandbox).
## See also ## See also
+52 -33
View File
@@ -1,17 +1,25 @@
# Sandbox Deployment Platform (SDP) # Sandbox Deployment Platform (SDP)
## Status (Slice 1build green, MVP core flow) ## Status (Slice 2sandboxes, routes, real auth, all MVP features)
The build is green: `./scripts/build.sh` produces three Linux/amd64 The build is green: `./scripts/build.sh` produces three Linux/amd64
binaries and a static dashboard. The core MVP loop works end to end binaries and a static dashboard. The full MVP flow works end to end:
login, deploy a microservice or the PHP gateway, watch progress and
logs in real time.
Sandbox / Template / Route / Environment management is **deferred to - Real Bitbucket auth via `git ls-remote` against the api-gateway.
Slice 2** and is not yet built. Real auth via agent-mediated - Real repo and branch listing via agent WS frames.
`git ls-remote` and real branch/repo listing from agents are also - Sandbox / template / environment CRUD with persisted metadata in
deferred (the current code has hardcoded fixtures and an "accept any SQLite.
creds if an agent is connected" stub for these). - Route overrides per sandbox, with live read-back of the
`<service>_url` map from the gateway's `config.php` after every
branch switch. The agent patches the file and gracefully reloads
apache.
- Per-deploy port binding: the user picks the host port per service
(e.g. eredar at `172.18.136.92:9001`), the container's exposed port
is published to that port.
- Erangel deploy: `git reset --hard → fetch → checkout → pull →
composer install → start container → re-apply route overrides`.
Per-branch OCP-default snapshot persisted to
`<repo>/.sdp/ocp-defaults.json`.
See [Status checklist](#status-checklist) at the bottom of this See [Status checklist](#status-checklist) at the bottom of this
document for a per-feature status. document for a per-feature status.
@@ -114,16 +122,19 @@ IP Address:
172.18.139.186 172.18.139.186
Repository Root: Repository Root:
~/SDP /var/www/html/erangel-ocean
``` ```
Contains: Contains:
```text ```text
~/SDP /var/www/html/erangel-ocean
``` ```
The API Gateway repository. The API Gateway repository (erangel). The container
`php:8.3-apache` bind-mounts this path at the same path inside the
container and serves the gateway at `/erangel/`, mirroring the
production URL space.
--- ---
@@ -1460,37 +1471,45 @@ scheduled for Slice 2. `later` = out of scope for MVP.
the per-operation Bitbucket creds. the per-operation Bitbucket creds.
- `done` Micro agent runs `git fetch → checkout → pull → go build → - `done` Micro agent runs `git fetch → checkout → pull → go build →
docker run` and streams progress and logs back. docker run` and streams progress and logs back.
- `done` Gateway agent runs `git fetch → checkout → pull → composer - `done` Gateway agent runs `git reset --hard → fetch → checkout →
install (best-effort) → docker run` and streams progress and logs pull → composer install (best-effort) → docker run → re-apply route
overrides → apache graceful reload` and streams progress and logs
back. back.
- `done` Dashboard subscribes to a deployment by id over WebSocket - `done` Dashboard subscribes to a deployment by id over WebSocket
and renders stages + live log tail. and renders stages + live log tail.
- `done` SQLite persistence for deployment rows, stage transitions, - `done` SQLite persistence for deployment rows, stage transitions,
and append-only log files. and append-only log files.
- `next` Replace `validateViaAgent` stub with a real - `done` Real `validateViaAgent` via the agent's `git ls-remote`
`git ls-remote` frame. frame.
- `next` Replace hardcoded `handleListRepos` / - `done` Real `list_repos` / `list_branches` via agent frames; the
`handleListBranches` with agent frames (the `gitutil.ListBranches` hardcoded fixtures are gone.
helper and the `agentlib` frame protocol are partially set up but - `done` `list_routes` RPC exposes the live `<key>_url` map from
not wired through). the gateway's `config.php` after every branch switch.
- `done` `GET /api/deployments` reads deployment history from
SQLite (filterable by sandbox).
## Sandbox & routing (Slice 2) ## Sandbox & routing
- `next` Sandbox CRUD (data model + REST endpoints + dashboard page). - `done` Sandbox CRUD (data model + REST endpoints + dashboard
- `next` Sandbox template CRUD and "clone template into sandbox". pages).
- `next` Route management (sandbox vs OCP per service). - `done` Sandbox template CRUD and "clone template into sandbox".
- `next` Environment CRUD (persisted named envs, not just inline). - `done` Route management (sandbox vs OCP per service) with live
- `next` Actual route push to the API Gateway (the gateway agent read-back from the gateway's `config.php`.
has to update the gateway's routing config, currently this is - `done` Environment CRUD (persisted named envs, not just inline).
the manual `scripts/patch-nginx.sh` step). - `done` Actual route push to the API Gateway: the gateway agent
- `next` Port allocation table and helpers. rewrites `application/config/production/config.php` and gracefully
reloads apache. A per-branch OCP-default snapshot is captured
automatically and persisted to `<repo>/.sdp/ocp-defaults.json`.
- `done` Per-deploy port binding: the user specifies the host port;
the agent publishes the container's exposed port to it. Concurrency
is "one live container per repo" (the stable name is `sdp-<repo>`).
## Auth ## Auth
- `done` Login endpoint accepts any creds if an agent is connected - `done` Real auth via agent-mediated `git ls-remote` against the
(MVP stub). api-gateway. Login fails fast if no gateway agent is connected.
- `done` Session cookie + in-memory session store. - `done` Session cookie + in-memory session store, 12-hour TTL,
- `next` Real auth via agent-mediated `git ls-remote`. logout invalidates the token.
- `later` RBAC roles (admin / backend / qa / viewer). - `later` RBAC roles (admin / backend / qa / viewer).
## Out of scope for MVP (per the "Future Enhancements" section) ## Out of scope for MVP (per the "Future Enhancements" section)
+7 -2
View File
@@ -5,8 +5,13 @@ import (
"os" "os"
) )
// Slice-2: the control plane listens on an unusual port to avoid
// collisions with anything else on the VM. Nginx on 186 proxies
// /sandbox/credit-card/api/ and /sandbox/credit-card/ws/ to it.
const defaultAddr = ":3452"
type Config struct { type Config struct {
Addr string // listen addr, e.g. ":8080" Addr string // listen addr, default :3452
DataDir string // SQLite + .log files live here DataDir string // SQLite + .log files live here
AgentHealth string // map of nodeID -> agent base URL (TODO: real map) AgentHealth string // map of nodeID -> agent base URL (TODO: real map)
} }
@@ -14,7 +19,7 @@ type Config struct {
// Load reads flags and env. Env wins over defaults; flags win over env. // Load reads flags and env. Env wins over defaults; flags win over env.
func Load() Config { func Load() Config {
c := Config{ c := Config{
Addr: envOr("SDP_ADDR", ":8080"), Addr: envOr("SDP_ADDR", defaultAddr),
DataDir: envOr("SDP_DATA", "./data"), DataDir: envOr("SDP_DATA", "./data"),
AgentHealth: envOr("SDP_AGENT_HEALTH", ""), AgentHealth: envOr("SDP_AGENT_HEALTH", ""),
} }
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[165],{3155:function(e,t,n){(window.__NEXT_P=window.__NEXT_P||[]).push(["/_not-found",function(){return n(4032)}])},4032:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return i}}),n(6921);let o=n(3827);n(4090);let r={error:{fontFamily:'system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"',height:"100vh",textAlign:"center",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center"},desc:{display:"inline-block"},h1:{display:"inline-block",margin:"0 20px 0 0",padding:"0 23px 0 0",fontSize:24,fontWeight:500,verticalAlign:"top",lineHeight:"49px"},h2:{fontSize:14,fontWeight:400,lineHeight:"49px",margin:0}};function i(){return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)("title",{children:"404: This page could not be found."}),(0,o.jsx)("div",{style:r.error,children:(0,o.jsxs)("div",{children:[(0,o.jsx)("style",{dangerouslySetInnerHTML:{__html:"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}),(0,o.jsx)("h1",{className:"next-error-h1",style:r.h1,children:"404"}),(0,o.jsx)("div",{style:r.desc,children:(0,o.jsx)("h2",{style:r.h2,children:"This page could not be found."})})]})})]})}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)}},function(e){e.O(0,[971,69,744],function(){return e(e.s=3155)}),_N_E=e.O()}]);
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[185],{8216:function(n,e,u){Promise.resolve().then(u.t.bind(u,3385,23))},3385:function(){}},function(n){n.O(0,[971,69,744],function(){return n(n.s=8216)}),_N_E=n.O()}]);
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[744],{3734:function(e,n,t){Promise.resolve().then(t.t.bind(t,7690,23)),Promise.resolve().then(t.t.bind(t,8955,23)),Promise.resolve().then(t.t.bind(t,5613,23)),Promise.resolve().then(t.t.bind(t,1902,23)),Promise.resolve().then(t.t.bind(t,1778,23)),Promise.resolve().then(t.t.bind(t,7831,23))}},function(e){var n=function(n){return e(e.s=n)};e.O(0,[971,69],function(){return n(5317),n(3734)}),_N_E=e.O()}]);
@@ -0,0 +1 @@
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[888],{1597:function(n,_,u){(window.__NEXT_P=window.__NEXT_P||[]).push(["/_app",function(){return u(7174)}])}},function(n){var _=function(_){return n(n.s=_)};n.O(0,[774,179],function(){return _(1597),_(4546)}),_N_E=n.O()}]);
@@ -0,0 +1 @@
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[820],{1981:function(n,_,u){(window.__NEXT_P=window.__NEXT_P||[]).push(["/_error",function(){return u(5103)}])}},function(n){n.O(0,[888,774,179],function(){return n(n.s=1981)}),_N_E=n.O()}]);
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
!function(){"use strict";var e,t,n,r,o,u,i,c,f,a={},l={};function d(e){var t=l[e];if(void 0!==t)return t.exports;var n=l[e]={exports:{}},r=!0;try{a[e](n,n.exports,d),r=!1}finally{r&&delete l[e]}return n.exports}d.m=a,e=[],d.O=function(t,n,r,o){if(n){o=o||0;for(var u=e.length;u>0&&e[u-1][2]>o;u--)e[u]=e[u-1];e[u]=[n,r,o];return}for(var i=1/0,u=0;u<e.length;u++){for(var n=e[u][0],r=e[u][1],o=e[u][2],c=!0,f=0;f<n.length;f++)i>=o&&Object.keys(d.O).every(function(e){return d.O[e](n[f])})?n.splice(f--,1):(c=!1,o<i&&(i=o));if(c){e.splice(u--,1);var a=r();void 0!==a&&(t=a)}}return t},d.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return d.d(t,{a:t}),t},n=Object.getPrototypeOf?function(e){return Object.getPrototypeOf(e)}:function(e){return e.__proto__},d.t=function(e,r){if(1&r&&(e=this(e)),8&r||"object"==typeof e&&e&&(4&r&&e.__esModule||16&r&&"function"==typeof e.then))return e;var o=Object.create(null);d.r(o);var u={};t=t||[null,n({}),n([]),n(n)];for(var i=2&r&&e;"object"==typeof i&&!~t.indexOf(i);i=n(i))Object.getOwnPropertyNames(i).forEach(function(t){u[t]=function(){return e[t]}});return u.default=function(){return e},d.d(o,u),o},d.d=function(e,t){for(var n in t)d.o(t,n)&&!d.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},d.f={},d.e=function(e){return Promise.all(Object.keys(d.f).reduce(function(t,n){return d.f[n](e,t),t},[]))},d.u=function(e){},d.miniCssF=function(e){return"static/css/157e72c563f45c59.css"},d.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r={},o="_N_E:",d.l=function(e,t,n,u){if(r[e]){r[e].push(t);return}if(void 0!==n)for(var i,c,f=document.getElementsByTagName("script"),a=0;a<f.length;a++){var l=f[a];if(l.getAttribute("src")==e||l.getAttribute("data-webpack")==o+n){i=l;break}}i||(c=!0,(i=document.createElement("script")).charset="utf-8",i.timeout=120,d.nc&&i.setAttribute("nonce",d.nc),i.setAttribute("data-webpack",o+n),i.src=d.tu(e)),r[e]=[t];var s=function(t,n){i.onerror=i.onload=null,clearTimeout(p);var o=r[e];if(delete r[e],i.parentNode&&i.parentNode.removeChild(i),o&&o.forEach(function(e){return e(n)}),t)return t(n)},p=setTimeout(s.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=s.bind(null,i.onerror),i.onload=s.bind(null,i.onload),c&&document.head.appendChild(i)},d.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},d.tt=function(){return void 0===u&&(u={createScriptURL:function(e){return e}},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(u=trustedTypes.createPolicy("nextjs#bundler",u))),u},d.tu=function(e){return d.tt().createScriptURL(e)},d.p="/sandbox/credit-card/_next/",i={272:0},d.f.j=function(e,t){var n=d.o(i,e)?i[e]:void 0;if(0!==n){if(n)t.push(n[2]);else if(272!=e){var r=new Promise(function(t,r){n=i[e]=[t,r]});t.push(n[2]=r);var o=d.p+d.u(e),u=Error();d.l(o,function(t){if(d.o(i,e)&&(0!==(n=i[e])&&(i[e]=void 0),n)){var r=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src;u.message="Loading chunk "+e+" failed.\n("+r+": "+o+")",u.name="ChunkLoadError",u.type=r,u.request=o,n[1](u)}},"chunk-"+e,e)}else i[e]=0}},d.O.j=function(e){return 0===i[e]},c=function(e,t){var n,r,o=t[0],u=t[1],c=t[2],f=0;if(o.some(function(e){return 0!==i[e]})){for(n in u)d.o(u,n)&&(d.m[n]=u[n]);if(c)var a=c(d)}for(e&&e(t);f<o.length;f++)r=o[f],d.o(i,r)&&i[r]&&i[r][0](),i[r]=0;return d.O(a)},(f=self.webpackChunk_N_E=self.webpackChunk_N_E||[]).forEach(c.bind(null,0)),f.push=c.bind(null,f.push.bind(f)),d.nc=void 0}();
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
self.__BUILD_MANIFEST={__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/_error":["static/chunks/pages/_error-9a890acb1e81c3fc.js"],sortedPages:["/_app","/_error"]},self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();
@@ -0,0 +1 @@
self.__SSG_MANIFEST=new Set(["\u002Fdashboard\u002Fsandboxes\u002F[id]"]);self.__SSG_MANIFEST_CB&&self.__SSG_MANIFEST_CB()
File diff suppressed because one or more lines are too long
+7
View File
@@ -0,0 +1,7 @@
2:I[3498,["504","static/chunks/504-b386cf1902f7f6c4.js","303","static/chunks/303-30e01c8e944885c6.js","702","static/chunks/app/dashboard/page-fe092df0d98d16a1.js"],"Dashboard"]
3:I[9534,["504","static/chunks/504-b386cf1902f7f6c4.js","792","static/chunks/792-ff5864056e955fdc.js","663","static/chunks/app/dashboard/layout-49ba819eac821f2e.js"],"DashboardNav"]
4:I[5613,[],""]
5:I[1778,[],""]
0:["vJ6MiW7DlY0bX8PUKZLrC",[[["",{"children":["dashboard",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["dashboard",{"children":["__PAGE__",{},["$L1",["$","$L2",null,{}],null]]},[null,["$","div",null,{"className":"min-h-screen bg-zinc-50","children":[["$","$L3",null,{}],["$","main",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","dashboard","children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined","styles":null}]}]]}],null]]},[null,["$","html",null,{"lang":"en","className":"h-full","children":["$","body",null,{"className":"h-full bg-background text-foreground antialiased","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[],"styles":null}]}]}],null]],[[["$","link","0",{"rel":"stylesheet","href":"/sandbox/credit-card/_next/static/css/157e72c563f45c59.css","precedence":"next","crossOrigin":""}]],"$L6"]]]]
6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"SDP"}],["$","meta","3",{"name":"description","content":"Sandbox Deployment Platform"}]]
1:null
File diff suppressed because one or more lines are too long
+8
View File
@@ -0,0 +1,8 @@
2:I[7831,[],""]
3:I[5405,["504","static/chunks/504-b386cf1902f7f6c4.js","17","static/chunks/app/dashboard/environments/page-a627adbd0bb7fff2.js"],""]
4:I[5613,[],""]
5:I[1778,[],""]
6:I[9534,["504","static/chunks/504-b386cf1902f7f6c4.js","792","static/chunks/792-ff5864056e955fdc.js","663","static/chunks/app/dashboard/layout-49ba819eac821f2e.js"],"DashboardNav"]
0:["vJ6MiW7DlY0bX8PUKZLrC",[[["",{"children":["dashboard",{"children":["environments",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",{"children":["dashboard",{"children":["environments",{"children":["__PAGE__",{},["$L1",["$","$L2",null,{"propsForComponent":{"params":{}},"Component":"$3","isStaticGeneration":true}],null]]},["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","dashboard","children","environments","children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined","styles":null}]]},[null,["$","div",null,{"className":"min-h-screen bg-zinc-50","children":[["$","$L6",null,{}],["$","main",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","dashboard","children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined","styles":null}]}]]}],null]]},[null,["$","html",null,{"lang":"en","className":"h-full","children":["$","body",null,{"className":"h-full bg-background text-foreground antialiased","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[],"styles":null}]}]}],null]],[[["$","link","0",{"rel":"stylesheet","href":"/sandbox/credit-card/_next/static/css/157e72c563f45c59.css","precedence":"next","crossOrigin":""}]],"$L7"]]]]
7:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"SDP"}],["$","meta","3",{"name":"description","content":"Sandbox Deployment Platform"}]]
1:null
File diff suppressed because one or more lines are too long
+8
View File
@@ -0,0 +1,8 @@
2:I[7831,[],""]
3:I[3331,["504","static/chunks/504-b386cf1902f7f6c4.js","834","static/chunks/app/dashboard/history/page-d2d7ef8172cb6bd1.js"],""]
4:I[5613,[],""]
5:I[1778,[],""]
6:I[9534,["504","static/chunks/504-b386cf1902f7f6c4.js","792","static/chunks/792-ff5864056e955fdc.js","663","static/chunks/app/dashboard/layout-49ba819eac821f2e.js"],"DashboardNav"]
0:["vJ6MiW7DlY0bX8PUKZLrC",[[["",{"children":["dashboard",{"children":["history",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",{"children":["dashboard",{"children":["history",{"children":["__PAGE__",{},["$L1",["$","$L2",null,{"propsForComponent":{"params":{}},"Component":"$3","isStaticGeneration":true}],null]]},["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","dashboard","children","history","children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined","styles":null}]]},[null,["$","div",null,{"className":"min-h-screen bg-zinc-50","children":[["$","$L6",null,{}],["$","main",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","dashboard","children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined","styles":null}]}]]}],null]]},[null,["$","html",null,{"lang":"en","className":"h-full","children":["$","body",null,{"className":"h-full bg-background text-foreground antialiased","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[],"styles":null}]}]}],null]],[[["$","link","0",{"rel":"stylesheet","href":"/sandbox/credit-card/_next/static/css/157e72c563f45c59.css","precedence":"next","crossOrigin":""}]],"$L7"]]]]
7:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"SDP"}],["$","meta","3",{"name":"description","content":"Sandbox Deployment Platform"}]]
1:null
File diff suppressed because one or more lines are too long
+8
View File
@@ -0,0 +1,8 @@
2:I[7831,[],""]
3:I[7334,["504","static/chunks/504-b386cf1902f7f6c4.js","303","static/chunks/303-30e01c8e944885c6.js","792","static/chunks/792-ff5864056e955fdc.js","509","static/chunks/app/dashboard/sandboxes/page-fa8d8c9854e58bd7.js"],""]
4:I[5613,[],""]
5:I[1778,[],""]
6:I[9534,["504","static/chunks/504-b386cf1902f7f6c4.js","792","static/chunks/792-ff5864056e955fdc.js","663","static/chunks/app/dashboard/layout-49ba819eac821f2e.js"],"DashboardNav"]
0:["vJ6MiW7DlY0bX8PUKZLrC",[[["",{"children":["dashboard",{"children":["sandboxes",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",{"children":["dashboard",{"children":["sandboxes",{"children":["__PAGE__",{},["$L1",["$","$L2",null,{"propsForComponent":{"params":{}},"Component":"$3","isStaticGeneration":true}],null]]},["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","dashboard","children","sandboxes","children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined","styles":null}]]},[null,["$","div",null,{"className":"min-h-screen bg-zinc-50","children":[["$","$L6",null,{}],["$","main",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","dashboard","children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined","styles":null}]}]]}],null]]},[null,["$","html",null,{"lang":"en","className":"h-full","children":["$","body",null,{"className":"h-full bg-background text-foreground antialiased","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[],"styles":null}]}]}],null]],[[["$","link","0",{"rel":"stylesheet","href":"/sandbox/credit-card/_next/static/css/157e72c563f45c59.css","precedence":"next","crossOrigin":""}]],"$L7"]]]]
7:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"SDP"}],["$","meta","3",{"name":"description","content":"Sandbox Deployment Platform"}]]
1:null
File diff suppressed because one or more lines are too long
+8
View File
@@ -0,0 +1,8 @@
2:I[1103,["504","static/chunks/504-b386cf1902f7f6c4.js","882","static/chunks/app/dashboard/sandboxes/%5Bid%5D/page-c1742b81a03d9067.js"],""]
3:I[5613,[],""]
5:I[1778,[],""]
6:I[9534,["504","static/chunks/504-b386cf1902f7f6c4.js","792","static/chunks/792-ff5864056e955fdc.js","663","static/chunks/app/dashboard/layout-49ba819eac821f2e.js"],"DashboardNav"]
4:["id","_","d"]
0:["vJ6MiW7DlY0bX8PUKZLrC",[[["",{"children":["dashboard",{"children":["sandboxes",{"children":[["id","_","d"],{"children":["__PAGE__?{\"id\":\"_\"}",{}]}]}]}]},"$undefined","$undefined",true],["",{"children":["dashboard",{"children":["sandboxes",{"children":[["id","_","d"],{"children":["__PAGE__",{},["$L1",["$","$L2",null,{}],null]]},["$","$L3",null,{"parallelRouterKey":"children","segmentPath":["children","dashboard","children","sandboxes","children","$4","children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined","styles":null}]]},["$","$L3",null,{"parallelRouterKey":"children","segmentPath":["children","dashboard","children","sandboxes","children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined","styles":null}]]},[null,["$","div",null,{"className":"min-h-screen bg-zinc-50","children":[["$","$L6",null,{}],["$","main",null,{"children":["$","$L3",null,{"parallelRouterKey":"children","segmentPath":["children","dashboard","children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined","styles":null}]}]]}],null]]},[null,["$","html",null,{"lang":"en","className":"h-full","children":["$","body",null,{"className":"h-full bg-background text-foreground antialiased","children":["$","$L3",null,{"parallelRouterKey":"children","segmentPath":["children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[],"styles":null}]}]}],null]],[[["$","link","0",{"rel":"stylesheet","href":"/sandbox/credit-card/_next/static/css/157e72c563f45c59.css","precedence":"next","crossOrigin":""}]],"$L7"]]]]
7:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"SDP"}],["$","meta","3",{"name":"description","content":"Sandbox Deployment Platform"}]]
1:null
File diff suppressed because one or more lines are too long
+8
View File
@@ -0,0 +1,8 @@
2:I[7831,[],""]
3:I[4700,["504","static/chunks/504-b386cf1902f7f6c4.js","123","static/chunks/app/dashboard/templates/page-5d6b88cb4f5f8cdf.js"],""]
4:I[5613,[],""]
5:I[1778,[],""]
6:I[9534,["504","static/chunks/504-b386cf1902f7f6c4.js","792","static/chunks/792-ff5864056e955fdc.js","663","static/chunks/app/dashboard/layout-49ba819eac821f2e.js"],"DashboardNav"]
0:["vJ6MiW7DlY0bX8PUKZLrC",[[["",{"children":["dashboard",{"children":["templates",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",{"children":["dashboard",{"children":["templates",{"children":["__PAGE__",{},["$L1",["$","$L2",null,{"propsForComponent":{"params":{}},"Component":"$3","isStaticGeneration":true}],null]]},["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","dashboard","children","templates","children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined","styles":null}]]},[null,["$","div",null,{"className":"min-h-screen bg-zinc-50","children":[["$","$L6",null,{}],["$","main",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","dashboard","children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined","styles":null}]}]]}],null]]},[null,["$","html",null,{"lang":"en","className":"h-full","children":["$","body",null,{"className":"h-full bg-background text-foreground antialiased","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[],"styles":null}]}]}],null]],[[["$","link","0",{"rel":"stylesheet","href":"/sandbox/credit-card/_next/static/css/157e72c563f45c59.css","precedence":"next","crossOrigin":""}]],"$L7"]]]]
7:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"SDP"}],["$","meta","3",{"name":"description","content":"Sandbox Deployment Platform"}]]
1:null
File diff suppressed because one or more lines are too long
+6
View File
@@ -0,0 +1,6 @@
2:I[5733,["504","static/chunks/504-b386cf1902f7f6c4.js","931","static/chunks/app/page-0f77936ff7b5e685.js"],"LoginForm"]
3:I[5613,[],""]
4:I[1778,[],""]
0:["vJ6MiW7DlY0bX8PUKZLrC",[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",{"children":["__PAGE__",{},["$L1",["$","main",null,{"className":"flex min-h-screen items-center justify-center bg-muted/30 p-4","children":["$","$L2",null,{}]}],null]]},[null,["$","html",null,{"lang":"en","className":"h-full","children":["$","body",null,{"className":"h-full bg-background text-foreground antialiased","children":["$","$L3",null,{"parallelRouterKey":"children","segmentPath":["children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[],"styles":null}]}]}],null]],[[["$","link","0",{"rel":"stylesheet","href":"/sandbox/credit-card/_next/static/css/157e72c563f45c59.css","precedence":"next","crossOrigin":""}]],"$L5"]]]]
5:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"SDP"}],["$","meta","3",{"name":"description","content":"Sandbox Deployment Platform"}]]
1:null
+4 -4
View File
@@ -10,12 +10,12 @@ services:
image: alpine:latest image: alpine:latest
container_name: sdp-control-plane container_name: sdp-control-plane
restart: unless-stopped restart: unless-stopped
command: ["/SDP/bin/control-plane", "-addr", ":8080", "-data", "/SDP/data"] command: ["/SDP/bin/control-plane", "-addr", ":3452", "-data", "/SDP/data"]
volumes: volumes:
- ./bin/control-plane:/SDP/bin/control-plane:ro - ./bin/control-plane:/SDP/bin/control-plane:ro
- sdp-data:/SDP/data - sdp-data:/SDP/data
ports: ports:
- "8080:8080" - "3452:3452"
agent-micro: agent-micro:
image: alpine:latest image: alpine:latest
@@ -28,7 +28,7 @@ services:
- -node - -node
- micro - micro
- -cp - -cp
- ws://control-plane:8080/ws/agent - ws://control-plane:3452/ws/agent
volumes: volumes:
- ./bin/agent-micro:/SDP/bin/agent-micro:ro - ./bin/agent-micro:/SDP/bin/agent-micro:ro
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
@@ -48,7 +48,7 @@ services:
- -node - -node
- gateway - gateway
- -cp - -cp
- ws://control-plane:8080/ws/agent - ws://control-plane:3452/ws/agent
volumes: volumes:
- ./bin/agent-gateway:/SDP/bin/agent-gateway:ro - ./bin/agent-gateway:/SDP/bin/agent-gateway:ro
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
+4 -3
View File
@@ -1,11 +1,12 @@
# SDP nginx serves the static NextJS export and proxies API + WS # SDP nginx reference config for serving the dashboard at the host
# to the Go control plane. # root. The actual deployment on 186 mounts the dashboard under
# /sandbox/credit-card; see nginx/sandbox.conf for that.
# #
# try_files: any unknown path falls back to /index.html so client-side # try_files: any unknown path falls back to /index.html so client-side
# routing works. /api and /ws are matched first and proxied upstream. # routing works. /api and /ws are matched first and proxied upstream.
upstream control_plane { upstream control_plane {
server 127.0.0.1:8080; server 127.0.0.1:3452;
keepalive 16; keepalive 16;
} }
+47
View File
@@ -0,0 +1,47 @@
# SDP nginx for 186 — mount the dashboard under /sandbox/credit-card
# and proxy /api/ + /ws/ to the control plane on 127.0.0.1:3452.
#
# Splice the four location blocks into the existing
# /etc/nginx/sites-available/default server { } on 186. Order doesn't
# matter for these specific prefixes (they're disjoint), but
# longest-prefix-first is the convention.
#
# Verify with `sudo nginx -t` then `sudo systemctl reload nginx`.
# Static asset chunks (hashed filenames, safe to cache forever).
location /sandbox/credit-card/_next/static/ {
alias /home/administrator/SDP/dashboard/_next/static/;
expires 1y;
add_header Cache-Control "public, immutable";
}
# API: control plane on 127.0.0.1:3452. The proxy_pass has no path
# component, so the original /api/... URI is forwarded unchanged.
location /sandbox/credit-card/api/ {
proxy_pass http://127.0.0.1:3452;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 3600s;
}
# WebSocket: deployment log subscriptions + agent WS.
location /sandbox/credit-card/ws/ {
proxy_pass http://127.0.0.1:3452;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 3600s;
}
# Everything else under /sandbox/credit-card: serve the static
# dashboard. Client-side routes (e.g. /sandbox/credit-card/dashboard/sandboxes/abc)
# fall through to /index.html, which Next.js hydrates and the React
# Router takes over.
location /sandbox/credit-card {
alias /home/administrator/SDP/dashboard/;
index index.html;
try_files $uri $uri/ $uri.html /index.html;
}
+3 -8
View File
@@ -4,8 +4,8 @@
# 92 (micro): ~/SDP/agent-micro # 92 (micro): ~/SDP/agent-micro
# 186 (gateway): ~/SDP/control-plane, ~/SDP/agent-gateway, ~/SDP/dashboard # 186 (gateway): ~/SDP/control-plane, ~/SDP/agent-gateway, ~/SDP/dashboard
# #
# On 186 we also splice the SDP location into nginx's existing default site # Nginx is configured by hand on 186 (out of scope for this script).
# and reload. Run scripts/build.sh first. # Run scripts/build.sh first.
set -euo pipefail set -euo pipefail
cd "$(dirname "$0")/.." cd "$(dirname "$0")/.."
@@ -47,10 +47,5 @@ $SCP_186 -r "$REPO_ROOT/dashboard/out/." "$HOST_186:~/SDP/dashboard/"
$SSH_186 "$HOST_186" "chmod +x ~/SDP/bin/control-plane ~/SDP/bin/agent-gateway" $SSH_186 "$HOST_186" "chmod +x ~/SDP/bin/control-plane ~/SDP/bin/agent-gateway"
echo " control-plane, agent-gateway, dashboard copied" echo " control-plane, agent-gateway, dashboard copied"
# Patch nginx on 186
echo echo
echo "==> 186: patching nginx" echo "done. (configure nginx by hand on 186; see AGENTS.md for the location block.)"
"$REPO_ROOT/scripts/patch-nginx.sh"
echo
echo "done."