diff --git a/src/app/(dashboard)/environments/[id]/page.tsx b/src/app/(dashboard)/environments/[id]/page.tsx index ce19177e..f681d9b7 100644 --- a/src/app/(dashboard)/environments/[id]/page.tsx +++ b/src/app/(dashboard)/environments/[id]/page.tsx @@ -58,6 +58,10 @@ import { nodeStatusVariant, nodeStatusLabel } from "@/lib/status"; import { useTeamStore } from "@/stores/team-store"; import { EmptyState } from "@/components/empty-state"; import { QueryError } from "@/components/query-error"; +import { DemoDisabledNotice, DemoDisabledBadge } from "@/components/demo-disabled"; +import { isDemoMode } from "@/lib/is-demo-mode"; + +const DEMO_EXAMPLE_ENROLL_TOKEN = "vf_enroll_demo_example_REPLACE_WHEN_SELF_HOSTED"; export default function EnvironmentDetailPage({ params, @@ -547,13 +551,19 @@ export default function EnvironmentDetailPage({ - Agent Enrollment + + Agent Enrollment + + Generate a token for agents to enroll in this environment. The token is shown once — save it immediately. + {isDemoMode() && ( + + )} {enrollmentToken && (
@@ -584,8 +594,8 @@ export default function EnvironmentDetailPage({
)} - {/* Quick Start snippets — shown when a token exists */} - {env.hasEnrollmentToken && ( + {/* Quick Start snippets — shown when a token exists, or always in demo with an example token */} + {(env.hasEnrollmentToken || isDemoMode()) && (

Quick Start

@@ -598,7 +608,7 @@ export default function EnvironmentDetailPage({ className="h-6 w-6" aria-label="Copy Linux install command" onClick={async () => { - const token = enrollmentToken || ""; + const token = enrollmentToken || (isDemoMode() ? DEMO_EXAMPLE_ENROLL_TOKEN : ""); const cmd = `curl -sSfL https://raw.githubusercontent.com/TerrifiedBug/vectorflow/main/agent/install.sh | sudo bash -s -- --url ${window.location.origin} --token ${token}`; await copyToClipboard(cmd); toast.success("Command copied"); @@ -609,7 +619,7 @@ export default function EnvironmentDetailPage({
 {`curl -sSfL https://raw.githubusercontent.com/TerrifiedBug/vectorflow/main/agent/install.sh | \\
-  sudo bash -s -- --url ${typeof window !== "undefined" ? window.location.origin : "https://your-vectorflow-instance"} --token ${enrollmentToken || ""}`}
+  sudo bash -s -- --url ${typeof window !== "undefined" ? window.location.origin : "https://your-vectorflow-instance"} --token ${enrollmentToken || (isDemoMode() ? DEMO_EXAMPLE_ENROLL_TOKEN : "")}`}
                     
@@ -622,7 +632,7 @@ export default function EnvironmentDetailPage({ className="h-6 w-6" aria-label="Copy Docker run command" onClick={async () => { - const token = enrollmentToken || ""; + const token = enrollmentToken || (isDemoMode() ? DEMO_EXAMPLE_ENROLL_TOKEN : ""); const cmd = `docker run -d --name vf-agent --restart unless-stopped \\\n -e VF_URL=${window.location.origin} \\\n -e VF_TOKEN=${token} \\\n -v /var/lib/vf-agent:/var/lib/vf-agent \\\n ghcr.io/terrifiedbug/vectorflow-agent:latest`; await copyToClipboard(cmd); toast.success("Command copied"); @@ -634,7 +644,7 @@ export default function EnvironmentDetailPage({
 {`docker run -d --name vf-agent --restart unless-stopped \\
   -e VF_URL=${typeof window !== "undefined" ? window.location.origin : "https://your-vectorflow-instance"} \\
-  -e VF_TOKEN=${enrollmentToken || ""} \\
+  -e VF_TOKEN=${enrollmentToken || (isDemoMode() ? DEMO_EXAMPLE_ENROLL_TOKEN : "")} \\
   -v /var/lib/vf-agent:/var/lib/vf-agent \\
   ghcr.io/terrifiedbug/vectorflow-agent:latest`}
                     
@@ -654,7 +664,7 @@ export default function EnvironmentDetailPage({
diff --git a/src/app/(dashboard)/settings/_components/ai-settings.tsx b/src/app/(dashboard)/settings/_components/ai-settings.tsx index 4bcd236e..4806d44e 100644 --- a/src/app/(dashboard)/settings/_components/ai-settings.tsx +++ b/src/app/(dashboard)/settings/_components/ai-settings.tsx @@ -28,6 +28,7 @@ import { Switch } from "@/components/ui/switch"; import { Skeleton } from "@/components/ui/skeleton"; import { Badge } from "@/components/ui/badge"; import { QueryError } from "@/components/query-error"; +import { DemoDisabledBadge, DemoDisabledFieldset } from "@/components/demo-disabled"; const PROVIDER_DEFAULTS: Record = { openai: { baseUrl: "https://api.openai.com/v1", placeholder: "gpt-4o" }, @@ -118,6 +119,7 @@ function AiSettingsForm({ config, teamId }: { config: AiConfig; teamId: string } AI-Powered Suggestions + Configure an OpenAI-compatible AI provider for VRL code assistance and @@ -125,6 +127,7 @@ function AiSettingsForm({ config, teamId }: { config: AiConfig; teamId: string } + {/* Enable/Disable Toggle */}
@@ -243,6 +246,7 @@ function AiSettingsForm({ config, teamId }: { config: AiConfig; teamId: string } )}
+
diff --git a/src/app/(dashboard)/settings/_components/auth-settings.tsx b/src/app/(dashboard)/settings/_components/auth-settings.tsx index 507bc35f..102ee097 100644 --- a/src/app/(dashboard)/settings/_components/auth-settings.tsx +++ b/src/app/(dashboard)/settings/_components/auth-settings.tsx @@ -49,6 +49,7 @@ import { PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; +import { DemoDisabledBadge, DemoDisabledFieldset } from "@/components/demo-disabled"; // ─── Auth Tab ────────────────────────────────────────────────────────────────── @@ -246,7 +247,10 @@ export function AuthSettings() {
- OIDC / SSO Configuration + + OIDC / SSO Configuration + + Configure an OpenID Connect provider to enable single sign-on for your team. @@ -258,6 +262,7 @@ export function AuthSettings() {
+
@@ -395,17 +400,22 @@ export function AuthSettings() {
+
- IdP Group Mappings + + IdP Group Mappings + + Map identity provider groups to teams and roles. Used by both OIDC login (via groups claim) and SCIM sync (via group membership). +
{ e.preventDefault(); updateTeamMappingMutation.mutate({ @@ -626,6 +636,7 @@ export function AuthSettings() { )}
+
diff --git a/src/app/(dashboard)/settings/_components/backup-settings.tsx b/src/app/(dashboard)/settings/_components/backup-settings.tsx index 8a61135e..495c3550 100644 --- a/src/app/(dashboard)/settings/_components/backup-settings.tsx +++ b/src/app/(dashboard)/settings/_components/backup-settings.tsx @@ -42,6 +42,7 @@ import { TableRow, } from "@/components/ui/table"; import { Skeleton } from "@/components/ui/skeleton"; +import { DemoDisabledFieldset, DemoDisabledBadge } from "@/components/demo-disabled"; import { Switch } from "@/components/ui/switch"; import { EmptyState } from "@/components/empty-state"; import { ConfirmDialog } from "@/components/confirm-dialog"; @@ -220,12 +221,14 @@ export function BackupSettings() { return (
+ {/* Storage Backend */} Storage Backend + Choose where backup files are stored. S3-compatible storage works with AWS S3, MinIO, DigitalOcean Spaces, and Backblaze B2. @@ -603,6 +606,7 @@ export function BackupSettings() {
+ {/* Restore Dialog */}
- SCIM Provisioning + + SCIM Provisioning + + Enable SCIM 2.0 to automatically provision and deprovision users from your identity provider (Okta, Entra ID, etc.). @@ -115,6 +119,7 @@ export function ScimSettings() {
+
@@ -208,6 +213,7 @@ export function ScimSettings() {
+
diff --git a/src/components/demo-disabled.tsx b/src/components/demo-disabled.tsx new file mode 100644 index 00000000..e80b5ac8 --- /dev/null +++ b/src/components/demo-disabled.tsx @@ -0,0 +1,60 @@ +"use client"; + +import type { ReactNode } from "react"; +import { Lock } from "lucide-react"; +import { isDemoMode } from "@/lib/is-demo-mode"; + +const SECTION_CLASS = + "rounded-md border border-amber-500/40 bg-amber-500/10 px-3 py-2 text-sm text-amber-900 dark:bg-amber-500/5 dark:text-amber-200"; + +const BADGE_CLASS = + "inline-flex items-center gap-1 rounded-md border border-amber-500/40 bg-amber-500/10 px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-amber-900 dark:bg-amber-500/5 dark:text-amber-200"; + +export function DemoDisabledBadge({ className = "" }: { className?: string }) { + if (!isDemoMode()) return null; + return ( + + + Demo + + ); +} + +export function DemoDisabledNotice({ + message = "This area is read-only in the public demo. Self-host VectorFlow to configure it.", +}: { + message?: string; +}) { + if (!isDemoMode()) return null; + return ( +
+

+ + {message} +

+
+ ); +} + +/** + * Wraps form content. In demo mode, prepends a notice and disables every + * native form control inside via
. Outside demo mode, + * returns children unchanged. + */ +export function DemoDisabledFieldset({ + children, + message, +}: { + children: ReactNode; + message?: string; +}) { + if (!isDemoMode()) return <>{children}; + return ( +
+ +
+ {children} +
+
+ ); +} diff --git a/src/components/environment/git-sync-section.tsx b/src/components/environment/git-sync-section.tsx index 3e8891aa..011a3814 100644 --- a/src/components/environment/git-sync-section.tsx +++ b/src/components/environment/git-sync-section.tsx @@ -24,6 +24,7 @@ import { CardHeader, CardTitle, } from "@/components/ui/card"; +import { DemoDisabledBadge, DemoDisabledFieldset } from "@/components/demo-disabled"; interface GitSyncSectionProps { environmentId: string; @@ -166,7 +167,10 @@ export function GitSyncSection({
- Git Integration + + Git Integration + + Automatically commit pipeline YAML to a Git repository on deploy and delete. @@ -174,6 +178,7 @@ export function GitSyncSection({
+
)}
+
);