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),
],
);