diff --git a/public/locales/en.json b/public/locales/en.json index 5b4b23d9..4d2bf0d3 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -22,8 +22,6 @@ "tableCreatedHeader": "Created", "tableVersionHeader": "Revision", "noFluxError": "Please install flux to view this component", - "gitOpsTitle": "GitOps", - "kustomizationsTitle": "Kustomizations", "undefinedError": "Something went wrong" }, "ProvidersList": { @@ -32,7 +30,6 @@ "tableCreatedHeader": "Created" }, "ManagedResources": { - "header": "Managed Resources", "tableHeaderKind": "Kind", "tableHeaderName": "Name", "tableHeaderCreated": "Created", @@ -40,7 +37,6 @@ "tableHeaderReady": "Ready" }, "ProvidersConfig": { - "headerProviderConfigs": "Provider Configs", "tableHeaderProvider": "Provider", "tableHeaderName": "Name", "tableHeaderCreated": "Created", @@ -196,12 +192,18 @@ }, "McpPage": { "accessError": "Managed Control Plane does not have access information yet", - "componentsTitle": "Components", "overviewTitle": "Overview", + "dashboardTitle": "Dashboard", + "graphTitle": "Graph", + "componentsTitle": "Components", "crossplaneTitle": "Crossplane", - "gitOpsTitle": "GitOps", - "landscapersTitle": "Landscapers", - "graphTitle": "Graph" + "providersTitle": "Providers", + "providerConfigsTitle": "ProviderConfigs", + "managedResourcesTitle": "Managed Resources", + "fluxTitle": "Flux", + "gitRepositoriesTitle": "GitRepositories", + "kustomizationsTitle": "Kustomizations", + "landscapersTitle": "Landscapers" }, "McpHeader": { "nameLabel": "Name", @@ -301,7 +303,6 @@ "learnButton": "Learn how to do this in code" }, "Providers": { - "headerProviders": "Providers", "tableHeaderVersion": "Version", "tableHeaderName": "Name", "tableHeaderCreated": "Created", @@ -356,7 +357,9 @@ "remaining": "Remaining", "active": "Active", "copyToClipboardSuccessToast": "Copied to clipboard", - "copyToClipboardFailedToast": "Failed to copy to clipboard" + "copyToClipboardFailedToast": "Failed to copy to clipboard", + "resourcesCount": "Resources ({{count}})", + "itemsCount": "Items ({{count}})" }, "errors": { "installError": "Install error", diff --git a/src/components/ControlPlane/ComponentList.tsx b/src/components/ControlPlane/ComponentList.tsx index 1edb314a..26521ee2 100644 --- a/src/components/ControlPlane/ComponentList.tsx +++ b/src/components/ControlPlane/ComponentList.tsx @@ -1,8 +1,16 @@ -import { AnalyticalTable, AnalyticalTableColumnDefinition } from '@ui5/webcomponents-react'; +import { + AnalyticalTable, + AnalyticalTableColumnDefinition, + Panel, + Title, + Toolbar, + ToolbarButton, + ToolbarSpacer, +} from '@ui5/webcomponents-react'; import { ControlPlaneType } from '../../lib/api/types/crate/controlPlanes'; import { useTranslation } from 'react-i18next'; -export default function ComponentList({ mcp }: { mcp: ControlPlaneType }) { +export default function ComponentList({ mcp, onEditClick }: { mcp: ControlPlaneType; onEditClick: () => void }) { const { t } = useTranslation(); const data = [ @@ -40,8 +48,17 @@ export default function ComponentList({ mcp }: { mcp: ControlPlaneType }) { ]; return ( -
+ + {t('common.itemsCount', { count: data.length })} + + + + } + > -
+ ); } diff --git a/src/components/ControlPlane/FluxList.tsx b/src/components/ControlPlane/FluxList.tsx deleted file mode 100644 index a7f769b1..00000000 --- a/src/components/ControlPlane/FluxList.tsx +++ /dev/null @@ -1,208 +0,0 @@ -import ConfiguredAnalyticstable from '../Shared/ConfiguredAnalyticsTable.tsx'; -import { AnalyticalTableColumnDefinition, FlexBox, Title } from '@ui5/webcomponents-react'; -import IllustratedError from '../Shared/IllustratedError.tsx'; -import { useApiResource } from '../../lib/api/useApiResource'; -import { FluxRequest } from '../../lib/api/types/flux/listGitRepo'; -import { FluxKustomization, KustomizationsResponse } from '../../lib/api/types/flux/listKustomization'; -import { useTranslation } from 'react-i18next'; -import { formatDateAsTimeAgo } from '../../utils/i18n/timeAgo.ts'; - -import { YamlViewButton } from '../Yaml/YamlViewButton.tsx'; -import { useMemo } from 'react'; -import StatusFilter from '../Shared/StatusFilter/StatusFilter.tsx'; -import { ResourceStatusCell } from '../Shared/ResourceStatusCell.tsx'; -import { Resource } from '../../utils/removeManagedFieldsAndFilterData.ts'; - -export default function FluxList() { - const { data: gitReposData, error: repoErr, isLoading: repoIsLoading } = useApiResource(FluxRequest); //404 if component not enabled - const { - data: kustmizationData, - error: kustomizationErr, - isLoading: kustomizationIsLoading, - } = useApiResource(FluxKustomization); //404 if component not enabled - - const { t } = useTranslation(); - - interface CellData { - cell: { - value: T | null; // null for grouping rows - row: { - original?: FluxRow; // missing for grouping rows - }; - }; - } - - type FluxRow = { - name: string; - created: string; - isReady: boolean; - statusUpdateTime?: string; - item: unknown; - readyMessage: string; - }; - - 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: 125, - hAlign: 'Center', - Filter: ({ column }) => , - Cell: (cellData: CellData) => - cellData.cell.row.original?.isReady != null ? ( - - ) : null, - }, - { - Header: t('yaml.YAML'), - hAlign: 'Center', - width: 75, - accessor: 'yaml', - disableFilters: true, - Cell: (cellData: CellData) => ( - - ), - }, - ], - [t], - ); - - 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: 125, - hAlign: 'Center', - Filter: ({ column }) => , - Cell: (cellData: CellData) => - cellData.cell.row.original?.isReady != null ? ( - - ) : null, - }, - - { - Header: t('yaml.YAML'), - hAlign: 'Center', - width: 75, - accessor: 'yaml', - disableFilters: true, - Cell: (cellData: CellData) => ( - - ), - }, - ], - [t], - ); - - if (repoErr || kustomizationErr) { - return ( - - ); - } - - const gitReposRows: FluxRow[] = - gitReposData?.items?.map((item) => { - const readyObject = item.status?.conditions?.find((x) => x.type === 'Ready'); - return { - name: item.metadata.name, - isReady: readyObject?.status === 'True', - statusUpdateTime: readyObject?.lastTransitionTime, - revision: shortenCommitHash(item.status.artifact?.revision ?? '-'), - created: formatDateAsTimeAgo(item.metadata.creationTimestamp), - item: item, - readyMessage: readyObject?.message ?? readyObject?.reason ?? '', - }; - }) ?? []; - - const kustomizationsRows: FluxRow[] = - kustmizationData?.items?.map((item) => { - const readyObject = item.status?.conditions?.find((x) => x.type === 'Ready'); - return { - name: item.metadata.name, - isReady: readyObject?.status === 'True', - statusUpdateTime: readyObject?.lastTransitionTime, - created: formatDateAsTimeAgo(item.metadata.creationTimestamp), - item: item, - readyMessage: readyObject?.message ?? readyObject?.reason ?? '', - }; - }) ?? []; - - return ( - <> -
- - {t('FluxList.gitOpsTitle')} - - - -
-
- - {t('FluxList.kustomizationsTitle')} - - - -
- - ); -} - -function shortenCommitHash(commitHash: string): string { - //example hash: master@sha1:b3396adb98a6a0f5eeedd1a600beaf5e954a1f28 - const match = commitHash.match(/^([a-zA-Z0-9-_]+)@sha1:([a-f0-9]{40})/); - - if (match && match[2]) { - return `${match[1]}@${match[2].slice(0, 7)}`; - } - - //example output : master@b3396ad - return commitHash; -} diff --git a/src/components/ControlPlane/GitRepositories.tsx b/src/components/ControlPlane/GitRepositories.tsx new file mode 100644 index 00000000..43141edf --- /dev/null +++ b/src/components/ControlPlane/GitRepositories.tsx @@ -0,0 +1,136 @@ +import ConfiguredAnalyticstable from '../Shared/ConfiguredAnalyticsTable.tsx'; +import { AnalyticalTableColumnDefinition, Panel, Title, Toolbar, ToolbarSpacer } from '@ui5/webcomponents-react'; +import IllustratedError from '../Shared/IllustratedError.tsx'; +import { useApiResource } from '../../lib/api/useApiResource'; +import { FluxRequest } from '../../lib/api/types/flux/listGitRepo'; +import { KustomizationsResponse } from '../../lib/api/types/flux/listKustomization'; +import { useTranslation } from 'react-i18next'; +import { formatDateAsTimeAgo } from '../../utils/i18n/timeAgo.ts'; + +import { YamlViewButton } from '../Yaml/YamlViewButton.tsx'; +import { useMemo } from 'react'; +import StatusFilter from '../Shared/StatusFilter/StatusFilter.tsx'; +import { ResourceStatusCell } from '../Shared/ResourceStatusCell.tsx'; +import { Resource } from '../../utils/removeManagedFieldsAndFilterData.ts'; + +export function GitRepositories() { + const { data, error, isLoading } = useApiResource(FluxRequest); //404 if component not enabled + const { t } = useTranslation(); + + interface CellData { + cell: { + value: T | null; // null for grouping rows + row: { + original?: FluxRow; // missing for grouping rows + }; + }; + } + + type FluxRow = { + name: string; + created: string; + isReady: boolean; + statusUpdateTime?: string; + item: unknown; + readyMessage: string; + }; + + const columns: 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: 125, + hAlign: 'Center', + Filter: ({ column }) => , + Cell: (cellData: CellData) => + cellData.cell.row.original?.isReady != null ? ( + + ) : null, + }, + { + Header: t('yaml.YAML'), + hAlign: 'Center', + width: 75, + accessor: 'yaml', + disableFilters: true, + Cell: (cellData: CellData) => ( + + ), + }, + ], + [t], + ); + + if (error) { + return ( + + ); + } + + const rows: FluxRow[] = + data?.items?.map((item) => { + const readyObject = item.status?.conditions?.find((x) => x.type === 'Ready'); + return { + name: item.metadata.name, + isReady: readyObject?.status === 'True', + statusUpdateTime: readyObject?.lastTransitionTime, + revision: shortenCommitHash(item.status.artifact?.revision ?? '-'), + created: formatDateAsTimeAgo(item.metadata.creationTimestamp), + item: item, + readyMessage: readyObject?.message ?? readyObject?.reason ?? '', + }; + }) ?? []; + + return ( + + {t('common.resourcesCount', { count: rows.length })} + + + + } + > + + + ); +} + +function shortenCommitHash(commitHash: string): string { + //example hash: master@sha1:b3396adb98a6a0f5eeedd1a600beaf5e954a1f28 + const match = commitHash.match(/^([a-zA-Z0-9-_]+)@sha1:([a-f0-9]{40})/); + + if (match && match[2]) { + return `${match[1]}@${match[2].slice(0, 7)}`; + } + + //example output : master@b3396ad + return commitHash; +} diff --git a/src/components/ControlPlane/Kustomizations.tsx b/src/components/ControlPlane/Kustomizations.tsx new file mode 100644 index 00000000..15588f26 --- /dev/null +++ b/src/components/ControlPlane/Kustomizations.tsx @@ -0,0 +1,119 @@ +import ConfiguredAnalyticstable from '../Shared/ConfiguredAnalyticsTable.tsx'; +import { AnalyticalTableColumnDefinition, Panel, Title, Toolbar, ToolbarSpacer } from '@ui5/webcomponents-react'; +import IllustratedError from '../Shared/IllustratedError.tsx'; +import { useApiResource } from '../../lib/api/useApiResource'; +import { FluxKustomization } from '../../lib/api/types/flux/listKustomization'; +import { useTranslation } from 'react-i18next'; +import { formatDateAsTimeAgo } from '../../utils/i18n/timeAgo.ts'; + +import { YamlViewButton } from '../Yaml/YamlViewButton.tsx'; +import { useMemo } from 'react'; +import StatusFilter from '../Shared/StatusFilter/StatusFilter.tsx'; +import { ResourceStatusCell } from '../Shared/ResourceStatusCell.tsx'; +import { Resource } from '../../utils/removeManagedFieldsAndFilterData.ts'; + +export function Kustomizations() { + const { data, error, isLoading } = useApiResource(FluxKustomization); //404 if component not enabled + const { t } = useTranslation(); + + interface CellData { + cell: { + value: T | null; // null for grouping rows + row: { + original?: FluxRow; // missing for grouping rows + }; + }; + } + + type FluxRow = { + name: string; + created: string; + isReady: boolean; + statusUpdateTime?: string; + item: unknown; + readyMessage: string; + }; + + const columns: AnalyticalTableColumnDefinition[] = useMemo( + () => [ + { + Header: t('FluxList.tableNameHeader'), + accessor: 'name', + minWidth: 250, + }, + { + Header: t('FluxList.tableCreatedHeader'), + accessor: 'created', + }, + { + Header: t('FluxList.tableStatusHeader'), + accessor: 'status', + width: 125, + hAlign: 'Center', + Filter: ({ column }) => , + Cell: (cellData: CellData) => + cellData.cell.row.original?.isReady != null ? ( + + ) : null, + }, + + { + Header: t('yaml.YAML'), + hAlign: 'Center', + width: 75, + accessor: 'yaml', + disableFilters: true, + Cell: (cellData: CellData) => ( + + ), + }, + ], + [t], + ); + + if (error) { + return ( + + ); + } + + const rows: FluxRow[] = + data?.items?.map((item) => { + const readyObject = item.status?.conditions?.find((x) => x.type === 'Ready'); + return { + name: item.metadata.name, + isReady: readyObject?.status === 'True', + statusUpdateTime: readyObject?.lastTransitionTime, + created: formatDateAsTimeAgo(item.metadata.creationTimestamp), + item: item, + readyMessage: readyObject?.message ?? readyObject?.reason ?? '', + }; + }) ?? []; + + return ( + + {t('common.resourcesCount', { count: rows.length })} + + + + } + > + + + ); +} diff --git a/src/components/ControlPlane/ManagedResources.tsx b/src/components/ControlPlane/ManagedResources.tsx index 65cf7281..1fc31a74 100644 --- a/src/components/ControlPlane/ManagedResources.tsx +++ b/src/components/ControlPlane/ManagedResources.tsx @@ -3,7 +3,10 @@ import { AnalyticalTable, AnalyticalTableColumnDefinition, AnalyticalTableScaleWidthMode, + Panel, Title, + Toolbar, + ToolbarSpacer, } from '@ui5/webcomponents-react'; import { useApiResource } from '../../lib/api/useApiResource'; import { ManagedResourcesRequest } from '../../lib/api/types/crossplane/listManagedResources'; @@ -140,33 +143,41 @@ export function ManagedResources() { return ( <> - {t('ManagedResources.header')} - {error && } {!error && ( - + + {t('common.resourcesCount', { count: rows.length })} + + + } + > + + )} ); diff --git a/src/components/ControlPlane/Providers.tsx b/src/components/ControlPlane/Providers.tsx index 0e9dc16b..977a9537 100644 --- a/src/components/ControlPlane/Providers.tsx +++ b/src/components/ControlPlane/Providers.tsx @@ -4,7 +4,10 @@ import { AnalyticalTable, AnalyticalTableColumnDefinition, AnalyticalTableScaleWidthMode, + Panel, Title, + Toolbar, + ToolbarSpacer, } from '@ui5/webcomponents-react'; import { useApiResource } from '../../lib/api/useApiResource'; @@ -138,31 +141,39 @@ export function Providers() { return ( <> - {t('Providers.headerProviders')} - {error && } {!error && ( - + + {t('common.resourcesCount', { count: rows.length })} + + + } + > + + )} ); diff --git a/src/components/ControlPlane/ProvidersConfig.tsx b/src/components/ControlPlane/ProvidersConfig.tsx index 722c8cea..34ad4f4c 100644 --- a/src/components/ControlPlane/ProvidersConfig.tsx +++ b/src/components/ControlPlane/ProvidersConfig.tsx @@ -3,7 +3,10 @@ import { AnalyticalTable, AnalyticalTableColumnDefinition, AnalyticalTableScaleWidthMode, + Panel, Title, + Toolbar, + ToolbarSpacer, } from '@ui5/webcomponents-react'; import '@ui5/webcomponents-icons/dist/sys-enter-2'; import '@ui5/webcomponents-icons/dist/sys-cancel-2'; @@ -88,8 +91,15 @@ export function ProvidersConfig() { ); return ( - <> - {t('ProvidersConfig.headerProviderConfigs')} + + {t('common.resourcesCount', { count: rows.length })} + + + } + > - + ); } diff --git a/src/components/HintsCardsRow/GenericHintCard/GenericHintCard.tsx b/src/components/HintsCardsRow/GenericHintCard/GenericHintCard.tsx index dc6123e0..a0dedc95 100644 --- a/src/components/HintsCardsRow/GenericHintCard/GenericHintCard.tsx +++ b/src/components/HintsCardsRow/GenericHintCard/GenericHintCard.tsx @@ -15,6 +15,7 @@ export const GenericHintCard: React.FC = ({ isLoading, error, config, + onClick, }) => { const { t } = useTranslation(); const [hovered, setHovered] = useState(false); @@ -22,17 +23,6 @@ export const GenericHintCard: React.FC = ({ // Calculate segments and state using the provided calculator const hintState = config.calculateSegments(allItems, isLoading || false, error, enabled, t); - // Handle click navigation if scroll target is provided - const handleClick = - enabled && config.scrollTarget - ? () => { - const el = document.querySelector(config.scrollTarget!); - if (el) { - el.scrollIntoView({ behavior: 'smooth', block: 'start' }); - } - } - : undefined; - return (
= ({ className={cx({ [styles['disabled']]: !enabled, })} - onClick={handleClick} + onClick={() => (enabled ? onClick?.() : null)} onMouseEnter={enabled ? () => setHovered(true) : undefined} onMouseLeave={enabled ? () => setHovered(false) : undefined} > diff --git a/src/components/HintsCardsRow/GenericHintCard/genericHintConfigs.ts b/src/components/HintsCardsRow/GenericHintCard/genericHintConfigs.ts index b44eebc9..50531ee4 100644 --- a/src/components/HintsCardsRow/GenericHintCard/genericHintConfigs.ts +++ b/src/components/HintsCardsRow/GenericHintCard/genericHintConfigs.ts @@ -16,7 +16,6 @@ export const useCrossplaneHintConfig = (): GenericHintConfig => { subtitle: t('Hints.CrossplaneHint.subtitle'), iconSrc: '/crossplane-icon.png', iconAlt: 'Crossplane', - scrollTarget: '.crossplane-table-element', calculateSegments: (allItems, isLoading, error, enabled) => calculateCrossplaneSegments(allItems, isLoading, error, enabled, t), calculateHoverData: (allItems, enabled) => calculateCrossplaneHoverDataGeneric(allItems, enabled, t), @@ -31,7 +30,6 @@ export const useGitOpsHintConfig = (): GenericHintConfig => { subtitle: t('Hints.GitOpsHint.subtitle'), iconSrc: '/flux.png', iconAlt: 'Flux', - scrollTarget: '.cp-page-section-gitops', calculateSegments: (allItems, isLoading, error, enabled) => calculateGitOpsSegments(allItems, isLoading, error, enabled, t), calculateHoverData: (allItems, enabled) => calculateGitOpsHoverDataGeneric(allItems, enabled, t), diff --git a/src/components/HintsCardsRow/HintsCardsRow.tsx b/src/components/HintsCardsRow/HintsCardsRow.tsx index de68d23c..67f8d94a 100644 --- a/src/components/HintsCardsRow/HintsCardsRow.tsx +++ b/src/components/HintsCardsRow/HintsCardsRow.tsx @@ -8,9 +8,11 @@ import { resourcesInterval } from '../../lib/shared/constants'; import { useApiResource } from '../../lib/api/useApiResource'; import { ManagedResourceItem } from '../../lib/shared/types'; import React, { useMemo } from 'react'; +import { McpPageSectionId } from '../../spaces/mcp/pages/McpPage.tsx'; interface HintsProps { mcp: ControlPlaneType; + onNavigateToMcpSection: (sectionId: McpPageSectionId) => void; } // Export styles for use by hint components @@ -25,7 +27,7 @@ export const flattenManagedResources = (managedResources: ManagedResourcesRespon .flatMap((managedResource) => managedResource.items || []); }; -const HintsCardsRow: React.FC = ({ mcp }) => { +const HintsCardsRow: React.FC = ({ mcp, onNavigateToMcpSection }) => { const { data: managedResources, isLoading: managedResourcesLoading, @@ -69,6 +71,7 @@ const HintsCardsRow: React.FC = ({ mcp }) => { isLoading={managedResourcesLoading} error={managedResourcesError} config={crossplaneConfig} + onClick={() => onNavigateToMcpSection('crossplane')} /> = ({ mcp }) => { isLoading={managedResourcesLoading} error={managedResourcesError} config={gitOpsConfig} + onClick={() => onNavigateToMcpSection('flux')} /> (undefined); + const [selectedSectionId, setSelectedSectionId] = useState('overview'); const { data: mcp, error, @@ -92,7 +92,7 @@ export default function McpPage() { } + selectedSectionId={selectedSectionId} headerArea={ } + onSelectedSectionChange={() => setSelectedSectionId(undefined)} > - - - - - - - - - {t('McpPage.componentsTitle')}{' '} -