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>
This commit is contained in:
Tomas DvorakandDevin committed 2026-09-17 18:28:26 +02:00
1 parent d5e6bfc40d
commit 6d237eccf7
4 files changed
+112 -8

No files matched your search

+5 -3
View File
@@ -440,9 +440,11 @@ security scans produce real reports, not empty tables.
events live in process memory — a backend restart wipes them and
reverts replicas to policy minimums on next registration. Persist
to Postgres before leaving beta.
- [ ] **Demo-data parity for new pages**: `demo-data.ts` covers Projects/
Templates but the new Databases page and service Cron tab have no demo
fixtures — add them so `?demo=1` doesn't show empty sections.
- [x] **Demo-data parity for new pages**: `demoDatabases` fixture wired
into `/databases?demo=1`; `demoCronJobsByService` fixtures wired into
the service Cron tab (worker + api demo services). Scaling, Previews,
HA and Security sections still show empty in demo mode — acceptable:
those are interaction surfaces, not dashboards.
- [ ] **CI**: GitHub Actions — build, lint, typecheck, tests, compose boot
smoke; publish container images to GHCR with semver tags.
- [ ] **Release**: `v1.0.0` tag, changelog, release notes, screenshots refresh
@@ -47,6 +47,7 @@ import {
type DatabaseEntity,
type FailoverPolicy,
} from '@/lib/api-client';
import { demoDatabases } from '@/lib/demo-data';
import { formatRelative } from '@/lib/time';
import { getAuthBaseUrl, signOutAuthSession } from '@/lib/auth-client';
import { useBuildUpdates } from '@/lib/use-build-updates';
@@ -1535,12 +1536,15 @@ const dbStatusClass: Record<string, string> = {
export function DatabasesPage() {
const queryClient = useQueryClient();
const [searchParams] = useSearchParams();
const isDemoMode = searchParams.get('demo') === '1';
const [expandedId, setExpandedId] = useState<string | null>(null);
const [bindDb, setBindDb] = useState<DatabaseEntity | null>(null);
const databasesQuery = useQuery({
queryKey: ['databases'],
queryFn: listDatabases,
refetchInterval: 15_000,
enabled: !isDemoMode,
});
const invalidate = () => queryClient.invalidateQueries({ queryKey: ['databases'] });
@@ -1559,7 +1563,7 @@ export function DatabasesPage() {
onSuccess: () => window.setTimeout(invalidate, 3000),
});
const databases = databasesQuery.data ?? [];
const databases = isDemoMode ? demoDatabases : databasesQuery.data ?? [];
return (
<div>
@@ -30,7 +30,7 @@ import {
updateServiceVariables,
type CronJobEntity,
} from '@/lib/api-client';
import { getDemoProjectById, getDemoServiceById } from '@/lib/demo-data';
import { getDemoProjectById, getDemoServiceById, getDemoCronJobsByService } from '@/lib/demo-data';
import { parseDotenv, validateVariableRows, type VariableDraft } from '../variable-utils';
import { formatBytes, formatDate, formatRelative, seededMetric } from '@/lib/time';
import { EnhancedMetricCard, LineAreaChart, DonutChart } from '@/shared/components';
@@ -1290,14 +1290,14 @@ export function ServiceDetailPage() {
<div className="flex items-center justify-center py-12 text-[var(--text-muted)]">
<Loader2 size={20} className="animate-spin" />
</div>
) : (cronJobsQuery.data ?? []).length === 0 ? (
) : (isDemoMode ? getDemoCronJobsByService(serviceId) : cronJobsQuery.data ?? []).length === 0 ? (
<div className="panel-soft p-8 text-center">
<p className="text-sm text-[var(--text-secondary)]">No cron jobs configured.</p>
<p className="mt-1 text-xs text-[var(--text-muted)]">Create a job to run commands on a schedule inside this service's container.</p>
</div>
) : (
<div className="space-y-2">
{(cronJobsQuery.data ?? []).map((job) => (
{(isDemoMode ? getDemoCronJobsByService(serviceId) : cronJobsQuery.data ?? []).map((job) => (
<div key={job.id} className="panel-soft p-3">
<div className="flex items-center gap-3">
<button
+99 -1
View File
@@ -1,4 +1,4 @@
import type { ProjectEntity, ServiceEntity } from '@/lib/api-client';
import type { CronJobEntity, DatabaseEntity, ProjectEntity, ServiceEntity } from '@/lib/api-client';
import type { ServiceVariable } from '@/features/workspace/auto-connections';
export const demoProjects: ProjectEntity[] = [
@@ -231,3 +231,101 @@ export function getDemoServiceById(serviceId: string): ServiceEntity | undefined
export function getDemoVariablesByProject(projectId: string): Record<string, ServiceVariable[]> {
return demoVariablesByProject[projectId] ?? {};
}
export const demoDatabases: DatabaseEntity[] = [
{
id: 'demo-db-postgres',
name: 'core-postgres',
type: 'postgresql',
status: 'running',
connection_url: 'postgres://demo:***@core-postgres.internal:5432/app',
version: '16.4',
plan: 'standard',
region: 'local',
backup_schedule: '0 3 * * *',
next_backup_at: '2026-04-01T03:00:00Z',
created_at: '2026-03-12T10:05:00Z',
},
{
id: 'demo-db-redis',
name: 'cache-redis',
type: 'redis',
status: 'running',
connection_url: 'redis://:***@cache-redis.internal:6379',
version: '7.4',
plan: 'hobby',
region: 'local',
created_at: '2026-03-12T10:12:00Z',
},
{
id: 'demo-db-analytics',
name: 'analytics-clickhouse',
type: 'clickhouse',
status: 'stopped',
connection_url: 'clickhouse://analytics.internal:9000/default',
version: '25.3',
plan: 'business',
region: 'local',
created_at: '2026-02-18T08:00:00Z',
},
];
export const demoCronJobsByService: Record<string, CronJobEntity[]> = {
'demo-svc-worker': [
{
id: 'demo-cron-cleanup',
project_id: 'demo-project-core',
service_id: 'demo-svc-worker',
name: 'Nightly cleanup',
schedule: '0 2 * * *',
command: 'npm run cleanup:expired',
timezone: 'UTC',
enabled: true,
last_run_at: '2026-03-31T02:00:00Z',
next_run_at: '2026-04-01T02:00:00Z',
last_status: 'success',
retention: 50,
created_at: '2026-03-12T10:20:00Z',
updated_at: '2026-03-31T02:00:00Z',
},
{
id: 'demo-cron-report',
project_id: 'demo-project-core',
service_id: 'demo-svc-worker',
name: 'Hourly metrics rollup',
schedule: '15 * * * *',
command: 'npm run metrics:rollup',
timezone: 'UTC',
enabled: true,
last_run_at: '2026-03-31T09:15:00Z',
next_run_at: '2026-03-31T10:15:00Z',
last_status: 'success',
retention: 50,
created_at: '2026-03-12T10:21:00Z',
updated_at: '2026-03-31T09:15:00Z',
},
],
'demo-svc-api': [
{
id: 'demo-cron-warmup',
project_id: 'demo-project-core',
service_id: 'demo-svc-api',
name: 'Cache warmup',
schedule: '*/10 * * * *',
command: 'curl -sf http://localhost:8080/warm',
timezone: 'UTC',
enabled: false,
last_run_at: '2026-03-30T22:10:00Z',
next_run_at: '2026-03-30T22:20:00Z',
last_status: 'failed',
last_output: 'curl: (7) Failed to connect to localhost port 8080',
retention: 50,
created_at: '2026-03-12T10:22:00Z',
updated_at: '2026-03-30T22:10:00Z',
},
],
};
export function getDemoCronJobsByService(serviceId: string): CronJobEntity[] {
return demoCronJobsByService[serviceId] ?? [];
}