diff --git a/libs/locales/lib/en/translation.json b/libs/locales/lib/en/translation.json index 64488d8069..7b64cd60e0 100644 --- a/libs/locales/lib/en/translation.json +++ b/libs/locales/lib/en/translation.json @@ -121,8 +121,10 @@ "ai:Available": "Available", "ai:Back": "Back", "ai:Bare metal": "Bare metal", + "ai:Bare metal host": "Bare metal host", "ai:Bare metal host cannot be edited. Remove this host and add it again if a change is needed.": "Bare metal host cannot be edited. Remove this host and add it again if a change is needed.", "ai:Bare metal host cannot be removed from cluster.": "Bare metal host cannot be removed from cluster.", + "ai:Bare metal host events": "Bare metal host events", "ai:Bare metal host not found": "Bare metal host not found", "ai:Bare Metal Host related": "Bare Metal Host related", "ai:Base domain": "Base domain", @@ -260,6 +262,7 @@ "ai:Define the quantity of worker nodes and nodepools to create for your cluster. Additional worker nodes and nodepools can be added after the cluster is created.": "Define the quantity of worker nodes and nodepools to create for your cluster. Additional worker nodes and nodepools can be added after the cluster is created.", "ai:Defines how big the subnets for each individual node are out of the given CIDR. Must enter a whole number.": "Defines how big the subnets for each individual node are out of the given CIDR. Must enter a whole number.", "ai:Deleted hosts": "Deleted hosts", + "ai:Deleting": "Deleting", "ai:Deprovisioning": "Deprovisioning", "ai:Details": "Details", "ai:Developer Preview": "Developer Preview", @@ -521,6 +524,7 @@ "ai:Labels": "Labels", "ai:Labels matching hosts": "Labels matching hosts", "ai:Last observed condition:": "Last observed condition:", + "ai:Last updated:": "Last updated:", "ai:Launch OpenShift Console": "Launch OpenShift Console", "ai:Learn more": "Learn more", "ai:Learn more about configuration.": "Learn more about configuration.", @@ -611,6 +615,7 @@ "ai:NIC_plural": "NICs", "ai:NMState": "NMState", "ai:No available hosts with {{cpuArchitecture}} architecture were found": "No available hosts with {{cpuArchitecture}} architecture were found", + "ai:No BMH events found.": "No BMH events found.", "ai:No host is selected.": "No host is selected.", "ai:No host matches provided labels/locations": "No host matches provided labels/locations", "ai:No hosts found": "No hosts found", @@ -922,6 +927,7 @@ "ai:try again": "try again", "ai:Try logging into the machine directly through physical access, out-of-band management, or a virtual machine console. To generate a new bootable image file with password-based login enabled, download the full image file and patch it locally with a login password of your choice using": "Try logging into the machine directly through physical access, out-of-band management, or a virtual machine console. To generate a new bootable image file with password-based login enabled, download the full image file and patch it locally with a login password of your choice using", "ai:Two nodes control plane OpenShift must include an additional arbiter node.": "Two nodes control plane OpenShift must include an additional arbiter node.", + "ai:Type": "Type", "ai:Type or select location(s)": "Type or select location(s)", "ai:Unable to SSH into your hosts through the network?": "Unable to SSH into your hosts through the network?", "ai:Unique hostname": "Unique hostname", @@ -958,6 +964,7 @@ "ai:Verify that you can access your host machine using SSH, or a console such as BMC or virtual machine console. In the CLI, enter the following command:": "Verify that you can access your host machine using SSH, or a console such as BMC or virtual machine console. In the CLI, enter the following command:", "ai:View {{count}} affected host": "View {{count}} affected host", "ai:View {{count}} affected host_plural": "View {{count}} affected hosts", + "ai:View BMH events": "View BMH events", "ai:View cluster events": "View cluster events", "ai:View documentation": "View documentation", "ai:View host events": "View host events", diff --git a/libs/ui-lib/lib/cim/components/Agent/AgentStatus.tsx b/libs/ui-lib/lib/cim/components/Agent/AgentStatus.tsx index bc38009803..9e0c2e0b68 100644 --- a/libs/ui-lib/lib/cim/components/Agent/AgentStatus.tsx +++ b/libs/ui-lib/lib/cim/components/Agent/AgentStatus.tsx @@ -20,6 +20,7 @@ export type AgentStatusProps = { zIndex?: number; wizardStepId?: ClusterDeploymentWizardStepsType; isDay2?: boolean; + additionalBMHInfo?: React.ReactNode; }; const AgentStatus: React.FC = ({ @@ -29,6 +30,7 @@ const AgentStatus: React.FC = ({ zIndex, wizardStepId, isDay2, + additionalBMHInfo, }) => { const { t } = useTranslation(); const [host] = getAIHosts([agent]); @@ -65,6 +67,7 @@ const AgentStatus: React.FC = ({ ) : undefined } + additionalBMHInfo={additionalBMHInfo} {...status} > {pendingApproval && onApprove && ( diff --git a/libs/ui-lib/lib/cim/components/Agent/AgentTable.tsx b/libs/ui-lib/lib/cim/components/Agent/AgentTable.tsx index 23dbbb73ab..a0620d7370 100644 --- a/libs/ui-lib/lib/cim/components/Agent/AgentTable.tsx +++ b/libs/ui-lib/lib/cim/components/Agent/AgentTable.tsx @@ -3,7 +3,8 @@ import { AgentClusterInstallK8sResource, AgentK8sResource } from '../../types'; import { AgentTableActions } from '../ClusterDeployment/types'; import DefaultEmptyState from '../../../common/components/ui/uiState/EmptyState'; import { ConnectedIcon } from '@patternfly/react-icons/dist/js/icons/connected-icon'; -import { infraEnvColumn, agentStatusColumn, useAgentsTable } from './tableUtils'; +import { useAgentsTable } from './tableUtils'; +import { infraEnvColumn, agentStatusColumn } from './tableColumns'; import { cpuArchitectureColumn, cpuCoresColumn, @@ -11,7 +12,7 @@ import { hostnameColumn, memoryColumn, roleColumn, -} from '../../../common/components/hosts/tableUtils'; +} from '../../../common'; import HostsTable, { DefaultExpandComponent } from '../../../common/components/hosts/HostsTable'; import { usePagination } from '../../../common/components/hosts/usePagination'; import { useTranslation } from '../../../common/hooks/use-translation-wrapper'; diff --git a/libs/ui-lib/lib/cim/components/Agent/AgentsSelectionTable.tsx b/libs/ui-lib/lib/cim/components/Agent/AgentsSelectionTable.tsx index 0f096478a4..9283a48ebc 100644 --- a/libs/ui-lib/lib/cim/components/Agent/AgentsSelectionTable.tsx +++ b/libs/ui-lib/lib/cim/components/Agent/AgentsSelectionTable.tsx @@ -1,23 +1,14 @@ import React from 'react'; import { useField } from 'formik'; import HostsTable, { DefaultExpandComponent } from '../../../common/components/hosts/HostsTable'; -import { - cpuCoresColumn, - disksColumn, - memoryColumn, - roleColumn, -} from '../../../common/components/hosts/tableUtils'; +import { cpuCoresColumn, disksColumn, memoryColumn, roleColumn } from '../../../common'; +import { agentHostnameColumn, infraEnvColumn, agentStatusColumn } from './tableColumns'; import { AgentK8sResource } from '../../types/k8s/agent'; import { ClusterDeploymentHostsSelectionValues, AgentTableActions, } from '../ClusterDeployment/types'; -import { - infraEnvColumn, - agentStatusColumn, - useAgentsTable, - agentHostnameColumn, -} from './tableUtils'; +import { useAgentsTable } from './tableUtils'; import DefaultEmptyState from '../../../common/components/ui/uiState/EmptyState'; import { usePagination } from '../../../common/components/hosts/usePagination'; import { useFormikHelpers } from '../../../common/hooks/useFormikHelpers'; diff --git a/libs/ui-lib/lib/cim/components/Agent/BMHEventsModal.tsx b/libs/ui-lib/lib/cim/components/Agent/BMHEventsModal.tsx new file mode 100644 index 0000000000..2e6aec0ab6 --- /dev/null +++ b/libs/ui-lib/lib/cim/components/Agent/BMHEventsModal.tsx @@ -0,0 +1,87 @@ +import React from 'react'; +import { + Modal, + ModalVariant, + ModalHeader, + ModalBody, + Content, + ContentVariants, +} from '@patternfly/react-core'; +import { Table, Thead, Tr, Th, Tbody, Td, InnerScrollContainer } from '@patternfly/react-table'; +import { getHumanizedDateTime, LoadingState, useTranslation } from '../../../common'; +import { useEvents } from '../../hooks'; + +export const BMHEventsModal = ({ + isOpen, + onClose, + namespace, + bmhName, +}: { + isOpen: boolean; + onClose: () => void; + namespace: string; + bmhName: string; +}) => { + const { t } = useTranslation(); + const [events, loaded, error] = useEvents({ + namespace: namespace, + isList: true, + fieldSelector: `involvedObject.name=${bmhName}`, + }); + + let modalBody: React.ReactNode; + if (!loaded) { + modalBody = ; + } else if (error) { + modalBody = {error as string}; + } else if (!events.length) { + modalBody = {t('ai:No BMH events found.')}; + } else { + modalBody = ( +
+ + + + + + + + + + + {events + .sort( + (a, b) => + b.metadata?.creationTimestamp?.localeCompare( + a.metadata?.creationTimestamp || '', + ) || 0, + ) + .map((e, i) => ( + + + + + + ))} + +
{t('ai:Time')}{t('ai:Type')}{t('ai:Message')}
+ {getHumanizedDateTime(e.metadata?.creationTimestamp)} + {e.type}{e.message}
+
+
+ ); + } + + return ( + + + {modalBody} + + ); +}; diff --git a/libs/ui-lib/lib/cim/components/Agent/BMHStatus.tsx b/libs/ui-lib/lib/cim/components/Agent/BMHStatus.tsx index f00eb8055c..0329ea7201 100644 --- a/libs/ui-lib/lib/cim/components/Agent/BMHStatus.tsx +++ b/libs/ui-lib/lib/cim/components/Agent/BMHStatus.tsx @@ -1,27 +1,27 @@ import { Button, Popover } from '@patternfly/react-core'; import * as React from 'react'; import { getBMHStatus } from '../helpers/status'; +import { BareMetalHostK8sResource } from '../../types'; +import { BMHStatusInfo } from './BMHStatusInfo'; type BMHStatusProps = { bmhStatus: ReturnType; + bmh: BareMetalHostK8sResource; }; -const BMHStatus: React.FC = ({ bmhStatus }) => - bmhStatus.errorMessage ? ( - - - - ) : ( - <>{bmhStatus.state.title} - ); +const BMHStatus: React.FC = ({ bmhStatus, bmh }) => ( + } + minWidth="30rem" + maxWidth="50rem" + hideOnOutsideClick + zIndex={300} + > + + +); export default BMHStatus; diff --git a/libs/ui-lib/lib/cim/components/Agent/BMHStatusInfo.tsx b/libs/ui-lib/lib/cim/components/Agent/BMHStatusInfo.tsx new file mode 100644 index 0000000000..e1ba9452b6 --- /dev/null +++ b/libs/ui-lib/lib/cim/components/Agent/BMHStatusInfo.tsx @@ -0,0 +1,94 @@ +import React from 'react'; +import { + Level, + LevelItem, + Alert, + AlertVariant, + Content, + ContentVariants, + Button, + ButtonVariant, +} from '@patternfly/react-core'; +import { CheckCircleIcon } from '@patternfly/react-icons/dist/js/icons/check-circle-icon'; +import { PendingIcon } from '@patternfly/react-icons/dist/js/icons/pending-icon'; +import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/js/icons/exclamation-triangle-icon'; +import { t_global_color_status_success_default as okColor } from '@patternfly/react-tokens/dist/js/t_global_color_status_success_default'; +import { t_global_icon_color_status_warning_default as warningColor } from '@patternfly/react-tokens/dist/js/t_global_icon_color_status_warning_default'; + +import { getHumanizedDateTime, useTranslation } from '../../../common'; +import { BareMetalHostK8sResource } from '../../types'; +import { getBMHStatus } from '../helpers'; +import { BMHEventsModal } from './BMHEventsModal'; + +export const BMHStatusInfo = ({ + bmhStatus, + bmh, +}: { + bmhStatus: ReturnType; + bmh: BareMetalHostK8sResource; +}) => { + const { t } = useTranslation(); + const [isModalOpen, setModalOpen] = React.useState(false); + + let alertVariant = AlertVariant.warning; + if (!!bmh.status?.errorMessage) { + alertVariant = AlertVariant.danger; + } else if ( + bmh.status?.provisioning?.state === 'provisioned' || + bmh.status?.provisioning?.state === 'externally provisioned' + ) { + alertVariant = AlertVariant.success; + } + + let icon: React.ReactNode; + switch (bmh.status?.operationalStatus) { + case 'OK': + case 'discovered': + icon = ; + break; + case 'error': + icon = ; + break; + default: + icon = ; + break; + } + + return ( + <> + + {t('ai:Bare metal host')} + + {bmh.status?.operationalStatus} {icon} + + + + {!!bmh.status?.errorMessage && ( + {bmh.status?.errorMessage} + )} + + + + + + + + {t('ai:Last updated:')} {getHumanizedDateTime(bmh.status?.lastUpdated)} + + + + + + {isModalOpen && ( + setModalOpen(false)} + namespace={bmh.metadata?.namespace || ''} + bmhName={bmh.metadata?.name || ''} + /> + )} + + ); +}; diff --git a/libs/ui-lib/lib/cim/components/Agent/index.ts b/libs/ui-lib/lib/cim/components/Agent/index.ts index e41274b24a..39de081e0e 100644 --- a/libs/ui-lib/lib/cim/components/Agent/index.ts +++ b/libs/ui-lib/lib/cim/components/Agent/index.ts @@ -1,3 +1,5 @@ export { default as AgentTable } from './AgentTable'; export { default as BMCForm } from './BMCForm'; export * from './types'; +export * from './tableColumns'; +export * from './tableUtils'; diff --git a/libs/ui-lib/lib/cim/components/Agent/tableColumns.tsx b/libs/ui-lib/lib/cim/components/Agent/tableColumns.tsx new file mode 100644 index 0000000000..863cbbd8d0 --- /dev/null +++ b/libs/ui-lib/lib/cim/components/Agent/tableColumns.tsx @@ -0,0 +1,257 @@ +import * as React from 'react'; +import { TFunction } from 'i18next'; +import { Link } from 'react-router-dom-v5-compat'; +import { Host } from '@openshift-assisted/types/assisted-installer-service'; +import { HostsTableActions, getInventory, getHostname, Hostname } from '../../../common'; +import { TableRow } from '../../../common/components/hosts/AITable'; +import { HostStatus } from '../../../common/components/hosts/types'; +import { AgentK8sResource, BareMetalHostK8sResource } from '../../types'; +import { AgentTableActions, ClusterDeploymentWizardStepsType } from '../ClusterDeployment'; +import { AGENT_BMH_NAME_LABEL_KEY } from '../common'; +import { + getBMHStatus, + getWizardStepAgentStatus, + getAgentStatus, + getInfraEnvNameOfAgent, +} from '../helpers'; +import { AgentMachineK8sResource } from '../Hypershift'; +import AgentStatus from './AgentStatus'; +import BMHStatus from './BMHStatus'; +import { BMHStatusInfo } from './BMHStatusInfo'; + +export const agentHostnameColumn = ( + t: TFunction, + hosts: Host[], + agents: AgentK8sResource[], + bareMetalHosts: BareMetalHostK8sResource[], + onEditHostname?: HostsTableActions['onEditHost'], + canEditHostname?: HostsTableActions['canEditHostname'], + canEditBMH?: HostsTableActions['canEditBMH'], +): TableRow => ({ + header: { + title: t('ai:Hostname'), + props: { + id: 'col-header-hostname', // ACM jest tests require id over testId + modifier: 'breakWord', + }, + sort: true, + }, + cell: (host) => { + const inventory = getInventory(host); + const editHostname = onEditHostname ? () => onEditHostname(host) : undefined; + const computedHostname = getHostname(host, inventory); + + let readonly = true; + + const agent = agents.find((a) => a.metadata?.uid === host.id); + if (agent) { + readonly = canEditHostname ? !canEditHostname(host) : false; + } else { + const bmh = bareMetalHosts.find((bmh) => bmh.metadata?.uid === host.id); + if (bmh && canEditBMH) { + readonly = !canEditBMH(host); + } + } + + return { + title: ( + + ), + props: { 'data-testid': 'host-name' }, + sortableValue: computedHostname || '', + }; + }, +}); + +export const discoveryTypeColumn = ( + agents: AgentK8sResource[], + bareMetalHosts: BareMetalHostK8sResource[], + t: TFunction, +): TableRow => { + return { + header: { + title: t('ai:Discovery type'), + props: { + id: 'col-header-discovery-type', + }, + sort: true, + }, + cell: (host) => { + const agent = agents.find((a) => a.metadata?.uid === host.id); + let discoveryType = t('ai:Unknown'); + if (agent) { + // eslint-disable-next-line no-prototype-builtins + discoveryType = agent?.metadata?.labels?.hasOwnProperty(AGENT_BMH_NAME_LABEL_KEY) + ? t('ai:BMC') + : t('ai:Discovery ISO'); + } else { + const bmh = bareMetalHosts.find((bmh) => bmh.metadata?.uid === host.id); + if (bmh) { + discoveryType = t('ai:BMC'); + } + } + return { + title: discoveryType, + props: { 'data-testid': 'discovery-type' }, + sortableValue: discoveryType, + }; + }, + }; +}; + +type AgentStatusColumnProps = { + agents: AgentK8sResource[]; + agentStatuses: HostStatus; + bareMetalHosts?: BareMetalHostK8sResource[]; + bmhStatuses?: HostStatus; + onEditHostname?: AgentTableActions['onEditHost']; + onApprove?: AgentTableActions['onApprove']; + wizardStepId?: ClusterDeploymentWizardStepsType; + t: TFunction; + isDay2?: boolean; +}; + +export const agentStatusColumn = ({ + agents, + agentStatuses, + bareMetalHosts, + bmhStatuses, + onEditHostname, + onApprove, + wizardStepId, + t, + isDay2, +}: AgentStatusColumnProps): TableRow => { + return { + header: { + title: t('ai:Status'), + props: { + id: 'col-header-infraenvstatus', + }, + sort: true, + }, + cell: (host) => { + const agent = agents.find((a) => a.metadata?.uid === host.id); + const bmh = bareMetalHosts?.find( + (b) => + b.metadata?.annotations?.['bmac.agent-install.openshift.io/hostname'] === + host.requestedHostname, + ); + + let bmhStatus; + let title: React.ReactNode = '--'; + if (agent) { + const editHostname = onEditHostname ? () => onEditHostname(agent) : undefined; + title = ( + + } + /> + ); + } else if (bmh && bmhStatuses) { + bmhStatus = getBMHStatus(bmh, bmhStatuses); + title = ; + } + + return { + title, + props: { 'data-testid': 'host-status' }, + sortableValue: agent + ? wizardStepId + ? getWizardStepAgentStatus(agent, wizardStepId, t).status.title + : getAgentStatus(agent, agentStatuses).status.title + : bmhStatus?.state.title || '', + }; + }, + }; +}; + +export const clusterColumn = ( + agents: AgentK8sResource[], + agentMachines: AgentMachineK8sResource[], + getClusterDeploymentLink: (cd: { name: string; namespace: string }) => string, + t: TFunction, +): TableRow => { + return { + header: { + title: t('ai:Cluster'), + props: { + id: 'col-header-cluster', + }, + sort: true, + }, + cell: (host) => { + const agent = agents.find((a) => a.metadata?.uid === host.id); + let cdLink: string | undefined = undefined; + if (agent?.spec?.clusterDeploymentName) { + //hypershift + const amNamespace = agent.metadata?.annotations?.['agentMachineRefNamespace']; + if (amNamespace) { + const am = agentMachines.find( + (am) => + am.status?.agentRef?.name === agent.metadata?.name && + am.status?.agentRef?.namespace === agent.metadata?.namespace, + ); + const nodePool = am?.metadata?.annotations?.['hypershift.openshift.io/nodePool']; + if (nodePool) { + const hcNamespace = nodePool.split('/')[0]; + const hcName = agent.spec.clusterDeploymentName.name; + cdLink = getClusterDeploymentLink({ name: hcName, namespace: hcNamespace }); + } + } else { + cdLink = getClusterDeploymentLink(agent.spec.clusterDeploymentName); + } + } + return { + title: cdLink ? {agent?.spec?.clusterDeploymentName?.name} : '--', + props: { 'data-testid': 'cluster' }, + sortableValue: agent?.spec?.clusterDeploymentName?.name ?? '--', + }; + }, + }; +}; + +export const infraEnvColumn = (agents: AgentK8sResource[], t: TFunction): TableRow => { + return { + header: { + title: t('ai:Infrastructure env'), + props: { + id: 'col-header-infraenv', + }, + sort: true, + }, + cell: (host) => { + const agent = agents.find((a) => a.metadata?.uid === host.id); + const infraEnvName = getInfraEnvNameOfAgent(agent); + const title = infraEnvName ? ( + + {infraEnvName} + + ) : ( + '--' + ); + return { + title, + props: { 'data-testid': 'infra-env' }, + sortableValue: infraEnvName, + }; + }, + }; +}; diff --git a/libs/ui-lib/lib/cim/components/Agent/tableUtils.tsx b/libs/ui-lib/lib/cim/components/Agent/tableUtils.tsx index cf78a02a73..07a7cfa301 100644 --- a/libs/ui-lib/lib/cim/components/Agent/tableUtils.tsx +++ b/libs/ui-lib/lib/cim/components/Agent/tableUtils.tsx @@ -1,268 +1,29 @@ import * as React from 'react'; -import { Link } from 'react-router-dom-v5-compat'; import countBy from 'lodash-es/countBy'; -import { - HostsTableActions, - getInventory, - getHostname, - Hostname, - ActionCheck, -} from '../../../common'; +import { HostsTableActions, ActionCheck } from '../../../common'; import { AgentClusterInstallK8sResource, AgentK8sResource, InfraEnvK8sResource } from '../../types'; -import AgentStatus from './AgentStatus'; -import { ActionsResolver, TableRow } from '../../../common/components/hosts/AITable'; -import { AgentTableActions, ClusterDeploymentWizardStepsType } from '../ClusterDeployment/types'; +import { ActionsResolver } from '../../../common/components/hosts/AITable'; +import { AgentTableActions } from '../ClusterDeployment/types'; import { hostActionResolver } from '../../../common/components/hosts/tableUtils'; -import { getAgentClusterInstallOfAgent, getAIHosts, getInfraEnvNameOfAgent } from '../helpers'; +import { getAgentClusterInstallOfAgent, getAIHosts } from '../helpers'; import { isInstallationInProgress } from '../ClusterDeployment/helpers'; import { AGENT_BMH_NAME_LABEL_KEY, INFRAENV_GENERATED_AI_FLOW } from '../common'; import { BareMetalHostK8sResource } from '../../types/k8s/bare-metal-host'; -import BMHStatus from './BMHStatus'; import { getAgentStatus, getAgentStatusKey, getBMHStatus, getBMHStatusKey, - getWizardStepAgentStatus, } from '../helpers/status'; import { filterByHostname, getHostLabels } from '../../../common/components/hosts/utils'; import { agentStatus } from '../helpers/agentStatus'; import noop from 'lodash-es/noop.js'; import { useTranslation } from '../../../common/hooks/use-translation-wrapper'; import { TFunction } from 'i18next'; -import { AgentMachineK8sResource } from '../Hypershift/types'; import { Host } from '@openshift-assisted/types/assisted-installer-service'; import { HostStatus } from '../../../common/components/hosts/types'; -export const agentHostnameColumn = ( - t: TFunction, - hosts: Host[], - agents: AgentK8sResource[], - bareMetalHosts: BareMetalHostK8sResource[], - onEditHostname?: HostsTableActions['onEditHost'], - canEditHostname?: HostsTableActions['canEditHostname'], - canEditBMH?: HostsTableActions['canEditBMH'], -): TableRow => ({ - header: { - title: t('ai:Hostname'), - props: { - id: 'col-header-hostname', // ACM jest tests require id over testId - modifier: 'breakWord', - }, - sort: true, - }, - cell: (host) => { - const inventory = getInventory(host); - const editHostname = onEditHostname ? () => onEditHostname(host) : undefined; - const computedHostname = getHostname(host, inventory); - - let readonly = true; - - const agent = agents.find((a) => a.metadata?.uid === host.id); - if (agent) { - readonly = canEditHostname ? !canEditHostname(host) : false; - } else { - const bmh = bareMetalHosts.find((bmh) => bmh.metadata?.uid === host.id); - if (bmh && canEditBMH) { - readonly = !canEditBMH(host); - } - } - - return { - title: ( - - ), - props: { 'data-testid': 'host-name' }, - sortableValue: computedHostname || '', - }; - }, -}); - -export const discoveryTypeColumn = ( - agents: AgentK8sResource[], - bareMetalHosts: BareMetalHostK8sResource[], - t: TFunction, -): TableRow => { - return { - header: { - title: t('ai:Discovery type'), - props: { - id: 'col-header-discovery-type', - }, - sort: true, - }, - cell: (host) => { - const agent = agents.find((a) => a.metadata?.uid === host.id); - let discoveryType = t('ai:Unknown'); - if (agent) { - // eslint-disable-next-line no-prototype-builtins - discoveryType = agent?.metadata?.labels?.hasOwnProperty(AGENT_BMH_NAME_LABEL_KEY) - ? t('ai:BMC') - : t('ai:Discovery ISO'); - } else { - const bmh = bareMetalHosts.find((bmh) => bmh.metadata?.uid === host.id); - if (bmh) { - discoveryType = t('ai:BMC'); - } - } - return { - title: discoveryType, - props: { 'data-testid': 'discovery-type' }, - sortableValue: discoveryType, - }; - }, - }; -}; - -type AgentStatusColumnProps = { - agents: AgentK8sResource[]; - agentStatuses: HostStatus; - bareMetalHosts?: BareMetalHostK8sResource[]; - bmhStatuses?: HostStatus; - onEditHostname?: AgentTableActions['onEditHost']; - onApprove?: AgentTableActions['onApprove']; - wizardStepId?: ClusterDeploymentWizardStepsType; - t: TFunction; - isDay2?: boolean; -}; - -export const agentStatusColumn = ({ - agents, - agentStatuses, - bareMetalHosts, - bmhStatuses, - onEditHostname, - onApprove, - wizardStepId, - t, - isDay2, -}: AgentStatusColumnProps): TableRow => { - return { - header: { - title: t('ai:Status'), - props: { - id: 'col-header-infraenvstatus', - }, - sort: true, - }, - cell: (host) => { - const agent = agents.find((a) => a.metadata?.uid === host.id); - const bmh = bareMetalHosts?.find((b) => b.metadata?.uid === host.id); - let bmhStatus; - let title: React.ReactNode = '--'; - if (agent) { - const editHostname = onEditHostname ? () => onEditHostname(agent) : undefined; - title = ( - - ); - } else if (bmh && bmhStatuses) { - bmhStatus = getBMHStatus(bmh, bmhStatuses); - title = ; - } - - return { - title, - props: { 'data-testid': 'host-status' }, - sortableValue: agent - ? wizardStepId - ? getWizardStepAgentStatus(agent, wizardStepId, t).status.title - : getAgentStatus(agent, agentStatuses).status.title - : bmhStatus?.state.title || '', - }; - }, - }; -}; - -export const clusterColumn = ( - agents: AgentK8sResource[], - agentMachines: AgentMachineK8sResource[], - getClusterDeploymentLink: (cd: { name: string; namespace: string }) => string, - t: TFunction, -): TableRow => { - return { - header: { - title: t('ai:Cluster'), - props: { - id: 'col-header-cluster', - }, - sort: true, - }, - cell: (host) => { - const agent = agents.find((a) => a.metadata?.uid === host.id); - let cdLink: string | undefined = undefined; - if (agent?.spec?.clusterDeploymentName) { - //hypershift - const amNamespace = agent.metadata?.annotations?.['agentMachineRefNamespace']; - if (amNamespace) { - const am = agentMachines.find( - (am) => - am.status?.agentRef?.name === agent.metadata?.name && - am.status?.agentRef?.namespace === agent.metadata?.namespace, - ); - const nodePool = am?.metadata?.annotations?.['hypershift.openshift.io/nodePool']; - if (nodePool) { - const hcNamespace = nodePool.split('/')[0]; - const hcName = agent.spec.clusterDeploymentName.name; - cdLink = getClusterDeploymentLink({ name: hcName, namespace: hcNamespace }); - } - } else { - cdLink = getClusterDeploymentLink(agent.spec.clusterDeploymentName); - } - } - return { - title: cdLink ? {agent?.spec?.clusterDeploymentName?.name} : '--', - props: { 'data-testid': 'cluster' }, - sortableValue: agent?.spec?.clusterDeploymentName?.name ?? '--', - }; - }, - }; -}; - -export const infraEnvColumn = (agents: AgentK8sResource[], t: TFunction): TableRow => { - return { - header: { - title: t('ai:Infrastructure env'), - props: { - id: 'col-header-infraenv', - }, - sort: true, - }, - cell: (host) => { - const agent = agents.find((a) => a.metadata?.uid === host.id) as AgentK8sResource; - const infraEnvName = getInfraEnvNameOfAgent(agent); - const title = infraEnvName ? ( - - {infraEnvName} - - ) : ( - 'N/A' - ); - return { - title, - props: { 'data-testid': 'infra-env' }, - sortableValue: infraEnvName, - }; - }, - }; -}; - export const canEditBMH = (bmh: BareMetalHostK8sResource, t: TFunction): ActionCheck => { const canEdit = ['deprovisioning', 'pending', 'registering'].includes(getBMHStatusKey(bmh) || ''); diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentDetailsTable.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentDetailsTable.tsx index 39f51ced09..b50708e2e0 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentDetailsTable.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/ClusterDeploymentDetailsTable.tsx @@ -2,14 +2,14 @@ import * as React from 'react'; import { AgentClusterInstallK8sResource, AgentK8sResource } from '../../types'; import DefaultEmptyState from '../../../common/components/ui/uiState/EmptyState'; import { ConnectedIcon } from '@patternfly/react-icons/dist/js/icons/connected-icon'; -import { infraEnvColumn, agentStatusColumn, useAgentsTable } from '../Agent/tableUtils'; +import { useAgentsTable, infraEnvColumn, agentStatusColumn } from '../Agent'; import { cpuCoresColumn, disksColumn, hostnameColumn, memoryColumn, roleColumn, -} from '../../../common/components/hosts/tableUtils'; +} from '../../../common'; import HostsTable, { DefaultExpandComponent } from '../../../common/components/hosts/HostsTable'; import { usePagination } from '../../../common/components/hosts/usePagination'; import { useTranslation } from '../../../common/hooks/use-translation-wrapper'; diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/LocationsSelector.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/LocationsSelector.tsx index e87ae6c3d0..67ffd14b3c 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/LocationsSelector.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/LocationsSelector.tsx @@ -19,14 +19,14 @@ const LocationsLabelIcon: React.FC = () => {
}} + components={{ bold: }} i18nKey="ai:Keep the field empty to match any location." />
}} + components={{ bold: }} i18nKey="ai:Set {{agent_location_label_key}} label in Agent resource to specify its location." values={{ agent_location_label_key: AGENT_LOCATION_LABEL_KEY }} /> diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/clusterDetails/ClusterDetailsFormFields.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/clusterDetails/ClusterDetailsFormFields.tsx index 50cb89d843..8f275f07f8 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/clusterDetails/ClusterDetailsFormFields.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/clusterDetails/ClusterDetailsFormFields.tsx @@ -44,9 +44,9 @@ export const BaseDnsHelperText: React.FC<{ name?: string; baseDnsDomain?: string 'ai:All DNS records must be subdomains of this base and include the cluster name. This cannot be changed after cluster installation. The full cluster address will be:', )}{' '}
- + {name || '[Cluster Name]'}.{baseDnsDomain || '[example.com]'} - +
); }; diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/hostDiscovery/ClusterDeploymentHostDiscoveryTable.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/hostDiscovery/ClusterDeploymentHostDiscoveryTable.tsx index ab396d449d..b7cd04c9b2 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/hostDiscovery/ClusterDeploymentHostDiscoveryTable.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/hostDiscovery/ClusterDeploymentHostDiscoveryTable.tsx @@ -2,11 +2,11 @@ import * as React from 'react'; import { Stack, StackItem } from '@patternfly/react-core'; import { Host } from '@openshift-assisted/types/assisted-installer-service'; import { - discoveryTypeColumn, - agentStatusColumn, useAgentsTable, canChangeHostname, -} from '../../Agent/tableUtils'; + discoveryTypeColumn, + agentStatusColumn, +} from '../../Agent'; import HostsTable, { DefaultExpandComponent, HostsTableEmptyState, @@ -20,8 +20,6 @@ import { labelsColumn, memoryColumn, roleColumn, -} from '../../../../common/components/hosts/tableUtils'; -import { DiscoveryTroubleshootingModal, ChangeHostnameAction, MassChangeHostnameModal, diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/networking/ClusterDeploymentHostsNetworkTable.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/networking/ClusterDeploymentHostsNetworkTable.tsx index 1dca72d91d..f6863d55c5 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/networking/ClusterDeploymentHostsNetworkTable.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/networking/ClusterDeploymentHostsNetworkTable.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { ConnectedIcon } from '@patternfly/react-icons/dist/js/icons/connected-icon'; -import { EmptyState } from '../../../../common'; +import { EmptyState, activeNICColumn, hostnameColumn, roleColumn } from '../../../../common'; import { AgentTableActions } from '../types'; import { AgentClusterInstallK8sResource, @@ -9,14 +9,9 @@ import { } from '../../../types'; import { getAICluster, getIsSNOCluster } from '../../helpers'; import { AdditionalNTPSourcesDialogToggle } from '../components/AdditionalNTPSourcesDialogToggle'; -import { agentStatusColumn, useAgentsTable } from '../../Agent/tableUtils'; +import { agentStatusColumn, useAgentsTable } from '../../Agent'; import HostsTable from '../../../../common/components/hosts/HostsTable'; import { HostDetail } from '../../../../common/components/hosts/HostRowDetail'; -import { - activeNICColumn, - hostnameColumn, - roleColumn, -} from '../../../../common/components/hosts/tableUtils'; import { usePagination } from '../../../../common/components/hosts/usePagination'; import { useTranslation } from '../../../../common/hooks/use-translation-wrapper'; import { Host } from '@openshift-assisted/types/assisted-installer-service'; diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/networking/ClusterDeploymentNetworkingForm.tsx b/libs/ui-lib/lib/cim/components/ClusterDeployment/networking/ClusterDeploymentNetworkingForm.tsx index e31547a29e..601321395b 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/networking/ClusterDeploymentNetworkingForm.tsx +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/networking/ClusterDeploymentNetworkingForm.tsx @@ -99,7 +99,7 @@ const ClusterDeploymentNetworkingForm: React.FC }} + components={{ bold: }} i18nKey="ai:The hosts you selected are using different proxy settings. Configure a proxy that will be applied for these hosts. Configure at least one of the proxy settings below." /> diff --git a/libs/ui-lib/lib/cim/components/InfraEnv/InfraEnvAgentTable.tsx b/libs/ui-lib/lib/cim/components/InfraEnv/InfraEnvAgentTable.tsx index 1ae51fac1c..99041430c2 100644 --- a/libs/ui-lib/lib/cim/components/InfraEnv/InfraEnvAgentTable.tsx +++ b/libs/ui-lib/lib/cim/components/InfraEnv/InfraEnvAgentTable.tsx @@ -4,16 +4,18 @@ import { DropdownItem } from '@patternfly/react-core'; import { Host } from '@openshift-assisted/types/assisted-installer-service'; import { - discoveryTypeColumn, - agentStatusColumn, - clusterColumn, useAgentsTable, useAgentsFilter, - agentHostnameColumn, canEditBMH, canEditAgent, canChangeHostname, } from '../Agent/tableUtils'; +import { + discoveryTypeColumn, + agentStatusColumn, + clusterColumn, + agentHostnameColumn, +} from '../Agent/tableColumns'; import HostsTable, { DefaultExpandComponent, HostsTableEmptyState, @@ -25,8 +27,6 @@ import { gpusColumn, memoryColumn, labelsColumn, -} from '../../../common/components/hosts/tableUtils'; -import { DiscoveryTroubleshootingModal, MassChangeHostnameModal, DeleteHostAction, diff --git a/libs/ui-lib/lib/cim/components/common/ResourceAlerts.tsx b/libs/ui-lib/lib/cim/components/common/ResourceAlerts.tsx index 3263d3b8e3..4002da1a55 100644 --- a/libs/ui-lib/lib/cim/components/common/ResourceAlerts.tsx +++ b/libs/ui-lib/lib/cim/components/common/ResourceAlerts.tsx @@ -25,8 +25,8 @@ export const SingleResourceAlerts: React.FC<{ const [title, ...messages] = c.message.split(/:|\.,/); return ( - - {title} + + {title} {messages.map((m) => ( @@ -38,8 +38,8 @@ export const SingleResourceAlerts: React.FC<{ } else { return ( - - {c.type} + + {c.type} {c.reason || ''} diff --git a/libs/ui-lib/lib/cim/components/helpers/agentStatus.tsx b/libs/ui-lib/lib/cim/components/helpers/agentStatus.tsx index 648f2dc70d..808d203366 100644 --- a/libs/ui-lib/lib/cim/components/helpers/agentStatus.tsx +++ b/libs/ui-lib/lib/cim/components/helpers/agentStatus.tsx @@ -22,6 +22,11 @@ export const bmhStatus = (t: TFunction): HostStatus => ({ title: t('ai:Provisioning'), category: 'Bare Metal Host related', }, + deleting: { + key: 'deleting', + title: t('ai:Deleting'), + category: 'Bare Metal Host related', + }, provisioned: { key: 'provisioned', title: t('ai:Provisioned'), diff --git a/libs/ui-lib/lib/cim/components/modals/MassDeleteAgentModal.tsx b/libs/ui-lib/lib/cim/components/modals/MassDeleteAgentModal.tsx index 925f71d5f4..1f85c41e1c 100644 --- a/libs/ui-lib/lib/cim/components/modals/MassDeleteAgentModal.tsx +++ b/libs/ui-lib/lib/cim/components/modals/MassDeleteAgentModal.tsx @@ -110,7 +110,7 @@ const statusColumn = ( ); } else if (bmh) { bmhStatus = getBMHStatus(bmh, bmhStatuses); - title = ; + title = ; } return { diff --git a/libs/ui-lib/lib/cim/hooks/index.tsx b/libs/ui-lib/lib/cim/hooks/index.tsx index 843a385735..ef01807faf 100644 --- a/libs/ui-lib/lib/cim/hooks/index.tsx +++ b/libs/ui-lib/lib/cim/hooks/index.tsx @@ -1,2 +1,3 @@ export * from './useConfigMaps'; export * from './useAgentServiceConfig'; +export * from './useEvents'; diff --git a/libs/ui-lib/lib/cim/hooks/types.tsx b/libs/ui-lib/lib/cim/hooks/types.tsx index 4eee462098..3c8251f0ed 100644 --- a/libs/ui-lib/lib/cim/hooks/types.tsx +++ b/libs/ui-lib/lib/cim/hooks/types.tsx @@ -5,4 +5,7 @@ export type K8sWatchHookListProps = Omit< 'groupVersionKind' | 'name' | 'isList' > | null; -export type K8sWatchHookProps = Pick | null; +export type K8sWatchHookProps = Pick< + WatchK8sResource, + 'name' | 'namespace' | 'isList' | 'fieldSelector' +> | null; diff --git a/libs/ui-lib/lib/cim/hooks/useEvents.tsx b/libs/ui-lib/lib/cim/hooks/useEvents.tsx new file mode 100644 index 0000000000..2c501f5d5b --- /dev/null +++ b/libs/ui-lib/lib/cim/hooks/useEvents.tsx @@ -0,0 +1,14 @@ +import { useK8sWatchResource } from './useK8sWatchResource'; +import { EventK8sResource } from '../types'; +import { K8sWatchHookProps } from './types'; + +export const useEvents = (props: K8sWatchHookProps) => + useK8sWatchResource( + { + groupVersionKind: { + kind: 'Event', + version: 'v1', + }, + }, + props, + ); diff --git a/libs/ui-lib/lib/cim/types/fromOCP/k8sTypes.ts b/libs/ui-lib/lib/cim/types/fromOCP/k8sTypes.ts index 213c794063..62bf3701e2 100644 --- a/libs/ui-lib/lib/cim/types/fromOCP/k8sTypes.ts +++ b/libs/ui-lib/lib/cim/types/fromOCP/k8sTypes.ts @@ -43,3 +43,16 @@ export type RouteK8sResource = { wildcardPolicy?: string; }; } & K8sResourceCommon; + +export type EventK8sResource = { + type?: string; + message?: string; + eventTime?: string; + involvedObject?: { + apiVersion?: string; + kind?: string; + name?: string; + namespace?: string; + uid?: string; + }; +} & K8sResourceCommon; diff --git a/libs/ui-lib/lib/cim/types/k8s/bare-metal-host.ts b/libs/ui-lib/lib/cim/types/k8s/bare-metal-host.ts index 613de53e88..72b9666c03 100644 --- a/libs/ui-lib/lib/cim/types/k8s/bare-metal-host.ts +++ b/libs/ui-lib/lib/cim/types/k8s/bare-metal-host.ts @@ -28,5 +28,25 @@ export type BareMetalHostK8sResource = K8sResourceCommon & { }; errorMessage?: string; errorType?: string; + operationalStatus?: 'OK' | 'discovered' | 'error' | 'delayed' | 'detached'; + operationHistory?: { + register?: { + start?: string; + end?: string; + }; + provision?: { + start?: string; + end?: string; + }; + inspect?: { + start?: string; + end?: string; + }; + deprovision?: { + start?: string; + end?: string; + }; + }; + lastUpdated?: string; }; }; diff --git a/libs/ui-lib/lib/common/components/clusterConfiguration/DiscoveryInstructions.tsx b/libs/ui-lib/lib/common/components/clusterConfiguration/DiscoveryInstructions.tsx index f5ef985e68..d67f3adca8 100644 --- a/libs/ui-lib/lib/common/components/clusterConfiguration/DiscoveryInstructions.tsx +++ b/libs/ui-lib/lib/common/components/clusterConfiguration/DiscoveryInstructions.tsx @@ -32,7 +32,7 @@ const DiscoveryInstructions = ({ }} + components={{ bold: }} i18nKey="ai:Keep the Discovery ISO media connected to the device throughout the installation process and set each host to boot only one time from this device. " /> diff --git a/libs/ui-lib/lib/common/components/clusterConfiguration/ProxyFields.tsx b/libs/ui-lib/lib/common/components/clusterConfiguration/ProxyFields.tsx index f106ff97b1..615023710f 100644 --- a/libs/ui-lib/lib/common/components/clusterConfiguration/ProxyFields.tsx +++ b/libs/ui-lib/lib/common/components/clusterConfiguration/ProxyFields.tsx @@ -35,7 +35,7 @@ export const ProxyInputFields = () => {
}} + components={{ bold: }} i18nKey="ai:URL must start with http." />
@@ -58,7 +58,7 @@ export const ProxyInputFields = () => {
}} + components={{ bold: }} i18nKey="ai:URL must start with http (https schemes are not currently supported)." />
@@ -81,7 +81,7 @@ export const ProxyInputFields = () => {
}} + components={{ bold: }} i18nKey="ai:Use a comma to separate each listed domain. Preface a domain with . to include its subdomains. Use * to bypass the proxy for all destinations." />
diff --git a/libs/ui-lib/lib/common/components/hosts/HostStatus.tsx b/libs/ui-lib/lib/common/components/hosts/HostStatus.tsx index 1ae79b4eaa..ca73e85dca 100644 --- a/libs/ui-lib/lib/common/components/hosts/HostStatus.tsx +++ b/libs/ui-lib/lib/common/components/hosts/HostStatus.tsx @@ -1,6 +1,9 @@ import React from 'react'; import { Popover, Button, Content, FlexItem, Flex, Stack, StackItem } from '@patternfly/react-core'; import { PopoverProps } from '@patternfly/react-core/dist/js/components/Popover/Popover'; +import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/js/icons/exclamation-triangle-icon'; +import { UnknownIcon } from '@patternfly/react-icons/dist/js/icons/unknown-icon'; + import hdate from 'human-date'; import { Host, HostProgressInfo } from '@openshift-assisted/types/assisted-installer-service'; @@ -18,11 +21,9 @@ import OcpConsoleNodesSectionLink from './OcpConsoleNodesSectionLink'; import { toSentence } from '../ui/table/utils'; import { HostStatusProps } from './types'; import { UpdateDay2ApiVipPropsType } from './HostValidationGroups'; -import { UnknownIcon } from '@patternfly/react-icons/dist/js/icons/unknown-icon'; import { useTranslation } from '../../hooks/use-translation-wrapper'; import { hostStatus } from './status'; import { getApproveNodesInClLink } from '../../config'; -import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/js/icons/exclamation-triangle-icon'; const getTitleWithProgress = (host: Host, status: HostStatusProps['status']) => { const stages = getHostProgressStages(host); @@ -35,6 +36,7 @@ type HostStatusPopoverContentProps = ValidationInfoActionProps & { validationsInfo: ValidationsInfo; autoCSR?: boolean; additionalPopoverContent?: React.ReactNode; + additionalBMHInfo?: React.ReactNode; openshiftVersion?: string; }; @@ -42,6 +44,7 @@ const HostStatusPopoverContent: React.FC = ({ details, autoCSR, additionalPopoverContent, + additionalBMHInfo, openshiftVersion, ...props }) => { @@ -132,6 +135,7 @@ const HostStatusPopoverContent: React.FC = ({ )} + {additionalBMHInfo} ); @@ -180,6 +184,7 @@ type WithHostStatusPopoverProps = AdditionNtpSourcePropsType & zIndex?: number; autoCSR?: boolean; additionalPopoverContent?: React.ReactNode; + additionalBMHInfo?: React.ReactNode; openshiftVersion?: string; }; @@ -239,6 +244,7 @@ const HostStatus: React.FC> = ({ zIndex, autoCSR, additionalPopoverContent, + additionalBMHInfo, openshiftVersion, }) => { const [keepOnOutsideClick, onValidationActionToggle] = React.useState(false); @@ -264,12 +270,17 @@ const HostStatus: React.FC> = ({ zIndex, autoCSR, additionalPopoverContent, + additionalBMHInfo, openshiftVersion, }; const hostIcon = getHostStatusIcon(icon, host.progress, status); return ( - + { {(autoCSR && status.key === 'added-to-existing-cluster' diff --git a/libs/ui-lib/lib/common/components/hosts/HostValidationGroups.tsx b/libs/ui-lib/lib/common/components/hosts/HostValidationGroups.tsx index 9b0fa09c4e..b521819f16 100644 --- a/libs/ui-lib/lib/common/components/hosts/HostValidationGroups.tsx +++ b/libs/ui-lib/lib/common/components/hosts/HostValidationGroups.tsx @@ -67,7 +67,10 @@ const ValidationsAlert = ({
    {validations.map((v) => (
  • - {hostValidationLabels(t)[v.id] || v.id}:  + + {hostValidationLabels(t)[v.id] || v.id}: + +   {toSentence(v.message.replace(/\\n/, ' '))}{' '} {v.status === 'failure' && hostValidationFailureHints(t)[v.id]}
  • @@ -310,9 +313,7 @@ export const HostValidationGroups = ({ return ( - - {groupLabel} - + {groupLabel} {getValidationGroupState()} { diff --git a/libs/ui-lib/lib/common/components/hosts/MassChangeHostnameModal.tsx b/libs/ui-lib/lib/common/components/hosts/MassChangeHostnameModal.tsx index 0e316d2baa..3ea8f31038 100644 --- a/libs/ui-lib/lib/common/components/hosts/MassChangeHostnameModal.tsx +++ b/libs/ui-lib/lib/common/components/hosts/MassChangeHostnameModal.tsx @@ -186,7 +186,7 @@ const MassChangeHostnameForm = ({
    {t('ai:Rename hostnames using the custom template:')}
    - {`{{n}}`} {t('ai:to add a number.')} + {`{{n}}`} {t('ai:to add a number.')}
    @@ -212,16 +212,19 @@ const MassChangeHostnameForm = ({ {newHostnames.map((host, i) => ( -
    - {host.oldHostname} -
    + + {host.oldHostname} + ))}
    {newHostnames.map((_, i) => ( -
    - {'>'} -
    + + {'>'} + ))}
    diff --git a/libs/ui-lib/lib/common/components/hosts/index.ts b/libs/ui-lib/lib/common/components/hosts/index.ts index 22570a0ad0..9cd78b02e6 100644 --- a/libs/ui-lib/lib/common/components/hosts/index.ts +++ b/libs/ui-lib/lib/common/components/hosts/index.ts @@ -1,6 +1,8 @@ export * from './utils'; export * from './types'; export * from './HostValidationGroups'; +export * from './tableColumns'; +export * from './tableUtils'; export { default as RoleCell } from './RoleCell'; export { default as RoleDropdown } from './RoleDropdown'; diff --git a/libs/ui-lib/lib/common/components/hosts/tableColumns.tsx b/libs/ui-lib/lib/common/components/hosts/tableColumns.tsx new file mode 100644 index 0000000000..181adb5919 --- /dev/null +++ b/libs/ui-lib/lib/common/components/hosts/tableColumns.tsx @@ -0,0 +1,411 @@ +import React from 'react'; +import { TFunction } from 'i18next'; +import { Address4, Address6 } from 'ip-address'; +import { + Cluster, + Host, + HostUpdateParams, + Interface, +} from '@openshift-assisted/types/assisted-installer-service'; +import type { ValidationsInfo as HostValidationsInfo } from '../../types/hosts'; +import { getSubnet } from '../clusterConfiguration'; +import { LabelGroup, Label } from '@patternfly/react-core'; +import { selectMachineNetworkCIDR } from '../../selectors'; +import { stringToJSON } from '../../utils'; +import { DASH } from '../constants'; +import { getDateTimeCell } from '../ui'; +import { TableRow } from './AITable'; +import { getHostRowHardwareInfo } from './hardwareInfo'; +import Hostname from './Hostname'; +import HostPropertyValidationPopover from './HostPropertyValidationPopover'; +import HostsCount from './HostsCount'; +import HostStatus from './HostStatus'; +import RoleCell from './RoleCell'; +import { hostStatus } from './status'; +import { HostsTableActions } from './types'; +import { + getInventory, + getHostname, + getHostRole, + areOnlySoftValidationsFailing, + getHostStatus, + getHostLabels, +} from './utils'; + +const getSelectedNic = (nics: Interface[], currentSubnet: Address4 | Address6) => { + return nics.find((nic) => { + const ipv4Addresses = (nic.ipv4Addresses || []).reduce((addresses, address) => { + if (Address4.isValid(address)) { + addresses.push(new Address4(address)); + } + return addresses; + }, []); + + if (ipv4Addresses.find((address) => address.isInSubnet(currentSubnet))) { + return true; + } + + const ipv6Addresses = (nic.ipv6Addresses || []).reduce((addresses, address) => { + if (Address6.isValid(address)) { + addresses.push(new Address6(address)); + } + return addresses; + }, []); + + return ipv6Addresses.find((address) => address.isInSubnet(currentSubnet)); + }); +}; + +export const hostnameColumn = ( + t: TFunction, + onEditHostname?: HostsTableActions['onEditHost'], + hosts?: Host[], + canEditHostname?: HostsTableActions['canEditHostname'], +): TableRow => { + return { + header: { + title: t('ai:Hostname'), + props: { + id: 'col-header-hostname', // ACM jest tests require id over testId + }, + sort: true, + }, + cell: (host) => { + const inventory = getInventory(host); + const editHostname = onEditHostname ? () => onEditHostname(host) : undefined; + const computedHostname = getHostname(host, inventory); + return { + title: ( + + ), + props: { 'data-testid': 'host-name' }, + sortableValue: computedHostname || '', + }; + }, + }; +}; + +export const roleColumn = ( + t: TFunction, + canEditRole?: HostsTableActions['canEditRole'], + onEditRole?: HostsTableActions['onEditRole'], + schedulableMasters?: boolean, + clusterKind?: Cluster['kind'], +): TableRow => { + return { + header: { + title: t('ai:Role'), + props: { + id: 'col-header-role', + }, + sort: true, + }, + cell: (host) => { + const editRole = onEditRole + ? (role: HostUpdateParams['hostRole']) => onEditRole(host, role) + : undefined; + const isRoleEditable = canEditRole?.(host); + const hostRole = getHostRole(host, t, schedulableMasters, clusterKind); + return { + title: ( + + ), + props: { 'data-testid': 'host-role' }, + sortableValue: hostRole, + }; + }, + }; +}; + +export const statusColumn = ( + t: TFunction, + clusterStatus: Cluster['status'], + AdditionalNTPSourcesDialogToggleComponent?: React.FC, + onEditHostname?: HostsTableActions['onEditHost'], + UpdateDay2ApiVipDialogToggleComponent?: React.FC, +): TableRow => { + return { + header: { + title: t('ai:Status'), + props: { + id: 'col-header-status', + }, + sort: true, + }, + cell: (host) => { + const validationsInfo = stringToJSON(host.validationsInfo) || {}; + const editHostname = onEditHostname ? () => onEditHostname(host) : undefined; + const sublabel = + areOnlySoftValidationsFailing(validationsInfo) && + ['known', 'known-unbound'].includes(host.status) + ? t('ai:Some validations failed') + : undefined; + + const actualHostStatus = getHostStatus(host.status, clusterStatus); + + return { + title: ( + + ), + props: { 'data-testid': 'host-status' }, + sortableValue: host.status, + }; + }, + }; +}; + +export const discoveredAtColumn = (t: TFunction): TableRow => ({ + header: { + title: t('ai:Discovered on'), + props: { + id: 'col-header-discoveredat', + }, + sort: true, + }, + cell: (host) => { + const { createdAt } = host; + const dateTimeCell = getDateTimeCell(createdAt); + return { + title: dateTimeCell.title, + props: { 'data-testid': 'host-discovered-time' }, + sortableValue: dateTimeCell.sortableValue, + }; + }, +}); + +export const cpuArchitectureColumn = (t: TFunction): TableRow => ({ + header: { + title: t('ai:CPU Architecture'), + props: { + id: 'col-header-cpuarchitecture', + }, + sort: true, + }, + cell: (host) => { + const inventory = getInventory(host); + return { + title: inventory.cpu?.architecture, + props: { 'data-testid': 'host-cpu-architecture' }, + sortableValue: inventory.cpu?.architecture, + }; + }, +}); + +export const cpuCoresColumn = (t: TFunction): TableRow => ({ + header: { + title: t('ai:CPU Cores'), + props: { + id: 'col-header-cpucores', + }, + sort: true, + }, + cell: (host) => { + const inventory = getInventory(host); + const { cores } = getHostRowHardwareInfo(inventory); + const validationsInfo = stringToJSON(host.validationsInfo) || {}; + const cpuCoresValidation = validationsInfo?.hardware?.find( + (v) => v.id === 'has-cpu-cores-for-role', + ); + return { + title: ( + + {cores.title} + + ), + props: { 'data-testid': 'host-cpu-cores' }, + sortableValue: cores.sortableValue, + }; + }, +}); + +export const memoryColumn = (t: TFunction): TableRow => ({ + header: { + title: t('ai:Memory'), + props: { + id: 'col-header-memory', + }, + sort: true, + }, + cell: (host) => { + const inventory = getInventory(host); + const { memory } = getHostRowHardwareInfo(inventory); + const validationsInfo = stringToJSON(host.validationsInfo) || {}; + const memoryValidation = validationsInfo?.hardware?.find((v) => v.id === 'has-memory-for-role'); + return { + title: ( + + {memory.title} + + ), + props: { 'data-testid': 'host-memory' }, + sortableValue: memory.sortableValue, + }; + }, +}); + +export const disksColumn = (t: TFunction): TableRow => ({ + header: { + title: t('ai:Total storage'), + props: { + id: 'col-header-disk', + }, + sort: true, + }, + cell: (host) => { + const inventory = getInventory(host); + const { disk } = getHostRowHardwareInfo(inventory); + const validationsInfo = stringToJSON(host.validationsInfo) || {}; + const diskValidation = validationsInfo?.hardware?.find((v) => v.id === 'has-min-valid-disks'); + return { + title: ( + + {disk.title} + + ), + props: { 'data-testid': 'host-disks' }, + sortableValue: disk.sortableValue, + }; + }, +}); + +export const gpusColumn = (t: TFunction): TableRow => ({ + header: { + title: t('ai:GPUs'), + props: { + id: 'col-header-gpus', + }, + sort: true, + }, + cell: (host) => { + const { gpus } = getInventory(host); + return { + title: <>{gpus?.length ?? '--'}, + props: { 'data-testid': 'host-gpus' }, + sortableValue: gpus?.length ?? 0, + }; + }, +}); + +export const countColumn = (cluster: Cluster): TableRow => ({ + header: { title: }, +}); + +export const activeNICColumn = (cluster: Cluster, t: TFunction): TableRow => ({ + header: { + title: t('ai:Active NIC'), + sort: true, + }, + cell: (host) => { + const inventory = getInventory(host); + const nics = inventory.interfaces || []; + const machineNetworkCidr = selectMachineNetworkCIDR(cluster); + const currentSubnet = machineNetworkCidr ? getSubnet(machineNetworkCidr) : null; + const selectedNic = currentSubnet ? getSelectedNic(nics, currentSubnet) : null; + return { + title: selectedNic?.name || DASH, + props: { 'data-testid': 'nic-name' }, + sortableValue: selectedNic?.name || DASH, + }; + }, +}); + +export const ipv4Column = (cluster: Cluster): TableRow => ({ + header: { + title: 'IPv4 address', + sort: true, + }, + cell: (host) => { + const inventory = getInventory(host); + const nics = inventory.interfaces || []; + + const machineNetworkCidr = selectMachineNetworkCIDR(cluster); + const currentSubnet = machineNetworkCidr ? getSubnet(machineNetworkCidr) : null; + const selectedNic = currentSubnet ? getSelectedNic(nics, currentSubnet) : null; + return { + title: (selectedNic?.ipv4Addresses || []).join(', ') || DASH, + props: { 'data-testid': 'nic-ipv4' }, + sortableValue: (selectedNic?.ipv4Addresses || []).join(', ') || DASH, + }; + }, +}); + +export const ipv6Column = (cluster: Cluster): TableRow => ({ + header: { + title: 'IPv6 address', + sort: true, + }, + cell: (host) => { + const inventory = getInventory(host); + const nics = inventory.interfaces || []; + + const machineNetworkCidr = selectMachineNetworkCIDR(cluster); + const currentSubnet = machineNetworkCidr ? getSubnet(machineNetworkCidr) : null; + const selectedNic = currentSubnet ? getSelectedNic(nics, currentSubnet) : null; + return { + title: (selectedNic?.ipv6Addresses || []).join(', ') || DASH, + props: { 'data-testid': 'nic-ipv6' }, + sortableValue: (selectedNic?.ipv6Addresses || []).join(', ') || DASH, + }; + }, +}); + +export const macAddressColumn = (cluster: Cluster): TableRow => ({ + header: { + title: 'MAC address', + sort: true, + }, + cell: (host) => { + const inventory = getInventory(host); + const nics = inventory.interfaces || []; + + const machineNetworkCidr = selectMachineNetworkCIDR(cluster); + const currentSubnet = machineNetworkCidr ? getSubnet(machineNetworkCidr) : null; + const selectedNic = currentSubnet ? getSelectedNic(nics, currentSubnet) : null; + return { + title: selectedNic?.macAddress || DASH, + props: { 'data-testid': 'nic-mac-address' }, + sortableValue: selectedNic?.macAddress || DASH, + }; + }, +}); + +export const labelsColumn = (t: TFunction): TableRow => ({ + header: { + title: t('ai:Labels'), + props: { + id: 'col-header-labels', + }, + sort: false, + }, + cell: (host) => { + const labels = getHostLabels(host); + return { + title: + Object.entries(labels).length > 0 ? ( + + {Object.entries(labels).map(([key, value], index) => ( + + ))} + + ) : ( + '--' + ), + props: { 'data-testid': 'host-labels' }, + }; + }, +}); diff --git a/libs/ui-lib/lib/common/components/hosts/tableUtils.tsx b/libs/ui-lib/lib/common/components/hosts/tableUtils.tsx index 86fd103f8c..026d593d12 100644 --- a/libs/ui-lib/lib/common/components/hosts/tableUtils.tsx +++ b/libs/ui-lib/lib/common/components/hosts/tableUtils.tsx @@ -1,414 +1,9 @@ import * as React from 'react'; -import { Label, LabelGroup } from '@patternfly/react-core'; -import { Address4, Address6 } from 'ip-address'; -import type { - Cluster, - Host, - HostUpdateParams, - Interface, -} from '@openshift-assisted/types/assisted-installer-service'; -import type { ValidationsInfo as HostValidationsInfo } from '../../types/hosts'; -import { getSubnet } from '../clusterConfiguration'; -import { DASH } from '../constants'; -import { getDateTimeCell } from '../ui'; -import { ActionsResolver, TableRow } from './AITable'; -import { getHostRowHardwareInfo } from './hardwareInfo'; -import Hostname from './Hostname'; -import HostPropertyValidationPopover from './HostPropertyValidationPopover'; -import HostsCount from './HostsCount'; -import { HostsTableActions } from './types'; -import HostStatus from './HostStatus'; -import RoleCell from './RoleCell'; -import { - areOnlySoftValidationsFailing, - getHostLabels, - getHostname, - getHostRole, - getHostStatus, - getInventory, -} from './utils'; -import { selectMachineNetworkCIDR } from '../../selectors/clusterSelectors'; -import { hostStatus } from './status'; import { TFunction } from 'i18next'; -import { stringToJSON } from '../../utils'; - -export const getSelectedNic = (nics: Interface[], currentSubnet: Address4 | Address6) => { - return nics.find((nic) => { - const ipv4Addresses = (nic.ipv4Addresses || []).reduce((addresses, address) => { - if (Address4.isValid(address)) { - addresses.push(new Address4(address)); - } - return addresses; - }, []); - - if (ipv4Addresses.find((address) => address.isInSubnet(currentSubnet))) { - return true; - } - - const ipv6Addresses = (nic.ipv6Addresses || []).reduce((addresses, address) => { - if (Address6.isValid(address)) { - addresses.push(new Address6(address)); - } - return addresses; - }, []); - - return ipv6Addresses.find((address) => address.isInSubnet(currentSubnet)); - }); -}; - -export const hostnameColumn = ( - t: TFunction, - onEditHostname?: HostsTableActions['onEditHost'], - hosts?: Host[], - canEditHostname?: HostsTableActions['canEditHostname'], -): TableRow => { - return { - header: { - title: t('ai:Hostname'), - props: { - id: 'col-header-hostname', // ACM jest tests require id over testId - }, - sort: true, - }, - cell: (host) => { - const inventory = getInventory(host); - const editHostname = onEditHostname ? () => onEditHostname(host) : undefined; - const computedHostname = getHostname(host, inventory); - return { - title: ( - - ), - props: { 'data-testid': 'host-name' }, - sortableValue: computedHostname || '', - }; - }, - }; -}; - -export const roleColumn = ( - t: TFunction, - canEditRole?: HostsTableActions['canEditRole'], - onEditRole?: HostsTableActions['onEditRole'], - schedulableMasters?: boolean, - clusterKind?: Cluster['kind'], -): TableRow => { - return { - header: { - title: t('ai:Role'), - props: { - id: 'col-header-role', - }, - sort: true, - }, - cell: (host) => { - const editRole = onEditRole - ? (role: HostUpdateParams['hostRole']) => onEditRole(host, role) - : undefined; - const isRoleEditable = canEditRole?.(host); - const hostRole = getHostRole(host, t, schedulableMasters, clusterKind); - return { - title: ( - - ), - props: { 'data-testid': 'host-role' }, - sortableValue: hostRole, - }; - }, - }; -}; - -export const statusColumn = ( - t: TFunction, - clusterStatus: Cluster['status'], - AdditionalNTPSourcesDialogToggleComponent?: React.FC, - onEditHostname?: HostsTableActions['onEditHost'], - UpdateDay2ApiVipDialogToggleComponent?: React.FC, -): TableRow => { - return { - header: { - title: t('ai:Status'), - props: { - id: 'col-header-status', - }, - sort: true, - }, - cell: (host) => { - const validationsInfo = stringToJSON(host.validationsInfo) || {}; - const editHostname = onEditHostname ? () => onEditHostname(host) : undefined; - const sublabel = - areOnlySoftValidationsFailing(validationsInfo) && - ['known', 'known-unbound'].includes(host.status) - ? t('ai:Some validations failed') - : undefined; - - const actualHostStatus = getHostStatus(host.status, clusterStatus); - - return { - title: ( - - ), - props: { 'data-testid': 'host-status' }, - sortableValue: host.status, - }; - }, - }; -}; - -export const discoveredAtColumn = (t: TFunction): TableRow => ({ - header: { - title: t('ai:Discovered on'), - props: { - id: 'col-header-discoveredat', - }, - sort: true, - }, - cell: (host) => { - const { createdAt } = host; - const dateTimeCell = getDateTimeCell(createdAt); - return { - title: dateTimeCell.title, - props: { 'data-testid': 'host-discovered-time' }, - sortableValue: dateTimeCell.sortableValue, - }; - }, -}); - -export const cpuArchitectureColumn = (t: TFunction): TableRow => ({ - header: { - title: t('ai:CPU Architecture'), - props: { - id: 'col-header-cpuarchitecture', - }, - sort: true, - }, - cell: (host) => { - const inventory = getInventory(host); - return { - title: inventory.cpu?.architecture, - props: { 'data-testid': 'host-cpu-architecture' }, - sortableValue: inventory.cpu?.architecture, - }; - }, -}); - -export const cpuCoresColumn = (t: TFunction): TableRow => ({ - header: { - title: t('ai:CPU Cores'), - props: { - id: 'col-header-cpucores', - }, - sort: true, - }, - cell: (host) => { - const inventory = getInventory(host); - const { cores } = getHostRowHardwareInfo(inventory); - const validationsInfo = stringToJSON(host.validationsInfo) || {}; - const cpuCoresValidation = validationsInfo?.hardware?.find( - (v) => v.id === 'has-cpu-cores-for-role', - ); - return { - title: ( - - {cores.title} - - ), - props: { 'data-testid': 'host-cpu-cores' }, - sortableValue: cores.sortableValue, - }; - }, -}); - -export const memoryColumn = (t: TFunction): TableRow => ({ - header: { - title: t('ai:Memory'), - props: { - id: 'col-header-memory', - }, - sort: true, - }, - cell: (host) => { - const inventory = getInventory(host); - const { memory } = getHostRowHardwareInfo(inventory); - const validationsInfo = stringToJSON(host.validationsInfo) || {}; - const memoryValidation = validationsInfo?.hardware?.find((v) => v.id === 'has-memory-for-role'); - return { - title: ( - - {memory.title} - - ), - props: { 'data-testid': 'host-memory' }, - sortableValue: memory.sortableValue, - }; - }, -}); - -export const disksColumn = (t: TFunction): TableRow => ({ - header: { - title: t('ai:Total storage'), - props: { - id: 'col-header-disk', - }, - sort: true, - }, - cell: (host) => { - const inventory = getInventory(host); - const { disk } = getHostRowHardwareInfo(inventory); - const validationsInfo = stringToJSON(host.validationsInfo) || {}; - const diskValidation = validationsInfo?.hardware?.find((v) => v.id === 'has-min-valid-disks'); - return { - title: ( - - {disk.title} - - ), - props: { 'data-testid': 'host-disks' }, - sortableValue: disk.sortableValue, - }; - }, -}); - -export const gpusColumn = (t: TFunction): TableRow => ({ - header: { - title: t('ai:GPUs'), - props: { - id: 'col-header-gpus', - }, - sort: true, - }, - cell: (host) => { - const { gpus } = getInventory(host); - return { - title: <>{gpus?.length ?? '--'}, - props: { 'data-testid': 'host-gpus' }, - sortableValue: gpus?.length ?? 0, - }; - }, -}); - -export const countColumn = (cluster: Cluster): TableRow => ({ - header: { title: }, -}); - -export const activeNICColumn = (cluster: Cluster, t: TFunction): TableRow => ({ - header: { - title: t('ai:Active NIC'), - sort: true, - }, - cell: (host) => { - const inventory = getInventory(host); - const nics = inventory.interfaces || []; - const machineNetworkCidr = selectMachineNetworkCIDR(cluster); - const currentSubnet = machineNetworkCidr ? getSubnet(machineNetworkCidr) : null; - const selectedNic = currentSubnet ? getSelectedNic(nics, currentSubnet) : null; - return { - title: selectedNic?.name || DASH, - props: { 'data-testid': 'nic-name' }, - sortableValue: selectedNic?.name || DASH, - }; - }, -}); - -export const ipv4Column = (cluster: Cluster): TableRow => ({ - header: { - title: 'IPv4 address', - sort: true, - }, - cell: (host) => { - const inventory = getInventory(host); - const nics = inventory.interfaces || []; - - const machineNetworkCidr = selectMachineNetworkCIDR(cluster); - const currentSubnet = machineNetworkCidr ? getSubnet(machineNetworkCidr) : null; - const selectedNic = currentSubnet ? getSelectedNic(nics, currentSubnet) : null; - return { - title: (selectedNic?.ipv4Addresses || []).join(', ') || DASH, - props: { 'data-testid': 'nic-ipv4' }, - sortableValue: (selectedNic?.ipv4Addresses || []).join(', ') || DASH, - }; - }, -}); - -export const ipv6Column = (cluster: Cluster): TableRow => ({ - header: { - title: 'IPv6 address', - sort: true, - }, - cell: (host) => { - const inventory = getInventory(host); - const nics = inventory.interfaces || []; - - const machineNetworkCidr = selectMachineNetworkCIDR(cluster); - const currentSubnet = machineNetworkCidr ? getSubnet(machineNetworkCidr) : null; - const selectedNic = currentSubnet ? getSelectedNic(nics, currentSubnet) : null; - return { - title: (selectedNic?.ipv6Addresses || []).join(', ') || DASH, - props: { 'data-testid': 'nic-ipv6' }, - sortableValue: (selectedNic?.ipv6Addresses || []).join(', ') || DASH, - }; - }, -}); - -export const macAddressColumn = (cluster: Cluster): TableRow => ({ - header: { - title: 'MAC address', - sort: true, - }, - cell: (host) => { - const inventory = getInventory(host); - const nics = inventory.interfaces || []; - - const machineNetworkCidr = selectMachineNetworkCIDR(cluster); - const currentSubnet = machineNetworkCidr ? getSubnet(machineNetworkCidr) : null; - const selectedNic = currentSubnet ? getSelectedNic(nics, currentSubnet) : null; - return { - title: selectedNic?.macAddress || DASH, - props: { 'data-testid': 'nic-mac-address' }, - sortableValue: selectedNic?.macAddress || DASH, - }; - }, -}); - -export const labelsColumn = (t: TFunction): TableRow => ({ - header: { - title: t('ai:Labels'), - props: { - id: 'col-header-labels', - }, - sort: false, - }, - cell: (host) => { - const labels = getHostLabels(host); - return { - title: - Object.entries(labels).length > 0 ? ( - - {Object.entries(labels).map(([key, value], index) => ( - - ))} - - ) : ( - '--' - ), - props: { 'data-testid': 'host-labels' }, - }; - }, -}); +import type { Host } from '@openshift-assisted/types/assisted-installer-service'; +import { ActionsResolver } from './AITable'; +import { HostsTableActions } from './types'; +import { getHostname, getInventory } from './utils'; const ActionTitle: React.FC<{ disabled: boolean; description?: string; title: string }> = ({ title, diff --git a/libs/ui-lib/lib/common/components/hosts/types.ts b/libs/ui-lib/lib/common/components/hosts/types.ts index ebda603921..8410d197c9 100644 --- a/libs/ui-lib/lib/common/components/hosts/types.ts +++ b/libs/ui-lib/lib/common/components/hosts/types.ts @@ -91,5 +91,6 @@ export type HostStatusProps = AdditionNtpSourcePropsType & zIndex?: number; autoCSR?: boolean; additionalPopoverContent?: React.ReactNode; + additionalBMHInfo?: React.ReactNode; openshiftVersion?: string; }; diff --git a/libs/ui-lib/lib/common/components/ui/EventsList.tsx b/libs/ui-lib/lib/common/components/ui/EventsList.tsx index 538cf326aa..7493307b4b 100644 --- a/libs/ui-lib/lib/common/components/ui/EventsList.tsx +++ b/libs/ui-lib/lib/common/components/ui/EventsList.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Button, ButtonVariant, Label } from '@patternfly/react-core'; +import { Button, ButtonVariant, Content, Label } from '@patternfly/react-core'; import { Table, TableText, TableVariant, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; import { InfoCircleIcon } from '@patternfly/react-icons/dist/js/icons/info-circle-icon'; import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/js/icons/exclamation-triangle-icon'; @@ -71,7 +71,9 @@ const EventsList = ({ events, resetFilters }: EventsListProps) => { { title: ( - {getHumanizedDateTime(event.eventTime)} + + {getHumanizedDateTime(event.eventTime)} + ), }, diff --git a/libs/ui-lib/lib/ocm/components/clusterConfiguration/OcmBaseDomainField.tsx b/libs/ui-lib/lib/ocm/components/clusterConfiguration/OcmBaseDomainField.tsx index dceeaddfff..e050acbec3 100644 --- a/libs/ui-lib/lib/ocm/components/clusterConfiguration/OcmBaseDomainField.tsx +++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/OcmBaseDomainField.tsx @@ -36,9 +36,9 @@ export const BaseDnsHelperText = ({ Enter the name of your domain [domainname] or [domainname.com]. This cannot be changed after cluster installed. All DNS records must include the cluster name and be subdomains of the base you enter. The full cluster address will be:
    - + {name || '[Cluster Name]'}.{baseDnsDomain || '[domainname.com]'} - + diff --git a/libs/ui-lib/lib/ocm/components/clusterConfiguration/networkConfiguration/NetworkConfigurationTableBase.tsx b/libs/ui-lib/lib/ocm/components/clusterConfiguration/networkConfiguration/NetworkConfigurationTableBase.tsx index 1acb85ac03..f96648c955 100644 --- a/libs/ui-lib/lib/ocm/components/clusterConfiguration/networkConfiguration/NetworkConfigurationTableBase.tsx +++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/networkConfiguration/NetworkConfigurationTableBase.tsx @@ -1,8 +1,9 @@ import * as React from 'react'; -import { HostsTableActions, selectSchedulableMasters } from '../../../../common'; import NetworkingStatus from '../../hosts/NetworkingStatus'; import { useTranslation } from '../../../../common/hooks/use-translation-wrapper'; import { + HostsTableActions, + selectSchedulableMasters, hostnameColumn, roleColumn, activeNICColumn, @@ -10,7 +11,7 @@ import { ipv6Column, macAddressColumn, countColumn, -} from '../../../../common/components/hosts/tableUtils'; +} from '../../../../common'; import { ActionsResolver, TableRow } from '../../../../common/components/hosts/AITable'; import { usePagination } from '../../../../common/components/hosts/usePagination'; import { HostDetail } from '../../../../common/components/hosts/HostRowDetail'; diff --git a/libs/ui-lib/lib/ocm/components/clusterDetail/ResetClusterModal.tsx b/libs/ui-lib/lib/ocm/components/clusterDetail/ResetClusterModal.tsx index 9b316ebbc7..790e01478b 100644 --- a/libs/ui-lib/lib/ocm/components/clusterDetail/ResetClusterModal.tsx +++ b/libs/ui-lib/lib/ocm/components/clusterDetail/ResetClusterModal.tsx @@ -79,7 +79,8 @@ const ResetClusterModal: React.FC = () => { - Download the installation logs to troubleshoot or report a bug. + Download the installation logs to + troubleshoot or report a bug.
    Currently, {collectedLogsPercentage}% of the installation logs were collected and are ready for download. diff --git a/libs/ui-lib/lib/ocm/components/clusterWizard/OperatorsSelect.tsx b/libs/ui-lib/lib/ocm/components/clusterWizard/OperatorsSelect.tsx index 0c5353fd5c..0977a18137 100644 --- a/libs/ui-lib/lib/ocm/components/clusterWizard/OperatorsSelect.tsx +++ b/libs/ui-lib/lib/ocm/components/clusterWizard/OperatorsSelect.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { ExpandableSection, Stack, StackItem } from '@patternfly/react-core'; +import { Content, ExpandableSection, Stack, StackItem } from '@patternfly/react-core'; import { Bundle, Cluster, @@ -106,7 +106,7 @@ const OperatorsSelect = ({ return ( - {categoryName} + {categoryName} {categoryOperators.map((spec) => ( diff --git a/libs/ui-lib/lib/ocm/components/hosts/ClusterHostsTable.tsx b/libs/ui-lib/lib/ocm/components/hosts/ClusterHostsTable.tsx index 3a6023e480..dae0fa4b41 100644 --- a/libs/ui-lib/lib/ocm/components/hosts/ClusterHostsTable.tsx +++ b/libs/ui-lib/lib/ocm/components/hosts/ClusterHostsTable.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; -import { selectSchedulableMasters, isSNO } from '../../../common'; -import { AdditionalNTPSourcesDialogToggle } from './AdditionaNTPSourceDialogToggle'; import { + selectSchedulableMasters, + isSNO, discoveredAtColumn, disksColumn, hostnameColumn, @@ -9,7 +9,8 @@ import { roleColumn, statusColumn, cpuCoresColumn, -} from '../../../common/components/hosts/tableUtils'; +} from '../../../common'; +import { AdditionalNTPSourcesDialogToggle } from './AdditionaNTPSourceDialogToggle'; import { usePagination } from '../../../common/components/hosts/usePagination'; import { useHostsTable, HostsTableModals } from './use-hosts-table'; import HostsTable from '../../../common/components/hosts/HostsTable'; diff --git a/libs/ui-lib/lib/ocm/components/hosts/HostsDiscoveryTable.tsx b/libs/ui-lib/lib/ocm/components/hosts/HostsDiscoveryTable.tsx index d4030d87ad..2997385acd 100644 --- a/libs/ui-lib/lib/ocm/components/hosts/HostsDiscoveryTable.tsx +++ b/libs/ui-lib/lib/ocm/components/hosts/HostsDiscoveryTable.tsx @@ -6,9 +6,6 @@ import { isSNO, DeleteHostAction, TableToolbar, -} from '../../../common'; -import { HostsTableModals, useHostsTable } from './use-hosts-table'; -import { countColumn, cpuCoresColumn, discoveredAtColumn, @@ -16,7 +13,8 @@ import { hostnameColumn, memoryColumn, roleColumn, -} from '../../../common/components/hosts/tableUtils'; +} from '../../../common'; +import { HostsTableModals, useHostsTable } from './use-hosts-table'; import HostsTable from '../../../common/components/hosts/HostsTable'; import { HostDetail } from '../../../common/components/hosts/HostRowDetail'; import { ExpandComponentProps } from '../../../common/components/hosts/AITable'; diff --git a/libs/ui-lib/lib/ocm/components/hosts/HostsStorageTable.tsx b/libs/ui-lib/lib/ocm/components/hosts/HostsStorageTable.tsx index 880e38b550..ab03e4d591 100644 --- a/libs/ui-lib/lib/ocm/components/hosts/HostsStorageTable.tsx +++ b/libs/ui-lib/lib/ocm/components/hosts/HostsStorageTable.tsx @@ -5,18 +5,16 @@ import { disksColumn, hostnameColumn, roleColumn, -} from '../../../common/components/hosts/tableUtils'; -import { - numberOfDisksColumn, - odfUsageColumn, -} from '../../../common/components/storage/StorageUtils'; -import { useTranslation } from '../../../common/hooks/use-translation-wrapper'; -import { hasEnabledOperators, isCompact, selectSchedulableMasters, OPERATOR_NAME_ODF, } from '../../../common'; +import { + numberOfDisksColumn, + odfUsageColumn, +} from '../../../common/components/storage/StorageUtils'; +import { useTranslation } from '../../../common/hooks/use-translation-wrapper'; import { usePagination } from '../../../common/components/hosts/usePagination'; import { ExpandComponentProps } from '../../../common/components/hosts/AITable'; import CommonStorageTable from '../../../common/components/storage/StorageTable'; diff --git a/libs/ui-lib/lib/ocm/components/hosts/use-hosts-table.tsx b/libs/ui-lib/lib/ocm/components/hosts/use-hosts-table.tsx index 1aa238a0ff..7641456d2f 100644 --- a/libs/ui-lib/lib/ocm/components/hosts/use-hosts-table.tsx +++ b/libs/ui-lib/lib/ocm/components/hosts/use-hosts-table.tsx @@ -9,6 +9,8 @@ import { MassDeleteHostModal, isSNO, ResourceUIState, + hostnameColumn, + hostActionResolver, } from '../../../common'; import { AdditionalNTPSourcesDialog, @@ -38,7 +40,6 @@ import { EditHostFormValues, } from '../../../common/components/hosts'; import HostsTable from '../../../common/components/hosts/HostsTable'; -import { hostActionResolver, hostnameColumn } from '../../../common/components/hosts/tableUtils'; import ResetHostModal from './ResetHostModal'; import DeleteHostModal from './DeleteHostModal'; import { onFetchEvents } from '../fetching/fetchEvents';