This is an automated email from the ASF dual-hosted git repository.

andytaylor pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/artemis-console.git


The following commit(s) were added to refs/heads/main by this push:
     new 1ba2aca  ARTEMIS-5910 make web console tabs conditional on permission
1ba2aca is described below

commit 1ba2aca4fe99e40af0534758fd58fe8bc3fedbf6
Author: Domenico Francesco Bruscino <[email protected]>
AuthorDate: Mon Feb 23 14:05:24 2026 +0100

    ARTEMIS-5910 make web console tabs conditional on permission
    
    Many of the tabs on the web console show up even though the user doesn't
    have permission to execute the command corresponding to the tab. For
    example the "Connections" tab shows up even though the user can't
    execute the `listConnections` management operation.
---
 .../artemis-extension/package.json                 |   6 +-
 .../packages/artemis-console-plugin/package.json   |   1 +
 .../src/artemis-service.test.ts                    | 111 ++++++++++++
 .../artemis-console-plugin/src/artemis-service.ts  |  28 +++
 .../src/views/ArtemisTabView.test.tsx              | 201 +++++++++++++++++++++
 .../src/views/ArtemisTabView.tsx                   |  15 ++
 .../artemis-extension/yarn.lock                    |  24 +++
 7 files changed, 385 insertions(+), 1 deletion(-)

diff --git a/artemis-console-extension/artemis-extension/package.json 
b/artemis-console-extension/artemis-extension/package.json
index 2a4bfb9..e84595c 100644
--- a/artemis-console-extension/artemis-extension/package.json
+++ b/artemis-console-extension/artemis-extension/package.json
@@ -57,5 +57,9 @@
       "last 1 safari version"
     ]
   },
-  "packageManager": "[email protected]"
+  "packageManager": "[email protected]",
+  "devDependencies": {
+    "@testing-library/jest-dom": "^6.9.1",
+    "@testing-library/react": "^16.3.2"
+  }
 }
diff --git 
a/artemis-console-extension/artemis-extension/packages/artemis-console-plugin/package.json
 
b/artemis-console-extension/artemis-extension/packages/artemis-console-plugin/package.json
index f178d33..2d89736 100644
--- 
a/artemis-console-extension/artemis-extension/packages/artemis-console-plugin/package.json
+++ 
b/artemis-console-extension/artemis-extension/packages/artemis-console-plugin/package.json
@@ -37,6 +37,7 @@
     "@patternfly/react-topology": "^5.4.1",
     "@testing-library/dom": "^10.4.1",
     "@testing-library/jest-dom": "^6.9.1",
+    "@testing-library/react": "^16.3.2",
     "@types/jest": "^30.0.0",
     "@types/node": "^24.10.1",
     "@types/react": "^18.3.27",
diff --git 
a/artemis-console-extension/artemis-extension/packages/artemis-console-plugin/src/artemis-service.test.ts
 
b/artemis-console-extension/artemis-extension/packages/artemis-console-plugin/src/artemis-service.test.ts
index fbf9d88..8b43184 100644
--- 
a/artemis-console-extension/artemis-extension/packages/artemis-console-plugin/src/artemis-service.test.ts
+++ 
b/artemis-console-extension/artemis-extension/packages/artemis-console-plugin/src/artemis-service.test.ts
@@ -51,3 +51,114 @@ describe("Artemis Service basic tests", () => {
   })
 
 })
+
+/**
+ * Tests for permission checking methods
+ */
+describe("Artemis Service permission checks", () => {
+
+  const createMockBrokerNode = (hasInvokeRights: (sig: string) => boolean) => 
({
+    hasInvokeRights,
+    name: "mockBroker",
+    objectName: "org.apache.activemq.artemis:broker=mockBroker"
+  } as any)
+
+  test("canListConnections returns true when broker has invoke rights", () => {
+    const mockBroker = createMockBrokerNode((sig) => sig === 
"listConnections(java.lang.String,int,int)")
+    expect(artemisService.canListConnections(mockBroker)).toBe(true)
+  })
+
+  test("canListConnections returns false when broker lacks invoke rights", () 
=> {
+    const mockBroker = createMockBrokerNode(() => false)
+    expect(artemisService.canListConnections(mockBroker)).toBe(false)
+  })
+
+  test("canListConnections returns false when broker is undefined", () => {
+    expect(artemisService.canListConnections(undefined)).toBe(false)
+  })
+
+  test("canListSessions returns true when broker has invoke rights", () => {
+    const mockBroker = createMockBrokerNode((sig) => sig === 
"listSessions(java.lang.String,int,int)")
+    expect(artemisService.canListSessions(mockBroker)).toBe(true)
+  })
+
+  test("canListSessions returns false when broker lacks invoke rights", () => {
+    const mockBroker = createMockBrokerNode(() => false)
+    expect(artemisService.canListSessions(mockBroker)).toBe(false)
+  })
+
+  test("canListSessions returns false when broker is undefined", () => {
+    expect(artemisService.canListSessions(undefined)).toBe(false)
+  })
+
+  test("canListConsumers returns true when broker has invoke rights", () => {
+    const mockBroker = createMockBrokerNode((sig) => sig === 
"listConsumers(java.lang.String,int,int)")
+    expect(artemisService.canListConsumers(mockBroker)).toBe(true)
+  })
+
+  test("canListConsumers returns false when broker lacks invoke rights", () => 
{
+    const mockBroker = createMockBrokerNode(() => false)
+    expect(artemisService.canListConsumers(mockBroker)).toBe(false)
+  })
+
+  test("canListConsumers returns false when broker is undefined", () => {
+    expect(artemisService.canListConsumers(undefined)).toBe(false)
+  })
+
+  test("canListProducers returns true when broker has invoke rights", () => {
+    const mockBroker = createMockBrokerNode((sig) => sig === 
"listProducers(java.lang.String,int,int)")
+    expect(artemisService.canListProducers(mockBroker)).toBe(true)
+  })
+
+  test("canListProducers returns false when broker lacks invoke rights", () => 
{
+    const mockBroker = createMockBrokerNode(() => false)
+    expect(artemisService.canListProducers(mockBroker)).toBe(false)
+  })
+
+  test("canListProducers returns false when broker is undefined", () => {
+    expect(artemisService.canListProducers(undefined)).toBe(false)
+  })
+
+  test("canListAddresses returns true when broker has invoke rights", () => {
+    const mockBroker = createMockBrokerNode((sig) => sig === 
"listAddresses(java.lang.String,int,int)")
+    expect(artemisService.canListAddresses(mockBroker)).toBe(true)
+  })
+
+  test("canListAddresses returns false when broker lacks invoke rights", () => 
{
+    const mockBroker = createMockBrokerNode(() => false)
+    expect(artemisService.canListAddresses(mockBroker)).toBe(false)
+  })
+
+  test("canListAddresses returns false when broker is undefined", () => {
+    expect(artemisService.canListAddresses(undefined)).toBe(false)
+  })
+
+  test("canListQueues returns true when broker has invoke rights", () => {
+    const mockBroker = createMockBrokerNode((sig) => sig === 
"listQueues(java.lang.String,int,int)")
+    expect(artemisService.canListQueues(mockBroker)).toBe(true)
+  })
+
+  test("canListQueues returns false when broker lacks invoke rights", () => {
+    const mockBroker = createMockBrokerNode(() => false)
+    expect(artemisService.canListQueues(mockBroker)).toBe(false)
+  })
+
+  test("canListQueues returns false when broker is undefined", () => {
+    expect(artemisService.canListQueues(undefined)).toBe(false)
+  })
+
+  test("canListNetworkTopology returns true when broker has invoke rights", () 
=> {
+    const mockBroker = createMockBrokerNode((sig) => sig === 
"listNetworkTopology")
+    expect(artemisService.canListNetworkTopology(mockBroker)).toBe(true)
+  })
+
+  test("canListNetworkTopology returns false when broker lacks invoke rights", 
() => {
+    const mockBroker = createMockBrokerNode(() => false)
+    expect(artemisService.canListNetworkTopology(mockBroker)).toBe(false)
+  })
+
+  test("canListNetworkTopology returns false when broker is undefined", () => {
+    expect(artemisService.canListNetworkTopology(undefined)).toBe(false)
+  })
+
+})
diff --git 
a/artemis-console-extension/artemis-extension/packages/artemis-console-plugin/src/artemis-service.ts
 
b/artemis-console-extension/artemis-extension/packages/artemis-console-plugin/src/artemis-service.ts
index a268300..cc2ca74 100644
--- 
a/artemis-console-extension/artemis-extension/packages/artemis-console-plugin/src/artemis-service.ts
+++ 
b/artemis-console-extension/artemis-extension/packages/artemis-console-plugin/src/artemis-service.ts
@@ -781,6 +781,34 @@ class ArtemisService {
         }
         return false;
     }
+
+    canListConnections = (broker: MBeanNode | undefined): boolean => {
+        return (this.DEBUG_PRIVS && 
broker?.hasInvokeRights(LIST_CONNECTIONS_SIG)) ?? false
+    }
+
+    canListSessions = (broker: MBeanNode | undefined): boolean => {
+        return (this.DEBUG_PRIVS && 
broker?.hasInvokeRights(LIST_SESSIONS_SIG)) ?? false
+    }
+
+    canListConsumers = (broker: MBeanNode | undefined): boolean => {
+        return (this.DEBUG_PRIVS && 
broker?.hasInvokeRights(LIST_CONSUMERS_SIG)) ?? false
+    }
+
+    canListProducers = (broker: MBeanNode | undefined): boolean => {
+        return (this.DEBUG_PRIVS && 
broker?.hasInvokeRights(LIST_PRODUCERS_SIG)) ?? false
+    }
+
+    canListAddresses = (broker: MBeanNode | undefined): boolean => {
+        return (this.DEBUG_PRIVS && 
broker?.hasInvokeRights(LIST_ADDRESSES_SIG)) ?? false
+    }
+
+    canListQueues = (broker: MBeanNode | undefined): boolean => {
+        return (this.DEBUG_PRIVS && broker?.hasInvokeRights(LIST_QUEUES_SIG)) 
?? false
+    }
+
+    canListNetworkTopology = (broker: MBeanNode | undefined): boolean => {
+        return (this.DEBUG_PRIVS && 
broker?.hasInvokeRights(LIST_NETWORK_TOPOLOGY_SIG)) ?? false
+    }
 }
 
 export async function findMBeanInfo(brokerObjectName: string): 
Promise<MBeanNode | null> {
diff --git 
a/artemis-console-extension/artemis-extension/packages/artemis-console-plugin/src/views/ArtemisTabView.test.tsx
 
b/artemis-console-extension/artemis-extension/packages/artemis-console-plugin/src/views/ArtemisTabView.test.tsx
new file mode 100644
index 0000000..5619f36
--- /dev/null
+++ 
b/artemis-console-extension/artemis-extension/packages/artemis-console-plugin/src/views/ArtemisTabView.test.tsx
@@ -0,0 +1,201 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { render, screen } from '@testing-library/react'
+import { ArtemisTabs } from './ArtemisTabView'
+import { artemisService } from '../artemis-service'
+
+jest.mock('../artemis-service')
+jest.mock('../context', () => ({
+  useArtemisTree: jest.fn(() => ({
+    tree: {},
+    selectedNode: undefined,
+    brokerNode: undefined,
+    setSelectedNode: jest.fn(),
+    findAndSelectNode: jest.fn()
+  })),
+  ArtemisContext: {
+    Provider: ({ children }: any) => children
+  }
+}))
+jest.mock('../brokers/BrokerDiagram', () => ({
+  BrokerDiagram: () => null
+}))
+jest.mock('../producers/ProducerTable', () => ({
+  ProducerTable: () => null
+}))
+jest.mock('../consumers/ConsumerTable', () => ({
+  ConsumerTable: () => null
+}))
+jest.mock('../connections/ConnectionsTable', () => ({
+  ConnectionsTable: () => null
+}))
+jest.mock('../sessions/SessionsTable', () => ({
+  SessionsTable: () => null
+}))
+jest.mock('../addresses/AddressesTable', () => ({
+  AddressesTable: () => null
+}))
+jest.mock('../queues/QueuesView', () => ({
+  QueuesView: () => null
+}))
+jest.mock('../status/Status', () => ({
+  Status: () => null
+}))
+
+type PermissionConfig = {
+  canListConnections?: boolean
+  canListSessions?: boolean
+  canListProducers?: boolean
+  canListConsumers?: boolean
+  canListAddresses?: boolean
+  canListQueues?: boolean
+  canListNetworkTopology?: boolean
+}
+
+const setupPermissions = (config: PermissionConfig = {}) => {
+  const defaults = {
+    canListConnections: false,
+    canListSessions: false,
+    canListProducers: false,
+    canListConsumers: false,
+    canListAddresses: false,
+    canListQueues: false,
+    canListNetworkTopology: false
+  }
+
+  const permissions = { ...defaults, ...config }
+
+  jest.spyOn(artemisService, 
'canListConnections').mockReturnValue(permissions.canListConnections)
+  jest.spyOn(artemisService, 
'canListSessions').mockReturnValue(permissions.canListSessions)
+  jest.spyOn(artemisService, 
'canListProducers').mockReturnValue(permissions.canListProducers)
+  jest.spyOn(artemisService, 
'canListConsumers').mockReturnValue(permissions.canListConsumers)
+  jest.spyOn(artemisService, 
'canListAddresses').mockReturnValue(permissions.canListAddresses)
+  jest.spyOn(artemisService, 
'canListQueues').mockReturnValue(permissions.canListQueues)
+  jest.spyOn(artemisService, 
'canListNetworkTopology').mockReturnValue(permissions.canListNetworkTopology)
+}
+
+const assertTabVisibility = (expectedVisible: string[], expectedHidden: 
string[]) => {
+  expectedVisible.forEach(tabName => {
+    expect(screen.getByText(tabName)).toBeInTheDocument()
+  })
+  expectedHidden.forEach(tabName => {
+    expect(screen.queryByText(tabName)).not.toBeInTheDocument()
+  })
+}
+
+describe('ArtemisTabs', () => {
+
+  beforeEach(() => {
+    jest.clearAllMocks()
+  })
+
+  test('renders Status tab unconditionally', () => {
+    setupPermissions()
+    render(<ArtemisTabs />)
+    expect(screen.getByText('Status')).toBeInTheDocument()
+  })
+
+  test('renders Connections tab when canListConnections is true', () => {
+    setupPermissions({ canListConnections: true })
+    render(<ArtemisTabs />)
+    assertTabVisibility(
+      ['Status', 'Connections'],
+      ['Sessions', 'Producers', 'Consumers', 'Addresses', 'Queues', 'Broker 
Diagram']
+    )
+  })
+
+  test('renders Sessions tab when canListSessions is true', () => {
+    setupPermissions({ canListSessions: true })
+    render(<ArtemisTabs />)
+    assertTabVisibility(
+      ['Status', 'Sessions'],
+      ['Connections', 'Producers', 'Consumers', 'Addresses', 'Queues', 'Broker 
Diagram']
+    )
+  })
+
+  test('renders Producers tab when canListProducers is true', () => {
+    setupPermissions({ canListProducers: true })
+    render(<ArtemisTabs />)
+    assertTabVisibility(
+      ['Status', 'Producers'],
+      ['Connections', 'Sessions', 'Consumers', 'Addresses', 'Queues', 'Broker 
Diagram']
+    )
+  })
+
+  test('renders Consumers tab when canListConsumers is true', () => {
+    setupPermissions({ canListConsumers: true })
+    render(<ArtemisTabs />)
+    assertTabVisibility(
+      ['Status', 'Consumers'],
+      ['Connections', 'Sessions', 'Producers', 'Addresses', 'Queues', 'Broker 
Diagram']
+    )
+  })
+
+  test('renders Addresses tab when canListAddresses is true', () => {
+    setupPermissions({ canListAddresses: true })
+    render(<ArtemisTabs />)
+    assertTabVisibility(
+      ['Status', 'Addresses'],
+      ['Connections', 'Sessions', 'Producers', 'Consumers', 'Queues', 'Broker 
Diagram']
+    )
+  })
+
+  test('renders Queues tab when canListQueues is true', () => {
+    setupPermissions({ canListQueues: true })
+    render(<ArtemisTabs />)
+    assertTabVisibility(
+      ['Status', 'Queues'],
+      ['Connections', 'Sessions', 'Producers', 'Consumers', 'Addresses', 
'Broker Diagram']
+    )
+  })
+
+  test('renders Broker Diagram tab when canListNetworkTopology is true', () => 
{
+    setupPermissions({ canListNetworkTopology: true })
+    render(<ArtemisTabs />)
+    assertTabVisibility(
+      ['Status', 'Broker Diagram'],
+      ['Connections', 'Sessions', 'Producers', 'Consumers', 'Addresses', 
'Queues']
+    )
+  })
+
+  test('renders all tabs when all permissions are granted', () => {
+    setupPermissions({
+      canListConnections: true,
+      canListSessions: true,
+      canListProducers: true,
+      canListConsumers: true,
+      canListAddresses: true,
+      canListQueues: true,
+      canListNetworkTopology: true
+    })
+    render(<ArtemisTabs />)
+    assertTabVisibility(
+      ['Status', 'Connections', 'Sessions', 'Producers', 'Consumers', 
'Addresses', 'Queues', 'Broker Diagram'],
+      []
+    )
+  })
+
+  test('renders only Status tab when no permissions are granted', () => {
+    setupPermissions()
+    render(<ArtemisTabs />)
+    assertTabVisibility(
+      ['Status'],
+      ['Connections', 'Sessions', 'Producers', 'Consumers', 'Addresses', 
'Queues', 'Broker Diagram']
+    )
+  })
+
+})
diff --git 
a/artemis-console-extension/artemis-extension/packages/artemis-console-plugin/src/views/ArtemisTabView.tsx
 
b/artemis-console-extension/artemis-extension/packages/artemis-console-plugin/src/views/ArtemisTabView.tsx
index 8c04f6b..a46db2b 100644
--- 
a/artemis-console-extension/artemis-extension/packages/artemis-console-plugin/src/views/ArtemisTabView.tsx
+++ 
b/artemis-console-extension/artemis-extension/packages/artemis-console-plugin/src/views/ArtemisTabView.tsx
@@ -26,6 +26,7 @@ import { Status } from '../status/Status';
 import { Filter } from '../table/ArtemisTable';
 import { QueuesView } from '../queues/QueuesView';
 import { BrokerDiagram } from '../brokers/BrokerDiagram';
+import { artemisService } from '../artemis-service';
 
 
 export type Broker = {
@@ -72,38 +73,52 @@ export const ArtemisTabs: React.FunctionComponent = () => {
                   <Status/>
                 }
               </Tab>
+              { artemisService.canListConnections(brokerNode) &&
               <Tab eventKey={1} 
title={<TabTitleText>Connections</TabTitleText>} aria-label="connections">
                 {activeTabKey === 1 &&
                   <ConnectionsTable search={handleSearch} 
filter={searchFilter}/>
                 }
               </Tab>
+              }
+              { artemisService.canListSessions(brokerNode) &&
               <Tab eventKey={2} title={<TabTitleText>Sessions</TabTitleText>} 
aria-label="sessions">
                 {activeTabKey === 2 &&
                   <SessionsTable search={handleSearch} filter={searchFilter}/>
                 }
               </Tab>
+              }
+              { artemisService.canListProducers(brokerNode) &&
               <Tab eventKey={3} title={<TabTitleText>Producers</TabTitleText>} 
aria-label="producers">
                 {activeTabKey === 3 &&
                   <ProducerTable search={handleSearch} filter={searchFilter}/>
                 }
               </Tab>
+              }
+              { artemisService.canListConsumers(brokerNode) &&
               <Tab eventKey={4} title={<TabTitleText>Consumers</TabTitleText>} 
aria-label="consumers">
                 {activeTabKey === 4 &&
                   <ConsumerTable search={handleSearch} filter={searchFilter}/>
                 }
               </Tab>
+              }
+              { artemisService.canListAddresses(brokerNode) &&
               <Tab eventKey={5} title={<TabTitleText>Addresses</TabTitleText>} 
aria-label="addresses">
                 {activeTabKey === 5 &&
                   <AddressesTable search={handleSearch} filter={searchFilter}/>
                 }
               </Tab>
+              }
+              { artemisService.canListQueues(brokerNode) &&
               <Tab eventKey={6} title={<TabTitleText>Queues</TabTitleText>} 
aria-label="queues">
                 {activeTabKey === 6 &&
                   <QueuesView search={handleSearch} filter={searchFilter}/>
                 }
               </Tab>
+              }
+              { artemisService.canListNetworkTopology(brokerNode) &&
               <Tab eventKey={7} title={<TabTitleText>Broker 
Diagram</TabTitleText>} aria-label="broker-diagram">
               </Tab>
+              }
           </Tabs>
         </PageSection>
         <PageSection padding={{ default: 'noPadding' }}>
diff --git a/artemis-console-extension/artemis-extension/yarn.lock 
b/artemis-console-extension/artemis-extension/yarn.lock
index d616ca1..23a84d6 100644
--- a/artemis-console-extension/artemis-extension/yarn.lock
+++ b/artemis-console-extension/artemis-extension/yarn.lock
@@ -2085,6 +2085,26 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@testing-library/react@npm:^16.3.2":
+  version: 16.3.2
+  resolution: "@testing-library/react@npm:16.3.2"
+  dependencies:
+    "@babel/runtime": "npm:^7.12.5"
+  peerDependencies:
+    "@testing-library/dom": ^10.0.0
+    "@types/react": ^18.0.0 || ^19.0.0
+    "@types/react-dom": ^18.0.0 || ^19.0.0
+    react: ^18.0.0 || ^19.0.0
+    react-dom: ^18.0.0 || ^19.0.0
+  peerDependenciesMeta:
+    "@types/react":
+      optional: true
+    "@types/react-dom":
+      optional: true
+  checksum: 
10c0/f9c7f0915e1b5f7b750e6c7d8b51f091b8ae7ea99bacb761d7b8505ba25de9cfcb749a0f779f1650fb268b499dd79165dc7e1ee0b8b4cb63430d3ddc81ffe044
+  languageName: node
+  linkType: hard
+
 "@thumbmarkjs/thumbmarkjs@npm:^1.7.0":
   version: 1.7.1
   resolution: "@thumbmarkjs/thumbmarkjs@npm:1.7.1"
@@ -3537,6 +3557,9 @@ __metadata:
 "artemis-console-monorepo@workspace:.":
   version: 0.0.0-use.local
   resolution: "artemis-console-monorepo@workspace:."
+  dependencies:
+    "@testing-library/jest-dom": "npm:^6.9.1"
+    "@testing-library/react": "npm:^16.3.2"
   languageName: unknown
   linkType: soft
 
@@ -3556,6 +3579,7 @@ __metadata:
     "@patternfly/react-topology": "npm:^5.4.1"
     "@testing-library/dom": "npm:^10.4.1"
     "@testing-library/jest-dom": "npm:^6.9.1"
+    "@testing-library/react": "npm:^16.3.2"
     "@types/jest": "npm:^30.0.0"
     "@types/node": "npm:^24.10.1"
     "@types/react": "npm:^18.3.27"


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to