feat: add build-time variables form

This commit is contained in:
Lorenzo Migliorero
2024-07-22 11:17:59 +02:00
parent e00cbaeb8a
commit b3b7439617
4 changed files with 170 additions and 121 deletions

View File

@@ -1,42 +1,30 @@
import { CodeEditor } from "@/components/shared/code-editor"; import { Form } from "@/components/ui/form";
import { Button } from "@/components/ui/button"; import { Secrets } from "@/components/ui/secrets";
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 { api } from "@/utils/api"; import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod"; 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 { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod"; import { z } from "zod";
const addEnvironmentSchema = z.object({ const addEnvironmentSchema = z.object({
environment: z.string(), env: z.string(),
}); });
type EnvironmentSchema = z.infer<typeof addEnvironmentSchema>; type EnvironmentSchema = z.infer<typeof addEnvironmentSchema>;
const addBuildArgsSchema = z.object({
buildArgs: z.string(),
});
type BuildArgsSchema = z.infer<typeof addBuildArgsSchema>;
interface Props { interface Props {
applicationId: string; applicationId: string;
} }
export const ShowEnvironment = ({ applicationId }: Props) => { export const ShowEnvironment = ({ applicationId }: Props) => {
const [isEnvVisible, setIsEnvVisible] = useState(true); const saveEnvironmentMutation = api.application.saveEnvironment.useMutation();
const { mutateAsync, isLoading } = const saveBuildArgsMutation = api.application.saveBuildArgs.useMutation();
api.application.saveEnvironment.useMutation();
const { data, refetch } = api.application.one.useQuery( const { data, refetch } = api.application.one.useQuery(
{ {
@@ -46,26 +34,27 @@ export const ShowEnvironment = ({ applicationId }: Props) => {
enabled: !!applicationId, enabled: !!applicationId,
}, },
); );
const form = useForm<EnvironmentSchema>({
const envForm = useForm<EnvironmentSchema>({
defaultValues: { defaultValues: {
environment: "", env: data?.env || "",
}, },
resolver: zodResolver(addEnvironmentSchema), resolver: zodResolver(addEnvironmentSchema),
}); });
useEffect(() => { const buildArgsForm = useForm<BuildArgsSchema>({
if (data) { defaultValues: {
form.reset({ buildArgs: data?.buildArgs || "",
environment: data.env || "", },
}); resolver: zodResolver(addBuildArgsSchema),
} });
}, [form.reset, data, form]);
const onSubmit = async (data: EnvironmentSchema) => { const onEnvSubmit = async (data: EnvironmentSchema) => {
mutateAsync({ saveEnvironmentMutation
env: data.environment, .mutateAsync({
applicationId, env: data.env,
}) applicationId,
})
.then(async () => { .then(async () => {
toast.success("Environments Added"); toast.success("Environments Added");
await refetch(); await refetch();
@@ -74,94 +63,46 @@ export const ShowEnvironment = ({ applicationId }: Props) => {
toast.error("Error to add environment"); toast.error("Error to add environment");
}); });
}; };
useEffect(() => {
if (isEnvVisible) { const onBuildArgsSubmit = async (data: BuildArgsSchema) => {
if (data?.env) { saveBuildArgsMutation
const maskedLines = data.env .mutateAsync({
.split("\n") buildArgs: data.buildArgs,
.map((line) => "*".repeat(line.length)) applicationId,
.join("\n"); })
form.reset({ .then(async () => {
environment: maskedLines, toast.success("Buildargs Added");
}); await refetch();
} else { })
form.reset({ .catch(() => {
environment: "", toast.error("Error to add build-args");
});
}
} else {
form.reset({
environment: data?.env || "",
}); });
} };
}, [form.reset, data, form, isEnvVisible]);
return ( return (
<div className="flex w-full flex-col gap-5 "> <div className="flex w-full flex-col gap-5 ">
<Card className="bg-background"> <Form {...envForm}>
<CardHeader className="flex flex-row w-full items-center justify-between"> <form onSubmit={envForm.handleSubmit(onEnvSubmit)}>
<div> <Secrets
<CardTitle className="text-xl">Environment Settings</CardTitle> name="env"
<CardDescription> isLoading={saveEnvironmentMutation.isLoading}
You can add environment variables to your resource. title="Environment Settings"
</CardDescription> description="You can add environment variables to your resource."
</div> placeholder={["NODE_ENV=production", "PORT=3000"].join("\n")}
/>
<Toggle </form>
aria-label="Toggle bold" </Form>
pressed={isEnvVisible} <Form {...buildArgsForm}>
onPressedChange={setIsEnvVisible} <form onSubmit={buildArgsForm.handleSubmit(onBuildArgsSubmit)}>
> <Secrets
{isEnvVisible ? ( name="buildArgs"
<EyeOffIcon className="h-4 w-4 text-muted-foreground" /> isLoading={saveBuildArgsMutation.isLoading}
) : ( title="Build-time Variables"
<EyeIcon className="h-4 w-4 text-muted-foreground" /> description="Available only at build-time. See documentation here."
)} placeholder="NPM_TOKEN=xyz"
</Toggle> />
</CardHeader> </form>
<CardContent> </Form>
<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>
</div> </div>
); );
}; };

92
components/ui/secrets.tsx Normal file
View 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>
);
};

View File

@@ -9,6 +9,7 @@ import {
apiFindMonitoringStats, apiFindMonitoringStats,
apiFindOneApplication, apiFindOneApplication,
apiReloadApplication, apiReloadApplication,
apiSaveBuildArgs,
apiSaveBuildType, apiSaveBuildType,
apiSaveDockerProvider, apiSaveDockerProvider,
apiSaveEnvironmentVariables, apiSaveEnvironmentVariables,
@@ -190,6 +191,14 @@ export const applicationRouter = createTRPCRouter({
}); });
return true; return true;
}), }),
saveBuildArgs: protectedProcedure
.input(apiSaveBuildArgs)
.mutation(async ({ input }) => {
await updateApplication(input.applicationId, {
buildArgs: input.buildArgs,
});
return true;
}),
saveBuildType: protectedProcedure saveBuildType: protectedProcedure
.input(apiSaveBuildType) .input(apiSaveBuildType)
.mutation(async ({ input }) => { .mutation(async ({ input }) => {

View File

@@ -379,6 +379,13 @@ export const apiSaveEnvironmentVariables = createSchema
}) })
.required(); .required();
export const apiSaveBuildArgs = createSchema
.pick({
applicationId: true,
buildArgs: true,
})
.required();
export const apiFindMonitoringStats = createSchema export const apiFindMonitoringStats = createSchema
.pick({ .pick({
appName: true, appName: true,