Skip to content

Commit 674e323

Browse files
committed
feat(web): Implement generate and download functionality for helm template
1 parent cbea0e5 commit 674e323

6 files changed

Lines changed: 261 additions & 46 deletions

File tree

web/src/config/global.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
export const BASE_API = import.meta.env.VITE_API_CLIENT_BASE_URL;
2-
console.log(BASE_API);

web/src/enums/api.enums.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ export enum API {
1010
Basic = '/IaC-basic',
1111
BugFix = '/IaC-bugfix',
1212
Installation = '/IaC-install',
13+
HelmTemplate = '/Helm-template',
1314
}

web/src/pages/helm-template/helm-template.tsx

Lines changed: 171 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,41 @@
1-
import { FC, useState } from 'react';
1+
import { FC, FormEvent, useState } from 'react';
22
import { Plus, Trash2 } from 'lucide-react';
33
import { cn } from '@/lib/utils';
4+
import { usePost } from '@/core/react-query';
5+
import { API } from '@/enums/api.enums';
6+
import {
7+
HelmTemplateBody,
8+
HelmTemplateResponse,
9+
helmTemplateValidationError,
10+
} from './helm-template.types';
11+
import { toast } from 'sonner';
12+
import { isAxiosError } from 'axios';
13+
import Select, { SingleValue } from 'react-select';
14+
import { accessModesOptions, sizeOptions } from './data/select-options';
15+
import { selectStyle } from './styles/helm-template.style';
16+
import { OptionType } from '@/types/select.types';
17+
import { useDownload } from '@/hooks';
418

519
const HelmTemplate: FC = () => {
20+
const { mutateAsync: helmTemplateMutate, isPending: helmTemplatePending } =
21+
usePost<HelmTemplateResponse, HelmTemplateBody>(
22+
API.HelmTemplate,
23+
'helm-template',
24+
);
25+
const { download, isPending: downloadPending } = useDownload({
26+
downloadFileName: 'helm-template',
27+
source: 'helm',
28+
folderName: 'MyHelm',
29+
});
30+
31+
const [version, setVersion] = useState('');
32+
const [name, setName] = useState('');
33+
const [image, setImage] = useState('');
34+
const [targetPort, setTargetPort] = useState('');
35+
const [replicas, setReplicas] = useState('');
36+
const [sizeValue, setSizeValue] = useState('');
37+
const [sizeType, setSizeType] = useState<SingleValue<OptionType>>();
38+
const [accessModes, setAccessModes] = useState<SingleValue<OptionType>>();
639
const [environments, setEnvironments] = useState([
740
{
841
name: '',
@@ -13,6 +46,7 @@ const HelmTemplate: FC = () => {
1346
]);
1447
const [stateless, setStateless] = useState(false);
1548
const [ingress, setIngress] = useState(false);
49+
const [ingressHost, setIngressHost] = useState('');
1650

1751
const handleAddEnvironment = () => {
1852
setEnvironments((prev) => [
@@ -30,101 +64,184 @@ const HelmTemplate: FC = () => {
3064
setEnvironments((prev) => prev.filter((_, i) => i !== index));
3165
};
3266

67+
const handleEnvironmentChange = (
68+
index: number,
69+
field: string,
70+
value: string,
71+
) => {
72+
setEnvironments((prev) =>
73+
prev.map((env, i) => (i === index ? { ...env, [field]: value } : env)),
74+
);
75+
};
76+
77+
const handleForm = async (e: FormEvent) => {
78+
e.preventDefault();
79+
80+
try {
81+
const body: HelmTemplateBody = {
82+
api_version: parseInt(version),
83+
pods: [
84+
{
85+
name,
86+
image,
87+
target_port: parseInt(targetPort),
88+
replicas: parseInt(replicas),
89+
persistance: {
90+
size: `${sizeValue}${sizeType?.value as string}`,
91+
accessModes: accessModes?.value as string,
92+
},
93+
environment: environments.map((env) => ({
94+
name: env.name,
95+
value: env.value,
96+
})),
97+
stateless,
98+
ingress: {
99+
enabled: ingress,
100+
host: ingressHost,
101+
},
102+
},
103+
],
104+
};
105+
106+
await helmTemplateMutate(body);
107+
await download();
108+
} catch (error) {
109+
console.log(error);
110+
if (isAxiosError<helmTemplateValidationError>(error)) {
111+
toast.error(
112+
`${error.response?.data.detail[0].loc[error.response?.data.detail[0].loc.length - 1]} ${error.response?.data.detail[0].msg}`,
113+
);
114+
} else {
115+
toast.error('Something went wrong');
116+
}
117+
}
118+
};
119+
33120
return (
34-
<div className="scrollbar-thin flex h-[calc(100%-56px)] w-full items-center justify-center overflow-y-auto">
35-
<form className="flex h-full w-full max-w-[768px] flex-col justify-center">
36-
<div className="flex flex-col w-full mb-4">
121+
<div className="flex h-[calc(100%-56px)] w-full justify-center overflow-y-auto scrollbar-thin">
122+
<form onSubmit={handleForm} className="h-full w-full max-w-[768px]">
123+
<div className="mb-4 flex w-full flex-col">
37124
<label htmlFor="api_version" className="mb-1">
38125
Api Version
39126
</label>
40127
<input
41128
id="api_version"
42129
placeholder="2"
43-
className="w-full px-3 py-2 rounded-md outline-none"
130+
value={version}
131+
onChange={(e) => setVersion(e.target.value)}
132+
className="w-full rounded-md px-3 py-2 outline-none"
44133
/>
45134
</div>
46135
<h1 className="mb-4 text-2xl font-bold">Pods</h1>
47-
<div className="flex flex-col mb-4">
136+
<div className="mb-4 flex flex-col">
48137
<label htmlFor="pods_name" className="mb-1">
49138
Name
50139
</label>
51140
<input
52141
id="pods_name"
53-
placeholder="2"
54-
className="w-full px-3 py-2 rounded-md outline-none"
142+
placeholder="web"
143+
value={name}
144+
onChange={(e) => setName(e.target.value)}
145+
className="w-full rounded-md px-3 py-2 outline-none"
55146
/>
56147
</div>
57-
<div className="flex flex-col mb-4">
148+
<div className="mb-4 flex flex-col">
58149
<label htmlFor="pods_image" className="mb-1">
59150
Image
60151
</label>
61152
<input
62153
id="pods_image"
63154
placeholder="nginx"
64-
className="w-full px-3 py-2 rounded-md outline-none"
155+
value={image}
156+
onChange={(e) => setImage(e.target.value)}
157+
className="w-full rounded-md px-3 py-2 outline-none"
65158
/>
66159
</div>
67-
<div className="flex flex-col mb-4">
160+
<div className="mb-4 flex flex-col">
68161
<label htmlFor="pods_target_port" className="mb-1">
69162
Target Port
70163
</label>
71164
<input
72165
id="pods_target_port"
73-
placeholder="nginx"
74-
className="w-full px-3 py-2 rounded-md outline-none"
166+
placeholder="80"
167+
value={targetPort}
168+
onChange={(e) => setTargetPort(e.target.value)}
169+
className="w-full rounded-md px-3 py-2 outline-none"
75170
/>
76171
</div>
77-
<div className="flex flex-col mb-2">
172+
<div className="mb-2 flex flex-col">
78173
<label htmlFor="pods_replicas" className="mb-1">
79174
Replicas
80175
</label>
81176
<input
82177
id="pods_replicas"
83178
placeholder="1"
84-
className="w-full px-3 py-2 rounded-md outline-none"
179+
value={replicas}
180+
onChange={(e) => setReplicas(e.target.value)}
181+
className="w-full rounded-md px-3 py-2 outline-none"
85182
/>
86183
</div>
87184
<h2 className="mb-2 text-lg font-bold">Persistence</h2>
88-
<div className="flex flex-col mb-7">
89-
<label htmlFor="pods_persistence_size" className="mb-1">
90-
Size
91-
</label>
92-
<input
93-
id="pods_persistence_size"
94-
placeholder="iG1"
95-
className="w-full px-3 py-2 rounded-md outline-none"
96-
/>
185+
<div className="mb-7 flex flex-col">
186+
<p className="mb-1">Size</p>
187+
<div className="flex items-center gap-3">
188+
<input
189+
placeholder="Value"
190+
type="number"
191+
value={sizeValue}
192+
onChange={(e) => setSizeValue(e.target.value)}
193+
className="w-full gap-2 rounded-md px-3 py-2 outline-none"
194+
/>
195+
<Select
196+
placeholder="Select..."
197+
options={sizeOptions}
198+
value={sizeType}
199+
onChange={(e) => setSizeType(e)}
200+
className="h-10 w-full"
201+
styles={selectStyle}
202+
/>
203+
</div>
97204
</div>
98-
<div className="flex flex-col mb-2">
99-
<label htmlFor="pods_accessModes" className="mb-1">
100-
Access Modes
101-
</label>
102-
<input
103-
id="pods_accessModes"
104-
placeholder="ReadWriteOnce"
105-
className="w-full px-3 py-2 rounded-md outline-none"
205+
<div className="mb-2 flex flex-col">
206+
<label className="mb-1">Access Modes</label>
207+
<Select
208+
placeholder="Select..."
209+
options={accessModesOptions}
210+
value={accessModes}
211+
onChange={(e) => setAccessModes(e)}
212+
styles={selectStyle}
106213
/>
107214
</div>
108-
<div className="flex items-center mt-5 mb-2">
215+
<div className="mb-2 mt-5 flex items-center">
109216
<h3 className="text-lg font-bold">Environments</h3>
110-
<button className="ml-4 btn btn-xs" onClick={handleAddEnvironment}>
217+
<button
218+
type="button"
219+
className="btn btn-xs ml-4"
220+
onClick={handleAddEnvironment}
221+
>
111222
Add <Plus className="size-3" />
112223
</button>
113224
</div>
114225
<div className="grid grid-cols-2 gap-4">
115226
{environments.map((env, index) => (
116227
<div
117-
className="flex items-center border border-gray-500 divide-x divide-gray-500 rounded-md"
228+
className="flex items-center divide-x divide-gray-500 rounded-md border border-gray-500"
118229
key={index}
119230
>
120231
<input
121-
value={env.name}
122232
placeholder={env.namePlaceholder}
123-
className="w-full h-12 px-2 outline-none rounded-s-md"
233+
value={env.name}
234+
onChange={(e) =>
235+
handleEnvironmentChange(index, 'name', e.target.value)
236+
}
237+
className="h-12 w-full rounded-s-md px-2 outline-none"
124238
/>
125239
<input
126-
value={env.value}
127240
placeholder={env.valuePlaceholder}
241+
value={env.value}
242+
onChange={(e) =>
243+
handleEnvironmentChange(index, 'value', e.target.value)
244+
}
128245
className={cn('h-12 w-full px-2 outline-none', {
129246
'rounded-e-md': index === 0,
130247
})}
@@ -140,7 +257,7 @@ const HelmTemplate: FC = () => {
140257
</div>
141258
))}
142259
</div>
143-
<div className="flex justify-between mb-2 mt-7">
260+
<div className="mb-2 mt-7 flex justify-between">
144261
<label htmlFor="pods_stateless" className="mb-1">
145262
Stateless
146263
</label>
@@ -154,7 +271,7 @@ const HelmTemplate: FC = () => {
154271
/>
155272
</div>
156273
<h4 className="mt-5 text-lg font-bold">Ingress</h4>
157-
<div className="flex justify-between mt-3 mb-2">
274+
<div className="mb-2 mt-3 flex justify-between">
158275
<label htmlFor="pods_ingress_enabled" className="mb-1">
159276
Enabled
160277
</label>
@@ -167,18 +284,28 @@ const HelmTemplate: FC = () => {
167284
onChange={() => setIngress(!ingress)}
168285
/>
169286
</div>
170-
<div className="flex flex-col mt-3 mb-2">
287+
<div className="mb-2 mt-3 flex flex-col">
171288
<label htmlFor="pods_ingress_host" className="mb-1">
172289
Host
173290
</label>
174291
<input
175292
id="pods_ingress_host"
176293
placeholder="www.example.com"
177-
className="w-full px-3 py-2 rounded-md outline-none"
294+
value={ingressHost}
295+
onChange={(e) => setIngressHost(e.target.value)}
296+
className="w-full rounded-md px-3 py-2 outline-none"
178297
/>
179298
</div>
180-
<button className="w-full mt-3 text-white btn bg-orange-base hover:bg-orange-base/70">
181-
Submit
299+
<button
300+
type="submit"
301+
disabled={helmTemplatePending}
302+
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"
303+
>
304+
{helmTemplatePending
305+
? 'Generating...'
306+
: downloadPending
307+
? 'Downloading...'
308+
: 'Generate'}
182309
</button>
183310
</form>
184311
</div>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
export interface HelmTemplateBody {
2+
api_version: number;
3+
pods: [
4+
{
5+
name: string;
6+
image: string;
7+
target_port: number;
8+
replicas: number;
9+
persistance: {
10+
size: string;
11+
accessModes: string;
12+
};
13+
environment: {
14+
name: string;
15+
value: string;
16+
}[];
17+
18+
stateless: boolean;
19+
ingress: {
20+
enabled: boolean;
21+
host: string;
22+
};
23+
},
24+
];
25+
}
26+
27+
export interface HelmTemplateResponse {
28+
output: string;
29+
}
30+
31+
export interface helmTemplateValidationError {
32+
detail: [
33+
{
34+
type: string;
35+
loc: string[];
36+
msg: string;
37+
input: null;
38+
},
39+
];
40+
}

0 commit comments

Comments
 (0)