feat(destinations): add additionalFlags field for destination settings

- Introduced an optional `additionalFlags` field in the destination schema to allow users to specify extra parameters.
- Updated the form in the dashboard to include a textarea for entering additional flags.
- Modified the API router to handle the new `additionalFlags` input when creating or updating destinations.
- Adjusted database schema to accommodate the new field in the destination table.
This commit is contained in:
Mauricio Siu
2026-03-29 08:39:27 -06:00
parent ddfcd1a671
commit 4bbb2ece49
7 changed files with 8304 additions and 2 deletions
@@ -24,6 +24,7 @@ import {
FormMessage, FormMessage,
} from "@/components/ui/form"; } from "@/components/ui/form";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { import {
Select, Select,
SelectContent, SelectContent,
@@ -46,6 +47,7 @@ const addDestination = z.object({
region: z.string(), region: z.string(),
endpoint: z.string().min(1, "Endpoint is required"), endpoint: z.string().min(1, "Endpoint is required"),
serverId: z.string().optional(), serverId: z.string().optional(),
additionalFlags: z.array(z.string()).optional(),
}); });
type AddDestination = z.infer<typeof addDestination>; type AddDestination = z.infer<typeof addDestination>;
@@ -89,6 +91,7 @@ export const HandleDestinations = ({ destinationId }: Props) => {
region: "", region: "",
secretAccessKey: "", secretAccessKey: "",
endpoint: "", endpoint: "",
additionalFlags: [],
}, },
resolver: zodResolver(addDestination), resolver: zodResolver(addDestination),
}); });
@@ -102,6 +105,7 @@ export const HandleDestinations = ({ destinationId }: Props) => {
bucket: destination.bucket, bucket: destination.bucket,
region: destination.region, region: destination.region,
endpoint: destination.endpoint, endpoint: destination.endpoint,
additionalFlags: destination.additionalFlags ?? [],
}); });
} else { } else {
form.reset(); form.reset();
@@ -118,6 +122,7 @@ export const HandleDestinations = ({ destinationId }: Props) => {
region: data.region, region: data.region,
secretAccessKey: data.secretAccessKey, secretAccessKey: data.secretAccessKey,
destinationId: destinationId || "", destinationId: destinationId || "",
additionalFlags: data.additionalFlags ?? [],
}) })
.then(async () => { .then(async () => {
toast.success(`Destination ${destinationId ? "Updated" : "Created"}`); toast.success(`Destination ${destinationId ? "Updated" : "Created"}`);
@@ -179,6 +184,7 @@ export const HandleDestinations = ({ destinationId }: Props) => {
region, region,
secretAccessKey: secretKey, secretAccessKey: secretKey,
serverId, serverId,
additionalFlags: form.getValues("additionalFlags") ?? [],
}) })
.then(() => { .then(() => {
toast.success("Connection Success"); toast.success("Connection Success");
@@ -358,6 +364,33 @@ export const HandleDestinations = ({ destinationId }: Props) => {
</FormItem> </FormItem>
)} )}
/> />
<FormField
control={form.control}
name="additionalFlags"
render={({ field }) => (
<FormItem>
<FormLabel>Additional Flags (Optional)</FormLabel>
<FormControl>
<Textarea
placeholder={
"--s3-sign-accept-encoding=false\n--s3-chunk-size=50M"
}
className="h-20"
value={(field.value || []).join("\n")}
onChange={(e) =>
field.onChange(
e.target.value
.split("\n")
.map((f) => f.trim())
.filter((f) => f.length > 0),
)
}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form> </form>
<DialogFooter <DialogFooter
@@ -0,0 +1 @@
ALTER TABLE "destination" ADD COLUMN "additionalFlags" text[];
File diff suppressed because it is too large Load Diff
+7
View File
@@ -1086,6 +1086,13 @@
"when": 1774337356154, "when": 1774337356154,
"tag": "0154_careful_eternals", "tag": "0154_careful_eternals",
"breakpoints": true "breakpoints": true
},
{
"idx": 155,
"version": "7",
"when": 1774794547865,
"tag": "0155_careless_clea",
"breakpoints": true
} }
] ]
} }
+12 -2
View File
@@ -47,8 +47,15 @@ export const destinationRouter = createTRPCRouter({
testConnection: withPermission("destination", "create") testConnection: withPermission("destination", "create")
.input(apiCreateDestination) .input(apiCreateDestination)
.mutation(async ({ input }) => { .mutation(async ({ input }) => {
const { secretAccessKey, bucket, region, endpoint, accessKey, provider } = const {
input; secretAccessKey,
bucket,
region,
endpoint,
accessKey,
provider,
additionalFlags,
} = input;
try { try {
const rcloneFlags = [ const rcloneFlags = [
`--s3-access-key-id="${accessKey}"`, `--s3-access-key-id="${accessKey}"`,
@@ -65,6 +72,9 @@ export const destinationRouter = createTRPCRouter({
if (provider) { if (provider) {
rcloneFlags.unshift(`--s3-provider="${provider}"`); rcloneFlags.unshift(`--s3-provider="${provider}"`);
} }
if (additionalFlags?.length) {
rcloneFlags.push(...additionalFlags);
}
const rcloneDestination = `:s3:${bucket}`; const rcloneDestination = `:s3:${bucket}`;
const rcloneCommand = `rclone ls ${rcloneFlags.join(" ")} "${rcloneDestination}"`; const rcloneCommand = `rclone ls ${rcloneFlags.join(" ")} "${rcloneDestination}"`;
@@ -18,6 +18,7 @@ export const destinations = pgTable("destination", {
bucket: text("bucket").notNull(), bucket: text("bucket").notNull(),
region: text("region").notNull(), region: text("region").notNull(),
endpoint: text("endpoint").notNull(), endpoint: text("endpoint").notNull(),
additionalFlags: text("additionalFlags").array(),
organizationId: text("organizationId") organizationId: text("organizationId")
.notNull() .notNull()
.references(() => organization.id, { onDelete: "cascade" }), .references(() => organization.id, { onDelete: "cascade" }),
@@ -44,6 +45,7 @@ const createSchema = createInsertSchema(destinations, {
endpoint: z.string(), endpoint: z.string(),
secretAccessKey: z.string(), secretAccessKey: z.string(),
region: z.string(), region: z.string(),
additionalFlags: z.array(z.string()).default([]),
}); });
export const apiCreateDestination = createSchema export const apiCreateDestination = createSchema
@@ -55,6 +57,7 @@ export const apiCreateDestination = createSchema
region: true, region: true,
endpoint: true, endpoint: true,
secretAccessKey: true, secretAccessKey: true,
additionalFlags: true,
}) })
.required() .required()
.extend({ .extend({
@@ -81,6 +84,7 @@ export const apiUpdateDestination = createSchema
secretAccessKey: true, secretAccessKey: true,
destinationId: true, destinationId: true,
provider: true, provider: true,
additionalFlags: true,
}) })
.required() .required()
.extend({ .extend({
@@ -79,6 +79,10 @@ export const getS3Credentials = (destination: Destination) => {
rcloneFlags.unshift(`--s3-provider="${provider}"`); rcloneFlags.unshift(`--s3-provider="${provider}"`);
} }
if (destination.additionalFlags?.length) {
rcloneFlags.push(...destination.additionalFlags);
}
return rcloneFlags; return rcloneFlags;
}; };