You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by mc...@apache.org on 2024/01/26 17:58:39 UTC

(nifi) branch main updated: [NIFI-12622] - Parameter Providers - listing table (#8298)

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

mcgilman 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 0e87032ef3 [NIFI-12622] - Parameter Providers - listing table (#8298)
0e87032ef3 is described below

commit 0e87032ef3e2b74c1451f5859e277e6d9787b4d8
Author: Rob Fellows <ro...@gmail.com>
AuthorDate: Fri Jan 26 12:58:33 2024 -0500

    [NIFI-12622] - Parameter Providers - listing table (#8298)
    
    * [NIFI-12622] - Parameter Providers - listing table
    * Add a new Parameter Provider
    * Delete parameter provider
    * refactor to reduce duplicate code when creating new properties
    * support editing parameter providers
    * refactored inline service creation into PropertyTableHelperService
    * added parameter provider linking to access policies.
    
    * fix bugs in flow reducer not setting state properly when starting/stopping components and run once.
    
    * address review feedback
    
    * refactored nifiCommon to provide stripProtocol method that was implemented in loads of places. replaced all occurrences to the nifiCommon implementation.
    
    This closes #8298
---
 .../web/api/entity/ParameterProvidersEntity.java   |  21 ++
 .../ParameterProvidersEndpointMerger.java          |   2 +
 .../java/org/apache/nifi/web/api/FlowResource.java |   1 +
 .../service/access-policy.service.ts               |  17 +-
 .../component-access-policies.component.ts         |   3 +
 .../counter-table/counter-table.component.ts       |  11 +-
 .../service/controller-service.service.ts          |  53 ++-
 .../pages/flow-designer/service/flow.service.ts    |  27 +-
 .../flow-designer/service/parameter.service.ts     |  17 +-
 .../pages/flow-designer/service/queue.service.ts   |  17 +-
 .../controller-services.actions.ts                 |   2 +-
 .../controller-services.effects.ts                 | 152 ++-------
 .../state/controller-services/index.ts             |   7 -
 .../pages/flow-designer/state/flow/flow.effects.ts | 123 +------
 .../pages/flow-designer/state/flow/flow.reducer.ts |  15 +-
 .../controller-services.component.spec.ts          |   4 +-
 .../service/parameter-contexts.service.ts          |  17 +-
 .../parameter-context-listing.module.ts            |   2 +-
 .../parameter-context-table.component.ts           |  10 +-
 .../pages/provenance/service/provenance.service.ts |  15 +-
 .../src/app/pages/queue/service/queue.service.ts   |  23 +-
 .../settings/feature/settings-routing.module.ts    |  21 +-
 .../app/pages/settings/feature/settings.module.ts  |   4 +-
 .../settings/service/flow-analysis-rule.service.ts |  22 +-
 .../management-controller-service.service.ts       |  29 +-
 .../settings/service/parameter-provider.service.ts |  78 +++++
 .../settings/service/registry-client.service.ts    |  20 +-
 .../settings/service/reporting-task.service.ts     |  27 +-
 .../flow-analysis-rules.effects.ts                 | 123 +------
 .../nifi/src/app/pages/settings/state/index.ts     |   6 +-
 .../state/management-controller-services/index.ts  |   6 -
 .../management-controller-services.actions.ts      |   2 +-
 .../management-controller-services.effects.ts      | 134 ++------
 .../settings/state/parameter-providers/index.ts    | 117 +++++++
 .../parameter-providers.actions.ts                 |  99 ++++++
 .../parameter-providers.effects.ts                 | 359 +++++++++++++++++++++
 .../parameter-providers.reducer.ts                 | 106 ++++++
 .../parameter-providers.selectors.ts               |  56 ++++
 .../registry-clients/registry-clients.effects.ts   | 129 +-------
 .../reporting-tasks/reporting-tasks.effects.ts     | 109 +------
 .../create-parameter-provider.component.html}      |   8 +-
 .../create-parameter-provider.component.scss}      |  13 +-
 .../create-parameter-provider.component.spec.ts    |  81 +++++
 .../create-parameter-provider.component.ts}        |  33 +-
 .../edit-parameter-provider.component.html         |  91 ++++++
 .../edit-parameter-provider.component.scss}        |  29 +-
 .../edit-parameter-provider.component.spec.ts      | 170 ++++++++++
 .../edit-parameter-provider.component.ts           | 141 ++++++++
 ...arameter-provider-references.component.spec.ts} |  14 +-
 .../parameter-provider-references.component.ts     |  50 +++
 .../parameter-providers-references.component.html  |  76 +++++
 .../parameter-providers-references.component.scss} |  19 +-
 .../parameter-providers-table.component.html       | 121 +++++++
 .../parameter-providers-table.component.scss}      |  17 +-
 .../parameter-providers-table.component.spec.ts}   |  15 +-
 .../parameter-providers-table.component.ts         | 176 ++++++++++
 .../parameter-providers.component.html             |  37 ++-
 .../parameter-providers.component.spec.ts          |   9 +-
 .../parameter-providers.component.ts               |  98 +++++-
 .../parameter-providers.module.ts                  |   5 +-
 .../reporting-task-table.component.ts              |  10 +-
 .../src/app/pages/users/service/users.service.ts   |  21 +-
 .../src/app/service/component-state.service.ts     |  17 +-
 .../service/controller-service-state.service.ts    |  19 +-
 .../nifi/src/app/service/nifi-common.service.ts    |  13 +
 .../app/service/property-table-helper.service.ts   | 177 ++++++++++
 .../extension-types/extension-types.selectors.ts   |   5 +
 .../src/main/nifi/src/app/state/shared/index.ts    |  22 +-
 .../controller-service-table.component.ts          |  10 +-
 .../create-controller-service.component.spec.ts    |   4 +-
 .../create-controller-service.component.ts         |   4 +-
 .../extension-creation.component.ts                |  10 +-
 72 files changed, 2430 insertions(+), 1071 deletions(-)

diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ParameterProvidersEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ParameterProvidersEntity.java
index 900bdfb4c3..88972d4d2f 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ParameterProvidersEntity.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ParameterProvidersEntity.java
@@ -16,7 +16,12 @@
  */
 package org.apache.nifi.web.api.entity;
 
+import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.xml.bind.annotation.XmlRootElement;
+import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import org.apache.nifi.web.api.dto.util.TimeAdapter;
+
+import java.util.Date;
 import java.util.Set;
 
 /**
@@ -27,6 +32,8 @@ public class ParameterProvidersEntity extends Entity {
 
     private Set<ParameterProviderEntity> parameterProviders;
 
+    private Date currentTime;
+
     /**
      * @return list of parameter providers that are being serialized
      */
@@ -38,4 +45,18 @@ public class ParameterProvidersEntity extends Entity {
         this.parameterProviders = parameterProviders;
     }
 
+    /**
+     * @return current time on the server
+     */
+    @XmlJavaTypeAdapter(TimeAdapter.class)
+    @Schema(description = "The current time on the system.",
+            type = "string"
+    )
+    public Date getCurrentTime() {
+        return currentTime;
+    }
+
+    public void setCurrentTime(Date currentTime) {
+        this.currentTime = currentTime;
+    }
 }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ParameterProvidersEndpointMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ParameterProvidersEndpointMerger.java
index 079922f5d2..1b3e6807c6 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ParameterProvidersEndpointMerger.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ParameterProvidersEndpointMerger.java
@@ -23,6 +23,7 @@ import org.apache.nifi.web.api.entity.ParameterProviderEntity;
 import org.apache.nifi.web.api.entity.ParameterProvidersEntity;
 
 import java.net.URI;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -60,6 +61,7 @@ public class ParameterProvidersEndpointMerger extends AbstractSingleEntityEndpoi
         }
 
         clientEntity.setParameterProviders(new HashSet<>(providerEntities.values()));
+        clientEntity.setCurrentTime(new Date());
     }
 
 }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java
index fd9bb8c5d4..6ed92d1d73 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java
@@ -672,6 +672,7 @@ public class FlowResource extends ApplicationResource {
         // create the response entity
         final ParameterProvidersEntity entity = new ParameterProvidersEntity();
         entity.setParameterProviders(parameterProviders);
+        entity.setCurrentTime(new Date());
 
         // generate the response
         return generateOkResponse(entity).build();
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/access-policies/service/access-policy.service.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/access-policies/service/access-policy.service.ts
index 34ab4bf959..8fb0800b80 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/access-policies/service/access-policy.service.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/access-policies/service/access-policy.service.ts
@@ -33,19 +33,6 @@ export class AccessPolicyService {
         private nifiCommon: NiFiCommon
     ) {}
 
-    /**
-     * The NiFi model contain the url for each component. That URL is an absolute URL. Angular CSRF handling
-     * does not work on absolute URLs, so we need to strip off the proto for the request header to be added.
-     *
-     * https://stackoverflow.com/a/59586462
-     *
-     * @param url
-     * @private
-     */
-    private stripProtocol(url: string): string {
-        return this.nifiCommon.substringAfterFirst(url, ':');
-    }
-
     createAccessPolicy(resourceAction: ResourceAction): Observable<any> {
         let resource: string = `/${resourceAction.resource}`;
         if (resourceAction.resourceIdentifier) {
@@ -92,12 +79,12 @@ export class AccessPolicyService {
             }
         };
 
-        return this.httpClient.put(this.stripProtocol(accessPolicy.uri), payload);
+        return this.httpClient.put(this.nifiCommon.stripProtocol(accessPolicy.uri), payload);
     }
 
     deleteAccessPolicy(accessPolicy: AccessPolicyEntity): Observable<any> {
         const revision: any = this.client.getRevision(accessPolicy);
-        return this.httpClient.delete(this.stripProtocol(accessPolicy.uri), { params: revision });
+        return this.httpClient.delete(this.nifiCommon.stripProtocol(accessPolicy.uri), { params: revision });
     }
 
     getUsers(): Observable<any> {
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/access-policies/ui/component-access-policies/component-access-policies.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/access-policies/ui/component-access-policies/component-access-policies.component.ts
index b22bdeb566..5d52f50df3 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/access-policies/ui/component-access-policies/component-access-policies.component.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/access-policies/ui/component-access-policies/component-access-policies.component.ts
@@ -306,6 +306,7 @@ export class ComponentAccessPolicies implements OnInit, OnDestroy {
                 return 'icon-label';
             case 'remote-process-groups':
                 return 'icon-group-remote';
+            case 'parameter-providers':
             case 'parameter-contexts':
                 return 'icon-drop';
         }
@@ -329,6 +330,8 @@ export class ComponentAccessPolicies implements OnInit, OnDestroy {
                 return 'Remote Process Group';
             case 'parameter-contexts':
                 return 'Parameter Contexts';
+            case 'parameter-providers':
+                return 'Parameter Provider';
         }
 
         return 'Process Group';
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/counters/ui/counter-listing/counter-table/counter-table.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/counters/ui/counter-listing/counter-table/counter-table.component.ts
index 2ae859dfce..666aa21833 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/counters/ui/counter-listing/counter-table/counter-table.component.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/counters/ui/counter-listing/counter-table/counter-table.component.ts
@@ -42,11 +42,13 @@ export class CounterTable implements AfterViewInit {
     @Input() initialSortColumn: 'context' | 'name' | 'value' = 'context';
     @Input() initialSortDirection: 'asc' | 'desc' = 'asc';
 
+    activeSort: Sort = {
+        active: this.initialSortColumn,
+        direction: this.initialSortDirection
+    };
+
     @Input() set counters(counterEntities: CounterEntity[]) {
-        this.dataSource.data = this.sortEntities(counterEntities, {
-            active: this.initialSortColumn,
-            direction: this.initialSortDirection
-        });
+        this.dataSource.data = this.sortEntities(counterEntities, this.activeSort);
 
         this.dataSource.filterPredicate = (data: CounterEntity, filter: string) => {
             const { filterTerm, filterColumn } = JSON.parse(filter);
@@ -128,6 +130,7 @@ export class CounterTable implements AfterViewInit {
     }
 
     sortData(sort: Sort) {
+        this.activeSort = sort;
         this.dataSource.data = this.sortEntities(this.dataSource.data, sort);
     }
 
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/controller-service.service.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/controller-service.service.ts
index c354935607..cb3ffe42a6 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/controller-service.service.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/controller-service.service.ts
@@ -16,34 +16,22 @@
  */
 
 import { Injectable } from '@angular/core';
-import { Observable, throwError } from 'rxjs';
+import { EMPTY, Observable } from 'rxjs';
 import { HttpClient } from '@angular/common/http';
 import { Client } from '../../../service/client.service';
 import { NiFiCommon } from '../../../service/nifi-common.service';
-import { ControllerServiceEntity } from '../../../state/shared';
 import {
-    ConfigureControllerServiceRequest,
+    ControllerServiceCreator,
+    ControllerServiceEntity,
     CreateControllerServiceRequest,
-    DeleteControllerServiceRequest
-} from '../state/controller-services';
+    PropertyDescriptorRetriever
+} from '../../../state/shared';
+import { ConfigureControllerServiceRequest, DeleteControllerServiceRequest } from '../state/controller-services';
 
 @Injectable({ providedIn: 'root' })
-export class ControllerServiceService {
+export class ControllerServiceService implements ControllerServiceCreator, PropertyDescriptorRetriever {
     private static readonly API: string = '../nifi-api';
 
-    /**
-     * The NiFi model contain the url for each component. That URL is an absolute URL. Angular CSRF handling
-     * does not work on absolute URLs, so we need to strip off the proto for the request header to be added.
-     *
-     * https://stackoverflow.com/a/59586462
-     *
-     * @param url
-     * @private
-     */
-    private stripProtocol(url: string): string {
-        return this.nifiCommon.substringAfterFirst(url, ':');
-    }
-
     constructor(
         private httpClient: HttpClient,
         private client: Client,
@@ -69,17 +57,20 @@ export class ControllerServiceService {
     }
 
     createControllerService(createControllerService: CreateControllerServiceRequest): Observable<any> {
-        const processGroupId: string = createControllerService.processGroupId;
-        return this.httpClient.post(
-            `${ControllerServiceService.API}/process-groups/${processGroupId}/controller-services`,
-            {
-                revision: createControllerService.revision,
-                component: {
-                    bundle: createControllerService.controllerServiceBundle,
-                    type: createControllerService.controllerServiceType
+        if (createControllerService.processGroupId) {
+            const processGroupId: string = createControllerService.processGroupId;
+            return this.httpClient.post(
+                `${ControllerServiceService.API}/process-groups/${processGroupId}/controller-services`,
+                {
+                    revision: createControllerService.revision,
+                    component: {
+                        bundle: createControllerService.controllerServiceBundle,
+                        type: createControllerService.controllerServiceType
+                    }
                 }
-            }
-        );
+            );
+        }
+        return EMPTY;
     }
 
     getPropertyDescriptor(id: string, propertyName: string, sensitive: boolean): Observable<any> {
@@ -94,7 +85,7 @@ export class ControllerServiceService {
 
     updateControllerService(configureControllerService: ConfigureControllerServiceRequest): Observable<any> {
         return this.httpClient.put(
-            this.stripProtocol(configureControllerService.uri),
+            this.nifiCommon.stripProtocol(configureControllerService.uri),
             configureControllerService.payload
         );
     }
@@ -102,6 +93,6 @@ export class ControllerServiceService {
     deleteControllerService(deleteControllerService: DeleteControllerServiceRequest): Observable<any> {
         const entity: ControllerServiceEntity = deleteControllerService.controllerService;
         const revision: any = this.client.getRevision(entity);
-        return this.httpClient.delete(this.stripProtocol(entity.uri), { params: revision });
+        return this.httpClient.delete(this.nifiCommon.stripProtocol(entity.uri), { params: revision });
     }
 }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/flow.service.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/flow.service.ts
index b2acac8580..8d841d986c 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/flow.service.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/flow.service.ts
@@ -38,12 +38,12 @@ import {
     UpdateComponentRequest,
     UploadProcessGroupRequest
 } from '../state/flow';
-import { ComponentType } from '../../../state/shared';
+import { ComponentType, PropertyDescriptorRetriever } from '../../../state/shared';
 import { Client } from '../../../service/client.service';
 import { NiFiCommon } from '../../../service/nifi-common.service';
 
 @Injectable({ providedIn: 'root' })
-export class FlowService {
+export class FlowService implements PropertyDescriptorRetriever {
     private static readonly API: string = '../nifi-api';
 
     constructor(
@@ -53,19 +53,6 @@ export class FlowService {
         private nifiCommon: NiFiCommon
     ) {}
 
-    /**
-     * The NiFi model contain the url for each component. That URL is an absolute URL. Angular CSRF handling
-     * does not work on absolute URLs, so we need to strip off the proto for the request header to be added.
-     *
-     * https://stackoverflow.com/a/59586462
-     *
-     * @param url
-     * @private
-     */
-    private stripProtocol(url: string): string {
-        return this.nifiCommon.substringAfterFirst(url, ':');
-    }
-
     getFlow(processGroupId = 'root'): Observable<any> {
         // TODO - support uiOnly... this would mean that we need to load the entire resource prior to editing
         return this.httpClient.get(`${FlowService.API}/flow/process-groups/${processGroupId}`);
@@ -212,13 +199,13 @@ export class FlowService {
 
     updateComponent(updateComponent: UpdateComponentRequest): Observable<any> {
         // return throwError('API Error');
-        return this.httpClient.put(this.stripProtocol(updateComponent.uri), updateComponent.payload);
+        return this.httpClient.put(this.nifiCommon.stripProtocol(updateComponent.uri), updateComponent.payload);
     }
 
     deleteComponent(deleteComponent: DeleteComponentRequest): Observable<any> {
         // return throwError('API Error');
         const revision: any = this.client.getRevision(deleteComponent.entity);
-        return this.httpClient.delete(this.stripProtocol(deleteComponent.uri), { params: revision });
+        return this.httpClient.delete(this.nifiCommon.stripProtocol(deleteComponent.uri), { params: revision });
     }
 
     createSnippet(snippet: Snippet): Observable<any> {
@@ -250,7 +237,7 @@ export class FlowService {
             disconnectedNodeAcknowledged: false,
             state: 'RUN_ONCE'
         };
-        return this.httpClient.put(`${this.stripProtocol(request.uri)}/run-status`, startRequest);
+        return this.httpClient.put(`${this.nifiCommon.stripProtocol(request.uri)}/run-status`, startRequest);
     }
 
     startComponent(request: StartComponentRequest): Observable<any> {
@@ -259,7 +246,7 @@ export class FlowService {
             disconnectedNodeAcknowledged: false,
             state: request.type === ComponentType.RemoteProcessGroup ? 'TRANSMITTING' : 'RUNNING'
         };
-        return this.httpClient.put(`${this.stripProtocol(request.uri)}/run-status`, startRequest);
+        return this.httpClient.put(`${this.nifiCommon.stripProtocol(request.uri)}/run-status`, startRequest);
     }
 
     stopComponent(request: StopComponentRequest): Observable<any> {
@@ -268,7 +255,7 @@ export class FlowService {
             disconnectedNodeAcknowledged: false,
             state: 'STOPPED'
         };
-        return this.httpClient.put(`${this.stripProtocol(request.uri)}/run-status`, stopRequest);
+        return this.httpClient.put(`${this.nifiCommon.stripProtocol(request.uri)}/run-status`, stopRequest);
     }
 
     startProcessGroup(request: StartProcessGroupRequest): Observable<any> {
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/parameter.service.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/parameter.service.ts
index d8e1731c68..009a01c4ca 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/parameter.service.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/parameter.service.ts
@@ -30,19 +30,6 @@ export class ParameterService {
         private nifiCommon: NiFiCommon
     ) {}
 
-    /**
-     * The NiFi model contain the url for each component. That URL is an absolute URL. Angular CSRF handling
-     * does not work on absolute URLs, so we need to strip off the proto for the request header to be added.
-     *
-     * https://stackoverflow.com/a/59586462
-     *
-     * @param url
-     * @private
-     */
-    private stripProtocol(url: string): string {
-        return this.nifiCommon.substringAfterFirst(url, ':');
-    }
-
     getParameterContext(id: string, includeInheritedParameters: boolean): Observable<any> {
         return this.httpClient.get(`${ParameterService.API}/parameter-contexts/${id}`, {
             params: {
@@ -59,10 +46,10 @@ export class ParameterService {
     }
 
     pollParameterContextUpdate(updateRequest: ParameterContextUpdateRequest): Observable<any> {
-        return this.httpClient.get(this.stripProtocol(updateRequest.uri));
+        return this.httpClient.get(this.nifiCommon.stripProtocol(updateRequest.uri));
     }
 
     deleteParameterContextUpdate(updateRequest: ParameterContextUpdateRequest): Observable<any> {
-        return this.httpClient.delete(this.stripProtocol(updateRequest.uri));
+        return this.httpClient.delete(this.nifiCommon.stripProtocol(updateRequest.uri));
     }
 }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/queue.service.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/queue.service.ts
index df242809f3..f6c657bc53 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/queue.service.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/queue.service.ts
@@ -30,19 +30,6 @@ export class QueueService {
         private nifiCommon: NiFiCommon
     ) {}
 
-    /**
-     * The NiFi model contain the url for each component. That URL is an absolute URL. Angular CSRF handling
-     * does not work on absolute URLs, so we need to strip off the proto for the request header to be added.
-     *
-     * https://stackoverflow.com/a/59586462
-     *
-     * @param url
-     * @private
-     */
-    private stripProtocol(url: string): string {
-        return this.nifiCommon.substringAfterFirst(url, ':');
-    }
-
     submitEmptyQueueRequest(emptyQueueRequest: SubmitEmptyQueueRequest): Observable<any> {
         return this.httpClient.post(
             `${QueueService.API}/flowfile-queues/${emptyQueueRequest.connectionId}/drop-requests`,
@@ -58,10 +45,10 @@ export class QueueService {
     }
 
     pollEmptyQueueRequest(dropRequest: DropRequest): Observable<any> {
-        return this.httpClient.get(this.stripProtocol(dropRequest.uri));
+        return this.httpClient.get(this.nifiCommon.stripProtocol(dropRequest.uri));
     }
 
     deleteEmptyQueueRequest(dropRequest: DropRequest): Observable<any> {
-        return this.httpClient.delete(this.stripProtocol(dropRequest.uri));
+        return this.httpClient.delete(this.nifiCommon.stripProtocol(dropRequest.uri));
     }
 }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.actions.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.actions.ts
index 401f0da757..7dd7c9ff59 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.actions.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.actions.ts
@@ -19,7 +19,6 @@ import { createAction, props } from '@ngrx/store';
 import {
     ConfigureControllerServiceRequest,
     ConfigureControllerServiceSuccess,
-    CreateControllerServiceRequest,
     CreateControllerServiceSuccess,
     DeleteControllerServiceRequest,
     DeleteControllerServiceSuccess,
@@ -28,6 +27,7 @@ import {
     SelectControllerServiceRequest
 } from './index';
 import {
+    CreateControllerServiceRequest,
     DisableControllerServiceDialogRequest,
     EditControllerServiceDialogRequest,
     SetEnableControllerServiceDialogRequest
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.effects.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.effects.ts
index 4efe9641be..7997d62ca7 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.effects.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.effects.ts
@@ -18,20 +18,7 @@
 import { Injectable } from '@angular/core';
 import { Actions, concatLatestFrom, createEffect, ofType } from '@ngrx/effects';
 import * as ControllerServicesActions from './controller-services.actions';
-import {
-    catchError,
-    combineLatest,
-    filter,
-    from,
-    map,
-    NEVER,
-    Observable,
-    of,
-    switchMap,
-    take,
-    takeUntil,
-    tap
-} from 'rxjs';
+import { catchError, combineLatest, filter, from, map, NEVER, of, switchMap, take, takeUntil, tap } from 'rxjs';
 import { MatDialog } from '@angular/material/dialog';
 import { Store } from '@ngrx/store';
 import { NiFiState } from '../../../../state';
@@ -45,17 +32,10 @@ import {
     ControllerServiceReferencingComponent,
     EditParameterRequest,
     EditParameterResponse,
-    InlineServiceCreationRequest,
-    InlineServiceCreationResponse,
-    NewPropertyDialogRequest,
-    NewPropertyDialogResponse,
     Parameter,
     ParameterEntity,
-    Property,
-    PropertyDescriptor,
     UpdateControllerServiceRequest
 } from '../../../../state/shared';
-import { NewPropertyDialog } from '../../../../ui/common/new-property-dialog/new-property-dialog.component';
 import { Router } from '@angular/router';
 import { ExtensionTypesService } from '../../../../service/extension-types.service';
 import { selectCurrentProcessGroupId, selectSaving } from './controller-services.selectors';
@@ -68,6 +48,7 @@ import * as ParameterActions from '../parameter/parameter.actions';
 import { ParameterService } from '../../service/parameter.service';
 import { EnableControllerService } from '../../../../ui/common/controller-service/enable-controller-service/enable-controller-service.component';
 import { DisableControllerService } from '../../../../ui/common/controller-service/disable-controller-service/disable-controller-service.component';
+import { PropertyTableHelperService } from '../../../../service/property-table-helper.service';
 
 @Injectable()
 export class ControllerServicesEffects {
@@ -80,7 +61,8 @@ export class ControllerServicesEffects {
         private parameterService: ParameterService,
         private extensionTypesService: ExtensionTypesService,
         private dialog: MatDialog,
-        private router: Router
+        private router: Router,
+        private propertyTableHelperService: PropertyTableHelperService
     ) {}
 
     loadControllerServices$ = createEffect(() =>
@@ -225,36 +207,8 @@ export class ControllerServicesEffects {
 
                     editDialogReference.componentInstance.saving$ = this.store.select(selectSaving);
 
-                    editDialogReference.componentInstance.createNewProperty = (
-                        existingProperties: string[],
-                        allowsSensitive: boolean
-                    ): Observable<Property> => {
-                        const dialogRequest: NewPropertyDialogRequest = { existingProperties, allowsSensitive };
-                        const newPropertyDialogReference = this.dialog.open(NewPropertyDialog, {
-                            data: dialogRequest,
-                            panelClass: 'small-dialog'
-                        });
-
-                        return newPropertyDialogReference.componentInstance.newProperty.pipe(
-                            take(1),
-                            switchMap((dialogResponse: NewPropertyDialogResponse) => {
-                                return this.controllerServiceService
-                                    .getPropertyDescriptor(request.id, dialogResponse.name, dialogResponse.sensitive)
-                                    .pipe(
-                                        take(1),
-                                        map((response) => {
-                                            newPropertyDialogReference.close();
-
-                                            return {
-                                                property: dialogResponse.name,
-                                                value: null,
-                                                descriptor: response.propertyDescriptor
-                                            };
-                                        })
-                                    );
-                            })
-                        );
-                    };
+                    editDialogReference.componentInstance.createNewProperty =
+                        this.propertyTableHelperService.createNewProperty(request.id, this.controllerServiceService);
 
                     const goTo = (commands: string[], destination: string): void => {
                         if (editDialogReference.componentInstance.editControllerServiceForm.dirty) {
@@ -393,85 +347,21 @@ export class ControllerServicesEffects {
                         });
                     };
 
-                    editDialogReference.componentInstance.createNewService = (
-                        serviceRequest: InlineServiceCreationRequest
-                    ): Observable<InlineServiceCreationResponse> => {
-                        const descriptor: PropertyDescriptor = serviceRequest.descriptor;
-
-                        // fetch all services that implement the requested service api
-                        return this.extensionTypesService
-                            .getImplementingControllerServiceTypes(
-                                // @ts-ignore
-                                descriptor.identifiesControllerService,
-                                descriptor.identifiesControllerServiceBundle
-                            )
-                            .pipe(
-                                take(1),
-                                switchMap((implementingTypesResponse) => {
-                                    // show the create controller service dialog with the types that implemented the interface
-                                    const createServiceDialogReference = this.dialog.open(CreateControllerService, {
-                                        data: {
-                                            controllerServiceTypes: implementingTypesResponse.controllerServiceTypes
-                                        },
-                                        panelClass: 'medium-dialog'
-                                    });
-
-                                    return createServiceDialogReference.componentInstance.createControllerService.pipe(
-                                        take(1),
-                                        switchMap((controllerServiceType) => {
-                                            // typically this sequence would be implemented with ngrx actions, however we are
-                                            // currently in an edit session and we need to return both the value (new service id)
-                                            // and updated property descriptor so the table renders correctly
-                                            return this.controllerServiceService
-                                                .createControllerService({
-                                                    revision: {
-                                                        clientId: this.client.getClientId(),
-                                                        version: 0
-                                                    },
-                                                    processGroupId,
-                                                    controllerServiceType: controllerServiceType.type,
-                                                    controllerServiceBundle: controllerServiceType.bundle
-                                                })
-                                                .pipe(
-                                                    take(1),
-                                                    switchMap((createResponse) => {
-                                                        // dispatch an inline create service success action so the new service is in the state
-                                                        this.store.dispatch(
-                                                            ControllerServicesActions.inlineCreateControllerServiceSuccess(
-                                                                {
-                                                                    response: {
-                                                                        controllerService: createResponse
-                                                                    }
-                                                                }
-                                                            )
-                                                        );
-
-                                                        // fetch an updated property descriptor
-                                                        return this.controllerServiceService
-                                                            .getPropertyDescriptor(serviceId, descriptor.name, false)
-                                                            .pipe(
-                                                                take(1),
-                                                                map((descriptorResponse) => {
-                                                                    createServiceDialogReference.close();
-
-                                                                    return {
-                                                                        value: createResponse.id,
-                                                                        descriptor:
-                                                                            descriptorResponse.propertyDescriptor
-                                                                    };
-                                                                })
-                                                            );
-                                                    }),
-                                                    catchError((error) => {
-                                                        // TODO - show error
-                                                        return NEVER;
-                                                    })
-                                                );
-                                        })
-                                    );
-                                })
-                            );
-                    };
+                    editDialogReference.componentInstance.createNewService =
+                        this.propertyTableHelperService.createNewService(
+                            request.id,
+                            this.controllerServiceService,
+                            this.controllerServiceService,
+                            processGroupId,
+                            (createResponse) =>
+                                this.store.dispatch(
+                                    ControllerServicesActions.inlineCreateControllerServiceSuccess({
+                                        response: {
+                                            controllerService: createResponse
+                                        }
+                                    })
+                                )
+                        );
 
                     editDialogReference.componentInstance.editControllerService
                         .pipe(takeUntil(editDialogReference.afterClosed()))
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/controller-services/index.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/controller-services/index.ts
index b9551f7784..4cafd6f50e 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/controller-services/index.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/controller-services/index.ts
@@ -31,13 +31,6 @@ export interface LoadControllerServicesResponse {
     loadedTimestamp: string;
 }
 
-export interface CreateControllerServiceRequest {
-    processGroupId: string;
-    controllerServiceType: string;
-    controllerServiceBundle: Bundle;
-    revision: Revision;
-}
-
 export interface CreateControllerServiceSuccess {
     controllerService: ControllerServiceEntity;
 }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/flow.effects.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/flow.effects.ts
index bb2208c6db..1af9e879cf 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/flow.effects.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/flow.effects.ts
@@ -69,14 +69,8 @@ import {
     ComponentType,
     EditParameterRequest,
     EditParameterResponse,
-    InlineServiceCreationRequest,
-    InlineServiceCreationResponse,
-    NewPropertyDialogRequest,
-    NewPropertyDialogResponse,
     Parameter,
-    ParameterEntity,
-    Property,
-    PropertyDescriptor
+    ParameterEntity
 } from '../../../../state/shared';
 import { Router } from '@angular/router';
 import { Client } from '../../../../service/client.service';
@@ -86,7 +80,6 @@ import { selectProcessorTypes } from '../../../../state/extension-types/extensio
 import { NiFiState } from '../../../../state';
 import { CreateProcessor } from '../../ui/canvas/items/processor/create-processor/create-processor.component';
 import { EditProcessor } from '../../ui/canvas/items/processor/edit-processor/edit-processor.component';
-import { NewPropertyDialog } from '../../../../ui/common/new-property-dialog/new-property-dialog.component';
 import { BirdseyeView } from '../../service/birdseye-view.service';
 import { CreateProcessGroup } from '../../ui/canvas/items/process-group/create-process-group/create-process-group.component';
 import { CreateConnection } from '../../ui/canvas/items/connection/create-connection/create-connection.component';
@@ -94,13 +87,13 @@ import { EditConnectionComponent } from '../../ui/canvas/items/connection/edit-c
 import { OkDialog } from '../../../../ui/common/ok-dialog/ok-dialog.component';
 import { GroupComponents } from '../../ui/canvas/items/process-group/group-components/group-components.component';
 import { EditProcessGroup } from '../../ui/canvas/items/process-group/edit-process-group/edit-process-group.component';
-import { CreateControllerService } from '../../../../ui/common/controller-service/create-controller-service/create-controller-service.component';
 import { ExtensionTypesService } from '../../../../service/extension-types.service';
 import { ControllerServiceService } from '../../service/controller-service.service';
 import { YesNoDialog } from '../../../../ui/common/yes-no-dialog/yes-no-dialog.component';
 import { EditParameterDialog } from '../../../../ui/common/edit-parameter-dialog/edit-parameter-dialog.component';
 import { selectParameterSaving } from '../parameter/parameter.selectors';
 import { ParameterService } from '../../service/parameter.service';
+import { PropertyTableHelperService } from '../../../../service/property-table-helper.service';
 
 @Injectable()
 export class FlowEffects {
@@ -117,7 +110,8 @@ export class FlowEffects {
         private birdseyeView: BirdseyeView,
         private connectionManager: ConnectionManager,
         private router: Router,
-        private dialog: MatDialog
+        private dialog: MatDialog,
+        private propertyTableHelperService: PropertyTableHelperService
     ) {}
 
     reloadFlow$ = createEffect(() =>
@@ -852,36 +846,8 @@ export class FlowEffects {
 
                     editDialogReference.componentInstance.saving$ = this.store.select(selectSaving);
 
-                    editDialogReference.componentInstance.createNewProperty = (
-                        existingProperties: string[],
-                        allowsSensitive: boolean
-                    ): Observable<Property> => {
-                        const dialogRequest: NewPropertyDialogRequest = { existingProperties, allowsSensitive };
-                        const newPropertyDialogReference = this.dialog.open(NewPropertyDialog, {
-                            data: dialogRequest,
-                            panelClass: 'small-dialog'
-                        });
-
-                        return newPropertyDialogReference.componentInstance.newProperty.pipe(
-                            take(1),
-                            switchMap((dialogResponse: NewPropertyDialogResponse) => {
-                                return this.flowService
-                                    .getPropertyDescriptor(processorId, dialogResponse.name, dialogResponse.sensitive)
-                                    .pipe(
-                                        take(1),
-                                        map((response) => {
-                                            newPropertyDialogReference.close();
-
-                                            return {
-                                                property: dialogResponse.name,
-                                                value: null,
-                                                descriptor: response.propertyDescriptor
-                                            };
-                                        })
-                                    );
-                            })
-                        );
-                    };
+                    editDialogReference.componentInstance.createNewProperty =
+                        this.propertyTableHelperService.createNewProperty(processorId, this.flowService);
 
                     const goTo = (commands: string[], destination: string): void => {
                         if (editDialogReference.componentInstance.editProcessorForm.dirty) {
@@ -1013,74 +979,13 @@ export class FlowEffects {
                         });
                     };
 
-                    editDialogReference.componentInstance.createNewService = (
-                        serviceRequest: InlineServiceCreationRequest
-                    ): Observable<InlineServiceCreationResponse> => {
-                        const descriptor: PropertyDescriptor = serviceRequest.descriptor;
-
-                        // fetch all services that implement the requested service api
-                        return this.extensionTypesService
-                            .getImplementingControllerServiceTypes(
-                                // @ts-ignore
-                                descriptor.identifiesControllerService,
-                                descriptor.identifiesControllerServiceBundle
-                            )
-                            .pipe(
-                                take(1),
-                                switchMap((implementingTypesResponse) => {
-                                    // show the create controller service dialog with the types that implemented the interface
-                                    const createServiceDialogReference = this.dialog.open(CreateControllerService, {
-                                        data: {
-                                            controllerServiceTypes: implementingTypesResponse.controllerServiceTypes
-                                        },
-                                        panelClass: 'medium-dialog'
-                                    });
-
-                                    return createServiceDialogReference.componentInstance.createControllerService.pipe(
-                                        take(1),
-                                        switchMap((controllerServiceType) => {
-                                            // typically this sequence would be implemented with ngrx actions, however we are
-                                            // currently in an edit session and we need to return both the value (new service id)
-                                            // and updated property descriptor so the table renders correctly
-                                            return this.controllerServiceService
-                                                .createControllerService({
-                                                    revision: {
-                                                        clientId: this.client.getClientId(),
-                                                        version: 0
-                                                    },
-                                                    processGroupId,
-                                                    controllerServiceType: controllerServiceType.type,
-                                                    controllerServiceBundle: controllerServiceType.bundle
-                                                })
-                                                .pipe(
-                                                    take(1),
-                                                    switchMap((createReponse) => {
-                                                        // fetch an updated property descriptor
-                                                        return this.flowService
-                                                            .getPropertyDescriptor(processorId, descriptor.name, false)
-                                                            .pipe(
-                                                                take(1),
-                                                                map((descriptorResponse) => {
-                                                                    createServiceDialogReference.close();
-
-                                                                    return {
-                                                                        value: createReponse.id,
-                                                                        descriptor:
-                                                                            descriptorResponse.propertyDescriptor
-                                                                    };
-                                                                })
-                                                            );
-                                                    }),
-                                                    catchError((error) => {
-                                                        // TODO - show error
-                                                        return NEVER;
-                                                    })
-                                                );
-                                        })
-                                    );
-                                })
-                            );
-                    };
+                    editDialogReference.componentInstance.createNewService =
+                        this.propertyTableHelperService.createNewService(
+                            processorId,
+                            this.controllerServiceService,
+                            this.flowService,
+                            processGroupId
+                        );
 
                     editDialogReference.componentInstance.editProcessor
                         .pipe(takeUntil(editDialogReference.afterClosed()))
@@ -2331,7 +2236,7 @@ export class FlowEffects {
                 return from(this.flowService.getProcessGroup(request.id)).pipe(
                     map((response) =>
                         FlowActions.loadChildProcessGroupSuccess({
-                            response: response.component
+                            response
                         })
                     ),
                     catchError((error) => of(FlowActions.flowApiError({ error: error.error })))
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/flow.reducer.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/flow.reducer.ts
index 8a1af18d27..87e48cb1f5 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/flow.reducer.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/flow.reducer.ts
@@ -326,10 +326,7 @@ export const flowReducer = createReducer(
             if (collection) {
                 const componentIndex: number = collection.findIndex((f: any) => response.component.id === f.id);
                 if (componentIndex > -1) {
-                    collection[componentIndex] = {
-                        ...collection[componentIndex],
-                        ...response.component
-                    };
+                    collection[componentIndex] = response.component;
                 }
             }
 
@@ -343,10 +340,7 @@ export const flowReducer = createReducer(
             if (collection) {
                 const componentIndex: number = collection.findIndex((f: any) => response.component.id === f.id);
                 if (componentIndex > -1) {
-                    collection[componentIndex] = {
-                        ...collection[componentIndex],
-                        ...response.component
-                    };
+                    collection[componentIndex] = response.component;
                 }
             }
 
@@ -361,10 +355,7 @@ export const flowReducer = createReducer(
             if (collection) {
                 const componentIndex: number = collection.findIndex((f: any) => response.id === f.id);
                 if (componentIndex > -1) {
-                    collection[componentIndex] = {
-                        ...collection[componentIndex],
-                        ...response.component
-                    };
+                    collection[componentIndex] = response;
                 }
             }
 
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.spec.ts
index 34cf6502be..fccb8a6661 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.spec.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.spec.ts
@@ -22,6 +22,8 @@ import { provideMockStore } from '@ngrx/store/testing';
 import { initialState } from '../../state/controller-services/controller-services.reducer';
 import { RouterTestingModule } from '@angular/router/testing';
 import { Component } from '@angular/core';
+import { ControllerServicesModule } from './controller-services.module';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
 
 describe('ControllerServices', () => {
     let component: ControllerServices;
@@ -37,7 +39,7 @@ describe('ControllerServices', () => {
     beforeEach(() => {
         TestBed.configureTestingModule({
             declarations: [ControllerServices],
-            imports: [RouterTestingModule, MockNavigation],
+            imports: [RouterTestingModule, MockNavigation, ControllerServicesModule, HttpClientTestingModule],
             providers: [
                 provideMockStore({
                     initialState
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/service/parameter-contexts.service.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/service/parameter-contexts.service.ts
index d013707af0..ae6b5cc2ae 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/service/parameter-contexts.service.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/service/parameter-contexts.service.ts
@@ -37,19 +37,6 @@ export class ParameterContextService {
         private nifiCommon: NiFiCommon
     ) {}
 
-    /**
-     * The NiFi model contain the url for each component. That URL is an absolute URL. Angular CSRF handling
-     * does not work on absolute URLs, so we need to strip off the proto for the request header to be added.
-     *
-     * https://stackoverflow.com/a/59586462
-     *
-     * @param url
-     * @private
-     */
-    private stripProtocol(url: string): string {
-        return this.nifiCommon.substringAfterFirst(url, ':');
-    }
-
     getParameterContexts(): Observable<any> {
         return this.httpClient.get(`${ParameterContextService.API}/flow/parameter-contexts`);
     }
@@ -77,11 +64,11 @@ export class ParameterContextService {
     }
 
     pollParameterContextUpdate(updateRequest: ParameterContextUpdateRequest): Observable<any> {
-        return this.httpClient.get(this.stripProtocol(updateRequest.uri));
+        return this.httpClient.get(this.nifiCommon.stripProtocol(updateRequest.uri));
     }
 
     deleteParameterContextUpdate(updateRequest: ParameterContextUpdateRequest): Observable<any> {
-        return this.httpClient.delete(this.stripProtocol(updateRequest.uri));
+        return this.httpClient.delete(this.nifiCommon.stripProtocol(updateRequest.uri));
     }
 
     deleteParameterContext(deleteParameterContext: DeleteParameterContextRequest): Observable<any> {
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-context-listing.module.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-context-listing.module.ts
index 69787cc17e..24fdbcf7be 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-context-listing.module.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-context-listing.module.ts
@@ -28,7 +28,7 @@ import { RouterLink } from '@angular/router';
 
 @NgModule({
     declarations: [ParameterContextListing, ParameterContextTable],
-    exports: [ParameterContextListing],
+    exports: [ParameterContextListing, ParameterContextTable],
     imports: [
         CommonModule,
         NgxSkeletonLoaderModule,
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-context-table/parameter-context-table.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-context-table/parameter-context-table.component.ts
index 6925f25f9b..9794280610 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-context-table/parameter-context-table.component.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/parameter-context-table/parameter-context-table.component.ts
@@ -31,12 +31,13 @@ import { CurrentUser } from '../../../../../state/current-user';
 export class ParameterContextTable {
     @Input() initialSortColumn: 'name' | 'provider' | 'description' = 'name';
     @Input() initialSortDirection: 'asc' | 'desc' = 'asc';
+    activeSort: Sort = {
+        active: this.initialSortColumn,
+        direction: this.initialSortDirection
+    };
 
     @Input() set parameterContexts(parameterContextEntities: ParameterContextEntity[]) {
-        this.dataSource.data = this.sortEntities(parameterContextEntities, {
-            active: this.initialSortColumn,
-            direction: this.initialSortDirection
-        });
+        this.dataSource.data = this.sortEntities(parameterContextEntities, this.activeSort);
     }
 
     @Input() selectedParameterContextId!: string;
@@ -109,6 +110,7 @@ export class ParameterContextTable {
     }
 
     sortData(sort: Sort) {
+        this.activeSort = sort;
         this.dataSource.data = this.sortEntities(this.dataSource.data, sort);
     }
 
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/provenance/service/provenance.service.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/provenance/service/provenance.service.ts
index d3d73883e7..3e1d238699 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/provenance/service/provenance.service.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/provenance/service/provenance.service.ts
@@ -16,7 +16,7 @@
  */
 
 import { Injectable } from '@angular/core';
-import { Observable, throwError } from 'rxjs';
+import { Observable } from 'rxjs';
 import { HttpClient } from '@angular/common/http';
 import { NiFiCommon } from '../../../service/nifi-common.service';
 import { ProvenanceRequest } from '../state/provenance-event-listing';
@@ -31,19 +31,6 @@ export class ProvenanceService {
         private nifiCommon: NiFiCommon
     ) {}
 
-    /**
-     * The NiFi model contain the url for each component. That URL is an absolute URL. Angular CSRF handling
-     * does not work on absolute URLs, so we need to strip off the proto for the request header to be added.
-     *
-     * https://stackoverflow.com/a/59586462
-     *
-     * @param url
-     * @private
-     */
-    private stripProtocol(url: string): string {
-        return this.nifiCommon.substringAfterFirst(url, ':');
-    }
-
     getSearchOptions(): Observable<any> {
         return this.httpClient.get(`${ProvenanceService.API}/provenance/search-options`);
     }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/service/queue.service.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/service/queue.service.ts
index afdd3e763a..695d73ae87 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/service/queue.service.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/service/queue.service.ts
@@ -30,25 +30,12 @@ export class QueueService {
         private nifiCommon: NiFiCommon
     ) {}
 
-    /**
-     * The NiFi model contain the url for each component. That URL is an absolute URL. Angular CSRF handling
-     * does not work on absolute URLs, so we need to strip off the proto for the request header to be added.
-     *
-     * https://stackoverflow.com/a/59586462
-     *
-     * @param url
-     * @private
-     */
-    private stripProtocol(url: string): string {
-        return this.nifiCommon.substringAfterFirst(url, ':');
-    }
-
     getConnection(connectionId: string): Observable<any> {
         return this.httpClient.get(`${QueueService.API}/connections/${connectionId}`);
     }
 
     getFlowFile(flowfileSummary: FlowFileSummary): Observable<any> {
-        return this.httpClient.get(this.stripProtocol(flowfileSummary.uri));
+        return this.httpClient.get(this.nifiCommon.stripProtocol(flowfileSummary.uri));
     }
 
     submitQueueListingRequest(queueListingRequest: SubmitQueueListingRequest): Observable<any> {
@@ -59,15 +46,15 @@ export class QueueService {
     }
 
     pollQueueListingRequest(listingRequest: ListingRequest): Observable<any> {
-        return this.httpClient.get(this.stripProtocol(listingRequest.uri));
+        return this.httpClient.get(this.nifiCommon.stripProtocol(listingRequest.uri));
     }
 
     deleteQueueListingRequest(listingRequest: ListingRequest): Observable<any> {
-        return this.httpClient.delete(this.stripProtocol(listingRequest.uri));
+        return this.httpClient.delete(this.nifiCommon.stripProtocol(listingRequest.uri));
     }
 
     downloadContent(flowfileSummary: FlowFileSummary): void {
-        let dataUri: string = `${this.stripProtocol(flowfileSummary.uri)}/content`;
+        let dataUri: string = `${this.nifiCommon.stripProtocol(flowfileSummary.uri)}/content`;
 
         const queryParameters: any = {};
 
@@ -83,7 +70,7 @@ export class QueueService {
 
     viewContent(flowfileSummary: FlowFileSummary, contentViewerUrl: string): void {
         // build the uri to the data
-        let dataUri: string = `${this.stripProtocol(flowfileSummary.uri)}/content`;
+        let dataUri: string = `${this.nifiCommon.stripProtocol(flowfileSummary.uri)}/content`;
 
         const dataUriParameters: any = {};
 
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/feature/settings-routing.module.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/feature/settings-routing.module.ts
index 441a5bd06c..b60ccc93af 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/feature/settings-routing.module.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/feature/settings-routing.module.ts
@@ -99,7 +99,26 @@ const routes: Routes = [
                     }
                 ]
             },
-            { path: 'parameter-providers', component: ParameterProviders }
+            {
+                path: 'parameter-providers',
+                component: ParameterProviders,
+                children: [
+                    {
+                        path: ':id',
+                        component: ParameterProviders,
+                        children: [
+                            {
+                                path: 'edit',
+                                component: ParameterProviders
+                            },
+                            {
+                                path: 'fetch',
+                                component: ParameterProviders
+                            }
+                        ]
+                    }
+                ]
+            }
         ]
     }
 ];
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/feature/settings.module.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/feature/settings.module.ts
index ba43f59442..c15b377c6e 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/feature/settings.module.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/feature/settings.module.ts
@@ -35,6 +35,7 @@ import { ReportingTasksEffects } from '../state/reporting-tasks/reporting-tasks.
 import { RegistryClientsEffects } from '../state/registry-clients/registry-clients.effects';
 import { FlowAnalysisRulesEffects } from '../state/flow-analysis-rules/flow-analysis-rules.effects';
 import { Navigation } from '../../../ui/common/navigation/navigation.component';
+import { ParameterProvidersEffects } from '../state/parameter-providers/parameter-providers.effects';
 
 @NgModule({
     declarations: [Settings],
@@ -54,7 +55,8 @@ import { Navigation } from '../../../ui/common/navigation/navigation.component';
             ManagementControllerServicesEffects,
             ReportingTasksEffects,
             FlowAnalysisRulesEffects,
-            RegistryClientsEffects
+            RegistryClientsEffects,
+            ParameterProvidersEffects
         ),
         MatTabsModule,
         Navigation
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/service/flow-analysis-rule.service.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/service/flow-analysis-rule.service.ts
index b6f0a9ca14..c15ae5072c 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/service/flow-analysis-rule.service.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/service/flow-analysis-rule.service.ts
@@ -27,24 +27,12 @@ import {
     EnableFlowAnalysisRuleRequest,
     FlowAnalysisRuleEntity
 } from '../state/flow-analysis-rules';
+import { PropertyDescriptorRetriever } from '../../../state/shared';
 
 @Injectable({ providedIn: 'root' })
-export class FlowAnalysisRuleService {
+export class FlowAnalysisRuleService implements PropertyDescriptorRetriever {
     private static readonly API: string = '../nifi-api';
 
-    /**
-     * The NiFi model contain the url for each component. That URL is an absolute URL. Angular CSRF handling
-     * does not work on absolute URLs, so we need to strip off the proto for the request header to be added.
-     *
-     * https://stackoverflow.com/a/59586462
-     *
-     * @param url
-     * @private
-     */
-    private stripProtocol(url: string): string {
-        return this.nifiCommon.substringAfterFirst(url, ':');
-    }
-
     constructor(
         private httpClient: HttpClient,
         private client: Client,
@@ -68,7 +56,7 @@ export class FlowAnalysisRuleService {
     deleteFlowAnalysisRule(deleteFlowAnalysisRule: DeleteFlowAnalysisRuleRequest): Observable<any> {
         const entity: FlowAnalysisRuleEntity = deleteFlowAnalysisRule.flowAnalysisRule;
         const revision: any = this.client.getRevision(entity);
-        return this.httpClient.delete(this.stripProtocol(entity.uri), { params: revision });
+        return this.httpClient.delete(this.nifiCommon.stripProtocol(entity.uri), { params: revision });
     }
 
     getPropertyDescriptor(id: string, propertyName: string, sensitive: boolean): Observable<any> {
@@ -83,14 +71,14 @@ export class FlowAnalysisRuleService {
 
     updateFlowAnalysisRule(configureFlowAnalysisRule: ConfigureFlowAnalysisRuleRequest): Observable<any> {
         return this.httpClient.put(
-            this.stripProtocol(configureFlowAnalysisRule.uri),
+            this.nifiCommon.stripProtocol(configureFlowAnalysisRule.uri),
             configureFlowAnalysisRule.payload
         );
     }
 
     setEnable(flowAnalysisRule: EnableFlowAnalysisRuleRequest, enabled: boolean): Observable<any> {
         const entity: FlowAnalysisRuleEntity = flowAnalysisRule.flowAnalysisRule;
-        return this.httpClient.put(`${this.stripProtocol(entity.uri)}/run-status`, {
+        return this.httpClient.put(`${this.nifiCommon.stripProtocol(entity.uri)}/run-status`, {
             revision: this.client.getRevision(entity),
             state: enabled ? 'ENABLED' : 'DISABLED',
             uiOnly: true
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/service/management-controller-service.service.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/service/management-controller-service.service.ts
index e241e76544..9557f3d271 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/service/management-controller-service.service.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/service/management-controller-service.service.ts
@@ -16,34 +16,25 @@
  */
 
 import { Injectable } from '@angular/core';
-import { Observable, throwError } from 'rxjs';
+import { Observable } from 'rxjs';
 import { HttpClient } from '@angular/common/http';
 import {
     ConfigureControllerServiceRequest,
-    CreateControllerServiceRequest,
     DeleteControllerServiceRequest
 } from '../state/management-controller-services';
 import { Client } from '../../../service/client.service';
 import { NiFiCommon } from '../../../service/nifi-common.service';
-import { ControllerServiceEntity } from '../../../state/shared';
+import {
+    ControllerServiceCreator,
+    ControllerServiceEntity,
+    CreateControllerServiceRequest,
+    PropertyDescriptorRetriever
+} from '../../../state/shared';
 
 @Injectable({ providedIn: 'root' })
-export class ManagementControllerServiceService {
+export class ManagementControllerServiceService implements ControllerServiceCreator, PropertyDescriptorRetriever {
     private static readonly API: string = '../nifi-api';
 
-    /**
-     * The NiFi model contain the url for each component. That URL is an absolute URL. Angular CSRF handling
-     * does not work on absolute URLs, so we need to strip off the proto for the request header to be added.
-     *
-     * https://stackoverflow.com/a/59586462
-     *
-     * @param url
-     * @private
-     */
-    private stripProtocol(url: string): string {
-        return this.nifiCommon.substringAfterFirst(url, ':');
-    }
-
     constructor(
         private httpClient: HttpClient,
         private client: Client,
@@ -79,7 +70,7 @@ export class ManagementControllerServiceService {
 
     updateControllerService(configureControllerService: ConfigureControllerServiceRequest): Observable<any> {
         return this.httpClient.put(
-            this.stripProtocol(configureControllerService.uri),
+            this.nifiCommon.stripProtocol(configureControllerService.uri),
             configureControllerService.payload
         );
     }
@@ -87,6 +78,6 @@ export class ManagementControllerServiceService {
     deleteControllerService(deleteControllerService: DeleteControllerServiceRequest): Observable<any> {
         const entity: ControllerServiceEntity = deleteControllerService.controllerService;
         const revision: any = this.client.getRevision(entity);
-        return this.httpClient.delete(this.stripProtocol(entity.uri), { params: revision });
+        return this.httpClient.delete(this.nifiCommon.stripProtocol(entity.uri), { params: revision });
     }
 }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/service/parameter-provider.service.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/service/parameter-provider.service.ts
new file mode 100644
index 0000000000..ed22e317c0
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/service/parameter-provider.service.ts
@@ -0,0 +1,78 @@
+/*
+ * 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 { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { Client } from '../../../service/client.service';
+import { NiFiCommon } from '../../../service/nifi-common.service';
+import { Observable } from 'rxjs';
+import {
+    ConfigureParameterProviderRequest,
+    CreateParameterProviderRequest,
+    DeleteParameterProviderRequest,
+    ParameterProviderEntity
+} from '../state/parameter-providers';
+import { PropertyDescriptorRetriever, Revision } from '../../../state/shared';
+
+@Injectable({ providedIn: 'root' })
+export class ParameterProviderService implements PropertyDescriptorRetriever {
+    private static readonly API: string = '../nifi-api';
+
+    constructor(
+        private httpClient: HttpClient,
+        private client: Client,
+        private nifiCommon: NiFiCommon
+    ) {}
+
+    getParameterProviders(): Observable<any> {
+        return this.httpClient.get(`${ParameterProviderService.API}/flow/parameter-providers`);
+    }
+
+    createParameterProvider(request: CreateParameterProviderRequest) {
+        return this.httpClient.post(`${ParameterProviderService.API}/controller/parameter-providers`, {
+            revision: request.revision,
+            component: {
+                bundle: request.parameterProviderBundle,
+                type: request.parameterProviderType
+            }
+        });
+    }
+
+    deleteParameterProvider(request: DeleteParameterProviderRequest) {
+        const entity: ParameterProviderEntity = request.parameterProvider;
+        const revision: any = this.client.getRevision(entity);
+        const params: any = {
+            ...revision,
+            disconnectedNodeAcknowledged: false
+        };
+        return this.httpClient.delete(this.nifiCommon.stripProtocol(entity.uri), { params: revision });
+    }
+
+    getPropertyDescriptor(id: string, propertyName: string, sensitive: boolean): Observable<any> {
+        const params: any = {
+            propertyName,
+            sensitive
+        };
+        return this.httpClient.get(`${ParameterProviderService.API}/parameter-providers/${id}/descriptors`, {
+            params
+        });
+    }
+
+    updateParameterProvider(configureRequest: ConfigureParameterProviderRequest): Observable<any> {
+        return this.httpClient.put(this.nifiCommon.stripProtocol(configureRequest.uri), configureRequest.payload);
+    }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/service/registry-client.service.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/service/registry-client.service.ts
index b76e08b453..c1aeab3473 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/service/registry-client.service.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/service/registry-client.service.ts
@@ -26,24 +26,12 @@ import {
     EditRegistryClientRequest,
     RegistryClientEntity
 } from '../state/registry-clients';
+import { PropertyDescriptorRetriever } from '../../../state/shared';
 
 @Injectable({ providedIn: 'root' })
-export class RegistryClientService {
+export class RegistryClientService implements PropertyDescriptorRetriever {
     private static readonly API: string = '../nifi-api';
 
-    /**
-     * The NiFi model contain the url for each component. That URL is an absolute URL. Angular CSRF handling
-     * does not work on absolute URLs, so we need to strip off the proto for the request header to be added.
-     *
-     * https://stackoverflow.com/a/59586462
-     *
-     * @param url
-     * @private
-     */
-    private stripProtocol(url: string): string {
-        return this.nifiCommon.substringAfterFirst(url, ':');
-    }
-
     constructor(
         private httpClient: HttpClient,
         private client: Client,
@@ -69,12 +57,12 @@ export class RegistryClientService {
     }
 
     updateRegistryClient(request: EditRegistryClientRequest): Observable<any> {
-        return this.httpClient.put(this.stripProtocol(request.uri), request.payload);
+        return this.httpClient.put(this.nifiCommon.stripProtocol(request.uri), request.payload);
     }
 
     deleteRegistryClient(deleteRegistryClient: DeleteRegistryClientRequest): Observable<any> {
         const entity: RegistryClientEntity = deleteRegistryClient.registryClient;
         const revision: any = this.client.getRevision(entity);
-        return this.httpClient.delete(this.stripProtocol(entity.uri), { params: revision });
+        return this.httpClient.delete(this.nifiCommon.stripProtocol(entity.uri), { params: revision });
     }
 }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/service/reporting-task.service.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/service/reporting-task.service.ts
index 102fab05c5..d6358e6a3e 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/service/reporting-task.service.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/service/reporting-task.service.ts
@@ -28,24 +28,12 @@ import {
     StartReportingTaskRequest,
     StopReportingTaskRequest
 } from '../state/reporting-tasks';
+import { PropertyDescriptorRetriever } from '../../../state/shared';
 
 @Injectable({ providedIn: 'root' })
-export class ReportingTaskService {
+export class ReportingTaskService implements PropertyDescriptorRetriever {
     private static readonly API: string = '../nifi-api';
 
-    /**
-     * The NiFi model contain the url for each component. That URL is an absolute URL. Angular CSRF handling
-     * does not work on absolute URLs, so we need to strip off the proto for the request header to be added.
-     *
-     * https://stackoverflow.com/a/59586462
-     *
-     * @param url
-     * @private
-     */
-    private stripProtocol(url: string): string {
-        return this.nifiCommon.substringAfterFirst(url, ':');
-    }
-
     constructor(
         private httpClient: HttpClient,
         private client: Client,
@@ -69,7 +57,7 @@ export class ReportingTaskService {
     deleteReportingTask(deleteReportingTask: DeleteReportingTaskRequest): Observable<any> {
         const entity: ReportingTaskEntity = deleteReportingTask.reportingTask;
         const revision: any = this.client.getRevision(entity);
-        return this.httpClient.delete(this.stripProtocol(entity.uri), { params: revision });
+        return this.httpClient.delete(this.nifiCommon.stripProtocol(entity.uri), { params: revision });
     }
 
     startReportingTask(startReportingTask: StartReportingTaskRequest): Observable<any> {
@@ -79,7 +67,7 @@ export class ReportingTaskService {
             revision,
             state: 'RUNNING'
         };
-        return this.httpClient.put(`${this.stripProtocol(entity.uri)}/run-status`, payload);
+        return this.httpClient.put(`${this.nifiCommon.stripProtocol(entity.uri)}/run-status`, payload);
     }
 
     stopReportingTask(stopReportingTask: StopReportingTaskRequest): Observable<any> {
@@ -89,7 +77,7 @@ export class ReportingTaskService {
             revision,
             state: 'STOPPED'
         };
-        return this.httpClient.put(`${this.stripProtocol(entity.uri)}/run-status`, payload);
+        return this.httpClient.put(`${this.nifiCommon.stripProtocol(entity.uri)}/run-status`, payload);
     }
 
     getPropertyDescriptor(id: string, propertyName: string, sensitive: boolean): Observable<any> {
@@ -103,6 +91,9 @@ export class ReportingTaskService {
     }
 
     updateReportingTask(configureReportingTask: ConfigureReportingTaskRequest): Observable<any> {
-        return this.httpClient.put(this.stripProtocol(configureReportingTask.uri), configureReportingTask.payload);
+        return this.httpClient.put(
+            this.nifiCommon.stripProtocol(configureReportingTask.uri),
+            configureReportingTask.payload
+        );
     }
 }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/flow-analysis-rules/flow-analysis-rules.effects.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/flow-analysis-rules/flow-analysis-rules.effects.ts
index dadda56080..57d734a6cb 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/flow-analysis-rules/flow-analysis-rules.effects.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/flow-analysis-rules/flow-analysis-rules.effects.ts
@@ -18,7 +18,7 @@
 import { Injectable } from '@angular/core';
 import { Actions, concatLatestFrom, createEffect, ofType } from '@ngrx/effects';
 import * as FlowAnalysisRuleActions from './flow-analysis-rules.actions';
-import { catchError, from, map, NEVER, Observable, of, switchMap, take, takeUntil, tap } from 'rxjs';
+import { catchError, from, map, of, switchMap, take, takeUntil, tap } from 'rxjs';
 import { MatDialog } from '@angular/material/dialog';
 import { Store } from '@ngrx/store';
 import { NiFiState } from '../../../../state';
@@ -30,20 +30,11 @@ import { ManagementControllerServiceService } from '../../service/management-con
 import { CreateFlowAnalysisRule } from '../../ui/flow-analysis-rules/create-flow-analysis-rule/create-flow-analysis-rule.component';
 import { Router } from '@angular/router';
 import { selectSaving } from '../management-controller-services/management-controller-services.selectors';
-import {
-    InlineServiceCreationRequest,
-    InlineServiceCreationResponse,
-    NewPropertyDialogRequest,
-    NewPropertyDialogResponse,
-    Property,
-    PropertyDescriptor,
-    UpdateControllerServiceRequest
-} from '../../../../state/shared';
+import { UpdateControllerServiceRequest } from '../../../../state/shared';
 import { EditFlowAnalysisRule } from '../../ui/flow-analysis-rules/edit-flow-analysis-rule/edit-flow-analysis-rule.component';
 import { CreateFlowAnalysisRuleSuccess } from './index';
-import { NewPropertyDialog } from '../../../../ui/common/new-property-dialog/new-property-dialog.component';
-import { CreateControllerService } from '../../../../ui/common/controller-service/create-controller-service/create-controller-service.component';
 import { ExtensionTypesService } from '../../../../service/extension-types.service';
+import { PropertyTableHelperService } from '../../../../service/property-table-helper.service';
 
 @Injectable()
 export class FlowAnalysisRulesEffects {
@@ -55,7 +46,8 @@ export class FlowAnalysisRulesEffects {
         private extensionTypesService: ExtensionTypesService,
         private flowAnalysisRuleService: FlowAnalysisRuleService,
         private dialog: MatDialog,
-        private router: Router
+        private router: Router,
+        private propertyTableHelperService: PropertyTableHelperService
     ) {}
 
     loadFlowAnalysisRule$ = createEffect(() =>
@@ -225,36 +217,8 @@ export class FlowAnalysisRulesEffects {
 
                     editDialogReference.componentInstance.saving$ = this.store.select(selectSaving);
 
-                    editDialogReference.componentInstance.createNewProperty = (
-                        existingProperties: string[],
-                        allowsSensitive: boolean
-                    ): Observable<Property> => {
-                        const dialogRequest: NewPropertyDialogRequest = { existingProperties, allowsSensitive };
-                        const newPropertyDialogReference = this.dialog.open(NewPropertyDialog, {
-                            data: dialogRequest,
-                            panelClass: 'small-dialog'
-                        });
-
-                        return newPropertyDialogReference.componentInstance.newProperty.pipe(
-                            take(1),
-                            switchMap((dialogResponse: NewPropertyDialogResponse) => {
-                                return this.flowAnalysisRuleService
-                                    .getPropertyDescriptor(request.id, dialogResponse.name, dialogResponse.sensitive)
-                                    .pipe(
-                                        take(1),
-                                        map((response) => {
-                                            newPropertyDialogReference.close();
-
-                                            return {
-                                                property: dialogResponse.name,
-                                                value: null,
-                                                descriptor: response.propertyDescriptor
-                                            };
-                                        })
-                                    );
-                            })
-                        );
-                    };
+                    editDialogReference.componentInstance.createNewProperty =
+                        this.propertyTableHelperService.createNewProperty(request.id, this.flowAnalysisRuleService);
 
                     const goTo = (commands: string[], destination: string): void => {
                         if (editDialogReference.componentInstance.editFlowAnalysisRuleForm.dirty) {
@@ -285,73 +249,12 @@ export class FlowAnalysisRulesEffects {
                         goTo(commands, 'Controller Service');
                     };
 
-                    editDialogReference.componentInstance.createNewService = (
-                        request: InlineServiceCreationRequest
-                    ): Observable<InlineServiceCreationResponse> => {
-                        const descriptor: PropertyDescriptor = request.descriptor;
-
-                        // fetch all services that implement the requested service api
-                        return this.extensionTypesService
-                            .getImplementingControllerServiceTypes(
-                                // @ts-ignore
-                                descriptor.identifiesControllerService,
-                                descriptor.identifiesControllerServiceBundle
-                            )
-                            .pipe(
-                                take(1),
-                                switchMap((implementingTypesResponse) => {
-                                    // show the create controller service dialog with the types that implemented the interface
-                                    const createServiceDialogReference = this.dialog.open(CreateControllerService, {
-                                        data: {
-                                            controllerServiceTypes: implementingTypesResponse.controllerServiceTypes
-                                        },
-                                        panelClass: 'medium-dialog'
-                                    });
-
-                                    return createServiceDialogReference.componentInstance.createControllerService.pipe(
-                                        take(1),
-                                        switchMap((controllerServiceType) => {
-                                            // typically this sequence would be implemented with ngrx actions, however we are
-                                            // currently in an edit session and we need to return both the value (new service id)
-                                            // and updated property descriptor so the table renders correctly
-                                            return this.managementControllerServiceService
-                                                .createControllerService({
-                                                    revision: {
-                                                        clientId: this.client.getClientId(),
-                                                        version: 0
-                                                    },
-                                                    controllerServiceType: controllerServiceType.type,
-                                                    controllerServiceBundle: controllerServiceType.bundle
-                                                })
-                                                .pipe(
-                                                    take(1),
-                                                    switchMap((createResponse) => {
-                                                        // fetch an updated property descriptor
-                                                        return this.flowAnalysisRuleService
-                                                            .getPropertyDescriptor(ruleId, descriptor.name, false)
-                                                            .pipe(
-                                                                take(1),
-                                                                map((descriptorResponse) => {
-                                                                    createServiceDialogReference.close();
-
-                                                                    return {
-                                                                        value: createResponse.id,
-                                                                        descriptor:
-                                                                            descriptorResponse.propertyDescriptor
-                                                                    };
-                                                                })
-                                                            );
-                                                    }),
-                                                    catchError((error) => {
-                                                        // TODO - show error
-                                                        return NEVER;
-                                                    })
-                                                );
-                                        })
-                                    );
-                                })
-                            );
-                    };
+                    editDialogReference.componentInstance.createNewService =
+                        this.propertyTableHelperService.createNewService(
+                            request.id,
+                            this.managementControllerServiceService,
+                            this.flowAnalysisRuleService
+                        );
 
                     editDialogReference.componentInstance.editFlowAnalysisRule
                         .pipe(takeUntil(editDialogReference.afterClosed()))
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/index.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/index.ts
index 1182b01024..9367df4e90 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/index.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/index.ts
@@ -29,6 +29,8 @@ import { registryClientsFeatureKey, RegistryClientsState } from './registry-clie
 import { registryClientsReducer } from './registry-clients/registry-clients.reducer';
 import { flowAnalysisRulesFeatureKey, FlowAnalysisRulesState } from './flow-analysis-rules';
 import { flowAnalysisRulesReducer } from './flow-analysis-rules/flow-analysis-rules.reducer';
+import { parameterProvidersFeatureKey, ParameterProvidersState } from './parameter-providers';
+import { parameterProvidersReducer } from './parameter-providers/parameter-providers.reducer';
 
 export const settingsFeatureKey = 'settings';
 
@@ -38,6 +40,7 @@ export interface SettingsState {
     [reportingTasksFeatureKey]: ReportingTasksState;
     [flowAnalysisRulesFeatureKey]: FlowAnalysisRulesState;
     [registryClientsFeatureKey]: RegistryClientsState;
+    [parameterProvidersFeatureKey]: ParameterProvidersState;
 }
 
 export function reducers(state: SettingsState | undefined, action: Action) {
@@ -46,7 +49,8 @@ export function reducers(state: SettingsState | undefined, action: Action) {
         [managementControllerServicesFeatureKey]: managementControllerServicesReducer,
         [reportingTasksFeatureKey]: reportingTasksReducer,
         [flowAnalysisRulesFeatureKey]: flowAnalysisRulesReducer,
-        [registryClientsFeatureKey]: registryClientsReducer
+        [registryClientsFeatureKey]: registryClientsReducer,
+        [parameterProvidersFeatureKey]: parameterProvidersReducer
     })(state, action);
 }
 
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/index.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/index.ts
index 9db8417be2..b1725d338c 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/index.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/index.ts
@@ -24,12 +24,6 @@ export interface LoadManagementControllerServicesResponse {
     loadedTimestamp: string;
 }
 
-export interface CreateControllerServiceRequest {
-    controllerServiceType: string;
-    controllerServiceBundle: Bundle;
-    revision: Revision;
-}
-
 export interface CreateControllerServiceSuccess {
     controllerService: ControllerServiceEntity;
 }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.actions.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.actions.ts
index 7165d17de7..0ecaca9f51 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.actions.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.actions.ts
@@ -19,7 +19,6 @@ import { createAction, props } from '@ngrx/store';
 import {
     ConfigureControllerServiceRequest,
     ConfigureControllerServiceSuccess,
-    CreateControllerServiceRequest,
     CreateControllerServiceSuccess,
     DeleteControllerServiceRequest,
     DeleteControllerServiceSuccess,
@@ -27,6 +26,7 @@ import {
     SelectControllerServiceRequest
 } from './index';
 import {
+    CreateControllerServiceRequest,
     DisableControllerServiceDialogRequest,
     EditControllerServiceDialogRequest,
     SetEnableControllerServiceDialogRequest
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.effects.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.effects.ts
index 2e9ff9772e..62abf521cb 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.effects.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.effects.ts
@@ -33,18 +33,15 @@ import {
     ControllerServiceReferencingComponent,
     InlineServiceCreationRequest,
     InlineServiceCreationResponse,
-    NewPropertyDialogRequest,
-    NewPropertyDialogResponse,
-    Property,
     PropertyDescriptor,
     UpdateControllerServiceRequest
 } from '../../../../state/shared';
-import { NewPropertyDialog } from '../../../../ui/common/new-property-dialog/new-property-dialog.component';
 import { Router } from '@angular/router';
 import { ExtensionTypesService } from '../../../../service/extension-types.service';
 import { selectSaving } from './management-controller-services.selectors';
 import { EnableControllerService } from '../../../../ui/common/controller-service/enable-controller-service/enable-controller-service.component';
 import { DisableControllerService } from '../../../../ui/common/controller-service/disable-controller-service/disable-controller-service.component';
+import { PropertyTableHelperService } from '../../../../service/property-table-helper.service';
 
 @Injectable()
 export class ManagementControllerServicesEffects {
@@ -55,7 +52,8 @@ export class ManagementControllerServicesEffects {
         private managementControllerServiceService: ManagementControllerServiceService,
         private extensionTypesService: ExtensionTypesService,
         private dialog: MatDialog,
-        private router: Router
+        private router: Router,
+        private propertyTableHelperService: PropertyTableHelperService
     ) {}
 
     loadManagementControllerServices$ = createEffect(() =>
@@ -193,36 +191,11 @@ export class ManagementControllerServicesEffects {
 
                     editDialogReference.componentInstance.saving$ = this.store.select(selectSaving);
 
-                    editDialogReference.componentInstance.createNewProperty = (
-                        existingProperties: string[],
-                        allowsSensitive: boolean
-                    ): Observable<Property> => {
-                        const dialogRequest: NewPropertyDialogRequest = { existingProperties, allowsSensitive };
-                        const newPropertyDialogReference = this.dialog.open(NewPropertyDialog, {
-                            data: dialogRequest,
-                            panelClass: 'small-dialog'
-                        });
-
-                        return newPropertyDialogReference.componentInstance.newProperty.pipe(
-                            take(1),
-                            switchMap((dialogResponse: NewPropertyDialogResponse) => {
-                                return this.managementControllerServiceService
-                                    .getPropertyDescriptor(request.id, dialogResponse.name, dialogResponse.sensitive)
-                                    .pipe(
-                                        take(1),
-                                        map((response) => {
-                                            newPropertyDialogReference.close();
-
-                                            return {
-                                                property: dialogResponse.name,
-                                                value: null,
-                                                descriptor: response.propertyDescriptor
-                                            };
-                                        })
-                                    );
-                            })
+                    editDialogReference.componentInstance.createNewProperty =
+                        this.propertyTableHelperService.createNewProperty(
+                            request.id,
+                            this.managementControllerServiceService
                         );
-                    };
 
                     const goTo = (commands: string[], destination: string): void => {
                         if (editDialogReference.componentInstance.editControllerServiceForm.dirty) {
@@ -260,84 +233,21 @@ export class ManagementControllerServicesEffects {
                         goTo(route, component.referenceType);
                     };
 
-                    editDialogReference.componentInstance.createNewService = (
-                        request: InlineServiceCreationRequest
-                    ): Observable<InlineServiceCreationResponse> => {
-                        const descriptor: PropertyDescriptor = request.descriptor;
-
-                        // fetch all services that implement the requested service api
-                        return this.extensionTypesService
-                            .getImplementingControllerServiceTypes(
-                                // @ts-ignore
-                                descriptor.identifiesControllerService,
-                                descriptor.identifiesControllerServiceBundle
-                            )
-                            .pipe(
-                                take(1),
-                                switchMap((implementingTypesResponse) => {
-                                    // show the create controller service dialog with the types that implemented the interface
-                                    const createServiceDialogReference = this.dialog.open(CreateControllerService, {
-                                        data: {
-                                            controllerServiceTypes: implementingTypesResponse.controllerServiceTypes
-                                        },
-                                        panelClass: 'medium-dialog'
-                                    });
-
-                                    return createServiceDialogReference.componentInstance.createControllerService.pipe(
-                                        take(1),
-                                        switchMap((controllerServiceType) => {
-                                            // typically this sequence would be implemented with ngrx actions, however we are
-                                            // currently in an edit session and we need to return both the value (new service id)
-                                            // and updated property descriptor so the table renders correctly
-                                            return this.managementControllerServiceService
-                                                .createControllerService({
-                                                    revision: {
-                                                        clientId: this.client.getClientId(),
-                                                        version: 0
-                                                    },
-                                                    controllerServiceType: controllerServiceType.type,
-                                                    controllerServiceBundle: controllerServiceType.bundle
-                                                })
-                                                .pipe(
-                                                    take(1),
-                                                    switchMap((createResponse) => {
-                                                        // dispatch an inline create service success action so the new service is in the state
-                                                        this.store.dispatch(
-                                                            ManagementControllerServicesActions.inlineCreateControllerServiceSuccess(
-                                                                {
-                                                                    response: {
-                                                                        controllerService: createResponse
-                                                                    }
-                                                                }
-                                                            )
-                                                        );
-
-                                                        // fetch an updated property descriptor
-                                                        return this.managementControllerServiceService
-                                                            .getPropertyDescriptor(serviceId, descriptor.name, false)
-                                                            .pipe(
-                                                                take(1),
-                                                                map((descriptorResponse) => {
-                                                                    createServiceDialogReference.close();
-
-                                                                    return {
-                                                                        value: createResponse.id,
-                                                                        descriptor:
-                                                                            descriptorResponse.propertyDescriptor
-                                                                    };
-                                                                })
-                                                            );
-                                                    }),
-                                                    catchError((error) => {
-                                                        // TODO - show error
-                                                        return NEVER;
-                                                    })
-                                                );
-                                        })
-                                    );
-                                })
-                            );
-                    };
+                    editDialogReference.componentInstance.createNewService =
+                        this.propertyTableHelperService.createNewService(
+                            request.id,
+                            this.managementControllerServiceService,
+                            this.managementControllerServiceService,
+                            null,
+                            (createResponse) =>
+                                this.store.dispatch(
+                                    ManagementControllerServicesActions.inlineCreateControllerServiceSuccess({
+                                        response: {
+                                            controllerService: createResponse
+                                        }
+                                    })
+                                )
+                        );
 
                     editDialogReference.componentInstance.editControllerService
                         .pipe(takeUntil(editDialogReference.afterClosed()))
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/index.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/index.ts
new file mode 100644
index 0000000000..957951e435
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/index.ts
@@ -0,0 +1,117 @@
+/*
+ * 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 {
+    Bundle,
+    DocumentedType,
+    ParameterContextReferenceEntity,
+    Permissions,
+    PropertyDescriptor,
+    Revision
+} from '../../../../state/shared';
+
+export const parameterProvidersFeatureKey = 'parameterProviders';
+
+export interface ParameterProvider {
+    bundle: Bundle;
+    comments: string;
+    deprecated: boolean;
+    descriptors: { [key: string]: PropertyDescriptor };
+    extensionMissing: boolean;
+    id: string;
+    multipleVersionsAvailable: boolean;
+    name: string;
+    parameterGroupConfigurations: any[];
+    persistsState: boolean;
+    properties: { [key: string]: string };
+    referencingParameterContexts: ParameterContextReferenceEntity[];
+    restricted: boolean;
+    type: string;
+    validationStatus: string;
+    validationErrors?: string[];
+}
+
+export interface ParameterProviderEntity {
+    id: string;
+    bulletins: [];
+    component: ParameterProvider;
+    permissions: Permissions;
+    revision: Revision;
+    uri: string;
+}
+
+export interface ParameterProvidersState {
+    parameterProviders: ParameterProviderEntity[];
+    saving: boolean;
+    loadedTimestamp: string;
+    error: string | null;
+    status: 'pending' | 'loading' | 'error' | 'success';
+}
+
+export interface LoadParameterProvidersResponse {
+    parameterProviders: ParameterProviderEntity[];
+    loadedTimestamp: string;
+}
+
+export interface SelectParameterProviderRequest {
+    id: string;
+}
+
+export interface CreateParameterProviderDialogRequest {
+    parameterProviderTypes: DocumentedType[];
+}
+
+export interface CreateParameterProviderRequest {
+    parameterProviderType: string;
+    parameterProviderBundle: Bundle;
+    revision: Revision;
+}
+
+export interface CreateParameterProviderSuccessResponse {
+    parameterProvider: ParameterProviderEntity;
+}
+
+export interface DeleteParameterProviderRequest {
+    parameterProvider: ParameterProviderEntity;
+}
+
+export interface DeleteParameterProviderSuccess {
+    parameterProvider: ParameterProviderEntity;
+}
+
+export interface EditParameterProviderRequest {
+    id: string;
+    parameterProvider: ParameterProviderEntity;
+}
+
+export interface ConfigureParameterProviderRequest {
+    id: string;
+    uri: string;
+    payload: any;
+    postUpdateNavigation?: string[];
+}
+
+export interface ConfigureParameterProviderSuccess {
+    id: string;
+    parameterProvider: ParameterProviderEntity;
+    postUpdateNavigation?: string[];
+}
+
+export interface UpdateParameterProviderRequest {
+    payload: any;
+    postUpdateNavigation?: string[];
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.actions.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.actions.ts
new file mode 100644
index 0000000000..ae7166e57e
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.actions.ts
@@ -0,0 +1,99 @@
+/*
+ * 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 { createAction, props } from '@ngrx/store';
+import {
+    ConfigureParameterProviderRequest,
+    ConfigureParameterProviderSuccess,
+    CreateParameterProviderRequest,
+    CreateParameterProviderSuccessResponse,
+    DeleteParameterProviderRequest,
+    DeleteParameterProviderSuccess,
+    EditParameterProviderRequest,
+    LoadParameterProvidersResponse,
+    SelectParameterProviderRequest
+} from './index';
+
+const PARAMETER_PROVIDERS_PREFIX: string = '[Parameter Providers]';
+
+export const resetParameterProvidersState = createAction(`${PARAMETER_PROVIDERS_PREFIX} Reset Parameter Providers`);
+
+export const loadParameterProviders = createAction(`${PARAMETER_PROVIDERS_PREFIX} Load Parameter Providers`);
+
+export const loadParameterProvidersSuccess = createAction(
+    `${PARAMETER_PROVIDERS_PREFIX} Load Parameter Providers Success`,
+    props<{ response: LoadParameterProvidersResponse }>()
+);
+
+export const parameterProvidersApiError = createAction(
+    `${PARAMETER_PROVIDERS_PREFIX} Load Parameter Providers Error`,
+    props<{ error: string }>()
+);
+
+export const selectParameterProvider = createAction(
+    `${PARAMETER_PROVIDERS_PREFIX} Select Parameter Provider`,
+    props<{ request: SelectParameterProviderRequest }>()
+);
+
+export const openNewParameterProviderDialog = createAction(
+    `${PARAMETER_PROVIDERS_PREFIX} Open New Parameter Provider Dialog`
+);
+
+export const createParameterProvider = createAction(
+    `${PARAMETER_PROVIDERS_PREFIX} Create Parameter Provider`,
+    props<{ request: CreateParameterProviderRequest }>()
+);
+
+export const createParameterProviderSuccess = createAction(
+    `${PARAMETER_PROVIDERS_PREFIX} Create Parameter Provider Success`,
+    props<{ response: CreateParameterProviderSuccessResponse }>()
+);
+
+export const promptParameterProviderDeletion = createAction(
+    `${PARAMETER_PROVIDERS_PREFIX} Prompt Parameter Provider Deletion`,
+    props<{ request: DeleteParameterProviderRequest }>()
+);
+
+export const deleteParameterProvider = createAction(
+    `${PARAMETER_PROVIDERS_PREFIX} Delete Parameter Provider`,
+    props<{ request: DeleteParameterProviderRequest }>()
+);
+
+export const deleteParameterProviderSuccess = createAction(
+    `${PARAMETER_PROVIDERS_PREFIX} Delete Parameter Provider Success`,
+    props<{ response: DeleteParameterProviderSuccess }>()
+);
+
+export const navigateToEditParameterProvider = createAction(
+    `${PARAMETER_PROVIDERS_PREFIX} Navigate To Edit Parameter Provider`,
+    props<{ id: string }>()
+);
+
+export const openConfigureParameterProviderDialog = createAction(
+    `${PARAMETER_PROVIDERS_PREFIX} Open Configure Parameter Provider Dialog`,
+    props<{ request: EditParameterProviderRequest }>()
+);
+
+export const configureParameterProvider = createAction(
+    `${PARAMETER_PROVIDERS_PREFIX} Configure Parameter Provider`,
+    props<{ request: ConfigureParameterProviderRequest }>()
+);
+
+export const configureParameterProviderSuccess = createAction(
+    `${PARAMETER_PROVIDERS_PREFIX} Configure Parameter Provider Success`,
+    props<{ response: ConfigureParameterProviderSuccess }>()
+);
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.effects.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.effects.ts
new file mode 100644
index 0000000000..67cbdbfce1
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.effects.ts
@@ -0,0 +1,359 @@
+/*
+ * 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 { Injectable } from '@angular/core';
+import { Actions, concatLatestFrom, createEffect, ofType } from '@ngrx/effects';
+import { Store } from '@ngrx/store';
+import { NiFiState } from '../../../../state';
+import { Client } from '../../../../service/client.service';
+import { MatDialog } from '@angular/material/dialog';
+import { Router } from '@angular/router';
+import { ParameterProviderService } from '../../service/parameter-provider.service';
+import * as ParameterProviderActions from './parameter-providers.actions';
+import { loadParameterProviders, selectParameterProvider } from './parameter-providers.actions';
+import { catchError, from, map, of, switchMap, take, takeUntil, tap } from 'rxjs';
+import { selectSaving } from './parameter-providers.selectors';
+import { selectParameterProviderTypes } from '../../../../state/extension-types/extension-types.selectors';
+import { CreateParameterProvider } from '../../ui/parameter-providers/create-parameter-provider/create-parameter-provider.component';
+import { YesNoDialog } from '../../../../ui/common/yes-no-dialog/yes-no-dialog.component';
+import { EditParameterProvider } from '../../ui/parameter-providers/edit-parameter-provider/edit-parameter-provider.component';
+import { PropertyTableHelperService } from '../../../../service/property-table-helper.service';
+import { UpdateParameterProviderRequest } from './index';
+import { ManagementControllerServiceService } from '../../service/management-controller-service.service';
+
+@Injectable()
+export class ParameterProvidersEffects {
+    constructor(
+        private actions$: Actions,
+        private store: Store<NiFiState>,
+        private client: Client,
+        private dialog: MatDialog,
+        private router: Router,
+        private parameterProviderService: ParameterProviderService,
+        private propertyTableHelperService: PropertyTableHelperService,
+        private managementControllerServiceService: ManagementControllerServiceService
+    ) {}
+
+    loadParameterProviders$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(loadParameterProviders),
+            switchMap(() =>
+                from(this.parameterProviderService.getParameterProviders()).pipe(
+                    map((response) =>
+                        ParameterProviderActions.loadParameterProvidersSuccess({
+                            response: {
+                                parameterProviders: response.parameterProviders,
+                                loadedTimestamp: response.currentTime
+                            }
+                        })
+                    ),
+                    catchError((error) =>
+                        of(ParameterProviderActions.parameterProvidersApiError({ error: error.error }))
+                    )
+                )
+            )
+        )
+    );
+
+    selectParameterProvider$ = createEffect(
+        () =>
+            this.actions$.pipe(
+                ofType(selectParameterProvider),
+                map((action) => action.request),
+                tap((request) => {
+                    this.router.navigate(['/settings', 'parameter-providers', request.id]);
+                })
+            ),
+        { dispatch: false }
+    );
+
+    openNewParameterProviderDialog$ = createEffect(
+        () =>
+            this.actions$.pipe(
+                ofType(ParameterProviderActions.openNewParameterProviderDialog),
+                concatLatestFrom(() => this.store.select(selectParameterProviderTypes)),
+                tap(([action, parameterProviderTypes]) => {
+                    const dialogReference = this.dialog.open(CreateParameterProvider, {
+                        data: {
+                            parameterProviderTypes
+                        },
+                        panelClass: 'medium-dialog'
+                    });
+
+                    dialogReference.componentInstance.saving$ = this.store.select(selectSaving);
+
+                    dialogReference.componentInstance.createParameterProvider
+                        .pipe(take(1))
+                        .subscribe((parameterProviderType) => {
+                            this.store.dispatch(
+                                ParameterProviderActions.createParameterProvider({
+                                    request: {
+                                        parameterProviderType: parameterProviderType.type,
+                                        parameterProviderBundle: parameterProviderType.bundle,
+                                        revision: {
+                                            clientId: this.client.getClientId(),
+                                            version: 0
+                                        }
+                                    }
+                                })
+                            );
+                        });
+                })
+            ),
+        { dispatch: false }
+    );
+
+    createParameterProvider$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(ParameterProviderActions.createParameterProvider),
+            map((action) => action.request),
+            switchMap((request) =>
+                from(this.parameterProviderService.createParameterProvider(request)).pipe(
+                    map((response: any) =>
+                        ParameterProviderActions.createParameterProviderSuccess({
+                            response: {
+                                parameterProvider: response
+                            }
+                        })
+                    ),
+                    catchError((error) =>
+                        of(ParameterProviderActions.parameterProvidersApiError({ error: error.error }))
+                    )
+                )
+            )
+        )
+    );
+
+    createParameterProviderSuccess$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(ParameterProviderActions.createParameterProviderSuccess),
+            map((action) => action.response),
+            tap(() => {
+                this.dialog.closeAll();
+            }),
+            switchMap((response) =>
+                of(
+                    ParameterProviderActions.selectParameterProvider({
+                        request: {
+                            id: response.parameterProvider.id
+                        }
+                    })
+                )
+            )
+        )
+    );
+
+    promptParameterProviderDeletion$ = createEffect(
+        () =>
+            this.actions$.pipe(
+                ofType(ParameterProviderActions.promptParameterProviderDeletion),
+                map((action) => action.request),
+                tap((request) => {
+                    const dialogReference = this.dialog.open(YesNoDialog, {
+                        data: {
+                            title: 'Delete Parameter Provider',
+                            message: `Delete parameter provider ${request.parameterProvider.component.name}?`
+                        },
+                        panelClass: 'small-dialog'
+                    });
+
+                    dialogReference.componentInstance.yes.pipe(take(1)).subscribe(() =>
+                        this.store.dispatch(
+                            ParameterProviderActions.deleteParameterProvider({
+                                request
+                            })
+                        )
+                    );
+                })
+            ),
+        { dispatch: false }
+    );
+
+    deleteParameterProvider = createEffect(() =>
+        this.actions$.pipe(
+            ofType(ParameterProviderActions.deleteParameterProvider),
+            map((action) => action.request),
+            switchMap((request) =>
+                from(this.parameterProviderService.deleteParameterProvider(request)).pipe(
+                    map((response: any) =>
+                        ParameterProviderActions.deleteParameterProviderSuccess({
+                            response: {
+                                parameterProvider: response
+                            }
+                        })
+                    ),
+                    catchError((error) =>
+                        of(
+                            ParameterProviderActions.parameterProvidersApiError({
+                                error: error.error
+                            })
+                        )
+                    )
+                )
+            )
+        )
+    );
+
+    navigateToEditParameterProvider$ = createEffect(
+        () =>
+            this.actions$.pipe(
+                ofType(ParameterProviderActions.navigateToEditParameterProvider),
+                map((action) => action.id),
+                tap((id) => {
+                    this.router.navigate(['settings', 'parameter-providers', id, 'edit']);
+                })
+            ),
+        { dispatch: false }
+    );
+
+    openConfigureParameterProviderDialog$ = createEffect(
+        () =>
+            this.actions$.pipe(
+                ofType(ParameterProviderActions.openConfigureParameterProviderDialog),
+                map((action) => action.request),
+                tap((request) => {
+                    const id = request.id;
+                    const editDialogReference = this.dialog.open(EditParameterProvider, {
+                        data: {
+                            parameterProvider: request.parameterProvider
+                        },
+                        id,
+                        panelClass: 'large-dialog'
+                    });
+
+                    editDialogReference.componentInstance.saving$ = this.store.select(selectSaving);
+
+                    const goTo = (commands: string[], destination: string) => {
+                        // confirm navigating away while changes are unsaved
+                        if (editDialogReference.componentInstance.editParameterProviderForm.dirty) {
+                            const promptSaveDialogRef = this.dialog.open(YesNoDialog, {
+                                data: {
+                                    title: 'Parameter Provider Configuration',
+                                    message: `Save changes before going to this ${destination}`
+                                },
+                                panelClass: 'small-dialog'
+                            });
+
+                            promptSaveDialogRef.componentInstance.yes.pipe(take(1)).subscribe(() => {
+                                editDialogReference.componentInstance.submitForm(commands);
+                            });
+
+                            promptSaveDialogRef.componentInstance.no.pipe(take(1)).subscribe(() => {
+                                editDialogReference.close('ROUTED');
+                                this.router.navigate(commands);
+                            });
+                        } else {
+                            editDialogReference.close('ROUTED');
+                            this.router.navigate(commands);
+                        }
+                    };
+
+                    editDialogReference.componentInstance.goToReferencingParameterContext = (id: string) => {
+                        const commands: string[] = ['parameter-contexts', id];
+                        goTo(commands, 'Parameter Context');
+                    };
+
+                    editDialogReference.componentInstance.goToService = (serviceId: string) => {
+                        const commands: string[] = ['/settings', 'management-controller-services', serviceId];
+                        goTo(commands, 'Controller Service');
+                    };
+
+                    editDialogReference.componentInstance.createNewProperty =
+                        this.propertyTableHelperService.createNewProperty(request.id, this.parameterProviderService);
+
+                    editDialogReference.componentInstance.createNewService =
+                        this.propertyTableHelperService.createNewService(
+                            request.id,
+                            this.managementControllerServiceService,
+                            this.parameterProviderService
+                        );
+
+                    editDialogReference.componentInstance.editParameterProvider
+                        .pipe(takeUntil(editDialogReference.afterClosed()))
+                        .subscribe((updateRequest: UpdateParameterProviderRequest) => {
+                            this.store.dispatch(
+                                ParameterProviderActions.configureParameterProvider({
+                                    request: {
+                                        id: request.parameterProvider.id,
+                                        uri: request.parameterProvider.uri,
+                                        payload: updateRequest.payload,
+                                        postUpdateNavigation: updateRequest.postUpdateNavigation
+                                    }
+                                })
+                            );
+                        });
+
+                    editDialogReference.afterClosed().subscribe((response) => {
+                        if (response !== 'ROUTED') {
+                            this.store.dispatch(
+                                ParameterProviderActions.selectParameterProvider({
+                                    request: {
+                                        id
+                                    }
+                                })
+                            );
+                        }
+                    });
+                })
+            ),
+        { dispatch: false }
+    );
+
+    configureParameterProvider$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(ParameterProviderActions.configureParameterProvider),
+            map((action) => action.request),
+            switchMap((request) =>
+                from(this.parameterProviderService.updateParameterProvider(request)).pipe(
+                    map((response) =>
+                        ParameterProviderActions.configureParameterProviderSuccess({
+                            response: {
+                                id: request.id,
+                                parameterProvider: response,
+                                postUpdateNavigation: request.postUpdateNavigation
+                            }
+                        })
+                    ),
+                    catchError((error) =>
+                        of(
+                            ParameterProviderActions.parameterProvidersApiError({
+                                error: error.error
+                            })
+                        )
+                    )
+                )
+            )
+        )
+    );
+
+    configureParameterProviderSuccess$ = createEffect(
+        () =>
+            this.actions$.pipe(
+                ofType(ParameterProviderActions.configureParameterProviderSuccess),
+                map((action) => action.response),
+                tap((response) => {
+                    if (response.postUpdateNavigation) {
+                        this.router.navigate(response.postUpdateNavigation);
+                        this.dialog.getDialogById(response.id)?.close('ROUTED');
+                    } else {
+                        this.dialog.closeAll();
+                    }
+                })
+            ),
+        { dispatch: false }
+    );
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.reducer.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.reducer.ts
new file mode 100644
index 0000000000..574fcffd39
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.reducer.ts
@@ -0,0 +1,106 @@
+/*
+ * 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 { ParameterProvidersState } from './index';
+import { createReducer, on } from '@ngrx/store';
+import {
+    configureParameterProvider,
+    configureParameterProviderSuccess,
+    createParameterProvider,
+    createParameterProviderSuccess,
+    deleteParameterProvider,
+    deleteParameterProviderSuccess,
+    loadParameterProviders,
+    loadParameterProvidersSuccess,
+    parameterProvidersApiError,
+    resetParameterProvidersState
+} from './parameter-providers.actions';
+import { produce } from 'immer';
+
+export const initialParameterProvidersState: ParameterProvidersState = {
+    parameterProviders: [],
+    saving: false,
+    loadedTimestamp: '',
+    error: null,
+    status: 'pending'
+};
+
+export const parameterProvidersReducer = createReducer(
+    initialParameterProvidersState,
+
+    on(resetParameterProvidersState, (state: ParameterProvidersState) => ({
+        ...initialParameterProvidersState
+    })),
+
+    on(loadParameterProviders, (state: ParameterProvidersState) => ({
+        ...state,
+        status: 'loading' as const
+    })),
+
+    on(loadParameterProvidersSuccess, (state: ParameterProvidersState, { response }) => ({
+        ...state,
+        parameterProviders: response.parameterProviders,
+        loadedTimestamp: response.loadedTimestamp,
+        error: null,
+        status: 'success' as const
+    })),
+
+    on(parameterProvidersApiError, (state: ParameterProvidersState, { error }) => ({
+        ...state,
+        saving: false,
+        error,
+        status: 'error' as const
+    })),
+
+    on(createParameterProvider, configureParameterProvider, deleteParameterProvider, (state, { request }) => ({
+        ...state,
+        saving: true
+    })),
+
+    on(createParameterProviderSuccess, (state, { response }) => {
+        return produce(state, (draftState) => {
+            draftState.parameterProviders.push(response.parameterProvider);
+            draftState.saving = false;
+        });
+    }),
+
+    on(deleteParameterProviderSuccess, (state, { response }) => {
+        return produce(state, (draftState) => {
+            const idx = draftState.parameterProviders.findIndex(
+                (provider) => provider.id === response.parameterProvider.id
+            );
+            if (idx > -1) {
+                draftState.parameterProviders.splice(idx, 1);
+            }
+            draftState.saving = false;
+        });
+    }),
+
+    on(configureParameterProviderSuccess, (state, { response }) => {
+        return produce(state, (draftState) => {
+            const idx = draftState.parameterProviders.findIndex(
+                (provider) => provider.id === response.parameterProvider.id
+            );
+
+            if (idx > -1) {
+                draftState.parameterProviders[idx] = response.parameterProvider;
+            }
+
+            draftState.saving = false;
+        });
+    })
+);
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.selectors.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.selectors.ts
new file mode 100644
index 0000000000..0afa75b379
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.selectors.ts
@@ -0,0 +1,56 @@
+/*
+ * 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 { createSelector } from '@ngrx/store';
+import { selectSettingsState, SettingsState } from '../index';
+import { ParameterProviderEntity, parameterProvidersFeatureKey, ParameterProvidersState } from './index';
+import { selectCurrentRoute } from '../../../../state/router/router.selectors';
+
+export const selectParameterProvidersState = createSelector(
+    selectSettingsState,
+    (state: SettingsState) => state[parameterProvidersFeatureKey]
+);
+
+export const selectSaving = createSelector(
+    selectParameterProvidersState,
+    (state: ParameterProvidersState) => state.saving
+);
+
+export const selectParameterProviderIdFromRoute = createSelector(selectCurrentRoute, (route) => {
+    if (route) {
+        // always select the parameter provider from the route
+        return route.params.id;
+    }
+    return null;
+});
+
+export const selectSingleEditedParameterProvider = createSelector(selectCurrentRoute, (route) => {
+    if (route?.routeConfig?.path == 'edit') {
+        return route.params.id;
+    }
+    return null;
+});
+
+export const selectParameterProviders = createSelector(
+    selectParameterProvidersState,
+    (state: ParameterProvidersState) => state.parameterProviders
+);
+
+export const selectParameterProvider = (id: string) =>
+    createSelector(selectParameterProviders, (entities: ParameterProviderEntity[]) =>
+        entities.find((entity) => id == entity.id)
+    );
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/registry-clients/registry-clients.effects.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/registry-clients/registry-clients.effects.ts
index 37eeb8e480..bd431167f1 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/registry-clients/registry-clients.effects.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/registry-clients/registry-clients.effects.ts
@@ -18,7 +18,7 @@
 import { Injectable } from '@angular/core';
 import { Actions, concatLatestFrom, createEffect, ofType } from '@ngrx/effects';
 import * as RegistryClientsActions from './registry-clients.actions';
-import { catchError, from, map, NEVER, Observable, of, switchMap, take, takeUntil, tap } from 'rxjs';
+import { catchError, from, map, of, switchMap, take, takeUntil, tap } from 'rxjs';
 import { MatDialog } from '@angular/material/dialog';
 import { Store } from '@ngrx/store';
 import { NiFiState } from '../../../../state';
@@ -29,20 +29,11 @@ import { RegistryClientService } from '../../service/registry-client.service';
 import { CreateRegistryClient } from '../../ui/registry-clients/create-registry-client/create-registry-client.component';
 import { selectSaving } from './registry-clients.selectors';
 import { EditRegistryClient } from '../../ui/registry-clients/edit-registry-client/edit-registry-client.component';
-import {
-    InlineServiceCreationRequest,
-    InlineServiceCreationResponse,
-    NewPropertyDialogRequest,
-    NewPropertyDialogResponse,
-    Property,
-    PropertyDescriptor
-} from '../../../../state/shared';
-import { NewPropertyDialog } from '../../../../ui/common/new-property-dialog/new-property-dialog.component';
 import { ExtensionTypesService } from '../../../../service/extension-types.service';
-import { CreateControllerService } from '../../../../ui/common/controller-service/create-controller-service/create-controller-service.component';
 import { ManagementControllerServiceService } from '../../service/management-controller-service.service';
 import { Client } from '../../../../service/client.service';
 import { EditRegistryClientRequest } from './index';
+import { PropertyTableHelperService } from '../../../../service/property-table-helper.service';
 
 @Injectable()
 export class RegistryClientsEffects {
@@ -54,7 +45,8 @@ export class RegistryClientsEffects {
         private extensionTypesService: ExtensionTypesService,
         private managementControllerServiceService: ManagementControllerServiceService,
         private dialog: MatDialog,
-        private router: Router
+        private router: Router,
+        private propertyTableHelperService: PropertyTableHelperService
     ) {}
 
     loadRegistryClients$ = createEffect(() =>
@@ -181,40 +173,8 @@ export class RegistryClientsEffects {
 
                     editDialogReference.componentInstance.saving$ = this.store.select(selectSaving);
 
-                    editDialogReference.componentInstance.createNewProperty = (
-                        existingProperties: string[],
-                        allowsSensitive: boolean
-                    ): Observable<Property> => {
-                        const dialogRequest: NewPropertyDialogRequest = { existingProperties, allowsSensitive };
-                        const newPropertyDialogReference = this.dialog.open(NewPropertyDialog, {
-                            data: dialogRequest,
-                            panelClass: 'small-dialog'
-                        });
-
-                        return newPropertyDialogReference.componentInstance.newProperty.pipe(
-                            take(1),
-                            switchMap((dialogResponse: NewPropertyDialogResponse) => {
-                                return this.registryClientService
-                                    .getPropertyDescriptor(
-                                        registryClientId,
-                                        dialogResponse.name,
-                                        dialogResponse.sensitive
-                                    )
-                                    .pipe(
-                                        take(1),
-                                        map((response) => {
-                                            newPropertyDialogReference.close();
-
-                                            return {
-                                                property: dialogResponse.name,
-                                                value: null,
-                                                descriptor: response.propertyDescriptor
-                                            };
-                                        })
-                                    );
-                            })
-                        );
-                    };
+                    editDialogReference.componentInstance.createNewProperty =
+                        this.propertyTableHelperService.createNewProperty(registryClientId, this.registryClientService);
 
                     editDialogReference.componentInstance.goToService = (serviceId: string) => {
                         const commands: string[] = ['/settings', 'management-controller-services', serviceId];
@@ -242,77 +202,12 @@ export class RegistryClientsEffects {
                         }
                     };
 
-                    editDialogReference.componentInstance.createNewService = (
-                        request: InlineServiceCreationRequest
-                    ): Observable<InlineServiceCreationResponse> => {
-                        const descriptor: PropertyDescriptor = request.descriptor;
-
-                        // fetch all services that implement the requested service api
-                        return this.extensionTypesService
-                            .getImplementingControllerServiceTypes(
-                                // @ts-ignore
-                                descriptor.identifiesControllerService,
-                                descriptor.identifiesControllerServiceBundle
-                            )
-                            .pipe(
-                                take(1),
-                                switchMap((implementingTypesResponse) => {
-                                    // show the create controller service dialog with the types that implemented the interface
-                                    const createServiceDialogReference = this.dialog.open(CreateControllerService, {
-                                        data: {
-                                            controllerServiceTypes: implementingTypesResponse.controllerServiceTypes
-                                        },
-                                        panelClass: 'medium-dialog'
-                                    });
-
-                                    return createServiceDialogReference.componentInstance.createControllerService.pipe(
-                                        take(1),
-                                        switchMap((controllerServiceType) => {
-                                            // typically this sequence would be implemented with ngrx actions, however we are
-                                            // currently in an edit session and we need to return both the value (new service id)
-                                            // and updated property descriptor so the table renders correctly
-                                            return this.managementControllerServiceService
-                                                .createControllerService({
-                                                    revision: {
-                                                        clientId: this.client.getClientId(),
-                                                        version: 0
-                                                    },
-                                                    controllerServiceType: controllerServiceType.type,
-                                                    controllerServiceBundle: controllerServiceType.bundle
-                                                })
-                                                .pipe(
-                                                    take(1),
-                                                    switchMap((createReponse) => {
-                                                        // fetch an updated property descriptor
-                                                        return this.registryClientService
-                                                            .getPropertyDescriptor(
-                                                                registryClientId,
-                                                                descriptor.name,
-                                                                false
-                                                            )
-                                                            .pipe(
-                                                                take(1),
-                                                                map((descriptorResponse) => {
-                                                                    createServiceDialogReference.close();
-
-                                                                    return {
-                                                                        value: createReponse.id,
-                                                                        descriptor:
-                                                                            descriptorResponse.propertyDescriptor
-                                                                    };
-                                                                })
-                                                            );
-                                                    }),
-                                                    catchError((error) => {
-                                                        // TODO - show error
-                                                        return NEVER;
-                                                    })
-                                                );
-                                        })
-                                    );
-                                })
-                            );
-                    };
+                    editDialogReference.componentInstance.createNewService =
+                        this.propertyTableHelperService.createNewService(
+                            registryClientId,
+                            this.managementControllerServiceService,
+                            this.registryClientService
+                        );
 
                     editDialogReference.componentInstance.editRegistryClient
                         .pipe(takeUntil(editDialogReference.afterClosed()))
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/reporting-tasks/reporting-tasks.effects.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/reporting-tasks/reporting-tasks.effects.ts
index 05528bf004..b668f3da78 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/reporting-tasks/reporting-tasks.effects.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/reporting-tasks/reporting-tasks.effects.ts
@@ -44,6 +44,7 @@ import { ExtensionTypesService } from '../../../../service/extension-types.servi
 import { CreateControllerService } from '../../../../ui/common/controller-service/create-controller-service/create-controller-service.component';
 import { ManagementControllerServiceService } from '../../service/management-controller-service.service';
 import { Client } from '../../../../service/client.service';
+import { PropertyTableHelperService } from '../../../../service/property-table-helper.service';
 
 @Injectable()
 export class ReportingTasksEffects {
@@ -55,7 +56,8 @@ export class ReportingTasksEffects {
         private managementControllerServiceService: ManagementControllerServiceService,
         private extensionTypesService: ExtensionTypesService,
         private dialog: MatDialog,
-        private router: Router
+        private router: Router,
+        private propertyTableHelperService: PropertyTableHelperService
     ) {}
 
     loadReportingTasks$ = createEffect(() =>
@@ -225,36 +227,8 @@ export class ReportingTasksEffects {
 
                     editDialogReference.componentInstance.saving$ = this.store.select(selectSaving);
 
-                    editDialogReference.componentInstance.createNewProperty = (
-                        existingProperties: string[],
-                        allowsSensitive: boolean
-                    ): Observable<Property> => {
-                        const dialogRequest: NewPropertyDialogRequest = { existingProperties, allowsSensitive };
-                        const newPropertyDialogReference = this.dialog.open(NewPropertyDialog, {
-                            data: dialogRequest,
-                            panelClass: 'small-dialog'
-                        });
-
-                        return newPropertyDialogReference.componentInstance.newProperty.pipe(
-                            take(1),
-                            switchMap((dialogResponse: NewPropertyDialogResponse) => {
-                                return this.reportingTaskService
-                                    .getPropertyDescriptor(request.id, dialogResponse.name, dialogResponse.sensitive)
-                                    .pipe(
-                                        take(1),
-                                        map((response) => {
-                                            newPropertyDialogReference.close();
-
-                                            return {
-                                                property: dialogResponse.name,
-                                                value: null,
-                                                descriptor: response.propertyDescriptor
-                                            };
-                                        })
-                                    );
-                            })
-                        );
-                    };
+                    editDialogReference.componentInstance.createNewProperty =
+                        this.propertyTableHelperService.createNewProperty(request.id, this.reportingTaskService);
 
                     const goTo = (commands: string[], destination: string): void => {
                         if (editDialogReference.componentInstance.editReportingTaskForm.dirty) {
@@ -285,73 +259,12 @@ export class ReportingTasksEffects {
                         goTo(commands, 'Controller Service');
                     };
 
-                    editDialogReference.componentInstance.createNewService = (
-                        request: InlineServiceCreationRequest
-                    ): Observable<InlineServiceCreationResponse> => {
-                        const descriptor: PropertyDescriptor = request.descriptor;
-
-                        // fetch all services that implement the requested service api
-                        return this.extensionTypesService
-                            .getImplementingControllerServiceTypes(
-                                // @ts-ignore
-                                descriptor.identifiesControllerService,
-                                descriptor.identifiesControllerServiceBundle
-                            )
-                            .pipe(
-                                take(1),
-                                switchMap((implementingTypesResponse) => {
-                                    // show the create controller service dialog with the types that implemented the interface
-                                    const createServiceDialogReference = this.dialog.open(CreateControllerService, {
-                                        data: {
-                                            controllerServiceTypes: implementingTypesResponse.controllerServiceTypes
-                                        },
-                                        panelClass: 'medium-dialog'
-                                    });
-
-                                    return createServiceDialogReference.componentInstance.createControllerService.pipe(
-                                        take(1),
-                                        switchMap((controllerServiceType) => {
-                                            // typically this sequence would be implemented with ngrx actions, however we are
-                                            // currently in an edit session and we need to return both the value (new service id)
-                                            // and updated property descriptor so the table renders correctly
-                                            return this.managementControllerServiceService
-                                                .createControllerService({
-                                                    revision: {
-                                                        clientId: this.client.getClientId(),
-                                                        version: 0
-                                                    },
-                                                    controllerServiceType: controllerServiceType.type,
-                                                    controllerServiceBundle: controllerServiceType.bundle
-                                                })
-                                                .pipe(
-                                                    take(1),
-                                                    switchMap((createResponse) => {
-                                                        // fetch an updated property descriptor
-                                                        return this.reportingTaskService
-                                                            .getPropertyDescriptor(taskId, descriptor.name, false)
-                                                            .pipe(
-                                                                take(1),
-                                                                map((descriptorResponse) => {
-                                                                    createServiceDialogReference.close();
-
-                                                                    return {
-                                                                        value: createResponse.id,
-                                                                        descriptor:
-                                                                            descriptorResponse.propertyDescriptor
-                                                                    };
-                                                                })
-                                                            );
-                                                    }),
-                                                    catchError((error) => {
-                                                        // TODO - show error
-                                                        return NEVER;
-                                                    })
-                                                );
-                                        })
-                                    );
-                                })
-                            );
-                    };
+                    editDialogReference.componentInstance.createNewService =
+                        this.propertyTableHelperService.createNewService(
+                            request.id,
+                            this.managementControllerServiceService,
+                            this.reportingTaskService
+                        );
 
                     editDialogReference.componentInstance.editReportingTask
                         .pipe(takeUntil(editDialogReference.afterClosed()))
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/create-parameter-provider/create-parameter-provider.component.html
similarity index 73%
copy from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.html
copy to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/create-parameter-provider/create-parameter-provider.component.html
index 1e7c10a0b4..a1a278685e 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.html
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/create-parameter-provider/create-parameter-provider.component.html
@@ -6,7 +6,7 @@
   ~ (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
+  ~    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,
@@ -15,4 +15,8 @@
   ~ limitations under the License.
   -->
 
-<p>parameter-providers works!</p>
+<extension-creation
+    [componentType]="'Parameter Provider'"
+    [documentedTypes]="parameterProviderTypes"
+    [saving]="(saving$ | async)!"
+    (extensionTypeSelected)="parameterProviderTypeSelected($event)"></extension-creation>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/create-parameter-provider/create-parameter-provider.component.scss
similarity index 71%
copy from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.ts
copy to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/create-parameter-provider/create-parameter-provider.component.scss
index 1149a35b5f..b33f7cac34 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/create-parameter-provider/create-parameter-provider.component.scss
@@ -1,4 +1,4 @@
-/*
+/*!
  * 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.
@@ -6,7 +6,7 @@
  * (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
+ *    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,
@@ -14,12 +14,3 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-import { Component } from '@angular/core';
-
-@Component({
-    selector: 'parameter-providers',
-    templateUrl: './parameter-providers.component.html',
-    styleUrls: ['./parameter-providers.component.scss']
-})
-export class ParameterProviders {}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/create-parameter-provider/create-parameter-provider.component.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/create-parameter-provider/create-parameter-provider.component.spec.ts
new file mode 100644
index 0000000000..25517a106f
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/create-parameter-provider/create-parameter-provider.component.spec.ts
@@ -0,0 +1,81 @@
+/*
+ * 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 { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { CreateParameterProvider } from './create-parameter-provider.component';
+import { MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { provideMockStore } from '@ngrx/store/testing';
+import { initialParameterProvidersState } from '../../../state/parameter-providers/parameter-providers.reducer';
+import {
+    CreateParameterProviderDialogRequest,
+    CreateParameterProviderRequest
+} from '../../../state/parameter-providers';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+
+describe('CreateParameterProvider', () => {
+    let component: CreateParameterProvider;
+    let fixture: ComponentFixture<CreateParameterProvider>;
+    const data: CreateParameterProviderDialogRequest = {
+        parameterProviderTypes: [
+            {
+                type: 'org.apache.nifi.parameter.aws.AwsSecretsManagerParameterProvider',
+                bundle: {
+                    group: 'org.apache.nifi',
+                    artifact: 'nifi-aws-nar',
+                    version: '2.0.0-SNAPSHOT'
+                },
+                description:
+                    'Fetches parameters from AWS SecretsManager.  Each secret becomes a Parameter group, which can map to a Parameter Context, with key/value pairs in the secret mapping to Parameters in the group.',
+                restricted: false,
+                tags: ['secretsmanager', 'manager', 'aws', 'secrets']
+            },
+            {
+                type: 'org.apache.nifi.parameter.azure.AzureKeyVaultSecretsParameterProvider',
+                bundle: {
+                    group: 'org.apache.nifi',
+                    artifact: 'nifi-azure-nar',
+                    version: '2.0.0-SNAPSHOT'
+                },
+                description:
+                    "Fetches parameters from Azure Key Vault Secrets.  Each secret becomes a Parameter, which map to a Parameter Group byadding a secret tag named 'group-name'.",
+                restricted: false,
+                tags: ['keyvault', 'secrets', 'key', 'vault', 'azure']
+            }
+        ]
+    };
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            imports: [CreateParameterProvider, NoopAnimationsModule],
+            providers: [
+                {
+                    provide: MAT_DIALOG_DATA,
+                    useValue: data
+                },
+                provideMockStore({ initialState: initialParameterProvidersState })
+            ]
+        });
+        fixture = TestBed.createComponent(CreateParameterProvider);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it('should create', () => {
+        expect(component).toBeTruthy();
+    });
+});
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/create-controller-service/create-controller-service.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/create-parameter-provider/create-parameter-provider.component.ts
similarity index 53%
copy from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/create-controller-service/create-controller-service.component.ts
copy to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/create-parameter-provider/create-parameter-provider.component.ts
index 9eef6781c5..a23e226a8a 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/create-controller-service/create-controller-service.component.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/create-parameter-provider/create-parameter-provider.component.ts
@@ -6,7 +6,7 @@
  * (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
+ *    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,
@@ -16,30 +16,31 @@
  */
 
 import { Component, EventEmitter, Inject, Input, Output } from '@angular/core';
-import { MAT_DIALOG_DATA } from '@angular/material/dialog';
-import { CreateControllerServiceRequest, DocumentedType } from '../../../../state/shared';
-import { ExtensionCreation } from '../../extension-creation/extension-creation.component';
+import { CommonModule } from '@angular/common';
 import { Observable } from 'rxjs';
-import { AsyncPipe } from '@angular/common';
+import { DocumentedType } from '../../../../../state/shared';
+import { MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { CreateParameterProviderDialogRequest } from '../../../state/parameter-providers';
+import { ExtensionCreation } from '../../../../../ui/common/extension-creation/extension-creation.component';
 
 @Component({
-    selector: 'create-controller-service',
+    selector: 'create-parameter-provider',
     standalone: true,
-    imports: [ExtensionCreation, AsyncPipe],
-    templateUrl: './create-controller-service.component.html',
-    styleUrls: ['./create-controller-service.component.scss']
+    imports: [CommonModule, ExtensionCreation],
+    templateUrl: './create-parameter-provider.component.html',
+    styleUrls: ['./create-parameter-provider.component.scss']
 })
-export class CreateControllerService {
+export class CreateParameterProvider {
     @Input() saving$!: Observable<boolean>;
-    @Output() createControllerService: EventEmitter<DocumentedType> = new EventEmitter<DocumentedType>();
+    @Output() createParameterProvider: EventEmitter<DocumentedType> = new EventEmitter<DocumentedType>();
 
-    controllerServiceTypes: DocumentedType[];
+    parameterProviderTypes: DocumentedType[];
 
-    constructor(@Inject(MAT_DIALOG_DATA) private dialogRequest: CreateControllerServiceRequest) {
-        this.controllerServiceTypes = dialogRequest.controllerServiceTypes;
+    constructor(@Inject(MAT_DIALOG_DATA) private dialogRequest: CreateParameterProviderDialogRequest) {
+        this.parameterProviderTypes = dialogRequest.parameterProviderTypes;
     }
 
-    controllerServiceTypeSelected(controllerServiceType: DocumentedType): void {
-        this.createControllerService.next(controllerServiceType);
+    parameterProviderTypeSelected(parameterProviderType: DocumentedType) {
+        this.createParameterProvider.next(parameterProviderType);
     }
 }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/edit-parameter-provider/edit-parameter-provider.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/edit-parameter-provider/edit-parameter-provider.component.html
new file mode 100644
index 0000000000..a2eaf019a2
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/edit-parameter-provider/edit-parameter-provider.component.html
@@ -0,0 +1,91 @@
+<!--
+  ~ 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.
+  -->
+
+<h2 mat-dialog-title>Edit Parameter Provider</h2>
+<form class="parameter-provider-edit-form" [formGroup]="editParameterProviderForm">
+    <mat-dialog-content>
+        <mat-tab-group>
+            <mat-tab label="Settings">
+                <div class="tab-content py-4 flex gap-x-4">
+                    <div class="w-full">
+                        <div>
+                            <mat-form-field>
+                                <mat-label>Name</mat-label>
+                                <input matInput formControlName="name" type="text" />
+                            </mat-form-field>
+                        </div>
+                        <div class="flex flex-col mb-5">
+                            <div>Id</div>
+                            <div class="value">{{ request.parameterProvider.id }}</div>
+                        </div>
+                        <div class="flex flex-col mb-5">
+                            <div>Type</div>
+                            <div class="value">{{ formatType(request.parameterProvider) }}</div>
+                        </div>
+                        <div class="flex flex-col mb-5">
+                            <div>Bundle</div>
+                            <div class="value">{{ formatBundle(request.parameterProvider) }}</div>
+                        </div>
+                    </div>
+                    <div class="flex flex-col w-full">
+                        <div>Referencing Components</div>
+                        <div>
+                            <parameter-provider-references
+                                [parameterProviderReferences]="
+                                    request.parameterProvider.component.referencingParameterContexts
+                                "
+                                (goToParameterContext)="
+                                    navigateToParameterContext($event)
+                                "></parameter-provider-references>
+                        </div>
+                    </div>
+                </div>
+            </mat-tab>
+
+            <mat-tab label="Properties">
+                <div class="tab-content py-4">
+                    <property-table
+                        formControlName="properties"
+                        [createNewProperty]="createNewProperty"
+                        [createNewService]="createNewService"
+                        [goToService]="goToService"></property-table>
+                </div>
+            </mat-tab>
+
+            <mat-tab label="Comments">
+                <div class="tab-content py-4">
+                    <mat-form-field>
+                        <mat-label>Comments</mat-label>
+                        <textarea matInput formControlName="comments" type="text" rows="5"></textarea>
+                    </mat-form-field>
+                </div>
+            </mat-tab>
+        </mat-tab-group>
+    </mat-dialog-content>
+
+    <mat-dialog-actions align="end" *ngIf="{ value: (saving$ | async)! } as saving">
+        <button color="accent" mat-raised-button mat-dialog-close>Cancel</button>
+        <button
+            [disabled]="!editParameterProviderForm.dirty || editParameterProviderForm.invalid || saving.value"
+            type="button"
+            color="primary"
+            (click)="submitForm()"
+            mat-raised-button>
+            <span *nifiSpinner="saving.value">Apply</span>
+        </button>
+    </mat-dialog-actions>
+</form>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/edit-parameter-provider/edit-parameter-provider.component.scss
similarity index 66%
copy from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.ts
copy to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/edit-parameter-provider/edit-parameter-provider.component.scss
index 1149a35b5f..717bc5565d 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/edit-parameter-provider/edit-parameter-provider.component.scss
@@ -1,4 +1,4 @@
-/*
+/*!
  * 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.
@@ -6,7 +6,7 @@
  * (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
+ *    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,
@@ -15,11 +15,22 @@
  * limitations under the License.
  */
 
-import { Component } from '@angular/core';
+@use '@angular/material' as mat;
 
-@Component({
-    selector: 'parameter-providers',
-    templateUrl: './parameter-providers.component.html',
-    styleUrls: ['./parameter-providers.component.scss']
-})
-export class ParameterProviders {}
+.parameter-provider-edit-form {
+    @include mat.button-density(-1);
+
+    .mdc-dialog__content {
+        padding: 0 16px;
+        font-size: 14px;
+
+        .tab-content {
+            height: 475px;
+            overflow-y: auto;
+        }
+    }
+
+    .mat-mdc-form-field {
+        width: 100%;
+    }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/edit-parameter-provider/edit-parameter-provider.component.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/edit-parameter-provider/edit-parameter-provider.component.spec.ts
new file mode 100644
index 0000000000..dccbe3d0b2
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/edit-parameter-provider/edit-parameter-provider.component.spec.ts
@@ -0,0 +1,170 @@
+/*
+ * 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 { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { EditParameterProvider } from './edit-parameter-provider.component';
+import { MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { EditParameterProviderRequest } from '../../../state/parameter-providers';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+
+describe('EditParameterProvider', () => {
+    let component: EditParameterProvider;
+    let fixture: ComponentFixture<EditParameterProvider>;
+    const data: EditParameterProviderRequest = {
+        id: 'id',
+        parameterProvider: {
+            revision: {
+                clientId: '36ba1cc1-018d-1000-bc2c-787bc552d63d',
+                version: 6
+            },
+            id: '369487d7-018d-1000-817a-1d8d9a8f4a91',
+            uri: 'https://localhost:8443/nifi-api/parameter-providers/369487d7-018d-1000-817a-1d8d9a8f4a91',
+            permissions: {
+                canRead: true,
+                canWrite: true
+            },
+            bulletins: [],
+            component: {
+                id: '369487d7-018d-1000-817a-1d8d9a8f4a91',
+                name: 'Group 1 - FileParameterProvider',
+                type: 'org.apache.nifi.parameter.FileParameterProvider',
+                bundle: {
+                    group: 'org.apache.nifi',
+                    artifact: 'nifi-standard-nar',
+                    version: '2.0.0-SNAPSHOT'
+                },
+                comments: '',
+                persistsState: false,
+                restricted: true,
+                deprecated: false,
+                multipleVersionsAvailable: false,
+                properties: {
+                    'parameter-group-directories': '/Users/rfellows/tmp/parameterProviders/group1',
+                    'parameter-value-byte-limit': '256 B',
+                    'parameter-value-encoding': 'plaintext'
+                },
+                descriptors: {
+                    'parameter-group-directories': {
+                        name: 'parameter-group-directories',
+                        displayName: 'Parameter Group Directories',
+                        description:
+                            'A comma-separated list of directory absolute paths that will map to named parameter groups.  Each directory that contains files will map to a parameter group, named after the innermost directory in the path.  Files inside the directory will map to parameter names, whose values are the content of each respective file.',
+                        required: true,
+                        sensitive: false,
+                        dynamic: false,
+                        supportsEl: false,
+                        expressionLanguageScope: 'Not Supported',
+                        dependencies: []
+                    },
+                    'parameter-value-byte-limit': {
+                        name: 'parameter-value-byte-limit',
+                        displayName: 'Parameter Value Byte Limit',
+                        description:
+                            'The maximum byte size of a parameter value.  Since parameter values are pulled from the contents of files, this is a safeguard that can prevent memory issues if large files are included.',
+                        defaultValue: '256 B',
+                        required: true,
+                        sensitive: false,
+                        dynamic: false,
+                        supportsEl: false,
+                        expressionLanguageScope: 'Not Supported',
+                        dependencies: []
+                    },
+                    'parameter-value-encoding': {
+                        name: 'parameter-value-encoding',
+                        displayName: 'Parameter Value Encoding',
+                        description: 'Indicates how parameter values are encoded inside Parameter files.',
+                        defaultValue: 'base64',
+                        allowableValues: [
+                            {
+                                allowableValue: {
+                                    displayName: 'Base64',
+                                    value: 'base64',
+                                    description:
+                                        'File content is Base64-encoded, and will be decoded before providing the value as a Parameter.'
+                                },
+                                canRead: true
+                            },
+                            {
+                                allowableValue: {
+                                    displayName: 'Plain text',
+                                    value: 'plaintext',
+                                    description:
+                                        'File content is not encoded, and will be provided directly as a Parameter value.'
+                                },
+                                canRead: true
+                            }
+                        ],
+                        required: true,
+                        sensitive: false,
+                        dynamic: false,
+                        supportsEl: false,
+                        expressionLanguageScope: 'Not Supported',
+                        dependencies: []
+                    }
+                },
+                parameterGroupConfigurations: [
+                    {
+                        groupName: 'group1',
+                        parameterContextName: 'group1',
+                        parameterSensitivities: {
+                            bytes: 'NON_SENSITIVE',
+                            password: 'SENSITIVE',
+                            username: 'NON_SENSITIVE'
+                        },
+                        synchronized: true
+                    }
+                ],
+                referencingParameterContexts: [
+                    {
+                        id: '3716e18d-018d-1000-f203-4f6d571d572e',
+                        permissions: {
+                            canRead: true,
+                            canWrite: true
+                        },
+                        bulletins: [],
+                        component: {
+                            id: '3716e18d-018d-1000-f203-4f6d571d572e',
+                            name: 'group1'
+                        }
+                    }
+                ],
+                validationStatus: 'VALID',
+                extensionMissing: false
+            }
+        }
+    };
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            imports: [EditParameterProvider, NoopAnimationsModule],
+            providers: [
+                {
+                    provide: MAT_DIALOG_DATA,
+                    useValue: data
+                }
+            ]
+        });
+        fixture = TestBed.createComponent(EditParameterProvider);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it('should create', () => {
+        expect(component).toBeTruthy();
+    });
+});
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/edit-parameter-provider/edit-parameter-provider.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/edit-parameter-provider/edit-parameter-provider.component.ts
new file mode 100644
index 0000000000..2032ab2868
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/edit-parameter-provider/edit-parameter-provider.component.ts
@@ -0,0 +1,141 @@
+/*
+ * 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 { Component, EventEmitter, Inject, Input, Output } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
+import { MatTabsModule } from '@angular/material/tabs';
+import { MatButtonModule } from '@angular/material/button';
+import { NifiSpinnerDirective } from '../../../../../ui/common/spinner/nifi-spinner.directive';
+import { Observable } from 'rxjs';
+import {
+    ControllerServiceReferencingComponent,
+    InlineServiceCreationRequest,
+    InlineServiceCreationResponse,
+    ParameterContextReferenceEntity,
+    Property
+} from '../../../../../state/shared';
+import {
+    EditParameterProviderRequest,
+    ParameterProviderEntity,
+    UpdateParameterProviderRequest
+} from '../../../state/parameter-providers';
+import { AbstractControl, FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
+import { Client } from '../../../../../service/client.service';
+import { NiFiCommon } from '../../../../../service/nifi-common.service';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { MatInputModule } from '@angular/material/input';
+import { ControllerServiceReferences } from '../../../../../ui/common/controller-service/controller-service-references/controller-service-references.component';
+import { ParameterProviderReferences } from '../parameter-context-references/parameter-provider-references.component';
+import { PropertyTable } from '../../../../../ui/common/property-table/property-table.component';
+
+@Component({
+    selector: 'edit-parameter-provider',
+    standalone: true,
+    imports: [
+        CommonModule,
+        MatDialogModule,
+        MatTabsModule,
+        MatButtonModule,
+        NifiSpinnerDirective,
+        ReactiveFormsModule,
+        MatFormFieldModule,
+        MatInputModule,
+        ControllerServiceReferences,
+        ParameterProviderReferences,
+        PropertyTable
+    ],
+    templateUrl: './edit-parameter-provider.component.html',
+    styleUrls: ['./edit-parameter-provider.component.scss']
+})
+export class EditParameterProvider {
+    @Input() createNewProperty!: (existingProperties: string[], allowsSensitive: boolean) => Observable<Property>;
+    @Input() createNewService!: (request: InlineServiceCreationRequest) => Observable<InlineServiceCreationResponse>;
+    @Input() goToService!: (serviceId: string) => void;
+    @Input() goToReferencingParameterContext!: (parameterContextId: string) => void;
+    @Input() saving$!: Observable<boolean>;
+
+    @Output() editParameterProvider: EventEmitter<UpdateParameterProviderRequest> =
+        new EventEmitter<UpdateParameterProviderRequest>();
+
+    editParameterProviderForm: FormGroup;
+
+    constructor(
+        @Inject(MAT_DIALOG_DATA) public request: EditParameterProviderRequest,
+        private formBuilder: FormBuilder,
+        private client: Client,
+        private nifiCommon: NiFiCommon
+    ) {
+        const providerProperties = request.parameterProvider.component.properties;
+        const properties: Property[] = Object.entries(providerProperties).map((entry: any) => {
+            const [property, value] = entry;
+            return {
+                property,
+                value,
+                descriptor: request.parameterProvider.component.descriptors[property]
+            };
+        });
+
+        // build the form
+        this.editParameterProviderForm = this.formBuilder.group({
+            name: new FormControl(request.parameterProvider.component.name, Validators.required),
+            properties: new FormControl(properties),
+            comments: new FormControl(request.parameterProvider.component.comments)
+        });
+    }
+
+    formatType(entity: ParameterProviderEntity): string {
+        return this.nifiCommon.formatType(entity.component);
+    }
+
+    formatBundle(entity: ParameterProviderEntity): string {
+        return this.nifiCommon.formatBundle(entity.component.bundle);
+    }
+
+    submitForm(postUpdateNavigation?: string[]) {
+        const payload: any = {
+            revision: this.client.getRevision(this.request.parameterProvider),
+            component: {
+                id: this.request.parameterProvider.id,
+                name: this.editParameterProviderForm.get('name')?.value,
+                comments: this.editParameterProviderForm.get('comments')?.value
+            }
+        };
+
+        const propertyControl: AbstractControl | null = this.editParameterProviderForm.get('properties');
+        if (propertyControl && propertyControl.dirty) {
+            const properties: Property[] = propertyControl.value;
+            const values: { [key: string]: string | null } = {};
+            properties.forEach((property) => (values[property.property] = property.value));
+            payload.component.properties = values;
+            payload.component.sensitiveDynamicPropertyNames = properties
+                .filter((property) => property.descriptor.dynamic && property.descriptor.sensitive)
+                .map((property) => property.descriptor.name);
+        }
+
+        this.editParameterProvider.next({
+            payload,
+            postUpdateNavigation
+        });
+    }
+
+    navigateToParameterContext(parameterContextReference: ParameterContextReferenceEntity) {
+        if (parameterContextReference.component?.id) {
+            this.goToReferencingParameterContext(parameterContextReference.component?.id);
+        }
+    }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-context-references/parameter-provider-references.component.spec.ts
similarity index 71%
copy from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.spec.ts
copy to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-context-references/parameter-provider-references.component.spec.ts
index 842641ea8b..f7a7d2ec23 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.spec.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-context-references/parameter-provider-references.component.spec.ts
@@ -6,7 +6,7 @@
  * (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
+ *    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,
@@ -17,17 +17,17 @@
 
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 
-import { ParameterProviders } from './parameter-providers.component';
+import { ParameterProviderReferences } from './parameter-provider-references.component';
 
-describe('ParameterProviders', () => {
-    let component: ParameterProviders;
-    let fixture: ComponentFixture<ParameterProviders>;
+describe('ParameterProviderReferences', () => {
+    let component: ParameterProviderReferences;
+    let fixture: ComponentFixture<ParameterProviderReferences>;
 
     beforeEach(() => {
         TestBed.configureTestingModule({
-            declarations: [ParameterProviders]
+            imports: [ParameterProviderReferences]
         });
-        fixture = TestBed.createComponent(ParameterProviders);
+        fixture = TestBed.createComponent(ParameterProviderReferences);
         component = fixture.componentInstance;
         fixture.detectChanges();
     });
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-context-references/parameter-provider-references.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-context-references/parameter-provider-references.component.ts
new file mode 100644
index 0000000000..652aaac777
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-context-references/parameter-provider-references.component.ts
@@ -0,0 +1,50 @@
+/*
+ * 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 { Component, EventEmitter, Input, Output } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { ParameterContextReferenceEntity } from '../../../../../state/shared';
+import { NiFiCommon } from '../../../../../service/nifi-common.service';
+import { RouterLink } from '@angular/router';
+
+@Component({
+    selector: 'parameter-provider-references',
+    standalone: true,
+    imports: [CommonModule, RouterLink],
+    templateUrl: './parameter-providers-references.component.html',
+    styleUrls: ['./parameter-providers-references.component.scss']
+})
+export class ParameterProviderReferences {
+    @Input() parameterProviderReferences!: ParameterContextReferenceEntity[];
+    @Output() goToParameterContext: EventEmitter<ParameterContextReferenceEntity> =
+        new EventEmitter<ParameterContextReferenceEntity>();
+
+    constructor(private nifiCommon: NiFiCommon) {}
+
+    getUnauthorized(references: ParameterContextReferenceEntity[]) {
+        return references.filter((p) => !p.permissions.canRead);
+    }
+
+    getAuthorized(references: ParameterContextReferenceEntity[]) {
+        return references.filter((p) => p.permissions.canRead);
+    }
+
+    goToParameterContextClicked(event: MouseEvent, parameterContextReference: ParameterContextReferenceEntity) {
+        event.stopPropagation();
+        this.goToParameterContext.next(parameterContextReference);
+    }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-context-references/parameter-providers-references.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-context-references/parameter-providers-references.component.html
new file mode 100644
index 0000000000..84ec9b4444
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-context-references/parameter-providers-references.component.html
@@ -0,0 +1,76 @@
+<!--
+  ~ 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.
+  -->
+
+<div class="parameter-provider-references">
+    <div
+        *ngIf="!parameterProviderReferences || parameterProviderReferences?.length === 0; else hasReferences"
+        class="value">
+        No referencing components
+    </div>
+
+    <ng-template #hasReferences>
+        <ul>
+            <ng-container
+                *ngTemplateOutlet="
+                    parameterContexts;
+                    context: {
+                        $implicit: getAuthorized(parameterProviderReferences),
+                        label: 'Parameter Contexts'
+                    }
+                "></ng-container>
+
+            <ng-container
+                *ngTemplateOutlet="
+                    unauthorized;
+                    context: {
+                        $implicit: getUnauthorized(parameterProviderReferences),
+                        label: 'Unauthorized'
+                    }
+                "></ng-container>
+        </ul>
+    </ng-template>
+
+    <ng-template #parameterContexts let-references let-label="label">
+        <ng-container *ngIf="references.length > 0">
+            <li>
+                <h4>
+                    <span class="value">{{ label }}</span> ({{ references.length }})
+                </h4>
+                <div class="references">
+                    <div *ngFor="let reference of references" class="flex items-center gap-x-2">
+                        <a (click)="goToParameterContextClicked($event, reference)">{{ reference.component?.name }}</a>
+                    </div>
+                </div>
+            </li>
+        </ng-container>
+    </ng-template>
+
+    <ng-template #unauthorized let-references let-label="label">
+        <ng-container *ngIf="references.length > 0">
+            <li>
+                <h4>
+                    <span class="value">{{ label }}</span> ({{ references.length }})
+                </h4>
+                <div class="references">
+                    <div *ngFor="let reference of references" class="flex items-center gap-x-2">
+                        <div class="unset">{{ reference.id }}</div>
+                    </div>
+                </div>
+            </li>
+        </ng-container>
+    </ng-template>
+</div>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-context-references/parameter-providers-references.component.scss
similarity index 71%
copy from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.ts
copy to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-context-references/parameter-providers-references.component.scss
index 1149a35b5f..c6abda8ba2 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-context-references/parameter-providers-references.component.scss
@@ -1,4 +1,4 @@
-/*
+/*!
  * 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.
@@ -6,7 +6,7 @@
  * (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
+ *    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,
@@ -15,11 +15,12 @@
  * limitations under the License.
  */
 
-import { Component } from '@angular/core';
+.parameter-provider-references {
+    ul.nested {
+        padding-inline-start: 20px;
+    }
 
-@Component({
-    selector: 'parameter-providers',
-    templateUrl: './parameter-providers.component.html',
-    styleUrls: ['./parameter-providers.component.scss']
-})
-export class ParameterProviders {}
+    .references {
+        margin-left: 20px;
+    }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers-table/parameter-providers-table.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers-table/parameter-providers-table.component.html
new file mode 100644
index 0000000000..7c3eb1e925
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers-table/parameter-providers-table.component.html
@@ -0,0 +1,121 @@
+<!--
+  ~ 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.
+  -->
+
+<ng-container>
+    <div class="parameter-providers-table h-full flex flex-col">
+        <div class="flex-1 relative">
+            <div class="listing-table overflow-y-auto border absolute inset-0">
+                <table
+                    mat-table
+                    [dataSource]="dataSource"
+                    matSort
+                    matSortDisableClear
+                    (matSortChange)="sortData($event)"
+                    [matSortActive]="initialSortColumn"
+                    [matSortDirection]="initialSortDirection">
+                    <!-- More Details Column -->
+                    <ng-container matColumnDef="moreDetails">
+                        <th mat-header-cell *matHeaderCellDef></th>
+                        <td mat-cell *matCellDef="let item">
+                            <div class="flex items-center gap-x-3">
+                                <!-- TODO: open details -->
+                                <div class="pointer fa fa-info-circle" title="View details"></div>
+
+                                <!-- TODO: open documentation -->
+                                <div class="pointer fa fa-book" title="View Documentation"></div>
+
+                                <!-- Validation Errors -->
+                                <div *ngIf="hasErrors(item)">
+                                    <div
+                                        class="pointer fa fa-warning has-errors"
+                                        nifiTooltip
+                                        [delayClose]="false"
+                                        [tooltipComponentType]="ValidationErrorsTip"
+                                        [tooltipInputData]="getValidationErrorsTipData(item)"></div>
+                                </div>
+                            </div>
+                        </td>
+                    </ng-container>
+
+                    <!-- Name Column -->
+                    <ng-container matColumnDef="name">
+                        <th mat-header-cell *matHeaderCellDef mat-sort-header>
+                            <div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap">Name</div>
+                        </th>
+                        <td mat-cell *matCellDef="let item" [title]="formatName(item)">
+                            <div [ngClass]="{ unset: !canRead(item) }">{{ formatName(item) }}</div>
+                        </td>
+                    </ng-container>
+
+                    <!-- Type column -->
+                    <ng-container matColumnDef="type">
+                        <th mat-header-cell *matHeaderCellDef mat-sort-header>
+                            <div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap">Type</div>
+                        </th>
+                        <td mat-cell *matCellDef="let item" [title]="formatType(item)">
+                            {{ formatType(item) }}
+                        </td>
+                    </ng-container>
+
+                    <!-- Bundle column -->
+                    <ng-container matColumnDef="bundle">
+                        <th mat-header-cell *matHeaderCellDef mat-sort-header>
+                            <div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap">Bundle</div>
+                        </th>
+                        <td mat-cell *matCellDef="let item" [title]="formatBundle(item)">
+                            {{ formatBundle(item) }}
+                        </td>
+                    </ng-container>
+
+                    <ng-container matColumnDef="actions">
+                        <th mat-header-cell *matHeaderCellDef></th>
+                        <td mat-cell *matCellDef="let item">
+                            <div class="flex items-center gap-x-3">
+                                <div
+                                    class="pointer fa fa-pencil"
+                                    (click)="configureClicked(item, $event)"
+                                    title="Edit"></div>
+                                <div
+                                    class="pointer fa fa-arrow-circle-down"
+                                    (click)="fetchClicked(item, $event)"
+                                    title="Fetch Parameters"></div>
+                                <div
+                                    class="pointer fa fa-trash"
+                                    (click)="deleteClicked(item, $event)"
+                                    title="Remove"></div>
+                                <div
+                                    *ngIf="canManageAccessPolicies()"
+                                    class="pointer fa fa-key"
+                                    (click)="$event.stopPropagation()"
+                                    [routerLink]="getPolicyLink(item)"
+                                    title="Access Policies"></div>
+                            </div>
+                        </td>
+                    </ng-container>
+
+                    <tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
+                    <tr
+                        mat-row
+                        *matRowDef="let row; let even = even; columns: displayedColumns"
+                        [class.even]="even"
+                        (click)="selectParameterProvider.next(row)"
+                        [class.selected]="isSelected(row)"></tr>
+                </table>
+            </div>
+        </div>
+    </div>
+</ng-container>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers-table/parameter-providers-table.component.scss
similarity index 71%
copy from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.ts
copy to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers-table/parameter-providers-table.component.scss
index 1149a35b5f..e44f2b386a 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers-table/parameter-providers-table.component.scss
@@ -1,4 +1,4 @@
-/*
+/*!
  * 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.
@@ -6,7 +6,7 @@
  * (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
+ *    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,
@@ -15,11 +15,8 @@
  * limitations under the License.
  */
 
-import { Component } from '@angular/core';
-
-@Component({
-    selector: 'parameter-providers',
-    templateUrl: './parameter-providers.component.html',
-    styleUrls: ['./parameter-providers.component.scss']
-})
-export class ParameterProviders {}
+.parameter-providers-table {
+    .listing-table {
+        // overrides to the listing-table styles go here
+    }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers-table/parameter-providers-table.component.spec.ts
similarity index 68%
copy from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.spec.ts
copy to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers-table/parameter-providers-table.component.spec.ts
index 842641ea8b..5f6a0e3767 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.spec.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers-table/parameter-providers-table.component.spec.ts
@@ -6,7 +6,7 @@
  * (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
+ *    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,
@@ -17,17 +17,18 @@
 
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 
-import { ParameterProviders } from './parameter-providers.component';
+import { ParameterProvidersTable } from './parameter-providers-table.component';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
 
-describe('ParameterProviders', () => {
-    let component: ParameterProviders;
-    let fixture: ComponentFixture<ParameterProviders>;
+describe('ParameterProvidersTable', () => {
+    let component: ParameterProvidersTable;
+    let fixture: ComponentFixture<ParameterProvidersTable>;
 
     beforeEach(() => {
         TestBed.configureTestingModule({
-            declarations: [ParameterProviders]
+            imports: [ParameterProvidersTable, NoopAnimationsModule]
         });
-        fixture = TestBed.createComponent(ParameterProviders);
+        fixture = TestBed.createComponent(ParameterProvidersTable);
         component = fixture.componentInstance;
         fixture.detectChanges();
     });
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers-table/parameter-providers-table.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers-table/parameter-providers-table.component.ts
new file mode 100644
index 0000000000..2916239c6e
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers-table/parameter-providers-table.component.ts
@@ -0,0 +1,176 @@
+/*
+ * 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 { Component, EventEmitter, Input, Output } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { MatSortModule, Sort, SortDirection } from '@angular/material/sort';
+import { MatTableDataSource, MatTableModule } from '@angular/material/table';
+import { ParameterProviderEntity } from '../../../state/parameter-providers';
+import { NiFiCommon } from '../../../../../service/nifi-common.service';
+import { CurrentUser } from '../../../../../state/current-user';
+import { FlowConfiguration } from '../../../../../state/flow-configuration';
+import { MatPaginatorModule } from '@angular/material/paginator';
+import { SummaryTableFilterModule } from '../../../../summary/ui/common/summary-table-filter/summary-table-filter.module';
+import { PortStatusSnapshotEntity } from '../../../../summary/state/summary-listing';
+import { ValidationErrorsTip } from '../../../../../ui/common/tooltips/validation-errors-tip/validation-errors-tip.component';
+import { NifiTooltipDirective } from '../../../../../ui/common/tooltips/nifi-tooltip.directive';
+import { ControllerServiceEntity, ValidationErrorsTipInput } from '../../../../../state/shared';
+import { RouterLink } from '@angular/router';
+
+export type SupportedColumns = 'name' | 'type' | 'bundle';
+
+@Component({
+    selector: 'parameter-providers-table',
+    standalone: true,
+    imports: [
+        CommonModule,
+        MatPaginatorModule,
+        MatSortModule,
+        MatTableModule,
+        SummaryTableFilterModule,
+        NifiTooltipDirective,
+        RouterLink
+    ],
+    templateUrl: './parameter-providers-table.component.html',
+    styleUrls: ['./parameter-providers-table.component.scss', '../../../../../../assets/styles/listing-table.scss']
+})
+export class ParameterProvidersTable {
+    @Input() initialSortColumn: SupportedColumns = 'name';
+    @Input() initialSortDirection: SortDirection = 'asc';
+
+    displayedColumns: string[] = ['moreDetails', 'name', 'type', 'bundle', 'actions'];
+    dataSource: MatTableDataSource<ParameterProviderEntity> = new MatTableDataSource<ParameterProviderEntity>();
+    activeSort: Sort = {
+        active: this.initialSortColumn,
+        direction: this.initialSortDirection
+    };
+
+    constructor(private nifiCommon: NiFiCommon) {}
+
+    @Input() selectedParameterProviderId!: string;
+    @Input() currentUser!: CurrentUser;
+    @Input() flowConfiguration!: FlowConfiguration;
+
+    @Input() set parameterProviders(parameterProviders: ParameterProviderEntity[]) {
+        if (parameterProviders) {
+            this.dataSource.data = this.sortEntities(parameterProviders, this.activeSort);
+        }
+    }
+
+    @Output() selectParameterProvider: EventEmitter<ParameterProviderEntity> =
+        new EventEmitter<ParameterProviderEntity>();
+    @Output() configureParameterProvider: EventEmitter<ParameterProviderEntity> =
+        new EventEmitter<ParameterProviderEntity>();
+    @Output() deleteParameterProvider: EventEmitter<ParameterProviderEntity> =
+        new EventEmitter<ParameterProviderEntity>();
+    @Output() fetchParameterProvider: EventEmitter<ParameterProviderEntity> =
+        new EventEmitter<ParameterProviderEntity>();
+    @Output() manageAccessPolicies: EventEmitter<ParameterProviderEntity> = new EventEmitter<ParameterProviderEntity>();
+
+    protected readonly ValidationErrorsTip = ValidationErrorsTip;
+
+    canRead(entity: ParameterProviderEntity): boolean {
+        return entity.permissions.canRead;
+    }
+
+    canWrite(entity: ParameterProviderEntity): boolean {
+        return entity.permissions.canWrite;
+    }
+
+    canManageAccessPolicies(): boolean {
+        return this.flowConfiguration.supportsManagedAuthorizer && this.currentUser.tenantsPermissions.canRead;
+    }
+
+    isSelected(parameterProvider: ParameterProviderEntity): boolean {
+        if (this.selectedParameterProviderId) {
+            return parameterProvider.id === this.selectedParameterProviderId;
+        }
+        return false;
+    }
+
+    formatName(entity: ParameterProviderEntity): string {
+        return this.canRead(entity) ? entity.component.name : entity.id;
+    }
+
+    formatType(entity: ParameterProviderEntity): string {
+        return this.canRead(entity) ? this.nifiCommon.formatType(entity.component) : '';
+    }
+
+    formatBundle(entity: ParameterProviderEntity): string {
+        return this.canRead(entity) ? this.nifiCommon.formatBundle(entity.component.bundle) : '';
+    }
+
+    hasErrors(entity: ParameterProviderEntity): boolean {
+        return this.canRead(entity) && !this.nifiCommon.isEmpty(entity.component.validationErrors);
+    }
+
+    getValidationErrorsTipData(entity: ParameterProviderEntity): ValidationErrorsTipInput | null {
+        return {
+            isValidating: entity.component.validationStatus === 'VALIDATING',
+            validationErrors: entity.component?.validationErrors || []
+        };
+    }
+
+    sortData(sort: Sort) {
+        this.activeSort = sort;
+        this.dataSource.data = this.sortEntities(this.dataSource.data, sort);
+    }
+
+    private sortEntities(data: ParameterProviderEntity[], sort: Sort): ParameterProviderEntity[] {
+        if (!data) {
+            return [];
+        }
+        return data.slice().sort((a, b) => {
+            const isAsc: boolean = sort.direction === 'asc';
+            let retVal = 0;
+            switch (sort.active) {
+                case 'name':
+                    retVal = this.nifiCommon.compareString(this.formatName(a), this.formatName(b));
+                    break;
+                case 'type':
+                    retVal = this.nifiCommon.compareString(this.formatType(a), this.formatType(b));
+                    break;
+                case 'bundle':
+                    retVal = this.nifiCommon.compareString(this.formatBundle(a), this.formatBundle(b));
+                    break;
+                default:
+                    return 0;
+            }
+
+            return retVal * (isAsc ? 1 : -1);
+        });
+    }
+
+    configureClicked(entity: ParameterProviderEntity, event: MouseEvent) {
+        event.stopPropagation();
+        this.configureParameterProvider.next(entity);
+    }
+
+    fetchClicked(entity: ParameterProviderEntity, event: MouseEvent) {
+        event.stopPropagation();
+        this.fetchParameterProvider.next(entity);
+    }
+
+    deleteClicked(entity: ParameterProviderEntity, event: MouseEvent) {
+        event.stopPropagation();
+        this.deleteParameterProvider.next(entity);
+    }
+
+    getPolicyLink(entity: ParameterProviderEntity): string[] {
+        return ['/access-policies', 'read', 'component', 'parameter-providers', entity.id];
+    }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.html
index 1e7c10a0b4..d0b715d847 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.html
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.html
@@ -15,4 +15,39 @@
   ~ limitations under the License.
   -->
 
-<p>parameter-providers works!</p>
+<ng-container *ngIf="parameterProvidersState$ | async; let parameterProvidersState">
+    <div *ngIf="isInitialLoading(parameterProvidersState); else loaded">
+        <ngx-skeleton-loader count="3"></ngx-skeleton-loader>
+    </div>
+    <ng-template #loaded>
+        <div class="flex flex-col h-full gap-y-2" *ngIf="currentUser$ | async; let currentUser">
+            <div class="flex justify-end" *ngIf="currentUser.controllerPermissions.canWrite">
+                <button
+                    class="nifi-button"
+                    (click)="openNewParameterProviderDialog()"
+                    title="Add a new parameter provider">
+                    <i class="fa fa-plus"></i>
+                </button>
+            </div>
+            <div class="flex-1">
+                <parameter-providers-table
+                    [currentUser]="currentUser"
+                    [flowConfiguration]="(flowConfiguration$ | async)!"
+                    [parameterProviders]="parameterProvidersState.parameterProviders"
+                    [selectedParameterProviderId]="selectedParameterProviderId$ | async"
+                    (deleteParameterProvider)="deleteParameterProvider($event)"
+                    (configureParameterProvider)="openConfigureParameterProviderDialog($event)"
+                    (selectParameterProvider)="selectParameterProvider($event)"></parameter-providers-table>
+            </div>
+            <div class="flex justify-between">
+                <div class="refresh-container flex items-center gap-x-2">
+                    <button class="nifi-button" (click)="refreshParameterProvidersListing()">
+                        <i class="fa fa-refresh" [class.fa-spin]="parameterProvidersState.status === 'loading'"></i>
+                    </button>
+                    <div>Last updated:</div>
+                    <div class="refresh-timestamp">{{ parameterProvidersState.loadedTimestamp }}</div>
+                </div>
+            </div>
+        </div>
+    </ng-template>
+</ng-container>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.spec.ts
index 842641ea8b..66357cb49c 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.spec.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.spec.ts
@@ -18,6 +18,8 @@
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 
 import { ParameterProviders } from './parameter-providers.component';
+import { provideMockStore } from '@ngrx/store/testing';
+import { initialParameterProvidersState } from '../../state/parameter-providers/parameter-providers.reducer';
 
 describe('ParameterProviders', () => {
     let component: ParameterProviders;
@@ -25,7 +27,12 @@ describe('ParameterProviders', () => {
 
     beforeEach(() => {
         TestBed.configureTestingModule({
-            declarations: [ParameterProviders]
+            declarations: [ParameterProviders],
+            providers: [
+                provideMockStore({
+                    initialState: initialParameterProvidersState
+                })
+            ]
         });
         fixture = TestBed.createComponent(ParameterProviders);
         component = fixture.componentInstance;
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.ts
index 1149a35b5f..aab8243728 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.component.ts
@@ -15,11 +15,105 @@
  * limitations under the License.
  */
 
-import { Component } from '@angular/core';
+import { Component, OnDestroy, OnInit } from '@angular/core';
+import { Store } from '@ngrx/store';
+import { NiFiState } from '../../../../state';
+import { ParameterProviderEntity, ParameterProvidersState } from '../../state/parameter-providers';
+import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors';
+import {
+    selectParameterProvider,
+    selectParameterProviderIdFromRoute,
+    selectParameterProvidersState,
+    selectSingleEditedParameterProvider
+} from '../../state/parameter-providers/parameter-providers.selectors';
+import { selectFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.selectors';
+import { loadFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.actions';
+import * as ParameterProviderActions from '../../state/parameter-providers/parameter-providers.actions';
+import { initialParameterProvidersState } from '../../state/parameter-providers/parameter-providers.reducer';
+import { filter, switchMap, take } from 'rxjs';
+import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
+import { isDefinedAndNotNull } from '../../../../state/shared';
 
 @Component({
     selector: 'parameter-providers',
     templateUrl: './parameter-providers.component.html',
     styleUrls: ['./parameter-providers.component.scss']
 })
-export class ParameterProviders {}
+export class ParameterProviders implements OnInit, OnDestroy {
+    currentUser$ = this.store.select(selectCurrentUser);
+    parameterProvidersState$ = this.store.select(selectParameterProvidersState);
+    selectedParameterProviderId$ = this.store.select(selectParameterProviderIdFromRoute);
+    flowConfiguration$ = this.store.select(selectFlowConfiguration);
+
+    constructor(private store: Store<NiFiState>) {
+        this.store
+            .select(selectSingleEditedParameterProvider)
+            .pipe(
+                isDefinedAndNotNull(),
+                switchMap((id: string) =>
+                    this.store.select(selectParameterProvider(id)).pipe(isDefinedAndNotNull(), take(1))
+                ),
+                takeUntilDestroyed()
+            )
+            .subscribe((entity) => {
+                this.store.dispatch(
+                    ParameterProviderActions.openConfigureParameterProviderDialog({
+                        request: {
+                            id: entity.id,
+                            parameterProvider: entity
+                        }
+                    })
+                );
+            });
+    }
+
+    ngOnInit(): void {
+        this.store.dispatch(loadFlowConfiguration());
+        this.store.dispatch(ParameterProviderActions.loadParameterProviders());
+    }
+
+    ngOnDestroy(): void {
+        this.store.dispatch(ParameterProviderActions.resetParameterProvidersState());
+    }
+
+    isInitialLoading(state: ParameterProvidersState): boolean {
+        // using the current timestamp to detect the initial load event
+        return state.loadedTimestamp == initialParameterProvidersState.loadedTimestamp;
+    }
+
+    refreshParameterProvidersListing(): void {
+        this.store.dispatch(ParameterProviderActions.loadParameterProviders());
+    }
+
+    openNewParameterProviderDialog() {
+        this.store.dispatch(ParameterProviderActions.openNewParameterProviderDialog());
+    }
+
+    openConfigureParameterProviderDialog(parameterProvider: ParameterProviderEntity) {
+        this.store.dispatch(
+            ParameterProviderActions.navigateToEditParameterProvider({
+                id: parameterProvider.component.id
+            })
+        );
+    }
+
+    selectParameterProvider(parameterProvider: ParameterProviderEntity) {
+        this.store.dispatch(
+            ParameterProviderActions.selectParameterProvider({
+                request: {
+                    id: parameterProvider.id
+                }
+            })
+        );
+    }
+
+    deleteParameterProvider(parameterProvider: ParameterProviderEntity) {
+        this.store.dispatch(
+            ParameterProviderActions.promptParameterProviderDeletion({
+                request: {
+                    parameterProvider
+                }
+            })
+        );
+    }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.module.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.module.ts
index 9eff52810f..6ea65f94f6 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.module.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers.module.ts
@@ -18,10 +18,13 @@
 import { NgModule } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { ParameterProviders } from './parameter-providers.component';
+import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
+import { ParameterContextListingModule } from '../../../parameter-contexts/ui/parameter-context-listing/parameter-context-listing.module';
+import { ParameterProvidersTable } from './parameter-providers-table/parameter-providers-table.component';
 
 @NgModule({
     declarations: [ParameterProviders],
     exports: [ParameterProviders],
-    imports: [CommonModule]
+    imports: [CommonModule, NgxSkeletonLoaderModule, ParameterProvidersTable]
 })
 export class ParameterProvidersModule {}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.ts
index a09304f2ee..b63c92162d 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.ts
@@ -35,12 +35,13 @@ import { CurrentUser } from '../../../../../state/current-user';
 export class ReportingTaskTable {
     @Input() initialSortColumn: 'name' | 'type' | 'bundle' | 'state' = 'name';
     @Input() initialSortDirection: 'asc' | 'desc' = 'asc';
+    activeSort: Sort = {
+        active: this.initialSortColumn,
+        direction: this.initialSortDirection
+    };
 
     @Input() set reportingTasks(reportingTaskEntities: ReportingTaskEntity[]) {
-        this.dataSource.data = this.sortEntities(reportingTaskEntities, {
-            active: this.initialSortColumn,
-            direction: this.initialSortDirection
-        });
+        this.dataSource.data = this.sortEntities(reportingTaskEntities, this.activeSort);
     }
 
     @Input() selectedReportingTaskId!: string;
@@ -258,6 +259,7 @@ export class ReportingTaskTable {
     }
 
     sortData(sort: Sort) {
+        this.activeSort = sort;
         this.dataSource.data = this.sortEntities(this.dataSource.data, sort);
     }
 
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/service/users.service.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/service/users.service.ts
index 2b1f18e95f..75d8dfa953 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/service/users.service.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/service/users.service.ts
@@ -32,19 +32,6 @@ import {
 export class UsersService {
     private static readonly API: string = '../nifi-api';
 
-    /**
-     * The NiFi model contain the url for each component. That URL is an absolute URL. Angular CSRF handling
-     * does not work on absolute URLs, so we need to strip off the proto for the request header to be added.
-     *
-     * https://stackoverflow.com/a/59586462
-     *
-     * @param url
-     * @private
-     */
-    private stripProtocol(url: string): string {
-        return this.nifiCommon.substringAfterFirst(url, ':');
-    }
-
     constructor(
         private httpClient: HttpClient,
         private client: Client,
@@ -83,7 +70,7 @@ export class UsersService {
                 ...request.userPayload
             }
         };
-        return this.httpClient.put(this.stripProtocol(request.uri), payload);
+        return this.httpClient.put(this.nifiCommon.stripProtocol(request.uri), payload);
     }
 
     updateUserGroup(request: UpdateUserGroupRequest): Observable<any> {
@@ -94,16 +81,16 @@ export class UsersService {
                 ...request.userGroupPayload
             }
         };
-        return this.httpClient.put(this.stripProtocol(request.uri), payload);
+        return this.httpClient.put(this.nifiCommon.stripProtocol(request.uri), payload);
     }
 
     deleteUser(user: UserEntity): Observable<any> {
         const revision: any = this.client.getRevision(user);
-        return this.httpClient.delete(this.stripProtocol(user.uri), { params: revision });
+        return this.httpClient.delete(this.nifiCommon.stripProtocol(user.uri), { params: revision });
     }
 
     deleteUserGroup(userGroup: UserGroupEntity): Observable<any> {
         const revision: any = this.client.getRevision(userGroup);
-        return this.httpClient.delete(this.stripProtocol(userGroup.uri), { params: revision });
+        return this.httpClient.delete(this.nifiCommon.stripProtocol(userGroup.uri), { params: revision });
     }
 }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/component-state.service.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/component-state.service.ts
index f87d93b7b4..1fef85eb30 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/component-state.service.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/component-state.service.ts
@@ -28,24 +28,11 @@ export class ComponentStateService {
         private nifiCommon: NiFiCommon
     ) {}
 
-    /**
-     * The NiFi model contain the url for each component. That URL is an absolute URL. Angular CSRF handling
-     * does not work on absolute URLs, so we need to strip off the proto for the request header to be added.
-     *
-     * https://stackoverflow.com/a/59586462
-     *
-     * @param url
-     * @private
-     */
-    private stripProtocol(url: string): string {
-        return this.nifiCommon.substringAfterFirst(url, ':');
-    }
-
     getComponentState(request: LoadComponentStateRequest): Observable<any> {
-        return this.httpClient.get(`${this.stripProtocol(request.componentUri)}/state`);
+        return this.httpClient.get(`${this.nifiCommon.stripProtocol(request.componentUri)}/state`);
     }
 
     clearComponentState(request: ClearComponentStateRequest): Observable<any> {
-        return this.httpClient.post(`${this.stripProtocol(request.componentUri)}/state/clear-requests`, {});
+        return this.httpClient.post(`${this.nifiCommon.stripProtocol(request.componentUri)}/state/clear-requests`, {});
     }
 }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/controller-service-state.service.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/controller-service-state.service.ts
index 679ad50685..7d446c5aa7 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/controller-service-state.service.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/controller-service-state.service.ts
@@ -36,25 +36,12 @@ export class ControllerServiceStateService {
         private client: Client
     ) {}
 
-    /**
-     * The NiFi model contain the url for each component. That URL is an absolute URL. Angular CSRF handling
-     * does not work on absolute URLs, so we need to strip off the proto for the request header to be added.
-     *
-     * https://stackoverflow.com/a/59586462
-     *
-     * @param url
-     * @private
-     */
-    private stripProtocol(url: string): string {
-        return this.nifiCommon.substringAfterFirst(url, ':');
-    }
-
     getControllerService(id: string): Observable<any> {
         return this.httpClient.get(`${ControllerServiceStateService.API}/controller-services/${id}`);
     }
 
     setEnable(controllerService: ControllerServiceEntity, enabled: boolean): Observable<any> {
-        return this.httpClient.put(`${this.stripProtocol(controllerService.uri)}/run-status`, {
+        return this.httpClient.put(`${this.nifiCommon.stripProtocol(controllerService.uri)}/run-status`, {
             revision: this.client.getRevision(controllerService),
             state: enabled ? 'ENABLED' : 'DISABLED',
             uiOnly: true
@@ -69,7 +56,7 @@ export class ControllerServiceStateService {
             true
         );
 
-        return this.httpClient.put(`${this.stripProtocol(controllerService.uri)}/references`, {
+        return this.httpClient.put(`${this.nifiCommon.stripProtocol(controllerService.uri)}/references`, {
             id: controllerService.id,
             state: enabled ? 'ENABLED' : 'DISABLED',
             referencingComponentRevisions: referencingComponentRevisions,
@@ -89,7 +76,7 @@ export class ControllerServiceStateService {
             false
         );
 
-        return this.httpClient.put(`${this.stripProtocol(controllerService.uri)}/references`, {
+        return this.httpClient.put(`${this.nifiCommon.stripProtocol(controllerService.uri)}/references`, {
             id: controllerService.id,
             state: running ? 'RUNNING' : 'STOPPED',
             referencingComponentRevisions: referencingComponentRevisions,
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/nifi-common.service.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/nifi-common.service.ts
index 556cc95ba7..85ec206b4f 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/nifi-common.service.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/nifi-common.service.ts
@@ -511,4 +511,17 @@ export class NiFiCommon {
     public getAllPolicyTypeListing(): SelectOption[] {
         return this.policyTypeListing;
     }
+
+    /**
+     * The NiFi model contain the url for each component. That URL is an absolute URL. Angular CSRF handling
+     * does not work on absolute URLs, so we need to strip off the proto for the request header to be added.
+     *
+     * https://stackoverflow.com/a/59586462
+     *
+     * @param url
+     * @private
+     */
+    public stripProtocol(url: string): string {
+        return this.substringAfterFirst(url, ':');
+    }
 }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/property-table-helper.service.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/property-table-helper.service.ts
new file mode 100644
index 0000000000..c359980e6f
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/property-table-helper.service.ts
@@ -0,0 +1,177 @@
+/*
+ * 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 { Injectable } from '@angular/core';
+import { MatDialog } from '@angular/material/dialog';
+import { catchError, map, NEVER, Observable, switchMap, take } from 'rxjs';
+import {
+    ControllerServiceCreator,
+    ControllerServiceEntity,
+    CreateControllerServiceRequest,
+    InlineServiceCreationRequest,
+    InlineServiceCreationResponse,
+    NewPropertyDialogRequest,
+    NewPropertyDialogResponse,
+    Property,
+    PropertyDescriptor,
+    PropertyDescriptorRetriever
+} from '../state/shared';
+import { NewPropertyDialog } from '../ui/common/new-property-dialog/new-property-dialog.component';
+import { CreateControllerService } from '../ui/common/controller-service/create-controller-service/create-controller-service.component';
+import { ManagementControllerServiceService } from '../pages/settings/service/management-controller-service.service';
+import { ExtensionTypesService } from './extension-types.service';
+import { Client } from './client.service';
+
+@Injectable({
+    providedIn: 'root'
+})
+export class PropertyTableHelperService {
+    constructor(
+        private dialog: MatDialog,
+        private extensionTypesService: ExtensionTypesService,
+        private client: Client
+    ) {}
+
+    /**
+     * Returns a function that can be used to pass into a PropertyTable to support creating a new property
+     * @param id                        id of the component to create the property for
+     * @param propertyDescriptorService the service to call to get property descriptors
+     */
+    createNewProperty(
+        id: string,
+        propertyDescriptorService: PropertyDescriptorRetriever
+    ): (existingProperties: string[], allowsSensitive: boolean) => Observable<Property> {
+        return (existingProperties: string[], allowsSensitive: boolean) => {
+            const dialogRequest: NewPropertyDialogRequest = { existingProperties, allowsSensitive };
+            const newPropertyDialogReference = this.dialog.open(NewPropertyDialog, {
+                data: dialogRequest,
+                panelClass: 'small-dialog'
+            });
+
+            return newPropertyDialogReference.componentInstance.newProperty.pipe(
+                take(1),
+                switchMap((dialogResponse: NewPropertyDialogResponse) => {
+                    return propertyDescriptorService
+                        .getPropertyDescriptor(id, dialogResponse.name, dialogResponse.sensitive)
+                        .pipe(
+                            take(1),
+                            map((response) => {
+                                newPropertyDialogReference.close();
+
+                                return {
+                                    property: dialogResponse.name,
+                                    value: null,
+                                    descriptor: response.propertyDescriptor
+                                };
+                            })
+                        );
+                })
+            );
+        };
+    }
+
+    /**
+     * Returns a function that can be used to pass into a PropertyTable to create a controller service, inline.
+     *
+     * @param id                        id of the component where the inline-create controller service was initiated
+     * @param controllerServiceCreator  service to use to create the controller service and lookup property descriptors
+     * @param propertyDescriptorService the service to call to get property descriptors
+     * @param afterServiceCreated       OPTIONAL - callback function to possibly dispatch an action after the controller service has been created
+     * @param processGroupId            OPTIONAL - process group id to create the
+     */
+    createNewService(
+        id: string,
+        controllerServiceCreator: ControllerServiceCreator,
+        propertyDescriptorService: PropertyDescriptorRetriever,
+        processGroupId?: string | null,
+        afterServiceCreated?: (createdResponse: ControllerServiceEntity) => void
+    ): (request: InlineServiceCreationRequest) => Observable<InlineServiceCreationResponse> {
+        return (request: InlineServiceCreationRequest) => {
+            const descriptor: PropertyDescriptor = request.descriptor;
+
+            // fetch all services that implement the requested service api
+            return this.extensionTypesService
+                .getImplementingControllerServiceTypes(
+                    // @ts-ignore
+                    descriptor.identifiesControllerService,
+                    descriptor.identifiesControllerServiceBundle
+                )
+                .pipe(
+                    take(1),
+                    switchMap((implementingTypesResponse) => {
+                        // show the create controller service dialog with the types that implemented the interface
+                        const createServiceDialogReference = this.dialog.open(CreateControllerService, {
+                            data: {
+                                controllerServiceTypes: implementingTypesResponse.controllerServiceTypes
+                            },
+                            panelClass: 'medium-dialog'
+                        });
+
+                        return createServiceDialogReference.componentInstance.createControllerService.pipe(
+                            take(1),
+                            switchMap((controllerServiceType) => {
+                                // typically this sequence would be implemented with ngrx actions, however we are
+                                // currently in an edit session, and we need to return both the value (new service id)
+                                // and updated property descriptor so the table renders correctly
+                                const payload: CreateControllerServiceRequest = {
+                                    revision: {
+                                        clientId: this.client.getClientId(),
+                                        version: 0
+                                    },
+                                    controllerServiceType: controllerServiceType.type,
+                                    controllerServiceBundle: controllerServiceType.bundle
+                                };
+
+                                if (processGroupId) {
+                                    payload.processGroupId = processGroupId;
+                                }
+
+                                return controllerServiceCreator.createControllerService(payload).pipe(
+                                    take(1),
+                                    switchMap((createResponse) => {
+                                        // if provided, call the callback function
+                                        if (afterServiceCreated) {
+                                            afterServiceCreated(createResponse);
+                                        }
+
+                                        // fetch an updated property descriptor
+                                        return propertyDescriptorService
+                                            .getPropertyDescriptor(id, descriptor.name, false)
+                                            .pipe(
+                                                take(1),
+                                                map((descriptorResponse) => {
+                                                    createServiceDialogReference.close();
+
+                                                    return {
+                                                        value: createResponse.id,
+                                                        descriptor: descriptorResponse.propertyDescriptor
+                                                    };
+                                                })
+                                            );
+                                    }),
+                                    catchError((error) => {
+                                        // TODO - show error
+                                        return NEVER;
+                                    })
+                                );
+                            })
+                        );
+                    })
+                );
+        };
+    }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/extension-types/extension-types.selectors.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/extension-types/extension-types.selectors.ts
index db29215cb9..24203c3827 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/extension-types/extension-types.selectors.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/extension-types/extension-types.selectors.ts
@@ -51,6 +51,11 @@ export const selectFlowAnalysisRuleTypes = createSelector(
     (state: ExtensionTypesState) => state.flowAnalysisRuleTypes
 );
 
+export const selectParameterProviderTypes = createSelector(
+    selectExtensionTypesState,
+    (state: ExtensionTypesState) => state.parameterProviderTypes
+);
+
 export const selectTypesToIdentifyComponentRestrictions = createSelector(
     selectExtensionTypesState,
     (state: ExtensionTypesState) => {
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/shared/index.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/shared/index.ts
index dcd872cace..aec81d5933 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/shared/index.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/shared/index.ts
@@ -112,7 +112,7 @@ export interface EditTenantResponse {
     userGroup?: any;
 }
 
-export interface CreateControllerServiceRequest {
+export interface CreateControllerServiceDialogRequest {
     controllerServiceTypes: DocumentedType[];
 }
 
@@ -306,6 +306,7 @@ export interface ParameterContextReferenceEntity {
     permissions: Permissions;
     id: string;
     component?: ParameterContextReference;
+    bulletins?: BulletinEntity[];
 }
 
 export interface ParameterContextReference {
@@ -508,6 +509,10 @@ export interface PropertyDescriptor {
     identifiesControllerServiceBundle?: Bundle;
 }
 
+export interface PropertyDescriptorEntity {
+    propertyDescriptor: PropertyDescriptor;
+}
+
 export interface Property {
     property: string;
     value: string | null;
@@ -522,3 +527,18 @@ export interface InlineServiceCreationResponse {
     value: string;
     descriptor: PropertyDescriptor;
 }
+
+export interface PropertyDescriptorRetriever {
+    getPropertyDescriptor(id: string, propertyName: string, sensitive: boolean): Observable<PropertyDescriptorEntity>;
+}
+
+export interface CreateControllerServiceRequest {
+    processGroupId?: string;
+    controllerServiceType: string;
+    controllerServiceBundle: Bundle;
+    revision: Revision;
+}
+
+export interface ControllerServiceCreator {
+    createControllerService(createControllerService: CreateControllerServiceRequest): Observable<any>;
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/controller-service-table/controller-service-table.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/controller-service-table/controller-service-table.component.ts
index fe33870ba1..afa33ccef7 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/controller-service-table/controller-service-table.component.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/controller-service-table/controller-service-table.component.ts
@@ -55,12 +55,13 @@ import { CurrentUser } from '../../../../state/current-user';
 export class ControllerServiceTable {
     @Input() initialSortColumn: 'name' | 'type' | 'bundle' | 'state' | 'scope' = 'name';
     @Input() initialSortDirection: 'asc' | 'desc' = 'asc';
+    activeSort: Sort = {
+        active: this.initialSortColumn,
+        direction: this.initialSortDirection
+    };
 
     @Input() set controllerServices(controllerServiceEntities: ControllerServiceEntity[]) {
-        this.dataSource.data = this.sortEntities(controllerServiceEntities, {
-            active: this.initialSortColumn,
-            direction: this.initialSortDirection
-        });
+        this.dataSource.data = this.sortEntities(controllerServiceEntities, this.activeSort);
     }
 
     @Input() selectedServiceId!: string;
@@ -277,6 +278,7 @@ export class ControllerServiceTable {
     }
 
     sortData(sort: Sort) {
+        this.activeSort = sort;
         this.dataSource.data = this.sortEntities(this.dataSource.data, sort);
     }
 
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/create-controller-service/create-controller-service.component.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/create-controller-service/create-controller-service.component.spec.ts
index 951a5d206d..b7ebcc1c17 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/create-controller-service/create-controller-service.component.spec.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/create-controller-service/create-controller-service.component.spec.ts
@@ -18,7 +18,7 @@
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 
 import { CreateControllerService } from './create-controller-service.component';
-import { CreateControllerServiceRequest, DocumentedType } from '../../../../state/shared';
+import { CreateControllerServiceDialogRequest, DocumentedType } from '../../../../state/shared';
 import { MAT_DIALOG_DATA } from '@angular/material/dialog';
 import { provideMockStore } from '@ngrx/store/testing';
 import { initialState } from '../../../../state/extension-types/extension-types.reducer';
@@ -28,7 +28,7 @@ describe('CreateControllerService', () => {
     let component: CreateControllerService;
     let fixture: ComponentFixture<CreateControllerService>;
 
-    const data: CreateControllerServiceRequest = {
+    const data: CreateControllerServiceDialogRequest = {
         controllerServiceTypes: [
             {
                 type: 'org.apache.nifi.services.azure.storage.ADLSCredentialsControllerService',
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/create-controller-service/create-controller-service.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/create-controller-service/create-controller-service.component.ts
index 9eef6781c5..56d27ed3db 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/create-controller-service/create-controller-service.component.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/create-controller-service/create-controller-service.component.ts
@@ -17,7 +17,7 @@
 
 import { Component, EventEmitter, Inject, Input, Output } from '@angular/core';
 import { MAT_DIALOG_DATA } from '@angular/material/dialog';
-import { CreateControllerServiceRequest, DocumentedType } from '../../../../state/shared';
+import { CreateControllerServiceDialogRequest, DocumentedType } from '../../../../state/shared';
 import { ExtensionCreation } from '../../extension-creation/extension-creation.component';
 import { Observable } from 'rxjs';
 import { AsyncPipe } from '@angular/common';
@@ -35,7 +35,7 @@ export class CreateControllerService {
 
     controllerServiceTypes: DocumentedType[];
 
-    constructor(@Inject(MAT_DIALOG_DATA) private dialogRequest: CreateControllerServiceRequest) {
+    constructor(@Inject(MAT_DIALOG_DATA) private dialogRequest: CreateControllerServiceDialogRequest) {
         this.controllerServiceTypes = dialogRequest.controllerServiceTypes;
     }
 
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/extension-creation/extension-creation.component.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/extension-creation/extension-creation.component.ts
index 82d5872b04..ab439d166a 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/extension-creation/extension-creation.component.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/extension-creation/extension-creation.component.ts
@@ -49,16 +49,17 @@ export class ExtensionCreation {
             this.selectedType = documentedTypes[0];
         }
 
-        this.dataSource.data = this.sortEntities(documentedTypes, {
-            active: this.initialSortColumn,
-            direction: this.initialSortDirection
-        });
+        this.dataSource.data = this.sortEntities(documentedTypes, this.activeSort);
     }
 
     @Input() componentType!: string;
     @Input() saving!: boolean;
     @Input() initialSortColumn: 'type' | 'version' | 'tags' = 'type';
     @Input() initialSortDirection: 'asc' | 'desc' = 'asc';
+    activeSort: Sort = {
+        active: this.initialSortColumn,
+        direction: this.initialSortDirection
+    };
 
     @Output() extensionTypeSelected: EventEmitter<DocumentedType> = new EventEmitter<DocumentedType>();
 
@@ -151,6 +152,7 @@ export class ExtensionCreation {
     }
 
     sortData(sort: Sort) {
+        this.activeSort = sort;
         this.dataSource.data = this.sortEntities(this.dataSource.data, sort);
     }