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

rfellows pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new edaaf02498d NIFI-15987: Updating the global and connector specific 
policy managem… (#11304)
edaaf02498d is described below

commit edaaf02498db96cdf6213b47859f77f9c4f99b20
Author: Matt Gilman <[email protected]>
AuthorDate: Wed Jun 3 16:21:12 2026 -0400

    NIFI-15987: Updating the global and connector specific policy managem… 
(#11304)
    
    * NIFI-15987: Updating the global and connector specific policy management 
pages to include policies for view/write data and viewing provenance events.
    
    * NIFI-15987: Addressing review feedback.
    
    * NIFI-15987: Using tertiary color instead of primary to align with other 
component icons.
    
    * NIFI-15987: Addressing additional review feedback.
    
    * NIFI-15987: Addressing additional review feedback.
---
 .../service/access-policy.service.spec.ts          | 178 +++++++++++++
 .../service/access-policy.service.ts               |  49 +++-
 .../access-policy/access-policy.effects.spec.ts    | 275 +++++++++++++++++++++
 .../state/access-policy/access-policy.effects.ts   |   5 +-
 .../component-access-policies.component.html       |  21 ++
 .../component-access-policies.component.spec.ts    | 105 ++++++++
 .../component-access-policies.component.ts         |  26 +-
 .../global-access-policies.component.html          |  38 ++-
 .../global-access-policies.component.spec.ts       | 264 +++++++++++++++++++-
 .../global-access-policies.component.ts            | 159 ++++++++++--
 .../queue-listing/queue-listing.effects.spec.ts    | 202 +++++++++++++++
 .../state/queue-listing/queue-listing.effects.ts   |  42 ++--
 .../user-access-policies.component.spec.ts         | 123 +++++++++
 .../user-access-policies.component.ts              |  27 ++
 .../component-context.component.html               |   8 +-
 .../component-context.component.spec.ts            |  18 +-
 .../component-context.component.ts                 |  35 ++-
 .../shared/src/pipes/component-type-name.pipe.ts   |   2 +
 18 files changed, 1495 insertions(+), 82 deletions(-)

diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/service/access-policy.service.spec.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/service/access-policy.service.spec.ts
new file mode 100644
index 00000000000..732d18f5622
--- /dev/null
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/service/access-policy.service.spec.ts
@@ -0,0 +1,178 @@
+/*
+ * 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 { TestBed } from '@angular/core/testing';
+import { provideHttpClient } from '@angular/common/http';
+import { HttpTestingController, provideHttpClientTesting } from 
'@angular/common/http/testing';
+import { AccessPolicyService } from './access-policy.service';
+import { Action, ResourceAction } from '../state/shared';
+import { Client } from '../../../service/client.service';
+import { ClusterConnectionService } from 
'../../../service/cluster-connection.service';
+
+describe('AccessPolicyService', () => {
+    let service: AccessPolicyService;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            providers: [
+                AccessPolicyService,
+                provideHttpClient(),
+                provideHttpClientTesting(),
+                {
+                    provide: Client,
+                    useValue: {
+                        getClientId: () => 'client-id',
+                        getRevision: (entity: { revision: unknown }) => 
entity.revision
+                    }
+                },
+                {
+                    provide: ClusterConnectionService,
+                    useValue: { isDisconnectionAcknowledged: () => false }
+                }
+            ]
+        });
+
+        service = TestBed.inject(AccessPolicyService);
+    });
+
+    describe('buildResourcePath', () => {
+        function buildPath(resourceAction: ResourceAction): string {
+            return service.buildResourcePath(resourceAction);
+        }
+
+        it('should transform connectors data policy to /data/connectors', () 
=> {
+            expect(
+                buildPath({
+                    resource: 'connectors',
+                    resourceIdentifier: 'data',
+                    action: Action.Read
+                })
+            ).toBe('/data/connectors');
+        });
+
+        it('should transform connectors provenance-data policy to 
/provenance-data/connectors', () => {
+            expect(
+                buildPath({
+                    resource: 'connectors',
+                    resourceIdentifier: 'provenance-data',
+                    action: Action.Read
+                })
+            ).toBe('/provenance-data/connectors');
+        });
+
+        it('should build global connectors resource path', () => {
+            expect(
+                buildPath({
+                    resource: 'connectors',
+                    action: Action.Read
+                })
+            ).toBe('/connectors');
+        });
+
+        it('should build per-instance connector resource path', () => {
+            expect(
+                buildPath({
+                    resource: 'connectors',
+                    resourceIdentifier: 'connector-1',
+                    action: Action.Write
+                })
+            ).toBe('/connectors/connector-1');
+        });
+
+        it('should pass through non-connector resources unchanged', () => {
+            expect(
+                buildPath({
+                    resource: 'processors',
+                    resourceIdentifier: 'processor-1',
+                    action: Action.Read
+                })
+            ).toBe('/processors/processor-1');
+        });
+    });
+
+    describe('createAccessPolicy', () => {
+        it('should POST with canonical /data/connectors resource', () => {
+            const httpMock = TestBed.inject(HttpTestingController);
+
+            service
+                .createAccessPolicy({
+                    resource: 'connectors',
+                    resourceIdentifier: 'data',
+                    action: Action.Read
+                })
+                .subscribe();
+
+            const req = httpMock.expectOne('../nifi-api/policies');
+            expect(req.request.method).toBe('POST');
+            
expect(req.request.body.component.resource).toBe('/data/connectors');
+            
expect(req.request.body.component.resourceIdentifier).toBeUndefined();
+            req.flush({});
+            httpMock.verify();
+        });
+
+        it('should POST with canonical /provenance-data/connectors resource', 
() => {
+            const httpMock = TestBed.inject(HttpTestingController);
+
+            service
+                .createAccessPolicy({
+                    resource: 'connectors',
+                    resourceIdentifier: 'provenance-data',
+                    action: Action.Read
+                })
+                .subscribe();
+
+            const req = httpMock.expectOne('../nifi-api/policies');
+            expect(req.request.method).toBe('POST');
+            
expect(req.request.body.component.resource).toBe('/provenance-data/connectors');
+            
expect(req.request.body.component.resourceIdentifier).toBeUndefined();
+            req.flush({});
+            httpMock.verify();
+        });
+    });
+
+    describe('getAccessPolicy', () => {
+        it('should GET the canonical /data/connectors URL', () => {
+            const httpMock = TestBed.inject(HttpTestingController);
+
+            service
+                .getAccessPolicy({
+                    resource: 'connectors',
+                    resourceIdentifier: 'data',
+                    action: Action.Read
+                })
+                .subscribe();
+
+            
httpMock.expectOne('../nifi-api/policies/read/data/connectors').flush({});
+            httpMock.verify();
+        });
+
+        it('should GET the canonical /provenance-data/connectors URL', () => {
+            const httpMock = TestBed.inject(HttpTestingController);
+
+            service
+                .getAccessPolicy({
+                    resource: 'connectors',
+                    resourceIdentifier: 'provenance-data',
+                    action: Action.Read
+                })
+                .subscribe();
+
+            
httpMock.expectOne('../nifi-api/policies/read/provenance-data/connectors').flush({});
+            httpMock.verify();
+        });
+    });
+});
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/service/access-policy.service.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/service/access-policy.service.ts
index 8c17bfe49a4..40a1fbcd658 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/service/access-policy.service.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/service/access-policy.service.ts
@@ -31,6 +31,43 @@ export class AccessPolicyService {
 
     private static readonly API: string = '../nifi-api';
 
+    /**
+     * Transforms resourceAction for connector data/provenance policies.
+     * When the UI uses 'connectors' as resource with 'data' or 
'provenance-data' as resourceIdentifier,
+     * this transforms it to the API-expected format where 'data' or 
'provenance-data' becomes the
+     * resource prefix and 'connectors' becomes the suffix.
+     *
+     * Examples:
+     * - { resource: 'connectors', resourceIdentifier: 'data' } -> { resource: 
'data/connectors', resourceIdentifier: undefined }
+     * - { resource: 'connectors', resourceIdentifier: 'provenance-data' } -> 
{ resource: 'provenance-data/connectors', resourceIdentifier: undefined }
+     */
+    private transformConnectorPolicyResource(resourceAction: ResourceAction): 
ResourceAction {
+        if (
+            resourceAction.resource === 'connectors' &&
+            (resourceAction.resourceIdentifier === 'data' || 
resourceAction.resourceIdentifier === 'provenance-data')
+        ) {
+            return {
+                ...resourceAction,
+                resource: `${resourceAction.resourceIdentifier}/connectors`,
+                resourceIdentifier: undefined
+            };
+        }
+        return resourceAction;
+    }
+
+    /**
+     * Builds the API resource path from a ResourceAction.
+     * Handles connector data/provenance policy transformations.
+     */
+    buildResourcePath(resourceAction: ResourceAction): string {
+        const transformed = 
this.transformConnectorPolicyResource(resourceAction);
+        let resource = `/${transformed.resource}`;
+        if (transformed.resourceIdentifier) {
+            resource += `/${transformed.resourceIdentifier}`;
+        }
+        return resource;
+    }
+
     createAccessPolicy(
         resourceAction: ResourceAction,
         {
@@ -41,10 +78,7 @@ export class AccessPolicyService {
             users?: TenantEntity[];
         } = {}
     ): Observable<any> {
-        let resource = `/${resourceAction.resource}`;
-        if (resourceAction.resourceIdentifier) {
-            resource += `/${resourceAction.resourceIdentifier}`;
-        }
+        const resource = this.buildResourcePath(resourceAction);
 
         const payload: unknown = {
             revision: {
@@ -64,9 +98,10 @@ export class AccessPolicyService {
     }
 
     getAccessPolicy(resourceAction: ResourceAction): Observable<any> {
-        const path: string[] = [resourceAction.action, 
resourceAction.resource];
-        if (resourceAction.resourceIdentifier) {
-            path.push(resourceAction.resourceIdentifier);
+        const transformed = 
this.transformConnectorPolicyResource(resourceAction);
+        const path: string[] = [transformed.action, transformed.resource];
+        if (transformed.resourceIdentifier) {
+            path.push(transformed.resourceIdentifier);
         }
         return 
this.httpClient.get(`${AccessPolicyService.API}/policies/${path.join('/')}`);
     }
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/state/access-policy/access-policy.effects.spec.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/state/access-policy/access-policy.effects.spec.ts
new file mode 100644
index 00000000000..e14baa33d25
--- /dev/null
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/state/access-policy/access-policy.effects.spec.ts
@@ -0,0 +1,275 @@
+/*
+ * 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 { TestBed } from '@angular/core/testing';
+import { provideHttpClient } from '@angular/common/http';
+import { provideHttpClientTesting } from '@angular/common/http/testing';
+import { provideMockActions } from '@ngrx/effects/testing';
+import { provideMockStore } from '@ngrx/store/testing';
+import { Action } from '@ngrx/store';
+import { HttpErrorResponse } from '@angular/common/http';
+import { ReplaySubject, of, take, throwError } from 'rxjs';
+
+import { AccessPolicyEffects } from './access-policy.effects';
+import * as AccessPolicyActions from './access-policy.actions';
+import { AccessPolicyService } from '../../service/access-policy.service';
+import { AccessPolicyEntity, Action as PolicyAction, PolicyStatus, 
ResourceAction } from '../shared';
+import { Client } from '../../../../service/client.service';
+import { ClusterConnectionService } from 
'../../../../service/cluster-connection.service';
+import { ErrorHelper } from '../../../../service/error-helper.service';
+import { MatDialog } from '@angular/material/dialog';
+import { Router } from '@angular/router';
+
+describe('AccessPolicyEffects', () => {
+    let action$: ReplaySubject<Action>;
+
+    function createAccessPolicyEntity(resource: string): AccessPolicyEntity {
+        return {
+            id: 'policy-1',
+            component: {
+                id: 'policy-1',
+                resource,
+                action: PolicyAction.Read,
+                users: [],
+                userGroups: [],
+                configurable: true
+            },
+            revision: { version: 0, clientId: 'client-id' },
+            uri: '/policies/policy-1',
+            permissions: { canRead: true, canWrite: true },
+            generated: '2026-01-01 00:00:00 UTC'
+        } as AccessPolicyEntity;
+    }
+
+    async function setup() {
+        await TestBed.configureTestingModule({
+            providers: [
+                AccessPolicyEffects,
+                AccessPolicyService,
+                provideHttpClient(),
+                provideHttpClientTesting(),
+                provideMockActions(() => action$),
+                provideMockStore(),
+                {
+                    provide: Client,
+                    useValue: {
+                        getClientId: () => 'client-id',
+                        getRevision: (entity: { revision: unknown }) => 
entity.revision
+                    }
+                },
+                {
+                    provide: ClusterConnectionService,
+                    useValue: { isDisconnectionAcknowledged: () => false }
+                },
+                { provide: ErrorHelper, useValue: { getErrorString: 
vi.fn().mockReturnValue('error') } },
+                { provide: MatDialog, useValue: { open: vi.fn() } },
+                { provide: Router, useValue: { navigate: vi.fn() } }
+            ]
+        }).compileComponents();
+
+        const effects = TestBed.inject(AccessPolicyEffects);
+        const accessPolicyService = TestBed.inject(AccessPolicyService);
+
+        return { effects, accessPolicyService };
+    }
+
+    async function dispatchLoadAccessPolicy(
+        effects: AccessPolicyEffects,
+        resourceAction: ResourceAction
+    ): Promise<Action> {
+        action$.next(
+            AccessPolicyActions.loadAccessPolicy({
+                request: { resourceAction }
+            })
+        );
+
+        return new Promise((resolve) => {
+            effects.loadAccessPolicy$.pipe(take(1)).subscribe(resolve);
+        });
+    }
+
+    beforeEach(() => {
+        action$ = new ReplaySubject<Action>();
+    });
+
+    it('should create', async () => {
+        const { effects } = await setup();
+        expect(effects).toBeTruthy();
+    });
+
+    describe('loadAccessPolicy$', () => {
+        it('should mark connector data policy as Found when API resource 
matches buildResourcePath', async () => {
+            const { effects, accessPolicyService } = await setup();
+            const resourceAction: ResourceAction = {
+                resource: 'connectors',
+                resourceIdentifier: 'data',
+                action: PolicyAction.Read
+            };
+            const accessPolicy = createAccessPolicyEntity('/data/connectors');
+
+            vi.spyOn(accessPolicyService, 
'getAccessPolicy').mockReturnValue(of(accessPolicy) as never);
+
+            const result = await dispatchLoadAccessPolicy(effects, 
resourceAction);
+
+            expect(result).toEqual(
+                AccessPolicyActions.loadAccessPolicySuccess({
+                    response: {
+                        accessPolicy,
+                        policyStatus: PolicyStatus.Found
+                    }
+                })
+            );
+            
expect(accessPolicyService.getAccessPolicy).toHaveBeenCalledWith(resourceAction);
+        });
+
+        it('should mark connector data policy as Inherited when API resource 
differs from buildResourcePath', async () => {
+            const { effects, accessPolicyService } = await setup();
+            const resourceAction: ResourceAction = {
+                resource: 'connectors',
+                resourceIdentifier: 'data',
+                action: PolicyAction.Read
+            };
+            const accessPolicy = createAccessPolicyEntity('/connectors');
+
+            vi.spyOn(accessPolicyService, 
'getAccessPolicy').mockReturnValue(of(accessPolicy) as never);
+
+            const result = await dispatchLoadAccessPolicy(effects, 
resourceAction);
+
+            expect(result).toEqual(
+                AccessPolicyActions.loadAccessPolicySuccess({
+                    response: {
+                        accessPolicy,
+                        policyStatus: PolicyStatus.Inherited
+                    }
+                })
+            );
+        });
+
+        it('should mark connector provenance policy as Found when API resource 
matches buildResourcePath', async () => {
+            const { effects, accessPolicyService } = await setup();
+            const resourceAction: ResourceAction = {
+                resource: 'connectors',
+                resourceIdentifier: 'provenance-data',
+                action: PolicyAction.Read
+            };
+            const accessPolicy = 
createAccessPolicyEntity('/provenance-data/connectors');
+
+            vi.spyOn(accessPolicyService, 
'getAccessPolicy').mockReturnValue(of(accessPolicy) as never);
+
+            const result = await dispatchLoadAccessPolicy(effects, 
resourceAction);
+
+            expect(result).toEqual(
+                AccessPolicyActions.loadAccessPolicySuccess({
+                    response: {
+                        accessPolicy,
+                        policyStatus: PolicyStatus.Found
+                    }
+                })
+            );
+        });
+
+        it('should mark global connectors policy as Found when API resource is 
/connectors', async () => {
+            const { effects, accessPolicyService } = await setup();
+            const resourceAction: ResourceAction = {
+                resource: 'connectors',
+                action: PolicyAction.Write
+            };
+            const accessPolicy = createAccessPolicyEntity('/connectors');
+
+            vi.spyOn(accessPolicyService, 
'getAccessPolicy').mockReturnValue(of(accessPolicy) as never);
+
+            const result = await dispatchLoadAccessPolicy(effects, 
resourceAction);
+
+            expect(result).toEqual(
+                AccessPolicyActions.loadAccessPolicySuccess({
+                    response: {
+                        accessPolicy,
+                        policyStatus: PolicyStatus.Found
+                    }
+                })
+            );
+        });
+
+        it('should treat untransformed connector data path as Inherited', 
async () => {
+            const { effects, accessPolicyService } = await setup();
+            const resourceAction: ResourceAction = {
+                resource: 'connectors',
+                resourceIdentifier: 'data',
+                action: PolicyAction.Read
+            };
+            const accessPolicy = createAccessPolicyEntity('/connectors/data');
+
+            vi.spyOn(accessPolicyService, 
'getAccessPolicy').mockReturnValue(of(accessPolicy) as never);
+
+            const result = await dispatchLoadAccessPolicy(effects, 
resourceAction);
+
+            expect(result).toEqual(
+                AccessPolicyActions.loadAccessPolicySuccess({
+                    response: {
+                        accessPolicy,
+                        policyStatus: PolicyStatus.Inherited
+                    }
+                })
+            );
+        });
+
+        it('should reset access policy with NotFound on 404', async () => {
+            const { effects, accessPolicyService } = await setup();
+            const resourceAction: ResourceAction = {
+                resource: 'connectors',
+                action: PolicyAction.Read
+            };
+
+            vi.spyOn(accessPolicyService, 'getAccessPolicy').mockReturnValue(
+                throwError(() => new HttpErrorResponse({ status: 404 })) as 
never
+            );
+
+            const result = await dispatchLoadAccessPolicy(effects, 
resourceAction);
+
+            expect(result).toEqual(
+                AccessPolicyActions.resetAccessPolicy({
+                    response: {
+                        policyStatus: PolicyStatus.NotFound
+                    }
+                })
+            );
+        });
+
+        it('should reset access policy with Forbidden on 403', async () => {
+            const { effects, accessPolicyService } = await setup();
+            const resourceAction: ResourceAction = {
+                resource: 'connectors',
+                resourceIdentifier: 'data',
+                action: PolicyAction.Read
+            };
+
+            vi.spyOn(accessPolicyService, 'getAccessPolicy').mockReturnValue(
+                throwError(() => new HttpErrorResponse({ status: 403 })) as 
never
+            );
+
+            const result = await dispatchLoadAccessPolicy(effects, 
resourceAction);
+
+            expect(result).toEqual(
+                AccessPolicyActions.resetAccessPolicy({
+                    response: {
+                        policyStatus: PolicyStatus.Forbidden
+                    }
+                })
+            );
+        });
+    });
+});
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/state/access-policy/access-policy.effects.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/state/access-policy/access-policy.effects.ts
index ee8458b52ce..bae145365e0 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/state/access-policy/access-policy.effects.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/state/access-policy/access-policy.effects.ts
@@ -89,10 +89,7 @@ export class AccessPolicyEffects {
                     map((response) => {
                         const accessPolicy: AccessPolicyEntity = response;
 
-                        let requestedResource = 
`/${request.resourceAction.resource}`;
-                        if (request.resourceAction.resourceIdentifier) {
-                            requestedResource += 
`/${request.resourceAction.resourceIdentifier}`;
-                        }
+                        const requestedResource = 
this.accessPoliciesService.buildResourcePath(request.resourceAction);
 
                         let policyStatus: PolicyStatus | undefined;
                         if (accessPolicy.component.resource === 
requestedResource) {
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/ui/component-access-policies/component-access-policies.component.html
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/ui/component-access-policies/component-access-policies.component.html
index 88a5f2e1534..cefbb8cc43b 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/ui/component-access-policies/component-access-policies.component.html
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/ui/component-access-policies/component-access-policies.component.html
@@ -155,6 +155,27 @@
         <a (click)="overridePolicy()">Override</a> this policy.
     }
 </ng-template>
+<ng-template #inheritedFromConnectors let-policy 
let-supportsConfigurableAuthorizer="supportsConfigurableAuthorizer">
+    Showing effective policy inherited from global connectors policy.
+    @if (supportsConfigurableAuthorizer) {
+        <a (click)="overridePolicy()">Override</a> this policy.
+    }
+</ng-template>
+<ng-template #inheritedFromConnectorData let-policy 
let-supportsConfigurableAuthorizer="supportsConfigurableAuthorizer">
+    Showing effective policy inherited from global connector data policy.
+    @if (supportsConfigurableAuthorizer) {
+        <a (click)="overridePolicy()">Override</a> this policy.
+    }
+</ng-template>
+<ng-template
+    #inheritedFromConnectorProvenance
+    let-policy
+    let-supportsConfigurableAuthorizer="supportsConfigurableAuthorizer">
+    Showing effective policy inherited from global connector provenance policy.
+    @if (supportsConfigurableAuthorizer) {
+        <a (click)="overridePolicy()">Override</a> this policy.
+    }
+</ng-template>
 <ng-template #inheritedFromProcessGroup let-policy 
let-supportsConfigurableAuthorizer="supportsConfigurableAuthorizer">
     Showing effective policy inherited from <a 
[routerLink]="getInheritedProcessGroupRoute(policy)">Process Group</a>.
     @if (supportsConfigurableAuthorizer) {
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/ui/component-access-policies/component-access-policies.component.spec.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/ui/component-access-policies/component-access-policies.component.spec.ts
index 31fbf28426a..a0d985d666c 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/ui/component-access-policies/component-access-policies.component.spec.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/ui/component-access-policies/component-access-policies.component.spec.ts
@@ -15,8 +15,10 @@
  * limitations under the License.
  */
 
+import { TemplateRef } from '@angular/core';
 import { ComponentAccessPolicies } from 
'./component-access-policies.component';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ComponentType } from '@nifi/shared';
 import { provideMockStore } from '@ngrx/store/testing';
 import { initialState } from '../../state/access-policy/access-policy.reducer';
 import { initialState as initialErrorState } from 
'../../../../state/error/error.reducer';
@@ -52,4 +54,107 @@ describe('ComponentAccessPolicies', () => {
     it('should create', () => {
         expect(component).toBeTruthy();
     });
+
+    describe('isComponentPolicy', () => {
+        const createPolicyComponentState = (resource: string, 
allowRemoteAccess = false) => ({
+            resource,
+            allowRemoteAccess,
+            label: 'Test Component'
+        });
+
+        const createOption = (value: string) => ({
+            text: 'Test Option',
+            value,
+            description: 'Test description'
+        });
+
+        describe('connectors', () => {
+            it('should enable read-data policy for connectors', () => {
+                const state = createPolicyComponentState('connectors');
+                const option = createOption('read-data');
+                expect(component.isComponentPolicy(option, state as 
any)).toBe(true);
+            });
+
+            it('should enable write-data policy for connectors', () => {
+                const state = createPolicyComponentState('connectors');
+                const option = createOption('write-data');
+                expect(component.isComponentPolicy(option, state as 
any)).toBe(true);
+            });
+
+            it('should enable read-provenance-data policy for connectors', () 
=> {
+                const state = createPolicyComponentState('connectors');
+                const option = createOption('read-provenance-data');
+                expect(component.isComponentPolicy(option, state as 
any)).toBe(true);
+            });
+
+            it('should disable write-send-data policy for connectors', () => {
+                const state = createPolicyComponentState('connectors');
+                const option = createOption('write-send-data');
+                expect(component.isComponentPolicy(option, state as 
any)).toBe(false);
+            });
+
+            it('should disable write-receive-data policy for connectors', () 
=> {
+                const state = createPolicyComponentState('connectors');
+                const option = createOption('write-receive-data');
+                expect(component.isComponentPolicy(option, state as 
any)).toBe(false);
+            });
+
+            it('should enable component policies for connectors', () => {
+                const state = createPolicyComponentState('connectors');
+                
expect(component.isComponentPolicy(createOption('read-component'), state as 
any)).toBe(true);
+                
expect(component.isComponentPolicy(createOption('write-component'), state as 
any)).toBe(true);
+                
expect(component.isComponentPolicy(createOption('write-operation'), state as 
any)).toBe(true);
+            });
+        });
+
+        describe('parameter-contexts', () => {
+            it('should disable data policies for parameter-contexts', () => {
+                const state = createPolicyComponentState('parameter-contexts');
+                expect(component.isComponentPolicy(createOption('read-data'), 
state as any)).toBe(false);
+                
expect(component.isComponentPolicy(createOption('write-operation'), state as 
any)).toBe(false);
+            });
+        });
+    });
+
+    describe('getContextType', () => {
+        it('should return ComponentType.Connector for connectors resource', () 
=> {
+            component.resource = 'connectors';
+            expect(component.getContextType()).toBe(ComponentType.Connector);
+        });
+    });
+
+    describe('getTemplateForInheritedPolicy', () => {
+        const templateRef = {} as TemplateRef<unknown>;
+
+        beforeEach(() => {
+            component.inheritedFromConnectors = templateRef;
+            component.inheritedFromConnectorData = templateRef;
+            component.inheritedFromConnectorProvenance = templateRef;
+            component.inheritedFromProcessGroup = {} as TemplateRef<unknown>;
+        });
+
+        it('should use connector data template for /data/connectors', () => {
+            expect(
+                component.getTemplateForInheritedPolicy({
+                    component: { resource: '/data/connectors' }
+                } as any)
+            ).toBe(component.inheritedFromConnectorData);
+        });
+
+        it('should use connector provenance template for 
/provenance-data/connectors', () => {
+            expect(
+                component.getTemplateForInheritedPolicy({
+                    component: { resource: '/provenance-data/connectors' }
+                } as any)
+            ).toBe(component.inheritedFromConnectorProvenance);
+        });
+
+        it('should use connectors template for /connectors', () => {
+            expect(
+                component.getTemplateForInheritedPolicy({
+                    component: { resource: '/connectors' }
+                } as any)
+            ).toBe(component.inheritedFromConnectors);
+        });
+    });
 });
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/ui/component-access-policies/component-access-policies.component.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/ui/component-access-policies/component-access-policies.component.ts
index 742ba8dccc9..be7120e82f3 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/ui/component-access-policies/component-access-policies.component.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/ui/component-access-policies/component-access-policies.component.ts
@@ -49,7 +49,7 @@ import { PolicyComponentState } from 
'../../state/policy-component';
 import { ErrorContextKey } from '../../../../state/error';
 
 @Component({
-    selector: 'global-access-policies',
+    selector: 'component-access-policies',
     templateUrl: './component-access-policies.component.html',
     styleUrls: ['./component-access-policies.component.scss'],
     standalone: false
@@ -136,6 +136,9 @@ export class ComponentAccessPolicies implements OnInit, 
OnDestroy {
     @ViewChild('inheritedFromPolicies') inheritedFromPolicies!: 
TemplateRef<any>;
     @ViewChild('inheritedFromController') inheritedFromController!: 
TemplateRef<any>;
     @ViewChild('inheritedFromGlobalParameterContexts') 
inheritedFromGlobalParameterContexts!: TemplateRef<any>;
+    @ViewChild('inheritedFromConnectors') inheritedFromConnectors!: 
TemplateRef<any>;
+    @ViewChild('inheritedFromConnectorData') inheritedFromConnectorData!: 
TemplateRef<any>;
+    @ViewChild('inheritedFromConnectorProvenance') 
inheritedFromConnectorProvenance!: TemplateRef<any>;
     @ViewChild('inheritedFromProcessGroup') inheritedFromProcessGroup!: 
TemplateRef<any>;
 
     constructor() {
@@ -268,8 +271,7 @@ export class ComponentAccessPolicies implements OnInit, 
OnDestroy {
             }
         } else if (
             policyComponentState.resource === 'parameter-contexts' ||
-            policyComponentState.resource === 'parameter-providers' ||
-            policyComponentState.resource === 'connectors'
+            policyComponentState.resource === 'parameter-providers'
         ) {
             switch (option.value) {
                 case 'read-data':
@@ -280,6 +282,13 @@ export class ComponentAccessPolicies implements OnInit, 
OnDestroy {
                 case 'write-operation':
                     return false;
             }
+        } else if (policyComponentState.resource === 'connectors') {
+            // Connectors support data policies for queue actions but not 
site-to-site
+            switch (option.value) {
+                case 'write-send-data':
+                case 'write-receive-data':
+                    return false;
+            }
         } else if (policyComponentState.resource === 'labels') {
             switch (option.value) {
                 case 'write-operation':
@@ -332,8 +341,9 @@ export class ComponentAccessPolicies implements OnInit, 
OnDestroy {
             case 'parameter-providers':
             case 'parameter-contexts':
             case 'reporting-tasks':
-            case 'connectors':
                 return 'icon-drop';
+            case 'connectors':
+                return 'fa fa-plug';
         }
 
         return 'icon-group';
@@ -360,7 +370,7 @@ export class ComponentAccessPolicies implements OnInit, 
OnDestroy {
             case 'parameter-providers':
                 return ComponentType.ParameterProvider;
             case 'connectors':
-                return 'Connector';
+                return ComponentType.Connector;
         }
 
         return ComponentType.ProcessGroup;
@@ -430,6 +440,12 @@ export class ComponentAccessPolicies implements OnInit, 
OnDestroy {
             return this.inheritedFromController;
         } else if (policy.component.resource === '/parameter-contexts') {
             return this.inheritedFromGlobalParameterContexts;
+        } else if (policy.component.resource === '/data/connectors') {
+            return this.inheritedFromConnectorData;
+        } else if (policy.component.resource === 
'/provenance-data/connectors') {
+            return this.inheritedFromConnectorProvenance;
+        } else if (policy.component.resource === '/connectors') {
+            return this.inheritedFromConnectors;
         }
 
         return this.inheritedFromProcessGroup;
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/ui/global-access-policies/global-access-policies.component.html
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/ui/global-access-policies/global-access-policies.component.html
index a9b1dd5ad5f..cb118b46809 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/ui/global-access-policies/global-access-policies.component.html
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/ui/global-access-policies/global-access-policies.component.html
@@ -72,8 +72,21 @@
                                     <mat-form-field subscriptSizing="dynamic">
                                         <mat-label>Action</mat-label>
                                         <mat-select formControlName="action" 
(selectionChange)="actionChanged()">
-                                            <mat-option 
[value]="Action.Read">view</mat-option>
-                                            <mat-option 
[value]="Action.Write">modify</mat-option>
+                                            @if (supportsConnectorActions) {
+                                                @for (option of 
connectorActionOptions; track option) {
+                                                    <mat-option
+                                                        [value]="option.value"
+                                                        nifiTooltip
+                                                        
[tooltipComponentType]="TextTip"
+                                                        
[tooltipInputData]="option.description"
+                                                        [delayClose]="false"
+                                                        >{{ option.text }}
+                                                    </mat-option>
+                                                }
+                                            } @else {
+                                                <mat-option 
[value]="Action.Read">view</mat-option>
+                                                <mat-option 
[value]="Action.Write">modify</mat-option>
+                                            }
                                         </mat-select>
                                     </mat-form-field>
                                 </div>
@@ -167,6 +180,27 @@
         <a (click)="overridePolicy()">Override</a> this policy.
     }
 </ng-template>
+<ng-template #inheritedFromConnectors let-policy 
let-supportsConfigurableAuthorizer="supportsConfigurableAuthorizer">
+    Showing effective policy inherited from global connectors policy.
+    @if (supportsConfigurableAuthorizer) {
+        <a (click)="overridePolicy()">Override</a> this policy.
+    }
+</ng-template>
+<ng-template #inheritedFromConnectorData let-policy 
let-supportsConfigurableAuthorizer="supportsConfigurableAuthorizer">
+    Showing effective policy inherited from global connector data policy.
+    @if (supportsConfigurableAuthorizer) {
+        <a (click)="overridePolicy()">Override</a> this policy.
+    }
+</ng-template>
+<ng-template
+    #inheritedFromConnectorProvenance
+    let-policy
+    let-supportsConfigurableAuthorizer="supportsConfigurableAuthorizer">
+    Showing effective policy inherited from global connector provenance policy.
+    @if (supportsConfigurableAuthorizer) {
+        <a (click)="overridePolicy()">Override</a> this policy.
+    }
+</ng-template>
 <ng-template
     #inheritedFromNoRestrictions
     let-policy
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/ui/global-access-policies/global-access-policies.component.spec.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/ui/global-access-policies/global-access-policies.component.spec.ts
index 9b370969aef..065cf118d0e 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/ui/global-access-policies/global-access-policies.component.spec.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/ui/global-access-policies/global-access-policies.component.spec.ts
@@ -15,9 +15,10 @@
  * limitations under the License.
  */
 
+import { TemplateRef } from '@angular/core';
 import { GlobalAccessPolicies } from './global-access-policies.component';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { provideMockStore } from '@ngrx/store/testing';
+import { MockStore, provideMockStore } from '@ngrx/store/testing';
 import { initialState } from '../../state/access-policy/access-policy.reducer';
 import { initialState as initialErrorState } from 
'../../../../state/error/error.reducer';
 import { errorFeatureKey } from '../../../../state/error';
@@ -35,10 +36,14 @@ import { flowConfigurationFeatureKey } from 
'../../../../state/flow-configuratio
 import * as fromFlowConfiguration from 
'../../../../state/flow-configuration/flow-configuration.reducer';
 import { NgxSkeletonLoaderComponent } from 'ngx-skeleton-loader';
 import { selectCurrentRoute } from '@nifi/shared';
+import { Action } from '../../state/shared';
+import { selectGlobalAccessPolicy } from 
'../../state/access-policy/access-policy.actions';
+import type { MockInstance } from 'vitest';
 
 describe('GlobalAccessPolicies', () => {
     let component: GlobalAccessPolicies;
     let fixture: ComponentFixture<GlobalAccessPolicies>;
+    let store: MockStore;
 
     beforeEach(() => {
         TestBed.configureTestingModule({
@@ -72,10 +77,267 @@ describe('GlobalAccessPolicies', () => {
         });
         fixture = TestBed.createComponent(GlobalAccessPolicies);
         component = fixture.componentInstance;
+        store = TestBed.inject(MockStore);
         fixture.detectChanges();
     });
 
     it('should create', () => {
         expect(component).toBeTruthy();
     });
+
+    describe('connectorActionOptions', () => {
+        it('should have extended action options for connectors', () => {
+            expect(component.connectorActionOptions).toBeDefined();
+            expect(component.connectorActionOptions.length).toBe(5);
+
+            const values = component.connectorActionOptions.map((o) => 
o.value);
+            expect(values).toContain('read');
+            expect(values).toContain('write');
+            expect(values).toContain('read-data');
+            expect(values).toContain('write-data');
+            expect(values).toContain('read-provenance-data');
+        });
+    });
+
+    describe('resourceChanged', () => {
+        it('should set supportsConnectorActions to true when connectors is 
selected', () => {
+            component.resourceChanged('connectors');
+            expect(component.supportsConnectorActions).toBe(true);
+            expect(component.supportsReadWriteAction).toBe(true);
+        });
+
+        it('should set supportsConnectorActions to false for non-connector 
resources', () => {
+            component.resourceChanged('controller');
+            expect(component.supportsConnectorActions).toBe(false);
+            expect(component.supportsReadWriteAction).toBe(true);
+        });
+
+        it('should reset action to read when connectors is selected', () => {
+            component.resourceChanged('connectors');
+            expect(component.policyForm.get('action')?.value).toBe('read');
+        });
+    });
+
+    describe('actionChanged with connector extended actions', () => {
+        let dispatchSpy: MockInstance;
+
+        beforeEach(() => {
+            dispatchSpy = vi.spyOn(store, 'dispatch');
+        });
+
+        it('should map read-data action to connectors with data 
resourceIdentifier', () => {
+            component.policyForm.get('resource')?.setValue('connectors');
+            component.policyForm.get('action')?.setValue('read-data');
+            component.actionChanged();
+
+            expect(dispatchSpy).toHaveBeenCalledWith(
+                selectGlobalAccessPolicy({
+                    request: {
+                        resourceAction: {
+                            resource: 'connectors',
+                            action: Action.Read,
+                            resourceIdentifier: 'data'
+                        }
+                    }
+                })
+            );
+        });
+
+        it('should map write-data action to connectors with data 
resourceIdentifier', () => {
+            component.policyForm.get('resource')?.setValue('connectors');
+            component.policyForm.get('action')?.setValue('write-data');
+            component.actionChanged();
+
+            expect(dispatchSpy).toHaveBeenCalledWith(
+                selectGlobalAccessPolicy({
+                    request: {
+                        resourceAction: {
+                            resource: 'connectors',
+                            action: Action.Write,
+                            resourceIdentifier: 'data'
+                        }
+                    }
+                })
+            );
+        });
+
+        it('should map read-provenance-data action to connectors with 
provenance-data resourceIdentifier', () => {
+            component.policyForm.get('resource')?.setValue('connectors');
+            
component.policyForm.get('action')?.setValue('read-provenance-data');
+            component.actionChanged();
+
+            expect(dispatchSpy).toHaveBeenCalledWith(
+                selectGlobalAccessPolicy({
+                    request: {
+                        resourceAction: {
+                            resource: 'connectors',
+                            action: Action.Read,
+                            resourceIdentifier: 'provenance-data'
+                        }
+                    }
+                })
+            );
+        });
+
+        it('should map read action to connectors resource with read action', 
() => {
+            component.policyForm.get('resource')?.setValue('connectors');
+            component.policyForm.get('action')?.setValue('read');
+            component.actionChanged();
+
+            expect(dispatchSpy).toHaveBeenCalledWith(
+                selectGlobalAccessPolicy({
+                    request: {
+                        resourceAction: {
+                            resource: 'connectors',
+                            action: Action.Read,
+                            resourceIdentifier: undefined
+                        }
+                    }
+                })
+            );
+        });
+
+        it('should map write action to connectors resource with write action', 
() => {
+            component.policyForm.get('resource')?.setValue('connectors');
+            component.policyForm.get('action')?.setValue('write');
+            component.actionChanged();
+
+            expect(dispatchSpy).toHaveBeenCalledWith(
+                selectGlobalAccessPolicy({
+                    request: {
+                        resourceAction: {
+                            resource: 'connectors',
+                            action: Action.Write,
+                            resourceIdentifier: undefined
+                        }
+                    }
+                })
+            );
+        });
+    });
+
+    describe('mapRouteResourceToFormState', () => {
+        const callMapRouteResourceToFormState = (
+            comp: GlobalAccessPolicies,
+            resourceAction: { resource: string; action: string; 
resourceIdentifier?: string }
+        ) => {
+            return (comp as any).mapRouteResourceToFormState(resourceAction);
+        };
+
+        it('should map connectors with data resourceIdentifier and read action 
to read-data', () => {
+            const result = callMapRouteResourceToFormState(component, {
+                resource: 'connectors',
+                action: Action.Read,
+                resourceIdentifier: 'data'
+            });
+
+            expect(result).toEqual({
+                resource: 'connectors',
+                action: 'read-data',
+                supportsConnectorActions: true
+            });
+        });
+
+        it('should map connectors with data resourceIdentifier and write 
action to write-data', () => {
+            const result = callMapRouteResourceToFormState(component, {
+                resource: 'connectors',
+                action: Action.Write,
+                resourceIdentifier: 'data'
+            });
+
+            expect(result).toEqual({
+                resource: 'connectors',
+                action: 'write-data',
+                supportsConnectorActions: true
+            });
+        });
+
+        it('should map connectors with provenance-data resourceIdentifier to 
read-provenance-data', () => {
+            const result = callMapRouteResourceToFormState(component, {
+                resource: 'connectors',
+                action: Action.Read,
+                resourceIdentifier: 'provenance-data'
+            });
+
+            expect(result).toEqual({
+                resource: 'connectors',
+                action: 'read-provenance-data',
+                supportsConnectorActions: true
+            });
+        });
+
+        it('should map connectors with read action to connectors with read', 
() => {
+            const result = callMapRouteResourceToFormState(component, {
+                resource: 'connectors',
+                action: Action.Read
+            });
+
+            expect(result).toEqual({
+                resource: 'connectors',
+                action: 'read',
+                supportsConnectorActions: true
+            });
+        });
+
+        it('should map connectors with write action to connectors with write', 
() => {
+            const result = callMapRouteResourceToFormState(component, {
+                resource: 'connectors',
+                action: Action.Write
+            });
+
+            expect(result).toEqual({
+                resource: 'connectors',
+                action: 'write',
+                supportsConnectorActions: true
+            });
+        });
+
+        it('should pass through non-connector resources unchanged', () => {
+            const result = callMapRouteResourceToFormState(component, {
+                resource: 'controller',
+                action: Action.Read
+            });
+
+            expect(result).toEqual({
+                resource: 'controller',
+                action: Action.Read,
+                supportsConnectorActions: false
+            });
+        });
+    });
+
+    describe('getTemplateForInheritedPolicy', () => {
+        const templateRef = {} as TemplateRef<unknown>;
+
+        beforeEach(() => {
+            component.inheritedFromConnectors = templateRef;
+            component.inheritedFromConnectorData = templateRef;
+            component.inheritedFromConnectorProvenance = templateRef;
+            component.inheritedFromNoRestrictions = {} as TemplateRef<unknown>;
+        });
+
+        it('should use connector data template for /data/connectors', () => {
+            expect(
+                component.getTemplateForInheritedPolicy({
+                    component: { resource: '/data/connectors' }
+                } as any)
+            ).toBe(component.inheritedFromConnectorData);
+        });
+
+        it('should use connector provenance template for 
/provenance-data/connectors', () => {
+            expect(
+                component.getTemplateForInheritedPolicy({
+                    component: { resource: '/provenance-data/connectors' }
+                } as any)
+            ).toBe(component.inheritedFromConnectorProvenance);
+        });
+
+        it('should use connectors template for /connectors', () => {
+            expect(
+                component.getTemplateForInheritedPolicy({
+                    component: { resource: '/connectors' }
+                } as any)
+            ).toBe(component.inheritedFromConnectors);
+        });
+    });
 });
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/ui/global-access-policies/global-access-policies.component.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/ui/global-access-policies/global-access-policies.component.ts
index d84692cf9d4..cb34616fce3 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/ui/global-access-policies/global-access-policies.component.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/ui/global-access-policies/global-access-policies.component.ts
@@ -74,9 +74,42 @@ export class GlobalAccessPolicies implements OnInit, 
OnDestroy {
     requiredPermissionOptions!: SelectOption[];
     supportsReadWriteAction = false;
     supportsResourceIdentifier = false;
+    supportsConnectorActions = false;
+
+    // Extended action options for connectors policy
+    connectorActionOptions: SelectOption[] = [
+        {
+            text: 'view',
+            value: 'read',
+            description: 'Allows users to view Connectors'
+        },
+        {
+            text: 'modify',
+            value: 'write',
+            description: 'Allows users to modify Connectors'
+        },
+        {
+            text: 'view the data',
+            value: 'read-data',
+            description: 'Allows users to view metadata and content in 
flowfile queues in connector process groups'
+        },
+        {
+            text: 'modify the data',
+            value: 'write-data',
+            description: 'Allows users to empty flowfile queues in connector 
process groups'
+        },
+        {
+            text: 'view provenance',
+            value: 'read-provenance-data',
+            description: 'Allows users to view provenance events for connector 
components'
+        }
+    ];
 
     @ViewChild('inheritedFromPolicies') inheritedFromPolicies!: 
TemplateRef<any>;
     @ViewChild('inheritedFromController') inheritedFromController!: 
TemplateRef<any>;
+    @ViewChild('inheritedFromConnectors') inheritedFromConnectors!: 
TemplateRef<any>;
+    @ViewChild('inheritedFromConnectorData') inheritedFromConnectorData!: 
TemplateRef<any>;
+    @ViewChild('inheritedFromConnectorProvenance') 
inheritedFromConnectorProvenance!: TemplateRef<any>;
     @ViewChild('inheritedFromNoRestrictions') inheritedFromNoRestrictions!: 
TemplateRef<any>;
 
     constructor() {
@@ -137,12 +170,17 @@ export class GlobalAccessPolicies implements OnInit, 
OnDestroy {
             )
             .subscribe((resourceAction) => {
                 if (resourceAction) {
-                    this.supportsReadWriteAction = 
this.globalPolicySupportsReadWrite(resourceAction.resource);
+                    const mappedState = 
this.mapRouteResourceToFormState(resourceAction);
+
+                    this.supportsConnectorActions = 
mappedState.supportsConnectorActions;
+                    this.supportsReadWriteAction =
+                        mappedState.supportsConnectorActions ||
+                        
this.globalPolicySupportsReadWrite(mappedState.resource);
 
-                    
this.policyForm.get('resource')?.setValue(resourceAction.resource);
-                    
this.policyForm.get('action')?.setValue(resourceAction.action);
+                    
this.policyForm.get('resource')?.setValue(mappedState.resource);
+                    
this.policyForm.get('action')?.setValue(mappedState.action);
 
-                    
this.updateResourceIdentifierVisibility(resourceAction.resource);
+                    
this.updateResourceIdentifierVisibility(mappedState.resource);
 
                     if (resourceAction.resource === 'restricted-components' && 
resourceAction.resourceIdentifier) {
                         
this.policyForm.get('resourceIdentifier')?.setValue(resourceAction.resourceIdentifier);
@@ -159,6 +197,45 @@ export class GlobalAccessPolicies implements OnInit, 
OnDestroy {
             });
     }
 
+    /**
+     * Maps a route resource/action to form state, handling connector extended 
actions.
+     * When the route is 'connectors' with a resourceIdentifier of 'data' or 
'provenance-data',
+     * this maps to the appropriate extended action in the form.
+     */
+    private mapRouteResourceToFormState(resourceAction: {
+        resource: string;
+        action: string;
+        resourceIdentifier?: string;
+    }): { resource: string; action: string; supportsConnectorActions: boolean 
} {
+        if (resourceAction.resource === 'connectors') {
+            if (resourceAction.resourceIdentifier === 'data') {
+                return {
+                    resource: 'connectors',
+                    action: resourceAction.action === Action.Read ? 
'read-data' : 'write-data',
+                    supportsConnectorActions: true
+                };
+            } else if (resourceAction.resourceIdentifier === 
'provenance-data') {
+                return {
+                    resource: 'connectors',
+                    action: 'read-provenance-data',
+                    supportsConnectorActions: true
+                };
+            } else {
+                return {
+                    resource: 'connectors',
+                    action: resourceAction.action === Action.Read ? 'read' : 
'write',
+                    supportsConnectorActions: true
+                };
+            }
+        }
+
+        return {
+            resource: resourceAction.resource,
+            action: resourceAction.action,
+            supportsConnectorActions: false
+        };
+    }
+
     ngOnInit(): void {
         this.store.dispatch(loadTenants());
         this.store.dispatch(loadExtensionTypesForPolicies());
@@ -169,7 +246,13 @@ export class GlobalAccessPolicies implements OnInit, 
OnDestroy {
     }
 
     resourceChanged(value: string): void {
-        if (this.globalPolicySupportsReadWrite(value)) {
+        this.supportsConnectorActions = value === 'connectors';
+
+        if (this.supportsConnectorActions) {
+            this.supportsReadWriteAction = true;
+            this.supportsResourceIdentifier = false;
+            this.policyForm.get('action')?.setValue('read');
+        } else if (this.globalPolicySupportsReadWrite(value)) {
             this.supportsReadWriteAction = true;
             this.supportsResourceIdentifier = false;
 
@@ -184,24 +267,13 @@ export class GlobalAccessPolicies implements OnInit, 
OnDestroy {
             this.updateResourceIdentifierVisibility(value);
         }
 
-        this.store.dispatch(
-            selectGlobalAccessPolicy({
-                request: {
-                    resourceAction: {
-                        resource: this.policyForm.get('resource')?.value,
-                        action: this.policyForm.get('action')?.value,
-                        resourceIdentifier: 
this.policyForm.get('resourceIdentifier')?.value
-                    }
-                }
-            })
-        );
+        this.dispatchPolicySelection();
     }
 
     private globalPolicySupportsReadWrite(resource: string): boolean {
         return (
             resource === 'controller' ||
             resource === 'parameter-contexts' ||
-            resource === 'connectors' ||
             resource === 'counters' ||
             resource === 'policies' ||
             resource === 'tenants'
@@ -223,13 +295,54 @@ export class GlobalAccessPolicies implements OnInit, 
OnDestroy {
     }
 
     actionChanged(): void {
+        this.dispatchPolicySelection();
+    }
+
+    /**
+     * Dispatches the policy selection action with proper resource/action 
mapping.
+     * For connectors, extended actions like 'read-data' are mapped to use
+     * resourceIdentifier (e.g., 'connectors' with resourceIdentifier 'data').
+     */
+    private dispatchPolicySelection(): void {
+        const selectedResource = this.policyForm.get('resource')?.value;
+        const selectedAction = this.policyForm.get('action')?.value;
+
+        const resource = selectedResource;
+        let action = selectedAction;
+        let resourceIdentifier = 
this.policyForm.get('resourceIdentifier')?.value;
+
+        if (selectedResource === 'connectors') {
+            switch (selectedAction) {
+                case 'read':
+                    action = Action.Read;
+                    resourceIdentifier = undefined;
+                    break;
+                case 'write':
+                    action = Action.Write;
+                    resourceIdentifier = undefined;
+                    break;
+                case 'read-data':
+                    action = Action.Read;
+                    resourceIdentifier = 'data';
+                    break;
+                case 'write-data':
+                    action = Action.Write;
+                    resourceIdentifier = 'data';
+                    break;
+                case 'read-provenance-data':
+                    action = Action.Read;
+                    resourceIdentifier = 'provenance-data';
+                    break;
+            }
+        }
+
         this.store.dispatch(
             selectGlobalAccessPolicy({
                 request: {
                     resourceAction: {
-                        resource: this.policyForm.get('resource')?.value,
-                        action: this.policyForm.get('action')?.value,
-                        resourceIdentifier: 
this.policyForm.get('resourceIdentifier')?.value
+                        resource,
+                        action,
+                        resourceIdentifier
                     }
                 }
             })
@@ -255,6 +368,12 @@ export class GlobalAccessPolicies implements OnInit, 
OnDestroy {
             return this.inheritedFromPolicies;
         } else if (policy.component.resource === '/controller') {
             return this.inheritedFromController;
+        } else if (policy.component.resource === '/data/connectors') {
+            return this.inheritedFromConnectorData;
+        } else if (policy.component.resource === 
'/provenance-data/connectors') {
+            return this.inheritedFromConnectorProvenance;
+        } else if (policy.component.resource === '/connectors') {
+            return this.inheritedFromConnectors;
         }
 
         return this.inheritedFromNoRestrictions;
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/queue/state/queue-listing/queue-listing.effects.spec.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/queue/state/queue-listing/queue-listing.effects.spec.ts
new file mode 100644
index 00000000000..2bf633c7f01
--- /dev/null
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/queue/state/queue-listing/queue-listing.effects.spec.ts
@@ -0,0 +1,202 @@
+/*
+ * 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 { TestBed } from '@angular/core/testing';
+import { provideMockActions } from '@ngrx/effects/testing';
+import { MockStore, provideMockStore } from '@ngrx/store/testing';
+import { Action } from '@ngrx/store';
+import { MatDialog } from '@angular/material/dialog';
+import { HttpErrorResponse } from '@angular/common/http';
+import { firstValueFrom, Observable, of, Subject, throwError } from 'rxjs';
+import { QueueListingEffects } from './queue-listing.effects';
+import {
+    pollQueueListingRequest,
+    queueListingApiError,
+    stopPollingQueueListingRequest,
+    submitQueueListingRequest
+} from './queue-listing.actions';
+import { selectActiveListingRequest, selectSelectedConnection } from 
'./queue-listing.selectors';
+import { QueueService } from '../../service/queue.service';
+import { ErrorHelper } from '../../../../service/error-helper.service';
+import { NiFiCommon } from '@nifi/shared';
+import * as ErrorActions from '../../../../state/error/error.actions';
+import { ErrorContextKey } from '../../../../state/error';
+import { ListingRequest } from './index';
+
+function createListingRequest(overrides: Partial<ListingRequest> = {}): 
ListingRequest {
+    return {
+        id: 'listing-1',
+        uri: 'http://example/listing-requests/listing-1',
+        submissionTime: '2026-01-01T00:00:00Z',
+        lastUpdated: '2026-01-01T00:00:01Z',
+        percentCompleted: 0,
+        finished: false,
+        failureReason: '',
+        maxResults: 100,
+        sourceRunning: true,
+        destinationRunning: true,
+        state: 'RUNNING',
+        queueSize: { byteCount: 0, objectCount: 0 },
+        flowFileSummaries: [],
+        ...overrides
+    };
+}
+
+describe('QueueListingEffects', () => {
+    async function setup(
+        options: {
+            selectedConnection?: { id: string; label: string } | null;
+            activeListingRequest?: ListingRequest | null;
+            queueService?: Partial<QueueService>;
+        } = {}
+    ) {
+        let actions$: Observable<Action>;
+
+        const exit$ = new Subject<void>();
+        const mockDialog = {
+            open: vi.fn().mockReturnValue({
+                componentInstance: {
+                    exit: exit$.asObservable()
+                }
+            }),
+            closeAll: vi.fn()
+        };
+
+        await TestBed.configureTestingModule({
+            providers: [
+                QueueListingEffects,
+                provideMockActions(() => actions$),
+                provideMockStore({
+                    initialState: {},
+                    selectors: [
+                        {
+                            selector: selectSelectedConnection,
+                            value:
+                                options.selectedConnection !== undefined
+                                    ? options.selectedConnection
+                                    : { id: 'conn-1', label: 'Connection' }
+                        },
+                        {
+                            selector: selectActiveListingRequest,
+                            value:
+                                options.activeListingRequest !== undefined
+                                    ? options.activeListingRequest
+                                    : createListingRequest()
+                        }
+                    ]
+                }),
+                { provide: MatDialog, useValue: mockDialog },
+                {
+                    provide: QueueService,
+                    useValue: {
+                        submitQueueListingRequest: vi.fn(),
+                        pollQueueListingRequest: vi.fn(),
+                        ...options.queueService
+                    }
+                },
+                {
+                    provide: ErrorHelper,
+                    useValue: {
+                        getErrorString: vi.fn().mockReturnValue('queue listing 
failed')
+                    }
+                },
+                {
+                    provide: NiFiCommon,
+                    useValue: {
+                        isBlank: (value: string) => !value?.trim()
+                    }
+                }
+            ]
+        }).compileComponents();
+
+        const effects = TestBed.inject(QueueListingEffects);
+        const store = TestBed.inject(MockStore);
+
+        return {
+            effects,
+            store,
+            mockDialog,
+            actions$: (stream: Observable<Action>) => {
+                actions$ = stream;
+            }
+        };
+    }
+
+    beforeEach(() => {
+        vi.clearAllMocks();
+    });
+
+    describe('queueListingApiError$', () => {
+        it('should close dialogs, stop polling, and surface a queue banner 
error', async () => {
+            const { effects, store, mockDialog, actions$ } = await setup();
+            actions$(of(queueListingApiError({ error: 'listing failed' })));
+            const dispatchSpy = vi.spyOn(store, 'dispatch');
+
+            const action = await firstValueFrom(effects.queueListingApiError$);
+
+            expect(mockDialog.closeAll).toHaveBeenCalled();
+            
expect(dispatchSpy).toHaveBeenCalledWith(stopPollingQueueListingRequest());
+            expect(action).toEqual(
+                ErrorActions.addBannerError({
+                    errorContext: { errors: ['listing failed'], context: 
ErrorContextKey.QUEUE }
+                })
+            );
+        });
+    });
+
+    describe('submitQueueListingRequest$', () => {
+        it('should dispatch queueListingApiError when submit fails', async () 
=> {
+            const errorResponse = new HttpErrorResponse({ status: 500, 
statusText: 'Server Error' });
+            const { effects, actions$ } = await setup({
+                queueService: {
+                    submitQueueListingRequest: 
vi.fn().mockReturnValue(throwError(() => errorResponse))
+                }
+            });
+            actions$(of(submitQueueListingRequest({ request: { connectionId: 
'conn-1' } })));
+
+            const action = await 
firstValueFrom(effects.submitQueueListingRequest$);
+
+            expect(action).toEqual(
+                queueListingApiError({
+                    error: 'queue listing failed'
+                })
+            );
+        });
+    });
+
+    describe('pollQueueListingRequest$', () => {
+        it('should dispatch queueListingApiError when poll fails', async () => 
{
+            const errorResponse = new HttpErrorResponse({ status: 503, 
statusText: 'Service Unavailable' });
+            const pollQueueListingRequestSpy = 
vi.fn().mockReturnValue(throwError(() => errorResponse));
+            const { effects, actions$ } = await setup({
+                queueService: {
+                    pollQueueListingRequest: pollQueueListingRequestSpy
+                }
+            });
+            actions$(of(pollQueueListingRequest()));
+
+            const action = await 
firstValueFrom(effects.pollQueueListingRequest$);
+
+            expect(pollQueueListingRequestSpy).toHaveBeenCalledWith('conn-1', 
'listing-1');
+            expect(action).toEqual(
+                queueListingApiError({
+                    error: 'queue listing failed'
+                })
+            );
+        });
+    });
+});
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/queue/state/queue-listing/queue-listing.effects.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/queue/state/queue-listing/queue-listing.effects.ts
index e82ccfd880c..413f87c91c5 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/queue/state/queue-listing/queue-listing.effects.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/queue/state/queue-listing/queue-listing.effects.ts
@@ -37,7 +37,6 @@ import { isDefinedAndNotNull, NiFiCommon, LARGE_DIALOG } from 
'@nifi/shared';
 import { HttpErrorResponse } from '@angular/common/http';
 import * as ErrorActions from '../../../../state/error/error.actions';
 import { ErrorHelper } from '../../../../service/error-helper.service';
-import { stopPollingQueueListingRequest } from './queue-listing.actions';
 import { ErrorContextKey } from '../../../../state/error';
 
 @Injectable()
@@ -112,19 +111,13 @@ export class QueueListingEffects {
                             }
                         })
                     ),
-                    catchError((errorResponse: HttpErrorResponse) => {
-                        if 
(this.errorHelper.showErrorInContext(errorResponse.status)) {
-                            return of(
-                                QueueListingActions.queueListingApiError({
-                                    error: 
this.errorHelper.getErrorString(errorResponse)
-                                })
-                            );
-                        } else {
-                            
this.store.dispatch(stopPollingQueueListingRequest());
-
-                            return 
of(this.errorHelper.fullScreenError(errorResponse));
-                        }
-                    })
+                    catchError((errorResponse: HttpErrorResponse) =>
+                        of(
+                            QueueListingActions.queueListingApiError({
+                                error: 
this.errorHelper.getErrorString(errorResponse)
+                            })
+                        )
+                    )
                 );
             })
         )
@@ -183,19 +176,13 @@ export class QueueListingEffects {
                             }
                         })
                     ),
-                    catchError((errorResponse: HttpErrorResponse) => {
-                        if 
(this.errorHelper.showErrorInContext(errorResponse.status)) {
-                            return of(
-                                QueueListingActions.queueListingApiError({
-                                    error: 
this.errorHelper.getErrorString(errorResponse)
-                                })
-                            );
-                        } else {
-                            
this.store.dispatch(stopPollingQueueListingRequest());
-
-                            return 
of(this.errorHelper.fullScreenError(errorResponse));
-                        }
-                    })
+                    catchError((errorResponse: HttpErrorResponse) =>
+                        of(
+                            QueueListingActions.queueListingApiError({
+                                error: 
this.errorHelper.getErrorString(errorResponse)
+                            })
+                        )
+                    )
                 );
             })
         )
@@ -349,6 +336,7 @@ export class QueueListingEffects {
         this.actions$.pipe(
             ofType(QueueListingActions.queueListingApiError),
             tap(() => {
+                this.dialog.closeAll();
                 
this.store.dispatch(QueueListingActions.stopPollingQueueListingRequest());
             }),
             switchMap(({ error }) =>
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/users/ui/user-listing/user-access-policies/user-access-policies.component.spec.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/users/ui/user-listing/user-access-policies/user-access-policies.component.spec.ts
index 5fcba7ad631..fe839bdf3c2 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/users/ui/user-listing/user-access-policies/user-access-policies.component.spec.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/users/ui/user-listing/user-access-policies/user-access-policies.component.spec.ts
@@ -66,4 +66,127 @@ describe('UserAccessPolicies', () => {
     it('should create', () => {
         expect(component).toBeTruthy();
     });
+
+    describe('formatPolicy', () => {
+        it('should label per-instance connector policies', () => {
+            const label = component.formatPolicy({
+                permissions: { canRead: true, canWrite: true },
+                component: {
+                    resource: '/connectors/connector-1',
+                    action: 'read',
+                    componentReference: {
+                        id: 'connector-1',
+                        permissions: { canRead: true, canWrite: true },
+                        component: { name: 'My Connector' }
+                    }
+                }
+            } as any);
+
+            expect(label).toBe('Component policy for connector My Connector');
+        });
+
+        it('should label connector data policies', () => {
+            const label = component.formatPolicy({
+                permissions: { canRead: true, canWrite: true },
+                component: {
+                    resource: '/data/connectors/connector-1',
+                    action: 'read',
+                    componentReference: {
+                        id: 'connector-1',
+                        permissions: { canRead: true, canWrite: true },
+                        component: { name: 'My Connector' }
+                    }
+                }
+            } as any);
+
+            expect(label).toBe('Data policy for connector My Connector');
+        });
+
+        it('should label connector provenance policies', () => {
+            const label = component.formatPolicy({
+                permissions: { canRead: true, canWrite: true },
+                component: {
+                    resource: '/provenance-data/connectors/connector-1',
+                    action: 'read',
+                    componentReference: {
+                        id: 'connector-1',
+                        permissions: { canRead: true, canWrite: true },
+                        component: { name: 'My Connector' }
+                    }
+                }
+            } as any);
+
+            expect(label).toBe('Provenance policy for connector My Connector');
+        });
+
+        it('should label global connector data read policy', () => {
+            const label = component.formatPolicy({
+                permissions: { canRead: true, canWrite: true },
+                component: {
+                    resource: '/data/connectors',
+                    action: 'read',
+                    configurable: true
+                }
+            } as any);
+
+            expect(label).toBe('Global policy to view the data for 
connectors');
+        });
+
+        it('should label global connector data write policy', () => {
+            const label = component.formatPolicy({
+                permissions: { canRead: true, canWrite: true },
+                component: {
+                    resource: '/data/connectors',
+                    action: 'write',
+                    configurable: true
+                }
+            } as any);
+
+            expect(label).toBe('Global policy to modify the data for 
connectors');
+        });
+
+        it('should label global connector provenance policy', () => {
+            const label = component.formatPolicy({
+                permissions: { canRead: true, canWrite: true },
+                component: {
+                    resource: '/provenance-data/connectors',
+                    action: 'read',
+                    configurable: true
+                }
+            } as any);
+
+            expect(label).toBe('Global policy to view provenance for 
connectors');
+        });
+
+        it('should label global connectors policy via policy type listing', () 
=> {
+            const label = component.formatPolicy({
+                permissions: { canRead: true, canWrite: true },
+                component: {
+                    resource: '/connectors',
+                    action: 'read',
+                    configurable: true
+                }
+            } as any);
+
+            expect(label).toBe('Global policy to access connectors');
+        });
+    });
+
+    describe('getPolicyTargetLink', () => {
+        it('should link to the connectors listing for connector policies', () 
=> {
+            const link = component.getPolicyTargetLink({
+                permissions: { canRead: true, canWrite: true },
+                component: {
+                    resource: '/connectors/connector-1',
+                    action: 'read',
+                    componentReference: {
+                        id: 'connector-1',
+                        permissions: { canRead: true, canWrite: true }
+                    }
+                }
+            } as any);
+
+            expect(link).toEqual(['/connectors', 'connector-1']);
+        });
+    });
 });
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/users/ui/user-listing/user-access-policies/user-access-policies.component.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/users/ui/user-listing/user-access-policies/user-access-policies.component.ts
index 4d9814989ab..45dfe7a7c63 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/users/ui/user-listing/user-access-policies/user-access-policies.component.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/users/ui/user-listing/user-access-policies/user-access-policies.component.ts
@@ -106,6 +106,11 @@ export class UserAccessPolicies extends 
CloseOnEscapeDialog {
             // not restricted/global policy... check if user has access to the 
component reference
             return this.componentResourceParser(policy);
         } else {
+            const globalLabel = 
this.parseGlobalPolicyResource(policy.component.resource, 
policy.component.action);
+            if (globalLabel) {
+                return globalLabel;
+            }
+
             // may be a global policy
             const policyValue: string = 
this.nifiCommon.substringAfterLast(policy.component.resource, '/');
             const policyOption: SelectOption | undefined = 
this.nifiCommon.getPolicyTypeListing(policyValue);
@@ -119,6 +124,21 @@ export class UserAccessPolicies extends 
CloseOnEscapeDialog {
         }
     }
 
+    /**
+     * Labels global connector data and provenance policies whose resource 
paths are not
+     * represented in the policy type listing (e.g. /data/connectors vs 
/connectors).
+     */
+    private parseGlobalPolicyResource(resource: string, action: string): 
string | null {
+        if (resource === '/data/connectors') {
+            const text = action === 'write' ? 'modify the data' : 'view the 
data';
+            return `Global policy to ${text} for connectors`;
+        }
+        if (resource === '/provenance-data/connectors') {
+            return 'Global policy to view provenance for connectors';
+        }
+        return null;
+    }
+
     /**
      * Generates a human-readable restricted component policy string.
      *
@@ -156,6 +176,9 @@ export class UserAccessPolicies extends CloseOnEscapeDialog 
{
         } else if (resource.startsWith('/data')) {
             resource = this.nifiCommon.substringAfterFirst(resource, '/data');
             policyLabel += 'Data policy for ';
+        } else if (resource.startsWith('/provenance-data')) {
+            resource = this.nifiCommon.substringAfterFirst(resource, 
'/provenance-data');
+            policyLabel += 'Provenance policy for ';
         } else if (resource.startsWith('/operation')) {
             resource = this.nifiCommon.substringAfterFirst(resource, 
'/operation');
             policyLabel += 'Operate policy for ';
@@ -183,6 +206,8 @@ export class UserAccessPolicies extends CloseOnEscapeDialog 
{
             policyLabel += 'reporting task ';
         } else if (resource.startsWith('/parameter-contexts')) {
             policyLabel += 'parameter context ';
+        } else if (resource.startsWith('/connectors')) {
+            policyLabel += 'connector ';
         }
 
         const componentReference: ComponentReferenceEntity | undefined = 
policy.component.componentReference;
@@ -291,6 +316,8 @@ export class UserAccessPolicies extends CloseOnEscapeDialog 
{
             return ['/settings', 'reporting-tasks', componentReference.id];
         } else if (resource.indexOf('/parameter-contexts') >= 0) {
             return ['/parameter-contexts', componentReference.id];
+        } else if (resource.indexOf('/connectors') >= 0) {
+            return ['/connectors', componentReference.id];
         }
         return ['/'];
     }
diff --git 
a/nifi-frontend/src/main/frontend/libs/shared/src/components/component-context/component-context.component.html
 
b/nifi-frontend/src/main/frontend/libs/shared/src/components/component-context/component-context.component.html
index f7fdf08f8b2..c5498610dcc 100644
--- 
a/nifi-frontend/src/main/frontend/libs/shared/src/components/component-context/component-context.component.html
+++ 
b/nifi-frontend/src/main/frontend/libs/shared/src/components/component-context/component-context.component.html
@@ -17,8 +17,12 @@
 
 <div class="component-context flex flex-col">
     <div class="flex gap-x-1 items-center">
-        <div class="context-logo text-3xl flex flex-col">
-            <i class="icon tertiary-color" [class]="componentIconClass"></i>
+        <div class="context-logo text-3xl flex items-center">
+            @if (componentIcon.iconType === 'font-awesome') {
+                <i class="fa mr-2 tertiary-color" 
[class]="componentIcon.className"></i>
+            } @else {
+                <i class="icon tertiary-color" 
[class]="componentIcon.className"></i>
+            }
         </div>
         <div class="flex flex-col flex-1">
             <div class="context-name w-full">{{ name }}</div>
diff --git 
a/nifi-frontend/src/main/frontend/libs/shared/src/components/component-context/component-context.component.spec.ts
 
b/nifi-frontend/src/main/frontend/libs/shared/src/components/component-context/component-context.component.spec.ts
index 51ae4b3490f..05cef1325e3 100644
--- 
a/nifi-frontend/src/main/frontend/libs/shared/src/components/component-context/component-context.component.spec.ts
+++ 
b/nifi-frontend/src/main/frontend/libs/shared/src/components/component-context/component-context.component.spec.ts
@@ -16,7 +16,7 @@
  */
 
 import { ComponentFixture, TestBed } from '@angular/core/testing';
-
+import { ComponentType } from '../../types';
 import { ComponentContext } from './component-context.component';
 
 describe('ComponentContext', () => {
@@ -36,4 +36,20 @@ describe('ComponentContext', () => {
     it('should create', () => {
         expect(component).toBeTruthy();
     });
+
+    it('should use font awesome plug icon for connectors', () => {
+        component.type = ComponentType.Connector;
+        expect(component.componentIcon).toEqual({
+            className: 'fa-plug',
+            iconType: 'font-awesome'
+        });
+    });
+
+    it('should use flowfont for processors', () => {
+        component.type = ComponentType.Processor;
+        expect(component.componentIcon).toEqual({
+            className: 'icon-processor',
+            iconType: 'flowfont'
+        });
+    });
 });
diff --git 
a/nifi-frontend/src/main/frontend/libs/shared/src/components/component-context/component-context.component.ts
 
b/nifi-frontend/src/main/frontend/libs/shared/src/components/component-context/component-context.component.ts
index 535f049f557..b34c92b7bc8 100644
--- 
a/nifi-frontend/src/main/frontend/libs/shared/src/components/component-context/component-context.component.ts
+++ 
b/nifi-frontend/src/main/frontend/libs/shared/src/components/component-context/component-context.component.ts
@@ -20,6 +20,11 @@ import { ComponentTypeNamePipe } from 
'../../pipes/component-type-name.pipe';
 import { ComponentType } from '../../types';
 import { CopyDirective } from '../../directives/copy/copy.directive';
 
+interface IconMeta {
+    className: string;
+    iconType: 'font-awesome' | 'flowfont';
+}
+
 @Component({
     selector: 'component-context',
     imports: [ComponentTypeNamePipe, CopyDirective],
@@ -27,12 +32,14 @@ import { CopyDirective } from 
'../../directives/copy/copy.directive';
     styleUrl: './component-context.component.scss'
 })
 export class ComponentContext {
+    private static readonly DEFAULT_ICON: IconMeta = { className: 'icon-drop', 
iconType: 'flowfont' };
+
     private _componentType: ComponentType | string | null = 
ComponentType.Processor;
-    componentIconClass = '';
+    componentIcon: IconMeta = ComponentContext.DEFAULT_ICON;
 
     @Input() set type(type: ComponentType | string | null) {
         this._componentType = type;
-        this.componentIconClass = this.getIconClassName(type);
+        this.componentIcon = this.getIconMeta(type);
     }
 
     get type(): ComponentType | string | null {
@@ -42,29 +49,31 @@ export class ComponentContext {
     @Input() id: string | null = null;
     @Input() name: string | undefined = '';
 
-    private getIconClassName(type: ComponentType | string | null) {
+    private getIconMeta(type: ComponentType | string | null): IconMeta {
         if (type === null) {
-            return 'icon-drop';
+            return ComponentContext.DEFAULT_ICON;
         }
         switch (type) {
             case ComponentType.Connection:
-                return 'icon-connect';
+                return { className: 'icon-connect', iconType: 'flowfont' };
             case ComponentType.Processor:
-                return 'icon-processor';
+                return { className: 'icon-processor', iconType: 'flowfont' };
             case ComponentType.OutputPort:
-                return 'icon-port-out';
+                return { className: 'icon-port-out', iconType: 'flowfont' };
             case ComponentType.InputPort:
-                return 'icon-port-in';
+                return { className: 'icon-port-in', iconType: 'flowfont' };
             case ComponentType.ProcessGroup:
-                return 'icon-group';
+                return { className: 'icon-group', iconType: 'flowfont' };
             case ComponentType.Funnel:
-                return 'icon-funnel';
+                return { className: 'icon-funnel', iconType: 'flowfont' };
             case ComponentType.Label:
-                return 'icon-label';
+                return { className: 'icon-label', iconType: 'flowfont' };
             case ComponentType.RemoteProcessGroup:
-                return 'icon-group-remote';
+                return { className: 'icon-group-remote', iconType: 'flowfont' 
};
+            case ComponentType.Connector:
+                return { className: 'fa-plug', iconType: 'font-awesome' };
             default:
-                return 'icon-drop';
+                return ComponentContext.DEFAULT_ICON;
         }
     }
 }
diff --git 
a/nifi-frontend/src/main/frontend/libs/shared/src/pipes/component-type-name.pipe.ts
 
b/nifi-frontend/src/main/frontend/libs/shared/src/pipes/component-type-name.pipe.ts
index 5ce2cea550b..aeced9b968f 100644
--- 
a/nifi-frontend/src/main/frontend/libs/shared/src/pipes/component-type-name.pipe.ts
+++ 
b/nifi-frontend/src/main/frontend/libs/shared/src/pipes/component-type-name.pipe.ts
@@ -53,6 +53,8 @@ export class ComponentTypeNamePipe implements PipeTransform {
                 return 'Remote Process Group';
             case ComponentType.ReportingTask:
                 return 'Reporting Task';
+            case ComponentType.Connector:
+                return 'Connector';
             default:
                 return type;
         }


Reply via email to