diff --git a/PLAN.md b/PLAN.md index d04cc50..f384aaf 100644 --- a/PLAN.md +++ b/PLAN.md @@ -287,14 +287,52 @@ Full dashboard for visibility, manual control, and configuration. 22. **Embed in Go** — build SvelteKit to static, embed with `go:embed`, serve from Go 23. **Real-time updates** — SSE for deploy progress and instance status changes -### Phase 4: Hardening +### Phase 4: Volumes & Environment -24. **Blue-green deploys** — start new, health check, swap, stop old (zero downtime) -25. **Promote flow** — enforce `promote_from` for production deploys -26. **Auth on dashboard** — basic auth or token-based -27. **Graceful shutdown** — drain in-progress deploys on SIGTERM -28. **Structured logging** — JSON logs with deploy context -29. **Config export** — download current SQLite state as YAML +Persistent storage and app-specific configuration for deployed containers. + +24. **Environment variables per project** — key/value pairs stored in SQLite, sensitive values encrypted +25. **Per-stage env overrides** — e.g., `NODE_ENV=development` for dev, `NODE_ENV=production` for prod +26. **Volume mounts per project** — configurable source/target paths with shared/isolated modes +27. **Shared volumes** — all instances of a project mount the same host path (for stateless apps or shared uploads) +28. **Isolated volumes** — each instance gets its own subdirectory: `{source}/{stage}-{tag}/` → `{target}` (for stateful apps with local DBs/files) +29. **UI for volumes & env** — project settings page with key/value editor, volume list, shared/isolated toggle, per-stage override support + +Volume config per project: +```yaml +env: + NODE_ENV: production + DATABASE_URL: postgres://db:5432/myapp # shared external DB + SECRET_KEY: "..." # encrypted in SQLite +volumes: + - source: /data/my-app/uploads + target: /app/uploads + mode: shared # all instances share this path + - source: /data/my-app/data + target: /app/data + mode: isolated # auto-appends /{stage}-{tag}/ to source +``` + +Stage-level env overrides: +```yaml +stages: + dev: + env: + NODE_ENV: development # overrides project-level + DATABASE_URL: postgres://db:5432/myapp_dev + prod: + env: + NODE_ENV: production # uses project-level default +``` + +### Phase 5: Hardening + +30. **Blue-green deploys** — start new, health check, swap, stop old (zero downtime) +31. **Promote flow** — enforce `promote_from` for production deploys +32. **Auth on dashboard** — basic auth or token-based +33. **Graceful shutdown** — drain in-progress deploys on SIGTERM +34. **Structured logging** — JSON logs with deploy context +35. **Config export** — download current SQLite state as YAML ## Key Dependencies (Go) diff --git a/plans/docker-watcher-core/PLAN.md b/plans/docker-watcher-core/PLAN.md index 37f5bb4..c9381a5 100644 --- a/plans/docker-watcher-core/PLAN.md +++ b/plans/docker-watcher-core/PLAN.md @@ -31,11 +31,12 @@ A self-hosted tool that automates Docker container deployment with Nginx Proxy M - [x] Phase 6: Webhook Handler [domain: backend] → [subplan](./phase-6-webhook-handler.md) - [x] Phase 7: Deployer & Health Checker [domain: backend] → [subplan](./phase-7-deployer.md) - [x] Phase 8: REST API Layer [domain: backend] → [subplan](./phase-8-api-layer.md) -- [ ] Phase 9: SvelteKit Dashboard & Project Views [domain: frontend] → [subplan](./phase-9-dashboard.md) +- [x] Phase 9: SvelteKit Dashboard & Project Views [domain: frontend] → [subplan](./phase-9-dashboard.md) - [ ] Phase 10: Quick Deploy & Settings Pages [domain: frontend] → [subplan](./phase-10-settings-deploy.md) - [ ] Phase 11: Frontend Embed & Real-Time Updates [domain: fullstack] → [subplan](./phase-11-embed-sse.md) - [ ] Phase 12: Hardening [domain: backend] → [subplan](./phase-12-hardening.md) - [ ] Phase 13: Frontend Polish & Modern UI [domain: frontend] → [subplan](./phase-13-ui-polish.md) +- [ ] Phase 14: Volumes & Environment [domain: fullstack] → [subplan](./phase-14-volumes-env.md) ### Parallel Execution Notes @@ -54,11 +55,12 @@ A self-hosted tool that automates Docker container deployment with Nginx Proxy M | Phase 6: Webhook Handler | backend | ✅ Complete | ✅ Pass w/ fixes | ⏭️ Skip (Big Bang) | ✅ | | Phase 7: Deployer & Health | backend | ✅ Complete | ✅ Pass w/ fixes | ⏭️ Skip (Big Bang) | ✅ | | Phase 8: API Layer | backend | ✅ Complete | ✅ Pass w/ fixes | ⏭️ Skip (Big Bang) | ✅ | -| Phase 9: Dashboard | frontend | ⬜ Not Started | ⬜ | ⏭️ Skip (Big Bang) | ⬜ | +| Phase 9: Dashboard | frontend | ✅ Complete | ⬜ Pending | ⏭️ Skip (Big Bang) | ⬜ | | Phase 10: Settings & Deploy | frontend | ⬜ Not Started | ⬜ | ⏭️ Skip (Big Bang) | ⬜ | | Phase 11: Embed & SSE | fullstack | ⬜ Not Started | ⬜ | ⏭️ Skip (Big Bang) | ⬜ | | Phase 12: Hardening | backend | ⬜ Not Started | ⬜ | ⏭️ Skip (Big Bang) | ⬜ | -| Phase 13: UI Polish | frontend | ⬜ Not Started | ⬜ | ✅ Required (Final) | ⬜ | +| Phase 13: UI Polish | frontend | ⬜ Not Started | ⬜ | ⏭️ Skip (Big Bang) | ⬜ | +| Phase 14: Volumes & Env | fullstack | ⬜ Not Started | ⬜ | ✅ Required (Final) | ⬜ | ## Amendment Log @@ -76,6 +78,13 @@ A self-hosted tool that automates Docker container deployment with Nginx Proxy M **Why:** User wants bilingual support (English and Russian) in the dashboard **Impact on existing phases:** None — contained within Phase 13 +### Amendment 3 — 2026-03-27 + +**Type:** Added phase +**What changed:** Added Phase 14: Volumes & Environment — per-project env vars with per-stage overrides, volume mounts with shared/isolated modes, encryption for sensitive values, UI editor +**Why:** Missing from feature planner phases but present in root PLAN.md Phase 4 +**Impact on existing phases:** Phase 14 becomes the final phase (build/tests required). Phase 13 (UI Polish) remains but no longer the final phase for build enforcement. + ## Final Review - [ ] Comprehensive code review diff --git a/plans/docker-watcher-core/phase-14-volumes-env.md b/plans/docker-watcher-core/phase-14-volumes-env.md new file mode 100644 index 0000000..68edfcd --- /dev/null +++ b/plans/docker-watcher-core/phase-14-volumes-env.md @@ -0,0 +1,58 @@ +# Phase 14: Volumes & Environment + +**Status:** ⬜ Not Started +**Parent plan:** [PLAN.md](./PLAN.md) +**Domain:** fullstack + +## Objective +Implement per-project environment variables with per-stage overrides, volume mounts with shared/isolated modes, sensitive env value encryption, and UI for managing both. + +## Tasks + +- [ ] Task 1: Extend store schema — add `stage_env` table for per-stage env overrides (stage_id, key, value, encrypted bool) +- [ ] Task 2: Extend store schema — add `volumes` table for volume config (project_id, source, target, mode: shared|isolated) +- [ ] Task 3: Implement store CRUD for stage env overrides (Create, GetByStageID, Update, Delete) +- [ ] Task 4: Implement store CRUD for volumes (Create, GetByProjectID, Update, Delete) +- [ ] Task 5: Encrypt sensitive env values (values marked as secret) using crypto.Encrypt before storage +- [ ] Task 6: Merge env vars during deploy — project-level env + stage-level overrides, decrypt secrets +- [ ] Task 7: Compute volume mounts during deploy — shared mode uses path as-is, isolated mode appends `/{stage}-{tag}/` to source +- [ ] Task 8: Pass merged env vars and volume mounts to Docker container creation +- [ ] Task 9: API endpoints — CRUD for stage env vars and project volumes +- [ ] Task 10: Frontend — env var editor in project/stage settings (key/value pairs, secret toggle) +- [ ] Task 11: Frontend — volume editor in project settings (source/target/mode) +- [ ] Task 12: Frontend — per-stage env override UI showing inherited vs overridden values + +## Files to Modify/Create +- `internal/store/stage_env.go` — stage env CRUD +- `internal/store/volumes.go` — volume CRUD +- `internal/store/store.go` — add new tables to schema +- `internal/deployer/deployer.go` — merge env vars and compute volume mounts during deploy +- `internal/docker/container.go` — accept volume mounts in ContainerConfig +- `internal/api/stages.go` — add env var endpoints +- `internal/api/projects.go` — add volume endpoints +- `web/src/routes/projects/[id]/env/+page.svelte` — env var editor +- `web/src/routes/projects/[id]/volumes/+page.svelte` — volume editor + +## Acceptance Criteria +- Project-level env vars applied to all containers +- Stage-level overrides replace project-level values for matching keys +- Sensitive env values encrypted at rest, decrypted only during deploy +- Shared volumes: all instances mount same host path +- Isolated volumes: each instance gets `{source}/{stage}-{tag}/` subdirectory +- UI allows managing env vars and volumes per project and per stage + +## Notes +- Project `env` field already exists as JSON blob in the store — this phase may migrate to a proper table or keep JSON and add stage overrides separately +- Volume `mode` is either "shared" or "isolated" +- Isolated volume subdirectory is created automatically by Docker (bind mount creates parent dirs) +- Sensitive env display: masked in UI, "Change" button pattern (same as credentials page) + +## Review Checklist +- [ ] All tasks completed +- [ ] Env merge logic is correct (stage overrides project) +- [ ] Secret values never appear in plaintext in API responses +- [ ] Volume paths are validated (no path traversal) +- [ ] Isolated volume subdirectory naming is deterministic + +## Handoff to Next Phase + diff --git a/plans/docker-watcher-core/phase-9-dashboard.md b/plans/docker-watcher-core/phase-9-dashboard.md index c4df7fa..6dcd7e5 100644 --- a/plans/docker-watcher-core/phase-9-dashboard.md +++ b/plans/docker-watcher-core/phase-9-dashboard.md @@ -1,6 +1,6 @@ # Phase 9: SvelteKit Dashboard & Project Views -**Status:** ⬜ Not Started +**Status:** ✅ Complete **Parent plan:** [PLAN.md](./PLAN.md) **Domain:** frontend @@ -9,18 +9,18 @@ Build the SvelteKit frontend with the dashboard overview and project detail view ## Tasks -- [ ] Task 1: Initialize SvelteKit project in `web/` directory with TypeScript, static adapter -- [ ] Task 2: Set up Tailwind CSS (or project's preferred styling) -- [ ] Task 3: Create shared API client (`lib/api.ts`) — typed fetch wrapper for all backend endpoints -- [ ] Task 4: Define TypeScript types (`lib/types.ts`) — Project, Stage, Instance, Deploy, Registry, Settings -- [ ] Task 5: Create layout with navigation — sidebar or top nav with Dashboard, Projects, Deploy, Settings links -- [ ] Task 6: Dashboard page (`routes/+page.svelte`) — project overview cards with instance counts, status indicators, latest activity -- [ ] Task 7: Projects list page (`routes/projects/+page.svelte`) — all projects with quick stats, "Add Project" button -- [ ] Task 8: Project detail page (`routes/projects/[id]/+page.svelte`) — stages, instances per stage, controls -- [ ] Task 9: Instance controls — Stop, Start, Restart, Remove buttons with confirmation dialogs -- [ ] Task 10: Deploy history section in project detail — recent deploys with status, timestamp, tag -- [ ] Task 11: "Deploy new version" dropdown — list available tags from registry, trigger deploy -- [ ] Task 12: Create reusable components: StatusBadge, InstanceCard, ProjectCard, ConfirmDialog +- [x] Task 1: Initialize SvelteKit project in `web/` directory with TypeScript, static adapter +- [x] Task 2: Set up Tailwind CSS v4 with @tailwindcss/vite plugin +- [x] Task 3: Create shared API client (`lib/api.ts`) — typed fetch wrapper for all backend endpoints +- [x] Task 4: Define TypeScript types (`lib/types.ts`) — Project, Stage, Instance, Deploy, Registry, Settings +- [x] Task 5: Create layout with navigation — sidebar with Dashboard, Projects, Deploy, Settings links +- [x] Task 6: Dashboard page (`routes/+page.svelte`) — project overview cards with instance counts, status indicators +- [x] Task 7: Projects list page (`routes/projects/+page.svelte`) — all projects with quick stats, "Add Project" button +- [x] Task 8: Project detail page (`routes/projects/[id]/+page.svelte`) — stages, instances per stage, controls +- [x] Task 9: Instance controls — Stop, Start, Restart, Remove buttons with confirmation dialogs +- [x] Task 10: Deploy history section in project detail — recent deploys with status, timestamp, tag +- [x] Task 11: "Deploy new version" dropdown — list available tags from registry, trigger deploy +- [x] Task 12: Create reusable components: StatusBadge, InstanceCard, ProjectCard, ConfirmDialog ## Files to Modify/Create - `web/package.json` — SvelteKit project config @@ -61,4 +61,39 @@ Build the SvelteKit frontend with the dashboard overview and project detail view - [ ] Components are reusable and well-structured ## Handoff to Next Phase - + +Phase 9 is complete. All 14 files have been created in the `web/` directory: + +**Configuration files:** +- `web/package.json` — Svelte 5, SvelteKit 2, Tailwind CSS v4, static adapter, TypeScript +- `web/svelte.config.js` — Static adapter with SPA fallback (`index.html`) +- `web/vite.config.ts` — Tailwind v4 vite plugin + `/api` proxy to `localhost:8080` +- `web/tsconfig.json` — Strict TypeScript, bundler module resolution +- `web/src/app.html` — Base HTML shell +- `web/src/app.css` — Tailwind v4 import +- `web/src/routes/+layout.ts` — Disables SSR, enables prerender for static adapter + +**Core library:** +- `web/src/lib/types.ts` — All TypeScript types matching Go backend models exactly (Project, Stage, Instance, Deploy, DeployLog, Registry, Settings, ApiEnvelope) +- `web/src/lib/api.ts` — Full typed API client covering all endpoints (projects, instances, deploys, registries, settings). Unwraps envelope, throws `ApiError` on failure. + +**Components (Svelte 5 runes):** +- `StatusBadge.svelte` — Color-coded status pill (green/yellow/red/gray/blue) +- `ConfirmDialog.svelte` — Modal with danger/primary variants +- `InstanceCard.svelte` — Instance display with stop/start/restart/remove controls +- `ProjectCard.svelte` — Project summary card for dashboard grid + +**Pages:** +- `+layout.svelte` — Sidebar navigation (Dashboard, Projects, Deploy, Settings) +- `routes/+page.svelte` — Dashboard with stats cards and project grid +- `routes/projects/+page.svelte` — Project table with inline add-project form +- `routes/projects/[id]/+page.svelte` — Full project detail: stages, instances, deploy form, deploy history + +**Key decisions:** +- Used Svelte 5 runes (`$state`, `$derived`, `$effect`, `$props`) throughout +- Tailwind CSS v4 with `@tailwindcss/vite` plugin (no PostCSS config needed) +- Client-side only rendering (SSR disabled) for static adapter compatibility +- API client uses relative `/api/` paths — works in both dev (vite proxy) and prod (embedded) +- All API calls include loading spinners and error states with retry buttons + +**Ready for Phase 10:** Settings pages, Quick Deploy page, and remaining UI routes. The API client already includes all endpoint wrappers needed. diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte new file mode 100644 index 0000000..ad456e4 --- /dev/null +++ b/web/src/routes/+layout.svelte @@ -0,0 +1,78 @@ + + +
Total Projects
+{totalProjects}
+Running Instances
+{totalRunning}
+Failed Instances
++ {totalFailed} +
+{error}
+ +No projects yet.
+ + Add your first project + +{formError}
+{error}
+ +No projects configured yet.
+Click "Add Project" to get started.
+| Name | +Image | +Port | +Registry | +Created | ++ |
|---|---|---|---|---|---|
| + + {project.name} + + | ++ {project.image} + | ++ {project.port || '-'} + | ++ {project.registry || '-'} + | ++ {new Date(project.created_at).toLocaleDateString()} + | ++ + View + + | +
{error}
+ +Port
+{project.port || '-'}
+Registry
+{project.registry || '-'}
+Healthcheck
+{project.healthcheck || '-'}
+Created
+{new Date(project.created_at).toLocaleDateString()}
+No stages configured for this project.
+Loading tags...
+ {:else if availableTags.length > 0} + + {:else} + + {/if} +{deployError}
+ {/if} +No instances running
+ {:else} +No deploy history for this project.
+ {:else} +| Tag | +Status | +Started | +Finished | +Error | +
|---|---|---|---|---|
| {deploy.image_tag} | +
+ |
+ + {deploy.started_at ? new Date(deploy.started_at).toLocaleString() : '-'} + | ++ {deploy.finished_at ? new Date(deploy.finished_at).toLocaleString() : '-'} + | ++ {deploy.error || '-'} + | +
+ This secret URL receives image push notifications from your CI pipeline. +
+ + {#if webhookUrl} +
+ {webhookUrl}
+
+
+ No webhook URL configured
+ {/if} + ++ Warning: regenerating will invalidate the current URL. Update your CI pipelines. +
++ Manage credentials for Nginx Proxy Manager and registry tokens. All values are encrypted at + rest. +
+Credentials for managing proxy hosts via NPM API
+URL
+{npmUrl || 'Not set'}
+{npmEmail || 'Not set'}
+Password
+--------
++ Registry authentication tokens are managed per-registry in the + Registries + section. Each registry stores its token encrypted in the database. +
+Manage your container registries for image detection.
+Registry type for API compatibility
+No registries configured yet.
+ {#if !showForm} + + {/if} +{registry.url}
+