diff --git a/.env b/.env index 1844f4b..d117d9b 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= +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= diff --git a/.env.options.dist b/.env.options.dist index a04925e..d36b462 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= @@ -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= diff --git a/README.md b/README.md index e9e09cd..5614994 100644 --- a/README.md +++ b/README.md @@ -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 | diff --git a/package-lock.json b/package-lock.json index 7bcdf8c..b502cbe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index bff473d..9ee35fd 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/server/getDynamicIndex.ts b/server/getDynamicIndex.ts index 3be5d00..5990242 100644 --- a/server/getDynamicIndex.ts +++ b/server/getDynamicIndex.ts @@ -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 `` + } catch (e) { + console.error('Error processing icon SVG:', e) + return '' + } + } return ` @@ -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" /> - OpenAPI UI + ${titleText} + ${generateFavicon()} diff --git a/server/index.ts b/server/index.ts index f95b1ec..f14aca6 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 = @@ -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"'} } `, ) diff --git a/src/components/molecules/ManageableSidebar/styled.ts b/src/components/molecules/ManageableSidebar/styled.ts index 8be95f6..e80e757 100644 --- a/src/components/molecules/ManageableSidebar/styled.ts +++ b/src/components/molecules/ManageableSidebar/styled.ts @@ -18,6 +18,7 @@ const Container = styled.div` 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` /* 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` } && .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; 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/Header/organisms/User/User.tsx b/src/components/organisms/Header/organisms/User/User.tsx index efe7ac3..69f89ce 100644 --- a/src/components/organisms/Header/organisms/User/User.tsx +++ b/src/components/organisms/Header/organisms/User/User.tsx @@ -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: , // }, - { - key: '2', - label:
navigate(`${baseprefix}/inside/clusters`)}>Inside
, - }, + ...(BASE_HIDE_INSIDE === 'true' + ? [] + : [ + { + key: '2', + label:
navigate(`${baseprefix}/inside/clusters`)}>Inside
, + }, + ]), { key: '3', label: ( diff --git a/src/components/organisms/ListInsideClusterAndNs/ListInsideClusterAndNs.tsx b/src/components/organisms/ListInsideClusterAndNs/ListInsideClusterAndNs.tsx index b6fb99f..4f52a36 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: 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 = () => { /> )} - {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)} + /> + )} + + + )}