Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion frontend/packages/console-app/locales/en/console-app.json
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,21 @@
"Mark as unschedulable": "Mark as unschedulable",
"Unschedulable nodes won't accept new pods. This is useful for scheduling maintenance or preparing to decommission a node.": "Unschedulable nodes won't accept new pods. This is useful for scheduling maintenance or preparing to decommission a node.",
"Mark unschedulable": "Mark unschedulable",
"Edit groups": "Edit groups",
"Groups help you organize and select resources.": "Groups help you organize and select resources.",
"No existing nodes": "No existing nodes",
"Groups can only be created when there are existing nodes": "Groups can only be created when there are existing nodes",
"To get started, add a group": "To get started, add a group",
"Add new group": "Add new group",
"Enter a group name, then press return, space, or comma": "Enter a group name, then press return, space, or comma",
"Add": "Add",
"Nodes for group": "Nodes for group",
"Select a group above": "Select a group above",
"Groups have been updated.": "Groups have been updated.",
"Click reload to see the changes.": "Click reload to see the changes.",
"Error occurred": "Error occurred",
"Reload": "Reload",
"Groups for": "Groups for",
"View events": "View events",
"Activity": "Activity",
"Details": "Details",
Expand All @@ -360,6 +375,7 @@
"Not available": "Not available",
"Node addresses": "Node addresses",
"Uptime": "Uptime",
"Edit": "Edit",
"Inventory": "Inventory",
"Image": "Image",
"Images": "Images",
Expand Down Expand Up @@ -452,7 +468,6 @@
"New identity provider added.": "New identity provider added.",
"Authentication is being reconfigured. The new identity provider will be available once reconfiguration is complete.": "Authentication is being reconfigured. The new identity provider will be available once reconfiguration is complete.",
"View authentication conditions for reconfiguration status.": "View authentication conditions for reconfiguration status.",
"Add": "Add",
"Min available {{minAvailable}}": "Min available {{minAvailable}}",
"Max unavailable {{maxUnavailable}}": "Max unavailable {{maxUnavailable}}",
"Min available {{minAvailable}} of {{count}} pod_one": "Min available {{minAvailable}} of {{count}} pod",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { NodeKind } from '@console/dynamic-plugin-sdk/src';

export const GROUP_ANNOTATION = 'node.openshift.io/group';
export const GROUP_SEPARATOR = ',';

export type GroupNameMap = Record<string, string[]>;

export const getGroupsFromGroupLabel = (groupLabel?: string): string[] =>
groupLabel
?.split(GROUP_SEPARATOR)
.map((g) => g.trim())
.filter((g) => g.length > 0) ?? [];

export const getNodeGroupLabelFromGroups = (groups: string[]): string =>
groups.join(GROUP_SEPARATOR);

export const getNodeGroups = (node: NodeKind): string[] =>
getGroupsFromGroupLabel(node.metadata.annotations?.[GROUP_ANNOTATION]);

export const getExistingGroups = (nodes: NodeKind[]): string[] => {
const uniqueGroups = new Set<string>();
nodes.forEach((node) => {
getNodeGroups(node).forEach((group) => {
uniqueGroups.add(group);
});
});
return Array.from(uniqueGroups).sort((a, b) => a.localeCompare(b));
};

export const getGroupsByNameFromNodes = (nodes: NodeKind[]): GroupNameMap => {
const updatedGroupsByName: GroupNameMap = {};

nodes.forEach((node) => {
const groupNames = getNodeGroups(node);
groupNames.forEach((groupName) => {
const existingGroup = updatedGroupsByName[groupName] ?? [];
updatedGroupsByName[groupName] = [...existingGroup, node.metadata.name];
});
});
return updatedGroupsByName;
};

export const getNodeGroupLabelFromGroupNameMap = (
nodeName: string,
groupsByName: GroupNameMap,
): string | undefined => {
const groups = Object.keys(groupsByName).filter((key) => groupsByName[key].includes(nodeName));
return groups.length ? getNodeGroupLabelFromGroups(groups) : undefined;
};
39 changes: 38 additions & 1 deletion frontend/packages/console-app/src/components/nodes/NodesPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { FC } from 'react';
import { useMemo, useCallback, useEffect, Suspense } from 'react';
import { Button, ButtonVariant } from '@patternfly/react-core';
import { DataViewCheckboxFilter } from '@patternfly/react-data-view';
import { DataViewFilterOption } from '@patternfly/react-data-view/dist/cjs/DataViewFilters';
import * as _ from 'lodash';
Expand All @@ -17,12 +18,15 @@ import {
ConsoleDataViewRow,
ResourceFilters,
} from '@console/app/src/components/data-view/types';
import GroupsEditorModal from '@console/app/src/components/nodes/modals/GroupsEditorModal';
import { getNodeGroups } from '@console/app/src/components/nodes/NodeGroupUtils';
import {
getGroupVersionKindForResource,
K8sModel,
ListPageBody,
useAccessReview,
useFlag,
useOverlay,
} from '@console/dynamic-plugin-sdk/src/api/dynamic-core-api';
import {
K8sGroupVersionKind,
Expand Down Expand Up @@ -115,6 +119,9 @@ const nodeColumnInfo = Object.freeze({
status: {
id: 'status',
},
groups: {
id: 'groups',
},
machineOwner: {
id: 'machineOwner',
},
Expand Down Expand Up @@ -188,6 +195,13 @@ const useNodesColumns = (vmsEnabled: boolean): TableColumn<NodeRowItem>[] => {
modifier: 'nowrap',
},
},
{
title: t('console-app~Groups'),
id: nodeColumnInfo.groups.id,
props: {
modifier: 'nowrap',
},
},
{
title: t('console-app~Machine set'),
id: nodeColumnInfo.machineOwner.id,
Expand Down Expand Up @@ -357,6 +371,10 @@ const CPUCell: FC<{ cores: number; totalCores: number }> = ({ cores, totalCores
);
};

const GroupsCell: FC<{ node: NodeKind }> = ({ node }) => (
<>{getNodeGroups(node).sort().join(', ') || DASH}</>
);

const getNodeDataViewRows = (
rowData: RowProps<NodeRowItem, GetNodeStatusExtensions>[],
tableColumns: ConsoleDataViewColumn<NodeRowItem>[],
Expand Down Expand Up @@ -420,6 +438,9 @@ const getNodeDataViewRows = (
/>
),
},
[nodeColumnInfo.groups.id]: {
cell: node ? <GroupsCell node={node} /> : DASH,
},
[nodeColumnInfo.role.id]: {
cell: node ? <NodeRoles node={node} /> : DASH,
},
Expand Down Expand Up @@ -862,6 +883,7 @@ const useWatchResourcesIfAllowed = <R extends K8sResourceCommon[]>(
export const NodesPage: FC<NodesPageProps> = ({ selector }) => {
const dispatch = useDispatch();
const { t } = useTranslation();
const launchOverlay = useOverlay();

const [selectedColumns, , userSettingsLoaded] = useUserSettingsCompatibility<TableColumnsType>(
COLUMN_MANAGEMENT_CONFIGMAP_KEY,
Expand All @@ -870,6 +892,12 @@ export const NodesPage: FC<NodesPageProps> = ({ selector }) => {
true,
);

const [canEdit, isEditLoading] = useAccessReview({
group: NodeModel.apiGroup || '',
resource: NodeModel.plural,
verb: 'patch',
});

const [nodes, nodesLoaded, nodesLoadError] = useK8sWatchResource<NodeKind[]>({
groupVersionKind: {
kind: 'Node',
Expand Down Expand Up @@ -1009,7 +1037,16 @@ export const NodesPage: FC<NodesPageProps> = ({ selector }) => {

return (
<>
<ListPageHeader title={t('public~Nodes')} />
<ListPageHeader title={t('public~Nodes')}>
{!isEditLoading && canEdit ? (
<Button
variant={ButtonVariant.secondary}
onClick={() => launchOverlay(GroupsEditorModal, {})}
>
{t('console-app~Edit groups')}
</Button>
) : null}
</ListPageHeader>
<ListPageBody>
<NodeList
data={data}
Expand Down
Loading