diff --git a/public/locales/en.json b/public/locales/en.json index 77b0852e..24d31910 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -264,6 +264,7 @@ "copy": "Copy" }, "yaml": { - "copiedToClipboard": "YAML copied to clipboard!" + "copiedToClipboard": "YAML copied to clipboard!", + "YAML": "YAML" } } diff --git a/src/components/ControlPlane/FluxList.tsx b/src/components/ControlPlane/FluxList.tsx index 15ea06ac..8a4c9072 100644 --- a/src/components/ControlPlane/FluxList.tsx +++ b/src/components/ControlPlane/FluxList.tsx @@ -1,15 +1,21 @@ import ConfiguredAnalyticstable from '../Shared/ConfiguredAnalyticsTable.tsx'; import { AnalyticalTableColumnDefinition, + FlexBox, Title, } from '@ui5/webcomponents-react'; import IllustratedError from '../Shared/IllustratedError.tsx'; import useResource from '../../lib/api/useApiResource'; import { FluxRequest } from '../../lib/api/types/flux/listGitRepo'; -import { FluxKustomization } from '../../lib/api/types/flux/listKustomization'; +import { + FluxKustomization, + KustomizationsResponse, +} from '../../lib/api/types/flux/listKustomization'; import { useTranslation } from 'react-i18next'; import { timeAgo } from '../../utils/i18n/timeAgo.ts'; import { ResourceStatusCell } from '../Shared/ResourceStatusCell.tsx'; +import { YamlViewButton } from '../Yaml/YamlViewButton.tsx'; +import { useMemo } from 'react'; export default function FluxList() { const { @@ -39,8 +45,96 @@ export default function FluxList() { created: string; isReady: boolean; statusUpdateTime?: string; + item: unknown; }; + const gitReposColumns: AnalyticalTableColumnDefinition[] = useMemo( + () => [ + { + Header: t('FluxList.tableNameHeader'), + accessor: 'name', + minWidth: 250, + }, + { + Header: t('FluxList.tableCreatedHeader'), + accessor: 'created', + }, + { + Header: t('FluxList.tableVersionHeader'), + accessor: 'revision', + }, + { + Header: t('FluxList.tableStatusHeader'), + accessor: 'status', + width: 85, + hAlign: 'Center', + Cell: (cellData: CellData) => + cellData.cell.row.original?.isReady != null ? ( + + ) : null, + }, + { + Header: t('yaml.YAML'), + hAlign: 'Center', + width: 85, + accessor: 'yaml', + Cell: (cellData: CellData) => ( + + ), + }, + ], + [], + ); + + const kustomizationsColumns: AnalyticalTableColumnDefinition[] = useMemo( + () => [ + { + Header: t('FluxList.tableNameHeader'), + accessor: 'name', + minWidth: 250, + }, + { + Header: t('FluxList.tableCreatedHeader'), + accessor: 'created', + }, + { + Header: t('FluxList.tableStatusHeader'), + accessor: 'status', + width: 85, + hAlign: 'Center', + Cell: (cellData: CellData) => + cellData.cell.row.original?.isReady != null ? ( + + ) : null, + }, + + { + Header: t('yaml.YAML'), + hAlign: 'Center', + width: 85, + accessor: 'yaml', + Cell: (cellData: CellData) => ( + + ), + }, + ], + [], + ); + if (repoErr || kustomizationErr) { return ( ) => - cellData.cell.row.original?.isReady != null ? ( - - ) : null, - }, - { - Header: t('FluxList.tableVersionHeader'), - accessor: 'revision', - }, - { - Header: t('FluxList.tableCreatedHeader'), - accessor: 'created', - }, - ]; - - const kustomizationsColumns: AnalyticalTableColumnDefinition[] = [ - { - Header: t('FluxList.tableNameHeader'), - accessor: 'name', - }, - { - Header: t('FluxList.tableStatusHeader'), - accessor: 'status', - Cell: (cellData: CellData) => - cellData.cell.row.original?.isReady != null ? ( - - ) : null, - }, - { - Header: t('FluxList.tableCreatedHeader'), - accessor: 'created', - }, - ]; - const gitReposRows: FluxRow[] = gitReposData?.items?.map((item) => { return { @@ -117,6 +155,7 @@ export default function FluxList() { ?.lastTransitionTime, revision: shortenCommitHash(item.status.artifact?.revision ?? '-'), created: timeAgo.format(new Date(item.metadata.creationTimestamp)), + item: item, }; }) ?? []; @@ -130,14 +169,17 @@ export default function FluxList() { statusUpdateTime: item.status.conditions.find((x) => x.type === 'Ready') ?.lastTransitionTime, created: timeAgo.format(new Date(item.metadata.creationTimestamp)), + item: item, }; }) ?? []; return ( <> - {' '}
- {t('FluxList.gitOpsTitle')} + + {t('FluxList.gitOpsTitle')} + +
- {t('FluxList.kustomizationsTitle')} + + {t('FluxList.kustomizationsTitle')} + + { cell: { @@ -31,6 +33,7 @@ type ResourceRow = { syncedTransitionTime: string; ready: boolean; readyTransitionTime: string; + item: unknown; }; export function ManagedResources() { @@ -44,42 +47,59 @@ export function ManagedResources() { refreshInterval: resourcesInterval, // Resources are quite expensive to fetch, so we refresh every 30 seconds }); - const columns: AnalyticalTableColumnDefinition[] = [ - { - Header: t('ManagedResources.tableHeaderKind'), - accessor: 'kind', - }, - { - Header: t('ManagedResources.tableHeaderName'), - accessor: 'name', - }, - { - Header: t('ManagedResources.tableHeaderCreated'), - accessor: 'created', - }, - { - Header: t('ManagedResources.tableHeaderSynced'), - accessor: 'synced', - Cell: (cellData: CellData) => - cellData.cell.row.original?.synced != null ? ( - - ) : null, - }, - { - Header: t('ManagedResources.tableHeaderReady'), - accessor: 'ready', - Cell: (cellData: CellData) => - cellData.cell.row.original?.ready != null ? ( - - ) : null, - }, - ]; + const columns: AnalyticalTableColumnDefinition[] = useMemo( + () => [ + { + Header: t('ManagedResources.tableHeaderKind'), + accessor: 'kind', + }, + { + Header: t('ManagedResources.tableHeaderName'), + accessor: 'name', + }, + { + Header: t('ManagedResources.tableHeaderCreated'), + accessor: 'created', + }, + { + Header: t('ManagedResources.tableHeaderSynced'), + accessor: 'synced', + hAlign: 'Center', + width: 85, + Cell: (cellData: CellData) => + cellData.cell.row.original?.synced != null ? ( + + ) : null, + }, + { + Header: t('ManagedResources.tableHeaderReady'), + accessor: 'ready', + hAlign: 'Center', + width: 85, + Cell: (cellData: CellData) => + cellData.cell.row.original?.ready != null ? ( + + ) : null, + }, + { + Header: t('yaml.YAML'), + hAlign: 'Center', + width: 85, + accessor: 'yaml', + Cell: (cellData: CellData) => + !!cellData.cell.row.original?.item ? ( + + ) : undefined, + }, + ], + [], + ); const rows: ResourceRow[] = managedResources @@ -101,6 +121,7 @@ export function ManagedResources() { syncedTransitionTime: conditionSynced?.lastTransitionTime ?? '', ready: conditionReady?.status === 'True', readyTransitionTime: conditionReady?.lastTransitionTime ?? '', + item: item, }; }), ) ?? []; diff --git a/src/components/ControlPlane/Providers.tsx b/src/components/ControlPlane/Providers.tsx index 3376aa8f..4e154471 100644 --- a/src/components/ControlPlane/Providers.tsx +++ b/src/components/ControlPlane/Providers.tsx @@ -14,6 +14,9 @@ import { resourcesInterval } from '../../lib/shared/constants'; import { timeAgo } from '../../utils/i18n/timeAgo'; import { ResourceStatusCell } from '../Shared/ResourceStatusCell'; +import { YamlViewButton } from '../Yaml/YamlViewButton.tsx'; +import { useMemo } from 'react'; + interface CellData { cell: { value: T | null; // null for grouping rows @@ -31,6 +34,7 @@ type ProvidersRow = { installed: boolean; installedTransitionTime: string; created: string; + item: unknown; }; export function Providers() { @@ -44,42 +48,61 @@ export function Providers() { refreshInterval: resourcesInterval, }); - const columns: AnalyticalTableColumnDefinition[] = [ - { - Header: t('Providers.tableHeaderName'), - accessor: 'name', - }, - { - Header: t('Providers.tableHeaderVersion'), - accessor: 'version', - }, - { - Header: t('Providers.tableHeaderInstalled'), - accessor: 'installed', - Cell: (cellData: CellData) => - cellData.cell.row.original?.installed != null ? ( - - ) : null, - }, - { - Header: t('Providers.tableHeaderHealthy'), - accessor: 'healthy', - Cell: (cellData: CellData) => - cellData.cell.row.original?.installed != null ? ( - - ) : null, - }, - { - Header: t('Providers.tableHeaderCreated'), - accessor: 'created', - }, - ]; + const columns: AnalyticalTableColumnDefinition[] = useMemo( + () => [ + { + Header: t('Providers.tableHeaderName'), + accessor: 'name', + }, + { + Header: t('Providers.tableHeaderVersion'), + accessor: 'version', + }, + { + Header: t('Providers.tableHeaderCreated'), + accessor: 'created', + }, + { + Header: t('Providers.tableHeaderInstalled'), + accessor: 'installed', + hAlign: 'Center', + width: 85, + Cell: (cellData: CellData) => + cellData.cell.row.original?.installed != null ? ( + + ) : null, + }, + { + Header: t('Providers.tableHeaderHealthy'), + accessor: 'healthy', + hAlign: 'Center', + width: 85, + Cell: (cellData: CellData) => + cellData.cell.row.original?.installed != null ? ( + + ) : null, + }, + + { + Header: t('yaml.YAML'), + hAlign: 'Center', + width: 85, + accessor: 'yaml', + Cell: (cellData: CellData) => ( + + ), + }, + ], + [], + ); const rows: ProvidersRow[] = providers?.items?.map((item) => { @@ -89,7 +112,6 @@ export function Providers() { const healthy = item.status.conditions?.find( (condition) => condition.type === 'Healthy', ); - return { name: item.metadata.name, created: timeAgo.format(new Date(item.metadata.creationTimestamp)), @@ -98,6 +120,7 @@ export function Providers() { healthy: healthy?.status === 'True', healthyTransitionTime: healthy?.lastTransitionTime ?? '', version: item.spec.package.match(/\d+(\.\d+)+/g)?.toString() ?? '', + item: item, }; }) ?? []; diff --git a/src/components/ControlPlane/ProvidersConfig.tsx b/src/components/ControlPlane/ProvidersConfig.tsx index edef4cc8..04056e58 100644 --- a/src/components/ControlPlane/ProvidersConfig.tsx +++ b/src/components/ControlPlane/ProvidersConfig.tsx @@ -10,13 +10,27 @@ import '@ui5/webcomponents-icons/dist/sys-cancel-2'; import { useProvidersConfigResource } from '../../lib/api/useApiResource'; import { timeAgo } from '../../utils/i18n/timeAgo'; +import { YamlViewButton } from '../Yaml/YamlViewButton.tsx'; + +import { useMemo } from 'react'; + type Rows = { parent: string; name: string; usage: string; created: string; + resource: unknown; }; +interface CellData { + cell: { + value: T | null; // null for grouping rows + row: { + original?: Rows; // missing for grouping rows + }; + }; +} + export function ProvidersConfig() { const { t } = useTranslation(); const rows: Rows[] = []; @@ -33,29 +47,45 @@ export function ProvidersConfig() { name: config.metadata.name, usage: config.metadata.usage ? config.metadata.usage : '0', created: timeAgo.format(new Date(config.metadata.creationTimestamp)), + resource: config, }); }); }); } - const columns: AnalyticalTableColumnDefinition[] = [ - { - Header: t('ProvidersConfig.tableHeaderProvider'), - accessor: 'parent', - }, - { - Header: t('ProvidersConfig.tableHeaderName'), - accessor: 'name', - }, - { - Header: t('ProvidersConfig.tableHeaderUsage'), - accessor: 'usage', - }, - { - Header: t('ProvidersConfig.tableHeaderCreated'), - accessor: 'created', - }, - ]; + const columns: AnalyticalTableColumnDefinition[] = useMemo( + () => [ + { + Header: t('ProvidersConfig.tableHeaderProvider'), + accessor: 'parent', + }, + { + Header: t('ProvidersConfig.tableHeaderName'), + accessor: 'name', + }, + { + Header: t('ProvidersConfig.tableHeaderUsage'), + accessor: 'usage', + }, + { + Header: t('ProvidersConfig.tableHeaderCreated'), + accessor: 'created', + }, + { + Header: t('yaml.YAML'), + hAlign: 'Center', + width: 85, + accessor: 'yaml', + Cell: (cellData: CellData) => + cellData.cell.row.original?.resource ? ( + + ) : undefined, + }, + ], + [], + ); return ( <> diff --git a/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx b/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx index 8309286e..3a2f4030 100644 --- a/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx +++ b/src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx @@ -25,7 +25,7 @@ import { PatchMCPResourceForDeletionBody, } from '../../../lib/api/types/crate/deleteMCP.ts'; -import { YamlViewButton } from '../../Yaml/YamlViewButton.tsx'; +import { YamlViewButtonWithLoader } from '../../Yaml/YamlViewButtonWithLoader.tsx'; import { useToast } from '../../../context/ToastContext.tsx'; interface Props { @@ -70,14 +70,11 @@ export function ControlPlaneCard({
-
@@ -87,11 +84,15 @@ export function ControlPlaneCard({ alignItems="Center" className={styles.row} > - { + setDialogDeleteMcpIsOpen(true); + }} /> -
), }, @@ -73,6 +71,28 @@ export default function ProjectsList() { ), }, + { + Header: t('yaml.YAML'), + accessor: 'yaml', + width: 85, + hAlign: 'Center', + // eslint-disable-next-line @typescript-eslint/no-explicit-any + Cell: (instance: any) => ( +
+ +
+ ), + }, ], [], ); diff --git a/src/components/Shared/ResourceStatusCell.tsx b/src/components/Shared/ResourceStatusCell.tsx index 8062a2fd..d4ae40b6 100644 --- a/src/components/Shared/ResourceStatusCell.tsx +++ b/src/components/Shared/ResourceStatusCell.tsx @@ -8,7 +8,9 @@ export function ResourceStatusCell({ value, transitionTime }: StatusCellProps) { design={value ? 'Positive' : 'Negative'} name={value ? 'sys-enter-2' : 'sys-cancel-2'} showTooltip={true} - accessibleName={timeAgo.format(new Date(transitionTime))} + accessibleName={ + transitionTime ? timeAgo.format(new Date(transitionTime)) : '-' + } /> ); } diff --git a/src/components/Yaml/YamlIcon.module.css b/src/components/Yaml/YamlIcon.module.css new file mode 100644 index 00000000..6892cc41 --- /dev/null +++ b/src/components/Yaml/YamlIcon.module.css @@ -0,0 +1,5 @@ +.svg { + & path[id="label"] { + fill: #004CA3 + } +} \ No newline at end of file diff --git a/src/components/Yaml/YamlIcon.tsx b/src/components/Yaml/YamlIcon.tsx new file mode 100644 index 00000000..36c63285 --- /dev/null +++ b/src/components/Yaml/YamlIcon.tsx @@ -0,0 +1,37 @@ +import styles from './YamlIcon.module.css'; +export const YamlIcon = () => { + return ( + + + + + + + + + ); +}; diff --git a/src/components/Yaml/YamlLoader.tsx b/src/components/Yaml/YamlLoader.tsx index 9107b113..3f2cf021 100644 --- a/src/components/Yaml/YamlLoader.tsx +++ b/src/components/Yaml/YamlLoader.tsx @@ -1,4 +1,4 @@ -import { YamlViewButtonProps } from './YamlViewButton.tsx'; +import { YamlViewButtonProps } from './YamlViewButtonWithLoader.tsx'; import { FC } from 'react'; import { stringify } from 'yaml'; @@ -9,6 +9,10 @@ import Loading from '../Shared/Loading.tsx'; import IllustratedError from '../Shared/IllustratedError.tsx'; import YamlViewer from './YamlViewer.tsx'; import useResource from '../../lib/api/useApiResource'; +import { + removeManagedFieldsProperty, + Resource, +} from '../../utils/removeManagedFieldsProperty.ts'; export const YamlLoader: FC = ({ workspaceName, @@ -17,6 +21,8 @@ export const YamlLoader: FC = ({ }) => { const { isLoading, data, error } = useResource( ResourceObject(workspaceName ?? '', resourceType, resourceName), + undefined, + true, ); const { t } = useTranslation(); if (isLoading) return ; @@ -26,7 +32,7 @@ export const YamlLoader: FC = ({ return ( ); diff --git a/src/components/Yaml/YamlViewButton.tsx b/src/components/Yaml/YamlViewButton.tsx index 76921579..032aceea 100644 --- a/src/components/Yaml/YamlViewButton.tsx +++ b/src/components/Yaml/YamlViewButton.tsx @@ -1,56 +1,51 @@ -import { Bar, Button, Dialog } from '@ui5/webcomponents-react'; -import { FC, useState } from 'react'; -import { YamlLoader } from './YamlLoader.tsx'; +import { Button } from '@ui5/webcomponents-react'; +import { FC, useMemo, useState } from 'react'; +import styles from './YamlViewer.module.css'; import { useTranslation } from 'react-i18next'; +import YamlViewer from './YamlViewer.tsx'; +import { stringify } from 'yaml'; +import { + removeManagedFieldsProperty, + Resource, +} from '../../utils/removeManagedFieldsProperty.ts'; +import { YamlIcon } from './YamlIcon.tsx'; +import { YamlViewDialog } from './YamlViewDialog.tsx'; export type YamlViewButtonProps = { - workspaceName?: string; - resourceType: 'projects' | 'workspaces' | 'managedcontrolplanes'; - resourceName: string; + resourceObject: unknown; }; -export const YamlViewButton: FC = ({ - workspaceName, - resourceType, - resourceName, -}) => { +export const YamlViewButton: FC = ({ resourceObject }) => { const [isOpen, setIsOpen] = useState(false); const { t } = useTranslation(); + const resource = resourceObject as Resource; + const yamlString = useMemo(() => { + return stringify(removeManagedFieldsProperty(resource)); + }, [resource]); return ( - setIsOpen(false)}> - {t('common.close')} - - } + } - onClose={() => { - setIsOpen(false); - }} - > - {isOpen && ( - - )} - + /> + ); }; diff --git a/src/components/Yaml/YamlViewButtonWithLoader.tsx b/src/components/Yaml/YamlViewButtonWithLoader.tsx new file mode 100644 index 00000000..30e55a2b --- /dev/null +++ b/src/components/Yaml/YamlViewButtonWithLoader.tsx @@ -0,0 +1,49 @@ +import { Button } from '@ui5/webcomponents-react'; +import { FC, useState } from 'react'; +import { YamlLoader } from './YamlLoader.tsx'; +import { useTranslation } from 'react-i18next'; +import styles from './YamlViewer.module.css'; +import { YamlIcon } from './YamlIcon.tsx'; +import { YamlViewDialog } from './YamlViewDialog.tsx'; + +export type YamlViewButtonProps = { + workspaceName?: string; + resourceType: 'projects' | 'workspaces' | 'managedcontrolplanes'; + resourceName: string; +}; + +export const YamlViewButtonWithLoader: FC = ({ + workspaceName, + resourceType, + resourceName, +}) => { + const [isOpen, setIsOpen] = useState(false); + const { t } = useTranslation(); + return ( + + + } + /> + + + + ); +}; diff --git a/src/components/Yaml/YamlViewDialog.tsx b/src/components/Yaml/YamlViewDialog.tsx new file mode 100644 index 00000000..5415e05d --- /dev/null +++ b/src/components/Yaml/YamlViewDialog.tsx @@ -0,0 +1,40 @@ +import { Bar, Button, Dialog } from '@ui5/webcomponents-react'; + +import { FC, ReactNode } from 'react'; +import { useTranslation } from 'react-i18next'; + +export type YamlViewDialogProps = { + isOpen: boolean; + setIsOpen: (isOpen: boolean) => void; + dialogContent: ReactNode; +}; + +export const YamlViewDialog: FC = ({ + isOpen, + setIsOpen, + dialogContent, +}) => { + const { t } = useTranslation(); + return ( + e.stopPropagation()} + footer={ + setIsOpen(false)}> + {t('common.close')} + + } + /> + } + onClose={() => { + setIsOpen(false); + }} + > + {isOpen && dialogContent} + + ); +}; diff --git a/src/components/Yaml/YamlViewer.module.css b/src/components/Yaml/YamlViewer.module.css index 82c041ef..2ac78014 100644 --- a/src/components/Yaml/YamlViewer.module.css +++ b/src/components/Yaml/YamlViewer.module.css @@ -7,4 +7,16 @@ top: 0; right: 0; z-index: 1; +} + +.button { + width: 4rem; + display: flex; + align-items: center; + justify-content: center; + + svg { + width: 32px; + transform: translateY(1px); + } } \ No newline at end of file diff --git a/src/components/Yaml/YamlViewer.tsx b/src/components/Yaml/YamlViewer.tsx index 4de010ab..dd7a1db1 100644 --- a/src/components/Yaml/YamlViewer.tsx +++ b/src/components/Yaml/YamlViewer.tsx @@ -51,8 +51,6 @@ const YamlViewer: FC = ({ yamlString, filename }) => { language="yaml" style={isDarkMode ? materialDark : materialLight} showLineNumbers - wrapLines - wrapLongLines lineNumberStyle={{ paddingRight: '20px', minWidth: '40px', @@ -63,9 +61,13 @@ const YamlViewer: FC = ({ yamlString, filename }) => { padding: '20px', borderRadius: '4px', fontSize: '1rem', - width: '100%', background: 'transparent', }} + codeTagProps={{ + style: { + whiteSpace: 'pre-wrap', + }, + }} > {yamlString} diff --git a/src/lib/api/useApiResource.ts b/src/lib/api/useApiResource.ts index 1ff80541..d088433c 100644 --- a/src/lib/api/useApiResource.ts +++ b/src/lib/api/useApiResource.ts @@ -19,6 +19,7 @@ export { useApiResource as default }; export const useApiResource = ( resource: Resource, config?: SWRConfiguration, + excludeMcpConfig?: boolean, ) => { const apiConfig = useContext(ApiConfigContext); @@ -29,7 +30,7 @@ export const useApiResource = ( ([path, apiConfig]) => fetchApiServerJson( path, - apiConfig, + excludeMcpConfig ? { ...apiConfig, mcpConfig: undefined } : apiConfig, resource.jq, resource.method, resource.body, diff --git a/src/utils/removeManagedFieldsProperty.ts b/src/utils/removeManagedFieldsProperty.ts new file mode 100644 index 00000000..2706ead3 --- /dev/null +++ b/src/utils/removeManagedFieldsProperty.ts @@ -0,0 +1,36 @@ +export type Resource = { + kind: string; + items?: { + metadata: { + name: string; + managedFields?: unknown; + }; + }[]; + metadata: { + name: string; + managedFields?: unknown; + }; +}; + +export const removeManagedFieldsProperty = (resourceObject: Resource) => { + if (resourceObject?.metadata?.managedFields) { + return { + ...resourceObject, + metadata: { + ...resourceObject.metadata, + managedFields: undefined, + }, + }; + } + if (resourceObject?.items) { + return { + ...resourceObject, + items: resourceObject.items.map((item) => ({ + ...item, + metadata: { ...item.metadata, managedFields: undefined }, + })), + }; + } + + return resourceObject; +}; diff --git a/src/views/ControlPlanes/ControlPlaneView.tsx b/src/views/ControlPlanes/ControlPlaneView.tsx index fa152d04..3b957d8f 100644 --- a/src/views/ControlPlanes/ControlPlaneView.tsx +++ b/src/views/ControlPlanes/ControlPlaneView.tsx @@ -28,6 +28,8 @@ import ComponentList from '../../components/ControlPlane/ComponentList.tsx'; import MCPHealthPopoverButton from '../../components/ControlPlane/MCPHealthPopoverButton.tsx'; import useResource from '../../lib/api/useApiResource'; +import { YamlViewButtonWithLoader } from '../../components/Yaml/YamlViewButtonWithLoader.tsx'; + export default function ControlPlaneView() { const { projectName, workspaceName, controlPlaneName, contextName } = useParams(); @@ -79,6 +81,7 @@ export default function ControlPlaneView() { display: 'flex', flexDirection: 'row', justifyContent: 'space-between', + gap: '0.5rem', }} > - + }