diff --git a/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/Dialogs.story.tsx b/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/Dialogs.story.tsx
index c90f093de298b..39e9ad759c92a 100644
--- a/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/Dialogs.story.tsx
+++ b/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/Dialogs.story.tsx
@@ -16,7 +16,7 @@
* along with this program. If not, see .
*/
-import React from 'react';
+import React, { useEffect, useState } from 'react';
import { MemoryRouter } from 'react-router';
import { rest } from 'msw';
import { mswLoader } from 'msw-storybook-addon';
@@ -27,11 +27,26 @@ import { ResourceKind } from 'teleport/Discover/Shared';
import { PingTeleportProvider } from 'teleport/Discover/Shared/PingTeleportContext';
import { ContextProvider } from 'teleport';
+import {
+ INTERNAL_RESOURCE_ID_LABEL_KEY,
+ JoinToken,
+} from 'teleport/services/joinToken';
+import { clearCachedJoinTokenResult } from 'teleport/Discover/Shared/useJoinTokenSuspender';
+import {
+ DiscoverContextState,
+ DiscoverProvider,
+} from 'teleport/Discover/useDiscover';
+import {
+ IntegrationKind,
+ IntegrationStatusCode,
+} from 'teleport/services/integrations';
+import { DiscoverEventResource } from 'teleport/services/userEvent';
+
import { generateCmd } from 'teleport/Discover/Kubernetes/HelmChart/HelmChart';
-import { ManualHelmDialog } from './ManualHelmDialog';
-import { AgentWaitingDialog } from './AgentWaitingDialog';
import { EnrollmentDialog } from './EnrollmentDialog';
+import { AgentWaitingDialog } from './AgentWaitingDialog';
+import { ManualHelmDialog } from './ManualHelmDialog';
export default {
title: 'Teleport/Discover/Kube/EnrollEksClusters/Dialogs',
@@ -110,7 +125,7 @@ AgentWaitingDialogSuccess.parameters = {
},
};
-const helmCommand = generateCmd({
+const helmCommandProps = {
namespace: 'teleport-agent',
clusterName: 'EKS1',
proxyAddr: 'teleport-proxy.example.com:1234',
@@ -126,15 +141,98 @@ const helmCommand = generateCmd({
{ name: 'region', value: 'us-east-1' },
{ name: 'account-id', value: '1234567789012' },
],
-});
+};
-export const ManualHelmDialogStory = () => (
-
- {}}
- cancel={() => {}}
- />
-
-);
+export const ManualHelmDialogStory = () => {
+ const discoverCtx: DiscoverContextState = {
+ agentMeta: {
+ resourceName: 'kube-name',
+ agentMatcherLabels: [],
+ kube: {
+ kind: 'kube_cluster',
+ name: '',
+ labels: [],
+ },
+ awsIntegration: {
+ kind: IntegrationKind.AwsOidc,
+ name: 'test-oidc',
+ resourceType: 'integration',
+ spec: {
+ roleArn: 'arn:aws:iam::123456789012:role/test-role-arn',
+ },
+ statusCode: IntegrationStatusCode.Running,
+ },
+ },
+ currentStep: 0,
+ nextStep: () => null,
+ prevStep: () => null,
+ onSelectResource: () => null,
+ resourceSpec: {
+ name: 'Eks',
+ kind: ResourceKind.Kubernetes,
+ icon: 'Eks',
+ keywords: '',
+ event: DiscoverEventResource.KubernetesEks,
+ },
+ exitFlow: () => null,
+ viewConfig: null,
+ indexedViews: [],
+ setResourceSpec: () => null,
+ updateAgentMeta: () => null,
+ emitErrorEvent: () => null,
+ emitEvent: () => null,
+ eventState: null,
+ };
+
+ useEffect(() => {
+ return () => {
+ clearCachedJoinTokenResult([
+ ResourceKind.Kubernetes,
+ ResourceKind.Application,
+ ResourceKind.Discovery,
+ ]);
+ };
+ }, []);
+
+ const [, setToken] = useState();
+
+ return (
+
+
+
+ {
+ // Emulate real usage of ManualHelmDialog where setJoinTokenAndGetCommand updates the
+ // state of a parent.
+ setToken(token);
+ return generateCmd(helmCommandProps);
+ }}
+ confirmedCommands={() => {}}
+ cancel={() => {}}
+ />
+
+
+
+ );
+};
ManualHelmDialogStory.storyName = 'ManualHelmDialog';
+ManualHelmDialogStory.parameters = {
+ msw: {
+ handlers: [
+ rest.post(cfg.api.joinTokenPath, (req, res, ctx) => {
+ return res(
+ ctx.json({
+ id: 'token-id',
+ suggestedLabels: [
+ { name: INTERNAL_RESOURCE_ID_LABEL_KEY, value: 'resource-id' },
+ ],
+ })
+ );
+ }),
+ ],
+ },
+};
diff --git a/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/EnrollEksCluster.tsx b/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/EnrollEksCluster.tsx
index 583c391fd252a..699e3456d7ca0 100644
--- a/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/EnrollEksCluster.tsx
+++ b/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/EnrollEksCluster.tsx
@@ -16,7 +16,7 @@
* along with this program. If not, see .
*/
-import React, { useState } from 'react';
+import React, { useState, useCallback } from 'react';
import { Box, ButtonSecondary, ButtonText, Text, Toggle } from 'design';
import styled from 'styled-components';
import { FetchStatus } from 'design/DataTable/types';
@@ -39,14 +39,15 @@ import { isIamPermError } from 'teleport/Discover/Shared/Aws/error';
import { AgentStepProps } from 'teleport/Discover/types';
import useTeleport from 'teleport/useTeleport';
-import { useJoinTokenSuspender } from 'teleport/Discover/Shared/useJoinTokenSuspender';
import { generateCmd } from 'teleport/Discover/Kubernetes/HelmChart/HelmChart';
import { Kube } from 'teleport/services/kube';
-import { Header, ResourceKind } from '../../Shared';
+import { JoinToken } from 'teleport/services/joinToken';
+
+import { Header } from '../../Shared';
import { ClustersList } from './EksClustersList';
-import { ManualHelmDialog } from './ManualHelmDialog';
+import ManualHelmDialog from './ManualHelmDialog';
import { EnrollmentDialog } from './EnrollmentDialog';
import { AgentWaitingDialog } from './AgentWaitingDialog';
@@ -97,14 +98,11 @@ export function EnrollEksCluster(props: AgentStepProps) {
useState(false);
const [isManualHelmDialogShown, setIsManualHelmDialogShown] = useState(false);
const [waitingResourceId, setWaitingResourceId] = useState('');
+ // join token will be set only if user opens ManualHelmDialog,
+ // we delay it to avoid premature admin action MFA confirmation request.
+ const [joinToken, setJoinToken] = useState(null);
const ctx = useTeleport();
- const { joinToken } = useJoinTokenSuspender([
- ResourceKind.Kubernetes,
- ResourceKind.Application,
- ResourceKind.Discovery,
- ]);
-
function fetchClustersWithNewRegion(region: Regions) {
setSelectedRegion(region);
// Clear table when fetching with new region.
@@ -205,8 +203,6 @@ export function EnrollEksCluster(props: AgentStepProps) {
{
region: selectedRegion,
enableAppDiscovery: isAppDiscoveryEnabled,
- joinToken: joinToken.id,
- resourceId: joinToken.internalResourceId,
clusterNames: [selectedCluster.name],
}
);
@@ -262,23 +258,34 @@ export function EnrollEksCluster(props: AgentStepProps) {
!selectedCluster ||
enrollmentState.status !== 'notStarted';
- let command = '';
- if (selectedCluster) {
- command = generateCmd({
- namespace: 'teleport-agent',
- clusterName: selectedCluster.name,
- proxyAddr: ctx.storeUser.state.cluster.publicURL,
- tokenId: joinToken.id,
- clusterVersion: ctx.storeUser.state.cluster.authVersion,
- resourceId: joinToken.internalResourceId,
- isEnterprise: ctx.isEnterprise,
- isCloud: ctx.isCloud,
- automaticUpgradesEnabled: ctx.automaticUpgradesEnabled,
- automaticUpgradesTargetVersion: ctx.automaticUpgradesTargetVersion,
- joinLabels: [...selectedCluster.labels, ...selectedCluster.joinLabels],
- disableAppDiscovery: !isAppDiscoveryEnabled,
- });
- }
+ const setJoinTokenAndGetCommand = useCallback(
+ (token: JoinToken) => {
+ setJoinToken(token);
+ return generateCmd({
+ namespace: 'teleport-agent',
+ clusterName: selectedCluster.name,
+ proxyAddr: ctx.storeUser.state.cluster.publicURL,
+ clusterVersion: ctx.storeUser.state.cluster.authVersion,
+ tokenId: token.id,
+ resourceId: token.internalResourceId,
+ isEnterprise: ctx.isEnterprise,
+ isCloud: ctx.isCloud,
+ automaticUpgradesEnabled: ctx.automaticUpgradesEnabled,
+ automaticUpgradesTargetVersion: ctx.automaticUpgradesTargetVersion,
+ joinLabels: [...selectedCluster.labels, ...selectedCluster.joinLabels],
+ disableAppDiscovery: !isAppDiscoveryEnabled,
+ });
+ },
+ [
+ ctx.automaticUpgradesEnabled,
+ ctx.automaticUpgradesTargetVersion,
+ ctx.isCloud,
+ ctx.isEnterprise,
+ ctx.storeUser.state.cluster,
+ isAppDiscoveryEnabled,
+ selectedCluster,
+ ]
+ );
return (
@@ -366,7 +373,7 @@ export function EnrollEksCluster(props: AgentStepProps) {
)}
{isManualHelmDialogShown && (
setIsManualHelmDialogShown(false)}
confirmedCommands={() => {
setEnrollmentState({ status: 'awaitingAgent' });
@@ -377,7 +384,7 @@ export function EnrollEksCluster(props: AgentStepProps) {
)}
{isAgentWaitingDialogShown && (
{
diff --git a/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/ManualHelmDialog.tsx b/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/ManualHelmDialog.tsx
index ce2a0428fd4b8..593f1722b3227 100644
--- a/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/ManualHelmDialog.tsx
+++ b/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/ManualHelmDialog.tsx
@@ -17,62 +17,77 @@
*/
import Dialog, { DialogContent, DialogFooter } from 'design/DialogConfirmation';
-import { Box, Flex, ButtonPrimary, ButtonSecondary, Text } from 'design';
+import {
+ Box,
+ Flex,
+ ButtonPrimary,
+ ButtonSecondary,
+ Text,
+ Indicator,
+} from 'design';
-import React from 'react';
+import React, { Suspense, useState, useEffect } from 'react';
import styled from 'styled-components';
+import * as Icons from 'design/Icon';
+
import { TextSelectCopyMulti } from 'teleport/components/TextSelectCopy';
import { CommandBox } from 'teleport/Discover/Shared/CommandBox';
+import { useJoinTokenSuspender } from 'teleport/Discover/Shared/useJoinTokenSuspender';
+import { ResourceKind, TextIcon } from 'teleport/Discover/Shared';
+import { JoinToken } from 'teleport/services/joinToken';
+import { CatchError } from 'teleport/components/CatchError';
+
type ManualHelmDialogProps = {
- command: string;
+ setJoinTokenAndGetCommand(token: JoinToken): string;
confirmedCommands(): void;
cancel(): void;
};
-export function ManualHelmDialog({
- command,
+export default function Container(props: ManualHelmDialogProps) {
+ return (
+ (
+
+ )}
+ >
+ }
+ >
+ {/**/}
+ fallback={}
+
+
+ );
+}
+
+type FallbackDialogProps = {
+ cancel: () => void;
+ error?: Error;
+ showSpinner?: boolean;
+};
+
+const DialogWrapper = ({
+ children,
cancel,
- confirmedCommands,
-}: ManualHelmDialogProps) {
+ next,
+}: {
+ children: React.ReactNode;
+ cancel: () => void;
+ next?: () => void;
+}) => {
return (
);
+};
+
+const FallbackDialog = ({
+ error,
+ cancel,
+ showSpinner,
+}: FallbackDialogProps) => {
+ return (
+
+ {showSpinner && (
+
+
+
+ )}
+ {error && (
+
+
+
+ Encountered an error: {error.message}
+
+
+ )}
+
+ );
+};
+
+export function ManualHelmDialog({
+ setJoinTokenAndGetCommand,
+ cancel,
+ confirmedCommands,
+}: ManualHelmDialogProps) {
+ const { joinToken } = useJoinTokenSuspender([
+ ResourceKind.Kubernetes,
+ ResourceKind.Application,
+ ResourceKind.Discovery,
+ ]);
+ const [command, setCommand] = useState('');
+
+ useEffect(() => {
+ if (joinToken && !command) {
+ setCommand(setJoinTokenAndGetCommand(joinToken));
+ }
+ }, [joinToken, command, setJoinTokenAndGetCommand]);
+
+ return (
+
+
+ Step 1
+
+ Add teleport-agent chart to your charts repository
+
+
+
+
+ Step 2
+
+ Run the command below on the server your target EKS cluster is at.
+ It may take up to a minute for the Teleport Service to join after
+ running the command.
+
+ >
+ }
+ >
+
+
+
+ );
}
const StyledBox = styled(Box)`
diff --git a/web/packages/teleport/src/services/integrations/types.ts b/web/packages/teleport/src/services/integrations/types.ts
index 8094c0072e3d1..cf99cdc3c75a6 100644
--- a/web/packages/teleport/src/services/integrations/types.ts
+++ b/web/packages/teleport/src/services/integrations/types.ts
@@ -317,8 +317,6 @@ export type AwsEksCluster = {
export type EnrollEksClustersRequest = {
region: string;
enableAppDiscovery: boolean;
- joinToken: string;
- resourceId: string;
clusterNames: string[];
};