From fe5c5cbad144307a495a2e3d9ad05d3133f663e4 Mon Sep 17 00:00:00 2001 From: typescreep Date: Sat, 1 Nov 2025 22:32:46 +0300 Subject: [PATCH] white labels --- .env | 10 ++ .env.options.dist | 10 ++ README.md | 67 ++++++----- server/getDynamicIndex.ts | 20 +++- server/index.ts | 39 +++++++ src/components/organisms/Footer/Footer.tsx | 5 +- .../organisms/Header/organisms/Logo/Logo.tsx | 27 +++-- .../organisms/Header/organisms/Logo/utils.tsx | 17 +++ .../ListInsideClusterAndNs.tsx | 108 +++++++++++++----- .../customizationApiGroupAndVersion.ts | 33 ++++++ 10 files changed, 272 insertions(+), 64 deletions(-) create mode 100644 src/components/organisms/Header/organisms/Logo/utils.tsx diff --git a/.env b/.env index 4bed8ba..b60c478 100644 --- a/.env +++ b/.env @@ -1,3 +1,9 @@ +VITE_TITLE_TEXT="OpenAPI UI" +VITE_LOGO_TEXT="In-Cloud" +VITE_FOOTER_TEXT="PRO Robotech" +VITE_CUSTOM_LOGO_SVG="PHN2ZyB3aWR0aD0iMjYiIGhlaWdodD0iMzEiIHZpZXdCb3g9IjAgMCAyNiAzMSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4=ICAgICAgICAgIDxwYXRoICAgICAgICAgICAgZD0iTTE4Ljc0NjcgMTkuMzY0MUMxOC43NDY3IDIwLjgwOCAxNy42MDY5IDIxLjk3ODQgMTYuMjAwNyAyMS45Nzg0SDguODI1OTdDNy43MTkxMiAyMS45Nzg0IDYuODIxNyAyMS4wNTcgNi44MjE3IDE5LjkyMDRDNi44MjE3IDE4Ljc4MzcgNy43MTkxMiAxNy44NjIzIDguODI1OTcgMTcuODYyM0M4LjgzNTA3IDE3Ljg2MjMgOC44NDQxMiAxNy44NjI2IDguODUzMTcgMTcuODYyN0M5LjAxMDUyIDE2Ljg4NzMgOS44MzU3MiAxNi4xNDM1IDEwLjgzMDIgMTYuMTQzNUMxMC45MzgxIDE2LjE0MzUgMTEuMDQ0MSAxNi4xNTI0IDExLjE0NzQgMTYuMTY5MkMxMS42MjYxIDE1LjEzNzIgMTIuNjUxMiAxNC40MjM5IDEzLjgzODYgMTQuNDIzOUMxNS4yNTg3IDE0LjQyMzkgMTYuNDQ2NyAxNS40NDQzIDE2Ljc0NTMgMTYuODFDMTcuODg5OCAxNy4wNjYyIDE4Ljc0NjYgMTguMTEyMyAxOC43NDY3IDE5LjM2NDFaTTE0Ljc2OCAxMy45MTk0QzE1LjgwNzEgMTQuMTM2NyAxNi44OTQ0IDEzLjA3OCAxNy4xOTYzIDExLjU1NDhDMTcuNDk4NCAxMC4wMzE1IDE2LjkwMDggOC42MjA2MiAxNS44NjE2IDguNDAzMzdDMTQuODIyNSA4LjE4NjEzIDEzLjczNTMgOS4yNDQ4OCAxMy40MzMzIDEwLjc2ODFDMTMuMTMxMyAxMi4yOTEzIDEzLjcyODggMTMuNzAyMiAxNC43NjggMTMuOTE5NFpNMTEuMjcxIDEzLjkxOTRDMTIuMzEwMiAxMy43MDIyIDEyLjkwNzggMTIuMjkxMyAxMi42MDU3IDEwLjc2OEMxMi4zMDM3IDkuMjQ0NzkgMTEuMjE2NSA4LjE4NjA4IDEwLjE3NzQgOC40MDMzMkM5LjEzODI1IDguNjIwNTcgOC41NDA2OCAxMC4wMzE1IDguODQyNyAxMS41NTQ3QzkuMTQ0NjggMTMuMDc3OSAxMC4yMzE5IDE0LjEzNjYgMTEuMjcxIDEzLjkxOTRaTTguNDU0MTcgMTYuNDU1MkM5LjI1NDEyIDE2LjA2NTIgOS40NjMwNyAxNC43OTg0IDguOTIwODcgMTMuNjI1OEM4LjM3ODY2IDEyLjQ1MzEgNy4yOTA1NyAxMS44MTg3IDYuNDkwNTcgMTIuMjA4N0M1LjY5MDYyIDEyLjU5ODggNS40ODE2NyAxMy44NjU2IDYuMDIzODcgMTUuMDM4MkM2LjU2NjEyIDE2LjIxMDggNy42NTQxNyAxNi44NDUzIDguNDU0MTcgMTYuNDU1MlpNMTkuMjk2IDEyLjE1MzlDMTguNDU2IDExLjg2NTggMTcuNDUwNCAxMi42MzA0IDE3LjA0OTkgMTMuODYxN0MxNi42NDk0IDE1LjA5MjkgMTcuMDA1NyAxNi4zMjQ2IDE3Ljg0NTcgMTYuNjEyOEMxOC42ODU3IDE2LjkwMDggMTkuNjkxMyAxNi4xMzYyIDIwLjA5MTggMTQuOTA1QzIwLjQ5MjIgMTMuNjczNyAyMC4xMzYgMTIuNDQyIDE5LjI5NiAxMi4xNTM5Wk0yNiAyMS42NTc4QzI2LjAwMDEgMjIuNjE1MiAyNS41MDI3IDIzLjQ5OTkgMjQuNjk1NCAyMy45Nzg5TDE0LjMwNzIgMzAuMTQwNkMxMy40OTk5IDMwLjYxOTcgMTIuNTA0OCAzMC42MTk4IDExLjY5NyAzMC4xNDFMMS4zMDcxNiAyMy45ODI3QzAuNDk5NjYxIDIzLjUwMzkgMC4wMDIwNDA3NyAyMi42MTkzIDAuMDAxODE4OTYgMjEuNjYxOUw3LjQxNTEzZS0wOCA5LjM0MjA1Qy0wLjAwMDIyMTc0MSA4LjM4NDU2IDAuNDk3MjIxIDcuNDk5OTUgMS4zMDQ1NCA3LjAyMDgyTDExLjY5MjUgMC44NTkyNTJDMTIuNTAwMiAwLjM4MDM5NCAxMy40OTUyIDAuMzgwMjU3IDE0LjMwMjcgMC44NTg3OTdMMjQuNjkyOCA3LjAxNzA5QzI1LjUwMDMgNy40OTU4NSAyNS45OTc5IDguMzgwNDEgMjUuOTk4MiA5LjMzNzg2TDI2IDIxLjY1NzhaTTI0LjYwNjUgMjAuOTk3NkwyNC42MDQ4IDkuOTk4MzhDMjQuNjA0NyA5LjE0MzU3IDI0LjE2MDMgOC4zNTQwOCAyMy40Mzk0IDcuOTI2NDdMMTQuMTYzMSAyLjQyODQzQzEzLjQ0MjIgMi4wMDExIDEyLjU1MzcgMi4wMDEyMyAxMS44MzI4IDIuNDI4ODRMMi41NTgxOSA3LjkyOTg5QzEuODM3NiA4LjM1NzY4IDEuMzkzNCA5LjE0NzMgMS4zOTM1NyAxMC4wMDIxTDEuMzk1MjIgMjEuMDAxNEMxLjM5NTMxIDIxLjg1NjIgMS44Mzk3OCAyMi42NDU3IDIuNTYwNTQgMjMuMDczM0wxMS44MzY3IDI4LjU3MTRDMTIuNTU3OCAyOC45OTg4IDEzLjQ0NjQgMjguOTk4NyAxNC4xNjcxIDI4LjU3MUwyMy40NDE3IDIzLjA2OThDMjQuMTYyNSAyMi42NDIxIDI0LjYwNjYgMjEuODUyNCAyNC42MDY1IDIwLjk5NzZaIg==ICAgICAgICAgICAgZmlsbD0iY3VycmVudENvbG9yIg==ICAgICAgICAgIC8+ICAgICAgICA8L3N2Zz4=" +VITE_CUSTOM_TENANT_TEXT= + VITE_CUSTOMIZATION_API_GROUP=incloud.io VITE_CUSTOMIZATION_API_VERSION=v1alpha @@ -39,3 +45,7 @@ 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= diff --git a/.env.options.dist b/.env.options.dist index 4efefbc..504093f 100644 --- a/.env.options.dist +++ b/.env.options.dist @@ -1,3 +1,9 @@ +TITLE_TEXT= +LOGO_TEXT= +FOOTER_TEXT= +CUSTOM_LOGO_SVG= +CUSTOM_TENANT_TEXT= + KUBE_API_URL= CUSTOMIZATION_API_GROUP= @@ -41,3 +47,7 @@ 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= diff --git a/README.md b/README.md index e9e09cd..7c73b3f 100644 --- a/README.md +++ b/README.md @@ -9,30 +9,43 @@ 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 | +| `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` | +| `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 | diff --git a/server/getDynamicIndex.ts b/server/getDynamicIndex.ts index 3be5d00..eee0311 100644 --- a/server/getDynamicIndex.ts +++ b/server/getDynamicIndex.ts @@ -2,6 +2,23 @@ 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 { + const decodedSvg = Buffer.from(iconSvg, 'base64').toString('utf-8') + const dataUri = `data:image/svg+xml;base64,${decodedSvg}` + return `` + } catch (error) { + console.error('Error processing icon SVG:', error) + return '' + } + } return ` @@ -14,7 +31,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" /> - OpenAPI UI + ${titleText} + ${generateFavicon()} diff --git a/server/index.ts b/server/index.ts index d1bf62f..cb69d3d 100644 --- a/server/index.ts +++ b/server/index.ts @@ -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 = @@ -90,6 +96,19 @@ const 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') @@ -193,11 +212,31 @@ app.get(`${basePrefix ? basePrefix : ''}/env.js`, (_, res) => { ` window._env_ = { ${basePrefix ? ` BASEPREFIX: "${basePrefix}",` : ''} + TITLE_TEXT: ${JSON.stringify(TITLE_TEXT) || '"check envs"'}, + LOGO_TEXT: ${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"'}, NAVIGATE_FROM_CLUSTERLIST: ${JSON.stringify(NAVIGATE_FROM_CLUSTERLIST) || '"check envs"'}, diff --git a/src/components/organisms/Footer/Footer.tsx b/src/components/organisms/Footer/Footer.tsx index c302c01..f36d824 100644 --- a/src/components/organisms/Footer/Footer.tsx +++ b/src/components/organisms/Footer/Footer.tsx @@ -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 ( - PRO Robotech © {new Date().getFullYear()} + + {FOOTER_TEXT} © {new Date().getFullYear()} + ) } diff --git a/src/components/organisms/Header/organisms/Logo/Logo.tsx b/src/components/organisms/Header/organisms/Logo/Logo.tsx index 7823147..1adf2a0 100644 --- a/src/components/organisms/Header/organisms/Logo/Logo.tsx +++ b/src/components/organisms/Header/organisms/Logo/Logo.tsx @@ -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 ( - - - - navigate(`${baseprefix}`)}>In-Cloud - {tenant} + {CUSTOM_LOGO_SVG && typeof CUSTOM_LOGO_SVG === 'string' && CUSTOM_LOGO_SVG.length > 0 ? ( + renderLogo(CUSTOM_LOGO_SVG, token.colorText) + ) : ( + + + + )} + + navigate(`${baseprefix}`)}>{LOGO_TEXT} + + {CUSTOM_TENANT_TEXT && typeof CUSTOM_TENANT_TEXT === 'string' && CUSTOM_TENANT_TEXT.length > 0 + ? CUSTOM_TENANT_TEXT + : tenant} + ) diff --git a/src/components/organisms/Header/organisms/Logo/utils.tsx b/src/components/organisms/Header/organisms/Logo/utils.tsx new file mode 100644 index 0000000..1d04d21 --- /dev/null +++ b/src/components/organisms/Header/organisms/Logo/utils.tsx @@ -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
+ } catch (error) { + // eslint-disable-next-line no-console + console.error('Error decoding custom logo:', error) + return null + } + } + return null +} diff --git a/src/components/organisms/ListInsideClusterAndNs/ListInsideClusterAndNs.tsx b/src/components/organisms/ListInsideClusterAndNs/ListInsideClusterAndNs.tsx index b6fb99f..ac56e5f 100644 --- a/src/components/organisms/ListInsideClusterAndNs/ListInsideClusterAndNs.tsx +++ b/src/components/organisms/ListInsideClusterAndNs/ListInsideClusterAndNs.tsx @@ -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() const [selectedNamespace, setSelectedNamespace] = useState() + 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: 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: selectedCluster !== undefined && isCustomNamespaceResource, }) return ( @@ -55,33 +80,62 @@ export const ListInsideClusterAndNs: FC = () => { /> )} - {selectedCluster && namespacesData.isPending && } - {selectedCluster && namespacesData.error && ( - + {selectedCluster && (isCustomNamespaceResource ? namespacesDataCustom.isPending : namespacesData.isPending) && ( + )} - {selectedCluster && selectedCluster.length > 0 && namespacesData.data && namespacesData.data.items.length > 0 && ( - <> - Namespace - - ({ - label: ns.metadata.name, - value: ns.metadata.name, - }))} - filterOption={filterSelectOptions} - allowClear - showSearch - onSelect={value => { - if (typeof value === 'string') { - setSelectedNamespace(value) - } - }} - onClear={() => setSelectedNamespace(undefined)} - /> - - + {selectedCluster && (isCustomNamespaceResource ? namespacesDataCustom.error : namespacesData.error) && ( + )} + {selectedCluster && + selectedCluster.length > 0 && + ((!isCustomNamespaceResource && namespacesData.data && namespacesData.data.items.length > 0) || + (isCustomNamespaceResource && namespacesDataCustom.data && namespacesDataCustom.data.items.length > 0)) && ( + <> + Namespace + + {isCustomNamespaceResource ? ( + ({ + label: ns.metadata.name, + value: ns.metadata.name, + }))} + filterOption={filterSelectOptions} + allowClear + showSearch + onSelect={value => { + if (typeof value === 'string') { + setSelectedNamespace(value) + } + }} + onClear={() => setSelectedNamespace(undefined)} + /> + ) : ( + ({ + label: ns.metadata.name, + value: ns.metadata.name, + }))} + filterOption={filterSelectOptions} + allowClear + showSearch + onSelect={value => { + if (typeof value === 'string') { + setSelectedNamespace(value) + } + }} + onClear={() => setSelectedNamespace(undefined)} + /> + )} + + + )}