Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 50 additions & 36 deletions web/actions/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1194,32 +1194,38 @@ export async function restartService(serviceId: string) {

export async function abortRollout(serviceId: string) {
await requireAuth();
const updatedRollouts = await db
.update(rollouts)
.set({
status: "failed",
currentStage: "aborted",
completedAt: new Date(),
})
const activeRollouts = await db
.select({ id: rollouts.id, status: rollouts.status })
.from(rollouts)
.where(
and(
eq(rollouts.serviceId, serviceId),
eq(rollouts.status, "in_progress"),
inArray(rollouts.status, ["queued", "in_progress"]),
),
)
.returning();

const inProgressRollout = updatedRollouts[0];
);

if (!inProgressRollout) {
if (activeRollouts.length === 0) {
return { success: false, error: "No in-progress rollout found" };
}

await inngest.send(
inngestEvents.rolloutCancelled.create({
rolloutId: inProgressRollout.id,
}),
);
const activeRolloutIds = activeRollouts.map((rollout) => rollout.id);

await db
.update(rollouts)
.set({
status: "failed",
currentStage: "aborted",
completedAt: new Date(),
})
.where(inArray(rollouts.id, activeRolloutIds));

for (const rolloutId of activeRolloutIds) {
await inngest.send(
inngestEvents.rolloutCancelled.create({
rolloutId,
}),
);
}

await db
.update(deployments)
Expand All @@ -1231,10 +1237,13 @@ export async function abortRollout(serviceId: string) {
),
);

const rolloutDeployments = await db
.select()
.from(deployments)
.where(eq(deployments.rolloutId, inProgressRollout.id));
const rolloutDeployments =
activeRolloutIds.length > 0
? await db
.select()
.from(deployments)
.where(inArray(deployments.rolloutId, activeRolloutIds))
: [];

const serverContainers = new Map<string, string[]>();

Expand All @@ -1259,20 +1268,25 @@ export async function abortRollout(serviceId: string) {
.where(eq(deploymentPorts.deploymentId, dep.id));
}

await db
.delete(deployments)
.where(eq(deployments.rolloutId, inProgressRollout.id));

const pendingWork = await db
.select({ id: workQueue.id, payload: workQueue.payload })
.from(workQueue)
.where(
and(
eq(workQueue.status, "pending"),
inArray(workQueue.type, ["deploy", "reconcile"]),
inArray(workQueue.serverId, [...serverContainers.keys()]),
),
);
if (activeRolloutIds.length > 0) {
await db
.delete(deployments)
.where(inArray(deployments.rolloutId, activeRolloutIds));
}

const pendingWork =
serverContainers.size > 0
? await db
.select({ id: workQueue.id, payload: workQueue.payload })
.from(workQueue)
.where(
and(
eq(workQueue.status, "pending"),
inArray(workQueue.type, ["deploy", "reconcile"]),
inArray(workQueue.serverId, [...serverContainers.keys()]),
),
)
: [];

const rolloutDeploymentIds = new Set(rolloutDeployments.map((d) => d.id));
const workToDelete = pendingWork.filter((w) => {
Expand Down
11 changes: 9 additions & 2 deletions web/components/service/details/deployment-progress.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type StageInfo = {

const STAGES: StageInfo[] = [
{ id: "migrating", label: "Migrating" },
{ id: "queued", label: "Queued" },
{ id: "deploying", label: "Starting" },
{ id: "health_check", label: "Checking Health" },
{ id: "dns_sync", label: "Routing traffic" },
Expand Down Expand Up @@ -103,10 +104,16 @@ export function getBarState(

const latestRollout = service.rollouts?.[0];
const activeRollout =
latestRollout?.status === "in_progress" ? latestRollout : undefined;
latestRollout?.status === "queued" ||
latestRollout?.status === "in_progress"
? latestRollout
: undefined;

if (activeRollout) {
const currentStage = activeRollout.currentStage || "deploying";
const currentStage =
activeRollout.status === "queued"
? "queued"
: activeRollout.currentStage || "deploying";
return {
mode: "deploying",
stage: currentStage,
Expand Down
22 changes: 13 additions & 9 deletions web/components/service/details/rollout-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,16 @@
import {
ArrowLeft,
CheckCircle2,
Clock,
Loader2,
RotateCcw,
XCircle,
} from "lucide-react";
import { useRouter } from "next/navigation";
import {
Item,
ItemContent,
ItemDescription,
ItemTitle,
} from "@/components/ui/item";
import { LogViewer } from "@/components/logs/log-viewer";
import { Button } from "@/components/ui/button";
import type { Rollout, RolloutStatus, Service } from "@/db/types";
import { formatRelativeTime } from "@/lib/date";
import { LogViewer } from "@/components/logs/log-viewer";

type RolloutWithDates = Omit<Rollout, "createdAt" | "completedAt"> & {
createdAt: string | Date;
Expand All @@ -33,6 +28,12 @@ const STATUS_CONFIG: Record<
label: string;
}
> = {
queued: {
icon: Clock,
color: "text-slate-500",
bgColor: "bg-slate-500/10",
label: "Queued",
},
in_progress: {
icon: Loader2,
color: "text-blue-500",
Expand Down Expand Up @@ -60,6 +61,7 @@ const STATUS_CONFIG: Record<
};

const STAGE_LABELS: Record<string, string> = {
queued: "Queued",
preparing: "Preparing",
certificates: "Issuing Certificates",
deploying: "Deploying",
Expand Down Expand Up @@ -102,7 +104,9 @@ export function RolloutDetails({
const router = useRouter();
const config = STATUS_CONFIG[rollout.status as RolloutStatus];
const Icon = config.icon;
const isLive = rollout.status === "in_progress";
const isLive =
rollout.status === "queued" || rollout.status === "in_progress";
const isAnimated = rollout.status === "in_progress";

return (
<div className="space-y-6">
Expand All @@ -123,7 +127,7 @@ export function RolloutDetails({
<span
className={`inline-flex items-center gap-1.5 px-3 py-1.5 rounded-md text-sm font-medium ${config.color} ${config.bgColor}`}
>
<Icon className={`size-4 ${isLive ? "animate-spin" : ""}`} />
<Icon className={`size-4 ${isAnimated ? "animate-spin" : ""}`} />
{config.label}
</span>
{rollout.currentStage && isLive && (
Expand Down
20 changes: 15 additions & 5 deletions web/components/service/details/rollout-history.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"use client";

import { useState } from "react";
import { CheckCircle2, Clock, Loader2, RotateCcw, XCircle } from "lucide-react";
import Link from "next/link";
import { useState } from "react";
import useSWR from "swr";
import {
Empty,
Expand Down Expand Up @@ -39,6 +39,11 @@ const STATUS_CONFIG: Record<
label: string;
}
> = {
queued: {
icon: Clock,
color: "text-slate-500",
label: "Queued",
},
in_progress: {
icon: Loader2,
color: "text-blue-500",
Expand Down Expand Up @@ -77,6 +82,7 @@ function StatusBadge({ status }: { status: RolloutStatus }) {
}

const STAGE_LABELS: Record<string, string> = {
queued: "Queued",
preparing: "Preparing",
certificates: "Issuing Certificates",
deploying: "Deploying",
Expand Down Expand Up @@ -123,7 +129,9 @@ export function RolloutHistory({
revalidateOnFocus: true,
onSuccess: (data) => {
setHasInProgress(
data?.rollouts?.some((r) => r.status === "in_progress") ?? false,
data?.rollouts?.some(
(r) => r.status === "queued" || r.status === "in_progress",
) ?? false,
);
},
},
Expand Down Expand Up @@ -168,9 +176,11 @@ export function RolloutHistory({
<ItemContent>
<ItemTitle>
<span className="truncate">
{rollout.status === "in_progress"
? `Deploying — ${formatStage(rollout.currentStage)}`
: STATUS_CONFIG[rollout.status].label}
{rollout.status === "queued"
? "Queued"
: rollout.status === "in_progress"
? `Deploying — ${formatStage(rollout.currentStage)}`
: STATUS_CONFIG[rollout.status].label}
</span>
</ItemTitle>
<ItemDescription>
Expand Down
56 changes: 30 additions & 26 deletions web/components/service/service-canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ const CANVAS_WIDTH = 1320;
const CANVAS_HEIGHT = 900;
const MIN_CANVAS_SCALE = 0.5;
const SNAP_GRID_SIZE = 24;
const CANVAS_DOT_PATTERN =
"radial-gradient(circle, color-mix(in oklab, var(--muted-foreground) 36%, transparent) 1px, transparent 1px)";

function getCanvasScale() {
if (typeof window === "undefined") {
Expand Down Expand Up @@ -706,7 +708,7 @@ export function ServiceCanvas({
"
style={{
height: "calc(100vh - 3.5rem)",
backgroundImage: `radial-gradient(circle, rgb(161 161 170 / 0.2) 1px, transparent 1px)`,
backgroundImage: CANVAS_DOT_PATTERN,
backgroundSize: "24px 24px",
}}
>
Expand Down Expand Up @@ -764,7 +766,7 @@ export function ServiceCanvas({
"
style={{
height: "calc(100vh - 5rem)",
backgroundImage: `radial-gradient(circle, rgb(161 161 170 / 0.3) 1px, transparent 1px)`,
backgroundImage: CANVAS_DOT_PATTERN,
backgroundSize: "20px 20px",
}}
>
Expand Down Expand Up @@ -842,7 +844,7 @@ export function ServiceCanvas({
"
style={{
height: "calc(100vh - 3.5rem)",
backgroundImage: `radial-gradient(circle, rgb(161 161 170 / 0.2) 1px, transparent 1px)`,
backgroundImage: CANVAS_DOT_PATTERN,
backgroundSize: "24px 24px",
}}
>
Expand All @@ -855,34 +857,36 @@ export function ServiceCanvas({
<div className="absolute top-4 right-4">
<AddServiceMenu {...menuCallbacks} />
</div>
<div
className="relative mx-auto"
style={{
width: CANVAS_WIDTH * canvasScale,
height: CANVAS_HEIGHT * canvasScale,
}}
>
<div className="flex min-h-full items-center justify-center px-10 py-24">
<div
className="relative"
style={{
width: CANVAS_WIDTH,
height: CANVAS_HEIGHT,
transform: `scale(${canvasScale})`,
transformOrigin: "top left",
width: CANVAS_WIDTH * canvasScale,
height: CANVAS_HEIGHT * canvasScale,
}}
>
{services.map((service, index) => (
<DraggableServiceCard
key={service.id}
service={service}
index={index}
projectSlug={projectSlug}
envName={envName}
proxyDomain={proxyDomain}
canvasScale={canvasScale}
onPositionChange={handlePositionChange}
/>
))}
<div
className="relative"
style={{
width: CANVAS_WIDTH,
height: CANVAS_HEIGHT,
transform: `scale(${canvasScale})`,
transformOrigin: "top left",
}}
>
{services.map((service, index) => (
<DraggableServiceCard
key={service.id}
service={service}
index={index}
projectSlug={projectSlug}
envName={envName}
proxyDomain={proxyDomain}
canvasScale={canvasScale}
onPositionChange={handlePositionChange}
/>
))}
</div>
</div>
</div>
</ContextMenuTrigger>
Expand Down
3 changes: 2 additions & 1 deletion web/components/service/service-layout-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {
} from "react";
import useSWR from "swr";
import type { ServiceWithDetails as Service } from "@/db/types";
import type { ConfigChange } from "@/lib/service-config";
import { fetcher } from "@/lib/fetcher";
import type { ConfigChange } from "@/lib/service-config";
import {
buildCurrentConfig,
diffConfigs,
Expand Down Expand Up @@ -68,6 +68,7 @@ export function ServiceLayoutClient({
const isActive =
(svc.latestBuild != null &&
ACTIVE_BUILD_STATUSES.includes(svc.latestBuild.status)) ||
svc.rollouts?.[0]?.status === "queued" ||
svc.rollouts?.[0]?.status === "in_progress" ||
!!svc.migrationStatus ||
svc.deployments.some((d) =>
Expand Down
4 changes: 2 additions & 2 deletions web/db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -492,10 +492,10 @@ export const rollouts = pgTable(
.notNull()
.references(() => services.id, { onDelete: "cascade" }),
status: text("status", {
enum: ["in_progress", "completed", "failed", "rolled_back"],
enum: ["queued", "in_progress", "completed", "failed", "rolled_back"],
})
.notNull()
.default("in_progress"),
.default("queued"),
currentStage: text("current_stage"),
createdAt: timestamp("created_at", { withTimezone: true })
.defaultNow()
Expand Down
Loading
Loading