diff --git a/app/portainer/react/components/environments.ts b/app/portainer/react/components/environments.ts index b2219aec2..0ecddeb88 100644 --- a/app/portainer/react/components/environments.ts +++ b/app/portainer/react/components/environments.ts @@ -8,9 +8,10 @@ import { TagsDatatable } from '@/react/portainer/environments/TagsView/TagsDatat import { AzureEndpointConfigSection } from '@/react/portainer/environments/ItemView/AzureEndpointConfigSection/AzureEndpointConfigSection'; import { EnvironmentBasicConfigSection } from '@/react/portainer/environments/ItemView/EnvironmentBasicConfigSection/EnvironmentBasicConfigSection'; import { EdgeInformationPanel } from '@/react/portainer/environments/ItemView/EdgeInformationPanel/EdgeInformationPanel'; -import { withUIRouter } from '@/react-tools/withUIRouter'; +import { KubeConfigInfo } from '@/react/portainer/environments/ItemView/KubeConfigInfo/KubeConfigInfo'; import { withReactQuery } from '@/react-tools/withReactQuery'; import { withCurrentUser } from '@/react-tools/withCurrentUser'; +import { withUIRouter } from '@/react-tools/withUIRouter'; export const environmentsModule = angular .module('portainer.app.react.components.environments', []) @@ -23,6 +24,15 @@ export const environmentsModule = angular 'asyncMode', ]) ) + .component( + 'kubeConfigInfo', + r2a(withUIRouter(withReactQuery(KubeConfigInfo)), [ + 'environmentId', + 'environmentType', + 'edgeId', + 'status', + ]) + ) .component('kvmControl', r2a(KVMControl, ['deviceId', 'server', 'token'])) .component('tagsDatatable', r2a(TagsDatatable, ['dataset', 'onRemove'])) .component( diff --git a/app/portainer/views/endpoints/edit/endpoint.html b/app/portainer/views/endpoints/edit/endpoint.html index bff78c56e..b5220d10a 100644 --- a/app/portainer/views/endpoints/edit/endpoint.html +++ b/app/portainer/views/endpoints/edit/endpoint.html @@ -19,15 +19,9 @@ - - - -
- You should configure the features available in this Kubernetes environment in the - Kubernetes configuration view. -
-
-
+
+ +
diff --git a/app/react/portainer/environments/ItemView/EnvironmentBasicConfigSection/EnvironmentBasicConfigSection.tsx b/app/react/portainer/environments/ItemView/EnvironmentBasicConfigSection/EnvironmentBasicConfigSection.tsx index 01bb2d3e0..fa9608f53 100644 --- a/app/react/portainer/environments/ItemView/EnvironmentBasicConfigSection/EnvironmentBasicConfigSection.tsx +++ b/app/react/portainer/environments/ItemView/EnvironmentBasicConfigSection/EnvironmentBasicConfigSection.tsx @@ -1,7 +1,7 @@ import { FormControl } from '@@/form-components/FormControl'; import { FormSection } from '@@/form-components/FormSection'; import { Input } from '@@/form-components/Input'; -import { Icon } from '@@/Icon'; +import { TextTip } from '@@/Tip/TextTip'; interface Values { name: string; @@ -99,15 +99,10 @@ export function EnvironmentBasicConfigSection({ {isEdge && ( -
- + Use https connection on Edge agent to use private registries with credentials. -
+ )} )} diff --git a/app/react/portainer/environments/ItemView/KubeConfigInfo/KubeConfigInfo.test.tsx b/app/react/portainer/environments/ItemView/KubeConfigInfo/KubeConfigInfo.test.tsx new file mode 100644 index 000000000..35dfd96a5 --- /dev/null +++ b/app/react/portainer/environments/ItemView/KubeConfigInfo/KubeConfigInfo.test.tsx @@ -0,0 +1,132 @@ +import { render, screen } from '@testing-library/react'; + +import { + EnvironmentType, + EnvironmentStatus, +} from '@/react/portainer/environments/types'; +import { withTestRouter } from '@/react/test-utils/withRouter'; + +import { KubeConfigInfo } from './KubeConfigInfo'; + +function renderPanel( + environmentType: EnvironmentType, + status: EnvironmentStatus = EnvironmentStatus.Up, + edgeId?: string +) { + const Wrapped = withTestRouter(KubeConfigInfo); + return render( + + ); +} + +describe('KubeConfigInfo', () => { + describe('Visibility', () => { + it('should display for KubernetesLocal environment', () => { + renderPanel(EnvironmentType.KubernetesLocal); + + expect( + screen.getByText('Kubernetes features configuration') + ).toBeVisible(); + expect(screen.getByText('Kubernetes configuration')).toBeVisible(); + }); + + it('should display for AgentOnKubernetes environment', () => { + renderPanel(EnvironmentType.AgentOnKubernetes); + + expect( + screen.getByText('Kubernetes features configuration') + ).toBeVisible(); + }); + + it('should display for EdgeAgentOnKubernetes with EdgeID', () => { + renderPanel( + EnvironmentType.EdgeAgentOnKubernetes, + EnvironmentStatus.Up, + 'edge-123' + ); + + expect( + screen.getByText('Kubernetes features configuration') + ).toBeVisible(); + }); + + it('should not display for EdgeAgentOnKubernetes without EdgeID', () => { + renderPanel(EnvironmentType.EdgeAgentOnKubernetes); + + expect( + screen.queryByText('Kubernetes features configuration') + ).not.toBeInTheDocument(); + }); + + it('should not display when status is Down', () => { + renderPanel(EnvironmentType.KubernetesLocal, EnvironmentStatus.Down); + + expect( + screen.queryByText('Kubernetes features configuration') + ).not.toBeInTheDocument(); + }); + + it('should not display for Docker environment', () => { + renderPanel(EnvironmentType.Docker); + + expect( + screen.queryByText('Kubernetes features configuration') + ).not.toBeInTheDocument(); + }); + + it('should not display for Azure environment', () => { + renderPanel(EnvironmentType.Azure); + + expect( + screen.queryByText('Kubernetes features configuration') + ).not.toBeInTheDocument(); + }); + }); + + describe('Content', () => { + it('should display wrench icon', () => { + const { container } = renderPanel(EnvironmentType.KubernetesLocal); + + const icon = container.querySelector('svg'); + expect(icon).toBeInTheDocument(); + }); + + it('should display correct title', () => { + renderPanel(EnvironmentType.KubernetesLocal); + + expect( + screen.getByText('Kubernetes features configuration') + ).toBeVisible(); + }); + + it('should display correct message text', () => { + renderPanel(EnvironmentType.KubernetesLocal); + + expect( + screen.getByText(/You should configure the features available/i) + ).toBeVisible(); + }); + + it('should display link with correct text', () => { + renderPanel(EnvironmentType.KubernetesLocal); + + const link = screen.getByTestId('kubernetes-config-link'); + expect(link).toBeVisible(); + expect(link).toHaveTextContent('Kubernetes configuration'); + }); + + it('should have link element', () => { + renderPanel(EnvironmentType.KubernetesLocal); + + const link = screen.getByTestId('kubernetes-config-link'); + + // Link component renders an anchor tag + expect(link.tagName).toBe('A'); + }); + }); +}); diff --git a/app/react/portainer/environments/ItemView/KubeConfigInfo/KubeConfigInfo.tsx b/app/react/portainer/environments/ItemView/KubeConfigInfo/KubeConfigInfo.tsx new file mode 100644 index 000000000..ed09113bc --- /dev/null +++ b/app/react/portainer/environments/ItemView/KubeConfigInfo/KubeConfigInfo.tsx @@ -0,0 +1,57 @@ +import { Wrench } from 'lucide-react'; + +import { + EnvironmentId, + EnvironmentType, + EnvironmentStatus, +} from '@/react/portainer/environments/types'; +import { + isKubernetesEnvironment, + isEdgeEnvironment, +} from '@/react/portainer/environments/utils'; +import { InformationPanel } from '@/react/components/InformationPanel'; +import { Link } from '@/react/components/Link'; +import { Icon } from '@/react/components/Icon'; + +interface Props { + environmentId: EnvironmentId; + environmentType: EnvironmentType; + edgeId?: string; + status: EnvironmentStatus; +} + +export function KubeConfigInfo({ + environmentId, + environmentType, + edgeId, + status, +}: Props) { + const isVisible = + isKubernetesEnvironment(environmentType) && + (!isEdgeEnvironment(environmentType) || !!edgeId) && + status !== EnvironmentStatus.Down; + + if (!isVisible) { + return null; + } + + return ( + + + +
+ You should configure the features available in this Kubernetes + environment in the{' '} + + Kubernetes configuration + {' '} + view. +
+
+
+ ); +}