mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-10-28 15:51:26 +00:00
feat(canvas): brand tech icons on services, matching site preview
Resolve a service's tech (postgres, redis, go, node, nginx, kafka, ~50 slugs) from its image/name and render the Simple Icons logo in the tinted icon tile — canvas nodes, inspector, service detail header, and database rows — with graceful fallback to type icons. Demo services get resolvable images, ports, and domains; a Redis Cache service joins Core Platform so the demo canvas mirrors the landing preview (5 services, web/api/worker → api → postgres/redis). 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
e57f0470eb
commit
1425618c92
7 files changed
+176
-27
No files matched your search
@@ -51,6 +51,8 @@ import {
|
|||||||
type FailoverPolicy,
|
type FailoverPolicy,
|
||||||
} from '@/lib/api-client';
|
} from '@/lib/api-client';
|
||||||
import { demoDatabases } from '@/lib/demo-data';
|
import { demoDatabases } from '@/lib/demo-data';
|
||||||
|
import { ServiceIcon } from '@/features/workspace/canvas/ServiceIcon';
|
||||||
|
import { serviceAccent } from '@/features/workspace/canvas/service-visuals';
|
||||||
import { DocsBrowser } from '@/features/docs/DocsBrowser';
|
import { DocsBrowser } from '@/features/docs/DocsBrowser';
|
||||||
import { useAuthSession } from '@/lib/use-auth-session';
|
import { useAuthSession } from '@/lib/use-auth-session';
|
||||||
import { formatRelative } from '@/lib/time';
|
import { formatRelative } from '@/lib/time';
|
||||||
@@ -1668,8 +1670,14 @@ export function DatabasesPage() {
|
|||||||
onClick={() => setExpandedId(expanded ? null : (db.id ?? null))}
|
onClick={() => setExpandedId(expanded ? null : (db.id ?? null))}
|
||||||
className="flex w-full items-center gap-3 p-4 text-left"
|
className="flex w-full items-center gap-3 p-4 text-left"
|
||||||
>
|
>
|
||||||
<div className="w-9 h-9 rounded-[var(--radius-md)] bg-[var(--accent-primary-soft)] flex items-center justify-center shrink-0">
|
<div
|
||||||
<Database size={17} className="text-[var(--accent-primary)]" />
|
className="w-9 h-9 rounded-[var(--radius-md)] flex items-center justify-center shrink-0"
|
||||||
|
style={{
|
||||||
|
background: `${serviceAccent({ name: db.type })}1c`,
|
||||||
|
color: serviceAccent({ name: db.type }),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ServiceIcon service={{ name: db.type, type: 'database' }} size={17} />
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ import {
|
|||||||
type CronJobEntity,
|
type CronJobEntity,
|
||||||
} from '@/lib/api-client';
|
} from '@/lib/api-client';
|
||||||
import { getDemoProjectById, getDemoServiceById, getDemoCronJobsByService } from '@/lib/demo-data';
|
import { getDemoProjectById, getDemoServiceById, getDemoCronJobsByService } from '@/lib/demo-data';
|
||||||
|
import { ServiceIcon } from '@/features/workspace/canvas/ServiceIcon';
|
||||||
|
import { serviceAccent } from '@/features/workspace/canvas/service-visuals';
|
||||||
import { useAuthSession } from '@/lib/use-auth-session';
|
import { useAuthSession } from '@/lib/use-auth-session';
|
||||||
import { getCurrentUserProfile } from '@/lib/api-client';
|
import { getCurrentUserProfile } from '@/lib/api-client';
|
||||||
import { parseDotenv, validateVariableRows, type VariableDraft } from '../variable-utils';
|
import { parseDotenv, validateVariableRows, type VariableDraft } from '../variable-utils';
|
||||||
@@ -595,13 +597,12 @@ export function ServiceDetailPage() {
|
|||||||
style={{
|
style={{
|
||||||
width: '46px',
|
width: '46px',
|
||||||
height: '46px',
|
height: '46px',
|
||||||
background: 'var(--accent-primary)',
|
background: `${serviceAccent(service)}1f`,
|
||||||
|
color: serviceAccent(service),
|
||||||
marginRight: '14px'
|
marginRight: '14px'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="white">
|
<ServiceIcon service={service} size={22} />
|
||||||
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 14H9V8h2v8zm4 0h-2V8h2v8z"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center" style={{ gap: '10px' }}>
|
<div className="flex items-center" style={{ gap: '10px' }}>
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ import {
|
|||||||
import { hasCanvasMetadata, loadCanvasMetadata, saveCanvasMetadata } from '../storage';
|
import { hasCanvasMetadata, loadCanvasMetadata, saveCanvasMetadata } from '../storage';
|
||||||
import { computeLayeredLayout, layoutFromVariables, NODE_HEIGHT, NODE_WIDTH } from './auto-layout';
|
import { computeLayeredLayout, layoutFromVariables, NODE_HEIGHT, NODE_WIDTH } from './auto-layout';
|
||||||
import { GroupNode, ServiceNode, type GroupNodeData, type ServiceNodeData } from './nodes';
|
import { GroupNode, ServiceNode, type GroupNodeData, type ServiceNodeData } from './nodes';
|
||||||
import { serviceTypeColor, serviceTypeIcon } from './service-visuals';
|
import { ServiceIcon } from './ServiceIcon';
|
||||||
|
import { serviceAccent } from './service-visuals';
|
||||||
import {
|
import {
|
||||||
Plus,
|
Plus,
|
||||||
Layers,
|
Layers,
|
||||||
@@ -656,7 +657,7 @@ function CanvasInner({ projectId, services, variablesByService, onAddService, on
|
|||||||
if (node.type === 'groupNode') {
|
if (node.type === 'groupNode') {
|
||||||
return 'rgba(255,255,255,0.08)';
|
return 'rgba(255,255,255,0.08)';
|
||||||
}
|
}
|
||||||
return serviceTypeColor((node.data as ServiceNodeData).service.type);
|
return serviceAccent((node.data as ServiceNodeData).service);
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
@@ -762,9 +763,9 @@ function CanvasInner({ projectId, services, variablesByService, onAddService, on
|
|||||||
<div className="flex items-center gap-3 px-4 pt-4 pb-3 border-b border-[var(--border-subtle)]">
|
<div className="flex items-center gap-3 px-4 pt-4 pb-3 border-b border-[var(--border-subtle)]">
|
||||||
<div
|
<div
|
||||||
className="w-9 h-9 rounded-[var(--radius-md)] flex items-center justify-center flex-shrink-0"
|
className="w-9 h-9 rounded-[var(--radius-md)] flex items-center justify-center flex-shrink-0"
|
||||||
style={{ background: `${serviceTypeColor(selectedService.type)}1c`, color: serviceTypeColor(selectedService.type) }}
|
style={{ background: `${serviceAccent(selectedService)}1c`, color: serviceAccent(selectedService) }}
|
||||||
>
|
>
|
||||||
{serviceTypeIcon(selectedService.type, 16)}
|
<ServiceIcon service={selectedService} size={16} />
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<h3 className="text-sm font-semibold text-[var(--text-primary)] truncate">{selectedService.name}</h3>
|
<h3 className="text-sm font-semibold text-[var(--text-primary)] truncate">{selectedService.name}</h3>
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { serviceIcon, serviceIconUrl, serviceTypeIcon, type ServiceIconInput } from './service-visuals';
|
||||||
|
|
||||||
|
export function ServiceIcon({
|
||||||
|
service,
|
||||||
|
size = 16,
|
||||||
|
}: {
|
||||||
|
service: ServiceIconInput;
|
||||||
|
size?: number;
|
||||||
|
}) {
|
||||||
|
const [failed, setFailed] = useState(false);
|
||||||
|
const brand = serviceIcon(service);
|
||||||
|
if (brand && !failed) {
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
src={serviceIconUrl(brand)}
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
alt=""
|
||||||
|
loading="lazy"
|
||||||
|
onError={() => setFailed(true)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return serviceTypeIcon(service.type ?? 'service', size);
|
||||||
|
}
|
||||||
@@ -3,7 +3,8 @@ import type { ServiceEntity } from '@/lib/api-client';
|
|||||||
import { serviceStatusClass } from '@/lib/api-client';
|
import { serviceStatusClass } from '@/lib/api-client';
|
||||||
import { Box, ExternalLink, Copy, Layers, Loader2 } from 'lucide-react';
|
import { Box, ExternalLink, Copy, Layers, Loader2 } from 'lucide-react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { serviceTypeColor, serviceTypeIcon } from './service-visuals';
|
import { serviceIcon, serviceAccent } from './service-visuals';
|
||||||
|
import { ServiceIcon } from './ServiceIcon';
|
||||||
|
|
||||||
export type ServiceNodeData = {
|
export type ServiceNodeData = {
|
||||||
service: ServiceEntity;
|
service: ServiceEntity;
|
||||||
@@ -52,7 +53,8 @@ const TRANSIENT_STATUSES = new Set(['building', 'deploying', 'pending', 'rolling
|
|||||||
|
|
||||||
export function ServiceNode({ data }: NodeProps<ServiceNodeType>) {
|
export function ServiceNode({ data }: NodeProps<ServiceNodeType>) {
|
||||||
const { service, selected } = data;
|
const { service, selected } = data;
|
||||||
const typeColor = serviceTypeColor(service.type);
|
const brand = serviceIcon(service);
|
||||||
|
const typeColor = brand?.color ?? serviceAccent(service);
|
||||||
const replicas = service.replicas ?? 0;
|
const replicas = service.replicas ?? 0;
|
||||||
const stateColor = statusColor(service.status);
|
const stateColor = statusColor(service.status);
|
||||||
const isTransient = TRANSIENT_STATUSES.has(service.status);
|
const isTransient = TRANSIENT_STATUSES.has(service.status);
|
||||||
@@ -88,11 +90,15 @@ export function ServiceNode({ data }: NodeProps<ServiceNodeType>) {
|
|||||||
<div
|
<div
|
||||||
className="w-9 h-9 rounded-[var(--radius-md)] flex items-center justify-center flex-shrink-0 transition-all duration-200"
|
className="w-9 h-9 rounded-[var(--radius-md)] flex items-center justify-center flex-shrink-0 transition-all duration-200"
|
||||||
style={{
|
style={{
|
||||||
background: selected ? typeColor : `${typeColor}1c`,
|
background: selected && !brand ? typeColor : `${typeColor}1c`,
|
||||||
color: selected ? 'var(--bg-void)' : typeColor,
|
color: selected && !brand ? 'var(--bg-void)' : typeColor,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isTransient ? <Loader2 size={16} className="animate-spin" /> : serviceTypeIcon(service.type, 16)}
|
{isTransient ? (
|
||||||
|
<Loader2 size={16} className="animate-spin" />
|
||||||
|
) : (
|
||||||
|
<ServiceIcon service={service} size={17} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<h4 className="font-semibold text-[13px] leading-tight text-[var(--text-primary)] truncate tracking-tight">
|
<h4 className="font-semibold text-[13px] leading-tight text-[var(--text-primary)] truncate tracking-tight">
|
||||||
|
|||||||
@@ -29,3 +29,84 @@ export function serviceTypeColor(type: string): string {
|
|||||||
return '#9295a4';
|
return '#9295a4';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ServiceBrandIcon = {
|
||||||
|
slug: string;
|
||||||
|
color: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TECH_ICONS: (ServiceBrandIcon & { patterns: RegExp[] })[] = [
|
||||||
|
{ slug: 'postgresql', color: '#7ab8ff', patterns: [/postgres/i, /postgis/i] },
|
||||||
|
{ slug: 'redis', color: '#ff6b6b', patterns: [/redis/i, /dragonfly/i, /valkey/i, /keydb/i] },
|
||||||
|
{ slug: 'mysql', color: '#5ba4e5', patterns: [/mysql/i] },
|
||||||
|
{ slug: 'mariadb', color: '#4ea5ff', patterns: [/mariadb/i] },
|
||||||
|
{ slug: 'mongodb', color: '#47a248', patterns: [/mongo/i] },
|
||||||
|
{ slug: 'clickhouse', color: '#ffcc01', patterns: [/clickhouse/i] },
|
||||||
|
{ slug: 'couchdb', color: '#e42528', patterns: [/couchdb/i] },
|
||||||
|
{ slug: 'sqlite', color: '#69b7e4', patterns: [/sqlite/i] },
|
||||||
|
{ slug: 'influxdb', color: '#22adf6', patterns: [/influx/i] },
|
||||||
|
{ slug: 'elasticsearch', color: '#fec514', patterns: [/elastic/i, /opensearch/i] },
|
||||||
|
{ slug: 'meilisearch', color: '#ff5caa', patterns: [/meili/i] },
|
||||||
|
{ slug: 'typesense', color: '#d90368', patterns: [/typesense/i] },
|
||||||
|
{ slug: 'qdrant', color: '#dc244c', patterns: [/qdrant/i] },
|
||||||
|
{ slug: 'weaviate', color: '#01c9a7', patterns: [/weaviate/i] },
|
||||||
|
{ slug: 'minio', color: '#c72e49', patterns: [/minio/i] },
|
||||||
|
{ slug: 'rabbitmq', color: '#ff6600', patterns: [/rabbitmq/i, /rabbit/i] },
|
||||||
|
{ slug: 'apachekafka', color: '#d6dae4', patterns: [/kafka/i] },
|
||||||
|
{ slug: 'natsdotio', color: '#8be1d3', patterns: [/\bnats/i] },
|
||||||
|
{ slug: 'nginx', color: '#009639', patterns: [/nginx/i] },
|
||||||
|
{ slug: 'caddy', color: '#1f88c0', patterns: [/caddy/i] },
|
||||||
|
{ slug: 'traefikproxy', color: '#24a1c1', patterns: [/traefik/i] },
|
||||||
|
{ slug: 'nodedotjs', color: '#5fa04e', patterns: [/node(?=[:\-\d]|$)/i, /nodejs/i] },
|
||||||
|
{ slug: 'go', color: '#00add8', patterns: [/golang/i, /(?:^|[^a-z])go(?=[:\-\d]|$)/i] },
|
||||||
|
{ slug: 'python', color: '#6fa8dc', patterns: [/python/i, /\bpy(?:thon)?(?=[:\-\d]|$)/i] },
|
||||||
|
{ slug: 'rust', color: '#f74c00', patterns: [/rust/i] },
|
||||||
|
{ slug: 'php', color: '#8892bf', patterns: [/php/i] },
|
||||||
|
{ slug: 'ruby', color: '#cc342d', patterns: [/ruby/i, /rails/i] },
|
||||||
|
{ slug: 'openjdk', color: '#e6a23c', patterns: [/java/i, /openjdk/i, /jdk/i, /jre/i] },
|
||||||
|
{ slug: 'dotnet', color: '#6f4bd8', patterns: [/dotnet/i, /aspnet/i] },
|
||||||
|
{ slug: 'deno', color: '#70ffaf', patterns: [/\bdeno/i] },
|
||||||
|
{ slug: 'bun', color: '#f9f1e1', patterns: [/bun(?=[:\-\d]|$)/i] },
|
||||||
|
{ slug: 'react', color: '#61dafb', patterns: [/react/i] },
|
||||||
|
{ slug: 'vuedotjs', color: '#4fc08d', patterns: [/vue/i] },
|
||||||
|
{ slug: 'angular', color: '#dd0031', patterns: [/angular/i] },
|
||||||
|
{ slug: 'svelte', color: '#ff3e00', patterns: [/svelte/i] },
|
||||||
|
{ slug: 'nextdotjs', color: '#e6edf3', patterns: [/nextjs/i, /next\.js/i] },
|
||||||
|
{ slug: 'nuxtdotjs', color: '#00dc82', patterns: [/nuxt/i] },
|
||||||
|
{ slug: 'astro', color: '#ff5d01', patterns: [/astro/i] },
|
||||||
|
{ slug: 'prometheus', color: '#e6522c', patterns: [/prometheus/i] },
|
||||||
|
{ slug: 'grafana', color: '#f46800', patterns: [/grafana/i] },
|
||||||
|
{ slug: 'uptimekuma', color: '#5cdd8b', patterns: [/uptime.?kuma/i, /\bkuma/i] },
|
||||||
|
{ slug: 'n8n', color: '#ea4b71', patterns: [/n8n/i] },
|
||||||
|
{ slug: 'wordpress', color: '#4b9cd3', patterns: [/wordpress/i, /\bwp-/i] },
|
||||||
|
{ slug: 'ghost', color: '#a8b8c4', patterns: [/ghost/i] },
|
||||||
|
{ slug: 'nextcloud', color: '#0082c9', patterns: [/nextcloud/i] },
|
||||||
|
{ slug: 'immich', color: '#7c9ef5', patterns: [/immich/i] },
|
||||||
|
{ slug: 'supabase', color: '#3fcf8e', patterns: [/supabase/i] },
|
||||||
|
{ slug: 'pocketbase', color: '#b8dbe4', patterns: [/pocketbase/i] },
|
||||||
|
{ slug: 'keycloak', color: '#d6dae4', patterns: [/keycloak/i] },
|
||||||
|
{ slug: 'authentik', color: '#fd4b2d', patterns: [/authentik/i] },
|
||||||
|
{ slug: 'portainer', color: '#13bef9', patterns: [/portainer/i] },
|
||||||
|
{ slug: 'docker', color: '#2496ed', patterns: [/docker/i, /dind/i] },
|
||||||
|
{ slug: 'kubernetes', color: '#326ce5', patterns: [/kubernetes/i, /\bk8s/i] },
|
||||||
|
];
|
||||||
|
|
||||||
|
export type ServiceIconInput = { image?: string; name?: string; type?: string };
|
||||||
|
|
||||||
|
export function serviceIcon(service: ServiceIconInput): ServiceBrandIcon | null {
|
||||||
|
const text = `${service.image ?? ''} ${service.name ?? ''}`.toLowerCase();
|
||||||
|
for (const icon of TECH_ICONS) {
|
||||||
|
if (icon.patterns.some((pattern) => pattern.test(text))) {
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function serviceIconUrl(icon: ServiceBrandIcon): string {
|
||||||
|
return `https://cdn.simpleicons.org/${icon.slug}/${icon.color.replace('#', '')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function serviceAccent(service: ServiceIconInput): string {
|
||||||
|
return serviceIcon(service)?.color ?? serviceTypeColor(service.type ?? 'service');
|
||||||
|
}
|
||||||
@@ -10,9 +10,9 @@ export const demoProjects: ProjectEntity[] = [
|
|||||||
createdAt: '2026-03-12T09:30:00Z',
|
createdAt: '2026-03-12T09:30:00Z',
|
||||||
updatedAt: '2026-03-31T09:10:00Z',
|
updatedAt: '2026-03-31T09:10:00Z',
|
||||||
stats: {
|
stats: {
|
||||||
service_count: 4,
|
service_count: 5,
|
||||||
deployment_count: 27,
|
deployment_count: 27,
|
||||||
running_services: 3,
|
running_services: 4,
|
||||||
last_deployment: '2026-03-31T08:42:00Z',
|
last_deployment: '2026-03-31T08:42:00Z',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -55,9 +55,11 @@ export const demoServicesByProject: Record<string, ServiceEntity[]> = {
|
|||||||
type: 'web',
|
type: 'web',
|
||||||
status: 'running',
|
status: 'running',
|
||||||
environment: 'production',
|
environment: 'production',
|
||||||
image: 'ghcr.io/containr/web:2026.03.31',
|
image: 'ghcr.io/containr/web-react:v42',
|
||||||
command: 'npm run serve',
|
command: 'npm run serve',
|
||||||
gitBranch: 'main',
|
gitBranch: 'main',
|
||||||
|
port: 3000,
|
||||||
|
domain: 'web.containr.local',
|
||||||
createdAt: '2026-03-12T10:00:00Z',
|
createdAt: '2026-03-12T10:00:00Z',
|
||||||
updatedAt: '2026-03-31T08:42:00Z',
|
updatedAt: '2026-03-31T08:42:00Z',
|
||||||
},
|
},
|
||||||
@@ -68,9 +70,11 @@ export const demoServicesByProject: Record<string, ServiceEntity[]> = {
|
|||||||
type: 'web',
|
type: 'web',
|
||||||
status: 'running',
|
status: 'running',
|
||||||
environment: 'production',
|
environment: 'production',
|
||||||
image: 'ghcr.io/containr/api:2026.03.31',
|
image: 'ghcr.io/containr/api-go:v2.4.1',
|
||||||
command: './server',
|
command: './server',
|
||||||
gitBranch: 'main',
|
gitBranch: 'main',
|
||||||
|
port: 8080,
|
||||||
|
domain: 'api.containr.local',
|
||||||
createdAt: '2026-03-12T10:02:00Z',
|
createdAt: '2026-03-12T10:02:00Z',
|
||||||
updatedAt: '2026-03-31T08:43:00Z',
|
updatedAt: '2026-03-31T08:43:00Z',
|
||||||
},
|
},
|
||||||
@@ -81,9 +85,10 @@ export const demoServicesByProject: Record<string, ServiceEntity[]> = {
|
|||||||
type: 'worker',
|
type: 'worker',
|
||||||
status: 'building',
|
status: 'building',
|
||||||
environment: 'production',
|
environment: 'production',
|
||||||
image: 'ghcr.io/containr/worker:2026.03.31',
|
image: 'ghcr.io/containr/worker:edge',
|
||||||
command: './worker',
|
command: './worker',
|
||||||
gitBranch: 'main',
|
gitBranch: 'main',
|
||||||
|
port: 9100,
|
||||||
createdAt: '2026-03-12T10:05:00Z',
|
createdAt: '2026-03-12T10:05:00Z',
|
||||||
updatedAt: '2026-03-31T08:44:00Z',
|
updatedAt: '2026-03-31T08:44:00Z',
|
||||||
},
|
},
|
||||||
@@ -94,12 +99,27 @@ export const demoServicesByProject: Record<string, ServiceEntity[]> = {
|
|||||||
type: 'database',
|
type: 'database',
|
||||||
status: 'running',
|
status: 'running',
|
||||||
environment: 'production',
|
environment: 'production',
|
||||||
image: 'postgres:15-alpine',
|
image: 'postgres:16',
|
||||||
command: 'postgres',
|
command: 'postgres',
|
||||||
gitBranch: 'main',
|
gitBranch: 'main',
|
||||||
|
port: 5432,
|
||||||
createdAt: '2026-03-12T10:08:00Z',
|
createdAt: '2026-03-12T10:08:00Z',
|
||||||
updatedAt: '2026-03-31T08:40:00Z',
|
updatedAt: '2026-03-31T08:40:00Z',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'demo-svc-redis',
|
||||||
|
projectId: 'demo-project-core',
|
||||||
|
name: 'Redis Cache',
|
||||||
|
type: 'database',
|
||||||
|
status: 'running',
|
||||||
|
environment: 'production',
|
||||||
|
image: 'dragonfly:1.25',
|
||||||
|
command: 'redis-server',
|
||||||
|
gitBranch: 'main',
|
||||||
|
port: 6379,
|
||||||
|
createdAt: '2026-03-12T10:12:00Z',
|
||||||
|
updatedAt: '2026-03-31T08:40:00Z',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
'demo-project-growth': [
|
'demo-project-growth': [
|
||||||
{
|
{
|
||||||
@@ -109,9 +129,11 @@ export const demoServicesByProject: Record<string, ServiceEntity[]> = {
|
|||||||
type: 'web',
|
type: 'web',
|
||||||
status: 'running',
|
status: 'running',
|
||||||
environment: 'production',
|
environment: 'production',
|
||||||
image: 'ghcr.io/containr/marketing:latest',
|
image: 'nginx:1.27-alpine',
|
||||||
command: 'npm run start',
|
command: 'nginx -g daemon off;',
|
||||||
gitBranch: 'main',
|
gitBranch: 'main',
|
||||||
|
port: 8080,
|
||||||
|
domain: 'growth.containr.local',
|
||||||
createdAt: '2026-03-03T09:00:00Z',
|
createdAt: '2026-03-03T09:00:00Z',
|
||||||
updatedAt: '2026-03-30T12:10:00Z',
|
updatedAt: '2026-03-30T12:10:00Z',
|
||||||
},
|
},
|
||||||
@@ -122,9 +144,10 @@ export const demoServicesByProject: Record<string, ServiceEntity[]> = {
|
|||||||
type: 'web',
|
type: 'web',
|
||||||
status: 'running',
|
status: 'running',
|
||||||
environment: 'production',
|
environment: 'production',
|
||||||
image: 'ghcr.io/containr/campaign-api:latest',
|
image: 'node:20-alpine',
|
||||||
command: './api',
|
command: './api',
|
||||||
gitBranch: 'main',
|
gitBranch: 'main',
|
||||||
|
port: 3000,
|
||||||
createdAt: '2026-03-03T09:04:00Z',
|
createdAt: '2026-03-03T09:04:00Z',
|
||||||
updatedAt: '2026-03-30T12:08:00Z',
|
updatedAt: '2026-03-30T12:08:00Z',
|
||||||
},
|
},
|
||||||
@@ -135,9 +158,10 @@ export const demoServicesByProject: Record<string, ServiceEntity[]> = {
|
|||||||
type: 'database',
|
type: 'database',
|
||||||
status: 'running',
|
status: 'running',
|
||||||
environment: 'production',
|
environment: 'production',
|
||||||
image: 'redis:7-alpine',
|
image: 'dragonfly:1.25',
|
||||||
command: 'redis-server',
|
command: 'redis-server',
|
||||||
gitBranch: 'main',
|
gitBranch: 'main',
|
||||||
|
port: 6379,
|
||||||
createdAt: '2026-03-03T09:07:00Z',
|
createdAt: '2026-03-03T09:07:00Z',
|
||||||
updatedAt: '2026-03-30T12:07:00Z',
|
updatedAt: '2026-03-30T12:07:00Z',
|
||||||
},
|
},
|
||||||
@@ -150,9 +174,10 @@ export const demoServicesByProject: Record<string, ServiceEntity[]> = {
|
|||||||
type: 'web',
|
type: 'web',
|
||||||
status: 'running',
|
status: 'running',
|
||||||
environment: 'production',
|
environment: 'production',
|
||||||
image: 'ghcr.io/containr/inference-api:latest',
|
image: 'python:3.12-slim',
|
||||||
command: './serve',
|
command: './serve',
|
||||||
gitBranch: 'main',
|
gitBranch: 'main',
|
||||||
|
port: 8000,
|
||||||
createdAt: '2026-02-18T08:00:00Z',
|
createdAt: '2026-02-18T08:00:00Z',
|
||||||
updatedAt: '2026-03-29T18:00:00Z',
|
updatedAt: '2026-03-29T18:00:00Z',
|
||||||
},
|
},
|
||||||
@@ -163,7 +188,7 @@ export const demoServicesByProject: Record<string, ServiceEntity[]> = {
|
|||||||
type: 'worker',
|
type: 'worker',
|
||||||
status: 'failed',
|
status: 'failed',
|
||||||
environment: 'production',
|
environment: 'production',
|
||||||
image: 'ghcr.io/containr/evaluator:latest',
|
image: 'python:3.12-slim',
|
||||||
command: './run-jobs',
|
command: './run-jobs',
|
||||||
gitBranch: 'main',
|
gitBranch: 'main',
|
||||||
createdAt: '2026-02-18T08:03:00Z',
|
createdAt: '2026-02-18T08:03:00Z',
|
||||||
@@ -195,6 +220,7 @@ export const demoVariablesByProject: Record<string, Record<string, ServiceVariab
|
|||||||
] satisfies ServiceVariable[]
|
] satisfies ServiceVariable[]
|
||||||
),
|
),
|
||||||
'demo-svc-postgres': [],
|
'demo-svc-postgres': [],
|
||||||
|
'demo-svc-redis': [],
|
||||||
},
|
},
|
||||||
'demo-project-growth': {
|
'demo-project-growth': {
|
||||||
'demo-growth-site': (
|
'demo-growth-site': (
|
||||||
|
|||||||
Reference in new issue
Block a user