mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
feat: add build-time variables form
This commit is contained in:
@@ -1,42 +1,30 @@
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Toggle } from "@/components/ui/toggle";
|
||||
import { Form } from "@/components/ui/form";
|
||||
import { Secrets } from "@/components/ui/secrets";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { EyeIcon, EyeOffIcon } from "lucide-react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
const addEnvironmentSchema = z.object({
|
||||
environment: z.string(),
|
||||
env: z.string(),
|
||||
});
|
||||
|
||||
type EnvironmentSchema = z.infer<typeof addEnvironmentSchema>;
|
||||
|
||||
const addBuildArgsSchema = z.object({
|
||||
buildArgs: z.string(),
|
||||
});
|
||||
|
||||
type BuildArgsSchema = z.infer<typeof addBuildArgsSchema>;
|
||||
|
||||
interface Props {
|
||||
applicationId: string;
|
||||
}
|
||||
|
||||
export const ShowEnvironment = ({ applicationId }: Props) => {
|
||||
const [isEnvVisible, setIsEnvVisible] = useState(true);
|
||||
const { mutateAsync, isLoading } =
|
||||
api.application.saveEnvironment.useMutation();
|
||||
const saveEnvironmentMutation = api.application.saveEnvironment.useMutation();
|
||||
const saveBuildArgsMutation = api.application.saveBuildArgs.useMutation();
|
||||
|
||||
const { data, refetch } = api.application.one.useQuery(
|
||||
{
|
||||
@@ -46,26 +34,27 @@ export const ShowEnvironment = ({ applicationId }: Props) => {
|
||||
enabled: !!applicationId,
|
||||
},
|
||||
);
|
||||
const form = useForm<EnvironmentSchema>({
|
||||
|
||||
const envForm = useForm<EnvironmentSchema>({
|
||||
defaultValues: {
|
||||
environment: "",
|
||||
env: data?.env || "",
|
||||
},
|
||||
resolver: zodResolver(addEnvironmentSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
form.reset({
|
||||
environment: data.env || "",
|
||||
});
|
||||
}
|
||||
}, [form.reset, data, form]);
|
||||
const buildArgsForm = useForm<BuildArgsSchema>({
|
||||
defaultValues: {
|
||||
buildArgs: data?.buildArgs || "",
|
||||
},
|
||||
resolver: zodResolver(addBuildArgsSchema),
|
||||
});
|
||||
|
||||
const onSubmit = async (data: EnvironmentSchema) => {
|
||||
mutateAsync({
|
||||
env: data.environment,
|
||||
applicationId,
|
||||
})
|
||||
const onEnvSubmit = async (data: EnvironmentSchema) => {
|
||||
saveEnvironmentMutation
|
||||
.mutateAsync({
|
||||
env: data.env,
|
||||
applicationId,
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Environments Added");
|
||||
await refetch();
|
||||
@@ -74,94 +63,46 @@ export const ShowEnvironment = ({ applicationId }: Props) => {
|
||||
toast.error("Error to add environment");
|
||||
});
|
||||
};
|
||||
useEffect(() => {
|
||||
if (isEnvVisible) {
|
||||
if (data?.env) {
|
||||
const maskedLines = data.env
|
||||
.split("\n")
|
||||
.map((line) => "*".repeat(line.length))
|
||||
.join("\n");
|
||||
form.reset({
|
||||
environment: maskedLines,
|
||||
});
|
||||
} else {
|
||||
form.reset({
|
||||
environment: "",
|
||||
});
|
||||
}
|
||||
} else {
|
||||
form.reset({
|
||||
environment: data?.env || "",
|
||||
|
||||
const onBuildArgsSubmit = async (data: BuildArgsSchema) => {
|
||||
saveBuildArgsMutation
|
||||
.mutateAsync({
|
||||
buildArgs: data.buildArgs,
|
||||
applicationId,
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Buildargs Added");
|
||||
await refetch();
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to add build-args");
|
||||
});
|
||||
}
|
||||
}, [form.reset, data, form, isEnvVisible]);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-col gap-5 ">
|
||||
<Card className="bg-background">
|
||||
<CardHeader className="flex flex-row w-full items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="text-xl">Environment Settings</CardTitle>
|
||||
<CardDescription>
|
||||
You can add environment variables to your resource.
|
||||
</CardDescription>
|
||||
</div>
|
||||
|
||||
<Toggle
|
||||
aria-label="Toggle bold"
|
||||
pressed={isEnvVisible}
|
||||
onPressedChange={setIsEnvVisible}
|
||||
>
|
||||
{isEnvVisible ? (
|
||||
<EyeOffIcon className="h-4 w-4 text-muted-foreground" />
|
||||
) : (
|
||||
<EyeIcon className="h-4 w-4 text-muted-foreground" />
|
||||
)}
|
||||
</Toggle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Form {...form}>
|
||||
<form
|
||||
id="hook-form"
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="w-full space-y-4"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="environment"
|
||||
render={({ field }) => (
|
||||
<FormItem className="w-full">
|
||||
<FormControl>
|
||||
<CodeEditor
|
||||
language="properties"
|
||||
disabled={isEnvVisible}
|
||||
placeholder={`NODE_ENV=production
|
||||
PORT=3000
|
||||
`}
|
||||
className="h-96 font-mono"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex flex-row justify-end">
|
||||
<Button
|
||||
disabled={isEnvVisible}
|
||||
isLoading={isLoading}
|
||||
className="w-fit"
|
||||
type="submit"
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Form {...envForm}>
|
||||
<form onSubmit={envForm.handleSubmit(onEnvSubmit)}>
|
||||
<Secrets
|
||||
name="env"
|
||||
isLoading={saveEnvironmentMutation.isLoading}
|
||||
title="Environment Settings"
|
||||
description="You can add environment variables to your resource."
|
||||
placeholder={["NODE_ENV=production", "PORT=3000"].join("\n")}
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
<Form {...buildArgsForm}>
|
||||
<form onSubmit={buildArgsForm.handleSubmit(onBuildArgsSubmit)}>
|
||||
<Secrets
|
||||
name="buildArgs"
|
||||
isLoading={saveBuildArgsMutation.isLoading}
|
||||
title="Build-time Variables"
|
||||
description="Available only at build-time. See documentation here."
|
||||
placeholder="NPM_TOKEN=xyz"
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
92
components/ui/secrets.tsx
Normal file
92
components/ui/secrets.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import {
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Toggle } from "@/components/ui/toggle";
|
||||
import { EyeIcon, EyeOffIcon } from "lucide-react";
|
||||
import { type CSSProperties, useEffect, useState } from "react";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
|
||||
interface Props {
|
||||
name: string;
|
||||
title: string;
|
||||
description: string;
|
||||
placeholder: string;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export const Secrets = (props: Props) => {
|
||||
const [isVisible, setIsVisible] = useState(true);
|
||||
const form = useFormContext<Record<string, string>>();
|
||||
|
||||
return (
|
||||
<Card className="bg-background">
|
||||
<CardHeader className="flex flex-row w-full items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="text-xl">{props.title}</CardTitle>
|
||||
<CardDescription>{props.description}</CardDescription>
|
||||
</div>
|
||||
|
||||
<Toggle
|
||||
aria-label="Toggle bold"
|
||||
pressed={isVisible}
|
||||
onPressedChange={setIsVisible}
|
||||
>
|
||||
{isVisible ? (
|
||||
<EyeOffIcon className="h-4 w-4 text-muted-foreground" />
|
||||
) : (
|
||||
<EyeIcon className="h-4 w-4 text-muted-foreground" />
|
||||
)}
|
||||
</Toggle>
|
||||
</CardHeader>
|
||||
<CardContent className="w-full space-y-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={props.name}
|
||||
render={({ field }) => (
|
||||
<FormItem className="w-full">
|
||||
<FormControl>
|
||||
<CodeEditor
|
||||
style={
|
||||
{
|
||||
WebkitTextSecurity: isVisible ? "disc" : null,
|
||||
} as CSSProperties
|
||||
}
|
||||
language="properties"
|
||||
disabled={isVisible}
|
||||
placeholder={props.placeholder}
|
||||
className="h-96 font-mono"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex flex-row justify-end">
|
||||
<Button
|
||||
disabled={isVisible}
|
||||
isLoading={props.isLoading}
|
||||
className="w-fit"
|
||||
type="submit"
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
apiFindMonitoringStats,
|
||||
apiFindOneApplication,
|
||||
apiReloadApplication,
|
||||
apiSaveBuildArgs,
|
||||
apiSaveBuildType,
|
||||
apiSaveDockerProvider,
|
||||
apiSaveEnvironmentVariables,
|
||||
@@ -190,6 +191,14 @@ export const applicationRouter = createTRPCRouter({
|
||||
});
|
||||
return true;
|
||||
}),
|
||||
saveBuildArgs: protectedProcedure
|
||||
.input(apiSaveBuildArgs)
|
||||
.mutation(async ({ input }) => {
|
||||
await updateApplication(input.applicationId, {
|
||||
buildArgs: input.buildArgs,
|
||||
});
|
||||
return true;
|
||||
}),
|
||||
saveBuildType: protectedProcedure
|
||||
.input(apiSaveBuildType)
|
||||
.mutation(async ({ input }) => {
|
||||
|
||||
@@ -379,6 +379,13 @@ export const apiSaveEnvironmentVariables = createSchema
|
||||
})
|
||||
.required();
|
||||
|
||||
export const apiSaveBuildArgs = createSchema
|
||||
.pick({
|
||||
applicationId: true,
|
||||
buildArgs: true,
|
||||
})
|
||||
.required();
|
||||
|
||||
export const apiFindMonitoringStats = createSchema
|
||||
.pick({
|
||||
appName: true,
|
||||
|
||||
Reference in New Issue
Block a user