diff --git a/web/src/App.tsx b/web/src/App.tsx index ae9a0aa9..501f4910 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -12,12 +12,16 @@ import { Installation, S3, } from '@/pages'; +import { AnsibleLayout } from './pages/ansible/components/layout'; +import DockerAnsible from './pages/ansible/docker/docker'; +import NginxAnsible from './pages/ansible/nginx/nginx'; +import KubernetesAnsible from './pages/ansible/kuber/kuber'; function App() { const location = useLocation(); return (
-
+
}> } /> @@ -30,6 +34,11 @@ function App() { } /> } /> + }> + } /> + } /> + } /> + } /> diff --git a/web/src/components/form/form-input.tsx b/web/src/components/form/form-input.tsx index 50eb4c90..be46e833 100644 --- a/web/src/components/form/form-input.tsx +++ b/web/src/components/form/form-input.tsx @@ -4,7 +4,15 @@ import { FormFieldProps } from '../../types/form.types'; import { getNestedValue } from '@/lib/helper'; import { cn } from '@/lib/utils'; -export const FormInput = ({ name, label, error, ...props }: FormFieldProps) => { +export const FormInput = ({ + name, + label, + error, + isNumber, + inputType, + inputClass, + ...props +}: FormFieldProps) => { const { register, formState: { errors }, @@ -21,20 +29,27 @@ export const FormInput = ({ name, label, error, ...props }: FormFieldProps) => { name={name} > {label && ( -
- {label} : +
+ {label}
)} {errorMessage && (
- + {errorMessage}
diff --git a/web/src/components/form/form-select.tsx b/web/src/components/form/form-select.tsx index 25b0b513..e7591938 100644 --- a/web/src/components/form/form-select.tsx +++ b/web/src/components/form/form-select.tsx @@ -4,9 +4,9 @@ import { Controller, useFormContext } from 'react-hook-form'; import { FormFieldProps } from '../../types/form.types'; import Select from 'react-select'; import { getNestedValue } from '@/lib/helper'; -import { selectStyle } from '@/pages/helm-template/styles/helm-template.style'; import { useStyle } from '@/hooks'; import { cn } from '@/lib/utils'; +import { selectStyle } from '@/styles/select.styles'; interface OptionType { value: string; @@ -46,8 +46,8 @@ export const FormSelect = ({ name={name} > {label && ( -
- {label} : +
+ {label}
)} @@ -61,7 +61,7 @@ export const FormSelect = ({ placeholder={placeholder} className="w-full" {...props} - styles={selectStyle(darkMode)} + styles={selectStyle(darkMode, !!errorMessage)} /> )} /> diff --git a/web/src/components/form/form-wrapper.tsx b/web/src/components/form/form-wrapper.tsx index d3e92c95..a37eb225 100644 --- a/web/src/components/form/form-wrapper.tsx +++ b/web/src/components/form/form-wrapper.tsx @@ -16,7 +16,7 @@ export const FormWrapper = ({ methods, }: FormWrapperProps) => { return ( - + { diff --git a/web/src/enums/api.enums.ts b/web/src/enums/api.enums.ts index 029e3c7b..1d8f7fb6 100644 --- a/web/src/enums/api.enums.ts +++ b/web/src/enums/api.enums.ts @@ -12,3 +12,9 @@ export enum API { Installation = '/IaC-install', HelmTemplate = '/Helm-template', } + +export enum AnsibleTemplateAPI { + Docker = '/ansible-install/docker', + Nginx = '/ansible-install/nginx', + Kubernetes = '/ansible-install/kuber', +} diff --git a/web/src/pages/ansible/components/layout.tsx b/web/src/pages/ansible/components/layout.tsx new file mode 100644 index 00000000..d7f1abc7 --- /dev/null +++ b/web/src/pages/ansible/components/layout.tsx @@ -0,0 +1,40 @@ +import { FC } from 'react'; +import { NavLink, Outlet } from 'react-router'; + +const menu = [ + { + url: 'nginx', + title: 'Nginx Service', + }, + { + url: 'docker', + title: 'Docker Service', + }, + { + url: 'kuber', + title: 'Kubernetes Service', + }, +]; + +export const AnsibleLayout: FC = () => { + return ( +
+
+ {menu.map((link) => ( + + `block w-full p-4 text-center text-black outline-none transition-all dark:text-white ${isActive ? 'bg-orange-base text-white' : ''}` + } + > + {link.title} + + ))} +
+
+ +
+
+ ); +}; diff --git a/web/src/pages/ansible/docker/components/hosts-fields.tsx b/web/src/pages/ansible/docker/components/hosts-fields.tsx new file mode 100644 index 00000000..33e1f399 --- /dev/null +++ b/web/src/pages/ansible/docker/components/hosts-fields.tsx @@ -0,0 +1,46 @@ +import { FormInput } from '@/components/form/form-input'; +import { Plus, Trash2 } from 'lucide-react'; +import { FC } from 'react'; +import { useFieldArray, useFormContext } from 'react-hook-form'; + +const HostsField: FC = () => { + const { control } = useFormContext(); + + const { fields, append, remove } = useFieldArray({ + control, + name: 'hosts', + }); + + return ( +
+
+

Hosts

+ +
+
+ {fields.map((_, hostIdx) => ( +
+ + {hostIdx > 0 && ( + + )} +
+ ))} +
+
+ ); +}; + +export default HostsField; diff --git a/web/src/pages/ansible/docker/data/select-options.ts b/web/src/pages/ansible/docker/data/select-options.ts new file mode 100644 index 00000000..f87f0f4a --- /dev/null +++ b/web/src/pages/ansible/docker/data/select-options.ts @@ -0,0 +1,13 @@ +export const OSOptions = [ + { + value: 'ubuntu', + label: 'Ubuntu', + }, +]; + +export const versionOptions = [ + { + label: 'Latest', + value: 'latest', + }, +]; diff --git a/web/src/pages/ansible/docker/docker.tsx b/web/src/pages/ansible/docker/docker.tsx new file mode 100644 index 00000000..d4617770 --- /dev/null +++ b/web/src/pages/ansible/docker/docker.tsx @@ -0,0 +1,129 @@ +import { FC } from 'react'; +import { + dockerAnsibleSchema, + type DockerAnsible, + type DockerAnsibleBody, + type DockerAnsibleResponse, + type dockerTemplateValidationError, +} from './docker.types'; +import { AnsibleTemplateAPI } from '@/enums/api.enums'; +import { useDownload } from '@/hooks'; +import { usePost } from '@/core/react-query'; +import { isAxiosError } from 'axios'; +import { toast } from 'sonner'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { FormWrapper } from '@/components/form/form-wrapper'; +import { FormInput } from '@/components/form/form-input'; +import HostsField from './components/hosts-fields'; +import { FormSelect } from '@/components/form/form-select'; +import { OSOptions, versionOptions } from './data/select-options'; + +const DockerAnsible: FC = () => { + const defaultValues = { + ansible_user: '', + os: { label: 'Ubuntu', value: 'ubuntu' }, + hosts: [{ value: '' }], + version: { + label: 'Latest', + value: 'latest', + }, + }; + + const methods = useForm({ + resolver: zodResolver(dockerAnsibleSchema), + defaultValues, + }); + + const { mutateAsync: dockerAnsibleMutate, isPending: dockerAnsiblePending } = + usePost( + AnsibleTemplateAPI.Docker, + 'ansible-docker', + ); + + const { download, isPending: downloadPending } = useDownload({ + downloadFileName: 'DockerAnsible', + source: 'docker', + folderName: 'MyAnsible', + }); + + const handleSubmit = async (data: DockerAnsible) => { + try { + const body = { + ...data, + hosts: data.hosts.map((host) => host.value), + os: data.os.value, + version: data.version.value, + }; + + await dockerAnsibleMutate(body); + await download(); + } catch (error) { + console.log(error); + if (isAxiosError(error)) { + toast.error( + `${error.response?.data.detail[0].loc[error.response?.data.detail[0].loc.length - 1]} ${error.response?.data.detail[0].msg}`, + ); + } else { + toast.error('Something went wrong'); + } + } + }; + + return ( +
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+
+ ); +}; + +export default DockerAnsible; diff --git a/web/src/pages/ansible/docker/docker.types.ts b/web/src/pages/ansible/docker/docker.types.ts new file mode 100644 index 00000000..b4a2a5f2 --- /dev/null +++ b/web/src/pages/ansible/docker/docker.types.ts @@ -0,0 +1,48 @@ +import { z as zod } from 'zod'; + +export interface DockerAnsibleResponse { + output: string; +} + +export interface DockerAnsibleBody { + ansible_user: string; + ansible_port: number; + os: string; + hosts: string[]; + version: string; +} + +export interface dockerTemplateValidationError { + detail: [ + { + type: string; + loc: string[]; + msg: string; + input: null; + }, + ]; +} + +export const dockerAnsibleSchema = zod.object({ + ansible_user: zod.string().min(1, 'User is required!'), + ansible_port: zod + .number({ invalid_type_error: 'Port is required!' }) + .min(1, 'Port is required!'), + os: zod.object({ + label: zod.string(), + value: zod.string(), + }), + hosts: zod + .array( + zod.object({ + value: zod.string().min(1, 'Host is required!'), + }), + ) + .min(1), + version: zod.object({ + label: zod.string(), + value: zod.string(), + }), +}); + +export type DockerAnsible = zod.infer; diff --git a/web/src/pages/ansible/kuber/components/k8s-master-nodes.tsx b/web/src/pages/ansible/kuber/components/k8s-master-nodes.tsx new file mode 100644 index 00000000..2ac9bf28 --- /dev/null +++ b/web/src/pages/ansible/kuber/components/k8s-master-nodes.tsx @@ -0,0 +1,46 @@ +import { FormInput } from '@/components/form/form-input'; +import { Plus, Trash2 } from 'lucide-react'; +import { FC } from 'react'; +import { useFieldArray, useFormContext } from 'react-hook-form'; + +const K8SMasterNodes: FC = () => { + const { control } = useFormContext(); + + const { fields, append, remove } = useFieldArray({ + control, + name: 'k8s_master_nodes', + }); + + return ( +
+
+

K8s Master Nodes

+ +
+
+ {fields.map((_, nodeIdx) => ( +
+ + {nodeIdx > 0 && ( + + )} +
+ ))} +
+
+ ); +}; + +export default K8SMasterNodes; diff --git a/web/src/pages/ansible/kuber/components/k8s-worker-nodes.tsx b/web/src/pages/ansible/kuber/components/k8s-worker-nodes.tsx new file mode 100644 index 00000000..3a7ee574 --- /dev/null +++ b/web/src/pages/ansible/kuber/components/k8s-worker-nodes.tsx @@ -0,0 +1,46 @@ +import { FormInput } from '@/components/form/form-input'; +import { Plus, Trash2 } from 'lucide-react'; +import { FC } from 'react'; +import { useFieldArray, useFormContext } from 'react-hook-form'; + +const K8SWorkerNodes: FC = () => { + const { control } = useFormContext(); + + const { fields, append, remove } = useFieldArray({ + control, + name: 'k8s_worker_nodes', + }); + + return ( +
+
+

K8s Worker Nodes

+ +
+
+ {fields.map((_, nodeIdx) => ( +
+ + {nodeIdx > 0 && ( + + )} +
+ ))} +
+
+ ); +}; + +export default K8SWorkerNodes; diff --git a/web/src/pages/ansible/kuber/data/select-options.ts b/web/src/pages/ansible/kuber/data/select-options.ts new file mode 100644 index 00000000..01c1cf70 --- /dev/null +++ b/web/src/pages/ansible/kuber/data/select-options.ts @@ -0,0 +1,21 @@ +export const OSOptions = [ + { + value: 'ubuntu', + label: 'Ubuntu', + }, +]; + +export const versionOptions = [ + { + label: '1.31', + value: '1.31', + }, + { + label: '1.30', + value: '1.30', + }, + { + label: '1.29', + value: '1.29', + }, +]; diff --git a/web/src/pages/ansible/kuber/kuber.tsx b/web/src/pages/ansible/kuber/kuber.tsx new file mode 100644 index 00000000..b76a06a8 --- /dev/null +++ b/web/src/pages/ansible/kuber/kuber.tsx @@ -0,0 +1,131 @@ +import { zodResolver } from '@hookform/resolvers/zod'; +import { FC } from 'react'; +import { useForm } from 'react-hook-form'; +import { + KuberAnsible, + KuberAnsibleBody, + KuberAnsibleResponse, + kuberAnsibleSchema, + kuberTemplateValidationError, +} from './kuber.types'; +import { FormWrapper } from '@/components/form/form-wrapper'; +import { FormInput } from '@/components/form/form-input'; +import { FormSelect } from '@/components/form/form-select'; +import { usePost } from '@/core/react-query'; +import { AnsibleTemplateAPI } from '@/enums/api.enums'; +import { OSOptions, versionOptions } from './data/select-options'; +import { useDownload } from '@/hooks'; +import { isAxiosError } from 'axios'; +import { toast } from 'sonner'; +import K8SMasterNodes from './components/k8s-master-nodes'; +import K8SWorkerNodes from './components/k8s-worker-nodes'; + +const KubernetesAnsible: FC = () => { + const { mutateAsync: kuberAnsibleMutate, isPending: kuberAnsiblePending } = + usePost( + AnsibleTemplateAPI.Kubernetes, + 'ansible-docker', + ); + + const { download, isPending: downloadPending } = useDownload({ + downloadFileName: 'KubernetesAnsible', + source: 'kubernetes', + folderName: 'MyAnsible', + }); + + const defaultValues = { + ansible_user: '', + os: { label: 'Ubuntu', value: 'ubuntu' }, + k8s_worker_nodes: [{ value: '' }], + k8s_master_nodes: [{ value: '' }], + }; + + const methods = useForm({ + resolver: zodResolver(kuberAnsibleSchema), + defaultValues, + }); + + const handleSubmit = async (data: KuberAnsible) => { + try { + const body = { + ...data, + k8s_worker_nodes: data.k8s_worker_nodes.map((worker) => worker.value), + k8s_master_nodes: data.k8s_master_nodes.map((master) => master.value), + os: data.os.value, + version: data.version.value, + }; + + await kuberAnsibleMutate(body); + await download(); + } catch (error) { + console.log(error); + if (isAxiosError(error)) { + toast.error( + `${error.response?.data.detail[0].loc[error.response?.data.detail[0].loc.length - 1]} ${error.response?.data.detail[0].msg}`, + ); + } else { + toast.error('Something went wrong'); + } + } + }; + + return ( +
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+
+ ); +}; + +export default KubernetesAnsible; diff --git a/web/src/pages/ansible/kuber/kuber.types.ts b/web/src/pages/ansible/kuber/kuber.types.ts new file mode 100644 index 00000000..25bd1f76 --- /dev/null +++ b/web/src/pages/ansible/kuber/kuber.types.ts @@ -0,0 +1,54 @@ +import { z as zod } from 'zod'; + +export interface KuberAnsibleResponse { + output: string; +} + +export interface KuberAnsibleBody { + ansible_user: string; + ansible_port: number; + os: string; + k8s_worker_nodes: string[]; + k8s_master_nodes: string[]; + version: string; +} + +export interface kuberTemplateValidationError { + detail: [ + { + type: string; + loc: string[]; + msg: string; + input: null; + }, + ]; +} + +export const kuberAnsibleSchema = zod.object({ + ansible_user: zod.string().min(1, 'User is required!'), + ansible_port: zod + .number({ invalid_type_error: 'Port is required!' }) + .min(1, 'Port is required!'), + os: zod.object({ + label: zod.string(), + value: zod.string(), + }), + k8s_worker_nodes: zod + .array( + zod.object({ + value: zod.string().min(1, 'Required!').default('www.example.com'), + }), + ) + .min(1), + k8s_master_nodes: zod.array( + zod.object({ + value: zod.string().min(1, 'Required!').default('www.example.com'), + }), + ), + version: zod.object({ + label: zod.string().min(1, 'Required!'), + value: zod.string().min(1, 'Required!'), + }), +}); + +export type KuberAnsible = zod.infer; diff --git a/web/src/pages/ansible/nginx/components/hosts-field.tsx b/web/src/pages/ansible/nginx/components/hosts-field.tsx new file mode 100644 index 00000000..33e1f399 --- /dev/null +++ b/web/src/pages/ansible/nginx/components/hosts-field.tsx @@ -0,0 +1,46 @@ +import { FormInput } from '@/components/form/form-input'; +import { Plus, Trash2 } from 'lucide-react'; +import { FC } from 'react'; +import { useFieldArray, useFormContext } from 'react-hook-form'; + +const HostsField: FC = () => { + const { control } = useFormContext(); + + const { fields, append, remove } = useFieldArray({ + control, + name: 'hosts', + }); + + return ( +
+
+

Hosts

+ +
+
+ {fields.map((_, hostIdx) => ( +
+ + {hostIdx > 0 && ( + + )} +
+ ))} +
+
+ ); +}; + +export default HostsField; diff --git a/web/src/pages/ansible/nginx/data/select-options.ts b/web/src/pages/ansible/nginx/data/select-options.ts new file mode 100644 index 00000000..fdde30b8 --- /dev/null +++ b/web/src/pages/ansible/nginx/data/select-options.ts @@ -0,0 +1,29 @@ +export const OSOptions = [ + { + value: 'ubuntu', + label: 'Ubuntu', + }, +]; + +export const VersionOptions = [ + { + value: 'latest', + label: 'Latest', + }, + { + value: '1.27.3-1', + label: '1.27.3-1', + }, + { + value: '1.25.5-1', + label: '1.25.5-1', + }, + { + value: '1.23.4-1', + label: '1.23.4-1', + }, + { + value: '1.21.6-1', + label: '1.21.6-1', + }, +]; diff --git a/web/src/pages/ansible/nginx/nginx.tsx b/web/src/pages/ansible/nginx/nginx.tsx new file mode 100644 index 00000000..0affb759 --- /dev/null +++ b/web/src/pages/ansible/nginx/nginx.tsx @@ -0,0 +1,129 @@ +import { zodResolver } from '@hookform/resolvers/zod'; +import { FC } from 'react'; +import { useForm } from 'react-hook-form'; +import { + NginxAnsibleBody, + NginxAnsibleResponse, + nginxAnsibleSchema, + nginxTemplateValidationError, +} from './nginx.types'; +import { AnsibleTemplateAPI } from '@/enums/api.enums'; +import { usePost } from '@/core/react-query'; +import { useDownload } from '@/hooks'; +import { isAxiosError } from 'axios'; +import { toast } from 'sonner'; +import { FormWrapper } from '@/components/form/form-wrapper'; +import { FormInput } from '@/components/form/form-input'; +import { FormSelect } from '@/components/form/form-select'; +import HostsField from './components/hosts-field'; +import { OSOptions, VersionOptions } from './data/select-options'; +import type { NginxAnsible } from './nginx.types'; + +const NginxAnsible: FC = () => { + const defaultValues = { + ansible_user: '', + os: { label: 'Ubuntu', value: 'ubuntu' }, + hosts: [{ value: '' }], + version: { + label: 'Latest', + value: 'latest', + }, + }; + + const methods = useForm({ + resolver: zodResolver(nginxAnsibleSchema), + defaultValues, + }); + + const { mutateAsync: nginxAnsibleMutate, isPending: nginxAnsiblePending } = + usePost( + AnsibleTemplateAPI.Nginx, + 'ansible-nginx', + ); + + const { download, isPending: downloadPending } = useDownload({ + downloadFileName: 'NginxAnsible', + source: 'nginx', + folderName: 'MyAnsible', + }); + + const handleSubmit = async (data: NginxAnsible) => { + try { + const body = { + ...data, + hosts: data.hosts.map((host) => host.value), + os: data.os.value, + version: data.version.value, + }; + + await nginxAnsibleMutate(body); + await download(); + } catch (error) { + console.log(error); + if (isAxiosError(error)) { + toast.error( + `${error.response?.data.detail[0].loc[error.response?.data.detail[0].loc.length - 1]} ${error.response?.data.detail[0].msg}`, + ); + } else { + toast.error('Something went wrong'); + } + } + }; + + return ( +
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+
+ ); +}; + +export default NginxAnsible; diff --git a/web/src/pages/ansible/nginx/nginx.types.ts b/web/src/pages/ansible/nginx/nginx.types.ts new file mode 100644 index 00000000..77324d00 --- /dev/null +++ b/web/src/pages/ansible/nginx/nginx.types.ts @@ -0,0 +1,48 @@ +import { z as zod } from 'zod'; + +export interface NginxAnsibleResponse { + output: string; +} + +export interface NginxAnsibleBody { + ansible_user: string; + ansible_port: number; + os: string; + hosts: string[]; + version: string; +} + +export interface nginxTemplateValidationError { + detail: [ + { + type: string; + loc: string[]; + msg: string; + input: null; + }, + ]; +} + +export const nginxAnsibleSchema = zod.object({ + ansible_user: zod.string().min(1, 'User is required!'), + ansible_port: zod + .number({ invalid_type_error: 'Port is required!' }) + .min(1, 'Port is required!'), + os: zod.object({ + label: zod.string(), + value: zod.string(), + }), + hosts: zod + .array( + zod.object({ + value: zod.string().min(1, 'Host is required!'), + }), + ) + .min(1), + version: zod.object({ + label: zod.string(), + value: zod.string(), + }), +}); + +export type NginxAnsible = zod.infer; diff --git a/web/src/pages/basic/basic.tsx b/web/src/pages/basic/basic.tsx index 5657b210..6d78b223 100644 --- a/web/src/pages/basic/basic.tsx +++ b/web/src/pages/basic/basic.tsx @@ -60,7 +60,7 @@ const Basic: FC = () => { return (
-
+
@@ -72,7 +72,7 @@ const Basic: FC = () => { type="number" value={minToken} onChange={(e) => setMinToken(e.target.value)} - className="dark:bg-black-1 w-full rounded-md bg-gray-200 p-3 outline-none" + className="w-full rounded-md bg-gray-200 p-3 outline-none dark:bg-black-1" />
@@ -84,7 +84,7 @@ const Basic: FC = () => { type="number" value={maxToken} onChange={(e) => setMaxToken(e.target.value)} - className="dark:bg-black-1 w-full rounded-md bg-gray-200 p-3 outline-none" + className="w-full rounded-md bg-gray-200 p-3 outline-none dark:bg-black-1" />
@@ -96,14 +96,14 @@ const Basic: FC = () => { type="text" value={service} onChange={(e) => setService(e.target.value)} - className="dark:bg-black-1 w-full rounded-md bg-gray-200 p-3 outline-none" + className="w-full rounded-md bg-gray-200 p-3 outline-none dark:bg-black-1" />
{messages.map((message) => message.role === 'user' ? ( @@ -131,7 +131,7 @@ const Basic: FC = () => { value={input} onChange={(e) => setInput(e.target.value)} rows={2} - className="dark:bg-black-1 w-full resize-none rounded-md bg-gray-200 p-4 pr-16 outline-none" + className="w-full resize-none rounded-md bg-gray-200 p-4 pr-16 outline-none dark:bg-black-1" /> +
+
+ {fields.map((field, envIdx) => ( +
div]:mb-0', + { + 'divide-red-500 border-red-500 dark:divide-red-500 dark:border-red-500': + control.getFieldState( + `pods.${podIndex}.environment.${envIdx}.name`, + ).invalid, + }, + )} + key={field.id} + > + + + {envIdx > 0 && ( + + )} +
+ ))} +
+
+ ); +}; + +export default PodEnvironmentFields; diff --git a/web/src/pages/helm-template/helm-template.tsx b/web/src/pages/helm-template/helm-template.tsx index f008ef3d..4ff61952 100644 --- a/web/src/pages/helm-template/helm-template.tsx +++ b/web/src/pages/helm-template/helm-template.tsx @@ -6,7 +6,6 @@ import { API } from '@/enums/api.enums'; import { HelmTemplateBody, HelmTemplateResponse, - HelmTemplateSchema, helmTemplateSchema, helmTemplateValidationError, } from './helm-template.types'; @@ -14,13 +13,15 @@ import { toast } from 'sonner'; import { isAxiosError } from 'axios'; import { accessModesOptions, sizeOptions } from './data/select-options'; import { useDownload } from '@/hooks'; -import { useFieldArray, useForm, useFormContext } from 'react-hook-form'; +import { useFieldArray, useForm } from 'react-hook-form'; import { FormWrapper } from '@/components/form/form-wrapper'; import { zodResolver } from '@hookform/resolvers/zod'; import { FormInput } from '@/components/form/form-input'; import { FormCheckbox } from '@/components/form/form-checkbox'; import { FormSelect } from '@/components/form/form-select'; +import PodEnvironmentFields from './components/pod-environment-fields'; +import type { THelmTemplate } from './helm-template.types'; const HelmTemplate: FC = () => { const { mutateAsync: helmTemplateMutate, isPending: helmTemplatePending } = @@ -41,8 +42,7 @@ const HelmTemplate: FC = () => { { name: '', image: '', - target_port: null, - replicas: "", + replicas: '', persistance: {}, environment: [ { @@ -82,7 +82,7 @@ const HelmTemplate: FC = () => { remove(index); }; - const handleSubmit = async (data: HelmTemplateSchema) => { + const handleSubmit = async (data: THelmTemplate) => { try { const body_data = data.pods.map((data) => { const { mode, accessModes, size } = data.persistance; @@ -97,7 +97,7 @@ const HelmTemplate: FC = () => { }); const body: HelmTemplateBody = { - api_version: parseInt(data.api_version), + api_version: data.api_version, pods: body_data, }; @@ -125,6 +125,8 @@ const HelmTemplate: FC = () => { id="api_version" name="api_version" placeholder="2" + inputType="number" + isNumber={true} />
@@ -200,6 +202,8 @@ const HelmTemplate: FC = () => { name={`pods.${index}.target_port`} label="Target Port" placeholder="80" + inputType="number" + isNumber={true} />
@@ -208,6 +212,8 @@ const HelmTemplate: FC = () => { name={`pods.${index}.replicas`} label="Replicas" placeholder="1" + inputType="number" + isNumber={true} />

Persistence

@@ -219,7 +225,6 @@ const HelmTemplate: FC = () => { label="" placeholder="Value" /> - { }; export default HelmTemplate; - -interface PodEnvironmentFieldsProps { - podIndex: number; -} - -export const PodEnvironmentFields: React.FC = ({ - podIndex, -}) => { - const { control } = useFormContext(); - const { fields, append, remove } = useFieldArray({ - control, - name: `pods.${podIndex}.environment`, - }); - - return ( -
-
-

Environments

- - -
-
- {fields.map((field, envIdx) => ( -
- - - {envIdx > 0 && ( - - )} -
- ))} -
-
- ); -}; diff --git a/web/src/pages/helm-template/helm-template.types.ts b/web/src/pages/helm-template/helm-template.types.ts index e565cc07..518b5d84 100644 --- a/web/src/pages/helm-template/helm-template.types.ts +++ b/web/src/pages/helm-template/helm-template.types.ts @@ -23,8 +23,6 @@ export interface helmTemplateValidationError { export interface Pod { name: string; image: string; - target_port: string | null; - replicas: string | null; persistance: { size: string; accessModes: string; @@ -65,8 +63,12 @@ const ingressSchema = zod.object({ const podSchema = zod.object({ name: zod.string().min(1, 'Name is required'), image: zod.string().min(1, 'Image is required'), - target_port: zod.string().nullable(), - replicas: zod.string().min(1 ,"Replicas is required"), + target_port: zod + .number({ invalid_type_error: 'Target Port is required!' }) + .min(1, 'Target Port is required!'), + replicas: zod + .number({ invalid_type_error: 'Replicas is required!' }) + .min(1, 'Replicas is required'), persistance: persistanceSchema, environment: zod .array(environmentSchema) @@ -76,7 +78,9 @@ const podSchema = zod.object({ }); export const helmTemplateSchema = zod.object({ - api_version: zod.string().min(1, 'API version is required'), + api_version: zod + .number({ invalid_type_error: 'API version is required!' }) + .min(1, 'API version is required'), pods: zod.array(podSchema).min(1, 'At least one pod is required'), }); -export type HelmTemplateSchema = zod.infer; +export type THelmTemplate = zod.infer; diff --git a/web/src/pages/terraform-template/ARGOCD/argocd.tsx b/web/src/pages/terraform-template/ARGOCD/argocd.tsx index 7fc4b101..6bb0710e 100644 --- a/web/src/pages/terraform-template/ARGOCD/argocd.tsx +++ b/web/src/pages/terraform-template/ARGOCD/argocd.tsx @@ -138,7 +138,7 @@ const Argocd: FC = () => {
-

Argocd Repository

+

ArgoCD Repository

({ ...styles, - border: isDark ? 'none' : '1px solid #e3e3e3', + border: error ? '1px solid #ef4444' : '1px solid #6b7280', borderRadius: '6px', background: isDark ? '#121212' : '#fff', color: isDark ? '#fff' : '#121212', @@ -34,8 +35,11 @@ export const selectStyle = ( menu: (styles) => ({ ...styles, background: isDark ? '#121212' : '#fff', - border: 'none', + border: '1px solid #fff', + borderRadius: '6px', + boxShadow: '0 10px 10px 4px #000', }), + option: (styles) => ({ ...styles, background: isDark ? '#121212' : '#fff', diff --git a/web/src/types/form.types.ts b/web/src/types/form.types.ts index 26c29590..52f960de 100644 --- a/web/src/types/form.types.ts +++ b/web/src/types/form.types.ts @@ -8,6 +8,9 @@ export type FormFieldProps = { label: string; error?: string; placeholder?: string; + isNumber?: boolean; + inputType?: typeof HTMLInputElement.prototype.type; + inputClass?: string; } & ComponentPropsWithoutRef; export type FormConfig = {