From 06bed2c268db1ad1a26d8649a7bc768dd338ee6d Mon Sep 17 00:00:00 2001 From: logonoff Date: Tue, 25 Aug 2026 10:25:04 -0400 Subject: [PATCH] RFE-9146: Docs followup for service account impersonation Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../console-app/locales/en/console-app.json | 4 +-- .../src/actions/hooks/useGroupActions.ts | 4 +-- .../modals/add-group-users-modal.tsx | 2 +- ...mpersonate-user-modal-integration.spec.tsx | 6 ++++ .../__tests__/impersonate-user-modal.spec.tsx | 2 ++ .../modals/impersonate-user-modal.tsx | 30 ++++++++++++++----- frontend/public/components/secret.tsx | 2 +- .../components/secrets/create-secret/types.ts | 2 +- frontend/public/locales/en/public.json | 12 ++++---- 9 files changed, 44 insertions(+), 20 deletions(-) diff --git a/frontend/packages/console-app/locales/en/console-app.json b/frontend/packages/console-app/locales/en/console-app.json index 61ddd94c053..bc3aa5288fa 100644 --- a/frontend/packages/console-app/locales/en/console-app.json +++ b/frontend/packages/console-app/locales/en/console-app.json @@ -43,7 +43,7 @@ "Add shared applications, services, event sources, or source-to-image builders to your project. Cluster administrators can customize the content made available in the catalog.": "Add shared applications, services, event sources, or source-to-image builders to your project. Cluster administrators can customize the content made available in the catalog.", "Add storage": "Add storage", "Add to favorites": "Add to favorites", - "Add Users": "Add Users", + "Add users": "Add users", "Address": "Address", "Admin secret name": "Admin secret name", "Admin secret namespace": "Admin secret namespace", @@ -353,7 +353,7 @@ "Images": "Images", "ImageStreams": "ImageStreams", "Impersonate {{kind}} \"{{name}}\"": "Impersonate {{kind}} \"{{name}}\"", - "Impersonate Group {{name}}": "Impersonate Group {{name}}", + "Impersonate group {{name}}": "Impersonate group {{name}}", "Impersonate service account {{name}}": "Impersonate service account {{name}}", "Impersonate user {{name}}": "Impersonate user {{name}}", "In progress": "In progress", diff --git a/frontend/packages/console-app/src/actions/hooks/useGroupActions.ts b/frontend/packages/console-app/src/actions/hooks/useGroupActions.ts index 92ac0637376..064c45878fb 100644 --- a/frontend/packages/console-app/src/actions/hooks/useGroupActions.ts +++ b/frontend/packages/console-app/src/actions/hooks/useGroupActions.ts @@ -41,7 +41,7 @@ export const useGroupActions = (obj: GroupKind): Action[] => { }), impersonate: (): Action => ({ id: 'impersonate-group', - label: t('Impersonate Group {{name}}', { name: obj?.metadata?.name }), + label: t('Impersonate group {{name}}', { name: obj?.metadata?.name }), cta: () => { startImpersonate('Group', obj?.metadata?.name); navigate(window.SERVER_FLAGS.basePath); @@ -50,7 +50,7 @@ export const useGroupActions = (obj: GroupKind): Action[] => { }), addUsers: (): Action => ({ id: 'add-users', - label: t('Add Users'), + label: t('Add users'), cta: () => launchOverlay(AddGroupUsersModal, { group: obj }), accessReview: asAccessReview(GroupModel, obj, 'patch'), }), diff --git a/frontend/packages/console-app/src/components/modals/add-group-users-modal.tsx b/frontend/packages/console-app/src/components/modals/add-group-users-modal.tsx index 5cbbe53ae20..12311a8a90d 100644 --- a/frontend/packages/console-app/src/components/modals/add-group-users-modal.tsx +++ b/frontend/packages/console-app/src/components/modals/add-group-users-modal.tsx @@ -52,7 +52,7 @@ const AddGroupUsersModal: OverlayComponent = ({ group, return ( - + {!group?.metadata?.name ? ( diff --git a/frontend/public/components/modals/__tests__/impersonate-user-modal-integration.spec.tsx b/frontend/public/components/modals/__tests__/impersonate-user-modal-integration.spec.tsx index 20641d7d357..850531b327c 100644 --- a/frontend/public/components/modals/__tests__/impersonate-user-modal-integration.spec.tsx +++ b/frontend/public/components/modals/__tests__/impersonate-user-modal-integration.spec.tsx @@ -7,6 +7,7 @@ import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { Provider } from 'react-redux'; import { createStore } from 'redux'; +import { ProjectModel } from '@console/dynamic-plugin-sdk/src/models'; import * as UIActions from '../../../actions/ui'; import type { GroupKind } from '../../../module/k8s'; import { useK8sWatchResource } from '../../utils/k8s-watch-hook'; @@ -22,6 +23,11 @@ jest.mock('../../../actions/ui', () => ({ stopImpersonate: jest.fn(), })); +jest.mock('../../utils/list-dropdown', () => ({ + ...jest.requireActual('../../utils/list-dropdown'), + useProjectOrNamespaceModel: () => [ProjectModel, true], +})); + const mockGroups: GroupKind[] = [ { apiVersion: 'user.openshift.io/v1', diff --git a/frontend/public/components/modals/__tests__/impersonate-user-modal.spec.tsx b/frontend/public/components/modals/__tests__/impersonate-user-modal.spec.tsx index 3bb8a2b37f1..c3fff12a8c7 100644 --- a/frontend/public/components/modals/__tests__/impersonate-user-modal.spec.tsx +++ b/frontend/public/components/modals/__tests__/impersonate-user-modal.spec.tsx @@ -1,5 +1,6 @@ import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { ProjectModel } from '@console/dynamic-plugin-sdk/src/models'; import type { GroupKind } from '../../../module/k8s'; import { useK8sWatchResource } from '../../utils/k8s-watch-hook'; import { ImpersonateUserModal } from '../impersonate-user-modal'; @@ -28,6 +29,7 @@ jest.mock('../../utils/list-dropdown', () => ({ {selectedKey || 'Select project'} ), + useProjectOrNamespaceModel: () => [ProjectModel, true] as const, })); // Stub ResourceDropdown: emits a fixed service account selection on click diff --git a/frontend/public/components/modals/impersonate-user-modal.tsx b/frontend/public/components/modals/impersonate-user-modal.tsx index 0e23ef3750a..c79000bc435 100644 --- a/frontend/public/components/modals/impersonate-user-modal.tsx +++ b/frontend/public/components/modals/impersonate-user-modal.tsx @@ -31,11 +31,11 @@ import { import { RhUiCloseIcon, RhUiErrorFillIcon } from '@patternfly/react-icons'; import { useTranslation } from 'react-i18next'; import { ResourceDropdown } from '@console/shared/src/components/dropdown/ResourceDropdown'; -import { GroupModel, ServiceAccountModel } from '../../models'; +import { GroupModel, NamespaceModel, ServiceAccountModel } from '../../models'; import type { GroupKind, K8sResourceKind } from '../../module/k8s'; import { FieldLevelHelp } from '../utils/field-level-help'; import { useK8sWatchResource } from '../utils/k8s-watch-hook'; -import { NsDropdown } from '../utils/list-dropdown'; +import { NsDropdown, useProjectOrNamespaceModel } from '../utils/list-dropdown'; const SELECT_ALL_KEY = '__select_all__'; const MAX_VISIBLE_CHIPS = 5; @@ -70,6 +70,20 @@ export const ImpersonateUserModal: FC = ({ const [showAllGroups, setShowAllGroups] = useState(false); const [groupSearchFilter, setGroupSearchFilter] = useState(''); + const [projectNamespaceModel, projectNamespaceModelLoaded] = useProjectOrNamespaceModel(); + const serviceAccountNamespaceStrings = useMemo(() => { + if (!projectNamespaceModelLoaded || projectNamespaceModel.kind === NamespaceModel.kind) { + return { + label: t('Service account namespace'), + placeholder: t('Select a namespace'), + }; + } + return { + label: t('Service account project'), + placeholder: t('Select a project'), + }; + }, [projectNamespaceModel, projectNamespaceModelLoaded, t]); + // Fetch available groups from the cluster const [groups, groupsLoaded, groupsLoadError] = useK8sWatchResource({ groupVersionKind: { @@ -189,7 +203,7 @@ export const ImpersonateUserModal: FC = ({ setServiceAccountNameError(''); if (impersonateKind === 'User' && !username.trim()) { - setUsernameError(t('Username is required')); + setUsernameError(t('Select a username')); return false; } @@ -199,12 +213,12 @@ export const ImpersonateUserModal: FC = ({ let isValid = true; if (!serviceAccountNamespace.trim()) { - setServiceAccountNamespaceError(t('Service account namespace is required')); + setServiceAccountNamespaceError(serviceAccountNamespaceStrings.placeholder); isValid = false; } if (!serviceAccountName.trim()) { - setServiceAccountNameError(t('Service account name is required')); + setServiceAccountNameError(t('Select a service account')); isValid = false; } @@ -318,7 +332,7 @@ export const ImpersonateUserModal: FC = ({ variant={AlertVariant.warning} isInline title={t( - 'Impersonating a user or service account grants you their exact permissions. You must enter a username or service account, but you can also enter a group to simulate the permissions of a member of that group.', + 'Impersonating a user or service account grants you their exact permissions. You must enter a user name or service account, but you can also enter a group to simulate the permissions of a member of that group.', )} /> @@ -339,7 +353,7 @@ export const ImpersonateUserModal: FC = ({ { setImpersonateKind('ServiceAccount'); @@ -393,7 +407,7 @@ export const ImpersonateUserModal: FC = ({ ) : ( <> diff --git a/frontend/public/components/secret.tsx b/frontend/public/components/secret.tsx index 9c103133362..6c2ab7b0ff9 100644 --- a/frontend/public/components/secret.tsx +++ b/frontend/public/components/secret.tsx @@ -242,7 +242,7 @@ const SecretsPage: FC = (props) => { }, { id: SecretFilterValues.sa, - title: t('Service Account Token'), + title: t('Service account token'), }, { id: SecretFilterValues.opaque, diff --git a/frontend/public/components/secrets/create-secret/types.ts b/frontend/public/components/secrets/create-secret/types.ts index f0fae64004a..f93b88e5019 100644 --- a/frontend/public/components/secrets/create-secret/types.ts +++ b/frontend/public/components/secrets/create-secret/types.ts @@ -69,6 +69,6 @@ export enum SecretFilterValues { image = 'Image', source = 'Source', tls = 'TLS', - sa = 'Service Account Token', + sa = 'Service account token', opaque = 'Opaque', } diff --git a/frontend/public/locales/en/public.json b/frontend/public/locales/en/public.json index dabe9c1b96d..550451ba1a0 100644 --- a/frontend/public/locales/en/public.json +++ b/frontend/public/locales/en/public.json @@ -797,7 +797,7 @@ "Immutable, if set to true, ensures that data stored in the ConfigMap cannot be updated": "Immutable, if set to true, ensures that data stored in the ConfigMap cannot be updated", "Impersonate": "Impersonate", "Impersonate user": "Impersonate user", - "Impersonating a user or service account grants you their exact permissions. You must enter a username or service account, but you can also enter a group to simulate the permissions of a member of that group.": "Impersonating a user or service account grants you their exact permissions. You must enter a username or service account, but you can also enter a group to simulate the permissions of a member of that group.", + "Impersonating a user or service account grants you their exact permissions. You must enter a user name or service account, but you can also enter a group to simulate the permissions of a member of that group.": "Impersonating a user or service account grants you their exact permissions. You must enter a user name or service account, but you can also enter a group to simulate the permissions of a member of that group.", "Import code from your Git repository to be built and deployed": "Import code from your Git repository to be built and deployed", "Import from Git": "Import from Git", "Import more YAML": "Import more YAML", @@ -1362,8 +1362,11 @@ "Select {{resource}} to pause": "Select {{resource}} to pause", "Select a configuration to receive updates. Updates can be configured to receive information from Red Hat or a custom update service.": "Select a configuration to receive updates. Updates can be configured to receive information from Red Hat or a custom update service.", "Select a key": "Select a key", + "Select a namespace": "Select a namespace", + "Select a project": "Select a project", "Select a resource": "Select a resource", "Select a service account": "Select a service account", + "Select a username": "Select a username", "Select a version": "Select a version", "Select a workload": "Select a workload", "Select all": "Select all", @@ -1395,12 +1398,12 @@ "Send resolved alerts to this receiver?": "Send resolved alerts to this receiver?", "Served": "Served", "Service": "Service", + "Service account": "Service account", "Service account name": "Service account name", - "Service account name is required": "Service account name is required", "Service account name to impersonate": "Service account name to impersonate", "Service account namespace": "Service account namespace", - "Service account namespace is required": "Service account namespace is required", - "Service Account Token": "Service Account Token", + "Service account project": "Service account project", + "Service account token": "Service account token", "Service key": "Service key", "Service Level Agreement (SLA)": "Service Level Agreement (SLA)", "Service mesh": "Service mesh", @@ -1702,7 +1705,6 @@ "User preferences": "User preferences", "User Research Team": "User Research Team", "Username": "Username", - "Username is required": "Username is required", "Username to impersonate": "Username to impersonate", "Users": "Users", "Users are automatically added the first time they log in.": "Users are automatically added the first time they log in.",