@openshift-assisted/ui-lib 2.15.2-cim → 2.15.4-cim

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/build/@types/cim/components/Agent/tableUtils.d.ts +5 -1
  2. package/build/@types/cim/components/Agent/tableUtils.d.ts.map +1 -1
  3. package/build/@types/cim/components/ClusterDeployment/ClusterDeploymentDetailsForm.d.ts +1 -1
  4. package/build/@types/cim/components/ClusterDeployment/ClusterDeploymentDetailsForm.d.ts.map +1 -1
  5. package/build/@types/cim/components/ClusterDeployment/ClusterDetailsFormFields.d.ts +0 -1
  6. package/build/@types/cim/components/ClusterDeployment/ClusterDetailsFormFields.d.ts.map +1 -1
  7. package/build/@types/cim/components/Hypershift/HostedClusterWizard/DetailsStep/DetailsForm.d.ts.map +1 -1
  8. package/build/@types/cim/components/Hypershift/HostedClusterWizard/DetailsStep/DetailsStep.d.ts.map +1 -1
  9. package/build/@types/cim/components/Hypershift/HostedClusterWizard/DetailsStep/types.d.ts +6 -1
  10. package/build/@types/cim/components/Hypershift/HostedClusterWizard/DetailsStep/types.d.ts.map +1 -1
  11. package/build/@types/cim/components/Hypershift/HostedClusterWizard/HostsStep/HostsStep.d.ts.map +1 -1
  12. package/build/@types/cim/components/Hypershift/HostedClusterWizard/HostsStep/NodePoolForm.d.ts.map +1 -1
  13. package/build/@types/cim/components/Hypershift/forms/NodePoolAgentsForm.d.ts.map +1 -1
  14. package/build/@types/cim/components/Hypershift/hooks/useSupportedOCPVersions.d.ts +1 -1
  15. package/build/@types/cim/components/Hypershift/hooks/useSupportedOCPVersions.d.ts.map +1 -1
  16. package/build/@types/cim/components/InfraEnv/InfraEnvAgentTable.d.ts.map +1 -1
  17. package/build/@types/cim/components/helpers/versions.d.ts +1 -1
  18. package/build/@types/cim/components/helpers/versions.d.ts.map +1 -1
  19. package/build/@types/cim/index.d.ts +1 -0
  20. package/build/@types/cim/index.d.ts.map +1 -1
  21. package/build/@types/common/components/clusterConfiguration/index.d.ts +0 -1
  22. package/build/@types/common/components/clusterConfiguration/index.d.ts.map +1 -1
  23. package/build/@types/common/components/hosts/AITable.d.ts +3 -1
  24. package/build/@types/common/components/hosts/AITable.d.ts.map +1 -1
  25. package/build/@types/common/components/hosts/HostsTable.d.ts +2 -1
  26. package/build/@types/common/components/hosts/HostsTable.d.ts.map +1 -1
  27. package/build/@types/common/components/hosts/utils.d.ts +4 -1
  28. package/build/@types/common/components/hosts/utils.d.ts.map +1 -1
  29. package/build/@types/common/components/ui/formik/TextAreaField.d.ts.map +1 -1
  30. package/build/cjs/cim/components/Agent/tableUtils.js +4 -3
  31. package/build/cjs/cim/components/Agent/tableUtils.js.map +1 -1
  32. package/build/cjs/cim/components/ClusterDeployment/ClusterDeploymentDetailsForm.js +12 -17
  33. package/build/cjs/cim/components/ClusterDeployment/ClusterDeploymentDetailsForm.js.map +1 -1
  34. package/build/cjs/cim/components/ClusterDeployment/ClusterDetailsFormFields.js +23 -19
  35. package/build/cjs/cim/components/ClusterDeployment/ClusterDetailsFormFields.js.map +1 -1
  36. package/build/cjs/cim/components/Hypershift/HostedClusterWizard/DetailsStep/DetailsForm.js +21 -3
  37. package/build/cjs/cim/components/Hypershift/HostedClusterWizard/DetailsStep/DetailsForm.js.map +1 -1
  38. package/build/cjs/cim/components/Hypershift/HostedClusterWizard/DetailsStep/DetailsStep.js +3 -1
  39. package/build/cjs/cim/components/Hypershift/HostedClusterWizard/DetailsStep/DetailsStep.js.map +1 -1
  40. package/build/cjs/cim/components/Hypershift/HostedClusterWizard/HostsStep/HostsStep.js +2 -11
  41. package/build/cjs/cim/components/Hypershift/HostedClusterWizard/HostsStep/HostsStep.js.map +1 -1
  42. package/build/cjs/cim/components/Hypershift/HostedClusterWizard/HostsStep/NodePoolForm.js +0 -28
  43. package/build/cjs/cim/components/Hypershift/HostedClusterWizard/HostsStep/NodePoolForm.js.map +1 -1
  44. package/build/cjs/cim/components/Hypershift/forms/NodePoolAgentsForm.js +8 -3
  45. package/build/cjs/cim/components/Hypershift/forms/NodePoolAgentsForm.js.map +1 -1
  46. package/build/cjs/cim/components/Hypershift/hooks/useSupportedOCPVersions.js +3 -3
  47. package/build/cjs/cim/components/Hypershift/hooks/useSupportedOCPVersions.js.map +1 -1
  48. package/build/cjs/cim/components/Hypershift/modals/AddNodePoolModal.js +1 -1
  49. package/build/cjs/cim/components/Hypershift/modals/AddNodePoolModal.js.map +1 -1
  50. package/build/cjs/cim/components/Hypershift/modals/ManageHostsModal.js +2 -2
  51. package/build/cjs/cim/components/Hypershift/modals/ManageHostsModal.js.map +1 -1
  52. package/build/cjs/cim/components/InfraEnv/InfraEnvAgentTable.js +2 -2
  53. package/build/cjs/cim/components/InfraEnv/InfraEnvAgentTable.js.map +1 -1
  54. package/build/cjs/cim/components/helpers/versions.js +42 -27
  55. package/build/cjs/cim/components/helpers/versions.js.map +1 -1
  56. package/build/cjs/cim/global.css +12 -0
  57. package/build/cjs/cim/index.js +2 -0
  58. package/build/cjs/cim/index.js.map +1 -1
  59. package/build/cjs/common/components/clusterConfiguration/index.js +1 -3
  60. package/build/cjs/common/components/clusterConfiguration/index.js.map +1 -1
  61. package/build/cjs/common/components/hosts/AITable.js +54 -38
  62. package/build/cjs/common/components/hosts/AITable.js.map +1 -1
  63. package/build/cjs/common/components/hosts/HostsTable.js +8 -5
  64. package/build/cjs/common/components/hosts/HostsTable.js.map +1 -1
  65. package/build/cjs/common/components/hosts/utils.js +12 -2
  66. package/build/cjs/common/components/hosts/utils.js.map +1 -1
  67. package/build/cjs/common/components/ui/formik/TextAreaField.js +2 -1
  68. package/build/cjs/common/components/ui/formik/TextAreaField.js.map +1 -1
  69. package/lib/cim/components/Agent/tableUtils.tsx +4 -3
  70. package/lib/cim/components/ClusterDeployment/ClusterDeploymentDetailsForm.tsx +9 -19
  71. package/lib/cim/components/ClusterDeployment/ClusterDetailsFormFields.tsx +25 -22
  72. package/lib/cim/components/Hypershift/HostedClusterWizard/DetailsStep/DetailsForm.tsx +48 -3
  73. package/lib/cim/components/Hypershift/HostedClusterWizard/DetailsStep/DetailsStep.tsx +4 -0
  74. package/lib/cim/components/Hypershift/HostedClusterWizard/DetailsStep/types.ts +6 -1
  75. package/lib/cim/components/Hypershift/HostedClusterWizard/HostsStep/HostsStep.tsx +2 -13
  76. package/lib/cim/components/Hypershift/HostedClusterWizard/HostsStep/NodePoolForm.tsx +1 -33
  77. package/lib/cim/components/Hypershift/forms/NodePoolAgentsForm.tsx +24 -12
  78. package/lib/cim/components/Hypershift/hooks/useSupportedOCPVersions.ts +3 -2
  79. package/lib/cim/components/Hypershift/modals/AddNodePoolModal.tsx +1 -1
  80. package/lib/cim/components/Hypershift/modals/ManageHostsModal.tsx +2 -2
  81. package/lib/cim/components/InfraEnv/InfraEnvAgentTable.tsx +2 -0
  82. package/lib/cim/components/helpers/versions.ts +51 -34
  83. package/lib/cim/global.css +12 -0
  84. package/lib/cim/index.ts +3 -0
  85. package/lib/common/components/clusterConfiguration/index.ts +0 -1
  86. package/lib/common/components/hosts/AITable.tsx +61 -37
  87. package/lib/common/components/hosts/HostsTable.tsx +21 -9
  88. package/lib/common/components/hosts/utils.ts +18 -2
  89. package/lib/common/components/ui/formik/TextAreaField.tsx +14 -7
  90. package/package.json +3 -3
  91. package/build/@types/common/components/clusterConfiguration/OpenShiftVersionSelect.d.ts +0 -10
  92. package/build/@types/common/components/clusterConfiguration/OpenShiftVersionSelect.d.ts.map +0 -1
  93. package/build/cjs/common/components/clusterConfiguration/OpenShiftVersionSelect.js +0 -52
  94. package/build/cjs/common/components/clusterConfiguration/OpenShiftVersionSelect.js.map +0 -1
  95. package/lib/common/components/clusterConfiguration/OpenShiftVersionSelect.tsx +0 -81
@@ -6,8 +6,6 @@ import { Stack, StackItem } from '@patternfly/react-core';
6
6
  import HostsForm from './HostsForm';
7
7
  import { HostsStepProps, HostsFormValues, NodePoolFormValue } from './types';
8
8
  import { useTranslation } from '../../../../../common/hooks/use-translation-wrapper';
9
- import { getAgentsForSelection } from '../../../helpers';
10
- import { INFRAENV_AGENTINSTALL_LABEL_KEY } from '../../../common';
11
9
  import { TFunction } from 'i18next';
12
10
  import { NodePoolK8sResource } from '../../types';
13
11
 
@@ -61,16 +59,7 @@ const HostsStep: React.FC<HostsStepProps> = ({
61
59
  olmCatalogPlacement,
62
60
  }) => {
63
61
  const { t } = useTranslation();
64
- const availableAgents = getAgentsForSelection(agents);
65
62
 
66
- const infraEnvsWithAgents = infraEnvs.filter((ie) =>
67
- availableAgents.some(
68
- (a) =>
69
- a.metadata?.labels?.[INFRAENV_AGENTINSTALL_LABEL_KEY] === ie.metadata?.name &&
70
- a.metadata?.namespace === ie.metadata?.namespace &&
71
- !a.spec.clusterDeploymentName,
72
- ),
73
- );
74
63
  return (
75
64
  <Stack hasGutter>
76
65
  <StackItem>
@@ -84,7 +73,7 @@ const HostsStep: React.FC<HostsStepProps> = ({
84
73
  controllerAvailabilityPolicy: controllerAvailabilityPolicy || 'HighlyAvailable',
85
74
  infrastructureAvailabilityPolicy: infrastructureAvailabilityPolicy || 'HighlyAvailable',
86
75
  olmCatalogPlacement: olmCatalogPlacement || 'management',
87
- agentNamespace: initInfraEnv || infraEnvsWithAgents[0]?.metadata?.namespace || '',
76
+ agentNamespace: initInfraEnv || infraEnvs[0]?.metadata?.namespace || '',
88
77
  nodePools: initNodePools || [
89
78
  {
90
79
  nodePoolName: `nodepool-${clusterName}-1`,
@@ -106,7 +95,7 @@ const HostsStep: React.FC<HostsStepProps> = ({
106
95
  >
107
96
  <HostsForm
108
97
  onValuesChanged={onValuesChanged}
109
- infraEnvs={infraEnvsWithAgents}
98
+ infraEnvs={infraEnvs}
110
99
  agents={agents}
111
100
  clusterName={clusterName}
112
101
  initReleaseImage={initReleaseImage}
@@ -22,7 +22,6 @@ import { HostsFormValues } from './types';
22
22
  import { useFormikContext } from 'formik';
23
23
  import { getAgentsForSelection } from '../../../helpers';
24
24
  import { useTranslation } from '../../../../../common/hooks/use-translation-wrapper';
25
- import { useFormikHelpers } from '../../../../../common/hooks/useFormikHelpers';
26
25
 
27
26
  import './NodePoolForm.css';
28
27
  import NodePoolAgentsForm from '../../forms/NodePoolAgentsForm';
@@ -37,14 +36,8 @@ type NodePoolFormProps = {
37
36
  const NodePoolForm: React.FC<NodePoolFormProps> = ({ infraEnvs, agents, index, onRemove }) => {
38
37
  const { t } = useTranslation();
39
38
  const { values } = useFormikContext<HostsFormValues>();
40
- const { setValue: setCountValue } = useFormikHelpers(`nodePools[${index}].count`);
41
- const { setValue: setMinAutoscalingValue } = useFormikHelpers(
42
- `nodePools[${index}].autoscaling.minReplicas`,
43
- );
44
- const { setValue: setMaxAutoscalingValue } = useFormikHelpers(
45
- `nodePools[${index}].autoscaling.maxReplicas`,
46
- );
47
39
  const [isExpanded, setExpanded] = React.useState(true);
40
+
48
41
  const infraEnvAgents = React.useMemo(() => {
49
42
  const infraEnv = infraEnvs.find((ie) => ie.metadata?.namespace === values.agentNamespace);
50
43
  return agents.filter((agent) => isAgentOfInfraEnv(infraEnv, agent));
@@ -79,31 +72,6 @@ const NodePoolForm: React.FC<NodePoolFormProps> = ({ infraEnvs, agents, index, o
79
72
  Math.min(matchingAgents.length, availableAgents.length - previousNodePoolsCount),
80
73
  );
81
74
 
82
- const currentCount = values.nodePools[index].useAutoscaling
83
- ? values.nodePools[index].autoscaling.maxReplicas
84
- : values.nodePools[index].count;
85
-
86
- React.useEffect(() => {
87
- if (currentCount > maxAgents) {
88
- if (values.nodePools[index].useAutoscaling) {
89
- if (maxAgents === 0) {
90
- void setMinAutoscalingValue(maxAgents);
91
- }
92
- void setMaxAutoscalingValue(maxAgents);
93
- } else {
94
- void setCountValue(maxAgents);
95
- }
96
- }
97
- }, [
98
- maxAgents,
99
- currentCount,
100
- values.nodePools,
101
- index,
102
- setCountValue,
103
- setMinAutoscalingValue,
104
- setMaxAutoscalingValue,
105
- ]);
106
-
107
75
  return (
108
76
  <Stack hasGutter>
109
77
  <StackItem>
@@ -1,4 +1,4 @@
1
- import { Grid, GridItem } from '@patternfly/react-core';
1
+ import { Alert, AlertVariant, Grid, GridItem, Text } from '@patternfly/react-core';
2
2
  import * as React from 'react';
3
3
  import { CheckboxField, NumberInputField, PopoverIcon } from '../../../../common';
4
4
  import { useTranslation } from '../../../../common/hooks/use-translation-wrapper';
@@ -27,6 +27,7 @@ const NodePoolAgentsForm = ({
27
27
  }: NodePoolAgentsFormProps) => {
28
28
  const { t } = useTranslation();
29
29
 
30
+ const [{ value: count }] = useField<number>(countName);
30
31
  const [{ value: minValue }] = useField<number>(`${autoscalingName}.minReplicas`);
31
32
  const [{ value: maxValue }] = useField<number>(`${autoscalingName}.maxReplicas`);
32
33
  const [{ value: useAutoscalingValue }] = useField<boolean>(useAutoscalingName);
@@ -60,7 +61,7 @@ const NodePoolAgentsForm = ({
60
61
  label={t('ai:Minimum number of hosts')}
61
62
  isRequired
62
63
  minValue={1}
63
- maxValue={maxValue || maxAgents}
64
+ maxValue={maxValue}
64
65
  />
65
66
  </GridItem>
66
67
  <GridItem>
@@ -69,22 +70,33 @@ const NodePoolAgentsForm = ({
69
70
  label={t('ai:Maximum number of hosts')}
70
71
  isRequired
71
72
  minValue={minValue || 1}
72
- maxValue={maxAgents}
73
73
  helperText={helperText}
74
74
  />
75
75
  </GridItem>
76
76
  </>
77
77
  ) : (
78
+ <>
79
+ <GridItem>
80
+ <NumberInputField
81
+ label={t('ai:Number of hosts')}
82
+ idPostfix="count"
83
+ name={countName}
84
+ isRequired
85
+ minValue={0}
86
+ helperText={helperText}
87
+ />
88
+ </GridItem>
89
+ </>
90
+ )}
91
+ {(useAutoscalingValue ? maxValue : count) > maxAgents && (
78
92
  <GridItem>
79
- <NumberInputField
80
- label={t('ai:Number of hosts')}
81
- idPostfix="count"
82
- name={countName}
83
- isRequired
84
- minValue={0}
85
- maxValue={maxAgents}
86
- helperText={helperText}
87
- />
93
+ <Alert
94
+ isInline
95
+ variant={AlertVariant.warning}
96
+ title={t('ai:Host count exceeds the current number of available hosts.')}
97
+ >
98
+ <Text>{t('ai:Scale down the nodepool or make more hosts available.')}</Text>
99
+ </Alert>
88
100
  </GridItem>
89
101
  )}
90
102
  </Grid>
@@ -11,9 +11,10 @@ export const getSupportedCM = (configMaps: ConfigMapK8sResource[]) =>
11
11
  export const useSupportedOCPVersions = (
12
12
  clusterImages: ClusterImageSetK8sResource[],
13
13
  supportedVersionsCM?: ConfigMapK8sResource,
14
+ extended?: boolean,
14
15
  ) => {
15
16
  return React.useMemo(() => {
16
- const ocpVersions = getOCPVersions(clusterImages, false);
17
+ const ocpVersions = getOCPVersions(clusterImages, false, undefined, extended);
17
18
  const supportedVersionsString = supportedVersionsCM?.data?.['supported-versions'];
18
19
  if (supportedVersionsString) {
19
20
  try {
@@ -25,5 +26,5 @@ export const useSupportedOCPVersions = (
25
26
  }
26
27
  }
27
28
  return ocpVersions;
28
- }, [clusterImages, supportedVersionsCM]);
29
+ }, [clusterImages, extended, supportedVersionsCM?.data]);
29
30
  };
@@ -41,7 +41,7 @@ const AddNodePoolModal = ({
41
41
  const handleSubmit = async (values: NodePoolFormValues) => {
42
42
  setError(undefined);
43
43
  const nodePool: NodePoolK8sResource = {
44
- apiVersion: 'hypershift.openshift.io/v1alpha1',
44
+ apiVersion: 'hypershift.openshift.io/v1beta1',
45
45
  kind: 'NodePool',
46
46
  metadata: {
47
47
  name: values.nodePoolName,
@@ -66,7 +66,7 @@ const getPatches = (values: NodePoolFormValues, nodePool: NodePoolK8sResource) =
66
66
  );
67
67
  }
68
68
  } else {
69
- if (!!nodePool.spec.replicas) {
69
+ if (nodePool.spec.replicas !== undefined) {
70
70
  patches.push({
71
71
  op: 'replace',
72
72
  value: values.count,
@@ -127,7 +127,7 @@ const ManageHostsModal = ({
127
127
  agentLabels: labelsToFormikValue(
128
128
  nodePool.spec.platform?.agent?.agentLabelSelector?.matchLabels || {},
129
129
  ),
130
- count: nodePool.spec.replicas || 1,
130
+ count: nodePool.spec.replicas || 0,
131
131
  useAutoscaling: !!nodePool.spec.autoScaling,
132
132
  autoscaling: {
133
133
  minReplicas: nodePool.spec.autoScaling?.min || 1,
@@ -123,6 +123,7 @@ const InfraEnvAgentTable: React.FC<InfraEnvAgentTableProps> = ({
123
123
  setStatusFilter,
124
124
  statusFilter,
125
125
  filteredHosts: hosts,
126
+ sorted,
126
127
  } = useAgentsFilter({
127
128
  agents,
128
129
  agentStatuses,
@@ -226,6 +227,7 @@ const InfraEnvAgentTable: React.FC<InfraEnvAgentTableProps> = ({
226
227
  <StackItem>
227
228
  <HostsTable
228
229
  hosts={hosts}
230
+ alreadySorted={sorted}
229
231
  content={content}
230
232
  actionResolver={actionResolver}
231
233
  selectedIDs={selectedHostIDs}
@@ -61,48 +61,65 @@ export const isValidImageSet = (
61
61
  );
62
62
  };
63
63
 
64
+ // Extracts major.minor version, strips any non-numeric prefix (e.g., "img4.18.55" → "4.18")
65
+ const extractMajorMinor = (version: string): string => {
66
+ const versionWithoutPrefix = version.replace(/^[^\d]*/, '');
67
+ return versionWithoutPrefix.split('.').slice(0, 2).join('.');
68
+ };
69
+
70
+ // Converts a ClusterImageSet to an OpenshiftVersionOptionType
71
+ const toVersionOption = (
72
+ clusterImageSet: ClusterImageSetK8sResource,
73
+ ): OpenshiftVersionOptionType => {
74
+ const version =
75
+ getVersionFromReleaseImage(clusterImageSet.spec?.releaseImage) ||
76
+ clusterImageSet.metadata?.name ||
77
+ '';
78
+ const cpuArch = getCPUArchFromReleaseImage(clusterImageSet.spec?.releaseImage);
79
+ return {
80
+ label: `OpenShift ${version}`,
81
+ version,
82
+ value: clusterImageSet.metadata?.name as string,
83
+ default: false,
84
+ // (rawagner) ACM does not have the warning so changing to 'production'
85
+ // supportLevel : getSupportLevelFromChannel(clusterImageSet.metadata?.labels?.channel)
86
+ supportLevel: 'production',
87
+ cpuArchitectures: cpuArch ? [cpuArch as CpuArchitecture] : undefined,
88
+ };
89
+ };
90
+
91
+ /**
92
+ * In case there are osImages specified on the AgentServiceConfig, we want to limit
93
+ * the availability in the dropdown to only versions that are listed there.
94
+ * Otherwise the installation fails.
95
+ */
96
+ const hasMatchingOsImage = (
97
+ version: OpenshiftVersionOptionType,
98
+ osImageVersions: (string | undefined)[],
99
+ ): boolean => {
100
+ if (!osImageVersions.length) return true;
101
+ return osImageVersions.includes(extractMajorMinor(version.version));
102
+ };
103
+
64
104
  export const getOCPVersions = (
65
105
  clusterImageSets: ClusterImageSetK8sResource[],
66
- isNutanix?: boolean | undefined,
106
+ isNutanix?: boolean,
67
107
  osImages?: OsImage[],
68
108
  extended?: boolean,
69
109
  ): OpenshiftVersionOptionType[] => {
70
- const ocpImageVersions = Array.from(
71
- new Set(osImages?.map((osImage) => osImage.openshiftVersion)),
72
- );
110
+ const architectures = isNutanix ? supportedNutanixPlatforms : undefined;
111
+ const osImageVersions = [...new Set(osImages?.map((img) => img.openshiftVersion))];
73
112
 
74
113
  const versions = clusterImageSets
75
- .filter((clusterImageSet) =>
76
- isValidImageSet(clusterImageSet, isNutanix ? supportedNutanixPlatforms : undefined, extended),
77
- )
78
- .map((clusterImageSet): OpenshiftVersionOptionType => {
79
- const version = getVersionFromReleaseImage(clusterImageSet.spec?.releaseImage);
80
- const cpuArch = getCPUArchFromReleaseImage(clusterImageSet.spec?.releaseImage);
81
- return {
82
- label: `OpenShift ${version ? version : (clusterImageSet.metadata?.name as string)}`,
83
- version: version || clusterImageSet.metadata?.name || '',
84
- value: clusterImageSet.metadata?.name as string,
85
- default: false,
86
- // (rawagner) ACM does not have the warning so changing to 'production'
87
- supportLevel: 'production', // getSupportLevelFromChannel(clusterImageSet.metadata?.labels?.channel),
88
- cpuArchitectures: cpuArch ? [cpuArch as CpuArchitecture] : undefined,
89
- };
90
- })
91
- .filter((ocpVersion) => {
92
- const ver = ocpVersion.version.split('.').slice(0, 2).join('.');
93
- return !!ocpImageVersions.length ? ocpImageVersions.includes(ver) : true;
94
- })
95
- .sort(
96
- (versionA, versionB) => /* descending */ -1 * versionA.label.localeCompare(versionB.label),
97
- );
98
-
99
- if (versions.length) {
100
- // make sure that the pre-selected one is the first-one after sorting
101
- versions[0].default = true;
102
- }
114
+ .filter((clusterImageSet) => isValidImageSet(clusterImageSet, architectures, extended))
115
+ .map(toVersionOption)
116
+ .filter((v) => hasMatchingOsImage(v, osImageVersions))
117
+ .sort((a, b) => b.label.localeCompare(a.label));
118
+
119
+ // Mark first version as the default selection
120
+ versions[0] && (versions[0].default = true);
103
121
 
104
- const deduped = uniqBy(versions, (v) => v.version);
105
- return deduped;
122
+ return uniqBy(versions, (v) => v.version);
106
123
  };
107
124
 
108
125
  export const getSelectedVersion = (
@@ -0,0 +1,12 @@
1
+ /*
2
+ In readonly form controls in PF5, text is unreadable in dark theme due to its background color.
3
+ Original color is disabled-color-300, see effect at https://v5-archive.patternfly.org/utility-classes/background-color
4
+
5
+ Applied to:
6
+ - Add hosts modal
7
+ - AI cluster installation progress card
8
+ */
9
+ .pf-v5-theme-dark #add-host-modal .pf-v5-c-form-control.pf-m-readonly,
10
+ .pf-v5-theme-dark #aiprogress .pf-v5-c-form-control.pf-m-readonly {
11
+ --pf-v5-c-form-control--m-readonly--BackgroundColor: var(--pf-v5-global--disabled-color--200);
12
+ }
package/lib/cim/index.ts CHANGED
@@ -1,3 +1,6 @@
1
+ // Import global CIM styles
2
+ import './global.css';
3
+
1
4
  // without namespace
2
5
  export * from './types';
3
6
  export * from './components';
@@ -1,6 +1,5 @@
1
1
  export { default as SNOControlGroup } from './SNOControlGroup';
2
2
  export { default as SNODisclaimer } from './SNODisclaimer';
3
- export { default as OpenShiftVersionSelect } from './OpenShiftVersionSelect';
4
3
  export { default as UploadSSH } from './UploadSSH';
5
4
  export { default as ProxyFields, ProxyInputFields } from './ProxyFields';
6
5
  export { default as DownloadIsoModal } from './DownloadIsoModal';
@@ -80,6 +80,7 @@ export type AITableProps<R> = ReturnType<typeof usePagination> & {
80
80
  actionResolver?: ActionsResolver<R>;
81
81
  canSelectAll?: boolean;
82
82
  variant?: TableProps['variant'];
83
+ alreadySorted?: boolean;
83
84
  };
84
85
 
85
86
  // eslint-disable-next-line
@@ -103,8 +104,21 @@ const AITable = <R extends any>({
103
104
  perPageOptions,
104
105
  canSelectAll,
105
106
  variant,
107
+ alreadySorted,
106
108
  }: WithTestID & AITableProps<R>) => {
107
109
  const itemIDs = React.useMemo(() => data.map(getDataId), [data, getDataId]);
110
+ const [openRows, setOpenRows] = React.useState<OpenRows>({});
111
+
112
+ const sortByRef = React.useRef<ISortBy>();
113
+ const [sortBy, setSortBy] = React.useState<ISortBy>({
114
+ index: onSelect ? 1 : 0,
115
+ direction: SortByDirection.asc,
116
+ });
117
+
118
+ const dataRef = React.useRef(data);
119
+ if (dataRef.current !== data) {
120
+ dataRef.current = data;
121
+ }
108
122
 
109
123
  React.useEffect(() => {
110
124
  if (selectedIDs && setSelectedIDs) {
@@ -121,16 +135,29 @@ const AITable = <R extends any>({
121
135
  }
122
136
  }, [data, setSelectedIDs, selectedIDs, getDataId]);
123
137
 
124
- const [openRows, setOpenRows] = React.useState<OpenRows>({});
125
- const [sortBy, setSortBy] = React.useState<ISortBy>({
126
- index: onSelect ? 1 : 0,
127
- direction: SortByDirection.asc,
128
- });
138
+ React.useEffect(() => {
139
+ if (alreadySorted) {
140
+ sortByRef.current = sortBy;
141
+ setSortBy({
142
+ index: -1,
143
+ direction: SortByDirection.asc,
144
+ });
145
+ } else {
146
+ if (sortBy.index === -1 && sortByRef.current) {
147
+ setSortBy(sortByRef.current);
148
+ }
149
+ }
150
+ // eslint-disable-next-line react-hooks/exhaustive-deps
151
+ }, [alreadySorted]);
152
+
153
+ React.useEffect(() => {
154
+ if (!alreadySorted) {
155
+ sortByRef.current = sortBy;
156
+ } else if (alreadySorted && sortBy.index !== -1) {
157
+ sortByRef.current = sortBy;
158
+ }
159
+ }, [sortBy, alreadySorted]);
129
160
 
130
- const dataRef = React.useRef(data);
131
- if (dataRef.current !== data) {
132
- dataRef.current = data;
133
- }
134
161
  const onSelectAll = React.useCallback(
135
162
  (isChecked: boolean) => {
136
163
  setSelectedIDs?.(isChecked ? dataRef.current.map(getDataId) : []);
@@ -169,33 +196,27 @@ const AITable = <R extends any>({
169
196
  return [newContent, columns];
170
197
  }, [canSelectAll, content, getDataId, onSelect, onSelectAll]);
171
198
 
172
- const hostRows = React.useMemo(() => {
173
- const rows = (data || [])
174
- .map<IRow>((obj) => {
175
- const id = getDataId(obj);
176
- const cells = contentWithAdditions.filter((c) => !!c.cell).map((c) => c.cell?.(obj));
177
- const isOpen = !!openRows[id];
178
- return {
179
- isOpen,
180
- cells,
181
- key: `${id}-master`,
182
- id,
183
- actions: actionResolver ? actionResolver(obj) : undefined,
184
- nestedComponent: ExpandComponent ? <ExpandComponent obj={obj} /> : undefined,
185
- };
186
- })
187
- .slice((page - 1) * perPage, page * perPage);
188
- return rows;
189
- }, [
190
- data,
191
- page,
192
- perPage,
193
- ExpandComponent,
194
- getDataId,
195
- contentWithAdditions,
196
- openRows,
197
- actionResolver,
198
- ]);
199
+ const getRows = React.useCallback(
200
+ (data: R[]) =>
201
+ (data || [])
202
+ .map<IRow>((obj) => {
203
+ const id = getDataId(obj);
204
+ const cells = contentWithAdditions.filter((c) => !!c.cell).map((c) => c.cell?.(obj));
205
+ const isOpen = !!openRows[id];
206
+ return {
207
+ isOpen,
208
+ cells,
209
+ key: `${id}-master`,
210
+ id,
211
+ actions: actionResolver ? actionResolver(obj) : undefined,
212
+ nestedComponent: ExpandComponent ? <ExpandComponent obj={obj} /> : undefined,
213
+ };
214
+ })
215
+ .slice((page - 1) * perPage, page * perPage),
216
+ [page, perPage, getDataId, contentWithAdditions, openRows, actionResolver, ExpandComponent],
217
+ );
218
+
219
+ const hostRows = React.useMemo(() => getRows(data), [data, getRows]);
199
220
 
200
221
  const rows = React.useMemo(() => {
201
222
  if (hostRows.length) {
@@ -223,10 +244,13 @@ const AITable = <R extends any>({
223
244
  }, []);
224
245
 
225
246
  const sortedRows = React.useMemo(() => {
247
+ if (alreadySorted && sortBy.index === -1) {
248
+ return getRows(data);
249
+ }
226
250
  return rows.sort(
227
251
  rowSorter(sortBy, (row: IRow, index = 0) => row.cells?.[index] as string | HumanizedSortable),
228
252
  );
229
- }, [rows, sortBy]);
253
+ }, [alreadySorted, sortBy, rows, getRows, data]);
230
254
 
231
255
  return (
232
256
  <>
@@ -60,20 +60,32 @@ type HostsTableProps = ReturnType<typeof usePagination> &
60
60
  | 'variant'
61
61
  > & {
62
62
  hosts: Host[];
63
+ alreadySorted?: boolean;
63
64
  skipDisabled?: boolean;
64
65
  children: React.ReactNode;
65
66
  };
66
67
 
67
- const HostsTable = ({ hosts, skipDisabled, ...rest }: HostsTableProps & WithTestID) => {
68
- const data = React.useMemo(
69
- () =>
70
- (hosts || [])
71
- .filter((host) => !skipDisabled || host.status !== 'disabled')
72
- .sort((a, b) => (a.createdAt && b.createdAt && a.createdAt < b.createdAt ? -1 : 1)),
73
- [hosts, skipDisabled],
74
- );
68
+ const HostsTable = ({
69
+ hosts,
70
+ skipDisabled,
71
+ alreadySorted,
72
+ ...rest
73
+ }: HostsTableProps & WithTestID) => {
74
+ const data = React.useMemo(() => {
75
+ const filteredHosts = (hosts || []).filter(
76
+ (host) => !skipDisabled || host.status !== 'disabled',
77
+ );
78
+
79
+ return alreadySorted
80
+ ? filteredHosts
81
+ : filteredHosts.sort((a, b) =>
82
+ a.createdAt && b.createdAt && a.createdAt < b.createdAt ? -1 : 1,
83
+ );
84
+ }, [hosts, skipDisabled, alreadySorted]);
75
85
 
76
- return <AITable<Host> getDataId={getHostId} data={data} {...rest} />;
86
+ return (
87
+ <AITable<Host> getDataId={getHostId} data={data} alreadySorted={alreadySorted} {...rest} />
88
+ );
77
89
  };
78
90
 
79
91
  export default HostsTable;
@@ -196,7 +196,7 @@ export const getInventory = (host: Host) => {
196
196
 
197
197
  export const filterByHostname = (hosts: Host[], hostnameFilter: string | undefined) => {
198
198
  if (!hostnameFilter) {
199
- return hosts;
199
+ return { hosts, sorted: false };
200
200
  }
201
201
  const hostsWithHostname = hosts.map((host) => {
202
202
  const { inventory: inventoryString = '' } = host;
@@ -209,12 +209,28 @@ export const filterByHostname = (hosts: Host[], hostnameFilter: string | undefin
209
209
  });
210
210
 
211
211
  const fuse = new Fuse(hostsWithHostname, {
212
+ includeScore: true,
212
213
  ignoreLocation: true,
213
214
  threshold: 0.3,
214
215
  keys: ['hostname'],
215
216
  });
216
217
 
217
- return fuse.search(hostnameFilter).map(({ item }) => item.host);
218
+ const filteredHosts = fuse.search(hostnameFilter);
219
+
220
+ const sortedHosts = filteredHosts
221
+ .sort((a, b) => (a.score || 0) - (b.score || 0))
222
+ .map(({ item }) => item.host);
223
+
224
+ const sorted =
225
+ filteredHosts.length > 1 &&
226
+ filteredHosts.some(
227
+ (result, index) => index > 0 && (result.score || 0) !== (filteredHosts[0].score || 0),
228
+ );
229
+
230
+ return {
231
+ hosts: sortedHosts,
232
+ sorted,
233
+ };
218
234
  };
219
235
 
220
236
  export const getFailingHostValidations = (validationsInfo: HostValidationsInfo) =>
@@ -60,13 +60,20 @@ const TextAreaField: React.FC<TextAreaFieldProps> = ({
60
60
  {(errorMessage || helperText) && (
61
61
  <FormHelperText>
62
62
  <HelperText>
63
- <HelperTextItem
64
- icon={errorMessage && <ExclamationCircleIcon />}
65
- variant={errorMessage ? 'error' : 'default'}
66
- id={errorMessage ? `${fieldId}-helper-error` : `${fieldId}-helper`}
67
- >
68
- {errorMessage ? errorMessage : helperText}
69
- </HelperTextItem>
63
+ {errorMessage && (
64
+ <HelperTextItem
65
+ icon={<ExclamationCircleIcon />}
66
+ variant={'error'}
67
+ id={`${fieldId}-helper-error`}
68
+ >
69
+ {errorMessage}
70
+ </HelperTextItem>
71
+ )}
72
+ {helperText && (
73
+ <HelperTextItem variant={'default'} id={`${fieldId}-helper`}>
74
+ {helperText}
75
+ </HelperTextItem>
76
+ )}
70
77
  </HelperText>
71
78
  </FormHelperText>
72
79
  )}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "dependencies": {
3
- "@openshift-assisted/locales": "2.15.2-cim",
4
- "@openshift-assisted/types": "2.15.2-cim",
3
+ "@openshift-assisted/locales": "2.15.4-cim",
4
+ "@openshift-assisted/types": "2.15.4-cim",
5
5
  "@openshift-console/dynamic-plugin-sdk": "0.0.3",
6
6
  "@patternfly/patternfly": "5.2.0",
7
7
  "@patternfly/react-code-editor": "5.2.0",
@@ -140,5 +140,5 @@
140
140
  ]
141
141
  }
142
142
  },
143
- "version": "2.15.2-cim"
143
+ "version": "2.15.4-cim"
144
144
  }
@@ -1,10 +0,0 @@
1
- import React from 'react';
2
- import { OpenshiftVersionOptionType } from '../../types';
3
- import { SelectFieldProps } from '../ui/formik/types';
4
- type OpenShiftVersionSelectProps = {
5
- versions: OpenshiftVersionOptionType[];
6
- onChange?: SelectFieldProps['onChange'];
7
- };
8
- declare const OpenShiftVersionSelect: React.FC<OpenShiftVersionSelectProps>;
9
- export default OpenShiftVersionSelect;
10
- //# sourceMappingURL=OpenShiftVersionSelect.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"OpenShiftVersionSelect.d.ts","sourceRoot":"","sources":["../../../../../lib/common/components/clusterConfiguration/OpenShiftVersionSelect.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAM1B,OAAO,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAGzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AA4CtD,KAAK,2BAA2B,GAAG;IACjC,QAAQ,EAAE,0BAA0B,EAAE,CAAC;IACvC,QAAQ,CAAC,EAAE,gBAAgB,CAAC,UAAU,CAAC,CAAC;CACzC,CAAC;AACF,QAAA,MAAM,sBAAsB,EAAE,KAAK,CAAC,EAAE,CAAC,2BAA2B,CAqBjE,CAAC;AAEF,eAAe,sBAAsB,CAAC"}