diff --git a/apps/agent/src/common/AgentUIVersion.tsx b/apps/agent/src/common/AgentUIVersion.tsx new file mode 100644 index 0000000..65d3dbe --- /dev/null +++ b/apps/agent/src/common/AgentUIVersion.tsx @@ -0,0 +1,38 @@ +import React, { useState, useEffect } from 'react'; +import type { AgentApiInterface } from "@migration-planner-ui/agent-client/apis"; +import { useInjection } from '@migration-planner-ui/ioc'; +import { Symbols } from '#/main/Symbols'; + +export const AgentUIVersion: React.FC = () => { + const agentApi = useInjection(Symbols.AgentApi); + const [versionInfo, setVersionInfo] = useState(null); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchVersion = async ():Promise => { + try { + const statusReply = await agentApi.getAgentVersion(); + setVersionInfo(statusReply); + } catch (err) { + console.error('Error fetching agent version:', err); + setError('Error fetching version'); + } + }; + + fetchVersion(); + }, [agentApi]); + + if (error) { + return ; + } + + if (!versionInfo) { + return ; + } + + return ( + + ); +}; \ No newline at end of file diff --git a/apps/agent/src/login-form/LoginForm.tsx b/apps/agent/src/login-form/LoginForm.tsx index f117d38..eb2ae6d 100644 --- a/apps/agent/src/login-form/LoginForm.tsx +++ b/apps/agent/src/login-form/LoginForm.tsx @@ -174,6 +174,8 @@ export const LoginForm: React.FC = (props) => { name="isDataSharingAllowed" label="I agree to share aggregated data about my environment with Red Hat." aria-label="Share aggregated data" + onChange={(_event,checked)=>vm.handleChangeDataSharingAllowed(checked)} + isChecked={vm.isDataSharingChecked} /> @@ -236,7 +238,7 @@ export const LoginForm: React.FC = (props) => { + + {!isCancelHidden && ( + + )} + + + ); }; export const MigrationWizard: React.FC = () => { const computedHeight = useComputedHeightFromPageHeader(); const discoverSourcesContext = useDiscoverySources(); - const isDiscoverySourceUpToDate = discoverSourcesContext.sourceSelected?.status === "up-to-date"; - + const isDiscoverySourceUpToDate = + discoverSourcesContext.agentSelected?.status === "up-to-date"; + return ( + } > } + isDisabled={ + discoverSourcesContext.agentSelected?.status !== "up-to-date" || + discoverSourcesContext.sourceSelected === null + } > + } + isDisabled={ + discoverSourcesContext.agentSelected?.status !== "up-to-date" || + discoverSourcesContext.sourceSelected === null + } > diff --git a/apps/demo/src/migration-wizard/contexts/discovery-sources/@types/DiscoverySources.d.ts b/apps/demo/src/migration-wizard/contexts/discovery-sources/@types/DiscoverySources.d.ts index 65b8089..67a6b37 100644 --- a/apps/demo/src/migration-wizard/contexts/discovery-sources/@types/DiscoverySources.d.ts +++ b/apps/demo/src/migration-wizard/contexts/discovery-sources/@types/DiscoverySources.d.ts @@ -5,18 +5,25 @@ declare namespace DiscoverySources { errorLoadingSources?: Error; isDeletingSource: boolean; errorDeletingSource?: Error; - isCreatingSource: boolean; - errorCreatingSource?: Error; isDownloadingSource: boolean; errorDownloadingSource?: Error; isPolling: boolean; sourceSelected: Source; listSources: () => Promise; deleteSource: (id: string) => Promise; - createSource: (name: string, sourceSshKey: string) => Promise; - downloadSource: (sourceName: string, sourceSshKey: string) => Promise; + downloadSource: (sourceSshKey: string) => Promise; startPolling: (delay: number) => void; stopPolling: () => void; selectSource: (source:Source) => void; + agents: Agent[]|undefined; + isLoadingAgents: boolean; + errorLoadingAgents?: Error; + listAgents: () => Promise; + deleteAgent: (agent: Agent) => Promise; + isDeletingAgent: boolean; + errorDeletingAgent?: Error; + selectAgent: (agent:Agent) => void; + agentSelected: Agent; + selectSourceById: (sourceId:string)=>void; }; } diff --git a/apps/demo/src/migration-wizard/contexts/discovery-sources/Provider.tsx b/apps/demo/src/migration-wizard/contexts/discovery-sources/Provider.tsx index d2386fb..3b0022e 100644 --- a/apps/demo/src/migration-wizard/contexts/discovery-sources/Provider.tsx +++ b/apps/demo/src/migration-wizard/contexts/discovery-sources/Provider.tsx @@ -4,18 +4,26 @@ import React, { type PropsWithChildren, } from "react"; import { useAsyncFn, useInterval } from "react-use"; -import { type SourceApiInterface } from "@migration-planner-ui/api-client/apis"; +import { type SourceApiInterface, type AgentApiInterface } from "@migration-planner-ui/api-client/apis"; import { useInjection } from "@migration-planner-ui/ioc"; import { Symbols } from "#/main/Symbols"; import { Context } from "./Context"; -import { Source } from "@migration-planner-ui/api-client/models"; +import { Agent, Source } from "@migration-planner-ui/api-client/models"; export const Provider: React.FC = (props) => { const { children } = props; const [sourceSelected, setSourceSelected] = useState(null) + const [agentSelected, setAgentSelected] = useState(null) + const sourceApi = useInjection(Symbols.SourceApi); + const agentsApi = useInjection(Symbols.AgentApi); + + const [listAgentsState, listAgents] = useAsyncFn(async () => { + const agents = await agentsApi.listAgents(); + return agents; + }); const [listSourcesState, listSources] = useAsyncFn(async () => { const sources = await sourceApi.listSources(); @@ -27,20 +35,12 @@ export const Provider: React.FC = (props) => { return deletedSource; }); - const [createSourceState, createSource] = useAsyncFn(async (name: string, sshKey: string) => { - const createdSource = await sourceApi.createSource({ - sourceCreate: { name, sshKey }, - }); - return createdSource; - }); const [downloadSourceState, downloadSource] = useAsyncFn( - async (sourceName: string, sourceSshKey: string): Promise => { + async (sshKey:string): Promise => { const anchor = document.createElement("a"); - anchor.download = sourceName + ".ova"; - - const newSource = await createSource(sourceName, sourceSshKey); - const imageUrl = `/planner/api/v1/sources/${newSource.id}/image`; + anchor.download = 'image.ova'; + const imageUrl = `/planner/api/v1/image${sshKey ? '?sshKey=' + sshKey : ''}`; const response = await fetch(imageUrl, { method: 'HEAD' }); @@ -84,31 +84,60 @@ export const Provider: React.FC = (props) => { }, [isPolling]); useInterval(() => { listSources(); + listAgents(); }, pollingDelay); - const selectSource = useCallback((source: Source) => { + const selectSource = useCallback((source: Source|null) => { setSourceSelected(source); }, []); + const selectSourceById = useCallback((sourceId: string) => { + const source = listSourcesState.value?.find(source => source.id === sourceId); + setSourceSelected(source||null); + }, [listSourcesState.value]); + + + const selectAgent = useCallback(async (agent: Agent) => { + setAgentSelected(agent); + if (agent && agent.sourceId!==null) await selectSourceById(agent.sourceId ?? ''); + }, [selectSourceById]); + + + const [deleteAgentState, deleteAgent] = useAsyncFn(async (agent: Agent) => { + if (agent && agent.sourceId !== null) { + await deleteSource(agent.sourceId ?? ''); + selectSource(null); + } + const deletedAgent = await agentsApi.deleteAgent({id: agent.id}); + return deletedAgent; + }); + const ctx: DiscoverySources.Context = { sources: listSourcesState.value ?? [], isLoadingSources: listSourcesState.loading, errorLoadingSources: listSourcesState.error, isDeletingSource: deleteSourceState.loading, errorDeletingSource: deleteSourceState.error, - isCreatingSource: createSourceState.loading, - errorCreatingSource: createSourceState.error, isDownloadingSource: downloadSourceState.loading, - errorDownloadingSource: downloadSourceState.error, + errorDownloadingSource: downloadSourceState.error, isPolling, listSources, deleteSource, - createSource, downloadSource, startPolling, stopPolling, sourceSelected: sourceSelected, selectSource, + agents: listAgentsState.value ?? [], + isLoadingAgents: listAgentsState.loading, + errorLoadingAgents: listAgentsState.error, + listAgents, + deleteAgent, + isDeletingAgent: deleteAgentState.loading, + errorDeletingAgent: deleteAgentState.error, + selectAgent, + agentSelected: agentSelected, + selectSourceById }; return {children}; diff --git a/apps/demo/src/migration-wizard/steps/connect/ConnectStep.tsx b/apps/demo/src/migration-wizard/steps/connect/ConnectStep.tsx index aad81e9..edcbd16 100644 --- a/apps/demo/src/migration-wizard/steps/connect/ConnectStep.tsx +++ b/apps/demo/src/migration-wizard/steps/connect/ConnectStep.tsx @@ -31,15 +31,17 @@ export const ConnectStep: React.FC = () => { const toggleDiscoverySourceSetupModal = useCallback((): void => { setShouldShowDiscoverySetupModal((lastState) => !lastState); }, []); - const hasSources = discoverySourcesContext.sources.length > 0; - const [firstSource, ..._otherSources] = discoverySourcesContext.sources; + const hasAgents = discoverySourcesContext.agents && discoverySourcesContext.agents.length > 0; + const [firstAgent, ..._otherAgents] = discoverySourcesContext.agents || []; useEffect(() => { - if (!discoverySourcesContext.sourceSelected && firstSource) { - discoverySourcesContext.selectSource(firstSource); + if (!discoverySourcesContext.agentSelected && firstAgent) { + if (firstAgent.status === 'up-to-date') { + discoverySourcesContext.selectAgent(firstAgent); + } } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [firstSource]); + }, [firstAgent]); return ( @@ -69,7 +71,7 @@ export const ConnectStep: React.FC = () => { - {discoverySourcesContext.sourceSelected?.status === + {discoverySourcesContext.agentSelected?.status === "waiting-for-credentials" && ( { actionLinks={ - {discoverySourcesContext.sourceSelected?.credentialUrl} + {discoverySourcesContext.agentSelected?.credentialUrl} } > @@ -113,7 +115,7 @@ export const ConnectStep: React.FC = () => { - {hasSources && ( + {hasAgents && (