Skip to content
Merged
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
10 changes: 0 additions & 10 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,6 @@ updates:
labels:
- "dependencies"

- package-ecosystem: "npm"
directory: "/web"
schedule:
interval: "weekly"
day: "monday"
commit-message:
prefix: "chore"
labels:
- "dependencies"

- package-ecosystem: "github-actions"
directory: "/"
schedule:
Expand Down
7 changes: 0 additions & 7 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,6 @@ docker:
- "docker/**/*"
- "Dockerfile*"

web:
- changed-files:
- any-glob-to-any-file:
- "web/**/*"

tests:
- changed-files:
- any-glob-to-any-file:
Expand All @@ -45,5 +40,3 @@ dependencies:
- any-glob-to-any-file:
- "go.mod"
- "go.sum"
- "web/package.json"
- "web/package-lock.json"
15 changes: 0 additions & 15 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,3 @@ jobs:
run: |
CGO_ENABLED=1 go build -o dist/devcloud ./cmd/devcloud
CGO_ENABLED=1 go build -o dist/codegen ./cmd/codegen

- name: Set up Node.js
if: matrix.arch == 'amd64'
uses: actions/setup-node@v7
with:
node-version: "20"
cache: "npm"
cache-dependency-path: web/package-lock.json

- name: Build Next.js dashboard
if: matrix.arch == 'amd64'
run: |
cd web
npm ci
npm run build
5 changes: 0 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,6 @@ __pycache__/
*.pyo
.venv/

# Next.js
web/.next/
web/node_modules/
web/out/

# Worktrees
.worktrees/
tests/compatibility/data/
10 changes: 0 additions & 10 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,3 @@ repos:
types_or: [python]
- id: ruff-format
types_or: [python]

# Web (Next.js / TypeScript)
- repo: local
hooks:
- id: eslint
name: eslint
entry: bash -c 'cd web && npx eslint .'
language: system
pass_filenames: false
files: ^web/.*\.(ts|tsx|js|jsx)$
7 changes: 1 addition & 6 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
.PHONY: build test codegen run clean test-compat build-web build-all docker-build docker-run changelog stats
.PHONY: build test codegen run clean test-compat docker-build docker-run changelog stats

build:
go build -o dist/devcloud ./cmd/devcloud
go build -o dist/codegen ./cmd/codegen

build-web:
cd web && npm run build

build-all: build-web build

test:
CGO_ENABLED=1 go test ./... -v

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Today DevCloud targets **AWS**. Our long-term goal is to support the full range
- **Single binary, zero-config** — one Docker image, one port (4747), no config file required (embedded defaults)
- **Environment variable overrides** — `DEVCLOUD_SERVICES`, `DEVCLOUD_DATA_DIR`, `DEVCLOUD_PORT` for quick configuration without YAML
- **SDK/CLI compatible** — works with the AWS SDK, CLI, Terraform, CDK out of the box
- **Web dashboard** — real-time monitoring and resource browser
- **Admin API** — opt-in REST + WebSocket at `/devcloud/api/*` for service status, resource listing, and live request logs (`admin.enabled: true`). The web dashboard UI lives in a separate repository.

## Quick Start

Expand Down
5 changes: 5 additions & 0 deletions changes/unreleased/Changed-20260724-000001.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: Changed
body: Rename the `internal/dashboard` package to `internal/admin` and the `dashboard.enabled` config key to `admin.enabled`, reflecting its role as a general-purpose admin/introspection API rather than a UI-bound dashboard. The old `dashboard.enabled` key is still honoured for one release with a deprecation warning.
time: 2026-07-24T00:00:01.000000+09:00
custom:
Issue: "111"
5 changes: 5 additions & 0 deletions changes/unreleased/Removed-20260724-000000.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: Removed
body: Extract the Next.js web dashboard frontend to a separate repository. The Go binary no longer builds, bundles, or serves a web UI; it exposes only the opt-in admin API at `/devcloud/api/*`.
time: 2026-07-24T00:00:00.000000+09:00
custom:
Issue: "111"
39 changes: 16 additions & 23 deletions cmd/devcloud/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import (
"syscall"
"time"

"github.com/skyoo2003/devcloud/internal/admin"
"github.com/skyoo2003/devcloud/internal/config"
"github.com/skyoo2003/devcloud/internal/dashboard"
"github.com/skyoo2003/devcloud/internal/eventbus"
"github.com/skyoo2003/devcloud/internal/gateway"
"github.com/skyoo2003/devcloud/internal/plugin"
Expand Down Expand Up @@ -100,31 +100,24 @@ func main() {
slog.Warn("auth.enabled=true but SigV4 enforcement is not yet implemented; requests are accepted regardless of signature validity")
}

// Dashboard: build the API handler and wire the UI static files only
// when the operator opted in via dashboard.enabled. Otherwise expose a
// 404 handler so the dashboard routes don't leak service internals.
bus := eventbus.New()
logCollector := dashboard.NewLogCollector(1000)
dashHandler := http.NotFoundHandler()
webDir := ""
if cfg.Dashboard.Enabled {
dashAPI := dashboard.NewDashboardAPI(registry, logCollector)
hub := dashboard.NewHub(bus)
// Admin API: build the REST + WebSocket handler only when the operator
// opted in via admin.enabled. Otherwise expose a 404 handler so the admin
// routes don't leak service internals. This binary serves no web UI; the
// dashboard frontend lives in a separate repository and talks to this API.
logCollector := admin.NewLogCollector(1000)
adminHandler := http.NotFoundHandler()
if cfg.Admin.Enabled {
adminAPI := admin.NewAPI(registry, logCollector)
hub := admin.NewHub(eventbus.New())
go hub.Start()

dashMux := http.NewServeMux()
dashMux.Handle("/devcloud/api/", dashAPI.Handler())
dashMux.HandleFunc("/devcloud/api/ws", hub.ServeWS)
dashHandler = dashMux

if _, err := os.Stat("web/out"); err == nil {
webDir = "web/out"
slog.Info("dashboard UI enabled", "dir", webDir)
} else {
slog.Info("dashboard API enabled (UI disabled: web/out not found)")
}
adminMux := http.NewServeMux()
adminMux.Handle("/devcloud/api/", adminAPI.Handler())
adminMux.HandleFunc("/devcloud/api/ws", hub.ServeWS)
adminHandler = adminMux
slog.Info("admin API enabled")
}
gw := gateway.New(cfg.Server.Port, registry, dashHandler, logCollector, webDir)
gw := gateway.New(cfg.Server.Port, registry, adminHandler, logCollector)

sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
Expand Down
14 changes: 2 additions & 12 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,13 +1,4 @@
# Stage 1: Build Next.js static export
# Use host platform to avoid QEMU emulation — output is platform-independent static assets
FROM --platform=$BUILDPLATFORM node:20-alpine AS web-builder
WORKDIR /app/web
COPY web/package*.json ./
RUN npm ci
COPY web/ ./
RUN npm run build

# Stage 2: Build Go binary
# Stage 1: Build Go binary
FROM golang:1.26-alpine AS go-builder
RUN apk add --no-cache gcc musl-dev sqlite-dev
WORKDIR /app
Expand All @@ -18,14 +9,13 @@ ENV CGO_ENABLED=1
RUN go build -o /devcloud ./cmd/devcloud
RUN go build -o /codegen ./cmd/codegen

# Stage 3: Runtime
# Stage 2: Runtime
FROM alpine:3.20
RUN apk add --no-cache sqlite-libs ca-certificates su-exec
RUN adduser -D -H -h /app appuser
WORKDIR /app
COPY --from=go-builder /devcloud /app/devcloud
COPY --from=go-builder /codegen /app/codegen
COPY --from=web-builder /app/web/out /app/web/out
COPY smithy-models/ /app/smithy-models/
COPY internal/codegen/templates/ /app/templates/
COPY docker/entrypoint.sh /app/entrypoint.sh
Expand Down
12 changes: 0 additions & 12 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,3 @@ services:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- DEVCLOUD_SERVICES=s3,sqs,dynamodb,iam,sts,lambda

frontend:
image: node:20-alpine
working_dir: /app
ports:
- "3000:3000"
volumes:
- ../web:/app
- /app/node_modules
command: sh -c "npm install && npm run dev -- -H 0.0.0.0"
environment:
- NEXT_PUBLIC_API_URL=http://backend:4747
24 changes: 13 additions & 11 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ API Gateway (port 4747)
│ ├─ CORS (cross-origin handling)
│ ├─ RequestID (X-Amz-Request-Id)
│ ├─ RequestLogger (structured logging)
│ └─ LogCollector (dashboard live logs)
│ └─ LogCollector (admin API live logs)
├─ Route: /devcloud/api/* → Dashboard API
├─ Route: /devcloud/api/* → Admin API
Protocol Detector
Expand Down Expand Up @@ -135,18 +135,21 @@ A GitHub Actions workflow runs weekly to:
The event bus (`internal/eventbus/`) provides in-memory pub/sub for internal communication:

- Services publish events (resource created, deleted, etc.)
- Dashboard subscribes to stream real-time updates via WebSocket
- Loose coupling between services and dashboard
- The admin API subscribes to stream real-time updates via WebSocket
- Loose coupling between services and the admin API

## Dashboard
## Admin API

The dashboard (`internal/dashboard/`) provides:
The admin API (`internal/admin/`) provides:

- **REST API** at `/devcloud/api/` — service status, resource listing, request logs
- **WebSocket** at `/devcloud/api/ws` — real-time event streaming
- **Web UI** — Next.js static export served by the Go server

Log collector maintains a circular buffer of the last 1000 API requests for the dashboard log viewer.
It is disabled by default (`admin.enabled: false`). The web dashboard UI is a
separate project (its own repository) that consumes this API; the Go server
serves no UI itself.

Log collector maintains a circular buffer of the last 1000 API requests for the admin log endpoint.

## Directory Structure

Expand All @@ -162,10 +165,9 @@ devcloud/
│ ├── config/ # YAML config loading, env overrides
│ ├── generated/ # Auto-generated code (DO NOT EDIT; run `make stats` for count)
│ ├── services/ # Service implementations (run `make stats` for count)
│ ├── dashboard/ # Dashboard REST API + WebSocket
│ ├── admin/ # Admin REST API + WebSocket
│ ├── eventbus/ # In-memory event pub/sub
│ └── storage/ # Shared storage abstractions
├── web/ # Next.js dashboard (React, TypeScript, Tailwind)
├── docker/ # Dockerfile, docker-compose.yml
├── smithy-models/ # AWS Smithy JSON model files
├── tests/compatibility/ # Python/boto3 compatibility tests
Expand All @@ -183,7 +185,7 @@ devcloud/
4. Register service factories (run `make stats` for count)
5. Initialize services in dependency order (IAM before STS, etc.)
6. IAM store is shared with STS via plugin config options
7. Set up event bus, log collector, dashboard API
7. Set up event bus, log collector, admin API
8. Create gateway with middleware chain and service router
9. Start HTTP server on configured port
10. Wait for shutdown signal (SIGINT/SIGTERM), graceful shutdown with 15s timeout
8 changes: 5 additions & 3 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,13 @@ Service-specific options:
|-----|---------|-------------|
| `auth.enabled` | `false` | Enable SigV4 signature validation |

### Dashboard
### Admin API

| Key | Default | Description |
|-----|---------|-------------|
| `dashboard.enabled` | `false` | Enable the web dashboard |
| `admin.enabled` | `false` | Enable the admin REST + WebSocket API at `/devcloud/api/*` |

> The `dashboard` key was renamed to `admin`. The old `dashboard.enabled` key is still honoured for one release (with a deprecation warning); migrate to `admin.enabled`.

### Logging

Expand Down Expand Up @@ -88,7 +90,7 @@ services:
auth:
enabled: false

dashboard:
admin:
enabled: false

logging:
Expand Down
7 changes: 0 additions & 7 deletions docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
## Prerequisites

- Go 1.26+
- Node.js 20+ (for dashboard)
- SQLite3 development libraries (`brew install sqlite3` on macOS)
- Docker (optional, for Lambda runtime and integration tests)
- Python 3.10+ with pip (for compatibility tests)
Expand All @@ -17,12 +16,6 @@ cd devcloud
# Build Go binaries
make build

# Build Next.js dashboard
make build-web

# Build everything
make build-all

# Run the server
make run
```
Expand Down
20 changes: 12 additions & 8 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ Quick version:
```bash
git clone https://github.com/skyoo2003/devcloud.git
cd devcloud
make build-all # builds Go binary + Next.js dashboard
make run # starts server on port 4747
make build # builds the Go binaries (devcloud + codegen)
make run # starts server on port 4747
```

## Docker Compose (Development)
Expand Down Expand Up @@ -87,14 +87,18 @@ alias awslocal='aws --endpoint-url http://localhost:4747'
awslocal s3 ls
```

## Dashboard
## Admin API

When the server is running with `dashboard.enabled: true` in config, open http://localhost:4747 in your browser to access the web dashboard:
When the server runs with `admin.enabled: true` in config, an opt-in admin API
is exposed under `/devcloud/api/*`:

- Service status overview
- Resource browser (buckets, queues, tables, functions)
- Real-time API call logs
- WebSocket live updates
- `GET /devcloud/api/services` — service status overview
- `GET /devcloud/api/services/{id}/resources` — resource browser (buckets, queues, tables, functions)
- `GET /devcloud/api/metrics` — aggregate request/error counters
- `GET /devcloud/api/logs` — recent API call logs
- `GET /devcloud/api/ws` — WebSocket live updates

The web dashboard UI that consumes this API lives in a separate repository.

## Next Steps

Expand Down
6 changes: 3 additions & 3 deletions docs/plugin-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ type ServicePlugin interface {
| Method | Contract |
|--------|----------|
| `ServiceID()` | Stable lowercase identifier, e.g. `"s3"`. **Must equal the key the plugin is registered under** (the gateway routes by this key). Constant; safe to call before `Init`. |
| `ServiceName()` | Human-readable name for logs/dashboard. Must be non-empty. Constant; safe before `Init`. |
| `ServiceName()` | Human-readable name for logs/admin API. Must be non-empty. Constant; safe before `Init`. |
| `Protocol()` | One of the [`ProtocolType`](#protocols) constants. Constant; safe before `Init`. |
| `Init(config)` | Called once at startup before any request. Open stores, read `config.Options`. Return a non-nil error to abort startup (services in the fixed init order) or skip the service (others). |
| `Shutdown(ctx)` | Called once at graceful shutdown (15s budget). Flush/close resources. Idempotent-friendly. |
| `HandleRequest(ctx, op, req)` | Handle one API call. `op` is the operation name pre-extracted by the gateway (see [operation names](#operation-names)); it may be `""` for REST protocols, in which case derive it from method+path or `X-Amz-Target`. Return a `*Response`; return an `error` only for unexpected internal failures (the gateway wraps those as a `500 InternalError`). **Model AWS errors as a normal `*Response`** with the right status and error body, not as a Go `error`. |
| `ListResources(ctx)` | Return the resources this service currently holds (dashboard). May be empty. |
| `GetMetrics(ctx)` | Return request/error/resource counters (dashboard). May be zero-valued. |
| `ListResources(ctx)` | Return the resources this service currently holds (admin API). May be empty. |
| `GetMetrics(ctx)` | Return request/error/resource counters (admin API). May be zero-valued. |

These invariants are enforced for every registered service by
[`TestServicePluginConformance`](../cmd/devcloud/conformance_test.go).
Expand Down
Loading
Loading