diff --git a/web/components/builds/build-details.tsx b/web/components/builds/build-details.tsx index 6b7aa5b..3430971 100644 --- a/web/components/builds/build-details.tsx +++ b/web/components/builds/build-details.tsx @@ -27,6 +27,7 @@ import { ItemDescription, ItemTitle, } from "@/components/ui/item"; +import { StatusBadge } from "@/components/ui/status-badge"; import type { Build, BuildStatus, GithubRepo, Service } from "@/db/types"; import { formatElapsedDurationBetween, formatRelativeTime } from "@/lib/date"; import { fetcher } from "@/lib/fetcher"; @@ -46,56 +47,47 @@ const STATUS_CONFIG: Record< { icon: typeof CheckCircle2; color: string; - bgColor: string; label: string; } > = { pending: { icon: Clock, color: "text-slate-500", - bgColor: "bg-slate-500/10", label: "Queued", }, claimed: { icon: CircleDashed, color: "text-blue-500", - bgColor: "bg-blue-500/10", label: "Starting", }, cloning: { icon: Loader2, color: "text-blue-500", - bgColor: "bg-blue-500/10", label: "Cloning", }, building: { icon: Loader2, color: "text-blue-500", - bgColor: "bg-blue-500/10", label: "Building", }, pushing: { icon: Loader2, color: "text-blue-500", - bgColor: "bg-blue-500/10", label: "Pushing", }, completed: { icon: CheckCircle2, color: "text-green-500", - bgColor: "bg-green-500/10", label: "Completed", }, failed: { icon: XCircle, color: "text-red-500", - bgColor: "bg-red-500/10", label: "Failed", }, cancelled: { icon: AlertCircle, color: "text-slate-500", - bgColor: "bg-slate-500/10", label: "Cancelled", }, }; @@ -128,7 +120,6 @@ export function BuildDetails({ const build = data?.build || initialBuild; const config = STATUS_CONFIG[build.status]; - const Icon = config.icon; const isAnimated = ["cloning", "building", "pushing"].includes(build.status); const handleCancel = async () => { @@ -188,12 +179,12 @@ export function BuildDetails({
- - - {config.label} - + {build.commitSha.slice(0, 7)} diff --git a/web/components/builds/builds-viewer.tsx b/web/components/builds/builds-viewer.tsx index c98fa1a..0ce7433 100644 --- a/web/components/builds/builds-viewer.tsx +++ b/web/components/builds/builds-viewer.tsx @@ -34,19 +34,14 @@ import { ItemTitle, } from "@/components/ui/item"; import { Spinner } from "@/components/ui/spinner"; +import { StatusBadge } from "@/components/ui/status-badge"; import type { Build, BuildStatus } from "@/db/types"; import { formatElapsedDurationBetween, formatRelativeTime } from "@/lib/date"; import { fetcher } from "@/lib/fetcher"; type BuildListItem = Pick< Build, - | "id" - | "commitSha" - | "commitMessage" - | "branch" - | "author" - | "status" - | "error" + "id" | "commitSha" | "commitMessage" | "branch" | "author" | "status" > & { createdAt: string; startedAt: string | null; @@ -103,18 +98,17 @@ const STATUS_CONFIG: Record< }, }; -function StatusBadge({ status }: { status: BuildStatus }) { +function BuildStatusBadge({ status }: { status: BuildStatus }) { const config = STATUS_CONFIG[status]; - const Icon = config.icon; const isAnimated = ["cloning", "building", "pushing"].includes(status); return ( - - - {config.label} - + ); } @@ -243,7 +237,7 @@ export function BuildsViewer({ ) } > - + @@ -275,11 +269,6 @@ export function BuildsViewer({ )} - {build.error && ( -
-										{build.error}
-									
- )}
e.stopPropagation()}> {canCancel(build.status) && ( diff --git a/web/components/service/details/changelog-history.tsx b/web/components/service/details/changelog-history.tsx index 364db40..c2bcddc 100644 --- a/web/components/service/details/changelog-history.tsx +++ b/web/components/service/details/changelog-history.tsx @@ -1,7 +1,6 @@ "use client"; import { - ArrowRight, CheckCircle2, Clock, GitCommitHorizontal, @@ -12,7 +11,6 @@ import { import Link from "next/link"; import { useMemo } from "react"; import useSWRInfinite from "swr/infinite"; -import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Empty, @@ -21,6 +19,7 @@ import { EmptyTitle, } from "@/components/ui/empty"; import { Skeleton } from "@/components/ui/skeleton"; +import { StatusBadge } from "@/components/ui/status-badge"; import type { RolloutStatus } from "@/db/types"; import { formatDateTime, formatRelativeTime } from "@/lib/date"; import { fetcher } from "@/lib/fetcher"; @@ -68,23 +67,19 @@ function RolloutBadge({ icon: Clock, className: "text-muted-foreground", }; - const Icon = config.icon; return ( - } - > - - {config.label} - + /> ); } @@ -127,18 +122,25 @@ function RevisionChanges({ item }: { item: ServiceRevisionChangelogItem }) { } return ( -
+
{item.comparison.changes.map((change) => (
-
{change.field}
-
- {change.from} - - {change.to} -
+ {change.field} + + + {change.from} + + โ†’ + + {change.to} + +
))}
@@ -219,29 +221,30 @@ export function ChangelogHistory({ ) : ( -
- {revisions.map((revision) => ( -
-
-
-
+
+ {revisions.map((revision, index) => ( +
+ {index < revisions.length - 1 ? ( + + ) : null} +
+ +
+ {revision.comparison.kind === "initial" ? "Initial revision" : revision.comparison.kind === "changes" && revision.comparison.changes.length === 0 ? "Redeployed" : "Configuration updated"} -
-
+ {formatRelativeTime(revision.createdAt)} ยท{" "} {revision.id.slice(0, 8)} -
+
{revision.rollout ? ( ) : null}
- +
+ +
))}
diff --git a/web/components/service/details/rollout-details.tsx b/web/components/service/details/rollout-details.tsx index d6adce6..473ecd1 100644 --- a/web/components/service/details/rollout-details.tsx +++ b/web/components/service/details/rollout-details.tsx @@ -11,6 +11,7 @@ import { import { useRouter } from "next/navigation"; import { LogViewer } from "@/components/logs/log-viewer"; import { Button } from "@/components/ui/button"; +import { StatusBadge } from "@/components/ui/status-badge"; import type { Rollout, RolloutStatus, Service } from "@/db/types"; import { formatElapsedDurationBetween, formatRelativeTime } from "@/lib/date"; @@ -24,38 +25,32 @@ const STATUS_CONFIG: Record< { icon: typeof CheckCircle2; color: string; - bgColor: string; label: string; } > = { queued: { icon: Clock, color: "text-slate-500", - bgColor: "bg-slate-500/10", label: "Queued", }, in_progress: { icon: Loader2, color: "text-blue-500", - bgColor: "bg-blue-500/10", label: "In Progress", }, completed: { icon: CheckCircle2, color: "text-green-500", - bgColor: "bg-green-500/10", label: "Completed", }, failed: { icon: XCircle, color: "text-red-500", - bgColor: "bg-red-500/10", label: "Failed", }, rolled_back: { icon: RotateCcw, color: "text-orange-500", - bgColor: "bg-orange-500/10", label: "Rolled Back", }, }; @@ -88,7 +83,6 @@ export function RolloutDetails({ }) { const router = useRouter(); const config = STATUS_CONFIG[rollout.status as RolloutStatus]; - const Icon = config.icon; const isLive = rollout.status === "queued" || rollout.status === "in_progress"; const isAnimated = rollout.status === "in_progress"; @@ -109,12 +103,12 @@ export function RolloutDetails({
- - - {config.label} - + {rollout.currentStage && isLive && ( {formatStage(rollout.currentStage)} diff --git a/web/components/service/details/rollout-history.tsx b/web/components/service/details/rollout-history.tsx index 74dd12c..5665b9b 100644 --- a/web/components/service/details/rollout-history.tsx +++ b/web/components/service/details/rollout-history.tsx @@ -18,6 +18,7 @@ import { ItemTitle, } from "@/components/ui/item"; import { Skeleton } from "@/components/ui/skeleton"; +import { StatusBadge } from "@/components/ui/status-badge"; import type { RolloutStatus } from "@/db/types"; import { formatElapsedDurationBetween, formatRelativeTime } from "@/lib/date"; import { fetcher } from "@/lib/fetcher"; @@ -66,18 +67,24 @@ const STATUS_CONFIG: Record< }, }; -function StatusBadge({ status }: { status: RolloutStatus }) { +const STATUS_TITLES: Record, string> = { + queued: "Waiting to begin deployment", + completed: "Deployment completed successfully", + failed: "Deployment failed", + rolled_back: "Deployment rolled back", +}; + +function RolloutStatusBadge({ status }: { status: RolloutStatus }) { const config = STATUS_CONFIG[status]; - const Icon = config.icon; const isAnimated = status === "in_progress"; return ( - - - {config.label} - + ); } @@ -182,15 +189,13 @@ export function RolloutHistory({ href={`/dashboard/projects/${projectSlug}/${envName}/services/${serviceId}/rollouts/${rollout.id}`} > - + - {rollout.status === "queued" - ? "Queued" - : rollout.status === "in_progress" - ? `Deploying โ€” ${formatStage(rollout.currentStage)}` - : STATUS_CONFIG[rollout.status].label} + {rollout.status === "in_progress" + ? `Deploying โ€” ${formatStage(rollout.currentStage)}` + : STATUS_TITLES[rollout.status]} diff --git a/web/components/service/details/service-details-overview.tsx b/web/components/service/details/service-details-overview.tsx index a5d584b..5bc2672 100644 --- a/web/components/service/details/service-details-overview.tsx +++ b/web/components/service/details/service-details-overview.tsx @@ -1,13 +1,6 @@ "use client"; -import { - Activity, - Box, - Github, - Globe, - type LucideIcon, - Server, -} from "lucide-react"; +import { Box, Github, type LucideIcon } from "lucide-react"; import { type ReactNode, useMemo, useState } from "react"; import { CartesianGrid, @@ -20,7 +13,6 @@ import { } from "recharts"; import useSWR from "swr"; import { useService } from "@/components/service/service-layout-client"; -import { Badge } from "@/components/ui/badge"; import { Card } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; import type { ServiceWithDetails as Service } from "@/db/types"; @@ -442,148 +434,129 @@ function ServiceConfigPanel({ overview: OverviewData; }) { const primaryEndpoint = getPrimaryEndpoint(overview.endpoints); + const statusClasses = STATUS_TONE_CLASSES[overview.status.tone]; + const hasResourceLimits = + service.resourceCpuLimit != null || service.resourceMemoryLimitMb != null; return ( -
-
- - -

- {formatInstanceSummary(overview)} -

-
- - - {formatResources(service)} - - - } - > - - {overview.source.branch || overview.source.detail} - +
+
+
+ + + {overview.status.label} + +
+ + {formatInstanceSummary(overview)} + +
+ +
+
+ + + + {overview.source.branch ? ( + {overview.source.branch} + ) : null} {service.githubRootDir ? ( - {service.githubRootDir} + + {service.githubRootDir} + ) : null} - - {service.startCommand ? "Custom Command" : "Image Command"} - - - {service.healthCheckCmd ? "Health Check" : "No Health Check"} - - - - } - > - {formatPortSummary(service.ports || [])} + + {service.startCommand ? "Custom" : "Image default"} + + + {service.healthCheckCmd ? "Configured" : "None"} + + + {hasResourceLimits ? formatResources(service) : "Not set"} + +
+ +
+ {overview.serverSummaries.length === 0 ? ( +

No servers configured

+ ) : ( + overview.serverSummaries.map((server) => ( + + + {server.configured > 0 + ? `${server.running}/${server.configured} running` + : `${server.running} running`} + + + )) + )} +
+ +
+ + + + + {formatPortSummary(service.ports || [])} + {primaryEndpoint.kind !== "private" ? ( - {`${service.hostname || service.name}.internal`} + + {`${service.hostname || service.name}.internal`} + ) : null} - +
); } -function SummaryItem({ - icon: Icon, +function ConfigRow({ label, children, - className, + muted = false, }: { - icon: LucideIcon; label: string; children: ReactNode; - className?: string; + muted?: boolean; }) { return ( -
-
- - {label} -
-
+
+ {label} + {children} -
-
- ); -} - -function ConfigDigestItem({ - icon: Icon, - label, - primary, - children, - className, -}: { - icon: LucideIcon; - label: string; - primary: ReactNode; - children: ReactNode; - className?: string; -}) { - return ( -
-
- - {label} -
-
- {primary} -
-
{children}
+
); } -function ConfigChip({ - children, - tone = "muted", -}: { - children: ReactNode; - tone?: "active" | "muted"; -}) { - return ( - - {children} - +function SourcePrimary({ source }: { source: SourceInfo }) { + const Icon = source.icon; + const content = ( + <> + + {source.label} + ); -} -function SourcePrimary({ source }: { source: SourceInfo }) { if (source.href) { return ( - {source.label} + {content} ); } - return {source.label}; + return content; } function EndpointPrimary({ endpoint }: { endpoint: EndpointItem }) { @@ -593,48 +566,14 @@ function EndpointPrimary({ endpoint }: { endpoint: EndpointItem }) { href={endpoint.href} target="_blank" rel="noopener noreferrer" - className="block min-w-0 truncate hover:text-primary" + className="hover:text-primary" > {endpoint.label} ); } - return {endpoint.label}; -} - -function StatusValue({ status }: { status: ServiceStatus }) { - const classes = STATUS_TONE_CLASSES[status.tone]; - - return ( -
- - - {status.label} - -
- ); -} - -function ServerList({ servers }: { servers: ServerSummary[] }) { - if (servers.length === 0) { - return

No servers configured

; - } - - return ( -
- {servers.map((server) => ( - - {server.name} - - {server.configured > 0 - ? `${server.running}/${server.configured}` - : `${server.running} Running`} - - - ))} -
- ); + return <>{endpoint.label}; } function LegendMetric({ @@ -949,15 +888,13 @@ function formatInstanceSummary(overview: OverviewData): string { (total, server) => total + server.configured, 0, ); - const serverCount = overview.serverSummaries.length; - if (configured === 0) { return overview.runningDeployments > 0 - ? `${overview.runningDeployments} Running` - : "No Replicas"; + ? `${overview.runningDeployments} running` + : "No replicas"; } - return `${overview.runningDeployments}/${configured} Running Across ${formatCount(serverCount, "server")}`; + return `${overview.runningDeployments}/${configured} running`; } function getPrimaryEndpoint(endpoints: EndpointItem[]): EndpointItem { diff --git a/web/components/ui/status-badge.tsx b/web/components/ui/status-badge.tsx new file mode 100644 index 0000000..1f9c3dd --- /dev/null +++ b/web/components/ui/status-badge.tsx @@ -0,0 +1,34 @@ +import type * as React from "react"; +import { Badge } from "@/components/ui/badge"; +import { cn } from "@/lib/utils"; + +type StatusBadgeProps = Omit, "children"> & { + icon: React.ComponentType<{ className?: string }>; + label: string; + isAnimated?: boolean; +}; + +function StatusBadge({ + icon: Icon, + label, + isAnimated = false, + className, + variant = "outline", + ...props +}: StatusBadgeProps) { + return ( + svg]:size-3.5!", + className, + )} + {...props} + > + + {label} + + ); +} + +export { StatusBadge }; diff --git a/web/db/schema.ts b/web/db/schema.ts index 7f3ed25..ac0ddf9 100644 --- a/web/db/schema.ts +++ b/web/db/schema.ts @@ -383,119 +383,144 @@ export const environments = pgTable( ], ); -export const services = pgTable("services", { - id: text("id").primaryKey(), - projectId: text("project_id") - .notNull() - .references(() => projects.id, { onDelete: "cascade" }), - environmentId: text("environment_id") - .notNull() - .references(() => environments.id, { onDelete: "cascade" }), - name: text("name").notNull(), - hostname: text("hostname").unique(), - image: text("image").notNull(), - sourceType: text("source_type", { enum: ["image", "github"] }) - .notNull() - .default("image"), - githubRepoUrl: text("github_repo_url"), - githubBranch: text("github_branch").default("main"), - githubRootDir: text("github_root_dir"), - replicas: integer("replicas").notNull().default(1), - stateful: boolean("stateful").notNull().default(false), - lockedServerId: text("locked_server_id").references(() => servers.id, { - onDelete: "set null", - }), - healthCheckCmd: text("health_check_cmd"), - healthCheckInterval: integer("health_check_interval").default(10), - healthCheckTimeout: integer("health_check_timeout").default(5), - healthCheckRetries: integer("health_check_retries").default(3), - healthCheckStartPeriod: integer("health_check_start_period").default(30), - startCommand: text("start_command"), - resourceCpuLimit: real("resource_cpu_limit"), - resourceMemoryLimitMb: integer("resource_memory_limit_mb"), - serverlessEnabled: boolean("serverless_enabled").notNull().default(false), - serverlessSleepAfterSeconds: integer("serverless_sleep_after_seconds") - .notNull() - .default(300), - serverlessWakeTimeoutSeconds: integer("serverless_wake_timeout_seconds") - .notNull() - .default(300), - deploymentSchedule: text("deployment_schedule"), - lastScheduledDeploymentRunAt: timestamp("last_scheduled_deployment_run_at", { - withTimezone: true, - }), - backupEnabled: boolean("backup_enabled").default(false), - backupSchedule: text("backup_schedule"), - deletedAt: timestamp("deleted_at", { withTimezone: true }), - purgeAfter: timestamp("purge_after", { withTimezone: true }), - originalHostname: text("original_hostname"), - deletionStatus: text("deletion_status", { - enum: ["backing_up", "deleting", "restoring", "failed"], - }), - deletionError: text("deletion_error"), - canvasX: integer("canvas_x"), - canvasY: integer("canvas_y"), - migrationStatus: text("migration_status", { - enum: [ - "stopping", - "backing_up", - "deploying_target", - "restoring", - "starting", - "failed", - ], - }), - migrationTargetServerId: text("migration_target_server_id").references( - () => servers.id, - { onDelete: "set null" }, - ), - migrationBackupId: text("migration_backup_id"), - migrationError: text("migration_error"), - createdAt: timestamp("created_at", { withTimezone: true }) - .defaultNow() - .notNull(), -}); +export const services = pgTable( + "services", + { + id: text("id").primaryKey(), + projectId: text("project_id") + .notNull() + .references(() => projects.id, { onDelete: "cascade" }), + environmentId: text("environment_id") + .notNull() + .references(() => environments.id, { onDelete: "cascade" }), + name: text("name").notNull(), + hostname: text("hostname").unique(), + image: text("image").notNull(), + sourceType: text("source_type", { enum: ["image", "github"] }) + .notNull() + .default("image"), + githubRepoUrl: text("github_repo_url"), + githubBranch: text("github_branch").default("main"), + githubRootDir: text("github_root_dir"), + replicas: integer("replicas").notNull().default(1), + stateful: boolean("stateful").notNull().default(false), + lockedServerId: text("locked_server_id").references(() => servers.id, { + onDelete: "set null", + }), + healthCheckCmd: text("health_check_cmd"), + healthCheckInterval: integer("health_check_interval").default(10), + healthCheckTimeout: integer("health_check_timeout").default(5), + healthCheckRetries: integer("health_check_retries").default(3), + healthCheckStartPeriod: integer("health_check_start_period").default(30), + startCommand: text("start_command"), + resourceCpuLimit: real("resource_cpu_limit"), + resourceMemoryLimitMb: integer("resource_memory_limit_mb"), + serverlessEnabled: boolean("serverless_enabled").notNull().default(false), + serverlessSleepAfterSeconds: integer("serverless_sleep_after_seconds") + .notNull() + .default(300), + serverlessWakeTimeoutSeconds: integer("serverless_wake_timeout_seconds") + .notNull() + .default(300), + deploymentSchedule: text("deployment_schedule"), + lastScheduledDeploymentRunAt: timestamp( + "last_scheduled_deployment_run_at", + { + withTimezone: true, + }, + ), + backupEnabled: boolean("backup_enabled").default(false), + backupSchedule: text("backup_schedule"), + deletedAt: timestamp("deleted_at", { withTimezone: true }), + purgeAfter: timestamp("purge_after", { withTimezone: true }), + originalHostname: text("original_hostname"), + deletionStatus: text("deletion_status", { + enum: ["backing_up", "deleting", "restoring", "failed"], + }), + deletionError: text("deletion_error"), + canvasX: integer("canvas_x"), + canvasY: integer("canvas_y"), + migrationStatus: text("migration_status", { + enum: [ + "stopping", + "backing_up", + "deploying_target", + "restoring", + "starting", + "failed", + ], + }), + migrationTargetServerId: text("migration_target_server_id").references( + () => servers.id, + { onDelete: "set null" }, + ), + migrationBackupId: text("migration_backup_id"), + migrationError: text("migration_error"), + createdAt: timestamp("created_at", { withTimezone: true }) + .defaultNow() + .notNull(), + }, + (table) => [ + index("services_project_environment_idx").on( + table.projectId, + table.environmentId, + ), + index("services_environment_id_idx").on(table.environmentId), + ], +); -export const serviceReplicas = pgTable("service_replicas", { - id: text("id").primaryKey(), - serviceId: text("service_id") - .notNull() - .references(() => services.id, { onDelete: "cascade" }), - serverId: text("server_id") - .notNull() - .references(() => servers.id, { onDelete: "cascade" }), - count: integer("count").notNull().default(1), -}); +export const serviceReplicas = pgTable( + "service_replicas", + { + id: text("id").primaryKey(), + serviceId: text("service_id") + .notNull() + .references(() => services.id, { onDelete: "cascade" }), + serverId: text("server_id") + .notNull() + .references(() => servers.id, { onDelete: "cascade" }), + count: integer("count").notNull().default(1), + }, + (table) => [index("service_replicas_service_id_idx").on(table.serviceId)], +); -export const servicePorts = pgTable("service_ports", { - id: text("id").primaryKey(), - serviceId: text("service_id") - .notNull() - .references(() => services.id, { onDelete: "cascade" }), - port: integer("port").notNull(), - isPublic: boolean("is_public").notNull().default(false), - domain: text("domain").unique(), - protocol: text("protocol", { enum: ["http", "tcp", "udp"] }) - .notNull() - .default("http"), - externalPort: integer("external_port"), - tlsPassthrough: boolean("tls_passthrough").notNull().default(false), - createdAt: timestamp("created_at", { withTimezone: true }) - .defaultNow() - .notNull(), -}); +export const servicePorts = pgTable( + "service_ports", + { + id: text("id").primaryKey(), + serviceId: text("service_id") + .notNull() + .references(() => services.id, { onDelete: "cascade" }), + port: integer("port").notNull(), + isPublic: boolean("is_public").notNull().default(false), + domain: text("domain").unique(), + protocol: text("protocol", { enum: ["http", "tcp", "udp"] }) + .notNull() + .default("http"), + externalPort: integer("external_port"), + tlsPassthrough: boolean("tls_passthrough").notNull().default(false), + createdAt: timestamp("created_at", { withTimezone: true }) + .defaultNow() + .notNull(), + }, + (table) => [index("service_ports_service_id_idx").on(table.serviceId)], +); -export const serviceVolumes = pgTable("service_volumes", { - id: text("id").primaryKey(), - serviceId: text("service_id") - .notNull() - .references(() => services.id, { onDelete: "cascade" }), - name: text("name").notNull(), - containerPath: text("container_path").notNull(), - createdAt: timestamp("created_at", { withTimezone: true }) - .defaultNow() - .notNull(), -}); +export const serviceVolumes = pgTable( + "service_volumes", + { + id: text("id").primaryKey(), + serviceId: text("service_id") + .notNull() + .references(() => services.id, { onDelete: "cascade" }), + name: text("name").notNull(), + containerPath: text("container_path").notNull(), + createdAt: timestamp("created_at", { withTimezone: true }) + .defaultNow() + .notNull(), + }, + (table) => [index("service_volumes_service_id_idx").on(table.serviceId)], +); export const volumeBackups = pgTable( "volume_backups", @@ -528,26 +553,38 @@ export const volumeBackups = pgTable( completedAt: timestamp("completed_at", { withTimezone: true }), }, (table) => [ - index("volume_backups_volume_id_idx").on(table.volumeId), - index("volume_backups_service_id_idx").on(table.serviceId), + index("volume_backups_volume_status_created_at_idx").on( + table.volumeId, + table.status, + table.createdAt, + ), + index("volume_backups_service_created_at_idx").on( + table.serviceId, + table.createdAt, + ), + index("volume_backups_server_id_idx").on(table.serverId), index("volume_backups_created_at_idx").on(table.createdAt), ], ); -export const secrets = pgTable("secrets", { - id: text("id").primaryKey(), - serviceId: text("service_id") - .notNull() - .references(() => services.id, { onDelete: "cascade" }), - key: text("key").notNull(), - encryptedValue: text("encrypted_value").notNull(), - createdAt: timestamp("created_at", { withTimezone: true }) - .defaultNow() - .notNull(), - updatedAt: timestamp("updated_at", { withTimezone: true }) - .defaultNow() - .notNull(), -}); +export const secrets = pgTable( + "secrets", + { + id: text("id").primaryKey(), + serviceId: text("service_id") + .notNull() + .references(() => services.id, { onDelete: "cascade" }), + key: text("key").notNull(), + encryptedValue: text("encrypted_value").notNull(), + createdAt: timestamp("created_at", { withTimezone: true }) + .defaultNow() + .notNull(), + updatedAt: timestamp("updated_at", { withTimezone: true }) + .defaultNow() + .notNull(), + }, + (table) => [index("secrets_service_id_idx").on(table.serviceId)], +); export const serviceRevisions = pgTable( "service_revisions", @@ -568,7 +605,6 @@ export const serviceRevisions = pgTable( table.id, table.serviceId, ), - index("service_revisions_service_id_idx").on(table.serviceId), index("service_revisions_service_created_id_idx").on( table.serviceId, table.createdAt, @@ -644,11 +680,6 @@ export const deployments = pgTable( index("deployments_service_id_idx").on(table.serviceId), index("deployments_service_revision_id_idx").on(table.serviceRevisionId), index("deployments_server_id_idx").on(table.serverId), - index("deployments_runtime_desired_state_idx").on( - table.runtimeDesiredState, - ), - index("deployments_traffic_state_idx").on(table.trafficState), - index("deployments_observed_phase_idx").on(table.observedPhase), foreignKey({ name: "deployments_service_revision_service_fk", columns: [table.serviceRevisionId, table.serviceId], @@ -677,7 +708,10 @@ export const rollouts = pgTable( completedAt: timestamp("completed_at", { withTimezone: true }), }, (table) => [ - index("rollouts_service_id_idx").on(table.serviceId), + index("rollouts_service_created_at_idx").on( + table.serviceId, + table.createdAt, + ), index("rollouts_service_revision_id_idx").on(table.serviceRevisionId), foreignKey({ name: "rollouts_service_revision_service_fk", @@ -687,17 +721,23 @@ export const rollouts = pgTable( ], ); -export const deploymentPorts = pgTable("deployment_ports", { - id: text("id").primaryKey(), - deploymentId: text("deployment_id") - .notNull() - .references(() => deployments.id, { onDelete: "cascade" }), - containerPort: integer("container_port").notNull(), - hostPort: integer("host_port").notNull(), - createdAt: timestamp("created_at", { withTimezone: true }) - .defaultNow() - .notNull(), -}); +export const deploymentPorts = pgTable( + "deployment_ports", + { + id: text("id").primaryKey(), + deploymentId: text("deployment_id") + .notNull() + .references(() => deployments.id, { onDelete: "cascade" }), + containerPort: integer("container_port").notNull(), + hostPort: integer("host_port").notNull(), + createdAt: timestamp("created_at", { withTimezone: true }) + .defaultNow() + .notNull(), + }, + (table) => [ + index("deployment_ports_deployment_id_idx").on(table.deploymentId), + ], +); export const workQueue = pgTable( "work_queue", @@ -734,7 +774,11 @@ export const workQueue = pgTable( attempts: integer("attempts").notNull().default(0), }, (table) => [ - index("work_queue_server_status_idx").on(table.serverId, table.status), + index("work_queue_server_status_created_at_idx").on( + table.serverId, + table.status, + table.createdAt, + ), uniqueIndex("work_queue_one_active_agent_upgrade_idx") .on(table.serverId) .where( @@ -743,20 +787,24 @@ export const workQueue = pgTable( ], ); -export const githubInstallations = pgTable("github_installations", { - id: text("id").primaryKey(), - installationId: integer("installation_id").notNull().unique(), - accountLogin: text("account_login").notNull(), - accountType: text("account_type", { - enum: ["User", "Organization"], - }).notNull(), - userId: text("user_id") - .notNull() - .references(() => user.id, { onDelete: "cascade" }), - createdAt: timestamp("created_at", { withTimezone: true }) - .defaultNow() - .notNull(), -}); +export const githubInstallations = pgTable( + "github_installations", + { + id: text("id").primaryKey(), + installationId: integer("installation_id").notNull().unique(), + accountLogin: text("account_login").notNull(), + accountType: text("account_type", { + enum: ["User", "Organization"], + }).notNull(), + userId: text("user_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + createdAt: timestamp("created_at", { withTimezone: true }) + .defaultNow() + .notNull(), + }, + (table) => [index("github_installations_user_id_idx").on(table.userId)], +); export const githubRepos = pgTable( "github_repos", @@ -781,7 +829,6 @@ export const githubRepos = pgTable( }, (table) => [ index("github_repos_installation_id_idx").on(table.installationId), - index("github_repos_service_id_idx").on(table.serviceId), ], ); @@ -829,9 +876,10 @@ export const builds = pgTable( .notNull(), }, (table) => [ - index("builds_status_idx").on(table.status), - index("builds_service_id_idx").on(table.serviceId), + index("builds_service_created_at_idx").on(table.serviceId, table.createdAt), index("builds_github_repo_id_idx").on(table.githubRepoId), + index("builds_build_group_id_idx").on(table.buildGroupId), + index("builds_claimed_by_idx").on(table.claimedBy), ], );