[autofix.ci] apply automated fixes

This commit is contained in:
autofix-ci[bot]
2026-03-09 08:28:35 +00:00
committed by GitHub
parent 4330d7bd99
commit d8c7c1eaf4

View File

@@ -1,12 +1,13 @@
import { import {
ChevronDown, ChevronDown,
ChevronUp, ChevronUp,
Clock, Copy, Clock,
Loader2, Copy,
RefreshCcw, Loader2,
RocketIcon, RefreshCcw,
Settings, RocketIcon,
Trash2, Settings,
Trash2,
} from "lucide-react"; } from "lucide-react";
import React, { useEffect, useMemo, useState } from "react"; import React, { useEffect, useMemo, useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
@@ -17,11 +18,11 @@ import { StatusTooltip } from "@/components/shared/status-tooltip";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
Card, Card,
CardContent, CardContent,
CardDescription, CardDescription,
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "@/components/ui/card"; } from "@/components/ui/card";
import { api, type RouterOutputs } from "@/utils/api"; import { api, type RouterOutputs } from "@/utils/api";
import { ShowRollbackSettings } from "../rollbacks/show-rollback-settings"; import { ShowRollbackSettings } from "../rollbacks/show-rollback-settings";
@@ -33,453 +34,449 @@ import { ShowDeployment } from "./show-deployment";
import copy from "copy-to-clipboard"; import copy from "copy-to-clipboard";
interface Props { interface Props {
id: string; id: string;
type: type:
| "application" | "application"
| "compose" | "compose"
| "schedule" | "schedule"
| "server" | "server"
| "backup" | "backup"
| "previewDeployment" | "previewDeployment"
| "volumeBackup"; | "volumeBackup";
refreshToken?: string; refreshToken?: string;
serverId?: string; serverId?: string;
} }
export const formatDuration = (seconds: number) => { export const formatDuration = (seconds: number) => {
if (seconds < 60) return `${seconds}s`; if (seconds < 60) return `${seconds}s`;
const minutes = Math.floor(seconds / 60); const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60; const remainingSeconds = seconds % 60;
return `${minutes}m ${remainingSeconds}s`; return `${minutes}m ${remainingSeconds}s`;
}; };
export const ShowDeployments = ({ export const ShowDeployments = ({
id, id,
type, type,
refreshToken, refreshToken,
serverId, serverId,
}: Props) => { }: Props) => {
const [activeLog, setActiveLog] = useState< const [activeLog, setActiveLog] = useState<
RouterOutputs["deployment"]["all"][number] | null RouterOutputs["deployment"]["all"][number] | null
>(null); >(null);
const { data: deployments, isPending: isLoadingDeployments } = const { data: deployments, isPending: isLoadingDeployments } =
api.deployment.allByType.useQuery( api.deployment.allByType.useQuery(
{ {
id, id,
type, type,
}, },
{ {
enabled: !!id, enabled: !!id,
refetchInterval: 1000, refetchInterval: 1000,
}, },
); );
const { data: isCloud } = api.settings.isCloud.useQuery(); const { data: isCloud } = api.settings.isCloud.useQuery();
const { mutateAsync: rollback, isPending: isRollingBack } = const { mutateAsync: rollback, isPending: isRollingBack } =
api.rollback.rollback.useMutation(); api.rollback.rollback.useMutation();
const { mutateAsync: killProcess, isPending: isKillingProcess } = const { mutateAsync: killProcess, isPending: isKillingProcess } =
api.deployment.killProcess.useMutation(); api.deployment.killProcess.useMutation();
const { mutateAsync: removeDeployment, isPending: isRemovingDeployment } = const { mutateAsync: removeDeployment, isPending: isRemovingDeployment } =
api.deployment.removeDeployment.useMutation(); api.deployment.removeDeployment.useMutation();
// Cancel deployment mutations // Cancel deployment mutations
const { const {
mutateAsync: cancelApplicationDeployment, mutateAsync: cancelApplicationDeployment,
isPending: isCancellingApp, isPending: isCancellingApp,
} = api.application.cancelDeployment.useMutation(); } = api.application.cancelDeployment.useMutation();
const { const {
mutateAsync: cancelComposeDeployment, mutateAsync: cancelComposeDeployment,
isPending: isCancellingCompose, isPending: isCancellingCompose,
} = api.compose.cancelDeployment.useMutation(); } = api.compose.cancelDeployment.useMutation();
const [url, setUrl] = React.useState(""); const [url, setUrl] = React.useState("");
const [expandedDescriptions, setExpandedDescriptions] = useState<Set<string>>( const [expandedDescriptions, setExpandedDescriptions] = useState<Set<string>>(
new Set(), new Set(),
); );
const MAX_DESCRIPTION_LENGTH = 200; const MAX_DESCRIPTION_LENGTH = 200;
const truncateDescription = (description: string): string => { const truncateDescription = (description: string): string => {
if (description.length <= MAX_DESCRIPTION_LENGTH) { if (description.length <= MAX_DESCRIPTION_LENGTH) {
return description; return description;
} }
const truncated = description.slice(0, MAX_DESCRIPTION_LENGTH); const truncated = description.slice(0, MAX_DESCRIPTION_LENGTH);
const lastSpace = truncated.lastIndexOf(" "); const lastSpace = truncated.lastIndexOf(" ");
if (lastSpace > MAX_DESCRIPTION_LENGTH - 20 && lastSpace > 0) { if (lastSpace > MAX_DESCRIPTION_LENGTH - 20 && lastSpace > 0) {
return `${truncated.slice(0, lastSpace)}...`; return `${truncated.slice(0, lastSpace)}...`;
} }
return `${truncated}...`; return `${truncated}...`;
}; };
// Check for stuck deployment (more than 9 minutes) - only for the most recent deployment // Check for stuck deployment (more than 9 minutes) - only for the most recent deployment
const stuckDeployment = useMemo(() => { const stuckDeployment = useMemo(() => {
if (!isCloud || !deployments || deployments.length === 0) return null; if (!isCloud || !deployments || deployments.length === 0) return null;
const now = Date.now(); const now = Date.now();
const NINE_MINUTES = 10 * 60 * 1000; // 9 minutes in milliseconds const NINE_MINUTES = 10 * 60 * 1000; // 9 minutes in milliseconds
// Get the most recent deployment (first in the list since they're sorted by date) // Get the most recent deployment (first in the list since they're sorted by date)
const mostRecentDeployment = deployments[0]; const mostRecentDeployment = deployments[0];
if ( if (
!mostRecentDeployment || !mostRecentDeployment ||
mostRecentDeployment.status !== "running" || mostRecentDeployment.status !== "running" ||
!mostRecentDeployment.startedAt !mostRecentDeployment.startedAt
) { ) {
return null; return null;
} }
const startTime = new Date(mostRecentDeployment.startedAt).getTime(); const startTime = new Date(mostRecentDeployment.startedAt).getTime();
const elapsed = now - startTime; const elapsed = now - startTime;
return elapsed > NINE_MINUTES ? mostRecentDeployment : null; return elapsed > NINE_MINUTES ? mostRecentDeployment : null;
}, [isCloud, deployments]); }, [isCloud, deployments]);
useEffect(() => { useEffect(() => {
setUrl(document.location.origin); setUrl(document.location.origin);
}, []); }, []);
return ( return (
<Card className="bg-background border-none"> <Card className="bg-background border-none">
<CardHeader className="flex flex-row items-center justify-between flex-wrap gap-2"> <CardHeader className="flex flex-row items-center justify-between flex-wrap gap-2">
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<CardTitle className="text-xl">Deployments</CardTitle> <CardTitle className="text-xl">Deployments</CardTitle>
<CardDescription> <CardDescription>
See the last 10 deployments for this {type} See the last 10 deployments for this {type}
</CardDescription> </CardDescription>
</div> </div>
<div className="flex flex-row items-center flex-wrap gap-2"> <div className="flex flex-row items-center flex-wrap gap-2">
{(type === "application" || type === "compose") && ( {(type === "application" || type === "compose") && (
<ClearDeployments id={id} type={type}/> <ClearDeployments id={id} type={type} />
)} )}
{(type === "application" || type === "compose") && ( {(type === "application" || type === "compose") && (
<KillBuild id={id} type={type}/> <KillBuild id={id} type={type} />
)} )}
{(type === "application" || type === "compose") && ( {(type === "application" || type === "compose") && (
<CancelQueues id={id} type={type}/> <CancelQueues id={id} type={type} />
)} )}
{type === "application" && ( {type === "application" && (
<ShowRollbackSettings applicationId={id}> <ShowRollbackSettings applicationId={id}>
<Button variant="outline"> <Button variant="outline">
Configure Rollbacks <Settings className="size-4"/> Configure Rollbacks <Settings className="size-4" />
</Button> </Button>
</ShowRollbackSettings> </ShowRollbackSettings>
)} )}
</div> </div>
</CardHeader> </CardHeader>
<CardContent className="flex flex-col gap-4"> <CardContent className="flex flex-col gap-4">
{stuckDeployment && (type === "application" || type === "compose") && ( {stuckDeployment && (type === "application" || type === "compose") && (
<AlertBlock <AlertBlock
type="warning" type="warning"
className="flex-col items-start w-full p-4" className="flex-col items-start w-full p-4"
> >
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
<div> <div>
<div className="font-medium text-sm mb-1"> <div className="font-medium text-sm mb-1">
Build appears to be stuck Build appears to be stuck
</div> </div>
<p className="text-sm"> <p className="text-sm">
Hey! Looks like the build has been running for more than 10 Hey! Looks like the build has been running for more than 10
minutes. Would you like to cancel this deployment? minutes. Would you like to cancel this deployment?
</p> </p>
</div> </div>
<Button <Button
variant="destructive" variant="destructive"
size="sm" size="sm"
className="w-fit" className="w-fit"
isLoading={ isLoading={
type === "application" ? isCancellingApp : isCancellingCompose type === "application" ? isCancellingApp : isCancellingCompose
} }
onClick={async () => { onClick={async () => {
try { try {
if (type === "application") { if (type === "application") {
await cancelApplicationDeployment({ await cancelApplicationDeployment({
applicationId: id, applicationId: id,
}); });
} else if (type === "compose") { } else if (type === "compose") {
await cancelComposeDeployment({ await cancelComposeDeployment({
composeId: id, composeId: id,
}); });
} }
toast.success("Deployment cancellation requested"); toast.success("Deployment cancellation requested");
} catch (error) { } catch (error) {
toast.error( toast.error(
error instanceof Error error instanceof Error
? error.message ? error.message
: "Failed to cancel deployment", : "Failed to cancel deployment",
); );
} }
}} }}
> >
Cancel Deployment Cancel Deployment
</Button> </Button>
</div> </div>
</AlertBlock> </AlertBlock>
)} )}
{refreshToken && ( {refreshToken && (
<div className="flex flex-col gap-2 text-sm"> <div className="flex flex-col gap-2 text-sm">
<span> <span>
If you want to re-deploy this application use this URL in the If you want to re-deploy this application use this URL in the
config of your git provider or docker config of your git provider or docker
</span> </span>
<div className="flex flex-row items-center gap-2 flex-wrap"> <div className="flex flex-row items-center gap-2 flex-wrap">
<span>Webhook URL: </span> <span>Webhook URL: </span>
<div className="flex flex-row items-center gap-2"> <div className="flex flex-row items-center gap-2">
<Badge <Badge
className="p-2 rounded-md ml-1 mr-1 hover:border-primary hover:text-primary-foreground hover:bg-primary hover:cursor-pointer" className="p-2 rounded-md ml-1 mr-1 hover:border-primary hover:text-primary-foreground hover:bg-primary hover:cursor-pointer"
variant="outline" variant="outline"
onClick={() => { onClick={() => {
copy(`${url}/api/deploy${type === "compose" ? "/compose" : ""}/${refreshToken}`); copy(
toast.success("Copied to clipboard."); `${url}/api/deploy${type === "compose" ? "/compose" : ""}/${refreshToken}`,
}} );
> toast.success("Copied to clipboard.");
{`${url}/api/deploy${ }}
type === "compose" ? "/compose" : "" >
}/${refreshToken}`} {`${url}/api/deploy${
<Copy className="h-4 w-4 ml-1 text-muted-foreground"/> type === "compose" ? "/compose" : ""
</Badge> }/${refreshToken}`}
{(type === "application" || type === "compose") && ( <Copy className="h-4 w-4 ml-1 text-muted-foreground" />
<RefreshToken id={id} type={type}/> </Badge>
)} {(type === "application" || type === "compose") && (
</div> <RefreshToken id={id} type={type} />
</div> )}
</div> </div>
)} </div>
</div>
)}
{isLoadingDeployments ? ( {isLoadingDeployments ? (
<div className="flex w-full flex-row items-center justify-center gap-3 pt-10 min-h-[25vh]"> <div className="flex w-full flex-row items-center justify-center gap-3 pt-10 min-h-[25vh]">
<Loader2 className="size-6 text-muted-foreground animate-spin"/> <Loader2 className="size-6 text-muted-foreground animate-spin" />
<span className="text-base text-muted-foreground"> <span className="text-base text-muted-foreground">
Loading deployments... Loading deployments...
</span> </span>
</div> </div>
) : deployments?.length === 0 ? ( ) : deployments?.length === 0 ? (
<div className="flex w-full flex-col items-center justify-center gap-3 pt-10 min-h-[25vh]"> <div className="flex w-full flex-col items-center justify-center gap-3 pt-10 min-h-[25vh]">
<RocketIcon className="size-8 text-muted-foreground"/> <RocketIcon className="size-8 text-muted-foreground" />
<span className="text-base text-muted-foreground"> <span className="text-base text-muted-foreground">
No deployments found No deployments found
</span> </span>
</div> </div>
) : ( ) : (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
{deployments?.map((deployment, index) => { {deployments?.map((deployment, index) => {
const titleText = deployment?.title?.trim() || ""; const titleText = deployment?.title?.trim() || "";
const needsTruncation = titleText.length > MAX_DESCRIPTION_LENGTH; const needsTruncation = titleText.length > MAX_DESCRIPTION_LENGTH;
const isExpanded = expandedDescriptions.has( const isExpanded = expandedDescriptions.has(
deployment.deploymentId, deployment.deploymentId,
); );
const canDelete = const canDelete =
deployment.status === "done" || deployment.status === "error"; deployment.status === "done" || deployment.status === "error";
return ( return (
<div <div
key={deployment.deploymentId} key={deployment.deploymentId}
className="flex flex-col gap-4 rounded-lg border p-4 sm:flex-row sm:items-center sm:justify-between" className="flex flex-col gap-4 rounded-lg border p-4 sm:flex-row sm:items-center sm:justify-between"
> >
<div className="flex flex-1 flex-col min-w-0"> <div className="flex flex-1 flex-col min-w-0">
<span <span className="flex items-center gap-4 font-medium capitalize text-foreground">
className="flex items-center gap-4 font-medium capitalize text-foreground">
{index + 1}. {deployment.status} {index + 1}. {deployment.status}
<StatusTooltip <StatusTooltip
status={deployment?.status} status={deployment?.status}
className="size-2.5" className="size-2.5"
/> />
</span> </span>
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<span <span className="break-words text-sm text-muted-foreground whitespace-pre-wrap">
className="break-words text-sm text-muted-foreground whitespace-pre-wrap">
{isExpanded || !needsTruncation {isExpanded || !needsTruncation
? titleText ? titleText
: truncateDescription(titleText)} : truncateDescription(titleText)}
</span> </span>
{needsTruncation && ( {needsTruncation && (
<button <button
type="button" type="button"
onClick={() => { onClick={() => {
const next = new Set(expandedDescriptions); const next = new Set(expandedDescriptions);
if (next.has(deployment.deploymentId)) { if (next.has(deployment.deploymentId)) {
next.delete(deployment.deploymentId); next.delete(deployment.deploymentId);
} else { } else {
next.add(deployment.deploymentId); next.add(deployment.deploymentId);
} }
setExpandedDescriptions(next); setExpandedDescriptions(next);
}} }}
className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors w-fit mt-1 cursor-pointer" className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors w-fit mt-1 cursor-pointer"
aria-label={ aria-label={
isExpanded isExpanded
? "Collapse commit message" ? "Collapse commit message"
: "Expand commit message" : "Expand commit message"
} }
> >
{isExpanded ? ( {isExpanded ? (
<> <>
<ChevronUp className="size-3"/> <ChevronUp className="size-3" />
Show less Show less
</> </>
) : ( ) : (
<> <>
<ChevronDown className="size-3"/> <ChevronDown className="size-3" />
Show more Show more
</> </>
)} )}
</button> </button>
)} )}
{/* Hash (from description) - shown in compact form */} {/* Hash (from description) - shown in compact form */}
{deployment.description?.trim() && ( {deployment.description?.trim() && (
<span className="text-xs text-muted-foreground font-mono"> <span className="text-xs text-muted-foreground font-mono">
{deployment.description} {deployment.description}
</span> </span>
)} )}
</div> </div>
</div> </div>
<div <div className="flex w-full flex-col items-start gap-2 sm:w-auto sm:max-w-[300px] sm:items-end sm:justify-start">
className="flex w-full flex-col items-start gap-2 sm:w-auto sm:max-w-[300px] sm:items-end sm:justify-start"> <div className="text-sm capitalize text-muted-foreground flex flex-wrap items-center gap-2">
<div <DateTooltip date={deployment.createdAt} />
className="text-sm capitalize text-muted-foreground flex flex-wrap items-center gap-2"> {deployment.startedAt && deployment.finishedAt && (
<DateTooltip date={deployment.createdAt}/> <Badge
{deployment.startedAt && deployment.finishedAt && ( variant="outline"
<Badge className="text-[10px] gap-1 flex items-center"
variant="outline" >
className="text-[10px] gap-1 flex items-center" <Clock className="size-3" />
> {formatDuration(
<Clock className="size-3"/> Math.floor(
{formatDuration( (new Date(deployment.finishedAt).getTime() -
Math.floor( new Date(deployment.startedAt).getTime()) /
(new Date(deployment.finishedAt).getTime() - 1000,
new Date(deployment.startedAt).getTime()) / ),
1000, )}
), </Badge>
)} )}
</Badge> </div>
)}
</div>
<div <div className="flex w-full flex-col gap-2 sm:w-auto sm:flex-row sm:items-center sm:justify-end">
className="flex w-full flex-col gap-2 sm:w-auto sm:flex-row sm:items-center sm:justify-end"> {deployment.pid && deployment.status === "running" && (
{deployment.pid && deployment.status === "running" && ( <DialogAction
<DialogAction title="Kill Process"
title="Kill Process" description="Are you sure you want to kill the process?"
description="Are you sure you want to kill the process?" type="default"
type="default" onClick={async () => {
onClick={async () => { await killProcess({
await killProcess({ deploymentId: deployment.deploymentId,
deploymentId: deployment.deploymentId, })
}) .then(() => {
.then(() => { toast.success("Process killed successfully");
toast.success("Process killed successfully"); })
}) .catch(() => {
.catch(() => { toast.error("Error killing process");
toast.error("Error killing process"); });
}); }}
}} >
> <Button
<Button variant="destructive"
variant="destructive" size="sm"
size="sm" isLoading={isKillingProcess}
isLoading={isKillingProcess} className="w-full sm:w-auto"
className="w-full sm:w-auto" >
> Kill Process
Kill Process </Button>
</Button> </DialogAction>
</DialogAction> )}
)} <Button
<Button onClick={() => {
onClick={() => { setActiveLog(deployment);
setActiveLog(deployment); }}
}} className="w-full sm:w-auto"
className="w-full sm:w-auto" >
> View
View </Button>
</Button>
{canDelete && ( {canDelete && (
<DialogAction <DialogAction
title="Delete Deployment" title="Delete Deployment"
description="Are you sure you want to delete this deployment? This action cannot be undone." description="Are you sure you want to delete this deployment? This action cannot be undone."
type="default" type="default"
onClick={async () => { onClick={async () => {
try { try {
await removeDeployment({ await removeDeployment({
deploymentId: deployment.deploymentId, deploymentId: deployment.deploymentId,
}); });
toast.success("Deployment deleted successfully"); toast.success("Deployment deleted successfully");
} catch (error) { } catch (error) {
toast.error("Error deleting deployment"); toast.error("Error deleting deployment");
} }
}} }}
> >
<Button <Button
variant="destructive" variant="destructive"
size="sm" size="sm"
isLoading={isRemovingDeployment} isLoading={isRemovingDeployment}
> >
Delete Delete
<Trash2 className="size-4"/> <Trash2 className="size-4" />
</Button> </Button>
</DialogAction> </DialogAction>
)} )}
{deployment?.rollback && {deployment?.rollback &&
deployment.status === "done" && deployment.status === "done" &&
type === "application" && ( type === "application" && (
<DialogAction <DialogAction
title="Rollback to this deployment" title="Rollback to this deployment"
description={ description={
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
<p> <p>
Are you sure you want to rollback to this Are you sure you want to rollback to this
deployment? deployment?
</p> </p>
<AlertBlock type="info" className="text-sm"> <AlertBlock type="info" className="text-sm">
Please wait a few seconds while the image is Please wait a few seconds while the image is
pulled from the registry. Your application pulled from the registry. Your application
should be running shortly. should be running shortly.
</AlertBlock> </AlertBlock>
</div> </div>
} }
type="default" type="default"
onClick={async () => { onClick={async () => {
await rollback({ await rollback({
rollbackId: deployment.rollback.rollbackId, rollbackId: deployment.rollback.rollbackId,
}) })
.then(() => { .then(() => {
toast.success( toast.success(
"Rollback initiated successfully", "Rollback initiated successfully",
); );
}) })
.catch(() => { .catch(() => {
toast.error("Error initiating rollback"); toast.error("Error initiating rollback");
}); });
}} }}
> >
<Button <Button
variant="secondary" variant="secondary"
size="sm" size="sm"
isLoading={isRollingBack} isLoading={isRollingBack}
className="w-full sm:w-auto" className="w-full sm:w-auto"
> >
<RefreshCcw <RefreshCcw className="size-4 text-primary group-hover:text-red-500" />
className="size-4 text-primary group-hover:text-red-500"/> Rollback
Rollback </Button>
</Button> </DialogAction>
</DialogAction> )}
)} </div>
</div> </div>
</div> </div>
</div> );
); })}
})} </div>
</div> )}
)} <ShowDeployment
<ShowDeployment serverId={activeLog?.buildServerId || serverId}
serverId={activeLog?.buildServerId || serverId} open={Boolean(activeLog && activeLog.logPath !== null)}
open={Boolean(activeLog && activeLog.logPath !== null)} onClose={() => setActiveLog(null)}
onClose={() => setActiveLog(null)} logPath={activeLog?.logPath || ""}
logPath={activeLog?.logPath || ""} errorMessage={activeLog?.errorMessage || ""}
errorMessage={activeLog?.errorMessage || ""} />
/> </CardContent>
</CardContent> </Card>
</Card> );
);
}; };