mirror of
https://github.com/outbackdingo/openapi-ui.git
synced 2026-01-28 02:19:48 +00:00
header
This commit is contained in:
29
src/api/auth.ts
Normal file
29
src/api/auth.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import axios, { AxiosResponse } from 'axios'
|
||||
import { TAuthResponse } from 'localTypes/auth'
|
||||
import { handleError } from './handleResponse'
|
||||
|
||||
export const login = async (): Promise<TAuthResponse | undefined> => {
|
||||
let response: AxiosResponse<TAuthResponse> | undefined
|
||||
|
||||
try {
|
||||
response = await axios.get<TAuthResponse>('/oauth/token', { withCredentials: true })
|
||||
} catch (error) {
|
||||
handleError(error)
|
||||
}
|
||||
|
||||
return response?.data
|
||||
}
|
||||
|
||||
export const logout = async (): Promise<TAuthResponse | undefined> => {
|
||||
let response: AxiosResponse<TAuthResponse> | undefined
|
||||
|
||||
try {
|
||||
response = await axios.get<TAuthResponse>('/oauth/logout', { withCredentials: true })
|
||||
} catch (error) {
|
||||
handleError(error)
|
||||
} finally {
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
return response?.data
|
||||
}
|
||||
20
src/api/handleResponse.ts
Normal file
20
src/api/handleResponse.ts
Normal file
@@ -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')
|
||||
}
|
||||
}
|
||||
45
src/components/organisms/Header/Header.tsx
Normal file
45
src/components/organisms/Header/Header.tsx
Normal file
@@ -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 (
|
||||
<Row>
|
||||
<Col span={2}>
|
||||
<Logo />
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Selector
|
||||
clusterName={clusterName}
|
||||
projectName={projectName || possibleProject}
|
||||
instanceName={instanceName || possibleInstance}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<ManageableSidebar
|
||||
clusterName={clusterName}
|
||||
entryType={entryType}
|
||||
instanceName={instanceName}
|
||||
projectName={projectName}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={2}>
|
||||
{instanceName && projectName && (
|
||||
<AccessGroups clusterName={clusterName} instanceName={instanceName} projectName={projectName} />
|
||||
)}
|
||||
</Col>
|
||||
<Col span={2}>
|
||||
<Documentation key="SidebarDocumentation" />
|
||||
</Col>
|
||||
<Col span={2}>
|
||||
<User key="SidebarUser" />
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
1
src/components/organisms/Header/index.ts
Normal file
1
src/components/organisms/Header/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './Header'
|
||||
@@ -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<TAccessGroupsProps> = ({ 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}
|
||||
<Styled.FullWidthButton type="text" icon={<FireOutlined />} onClick={handleCopy}>
|
||||
{renderValue}
|
||||
</Styled.FullWidthButton>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './AccessGroups'
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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 (
|
||||
<Styled.FullWidthButton type="text" onClick={() => window.open(platformDocumentationUrl, '_blank')}>
|
||||
<FileTextOutlined />
|
||||
Documentaion
|
||||
</Styled.FullWidthButton>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './Documentation'
|
||||
@@ -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,
|
||||
}
|
||||
19
src/components/organisms/Header/organisms/Logo/Logo.tsx
Normal file
19
src/components/organisms/Header/organisms/Logo/Logo.tsx
Normal file
@@ -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 (
|
||||
<Styled.CursorPointer>
|
||||
<TitleWithNoTopMargin level={2} onClick={() => navigate(`${baseprefix}`)}>
|
||||
InCloud
|
||||
</TitleWithNoTopMargin>
|
||||
</Styled.CursorPointer>
|
||||
)
|
||||
}
|
||||
1
src/components/organisms/Header/organisms/Logo/index.ts
Normal file
1
src/components/organisms/Header/organisms/Logo/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './Logo'
|
||||
9
src/components/organisms/Header/organisms/Logo/styled.ts
Normal file
9
src/components/organisms/Header/organisms/Logo/styled.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import styled from 'styled-components'
|
||||
|
||||
const CursorPointer = styled.div`
|
||||
cursor: pointer;
|
||||
`
|
||||
|
||||
export const Styled = {
|
||||
CursorPointer,
|
||||
}
|
||||
@@ -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<TManageableSidebarProps> = ({
|
||||
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 (
|
||||
<ManageableSidebarWithDataProvider
|
||||
uri={`/api/clusters/${clusterName}/k8s/apis/${BASE_API_GROUP}/${BASE_API_VERSION}/sidebars/`}
|
||||
refetchInterval={5000}
|
||||
isEnabled={clusterName !== undefined}
|
||||
replaceValues={{
|
||||
clusterName,
|
||||
projectName,
|
||||
instanceName,
|
||||
namespace,
|
||||
syntheticProject,
|
||||
}}
|
||||
pathname={pathname}
|
||||
hidden={!visible}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './ManageableSidebar'
|
||||
@@ -0,0 +1,87 @@
|
||||
import React, { FC, useState } from 'react'
|
||||
import { Col, Row } from 'antd'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { RootState } from 'store/store'
|
||||
import { useNavSelector } from 'hooks/useNavSelector'
|
||||
import { useMountEffect } from 'hooks/useMountEffect'
|
||||
import { EntrySelect } from './molecules'
|
||||
|
||||
type TSelectorProps = {
|
||||
clusterName?: string
|
||||
projectName?: string
|
||||
instanceName?: string
|
||||
}
|
||||
|
||||
export const Selector: FC<TSelectorProps> = ({ clusterName, projectName, instanceName }) => {
|
||||
const navigate = useNavigate()
|
||||
const baseprefix = useSelector((state: RootState) => state.baseprefix.baseprefix)
|
||||
|
||||
const [selectedClusterName, setSelectedClusterName] = useState(clusterName)
|
||||
const [selectedProjectName, setSelectedProjectName] = useState(projectName)
|
||||
const [selectedInstanceName, setSelectedInstanceName] = useState(instanceName)
|
||||
|
||||
const { projectsInSidebar, instancesInSidebar, allInstancesLoadingSuccess, clustersInSidebar } = useNavSelector(
|
||||
selectedClusterName,
|
||||
projectName,
|
||||
)
|
||||
|
||||
const handleClusterChange = (value: string) => {
|
||||
setSelectedClusterName(value)
|
||||
navigate(`${baseprefix}/clusters/${value}`)
|
||||
setSelectedProjectName(undefined)
|
||||
setSelectedInstanceName(undefined)
|
||||
}
|
||||
|
||||
const handleProjectChange = (value: string) => {
|
||||
setSelectedProjectName(value)
|
||||
setSelectedInstanceName(undefined)
|
||||
navigate(`${baseprefix}/clusters/${selectedClusterName}/projects/${value}`)
|
||||
}
|
||||
|
||||
const handleInstanceChange = (value: string) => {
|
||||
setSelectedInstanceName(value)
|
||||
navigate(`${baseprefix}/${selectedClusterName}/${value}/${selectedProjectName}/non-crd-table/apps/v1/deployments`)
|
||||
}
|
||||
|
||||
useMountEffect(() => {
|
||||
setSelectedClusterName(clusterName)
|
||||
setSelectedProjectName(projectName)
|
||||
setSelectedInstanceName(instanceName)
|
||||
}, [projectName, instanceName, clusterName])
|
||||
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={8}>
|
||||
<EntrySelect
|
||||
placeholder="Cluster"
|
||||
options={clustersInSidebar}
|
||||
value={selectedClusterName}
|
||||
onChange={handleClusterChange}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<EntrySelect
|
||||
placeholder="Project"
|
||||
options={projectsInSidebar}
|
||||
value={selectedProjectName}
|
||||
onChange={handleProjectChange}
|
||||
disabled={selectedClusterName === undefined || projectsInSidebar.length === 0}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<EntrySelect
|
||||
placeholder="Intance"
|
||||
options={instancesInSidebar}
|
||||
value={selectedInstanceName}
|
||||
onChange={handleInstanceChange}
|
||||
disabled={
|
||||
selectedClusterName === undefined ||
|
||||
selectedProjectName === undefined ||
|
||||
(allInstancesLoadingSuccess && instancesInSidebar.length === 0)
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './Selector'
|
||||
@@ -0,0 +1,26 @@
|
||||
import React, { FC } from 'react'
|
||||
import { Select } from 'antd'
|
||||
|
||||
type TEntrySelectProps = {
|
||||
placeholder: string
|
||||
options: {
|
||||
label: string
|
||||
value: string
|
||||
}[]
|
||||
value?: string
|
||||
onChange: (val: string) => void
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export const EntrySelect: FC<TEntrySelectProps> = ({ placeholder, value, disabled, options, onChange }) => {
|
||||
return (
|
||||
<Select
|
||||
placeholder={placeholder}
|
||||
value={value || ''}
|
||||
options={options.map(({ value, label }) => ({ label, value }))}
|
||||
onChange={(selectedValue: string) => onChange(selectedValue)}
|
||||
disabled={disabled}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './EntrySelect'
|
||||
@@ -0,0 +1 @@
|
||||
export * from './EntrySelect'
|
||||
44
src/components/organisms/Header/organisms/User/User.tsx
Normal file
44
src/components/organisms/Header/organisms/User/User.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import React, { FC } from 'react'
|
||||
import { Tooltip, Dropdown } from 'antd'
|
||||
import { LogoutOutlined } from '@ant-design/icons'
|
||||
import { ThemeSelector } from 'components'
|
||||
import { useAuth } from 'hooks/useAuth'
|
||||
import { logout } from 'api/auth'
|
||||
import { Styled } from './styled'
|
||||
|
||||
export const User: FC = () => {
|
||||
const { fullName } = useAuth()
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
placement="top"
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
key: '1',
|
||||
label: <ThemeSelector />,
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
label: (
|
||||
<div onClick={() => logout()}>
|
||||
<LogoutOutlined /> Logout
|
||||
</div>
|
||||
),
|
||||
},
|
||||
],
|
||||
}}
|
||||
trigger={['click']}
|
||||
>
|
||||
<Styled.FullWidthButton type="text">
|
||||
{fullName && fullName.length > 25 ? (
|
||||
<Tooltip title={fullName}>
|
||||
<Styled.Name>{fullName.slice(25)}</Styled.Name>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Styled.Name>{fullName || 'User'}</Styled.Name>
|
||||
)}
|
||||
</Styled.FullWidthButton>
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
||||
1
src/components/organisms/Header/organisms/User/index.ts
Normal file
1
src/components/organisms/Header/organisms/User/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './User'
|
||||
19
src/components/organisms/Header/organisms/User/styled.ts
Normal file
19
src/components/organisms/Header/organisms/User/styled.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import styled from 'styled-components'
|
||||
import { Button } from 'antd'
|
||||
|
||||
const FullWidthButton = styled(Button)`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
`
|
||||
|
||||
const Name = styled.span`
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
`
|
||||
|
||||
export const Styled = {
|
||||
FullWidthButton,
|
||||
Name,
|
||||
}
|
||||
6
src/components/organisms/Header/organisms/index.ts
Normal file
6
src/components/organisms/Header/organisms/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export * from './Logo'
|
||||
export * from './Selector'
|
||||
export * from './ManageableSidebar'
|
||||
export * from './AccessGroups'
|
||||
export * from './Documentation'
|
||||
export * from './User'
|
||||
@@ -10,3 +10,4 @@ export * from './TableNonCrdInfo'
|
||||
export * from './TableBuiltinInfo'
|
||||
export * from './Forms'
|
||||
export * from './Factory'
|
||||
export * from './Header'
|
||||
|
||||
35
src/hooks/useAuth.ts
Normal file
35
src/hooks/useAuth.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { login } from 'api/auth'
|
||||
|
||||
export const useAuth = () => {
|
||||
const [fullName, setFullName] = useState<string>()
|
||||
const [requester, setRequester] = useState<{ name: string; email: string }>()
|
||||
const [loadingAuth, setLoadingAuth] = useState(false)
|
||||
const [error, setError] = useState<string>()
|
||||
|
||||
useEffect(() => {
|
||||
setLoadingAuth(true)
|
||||
if (!fullName || !requester) {
|
||||
login()
|
||||
.then(data => {
|
||||
if (data) {
|
||||
setFullName(data.name)
|
||||
setRequester({ name: data.name, email: data.email })
|
||||
setLoadingAuth(false)
|
||||
}
|
||||
})
|
||||
.catch(err => setError(err instanceof Error ? err.message : 'Unknown error'))
|
||||
.finally(() => setLoadingAuth(false))
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
if (import.meta.env.VITE_HAS_MOCKS === 'true') {
|
||||
return {
|
||||
fullName: 'John Doe',
|
||||
requester: { name: 'John Doe', email: 'cK5mH@example.com', loadingAuth: false, error: undefined },
|
||||
}
|
||||
}
|
||||
|
||||
return { fullName, requester, loadingAuth, error }
|
||||
}
|
||||
17
src/hooks/useMountEffect.ts
Normal file
17
src/hooks/useMountEffect.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { useEffect, useRef, DependencyList, EffectCallback } from 'react'
|
||||
|
||||
export const useMountEffect = (fn: EffectCallback, deps: DependencyList) => {
|
||||
const isMount = useRef(true)
|
||||
const fnRef = useRef<EffectCallback>(fn)
|
||||
fnRef.current = fn
|
||||
|
||||
useEffect(() => {
|
||||
if (isMount.current) {
|
||||
isMount.current = false
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
return fnRef.current()
|
||||
}, deps) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
}
|
||||
45
src/hooks/useNavSelector.ts
Normal file
45
src/hooks/useNavSelector.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { useApiResources, TClusterList, TSingleResource } from '@prorobotech/openapi-k8s-toolkit'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { RootState } from 'store/store'
|
||||
import { BASE_API_GROUP, BASE_RPROJECTS_VERSION } from 'constants/customizationApiGroupAndVersion'
|
||||
|
||||
const mappedClusterToOptionInSidebar = ({ name }: TClusterList[number]): { value: string; label: string } => ({
|
||||
value: name,
|
||||
label: name,
|
||||
})
|
||||
|
||||
const mappedToOptionInSidebar = ({ metadata }: TSingleResource): { value: string; label: string } => ({
|
||||
value: metadata.name,
|
||||
label: metadata.name,
|
||||
})
|
||||
|
||||
export const useNavSelector = (clusterName?: string, projectName?: string) => {
|
||||
const clusterList = useSelector((state: RootState) => state.clusterList.clusterList)
|
||||
|
||||
const { data: projects } = useApiResources({
|
||||
clusterName: clusterName || '',
|
||||
namespace: '',
|
||||
apiGroup: BASE_API_GROUP,
|
||||
apiVersion: BASE_RPROJECTS_VERSION,
|
||||
typeName: 'projects',
|
||||
limit: null,
|
||||
})
|
||||
|
||||
const { data: instances, isSuccess: allInstancesLoadingSuccess } = useApiResources({
|
||||
clusterName: clusterName || '',
|
||||
namespace: '',
|
||||
apiGroup: BASE_API_GROUP,
|
||||
apiVersion: BASE_RPROJECTS_VERSION,
|
||||
typeName: 'instances',
|
||||
limit: null,
|
||||
})
|
||||
|
||||
const clustersInSidebar = clusterList ? clusterList.map(mappedClusterToOptionInSidebar) : []
|
||||
const projectsInSidebar = clusterName && projects ? projects.items.map(mappedToOptionInSidebar) : []
|
||||
const instancesInSidebar =
|
||||
clusterName && instances
|
||||
? instances.items.filter(item => item.metadata.namespace === projectName).map(mappedToOptionInSidebar)
|
||||
: []
|
||||
|
||||
return { clustersInSidebar, projectsInSidebar, instancesInSidebar, allInstancesLoadingSuccess }
|
||||
}
|
||||
12
src/localTypes/auth.ts
Normal file
12
src/localTypes/auth.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export type TAuthResponse = {
|
||||
at_hash: string
|
||||
aud: string
|
||||
email: string
|
||||
email_verified: boolean
|
||||
exp: number
|
||||
groups: string[]
|
||||
iat: number
|
||||
iss: string
|
||||
name: string
|
||||
sub: string
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import type { RootState } from 'store/store'
|
||||
import { setTheme } from 'store/theme/theme/theme'
|
||||
import { setCluster } from 'store/cluster/cluster/cluster'
|
||||
import { setClusterList } from 'store/clusterList/clusterList/clusterList'
|
||||
import { DefaultLayout, DefaultColorProvider, TitleWithNoTopMargin, ThemeSelector } from 'components'
|
||||
import { DefaultLayout, DefaultColorProvider, Header } from 'components'
|
||||
import { Styled } from './styled'
|
||||
|
||||
type TBaseTemplateProps = {
|
||||
@@ -85,14 +85,9 @@ export const BaseTemplate: FC<TBaseTemplateProps> = ({ children, withNoCluster,
|
||||
<DefaultLayout.Layout $bgColor={token.colorBgLayout}>
|
||||
<DefaultLayout.ContentContainer>
|
||||
<DefaultLayout.ContentPadding $isFederation={isFederation}>
|
||||
{!isFederation && (
|
||||
<Styled.TitleAndThemeToggle>
|
||||
<TitleWithNoTopMargin level={1}>OpenAPI UI</TitleWithNoTopMargin>
|
||||
{clusterListQuery.error && (
|
||||
<Alert message={`Cluster List Error: ${clusterListQuery.error?.message} `} type="error" />
|
||||
)}
|
||||
<ThemeSelector />
|
||||
</Styled.TitleAndThemeToggle>
|
||||
<Header />
|
||||
{clusterListQuery.error && (
|
||||
<Alert message={`Cluster List Error: ${clusterListQuery.error?.message} `} type="error" />
|
||||
)}
|
||||
{children}
|
||||
</DefaultLayout.ContentPadding>
|
||||
|
||||
@@ -8,12 +8,6 @@ const Container = styled.div<TContainerProps>`
|
||||
min-height: 100vh;
|
||||
`
|
||||
|
||||
const TitleAndThemeToggle = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
`
|
||||
export const Styled = {
|
||||
Container,
|
||||
TitleAndThemeToggle,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user