Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"tableStatusHeader": "Status",
"tableCreatedHeader": "Created",
"tableVersionHeader": "Revision",
"tableUrlHeader": "Address",
"noFluxError": "Please install flux to view this component",
"undefinedError": "Something went wrong"
},
Expand Down Expand Up @@ -522,6 +523,26 @@
"specTitle": "Spec",
"branchTitle": "Branch",
"gitRepositoryCreated": "Git Repository created successfully",
"gitRepositoryCreationFailed": "Failed to create Git Repository: {{error}}"
"gitRepositoryCreationFailed": "Failed to create Git Repository: {{error}}",
"namespaceTitle": "Namespace"
},
"CreateKustomizationDialog": {
"dialogTitle": "Create Kustomization",
"metadataTitle": "Metadata",
"specTitle": "Spec",
"nameTitle": "Name",
"intervalTitle": "Interval",
"sourceRefTitle": "Source Reference",
"sourceRefNameTitle": "Name",
"gitRepositoryTitle": "Git Repository",
"pathTitle": "Path",
"pruneTitle": "Prune",
"targetNamespaceTitle": "Target Namespace",
"substitutionsTitle": "Substitutions",
"addSubstitutionButton": "Add Substitution",
"keyPlaceholder": "Key",
"valuePlaceholder": "Value",
"kustomizationCreated": "Kustomization created successfully",
"kustomizationCreationFailed": "Failed to create Kustomization: {{error}}"
}
}
14 changes: 14 additions & 0 deletions src/components/ControlPlane/GitRepositories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
Toolbar,
ToolbarSpacer,
Button,
Link,
} from '@ui5/webcomponents-react';
import '@ui5/webcomponents-icons/dist/add';
import IllustratedError from '../Shared/IllustratedError.tsx';
Expand Down Expand Up @@ -79,6 +80,19 @@ export function GitRepositories() {
Header: t('FluxList.tableNameHeader'),
accessor: 'name',
minWidth: 250,
Cell: ({ cell: { value } }) => <span id={`git-repository-${value}`}>{value}</span>,
},
{
Header: t('FluxList.tableUrlHeader', 'Address'),
accessor: 'item.spec.url',
Cell: ({ cell: { value } }) =>
value && value.startsWith('https') ? (
<Link href={value} target="_blank" rel="noopener noreferrer">
{value}
</Link>
) : (
value
),
},
{
Header: t('FluxList.tableCreatedHeader'),
Expand Down
119 changes: 85 additions & 34 deletions src/components/ControlPlane/Kustomizations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
Toolbar,
ToolbarSpacer,
Button,
Link,
} from '@ui5/webcomponents-react';
import IllustratedError from '../Shared/IllustratedError.tsx';
import { useApiResource } from '../../lib/api/useApiResource';
Expand All @@ -15,7 +16,8 @@ import { useTranslation } from 'react-i18next';
import { formatDateAsTimeAgo } from '../../utils/i18n/timeAgo.ts';

import { YamlViewButton } from '../Yaml/YamlViewButton.tsx';
import { Fragment, useCallback, useContext, useMemo, useRef } from 'react';
import { Fragment, useCallback, useContext, useMemo, useRef, useState, useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import StatusFilter from '../Shared/StatusFilter/StatusFilter.tsx';
import { ResourceStatusCell } from '../Shared/ResourceStatusCell.tsx';
import { Resource } from '../../utils/removeManagedFieldsAndFilterData.ts';
Expand All @@ -25,6 +27,7 @@ import { useHandleResourcePatch } from '../../hooks/useHandleResourcePatch.ts';
import { ErrorDialog, ErrorDialogHandle } from '../Shared/ErrorMessageBox.tsx';
import type { KustomizationsResponse } from '../../lib/api/types/flux/listKustomization';
import { ActionsMenu, type ActionItem } from './ActionsMenu';
import { CreateKustomizationDialog } from '../Dialogs/CreateKustomizationDialog';

import { ApiConfigContext } from '../Shared/k8s';
import { useHasMcpAdminRights } from '../../spaces/mcp/auth/useHasMcpAdminRights.ts';
Expand All @@ -41,6 +44,8 @@ export function Kustomizations() {
const { openInAsideWithApiConfig } = useSplitter();
const errorDialogRef = useRef<ErrorDialogHandle>(null);
const handlePatch = useHandleResourcePatch(errorDialogRef);
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);
const location = useLocation();

type FluxRow = {
name: string;
Expand Down Expand Up @@ -77,6 +82,23 @@ export function Kustomizations() {
Header: t('FluxList.tableNameHeader'),
accessor: 'name',
minWidth: 250,
Cell: ({ cell: { value } }) => <span id={`kustomization-${value}`}>{value}</span>,
},
{
Header: t('CreateKustomizationDialog.sourceRefTitle'),
accessor: 'item.spec.sourceRef.name',
Cell: ({ cell: { value } }) => (
<Link
onClick={() => {
const element = document.getElementById(`git-repository-${value}`);
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}}
>
{value}
</Link>
),
Comment on lines +91 to +101
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The scroll-to behavior in the Link's onClick handler uses document.getElementById and scrollIntoView directly. This is duplicated with the useEffect logic (lines 199-216). Consider extracting this into a reusable helper function to avoid code duplication.

Copilot uses AI. Check for mistakes.
},
{
Header: t('FluxList.tableCreatedHeader'),
Expand Down Expand Up @@ -153,6 +175,46 @@ export function Kustomizations() {
[t, openEditPanel, hasMCPAdminRights],
);

const rows: FluxRow[] = useMemo(
() =>
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,
kind: 'Kustomization',
apiVersion: 'kustomize.toolkit.fluxcd.io/v1',
metadata: { ...item.metadata },
} as KustomizationItem,
readyMessage: readyObject?.message ?? readyObject?.reason ?? '',
};
}) ?? [],
[data],
);

useEffect(() => {
if (!isLoading && rows.length > 0 && location.hash) {
const hash = location.hash.substring(1);
if (hash.startsWith('kustomization-')) {
const targetName = hash.replace('kustomization-', '');
const index = rows.findIndex((row) => row.name === targetName);

if (index !== -1) {
setTimeout(() => {
const element = document.getElementById(hash);
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}, 500);
Comment on lines +207 to +212
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The scroll behavior uses a hardcoded 500ms timeout. This magic number should be extracted as a named constant to improve code maintainability and make it easier to adjust if needed.

Copilot uses AI. Check for mistakes.
}
}
}
}, [isLoading, rows, location.hash]);
Comment on lines +199 to +216
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The scroll behavior implementation queries the DOM using document.getElementById which couples the component to the DOM structure. Additionally, there's no cleanup if the component unmounts during the timeout. Consider using a ref-based approach or adding cleanup logic to cancel the timeout.

Copilot uses AI. Check for mistakes.

if (error) {
return (
<IllustratedError
Expand All @@ -163,39 +225,28 @@ export function Kustomizations() {
);
}

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,
kind: 'Kustomization',
apiVersion: 'kustomize.toolkit.fluxcd.io/v1',
metadata: { ...item.metadata },
} as KustomizationItem,
readyMessage: readyObject?.message ?? readyObject?.reason ?? '',
};
}) ?? [];

return (
<Panel
fixed
header={
<Toolbar>
<Title>{t('common.resourcesCount', { count: rows.length })}</Title>
<YamlViewButton variant="resource" resource={data as unknown as Resource} />
<ToolbarSpacer />
</Toolbar>
}
>
<>
<ConfiguredAnalyticstable columns={columns} isLoading={isLoading} data={rows} />
<ErrorDialog ref={errorDialogRef} />
</>
</Panel>
<>
<Panel
fixed
header={
<Toolbar>
<Title>{t('common.resourcesCount', { count: rows.length })}</Title>
<YamlViewButton variant="resource" resource={data as unknown as Resource} />
<ToolbarSpacer />
<Button icon="add" onClick={() => setIsCreateDialogOpen(true)}>
{t('buttons.create')}
</Button>
</Toolbar>
}
>
<>
<ConfiguredAnalyticstable columns={columns} isLoading={isLoading} data={rows} />
<ErrorDialog ref={errorDialogRef} />
</>
</Panel>

<CreateKustomizationDialog isOpen={isCreateDialogOpen} onClose={() => setIsCreateDialogOpen(false)} />
</>
);
}
22 changes: 21 additions & 1 deletion src/components/ControlPlane/ManagedResources.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
Title,
Toolbar,
ToolbarSpacer,
Link,
} from '@ui5/webcomponents-react';
import {
useApiResource as _useApiResource,
Expand Down Expand Up @@ -42,6 +43,7 @@ import { useHandleResourcePatch as _useHandleResourcePatch } from '../../hooks/u

import { ApiConfigContext } from '../Shared/k8s';
import { useHasMcpAdminRights as _useHasMcpAdminRights } from '../../spaces/mcp/auth/useHasMcpAdminRights.ts';
import { useNavigateToTab } from '../../hooks/useNavigateToTab.ts';

interface StatusFilterColumn {
filterValue?: string;
Expand Down Expand Up @@ -94,6 +96,7 @@ export function ManagedResources({
const [pendingDeleteItem, setPendingDeleteItem] = useState<ManagedResourceItem | null>(null);
const errorDialogRef = useRef<ErrorDialogHandle>(null);
const handlePatch = useHandleResourcePatch(errorDialogRef);
const navigateToTab = useNavigateToTab();

const {
data: managedResources,
Expand Down Expand Up @@ -155,6 +158,23 @@ export function ManagedResources({
Header: t('ManagedResources.tableHeaderCreated'),
accessor: 'created',
},
{
Header: t('ManagedResources.tableHeaderManagedBy'),
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new "Managed By" column uses a translation key 'ManagedResources.tableHeaderManagedBy' but this key is not defined in the localization file (public/locales/en.json). This will result in the translation key being displayed instead of the proper text. Add the missing translation key.

Copilot uses AI. Check for mistakes.
accessor: (row: ResourceRow) =>
(row.item.metadata?.labels as unknown as Record<string, string> | undefined)?.[
'kustomize.toolkit.fluxcd.io/name'
],
Cell: ({ cell: { value } }) =>
value ? (
<Link
onClick={() => {
navigateToTab('flux', `kustomization-${value}`);
}}
>
{value}
</Link>
) : null,
},
Comment on lines +161 to +177
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new "Managed By" column added to ManagedResources lacks test coverage. Since ManagedResources.cy.tsx exists with comprehensive tests, consider adding test cases to verify the new column's behavior, including the link navigation functionality.

Copilot uses AI. Check for mistakes.
{
Header: t('ManagedResources.tableHeaderSynced'),
accessor: 'synced',
Expand Down Expand Up @@ -260,7 +280,7 @@ export function ManagedResources({
},
},
] as AnalyticalTableColumnDefinition[],
[t, openEditPanel, openDeleteDialog, hasMCPAdminRights],
[t, openEditPanel, openDeleteDialog, hasMCPAdminRights, navigateToTab],
);

const rows: ResourceRow[] =
Expand Down
Loading
Loading