feat: enhance volume backup functionality and schema

- Added support for volume backups in the deployment management interface by introducing a new volumeBackupId field in the deployment schema.
- Updated the volume backup schema to include relationships with deployments, allowing for better management and tracking of volume backups.
- Enhanced API routes to include application data when querying volume backups, improving the data returned for related entities.
- Updated UI components to reflect the new volume backup features and ensure seamless integration with existing functionalities.
This commit is contained in:
Mauricio Siu
2025-06-29 23:10:49 -06:00
parent 6eea02c098
commit 49edf17463
9 changed files with 6177 additions and 50 deletions

View File

@@ -14,7 +14,8 @@ interface Props {
| "schedule"
| "server"
| "backup"
| "previewDeployment";
| "previewDeployment"
| "volumeBackup";
serverId?: string;
refreshToken?: string;
children?: React.ReactNode;

View File

@@ -27,7 +27,8 @@ interface Props {
| "schedule"
| "server"
| "backup"
| "previewDeployment";
| "previewDeployment"
| "volumeBackup";
refreshToken?: string;
serverId?: string;
}

View File

@@ -15,9 +15,17 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
import { api } from "@/utils/api";
import { Clock, DatabaseBackup, Loader2, Play, Trash2 } from "lucide-react";
import {
ClipboardList,
Clock,
DatabaseBackup,
Loader2,
Play,
Trash2,
} from "lucide-react";
import { toast } from "sonner";
import { HandleVolumeBackups } from "./handle-volume-backups";
import { ShowDeploymentsModal } from "../deployments/show-deployments-modal";
interface Props {
id: string;
@@ -86,6 +94,14 @@ export const ShowVolumeBackups = ({
) : volumeBackups && volumeBackups.length > 0 ? (
<div className="grid xl:grid-cols-2 gap-4 grid-cols-1 h-full">
{volumeBackups.map((volumeBackup) => {
const serverId =
volumeBackup.application?.serverId ||
volumeBackup.postgres?.serverId ||
volumeBackup.mysql?.serverId ||
volumeBackup.mariadb?.serverId ||
volumeBackup.mongo?.serverId ||
volumeBackup.redis?.serverId ||
volumeBackup.compose?.serverId;
return (
<div
key={volumeBackup.volumeBackupId}
@@ -116,34 +132,20 @@ export const ShowVolumeBackups = ({
>
Cron: {volumeBackup.cronExpression}
</Badge>
{/* {schedule.scheduleType !== "server" &&
schedule.scheduleType !== "dokploy-server" && (
<>
<span className="text-xs text-muted-foreground/50">
</span>
<Badge
variant="outline"
className="font-mono text-[10px] bg-transparent"
>
{schedule.shellType}
</Badge>
</>
)} */}
</div>
</div>
</div>
<div className="flex items-center gap-1.5">
{/* <ShowDeploymentsModal
id={schedule.scheduleId}
type="schedule"
<ShowDeploymentsModal
id={volumeBackup.volumeBackupId}
type="volumeBackup"
serverId={serverId || undefined}
>
<Button variant="ghost" size="icon">
<ClipboardList className="size-4 transition-colors " />
</Button>
</ShowDeploymentsModal> */}
</ShowDeploymentsModal>
<TooltipProvider delayDuration={0}>
<Tooltip>

View File

@@ -0,0 +1,2 @@
ALTER TABLE "deployment" ADD COLUMN "volumeBackupId" text;--> statement-breakpoint
ALTER TABLE "deployment" ADD CONSTRAINT "deployment_volumeBackupId_volume_backup_volumeBackupId_fk" FOREIGN KEY ("volumeBackupId") REFERENCES "public"."volume_backup"("volumeBackupId") ON DELETE cascade ON UPDATE no action;

File diff suppressed because it is too large Load Diff

View File

@@ -743,6 +743,13 @@
"when": 1751259917258,
"tag": "0105_unknown_firelord",
"breakpoints": true
},
{
"idx": 106,
"version": "7",
"when": 1751260111986,
"tag": "0106_furry_gargoyle",
"breakpoints": true
}
]
}

View File

@@ -34,6 +34,15 @@ export const volumeBackupsRouter = createTRPCRouter({
.query(async ({ input }) => {
return await db.query.volumeBackups.findMany({
where: eq(volumeBackups[`${input.volumeBackupType}Id`], input.id),
with: {
application: true,
postgres: true,
mysql: true,
mariadb: true,
mongo: true,
redis: true,
compose: true,
},
});
}),
create: protectedProcedure

View File

@@ -16,6 +16,7 @@ import { previewDeployments } from "./preview-deployments";
import { schedules } from "./schedule";
import { server } from "./server";
import { rollbacks } from "./rollbacks";
import { volumeBackups } from "./volume-backups";
export const deploymentStatus = pgEnum("deploymentStatus", [
"running",
"done",
@@ -64,6 +65,10 @@ export const deployments = pgTable("deployment", {
(): AnyPgColumn => rollbacks.rollbackId,
{ onDelete: "cascade" },
),
volumeBackupId: text("volumeBackupId").references(
(): AnyPgColumn => volumeBackups.volumeBackupId,
{ onDelete: "cascade" },
),
});
export const deploymentsRelations = relations(deployments, ({ one }) => ({
@@ -95,6 +100,10 @@ export const deploymentsRelations = relations(deployments, ({ one }) => ({
fields: [deployments.deploymentId],
references: [rollbacks.deploymentId],
}),
volumeBackup: one(volumeBackups, {
fields: [deployments.volumeBackupId],
references: [volumeBackups.volumeBackupId],
}),
}));
const schema = createInsertSchema(deployments, {
@@ -216,6 +225,7 @@ export const apiFindAllByType = z
"schedule",
"previewDeployment",
"backup",
"volumeBackup",
]),
})
.required();

View File

@@ -12,6 +12,7 @@ import { compose } from "./compose";
import { postgres } from "./postgres";
import { mariadb } from "./mariadb";
import { destinations } from "./destination";
import { deployments } from "./deployment";
export const volumeBackups = pgTable("volume_backup", {
volumeBackupId: text("volumeBackupId")
@@ -61,36 +62,44 @@ export const volumeBackups = pgTable("volume_backup", {
export type VolumeBackup = typeof volumeBackups.$inferSelect;
export const volumeBackupsRelations = relations(volumeBackups, ({ one }) => ({
application: one(applications, {
fields: [volumeBackups.applicationId],
references: [applications.applicationId],
export const volumeBackupsRelations = relations(
volumeBackups,
({ one, many }) => ({
application: one(applications, {
fields: [volumeBackups.applicationId],
references: [applications.applicationId],
}),
postgres: one(postgres, {
fields: [volumeBackups.postgresId],
references: [postgres.postgresId],
}),
mariadb: one(mariadb, {
fields: [volumeBackups.mariadbId],
references: [mariadb.mariadbId],
}),
mongo: one(mongo, {
fields: [volumeBackups.mongoId],
references: [mongo.mongoId],
}),
mysql: one(mysql, {
fields: [volumeBackups.mysqlId],
references: [mysql.mysqlId],
}),
redis: one(redis, {
fields: [volumeBackups.redisId],
references: [redis.redisId],
}),
compose: one(compose, {
fields: [volumeBackups.composeId],
references: [compose.composeId],
}),
destination: one(destinations, {
fields: [volumeBackups.destinationId],
references: [destinations.destinationId],
}),
deployments: many(deployments),
}),
postgres: one(postgres, {
fields: [volumeBackups.postgresId],
references: [postgres.postgresId],
}),
mariadb: one(mariadb, {
fields: [volumeBackups.mariadbId],
references: [mariadb.mariadbId],
}),
mongo: one(mongo, {
fields: [volumeBackups.mongoId],
references: [mongo.mongoId],
}),
mysql: one(mysql, {
fields: [volumeBackups.mysqlId],
references: [mysql.mysqlId],
}),
redis: one(redis, {
fields: [volumeBackups.redisId],
references: [redis.redisId],
}),
compose: one(compose, {
fields: [volumeBackups.composeId],
references: [compose.composeId],
}),
}));
);
export const createVolumeBackupSchema = createInsertSchema(volumeBackups).omit({
volumeBackupId: true,