Update deployment and backup components for improved handling and user experience

- Refined the `ShowDeployments` component to conditionally render `CancelQueues` and `RefreshToken` based on deployment type, enhancing flexibility.
- Removed unnecessary console logging in `RestoreBackup` and updated input field to disable when the database type is "web-server", improving user guidance.
- Enhanced logging in `runWebServerBackup` to provide clearer output during backup operations, ensuring better traceability of actions.
This commit is contained in:
Mauricio Siu
2025-05-04 13:19:45 -06:00
parent 06c9e43143
commit 9aa56870b0
6 changed files with 64 additions and 143 deletions

View File

@@ -8,7 +8,13 @@ import { ShowDeployments } from "./show-deployments";
interface Props {
id: string;
type: "application" | "compose" | "schedule" | "server" | "backup";
type:
| "application"
| "compose"
| "schedule"
| "server"
| "backup"
| "previewDeployment";
serverId?: string;
refreshToken?: string;
children?: React.ReactNode;

View File

@@ -18,7 +18,13 @@ import { Badge } from "@/components/ui/badge";
interface Props {
id: string;
type: "application" | "compose" | "schedule" | "server" | "backup";
type:
| "application"
| "compose"
| "schedule"
| "server"
| "backup"
| "previewDeployment";
refreshToken?: string;
serverId?: string;
}
@@ -65,7 +71,9 @@ export const ShowDeployments = ({
See all the 10 last deployments for this {type}
</CardDescription>
</div>
{refreshToken && <CancelQueues id={id} type={type} />}
{(type === "application" || type === "compose") && (
<CancelQueues id={id} type={type} />
)}
</CardHeader>
<CardContent className="flex flex-col gap-4">
{refreshToken && (
@@ -80,7 +88,9 @@ export const ShowDeployments = ({
<span className="break-all text-muted-foreground">
{`${url}/api/deploy/${refreshToken}`}
</span>
<RefreshToken id={id} type={type} />
{(type === "application" || type === "compose") && (
<RefreshToken id={id} type={type} />
)}
</div>
</div>
</div>

View File

@@ -1,100 +0,0 @@
import { DateTooltip } from "@/components/shared/date-tooltip";
import { StatusTooltip } from "@/components/shared/status-tooltip";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import type { RouterOutputs } from "@/utils/api";
import { useState } from "react";
import { ShowDeployment } from "../deployments/show-deployment";
interface Props {
deployments: RouterOutputs["deployment"]["all"];
serverId?: string;
trigger?: React.ReactNode;
}
export const ShowPreviewBuilds = ({
deployments,
serverId,
trigger,
}: Props) => {
const [activeLog, setActiveLog] = useState<
RouterOutputs["deployment"]["all"][number] | null
>(null);
const [isOpen, setIsOpen] = useState(false);
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
{trigger ? (
trigger
) : (
<Button className="sm:w-auto w-full" size="sm" variant="outline">
View Builds
</Button>
)}
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-5xl">
<DialogHeader>
<DialogTitle>Preview Builds</DialogTitle>
<DialogDescription>
See all the preview builds for this application on this Pull Request
</DialogDescription>
</DialogHeader>
<div className="grid gap-4">
{deployments?.map((deployment) => (
<div
key={deployment.deploymentId}
className="flex items-center justify-between rounded-lg border p-4 gap-2"
>
<div className="flex flex-col">
<span className="flex items-center gap-4 font-medium capitalize text-foreground">
{deployment.status}
<StatusTooltip
status={deployment?.status}
className="size-2.5"
/>
</span>
<span className="text-sm text-muted-foreground">
{deployment.title}
</span>
{deployment.description && (
<span className="break-all text-sm text-muted-foreground">
{deployment.description}
</span>
)}
</div>
<div className="flex flex-col items-end gap-2">
<div className="text-sm capitalize text-muted-foreground">
<DateTooltip date={deployment.createdAt} />
</div>
<Button
onClick={() => {
setActiveLog(deployment);
}}
>
View
</Button>
</div>
</div>
))}
</div>
</DialogContent>
<ShowDeployment
serverId={serverId || ""}
open={Boolean(activeLog && activeLog.logPath !== null)}
onClose={() => setActiveLog(null)}
logPath={activeLog?.logPath || ""}
errorMessage={activeLog?.errorMessage || ""}
/>
</Dialog>
);
};

View File

@@ -17,7 +17,7 @@ import {
ExternalLink,
FileText,
GitPullRequest,
Layers,
Loader2,
PenSquare,
RocketIcon,
Trash2,
@@ -25,8 +25,8 @@ import {
import { toast } from "sonner";
import { ShowModalLogs } from "../../settings/web-server/show-modal-logs";
import { AddPreviewDomain } from "./add-preview-domain";
import { ShowPreviewBuilds } from "./show-preview-builds";
import { ShowPreviewSettings } from "./show-preview-settings";
import { ShowDeploymentsModal } from "../deployments/show-deployments-modal";
interface Props {
applicationId: string;
@@ -38,13 +38,16 @@ export const ShowPreviewDeployments = ({ applicationId }: Props) => {
const { mutateAsync: deletePreviewDeployment, isLoading } =
api.previewDeployment.delete.useMutation();
const { data: previewDeployments, refetch: refetchPreviewDeployments } =
api.previewDeployment.all.useQuery(
{ applicationId },
{
enabled: !!applicationId,
},
);
const {
data: previewDeployments,
refetch: refetchPreviewDeployments,
isLoading: isLoadingPreviewDeployments,
} = api.previewDeployment.all.useQuery(
{ applicationId },
{
enabled: !!applicationId,
},
);
const handleDeletePreviewDeployment = async (previewDeploymentId: string) => {
deletePreviewDeployment({
@@ -80,8 +83,15 @@ export const ShowPreviewDeployments = ({ applicationId }: Props) => {
each pull request you create.
</span>
</div>
{!previewDeployments?.length ? (
<div className="flex w-full flex-col items-center justify-center gap-3 pt-10">
{isLoadingPreviewDeployments ? (
<div className="flex w-full flex-row items-center justify-center gap-3 min-h-[35vh]">
<Loader2 className="size-5 text-muted-foreground animate-spin" />
<span className="text-base text-muted-foreground">
Loading preview deployments...
</span>
</div>
) : !previewDeployments?.length ? (
<div className="flex w-full flex-col items-center justify-center gap-3 min-h-[35vh]">
<RocketIcon className="size-8 text-muted-foreground" />
<span className="text-base text-muted-foreground">
No preview deployments found
@@ -168,19 +178,10 @@ export const ShowPreviewDeployments = ({ applicationId }: Props) => {
</Button>
</ShowModalLogs>
<ShowPreviewBuilds
deployments={deployment.deployments || []}
<ShowDeploymentsModal
id={deployment.previewDeploymentId}
type="previewDeployment"
serverId={data?.serverId || ""}
trigger={
<Button
variant="outline"
size="sm"
className="gap-2"
>
<Layers className="size-4" />
Builds
</Button>
}
/>
<AddPreviewDomain

View File

@@ -140,8 +140,6 @@ const RestoreBackupSchema = z
});
}
console.log(data.backupType, { metadata: data.metadata });
if (data.backupType === "compose" && !data.metadata?.serviceName) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
@@ -532,7 +530,11 @@ export const RestoreBackup = ({
<FormItem>
<FormLabel>Database Name</FormLabel>
<FormControl>
<Input placeholder="Enter database name" {...field} />
<Input
placeholder="Enter database name"
{...field}
disabled={databaseType === "web-server"}
/>
</FormControl>
<FormMessage />
</FormItem>
@@ -789,10 +791,10 @@ export const RestoreBackup = ({
isLoading={isDeploying}
form="hook-form-restore-backup"
type="submit"
disabled={
!form.watch("backupFile") ||
(backupType === "compose" && !form.watch("databaseType"))
}
// disabled={
// !form.watch("backupFile") ||
// (backupType === "compose" && !form.watch("databaseType"))
// }
>
Restore
</Button>

View File

@@ -42,35 +42,35 @@ export const runWebServerBackup = async (backup: BackupSchedule) => {
);
if (!containerId) {
writeStream.write("PostgreSQL container not found❌");
writeStream.write("Dokploy postgres container not found❌\n");
writeStream.end();
throw new Error("PostgreSQL container not found");
throw new Error("Dokploy postgres container not found");
}
writeStream.write(`PostgreSQL container ID: ${containerId}`);
writeStream.write(`Dokploy postgres container ID: ${containerId}\n`);
const postgresContainerId = containerId.trim();
const postgresCommand = `docker exec ${postgresContainerId} pg_dump -v -Fc -U dokploy -d dokploy > '${tempDir}/database.sql'`;
writeStream.write(`Running command: ${postgresCommand}`);
writeStream.write(`Running command: ${postgresCommand}\n`);
await execAsync(postgresCommand);
await execAsync(`cp -r ${BASE_PATH}/* ${tempDir}/filesystem/`);
writeStream.write("Copied filesystem to temp directory");
writeStream.write("Copied filesystem to temp directory\n");
await execAsync(
// Zip all .sql files since we created more than one
`cd ${tempDir} && zip -r ${backupFileName} *.sql filesystem/ > /dev/null 2>&1`,
);
writeStream.write("Zipped database and filesystem");
writeStream.write("Zipped database and filesystem\n");
const uploadCommand = `rclone copyto ${rcloneFlags.join(" ")} "${tempDir}/${backupFileName}" "${s3Path}"`;
writeStream.write(`Running command: ${uploadCommand}`);
writeStream.write(`Running command: ${uploadCommand}\n`);
await execAsync(uploadCommand);
writeStream.write("Uploaded backup to S3 ✅");
writeStream.write("Uploaded backup to S3 ✅\n");
writeStream.end();
await updateDeploymentStatus(deployment.deploymentId, "done");
return true;
@@ -80,7 +80,9 @@ export const runWebServerBackup = async (backup: BackupSchedule) => {
} catch (error) {
console.error("Backup error:", error);
writeStream.write("Backup error❌\n");
writeStream.write(error instanceof Error ? error.message : "Unknown error");
writeStream.write(
error instanceof Error ? error.message : "Unknown error\n",
);
writeStream.end();
await updateDeploymentStatus(deployment.deploymentId, "error");
throw error;