Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 19 additions & 9 deletions src/app/(dashboard)/environments/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -547,13 +551,19 @@ export default function EnvironmentDetailPage({
<TabsContent value="enrollment" className="space-y-6">
<Card>
<CardHeader>
<CardTitle className="text-base">Agent Enrollment</CardTitle>
<CardTitle className="text-base flex items-center gap-2">
Agent Enrollment
<DemoDisabledBadge className="ml-auto" />
</CardTitle>
<CardDescription>
Generate a token for agents to enroll in this environment.
The token is shown once — save it immediately.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{isDemoMode() && (
<DemoDisabledNotice message="Agent enrollment is disabled in the public demo. The token shown below is an example and will not be accepted by the server. The /api/agent/enroll endpoint returns 403 in demo mode." />
)}
{enrollmentToken && (
<div className="space-y-2">
<Label>Token (save this — it won&apos;t be shown again)</Label>
Expand Down Expand Up @@ -584,8 +594,8 @@ export default function EnvironmentDetailPage({
</div>
)}

{/* 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()) && (
<div className="space-y-3 rounded-md border p-4">
<p className="text-sm font-medium">Quick Start</p>

Expand All @@ -598,7 +608,7 @@ export default function EnvironmentDetailPage({
className="h-6 w-6"
aria-label="Copy Linux install command"
onClick={async () => {
const token = enrollmentToken || "<enrollment-token>";
const token = enrollmentToken || (isDemoMode() ? DEMO_EXAMPLE_ENROLL_TOKEN : "<enrollment-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");
Expand All @@ -609,7 +619,7 @@ export default function EnvironmentDetailPage({
</div>
<pre className="overflow-x-auto rounded bg-muted px-3 py-2 text-xs">
{`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 || "<enrollment-token>"}`}
sudo bash -s -- --url ${typeof window !== "undefined" ? window.location.origin : "https://your-vectorflow-instance"} --token ${enrollmentToken || (isDemoMode() ? DEMO_EXAMPLE_ENROLL_TOKEN : "<enrollment-token>")}`}
</pre>
</div>

Expand All @@ -622,7 +632,7 @@ export default function EnvironmentDetailPage({
className="h-6 w-6"
aria-label="Copy Docker run command"
onClick={async () => {
const token = enrollmentToken || "<enrollment-token>";
const token = enrollmentToken || (isDemoMode() ? DEMO_EXAMPLE_ENROLL_TOKEN : "<enrollment-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");
Expand All @@ -634,7 +644,7 @@ export default function EnvironmentDetailPage({
<pre className="overflow-x-auto rounded bg-muted px-3 py-2 text-xs">
{`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 || "<enrollment-token>"} \\
-e VF_TOKEN=${enrollmentToken || (isDemoMode() ? DEMO_EXAMPLE_ENROLL_TOKEN : "<enrollment-token>")} \\
-v /var/lib/vf-agent:/var/lib/vf-agent \\
ghcr.io/terrifiedbug/vectorflow-agent:latest`}
</pre>
Expand All @@ -654,7 +664,7 @@ export default function EnvironmentDetailPage({
<div className="flex gap-2">
<Button
onClick={() => generateTokenMutation.mutate({ environmentId: id })}
disabled={generateTokenMutation.isPending}
disabled={generateTokenMutation.isPending || isDemoMode()}
size="sm"
>
{generateTokenMutation.isPending ? "Generating..." : env.hasEnrollmentToken ? "Regenerate Token" : "Generate Token"}
Expand All @@ -664,7 +674,7 @@ export default function EnvironmentDetailPage({
variant="destructive"
size="sm"
onClick={() => revokeTokenMutation.mutate({ environmentId: id })}
disabled={revokeTokenMutation.isPending}
disabled={revokeTokenMutation.isPending || isDemoMode()}
>
{revokeTokenMutation.isPending ? "Revoking..." : "Revoke Token"}
</Button>
Expand Down
4 changes: 4 additions & 0 deletions src/app/(dashboard)/settings/_components/ai-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, { baseUrl: string; placeholder: string }> = {
openai: { baseUrl: "https://api.openai.com/v1", placeholder: "gpt-4o" },
Expand Down Expand Up @@ -118,13 +119,15 @@ function AiSettingsForm({ config, teamId }: { config: AiConfig; teamId: string }
<CardTitle className="flex items-center gap-2">
<Sparkles className="h-4 w-4" />
AI-Powered Suggestions
<DemoDisabledBadge className="ml-auto" />
</CardTitle>
<CardDescription>
Configure an OpenAI-compatible AI provider for VRL code assistance and
pipeline generation. Credentials are encrypted at rest and scoped to this team.
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<DemoDisabledFieldset message="AI provider configuration is disabled in the public demo to prevent outbound calls to external LLM APIs.">
{/* Enable/Disable Toggle */}
<div className="flex items-center justify-between">
<div className="space-y-0.5">
Expand Down Expand Up @@ -243,6 +246,7 @@ function AiSettingsForm({ config, teamId }: { config: AiConfig; teamId: string }
</Badge>
)}
</div>
</DemoDisabledFieldset>
</CardContent>
</Card>
</div>
Expand Down
15 changes: 13 additions & 2 deletions src/app/(dashboard)/settings/_components/auth-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import {
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { DemoDisabledBadge, DemoDisabledFieldset } from "@/components/demo-disabled";

// ─── Auth Tab ──────────────────────────────────────────────────────────────────

Expand Down Expand Up @@ -246,7 +247,10 @@ export function AuthSettings() {
<div className="space-y-6">
<Card>
<CardHeader>
<CardTitle>OIDC / SSO Configuration</CardTitle>
<CardTitle className="flex items-center gap-2">
OIDC / SSO Configuration
<DemoDisabledBadge className="ml-auto" />
</CardTitle>
<CardDescription>
Configure an OpenID Connect provider to enable single sign-on for your
team.
Expand All @@ -258,6 +262,7 @@ export function AuthSettings() {
</div>
</CardHeader>
<CardContent>
<DemoDisabledFieldset message="OIDC / SSO configuration is disabled in the public demo. The fields below cannot be edited or saved.">
<form onSubmit={handleSave} className="space-y-6">
<div className="space-y-2">
<Label htmlFor="oidc-issuer">Issuer URL <span className="text-destructive">*</span></Label>
Expand Down Expand Up @@ -395,17 +400,22 @@ export function AuthSettings() {
</Button>
</div>
</form>
</DemoDisabledFieldset>
</CardContent>
</Card>

<Card>
<CardHeader>
<CardTitle>IdP Group Mappings</CardTitle>
<CardTitle className="flex items-center gap-2">
IdP Group Mappings
<DemoDisabledBadge className="ml-auto" />
</CardTitle>
<CardDescription>
Map identity provider groups to teams and roles. Used by both OIDC login (via groups claim) and SCIM sync (via group membership).
</CardDescription>
</CardHeader>
<CardContent>
<DemoDisabledFieldset message="Group-to-team mappings are disabled in the public demo.">
<form onSubmit={(e) => {
e.preventDefault();
updateTeamMappingMutation.mutate({
Expand Down Expand Up @@ -626,6 +636,7 @@ export function AuthSettings() {
)}
</Button>
</form>
</DemoDisabledFieldset>
</CardContent>
</Card>
</div>
Expand Down
4 changes: 4 additions & 0 deletions src/app/(dashboard)/settings/_components/backup-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -220,12 +221,14 @@ export function BackupSettings() {

return (
<div className="space-y-6">
<DemoDisabledFieldset message="Backup creation, restore, and S3 storage configuration are disabled in the public demo. The buttons and inputs below are read-only.">
{/* Storage Backend */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<HardDrive className="h-5 w-5" />
Storage Backend
<DemoDisabledBadge className="ml-auto" />
</CardTitle>
<CardDescription>
Choose where backup files are stored. S3-compatible storage works with AWS S3, MinIO, DigitalOcean Spaces, and Backblaze B2.
Expand Down Expand Up @@ -603,6 +606,7 @@ export function BackupSettings() {
</div>
</CardContent>
</Card>
</DemoDisabledFieldset>

{/* Restore Dialog */}
<RestoreDialog
Expand Down
8 changes: 7 additions & 1 deletion src/app/(dashboard)/settings/_components/scim-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { DemoDisabledBadge, DemoDisabledFieldset } from "@/components/demo-disabled";

// ─── SCIM Provisioning Section ──────────────────────────────────────────────

Expand Down Expand Up @@ -103,7 +104,10 @@ export function ScimSettings() {
<CardHeader>
<div className="flex items-center justify-between">
<div>
<CardTitle>SCIM Provisioning</CardTitle>
<CardTitle className="flex items-center gap-2">
SCIM Provisioning
<DemoDisabledBadge />
</CardTitle>
<CardDescription>
Enable SCIM 2.0 to automatically provision and deprovision users
from your identity provider (Okta, Entra ID, etc.).
Expand All @@ -115,6 +119,7 @@ export function ScimSettings() {
</div>
</CardHeader>
<CardContent className="space-y-6">
<DemoDisabledFieldset message="SCIM provisioning is disabled in the public demo. The toggle, base URL, and token generator below cannot be used.">
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label>Enable SCIM</Label>
Expand Down Expand Up @@ -208,6 +213,7 @@ export function ScimSettings() {
</ol>
</div>
</div>
</DemoDisabledFieldset>
</CardContent>
</Card>

Expand Down
60 changes: 60 additions & 0 deletions src/components/demo-disabled.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<span className={`${BADGE_CLASS} ${className}`}>
<Lock className="h-3 w-3" />
Demo
</span>
);
}

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 (
<div className={SECTION_CLASS}>
<p className="flex items-start gap-2">
<Lock className="mt-0.5 h-4 w-4 shrink-0" />
<span>{message}</span>
</p>
</div>
);
}

/**
* Wraps form content. In demo mode, prepends a notice and disables every
* native form control inside via <fieldset disabled>. Outside demo mode,
* returns children unchanged.
*/
export function DemoDisabledFieldset({
children,
message,
}: {
children: ReactNode;
message?: string;
}) {
if (!isDemoMode()) return <>{children}</>;
return (
<div className="space-y-4">
<DemoDisabledNotice message={message} />
<fieldset disabled className="space-y-6 opacity-70">
{children}
</fieldset>
</div>
);
}
8 changes: 7 additions & 1 deletion src/components/environment/git-sync-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { DemoDisabledBadge, DemoDisabledFieldset } from "@/components/demo-disabled";

interface GitSyncSectionProps {
environmentId: string;
Expand Down Expand Up @@ -166,14 +167,18 @@ export function GitSyncSection({
<div className="flex items-center gap-2">
<GitBranch className="h-5 w-5" />
<div>
<CardTitle>Git Integration</CardTitle>
<CardTitle className="flex items-center gap-2">
Git Integration
<DemoDisabledBadge />
</CardTitle>
<CardDescription>
Automatically commit pipeline YAML to a Git repository on deploy and delete.
</CardDescription>
</div>
</div>
</CardHeader>
<CardContent className="space-y-4">
<DemoDisabledFieldset message="Git repository configuration is disabled in the public demo. Self-host VectorFlow to connect a real GitHub, GitLab, or Bitbucket repository.">
<div className="space-y-2">
<Label htmlFor="git-repo-url">Repository URL</Label>
<Input
Expand Down Expand Up @@ -464,6 +469,7 @@ export function GitSyncSection({
</Button>
)}
</div>
</DemoDisabledFieldset>
</CardContent>
</Card>
);
Expand Down
Loading