Skip to content

Commit 2525686

Browse files
authored
Merge pull request #122 from MiladSadeghi/master
Update Web App to Ansible API Services
2 parents 6a3d148 + b4745fe commit 2525686

File tree

29 files changed

+998
-109
lines changed

29 files changed

+998
-109
lines changed

web/src/App.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,16 @@ import {
1212
Installation,
1313
S3,
1414
} from '@/pages';
15+
import { AnsibleLayout } from './pages/ansible/components/layout';
16+
import DockerAnsible from './pages/ansible/docker/docker';
17+
import NginxAnsible from './pages/ansible/nginx/nginx';
18+
import KubernetesAnsible from './pages/ansible/kuber/kuber';
1519

1620
function App() {
1721
const location = useLocation();
1822
return (
1923
<div>
20-
<div className="container mx-auto border-l border-r border-gray-700 h-dvh max-w-7xl">
24+
<div className="container mx-auto h-dvh max-w-7xl border-l border-r border-gray-700">
2125
<Routes location={location}>
2226
<Route element={<MainLayout />}>
2327
<Route index element={<Basic />} />
@@ -30,6 +34,11 @@ function App() {
3034
<Route path="iam" element={<IAM />} />
3135
<Route path="argocd" element={<Argocd />} />
3236
</Route>
37+
<Route path="ansible-template" element={<AnsibleLayout />}>
38+
<Route path="docker" element={<DockerAnsible />} />
39+
<Route path="nginx" element={<NginxAnsible />} />
40+
<Route path="kuber" element={<KubernetesAnsible />} />
41+
</Route>
3342
<Route path="installation" element={<Installation />} />
3443
</Route>
3544
</Routes>

web/src/components/form/form-input.tsx

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,15 @@ import { FormFieldProps } from '../../types/form.types';
44
import { getNestedValue } from '@/lib/helper';
55
import { cn } from '@/lib/utils';
66

7-
export const FormInput = ({ name, label, error, ...props }: FormFieldProps) => {
7+
export const FormInput = ({
8+
name,
9+
label,
10+
error,
11+
isNumber,
12+
inputType,
13+
inputClass,
14+
...props
15+
}: FormFieldProps) => {
816
const {
917
register,
1018
formState: { errors },
@@ -21,20 +29,27 @@ export const FormInput = ({ name, label, error, ...props }: FormFieldProps) => {
2129
name={name}
2230
>
2331
{label && (
24-
<div className="mb-2 flex items-baseline justify-between">
25-
<Form.Label className="form-label">{label} :</Form.Label>
32+
<div className="flex items-baseline justify-between mb-1">
33+
<Form.Label className="form-label">{label}</Form.Label>
2634
</div>
2735
)}
2836
<Form.Control asChild>
2937
<input
30-
className="w-full rounded-md border border-gray-200 px-3 py-2 outline-none dark:border-none"
31-
{...register(name)}
38+
type={inputType}
39+
className={cn(
40+
'w-full rounded-md border border-gray-500 px-3 py-2 outline-none transition-all focus:border-orange-base',
41+
props.className,
42+
{
43+
'border-red-500 dark:border': errorMessage,
44+
},
45+
)}
46+
{...register(name, { ...(isNumber && { valueAsNumber: true }) })}
3247
{...props}
3348
/>
3449
</Form.Control>
3550
{errorMessage && (
3651
<div className="absolute left-0 top-full">
37-
<Form.Message className="form-message ml-auto text-sm text-red-500">
52+
<Form.Message className="ml-auto text-sm text-red-500 form-message">
3853
{errorMessage}
3954
</Form.Message>
4055
</div>

web/src/components/form/form-select.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import { Controller, useFormContext } from 'react-hook-form';
44
import { FormFieldProps } from '../../types/form.types';
55
import Select from 'react-select';
66
import { getNestedValue } from '@/lib/helper';
7-
import { selectStyle } from '@/pages/helm-template/styles/helm-template.style';
87
import { useStyle } from '@/hooks';
98
import { cn } from '@/lib/utils';
9+
import { selectStyle } from '@/styles/select.styles';
1010

1111
interface OptionType {
1212
value: string;
@@ -46,8 +46,8 @@ export const FormSelect = ({
4646
name={name}
4747
>
4848
{label && (
49-
<div className="mb-2 flex items-baseline justify-between">
50-
<Form.Label className="form-label">{label} :</Form.Label>
49+
<div className="mb-1 flex items-baseline justify-between">
50+
<Form.Label className="form-label">{label}</Form.Label>
5151
</div>
5252
)}
5353
<Form.Control asChild>
@@ -61,7 +61,7 @@ export const FormSelect = ({
6161
placeholder={placeholder}
6262
className="w-full"
6363
{...props}
64-
styles={selectStyle(darkMode)}
64+
styles={selectStyle(darkMode, !!errorMessage)}
6565
/>
6666
)}
6767
/>

web/src/components/form/form-wrapper.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export const FormWrapper = <T extends z.ZodType>({
1616
methods,
1717
}: FormWrapperProps<T>) => {
1818
return (
19-
<FormProvider {...methods} >
19+
<FormProvider {...methods}>
2020
<Form.Root
2121
onSubmit={methods.handleSubmit(onSubmit)}
2222
className="text-black dark:text-white"

web/src/components/navbar/navbar.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,22 @@ const navbar = [
1111
url: '/bug-fix',
1212
title: 'Bug Fix',
1313
},
14-
{
15-
url: '/terraform-template',
16-
title: 'Terraform Template',
17-
},
1814
{
1915
url: '/installation',
2016
title: 'Installation',
2117
},
18+
{
19+
url: '/terraform-template',
20+
title: 'Terraform Template',
21+
},
2222
{
2323
url: '/helm-template',
2424
title: 'Helm Template',
2525
},
26+
{
27+
url: '/ansible-template',
28+
title: 'Ansible Template',
29+
},
2630
];
2731

2832
const Navbar: FC = () => {

web/src/enums/api.enums.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,9 @@ export enum API {
1212
Installation = '/IaC-install',
1313
HelmTemplate = '/Helm-template',
1414
}
15+
16+
export enum AnsibleTemplateAPI {
17+
Docker = '/ansible-install/docker',
18+
Nginx = '/ansible-install/nginx',
19+
Kubernetes = '/ansible-install/kuber',
20+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { FC } from 'react';
2+
import { NavLink, Outlet } from 'react-router';
3+
4+
const menu = [
5+
{
6+
url: 'nginx',
7+
title: 'Nginx Service',
8+
},
9+
{
10+
url: 'docker',
11+
title: 'Docker Service',
12+
},
13+
{
14+
url: 'kuber',
15+
title: 'Kubernetes Service',
16+
},
17+
];
18+
19+
export const AnsibleLayout: FC = () => {
20+
return (
21+
<div className="flex h-[calc(100vh-56px)] items-center">
22+
<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">
23+
{menu.map((link) => (
24+
<NavLink
25+
key={link.url}
26+
to={link.url}
27+
className={({ isActive }) =>
28+
`block w-full p-4 text-center text-black outline-none transition-all dark:text-white ${isActive ? 'bg-orange-base text-white' : ''}`
29+
}
30+
>
31+
{link.title}
32+
</NavLink>
33+
))}
34+
</div>
35+
<div className="flex h-full w-2/3 items-center justify-center">
36+
<Outlet />
37+
</div>
38+
</div>
39+
);
40+
};
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { FormInput } from '@/components/form/form-input';
2+
import { Plus, Trash2 } from 'lucide-react';
3+
import { FC } from 'react';
4+
import { useFieldArray, useFormContext } from 'react-hook-form';
5+
6+
const HostsField: FC = () => {
7+
const { control } = useFormContext();
8+
9+
const { fields, append, remove } = useFieldArray({
10+
control,
11+
name: 'hosts',
12+
});
13+
14+
return (
15+
<div>
16+
<div className="flex items-center mb-2">
17+
<p className="text-lg font-bold">Hosts</p>
18+
<button type="button" onClick={append} className="ml-4 btn btn-xs">
19+
Add <Plus className="size-3" />
20+
</button>
21+
</div>
22+
<div className="space-y-2">
23+
{fields.map((_, hostIdx) => (
24+
<div className="relative" key={hostIdx}>
25+
<FormInput
26+
id={`hosts_input.${hostIdx}`}
27+
name={`hosts.${hostIdx}.value`}
28+
label=""
29+
placeholder="www.example.com"
30+
/>
31+
{hostIdx > 0 && (
32+
<button
33+
onClick={() => remove(hostIdx)}
34+
className="absolute right-3 top-3"
35+
>
36+
<Trash2 className="size-4" />
37+
</button>
38+
)}
39+
</div>
40+
))}
41+
</div>
42+
</div>
43+
);
44+
};
45+
46+
export default HostsField;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export const OSOptions = [
2+
{
3+
value: 'ubuntu',
4+
label: 'Ubuntu',
5+
},
6+
];
7+
8+
export const versionOptions = [
9+
{
10+
label: 'Latest',
11+
value: 'latest',
12+
},
13+
];
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { FC } from 'react';
2+
import {
3+
dockerAnsibleSchema,
4+
type DockerAnsible,
5+
type DockerAnsibleBody,
6+
type DockerAnsibleResponse,
7+
type dockerTemplateValidationError,
8+
} from './docker.types';
9+
import { AnsibleTemplateAPI } from '@/enums/api.enums';
10+
import { useDownload } from '@/hooks';
11+
import { usePost } from '@/core/react-query';
12+
import { isAxiosError } from 'axios';
13+
import { toast } from 'sonner';
14+
import { useForm } from 'react-hook-form';
15+
import { zodResolver } from '@hookform/resolvers/zod';
16+
import { FormWrapper } from '@/components/form/form-wrapper';
17+
import { FormInput } from '@/components/form/form-input';
18+
import HostsField from './components/hosts-fields';
19+
import { FormSelect } from '@/components/form/form-select';
20+
import { OSOptions, versionOptions } from './data/select-options';
21+
22+
const DockerAnsible: FC = () => {
23+
const defaultValues = {
24+
ansible_user: '',
25+
os: { label: 'Ubuntu', value: 'ubuntu' },
26+
hosts: [{ value: '' }],
27+
version: {
28+
label: 'Latest',
29+
value: 'latest',
30+
},
31+
};
32+
33+
const methods = useForm<DockerAnsible>({
34+
resolver: zodResolver(dockerAnsibleSchema),
35+
defaultValues,
36+
});
37+
38+
const { mutateAsync: dockerAnsibleMutate, isPending: dockerAnsiblePending } =
39+
usePost<DockerAnsibleResponse, DockerAnsibleBody>(
40+
AnsibleTemplateAPI.Docker,
41+
'ansible-docker',
42+
);
43+
44+
const { download, isPending: downloadPending } = useDownload({
45+
downloadFileName: 'DockerAnsible',
46+
source: 'docker',
47+
folderName: 'MyAnsible',
48+
});
49+
50+
const handleSubmit = async (data: DockerAnsible) => {
51+
try {
52+
const body = {
53+
...data,
54+
hosts: data.hosts.map((host) => host.value),
55+
os: data.os.value,
56+
version: data.version.value,
57+
};
58+
59+
await dockerAnsibleMutate(body);
60+
await download();
61+
} catch (error) {
62+
console.log(error);
63+
if (isAxiosError<dockerTemplateValidationError>(error)) {
64+
toast.error(
65+
`${error.response?.data.detail[0].loc[error.response?.data.detail[0].loc.length - 1]} ${error.response?.data.detail[0].msg}`,
66+
);
67+
} else {
68+
toast.error('Something went wrong');
69+
}
70+
}
71+
};
72+
73+
return (
74+
<div className="w-full max-w-96 text-black dark:text-white">
75+
<FormWrapper methods={methods} onSubmit={handleSubmit}>
76+
<div className="mb-4">
77+
<FormInput
78+
id="ansible_user"
79+
name={`ansible_user`}
80+
label="User"
81+
placeholder="root"
82+
/>
83+
</div>
84+
<div className="mb-4">
85+
<FormInput
86+
id="ansible_port"
87+
name={`ansible_port`}
88+
label="Port"
89+
placeholder="22"
90+
inputType={'number'}
91+
isNumber={true}
92+
/>
93+
</div>
94+
<div className="mb-4">
95+
<FormSelect
96+
name={`os`}
97+
label="OS"
98+
placeholder="Select..."
99+
options={OSOptions}
100+
/>
101+
</div>
102+
<div className="mb-4">
103+
<HostsField />
104+
</div>
105+
<div className="mb-4">
106+
<FormSelect
107+
name={`version`}
108+
label="Version"
109+
placeholder="Select..."
110+
options={versionOptions}
111+
/>
112+
</div>
113+
<button
114+
type="submit"
115+
disabled={dockerAnsiblePending}
116+
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"
117+
>
118+
{dockerAnsiblePending
119+
? 'Generating...'
120+
: downloadPending
121+
? 'Downloading...'
122+
: 'Generate'}
123+
</button>
124+
</FormWrapper>
125+
</div>
126+
);
127+
};
128+
129+
export default DockerAnsible;

0 commit comments

Comments
 (0)