Skip to content

Commit

Permalink
Merge pull request #122 from MiladSadeghi/master
Browse files Browse the repository at this point in the history
Update Web App to Ansible API Services
  • Loading branch information
mohammadll authored Dec 4, 2024
2 parents 6a3d148 + b4745fe commit 2525686
Show file tree
Hide file tree
Showing 29 changed files with 998 additions and 109 deletions.
11 changes: 10 additions & 1 deletion web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div>
<div className="container mx-auto border-l border-r border-gray-700 h-dvh max-w-7xl">
<div className="container mx-auto h-dvh max-w-7xl border-l border-r border-gray-700">
<Routes location={location}>
<Route element={<MainLayout />}>
<Route index element={<Basic />} />
Expand All @@ -30,6 +34,11 @@ function App() {
<Route path="iam" element={<IAM />} />
<Route path="argocd" element={<Argocd />} />
</Route>
<Route path="ansible-template" element={<AnsibleLayout />}>
<Route path="docker" element={<DockerAnsible />} />
<Route path="nginx" element={<NginxAnsible />} />
<Route path="kuber" element={<KubernetesAnsible />} />
</Route>
<Route path="installation" element={<Installation />} />
</Route>
</Routes>
Expand Down
27 changes: 21 additions & 6 deletions web/src/components/form/form-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand All @@ -21,20 +29,27 @@ export const FormInput = ({ name, label, error, ...props }: FormFieldProps) => {
name={name}
>
{label && (
<div className="mb-2 flex items-baseline justify-between">
<Form.Label className="form-label">{label} :</Form.Label>
<div className="flex items-baseline justify-between mb-1">
<Form.Label className="form-label">{label}</Form.Label>
</div>
)}
<Form.Control asChild>
<input
className="w-full rounded-md border border-gray-200 px-3 py-2 outline-none dark:border-none"
{...register(name)}
type={inputType}
className={cn(
'w-full rounded-md border border-gray-500 px-3 py-2 outline-none transition-all focus:border-orange-base',
props.className,
{
'border-red-500 dark:border': errorMessage,
},
)}
{...register(name, { ...(isNumber && { valueAsNumber: true }) })}
{...props}
/>
</Form.Control>
{errorMessage && (
<div className="absolute left-0 top-full">
<Form.Message className="form-message ml-auto text-sm text-red-500">
<Form.Message className="ml-auto text-sm text-red-500 form-message">
{errorMessage}
</Form.Message>
</div>
Expand Down
8 changes: 4 additions & 4 deletions web/src/components/form/form-select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -46,8 +46,8 @@ export const FormSelect = ({
name={name}
>
{label && (
<div className="mb-2 flex items-baseline justify-between">
<Form.Label className="form-label">{label} :</Form.Label>
<div className="mb-1 flex items-baseline justify-between">
<Form.Label className="form-label">{label}</Form.Label>
</div>
)}
<Form.Control asChild>
Expand All @@ -61,7 +61,7 @@ export const FormSelect = ({
placeholder={placeholder}
className="w-full"
{...props}
styles={selectStyle(darkMode)}
styles={selectStyle(darkMode, !!errorMessage)}
/>
)}
/>
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/form/form-wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const FormWrapper = <T extends z.ZodType>({
methods,
}: FormWrapperProps<T>) => {
return (
<FormProvider {...methods} >
<FormProvider {...methods}>
<Form.Root
onSubmit={methods.handleSubmit(onSubmit)}
className="text-black dark:text-white"
Expand Down
12 changes: 8 additions & 4 deletions web/src/components/navbar/navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,22 @@ const navbar = [
url: '/bug-fix',
title: 'Bug Fix',
},
{
url: '/terraform-template',
title: 'Terraform Template',
},
{
url: '/installation',
title: 'Installation',
},
{
url: '/terraform-template',
title: 'Terraform Template',
},
{
url: '/helm-template',
title: 'Helm Template',
},
{
url: '/ansible-template',
title: 'Ansible Template',
},
];

const Navbar: FC = () => {
Expand Down
6 changes: 6 additions & 0 deletions web/src/enums/api.enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
}
40 changes: 40 additions & 0 deletions web/src/pages/ansible/components/layout.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="flex h-[calc(100vh-56px)] items-center">
<div className="flex h-full w-full max-w-96 flex-col items-center justify-center divide-y divide-gray-500 border-r border-gray-500">
{menu.map((link) => (
<NavLink
key={link.url}
to={link.url}
className={({ isActive }) =>
`block w-full p-4 text-center text-black outline-none transition-all dark:text-white ${isActive ? 'bg-orange-base text-white' : ''}`
}
>
{link.title}
</NavLink>
))}
</div>
<div className="flex h-full w-2/3 items-center justify-center">
<Outlet />
</div>
</div>
);
};
46 changes: 46 additions & 0 deletions web/src/pages/ansible/docker/components/hosts-fields.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div>
<div className="flex items-center mb-2">
<p className="text-lg font-bold">Hosts</p>
<button type="button" onClick={append} className="ml-4 btn btn-xs">
Add <Plus className="size-3" />
</button>
</div>
<div className="space-y-2">
{fields.map((_, hostIdx) => (
<div className="relative" key={hostIdx}>
<FormInput
id={`hosts_input.${hostIdx}`}
name={`hosts.${hostIdx}.value`}
label=""
placeholder="www.example.com"
/>
{hostIdx > 0 && (
<button
onClick={() => remove(hostIdx)}
className="absolute right-3 top-3"
>
<Trash2 className="size-4" />
</button>
)}
</div>
))}
</div>
</div>
);
};

export default HostsField;
13 changes: 13 additions & 0 deletions web/src/pages/ansible/docker/data/select-options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export const OSOptions = [
{
value: 'ubuntu',
label: 'Ubuntu',
},
];

export const versionOptions = [
{
label: 'Latest',
value: 'latest',
},
];
129 changes: 129 additions & 0 deletions web/src/pages/ansible/docker/docker.tsx
Original file line number Diff line number Diff line change
@@ -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<DockerAnsible>({
resolver: zodResolver(dockerAnsibleSchema),
defaultValues,
});

const { mutateAsync: dockerAnsibleMutate, isPending: dockerAnsiblePending } =
usePost<DockerAnsibleResponse, DockerAnsibleBody>(
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<dockerTemplateValidationError>(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 (
<div className="w-full max-w-96 text-black dark:text-white">
<FormWrapper methods={methods} onSubmit={handleSubmit}>
<div className="mb-4">
<FormInput
id="ansible_user"
name={`ansible_user`}
label="User"
placeholder="root"
/>
</div>
<div className="mb-4">
<FormInput
id="ansible_port"
name={`ansible_port`}
label="Port"
placeholder="22"
inputType={'number'}
isNumber={true}
/>
</div>
<div className="mb-4">
<FormSelect
name={`os`}
label="OS"
placeholder="Select..."
options={OSOptions}
/>
</div>
<div className="mb-4">
<HostsField />
</div>
<div className="mb-4">
<FormSelect
name={`version`}
label="Version"
placeholder="Select..."
options={versionOptions}
/>
</div>
<button
type="submit"
disabled={dockerAnsiblePending}
className="btn mt-3 w-full bg-orange-base text-white hover:bg-orange-base/70 disabled:bg-orange-base/50 disabled:text-white/70"
>
{dockerAnsiblePending
? 'Generating...'
: downloadPending
? 'Downloading...'
: 'Generate'}
</button>
</FormWrapper>
</div>
);
};

export default DockerAnsible;
Loading

0 comments on commit 2525686

Please sign in to comment.