mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-10-28 07:41:27 +00:00
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:
1 parent
d5e6bfc40d
commit
6d237eccf7
4 files changed
+112
-8
No files matched your search
+5
-3
@@ -440,9 +440,11 @@ security scans produce real reports, not empty tables.
|
|||||||
events live in process memory — a backend restart wipes them and
|
events live in process memory — a backend restart wipes them and
|
||||||
reverts replicas to policy minimums on next registration. Persist
|
reverts replicas to policy minimums on next registration. Persist
|
||||||
to Postgres before leaving beta.
|
to Postgres before leaving beta.
|
||||||
- [ ] **Demo-data parity for new pages**: `demo-data.ts` covers Projects/
|
- [x] **Demo-data parity for new pages**: `demoDatabases` fixture wired
|
||||||
Templates but the new Databases page and service Cron tab have no demo
|
into `/databases?demo=1`; `demoCronJobsByService` fixtures wired into
|
||||||
fixtures — add them so `?demo=1` doesn't show empty sections.
|
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
|
- [ ] **CI**: GitHub Actions — build, lint, typecheck, tests, compose boot
|
||||||
smoke; publish container images to GHCR with semver tags.
|
smoke; publish container images to GHCR with semver tags.
|
||||||
- [ ] **Release**: `v1.0.0` tag, changelog, release notes, screenshots refresh
|
- [ ] **Release**: `v1.0.0` tag, changelog, release notes, screenshots refresh
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import {
|
|||||||
type DatabaseEntity,
|
type DatabaseEntity,
|
||||||
type FailoverPolicy,
|
type FailoverPolicy,
|
||||||
} from '@/lib/api-client';
|
} from '@/lib/api-client';
|
||||||
|
import { demoDatabases } from '@/lib/demo-data';
|
||||||
import { formatRelative } from '@/lib/time';
|
import { formatRelative } from '@/lib/time';
|
||||||
import { getAuthBaseUrl, signOutAuthSession } from '@/lib/auth-client';
|
import { getAuthBaseUrl, signOutAuthSession } from '@/lib/auth-client';
|
||||||
import { useBuildUpdates } from '@/lib/use-build-updates';
|
import { useBuildUpdates } from '@/lib/use-build-updates';
|
||||||
@@ -1535,12 +1536,15 @@ const dbStatusClass: Record<string, string> = {
|
|||||||
|
|
||||||
export function DatabasesPage() {
|
export function DatabasesPage() {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const isDemoMode = searchParams.get('demo') === '1';
|
||||||
const [expandedId, setExpandedId] = useState<string | null>(null);
|
const [expandedId, setExpandedId] = useState<string | null>(null);
|
||||||
const [bindDb, setBindDb] = useState<DatabaseEntity | null>(null);
|
const [bindDb, setBindDb] = useState<DatabaseEntity | null>(null);
|
||||||
const databasesQuery = useQuery({
|
const databasesQuery = useQuery({
|
||||||
queryKey: ['databases'],
|
queryKey: ['databases'],
|
||||||
queryFn: listDatabases,
|
queryFn: listDatabases,
|
||||||
refetchInterval: 15_000,
|
refetchInterval: 15_000,
|
||||||
|
enabled: !isDemoMode,
|
||||||
});
|
});
|
||||||
|
|
||||||
const invalidate = () => queryClient.invalidateQueries({ queryKey: ['databases'] });
|
const invalidate = () => queryClient.invalidateQueries({ queryKey: ['databases'] });
|
||||||
@@ -1559,7 +1563,7 @@ export function DatabasesPage() {
|
|||||||
onSuccess: () => window.setTimeout(invalidate, 3000),
|
onSuccess: () => window.setTimeout(invalidate, 3000),
|
||||||
});
|
});
|
||||||
|
|
||||||
const databases = databasesQuery.data ?? [];
|
const databases = isDemoMode ? demoDatabases : databasesQuery.data ?? [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import {
|
|||||||
updateServiceVariables,
|
updateServiceVariables,
|
||||||
type CronJobEntity,
|
type CronJobEntity,
|
||||||
} from '@/lib/api-client';
|
} 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 { parseDotenv, validateVariableRows, type VariableDraft } from '../variable-utils';
|
||||||
import { formatBytes, formatDate, formatRelative, seededMetric } from '@/lib/time';
|
import { formatBytes, formatDate, formatRelative, seededMetric } from '@/lib/time';
|
||||||
import { EnhancedMetricCard, LineAreaChart, DonutChart } from '@/shared/components';
|
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)]">
|
<div className="flex items-center justify-center py-12 text-[var(--text-muted)]">
|
||||||
<Loader2 size={20} className="animate-spin" />
|
<Loader2 size={20} className="animate-spin" />
|
||||||
</div>
|
</div>
|
||||||
) : (cronJobsQuery.data ?? []).length === 0 ? (
|
) : (isDemoMode ? getDemoCronJobsByService(serviceId) : cronJobsQuery.data ?? []).length === 0 ? (
|
||||||
<div className="panel-soft p-8 text-center">
|
<div className="panel-soft p-8 text-center">
|
||||||
<p className="text-sm text-[var(--text-secondary)]">No cron jobs configured.</p>
|
<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>
|
<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>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-2">
|
<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 key={job.id} className="panel-soft p-3">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -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';
|
import type { ServiceVariable } from '@/features/workspace/auto-connections';
|
||||||
|
|
||||||
export const demoProjects: ProjectEntity[] = [
|
export const demoProjects: ProjectEntity[] = [
|
||||||
@@ -231,3 +231,101 @@ export function getDemoServiceById(serviceId: string): ServiceEntity | undefined
|
|||||||
export function getDemoVariablesByProject(projectId: string): Record<string, ServiceVariable[]> {
|
export function getDemoVariablesByProject(projectId: string): Record<string, ServiceVariable[]> {
|
||||||
return demoVariablesByProject[projectId] ?? {};
|
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] ?? [];
|
||||||
|
}
|
||||||
Reference in new issue
Block a user