diff --git a/src/api/auth.ts b/src/api/auth.ts new file mode 100644 index 0000000..6ee752d --- /dev/null +++ b/src/api/auth.ts @@ -0,0 +1,29 @@ +import axios, { AxiosResponse } from 'axios' +import { TAuthResponse } from 'localTypes/auth' +import { handleError } from './handleResponse' + +export const login = async (): Promise => { + let response: AxiosResponse | undefined + + try { + response = await axios.get('/oauth/token', { withCredentials: true }) + } catch (error) { + handleError(error) + } + + return response?.data +} + +export const logout = async (): Promise => { + let response: AxiosResponse | undefined + + try { + response = await axios.get('/oauth/logout', { withCredentials: true }) + } catch (error) { + handleError(error) + } finally { + window.location.reload() + } + + return response?.data +} diff --git a/src/api/handleResponse.ts b/src/api/handleResponse.ts new file mode 100644 index 0000000..e538965 --- /dev/null +++ b/src/api/handleResponse.ts @@ -0,0 +1,20 @@ +import axios, { isAxiosError } from 'axios' + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const handleError = (error: any) => { + if (isAxiosError(error)) { + if (error.status === 401) { + try { + axios.get('/oauth/logout', { + method: 'GET', + withCredentials: true, + }) + } finally { + window.location.reload() + } + } + throw new Error(`Request failed with status ${error.status}: ${error.message}`) + } else { + throw new Error('Non axios error') + } +} diff --git a/src/components/organisms/Header/Header.tsx b/src/components/organisms/Header/Header.tsx new file mode 100644 index 0000000..1191c6b --- /dev/null +++ b/src/components/organisms/Header/Header.tsx @@ -0,0 +1,45 @@ +import React, { FC } from 'react' +import { Row, Col } from 'antd' +import { useParams } from 'react-router-dom' +import { AccessGroups, Documentation, Logo, ManageableSidebar, Selector, User } from './organisms' + +export const Header: FC = () => { + const { projectName, instanceName, clusterName, entryType, namespace, syntheticProject } = useParams() + + const possibleProject = syntheticProject && namespace ? syntheticProject : namespace + const possibleInstance = syntheticProject && namespace ? namespace : undefined + + return ( + + + + + + + + + + + + {instanceName && projectName && ( + + )} + + + + + + + + + ) +} diff --git a/src/components/organisms/Header/index.ts b/src/components/organisms/Header/index.ts new file mode 100644 index 0000000..f887995 --- /dev/null +++ b/src/components/organisms/Header/index.ts @@ -0,0 +1 @@ +export * from './Header' diff --git a/src/components/organisms/Header/organisms/AccessGroups/AccessGroups.tsx b/src/components/organisms/Header/organisms/AccessGroups/AccessGroups.tsx new file mode 100644 index 0000000..2b04eba --- /dev/null +++ b/src/components/organisms/Header/organisms/AccessGroups/AccessGroups.tsx @@ -0,0 +1,46 @@ +import React, { FC, useCallback } from 'react' +import { notification } from 'antd' +import { FireOutlined } from '@ant-design/icons' +import { useSelector } from 'react-redux' +import { RootState } from 'store/store' +import { Styled } from './styled' + +type TAccessGroupsProps = { + clusterName: string | undefined + instanceName: string + projectName: string +} + +export const AccessGroups: FC = ({ clusterName, projectName, instanceName }) => { + const [api, contextHolder] = notification.useNotification() + const clusterList = useSelector((state: RootState) => state.clusterList.clusterList) + + const cluster = clusterList ? clusterList.find(({ name }) => name === clusterName) : undefined + const clusterTenant = cluster?.tenant || '' + + const shortName = instanceName.startsWith(`${projectName}-`) + ? instanceName.substring(`${projectName}-`.length) + : instanceName + + const value = `${projectName}:${shortName}:${clusterTenant}` + + const renderValue = value.length > 37 ? `${value.slice(0, 34)}...` : value + + const handleCopy = useCallback(() => { + navigator.clipboard.writeText(value) + api.success({ + message: 'Access Group copied:', + description: value, + key: 'copy-success', + }) + }, [value, api]) + + return ( + <> + {contextHolder} + } onClick={handleCopy}> + {renderValue} + + + ) +} diff --git a/src/components/organisms/Header/organisms/AccessGroups/index.ts b/src/components/organisms/Header/organisms/AccessGroups/index.ts new file mode 100644 index 0000000..c78ac40 --- /dev/null +++ b/src/components/organisms/Header/organisms/AccessGroups/index.ts @@ -0,0 +1 @@ +export * from './AccessGroups' diff --git a/src/components/organisms/Header/organisms/AccessGroups/styled.ts b/src/components/organisms/Header/organisms/AccessGroups/styled.ts new file mode 100644 index 0000000..0a7265c --- /dev/null +++ b/src/components/organisms/Header/organisms/AccessGroups/styled.ts @@ -0,0 +1,15 @@ +import styled from 'styled-components' +import { Button } from 'antd' + +const FullWidthButton = styled(Button)` + width: 100%; + display: flex; + justify-content: flex-start; + cursor: pointer; + color: ${({ theme }) => theme.colorText}; + padding-left: 16px; +` + +export const Styled = { + FullWidthButton, +} diff --git a/src/components/organisms/Header/organisms/Documentation/Documentation.tsx b/src/components/organisms/Header/organisms/Documentation/Documentation.tsx new file mode 100644 index 0000000..84ef974 --- /dev/null +++ b/src/components/organisms/Header/organisms/Documentation/Documentation.tsx @@ -0,0 +1,14 @@ +import React, { FC } from 'react' +import { FileTextOutlined } from '@ant-design/icons' +import { Styled } from './styled' + +export const Documentation: FC = () => { + const platformDocumentationUrl = '/docs' + + return ( + window.open(platformDocumentationUrl, '_blank')}> + + Documentaion + + ) +} diff --git a/src/components/organisms/Header/organisms/Documentation/index.ts b/src/components/organisms/Header/organisms/Documentation/index.ts new file mode 100644 index 0000000..a1bd5de --- /dev/null +++ b/src/components/organisms/Header/organisms/Documentation/index.ts @@ -0,0 +1 @@ +export * from './Documentation' diff --git a/src/components/organisms/Header/organisms/Documentation/styled.ts b/src/components/organisms/Header/organisms/Documentation/styled.ts new file mode 100644 index 0000000..39eec07 --- /dev/null +++ b/src/components/organisms/Header/organisms/Documentation/styled.ts @@ -0,0 +1,12 @@ +import styled from 'styled-components' +import { Button } from 'antd' + +const FullWidthButton = styled(Button)` + width: 100%; + display: flex; + justify-content: flex-start; +` + +export const Styled = { + FullWidthButton, +} diff --git a/src/components/organisms/Header/organisms/Logo/Logo.tsx b/src/components/organisms/Header/organisms/Logo/Logo.tsx new file mode 100644 index 0000000..758936c --- /dev/null +++ b/src/components/organisms/Header/organisms/Logo/Logo.tsx @@ -0,0 +1,19 @@ +import React, { FC } from 'react' +import { useNavigate } from 'react-router-dom' +import { useSelector } from 'react-redux' +import { RootState } from 'store/store' +import { TitleWithNoTopMargin } from 'components/atoms' +import { Styled } from './styled' + +export const Logo: FC = () => { + const navigate = useNavigate() + const baseprefix = useSelector((state: RootState) => state.baseprefix.baseprefix) + + return ( + + navigate(`${baseprefix}`)}> + InCloud + + + ) +} diff --git a/src/components/organisms/Header/organisms/Logo/index.ts b/src/components/organisms/Header/organisms/Logo/index.ts new file mode 100644 index 0000000..104895e --- /dev/null +++ b/src/components/organisms/Header/organisms/Logo/index.ts @@ -0,0 +1 @@ +export * from './Logo' diff --git a/src/components/organisms/Header/organisms/Logo/styled.ts b/src/components/organisms/Header/organisms/Logo/styled.ts new file mode 100644 index 0000000..093c036 --- /dev/null +++ b/src/components/organisms/Header/organisms/Logo/styled.ts @@ -0,0 +1,9 @@ +import styled from 'styled-components' + +const CursorPointer = styled.div` + cursor: pointer; +` + +export const Styled = { + CursorPointer, +} diff --git a/src/components/organisms/Header/organisms/ManageableSidebar/ManageableSidebar.tsx b/src/components/organisms/Header/organisms/ManageableSidebar/ManageableSidebar.tsx new file mode 100644 index 0000000..8f60ccf --- /dev/null +++ b/src/components/organisms/Header/organisms/ManageableSidebar/ManageableSidebar.tsx @@ -0,0 +1,44 @@ +import React, { FC } from 'react' +import { useLocation, useParams } from 'react-router-dom' +import { ManageableSidebarWithDataProvider } from '@prorobotech/openapi-k8s-toolkit' +import { BASE_API_GROUP, BASE_API_VERSION } from 'constants/customizationApiGroupAndVersion' + +type TManageableSidebarProps = { + clusterName?: string + entryType?: string + instanceName?: string + projectName?: string +} + +export const ManageableSidebar: FC = ({ + clusterName, + projectName, + instanceName, + entryType, +}) => { + const { pathname } = useLocation() + const params = useParams() + const namespace = params?.namespace || '' + const syntheticProject = params?.syntheticProject || '' + + const creating = projectName === 'create' || instanceName === 'create' + const updating = entryType === 'update' + const visible = !creating && !updating + + return ( +