diff --git a/web/packages/teleport/src/Discover/Database/CreateDatabase/CreateDatabase.tsx b/web/packages/teleport/src/Discover/Database/CreateDatabase/CreateDatabase.tsx index c741944ebf6bc..79bceda4fcae4 100644 --- a/web/packages/teleport/src/Discover/Database/CreateDatabase/CreateDatabase.tsx +++ b/web/packages/teleport/src/Discover/Database/CreateDatabase/CreateDatabase.tsx @@ -25,6 +25,7 @@ import TextEditor from 'shared/components/TextEditor'; import Validation, { Validator } from 'shared/components/Validation'; import { requiredField } from 'shared/components/Validation/rules'; +import { ResourceLabelTooltip } from 'teleport/Discover/Shared/ResourceLabelTooltip'; import type { ResourceLabel } from 'teleport/services/agents'; import { @@ -162,13 +163,13 @@ export function CreateDatabaseView({ /> - Labels (optional) - - Labels make this new database discoverable by the database - service.
- Not defining labels is equivalent to asterisks (any - database service can discover this database). -
+ + Labels (optional) + + (agentMeta.resourceName); const showHint = useShowHint(active); + useEffect(() => { + return () => clearCachedJoinTokenResult([ResourceKind.Database]); + }, []); + function handleNextStep() { updateAgentMeta({ ...agentMeta, diff --git a/web/packages/teleport/src/Discover/Database/EnrollRdsDatabase/SingleEnrollment.tsx b/web/packages/teleport/src/Discover/Database/EnrollRdsDatabase/SingleEnrollment.tsx index eba8893130f3e..d60f8a992535a 100644 --- a/web/packages/teleport/src/Discover/Database/EnrollRdsDatabase/SingleEnrollment.tsx +++ b/web/packages/teleport/src/Discover/Database/EnrollRdsDatabase/SingleEnrollment.tsx @@ -18,13 +18,16 @@ import { useEffect, useState } from 'react'; -import { Text } from 'design'; +import { Flex, Subtitle1, Text } from 'design'; import { FetchStatus } from 'design/DataTable/types'; +import Validation, { Validator } from 'shared/components/Validation'; import { Attempt } from 'shared/hooks/useAttemptNext'; import { getErrMessage } from 'shared/utils/errorType'; import { getRdsEngineIdentifier } from 'teleport/Discover/SelectResource/types'; +import { ResourceLabelTooltip } from 'teleport/Discover/Shared/ResourceLabelTooltip'; import { useDiscover } from 'teleport/Discover/useDiscover'; +import { ResourceLabel } from 'teleport/services/agents'; import { Database } from 'teleport/services/databases'; import { AwsRdsDatabase, @@ -33,7 +36,7 @@ import { Vpc, } from 'teleport/services/integrations'; -import { ActionButtons } from '../../Shared'; +import { ActionButtons, LabelsCreater } from '../../Shared'; import { CreateDatabaseDialog } from '../CreateDatabase/CreateDatabaseDialog'; import { useCreateDatabase } from '../CreateDatabase/useCreateDatabase'; import { DatabaseList } from './RdsDatabaseList'; @@ -90,6 +93,7 @@ export function SingleEnrollment({ const [tableData, setTableData] = useState(); const [selectedDb, setSelectedDb] = useState(); + const [customLabels, setCustomLabels] = useState([]); useEffect(() => { if (vpc) { @@ -98,6 +102,12 @@ export function SingleEnrollment({ } }, [vpc]); + function onSelectRds(rds: CheckedAwsRdsDatabase) { + // when changing selected db, clear defined labels + setCustomLabels([]); + setSelectedDb(rds); + } + function fetchNextPage() { fetchRdsDatabases({ ...tableData }, vpc); } @@ -175,6 +185,17 @@ export function SingleEnrollment({ } } + function handleOnProceedWithValidation( + validator: Validator, + { overwriteDb = false } = {} + ) { + if (!validator.validate()) { + return; + } + + handleOnProceed({ overwriteDb }); + } + function handleOnProceed({ overwriteDb = false } = {}) { // Corner case where if registering db fails a user can: // 1) change region, which will list new databases or @@ -185,7 +206,9 @@ export function SingleEnrollment({ name: selectedDb.name, protocol: selectedDb.engine, uri: selectedDb.uri, - labels: selectedDb.labels, + // The labels from the `selectedDb` are AWS tags which + // will be imported as is. + labels: [...selectedDb.labels, ...customLabels], awsRds: selectedDb, awsRegion: region, awsVpcId: vpc.id, @@ -198,23 +221,47 @@ export function SingleEnrollment({ return ( <> - {showTable && ( - <> - Select an RDS database to enroll: - - - )} - + + {({ validator }) => ( + <> + {showTable && ( + <> + Select an RDS database to enroll: + + {selectedDb && ( + <> + + Optionally Add More Labels + + + + + )} + + )} + handleOnProceedWithValidation(validator)} + disableProceed={disableBtns || !showTable || !selectedDb} + /> + + )} + {attempt.status !== '' && ( (null); + const [customLabels, setCustomLabels] = useState([]); const ctx = useTeleport(); + function onSelectCluster(eks: CheckedEksCluster) { + // when changing selected cluster, clear defined labels + setCustomLabels([]); + setSelectedCluster(eks); + } + + function clearSelectedCluster() { + setSelectedCluster(null); + setCustomLabels([]); + } + function fetchClustersWithNewRegion(region: Regions) { setSelectedRegion(region); // Clear table when fetching with new region. @@ -148,7 +164,7 @@ export function EnrollEksCluster(props: AgentStepProps) { } function refreshClustersList() { - setSelectedCluster(null); + clearSelectedCluster(); // When refreshing, start the table back at page 1. fetchClusters({ ...tableData, startKey: '', items: [] }); } @@ -214,9 +230,7 @@ export function EnrollEksCluster(props: AgentStepProps) { if (tableData.items.length > 0) { setTableData(emptyTableData); } - if (selectedCluster) { - setSelectedCluster(null); - } + clearSelectedCluster(); setEnrollmentState({ status: 'notStarted' }); } @@ -279,6 +293,21 @@ export function EnrollEksCluster(props: AgentStepProps) { } as EksMeta); } + function showManualHelmDialog(validator: Validator) { + if (!validator.validate()) { + return; + } + + setIsManualHelmDialogShown(true); + } + + async function enrollWithValidation(validator: Validator) { + if (!validator.validate()) { + return; + } + return enroll(); + } + async function enroll() { const integrationName = (agentMeta as EksMeta).awsIntegration.name; setEnrollmentState({ status: 'enrolling' }); @@ -290,6 +319,7 @@ export function EnrollEksCluster(props: AgentStepProps) { region: selectedRegion, enableAppDiscovery: isAppDiscoveryEnabled, clusterNames: [selectedCluster.name], + extraLabels: customLabels, } ); @@ -380,7 +410,14 @@ export function EnrollEksCluster(props: AgentStepProps) { isCloud: ctx.isCloud, automaticUpgradesEnabled: ctx.automaticUpgradesEnabled, automaticUpgradesTargetVersion: ctx.automaticUpgradesTargetVersion, - joinLabels: [...selectedCluster.labels, ...selectedCluster.joinLabels], + // The labels from the `selectedCluster` are AWS tags which + // will be imported as is. `joinLabels` are internal Teleport labels + // added to each cluster when listing clusters. + joinLabels: [ + ...selectedCluster.labels, + ...selectedCluster.joinLabels, + ...customLabels, + ], disableAppDiscovery: !isAppDiscoveryEnabled, }); }, @@ -392,6 +429,7 @@ export function EnrollEksCluster(props: AgentStepProps) { ctx.storeUser.state.cluster, isAppDiscoveryEnabled, selectedCluster, + customLabels, ] ); @@ -457,7 +495,7 @@ export function EnrollEksCluster(props: AgentStepProps) { autoDiscovery={isAutoDiscoveryEnabled} fetchStatus={tableData.fetchStatus} selectedCluster={selectedCluster} - onSelectCluster={setSelectedCluster} + onSelectCluster={onSelectCluster} fetchNextPage={fetchNextPage} /> )} @@ -469,32 +507,60 @@ export function EnrollEksCluster(props: AgentStepProps) { /> )} {!isAutoDiscoveryEnabled && ( - - Automatically enroll selected EKS cluster - - - Enroll EKS Cluster - - - { - setIsManualHelmDialogShown(b => !b); - }} - > - Or enroll manually - - - - + + {({ validator }) => ( + <> + {selectedCluster && ( + <> + + Optionally Add More Labels + + + + + )} + + + Automatically enroll selected EKS cluster + + + enrollWithValidation(validator)} + disabled={enrollmentNotAllowed} + mt={2} + mb={2} + > + Enroll EKS Cluster + + + showManualHelmDialog(validator)} + > + Or enroll manually + + + + + + )} + )} {isAutoDiscoveryEnabled && ( { if (joinToken && !command) { setCommand(setJoinTokenAndGetCommand(joinToken)); } + + return () => clearCachedJoinTokenResult(resourceKinds); }, [joinToken, command, setJoinTokenAndGetCommand]); return ( diff --git a/web/packages/teleport/src/Discover/Kubernetes/HelmChart/HelmChart.story.tsx b/web/packages/teleport/src/Discover/Kubernetes/SelfHosted/HelmChart/HelmChart.story.tsx similarity index 100% rename from web/packages/teleport/src/Discover/Kubernetes/HelmChart/HelmChart.story.tsx rename to web/packages/teleport/src/Discover/Kubernetes/SelfHosted/HelmChart/HelmChart.story.tsx diff --git a/web/packages/teleport/src/Discover/Kubernetes/HelmChart/HelmChart.test.tsx b/web/packages/teleport/src/Discover/Kubernetes/SelfHosted/HelmChart/HelmChart.test.tsx similarity index 100% rename from web/packages/teleport/src/Discover/Kubernetes/HelmChart/HelmChart.test.tsx rename to web/packages/teleport/src/Discover/Kubernetes/SelfHosted/HelmChart/HelmChart.test.tsx diff --git a/web/packages/teleport/src/Discover/Kubernetes/HelmChart/HelmChart.tsx b/web/packages/teleport/src/Discover/Kubernetes/SelfHosted/HelmChart/HelmChart.tsx similarity index 80% rename from web/packages/teleport/src/Discover/Kubernetes/HelmChart/HelmChart.tsx rename to web/packages/teleport/src/Discover/Kubernetes/SelfHosted/HelmChart/HelmChart.tsx index ef91a9ff0160d..454e7bd8f57a4 100644 --- a/web/packages/teleport/src/Discover/Kubernetes/HelmChart/HelmChart.tsx +++ b/web/packages/teleport/src/Discover/Kubernetes/SelfHosted/HelmChart/HelmChart.tsx @@ -16,10 +16,19 @@ * along with this program. If not, see . */ -import { Suspense, useState } from 'react'; +import { Suspense, useEffect, useState } from 'react'; import styled from 'styled-components'; -import { Box, ButtonSecondary, H3, Link, Mark, Subtitle3, Text } from 'design'; +import { + Box, + ButtonSecondary, + Flex, + H3, + Link, + Mark, + Subtitle3, + Text, +} from 'design'; import * as Icons from 'design/Icon'; import { P } from 'design/Text/Text'; import FieldInput from 'shared/components/FieldInput'; @@ -35,6 +44,7 @@ import { WaitingInfo, } from 'teleport/Discover/Shared/HintBox'; import { usePingTeleport } from 'teleport/Discover/Shared/PingTeleportContext'; +import { ResourceLabelTooltip } from 'teleport/Discover/Shared/ResourceLabelTooltip'; import { clearCachedJoinTokenResult, useJoinTokenSuspender, @@ -49,16 +59,18 @@ import { ActionButtons, Header, HeaderSubtitle, + LabelsCreater, ResourceKind, TextIcon, useShowHint, -} from '../../Shared'; -import type { AgentStepProps } from '../../types'; +} from '../../../Shared'; +import type { AgentStepProps } from '../../../types'; export default function Container(props: AgentStepProps) { const [namespace, setNamespace] = useState(''); const [clusterName, setClusterName] = useState(''); const [showHelmChart, setShowHelmChart] = useState(false); + const [labels, setLabels] = useState([]); return ( // This outer CatchError and Suspense handles @@ -82,6 +94,9 @@ export default function Container(props: AgentStepProps) { setNamespace={setNamespace} clusterName={clusterName} setClusterName={setClusterName} + labels={labels} + onChangeLabels={setLabels} + generateScript={fallbackProps.retry} /> null} @@ -102,6 +117,9 @@ export default function Container(props: AgentStepProps) { setNamespace={setNamespace} clusterName={clusterName} setClusterName={setClusterName} + labels={labels} + onChangeLabels={setLabels} + processing={true} /> null} @@ -122,6 +140,8 @@ export default function Container(props: AgentStepProps) { setNamespace={setNamespace} clusterName={clusterName} setClusterName={setClusterName} + labels={labels} + onChangeLabels={setLabels} /> null} @@ -138,6 +158,8 @@ export default function Container(props: AgentStepProps) { setNamespace={setNamespace} clusterName={clusterName} setClusterName={setClusterName} + labels={labels} + onChangeLabels={setLabels} /> )} @@ -145,6 +167,12 @@ export default function Container(props: AgentStepProps) { ); } +const resourceKinds = [ + ResourceKind.Kubernetes, + ResourceKind.Application, + ResourceKind.Discovery, +]; + export function HelmChart( props: AgentStepProps & { onEdit: () => void; @@ -152,26 +180,33 @@ export function HelmChart( setNamespace(n: string): void; clusterName: string; setClusterName(c: string): void; + labels: ResourceLabel[]; + onChangeLabels(l: ResourceLabel[]): void; } ) { - const { joinToken, reloadJoinToken } = useJoinTokenSuspender([ - ResourceKind.Kubernetes, - ResourceKind.Application, - ResourceKind.Discovery, - ]); + const { joinToken, reloadJoinToken } = useJoinTokenSuspender({ + resourceKinds, + suggestedLabels: props.labels, + }); + + useEffect(() => { + return () => clearCachedJoinTokenResult(resourceKinds); + }); return ( props.onEdit()} generateScript={reloadJoinToken} namespace={props.namespace} setNamespace={props.setNamespace} clusterName={props.clusterName} setClusterName={props.setClusterName} + labels={props.labels} + onChangeLabels={props.onChangeLabels} /> ); @@ -233,8 +269,11 @@ const StepTwo = ({ setClusterName, error, generateScript, - disabled, + showHelmChart, onEdit, + labels, + onChangeLabels, + processing, }: { error?: Error; generateScript?(): void; @@ -242,11 +281,19 @@ const StepTwo = ({ setNamespace(n: string): void; clusterName: string; setClusterName(c: string): void; - disabled?: boolean; + showHelmChart?: boolean; + processing?: boolean; onEdit: () => void; + labels: ResourceLabel[]; + onChangeLabels(l: ResourceLabel[]): void; }) => { - function handleSubmit(validator: Validator) { - if (!validator.validate()) { + const disabled = showHelmChart || processing; + + function handleSubmit( + inputFieldValidator: Validator, + labelsValidator: Validator + ) { + if (!inputFieldValidator.validate() || !labelsValidator.validate()) { return; } generateScript(); @@ -262,7 +309,7 @@ const StepTwo = ({ - {({ validator }) => ( + {({ validator: inputFieldValidator }) => ( <> setClusterName(e.target.value)} /> - {disabled ? ( - onEdit()} - > - Edit - - ) : ( - handleSubmit(validator)} - > - Next - - )} + + Add Labels (Optional) + + + + {({ validator: labelsValidator }) => ( + <> + + + + {showHelmChart ? ( + onEdit()} + > + Edit + + ) : ( + + handleSubmit(inputFieldValidator, labelsValidator) + } + disabled={processing} + > + Generate Command + + )} + + )} + )} @@ -391,6 +460,7 @@ const InstallHelmChart = ({ nextStep, prevStep, updateAgentMeta, + labels, }: { namespace: string; clusterName: string; @@ -398,6 +468,7 @@ const InstallHelmChart = ({ nextStep(): void; prevStep(): void; updateAgentMeta(a: AgentMeta): void; + labels: ResourceLabel[]; }) => { const ctx = useTeleport(); @@ -477,6 +548,7 @@ const InstallHelmChart = ({ isCloud: ctx.isCloud, automaticUpgradesEnabled: ctx.automaticUpgradesEnabled, automaticUpgradesTargetVersion: ctx.automaticUpgradesTargetVersion, + joinLabels: labels, }); return ( diff --git a/web/packages/teleport/src/Discover/Kubernetes/HelmChart/index.ts b/web/packages/teleport/src/Discover/Kubernetes/SelfHosted/HelmChart/index.ts similarity index 89% rename from web/packages/teleport/src/Discover/Kubernetes/HelmChart/index.ts rename to web/packages/teleport/src/Discover/Kubernetes/SelfHosted/HelmChart/index.ts index 0239113fe6f88..b995808c6f4e1 100644 --- a/web/packages/teleport/src/Discover/Kubernetes/HelmChart/index.ts +++ b/web/packages/teleport/src/Discover/Kubernetes/SelfHosted/HelmChart/index.ts @@ -16,6 +16,6 @@ * along with this program. If not, see . */ -import HelmChart from './HelmChart'; +import HelmChart, { generateCmd } from './HelmChart'; -export { HelmChart }; +export { HelmChart, generateCmd }; diff --git a/web/packages/teleport/src/Discover/Kubernetes/SelfHosted/index.ts b/web/packages/teleport/src/Discover/Kubernetes/SelfHosted/index.ts new file mode 100644 index 0000000000000..6c6ef4aff54b0 --- /dev/null +++ b/web/packages/teleport/src/Discover/Kubernetes/SelfHosted/index.ts @@ -0,0 +1,19 @@ +/** + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +export * from './HelmChart'; diff --git a/web/packages/teleport/src/Discover/Kubernetes/index.tsx b/web/packages/teleport/src/Discover/Kubernetes/index.tsx index f5b668cab05b4..81595fd4d2282 100644 --- a/web/packages/teleport/src/Discover/Kubernetes/index.tsx +++ b/web/packages/teleport/src/Discover/Kubernetes/index.tsx @@ -24,8 +24,8 @@ import { KubeLocation, ResourceSpec } from 'teleport/Discover/SelectResource'; import { AwsAccount, Finished, ResourceKind } from 'teleport/Discover/Shared'; import { DiscoverEvent } from 'teleport/services/userEvent'; -import { HelmChart } from './HelmChart'; import { KubeWrapper } from './KubeWrapper'; +import { HelmChart } from './SelfHosted'; import { SetupAccess } from './SetupAccess'; import { TestConnection } from './TestConnection'; diff --git a/web/packages/teleport/src/Discover/Server/DownloadScript/DownloadScript.story.tsx b/web/packages/teleport/src/Discover/Server/DownloadScript/DownloadScript.story.tsx index 7dad3e0ec67de..277c05492f7ac 100644 --- a/web/packages/teleport/src/Discover/Server/DownloadScript/DownloadScript.story.tsx +++ b/web/packages/teleport/src/Discover/Server/DownloadScript/DownloadScript.story.tsx @@ -72,7 +72,7 @@ export const Polling: StoryObj = { render() { return ( - + null} /> ); }, @@ -95,7 +95,7 @@ export const PollingSuccess: StoryObj = { render() { return ( - + null} /> ); }, @@ -120,7 +120,7 @@ export const PollingError: StoryObj = { render() { return ( - + null} /> ); }, @@ -139,7 +139,7 @@ export const Processing: StoryObj = { render() { return ( - + null} /> ); }, @@ -163,7 +163,7 @@ export const Failed: StoryObj = { render() { return ( - + null} /> ); }, diff --git a/web/packages/teleport/src/Discover/Server/DownloadScript/DownloadScript.tsx b/web/packages/teleport/src/Discover/Server/DownloadScript/DownloadScript.tsx index 9ce6b53edcb55..7e8de453f7f73 100644 --- a/web/packages/teleport/src/Discover/Server/DownloadScript/DownloadScript.tsx +++ b/web/packages/teleport/src/Discover/Server/DownloadScript/DownloadScript.tsx @@ -16,34 +16,38 @@ * along with this program. If not, see . */ -import React, { Suspense, useEffect, useState } from 'react'; +import { Suspense, useEffect, useState } from 'react'; -import { Box, Indicator, Mark, Text } from 'design'; +import { Box, ButtonSecondary, Flex, Mark, Text } from 'design'; import * as Icons from 'design/Icon'; -import { P } from 'design/Text/Text'; +import { H3, Subtitle3 } from 'design/Text/Text'; +import Validation, { Validator } from 'shared/components/Validation'; import { CatchError } from 'teleport/components/CatchError'; import { TextSelectCopyMulti } from 'teleport/components/TextSelectCopy'; import cfg from 'teleport/config'; -import { CommandBox } from 'teleport/Discover/Shared/CommandBox'; import { HintBox, SuccessBox, WaitingInfo, } from 'teleport/Discover/Shared/HintBox'; import { usePingTeleport } from 'teleport/Discover/Shared/PingTeleportContext'; +import { ResourceLabelTooltip } from 'teleport/Discover/Shared/ResourceLabelTooltip/ResourceLabelTooltip'; import { clearCachedJoinTokenResult, useJoinTokenSuspender, } from 'teleport/Discover/Shared/useJoinTokenSuspender'; +import { ResourceLabel } from 'teleport/services/agents'; import { JoinToken } from 'teleport/services/joinToken'; -import type { Node } from 'teleport/services/nodes'; +import { Node } from 'teleport/services/nodes'; import { ActionButtons, Header, HeaderSubtitle, + LabelsCreater, ResourceKind, + StyledBox, TextIcon, } from '../../Shared'; import { AgentStepProps } from '../../types'; @@ -51,38 +55,148 @@ import { AgentStepProps } from '../../types'; const SHOW_HINT_TIMEOUT = 1000 * 60 * 5; // 5 minutes export default function Container(props: AgentStepProps) { + const [labels, setLabels] = useState([]); + const [showScript, setShowScript] = useState(false); + + function toggleShowScript(validator: Validator) { + if (!validator.validate()) { + return; + } + setShowScript(!showScript); + } + + const commonProps = { + labels, + onChangeLabels: setLabels, + showScript, + onShowScript: toggleShowScript, + onPrev: props.prevStep, + }; + return ( clearCachedJoinTokenResult([ResourceKind.Server])} fallbackFn={fbProps => ( - + <> + + + )} > - -
+ <> + + + } > - + + + {showScript && } ); } -export function DownloadScript(props: AgentStepProps) { +const Heading = () => ( + <> +
Configure Resource
+ + Install and configure the Teleport SSH Service + + +); + +export function StepOne({ + labels, + onChangeLabels, + showScript, + onShowScript, + error, + processing = false, + onPrev, +}: { + labels: ResourceLabel[]; + onChangeLabels(l: ResourceLabel[]): void; + showScript: boolean; + onShowScript(validator: Validator): void; + error?: Error; + processing?: boolean; + onPrev(): void; +}) { + const nextLabelTxt = labels.length + ? 'Finish Adding Labels' + : 'Skip Adding Labels'; + return ( + <> + +
+

Step 1 (Optional)

+ + Add Labels + + +
+ + {({ validator }) => ( + <> + + {error && ( + + + Encountered Error: {error.message} + + )} + + onShowScript(validator)} + disabled={processing} + > + {showScript && !error ? 'Edit Labels' : nextLabelTxt} + + + + )} + +
+ {(!showScript || processing || error) && ( + null} + disableProceed={true} + onPrev={onPrev} + /> + )} + + ); +} + +export function StepTwoWithActionBtns( + props: AgentStepProps & { labels: ResourceLabel[] } +) { // Fetches join token. - const { joinToken } = useJoinTokenSuspender([ResourceKind.Server]); + const { joinToken } = useJoinTokenSuspender({ + resourceKinds: [ResourceKind.Server], + suggestedLabels: props.labels, + }); // Starts resource querying interval. const { result, active } = usePingTeleport(joinToken); @@ -92,7 +206,10 @@ export function DownloadScript(props: AgentStepProps) { if (active) { const id = window.setTimeout(() => setShowHint(true), SHOW_HINT_TIMEOUT); - return () => window.clearTimeout(id); + return () => { + window.clearTimeout(id); + clearCachedJoinTokenResult([ResourceKind.Server]); + }; } }, [active]); @@ -153,17 +270,22 @@ export function DownloadScript(props: AgentStepProps) { return ( <> -
Configure Resource
- - Install and configure the Teleport Service - -

Run the following command on the server you want to add.

- - - - {hint} + {joinToken && ( + <> + +
+

Step 2

+ + Run the following command on the server you want to add + +
+ +
+ {hint} + + )} { - return ( - <> -
Configure Resource
- - Install and configure the Teleport Service. -
- Run the following command on the server you want to add. -
- {children} - - - ); -}; - function createBashCommand(tokenId: string) { return `sudo bash -c "$(curl -fsSL ${cfg.getNodeScriptUrl(tokenId)})"`; } diff --git a/web/packages/teleport/src/Discover/Shared/LabelsCreater/LabelsCreater.tsx b/web/packages/teleport/src/Discover/Shared/LabelsCreater/LabelsCreater.tsx index dbf829e911fdd..1e733540d3c84 100644 --- a/web/packages/teleport/src/Discover/Shared/LabelsCreater/LabelsCreater.tsx +++ b/web/packages/teleport/src/Discover/Shared/LabelsCreater/LabelsCreater.tsx @@ -189,7 +189,7 @@ export function LabelsCreater({ })} { active: boolean; start: (tokenOrTerm: JoinToken | string) => void; result: T | null; + stop: () => void; } const pingTeleportContext = @@ -117,6 +118,7 @@ export function PingTeleportProvider(props: { active, start, result, + stop: () => setActive(false), }} > {props.children} @@ -137,6 +139,8 @@ export function usePingTeleport(tokenOrTerm: JoinToken | string) { if (!ctx.active && !ctx.result) { ctx.start(tokenOrTerm); } + + return () => ctx.stop(); }, []); return ctx; diff --git a/web/packages/teleport/src/Discover/Shared/useJoinTokenSuspender.ts b/web/packages/teleport/src/Discover/Shared/useJoinTokenSuspender.ts index 18e7f9e3f007c..f82c9d4d64a6d 100644 --- a/web/packages/teleport/src/Discover/Shared/useJoinTokenSuspender.ts +++ b/web/packages/teleport/src/Discover/Shared/useJoinTokenSuspender.ts @@ -41,11 +41,25 @@ export function clearCachedJoinTokenResult(resourceKinds: ResourceKind[]) { joinTokenCache.delete(resourceKinds.sort().join()); } -export function useJoinTokenSuspender( - resourceKinds: ResourceKind[], - suggestedAgentMatcherLabels: ResourceLabel[] = [], - joinMethod: JoinMethod = 'token' -): { +export function useJoinTokenSuspender({ + resourceKinds, + suggestedAgentMatcherLabels = [], + joinMethod = 'token', + suggestedLabels = [], +}: { + resourceKinds: ResourceKind[]; + /** + * labels used for the agent that will be created + * using a join token (eg: db agent) + */ + suggestedAgentMatcherLabels?: ResourceLabel[]; + joinMethod?: JoinMethod; + /** + * labels for a non-agent resource that will be created + * using a join token (currently only can be applied to server resource kind). + */ + suggestedLabels?: ResourceLabel[]; +}): { joinToken: JoinToken; reloadJoinToken: () => void; } { @@ -68,6 +82,7 @@ export function useJoinTokenSuspender( roles: resourceKinds.map(resourceKindToJoinRole), method: joinMethod, suggestedAgentMatcherLabels, + suggestedLabels, }, abortController.signal )