Merge pull request #166 from PRO-Robotech/release/1.0.3

Release/1.0.3
This commit is contained in:
typescreep
2025-11-08 17:36:23 +03:00
committed by GitHub
16 changed files with 395 additions and 82 deletions

20
.env
View File

@@ -1,3 +1,9 @@
VITE_TITLE_TEXT="OpenAPI UI"
VITE_LOGO_TEXT="In-Cloud"
VITE_FOOTER_TEXT="PRO Robotech"
VITE_CUSTOM_LOGO_SVG=
VITE_CUSTOM_TENANT_TEXT=
VITE_CUSTOMIZATION_API_GROUP=incloud.io
VITE_CUSTOMIZATION_API_VERSION=v1alpha
@@ -6,7 +12,9 @@ VITE_CUSTOMIZATION_NAVIGATION_RESOURCE=navigation
VITE_USE_NAMESPACE_NAV=true
VITE_NAVIGATE_FROM_CLUSTERLIST=/openapi-ui/clusters/~recordValue~
VITE_HIDE_INSIDE=false
VITE_NAVIGATE_FROM_CLUSTERLIST=/openapi-ui/~recordValue~/builtin-table/namespaces
VITE_PROJECTS_API_GROUP=incloud.io
VITE_PROJECTS_VERSION=v1alpha
@@ -33,3 +41,13 @@ VITE_REMOVE_BACKLINK_TEXT=true
VITE_DOCS_URL=https://in-cloud.io/docs/tech-docs/introduction/
VITE_SEARCH_TABLE_CUSTOMIZATION_PREFIX=stock-
VITE_BASE_FACTORY_NAMESPACED_API_KEY=base-factory-namespaced-api
VITE_BASE_FACTORY_CLUSTERSCOPED_API_KEY=base-factory-clusterscoped-api
VITE_BASE_FACTORY_NAMESPACED_BUILTIN_KEY=base-factory-namespaced-builtin
VITE_BASE_FACTORY_CLUSTERSCOPED_BUILTIN_KEY=base-factory-clusterscoped-builtin
VITE_BASE_NAMESPACE_FACTORY_KEY=base-factory-clusterscoped-builtin
VITE_CUSTOM_NAMESPACE_API_RESOURCE_API_GROUP=
VITE_CUSTOM_NAMESPACE_API_RESOURCE_API_VERSION=
VITE_CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME=

View File

@@ -1,3 +1,9 @@
TITLE_TEXT=
LOGO_TEXT=
FOOTER_TEXT=
CUSTOM_LOGO_SVG=
CUSTOM_TENANT_TEXT=
KUBE_API_URL=
CUSTOMIZATION_API_GROUP=
@@ -8,6 +14,8 @@ CUSTOMIZATION_NAVIGATION_RESOURCE=
USE_NAMESPACE_NAV=
HIDE_INSIDE=
NAVIGATE_FROM_CLUSTERLIST=
PROJECTS_API_GROUP=
@@ -35,3 +43,13 @@ REMOVE_BACKLINK_TEXT=
DOCS_URL=
SEARCH_TABLE_CUSTOMIZATION_PREFIX=
BASE_FACTORY_NAMESPACED_API_KEY=
BASE_FACTORY_CLUSTERSCOPED_API_KEY=
BASE_FACTORY_NAMESPACED_BUILTIN_KEY=
BASE_FACTORY_CLUSTERSCOPED_BUILTIN_KEY=
BASE_NAMESPACE_FACTORY_KEY=
CUSTOM_NAMESPACE_API_RESOURCE_API_GROUP=
CUSTOM_NAMESPACE_API_RESOURCE_API_VERSION=
CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME=

View File

@@ -9,30 +9,45 @@ Define interfaces in YAML; the app discovers CRDs, watches their objects, and bu
This app can be configured through environment variables.
| Variable | Type | Description |
| ---------------------------------------- | --------- | --------------------------------------------------------------------------------------- |
| `BASEPREFIX` | `string` | Base URL for the app. `/openapi-ui` |
| `KUBE_API_URL` | `string` | URL for the Kubernetes API. `http://api.incloud-web.svc.default.in-cloud.internal:8081` |
| `BFF_URL` | `string` | URL for the BFF |
| `LOGIN_URL` | `string` | Login endpoint. `/oauth/token` |
| `LOGOUT_URL` | `string` | Logout endpoint. `/oauth/logout` |
| `LOGIN_USERNAME_FIELD` | `string` | Field from login endpoint response. `name` |
| `CUSTOMIZATION_API_GROUP` | `string` | API group for customization resources. `front.in-cloud.io` |
| `CUSTOMIZATION_API_VERSION` | `string` | API version for customization resources. `v1alpha1` |
| `CUSTOMIZATION_NAVIGATION_RESOURCE_NAME` | `string` | Resource plural name for navigation settings. `navigations` |
| `CUSTOMIZATION_NAVIGATION_RESOURCE` | `string` | Resource name for navigation settings. `navigation` |
| `USE_NAMESPACE_NAV` | `boolean` | Use namespaces instead of project/instances. `true` |
| `NAVIGATE_FROM_CLUSTERLIST` | `string` | Location to be navigated after selecting cluster. `/openapi-ui/clusters/~recordValue~` |
| `PROJECTS_API_GROUP` | `string` | API group for projects resources. If not using namespace nav. |
| `PROJECTS_VERSION` | `string` | API version for projects resources. If not using namespace nav. |
| `PROJECTS_RESOURCE_NAME` | `string` | Plural name for projects resources. If not using namespace nav. |
| `INSTANCES_API_GROUP` | `string` | API group for instances resources. If not using namespace nav. |
| `INSTANCES_VERSION` | `string` | API version for instances resources. If not using namespace nav. |
| `INSTANCES_RESOURCE_NAME` | `string` | Plural name for instances resources. If not using namespace nav. |
| `MARKETPLACE_RESOURCE_NAME` | `string` | Plural name for marketplace resources for related factory component. |
| `MARKETPLACE_KIND` | `string` | Kind name for marketplace resources for related factory component. |
| `NODE_TERMINAL_DEFAULT_PROFILE` | `string` | Default profile for node terminal component. `baseline` |
| `REMOVE_BACKLINK` | `boolean` | Remove backlink arrow from right-side navigation |
| `REMOVE_BACKLINK_TEXT` | `boolean` | Remove backlink text from right-side navigation |
| `DOCS_URL` | `string` | URL to navigate from question mark |
| `SEARCH_TABLE_CUSTOMIZATION_PREFIX` | `string` | Search tables Customization id prefix |
| Variable | Type | Description |
| --------------------------------------------- | --------- | --------------------------------------------------------------------------------------- |
| `BASEPREFIX` | `string` | Base URL for the app. `/openapi-ui` |
| `KUBE_API_URL` | `string` | URL for the Kubernetes API. `http://api.incloud-web.svc.default.in-cloud.internal:8081` |
| `BFF_URL` | `string` | URL for the BFF |
| `TITLE_TEXT` | `string` | Page title |
| `LOGO_TEXT` | `string` | Logo text near icon |
| `ICON_SVG` | `string` | Favicon base64 encoded |
| `FOOTER_TEXT` | `string` | Footer text |
| `CUSTOM_LOGO_SVG` | `string` | Base64 encoded svg |
| `CUSTOM_TENANT_TEXT` | `string` | Custom tenant text override |
| `LOGIN_URL` | `string` | Login endpoint. `/oauth/token` |
| `LOGOUT_URL` | `string` | Logout endpoint. `/oauth/logout` |
| `LOGIN_USERNAME_FIELD` | `string` | Field from login endpoint response. `name` |
| `CUSTOMIZATION_API_GROUP` | `string` | API group for customization resources. `front.in-cloud.io` |
| `CUSTOMIZATION_API_VERSION` | `string` | API version for customization resources. `v1alpha1` |
| `CUSTOMIZATION_NAVIGATION_RESOURCE_NAME` | `string` | Resource plural name for navigation settings. `navigations` |
| `CUSTOMIZATION_NAVIGATION_RESOURCE` | `string` | Resource name for navigation settings. `navigation` |
| `USE_NAMESPACE_NAV` | `boolean` | Use namespaces instead of project/instances. `true` |
| `HIDE_INSIDE` | `boolean` | Use namespaces instead of project/instances. `true` |
| `NAVIGATE_FROM_CLUSTERLIST` | `string` | Location to be navigated after selecting cluster. `/openapi-ui/clusters/~recordValue~` |
| `PROJECTS_API_GROUP` | `string` | API group for projects resources. If not using namespace nav. |
| `PROJECTS_VERSION` | `string` | API version for projects resources. If not using namespace nav. |
| `PROJECTS_RESOURCE_NAME` | `string` | Plural name for projects resources. If not using namespace nav. |
| `INSTANCES_API_GROUP` | `string` | API group for instances resources. If not using namespace nav. |
| `INSTANCES_VERSION` | `string` | API version for instances resources. If not using namespace nav. |
| `INSTANCES_RESOURCE_NAME` | `string` | Plural name for instances resources. If not using namespace nav. |
| `MARKETPLACE_RESOURCE_NAME` | `string` | Plural name for marketplace resources for related factory component. |
| `MARKETPLACE_KIND` | `string` | Kind name for marketplace resources for related factory component. |
| `NODE_TERMINAL_DEFAULT_PROFILE` | `string` | Default profile for node terminal component. `baseline` |
| `REMOVE_BACKLINK` | `boolean` | Remove backlink arrow from right-side navigation |
| `REMOVE_BACKLINK_TEXT` | `boolean` | Remove backlink text from right-side navigation |
| `DOCS_URL` | `string` | URL to navigate from question mark |
| `SEARCH_TABLE_CUSTOMIZATION_PREFIX` | `string` | Search tables Customization id prefix |
| `BASE_FACTORY_NAMESPACED_API_KEY` | `string` | Base factory key for namespaced API resources |
| `BASE_FACTORY_CLUSTERSCOPED_API_KEY` | `string` | Base factory key for clusterscoped API resources |
| `BASE_FACTORY_NAMESPACED_BUILTIN_KEY` | `string` | Base factory key for namespaced builtin (v1) resources |
| `BASE_FACTORY_CLUSTERSCOPED_BUILTIN_KEY` | `string` | Base factory key for clusterscoped builtin (v1) resources |
| `BASE_NAMESPACE_FACTORY_KEY` | `string` | Base factory key for namespaces |
| `CUSTOM_NAMESPACE_API_RESOURCE_API_GROUP` | `string` | Custom namespace resource: api group |
| `CUSTOM_NAMESPACE_API_RESOURCE_API_VERSION` | `string` | Custom namespace resource: api version |
| `CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME` | `string` | Custom namespace resource: resource name |

8
package-lock.json generated
View File

@@ -11,7 +11,7 @@
"@ant-design/icons": "5.6.0",
"@monaco-editor/react": "4.6.0",
"@originjs/vite-plugin-federation": "1.3.6",
"@prorobotech/openapi-k8s-toolkit": "^0.0.1-alpha.137",
"@prorobotech/openapi-k8s-toolkit": "^1.0.3",
"@readme/openapi-parser": "4.0.0",
"@reduxjs/toolkit": "2.2.5",
"@tanstack/react-query": "5.62.2",
@@ -2804,9 +2804,9 @@
}
},
"node_modules/@prorobotech/openapi-k8s-toolkit": {
"version": "0.0.1-alpha.137",
"resolved": "https://registry.npmjs.org/@prorobotech/openapi-k8s-toolkit/-/openapi-k8s-toolkit-0.0.1-alpha.137.tgz",
"integrity": "sha512-40wpiC8aRBmXZiNnkqYjqo15UEjF9mowoSGqe1/xzxFdjjeZtSBoDoGhLMFy/j6QBCvgaYnNH7mYqxZg5MS7rg==",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@prorobotech/openapi-k8s-toolkit/-/openapi-k8s-toolkit-1.0.3.tgz",
"integrity": "sha512-A8RFEd8CYdvYwnGHSOFEGZ+hotMWvtRRnXsZAcFycBF+oy6GNxCOCSfHv8IWlXfD7Rr4i7Xhp8MtDijAty+80g==",
"license": "MIT",
"dependencies": {
"@monaco-editor/react": "4.6.0",

View File

@@ -20,7 +20,7 @@
"@ant-design/icons": "5.6.0",
"@monaco-editor/react": "4.6.0",
"@originjs/vite-plugin-federation": "1.3.6",
"@prorobotech/openapi-k8s-toolkit": "0.0.1-alpha.137",
"@prorobotech/openapi-k8s-toolkit": "1.0.3",
"@readme/openapi-parser": "4.0.0",
"@reduxjs/toolkit": "2.2.5",
"@tanstack/react-query": "5.62.2",

View File

@@ -2,6 +2,27 @@ export const getDynamicIndex = (baseprefix: string): string => {
try {
const mainJs = 'index-react.js'
const mainCss = 'style.css'
const titleText = process.env.TITLE_TEXT || 'OpenAPI UI'
const iconSvg = process.env.ICON_SVG || ''
// Generate favicon from SVG if provided
const generateFavicon = (): string => {
if (!iconSvg) return ''
try {
// If iconSvg is base64-of-base64, unwrap once
const maybeInner = Buffer.from(iconSvg, 'base64').toString('utf8')
const payload =
/^[A-Za-z0-9+/=\n\r]+$/.test(maybeInner) && !maybeInner.trim().startsWith('<')
? maybeInner // double-encoded → use inner base64
: iconSvg // single-encoded → already fine
const dataUri = `data:image/svg+xml;base64,${payload}`
return `<link rel="icon" type="image/svg+xml" href="${dataUri}">`
} catch (e) {
console.error('Error processing icon SVG:', e)
return ''
}
}
return `<html>
<head>
@@ -14,7 +35,8 @@ export const getDynamicIndex = (baseprefix: string): string => {
href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,700;0,900;1,100;1,300;1,400;1,700;1,900&display=swap"
rel="stylesheet"
/>
<title>OpenAPI UI</title>
<title>${titleText}</title>
${generateFavicon()}
<script src="${baseprefix}/env.js"></script>
<script type="module" crossorigin src="${baseprefix}/${mainJs}"></script>
<link rel="stylesheet" crossorigin href="${baseprefix}/${mainCss}">

View File

@@ -18,6 +18,12 @@ if (process.env.LOCAL === 'true') {
const KUBE_API_URL = process.env.LOCAL === 'true' ? options?.KUBE_API_URL : process.env.KUBE_API_URL
const TITLE_TEXT = process.env.LOCAL === 'true' ? options?.TITLE_TEXT : process.env.TITLE_TEXT
const LOGO_TEXT = process.env.LOCAL === 'true' ? options?.LOGO_TEXT : process.env.LOGO_TEXT
const FOOTER_TEXT = process.env.LOCAL === 'true' ? options?.FOOTER_TEXT : process.env.FOOTER_TEXT
const CUSTOM_LOGO_SVG = process.env.LOCAL === 'true' ? options?.CUSTOM_LOGO_SVG : process.env.CUSTOM_LOGO_SVG
const CUSTOM_TENANT_TEXT = process.env.LOCAL === 'true' ? options?.CUSTOM_TENANT_TEXT : process.env.CUSTOM_TENANT_TEXT
const CUSTOMIZATION_API_GROUP =
process.env.LOCAL === 'true' ? options?.CUSTOMIZATION_API_GROUP : process.env.CUSTOMIZATION_API_GROUP
const CUSTOMIZATION_API_VERSION =
@@ -33,6 +39,7 @@ const 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 HIDE_INSIDE = process.env.LOCAL === 'true' ? options?.HIDE_INSIDE : process.env.HIDE_INSIDE
const NAVIGATE_FROM_CLUSTERLIST =
process.env.LOCAL === 'true' ? options?.NAVIGATE_FROM_CLUSTERLIST : process.env.NAVIGATE_FROM_CLUSTERLIST
@@ -73,6 +80,36 @@ const SEARCH_TABLE_CUSTOMIZATION_PREFIX =
? options?.SEARCH_TABLE_CUSTOMIZATION_PREFIX
: process.env.SEARCH_TABLE_CUSTOMIZATION_PREFIX
const BASE_FACTORY_NAMESPACED_API_KEY =
process.env.LOCAL === 'true' ? options?.BASE_FACTORY_NAMESPACED_API_KEY : process.env.BASE_FACTORY_NAMESPACED_API_KEY
const BASE_FACTORY_CLUSTERSCOPED_API_KEY =
process.env.LOCAL === 'true'
? options?.BASE_FACTORY_CLUSTERSCOPED_API_KEY
: process.env.BASE_FACTORY_CLUSTERSCOPED_API_KEY
const BASE_FACTORY_NAMESPACED_BUILTIN_KEY =
process.env.LOCAL === 'true'
? options?.BASE_FACTORY_NAMESPACED_BUILTIN_KEY
: process.env.BASE_FACTORY_NAMESPACED_BUILTIN_KEY
const BASE_FACTORY_CLUSTERSCOPED_BUILTIN_KEY =
process.env.LOCAL === 'true'
? options?.BASE_FACTORY_CLUSTERSCOPED_BUILTIN_KEY
: process.env.BASE_FACTORY_CLUSTERSCOPED_BUILTIN_KEY
const BASE_NAMESPACE_FACTORY_KEY =
process.env.LOCAL === 'true' ? options?.BASE_NAMESPACE_FACTORY_KEY : process.env.BASE_NAMESPACE_FACTORY_KEY
const CUSTOM_NAMESPACE_API_RESOURCE_API_GROUP =
process.env.LOCAL === 'true'
? options?.CUSTOM_NAMESPACE_API_RESOURCE_API_GROUP
: process.env.CUSTOM_NAMESPACE_API_RESOURCE_API_GROUP
const CUSTOM_NAMESPACE_API_RESOURCE_API_VERSION =
process.env.LOCAL === 'true'
? options?.CUSTOM_NAMESPACE_API_RESOURCE_API_VERSION
: process.env.CUSTOM_NAMESPACE_API_RESOURCE_API_VERSION
const CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME =
process.env.LOCAL === 'true'
? options?.CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME
: process.env.CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME
const healthcheck = require('express-healthcheck')
const promBundle = require('express-prom-bundle')
@@ -176,13 +213,34 @@ app.get(`${basePrefix ? basePrefix : ''}/env.js`, (_, res) => {
`
window._env_ = {
${basePrefix ? ` BASEPREFIX: "${basePrefix}",` : ''}
TITLE_TEXT: ${JSON.stringify(TITLE_TEXT) || '"check envs"'},
LOGO_TEXT: ${LOGO_TEXT !== undefined ? JSON.stringify(LOGO_TEXT) : '"check envs"'},
FOOTER_TEXT: ${JSON.stringify(FOOTER_TEXT) || '"check envs"'},
${CUSTOM_LOGO_SVG ? ` CUSTOM_LOGO_SVG: "${CUSTOM_LOGO_SVG}",` : ''}
${CUSTOM_TENANT_TEXT ? ` CUSTOM_TENANT_TEXT: "${CUSTOM_TENANT_TEXT}",` : ''}
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"'
},
${
CUSTOM_NAMESPACE_API_RESOURCE_API_GROUP
? ` CUSTOM_NAMESPACE_API_RESOURCE_API_GROUP: "${CUSTOM_NAMESPACE_API_RESOURCE_API_GROUP}",`
: ''
}
${
CUSTOM_NAMESPACE_API_RESOURCE_API_VERSION
? ` CUSTOM_NAMESPACE_API_RESOURCE_API_VERSION: "${CUSTOM_NAMESPACE_API_RESOURCE_API_VERSION}",`
: ''
}
${
CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME
? ` CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME: "${CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME}",`
: ''
}
CUSTOMIZATION_NAVIGATION_RESOURCE: ${JSON.stringify(CUSTOMIZATION_NAVIGATION_RESOURCE) || '"check envs"'},
USE_NAMESPACE_NAV: ${USE_NAMESPACE_NAV ? JSON.stringify(USE_NAMESPACE_NAV).toLowerCase() : '"false"'},
HIDE_INSIDE: ${HIDE_INSIDE ? JSON.stringify(HIDE_INSIDE).toLowerCase() : '"false"'},
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"'},
@@ -199,7 +257,14 @@ app.get(`${basePrefix ? basePrefix : ''}/env.js`, (_, res) => {
DOCS_URL: ${JSON.stringify(DOCS_URL) || '"/docs"'},
SEARCH_TABLE_CUSTOMIZATION_PREFIX: ${JSON.stringify(SEARCH_TABLE_CUSTOMIZATION_PREFIX) || '"search-"'},
REMOVE_BACKLINK: ${!!REMOVE_BACKLINK ? JSON.stringify(REMOVE_BACKLINK).toLowerCase() : '"false"'},
REMOVE_BACKLINK_TEXT: ${!!REMOVE_BACKLINK_TEXT ? JSON.stringify(REMOVE_BACKLINK_TEXT).toLowerCase() : '"false"'}
REMOVE_BACKLINK_TEXT: ${!!REMOVE_BACKLINK_TEXT ? JSON.stringify(REMOVE_BACKLINK_TEXT).toLowerCase() : '"false"'},
BASE_FACTORY_NAMESPACED_API_KEY: ${JSON.stringify(BASE_FACTORY_NAMESPACED_API_KEY) || '"check envs"'},
BASE_FACTORY_CLUSTERSCOPED_API_KEY: ${JSON.stringify(BASE_FACTORY_CLUSTERSCOPED_API_KEY) || '"check envs"'},
BASE_FACTORY_NAMESPACED_BUILTIN_KEY: ${JSON.stringify(BASE_FACTORY_NAMESPACED_BUILTIN_KEY) || '"check envs"'},
BASE_FACTORY_CLUSTERSCOPED_BUILTIN_KEY: ${
JSON.stringify(BASE_FACTORY_CLUSTERSCOPED_BUILTIN_KEY) || '"check envs"'
},
BASE_NAMESPACE_FACTORY_KEY: ${JSON.stringify(BASE_NAMESPACE_FACTORY_KEY) || '"check envs"'}
}
`,
)

View File

@@ -18,6 +18,7 @@ const Container = styled.div<TContainerProps>`
direction: rtl;
max-height: ${({ $maxHeight }) => $maxHeight || 'initial'};
user-select: none;
border-top-right-radius: 7px;
& ul {
direction: ltr;
@@ -50,9 +51,9 @@ const Container = styled.div<TContainerProps>`
/* corner radius */
&& .ant-menu li:first-child div:first-child {
border-top-right-radius: 8px;
}
/* && .ant-menu li:first-child div:first-child {
border-top-right-radius: 4px;
} */
/* selected header bgcolor */
@@ -71,7 +72,7 @@ const Container = styled.div<TContainerProps>`
}
&& .ant-menu-sub .ant-menu-item.ant-menu-item-selected {
width: 225px;
width: 214px;
margin-left: 25px !important;
padding-left: 23px !important;
transition: padding 0s;

View File

@@ -1,11 +1,14 @@
import React, { FC } from 'react'
import { Typography } from 'antd'
import { FOOTER_TEXT } from 'constants/customizationApiGroupAndVersion'
import { Styled } from './styled'
export const Footer: FC = () => {
return (
<Styled.Container>
<Typography.Text type="secondary">PRO Robotech © {new Date().getFullYear()}</Typography.Text>
<Typography.Text type="secondary">
{FOOTER_TEXT} © {new Date().getFullYear()}
</Typography.Text>
</Styled.Container>
)
}

View File

@@ -3,6 +3,8 @@ import { Flex, theme as antdtheme } from 'antd'
import { useNavigate } from 'react-router-dom'
import { useSelector } from 'react-redux'
import { RootState } from 'store/store'
import { LOGO_TEXT, CUSTOM_LOGO_SVG, CUSTOM_TENANT_TEXT } from 'constants/customizationApiGroupAndVersion'
import { renderLogo } from './utils'
import { Styled } from './styled'
export const Logo: FC = () => {
@@ -17,14 +19,23 @@ export const Logo: FC = () => {
return (
<Styled.CursorPointer $svgHoverFill={token.colorInfoActive}>
<Flex gap={8} align="center">
<svg width="26" height="31" viewBox="0 0 26 31" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M18.7467 19.3641C18.7467 20.808 17.6069 21.9784 16.2007 21.9784H8.82597C7.71912 21.9784 6.8217 21.057 6.8217 19.9204C6.8217 18.7837 7.71912 17.8623 8.82597 17.8623C8.83507 17.8623 8.84412 17.8626 8.85317 17.8627C9.01052 16.8873 9.83572 16.1435 10.8302 16.1435C10.9381 16.1435 11.0441 16.1524 11.1474 16.1692C11.6261 15.1372 12.6512 14.4239 13.8386 14.4239C15.2587 14.4239 16.4467 15.4443 16.7453 16.81C17.8898 17.0662 18.7466 18.1123 18.7467 19.3641ZM14.768 13.9194C15.8071 14.1367 16.8944 13.078 17.1963 11.5548C17.4984 10.0315 16.9008 8.62062 15.8616 8.40337C14.8225 8.18613 13.7353 9.24488 13.4333 10.7681C13.1313 12.2913 13.7288 13.7022 14.768 13.9194ZM11.271 13.9194C12.3102 13.7022 12.9078 12.2913 12.6057 10.768C12.3037 9.24479 11.2165 8.18608 10.1774 8.40332C9.13825 8.62057 8.54068 10.0315 8.8427 11.5547C9.14468 13.0779 10.2319 14.1366 11.271 13.9194ZM8.45417 16.4552C9.25412 16.0652 9.46307 14.7984 8.92087 13.6258C8.37866 12.4531 7.29057 11.8187 6.49057 12.2087C5.69062 12.5988 5.48167 13.8656 6.02387 15.0382C6.56612 16.2108 7.65417 16.8453 8.45417 16.4552ZM19.296 12.1539C18.456 11.8658 17.4504 12.6304 17.0499 13.8617C16.6494 15.0929 17.0057 16.3246 17.8457 16.6128C18.6857 16.9008 19.6913 16.1362 20.0918 14.905C20.4922 13.6737 20.136 12.442 19.296 12.1539ZM26 21.6578C26.0001 22.6152 25.5027 23.4999 24.6954 23.9789L14.3072 30.1406C13.4999 30.6197 12.5048 30.6198 11.697 30.141L1.30716 23.9827C0.499661 23.5039 0.00204077 22.6193 0.00181896 21.6619L7.41513e-08 9.34205C-0.000221741 8.38456 0.497221 7.49995 1.30454 7.02082L11.6925 0.859252C12.5002 0.380394 13.4952 0.380257 14.3027 0.858797L24.6928 7.01709C25.5003 7.49585 25.9979 8.38041 25.9982 9.33786L26 21.6578ZM24.6065 20.9976L24.6048 9.99838C24.6047 9.14357 24.1603 8.35408 23.4394 7.92647L14.1631 2.42843C13.4422 2.0011 12.5537 2.00123 11.8328 2.42884L2.55819 7.92989C1.8376 8.35768 1.3934 9.1473 1.39357 10.0021L1.39522 21.0014C1.39531 21.8562 1.83978 22.6457 2.56054 23.0733L11.8367 28.5714C12.5578 28.9988 13.4464 28.9987 14.1671 28.571L23.4417 23.0698C24.1625 22.6421 24.6066 21.8524 24.6065 20.9976Z"
fill={token.colorText}
/>
</svg>
<Styled.LogoText onClick={() => navigate(`${baseprefix}`)}>In-Cloud</Styled.LogoText>
<Styled.TenantText $color={token.colorTextDescription}>{tenant}</Styled.TenantText>
{CUSTOM_LOGO_SVG && typeof CUSTOM_LOGO_SVG === 'string' && CUSTOM_LOGO_SVG.length > 0 ? (
renderLogo(CUSTOM_LOGO_SVG, token.colorText)
) : (
<svg width="26" height="31" viewBox="0 0 26 31" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M18.7467 19.3641C18.7467 20.808 17.6069 21.9784 16.2007 21.9784H8.82597C7.71912 21.9784 6.8217 21.057 6.8217 19.9204C6.8217 18.7837 7.71912 17.8623 8.82597 17.8623C8.83507 17.8623 8.84412 17.8626 8.85317 17.8627C9.01052 16.8873 9.83572 16.1435 10.8302 16.1435C10.9381 16.1435 11.0441 16.1524 11.1474 16.1692C11.6261 15.1372 12.6512 14.4239 13.8386 14.4239C15.2587 14.4239 16.4467 15.4443 16.7453 16.81C17.8898 17.0662 18.7466 18.1123 18.7467 19.3641ZM14.768 13.9194C15.8071 14.1367 16.8944 13.078 17.1963 11.5548C17.4984 10.0315 16.9008 8.62062 15.8616 8.40337C14.8225 8.18613 13.7353 9.24488 13.4333 10.7681C13.1313 12.2913 13.7288 13.7022 14.768 13.9194ZM11.271 13.9194C12.3102 13.7022 12.9078 12.2913 12.6057 10.768C12.3037 9.24479 11.2165 8.18608 10.1774 8.40332C9.13825 8.62057 8.54068 10.0315 8.8427 11.5547C9.14468 13.0779 10.2319 14.1366 11.271 13.9194ZM8.45417 16.4552C9.25412 16.0652 9.46307 14.7984 8.92087 13.6258C8.37866 12.4531 7.29057 11.8187 6.49057 12.2087C5.69062 12.5988 5.48167 13.8656 6.02387 15.0382C6.56612 16.2108 7.65417 16.8453 8.45417 16.4552ZM19.296 12.1539C18.456 11.8658 17.4504 12.6304 17.0499 13.8617C16.6494 15.0929 17.0057 16.3246 17.8457 16.6128C18.6857 16.9008 19.6913 16.1362 20.0918 14.905C20.4922 13.6737 20.136 12.442 19.296 12.1539ZM26 21.6578C26.0001 22.6152 25.5027 23.4999 24.6954 23.9789L14.3072 30.1406C13.4999 30.6197 12.5048 30.6198 11.697 30.141L1.30716 23.9827C0.499661 23.5039 0.00204077 22.6193 0.00181896 21.6619L7.41513e-08 9.34205C-0.000221741 8.38456 0.497221 7.49995 1.30454 7.02082L11.6925 0.859252C12.5002 0.380394 13.4952 0.380257 14.3027 0.858797L24.6928 7.01709C25.5003 7.49585 25.9979 8.38041 25.9982 9.33786L26 21.6578ZM24.6065 20.9976L24.6048 9.99838C24.6047 9.14357 24.1603 8.35408 23.4394 7.92647L14.1631 2.42843C13.4422 2.0011 12.5537 2.00123 11.8328 2.42884L2.55819 7.92989C1.8376 8.35768 1.3934 9.1473 1.39357 10.0021L1.39522 21.0014C1.39531 21.8562 1.83978 22.6457 2.56054 23.0733L11.8367 28.5714C12.5578 28.9988 13.4464 28.9987 14.1671 28.571L23.4417 23.0698C24.1625 22.6421 24.6066 21.8524 24.6065 20.9976Z"
fill={token.colorText}
/>
</svg>
)}
<Styled.LogoText onClick={() => navigate(`${baseprefix}`)}>{LOGO_TEXT}</Styled.LogoText>
<Styled.TenantText $color={token.colorTextDescription}>
{CUSTOM_TENANT_TEXT && typeof CUSTOM_TENANT_TEXT === 'string' && CUSTOM_TENANT_TEXT.length > 0
? CUSTOM_TENANT_TEXT
: tenant}
</Styled.TenantText>
</Flex>
</Styled.CursorPointer>
)

View File

@@ -0,0 +1,17 @@
export const renderLogo = (customLogo: string, colorText: string): JSX.Element | null => {
if (customLogo) {
// Decode base64 SVG and replace all fill placeholders
try {
const decodedSvg = atob(customLogo)
// Replace all instances of {token.colorText} with actual color
const svgWithFill = decodedSvg.replace(/\{token\.colorText\}/g, `"${colorText}"`)
// eslint-disable-next-line react/no-danger
return <div dangerouslySetInnerHTML={{ __html: svgWithFill }} />
} catch (error) {
// eslint-disable-next-line no-console
console.error('Error decoding custom logo:', error)
return null
}
}
return null
}

View File

@@ -6,6 +6,7 @@ import { useSelector } from 'react-redux'
import type { RootState } from 'store/store'
import { useAuth } from 'hooks/useAuth'
import { logout } from 'api/auth'
import { BASE_HIDE_INSIDE } from 'constants/customizationApiGroupAndVersion'
import { Styled } from './styled'
export const User: FC = () => {
@@ -23,10 +24,14 @@ export const User: FC = () => {
// key: '1',
// label: <ThemeSelector />,
// },
{
key: '2',
label: <div onClick={() => navigate(`${baseprefix}/inside/clusters`)}>Inside</div>,
},
...(BASE_HIDE_INSIDE === 'true'
? []
: [
{
key: '2',
label: <div onClick={() => navigate(`${baseprefix}/inside/clusters`)}>Inside</div>,
},
]),
{
key: '3',
label: (

View File

@@ -1,10 +1,15 @@
import React, { FC, useState } from 'react'
import { Button, Alert, Spin, Typography } from 'antd'
import { filterSelectOptions, Spacer, useBuiltinResources } from '@prorobotech/openapi-k8s-toolkit'
import { filterSelectOptions, Spacer, useBuiltinResources, useApiResources } from '@prorobotech/openapi-k8s-toolkit'
import { useNavigate } from 'react-router-dom'
import { useSelector, useDispatch } from 'react-redux'
import { RootState } from 'store/store'
import { setCluster } from 'store/cluster/cluster/cluster'
import {
CUSTOM_NAMESPACE_API_RESOURCE_API_GROUP,
CUSTOM_NAMESPACE_API_RESOURCE_API_VERSION,
CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME,
} from 'constants/customizationApiGroupAndVersion'
import { Styled } from './styled'
export const ListInsideClusterAndNs: FC = () => {
@@ -17,11 +22,31 @@ export const ListInsideClusterAndNs: FC = () => {
const [selectedCluster, setSelectedCluster] = useState<string>()
const [selectedNamespace, setSelectedNamespace] = useState<string>()
const isCustomNamespaceResource =
CUSTOM_NAMESPACE_API_RESOURCE_API_GROUP &&
typeof CUSTOM_NAMESPACE_API_RESOURCE_API_GROUP === 'string' &&
CUSTOM_NAMESPACE_API_RESOURCE_API_GROUP.length > 0 &&
CUSTOM_NAMESPACE_API_RESOURCE_API_VERSION &&
typeof CUSTOM_NAMESPACE_API_RESOURCE_API_VERSION === 'string' &&
CUSTOM_NAMESPACE_API_RESOURCE_API_VERSION.length > 0 &&
CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME &&
typeof CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME === 'string' &&
CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME.length > 0
const namespacesData = useBuiltinResources({
clusterName: selectedCluster || '',
typeName: 'namespaces',
limit: null,
isEnabled: selectedCluster !== undefined,
isEnabled: Boolean(selectedCluster !== undefined && !isCustomNamespaceResource),
})
const namespacesDataCustom = useApiResources({
clusterName: selectedCluster || '',
apiGroup: CUSTOM_NAMESPACE_API_RESOURCE_API_GROUP,
apiVersion: CUSTOM_NAMESPACE_API_RESOURCE_API_VERSION,
typeName: CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME,
limit: null,
isEnabled: Boolean(selectedCluster !== undefined && isCustomNamespaceResource),
})
return (
@@ -55,33 +80,62 @@ export const ListInsideClusterAndNs: FC = () => {
/>
)}
<Spacer $space={8} $samespace />
{selectedCluster && namespacesData.isPending && <Spin />}
{selectedCluster && namespacesData.error && (
<Alert message={`An error has occurred: ${namespacesData.error?.message} `} type="error" />
{selectedCluster && (isCustomNamespaceResource ? namespacesDataCustom.isPending : namespacesData.isPending) && (
<Spin />
)}
{selectedCluster && selectedCluster.length > 0 && namespacesData.data && namespacesData.data.items.length > 0 && (
<>
<Typography.Text>Namespace</Typography.Text>
<Spacer $space={8} $samespace />
<Styled.FullWidthSelect
placeholder="Choose namespace"
options={namespacesData.data.items.map(ns => ({
label: ns.metadata.name,
value: ns.metadata.name,
}))}
filterOption={filterSelectOptions}
allowClear
showSearch
onSelect={value => {
if (typeof value === 'string') {
setSelectedNamespace(value)
}
}}
onClear={() => setSelectedNamespace(undefined)}
/>
<Spacer $space={8} $samespace />
</>
{selectedCluster && (isCustomNamespaceResource ? namespacesDataCustom.error : namespacesData.error) && (
<Alert
message={`An error has occurred: ${
isCustomNamespaceResource ? namespacesDataCustom.error?.message : namespacesData.error?.message
} `}
type="error"
/>
)}
{selectedCluster &&
selectedCluster.length > 0 &&
((!isCustomNamespaceResource && namespacesData.data && namespacesData.data.items.length > 0) ||
(isCustomNamespaceResource && namespacesDataCustom.data && namespacesDataCustom.data.items.length > 0)) && (
<>
<Typography.Text>Namespace</Typography.Text>
<Spacer $space={8} $samespace />
{isCustomNamespaceResource ? (
<Styled.FullWidthSelect
placeholder="Choose namespace"
options={namespacesDataCustom.data?.items.map(ns => ({
label: ns.metadata.name,
value: ns.metadata.name,
}))}
filterOption={filterSelectOptions}
allowClear
showSearch
onSelect={value => {
if (typeof value === 'string') {
setSelectedNamespace(value)
}
}}
onClear={() => setSelectedNamespace(undefined)}
/>
) : (
<Styled.FullWidthSelect
placeholder="Choose namespace"
options={namespacesData.data?.items.map(ns => ({
label: ns.metadata.name,
value: ns.metadata.name,
}))}
filterOption={filterSelectOptions}
allowClear
showSearch
onSelect={value => {
if (typeof value === 'string') {
setSelectedNamespace(value)
}
}}
onClear={() => setSelectedNamespace(undefined)}
/>
)}
<Spacer $space={8} $samespace />
</>
)}
<Button
onClick={() =>
navigate(`${baseprefix}/inside/${cluster}${selectedNamespace ? `/${selectedNamespace}` : ''}/apis`)

View File

@@ -11,6 +11,7 @@ const BackgroundContainer = styled.div<TBackgroundContainerProps>`
border-top-right-radius: ${({ $borderRadius }) => $borderRadius}px;
border: 1px ${({ $borderColor }) => $borderColor} solid;
border-left: 0;
box-sizing: border-box;
width: 250px;
height: calc(100vh - ${HEAD_FIRST_ROW}px);
`

View File

@@ -1,4 +1,25 @@
/* eslint-disable no-underscore-dangle */
export const TITLE_TEXT = import.meta.env.DEV
? window._env_.TITLE_TEXT || import.meta.env.VITE_TITLE_TEXT
: window._env_.TITLE_TEXT
export const LOGO_TEXT = import.meta.env.DEV
? window._env_.LOGO_TEXT || import.meta.env.VITE_LOGO_TEXT
: window._env_.LOGO_TEXT
export const FOOTER_TEXT = import.meta.env.DEV
? window._env_.FOOTER_TEXT || import.meta.env.VITE_FOOTER_TEXT
: window._env_.FOOTER_TEXT
export const CUSTOM_LOGO_SVG = import.meta.env.DEV
? window._env_.CUSTOM_LOGO_SVG || import.meta.env.VITE_CUSTOM_LOGO_SVG
: window._env_.CUSTOM_LOGO_SVG
export const CUSTOM_TENANT_TEXT = import.meta.env.DEV
? window._env_.CUSTOM_TENANT_TEXT || import.meta.env.VITE_CUSTOM_TENANT_TEXT
: window._env_.CUSTOM_TENANT_TEXT
export const BASE_API_GROUP = import.meta.env.DEV
? window._env_.CUSTOMIZATION_API_GROUP || import.meta.env.VITE_CUSTOMIZATION_API_GROUP
: window._env_.CUSTOMIZATION_API_GROUP
@@ -17,6 +38,10 @@ export const BASE_USE_NAMESPACE_NAV = import.meta.env.DEV
? window._env_.USE_NAMESPACE_NAV || import.meta.env.VITE_USE_NAMESPACE_NAV
: window._env_.USE_NAMESPACE_NAV
export const BASE_HIDE_INSIDE = import.meta.env.DEV
? window._env_.HIDE_INSIDE || import.meta.env.VITE_HIDE_INSIDE
: window._env_.HIDE_INSIDE
export const BASE_NAVIGATE_FROM_CLUSTERLIST = import.meta.env.DEV
? window._env_.NAVIGATE_FROM_CLUSTERLIST || import.meta.env.VITE_NAVIGATE_FROM_CLUSTERLIST
: window._env_.NAVIGATE_FROM_CLUSTERLIST
@@ -77,3 +102,31 @@ export const BASE_REMOVE_BACKLINK_TEXT = import.meta.env.DEV
? window._env_.REMOVE_BACKLINK_TEXT === 'true' ||
import.meta.env.VITE_REMOVE_BACKLINK_TEXT?.toString().toLowerCase() === 'true'
: window._env_.REMOVE_BACKLINK_TEXT === 'true'
export const BASE_FACTORY_NAMESPACED_API_KEY = import.meta.env.DEV
? window._env_.BASE_FACTORY_NAMESPACED_API_KEY || import.meta.env.VITE_BASE_FACTORY_NAMESPACED_API_KEY
: window._env_.BASE_FACTORY_NAMESPACED_API_KEY
export const BASE_FACTORY_CLUSTERSCOPED_API_KEY = import.meta.env.DEV
? window._env_.BASE_FACTORY_CLUSTERSCOPED_API_KEY || import.meta.env.VITE_BASE_FACTORY_CLUSTERSCOPED_API_KEY
: window._env_.BASE_FACTORY_CLUSTERSCOPED_API_KEY
export const BASE_FACTORY_NAMESPACED_BUILTIN_KEY = import.meta.env.DEV
? window._env_.BASE_FACTORY_NAMESPACED_BUILTIN_KEY || import.meta.env.VITE_BASE_FACTORY_NAMESPACED_BUILTIN_KEY
: window._env_.BASE_FACTORY_NAMESPACED_BUILTIN_KEY
export const BASE_FACTORY_CLUSTERSCOPED_BUILTIN_KEY = import.meta.env.DEV
? window._env_.BASE_FACTORY_CLUSTERSCOPED_BUILTIN_KEY || import.meta.env.VITE_BASE_FACTORY_CLUSTERSCOPED_BUILTIN_KEY
: window._env_.BASE_FACTORY_CLUSTERSCOPED_BUILTIN_KEY
export const BASE_NAMESPACE_FACTORY_KEY = import.meta.env.DEV
? window._env_.BASE_NAMESPACE_FACTORY_KEY || import.meta.env.VITE_BASE_NAMESPACE_FACTORY_KEY
: window._env_.BASE_NAMESPACE_FACTORY_KEY
export const CUSTOM_NAMESPACE_API_RESOURCE_API_GROUP = import.meta.env.DEV
? window._env_.CUSTOM_NAMESPACE_API_RESOURCE_API_GROUP || import.meta.env.VITE_CUSTOM_NAMESPACE_API_RESOURCE_API_GROUP
: window._env_.CUSTOM_NAMESPACE_API_RESOURCE_API_GROUP
export const CUSTOM_NAMESPACE_API_RESOURCE_API_VERSION = import.meta.env.DEV
? window._env_.CUSTOM_NAMESPACE_API_RESOURCE_API_VERSION ||
import.meta.env.VITE_CUSTOM_NAMESPACE_API_RESOURCE_API_VERSION
: window._env_.CUSTOM_NAMESPACE_API_RESOURCE_API_VERSION
export const CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME = import.meta.env.DEV
? window._env_.CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME ||
import.meta.env.VITE_CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME
: window._env_.CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME

View File

@@ -1,6 +1,11 @@
import { TClusterList, TSingleResource, useBuiltinResources } from '@prorobotech/openapi-k8s-toolkit'
import { TClusterList, TSingleResource, useBuiltinResources, useApiResources } from '@prorobotech/openapi-k8s-toolkit'
import { useSelector } from 'react-redux'
import { RootState } from 'store/store'
import {
CUSTOM_NAMESPACE_API_RESOURCE_API_GROUP,
CUSTOM_NAMESPACE_API_RESOURCE_API_VERSION,
CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME,
} from 'constants/customizationApiGroupAndVersion'
const mappedClusterToOptionInSidebar = ({ name }: TClusterList[number]): { value: string; label: string } => ({
value: name,
@@ -15,15 +20,40 @@ const mappedNamespaceToOptionInSidebar = ({ metadata }: TSingleResource): { valu
export const useNavSelectorInside = (clusterName?: string) => {
const clusterList = useSelector((state: RootState) => state.clusterList.clusterList)
const isCustomNamespaceResource =
CUSTOM_NAMESPACE_API_RESOURCE_API_GROUP &&
typeof CUSTOM_NAMESPACE_API_RESOURCE_API_GROUP === 'string' &&
CUSTOM_NAMESPACE_API_RESOURCE_API_GROUP.length > 0 &&
CUSTOM_NAMESPACE_API_RESOURCE_API_VERSION &&
typeof CUSTOM_NAMESPACE_API_RESOURCE_API_VERSION === 'string' &&
CUSTOM_NAMESPACE_API_RESOURCE_API_VERSION.length > 0 &&
CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME &&
typeof CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME === 'string' &&
CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME.length > 0
const { data: namespaces } = useBuiltinResources({
clusterName: clusterName || '',
typeName: 'namespaces',
limit: null,
isEnabled: Boolean(clusterName),
isEnabled: Boolean(clusterName !== undefined && !isCustomNamespaceResource),
})
const { data: namespacesCustom } = useApiResources({
clusterName: clusterName || '',
apiGroup: CUSTOM_NAMESPACE_API_RESOURCE_API_GROUP,
apiVersion: CUSTOM_NAMESPACE_API_RESOURCE_API_VERSION,
typeName: CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME,
limit: null,
isEnabled: Boolean(clusterName !== undefined && isCustomNamespaceResource),
})
const clustersInSidebar = clusterList ? clusterList.map(mappedClusterToOptionInSidebar) : []
const namespacesInSidebar = clusterName && namespaces ? namespaces.items.map(mappedNamespaceToOptionInSidebar) : []
const namespacesInSidebarCustom =
clusterName && namespacesCustom ? namespacesCustom.items.map(mappedNamespaceToOptionInSidebar) : []
return { clustersInSidebar, namespacesInSidebar }
return {
clustersInSidebar,
namespacesInSidebar: isCustomNamespaceResource ? namespacesInSidebarCustom : namespacesInSidebar,
}
}