diff --git a/Queries/Application/index.js b/Queries/Application/index.js new file mode 100644 index 0000000..23635a4 --- /dev/null +++ b/Queries/Application/index.js @@ -0,0 +1,42 @@ +import apiClient from '../../utlis/apiClient'; +import { APPLICATION_ENDPOINT } from '../../utlis/apiEndpoints'; + +const url = APPLICATION_ENDPOINT; + +export const getApplication = async () => { + try { + const data = await apiClient.get(url); + return data; + } catch (error) { + throw new Error(error.response?.data?.error?.message || 'Failed to fetch applications'); + } +}; + +export const addEnvironment = async (id, values) => { + const updatedUrl = `${url}/${id}/environments`; + try { + const response = await apiClient.post(updatedUrl, values); + return response; + } catch (error) { + throw new Error(error.response?.data?.error?.message || 'Failed to add environment'); + } +}; + +export const getApplicationById = async (id) => { + const updatedUrl = `${url}/${id}`; + try { + const response = await apiClient.get(updatedUrl); + return response; + } catch (error) { + throw new Error(error.response?.data?.error?.message || 'Failed to fetch application'); + } +}; + +export const addApplication = async (values) => { + try { + const response = await apiClient.post(url, values); + return response; + } catch (error) { + throw new Error(error.response?.data?.error?.message || 'Failed to add application'); + } +}; diff --git a/Queries/DeploymentSpace/index.js b/Queries/DeploymentSpace/index.js new file mode 100644 index 0000000..4de7696 --- /dev/null +++ b/Queries/DeploymentSpace/index.js @@ -0,0 +1,14 @@ +import apiClient from '../../utlis/apiClient'; +import { ENVIRONMENT_ENDPOINT } from '../../utlis/apiEndpoints'; + +const url = ENVIRONMENT_ENDPOINT; + +export const addDeploymentConfig = async (id, values) => { + const updatedUrl = `${url}/${id}/deploymentspace`; + try { + const response = await apiClient.post(updatedUrl, values); + return response; + } catch (error) { + throw new Error(error.response?.data?.error?.message || 'Failed to add deployment space'); + } +}; diff --git a/README.md b/README.md index b34c254..5f0fef3 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Zop is a comprehensive tool for managing cloud infrastructure. It consists of th Run the following command to pull and start the Docker image for the zop-api: ```bash - docker run -d -p 8000:8000 --name zop-api zop.dev/zop-api:v0.0.1 + docker run -d -p 8000:8000 --name zop-api zopdev/zop-api:v0.0.1 ``` #### zop-ui @@ -32,7 +32,7 @@ Run the following command to pull and start the Docker image for the zop-api: Run the following command to pull and start the Docker image for the zop-ui: ```bash - docker run -d -p 3000:3000 -e NEXT_PUBLIC_API_BASE_URL='http://localhost:8000' --name zop-ui zop.dev/zop-ui:v0.0.1 + docker run -d -p 3000:3000 -e NEXT_PUBLIC_API_BASE_URL='http://localhost:8000' --name zop-ui zopdev/zop-ui:v0.0.1 ``` > **Note:** The environment variable `NEXT_PUBLIC_API_BASE_URL` is used by zop-ui to connect to the diff --git a/components/Animation/animateHeight.js b/components/Animation/animateHeight.js new file mode 100644 index 0000000..953e25a --- /dev/null +++ b/components/Animation/animateHeight.js @@ -0,0 +1,57 @@ +import React, { useEffect, useRef, useState } from 'react'; + +const AnimateHeight = ({ children, className, dependency, customAnimation }) => { + const [height, setHeight] = useState(0); + const parentRef = useRef(null); + + let time; + const handleChange = () => { + if (!parentRef.current) { + return; + } + const childFirst = parentRef.current.children[0]; + const heightChild = childFirst.getBoundingClientRect().height; + setHeight(heightChild); + time = setTimeout(() => { + if (heightChild !== childFirst.getBoundingClientRect().height) { + handleChange(); + } + }, 100); + }; + + useEffect(() => { + handleChange(); + return () => { + clearTimeout(time); + }; + }, [children, dependency]); + + useEffect(() => { + if (!parentRef.current) { + return; + } + const resizeOb = new ResizeObserver(handleChange); + const childFirst = parentRef.current.children[0]; + if (childFirst) resizeOb.observe(childFirst); + + return () => { + if (childFirst) resizeOb.unobserve(childFirst); + }; + }, [parentRef.current]); + + return ( +
+
+
{children}
+
+
+ ); +}; + +export default AnimateHeight; diff --git a/components/BreadCrumb/index.js b/components/BreadCrumb/index.js new file mode 100644 index 0000000..2efaf6c --- /dev/null +++ b/components/BreadCrumb/index.js @@ -0,0 +1,42 @@ +import { ChevronRightIcon } from '@heroicons/react/20/solid'; +import Link from 'next/link'; + +export default function BreadCrumbComp({ breadcrumbList, style }) { + return ( + <> + {breadcrumbList[1]?.name !== 'loading...' && ( + + )} + + ); +} diff --git a/components/Cards/applicationCard.js b/components/Cards/applicationCard.js new file mode 100644 index 0000000..4665ab5 --- /dev/null +++ b/components/Cards/applicationCard.js @@ -0,0 +1,94 @@ +import React, { useEffect } from 'react'; +import { useRouter } from 'next/navigation'; +import { formatTime } from '../../utlis/helperFunc'; + +const ApplicationCard = ({ data, view }) => { + const router = useRouter(); + + useEffect(() => { + if (data?.id) { + router.prefetch(`/applications/${data?.id}`); + } + }, [data?.id, router]); + + const handleRedirect = () => { + router.push(`/applications/${data?.id}/deploymentSpace`); + }; + + return ( +
+
+

{data?.name}

+
+ +
+
+ {data?.services !== 0 && ( +

{data?.environments?.length}

+ )} +

+   + {data?.environments?.length === 1 + ? 'Environment' + : data?.environments?.length > 1 + ? 'Environments' + : ' '} +

+
+
+ + {data?.environments?.length > 1 ? ( +
+ {data.environments.map((single) => ( +
+
+ {/*

Environment

*/} +

{single.name}

+
+ + {/*
+
+

Order

+
+

{single.level ?? '1'}

+
*/} +
+ ))} +
+ ) : ( +
+
+

Environment

+

{data?.environments?.[0]?.name}

+
+ +
+

Order

+

{data?.environments?.[0]?.level}

+
+
+ )} + +
+ Updated At{' '} + {/* + {item?.updatedByIdentifier || item?.createdByIdentifier} + */} + {formatTime(data?.updatedAt)} +
+ {/*

+ {formatTime(data?.updatedAt)} +

*/} +
+ ); +}; + +export default ApplicationCard; diff --git a/components/Cards/cloudAccountCard.js b/components/Cards/cloudAccountCard.js index 535fade..9f9b005 100644 --- a/components/Cards/cloudAccountCard.js +++ b/components/Cards/cloudAccountCard.js @@ -1,127 +1,24 @@ import React from 'react'; import { formatTime } from '../../utlis/helperFunc'; -import { - PROVIDER_ICON_MAPPER, - // REFRESH_STATUS, - // colourCode, -} from '../../constant'; -// import DotWithProgress from "../Loader/DotWithProgression"; -// import { LogSvg } from "../../svg/sidebar/logs"; -// import IconButton from "../FormComponent/IconButton"; -// import RefreshIcon from "../../svg/refresh"; -// import Tooltip from "../FormComponent/toolTip"; -// import { useRouter } from "next/router"; - -const CloudAccountCard = ({ - item, - view, - // handleLogsOpen, - // handleRetryStatus, - // putToProvider, -}) => { - // const router = useRouter(); - - // useEffect(() => { - // if (item?.id) { - // router.prefetch(`/cloud-accounts/${item.id}/infrastructure`); - // } - // }, [item?.id, router]); - - // const handleRetry = (e, id) => { - // e.stopPropagation(); - // handleRetryStatus(id); - // }; - - // const latestLog = (e, data) => { - // e.stopPropagation(); - // handleLogsOpen(data); - // }; - - // const handleRedirect = () => { - // router.push(`/cloud-accounts/${item?.id}/infrastructure`); - // }; +import { PROVIDER_ICON_MAPPER } from '../../constant'; +const CloudAccountCard = ({ item }) => { return (
{PROVIDER_ICON_MAPPER?.[item?.provider]} {item?.name} - {/* {view === "cloudAccount" && ( - -
- {REFRESH_STATUS?.includes(item?.status) ? ( - - ) : ( - <> - - - )} -
-
- )} */} - {/* {item?.retry && ( - -
- handleRetry(e, item?.id)} - > - - -
-
- )} */} - {/* {view === "cloudAccount" && - REFRESH_STATUS?.includes(item?.status) && - item?.clusterId?.defaultId && ( -
- - latestLog(e, item)}> - - - -
- )} */}
{item?.providerId} - {/* - - {item?.svcGroup === 1 - ? `1 App` - : item?.svcGroup > 1 - ? `${item?.svcGroup} Apps` - : ""} - - */}
- Updated At{' '} - {/* - {item?.updatedByIdentifier || item?.createdByIdentifier} - */} - {formatTime(item?.updatedAt)} + Updated At {formatTime(item?.updatedAt)}
- - {/*
- {formatTime(item?.updatedAt)} -
*/}
); }; diff --git a/components/Input/inputWithButton.js b/components/Input/inputWithButton.js new file mode 100644 index 0000000..0948192 --- /dev/null +++ b/components/Input/inputWithButton.js @@ -0,0 +1,67 @@ +import { PlusCircleIcon } from '@heroicons/react/16/solid'; +import { useEffect, useState } from 'react'; + +export default function InputWithButton({ + type = '', + value, + error = false, + name, + required, + testExp, + helperText, + helperTextClass, + errorTextClass, + errorText, + onChange, + onClick, + inputProps, + className, + ...props +}) { + const [internalError, setInternalError] = useState(error); + useEffect(() => { + setInternalError(error); + }, [error]); + return ( +
+
+
+ { + if (testExp && !new RegExp(testExp).test(e.target.value)) { + setInternalError(true); + } else { + setInternalError(error); + } + if (onChange) onChange(e); + }} + /> + {/*
+ +
+ {helperText != null && ( +

{helperText}  

+ )} + {internalError && errorText && ( +

{errorText}  

+ )} +
+ ); +} diff --git a/components/JsonTable/index.js b/components/JsonTable/index.js new file mode 100644 index 0000000..398a1bf --- /dev/null +++ b/components/JsonTable/index.js @@ -0,0 +1,348 @@ +import React, { useEffect, useRef } from 'react'; + +const INITIAL_CELL_STYLES = { + minWidth: 500, + maxWidth: 500, + wordBreak: 'break-all', + marginRight: 40, + padding: 10, + paddingBottom: 10, +}; + +const INITIAL_KEY_CELL_STYLES = { + minWidth: 200, + maxWidth: 200, + wordBreak: 'break-all', + marginRight: 40, + paddingTop: 10, +}; + +const COLOURS = { + BORDER: '#e5e7eb', +}; + +const JsonComparisonTable = ({ + data, + keysStyles = {}, + screenOffsetTop, + isKeysVisible = false, + difference = false, + isKeysScrollable = false, + isAlternateColStyle = true, + isAlternateRowStyle = true, +}) => { + const dataTableRef = useRef(null); + const dataHeaderRef = useRef(null); + const parentRef = useRef(null); + const keyRefs = useRef([]); + const cellRefs = useRef([]); + + useEffect(() => { + const handleScrollBody = (e) => { + if (dataHeaderRef.current) { + dataHeaderRef.current.scrollLeft = e.target.scrollLeft; + } + }; + + const handleScrollHeader = (e) => { + if (dataHeaderRef.current) { + dataTableRef.current.scrollLeft = e.target.scrollLeft; + } + }; + + if (dataTableRef.current) { + dataTableRef.current.addEventListener('scroll', handleScrollBody); + dataHeaderRef.current.addEventListener('scroll', handleScrollHeader); + } + + return () => { + if (dataTableRef.current) { + dataTableRef.current.removeEventListener('scroll', handleScrollBody); + dataHeaderRef.current.removeEventListener('scroll', handleScrollHeader); + } + }; + }, [dataTableRef]); + + useEffect(() => { + const resizeObserver = new ResizeObserver((entries) => { + for (const entry of entries) { + const height = entry.contentRect.height; + const index = Number(entry.target.getAttribute('data-index')); + const keyElement = keyRefs.current[index]; + const maxHeight = Math.max(height, keyElement?.clientHeight); + if (keyElement) { + // Check if keyElement exists before accessing its style + keyElement.style.height = `${maxHeight}px`; + } + } + }); + + keyRefs.current.forEach((keyRef, index) => { + let maxHeight = 0; + if (keyRef && cellRefs.current[index]) { + for (const elements of cellRefs.current[index]) { + resizeObserver.observe(elements); + if (elements.clientHeight > maxHeight) { + maxHeight = Math.max(maxHeight, elements.clientHeight); + } + } + } + + keyRef.style.height = `${maxHeight}px`; + }); + + return () => { + resizeObserver.disconnect(); + }; + }, [data]); + + const getColumStyles = (dataColumn) => { + let styles = { ...INITIAL_CELL_STYLES }; + + if (dataColumn) { + styles = { + ...styles, + ...(dataColumn.styles ?? {}), + }; + } + + return styles; + }; + + const keys = new Set(); + + for (const columns of data) { + for (const key in columns.data) { + keys.add(key); + } + } + + const keysOfData = Array.from(keys); + + const keyCellStyles = { ...INITIAL_KEY_CELL_STYLES, ...keysStyles }; + + const getFormattedCell = (dataCell) => { + if (typeof dataCell === 'string' || typeof dataCell === 'number') { + return dataCell; + } + + if (typeof dataCell === 'function') { + return dataCell(); + } + + if (typeof dataCell === 'object') { + return JSON.stringify(dataCell); + } + + return ''; + }; + + const isKeyDifferent = (key, value) => { + if (data.length === 0) return null; + if (!value) return null; + if (typeof value !== 'string') return null; + + for (let i = 1; i < data.length; i++) { + if (data[i].data[key] && data[i].data[key] !== value) { + return true; + } + } + + return false; + }; + + return ( +
+
+
+ {isKeysVisible && ( +
+ {/* Key */} +
+ )} +
+ {data.map((single, i) => ( +
+
+ {single.label} +
+
+ ))} +
+
+ +
+ {isKeysVisible && ( +
+ {keysOfData.map((key, iRow) => ( +
(keyRefs.current[iRow] = el)} + > + + {key} + +
+ ))} +
+ )} + +
+ {keysOfData.map((key, iRow) => ( +
+ {data.map((single, i) => ( +
{ + if (cellRefs.current[iRow]) { + cellRefs.current[iRow][i] = el; + } else { + cellRefs.current[iRow] = [el]; + } + }} + > +
+ {difference && isKeyDifferent(key, single.data[key]) === true && ( +
+ +
+ )} + {getFormattedCell(single.data[key])} +
+
+ ))} +
+ ))} +
+
+
+
+ ); +}; + +export default function JsonComparisonTableContainer({ + data, + isKeysVisible, + screenOffsetTop, + keysStyles, + difference, + isKeysScrollable, + isAlternateColStyle, + isAlternateRowStyle, +}) { + return ( +
+ +
+ ); +} diff --git a/components/Loaders/LinearLoader/index.js b/components/Loaders/LinearLoader/index.js new file mode 100644 index 0000000..26bb3e4 --- /dev/null +++ b/components/Loaders/LinearLoader/index.js @@ -0,0 +1,19 @@ +import React from 'react'; + +export default function CustomLinearProgress({ isLoading, classNames = {} }) { + return ( +
+ +
+ ); +} diff --git a/components/Table/table.js b/components/Table/table.js new file mode 100644 index 0000000..a1ffe5a --- /dev/null +++ b/components/Table/table.js @@ -0,0 +1,28 @@ +import React from 'react'; +import TableHeader from './tableHeader'; +import TableBody from './tableBody'; + +const Table = ({ headers, data, onEdit, onDelete, action }) => { + return ( +
+ + + {headers.map((header) => ( + + ))} + + + + +
+
+ ); +}; + +export default Table; diff --git a/components/Table/tableBody.js b/components/Table/tableBody.js new file mode 100644 index 0000000..1fa356d --- /dev/null +++ b/components/Table/tableBody.js @@ -0,0 +1,39 @@ +import React from 'react'; + +const TableBody = ({ data, headers, onEdit, onDelete, action = true }) => { + return ( + + {data?.map((row, rowIndex) => ( + + {headers.map((header) => ( + + {/* Allow React components or plain text */} + {typeof row[header.key] === 'function' ? row[header.key]() : row[header.key]} + + ))} + {action && ( + +
+ {onEdit && ( + + )} + +
+ + )} + + ))} + + ); +}; + +export default TableBody; diff --git a/components/Table/tableHeader.js b/components/Table/tableHeader.js new file mode 100644 index 0000000..ffa02df --- /dev/null +++ b/components/Table/tableHeader.js @@ -0,0 +1,26 @@ +import React from 'react'; + +const TableHeader = ({ headers, action = true }) => { + return ( + + + {headers.map((header) => ( + + {header.label} + + ))} + {action && ( + Actions + )} + + + ); +}; + +export default TableHeader; diff --git a/hooks/Header/addHeader.js b/hooks/Header/addHeader.js new file mode 100644 index 0000000..9b9f0c4 --- /dev/null +++ b/hooks/Header/addHeader.js @@ -0,0 +1,37 @@ +import { useContext, useEffect } from 'react'; +import { getApplication } from '../../Queries/Application'; + +const { AppContext } = require('../../libs/context'); +const { getCloudAccounts } = require('../../Queries/CloudAccount'); + +export function useInitializeHeader() { + const { setAppData } = useContext(AppContext); + + const handleAppData = (entity, values) => + setAppData((prevValues) => ({ ...prevValues, [entity]: values })); + + const fetchCloudAccounts = async () => { + try { + const data = await getCloudAccounts(); + handleAppData('CLOUD_ACCOUNT_DATA', { data: data.data, isSuccess: true }); + } catch (error) { + handleAppData('CLOUD_ACCOUNT_DATA', { data: [], isSuccess: false }); + } + }; + + const fetchApplications = async () => { + try { + const data = await getApplication(); + handleAppData('APPLICATION_DATA', { data: data.data, isSuccess: true }); + } catch (error) { + handleAppData('APPLICATION_DATA', { data: [], isSuccess: false }); + } + }; + + useEffect(() => { + fetchCloudAccounts(); + fetchApplications(); + }, []); + + return {}; +} diff --git a/hooks/application/addApplication.js b/hooks/application/addApplication.js new file mode 100644 index 0000000..c698fa0 --- /dev/null +++ b/hooks/application/addApplication.js @@ -0,0 +1,41 @@ +import { useRouter } from 'next/navigation'; +import { useState } from 'react'; +import { addApplication } from '../../Queries/Application'; + +// import { addApplication } from '../../Queries/Application'; + +const useAddApplication = () => { + const router = useRouter(); + const [values, setValues] = useState({ + name: '', + environments: [], + }); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const handleSubmit = async (values) => { + setIsLoading(true); + setError(null); + + try { + const data = await addApplication(values); + setError(null); + router.push('/applications'); + return data; + } catch (error) { + setError(error.message); + } finally { + setIsLoading(false); + } + }; + + return { + values, + setValues, + handleSubmit, + isLoading, + error, + }; +}; + +export default useAddApplication; diff --git a/hooks/application/getApplicationList.js b/hooks/application/getApplicationList.js new file mode 100644 index 0000000..3d0404c --- /dev/null +++ b/hooks/application/getApplicationList.js @@ -0,0 +1,27 @@ +import { useState, useEffect } from 'react'; +import { getApplication } from '../../Queries/Application'; + +const useApplicationList = () => { + const [applications, setApplications] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchApplications = async () => { + try { + const data = await getApplication(); + setApplications(data); + } catch (error) { + setError(error.message); + } finally { + setLoading(false); + } + }; + + fetchApplications(); + }, []); + + return { applications, loading, error }; +}; + +export default useApplicationList; diff --git a/hooks/cloudAccount/addCloudAccount.js b/hooks/cloudAccount/addCloudAccount.js index 98b9420..5942cc4 100644 --- a/hooks/cloudAccount/addCloudAccount.js +++ b/hooks/cloudAccount/addCloudAccount.js @@ -21,7 +21,7 @@ const formateData = async (values, provider) => { const finalValues = { // orgId: tokenInfo?.["tenant-id"], name: values.name, - provider: provider, + provider, // configs: { // name: // provider !== "aws" diff --git a/hooks/deploymentSpace/addDeploymentSpace.js b/hooks/deploymentSpace/addDeploymentSpace.js new file mode 100644 index 0000000..b658fc3 --- /dev/null +++ b/hooks/deploymentSpace/addDeploymentSpace.js @@ -0,0 +1,37 @@ +import { useParams, useRouter } from 'next/navigation'; +import { useState } from 'react'; +import { addDeploymentConfig } from '../../Queries/DeploymentSpace'; + +const useAddDeploymentConfig = () => { + const router = useRouter(); + const params = useParams(); + // const [values, setValues] = useState({ name: '' }); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const handleSubmit = async (id, values) => { + setIsLoading(true); + setError(null); + + try { + const data = await addDeploymentConfig(id, values); + setError(null); + router.push(`/applications/${params?.['application-id']}/deploymentSpace`); + return data; + } catch (error) { + setError(error.message); + } finally { + setIsLoading(false); + } + }; + + return { + // values, + // setValues, + handleSubmit, + isLoading, + error, + }; +}; + +export default useAddDeploymentConfig; diff --git a/hooks/deploymentSpace/getDeploymentSpace.js b/hooks/deploymentSpace/getDeploymentSpace.js new file mode 100644 index 0000000..4581ed4 --- /dev/null +++ b/hooks/deploymentSpace/getDeploymentSpace.js @@ -0,0 +1,108 @@ +import { useEffect, useState } from 'react'; +import { getApplicationById } from '../../Queries/Application'; +import { useParams } from 'next/navigation'; + +const datas = { + data: { + id: 1, + name: 'application_1', + environments: [ + { + id: 1, + name: 'prod', + level: 1, + applicationID: 1, + deploymentSpace: null, + createdAt: '2024-12-15T18:11:52Z', + updatedAt: '2024-12-15T18:11:52Z', + }, + { + id: 2, + name: 'test', + level: 3, + applicationID: 1, + deploymentSpace: null, + createdAt: '2024-12-15T18:11:52Z', + updatedAt: '2024-12-15T18:11:52Z', + }, + { + id: 3, + name: 'stage', + level: 2, + applicationID: 1, + deploymentSpace: null, + createdAt: '2024-12-15T18:11:52Z', + updatedAt: '2024-12-15T18:11:52Z', + }, + { + id: 4, + name: 'env3', + level: 0, + applicationID: 1, + deploymentSpace: null, + createdAt: '2024-12-15T18:12:07Z', + updatedAt: '2024-12-15T18:12:07Z', + }, + { + id: 5, + name: 'Test-env', + level: 4, + applicationID: 1, + deploymentSpace: null, + createdAt: '2024-12-16T06:29:49Z', + updatedAt: '2024-12-16T06:29:49Z', + }, + { + id: 6, + name: 'stage', + level: 1, + applicationId: 1, + deploymentSpace: { + name: 'exampleName', + next: { + name: 'gke', + next: { + name: 'test-gcp-04dec', + next: { + name: 'monitoring', + next: null, + }, + }, + }, + }, + createdAt: '2024-12-15T16:05:06Z', + updatedAt: '2024-12-15T16:05:06Z', + }, + ], + createdAt: '2024-12-15T18:11:52Z', + updatedAt: '2024-12-15T18:11:52Z', + }, +}; + +const useGetDeploymentSpace = () => { + const params = useParams(); + + const [value, setValue] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const getAppById = async () => { + try { + const data = await getApplicationById(params?.['application-id']); + setValue(data); + return value; + } catch (e) { + setError(e); + } finally { + setLoading(false); + } + }; + + getAppById(); + }, []); + + return { value, loading, error }; +}; + +export default useGetDeploymentSpace; diff --git a/hooks/environment/addEnvironment.js b/hooks/environment/addEnvironment.js new file mode 100644 index 0000000..eec065e --- /dev/null +++ b/hooks/environment/addEnvironment.js @@ -0,0 +1,37 @@ +import { useParams, useRouter } from 'next/navigation'; +import { useState } from 'react'; +import { addEnvironment } from '../../Queries/Application'; + +const useAddEnvironment = () => { + const router = useRouter(); + const params = useParams(); + const [values, setValues] = useState({ name: '' }); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const handleSubmit = async (id, values) => { + setIsLoading(true); + setError(null); + + try { + const data = await addEnvironment(id, values); + setError(null); + router.push(`/applications/${params?.['application-id']}/deploymentSpace`); + return data; + } catch (error) { + setError(error.message); + } finally { + setIsLoading(false); + } + }; + + return { + values, + setValues, + handleSubmit, + isLoading, + error, + }; +}; + +export default useAddEnvironment; diff --git a/libs/context.js b/libs/context.js new file mode 100644 index 0000000..2459265 --- /dev/null +++ b/libs/context.js @@ -0,0 +1,18 @@ +'use client'; + +import { createContext, useState } from 'react'; + +function ContextProvider({ children }) { + const [appData, setAppData] = useState({ + CLOUD_ACCOUNT_DATA: { data: [], isSuccess: false }, + // CLUSTERS_DATA: { data: [], isSuccess: false, isError: false }, + // NAMESPACE_DATA: { data: [], isSuccess: false }, + APPLICATION_DATA: { data: [], isSuccess: false }, + }); + + return {children}; +} + +export default ContextProvider; + +export const AppContext = createContext(); diff --git a/partials/Environment/addForm.js b/partials/Environment/addForm.js new file mode 100644 index 0000000..340e907 --- /dev/null +++ b/partials/Environment/addForm.js @@ -0,0 +1,65 @@ +import React from 'react'; +import Label from '../../components/Label'; +import Input from '../../components/Input'; +import useAddEnvironment from '../../hooks/environment/addEnvironment'; +import Button from '../../components/Button'; +import ErrorComponent from '../../components/ErrorComponent'; +import { useParams, usePathname, useRouter } from 'next/navigation'; + +const AddEnvironment = () => { + const router = useParams(); + const { values, setValues, handleSubmit, isLoading, error } = useAddEnvironment(); + + const handleChange = (e) => { + setValues({ ...values, [e.target.name]: e.target.value }); + }; + + const handleOnSubmit = (e) => { + e.preventDefault(); + handleSubmit(router?.['application-id'], values); + }; + + return ( +
+
+
+ +
+ } + // inputProps={provider === 'azure' ? { minLength: 6 } : { maxLength: 16, minLength: 6 }} + /> +
+
+ +
+ {error && ( + + )} +
+
+
+ +
+
+ ); +}; + +export default AddEnvironment; diff --git a/partials/Header/index.js b/partials/Header/index.js index 9de962e..cc67dcd 100644 --- a/partials/Header/index.js +++ b/partials/Header/index.js @@ -1,14 +1,17 @@ 'use client'; -import { Fragment, useState } from 'react'; +import { Fragment, useContext, useEffect, useState } from 'react'; import { Disclosure, Transition, Dialog } from '@headlessui/react'; import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline'; import AppLogo from '../../svg/appLogo'; import Image from 'next/image'; import Link from 'next/link'; -import { useRouter } from 'next/navigation'; +import { usePathname, useRouter } from 'next/navigation'; import CloudAccountSVG from '../../svg/cloudAccount'; import APP_LOGO_IMAGE from '../../public/applogoWithText.svg'; +import ApplicationSvg from '../../svg/application'; +import { useInitializeHeader } from '../../hooks/Header/addHeader'; +import { AppContext } from '../../libs/context'; function classNames(...classes) { return classes.filter(Boolean).join(' '); @@ -16,34 +19,23 @@ function classNames(...classes) { export function TopBar() { const router = useRouter(); - const [tab] = useState(0); + const pathname = usePathname(); + useInitializeHeader(); + const [tab, setTab] = useState(); const [sidebarOpen, setSidebarOpen] = useState(false); + const { appData } = useContext(AppContext); - // useEffect(() => { - // let DashboardView = navigation - // .map(function (ele) { - // return `/${ele.routeName.split("/")[1]}`; - // }) - // .indexOf(`/${router.asPath.split("/")[1]}`); + useEffect(() => { + let DashboardView = navigation + .map(function (ele) { + return `/${ele.routeName.split('/')[1]}`; + }) + .indexOf(`/${pathname.split('/')[1]}`); - // DashboardView = DashboardView !== -1 ? DashboardView || 0 : -1; - // if ( - // DashboardView === -1 && - // `${router.asPath.split("/")[1]}` === "table" && - // router?.query?.["cloud-account-id"] === undefined - // ) { - // setTab(0); - // } else if ( - // DashboardView === -1 && - // `${router.asPath.split("/")[1]}` === "table" && - // router?.query?.["cloud-account-id"] && - // router?.query?.["application-id"] === undefined - // ) { - // setTab(1); - // } else { - // setTab(DashboardView); - // } - // }, [router.route]); + DashboardView = DashboardView !== -1 ? DashboardView || 0 : -1; + + setTab(DashboardView); + }, [pathname]); // const handleAppData = (entity, values) => // setAppData((prevValues) => ({ ...prevValues, [entity]: values })); @@ -118,14 +110,14 @@ export function TopBar() { isLoading: false, icon: CloudAccountSVG, }, - // { - // name: "Applications", - // routeName: "/applications", - // hover: true, - // selected: true, - // isLoading: false, - // icon: ApplicationSvg, - // }, + { + name: 'Applications', + routeName: '/applications', + hover: true, + selected: true, + isLoading: false, + icon: ApplicationSvg, + }, // { // name: "Observability", // routeName: diff --git a/partials/application/createForm.js b/partials/application/createForm.js new file mode 100644 index 0000000..f32128a --- /dev/null +++ b/partials/application/createForm.js @@ -0,0 +1,148 @@ +'use client'; + +import React, { useState } from 'react'; +import Label from '../../components/Label'; +import Input from '../../components/Input'; +import InputWithButton from '../../components/Input/inputWithButton'; +import DraggableList from '../draggableList'; +import Button from '../../components/Button'; +import useAddApplication from '../../hooks/application/addApplication'; +import ErrorComponent from '../../components/ErrorComponent'; + +const CreateAppForm = () => { + const { values, setValues, handleSubmit, isLoading, error } = useAddApplication(); + + const [chips, setChips] = useState([ + { name: 'prod', selected: false }, + { name: 'stage', selected: false }, + { name: 'test', selected: false }, + ]); + const [inputValue, setInputValue] = useState(''); + + // Handle input change + const handleInputChange = (e) => setInputValue(e.target.value); + + // Add new chip + const handleAddChip = () => { + const chipName = inputValue.trim(); + if (chipName && !chips.some((chip) => chip.name === chipName)) { + setChips([...chips, { name: chipName, selected: true }]); + setInputValue(''); // Clear input + } + }; + + // Toggle chip selection + const toggleChipSelection = (name) => { + setChips((prevChips) => + prevChips.map((chip) => (chip.name === name ? { ...chip, selected: !chip.selected } : chip)), + ); + }; + + const handleChange = (e) => { + setValues({ ...values, [e.target.name]: e.target.value }); + }; + + const handleListUpdate = (updatedList) => { + setValues({ ...values, environments: updatedList }); + }; + + const handleOnSubmit = (e) => { + e.preventDefault(); + handleSubmit(values); + }; + + return ( +
+
+
+ +
+ } + // inputProps={provider === 'azure' ? { minLength: 6 } : { maxLength: 16, minLength: 6 }} + /> +
+
+
+ +
+ } + // inputProps={provider === 'azure' ? { minLength: 6 } : { maxLength: 16, minLength: 6 }} + /> +
+
+
+ +
+ {chips.map((chip) => ( + toggleChipSelection(chip.name)} + className={`inline-flex items-center rounded-full px-3 py-2 text-xs font-medium cursor-pointer hover:bg-gray-200 ${ + chip.selected + ? 'bg-primary-100 text-primary-600 hover:!bg-primary-200 ' + : 'bg-gray-100 text-gray-600' + }`} + > + {chip.name} + + ))} +
+
+ +
+ {values?.environments?.length > 0 && ( + + )} + + {values?.environments?.length > 0 && ( +

+ *Rearrange the order by drag and drop +

+ )} +
+
+ +
+
+ {error && ( + + )} +
+
+ +
+ +
+
+ ); +}; + +export default CreateAppForm; diff --git a/partials/cloudAccount/createForm.js b/partials/cloudAccount/createForm.js index 910ddfb..278852b 100644 --- a/partials/cloudAccount/createForm.js +++ b/partials/cloudAccount/createForm.js @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import Button from '../../components/Button'; import Label from '../../components/Label'; import Input from '../../components/Input'; diff --git a/partials/draggableList/index.js b/partials/draggableList/index.js new file mode 100644 index 0000000..ea839d6 --- /dev/null +++ b/partials/draggableList/index.js @@ -0,0 +1,71 @@ +import { ChevronRightIcon } from '@heroicons/react/20/solid'; +import { useEffect, useRef, useState } from 'react'; + +export default function DraggableList({ chips, handleListUpdate = () => {}, disableDrag = false }) { + const [pages, setPages] = useState([]); + + useEffect(() => { + const updatedList = chips + ?.filter((item) => item.selected) + .map((item, index) => ({ ...item, level: index + 1 })); + setPages(updatedList); + handleListUpdate(updatedList); + }, [chips]); + + const draggingItem = useRef(); + const dragOverItem = useRef(); + + const handleDragStart = (e, position) => { + if (disableDrag) return; + draggingItem.current = position; + }; + + const handleDragEnter = (e, position) => { + if (disableDrag) return; + dragOverItem.current = position; + const listCopy = [...pages]; + const draggingItemContent = listCopy[draggingItem.current]; + listCopy.splice(draggingItem.current, 1); + listCopy.splice(dragOverItem.current, 0, draggingItemContent); + + // Update orders based on the new position + const updatedList = listCopy.map((item, index) => ({ + ...item, + order: index + 1, // Update the `order` based on position + })); + + draggingItem.current = dragOverItem.current; + dragOverItem.current = null; + setPages(updatedList); + handleListUpdate(updatedList); + }; + + return ( + + ); +} diff --git a/partials/nestedList/deploymentConfig.js b/partials/nestedList/deploymentConfig.js new file mode 100644 index 0000000..eedde84 --- /dev/null +++ b/partials/nestedList/deploymentConfig.js @@ -0,0 +1,48 @@ +function deepMerge(target, source) { + for (const key in source) { + if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) { + target[key] = target[key] || {}; + deepMerge(target[key], source[key]); + } else { + target[key] = source[key]; + } + } +} + +function transformArray(data) { + return data.reduce((result, item) => { + const keys = item.type.split('.'); // Split the type by '.' for nesting + let currentLevel = result; + + keys.forEach((key, index) => { + if (!currentLevel[key]) { + currentLevel[key] = {}; + } + if (index === keys.length - 1) { + // Merge the item's properties, including the 'type' key, into the deepest level + Object.assign(currentLevel[key], item); + } else { + currentLevel = currentLevel[key]; + } + }); + + return result; + }, {}); +} + +export function transformDeploymentConfigData(input) { + const result = {}; + + for (const [key, value] of Object.entries(input)) { + if (Array.isArray(value)) { + const transformedArray = transformArray(value); + deepMerge(result, transformedArray); + } else if (value && typeof value === 'object' && 'type' in value) { + result[value.type] = value; + } else { + result[key] = value; + } + } + + return result; +} diff --git a/partials/nestedList/index.js b/partials/nestedList/index.js new file mode 100644 index 0000000..042b967 --- /dev/null +++ b/partials/nestedList/index.js @@ -0,0 +1,248 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import axios from 'axios'; +import Label from '../../components/Label'; +import SimpleLoader from '../../components/Loaders/SimpleLoader'; +import AnimateHeight from '../../components/Animation/animateHeight'; +import useAddDeploymentConfig from '../../hooks/deploymentSpace/addDeploymentSpace'; +import { useParams } from 'next/navigation'; +import Button from '../../components/Button'; +import ErrorComponent from '../../components/ErrorComponent'; +import { transformDeploymentConfigData } from './deploymentConfig'; + +const StepperComponent = () => { + const params = useParams(); + const { handleSubmit, isLoading, error } = useAddDeploymentConfig(); + + const [step, setStep] = useState(1); + const [cloudAccounts, setCloudAccounts] = useState([]); + const [selectedCloudAccount, setSelectedCloudAccount] = useState(null); + const [deploymentOptions, setDeploymentOptions] = useState([]); + const [selectedOption, setSelectedOption] = useState(null); + const [dropdowns, setDropdowns] = useState([]); + const [loading, setLoading] = useState(false); + const [showSubmit, setShowSubmit] = useState(false); + + // Fetch cloud accounts initially + useEffect(() => { + const fetchCloudAccounts = async () => { + setLoading(true); + try { + const { data } = await axios.get('http://localhost:8000/cloud-accounts'); + setCloudAccounts(data?.data || []); + } catch (error) { + console.error('Error fetching cloud accounts:', error); + } finally { + setLoading(false); + } + }; + + fetchCloudAccounts(); + }, []); + + // Handle cloud account selection + const handleCloudAccountChange = async (selectedData) => { + setSelectedCloudAccount(selectedData); + resetSteps(2); + setLoading(true); + + try { + const { data } = await axios.get( + `http://localhost:8000/cloud-accounts/${selectedData?.id}/deployment-space/options`, + ); + setDeploymentOptions(data?.data || []); + } catch (error) { + console.error('Error fetching deployment options:', error); + } finally { + setLoading(false); + } + }; + + // Handle deployment option selection + const handleDeploymentOptionChange = async (option) => { + setSelectedOption(option); + resetSteps(3); + + setLoading(true); + + try { + const { data } = await axios.get(`http://localhost:8000${option.path}`); + if (data?.data?.next) { + addDropdown(data.data.options || [], data.data.next, data.data.metadata); + } else { + alert('All selections complete.'); + } + } catch (error) { + console.error('Error fetching additional options:', error); + } finally { + setLoading(false); + } + }; + + // Handle dynamic dropdown changes + const handleDropdownChange = async (index, selectedOption) => { + const updatedDropdowns = [...dropdowns]; + updatedDropdowns[index].selected = selectedOption; + setDropdowns(updatedDropdowns.slice(0, index + 1)); + + const next = updatedDropdowns[index].next; + if (!next) { + setShowSubmit(true); + return; + } + + const queryParams = buildQueryParams(next.params, selectedOption); + const apiUrl = `http://localhost:8000${next.path}?${queryParams}`; + + setLoading(true); + + try { + const { data } = await axios.get(apiUrl); + if (data?.data?.options) { + addDropdown(data.data.options || [], data.data.next, data.data.metadata); + } else { + alert('All selections complete.'); + } + } catch (error) { + console.error('Error fetching next options:', error); + } finally { + setLoading(false); + } + }; + + // Utility: Reset steps and dropdowns + const resetSteps = (newStep) => { + setStep(newStep); + // setDeploymentOptions([]); + setDropdowns([]); + }; + + // Utility: Add a new dropdown + const addDropdown = (options, next, metadata) => { + setDropdowns((prev) => [...prev, { options, selected: null, next, metadata }]); + }; + + // Utility: Build query parameters for API requests + const buildQueryParams = (params, option) => { + const queryParams = new URLSearchParams(); + for (const key in params) { + if (option[params[key]] !== undefined) { + queryParams.append(key, option[params[key]]); + } + } + return queryParams.toString(); + }; + + // Handle submit button click + const handleFormSubmit = () => { + const selectedData = { + cloudAccount: selectedCloudAccount, + deploymentOption: selectedOption, + dropdownSelections: dropdowns.map((dropdown) => dropdown.selected), + }; + + const updatedData = transformDeploymentConfigData(selectedData); + + handleSubmit(params?.['environment-id'], updatedData); + }; + + return ( + +
+ {/* Step 1: Cloud Accounts */} + {step >= 1 && ( +
+ + +
+ )} + {/* Step 2: Deployment Options */} + {step >= 2 && ( +
+ + +
+ )} + {/* Step 3+: Dynamic Dropdowns */} + {dropdowns.map((dropdown, index) => ( +
+ + +
+ ))} + {/* Submit Button */} + {loading && } +
+
+ {error && ( + + )} +
+
+ {showSubmit && ( + + )} +
+
+ ); +}; + +export default StepperComponent; diff --git a/partials/sideBar/index.js b/partials/sideBar/index.js new file mode 100644 index 0000000..ca478a1 --- /dev/null +++ b/partials/sideBar/index.js @@ -0,0 +1,335 @@ +import { useEffect, useState, useRef } from 'react'; +// import { +// Alerts, +// AuditLog, +// Databases, +// Infra, +// LogSvg, +// Metrics, +// Permission, +// Services, +// Settings, +// SummaryIcon, +// TracesIcon, +// } from '../svg/sidebar'; + +import Link from 'next/link'; +import { useParams, usePathname, useRouter, useSearchParams } from 'next/navigation'; +import { ConfigDiff } from '../../svg/configDiff'; +import { DeploymentSpace } from '../../svg/deploymentSpace'; +// import CronJob from '../svg/sidebar/cronJob'; +// import HelmPackageIcon from '../svg/sidebar/helm_package'; +// import { Profile } from '../svg/sidebar/profile'; + +function classNames(...classes) { + return classes.filter(Boolean).join(' '); +} + +export default function Sidebar({ showMobileBar }) { + const router = useRouter(); + const searchParams = useParams(); + const pathname = usePathname(); + const [selectedView, setSelectedView] = useState('Null'); + const [value, setValue] = useState(0); + const selectedTabRef = useRef(null); // Create a ref for the selected tab + + useEffect(() => { + // Scroll the selected tab into view when the component mounts + if (selectedTabRef.current) { + selectedTabRef.current.scrollIntoView({ + behavior: 'smooth', + block: 'nearest', + inline: 'start', + }); + } + }, []); + + // const cloudOption = [ + // { + // accessTo: 'all', + // link: `cloud-accounts/${searchParams['cloud-account-id']}/infrastructure`, + // text: 'Infrastructure', + // icon: Infra, + // current: true, + // }, + // { + // accessTo: 'all', + // link: `cloud-accounts/${searchParams['cloud-account-id']}/audit-log`, + // text: 'Audit Log', + // icon: AuditLog, + // }, + // { + // accessTo: 'all', + // link: `cloud-accounts/${searchParams['cloud-account-id']}/permissions`, + // text: 'Permissions', + // icon: Permission, + // }, + + // { + // accessTo: 'all', + // link: `cloud-accounts/${searchParams['cloud-account-id']}/settings`, + // text: 'Settings', + // icon: Settings, + // }, + // ]; + + const appOption = [ + { + accessTo: 'all', + link: `applications/${searchParams['application-id']}/deploymentSpace`, + text: 'Deployment Space', + icon: DeploymentSpace, + }, + { + accessTo: 'all', + link: `applications/${searchParams['application-id']}/configDiff`, + text: 'Config Diff', + icon: ConfigDiff, + }, + // { + // accessTo: 'all', + // link: `applications/${searchParams['application-id']}/cron-jobs`, + // text: 'Cron Jobs', + // // icon: CronJob, + // }, + // { + // accessTo: 'all', + // link: `applications/${searchParams['application-id']}/datastore`, + // text: 'Data Store', + // // icon: Databases, + // }, + // { + // accessTo: 'all', + // link: `applications/${searchParams['application-id']}/helm-package`, + // text: 'Helm Package', + // // icon: HelmPackageIcon, + // }, + // { + // accessTo: 'all', + // heading: true, + // headingTitle: 'Observability', + // }, + // { + // accessTo: 'all', + // link: `applications/${searchParams['application-id']}/logs`, + // text: 'Logs', + // // icon: LogSvg, + // }, + // { + // accessTo: 'all', + // link: `applications/${searchParams['application-id']}/traces`, + // text: 'Traces', + // // icon: TracesIcon, + // }, + // { + // accessTo: 'all', + // link: `applications/${searchParams['application-id']}/metric`, + // text: 'Metrics', + // // icon: Metrics, + // }, + // { + // accessTo: 'all', + // link: `applications/${searchParams['application-id']}/alerts`, + // text: 'Alerts', + // // icon: Alerts, + // }, + // { + // accessTo: 'all', + // heading: true, + // headingTitle: '', + // divider: true, + // }, + // { + // accessTo: 'all', + // link: `applications/${searchParams['application-id']}/audit-log`, + // text: 'Audit Log', + // // icon: AuditLog, + // }, + + // { + // accessTo: 'all', + // link: `applications/${searchParams['application-id']}/permissions`, + // text: 'Permissions', + // // icon: Permission, + // }, + + // { + // accessTo: 'all', + // link: `applications/${searchParams['application-id']}/settings`, + // text: 'Settings', + // // icon: Settings, + // }, + ]; + // const observabilityOption = [ + // { + // accessTo: 'all', + // link: `observability`, + // text: 'Observability', + // icon: TracesIcon, + // }, + + // // { + // // accessTo: 'all', + // // link: `observability/integrations`, + // // text: 'Integrations', + // // icon: Settings, + // // }, + // ]; + + // const settingsOption = [ + // { + // accessTo: 'all', + // // link: `cloud-accounts/${searchParams['cloud-account-id']}/infrastructure`, + // text: 'Profile', + // icon: Profile, + // current: true, + // }, + // { + // accessTo: 'all', + // // link: `cloud-accounts/${searchParams['cloud-account-id']}/audit-log`, + // text: 'Audit Log', + // icon: AuditLog, + // }, + // { + // accessTo: 'all', + // // link: `cloud-accounts/${searchParams['cloud-account-id']}/permissions`, + // text: 'Permissions', + // icon: Permission, + // }, + + // { + // accessTo: 'all', + // // link: `cloud-accounts/${searchParams['cloud-account-id']}/settings`, + // text: 'Settings', + // icon: Settings, + // }, + // ]; + + const SideBarView = { + // cloudView: cloudOption, + appView: appOption, + // observability: observabilityOption, + // setting: settingsOption, + Null: [], + }; + + useEffect(() => { + if ( + searchParams['cloud-account-id'] !== undefined && + searchParams['application-id'] === undefined + ) { + setSelectedView('cloudView'); + } else if ( + searchParams['cloud-account-id'] === undefined && + searchParams['application-id'] !== undefined + ) { + setSelectedView('appView'); + } + // else if (router.pathname.includes('observability')) { + // setSelectedView('observability'); + // } else if (router.pathname.includes('setting')) { + // setSelectedView('setting'); + // } + }, [router]); + + useEffect(() => { + const list = SideBarView[selectedView].map(function (ele) { + return ele?.link?.split('/').pop(); + }); + + let DashboardView = list.indexOf(pathname.split('/').pop()); + + if (DashboardView === -1) { + DashboardView = + list.indexOf(pathname.split('/')?.[3]) !== -1 && pathname.split('/')?.[3] + ? list.indexOf(pathname.split('/')?.[3]) + : 0; + } + + setValue(DashboardView); + }, [pathname, selectedView]); + + return showMobileBar ? ( +
+ +
+ ) : ( +
+ +
+ ); +} diff --git a/src/app/applications/[application-id]/configDiff/page.js b/src/app/applications/[application-id]/configDiff/page.js new file mode 100644 index 0000000..57eaf6a --- /dev/null +++ b/src/app/applications/[application-id]/configDiff/page.js @@ -0,0 +1,78 @@ +'use client'; + +import React, { useContext, useEffect, useState } from 'react'; +import HeadingComponent from '../../../../../components/HeaderComponents'; +import JsonComparisonTableContainer from '../../../../../components/JsonTable'; +import useGetDeploymentSpace from '../../../../../hooks/deploymentSpace/getDeploymentSpace'; +import { formatTime } from '../../../../../utlis/helperFunc'; +import DraggableList from '../../../../../partials/draggableList'; +import CustomLinearProgress from '../../../../../components/Loaders/LinearLoader'; +import ErrorComponent from '../../../../../components/ErrorComponent'; +import { useParams } from 'next/navigation'; +import BreadCrumbComp from '../../../../../components/BreadCrumb'; +import { AppContext } from '../../../../../libs/context'; + +const handleDeploymentList = (data) => { + const chips = []; + while (data?.name) { + chips.push({ name: data.name, selected: true }); + data = data.next; + } + return ; +}; + +function transformEnvironments(inputArray) { + return inputArray?.map((item) => ({ + id: item.id, + label: item.name, + data: { + Name: item.name, + 'Deployment Space': () => handleDeploymentList(item.deploymentSpace), + 'Created at': formatTime(item.createdAt), + 'Updated at': formatTime(item.updatedAt), + }, + })); +} + +const ConfigDiff = () => { + const params = useParams(); + const { appData } = useContext(AppContext); + const { value, loading, error } = useGetDeploymentSpace(); + const [data, setData] = useState([]); + + useEffect(() => { + if (value?.data?.environments) { + const data = transformEnvironments(value?.data?.environments); + setData(data); + } + }, [value]); + + const appName = appData?.APPLICATION_DATA?.data?.filter( + (item) => item.id === Number(params?.['application-id']), + )?.[0]; + + const breadcrumbList = [ + { name: 'Applications', link: '/applications' }, + { + name: appData?.APPLICATION_DATA?.isSuccess ? appName?.name : 'loading...', + link: `/applications/${params?.['application-id']}/deploymentSpace`, + }, + { + name: 'Config Diff', + link: `#`, + disable: true, + }, + ]; + + return ( +
+ + + + + {error && } +
+ ); +}; + +export default ConfigDiff; diff --git a/src/app/applications/[application-id]/deploymentSpace/[environment-id]/configureDeploymentSpace/page.js b/src/app/applications/[application-id]/deploymentSpace/[environment-id]/configureDeploymentSpace/page.js new file mode 100644 index 0000000..047f452 --- /dev/null +++ b/src/app/applications/[application-id]/deploymentSpace/[environment-id]/configureDeploymentSpace/page.js @@ -0,0 +1,59 @@ +'use client'; + +import Head from 'next/head'; +import React, { useContext } from 'react'; +import StepperComponent from '../../../../../../../partials/nestedList'; +import { AppContext } from '../../../../../../../libs/context'; +import { useParams } from 'next/navigation'; +import BreadCrumbComp from '../../../../../../../components/BreadCrumb'; +// import AddEnvironment from '../../../../../../partials/Environment/addForm'; + +const AddDeploymentSpace = () => { + const { appData } = useContext(AppContext); + const params = useParams(); + + const data = appData?.APPLICATION_DATA?.data?.filter( + (item) => item.id === Number(params?.['application-id']), + )?.[0]; + + const breadcrumbList = [ + { name: 'Applications', link: '/applications' }, + { + name: appData?.APPLICATION_DATA?.isSuccess ? data?.name : 'loading...', + link: `/applications/${params?.['application-id']}/deploymentSpace`, + }, + { + name: 'Configure Deployment Space', + link: `#`, + disable: true, + }, + ]; + + return ( + <> + + Configure Deployment Space + + + +
+
+
+
+

{`Configure Deployment Space`}

+

+ Set up a deployment space for an environment to be used for deployments. Customize + it further after setup. +

+
+
+ +
+
+
+
+ + ); +}; + +export default AddDeploymentSpace; diff --git a/src/app/applications/[application-id]/deploymentSpace/addEnvironment/page.js b/src/app/applications/[application-id]/deploymentSpace/addEnvironment/page.js new file mode 100644 index 0000000..6868731 --- /dev/null +++ b/src/app/applications/[application-id]/deploymentSpace/addEnvironment/page.js @@ -0,0 +1,58 @@ +'use client'; + +import Head from 'next/head'; +import React, { useContext } from 'react'; +import AddEnvironment from '../../../../../../partials/Environment/addForm'; +import { AppContext } from '../../../../../../libs/context'; +import BreadCrumbComp from '../../../../../../components/BreadCrumb'; +import { useParams } from 'next/navigation'; + +const CreateEnvironment = () => { + const { appData } = useContext(AppContext); + const params = useParams(); + + const data = appData?.APPLICATION_DATA?.data?.filter( + (item) => item.id === Number(params?.['application-id']), + )?.[0]; + + const breadcrumbList = [ + { name: 'Applications', link: '/applications' }, + { + name: appData?.APPLICATION_DATA?.isSuccess ? data?.name : 'loading...', + link: `/applications/${params?.['application-id']}/deploymentSpace`, + }, + { + name: 'Add Environment', + link: `#`, + disable: true, + }, + ]; + + return ( + <> + + Add Environment + + + +
+
+
+
+

{`Add Environment`}

+

+ Create a new environment by providing a unique name. This allows you to define a + workspace tailored to your needs. You can customize it further after setup. +

+
+
+ +
+
+
+
+ + ); +}; + +export default CreateEnvironment; diff --git a/src/app/applications/[application-id]/deploymentSpace/page.js b/src/app/applications/[application-id]/deploymentSpace/page.js new file mode 100644 index 0000000..5604d3d --- /dev/null +++ b/src/app/applications/[application-id]/deploymentSpace/page.js @@ -0,0 +1,114 @@ +'use client'; + +import React, { useContext } from 'react'; +import HeadingComponent from '../../../../../components/HeaderComponents'; +import Link from 'next/link'; +import { useParams, usePathname, useRouter } from 'next/navigation'; +import { PlusCircleIcon, PlusIcon } from '@heroicons/react/20/solid'; +import Table from '../../../../../components/Table/table'; +import DraggableList from '../../../../../partials/draggableList'; +import useGetDeploymentSpace from '../../../../../hooks/deploymentSpace/getDeploymentSpace'; +import BreadCrumbComp from '../../../../../components/BreadCrumb'; +import CustomLinearProgress from '../../../../../components/Loaders/LinearLoader'; +import ErrorComponent from '../../../../../components/ErrorComponent'; +import { AppContext } from '../../../../../libs/context'; + +const headers = [ + { key: 'name', label: 'Environment', align: 'left', colClassName: 'sm:w-1/2' }, + { key: 'deployment_space', label: 'Deployment Space', align: 'left', colClassName: 'sm:w-1/2' }, +]; + +const DeploymentSpace = () => { + const pathname = usePathname(); + const router = useRouter(); + const params = useParams(); + const { appData } = useContext(AppContext); + + const { value, loading, error } = useGetDeploymentSpace(); + + const handleAddEnvConfig = (id) => { + router.push(`${pathname}/${id}/configureDeploymentSpace`); + }; + + const handleDeploymentList = (data) => { + const chips = []; + while (data?.name) { + chips.push({ name: data.name, selected: true }); + data = data.next; + } + return ; + }; + + const body = value?.data?.environments?.map((item) => { + return { + id: item.id, + name: item.name, + deployment_space: item?.deploymentSpace ? ( + handleDeploymentList(item.deploymentSpace) + ) : ( + // + + ), + }; + }); + + // const handleEdit = (row) => alert(`Edit ${row.name}`); + const handleDelete = (row) => alert(`Delete ${row.name}`); + + const data = appData?.APPLICATION_DATA?.data?.filter( + (item) => item.id === Number(params?.['application-id']), + )?.[0]; + + const breadcrumbList = [ + { name: 'Applications', link: '/applications' }, + { + name: appData?.APPLICATION_DATA?.isSuccess ? data?.name : 'loading...', + link: `#`, + disable: true, + }, + ]; + + return ( +
+ + + + {