Commit Graph
22 Commits
Author SHA1 Message Date
Tomas DvorakandDevin 5dc9b5f115 feat: external databases — register, probe, encrypted credentials
Databases hosted outside Containr can now be registered alongside
managed ones. The connection is probed before storing (protocol-level
for postgres/redis-family via real drivers, TCP dial for the rest),
credentials are encrypted at rest and never returned, and external rows
skip managed lifecycle actions, backups, and Docker metrics.

- POST /databases/register-external — probe + create
- POST /databases/test-connection — ad-hoc probe (ok/latency_ms/error)
- POST /databases/{id}/test-connection — re-probe stored credentials
- provider/external fields on database services; privileged reads get a
  composed connection URL with loopback hosts rewritten to the caller's
- CLI: databases register + test-connection; MCP: 3 new tools
- UI: managed/external toggle in the create modal, external badge and
  host display, per-row test button, backups section hidden
- Migration widens the plan CHECK to admit 'external'

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-08 22:09:42 +02:00
Tomas DvorakandDevin 3664eaed1a feat(services): clone/move ops + owner-scoped registry credentials (C4)
Service lifecycle: POST /services/:id/clone duplicates config, volumes,
domains, access gates, builder settings, and variables (secret ciphertext
copied verbatim); POST /services/:id/move re-parents to another owned
project. Both enforce ownership, reject name conflicts, and audit.

Registries: CRUD at /registries — host-matched pull credentials for
prebuilt-image deploys, encrypted at rest via secrets pkg, never returned
(has_password flag). Empty password on update keeps the stored credential;
host normalized (scheme/case/trailing slash). Credentials thread through
BuildConfig → BuildRequest → pull auth config, used only for the pull.

Surfaces: CLI services clone|move + registries group, 7 MCP tools,
OpenAPI paths/schemas, frontend Lifecycle card (clone/move/delete) and
Settings registries section, llms.txt + CLI.md.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-08 17:21:27 +02:00
Tomas Dvorak e881747388 feat: personal access tokens with scoped bearer auth
Add cnp_ personal access tokens so the CLI, MCP server, and external
agents authenticate without a browser session — the agent-first spine
from ROADMAP2 phase A. Modeled on agent_auth_tokens: raw token shown
once, only SHA-256 hash stored.

- user_tokens table (goose migration + sqlc), name/prefix/scope/expiry,
  owner-scoped revoke, last_used_at stamping
- middleware: cnp_ bearer resolution, read scope gates mutations with
  403 SCOPE_INSUFFICIENT, non-admin scopes cap is_admin even for admin
  owners — a token never outranks its scope or its owner
- GET/POST/DELETE /api/v1/user/tokens + OpenAPI + generated types
- Settings UI: create with scope/expiry pickers, one-time token display
  with copy, list with last-used/expiry, confirm-revoke
- docs: OPERATIONS.md section, README feature line

Verified end-to-end against live Postgres: create/list/revoke, read
scope blocks PUT (403), write PAT on admin owner blocked from admin
routes (403), admin scope works, revoked token returns 401.

Authored By: TDvorak <[email protected]>
2026-10-08 14:31:29 +02:00
Tomas Dvorak 38e0bc1802 merge: resolve main into feat/app-demo-polish 2026-09-23 19:06:42 +02:00
Tomas DvorakandDevin 1425618c92 feat(canvas): brand tech icons on services, matching site preview
Resolve a service's tech (postgres, redis, go, node, nginx, kafka,
~50 slugs) from its image/name and render the Simple Icons logo in
the tinted icon tile — canvas nodes, inspector, service detail
header, and database rows — with graceful fallback to type icons.
Demo services get resolvable images, ports, and domains; a Redis
Cache service joins Core Platform so the demo canvas mirrors the
landing preview (5 services, web/api/worker → api → postgres/redis).

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-23 18:12:15 +02:00
Tomas DvorakandDevin 5b0a8e0759 feat(app): unified demo mode + public-demo build for Vercel
- Central demo-mode helper (lib/demo-mode.ts): ?demo=1 query flag plus
  VITE_PUBLIC_DEMO build flag that makes every route demo for the
  public Vercel showcase.
- Single demo fixture source (lib/demo-data.ts): projects, services,
  builds, databases, cron, HA, security, host monitoring, templates.
  Fixes cross-page ID drift that broke project navigation.
- Demo support on Projects, HA, Security, Usage, Templates, Landing,
  Service detail; DemoRestricted gate on People/Settings/Audit/Admin.
- Demo-aware PlatformShell: nav filtering, env:demo, demo account menu,
  ?demo=1 preserved through internal links.
- TemplatesPage: selection moved into ?template= URL param for deep
  links; template fixtures shared via demo-data.
- requestJson returns {} for unparseable 200 bodies (SPA fallback) so
  envelope unwraps degrade instead of dereferencing null.
- Recharts ResponsiveContainer initialDimension kills -1 warnings.
- vercel.json: vite framework + SPA rewrites for direct deep links.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-23 17:38:02 +02:00
Tomas DvorakandDevin d48d9c293a fix(frontend): queryFn signatures + Link import
useQuery received api fns typed (params?) => Promise<T>; React Query
passes a QueryFunctionContext, so data degraded to {} and broke tsc.
Wrap calls in () => and import Link from react-router-dom.

Authored By: TDvorak <[email protected]>

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-23 11:44:36 +02:00
Tomas DvorakandDevin 8b6214a0f5 feat: public platform, admin console, templates, landing docs
Projects become publicly browsable after admin approval; management and
secret-adjacent reads (logs, variables, exec, connection URLs) require
owner/member/admin auth, and platform controls sit behind RequireAdmin.

- Backend: OptionalAuth middleware, read/authed/admin route groups,
  projects.is_approved + service_templates.owner_id migration,
  visibility-aware sqlc queries, admin overview/users/approval endpoints,
  EnsureAdminAccount bootstrap (ADMIN_* env), 30 seeded templates with
  owner CRUD, managed-database connection URLs gated owner+admin, fix
  DB.Stats() infinite recursion.
- Frontend: expandable persisted sidebar + clickable user menu, public
  landing page with searchable docs browser (GitHub sync, bundled
  snapshot + localStorage fallback), DocsBrowser shared with /docs,
  template create/edit/JSON-upload for owned templates, database create
  dialog, admin console (stats, approvals, project edit/delete, user
  admin toggles), HA/Security/Usage polish with dead CSS tokens mapped
  to real theme vars, anonymous-safe gating on every mutation control.
- Tests: safe window.localStorage access fixes happy-dom crashes in
  PlatformShell and canvas storage.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-22 19:58:59 +02:00
Tomas DvorakandDevin c16b32f56a feat: owner accounts and in-app platform settings
- First registered account becomes admin (users.is_admin, backfilled
  to earliest account); better-auth mirror path does the same
- Public registration closes after bootstrap; admin can reopen it via
  GET/PUT /api/v1/settings (admin-gated) or SIGNUP_ENABLED env
- In-app settings override env vars; secrets stored in app_settings,
  token values never returned by the API
- Saving a Cloudflare Tunnel token pulls and runs a managed
  cloudflared sidecar on the backend's networks; clearing removes it;
  startup restores it from saved settings
- group_add DOCKER_GID grants the non-root backend user Docker socket
  access; install.sh probes the in-container socket gid
- Settings page gains an owner-only Platform section (registration
  toggle, tunnel token, link to Cloudflare dashboard); sign-up page
  stays reachable while registration is open
- OpenAPI spec + generated types updated; manual user creation now
  requires admin

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-21 18:05:22 +02:00
Tomas DvorakandDevin 6d237eccf7 feat: demo-data parity for databases and cron tab
Adds demoDatabases fixtures (postgres/redis/clickhouse) wired into
/databases?demo=1 and demoCronJobsByService fixtures for the service
Cron tab, so demo mode no longer renders empty sections on the new
surfaces.

Authored By: TDvorak <[email protected]>

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-17 18:28:26 +02:00
Tomas DvorakandDevin d5e6bfc40d feat: security center page (/security)
Project-scoped security surface: scan trigger (dependency /
configuration / comprehensive, optional service), security score and
vulnerability metric cards, findings list with severity badges and
resolve/ignore actions, and scan history.

Fixed a latent bug in GET /projects/:id/security/metrics: it selected a
non-existent `score` column (the score lives in the security_scans
summary jsonb), so the endpoint 500'd on every project.

Verified live: comprehensive scan produced 4 real findings (insecure
public URL, curl|sh build, http source, unpinned tag) with score 47;
resolve flow and metrics aggregation confirmed.

Authored By: TDvorak <[email protected]>

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-17 18:24:19 +02:00
Tomas DvorakandDevin d5c99acb8c feat: high-availability page (/ha)
Platform-level HA surface: manager enable/disable toggle, status cards
(nodes, health checks, active alerts), manual failover with
confirmation, per-service failover policy CRUD with project/service
picker, active alert resolution, and health check result list.

Verified live: status, enable/disable, policy set/list/get/disable,
manual failover, alerts and health results endpoints. HA state is
in-memory — noted in roadmap alongside autoscaling persistence debt.

Authored By: TDvorak <[email protected]>

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-17 18:17:49 +02:00
Tomas Dvorak 08d92fb10f feat(backups): scheduled database backups via cron tick
- database_services gains backup_schedule + next_backup_at (goose migration,
  sqlc schema, new queries).
- PUT /databases/:id accepts backup_schedule — validated cron expression,
  empty string disables; invalid returns 400.
- Cron scheduler tick now also folds due databases into the existing
  snapshot pipeline (createBackupProcess).
- Databases page backups section gets a schedule row showing current expr
  and next run.
- resolveBackupConfig reports real next_backup/enabled instead of a fake
  last+24h estimate.
- Verified e2e: '* * * * *' produced three consecutive completed archives
  containing the seeded payload; invalid schedule rejected.

Authored By: TDvorak <[email protected]>
2026-09-17 17:46:02 +02:00
Tomas Dvorak aeb91b03e9 feat(databases): bind managed DB connection URL to a service
Databases page cards gain a Bind action: pick project → service → variable
key (default DATABASE_URL) and the connection_url is injected as a secret
env var via the existing variables endpoint. Verified e2e: merged PUT lands
the real URL and preserves masked secret values.

Authored By: TDvorak <[email protected]>
2026-09-17 17:35:05 +02:00
Tomas Dvorak bcd3a83aa4 feat(backups): archive download endpoint + GitHub App install surfacing
- GET /databases/:id/backups/:bid/download streams the archive out of the
  containr-db-backups volume via a stopped utility container and
  CopyFromContainer; daemon logs sanitize non-UTF-8 so binary through
  container logs was not viable. Verified e2e: byte-identical payload,
  404 missing/cross-user, 401 unauthenticated.
- Settings Git Providers gains an Install GitHub App action that opens the
  install_url and completes the installation_id return via connect endpoint.
- ROADMAP: mark both items done with verification evidence.

Authored By: TDvorak <[email protected]>
2026-09-17 17:30:30 +02:00
Tomas DvorakandDevin 18fe56f948 Add Databases management page and roadmap coverage audit
/databases page (new nav item): expandable managed-DB cards with
connection URL + copy, start/stop/restart actions, metrics summary, and
backup list with create/restore. Verified e2e against a real postgres:16
container — provision, psql connect, stop/start, seed, 6.4MB backup,
drop, restore with data recovered.

ROADMAP: marked databases/cron/git-connect/agents done with evidence,
corrected stale inventory rows (frontend now consumes 52 API ops),
added uncovered items — GitHub App install surfacing, database→service
binding, backup export + scheduled backups, service exec console,
OAuth e2e w/ real creds, remaining sqlc port, demo-data parity.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-17 17:15:11 +02:00
Tomas DvorakandDevin aab785afc1 Add agent onboarding tokens with Usage-page issuance UI
Per-node onboarding tokens replace the placeholder <token> in the agent
connect command: raw cagt_ tokens are returned once at issuance, only
sha256 hashes persist in agent_auth_tokens, and listing exposes metadata
only. Public agent routes accept env-configured shared tokens or any
active DB-issued token; successful validation stamps last_used_at and
revoked tokens are rejected — verified end-to-end against a real stack
(register, heartbeat, revoke → 401, bogus → 401).

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-17 16:46:26 +02:00
Tomas DvorakandDevin 2bfa683084 feat(audit): filterable audit log page under Settings
The audit trail existed only as a summary card — no table, no filters.
The list endpoint was also self-scoped and never returned user_email
(the field existed on the struct but was never selected).

Backend: GET /audit-logs now joins users for email and accepts
user_id / actor (email ILIKE) / since (RFC3339) filters alongside the
existing resource/action/page/limit. Widening past self-scope matches
the platform's no-roles trust model — every authed user can already
create users. RLS on audit_logs is dormant (superuser connection,
app.current_user_id never set).

Frontend: /settings/audit-logs page with deferred filter inputs, time
range select, paginated table (time, actor, resource, action, id, IP,
details), linked from a Settings card. Spec updated and types
regenerated.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-17 16:36:05 +02:00
Tomas DvorakandDevin 37a56dc6b6 Vertice restyle: fullscreen layout, lime/gray palette, mini-canvas project cards
- Token swap to near-black grays + lime accent + yellow secondary across
  index.css, all shared chart/card components, and every feature page
- PlatformShell: 62px icon rail, 46px telemetry ticker topbar, containr
  wordmark, env pill; mobile header preserved
- ProjectsPage rebuilt: cursor heading, host stat row (CPU sparkline,
  MEM/DISK meters, DEPLOYS), Railway-style mini-canvas project cards,
  mono deployment feed table
- All layout-level max-w-[1400px] caps removed -> w-full px-8
- New lime containr.svg synced to public/, root, and dist
- Auth glow, chart series colors, service-type colors retuned to palette
- Demo mode now seeds representative host telemetry
- Smoke test updated for new heading/copy

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-15 21:03:07 +02:00
Tomas DvorakandDevin f2d980e384 Visual pass: inspiration-style metric cards on Usage and service detail
- Service detail strip: EnhancedMetricCard with CPU sparkline, memory
  gauge, RX-rate sparkline, instance count — real telemetry or honest
  empty states, seeded series only in demo mode
- Usage: host monitoring becomes CPU sparkline + memory gauge + disk
  segmented-bar trio; build stats upgraded to EnhancedMetricCard with a
  real builds-per-day bar chart
- DonutChart: fix gauge geometry — arc baseline was below the viewBox,
  so only a sliver rendered; also fix SegmentedBar dynamic Tailwind
  class that never compiled
- auth sidecar: trust localhost:5175 origin for the second dev server

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-15 19:21:02 +02:00
Tomas DvorakandDevin 9f3b196032 Overhaul: real metrics, Git integration UI, open-source hygiene, root cleanup
Backend:
- GET /services/:id/metrics with real Docker container stats
- DELETE /git/providers/:id and GET repo branches endpoint

Frontend:
- Service detail shows real telemetry or honest empty states
- Live status distinguishes idle/polling/reconnecting
- Git providers section in Settings; repo+branch pickers in Add Service
- Real logo, compact Projects header, canvas fitView fix
- index.css deduplicated (1725 -> 1384 lines)
- Fixed smoke tests, lint errors, ToastProvider/context unification

Repo hygiene:
- Added LICENSE (MIT), CONTRIBUTING.md, SECURITY.md; rewrote README
- Untracked .env.prod and data/letsencrypt secrets; fixed .gitignore
- Removed legacy root src/internal/cmd and stale configs/docs/artifacts
- Moved guides and design references under docs/

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-15 18:59:40 +02:00
Tomas Dvorak 08cb5754f3 small fix, don't worry about it 2026-04-10 12:02:36 +02:00