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 (
+
+ );
+};
+
+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...' && (
+
+
+ {breadcrumbList.map((page, idx) => (
+
+
+
+ {page.name?.length > 16
+ ? `${page.name.slice(0, 10)}...${page.name.slice(-3)}`
+ : page.name}
+
+ {breadcrumbList.length !== idx + 1 && (
+
+ )}
+
+
+ ))}
+
+
+ )}
+ >
+ );
+}
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}
+
+
+ {/*
+
+
{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) => (
+
+ ))}
+
+
+
+
+ {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 && (
+ onEdit(row)} className="text-blue-500 hover:text-blue-700">
+ Edit
+
+ )}
+ onDelete(row)} className="text-red-500 hover:text-red-700">
+ Delete
+
+
+
+ )}
+
+ ))}
+
+ );
+};
+
+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 (
+
+ );
+};
+
+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 (
+
+ );
+};
+
+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 (
+
+
+ {pages
+ ?.filter((item) => item.selected)
+ .map((page, idx) => (
+
+
+ {idx !== 0 && (
+
+ )}
+
handleDragStart(e, idx)}
+ onDragEnter={(e) => handleDragEnter(e, idx)}
+ onDragOver={(e) => (disableDrag ? undefined : e.preventDefault())}
+ draggable={!disableDrag}
+ >
+ {page.name}
+
+
+
+ ))}
+
+
+ );
+}
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 && (
+
+ Select Cloud Account
+ {
+ const selectedId = e.target.value;
+ const selectedAccount = cloudAccounts.find(
+ (account) => account.id.toString() === selectedId,
+ );
+ handleCloudAccountChange(selectedAccount); // Pass the full selected object
+ }}
+ >
+
+ Select a cloud account
+
+ {cloudAccounts.map((account) => (
+
+ {account.name}
+
+ ))}
+
+
+ )}
+ {/* Step 2: Deployment Options */}
+ {step >= 2 && (
+
+ Select Deployment Space
+ {
+ handleDeploymentOptionChange(deploymentOptions[e.target.selectedIndex - 1]);
+ }}
+ >
+
+ Select an option
+
+ {deploymentOptions.map((option, index) => (
+
+ {option.name}
+
+ ))}
+
+
+ )}
+ {/* Step 3+: Dynamic Dropdowns */}
+ {dropdowns.map((dropdown, index) => (
+
+ Select {dropdown?.metadata?.name}
+
+ handleDropdownChange(index, dropdown.options[e.target.selectedIndex - 1])
+ }
+ >
+
+ Select an option
+
+ {dropdown.options.map((option, i) => (
+
+ {option.name}
+
+ ))}
+
+
+ ))}
+ {/* Submit Button */}
+ {loading &&
}
+
+
+ {error && (
+
+ )}
+
+
+ {showSubmit && (
+
+ Add
+ {isLoading && (
+
+ )}
+
+ )}
+
+
+ );
+};
+
+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 ? (
+
+
+ {SideBarView[selectedView].map((item, index) => {
+ return (
+ !item?.heading && (
+
+
+
+ {item.text}
+
+ )
+ );
+ })}
+
+
+ ) : (
+
+
+
+
+
+ {SideBarView[selectedView].map((item, index) => {
+ return item?.heading ? (
+ item?.divider ? (
+
+ ) : (
+
+
+ {item?.headingTitle}
+
+
+ )
+ ) : (
+
+
+
+
+ {item.text}
+
+
+ );
+ })}
+
+
+
+
+
+ );
+}
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)
+ ) : (
+ //
+ handleAddEnvConfig(item.id)}
+ >
+
+
+
Configure deployment space
+
{' '}
+
+ ),
+ };
+ });
+
+ // 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 (
+
+
+
+
+ { }
+ Add Environment
+
+ //
+ }
+ />
+
+
+
+ {error && }
+
+ );
+};
+
+export default DeploymentSpace;
diff --git a/src/app/applications/[application-id]/layout.js b/src/app/applications/[application-id]/layout.js
new file mode 100644
index 0000000..0007747
--- /dev/null
+++ b/src/app/applications/[application-id]/layout.js
@@ -0,0 +1,17 @@
+'use client';
+
+import React from 'react';
+import Sidebar from '../../../../partials/sideBar';
+
+const Layout = ({ children }) => {
+ return (
+
+ );
+};
+
+export default Layout;
diff --git a/src/app/applications/create/page.js b/src/app/applications/create/page.js
new file mode 100644
index 0000000..6b04a3a
--- /dev/null
+++ b/src/app/applications/create/page.js
@@ -0,0 +1,33 @@
+import Head from 'next/head';
+import React from 'react';
+import CreateAppForm from '../../../../partials/application/createForm';
+
+const CreateApplication = () => {
+ return (
+ <>
+
+ Create Application
+
+
+
+
+
+
+
{`Add Application`}
+
+ Add application details by giving it a unique name and configuring environments as
+ needed.
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default CreateApplication;
diff --git a/src/app/applications/page.js b/src/app/applications/page.js
new file mode 100644
index 0000000..7f4cd39
--- /dev/null
+++ b/src/app/applications/page.js
@@ -0,0 +1,66 @@
+'use client';
+
+import React from 'react';
+import HeadingComponent from '../../../components/HeaderComponents';
+import { PlusCircleIcon } from '@heroicons/react/20/solid';
+import EmptyComponent from '../../../components/EmptyPageComponent';
+import BlankCloudAccountSvg from '../../../svg/emptyCloudAccount';
+import Link from 'next/link';
+import CompleteLoader from '../../../components/Loaders/CompletePageLoader';
+import ErrorComponent from '../../../components/ErrorComponent';
+import useApplicationList from '../../../hooks/application/getApplicationList';
+import ApplicationCard from '../../../components/Cards/applicationCard';
+
+const CloudAccounts = () => {
+ const { applications, loading, error } = useApplicationList();
+
+ return (
+
+ {applications?.data?.length > 0 && (
+
+
+
+ Add Application
+
+
+ }
+ />
+ )}
+
+ {loading && }
+
+ {applications?.data?.map((item, idx) => {
+ return (
+
+ );
+ })}
+
+
+ {applications?.data?.length === 0 && (
+ }
+ redirectLink={'/applications/create'}
+ buttonTitle={'Add Application'}
+ title={'Please start by setting up your first application'}
+ />
+ )}
+
+ {error && }
+
+ );
+};
+
+export default CloudAccounts;
diff --git a/src/app/cloud-accounts/create/page.js b/src/app/cloud-accounts/create/page.js
index d78875d..c169ead 100644
--- a/src/app/cloud-accounts/create/page.js
+++ b/src/app/cloud-accounts/create/page.js
@@ -31,24 +31,26 @@ const CreateCloud = () => {
};
return (
-
-
-
-
-
Add Cloud Account
-
- To add a cloud account, provide your Service Account Key JSON and assign a meaningful
- name to identify your cloud account easily.
-
-
-
-
+
+
+
+
+
+
Add Cloud Account
+
+ To add a cloud account, provide your Service Account Key JSON and assign a
+ meaningful name to identify your cloud account easily.
+
+
+
+
+
diff --git a/src/app/cloud-accounts/page.js b/src/app/cloud-accounts/page.js
index 29a14e3..ca3898e 100644
--- a/src/app/cloud-accounts/page.js
+++ b/src/app/cloud-accounts/page.js
@@ -15,7 +15,7 @@ const CloudAccounts = () => {
const { cloudAccounts, loading, error } = useCloudAccountList();
return (
-
+
{cloudAccounts?.data?.length > 0 && (
{
{loading && }
{cloudAccounts?.data?.map((item, idx) => {
- return (
-
- );
+ return ;
})}
diff --git a/src/app/globals.css b/src/app/globals.css
index e2c92e2..c91c743 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -18,3 +18,19 @@ body {
z-index: 1500;
transform: translate(-50%, -50%);
}
+
+::-webkit-scrollbar {
+ height: 4px;
+ width: 0px;
+ background: #cbd5e1;
+}
+
+.scroll-hidden::-webkit-scrollbar {
+ display: none;
+}
+
+/* Hide scrollbar for IE, Edge and Firefox */
+.scroll-hidden {
+ -ms-overflow-style: none; /* IE and Edge */
+ scrollbar-width: none; /* Firefox */
+}
diff --git a/src/app/layout.js b/src/app/layout.js
index a4084fc..ad7d45f 100644
--- a/src/app/layout.js
+++ b/src/app/layout.js
@@ -3,6 +3,7 @@ import './globals.css';
import ErrorCatcher from '../../components/ErrorCatcher/ErrorCatcher';
import NotFound from './not-found';
import dynamic from 'next/dynamic';
+import ContextProvider from '../../libs/context';
const TopBarWrapper = dynamic(() => import('../../partials/Header'), {
// ssr: false,
@@ -30,8 +31,11 @@ export default function RootLayout({ children }) {
}>
-
- {children}
+
+
+ {/* {children}
*/}
+ {children}
+
diff --git a/svg/application.js b/svg/application.js
new file mode 100644
index 0000000..924b91e
--- /dev/null
+++ b/svg/application.js
@@ -0,0 +1,46 @@
+import React from 'react';
+
+const ApplicationSvg = ({ color = '#9197B3' }) => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default ApplicationSvg;
diff --git a/svg/configDiff.js b/svg/configDiff.js
new file mode 100644
index 0000000..dfc587b
--- /dev/null
+++ b/svg/configDiff.js
@@ -0,0 +1,41 @@
+import React from 'react';
+
+export const ConfigDiff = ({ color = '#9197B3', ...rest }) => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/svg/deploymentSpace.js b/svg/deploymentSpace.js
new file mode 100644
index 0000000..95e447f
--- /dev/null
+++ b/svg/deploymentSpace.js
@@ -0,0 +1,81 @@
+import React from 'react';
+
+export const DeploymentSpace = ({ color = '#9197B3', ...rest }) => {
+ return (
+
+
+
+ );
+};
diff --git a/utlis/apiClient.js b/utlis/apiClient.js
index b89de66..31d3897 100644
--- a/utlis/apiClient.js
+++ b/utlis/apiClient.js
@@ -3,48 +3,28 @@ import axiosInstance from './axiosInstance';
// Generic API methods
const apiClient = {
get: async (url, params = {}) => {
- try {
- const response = await axiosInstance.get(url, { params });
- return response.data;
- } catch (error) {
- throw error;
- }
+ const response = await axiosInstance.get(url, { params });
+ return response.data;
},
post: async (url, data = {}) => {
- try {
- const response = await axiosInstance.post(url, data);
- return response.data;
- } catch (error) {
- throw error;
- }
+ const response = await axiosInstance.post(url, data);
+ return response.data;
},
put: async (url, data = {}) => {
- try {
- const response = await axiosInstance.put(url, data);
- return response.data;
- } catch (error) {
- throw error;
- }
+ const response = await axiosInstance.put(url, data);
+ return response.data;
},
patch: async (url, data = {}) => {
- try {
- const response = await axiosInstance.patch(url, data);
- return response.data;
- } catch (error) {
- throw error;
- }
+ const response = await axiosInstance.patch(url, data);
+ return response.data;
},
delete: async (url) => {
- try {
- const response = await axiosInstance.delete(url);
- return response.data;
- } catch (error) {
- throw error;
- }
+ const response = await axiosInstance.delete(url);
+ return response.data;
},
};
diff --git a/utlis/apiEndpoints.js b/utlis/apiEndpoints.js
index 74b0ac8..07b98fc 100644
--- a/utlis/apiEndpoints.js
+++ b/utlis/apiEndpoints.js
@@ -1 +1,3 @@
export const CLOUD_ACCOUNT_ENDPOINT = '/cloud-accounts';
+export const APPLICATION_ENDPOINT = '/applications';
+export const ENVIRONMENT_ENDPOINT = '/environments';