@openshift-assisted/ui-lib 2.16.3-cim → 2.16.5-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 (62) hide show
  1. package/build/@types/cim/components/Agent/tableUtils.d.ts +4 -1
  2. package/build/@types/cim/components/Agent/tableUtils.d.ts.map +1 -1
  3. package/build/@types/cim/components/ClusterDeployment/hostDiscovery/ClusterDeploymentHostDiscoveryTable.d.ts.map +1 -1
  4. package/build/@types/cim/components/ClusterDeployment/hostSelection/ClusterDeploymentHostSelectionStep.d.ts.map +1 -1
  5. package/build/@types/cim/components/ClusterDeployment/hostSelection/ClusterDeploymentHostsSelection.d.ts.map +1 -1
  6. package/build/@types/cim/components/ClusterDeployment/types.d.ts +1 -0
  7. package/build/@types/cim/components/ClusterDeployment/types.d.ts.map +1 -1
  8. package/build/@types/cim/components/InfraEnv/InfraEnvAgentTable.d.ts.map +1 -1
  9. package/build/@types/cim/components/InfraEnv/InfraEnvOpenShiftVersionDropdown.d.ts.map +1 -1
  10. package/build/@types/cim/components/InfraEnv/InfraTableToolbar.d.ts +5 -0
  11. package/build/@types/cim/components/InfraEnv/InfraTableToolbar.d.ts.map +1 -1
  12. package/build/@types/cim/components/helpers/agents.d.ts +1 -1
  13. package/build/@types/cim/components/helpers/agents.d.ts.map +1 -1
  14. package/build/@types/cim/components/helpers/toAssisted.d.ts.map +1 -1
  15. package/build/@types/common/components/hosts/tableUtils.d.ts +2 -0
  16. package/build/@types/common/components/hosts/tableUtils.d.ts.map +1 -1
  17. package/build/@types/common/components/hosts/utils.d.ts +3 -0
  18. package/build/@types/common/components/hosts/utils.d.ts.map +1 -1
  19. package/build/cjs/cim/components/Agent/tableUtils.js +31 -3
  20. package/build/cjs/cim/components/Agent/tableUtils.js.map +1 -1
  21. package/build/cjs/cim/components/ClusterDeployment/hostDiscovery/ClusterDeploymentHostDiscoveryTable.js +8 -1
  22. package/build/cjs/cim/components/ClusterDeployment/hostDiscovery/ClusterDeploymentHostDiscoveryTable.js.map +1 -1
  23. package/build/cjs/cim/components/ClusterDeployment/hostSelection/ClusterDeploymentHostSelectionStep.js +1 -1
  24. package/build/cjs/cim/components/ClusterDeployment/hostSelection/ClusterDeploymentHostSelectionStep.js.map +1 -1
  25. package/build/cjs/cim/components/ClusterDeployment/hostSelection/ClusterDeploymentHostsSelection.js +3 -3
  26. package/build/cjs/cim/components/ClusterDeployment/hostSelection/ClusterDeploymentHostsSelection.js.map +1 -1
  27. package/build/cjs/cim/components/InfraEnv/InfraEnvAgentTable.js +5 -2
  28. package/build/cjs/cim/components/InfraEnv/InfraEnvAgentTable.js.map +1 -1
  29. package/build/cjs/cim/components/InfraEnv/InfraEnvOpenShiftVersionDropdown.js +3 -1
  30. package/build/cjs/cim/components/InfraEnv/InfraEnvOpenShiftVersionDropdown.js.map +1 -1
  31. package/build/cjs/cim/components/InfraEnv/InfraTableToolbar.js +25 -3
  32. package/build/cjs/cim/components/InfraEnv/InfraTableToolbar.js.map +1 -1
  33. package/build/cjs/cim/components/helpers/agents.js +4 -1
  34. package/build/cjs/cim/components/helpers/agents.js.map +1 -1
  35. package/build/cjs/cim/components/helpers/toAssisted.js +2 -1
  36. package/build/cjs/cim/components/helpers/toAssisted.js.map +1 -1
  37. package/build/cjs/cim/components/modals/MassApproveAgentModal.js +2 -2
  38. package/build/cjs/cim/components/modals/MassApproveAgentModal.js.map +1 -1
  39. package/build/cjs/common/components/clusterWizard/clusterDetailsValidation.js +7 -7
  40. package/build/cjs/common/components/clusterWizard/clusterDetailsValidation.js.map +1 -1
  41. package/build/cjs/common/components/hosts/tableUtils.js +41 -1
  42. package/build/cjs/common/components/hosts/tableUtils.js.map +1 -1
  43. package/build/cjs/common/components/hosts/utils.js +5 -1
  44. package/build/cjs/common/components/hosts/utils.js.map +1 -1
  45. package/build/cjs/common/components/ui/formik/validationSchemas.js +1 -1
  46. package/build/cjs/common/components/ui/formik/validationSchemas.js.map +1 -1
  47. package/lib/cim/components/Agent/tableUtils.tsx +43 -2
  48. package/lib/cim/components/ClusterDeployment/hostDiscovery/ClusterDeploymentHostDiscoveryTable.tsx +10 -1
  49. package/lib/cim/components/ClusterDeployment/hostSelection/ClusterDeploymentHostSelectionStep.tsx +1 -0
  50. package/lib/cim/components/ClusterDeployment/hostSelection/ClusterDeploymentHostsSelection.tsx +3 -2
  51. package/lib/cim/components/ClusterDeployment/types.ts +1 -0
  52. package/lib/cim/components/InfraEnv/InfraEnvAgentTable.tsx +11 -0
  53. package/lib/cim/components/InfraEnv/InfraEnvOpenShiftVersionDropdown.tsx +11 -9
  54. package/lib/cim/components/InfraEnv/InfraTableToolbar.tsx +71 -1
  55. package/lib/cim/components/helpers/agents.ts +5 -1
  56. package/lib/cim/components/helpers/toAssisted.ts +1 -0
  57. package/lib/cim/components/modals/MassApproveAgentModal.tsx +1 -1
  58. package/lib/common/components/clusterWizard/clusterDetailsValidation.ts +7 -7
  59. package/lib/common/components/hosts/tableUtils.tsx +45 -0
  60. package/lib/common/components/hosts/utils.ts +4 -0
  61. package/lib/common/components/ui/formik/validationSchemas.ts +1 -1
  62. package/package.json +3 -3
@@ -1,5 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { Link } from 'react-router-dom-v5-compat';
3
+ import countBy from 'lodash-es/countBy';
3
4
 
4
5
  import {
5
6
  HostsTableActions,
@@ -25,7 +26,7 @@ import {
25
26
  getBMHStatusKey,
26
27
  getWizardStepAgentStatus,
27
28
  } from '../helpers/status';
28
- import { filterByHostname } from '../../../common/components/hosts/utils';
29
+ import { filterByHostname, getHostLabels } from '../../../common/components/hosts/utils';
29
30
  import { agentStatus } from '../helpers/agentStatus';
30
31
  import noop from 'lodash-es/noop.js';
31
32
  import { useTranslation } from '../../../common/hooks/use-translation-wrapper';
@@ -401,6 +402,7 @@ export const useAgentsTable = (
401
402
  } = tableActions || {};
402
403
  const { t } = useTranslation();
403
404
  const agentStatuses = agentStatus(t);
405
+
404
406
  const [hosts, actions] = React.useMemo(
405
407
  (): [Host[], HostsTableActions] => [
406
408
  getAIHosts(agents, bmhs, infraEnv),
@@ -548,6 +550,22 @@ const filterByStatus = (
548
550
  });
549
551
  };
550
552
 
553
+ const filterByLabels = (hosts: Host[], labelFilter: string[]) => {
554
+ if (!labelFilter.length) {
555
+ return hosts;
556
+ }
557
+
558
+ return hosts.filter((h) => {
559
+ const labels = getHostLabels(h);
560
+
561
+ return labelFilter.some((l) =>
562
+ Object.entries(labels)
563
+ .map(([key, value]) => `${key} = ${value}`)
564
+ .includes(l),
565
+ );
566
+ });
567
+ };
568
+
551
569
  export const filterHosts = (
552
570
  hosts: Host[],
553
571
  agents: AgentK8sResource[],
@@ -555,11 +573,26 @@ export const filterHosts = (
555
573
  bareMetalHosts: BareMetalHostK8sResource[],
556
574
  hostnameFilter: string | undefined,
557
575
  statusFilter: string[],
576
+ labelFilter: string[],
558
577
  ) => {
559
578
  const { hosts: byHostname, sorted } = filterByHostname(hosts, hostnameFilter);
560
579
  const byStatus = filterByStatus(byHostname, agents, agentStatuses, bareMetalHosts, statusFilter);
580
+ const byLabels = filterByLabels(byStatus, labelFilter);
561
581
 
562
- return { hosts: byStatus, sorted };
582
+ return { hosts: byLabels, sorted };
583
+ };
584
+
585
+ const getAllLabels = (hosts: Host[]) => {
586
+ const labelArray = hosts
587
+ .map((host) => {
588
+ const labels = getHostLabels(host);
589
+
590
+ return Object.entries(labels).map(([key, val]) => `${key} = ${val}`);
591
+ })
592
+ .flat()
593
+ .sort();
594
+
595
+ return countBy(labelArray);
563
596
  };
564
597
 
565
598
  type UseAgentsFilterArgs = {
@@ -581,10 +614,12 @@ export const useAgentsFilter = ({
581
614
  }: UseAgentsFilterArgs) => {
582
615
  const [hostnameFilter, setHostnameFilter] = React.useState<string>();
583
616
  const [statusFilter, setStatusFilter] = React.useState<string[]>([]);
617
+ const [labelFilter, setLabelFilter] = React.useState<string[]>([]);
584
618
  const statusCount = Object.keys(agentStatuses).reduce((acc, curr) => {
585
619
  acc[agentStatuses[curr].title] = 0;
586
620
  return acc;
587
621
  }, {} as StatusCount);
622
+
588
623
  hosts.forEach((host) => {
589
624
  const agent = agents.find((a) => a.metadata?.uid === host.id);
590
625
  const bmh = bmhs.find((bmh) => bmh.metadata?.uid === host.id);
@@ -597,6 +632,8 @@ export const useAgentsFilter = ({
597
632
  }
598
633
  });
599
634
 
635
+ const hostLabels = getAllLabels(hosts);
636
+
600
637
  const { hosts: filteredHosts, sorted } = filterHosts(
601
638
  hosts,
602
639
  agents,
@@ -604,6 +641,7 @@ export const useAgentsFilter = ({
604
641
  bmhs,
605
642
  hostnameFilter,
606
643
  statusFilter,
644
+ labelFilter,
607
645
  );
608
646
 
609
647
  return {
@@ -612,6 +650,9 @@ export const useAgentsFilter = ({
612
650
  setHostnameFilter,
613
651
  statusFilter,
614
652
  setStatusFilter,
653
+ hostLabels,
654
+ labelFilter,
655
+ setLabelFilter,
615
656
  filteredHosts,
616
657
  sorted,
617
658
  };
@@ -15,7 +15,9 @@ import {
15
15
  cpuCoresColumn,
16
16
  discoveredAtColumn,
17
17
  disksColumn,
18
+ gpusColumn,
18
19
  hostnameColumn,
20
+ labelsColumn,
19
21
  memoryColumn,
20
22
  roleColumn,
21
23
  } from '../../../../common/components/hosts/tableUtils';
@@ -93,7 +95,14 @@ const ClusterDeploymentHostDiscoveryTable: React.FC<ClusterDeploymentHostDiscove
93
95
  }),
94
96
  roleColumn(t, hostActions.canEditRole, hostActions.onEditRole),
95
97
  ...(addAll
96
- ? [discoveredAtColumn(t), cpuCoresColumn(t), memoryColumn(t), disksColumn(t)]
98
+ ? [
99
+ discoveredAtColumn(t),
100
+ cpuCoresColumn(t),
101
+ gpusColumn(t),
102
+ memoryColumn(t),
103
+ disksColumn(t),
104
+ labelsColumn(t),
105
+ ]
97
106
  : []),
98
107
  ],
99
108
  [
@@ -313,6 +313,7 @@ const HostSelectionForm: React.FC<HostSelectionFormProps> = ({
313
313
  onAutoSelectChange={onAutoSelectChange}
314
314
  onHostSelect={onHostSelect}
315
315
  isNutanix={isNutanix}
316
+ hostsBinding={nextRequested && !showClusterErrors}
316
317
  />
317
318
  </GridItem>
318
319
  {(showClusterErrors || showFormErrors) && !!alerts.length && (
@@ -42,6 +42,7 @@ const ClusterDeploymentHostsSelection: React.FC<ClusterDeploymentHostsSelectionP
42
42
  onHostSelect,
43
43
  onSetInstallationDiskId,
44
44
  isNutanix,
45
+ hostsBinding,
45
46
  }) => {
46
47
  const { t } = useTranslation();
47
48
  const { values } = useFormikContext<ClusterDeploymentHostsSelectionValues>();
@@ -54,7 +55,7 @@ const ClusterDeploymentHostsSelection: React.FC<ClusterDeploymentHostsSelectionP
54
55
  const cpuArchitecture = getClusterDeploymentCpuArchitecture(clusterDeployment);
55
56
 
56
57
  const availableAgents = React.useMemo(() => {
57
- const filtered = getAgentsForSelection(agents).filter(
58
+ const filtered = getAgentsForSelection(agents, hostsBinding).filter(
58
59
  (agent) =>
59
60
  (agent.spec.clusterDeploymentName?.name === cdName &&
60
61
  agent.spec.clusterDeploymentName?.namespace === cdNamespace) ||
@@ -66,7 +67,7 @@ const ClusterDeploymentHostsSelection: React.FC<ClusterDeploymentHostsSelectionP
66
67
  return isNutanix
67
68
  ? filtered.filter((a) => a.status?.inventory?.systemVendor?.manufacturer === 'Nutanix')
68
69
  : filtered;
69
- }, [agents, cdNamespace, cdName, cpuArchitecture, isNutanix]);
70
+ }, [agents, hostsBinding, isNutanix, cdName, cdNamespace, cpuArchitecture]);
70
71
 
71
72
  const hostRequirementText = getHostRequirementText(isSNOCluster, isTNA, t);
72
73
 
@@ -178,6 +178,7 @@ export type ClusterDeploymentHostsSelectionProps = {
178
178
  onAutoSelectChange: VoidFunction;
179
179
  onHostSelect: VoidFunction;
180
180
  isNutanix: boolean;
181
+ hostsBinding?: boolean;
181
182
  };
182
183
 
183
184
  export type InfraEnvAgentTableProps = Pick<
@@ -22,7 +22,9 @@ import {
22
22
  cpuCoresColumn,
23
23
  discoveredAtColumn,
24
24
  disksColumn,
25
+ gpusColumn,
25
26
  memoryColumn,
27
+ labelsColumn,
26
28
  } from '../../../common/components/hosts/tableUtils';
27
29
  import {
28
30
  DiscoveryTroubleshootingModal,
@@ -122,6 +124,9 @@ const InfraEnvAgentTable: React.FC<InfraEnvAgentTableProps> = ({
122
124
  setHostnameFilter,
123
125
  setStatusFilter,
124
126
  statusFilter,
127
+ labelFilter,
128
+ setLabelFilter,
129
+ hostLabels,
125
130
  filteredHosts: hosts,
126
131
  sorted,
127
132
  } = useAgentsFilter({
@@ -157,8 +162,10 @@ const InfraEnvAgentTable: React.FC<InfraEnvAgentTableProps> = ({
157
162
  clusterColumn(agents, agentMachines, getClusterDeploymentLink, t),
158
163
  discoveredAtColumn(t),
159
164
  cpuCoresColumn(t),
165
+ gpusColumn(t),
160
166
  memoryColumn(t),
161
167
  disksColumn(t),
168
+ labelsColumn(t),
162
169
  ].filter(Boolean),
163
170
  [
164
171
  t,
@@ -219,6 +226,9 @@ const InfraEnvAgentTable: React.FC<InfraEnvAgentTableProps> = ({
219
226
  hostnameFilter={hostnameFilter}
220
227
  statusFilter={statusFilter}
221
228
  setStatusFilter={setStatusFilter}
229
+ hostLabels={hostLabels}
230
+ labelFilter={labelFilter}
231
+ setLabelFilter={setLabelFilter}
222
232
  selectedHostIDs={selectedHostIDs}
223
233
  setHostnameFilter={setHostnameFilter}
224
234
  {...paginationProps}
@@ -241,6 +251,7 @@ const InfraEnvAgentTable: React.FC<InfraEnvAgentTableProps> = ({
241
251
  onClearFilters={() => {
242
252
  setHostnameFilter(undefined);
243
253
  setStatusFilter([]);
254
+ setLabelFilter([]);
244
255
  }}
245
256
  />
246
257
  ) : (
@@ -65,15 +65,17 @@ const InfraEnvOpenShiftVersionDropdown = ({ osImages }: { osImages: OsImage[] })
65
65
  onOpenChange={() => setOsImageOpen(!osImageOpen)}
66
66
  >
67
67
  <DropdownList>
68
- {filteredImages.map((image) => (
69
- <DropdownItem
70
- key={image.openshiftVersion}
71
- id={image.openshiftVersion}
72
- value={image.openshiftVersion}
73
- >
74
- {`OpenShift ${image.openshiftVersion}`}
75
- </DropdownItem>
76
- ))}
68
+ {filteredImages
69
+ .sort((a, b) => b.openshiftVersion.localeCompare(a.openshiftVersion))
70
+ .map((image) => (
71
+ <DropdownItem
72
+ key={image.openshiftVersion}
73
+ id={image.openshiftVersion}
74
+ value={image.openshiftVersion}
75
+ >
76
+ {`OpenShift ${image.openshiftVersion}`}
77
+ </DropdownItem>
78
+ ))}
77
79
  </DropdownList>
78
80
  </Dropdown>
79
81
  </Tooltip>
@@ -66,11 +66,14 @@ type InfraTableToolbarProps = ReturnType<typeof usePagination> & {
66
66
  setSelectedHostIDs: (items: string[]) => void;
67
67
  massActions: React.ReactNode[];
68
68
  hosts: Host[];
69
+ hostLabels: { [key in string]: number };
69
70
  statusFilter: string[];
71
+ labelFilter: string[];
70
72
  statusCount: StatusCount;
71
73
  hostnameFilter: string | undefined;
72
74
  setHostnameFilter: (hostname: string | undefined) => void;
73
75
  setStatusFilter: (status: string[]) => void;
76
+ setLabelFilter: (label: string[]) => void;
74
77
  selectedHostIDs: string[];
75
78
  };
76
79
 
@@ -78,17 +81,22 @@ const InfraTableToolbar: React.FC<InfraTableToolbarProps> = ({
78
81
  setSelectedHostIDs,
79
82
  massActions,
80
83
  hosts,
84
+ hostLabels,
81
85
  statusFilter,
86
+ labelFilter,
82
87
  statusCount,
83
88
  hostnameFilter,
84
89
  setHostnameFilter,
85
90
  setStatusFilter,
91
+ setLabelFilter,
86
92
  selectedHostIDs,
87
93
  ...paginationProps
88
94
  }) => {
89
95
  const { t } = useTranslation();
90
96
  const agentStatuses = agentStatus(t);
91
97
  const [statusFilterOpen, setStatusFilterOpen] = React.useState(false);
98
+ const [labelFilterOpen, setLabelFilterOpen] = React.useState(false);
99
+
92
100
  const itemIDs = React.useMemo(() => hosts.map((h) => h.id), [hosts]);
93
101
  const filterStatuses = React.useMemo(() => getStatusesForFilter(agentStatuses), [agentStatuses]);
94
102
  const categoryLabels = getCategoryLabels(t);
@@ -106,13 +114,29 @@ const InfraTableToolbar: React.FC<InfraTableToolbarProps> = ({
106
114
  }
107
115
  };
108
116
 
117
+ const onLabelFilterToggle = () => setLabelFilterOpen(!labelFilterOpen);
118
+ const onLabelFilterSelect = (
119
+ e?: React.MouseEvent<Element, MouseEvent>,
120
+ value?: string | number,
121
+ ) => {
122
+ if (!labelFilter?.includes(value as string)) {
123
+ const newItems = (labelFilter ? [...labelFilter, value] : [value]) as string[];
124
+ setLabelFilter(newItems);
125
+ } else {
126
+ setLabelFilter(labelFilter?.filter((f) => f !== value));
127
+ }
128
+ };
129
+
109
130
  return (
110
131
  <TableToolbar
111
132
  selectedIDs={selectedHostIDs || []}
112
133
  itemIDs={itemIDs}
113
134
  setSelectedIDs={setSelectedHostIDs}
114
135
  actions={massActions}
115
- clearAllFilters={() => setStatusFilter([])}
136
+ clearAllFilters={() => {
137
+ setStatusFilter([]);
138
+ setLabelFilter([]);
139
+ }}
116
140
  {...paginationProps}
117
141
  >
118
142
  <ToolbarItem>
@@ -123,6 +147,7 @@ const InfraTableToolbar: React.FC<InfraTableToolbarProps> = ({
123
147
  onClear={() => setHostnameFilter(undefined)}
124
148
  />
125
149
  </ToolbarItem>
150
+
126
151
  <ToolbarItem>
127
152
  <ToolbarFilter
128
153
  chips={statusFilter}
@@ -175,6 +200,51 @@ const InfraTableToolbar: React.FC<InfraTableToolbarProps> = ({
175
200
  </Dropdown>
176
201
  </ToolbarFilter>
177
202
  </ToolbarItem>
203
+
204
+ <ToolbarItem>
205
+ <ToolbarFilter
206
+ chips={labelFilter}
207
+ deleteChip={(_, chip) => setLabelFilter(labelFilter.filter((f) => f !== chip))}
208
+ deleteChipGroup={() => setLabelFilter([])}
209
+ categoryName={'Labels'}
210
+ >
211
+ <Dropdown
212
+ isOpen={labelFilterOpen}
213
+ onSelect={onLabelFilterSelect}
214
+ onOpenChange={onLabelFilterToggle}
215
+ shouldFocusToggleOnSelect
216
+ toggle={(toggleRef: React.Ref<MenuToggleElement>) => (
217
+ <MenuToggle
218
+ ref={toggleRef}
219
+ isFullWidth
220
+ onClick={onLabelFilterToggle}
221
+ isExpanded={labelFilterOpen}
222
+ icon={<FilterIcon />}
223
+ >
224
+ {t('ai:Labels')}
225
+ </MenuToggle>
226
+ )}
227
+ >
228
+ <DropdownGroup label={t('ai:Labels')}>
229
+ {Object.entries(hostLabels).map(([label, count]) => (
230
+ <DropdownItem
231
+ hasCheckbox
232
+ key={`host-label-${label}`}
233
+ value={label}
234
+ isSelected={labelFilter?.includes(label)}
235
+ >
236
+ <Split hasGutter>
237
+ <SplitItem>{label}</SplitItem>
238
+ <SplitItem>
239
+ <Badge isRead>{count || 0}</Badge>
240
+ </SplitItem>
241
+ </Split>
242
+ </DropdownItem>
243
+ ))}
244
+ </DropdownGroup>
245
+ </Dropdown>
246
+ </ToolbarFilter>
247
+ </ToolbarItem>
178
248
  </TableToolbar>
179
249
  );
180
250
  };
@@ -18,9 +18,13 @@ const AGENT_FOR_SELECTION_STATUSES: AgentStatus[] = [
18
18
  export const hostToAgent = (agents: AgentK8sResource[] = [], host: Host) =>
19
19
  agents.find((a) => a.metadata?.uid === host.id) as AgentK8sResource;
20
20
 
21
- export const getAgentsForSelection = (agents: AgentK8sResource[]) =>
21
+ export const getAgentsForSelection = (agents: AgentK8sResource[], isSubmitting?: boolean) =>
22
22
  agents.filter((agent) => {
23
23
  const key = getAgentStatusKey(agent);
24
+ if (isSubmitting) {
25
+ return [...AGENT_FOR_SELECTION_STATUSES, 'specSyncErr'].includes(key as AgentStatus);
26
+ }
27
+
24
28
  return AGENT_FOR_SELECTION_STATUSES.includes(key as AgentStatus);
25
29
  });
26
30
 
@@ -75,6 +75,7 @@ export const getAIHosts = (
75
75
  infraEnvId: `${agent.metadata?.namespace || ''}/${
76
76
  agent.metadata?.labels?.['infraenvs.agent-install.openshift.io'] || ''
77
77
  }`,
78
+ nodeLabels: JSON.stringify(agent.metadata?.labels || {}),
78
79
  };
79
80
  });
80
81
 
@@ -41,7 +41,7 @@ const hostnameColumn = (agents: AgentK8sResource[], t: TFunction): TableRow<Host
41
41
  },
42
42
  cell: (host) => {
43
43
  const agent = agents.find((a) => a.metadata?.uid === host.id);
44
- const hostname = agent?.spec.hostname || agent?.status?.inventory.hostname;
44
+ const hostname = agent?.spec?.hostname || agent?.status?.inventory?.hostname;
45
45
  return {
46
46
  title: <ApproveTableRow agent={agent}>{hostname}</ApproveTableRow>,
47
47
  props: { 'data-testid': 'hostname' },
@@ -104,16 +104,16 @@ export const getClusterDetailsValidationSchema = ({
104
104
  return Yup.object({
105
105
  name: validateName(),
106
106
  baseDnsDomain: isOcm
107
- ? baseDomainValidationSchema.required('Required')
108
- : dnsNameValidationSchema.required('Required'),
107
+ ? baseDomainValidationSchema.required(t('ai:Required field'))
108
+ : dnsNameValidationSchema.required(t('ai:Required field')),
109
109
  });
110
110
  }
111
111
  return Yup.object({
112
112
  name: validateName(),
113
113
  baseDnsDomain: isOcm
114
- ? baseDomainValidationSchema.required('Required')
115
- : dnsNameValidationSchema.required('Required'),
116
- pullSecret: pullSecretValidationSchema.required('Required.'),
114
+ ? baseDomainValidationSchema.required(t('ai:Required field'))
115
+ : dnsNameValidationSchema.required(t('ai:Required field')),
116
+ pullSecret: pullSecretValidationSchema.required(t('ai:Required field')),
117
117
  diskEncryptionTangServers: Yup.array().when('diskEncryptionMode', {
118
118
  is: (diskEncryptionMode: DiskEncryption['mode']) => {
119
119
  return diskEncryptionMode === 'tang';
@@ -125,8 +125,8 @@ export const getClusterDetailsValidationSchema = ({
125
125
  .url(
126
126
  t('ai:Tang Server URL must be a valid URL starting with "http://" or "https://"'),
127
127
  )
128
- .required(t('ai:Required.')),
129
- thumbprint: Yup.string().required(t('ai:Required.')),
128
+ .required(t('ai:Required field')),
129
+ thumbprint: Yup.string().required(t('ai:Required field')),
130
130
  }),
131
131
  ),
132
132
  }),
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react';
2
+ import { Label, LabelGroup } from '@patternfly/react-core';
2
3
  import { Address4, Address6 } from 'ip-address';
3
4
  import type {
4
5
  Cluster,
@@ -20,6 +21,7 @@ import HostStatus from './HostStatus';
20
21
  import RoleCell from './RoleCell';
21
22
  import {
22
23
  areOnlySoftValidationsFailing,
24
+ getHostLabels,
23
25
  getHostname,
24
26
  getHostRole,
25
27
  getHostStatus,
@@ -280,6 +282,24 @@ export const disksColumn = (t: TFunction): TableRow<Host> => ({
280
282
  },
281
283
  });
282
284
 
285
+ export const gpusColumn = (t: TFunction): TableRow<Host> => ({
286
+ header: {
287
+ title: t('ai:GPUs'),
288
+ props: {
289
+ id: 'col-header-gpus',
290
+ },
291
+ sort: true,
292
+ },
293
+ cell: (host) => {
294
+ const { gpus } = getInventory(host);
295
+ return {
296
+ title: <>{gpus?.length ?? '--'}</>,
297
+ props: { 'data-testid': 'host-gpus' },
298
+ sortableValue: gpus?.length ?? 0,
299
+ };
300
+ },
301
+ });
302
+
283
303
  export const countColumn = (cluster: Cluster): TableRow<Host> => ({
284
304
  header: { title: <HostsCount cluster={cluster} inParenthesis /> },
285
305
  });
@@ -363,6 +383,31 @@ export const macAddressColumn = (cluster: Cluster): TableRow<Host> => ({
363
383
  },
364
384
  });
365
385
 
386
+ export const labelsColumn = (t: TFunction): TableRow<Host> => ({
387
+ header: {
388
+ title: t('ai:Labels'),
389
+ props: {
390
+ id: 'col-header-labels',
391
+ },
392
+ sort: false,
393
+ },
394
+ cell: (host) => {
395
+ const labels = getHostLabels(host);
396
+ return {
397
+ title: (
398
+ <LabelGroup>
399
+ {Object.entries(labels).map(([key, value], index) => (
400
+ <Label key={`${host.id}-host-label-${index}`} isCompact>
401
+ {key} = {value}
402
+ </Label>
403
+ ))}
404
+ </LabelGroup>
405
+ ),
406
+ props: { 'data-testid': 'host-labels' },
407
+ };
408
+ },
409
+ });
410
+
366
411
  const ActionTitle: React.FC<{ disabled: boolean; description?: string; title: string }> = ({
367
412
  title,
368
413
  description,
@@ -205,6 +205,10 @@ export const getInventory = (host: Host) => {
205
205
  return stringToJSON<Inventory>(inventoryString) || {};
206
206
  };
207
207
 
208
+ export const getHostLabels = (host: Host) => {
209
+ return host.nodeLabels ? (JSON.parse(host.nodeLabels) as { [key: string]: string }) : {};
210
+ };
211
+
208
212
  export const filterByHostname = (hosts: Host[], hostnameFilter: string | undefined) => {
209
213
  if (!hostnameFilter) {
210
214
  return { hosts, sorted: false };
@@ -57,7 +57,7 @@ export const nameValidationSchema = (
57
57
  ) => {
58
58
  const clusterNameValidationMessagesList = clusterNameValidationMessages(t);
59
59
  return Yup.string()
60
- .required('Required')
60
+ .required(t('ai:Required field'))
61
61
  .matches(CLUSTER_NAME_VALID_CHARS_REGEX, {
62
62
  message: clusterNameValidationMessagesList.INVALID_VALUE,
63
63
  excludeEmptyString: true,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "dependencies": {
3
- "@openshift-assisted/locales": "2.16.3-cim",
4
- "@openshift-assisted/types": "2.16.3-cim",
3
+ "@openshift-assisted/locales": "2.16.5-cim",
4
+ "@openshift-assisted/types": "2.16.5-cim",
5
5
  "@openshift-console/dynamic-plugin-sdk": "1.0.0",
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.16.3-cim"
143
+ "version": "2.16.5-cim"
144
144
  }