feat(destinations): add createdAt timestamp and display creation date

This commit is contained in:
Mauricio Siu
2025-03-06 23:46:21 -06:00
parent 6166963b00
commit 29eb490e2d
7 changed files with 5209 additions and 44 deletions

View File

@@ -39,12 +39,12 @@ import { S3_PROVIDERS } from "./constants";
const addDestination = z.object({ const addDestination = z.object({
name: z.string().min(1, "Name is required"), name: z.string().min(1, "Name is required"),
provider: z.string().optional(), provider: z.string().min(1, "Provider is required"),
accessKeyId: z.string(), accessKeyId: z.string().min(1, "Access Key Id is required"),
secretAccessKey: z.string(), secretAccessKey: z.string().min(1, "Secret Access Key is required"),
bucket: z.string(), bucket: z.string().min(1, "Bucket is required"),
region: z.string(), region: z.string(),
endpoint: z.string(), endpoint: z.string().min(1, "Endpoint is required"),
serverId: z.string().optional(), serverId: z.string().optional(),
}); });
@@ -129,6 +129,58 @@ export const HandleDestinations = ({ destinationId }: Props) => {
); );
}); });
}; };
const handleTestConnection = async (serverId?: string) => {
const result = await form.trigger([
"provider",
"accessKeyId",
"secretAccessKey",
"bucket",
"endpoint",
]);
if (!result) {
const errors = form.formState.errors;
const errorFields = Object.entries(errors)
.map(([field, error]) => `${field}: ${error?.message}`)
.filter(Boolean)
.join("\n");
toast.error("Please fill all required fields", {
description: errorFields,
});
return;
}
const provider = form.getValues("provider");
const accessKey = form.getValues("accessKeyId");
const secretKey = form.getValues("secretAccessKey");
const bucket = form.getValues("bucket");
const endpoint = form.getValues("endpoint");
const region = form.getValues("region");
const connectionString = `:s3,provider=${provider},access_key_id=${accessKey},secret_access_key=${secretKey},endpoint=${endpoint}${region ? `,region=${region}` : ""}:${bucket}`;
await testConnection({
provider,
accessKey,
bucket,
endpoint,
name: "Test",
region,
secretAccessKey: secretKey,
serverId,
})
.then(() => {
toast.success("Connection Success");
})
.catch((e) => {
toast.error("Error connecting to provider", {
description: `${e.message}\n\nTry manually: rclone ls ${connectionString}`,
});
});
};
return ( return (
<Dialog open={open} onOpenChange={setOpen}> <Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger className="" asChild> <DialogTrigger className="" asChild>
@@ -349,26 +401,9 @@ export const HandleDestinations = ({ destinationId }: Props) => {
<Button <Button
type="button" type="button"
variant={"secondary"} variant={"secondary"}
isLoading={isLoading} isLoading={isLoadingConnection}
onClick={async () => { onClick={async () => {
await testConnection({ await handleTestConnection(form.getValues("serverId"));
provider: form.getValues("provider") || "",
accessKey: form.getValues("accessKeyId"),
bucket: form.getValues("bucket"),
endpoint: form.getValues("endpoint"),
name: "Test",
region: form.getValues("region"),
secretAccessKey: form.getValues("secretAccessKey"),
serverId: form.getValues("serverId"),
})
.then(async () => {
toast.success("Connection Success");
})
.catch((e) => {
toast.error("Error connecting the provider", {
description: e.message,
});
});
}} }}
> >
Test Connection Test Connection
@@ -380,21 +415,7 @@ export const HandleDestinations = ({ destinationId }: Props) => {
type="button" type="button"
variant="secondary" variant="secondary"
onClick={async () => { onClick={async () => {
await testConnection({ await handleTestConnection();
provider: form.getValues("provider") || "",
accessKey: form.getValues("accessKeyId"),
bucket: form.getValues("bucket"),
endpoint: form.getValues("endpoint"),
name: "Test",
region: form.getValues("region"),
secretAccessKey: form.getValues("secretAccessKey"),
})
.then(async () => {
toast.success("Connection Success");
})
.catch(() => {
toast.error("Error connecting the provider");
});
}} }}
> >
Test connection Test connection

View File

@@ -56,9 +56,17 @@ export const ShowDestinations = () => {
className="flex items-center justify-between bg-sidebar p-1 w-full rounded-lg" className="flex items-center justify-between bg-sidebar p-1 w-full rounded-lg"
> >
<div className="flex items-center justify-between p-3.5 rounded-lg bg-background border w-full"> <div className="flex items-center justify-between p-3.5 rounded-lg bg-background border w-full">
<span className="text-sm"> <div className="flex flex-col gap-1">
{index + 1}. {destination.name} <span className="text-sm">
</span> {index + 1}. {destination.name}
</span>
<span className="text-xs text-muted-foreground">
Created at:{" "}
{new Date(
destination.createdAt,
).toLocaleDateString()}
</span>
</div>
<div className="flex flex-row gap-1"> <div className="flex flex-row gap-1">
<HandleDestinations <HandleDestinations
destinationId={destination.destinationId} destinationId={destination.destinationId}

View File

@@ -0,0 +1 @@
ALTER TABLE "destination" ADD COLUMN "createdAt" timestamp DEFAULT now() NOT NULL;

File diff suppressed because it is too large Load Diff

View File

@@ -491,6 +491,13 @@
"when": 1741152916611, "when": 1741152916611,
"tag": "0069_legal_bill_hollister", "tag": "0069_legal_bill_hollister",
"breakpoints": true "breakpoints": true
},
{
"idx": 70,
"version": "7",
"when": 1741322697251,
"tag": "0070_useful_serpent_society",
"breakpoints": true
} }
] ]
} }

View File

@@ -21,7 +21,7 @@ import {
updateDestinationById, updateDestinationById,
} from "@dokploy/server"; } from "@dokploy/server";
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm"; import { eq, desc } from "drizzle-orm";
export const destinationRouter = createTRPCRouter({ export const destinationRouter = createTRPCRouter({
create: adminProcedure create: adminProcedure
@@ -98,6 +98,7 @@ export const destinationRouter = createTRPCRouter({
all: protectedProcedure.query(async ({ ctx }) => { all: protectedProcedure.query(async ({ ctx }) => {
return await db.query.destinations.findMany({ return await db.query.destinations.findMany({
where: eq(destinations.organizationId, ctx.session.activeOrganizationId), where: eq(destinations.organizationId, ctx.session.activeOrganizationId),
orderBy: [desc(destinations.createdAt)],
}); });
}), }),
remove: adminProcedure remove: adminProcedure

View File

@@ -1,5 +1,5 @@
import { relations } from "drizzle-orm"; import { relations } from "drizzle-orm";
import { pgTable, text } from "drizzle-orm/pg-core"; import { pgTable, text, timestamp } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod"; import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { z } from "zod"; import { z } from "zod";
@@ -21,6 +21,7 @@ export const destinations = pgTable("destination", {
organizationId: text("organizationId") organizationId: text("organizationId")
.notNull() .notNull()
.references(() => organization.id, { onDelete: "cascade" }), .references(() => organization.id, { onDelete: "cascade" }),
createdAt: timestamp("createdAt").notNull().defaultNow(),
}); });
export const destinationsRelations = relations( export const destinationsRelations = relations(