mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
feat(destinations): add createdAt timestamp and display creation date
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
1
apps/dokploy/drizzle/0070_useful_serpent_society.sql
Normal file
1
apps/dokploy/drizzle/0070_useful_serpent_society.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "destination" ADD COLUMN "createdAt" timestamp DEFAULT now() NOT NULL;
|
||||||
5126
apps/dokploy/drizzle/meta/0070_snapshot.json
Normal file
5126
apps/dokploy/drizzle/meta/0070_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user