Skip to content

Commit a4b24fa

Browse files
authored
feat(compose): Merge pull request #129 from MiladSadeghi/pr-127
Resolve docker compose issue
2 parents ce99394 + 58980b4 commit a4b24fa

File tree

13 files changed

+1062
-0
lines changed

13 files changed

+1062
-0
lines changed

web/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { AnsibleLayout } from './pages/ansible/components/layout';
1616
import DockerAnsible from './pages/ansible/docker/docker';
1717
import NginxAnsible from './pages/ansible/nginx/nginx';
1818
import KubernetesAnsible from './pages/ansible/kuber/kuber';
19+
import DockerCompose from './pages/docker-compose/docker-compose';
1920

2021
function App() {
2122
const location = useLocation();
@@ -27,6 +28,7 @@ function App() {
2728
<Route index element={<Basic />} />
2829
<Route path="bug-fix" element={<BugFix />} />
2930
<Route path="helm-template" element={<HelmTemplate />} />
31+
<Route path="docker-compose" element={<DockerCompose />} />
3032
<Route path="terraform-template" element={<TerraformTemplate />}>
3133
<Route path="docker" element={<Docker />} />
3234
<Route path="ec2" element={<EC2 />} />

web/src/components/navbar/navbar.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ const navbar = [
2323
url: '/helm-template',
2424
title: 'Helm Template',
2525
},
26+
{
27+
url: '/docker-compose',
28+
title: 'Docker Compose',
29+
},
2630
{
2731
url: '/ansible-template',
2832
title: 'Ansible Template',

web/src/enums/api.enums.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export enum API {
1111
BugFix = '/IaC-bugfix',
1212
Installation = '/IaC-install',
1313
HelmTemplate = '/Helm-template',
14+
DockerCompose = "/docker-compose"
1415
}
1516

1617
export enum AnsibleTemplateAPI {

web/src/lib/helper.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,36 @@
1+
import { IServiceConfig } from '@/pages/docker-compose/docker-compose.type';
2+
13
export const getNestedValue = (obj: any, path: string) => {
24
return path.split('.').reduce((acc, part) => acc && acc[part], obj);
35
};
6+
7+
export const convertKVtoObject = (
8+
kvArray: Array<{ key: string; value: string } | null>,
9+
) => {
10+
return kvArray?.reduce(
11+
(acc, curr) => {
12+
13+
if (curr && acc) {
14+
acc[curr.key] = curr?.value;
15+
16+
}
17+
return acc;
18+
},
19+
{} as Record<string, string> | null,
20+
);
21+
};
22+
23+
interface Service {
24+
name: string;
25+
[key: string]: any;
26+
}
27+
28+
export const convertServicesToObject = (
29+
services: Service[],
30+
): IServiceConfig => {
31+
return services.reduce((acc, service) => {
32+
const { name, ...serviceWithoutName } = service;
33+
acc[name] = serviceWithoutName as IServiceConfig[string];
34+
return acc;
35+
}, {} as IServiceConfig);
36+
};
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { FC } from 'react';
2+
import { Plus, Trash2 } from 'lucide-react';
3+
import { useFieldArray, useFormContext } from 'react-hook-form';
4+
import { FormInput } from '@/components/form/form-input';
5+
import { FormCheckbox } from '@/components/form/form-checkbox';
6+
import { FormSelect } from '@/components/form/form-select';
7+
8+
const defaultNetworkDrivers = ['bridge', 'host', 'none', 'overlay'] as const;
9+
10+
const NetworkFields: FC = () => {
11+
const { control, watch } = useFormContext();
12+
13+
const { fields, append, remove } = useFieldArray({
14+
control,
15+
name: 'networks.app_network',
16+
});
17+
18+
const customNetwork = watch('networks.custom');
19+
20+
const handleRemoveNetwork = (index: number) => {
21+
remove(index);
22+
};
23+
24+
const handleAppendNetwork = () => {
25+
const networkData = customNetwork
26+
? {
27+
network_name: '',
28+
external: false,
29+
name: '',
30+
}
31+
: {
32+
network_name: '',
33+
driver: {
34+
label: 'bridge',
35+
value: 'bridge',
36+
},
37+
};
38+
39+
append(networkData);
40+
};
41+
42+
return (
43+
<div>
44+
<div className="mb-4 flex items-center justify-between">
45+
<div className="flex items-center">
46+
<p className="text-2xl font-bold">Networks</p>
47+
<button
48+
type="button"
49+
onClick={handleAppendNetwork}
50+
className="btn btn-xs ml-4"
51+
>
52+
Add <Plus className="size-3" />
53+
</button>
54+
</div>
55+
<FormCheckbox label="Custom" name="networks.custom" />
56+
</div>
57+
58+
<div className="space-y-4">
59+
<div className="w-full rounded-md border border-gray-500 p-5">
60+
{fields.map((field, index) => (
61+
<div key={field.id} className="mb-4">
62+
<div className="mb-4 flex items-center justify-between">
63+
<p className="font-semibold">Network #{index + 1}</p>
64+
{index > 0 && (
65+
<button
66+
type="button"
67+
onClick={() => handleRemoveNetwork(index)}
68+
>
69+
<Trash2 className="size-4" color="red" />
70+
</button>
71+
)}
72+
</div>
73+
74+
<div>
75+
{customNetwork && (
76+
<div className="mb-2 flex justify-end">
77+
<FormCheckbox
78+
name={`networks.app_network.${index}.external`}
79+
label="External Network"
80+
/>
81+
</div>
82+
)}
83+
<div className="flex items-center gap-3 [&>div]:flex-1">
84+
<FormInput
85+
name={`networks.app_network.${index}.network_name`}
86+
label="App Network"
87+
placeholder="network_name"
88+
/>
89+
{!customNetwork && (
90+
<FormSelect
91+
name={`networks.app_network.${index}.driver`}
92+
label="Network Driver"
93+
options={defaultNetworkDrivers.map((driver) => ({
94+
label: driver,
95+
value: driver,
96+
}))}
97+
/>
98+
)}
99+
{customNetwork && (
100+
<FormInput
101+
name={`networks.app_network.${index}.name`}
102+
label="Name"
103+
placeholder="Name"
104+
/>
105+
)}
106+
</div>
107+
</div>
108+
</div>
109+
))}
110+
</div>
111+
</div>
112+
</div>
113+
);
114+
};
115+
116+
export default NetworkFields;
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { FC } from 'react';
2+
import { Plus, Trash2 } from 'lucide-react';
3+
import { useFieldArray, useFormContext } from 'react-hook-form';
4+
import { FormInput } from '@/components/form/form-input';
5+
import { FormCheckbox } from '@/components/form/form-checkbox';
6+
import { cn } from '@/lib/utils';
7+
8+
type ServiceBuildFieldsProps = {
9+
serviceIndex: number;
10+
};
11+
12+
export const ServiceBuildFields: FC<ServiceBuildFieldsProps> = ({
13+
serviceIndex,
14+
}) => {
15+
const { control, watch } = useFormContext();
16+
const buildEnabled = watch(`services.${serviceIndex}.build.enabled`);
17+
18+
const { fields, append, remove } = useFieldArray({
19+
control,
20+
name: `services.${serviceIndex}.build.args`,
21+
});
22+
23+
return (
24+
<div className="mb-2 mt-6">
25+
<div className="mb-4 flex items-center justify-between">
26+
<p className="text-base font-bold">Build Configuration</p>
27+
<FormCheckbox
28+
name={`services.${serviceIndex}.build.enabled`}
29+
label="Enable Build"
30+
/>
31+
</div>
32+
33+
{buildEnabled && (
34+
<div className="space-y-4 rounded-md border border-gray-200 p-4 dark:border-gray-500">
35+
<div className="flex gap-2 [&>div]:flex-1">
36+
<FormInput
37+
name={`services.${serviceIndex}.build.context`}
38+
label="Context"
39+
placeholder="."
40+
/>
41+
<FormInput
42+
name={`services.${serviceIndex}.build.dockerfile`}
43+
label="Dockerfile"
44+
placeholder="Dockerfile"
45+
/>
46+
</div>
47+
48+
<div className="space-y-2">
49+
<div className="flex items-center">
50+
<p className="text-sm font-semibold">Build Arguments</p>
51+
<button
52+
type="button"
53+
onClick={() => append({ key: '', value: '' })}
54+
className="btn btn-xs ml-4"
55+
>
56+
Add <Plus className="size-3" />
57+
</button>
58+
</div>
59+
60+
{fields.map((field, idx) => (
61+
<div className="flex items-center" key={field.id}>
62+
<div
63+
className={cn(
64+
'flex items-center divide-x divide-gray-200 rounded-md border border-gray-200 dark:divide-gray-500 dark:border-gray-500',
65+
{
66+
'divide-red-500 border-red-500 dark:divide-red-500 dark:border-red-500':
67+
control.getFieldState(
68+
`services.${serviceIndex}.volumes.${idx}`,
69+
).invalid,
70+
},
71+
)}
72+
key={field.id}
73+
>
74+
<FormInput
75+
label=""
76+
name={`services.${serviceIndex}.build.args.${idx}.key`}
77+
placeholder="Key"
78+
className="h-12 w-full rounded-s-md px-2"
79+
/>
80+
<FormInput
81+
label=""
82+
name={`services.${serviceIndex}.build.args.${idx}.value`}
83+
placeholder="Value"
84+
className="h-12 w-full rounded-e-md px-2"
85+
/>
86+
<button
87+
type="button"
88+
onClick={() => remove(idx)}
89+
className="btn btn-error rounded-e-md rounded-s-none"
90+
>
91+
<Trash2 />
92+
</button>
93+
</div>
94+
</div>
95+
))}
96+
</div>
97+
</div>
98+
)}
99+
</div>
100+
);
101+
};
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { FC } from 'react';
2+
import { Plus, Trash2 } from 'lucide-react';
3+
import { useFieldArray, useFormContext } from 'react-hook-form';
4+
import { FormInput } from '@/components/form/form-input';
5+
import { cn } from '@/lib/utils';
6+
7+
type ServiceDependsOnFieldsProps = {
8+
serviceIndex: number;
9+
};
10+
11+
const ServiceDependsOnFields: FC<ServiceDependsOnFieldsProps> = ({
12+
serviceIndex,
13+
}) => {
14+
const { control } = useFormContext();
15+
16+
const { fields, append, remove } = useFieldArray({
17+
control,
18+
name: `services.${serviceIndex}.depends_on`,
19+
});
20+
21+
return (
22+
<div className="mb-2 mt-6">
23+
<div className="mb-2 flex items-center">
24+
<p className="text-base font-bold">Depends On</p>
25+
26+
<button
27+
type="button"
28+
onClick={() => append('')}
29+
className="btn btn-xs ml-4"
30+
>
31+
Add <Plus className="size-3" />
32+
</button>
33+
</div>
34+
<div className="flex gap-4">
35+
{fields.map((field, depIdx) => (
36+
<div
37+
className={cn(
38+
'mb-4 flex items-center divide-x divide-gray-200 rounded-md border border-gray-200 dark:divide-gray-500 dark:border-gray-500 [&>div]:mb-0',
39+
)}
40+
key={field.id}
41+
>
42+
<FormInput
43+
id={`depends_on_${depIdx}`}
44+
name={`services.${serviceIndex}.depends_on.${depIdx}`}
45+
label=""
46+
placeholder="Service name"
47+
className="h-12 w-full rounded-s-md px-2 outline-none"
48+
/>
49+
50+
<button
51+
onClick={() => remove(depIdx)}
52+
className="btn btn-error rounded-e-md rounded-s-none"
53+
>
54+
<Trash2 />
55+
</button>
56+
</div>
57+
))}
58+
</div>
59+
</div>
60+
);
61+
};
62+
63+
export default ServiceDependsOnFields;

0 commit comments

Comments
 (0)