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>
This commit is contained in:
Tomas DvorakandDevin committed 2026-09-22 19:58:59 +02:00
1 parent 8f5789bf93
commit 8b6214a0f5
62 files changed
+4986 -1133

No files matched your search

@@ -14,12 +14,14 @@ import {
FolderOpen,
X,
} from 'lucide-react';
import { useAuthSession } from '@/lib/use-auth-session';
const demoProjects: ProjectEntity[] = [
{
id: 'project-demo',
name: 'core-services',
description: 'Sample project with mock services for UI preview.',
isApproved: true,
createdAt: new Date(Date.now() - 14 * 86_400_000).toISOString(),
updatedAt: new Date().toISOString(),
stats: { service_count: 5, deployment_count: 12, running_services: 5, last_deployment: null },
@@ -28,6 +30,7 @@ const demoProjects: ProjectEntity[] = [
id: 'project-staging',
name: 'ml-pipeline',
description: 'Pre-production environment for testing new releases.',
isApproved: true,
createdAt: new Date(Date.now() - 7 * 86_400_000).toISOString(),
updatedAt: new Date(Date.now() - 2 * 86_400_000).toISOString(),
stats: { service_count: 2, deployment_count: 5, running_services: 1, last_deployment: null },
@@ -141,6 +144,15 @@ export function ProjectsPage() {
const [search, setSearch] = useState('');
const [isCreateOpen, setCreateOpen] = useState(false);
const [form, setForm] = useState({ name: '', description: '' });
const sessionQuery = useAuthSession({ enabled: !isDemoMode });
const signedIn = isDemoMode || Boolean(sessionQuery.data);
const openCreate = () => {
if (!signedIn) {
navigate('/auth/sign-in');
return;
}
setCreateOpen(true);
};
const projectHref = (projectId: string) =>
isDemoMode ? `/projects/${projectId}?demo=1` : `/projects/${projectId}`;
@@ -238,7 +250,7 @@ export function ProjectsPage() {
style={{ fontSize: 11.5 }}
/>
</div>
<button onClick={() => setCreateOpen(true)} className="v-btn">
<button onClick={openCreate} className="v-btn">
+ new project
</button>
</div>
@@ -310,10 +322,12 @@ export function ProjectsPage() {
</div>
<p className="text-xl font-semibold text-[var(--text-primary)]">No projects yet</p>
<p className="mx-auto mt-2 max-w-md text-[var(--text-secondary)]">
Create your first project to start deploying services with visual topology management.
{signedIn
? 'Create your first project to start deploying services with visual topology management.'
: 'No public projects yet. Sign in to create one — an admin approves it before it goes public.'}
</p>
<button onClick={() => setCreateOpen(true)} className="v-btn mt-6">
+ create project
<button onClick={openCreate} className="v-btn mt-6">
{signedIn ? '+ create project' : 'sign in to create'}
</button>
</div>
) : null}
@@ -357,6 +371,9 @@ export function ProjectsPage() {
<p className="mt-1 text-sm text-[var(--text-secondary)]">
Projects organize your services and provide a visual canvas for topology management.
</p>
<p className="mt-2 rounded-[var(--radius-md)] bg-[var(--warning-soft)] px-3 py-2 text-xs text-[var(--warning)]">
New projects stay private until a platform admin approves them for public listing.
</p>
<div className="mt-6 space-y-4">
<div>