Phases 11, 12: mcrctl CLI tool and mcr-web UI
Phase 11 implements the admin CLI with dual REST/gRPC transport, global flags (--server, --grpc, --token, --ca-cert, --json), and all commands: status, repo list/delete, policy CRUD, audit tail, gc trigger/status/reconcile, and snapshot. Phase 12 implements the HTMX web UI with chi router, session-based auth (HttpOnly/Secure/SameSite=Strict cookies), CSRF protection (HMAC-SHA256 signed double-submit), and pages for dashboard, repositories, manifest detail, policy management, and audit log. Security: CSRF via signed double-submit cookie, session cookies with HttpOnly/Secure/SameSite=Strict, TLS 1.3 minimum on all connections, form body size limits via http.MaxBytesReader. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
113
PROGRESS.md
113
PROGRESS.md
@@ -6,7 +6,7 @@ See `PROJECT_PLAN.md` for the implementation roadmap and
|
||||
|
||||
## Current State
|
||||
|
||||
**Phase:** 10 complete, ready for Phase 11
|
||||
**Phase:** 12 complete, ready for Phase 13
|
||||
**Last updated:** 2026-03-19
|
||||
|
||||
### Completed
|
||||
@@ -22,6 +22,8 @@ See `PROJECT_PLAN.md` for the implementation roadmap and
|
||||
- Phase 8: Admin REST API (all 5 steps)
|
||||
- Phase 9: Garbage collection (all 2 steps)
|
||||
- Phase 10: gRPC admin API (all 4 steps)
|
||||
- Phase 11: CLI tool (all 3 steps)
|
||||
- Phase 12: Web UI (all 5 steps)
|
||||
- `ARCHITECTURE.md` — Full design specification (18 sections)
|
||||
- `CLAUDE.md` — AI development guidance
|
||||
- `PROJECT_PLAN.md` — Implementation plan (14 phases, 40+ steps)
|
||||
@@ -29,7 +31,114 @@ See `PROJECT_PLAN.md` for the implementation roadmap and
|
||||
|
||||
### Next Steps
|
||||
|
||||
1. Phase 11 (CLI tool) and Phase 12 (web UI)
|
||||
1. Phase 13 (deployment artifacts)
|
||||
|
||||
---
|
||||
|
||||
### 2026-03-19 — Batch C: Phase 11 (CLI tool) + Phase 12 (Web UI)
|
||||
|
||||
**Task:** Implement the admin CLI and HTMX-based web UI — the two
|
||||
remaining user-facing layers. Both depend on Phase 10 (gRPC) but not
|
||||
on each other; implemented in parallel.
|
||||
|
||||
**Changes:**
|
||||
|
||||
Phase 11 — `cmd/mcrctl/` (Steps 11.1–11.3):
|
||||
|
||||
Step 11.1 — Client and connection setup:
|
||||
- `client.go`: `apiClient` struct wrapping both `*http.Client` (REST)
|
||||
and gRPC service clients (Registry, Policy, Audit, Admin); `newClient()`
|
||||
builds from flags; TLS 1.3 minimum with optional custom CA cert;
|
||||
gRPC dial uses `grpc.ForceCodecV2(mcrv1.JSONCodec{})` for JSON codec;
|
||||
`restDo()` helper with `Authorization: Bearer` header and JSON error
|
||||
parsing; transport auto-selected based on `--grpc` flag
|
||||
|
||||
Step 11.2 — Status and repository commands:
|
||||
- `main.go`: global persistent flags `--server`, `--grpc`, `--token`
|
||||
(fallback `MCR_TOKEN`), `--ca-cert`, `--json`; `PersistentPreRunE`
|
||||
resolves token and creates client; `status` command (gRPC + REST);
|
||||
`repo list` with table/JSON output; `repo delete` with confirmation
|
||||
prompt
|
||||
- `output.go`: `formatSize()` (B/KB/MB/GB/TB), `printJSON()` (indented),
|
||||
`printTable()` via `text/tabwriter`
|
||||
|
||||
Step 11.3 — Policy, audit, GC, and snapshot commands:
|
||||
- `main.go`: `policy list|create|update|delete` (full CRUD, `--rule`
|
||||
flag for JSON body, confirmation on delete); `audit tail` with
|
||||
`--n` and `--event-type` flags; `gc` with `--reconcile` flag;
|
||||
`gc status`; `snapshot`; all commands support both REST and gRPC
|
||||
- `client_test.go`: 10 tests covering formatSize, printJSON, printTable,
|
||||
token resolution from env/flag, newClient REST mode, CA cert error
|
||||
handling, restDo success/error/POST paths
|
||||
|
||||
Phase 12 — `cmd/mcr-web/` + `internal/webserver/` + `web/` (Steps 12.1–12.5):
|
||||
|
||||
Step 12.1 — Web server scaffolding:
|
||||
- `cmd/mcr-web/main.go`: reads `[web]` config section, creates gRPC
|
||||
connection with TLS 1.3 and JSON codec, creates MCIAS auth client for
|
||||
login, generates random 32-byte CSRF key, creates webserver, starts
|
||||
HTTPS with TLS 1.3, graceful shutdown on SIGINT/SIGTERM
|
||||
- `internal/webserver/server.go`: `Server` struct with chi router,
|
||||
gRPC service clients, CSRF key, login function; `New()` constructor;
|
||||
chi middleware (Recoverer, RequestID, RealIP); routes for all pages;
|
||||
session-protected route groups; static file serving from embedded FS
|
||||
- `web/embed.go`: `//go:embed templates static` directive
|
||||
- `web/static/style.css`: minimal clean CSS (system fonts, 1200px
|
||||
container, table styling, form styling, nav bar, stat cards, badges,
|
||||
pagination, responsive breakpoints)
|
||||
|
||||
Step 12.2 — Login and authentication:
|
||||
- `internal/webserver/auth.go`: session middleware (checks `mcr_session`
|
||||
cookie, redirects to `/login` if absent); login page (GET renders
|
||||
form with CSRF token); login submit (POST validates CSRF, calls
|
||||
`loginFn`, sets session cookie HttpOnly/Secure/SameSite=Strict);
|
||||
logout (clears cookie, redirects); CSRF via signed double-submit
|
||||
cookie (HMAC-SHA256)
|
||||
- `web/templates/login.html`: centered login form with CSRF hidden field
|
||||
|
||||
Step 12.3 — Dashboard and repository browsing:
|
||||
- `internal/webserver/handlers.go`: `handleDashboard()` (repo count,
|
||||
total size, recent audit events via gRPC); `handleRepositories()`
|
||||
(list table); `handleRepositoryDetail()` (tags, manifests, repo
|
||||
name with `/` support); `handleManifestDetail()` (manifest info
|
||||
by digest)
|
||||
- `internal/webserver/templates.go`: template loading from embedded FS
|
||||
with layout-page composition, function map (formatSize, formatTime,
|
||||
truncate, joinStrings), render helper
|
||||
- `web/templates/layout.html`: HTML5 base with nav bar, htmx CDN
|
||||
- `web/templates/dashboard.html`: stats cards + recent activity table
|
||||
- `web/templates/repositories.html`: repo list table
|
||||
- `web/templates/repository_detail.html`: tags + manifests tables
|
||||
- `web/templates/manifest_detail.html`: digest, media type, size
|
||||
|
||||
Step 12.4 — Policy management (admin only):
|
||||
- `internal/webserver/handlers.go`: `handlePolicies()` (list rules
|
||||
with CSRF token); `handleCreatePolicy()` (form with body limit,
|
||||
CSRF validation); `handleTogglePolicy()` (get+toggle enabled via
|
||||
UpdatePolicyRule with field mask); `handleDeletePolicy()` (CSRF +
|
||||
delete); PermissionDenied → "Access denied"
|
||||
- `web/templates/policies.html`: create form + rules table with
|
||||
toggle/delete actions
|
||||
|
||||
Step 12.5 — Audit log viewer (admin only):
|
||||
- `internal/webserver/handlers.go`: `handleAudit()` with pagination
|
||||
(fetch N+1 for next-page detection), filters (event type, repository,
|
||||
date range), URL builder for pagination links
|
||||
- `web/templates/audit.html`: filter form + paginated event table
|
||||
|
||||
**Verification:**
|
||||
- `make all` passes: vet clean, lint 0 issues, all tests passing,
|
||||
all 3 binaries built
|
||||
- CLI tests (10 new): formatSize (5 values: B through TB), printJSON
|
||||
output correctness, printTable header and row rendering, token from
|
||||
env var, token flag overrides env, newClient REST mode, CA cert
|
||||
errors (missing file, invalid PEM), restDo success with auth header,
|
||||
restDo error response parsing, restDo POST with body
|
||||
- Web UI tests (15 new): login page renders, invalid credentials error,
|
||||
CSRF token validation, dashboard requires session (redirect),
|
||||
dashboard with session, repositories page, repository detail,
|
||||
logout (cookie clearing), policies page, audit page, static files,
|
||||
formatSize, formatTime, truncate, login success sets cookie
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user