mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-10-28 15:51:26 +00:00
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:
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>
|
||||
|
||||
Reference in new issue
Block a user