Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,8 @@ env/

### Node.js
node_modules/
web/.next/
web/out/
web/.turbo/
*.tsbuildinfo
.pnpm-store/
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ K8S_NAMESPACE ?= agentenv-system
K8S_RUNTIME_IMAGE ?= agentenv-runtime:latest
K8S_GATEWAY_IMAGE ?= agentenv-gateway:latest
K8S_SCHEDULER_IMAGE ?= agentenv-scheduler:latest
K8S_WEB_IMAGE ?= agentenv-web:latest
K3S_CTR ?= sudo k3s ctr

# aenv home path.
Expand Down Expand Up @@ -237,19 +238,23 @@ k8s-build:
$(DOCKER) build $(if $(APT_MIRROR_BASE),--build-arg APT_MIRROR_BASE="$(APT_MIRROR_BASE)",) -f deploy/docker/Dockerfile.agentenv -t $(K8S_RUNTIME_IMAGE) .
$(DOCKER) build -f deploy/docker/Dockerfile.gateway -t $(K8S_GATEWAY_IMAGE) .
$(DOCKER) build -f deploy/docker/Dockerfile.scheduler -t $(K8S_SCHEDULER_IMAGE) .
$(DOCKER) build -f deploy/docker/Dockerfile.web -t $(K8S_WEB_IMAGE) web

k8s-redeploy:
$(KUBECTL) rollout restart deploy/agentenv-gateway -n $(K8S_NAMESPACE)
$(KUBECTL) rollout restart deploy/agentenv-scheduler -n $(K8S_NAMESPACE)
$(KUBECTL) rollout restart deploy/agentenv-web -n $(K8S_NAMESPACE)
$(KUBECTL) rollout restart ds/agentenv-node -n $(K8S_NAMESPACE)
$(KUBECTL) rollout status deploy/agentenv-gateway -n $(K8S_NAMESPACE)
$(KUBECTL) rollout status deploy/agentenv-scheduler -n $(K8S_NAMESPACE)
$(KUBECTL) rollout status deploy/agentenv-web -n $(K8S_NAMESPACE)
$(KUBECTL) rollout status ds/agentenv-node -n $(K8S_NAMESPACE)

k8s-load-dev:
$(DOCKER) save $(K8S_RUNTIME_IMAGE) | $(K3S_CTR) images import -
$(DOCKER) save $(K8S_GATEWAY_IMAGE) | $(K3S_CTR) images import -
$(DOCKER) save $(K8S_SCHEDULER_IMAGE) | $(K3S_CTR) images import -
$(DOCKER) save $(K8S_WEB_IMAGE) | $(K3S_CTR) images import -

k8s-refresh-dev: k8s-build k8s-load-dev k8s-redeploy

Expand Down
16 changes: 16 additions & 0 deletions deploy/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,22 @@ services:
- "8080:8080"
restart: unless-stopped

web:
build:
context: ../web
dockerfile: ../deploy/docker/Dockerfile.web
image: agentenv-web:latest
container_name: agentenv-web
depends_on:
- gateway
ports:
- "3000:3000"
environment:
NODE_ENV: production
PORT: "3000"
AENV_DEFAULT_GATEWAY_URL: ${AENV_DEFAULT_GATEWAY_URL:-http://127.0.0.1:8080}
restart: unless-stopped

agentenv-a:
<<: *agentenv-base
build:
Expand Down
32 changes: 32 additions & 0 deletions deploy/docker/Dockerfile.web
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
FROM node:22-bookworm-slim AS deps
WORKDIR /app
RUN corepack enable && corepack prepare pnpm@11.0.8 --activate
COPY package.json pnpm-lock.yaml ./
COPY pnpm-workspace.yaml* ./
RUN pnpm install --frozen-lockfile

FROM node:22-bookworm-slim AS builder
WORKDIR /app
RUN corepack enable && corepack prepare pnpm@11.0.8 --activate
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN pnpm build

FROM node:22-bookworm-slim AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV PORT=3000
ENV HOSTNAME=0.0.0.0

RUN groupadd --system --gid 1001 nodejs \
&& useradd --system --uid 1001 --gid nodejs nextjs

COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs
EXPOSE 3000
CMD ["node", "server.js"]
5 changes: 5 additions & 0 deletions deploy/k8s/base/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ resources:
- scheduler-deployment.yaml
- gateway-service.yaml
- gateway-deployment.yaml
- web-service.yaml
- web-deployment.yaml
- agentenv-headless-service.yaml
- agentenv-ublk-daemon-metrics-service.yaml
- agentenv-daemonset.yaml
Expand Down Expand Up @@ -40,6 +42,9 @@ images:
- name: agentenv-runtime
newName: agentenv-runtime
newTag: latest
- name: agentenv-web
newName: agentenv-web
newTag: latest

generatorOptions:
disableNameSuffixHash: true
50 changes: 50 additions & 0 deletions deploy/k8s/base/web-deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: agentenv-web
labels:
app.kubernetes.io/name: agentenv-web
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: agentenv-web
template:
metadata:
labels:
app.kubernetes.io/name: agentenv-web
spec:
nodeSelector:
kubernetes.io/os: linux
containers:
- name: web
image: agentenv-web:latest
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 3000
env:
- name: NODE_ENV
value: production
- name: PORT
value: "3000"
- name: AENV_DEFAULT_GATEWAY_URL
value: http://agentenv-gateway:8080
readinessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 15
periodSeconds: 20
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
memory: 512Mi
13 changes: 13 additions & 0 deletions deploy/k8s/base/web-service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
name: agentenv-web
labels:
app.kubernetes.io/name: agentenv-web
spec:
selector:
app.kubernetes.io/name: agentenv-web
ports:
- name: http
port: 3000
targetPort: http
1 change: 1 addition & 0 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- [Quick Start](./getting-started/quickstart.md)
- [On-Demand Loading](./getting-started/on-demand-loading.md)
- [aenv CLI Reference](./getting-started/aenv-cli.md)
- [Web UI](./getting-started/web-ui.md)

# Deployment

Expand Down
1 change: 1 addition & 0 deletions docs/src/deployment/docker-compose.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Run a full multi-node stack on a single host using Docker Compose. This simulate
|---------|------|-------------|
| Gateway | `:8080` | HTTP/WebSocket reverse proxy |
| Scheduler | `:9090` | gRPC node selection and sandbox binding |
| Web UI | `:3000` | Control-plane console (Next.js) |
| agentenv-a | `:8001` | AgentENV runtime node A |
| agentenv-b | `:8002` | AgentENV runtime node B |

Expand Down
11 changes: 10 additions & 1 deletion docs/src/deployment/kubernetes.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Deploy AgentENV across a Kubernetes cluster with a gateway, scheduler, and runti
|----------|------|-------------|
| `agentenv-gateway` | Deployment + ClusterIP Service | HTTP reverse proxy for client traffic |
| `agentenv-scheduler` | Deployment (single replica) + ClusterIP Service | gRPC node selection and sandbox binding |
| `agentenv-web` | Deployment + ClusterIP Service | Control-plane Web UI (Next.js) |
| `agentenv-node` | DaemonSet (privileged) | One runtime Pod per Kubernetes node |
| `agentenv-nodes` | Headless Service | Used by the scheduler for EndpointSlice discovery |

Expand Down Expand Up @@ -37,7 +38,7 @@ cd AgentENV
make k8s-build
```

This builds three images: `agentenv-runtime:latest`, `agentenv-gateway:latest`, and `agentenv-scheduler:latest`.
This builds four images: `agentenv-runtime:latest`, `agentenv-gateway:latest`, `agentenv-scheduler:latest`, and `agentenv-web:latest`.

## Deploy

Expand Down Expand Up @@ -66,6 +67,14 @@ wildcard DNS for `*.sandbox.example.com`.

The default overlay is `deploy/k8s/overlays/default`, targeting the `agentenv-system` namespace. The gateway is exposed as ClusterIP by default. Add your own Ingress or LoadBalancer for external access.

Access the Web UI with:

```bash
kubectl -n agentenv-system port-forward svc/agentenv-web 3000:3000
```

Then open [http://127.0.0.1:3000](http://127.0.0.1:3000). See [Web UI](../getting-started/web-ui.md).

The make targets build a temporary Kustomize context so runtime Pods mount the repository's `config/default.toml` rather than a separate checked-in copy.

The runtime DaemonSet injects scheduler-report wiring for each node Pod:
Expand Down
50 changes: 50 additions & 0 deletions docs/src/getting-started/web-ui.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Web UI (control plane)

The AgentENV Web UI is a Next.js console for operators and developers to manage sandboxes, snapshots, templates, and nodes through the **Gateway HTTP API**.

## Prerequisites

- A running AgentENV Gateway (single-node `:8000` or multi-node Gateway `:8080`)
- An API key header value (`X-API-Key`) accepted by the deployment
- Optional admin token (`X-Admin-Token`) for `/nodes` APIs

## Local development

```bash
cd web
pnpm install
pnpm dev
```

Open [http://localhost:3000](http://localhost:3000). In **Settings**, set:

| Field | Example (Compose Gateway) | Example (single node) |
|---|---|---|
| Gateway URL | `http://127.0.0.1:8080` | `http://127.0.0.1:8000` |
| API key | any non-empty key | same |
| Admin token | optional | optional |

Credentials are stored in httpOnly session cookies. They are cleared on logout and, because the cookies carry no expiry, when the browser session ends.

## Docker Compose

The `web` service is defined in `deploy/docker-compose.yml` and published on host port **3000**.

```bash
docker compose -f deploy/docker-compose.yml up -d --build web
```

Then open [http://127.0.0.1:3000](http://127.0.0.1:3000) and point Settings at `http://127.0.0.1:8080` (Gateway published on the host).

Build context for the image is `web/` using `deploy/docker/Dockerfile.web`.

## Kubernetes

Kustomize base includes `agentenv-web` Deployment + Service (`deploy/k8s/base/`).

```bash
make k8s-apply
kubectl -n agentenv-system port-forward svc/agentenv-web 3000:3000
```

Default in-cluster Gateway URL env: `http://agentenv-gateway:8080`. When using the UI from a browser on your laptop via port-forward, set Settings to the Gateway URL **reachable from the Next.js server** (in-cluster service name if server-side fetches run in-pod) or use host-accessible URLs consistently.
13 changes: 13 additions & 0 deletions web/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
node_modules
.next
.git
.gitignore
*.md
.env*
.env*.local
eslint.config.*
.next/
out/
coverage/
.turbo
.DS_Store
41 changes: 41 additions & 0 deletions web/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# env files (can opt-in for committing if needed)
.env*

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
31 changes: 31 additions & 0 deletions web/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# AgentENV Web UI (`web/`)

Control-plane console for [Issue #6](https://github.com/kvcache-ai/AgentENV/issues/6).

## Stack

- Next.js App Router + TypeScript
- Tailwind CSS v4 + shadcn/ui
- pnpm

## Develop

```bash
cd web
pnpm install
pnpm dev
```

Open http://localhost:3000 — configure Gateway (default Compose `:8080`) under **Settings**.

## Conventions

- Talk to **Gateway HTTP only** (never Scheduler gRPC).
- Credentials live in **httpOnly cookies** (`src/lib/session.ts`); never log full secrets.
- Upstream calls: `src/lib/api/client.ts` (`gatewayFetch`).
- Feature routes live under `src/app/(console)/`.
- Use existing shadcn components in `src/components/ui/`.

## Non-goals (v1)

No browser terminal, filesystem browser, or in-sandbox process execution.
25 changes: 25 additions & 0 deletions web/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "base-nova",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"rtl": false,
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"menuColor": "default",
"menuAccent": "subtle",
"registries": {}
}
Loading
Loading