use namespace nav & navigation resource

This commit is contained in:
typescreep
2025-07-16 23:20:53 +03:00
parent d2967157de
commit b9b0837f0a
10 changed files with 173 additions and 17 deletions

11
.env
View File

@@ -1,11 +1,22 @@
VITE_CUSTOMIZATION_API_GROUP=incloud.io
VITE_CUSTOMIZATION_API_VERSION=v1alpha
VITE_CUSTOMIZATION_NAVIGATION_RESOURCE_NAME=navigations
VITE_CUSTOMIZATION_NAVIGATION_RESOURCE=navigation
VITE_USE_NAMESPACE_NAV=true
VITE_NAVIGATE_FROM_CLUSTERLIST=/openapi-ui/clusters/~recordValue~
VITE_PROJECTS_API_GROUP=incloud.io
VITE_PROJECTS_VERSION=v1alpha
VITE_PROJECTS_RESOURCE_NAME=projects
VITE_MARKETPLACE_RESOURCE_NAME=marketplacepanels
VITE_MARKETPLACE_KIND=MarketplacePanel
VITE_INSTANCES_API_GROUP=incloud.io
VITE_INSTANCES_VERSION=v1alpha1
VITE_INSTANCES_RESOURCE_NAME=v1alpha1
VITE_BFF_URL=

View File

@@ -1,12 +1,24 @@
KUBE_API_URL=
CUSTOMIZATION_API_GROUP=
CUSTOMIZATION_API_VERSION=
CUSTOMIZATION_NAVIGATION_RESOURCE_NAME=
CUSTOMIZATION_NAVIGATION_RESOURCE=
USE_NAMESPACE_NAV=
NAVIGATE_FROM_CLUSTERLIST=
PROJECTS_API_GROUP=
PROJECTS_VERSION=
PROJECTS_RESOURCE_NAME=
MARKETPLACE_RESOURCE_NAME=
MARKETPLACE_KIND=
INSTANCES_API_GROUP=
INSTANCES_VERSION=
INSTANCES_RESOURCE_NAME=
BFF_URL=

View File

@@ -17,22 +17,41 @@ if (process.env.LOCAL === 'true') {
}
const KUBE_API_URL = process.env.LOCAL === 'true' ? options?.KUBE_API_URL : process.env.KUBE_API_URL
const CUSTOMIZATION_API_GROUP =
process.env.LOCAL === 'true' ? options?.CUSTOMIZATION_API_GROUP : process.env.CUSTOMIZATION_API_GROUP
const CUSTOMIZATION_API_VERSION =
process.env.LOCAL === 'true' ? options?.CUSTOMIZATION_API_VERSION : process.env.CUSTOMIZATION_API_VERSION
const CUSTOMIZATION_NAVIGATION_RESOURCE_NAME =
process.env.LOCAL === 'true'
? options?.CUSTOMIZATION_NAVIGATION_RESOURCE_NAME
: process.env.CUSTOMIZATION_NAVIGATION_RESOURCE_NAME
const CUSTOMIZATION_NAVIGATION_RESOURCE =
process.env.LOCAL === 'true'
? options?.CUSTOMIZATION_NAVIGATION_RESOURCE
: process.env.CUSTOMIZATION_NAVIGATION_RESOURCE
const USE_NAMESPACE_NAV = process.env.LOCAL === 'true' ? options?.USE_NAMESPACE_NAV : process.env.USE_NAMESPACE_NAV
const NAVIGATE_FROM_CLUSTERLIST =
process.env.LOCAL === 'true' ? options?.NAVIGATE_FROM_CLUSTERLIST : process.env.NAVIGATE_FROM_CLUSTERLIST
const PROJECTS_API_GROUP = process.env.LOCAL === 'true' ? options?.PROJECTS_API_GROUP : process.env.PROJECTS_API_GROUP
const PROJECTS_VERSION = process.env.LOCAL === 'true' ? options?.PROJECTS_VERSION : process.env.PROJECTS_VERSION
const PROJECTS_RESOURCE_NAME =
process.env.LOCAL === 'true' ? options?.PROJECTS_RESOURCE_NAME : process.env.PROJECTS_RESOURCE_NAME
const MARKETPLACE_RESOURCE_NAME =
process.env.LOCAL === 'true' ? options?.MARKETPLACE_RESOURCE_NAME : process.env.MARKETPLACE_RESOURCE_NAME
const MARKETPLACE_KIND = process.env.LOCAL === 'true' ? options?.MARKETPLACE_KIND : process.env.MARKETPLACE_KIND
const INSTANCES_API_GROUP =
process.env.LOCAL === 'true' ? options?.INSTANCES_API_GROUP : process.env.INSTANCES_API_GROUP
const INSTANCES_VERSION = process.env.LOCAL === 'true' ? options?.INSTANCES_VERSION : process.env.INSTANCES_VERSION
const INSTANCES_RESOURCE_NAME =
process.env.LOCAL === 'true' ? options?.INSTANCES_RESOURCE_NAME : process.env.INSTANCES_RESOURCE_NAME
const BFF_URL = process.env.LOCAL === 'true' ? options?.BFF_URL : process.env.BFF_URL
const healthcheck = require('express-healthcheck')
@@ -140,6 +159,12 @@ app.get(`${basePrefix ? basePrefix : ''}/env.js`, (_, res) => {
${basePrefix ? ` BASEPREFIX: "${basePrefix}",` : ''}
CUSTOMIZATION_API_GROUP: ${JSON.stringify(CUSTOMIZATION_API_GROUP) || '"check envs"'},
CUSTOMIZATION_API_VERSION: ${JSON.stringify(CUSTOMIZATION_API_VERSION) || '"check envs"'},
CUSTOMIZATION_NAVIGATION_RESOURCE_NAME: ${
JSON.stringify(CUSTOMIZATION_NAVIGATION_RESOURCE_NAME) || '"check envs"'
},
CUSTOMIZATION_NAVIGATION_RESOURCE: ${JSON.stringify(CUSTOMIZATION_NAVIGATION_RESOURCE) || '"check envs"'},
USE_NAMESPACE_NAV: ${!!USE_NAMESPACE_NAV},
NAVIGATE_FROM_CLUSTERLIST: ${JSON.stringify(NAVIGATE_FROM_CLUSTERLIST) || '"check envs"'},
PROJECTS_API_GROUP: ${JSON.stringify(PROJECTS_API_GROUP) || '"check envs"'},
PROJECTS_VERSION: ${JSON.stringify(PROJECTS_VERSION) || '"check envs"'},
PROJECTS_RESOURCE_NAME: ${JSON.stringify(PROJECTS_RESOURCE_NAME) || '"check envs"'},

View File

@@ -2,7 +2,8 @@ import React, { FC } from 'react'
import { theme } from 'antd'
import { useParams } from 'react-router-dom'
import { HEAD_SECOND_ROW } from 'constants/blocksSizes'
import { Selector, SelectorInside } from './organisms'
import { BASE_USE_NAMESPACE_NAV } from 'constants/customizationApiGroupAndVersion'
import { Selector, SelectorInside, SelectorNamespace } from './organisms'
import { Styled } from './styled'
type THeaderProps = {
@@ -20,15 +21,15 @@ export const HeaderSecond: FC<THeaderProps> = ({ inside }) => {
return (
<Styled.BackgroundContainer $bgColor={token.colorFillSecondary} $borderRadius={token.borderRadius}>
<Styled.PaddingContainer $height={HEAD_SECOND_ROW}>
{inside ? (
<SelectorInside clusterName={clusterName} namespace={namespace} />
) : (
{inside && !BASE_USE_NAMESPACE_NAV && <SelectorInside clusterName={clusterName} namespace={namespace} />}
{!inside && !BASE_USE_NAMESPACE_NAV && (
<Selector
clusterName={clusterName}
projectName={projectName || possibleProject}
instanceName={instanceName || possibleInstance}
/>
)}
{BASE_USE_NAMESPACE_NAV && <SelectorNamespace clusterName={clusterName} namespace={namespace} />}
</Styled.PaddingContainer>
</Styled.BackgroundContainer>
)

View File

@@ -1,15 +1,15 @@
import React, { FC, useState } from 'react'
import { Flex } from 'antd'
import { useNavigate } from 'react-router-dom'
import { useSelector } from 'react-redux'
import { RootState } from 'store/store'
import { useDirectUnknownResource } from '@prorobotech/openapi-k8s-toolkit'
import { useNavSelector } from 'hooks/useNavSelector'
import { useMountEffect } from 'hooks/useMountEffect'
import { EntrySelect } from 'components/atoms'
import {
BASE_INSTANCES_API_GROUP,
BASE_INSTANCES_VERSION,
BASE_INSTANCES_RESOURCE_NAME,
BASE_API_GROUP,
BASE_API_VERSION,
BASE_CUSTOMIZATION_NAVIGATION_RESOURCE_NAME,
BASE_CUSTOMIZATION_NAVIGATION_RESOURCE,
} from 'constants/customizationApiGroupAndVersion'
type TSelectorProps = {
@@ -20,7 +20,6 @@ type TSelectorProps = {
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)
@@ -32,6 +31,15 @@ export const Selector: FC<TSelectorProps> = ({ clusterName, projectName, instanc
projectName,
)
const { data: navigationData } = useDirectUnknownResource<{
spec: { projects: { clear: string; change: string }; instances: { clear: string; change: string } }
}>({
uri: `/api/clusters/${clusterName}/k8s/apis/${BASE_API_GROUP}/${BASE_API_VERSION}/${BASE_CUSTOMIZATION_NAVIGATION_RESOURCE_NAME}/${BASE_CUSTOMIZATION_NAVIGATION_RESOURCE}`,
refetchInterval: false,
queryKey: ['navigation', clusterName || 'no-cluster'],
isEnabled: clusterName !== undefined,
})
// const handleClusterChange = (value: string) => {
// setSelectedClusterName(value)
// navigate(`${baseprefix}/clusters/${value}`)
@@ -40,20 +48,34 @@ export const Selector: FC<TSelectorProps> = ({ clusterName, projectName, instanc
const handleProjectChange = (value?: string) => {
if (value) {
setSelectedProjectName(value)
navigate(`${baseprefix}/clusters/${selectedClusterName}/projects/${value}`)
const changeUrl =
navigationData?.spec.projects.change
.replace('{selectedCluster}', selectedClusterName || 'no-cluster')
.replace('{value}', value) || 'no navigation data'
navigate(changeUrl)
} else {
navigate(`${baseprefix}/clusters/${selectedClusterName}/`)
const clearUrl =
navigationData?.spec.projects.clear.replace('{selectedCluster}', selectedClusterName || 'no-cluster') ||
'no navigation data'
navigate(clearUrl)
}
}
const handleInstanceChange = (value?: string) => {
if (value) {
setSelectedInstanceName(value)
navigate(`${baseprefix}/${selectedClusterName}/${value}/${selectedProjectName}/api-table/apps/v1/deployments`)
const changeUrl =
navigationData?.spec.instances.change
.replace('{selectedCluster}', selectedClusterName || 'no-cluster')
.replace('{selectedProject}', selectedProjectName || 'no-project')
.replace('{value}', value) || 'no navigation data'
navigate(changeUrl)
} else {
navigate(
`${baseprefix}/${selectedClusterName}/${selectedProjectName}/api-table/${BASE_INSTANCES_API_GROUP}/${BASE_INSTANCES_VERSION}/${BASE_INSTANCES_RESOURCE_NAME}`,
)
const clearUrl =
navigationData?.spec.instances.clear
.replace('{selectedCluster}', selectedClusterName || 'no-cluster')
.replace('{selectedProject}', selectedProjectName || 'no-project') || 'no navigation data'
navigate(clearUrl)
}
}

View File

@@ -0,0 +1,69 @@
import React, { FC, useState } from 'react'
import { Flex } from 'antd'
import { useNavigate } from 'react-router-dom'
import { useDirectUnknownResource } from '@prorobotech/openapi-k8s-toolkit'
import { useNavSelectorInside } from 'hooks/useNavSelectorInside'
import { useMountEffect } from 'hooks/useMountEffect'
import { EntrySelect } from 'components/atoms'
import {
BASE_API_GROUP,
BASE_API_VERSION,
BASE_CUSTOMIZATION_NAVIGATION_RESOURCE_NAME,
BASE_CUSTOMIZATION_NAVIGATION_RESOURCE,
} from 'constants/customizationApiGroupAndVersion'
type TSelectorNamespaceProps = {
clusterName?: string
namespace?: string
}
export const SelectorNamespace: FC<TSelectorNamespaceProps> = ({ clusterName, namespace }) => {
const navigate = useNavigate()
const [selectedClusterName, setSelectedClusterName] = useState(clusterName)
const [selectedNamespace, setSelectedNamespace] = useState(namespace)
const { namespacesInSidebar } = useNavSelectorInside(selectedClusterName)
const { data: navigationData } = useDirectUnknownResource<{
spec: { namespaces: { clear: string; change: string } }
}>({
uri: `/api/clusters/${clusterName}/k8s/apis/${BASE_API_GROUP}/${BASE_API_VERSION}/${BASE_CUSTOMIZATION_NAVIGATION_RESOURCE_NAME}/${BASE_CUSTOMIZATION_NAVIGATION_RESOURCE}`,
refetchInterval: false,
queryKey: ['navigation', clusterName || 'no-cluster'],
isEnabled: clusterName !== undefined,
})
const handleNamepsaceChange = (value?: string) => {
if (value) {
setSelectedNamespace(value)
const changeUrl =
navigationData?.spec.namespaces.change
.replace('{selectedCluster}', selectedClusterName || 'no-cluster')
.replace('{value}', value) || 'no navigation data'
navigate(changeUrl)
} else {
const clearUrl =
navigationData?.spec.namespaces.clear.replace('{selectedCluster}', selectedClusterName || 'no-cluster') ||
'no navigation data'
navigate(clearUrl)
}
}
useMountEffect(() => {
setSelectedClusterName(clusterName)
setSelectedNamespace(namespace)
}, [namespace, clusterName])
return (
<Flex gap={18} justify="start">
<EntrySelect
placeholder="Namespace"
options={namespacesInSidebar}
value={selectedNamespace}
onChange={handleNamepsaceChange}
disabled={selectedClusterName === undefined || namespacesInSidebar.length === 0}
/>
</Flex>
)
}

View File

@@ -0,0 +1 @@
export * from './SelectorNamespace'

View File

@@ -1,2 +1,3 @@
export * from './Selector'
export * from './SelectorInside'
export * from './SelectorNamespace'

View File

@@ -3,6 +3,7 @@ import { EnrichedTable } from '@prorobotech/openapi-k8s-toolkit'
import { useSelector } from 'react-redux'
import { RootState } from 'store/store'
import { TABLE_PROPS } from 'constants/tableProps'
import { BASE_NAVIGATE_FROM_CLUSTERLIST } from 'constants/customizationApiGroupAndVersion'
export const ListClusters: FC = () => {
const theme = useSelector((state: RootState) => state.openapiTheme.theme)
@@ -40,7 +41,7 @@ export const ListClusters: FC = () => {
key: 'description',
},
]}
pathToNavigate={`${baseprefix}/clusters/~recordValue~`}
pathToNavigate={BASE_NAVIGATE_FROM_CLUSTERLIST}
recordKeysForNavigation={['name']}
withoutControls
tableProps={TABLE_PROPS}

View File

@@ -1,13 +1,26 @@
/* eslint-disable no-underscore-dangle */
export const BASE_API_GROUP = window._env_.CUSTOMIZATION_API_GROUP || import.meta.env.VITE_CUSTOMIZATION_API_GROUP
export const BASE_API_VERSION = window._env_.CUSTOMIZATION_API_VERSION || import.meta.env.VITE_CUSTOMIZATION_API_VERSION
export const BASE_CUSTOMIZATION_NAVIGATION_RESOURCE_NAME =
window._env_.CUSTOMIZATION_NAVIGATION_RESOURCE_NAME || import.meta.env.VITE_CUSTOMIZATION_NAVIGATION_RESOURCE_NAME
export const BASE_CUSTOMIZATION_NAVIGATION_RESOURCE =
window._env_.CUSTOMIZATION_NAVIGATION_RESOURCE || import.meta.env.VITE_CUSTOMIZATION_NAVIGATION_RESOURCE
export const BASE_USE_NAMESPACE_NAV = window._env_.USE_NAMESPACE_NAV || import.meta.env.VITE_USE_NAMESPACE_NAV
export const BASE_NAVIGATE_FROM_CLUSTERLIST =
window._env_.NAVIGATE_FROM_CLUSTERLIST || import.meta.env.VITE_NAVIGATE_FROM_CLUSTERLIST
export const BASE_PROJECTS_API_GROUP = window._env_.PROJECTS_API_GROUP || import.meta.env.VITE_PROJECTS_API_GROUP
export const BASE_PROJECTS_VERSION = window._env_.PROJECTS_VERSION || import.meta.env.VITE_PROJECTS_VERSION
export const BASE_PROJECTS_RESOURCE_NAME =
window._env_.PROJECTS_RESOURCE_NAME || import.meta.env.VITE_PROJECTS_RESOURCE_NAME
export const BASE_MARKETPLACE_RESOURCE_NAME =
window._env_.MARKETPLACE_RESOURCE_NAME || import.meta.env.VITE_MARKETPLACE_RESOURCE_NAME
export const BASE_MARKETPLACE_KIND = window._env_.MARKETPLACE_KIND || import.meta.env.VITE_MARKETPLACE_KIND
export const BASE_INSTANCES_API_GROUP = window._env_.INSTANCES_API_GROUP || import.meta.env.VITE_INSTANCES_API_GROUP
export const BASE_INSTANCES_VERSION = window._env_.INSTANCES_VERSION || import.meta.env.VITE_INSTANCES_VERSION
export const BASE_INSTANCES_RESOURCE_NAME =