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 @@
-
-
-
-
-
-
+
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.
+
+
+
+ );
+}