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
32 changes: 31 additions & 1 deletion web/actions/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1016,7 +1016,37 @@ export async function abortRollout(serviceId: string) {
.delete(deployments)
.where(eq(deployments.rolloutId, inProgressRollout.id));

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

const rolloutDeploymentIds = new Set(rolloutDeployments.map((d) => d.id));
const workToDelete = pendingWork.filter((w) => {
try {
const parsed = JSON.parse(w.payload);
return rolloutDeploymentIds.has(parsed.deploymentId);
} catch {
return false;
}
});

if (workToDelete.length > 0) {
await db
.delete(workQueue)
.where(
inArray(
workQueue.id,
workToDelete.map((w) => w.id),
),
);
}

return { success: true };
}
Expand Down
72 changes: 33 additions & 39 deletions web/components/service/details/deployment-canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -305,27 +305,19 @@ export function DeploymentCanvas({ service }: DeploymentCanvasProps) {

if (service.deployments.length === 0) {
return (
<>
<div className="flex flex-col items-center justify-center pt-8 pb-12 md:hidden">
<div className="w-16 h-16 mx-auto rounded-2xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center">
<Box className="h-8 w-8 text-slate-400" />
</div>
<p className="text-muted-foreground mt-4">No deployments yet.</p>
</div>
<CanvasWrapper
height="auto"
isEmpty
className="hidden md:flex min-h-[300px]"
emptyContent={
<div className="text-center space-y-4">
<div className="w-16 h-16 mx-auto rounded-2xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center">
<Box className="h-8 w-8 text-slate-400" />
</div>
<p className="text-muted-foreground">No deployments yet.</p>
<CanvasWrapper
height="auto"
isEmpty
className="min-h-[200px] md:min-h-[300px]"
emptyContent={
<div className="text-center space-y-4">
<div className="w-16 h-16 mx-auto rounded-2xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center">
<Box className="h-8 w-8 text-slate-400" />
</div>
}
/>
</>
<p className="text-muted-foreground">No deployments yet.</p>
</div>
}
/>
);
}

Expand All @@ -335,26 +327,28 @@ export function DeploymentCanvas({ service }: DeploymentCanvasProps) {

return (
<>
<div className="flex flex-col gap-4 pb-4 md:hidden">
{hasEndpoints && (
<EndpointsCard
publicPorts={publicPorts}
tcpUdpPorts={tcpUdpPorts}
proxyDomain={proxyDomain}
internalHostname={`${service.hostname || service.name}.internal`}
hasRunningDeployments={hasRunningDeployments}
/>
)}
{serverGroups.map((group) => (
<div key={group.serverName}>
<ServerBox
serverName={group.serverName}
deployments={group.deployments}
<CanvasWrapper height="auto" className="md:hidden">
<div className="flex flex-col gap-4">
{hasEndpoints && (
<EndpointsCard
publicPorts={publicPorts}
tcpUdpPorts={tcpUdpPorts}
proxyDomain={proxyDomain}
internalHostname={`${service.hostname || service.name}.internal`}
hasRunningDeployments={hasRunningDeployments}
/>
{hasVolumes && <VolumeCard volumes={service.volumes!} />}
</div>
))}
</div>
)}
{serverGroups.map((group) => (
<div key={group.serverName}>
<ServerBox
serverName={group.serverName}
deployments={group.deployments}
/>
{hasVolumes && <VolumeCard volumes={service.volumes!} />}
</div>
))}
</div>
</CanvasWrapper>

<CanvasWrapper
height="auto"
Expand Down
4 changes: 3 additions & 1 deletion web/components/service/details/deployment-progress.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,9 @@ export const DeploymentProgress = memo(function DeploymentProgress({
opacity: isVisible ? 1 : 0,
}}
>
<div className="overflow-hidden pb-4">{content}</div>
<div className="overflow-hidden">
{content && <div className="pb-4">{content}</div>}
</div>
</div>
);
});
4 changes: 3 additions & 1 deletion web/components/service/details/pending-changes-banner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ export const PendingChangesBanner = memo(function PendingChangesBanner({
opacity: showBanner ? 1 : 0,
}}
>
<div className="overflow-hidden pb-4">
<div className="overflow-hidden">
<div className="pb-4">
<div className="rounded-lg border bg-card p-4">
<div className="flex items-start justify-between gap-4">
<div className="flex items-start gap-3 min-w-0">
Expand Down Expand Up @@ -122,6 +123,7 @@ export const PendingChangesBanner = memo(function PendingChangesBanner({
</div>
</div>
</div>
</div>
</div>
);
});
5 changes: 5 additions & 0 deletions web/lib/agent-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,10 @@ export async function applyStatusReport(
}

if (deployment.status === "pending" || deployment.status === "pulling") {
if (container.status !== "running") {
continue;
}

const service = await db
.select()
.from(services)
Expand Down Expand Up @@ -306,6 +310,7 @@ export async function applyStatusReport(

if (
deployment.status === "starting" &&
container.status === "running" &&
(healthStatus === "healthy" || healthStatus === "none")
) {
console.log(
Expand Down
83 changes: 59 additions & 24 deletions web/lib/email/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ export async function sendBuildFailureAlert(

type DeploymentFailureAlertOptions = {
serviceId: string;
serverId: string;
serverId: string | null;
failedStage?: string;
};

Expand All @@ -272,44 +272,79 @@ export async function sendDeploymentFailureAlert(
return;
}

const [result] = await db
.select({
serviceName: services.name,
projectName: projects.name,
projectSlug: projects.slug,
envName: environments.name,
serverName: servers.name,
})
.from(services)
.innerJoin(projects, eq(projects.id, services.projectId))
.innerJoin(environments, eq(environments.id, services.environmentId))
.innerJoin(servers, eq(servers.id, options.serverId))
.where(eq(services.id, options.serviceId));

if (!result) {
return;
let serviceName: string;
let projectName: string;
let projectSlug: string;
let envName: string;
let serverName: string;

if (options.serverId) {
const [result] = await db
.select({
serviceName: services.name,
projectName: projects.name,
projectSlug: projects.slug,
envName: environments.name,
serverName: servers.name,
})
.from(services)
.innerJoin(projects, eq(projects.id, services.projectId))
.innerJoin(environments, eq(environments.id, services.environmentId))
.innerJoin(servers, eq(servers.id, options.serverId))
.where(eq(services.id, options.serviceId));

if (!result) {
return;
}

serviceName = result.serviceName;
projectName = result.projectName;
projectSlug = result.projectSlug;
envName = result.envName;
serverName = result.serverName;
} else {
const [result] = await db
.select({
serviceName: services.name,
projectName: projects.name,
projectSlug: projects.slug,
envName: environments.name,
})
.from(services)
.innerJoin(projects, eq(projects.id, services.projectId))
.innerJoin(environments, eq(environments.id, services.environmentId))
.where(eq(services.id, options.serviceId));

if (!result) {
return;
}

serviceName = result.serviceName;
projectName = result.projectName;
projectSlug = result.projectSlug;
envName = result.envName;
serverName = "Unknown";
}

const baseUrl = getAppBaseUrl();
const serviceUrl = baseUrl
? `${baseUrl}/dashboard/projects/${result.projectSlug}/${result.envName}/services/${options.serviceId}`
? `${baseUrl}/dashboard/projects/${projectSlug}/${envName}/services/${options.serviceId}`
: undefined;

const details = [
{ label: "Service", value: result.serviceName },
{ label: "Project", value: result.projectName },
{ label: "Server", value: result.serverName },
{ label: "Service", value: serviceName },
{ label: "Project", value: projectName },
{ label: "Server", value: serverName },
...(options.failedStage
? [{ label: "Failed Stage", value: options.failedStage }]
: []),
];

await sendAlert({
subject: `Deployment Failed: ${result.serviceName}`,
subject: `Deployment Failed: ${serviceName}`,
template: Alert({
bannerText: "DEPLOYMENT FAILED",
heading: "Deployment Failure Alert",
description: `The deployment for service "${result.serviceName}" in project "${result.projectName}" has failed on server "${result.serverName}".`,
description: `The deployment for service "${serviceName}" in project "${projectName}" has failed on server "${serverName}".`,
details,
buttonText: serviceUrl ? "View Service" : undefined,
buttonUrl: serviceUrl,
Expand Down
7 changes: 0 additions & 7 deletions web/lib/inngest/functions/rollout-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,13 +211,6 @@ export async function prepareRollingUpdate(
(d) => d.status === "running" || d.status === "healthy",
);

for (const dep of runningDeployments) {
await db
.update(deployments)
.set({ status: "draining" })
.where(eq(deployments.id, dep.id));
}

return { deploymentIds: runningDeployments.map((d) => d.id) };
}

Expand Down
25 changes: 19 additions & 6 deletions web/lib/inngest/functions/rollout-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,24 @@ export async function handleRolloutFailure(
.from(deployments)
.where(eq(deployments.rolloutId, rolloutId));

if (rolloutDeployments.length === 0) return;
await db
.update(rollouts)
.set({ status: "rolled_back", completedAt: new Date() })
.where(eq(rollouts.id, rolloutId));

if (rolloutDeployments.length === 0) {
sendDeploymentFailureAlert({
serviceId,
serverId: null,
failedStage: reason,
}).catch((error) => {
console.error(
"[rollout:failure] failed to send deployment failure alert:",
error,
);
});
return;
}

const serverId = rolloutDeployments[0].serverId;

Expand All @@ -41,16 +58,12 @@ export async function handleRolloutFailure(
"pulling",
"starting",
"healthy",
"running",
"failed",
]),
),
);

await db
.update(rollouts)
.set({ status: "rolled_back", completedAt: new Date() })
.where(eq(rollouts.id, rolloutId));

sendDeploymentFailureAlert({
serviceId,
serverId,
Expand Down
Loading