From 9ef54fa8831df0887d71401ca886fb58173d82da Mon Sep 17 00:00:00 2001 From: Matthew Short Date: Fri, 8 Aug 2025 15:14:57 -0700 Subject: [PATCH 1/5] ACM-22860 - RBAC UI Implementation - Roles - Permissions - built skeleton page and mock data Signed-off-by: Matthew Short --- frontend/src/NavigationPath.tsx | 3 + .../Identities/IdentitiesManagement.tsx | 2 + .../Identities/IdentitiesPage.tsx | 4 + .../Identities/Permissions/Permissions.tsx | 119 +++++++++ .../mock-data/kubevirt.io:admin.json | 224 +++++++++++++++++ .../mock-data/kubevirt.io:edit.json | 230 ++++++++++++++++++ .../mock-data/kubevirt.io:view.json | 154 ++++++++++++ 7 files changed, 736 insertions(+) create mode 100644 frontend/src/routes/UserManagement/Identities/Permissions/Permissions.tsx create mode 100644 frontend/src/routes/UserManagement/Identities/Permissions/mock-data/kubevirt.io:admin.json create mode 100644 frontend/src/routes/UserManagement/Identities/Permissions/mock-data/kubevirt.io:edit.json create mode 100644 frontend/src/routes/UserManagement/Identities/Permissions/mock-data/kubevirt.io:view.json diff --git a/frontend/src/NavigationPath.tsx b/frontend/src/NavigationPath.tsx index 5476738a054..a1d7b5a1972 100644 --- a/frontend/src/NavigationPath.tsx +++ b/frontend/src/NavigationPath.tsx @@ -205,6 +205,9 @@ export enum NavigationPath { identitiesServiceAccountsRoleAssignments = '/multicloud/user-management/identities/service-accounts/:id/role-assignments', identitiesServiceAccountsGroups = '/multicloud/user-management/identities/service-accounts/:id/groups', + // TODO: Move Permissions to proper location when ready + identitiesPermissions = '/multicloud/user-management/identities/permissions', + // RBAC Roles roles = '/multicloud/user-management/roles', rolesDetails = '/multicloud/user-management/roles/:id', diff --git a/frontend/src/routes/UserManagement/Identities/IdentitiesManagement.tsx b/frontend/src/routes/UserManagement/Identities/IdentitiesManagement.tsx index d89681c45ee..52e2e1a2eb9 100644 --- a/frontend/src/routes/UserManagement/Identities/IdentitiesManagement.tsx +++ b/frontend/src/routes/UserManagement/Identities/IdentitiesManagement.tsx @@ -5,6 +5,7 @@ import IdentitiesPage from './IdentitiesPage' import { Users } from './Users/Users' import { Groups } from './Groups/Groups' import { ServiceAccounts } from './ServiceAccounts/ServiceAccounts' +import { Permissions } from './Permissions/Permissions' import { UserDetail } from './Users/UserDetail' import { UserYaml } from './Users/UserYaml' import { UserRoleAssignments } from './Users/UserRoleAssignments' @@ -64,6 +65,7 @@ export default function IdentitiesManagement() { } /> } /> } /> + } /> {/* Default redirect to users */} diff --git a/frontend/src/routes/UserManagement/Identities/IdentitiesPage.tsx b/frontend/src/routes/UserManagement/Identities/IdentitiesPage.tsx index 07f04b60292..a9e0170b470 100644 --- a/frontend/src/routes/UserManagement/Identities/IdentitiesPage.tsx +++ b/frontend/src/routes/UserManagement/Identities/IdentitiesPage.tsx @@ -11,6 +11,7 @@ export default function IdentitiesPage() { const isUsersActive = location.pathname.startsWith(NavigationPath.identitiesUsers) const isGroupsActive = location.pathname.startsWith(NavigationPath.identitiesGroups) const isServiceAccountsActive = location.pathname.startsWith(NavigationPath.identitiesServiceAccounts) + const isPermissionsActive = location.pathname.startsWith(NavigationPath.identitiesPermissions) return ( {t('Service Accounts')} + + {t('Permissions')} + } /> diff --git a/frontend/src/routes/UserManagement/Identities/Permissions/Permissions.tsx b/frontend/src/routes/UserManagement/Identities/Permissions/Permissions.tsx new file mode 100644 index 00000000000..6a837cf7769 --- /dev/null +++ b/frontend/src/routes/UserManagement/Identities/Permissions/Permissions.tsx @@ -0,0 +1,119 @@ +/* Copyright Contributors to the Open Cluster Management project */ + +import { PageSection } from '@patternfly/react-core' +import { cellWidth } from '@patternfly/react-table' +import { useMemo } from 'react' +import { useTranslation } from '../../../../lib/acm-i18next' +import { AcmTable, IAcmTableColumn } from '../../../../ui-components' + +interface Permission { + id: string + actions: string[] + apiGroups: string[] + resources: string[] +} + +const mockPermissions: Permission[] = [ + { + id: '1', + actions: ['use'], + apiGroups: ['security.openshift.io'], + resources: ['SCC securitycontextconstraints'], + }, + { + id: '2', + actions: ['get', 'list', 'watch', 'update', 'patch'], + apiGroups: ['operator.openshift.io'], + resources: ['CCSI clustercsidrivers'], + }, + { + id: '3', + actions: ['get', 'list', 'watch', 'update', 'patch'], + apiGroups: ['operator.openshift.io'], + resources: ['CCSI clustercsidrivers/status'], + }, + { + id: '4', + actions: ['get', 'list', 'watch', 'create', 'update', 'patch', 'delete'], + apiGroups: [], + resources: ['CM configmaps'], + }, + { + id: '5', + actions: ['watch', 'list', 'get'], + apiGroups: [], + resources: ['CM configmaps'], + }, + { + id: '6', + actions: ['watch', 'list', 'get', 'create', 'delete', 'patch', 'update'], + apiGroups: ['rbac.authorization.k8s.io'], + resources: ['CRB clusterrolebindings'], + }, + { + id: '7', + actions: ['watch', 'list', 'get', 'create', 'delete', 'patch', 'update'], + apiGroups: ['rbac.authorization.k8s.io'], + resources: ['CR clusterroles', 'RB rolebindings', 'R roles'], + }, +] + +export function Permissions() { + const { t } = useTranslation() + + const columns = useMemo[]>( + () => [ + { + header: t('Actions'), + sort: 'actions', + search: 'actions', + cell: (item) => { + return ( +
+ {item.actions.map((action, index) => ( +
{action}
+ ))} +
+ ) + }, + transforms: [cellWidth(15)], + }, + { + header: t('API groups'), + sort: 'apiGroups', + search: 'apiGroups', + cell: (item) => { + return item.apiGroups.length > 0 ? item.apiGroups.join(', ') : '' + }, + transforms: [cellWidth(25)], + }, + { + header: t('Resources'), + sort: 'resources', + search: 'resources', + cell: (item) => { + return item.resources.join(', ') + }, + transforms: [cellWidth(60)], + }, + ], + [t] + ) + + const keyFn = (item: Permission) => item.id + + return ( + + + id="permissions-table" + key="permissions-table" + columns={columns} + keyFn={keyFn} + items={mockPermissions} + autoHidePagination + initialPerPage={100} + emptyState={
{t('No permissions found')}
} + /> +
+ ) +} diff --git a/frontend/src/routes/UserManagement/Identities/Permissions/mock-data/kubevirt.io:admin.json b/frontend/src/routes/UserManagement/Identities/Permissions/mock-data/kubevirt.io:admin.json new file mode 100644 index 00000000000..83dbc81cdca --- /dev/null +++ b/frontend/src/routes/UserManagement/Identities/Permissions/mock-data/kubevirt.io:admin.json @@ -0,0 +1,224 @@ +{ + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRole", + "metadata": { + "creationTimestamp": "2025-07-31T02:03:00Z", + "labels": { + "rbac.open-cluster-management.io/filter": "vm-clusterroles" + }, + "name": "kubevirt.io:admin", + "resourceVersion": "1270329", + "uid": "53e9b312-c662-475e-8531-7894596ef26e" + }, + "rules": [ + { + "apiGroups": [ + "subresources.kubevirt.io" + ], + "resources": [ + "virtualmachineinstances/console", + "virtualmachineinstances/vnc", + "virtualmachineinstances/vnc/screenshot", + "virtualmachineinstances/portforward", + "virtualmachineinstances/guestosinfo", + "virtualmachineinstances/filesystemlist", + "virtualmachineinstances/userlist", + "virtualmachineinstances/sev/fetchcertchain", + "virtualmachineinstances/sev/querylaunchmeasurement", + "virtualmachineinstances/usbredir" + ], + "verbs": [ + "get" + ] + }, + { + "apiGroups": [ + "subresources.kubevirt.io" + ], + "resources": [ + "virtualmachineinstances/pause", + "virtualmachineinstances/unpause", + "virtualmachineinstances/addvolume", + "virtualmachineinstances/removevolume", + "virtualmachineinstances/freeze", + "virtualmachineinstances/unfreeze", + "virtualmachineinstances/softreboot", + "virtualmachineinstances/sev/setupsession", + "virtualmachineinstances/sev/injectlaunchsecret" + ], + "verbs": [ + "update" + ] + }, + { + "apiGroups": [ + "subresources.kubevirt.io" + ], + "resources": [ + "virtualmachines/expand-spec", + "virtualmachines/portforward" + ], + "verbs": [ + "get" + ] + }, + { + "apiGroups": [ + "subresources.kubevirt.io" + ], + "resources": [ + "virtualmachines/start", + "virtualmachines/stop", + "virtualmachines/restart", + "virtualmachines/addvolume", + "virtualmachines/removevolume", + "virtualmachines/migrate", + "virtualmachines/memorydump" + ], + "verbs": [ + "update" + ] + }, + { + "apiGroups": [ + "subresources.kubevirt.io" + ], + "resources": [ + "expand-vm-spec" + ], + "verbs": [ + "update" + ] + }, + { + "apiGroups": [ + "kubevirt.io" + ], + "resources": [ + "virtualmachines", + "virtualmachineinstances", + "virtualmachineinstancepresets", + "virtualmachineinstancereplicasets", + "virtualmachineinstancemigrations" + ], + "verbs": [ + "get", + "delete", + "create", + "update", + "patch", + "list", + "watch", + "deletecollection" + ] + }, + { + "apiGroups": [ + "snapshot.kubevirt.io" + ], + "resources": [ + "virtualmachinesnapshots", + "virtualmachinesnapshotcontents", + "virtualmachinerestores" + ], + "verbs": [ + "get", + "delete", + "create", + "update", + "patch", + "list", + "watch", + "deletecollection" + ] + }, + { + "apiGroups": [ + "export.kubevirt.io" + ], + "resources": [ + "virtualmachineexports" + ], + "verbs": [ + "get", + "delete", + "create", + "update", + "patch", + "list", + "watch", + "deletecollection" + ] + }, + { + "apiGroups": [ + "clone.kubevirt.io" + ], + "resources": [ + "virtualmachineclones" + ], + "verbs": [ + "get", + "delete", + "create", + "update", + "patch", + "list", + "watch", + "deletecollection" + ] + }, + { + "apiGroups": [ + "instancetype.kubevirt.io" + ], + "resources": [ + "virtualmachineinstancetypes", + "virtualmachineclusterinstancetypes", + "virtualmachinepreferences", + "virtualmachineclusterpreferences" + ], + "verbs": [ + "get", + "delete", + "create", + "update", + "patch", + "list", + "watch", + "deletecollection" + ] + }, + { + "apiGroups": [ + "pool.kubevirt.io" + ], + "resources": [ + "virtualmachinepools" + ], + "verbs": [ + "get", + "delete", + "create", + "update", + "patch", + "list", + "watch", + "deletecollection" + ] + }, + { + "apiGroups": [ + "migrations.kubevirt.io" + ], + "resources": [ + "migrationpolicies" + ], + "verbs": [ + "get", + "list", + "watch" + ] + } + ] +} diff --git a/frontend/src/routes/UserManagement/Identities/Permissions/mock-data/kubevirt.io:edit.json b/frontend/src/routes/UserManagement/Identities/Permissions/mock-data/kubevirt.io:edit.json new file mode 100644 index 00000000000..c0d1d0b0456 --- /dev/null +++ b/frontend/src/routes/UserManagement/Identities/Permissions/mock-data/kubevirt.io:edit.json @@ -0,0 +1,230 @@ +{ + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRole", + "metadata": { + "creationTimestamp": "2025-07-31T02:03:01Z", + "labels": { + "rbac.open-cluster-management.io/filter": "vm-clusterroles" + }, + "name": "kubevirt.io:edit", + "resourceVersion": "1270331", + "uid": "f03e4846-9bc0-49a3-b288-c40d098b35df" + }, + "rules": [ + { + "apiGroups": [ + "subresources.kubevirt.io" + ], + "resources": [ + "virtualmachineinstances/console", + "virtualmachineinstances/vnc", + "virtualmachineinstances/vnc/screenshot", + "virtualmachineinstances/portforward", + "virtualmachineinstances/guestosinfo", + "virtualmachineinstances/filesystemlist", + "virtualmachineinstances/userlist", + "virtualmachineinstances/sev/fetchcertchain", + "virtualmachineinstances/sev/querylaunchmeasurement", + "virtualmachineinstances/usbredir" + ], + "verbs": [ + "get" + ] + }, + { + "apiGroups": [ + "subresources.kubevirt.io" + ], + "resources": [ + "virtualmachineinstances/pause", + "virtualmachineinstances/unpause", + "virtualmachineinstances/addvolume", + "virtualmachineinstances/removevolume", + "virtualmachineinstances/freeze", + "virtualmachineinstances/unfreeze", + "virtualmachineinstances/softreboot", + "virtualmachineinstances/sev/setupsession", + "virtualmachineinstances/sev/injectlaunchsecret" + ], + "verbs": [ + "update" + ] + }, + { + "apiGroups": [ + "subresources.kubevirt.io" + ], + "resources": [ + "virtualmachines/expand-spec", + "virtualmachines/portforward" + ], + "verbs": [ + "get" + ] + }, + { + "apiGroups": [ + "subresources.kubevirt.io" + ], + "resources": [ + "virtualmachines/start", + "virtualmachines/stop", + "virtualmachines/restart", + "virtualmachines/addvolume", + "virtualmachines/removevolume", + "virtualmachines/migrate", + "virtualmachines/memorydump" + ], + "verbs": [ + "update" + ] + }, + { + "apiGroups": [ + "subresources.kubevirt.io" + ], + "resources": [ + "expand-vm-spec" + ], + "verbs": [ + "update" + ] + }, + { + "apiGroups": [ + "kubevirt.io" + ], + "resources": [ + "virtualmachines", + "virtualmachineinstances", + "virtualmachineinstancepresets", + "virtualmachineinstancereplicasets", + "virtualmachineinstancemigrations" + ], + "verbs": [ + "get", + "delete", + "create", + "update", + "patch", + "list", + "watch" + ] + }, + { + "apiGroups": [ + "snapshot.kubevirt.io" + ], + "resources": [ + "virtualmachinesnapshots", + "virtualmachinesnapshotcontents", + "virtualmachinerestores" + ], + "verbs": [ + "get", + "delete", + "create", + "update", + "patch", + "list", + "watch" + ] + }, + { + "apiGroups": [ + "export.kubevirt.io" + ], + "resources": [ + "virtualmachineexports" + ], + "verbs": [ + "get", + "delete", + "create", + "update", + "patch", + "list", + "watch" + ] + }, + { + "apiGroups": [ + "clone.kubevirt.io" + ], + "resources": [ + "virtualmachineclones" + ], + "verbs": [ + "get", + "delete", + "create", + "update", + "patch", + "list", + "watch" + ] + }, + { + "apiGroups": [ + "instancetype.kubevirt.io" + ], + "resources": [ + "virtualmachineinstancetypes", + "virtualmachineclusterinstancetypes", + "virtualmachinepreferences", + "virtualmachineclusterpreferences" + ], + "verbs": [ + "get", + "delete", + "create", + "update", + "patch", + "list", + "watch" + ] + }, + { + "apiGroups": [ + "pool.kubevirt.io" + ], + "resources": [ + "virtualmachinepools" + ], + "verbs": [ + "get", + "delete", + "create", + "update", + "patch", + "list", + "watch" + ] + }, + { + "apiGroups": [ + "kubevirt.io" + ], + "resources": [ + "kubevirts" + ], + "verbs": [ + "get", + "list" + ] + }, + { + "apiGroups": [ + "migrations.kubevirt.io" + ], + "resources": [ + "migrationpolicies" + ], + "verbs": [ + "get", + "list", + "watch" + ] + } + ] +} diff --git a/frontend/src/routes/UserManagement/Identities/Permissions/mock-data/kubevirt.io:view.json b/frontend/src/routes/UserManagement/Identities/Permissions/mock-data/kubevirt.io:view.json new file mode 100644 index 00000000000..6c6aaf9a5c7 --- /dev/null +++ b/frontend/src/routes/UserManagement/Identities/Permissions/mock-data/kubevirt.io:view.json @@ -0,0 +1,154 @@ +{ + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRole", + "metadata": { + "creationTimestamp": "2025-07-31T02:03:01Z", + "labels": { + "rbac.open-cluster-management.io/filter": "vm-clusterroles" + }, + "name": "kubevirt.io:view", + "resourceVersion": "1270332", + "uid": "94469dde-b61f-4ee3-864a-12b72a50a7df" + }, + "rules": [ + { + "apiGroups": [ + "kubevirt.io" + ], + "resources": [ + "kubevirts" + ], + "verbs": [ + "get", + "list" + ] + }, + { + "apiGroups": [ + "subresources.kubevirt.io" + ], + "resources": [ + "virtualmachines/expand-spec", + "virtualmachineinstances/guestosinfo", + "virtualmachineinstances/filesystemlist", + "virtualmachineinstances/userlist", + "virtualmachineinstances/sev/fetchcertchain", + "virtualmachineinstances/sev/querylaunchmeasurement" + ], + "verbs": [ + "get" + ] + }, + { + "apiGroups": [ + "subresources.kubevirt.io" + ], + "resources": [ + "expand-vm-spec" + ], + "verbs": [ + "update" + ] + }, + { + "apiGroups": [ + "kubevirt.io" + ], + "resources": [ + "virtualmachines", + "virtualmachineinstances", + "virtualmachineinstancepresets", + "virtualmachineinstancereplicasets", + "virtualmachineinstancemigrations" + ], + "verbs": [ + "get", + "list", + "watch" + ] + }, + { + "apiGroups": [ + "snapshot.kubevirt.io" + ], + "resources": [ + "virtualmachinesnapshots", + "virtualmachinesnapshotcontents", + "virtualmachinerestores" + ], + "verbs": [ + "get", + "list", + "watch" + ] + }, + { + "apiGroups": [ + "export.kubevirt.io" + ], + "resources": [ + "virtualmachineexports" + ], + "verbs": [ + "get", + "list", + "watch" + ] + }, + { + "apiGroups": [ + "clone.kubevirt.io" + ], + "resources": [ + "virtualmachineclones" + ], + "verbs": [ + "get", + "list", + "watch" + ] + }, + { + "apiGroups": [ + "instancetype.kubevirt.io" + ], + "resources": [ + "virtualmachineinstancetypes", + "virtualmachineclusterinstancetypes", + "virtualmachinepreferences", + "virtualmachineclusterpreferences" + ], + "verbs": [ + "get", + "list", + "watch" + ] + }, + { + "apiGroups": [ + "pool.kubevirt.io" + ], + "resources": [ + "virtualmachinepools" + ], + "verbs": [ + "get", + "list", + "watch" + ] + }, + { + "apiGroups": [ + "migrations.kubevirt.io" + ], + "resources": [ + "migrationpolicies" + ], + "verbs": [ + "get", + "list", + "watch" + ] + } + ] +} From 634eaab6604072a2c98c908aef8e06aec01b72b6 Mon Sep 17 00:00:00 2001 From: Matthew Short Date: Fri, 8 Aug 2025 17:12:10 -0700 Subject: [PATCH 2/5] Implemented realistic mock data, added column id, added bold style for verbs Signed-off-by: Matthew Short --- .../Identities/Permissions/Permissions.tsx | 58 +++++-------------- 1 file changed, 13 insertions(+), 45 deletions(-) diff --git a/frontend/src/routes/UserManagement/Identities/Permissions/Permissions.tsx b/frontend/src/routes/UserManagement/Identities/Permissions/Permissions.tsx index 6a837cf7769..f807ed25fc1 100644 --- a/frontend/src/routes/UserManagement/Identities/Permissions/Permissions.tsx +++ b/frontend/src/routes/UserManagement/Identities/Permissions/Permissions.tsx @@ -5,6 +5,7 @@ import { cellWidth } from '@patternfly/react-table' import { useMemo } from 'react' import { useTranslation } from '../../../../lib/acm-i18next' import { AcmTable, IAcmTableColumn } from '../../../../ui-components' +import clusterRoleData from './mock-data/kubevirt.io:admin.json' interface Permission { id: string @@ -13,50 +14,12 @@ interface Permission { resources: string[] } -const mockPermissions: Permission[] = [ - { - id: '1', - actions: ['use'], - apiGroups: ['security.openshift.io'], - resources: ['SCC securitycontextconstraints'], - }, - { - id: '2', - actions: ['get', 'list', 'watch', 'update', 'patch'], - apiGroups: ['operator.openshift.io'], - resources: ['CCSI clustercsidrivers'], - }, - { - id: '3', - actions: ['get', 'list', 'watch', 'update', 'patch'], - apiGroups: ['operator.openshift.io'], - resources: ['CCSI clustercsidrivers/status'], - }, - { - id: '4', - actions: ['get', 'list', 'watch', 'create', 'update', 'patch', 'delete'], - apiGroups: [], - resources: ['CM configmaps'], - }, - { - id: '5', - actions: ['watch', 'list', 'get'], - apiGroups: [], - resources: ['CM configmaps'], - }, - { - id: '6', - actions: ['watch', 'list', 'get', 'create', 'delete', 'patch', 'update'], - apiGroups: ['rbac.authorization.k8s.io'], - resources: ['CRB clusterrolebindings'], - }, - { - id: '7', - actions: ['watch', 'list', 'get', 'create', 'delete', 'patch', 'update'], - apiGroups: ['rbac.authorization.k8s.io'], - resources: ['CR clusterroles', 'RB rolebindings', 'R roles'], - }, -] +const mockPermissions: Permission[] = clusterRoleData.rules.map((rule, index) => ({ + id: `${index + 1}`, + actions: rule.verbs, + apiGroups: rule.apiGroups, + resources: rule.resources, +})) export function Permissions() { const { t } = useTranslation() @@ -64,6 +27,7 @@ export function Permissions() { const columns = useMemo[]>( () => [ { + id: 'actions', header: t('Actions'), sort: 'actions', search: 'actions', @@ -71,7 +35,9 @@ export function Permissions() { return (
{item.actions.map((action, index) => ( -
{action}
+
+ {action} +
))}
) @@ -79,6 +45,7 @@ export function Permissions() { transforms: [cellWidth(15)], }, { + id: 'apiGroups', header: t('API groups'), sort: 'apiGroups', search: 'apiGroups', @@ -88,6 +55,7 @@ export function Permissions() { transforms: [cellWidth(25)], }, { + id: 'resources', header: t('Resources'), sort: 'resources', search: 'resources', From 9e1525765d7a05400a4d051d7678c958e4ffc0c0 Mon Sep 17 00:00:00 2001 From: Matthew Short Date: Fri, 8 Aug 2025 21:24:37 -0700 Subject: [PATCH 3/5] Changed to use existing interfaces, minor variable names and verbage changes Signed-off-by: Matthew Short --- frontend/src/resources/kubernetes-client.ts | 2 +- frontend/src/resources/rbac.ts | 6 +-- .../Identities/Permissions/Permissions.tsx | 39 +++++++------------ 3 files changed, 18 insertions(+), 29 deletions(-) diff --git a/frontend/src/resources/kubernetes-client.ts b/frontend/src/resources/kubernetes-client.ts index 4f136ce577f..802f66dbb25 100644 --- a/frontend/src/resources/kubernetes-client.ts +++ b/frontend/src/resources/kubernetes-client.ts @@ -1,6 +1,6 @@ /* Copyright Contributors to the Open Cluster Management project */ import { GroupKindType, ServiceAccountKindType, UserKindType } from './rbac' -export interface PolicyRule { +export interface Rule { verbs: string[] apiGroups: string[] resources: string[] diff --git a/frontend/src/resources/rbac.ts b/frontend/src/resources/rbac.ts index 5207cff4bb7..263aff9cffb 100644 --- a/frontend/src/resources/rbac.ts +++ b/frontend/src/resources/rbac.ts @@ -2,7 +2,7 @@ import { Metadata } from './metadata' import { IResourceDefinition } from './resource' import { listResources } from './utils/resource-request' -import { PolicyRule, LocalObjectReference, Subject, RoleRef } from './kubernetes-client' +import { Rule, LocalObjectReference, Subject, RoleRef } from './kubernetes-client' import { ObjectReference } from '@openshift-console/dynamic-plugin-sdk' export const UserApiVersion = 'user.openshift.io/v1' @@ -89,7 +89,7 @@ export interface ClusterRole { apiVersion: RbacApiVersionType kind: ClusterRoleKindType metadata: Metadata - rules: PolicyRule[] + rules: Rule[] } export interface ClusterRoleBinding { @@ -104,7 +104,7 @@ export interface Role { apiVersion: RbacApiVersionType kind: RoleKindType metadata: Metadata - rules: PolicyRule[] + rules: Rule[] } export interface RoleBinding { diff --git a/frontend/src/routes/UserManagement/Identities/Permissions/Permissions.tsx b/frontend/src/routes/UserManagement/Identities/Permissions/Permissions.tsx index f807ed25fc1..b328c584fb9 100644 --- a/frontend/src/routes/UserManagement/Identities/Permissions/Permissions.tsx +++ b/frontend/src/routes/UserManagement/Identities/Permissions/Permissions.tsx @@ -5,38 +5,29 @@ import { cellWidth } from '@patternfly/react-table' import { useMemo } from 'react' import { useTranslation } from '../../../../lib/acm-i18next' import { AcmTable, IAcmTableColumn } from '../../../../ui-components' +import { ClusterRole } from '../../../../resources/rbac' +import { Rule } from '../../../../resources/kubernetes-client' import clusterRoleData from './mock-data/kubevirt.io:admin.json' -interface Permission { - id: string - actions: string[] - apiGroups: string[] - resources: string[] -} - -const mockPermissions: Permission[] = clusterRoleData.rules.map((rule, index) => ({ - id: `${index + 1}`, - actions: rule.verbs, - apiGroups: rule.apiGroups, - resources: rule.resources, -})) +const clusterRole = clusterRoleData as ClusterRole +const rules: Rule[] = clusterRole.rules export function Permissions() { const { t } = useTranslation() - const columns = useMemo[]>( + const columns = useMemo[]>( () => [ { - id: 'actions', + id: 'verbs', header: t('Actions'), - sort: 'actions', - search: 'actions', + sort: 'verbs', + search: 'verbs', cell: (item) => { return (
- {item.actions.map((action, index) => ( + {item.verbs.map((verb, index) => (
- {action} + {verb}
))}
@@ -60,7 +51,7 @@ export function Permissions() { sort: 'resources', search: 'resources', cell: (item) => { - return item.resources.join(', ') + return item.resources.join(' ') }, transforms: [cellWidth(60)], }, @@ -68,18 +59,16 @@ export function Permissions() { [t] ) - const keyFn = (item: Permission) => item.id + const keyFn = (rule: Rule) => `rule-${rules.indexOf(rule)}` return ( - + id="permissions-table" key="permissions-table" columns={columns} keyFn={keyFn} - items={mockPermissions} - autoHidePagination - initialPerPage={100} + items={rules} emptyState={
{t('No permissions found')}
} />
From 6ff15ddef6094439082563d40fefcfe0f41adb0d Mon Sep 17 00:00:00 2001 From: Matthew Short Date: Fri, 8 Aug 2025 22:52:11 -0700 Subject: [PATCH 4/5] Added resource abbreviations with labels, fixed pagination, changed minor verbiage Signed-off-by: Matthew Short --- .../Identities/Permissions/Permissions.tsx | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/frontend/src/routes/UserManagement/Identities/Permissions/Permissions.tsx b/frontend/src/routes/UserManagement/Identities/Permissions/Permissions.tsx index b328c584fb9..cdc6831363d 100644 --- a/frontend/src/routes/UserManagement/Identities/Permissions/Permissions.tsx +++ b/frontend/src/routes/UserManagement/Identities/Permissions/Permissions.tsx @@ -1,6 +1,6 @@ /* Copyright Contributors to the Open Cluster Management project */ -import { PageSection } from '@patternfly/react-core' +import { PageSection, Label, Flex, FlexItem } from '@patternfly/react-core' import { cellWidth } from '@patternfly/react-table' import { useMemo } from 'react' import { useTranslation } from '../../../../lib/acm-i18next' @@ -12,13 +12,19 @@ import clusterRoleData from './mock-data/kubevirt.io:admin.json' const clusterRole = clusterRoleData as ClusterRole const rules: Rule[] = clusterRole.rules +const blacklist = ['ASS', 'FART'] +export const kindToAbbreviation = (kind: string) => { + const abbreviatedKind = (kind.replace(/[^A-Z]/g, '') || kind.toUpperCase()).slice(0, 4) + return blacklist.includes(abbreviatedKind) ? abbreviatedKind.slice(0, -1) : abbreviatedKind +} + export function Permissions() { const { t } = useTranslation() const columns = useMemo[]>( () => [ { - id: 'verbs', + id: 'actions', header: t('Actions'), sort: 'verbs', search: 'verbs', @@ -51,7 +57,18 @@ export function Permissions() { sort: 'resources', search: 'resources', cell: (item) => { - return item.resources.join(' ') + return ( + + {item.resources.map((resource, index) => ( + + {' '} + {resource} + + ))} + + ) }, transforms: [cellWidth(60)], }, @@ -70,6 +87,8 @@ export function Permissions() { keyFn={keyFn} items={rules} emptyState={
{t('No permissions found')}
} + autoHidePagination={true} + initialPerPage={100} /> ) From 81b4b76b6d136fdffd2a5b23771fde7e932c7c1f Mon Sep 17 00:00:00 2001 From: Matthew Short Date: Fri, 8 Aug 2025 23:23:50 -0700 Subject: [PATCH 5/5] Fixed search and sort Signed-off-by: Matthew Short --- .../Identities/Permissions/Permissions.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/frontend/src/routes/UserManagement/Identities/Permissions/Permissions.tsx b/frontend/src/routes/UserManagement/Identities/Permissions/Permissions.tsx index cdc6831363d..44900ab6466 100644 --- a/frontend/src/routes/UserManagement/Identities/Permissions/Permissions.tsx +++ b/frontend/src/routes/UserManagement/Identities/Permissions/Permissions.tsx @@ -4,7 +4,7 @@ import { PageSection, Label, Flex, FlexItem } from '@patternfly/react-core' import { cellWidth } from '@patternfly/react-table' import { useMemo } from 'react' import { useTranslation } from '../../../../lib/acm-i18next' -import { AcmTable, IAcmTableColumn } from '../../../../ui-components' +import { AcmTable, IAcmTableColumn, compareStrings } from '../../../../ui-components' import { ClusterRole } from '../../../../resources/rbac' import { Rule } from '../../../../resources/kubernetes-client' import clusterRoleData from './mock-data/kubevirt.io:admin.json' @@ -26,7 +26,7 @@ export function Permissions() { { id: 'actions', header: t('Actions'), - sort: 'verbs', + sort: (a: Rule, b: Rule) => compareStrings(a.verbs.join(', '), b.verbs.join(', ')), search: 'verbs', cell: (item) => { return ( @@ -44,7 +44,7 @@ export function Permissions() { { id: 'apiGroups', header: t('API groups'), - sort: 'apiGroups', + sort: (a: Rule, b: Rule) => compareStrings(a.apiGroups.join(', '), b.apiGroups.join(', ')), search: 'apiGroups', cell: (item) => { return item.apiGroups.length > 0 ? item.apiGroups.join(', ') : '' @@ -54,7 +54,7 @@ export function Permissions() { { id: 'resources', header: t('Resources'), - sort: 'resources', + sort: (a: Rule, b: Rule) => compareStrings(a.resources.join(', '), b.resources.join(', ')), search: 'resources', cell: (item) => { return ( @@ -89,6 +89,7 @@ export function Permissions() { emptyState={
{t('No permissions found')}
} autoHidePagination={true} initialPerPage={100} + fuseThreshold={0.1} /> )