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 f9c1c3f042 [NIFI-12778] manage remote ports (#8433)
f9c1c3f042 is described below

commit f9c1c3f04291fc7de8add93ce4d485a38fed6b9d
Author: Scott Aslan <scottyas...@users.noreply.github.com>
AuthorDate: Thu Feb 29 16:09:32 2024 -0500

    [NIFI-12778] manage remote ports (#8433)
    
    * [NIFI-12778] manage remote ports
    
    * update last refreshed timestamp and loadedTimestamp
    
    * address review feedback
    
    * final touches
    
    * address addition review comments
    
    * formatDuration check isDurationBlank
    
    This closes #8433
---
 .../feature/flow-designer-routing.module.ts        |  13 +
 .../service/canvas-context-menu.service.ts         |  15 +-
 .../service/manage-remote-port.service.ts          |  72 +++++
 .../pages/flow-designer/state/flow/flow.actions.ts |   8 +-
 .../pages/flow-designer/state/flow/flow.effects.ts |  12 +
 .../app/pages/flow-designer/state/flow/index.ts    |   8 +
 .../state/manage-remote-ports/index.ts             | 100 ++++++
 .../manage-remote-ports.actions.ts                 |  77 +++++
 .../manage-remote-ports.effects.ts                 | 277 ++++++++++++++++
 .../manage-remote-ports.reducer.ts                 |  75 +++++
 .../manage-remote-ports.selectors.ts               |  55 ++++
 .../controller-services.component.spec.ts          |   3 +-
 .../_manage-remote-ports.component-theme.scss      |  45 +++
 .../edit-remote-port.component.html                |  72 +++++
 .../edit-remote-port.component.scss                |  27 ++
 .../edit-remote-port.component.spec.ts             |  63 ++++
 .../edit-remote-port/edit-remote-port.component.ts | 107 ++++++
 .../manage-remote-ports-routing.module.ts}         |  25 +-
 .../manage-remote-ports.component.html             | 240 ++++++++++++++
 .../manage-remote-ports.component.scss             |  38 +++
 .../manage-remote-ports.component.spec.ts}         |  17 +-
 .../manage-remote-ports.component.ts               | 358 +++++++++++++++++++++
 .../manage-remote-ports.module.ts                  |  52 +++
 .../nifi/src/app/service/nifi-common.service.ts    |  27 ++
 .../src/main/nifi/src/assets/themes/nifi.scss      |   2 +-
 .../src/main/nifi/src/assets/themes/purple.scss    |   2 +-
 .../src/main/nifi/src/styles.scss                  |   7 +
 27 files changed, 1763 insertions(+), 34 deletions(-)

diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/feature/flow-designer-routing.module.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/feature/flow-designer-routing.module.ts
index b19a2fa793..5e1c862606 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/feature/flow-designer-routing.module.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/feature/flow-designer-routing.module.ts
@@ -39,6 +39,19 @@ const routes: Routes = [
             }
         ]
     },
+    {
+        path: 'remote-process-group/:rpgId',
+        component: FlowDesigner,
+        children: [
+            {
+                path: 'manage-remote-ports',
+                loadChildren: () =>
+                    
import('../ui/manage-remote-ports/manage-remote-ports.module').then(
+                        (m) => m.ManageRemotePortsModule
+                    )
+            }
+        ]
+    },
     { path: '', component: RootGroupRedirector, canActivate: [rootGroupGuard] }
 ];
 
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/canvas-context-menu.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/canvas-context-menu.service.ts
index 01e3f8e440..d7e091bd0c 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-context-menu.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/canvas-context-menu.service.ts
@@ -32,6 +32,7 @@ import {
     navigateToEditComponent,
     navigateToEditCurrentProcessGroup,
     navigateToManageComponentPolicies,
+    navigateToManageRemotePorts,
     navigateToProvenanceForComponent,
     navigateToQueueListing,
     navigateToViewStatusHistoryForComponent,
@@ -771,12 +772,20 @@ export class CanvasContextMenu implements 
ContextMenuDefinitionProvider {
             },
             {
                 condition: (selection: any) => {
-                    return this.canvasUtils.isRemoteProcessGroup(selection);
+                    return this.canvasUtils.canRead(selection) && 
this.canvasUtils.isRemoteProcessGroup(selection);
                 },
                 clazz: 'fa fa-cloud',
                 text: 'Manage remote ports',
-                action: () => {
-                    // TODO - remotePorts
+                action: (selection: any) => {
+                    const selectionData = selection.datum();
+
+                    this.store.dispatch(
+                        navigateToManageRemotePorts({
+                            request: {
+                                id: selectionData.id
+                            }
+                        })
+                    );
                 }
             },
             {
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/manage-remote-port.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/manage-remote-port.service.ts
new file mode 100644
index 0000000000..f923cdb8b2
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/manage-remote-port.service.ts
@@ -0,0 +1,72 @@
+/*
+ * 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 { Observable } from 'rxjs';
+import { HttpClient } from '@angular/common/http';
+import { NiFiCommon } from '../../../service/nifi-common.service';
+import { ConfigureRemotePortRequest, ToggleRemotePortTransmissionRequest } 
from '../state/manage-remote-ports';
+import { Client } from '../../../service/client.service';
+import { ComponentType } from '../../../state/shared';
+
+@Injectable({ providedIn: 'root' })
+export class ManageRemotePortService {
+    private static readonly API: string = '../nifi-api';
+
+    constructor(
+        private httpClient: HttpClient,
+        private client: Client,
+        private nifiCommon: NiFiCommon
+    ) {}
+
+    getRemotePorts(rpgId: string): Observable<any> {
+        return 
this.httpClient.get(`${ManageRemotePortService.API}/remote-process-groups/${rpgId}`);
+    }
+
+    updateRemotePort(configureRemotePortRequest: ConfigureRemotePortRequest): 
Observable<any> {
+        const type =
+            configureRemotePortRequest.payload.type === 
ComponentType.InputPort ? 'input-ports' : 'output-ports';
+        return this.httpClient.put(
+            
`${this.nifiCommon.stripProtocol(configureRemotePortRequest.uri)}/${type}/${
+                configureRemotePortRequest.payload.remoteProcessGroupPort.id
+            }`,
+            {
+                revision: configureRemotePortRequest.payload.revision,
+                remoteProcessGroupPort: 
configureRemotePortRequest.payload.remoteProcessGroupPort,
+                disconnectedNodeAcknowledged: 
configureRemotePortRequest.payload.disconnectedNodeAcknowledged
+            }
+        );
+    }
+
+    updateRemotePortTransmission(
+        toggleRemotePortTransmissionRequest: 
ToggleRemotePortTransmissionRequest
+    ): Observable<any> {
+        const payload: any = {
+            revision: 
this.client.getRevision(toggleRemotePortTransmissionRequest.rpg),
+            disconnectedNodeAcknowledged: 
toggleRemotePortTransmissionRequest.disconnectedNodeAcknowledged,
+            state: toggleRemotePortTransmissionRequest.state
+        };
+
+        const type =
+            toggleRemotePortTransmissionRequest.type === 
ComponentType.InputPort ? 'input-ports' : 'output-ports';
+
+        return this.httpClient.put(
+            
`${ManageRemotePortService.API}/remote-process-groups/${toggleRemotePortTransmissionRequest.rpg.id}/${type}/${toggleRemotePortTransmissionRequest.portId}/run-status`,
+            payload
+        );
+    }
+}
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.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/flow/flow.actions.ts
index 7dfb10a634..f2276dca01 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.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/flow/flow.actions.ts
@@ -76,7 +76,8 @@ import {
     ImportFromRegistryDialogRequest,
     ImportFromRegistryRequest,
     GoToRemoteProcessGroupRequest,
-    RefreshRemoteProcessGroupRequest
+    RefreshRemoteProcessGroupRequest,
+    RpgManageRemotePortsRequest
 } from './index';
 import { StatusHistoryRequest } from '../../../../state/status-history';
 
@@ -400,6 +401,11 @@ export const openEditRemoteProcessGroupDialog = 
createAction(
     props<{ request: EditComponentDialogRequest }>()
 );
 
+export const navigateToManageRemotePorts = createAction(
+    `${CANVAS_PREFIX} Open Remote Process Group Manage Remote Ports`,
+    props<{ request: RpgManageRemotePortsRequest }>()
+);
+
 export const updateComponent = createAction(
     `${CANVAS_PREFIX} Update Component`,
     props<{ request: UpdateComponentRequest }>()
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 cd0015d8df..8c0716c826 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
@@ -1319,6 +1319,18 @@ export class FlowEffects {
         { dispatch: false }
     );
 
+    navigateToManageRemotePorts$ = createEffect(
+        () =>
+            this.actions$.pipe(
+                ofType(FlowActions.navigateToManageRemotePorts),
+                map((action) => action.request),
+                tap((request) => {
+                    this.router.navigate(['/remote-process-group', request.id, 
'manage-remote-ports']);
+                })
+            ),
+        { dispatch: false }
+    );
+
     openEditRemoteProcessGroupDialog$ = createEffect(
         () =>
             this.actions$.pipe(
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/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/flow/index.ts
index 384451a8bc..2d929ced3b 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/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/flow/index.ts
@@ -270,6 +270,14 @@ export interface EditComponentDialogRequest {
     entity: any;
 }
 
+export interface EditRemotePortDialogRequest extends 
EditComponentDialogRequest {
+    rpg?: any;
+}
+
+export interface RpgManageRemotePortsRequest {
+    id: string;
+}
+
 export interface NavigateToControllerServicesRequest {
     id: string;
 }
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/manage-remote-ports/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/manage-remote-ports/index.ts
new file mode 100644
index 0000000000..4f547e56a9
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/manage-remote-ports/index.ts
@@ -0,0 +1,100 @@
+/*
+ * 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 { ComponentType } from '../../../../state/shared';
+
+export const remotePortsFeatureKey = 'remotePortListing';
+
+export interface PortSummary {
+    batchSettings: {
+        count?: number;
+        size?: string;
+        duration?: string;
+    };
+    comments: string;
+    concurrentlySchedulableTaskCount: number;
+    connected: boolean;
+    exists: boolean;
+    groupId: string;
+    id: string;
+    name: string;
+    targetId: string;
+    targetRunning: boolean;
+    transmitting: boolean;
+    useCompression: boolean;
+    versionedComponentId: string;
+    type?: ComponentType.InputPort | ComponentType.OutputPort;
+}
+
+export interface EditRemotePortDialogRequest {
+    id: string;
+    port: PortSummary;
+    rpg: any;
+}
+
+export interface ToggleRemotePortTransmissionRequest {
+    rpg: any;
+    portId: string;
+    disconnectedNodeAcknowledged: boolean;
+    state: string;
+    type: ComponentType.InputPort | ComponentType.OutputPort | undefined;
+}
+
+export interface StartRemotePortTransmissionRequest {
+    rpg: any;
+    port: PortSummary;
+}
+
+export interface StopRemotePortTransmissionRequest {
+    rpg: any;
+    port: PortSummary;
+}
+
+export interface LoadRemotePortsRequest {
+    rpgId: string;
+}
+
+export interface LoadRemotePortsResponse {
+    ports: PortSummary[];
+    rpg: any;
+    loadedTimestamp: string;
+}
+
+export interface ConfigureRemotePortRequest {
+    id: string;
+    uri: string;
+    payload: any;
+    postUpdateNavigation?: string[];
+}
+
+export interface ConfigureRemotePortSuccess {
+    id: string;
+    port: any;
+}
+
+export interface SelectRemotePortRequest {
+    rpgId: string;
+    id: string;
+}
+
+export interface RemotePortsState {
+    ports: PortSummary[];
+    saving: boolean;
+    rpg: any;
+    loadedTimestamp: string;
+    status: 'pending' | 'loading' | 'success';
+}
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/manage-remote-ports/manage-remote-ports.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/manage-remote-ports/manage-remote-ports.actions.ts
new file mode 100644
index 0000000000..a084dc5b2d
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/manage-remote-ports/manage-remote-ports.actions.ts
@@ -0,0 +1,77 @@
+/*
+ * 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 {
+    ConfigureRemotePortRequest,
+    ConfigureRemotePortSuccess,
+    EditRemotePortDialogRequest,
+    LoadRemotePortsRequest,
+    LoadRemotePortsResponse,
+    SelectRemotePortRequest,
+    StartRemotePortTransmissionRequest,
+    StopRemotePortTransmissionRequest
+} from './index';
+
+export const resetRemotePortsState = createAction('[Manage Remote Ports] Reset 
Remote Ports State');
+
+export const loadRemotePorts = createAction(
+    '[Manage Remote Ports] Load Remote Ports',
+    props<{ request: LoadRemotePortsRequest }>()
+);
+
+export const loadRemotePortsSuccess = createAction(
+    '[Manage Remote Ports] Load Remote Ports Success',
+    props<{ response: LoadRemotePortsResponse }>()
+);
+
+export const remotePortsBannerApiError = createAction(
+    '[Manage Remote Ports] Remote Ports Banner Api Error',
+    props<{ error: string }>()
+);
+
+export const navigateToEditPort = createAction('[Manage Remote Ports] Navigate 
To Edit Port', props<{ id: string }>());
+
+export const openConfigureRemotePortDialog = createAction(
+    '[Manage Remote Ports] Open Configure Port Dialog',
+    props<{ request: EditRemotePortDialogRequest }>()
+);
+
+export const configureRemotePort = createAction(
+    '[Manage Remote Ports] Configure Port',
+    props<{ request: ConfigureRemotePortRequest }>()
+);
+
+export const configureRemotePortSuccess = createAction(
+    '[Manage Remote Ports] Configure Port Success',
+    props<{ response: ConfigureRemotePortSuccess }>()
+);
+
+export const startRemotePortTransmission = createAction(
+    '[Manage Remote Ports] Start Port Transmission',
+    props<{ request: StartRemotePortTransmissionRequest }>()
+);
+
+export const stopRemotePortTransmission = createAction(
+    '[Manage Remote Ports] Stop Port Transmission',
+    props<{ request: StopRemotePortTransmissionRequest }>()
+);
+
+export const selectRemotePort = createAction(
+    '[Manage Remote Ports] Select Port Summary',
+    props<{ request: SelectRemotePortRequest }>()
+);
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/manage-remote-ports/manage-remote-ports.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/manage-remote-ports/manage-remote-ports.effects.ts
new file mode 100644
index 0000000000..7866350688
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/manage-remote-ports/manage-remote-ports.effects.ts
@@ -0,0 +1,277 @@
+/*
+ * 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 * as ManageRemotePortsActions from './manage-remote-ports.actions';
+import { catchError, from, map, of, switchMap, tap } from 'rxjs';
+import { MatDialog } from '@angular/material/dialog';
+import { Store } from '@ngrx/store';
+import { NiFiState } from '../../../../state';
+import { Router } from '@angular/router';
+import { selectRpg, selectRpgIdFromRoute, selectStatus } from 
'./manage-remote-ports.selectors';
+import * as ErrorActions from '../../../../state/error/error.actions';
+import { ErrorHelper } from '../../../../service/error-helper.service';
+import { HttpErrorResponse } from '@angular/common/http';
+import { ManageRemotePortService } from 
'../../service/manage-remote-port.service';
+import { PortSummary } from './index';
+import { EditRemotePortComponent } from 
'../../ui/manage-remote-ports/edit-remote-port/edit-remote-port.component';
+import { EditRemotePortDialogRequest } from '../flow';
+import { ComponentType, isDefinedAndNotNull } from '../../../../state/shared';
+import { selectTimeOffset } from 
'../../../../state/flow-configuration/flow-configuration.selectors';
+import { selectAbout } from '../../../../state/about/about.selectors';
+
+@Injectable()
+export class ManageRemotePortsEffects {
+    constructor(
+        private actions$: Actions,
+        private store: Store<NiFiState>,
+        private manageRemotePortService: ManageRemotePortService,
+        private errorHelper: ErrorHelper,
+        private dialog: MatDialog,
+        private router: Router
+    ) {}
+
+    loadRemotePorts$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(ManageRemotePortsActions.loadRemotePorts),
+            map((action) => action.request),
+            concatLatestFrom(() => [
+                this.store.select(selectStatus),
+                
this.store.select(selectTimeOffset).pipe(isDefinedAndNotNull()),
+                this.store.select(selectAbout).pipe(isDefinedAndNotNull())
+            ]),
+            switchMap(([request, status, timeOffset, about]) => {
+                return 
this.manageRemotePortService.getRemotePorts(request.rpgId).pipe(
+                    map((response) => {
+                        // get the current user time to properly convert the 
server time
+                        const now: Date = new Date();
+
+                        // convert the user offset to millis
+                        const userTimeOffset: number = now.getTimezoneOffset() 
* 60 * 1000;
+
+                        // create the proper date by adjusting by the offsets
+                        const date: Date = new Date(Date.now() + 
userTimeOffset + timeOffset);
+
+                        const ports: PortSummary[] = [];
+
+                        
response.component.contents.inputPorts.forEach((inputPort: PortSummary) => {
+                            const port = {
+                                ...inputPort,
+                                type: ComponentType.InputPort
+                            } as PortSummary;
+
+                            ports.push(port);
+                        });
+
+                        
response.component.contents.outputPorts.forEach((outputPort: PortSummary) => {
+                            const port = {
+                                ...outputPort,
+                                type: ComponentType.OutputPort
+                            } as PortSummary;
+
+                            ports.push(port);
+                        });
+
+                        return 
ManageRemotePortsActions.loadRemotePortsSuccess({
+                            response: {
+                                ports,
+                                rpg: response,
+                                loadedTimestamp: 
`${date.getHours()}:${date.getMinutes()}:${date.getSeconds()} ${
+                                    about.timezone
+                                }`
+                            }
+                        });
+                    }),
+                    catchError((errorResponse: HttpErrorResponse) =>
+                        of(this.errorHelper.handleLoadingError(status, 
errorResponse))
+                    )
+                );
+            })
+        )
+    );
+
+    navigateToEditPort$ = createEffect(
+        () =>
+            this.actions$.pipe(
+                ofType(ManageRemotePortsActions.navigateToEditPort),
+                map((action) => action.id),
+                concatLatestFrom(() => 
this.store.select(selectRpgIdFromRoute)),
+                tap(([id, rpgId]) => {
+                    this.router.navigate(['/remote-process-group', rpgId, 
'manage-remote-ports', id, 'edit']);
+                })
+            ),
+        { dispatch: false }
+    );
+
+    remotePortsBannerApiError$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(ManageRemotePortsActions.remotePortsBannerApiError),
+            map((action) => action.error),
+            switchMap((error) => of(ErrorActions.addBannerError({ error })))
+        )
+    );
+
+    startRemotePortTransmission$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(ManageRemotePortsActions.startRemotePortTransmission),
+            map((action) => action.request),
+            switchMap((request) => {
+                return this.manageRemotePortService
+                    .updateRemotePortTransmission({
+                        portId: request.port.id,
+                        rpg: request.rpg,
+                        disconnectedNodeAcknowledged: false,
+                        type: request.port.type,
+                        state: 'TRANSMITTING'
+                    })
+                    .pipe(
+                        map((response) => {
+                            return ManageRemotePortsActions.loadRemotePorts({
+                                request: {
+                                    rpgId: 
response.remoteProcessGroupPort.groupId
+                                }
+                            });
+                        }),
+                        catchError((errorResponse: HttpErrorResponse) =>
+                            of(ErrorActions.snackBarError({ error: 
errorResponse.error }))
+                        )
+                    );
+            })
+        )
+    );
+
+    stopRemotePortTransmission$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(ManageRemotePortsActions.stopRemotePortTransmission),
+            map((action) => action.request),
+            switchMap((request) => {
+                return this.manageRemotePortService
+                    .updateRemotePortTransmission({
+                        portId: request.port.id,
+                        rpg: request.rpg,
+                        disconnectedNodeAcknowledged: false,
+                        type: request.port.type,
+                        state: 'STOPPED'
+                    })
+                    .pipe(
+                        map((response) => {
+                            return ManageRemotePortsActions.loadRemotePorts({
+                                request: {
+                                    rpgId: 
response.remoteProcessGroupPort.groupId
+                                }
+                            });
+                        }),
+                        catchError((errorResponse: HttpErrorResponse) =>
+                            of(ErrorActions.snackBarError({ error: 
errorResponse.error }))
+                        )
+                    );
+            })
+        )
+    );
+
+    selectRemotePort$ = createEffect(
+        () =>
+            this.actions$.pipe(
+                ofType(ManageRemotePortsActions.selectRemotePort),
+                map((action) => action.request),
+                tap((request) => {
+                    this.router.navigate(['/remote-process-group', 
request.rpgId, 'manage-remote-ports', request.id]);
+                })
+            ),
+        { dispatch: false }
+    );
+
+    openConfigureRemotePortDialog$ = createEffect(
+        () =>
+            this.actions$.pipe(
+                ofType(ManageRemotePortsActions.openConfigureRemotePortDialog),
+                map((action) => action.request),
+                concatLatestFrom(() => 
[this.store.select(selectRpg).pipe(isDefinedAndNotNull())]),
+                tap(([request, rpg]) => {
+                    const portId: string = request.id;
+
+                    const editDialogReference = 
this.dialog.open(EditRemotePortComponent, {
+                        data: {
+                            type: request.port.type,
+                            entity: request.port,
+                            rpg
+                        } as EditRemotePortDialogRequest,
+                        id: portId
+                    });
+
+                    editDialogReference.afterClosed().subscribe((response) => {
+                        this.store.dispatch(ErrorActions.clearBannerErrors());
+                        if (response != 'ROUTED') {
+                            this.store.dispatch(
+                                ManageRemotePortsActions.selectRemotePort({
+                                    request: {
+                                        rpgId: rpg.id,
+                                        id: portId
+                                    }
+                                })
+                            );
+                        }
+                    });
+                })
+            ),
+        { dispatch: false }
+    );
+
+    configureRemotePort$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(ManageRemotePortsActions.configureRemotePort),
+            map((action) => action.request),
+            switchMap((request) =>
+                
from(this.manageRemotePortService.updateRemotePort(request)).pipe(
+                    map((response) =>
+                        ManageRemotePortsActions.configureRemotePortSuccess({
+                            response: {
+                                id: request.id,
+                                port: response.remoteProcessGroupPort
+                            }
+                        })
+                    ),
+                    catchError((errorResponse: HttpErrorResponse) => {
+                        if 
(this.errorHelper.showErrorInContext(errorResponse.status)) {
+                            return of(
+                                
ManageRemotePortsActions.remotePortsBannerApiError({
+                                    error: errorResponse.error
+                                })
+                            );
+                        } else {
+                            
this.dialog.getDialogById(request.id)?.close('ROUTED');
+                            return 
of(this.errorHelper.fullScreenError(errorResponse));
+                        }
+                    })
+                )
+            )
+        )
+    );
+
+    configureRemotePortSuccess$ = createEffect(
+        () =>
+            this.actions$.pipe(
+                ofType(ManageRemotePortsActions.configureRemotePortSuccess),
+                map((action) => action.response),
+                tap(() => {
+                    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/flow-designer/state/manage-remote-ports/manage-remote-ports.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/manage-remote-ports/manage-remote-ports.reducer.ts
new file mode 100644
index 0000000000..98b5f244d6
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/manage-remote-ports/manage-remote-ports.reducer.ts
@@ -0,0 +1,75 @@
+/*
+ * 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 { createReducer, on } from '@ngrx/store';
+import {
+    configureRemotePort,
+    configureRemotePortSuccess,
+    loadRemotePorts,
+    loadRemotePortsSuccess,
+    remotePortsBannerApiError,
+    resetRemotePortsState
+} from './manage-remote-ports.actions';
+import { produce } from 'immer';
+import { RemotePortsState } from './index';
+
+export const initialState: RemotePortsState = {
+    ports: [],
+    saving: false,
+    loadedTimestamp: '',
+    rpg: null,
+    status: 'pending'
+};
+
+export const manageRemotePortsReducer = createReducer(
+    initialState,
+    on(resetRemotePortsState, () => ({
+        ...initialState
+    })),
+    on(loadRemotePorts, (state) => ({
+        ...state,
+        status: 'loading' as const
+    })),
+    on(loadRemotePortsSuccess, (state, { response }) => ({
+        ...state,
+        ports: response.ports,
+        loadedTimestamp: response.loadedTimestamp,
+        rpg: response.rpg,
+        status: 'success' as const
+    })),
+    on(remotePortsBannerApiError, (state) => ({
+        ...state,
+        saving: false
+    })),
+    on(configureRemotePort, (state) => ({
+        ...state,
+        saving: true
+    })),
+    on(configureRemotePortSuccess, (state, { response }) => {
+        return produce(state, (draftState) => {
+            const componentIndex: number = draftState.ports.findIndex((f: any) 
=> response.id === f.id);
+            const port = {
+                ...response.port,
+                type: state.ports[componentIndex].type
+            };
+            if (componentIndex > -1) {
+                draftState.ports[componentIndex] = port;
+            }
+            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/flow-designer/state/manage-remote-ports/manage-remote-ports.selectors.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/manage-remote-ports/manage-remote-ports.selectors.ts
new file mode 100644
index 0000000000..045f6c70c4
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/manage-remote-ports/manage-remote-ports.selectors.ts
@@ -0,0 +1,55 @@
+/*
+ * 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 { createFeatureSelector, createSelector } from '@ngrx/store';
+import { selectCurrentRoute } from '../../../../state/router/router.selectors';
+import { remotePortsFeatureKey, RemotePortsState } from './index';
+
+export const selectRemotePortsState = 
createFeatureSelector<RemotePortsState>(remotePortsFeatureKey);
+
+export const selectSaving = createSelector(selectRemotePortsState, (state: 
RemotePortsState) => state.saving);
+
+export const selectStatus = createSelector(selectRemotePortsState, (state: 
RemotePortsState) => state.status);
+
+export const selectRpgIdFromRoute = createSelector(selectCurrentRoute, (route) 
=> {
+    if (route) {
+        // always select the rpg id from the route
+        return route.params.rpgId;
+    }
+    return null;
+});
+
+export const selectPortIdFromRoute = createSelector(selectCurrentRoute, 
(route) => {
+    if (route) {
+        // always select the port id from the route
+        return route.params.id;
+    }
+    return null;
+});
+
+export const selectSingleEditedPort = createSelector(selectCurrentRoute, 
(route) => {
+    if (route?.routeConfig?.path == 'edit') {
+        return route.params.id;
+    }
+    return null;
+});
+
+export const selectPorts = createSelector(selectRemotePortsState, (state: 
RemotePortsState) => state.ports);
+export const selectRpg = createSelector(selectRemotePortsState, (state: 
RemotePortsState) => state.rpg);
+
+export const selectPort = (id: string) =>
+    createSelector(selectPorts, (port: any[]) => port.find((port) => id == 
port.id));
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 fccb8a6661..c384ba515f 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,7 +22,6 @@ 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', () => {
@@ -39,7 +38,7 @@ describe('ControllerServices', () => {
     beforeEach(() => {
         TestBed.configureTestingModule({
             declarations: [ControllerServices],
-            imports: [RouterTestingModule, MockNavigation, 
ControllerServicesModule, HttpClientTestingModule],
+            imports: [RouterTestingModule, MockNavigation, 
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/flow-designer/ui/manage-remote-ports/_manage-remote-ports.component-theme.scss
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/manage-remote-ports/_manage-remote-ports.component-theme.scss
new file mode 100644
index 0000000000..30dd724e16
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/manage-remote-ports/_manage-remote-ports.component-theme.scss
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+@use 'sass:map';
+@use '@angular/material' as mat;
+
+@mixin nifi-theme($theme, $canvas-theme) {
+    // Get the color config from the theme.
+    $color-config: mat.get-color-config($theme);
+    $canvas-color-config: mat.get-color-config($canvas-theme);
+
+    // Get the color palette from the color-config.
+    $primary-palette: map.get($color-config, 'primary');
+    $canvas-accent-palette: map.get($canvas-color-config, 'accent');
+
+    // Get hues from palette
+    $primary-palette-500: mat.get-color-from-palette($primary-palette, 500);
+    $canvas-accent-palette-A200: 
mat.get-color-from-palette($canvas-accent-palette, 'A200');
+
+    .manage-remote-ports-header {
+        color: $primary-palette-500;
+    }
+
+    .manage-remote-ports-table {
+        .listing-table {
+            .fa.fa-warning {
+                color: $canvas-accent-palette-A200;
+            }
+        }
+    }
+}
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/manage-remote-ports/edit-remote-port/edit-remote-port.component.html
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/manage-remote-ports/edit-remote-port/edit-remote-port.component.html
new file mode 100644
index 0000000000..d2cf80e4d6
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/manage-remote-ports/edit-remote-port/edit-remote-port.component.html
@@ -0,0 +1,72 @@
+<!--
+  ~ 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 Remote {{ portTypeLabel }}</h2>
+<form class="edit-remote-port-form" [formGroup]="editPortForm">
+    <error-banner></error-banner>
+    <mat-dialog-content>
+        <div>
+            <div class="flex flex-col mb-5">
+                <div>Name</div>
+                <div class="overflow-ellipsis overflow-hidden 
whitespace-nowrap value" [title]="request.entity.name">
+                    {{ request.entity.name }}
+                </div>
+            </div>
+        </div>
+        <div>
+            <mat-form-field>
+                <mat-label>Concurrent Tasks</mat-label>
+                <input matInput formControlName="concurrentTasks" type="text" 
/>
+            </mat-form-field>
+        </div>
+        <div class="mb-3.5">
+            <mat-label>Compressed</mat-label>
+            <mat-checkbox color="primary" 
formControlName="compressed"></mat-checkbox>
+        </div>
+        <div>
+            <mat-form-field>
+                <mat-label>Batch Count</mat-label>
+                <input matInput formControlName="count" type="text" />
+            </mat-form-field>
+        </div>
+        <div>
+            <mat-form-field>
+                <mat-label>Batch Size</mat-label>
+                <input matInput formControlName="size" type="text" />
+            </mat-form-field>
+        </div>
+        <div>
+            <mat-form-field>
+                <mat-label>Batch Duration</mat-label>
+                <input matInput formControlName="duration" type="text" />
+            </mat-form-field>
+        </div>
+    </mat-dialog-content>
+    @if ({ value: (saving$ | async)! }; as saving) {
+        <mat-dialog-actions align="end">
+            <button color="primary" mat-stroked-button 
mat-dialog-close>Cancel</button>
+            <button
+                [disabled]="!editPortForm.dirty || editPortForm.invalid || 
saving.value"
+                type="button"
+                color="primary"
+                (click)="editRemotePort()"
+                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/flow-designer/ui/manage-remote-ports/edit-remote-port/edit-remote-port.component.scss
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/manage-remote-ports/edit-remote-port/edit-remote-port.component.scss
new file mode 100644
index 0000000000..b4a795bb96
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/manage-remote-ports/edit-remote-port/edit-remote-port.component.scss
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+@use '@angular/material' as mat;
+
+.edit-remote-port-form {
+    @include mat.button-density(-1);
+
+    width: 500px;
+    .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/flow-designer/ui/manage-remote-ports/edit-remote-port/edit-remote-port.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/manage-remote-ports/edit-remote-port/edit-remote-port.component.spec.ts
new file mode 100644
index 0000000000..078fb0e334
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/manage-remote-ports/edit-remote-port/edit-remote-port.component.spec.ts
@@ -0,0 +1,63 @@
+/*
+ * 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 { EditRemotePortComponent } from './edit-remote-port.component';
+import { MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { provideMockStore } from '@ngrx/store/testing';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { EditComponentDialogRequest } from '../../../state/flow';
+import { ComponentType } from '../../../../../state/shared';
+import { initialState } from 
'../../../state/manage-remote-ports/manage-remote-ports.reducer';
+
+describe('EditRemotePortComponent', () => {
+    let component: EditRemotePortComponent;
+    let fixture: ComponentFixture<EditRemotePortComponent>;
+
+    const data: EditComponentDialogRequest = {
+        type: ComponentType.OutputPort,
+        uri: 
'https://localhost:4200/nifi-api/remote-process-groups/95a4b210-018b-1000-772a-5a9ebfa03287',
+        entity: {
+            id: 'a687e30e-018b-1000-f904-849a9f8e6bdb',
+            groupId: '95a4b210-018b-1000-772a-5a9ebfa03287',
+            name: 'out',
+            transmitting: false,
+            concurrentlySchedulableTaskCount: 1,
+            useCompression: true,
+            batchSettings: {
+                count: '',
+                size: '',
+                duration: ''
+            }
+        }
+    };
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            imports: [EditRemotePortComponent, NoopAnimationsModule],
+            providers: [{ provide: MAT_DIALOG_DATA, useValue: data }, 
provideMockStore({ initialState })]
+        });
+        fixture = TestBed.createComponent(EditRemotePortComponent);
+        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/flow-designer/ui/manage-remote-ports/edit-remote-port/edit-remote-port.component.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/manage-remote-ports/edit-remote-port/edit-remote-port.component.ts
new file mode 100644
index 0000000000..d07f664d0d
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/manage-remote-ports/edit-remote-port/edit-remote-port.component.ts
@@ -0,0 +1,107 @@
+/*
+ * 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, Inject } from '@angular/core';
+import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
+import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators 
} from '@angular/forms';
+import { Store } from '@ngrx/store';
+import { MatInputModule } from '@angular/material/input';
+import { MatCheckboxModule } from '@angular/material/checkbox';
+import { MatButtonModule } from '@angular/material/button';
+import { AsyncPipe } from '@angular/common';
+import { ErrorBanner } from 
'../../../../../ui/common/error-banner/error-banner.component';
+import { NifiSpinnerDirective } from 
'../../../../../ui/common/spinner/nifi-spinner.directive';
+import { selectSaving } from 
'../../../state/manage-remote-ports/manage-remote-ports.selectors';
+import { EditRemotePortDialogRequest } from '../../../state/flow';
+import { Client } from '../../../../../service/client.service';
+import { ComponentType } from '../../../../../state/shared';
+import { PortSummary } from '../../../state/manage-remote-ports';
+import { configureRemotePort } from 
'../../../state/manage-remote-ports/manage-remote-ports.actions';
+
+@Component({
+    standalone: true,
+    templateUrl: './edit-remote-port.component.html',
+    imports: [
+        ReactiveFormsModule,
+        ErrorBanner,
+        MatDialogModule,
+        MatInputModule,
+        MatCheckboxModule,
+        MatButtonModule,
+        AsyncPipe,
+        NifiSpinnerDirective
+    ],
+    styleUrls: ['./edit-remote-port.component.scss']
+})
+export class EditRemotePortComponent {
+    saving$ = this.store.select(selectSaving);
+
+    editPortForm: FormGroup;
+    portTypeLabel: string;
+
+    constructor(
+        @Inject(MAT_DIALOG_DATA) public request: EditRemotePortDialogRequest,
+        private formBuilder: FormBuilder,
+        private store: Store<CanvasState>,
+        private client: Client
+    ) {
+        // set the port type name
+        if (ComponentType.InputPort == this.request.type) {
+            this.portTypeLabel = 'Input Port';
+        } else {
+            this.portTypeLabel = 'Output Port';
+        }
+
+        // build the form
+        this.editPortForm = this.formBuilder.group({
+            concurrentTasks: new 
FormControl(request.entity.concurrentlySchedulableTaskCount, 
Validators.required),
+            compressed: new FormControl(request.entity.useCompression),
+            count: new FormControl(request.entity.batchSettings.count),
+            size: new FormControl(request.entity.batchSettings.size),
+            duration: new FormControl(request.entity.batchSettings.duration)
+        });
+    }
+
+    editRemotePort() {
+        const payload: any = {
+            revision: this.client.getRevision(this.request.rpg),
+            disconnectedNodeAcknowledged: false,
+            type: this.request.type,
+            remoteProcessGroupPort: {
+                concurrentlySchedulableTaskCount: 
this.editPortForm.get('concurrentTasks')?.value,
+                useCompression: this.editPortForm.get('compressed')?.value,
+                batchSettings: {
+                    count: this.editPortForm.get('count')?.value,
+                    size: this.editPortForm.get('size')?.value,
+                    duration: this.editPortForm.get('duration')?.value
+                },
+                id: this.request.entity.id,
+                groupId: this.request.entity.groupId
+            } as PortSummary
+        };
+
+        this.store.dispatch(
+            configureRemotePort({
+                request: {
+                    id: this.request.entity.id,
+                    uri: this.request.rpg.uri,
+                    payload
+                }
+            })
+        );
+    }
+}
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/feature/flow-designer-routing.module.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/manage-remote-ports/manage-remote-ports-routing.module.ts
similarity index 56%
copy from 
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/feature/flow-designer-routing.module.ts
copy to 
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/manage-remote-ports/manage-remote-ports-routing.module.ts
index b19a2fa793..db63639f5c 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/feature/flow-designer-routing.module.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/manage-remote-ports/manage-remote-ports-routing.module.ts
@@ -17,33 +17,24 @@
 
 import { NgModule } from '@angular/core';
 import { RouterModule, Routes } from '@angular/router';
-import { FlowDesigner } from './flow-designer.component';
-import { RootGroupRedirector } from 
'../ui/root/redirector/root-group-redirector.component';
-import { rootGroupGuard } from '../ui/root/guard/root-group.guard';
+import { ManageRemotePorts } from './manage-remote-ports.component';
 
 const routes: Routes = [
     {
-        path: 'process-groups/:processGroupId',
-        component: FlowDesigner,
+        path: '',
+        component: ManageRemotePorts,
         children: [
             {
-                path: 'controller-services',
-                loadChildren: () =>
-                    
import('../ui/controller-service/controller-services.module').then(
-                        (m) => m.ControllerServicesModule
-                    )
-            },
-            {
-                path: '',
-                loadChildren: () => 
import('../ui/canvas/canvas.module').then((m) => m.CanvasModule)
+                path: ':id',
+                component: ManageRemotePorts,
+                children: [{ path: 'edit', component: ManageRemotePorts }]
             }
         ]
-    },
-    { path: '', component: RootGroupRedirector, canActivate: [rootGroupGuard] }
+    }
 ];
 
 @NgModule({
     imports: [RouterModule.forChild(routes)],
     exports: [RouterModule]
 })
-export class FlowDesignerRoutingModule {}
+export class ManageRemotePortsRoutingModule {}
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/manage-remote-ports/manage-remote-ports.component.html
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/manage-remote-ports/manage-remote-ports.component.html
new file mode 100644
index 0000000000..a4647849b5
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/manage-remote-ports/manage-remote-ports.component.html
@@ -0,0 +1,240 @@
+<!--
+  ~ 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="pb-5 flex flex-col h-screen justify-between gap-y-5">
+    <header class="nifi-header">
+        <navigation></navigation>
+    </header>
+    <div class="px-5 flex-1 flex flex-col">
+        <h3 class="text-xl bold manage-remote-ports-header pb-5">Manage Remote 
Ports</h3>
+        @if (portsState$ | async; as portsState) {
+            <div class="grid-container grid grid-cols-2">
+                <div class="col-span-1 pr-5">
+                    <div class="flex flex-col mb-5">
+                        <div>Name</div>
+                        <div
+                            class="overflow-ellipsis overflow-hidden 
whitespace-nowrap value"
+                            [title]="portsState.rpg?.id">
+                            {{ portsState.rpg?.id }}
+                        </div>
+                    </div>
+                </div>
+                <div class="col-span-1">
+                    <div class="flex flex-col mb-5">
+                        <div>Urls</div>
+                        <div
+                            class="overflow-ellipsis overflow-hidden 
whitespace-nowrap value"
+                            [title]="portsState.rpg?.component?.targetUri">
+                            {{ portsState.rpg?.component?.targetUri }}
+                        </div>
+                    </div>
+                </div>
+            </div>
+            @if (isInitialLoading(portsState)) {
+                <div>
+                    <ngx-skeleton-loader count="3"></ngx-skeleton-loader>
+                </div>
+            } @else {
+                <div class="flex flex-col h-full gap-y-2">
+                    <div class="flex-1">
+                        <div class="manage-remote-ports-table relative h-full 
border">
+                            <div class="listing-table absolute inset-0 
overflow-y-auto">
+                                <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">
+                                                @if (hasComments(item)) {
+                                                    <div>
+                                                        <div
+                                                            class="pointer fa 
fa-comment"
+                                                            nifiTooltip
+                                                            
[delayClose]="false"
+                                                            
[tooltipComponentType]="TextTip"
+                                                            
[tooltipInputData]="getCommentsTipData(item)"></div>
+                                                    </div>
+                                                }
+                                                @if (portExists(item)) {
+                                                    <div>
+                                                        <div
+                                                            class="pointer fa 
fa-warning"
+                                                            nifiTooltip
+                                                            
[delayClose]="false"
+                                                            
[tooltipComponentType]="TextTip"
+                                                            
[tooltipInputData]="getDisconnectedTipData()"></div>
+                                                    </div>
+                                                }
+                                            </div>
+                                        </td>
+                                    </ng-container>
+
+                                    <!-- Name Column -->
+                                    <ng-container matColumnDef="name">
+                                        <th mat-header-cell *matHeaderCellDef 
mat-sort-header>
+                                            <div class="overflow-ellipsis 
overflow-hidden whitespace-nowrap">Name</div>
+                                        </th>
+                                        <td mat-cell *matCellDef="let item" 
[title]="formatName(item)">
+                                            {{ formatName(item) }}
+                                        </td>
+                                    </ng-container>
+
+                                    <!-- Type Column -->
+                                    <ng-container matColumnDef="type">
+                                        <th mat-header-cell *matHeaderCellDef 
mat-sort-header>
+                                            <div class="overflow-ellipsis 
overflow-hidden whitespace-nowrap">Type</div>
+                                        </th>
+                                        <td mat-cell *matCellDef="let item" 
[title]="formatType(item)">
+                                            {{ formatType(item) }}
+                                        </td>
+                                    </ng-container>
+
+                                    <!-- Tasks Column -->
+                                    <ng-container matColumnDef="tasks">
+                                        <th mat-header-cell *matHeaderCellDef 
mat-sort-header>
+                                            <div class="overflow-ellipsis 
overflow-hidden whitespace-nowrap">
+                                                Concurrent Tasks
+                                            </div>
+                                        </th>
+                                        <td mat-cell *matCellDef="let item" 
[title]="formatTasks(item)">
+                                            <span 
[class.blank]="!item.concurrentlySchedulableTaskCount">
+                                                {{ formatTasks(item) }}
+                                            </span>
+                                        </td>
+                                    </ng-container>
+
+                                    <!-- Compression Column -->
+                                    <ng-container matColumnDef="compression">
+                                        <th mat-header-cell *matHeaderCellDef 
mat-sort-header>
+                                            <div class="overflow-ellipsis 
overflow-hidden whitespace-nowrap">
+                                                Compressed
+                                            </div>
+                                        </th>
+                                        <td mat-cell *matCellDef="let item" 
[title]="formatCompression(item)">
+                                            {{ formatCompression(item) }}
+                                        </td>
+                                    </ng-container>
+
+                                    <!-- Batch Count Column -->
+                                    <ng-container matColumnDef="count">
+                                        <th mat-header-cell *matHeaderCellDef 
mat-sort-header>
+                                            <div class="overflow-ellipsis 
overflow-hidden whitespace-nowrap">
+                                                Batch Count
+                                            </div>
+                                        </th>
+                                        <td mat-cell *matCellDef="let item" 
[title]="formatCount(item)">
+                                            <span 
[class.blank]="isCountBlank(item)">
+                                                {{ formatCount(item) }}
+                                            </span>
+                                        </td>
+                                    </ng-container>
+
+                                    <!-- Batch Size Column -->
+                                    <ng-container matColumnDef="size">
+                                        <th mat-header-cell *matHeaderCellDef 
mat-sort-header>
+                                            <div class="overflow-ellipsis 
overflow-hidden whitespace-nowrap">
+                                                Batch Size
+                                            </div>
+                                        </th>
+                                        <td mat-cell *matCellDef="let item" 
[title]="formatSize(item)">
+                                            <span 
[class.blank]="isSizeBlank(item)">
+                                                {{ formatSize(item) }}
+                                            </span>
+                                        </td>
+                                    </ng-container>
+
+                                    <!-- Batch Duration Column -->
+                                    <ng-container matColumnDef="duration">
+                                        <th mat-header-cell *matHeaderCellDef 
mat-sort-header>
+                                            <div class="overflow-ellipsis 
overflow-hidden whitespace-nowrap">
+                                                Batch Duration
+                                            </div>
+                                        </th>
+                                        <td mat-cell *matCellDef="let item" 
[title]="formatDuration(item)">
+                                            <span 
[class.blank]="isDurationBlank(item)">
+                                                {{ formatDuration(item) }}
+                                            </span>
+                                        </td>
+                                    </ng-container>
+
+                                    <!-- Actions Column -->
+                                    <ng-container matColumnDef="actions">
+                                        <th mat-header-cell 
*matHeaderCellDef></th>
+                                        <td mat-cell *matCellDef="let port">
+                                            <div class="flex items-center 
gap-x-3">
+                                                @if (
+                                                    port.exists === true &&
+                                                    port.connected === true &&
+                                                    port.transmitting === false
+                                                ) {
+                                                    <div
+                                                        class="pointer fa 
fa-pencil"
+                                                        
(click)="configureClicked(port, $event)"
+                                                        title="Edit 
Port"></div>
+                                                }
+
+                                                @if (currentRpg) {
+                                                    @if (port.transmitting) {
+                                                        <div
+                                                            class="pointer 
transmitting fa fa-bullseye"
+                                                            
(click)="toggleTransmission(port)"
+                                                            
title="Transmitting: click to toggle port transmission"></div>
+                                                    } @else {
+                                                        @if (port.connected && 
port.exists) {
+                                                            <div
+                                                                class="pointer 
not-transmitting icon icon-transmit-false"
+                                                                
(click)="toggleTransmission(port)"
+                                                                title="Not 
Transmitting: click to toggle port transmission"></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"
+                                        (click)="select(row)"
+                                        [class.selected]="isSelected(row)"
+                                        [class.even]="even"></tr>
+                                </table>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="flex justify-between">
+                        <div class="refresh-container flex items-center 
gap-x-2">
+                            <button class="nifi-button" 
(click)="refreshManageRemotePortsListing()">
+                                <i class="fa fa-refresh" 
[class.fa-spin]="portsState.status === 'loading'"></i>
+                            </button>
+                            <div>Last updated:</div>
+                            <div class="refresh-timestamp">{{ 
portsState.loadedTimestamp }}</div>
+                        </div>
+                    </div>
+                </div>
+            }
+        }
+    </div>
+</div>
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/manage-remote-ports/manage-remote-ports.component.scss
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/manage-remote-ports/manage-remote-ports.component.scss
new file mode 100644
index 0000000000..c998c77801
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/manage-remote-ports/manage-remote-ports.component.scss
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+@use '@angular/material' as mat;
+
+.refresh-container {
+    line-height: normal;
+}
+
+.manage-remote-ports-table.listing-table {
+    @include mat.table-density(-4);
+
+    table {
+        .mat-column-moreDetails {
+            width: 32px;
+            min-width: 32px;
+        }
+
+        .mat-column-actions {
+            width: 75px;
+            min-width: 75px;
+        }
+    }
+}
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/manage-remote-ports/manage-remote-ports.component.spec.ts
similarity index 73%
copy from 
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
copy to 
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/manage-remote-ports/manage-remote-ports.component.spec.ts
index fccb8a6661..987754a900 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/manage-remote-ports/manage-remote-ports.component.spec.ts
@@ -17,17 +17,16 @@
 
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 
-import { ControllerServices } from './controller-services.component';
+import { ManageRemotePorts } from './manage-remote-ports.component';
 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';
+import { initialState } from 
'../../state/manage-remote-ports/manage-remote-ports.reducer';
 
-describe('ControllerServices', () => {
-    let component: ControllerServices;
-    let fixture: ComponentFixture<ControllerServices>;
+describe('ManageRemotePorts', () => {
+    let component: ManageRemotePorts;
+    let fixture: ComponentFixture<ManageRemotePorts>;
 
     @Component({
         selector: 'navigation',
@@ -38,15 +37,15 @@ describe('ControllerServices', () => {
 
     beforeEach(() => {
         TestBed.configureTestingModule({
-            declarations: [ControllerServices],
-            imports: [RouterTestingModule, MockNavigation, 
ControllerServicesModule, HttpClientTestingModule],
+            declarations: [ManageRemotePorts],
+            imports: [RouterTestingModule, MockNavigation, 
HttpClientTestingModule],
             providers: [
                 provideMockStore({
                     initialState
                 })
             ]
         });
-        fixture = TestBed.createComponent(ControllerServices);
+        fixture = TestBed.createComponent(ManageRemotePorts);
         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/flow-designer/ui/manage-remote-ports/manage-remote-ports.component.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/manage-remote-ports/manage-remote-ports.component.ts
new file mode 100644
index 0000000000..6e7388e30d
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/manage-remote-ports/manage-remote-ports.component.ts
@@ -0,0 +1,358 @@
+/*
+ * 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, OnDestroy, OnInit } from '@angular/core';
+import { Store } from '@ngrx/store';
+import { filter, switchMap, take, tap } from 'rxjs';
+import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
+import {
+    selectRemotePortsState,
+    selectPort,
+    selectPortIdFromRoute,
+    selectPorts,
+    selectRpg,
+    selectRpgIdFromRoute,
+    selectSingleEditedPort
+} from '../../state/manage-remote-ports/manage-remote-ports.selectors';
+import { RemotePortsState, PortSummary } from 
'../../state/manage-remote-ports';
+import {
+    loadRemotePorts,
+    navigateToEditPort,
+    openConfigureRemotePortDialog,
+    resetRemotePortsState,
+    selectRemotePort,
+    startRemotePortTransmission,
+    stopRemotePortTransmission
+} from '../../state/manage-remote-ports/manage-remote-ports.actions';
+import { initialState } from 
'../../state/manage-remote-ports/manage-remote-ports.reducer';
+import { isDefinedAndNotNull, TextTipInput } from '../../../../state/shared';
+import { selectCurrentUser } from 
'../../../../state/current-user/current-user.selectors';
+import { NiFiState } from '../../../../state';
+import { NiFiCommon } from '../../../../service/nifi-common.service';
+import { MatTableDataSource } from '@angular/material/table';
+import { Sort } from '@angular/material/sort';
+import { TextTip } from 
'../../../../ui/common/tooltips/text-tip/text-tip.component';
+import { concatLatestFrom } from '@ngrx/effects';
+import { loadFlowConfiguration } from 
'../../../../state/flow-configuration/flow-configuration.actions';
+import {
+    selectFlowConfiguration,
+    selectTimeOffset
+} from '../../../../state/flow-configuration/flow-configuration.selectors';
+import { selectAbout } from '../../../../state/about/about.selectors';
+import { loadAbout } from '../../../../state/about/about.actions';
+
+@Component({
+    templateUrl: './manage-remote-ports.component.html',
+    styleUrls: ['./manage-remote-ports.component.scss']
+})
+export class ManageRemotePorts implements OnInit, OnDestroy {
+    initialSortColumn: 'name' | 'type' | 'tasks' | 'count' | 'size' | 
'duration' | 'compression' | 'actions' = 'name';
+    initialSortDirection: 'asc' | 'desc' = 'asc';
+    activeSort: Sort = {
+        active: this.initialSortColumn,
+        direction: this.initialSortDirection
+    };
+    portsState$ = this.store.select(selectRemotePortsState);
+    selectedRpgId$ = this.store.select(selectRpgIdFromRoute);
+    selectedPortId!: string;
+    currentUser$ = this.store.select(selectCurrentUser);
+    flowConfiguration$ = 
this.store.select(selectFlowConfiguration).pipe(isDefinedAndNotNull());
+    displayedColumns: string[] = [
+        'moreDetails',
+        'name',
+        'type',
+        'tasks',
+        'count',
+        'size',
+        'duration',
+        'compression',
+        'actions'
+    ];
+    dataSource: MatTableDataSource<PortSummary> = new 
MatTableDataSource<PortSummary>([]);
+    protected readonly TextTip = TextTip;
+
+    private currentRpgId!: string;
+    protected currentRpg: any | null = null;
+
+    constructor(
+        private store: Store<NiFiState>,
+        private nifiCommon: NiFiCommon
+    ) {
+        // load the ports after the flow configuration `timeOffset` and about 
`timezone` are loaded into the store
+        this.store
+            .select(selectTimeOffset)
+            .pipe(
+                isDefinedAndNotNull(),
+                switchMap(() => this.store.select(selectAbout)),
+                isDefinedAndNotNull(),
+                switchMap(() => this.store.select(selectRpgIdFromRoute)),
+                tap((rpgId) => (this.currentRpgId = rpgId)),
+                takeUntilDestroyed()
+            )
+            .subscribe((rpgId) => {
+                this.store.dispatch(
+                    loadRemotePorts({
+                        request: {
+                            rpgId
+                        }
+                    })
+                );
+            });
+
+        // track selection using the port id from the route
+        this.store
+            .select(selectPortIdFromRoute)
+            .pipe(isDefinedAndNotNull(), takeUntilDestroyed())
+            .subscribe((portId) => {
+                this.selectedPortId = portId;
+            });
+
+        // data for table
+        this.store
+            .select(selectPorts)
+            .pipe(isDefinedAndNotNull(), takeUntilDestroyed())
+            .subscribe((ports) => {
+                this.dataSource = new 
MatTableDataSource<PortSummary>(this.sortEntities(ports, this.activeSort));
+            });
+
+        // the current RPG Entity
+        this.store
+            .select(selectRpg)
+            .pipe(
+                isDefinedAndNotNull(),
+                tap((rpg) => (this.currentRpg = rpg)),
+                takeUntilDestroyed()
+            )
+            .subscribe();
+
+        // handle editing remote port deep link
+        this.store
+            .select(selectSingleEditedPort)
+            .pipe(
+                isDefinedAndNotNull(),
+                switchMap((id: string) =>
+                    this.store.select(selectPort(id)).pipe(
+                        filter((entity) => entity != null),
+                        take(1)
+                    )
+                ),
+                concatLatestFrom(() => 
[this.store.select(selectRpg).pipe(isDefinedAndNotNull())]),
+                takeUntilDestroyed()
+            )
+            .subscribe(([entity, rpg]) => {
+                if (entity) {
+                    this.store.dispatch(
+                        openConfigureRemotePortDialog({
+                            request: {
+                                id: entity.id,
+                                port: entity,
+                                rpg
+                            }
+                        })
+                    );
+                }
+            });
+    }
+
+    ngOnInit(): void {
+        this.store.dispatch(loadFlowConfiguration());
+        this.store.dispatch(loadAbout());
+    }
+
+    isInitialLoading(state: RemotePortsState): boolean {
+        // using the current timestamp to detect the initial load event
+        return state.loadedTimestamp == initialState.loadedTimestamp;
+    }
+
+    refreshManageRemotePortsListing(): void {
+        this.store.dispatch(
+            loadRemotePorts({
+                request: {
+                    rpgId: this.currentRpgId
+                }
+            })
+        );
+    }
+
+    formatName(entity: PortSummary): string {
+        return entity.name;
+    }
+
+    formatTasks(entity: PortSummary): string {
+        return entity.concurrentlySchedulableTaskCount ? 
`${entity.concurrentlySchedulableTaskCount}` : 'No value set';
+    }
+
+    formatCount(entity: PortSummary): string {
+        if (!this.isCountBlank(entity)) {
+            return `${entity.batchSettings.count}`;
+        }
+        return 'No value set';
+    }
+
+    isCountBlank(entity: PortSummary): boolean {
+        return this.nifiCommon.isUndefined(entity.batchSettings.count);
+    }
+
+    formatSize(entity: PortSummary): string {
+        if (!this.isSizeBlank(entity)) {
+            return `${entity.batchSettings.size}`;
+        }
+        return 'No value set';
+    }
+
+    isSizeBlank(entity: PortSummary): boolean {
+        return this.nifiCommon.isBlank(entity.batchSettings.size);
+    }
+
+    formatDuration(entity: PortSummary): string {
+        if (!this.isDurationBlank(entity)) {
+            return `${entity.batchSettings.duration}`;
+        }
+        return 'No value set';
+    }
+
+    isDurationBlank(entity: PortSummary): boolean {
+        return this.nifiCommon.isBlank(entity.batchSettings.duration);
+    }
+
+    formatCompression(entity: PortSummary): string {
+        return entity.useCompression ? 'Yes' : 'No';
+    }
+
+    formatType(entity: PortSummary): string {
+        return entity.type || '';
+    }
+
+    configureClicked(port: PortSummary, event: MouseEvent): void {
+        event.stopPropagation();
+        this.store.dispatch(
+            navigateToEditPort({
+                id: port.id
+            })
+        );
+    }
+
+    hasComments(entity: PortSummary): boolean {
+        return !this.nifiCommon.isBlank(entity.comments);
+    }
+
+    portExists(entity: PortSummary): boolean {
+        return !entity.exists;
+    }
+
+    getCommentsTipData(entity: PortSummary): TextTipInput {
+        return {
+            text: entity.comments
+        };
+    }
+
+    getDisconnectedTipData(): TextTipInput {
+        return {
+            text: 'This port has been removed.'
+        };
+    }
+
+    toggleTransmission(port: PortSummary): void {
+        if (this.currentRpg) {
+            if (port.transmitting) {
+                this.store.dispatch(
+                    stopRemotePortTransmission({
+                        request: {
+                            rpg: this.currentRpg,
+                            port
+                        }
+                    })
+                );
+            } else {
+                if (port.connected && port.exists) {
+                    this.store.dispatch(
+                        startRemotePortTransmission({
+                            request: {
+                                rpg: this.currentRpg,
+                                port
+                            }
+                        })
+                    );
+                }
+            }
+        }
+    }
+
+    select(entity: PortSummary): void {
+        this.store.dispatch(
+            selectRemotePort({
+                request: {
+                    rpgId: this.currentRpgId,
+                    id: entity.id
+                }
+            })
+        );
+    }
+
+    isSelected(entity: any): boolean {
+        if (this.selectedPortId) {
+            return entity.id == this.selectedPortId;
+        }
+        return false;
+    }
+
+    sortData(sort: Sort) {
+        this.activeSort = sort;
+        this.dataSource.data = this.sortEntities(this.dataSource.data, sort);
+    }
+
+    private sortEntities(data: PortSummary[], sort: Sort): PortSummary[] {
+        if (!data) {
+            return [];
+        }
+        return data.slice().sort((a, b) => {
+            const isAsc = 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 'tasks':
+                    retVal = 
this.nifiCommon.compareString(this.formatTasks(a), this.formatTasks(b));
+                    break;
+                case 'compression':
+                    retVal = 
this.nifiCommon.compareString(this.formatCompression(a), 
this.formatCompression(b));
+                    break;
+                case 'count':
+                    retVal = 
this.nifiCommon.compareString(this.formatCount(a), this.formatCount(b));
+                    break;
+                case 'size':
+                    retVal = this.nifiCommon.compareString(this.formatSize(a), 
this.formatSize(b));
+                    break;
+                case 'duration':
+                    retVal = 
this.nifiCommon.compareString(this.formatDuration(a), this.formatDuration(b));
+                    break;
+                default:
+                    return 0;
+            }
+            return retVal * (isAsc ? 1 : -1);
+        });
+    }
+
+    ngOnDestroy(): void {
+        this.store.dispatch(resetRemotePortsState());
+    }
+}
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/manage-remote-ports/manage-remote-ports.module.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/manage-remote-ports/manage-remote-ports.module.ts
new file mode 100644
index 0000000000..9578ec9ce8
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/manage-remote-ports/manage-remote-ports.module.ts
@@ -0,0 +1,52 @@
+/*
+ * 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 { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { ManageRemotePorts } from './manage-remote-ports.component';
+import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
+import { ControllerServiceTable } from 
'../../../../ui/common/controller-service/controller-service-table/controller-service-table.component';
+import { ManageRemotePortsRoutingModule } from 
'./manage-remote-ports-routing.module';
+import { Breadcrumbs } from '../common/breadcrumbs/breadcrumbs.component';
+import { Navigation } from 
'../../../../ui/common/navigation/navigation.component';
+import { MatTableModule } from '@angular/material/table';
+import { MatSortModule } from '@angular/material/sort';
+import { NifiTooltipDirective } from 
'../../../../ui/common/tooltips/nifi-tooltip.directive';
+import { StoreModule } from '@ngrx/store';
+import { EffectsModule } from '@ngrx/effects';
+import { ManageRemotePortsEffects } from 
'../../state/manage-remote-ports/manage-remote-ports.effects';
+import { remotePortsFeatureKey } from '../../state/manage-remote-ports';
+import { manageRemotePortsReducer } from 
'../../state/manage-remote-ports/manage-remote-ports.reducer';
+
+@NgModule({
+    declarations: [ManageRemotePorts],
+    exports: [ManageRemotePorts],
+    imports: [
+        CommonModule,
+        NgxSkeletonLoaderModule,
+        ManageRemotePortsRoutingModule,
+        StoreModule.forFeature(remotePortsFeatureKey, 
manageRemotePortsReducer),
+        EffectsModule.forFeature(ManageRemotePortsEffects),
+        ControllerServiceTable,
+        Breadcrumbs,
+        Navigation,
+        MatTableModule,
+        MatSortModule,
+        NifiTooltipDirective
+    ]
+})
+export class ManageRemotePortsModule {}
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 bbe69cfdcd..be8e1b4c12 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
@@ -184,6 +184,33 @@ export class NiFiCommon {
         return true;
     }
 
+    /**
+     * Determines if the specified object is defined and not null.
+     *
+     * @argument {object} obj   The object to test
+     */
+    public isDefinedAndNotNull(obj: any) {
+        return !this.isUndefined(obj) && !this.isNull(obj);
+    }
+
+    /**
+     * Determines if the specified object is undefined.
+     *
+     * @argument {object} obj   The object to test
+     */
+    public isUndefined(obj: any) {
+        return typeof obj === 'undefined';
+    }
+
+    /**
+     * Determines if the specified object is null.
+     *
+     * @argument {object} obj   The object to test
+     */
+    public isNull(obj: any) {
+        return obj === null;
+    }
+
     /**
      * Determines if the specified array is empty. If the specified arg is not 
an
      * array, then true is returned.
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/themes/nifi.scss
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/themes/nifi.scss
index 34ada14216..b8ff67946d 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/themes/nifi.scss
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/themes/nifi.scss
@@ -170,7 +170,7 @@ $nifi-canvas-dark-palette: (
     500: #acacac, // g.connection rect.backpressure-object, g.connection 
rect.backpressure-data-size, .cdk-drag-disabled, .resizable-triangle
     600: #545454, // .canvas-background, .navigation-control, 
.operation-control, .lineage
     700: #696060, // .canvas-background, g.component rect.body.unauthorized, 
g.component rect.processor-icon-container.unauthorized, g.connection 
rect.body.unauthorized, #birdseye, .lineage
-    800: rgba(#6b6464, 0.5), // .even, .remote-process-group-sent-stats, 
.processor-stats-in-out, .process-group-queued-stats, 
.process-group-read-write-stats
+    800: rgba(#6b6464, 1), // .even, .remote-process-group-sent-stats, 
.processor-stats-in-out, .process-group-queued-stats, 
.process-group-read-write-stats
     900: rgba(#252424, 0.97), // circle.flowfile-link, 
.processor-read-write-stats, .process-group-stats-in-out, .tooltip, 
.property-editor, .disabled, .enabled, .stopped, .running, .has-errors, 
.invalid, .validating, .transmitting, .not-transmitting, .up-to-date, 
.locally-modified, .sync-failure, .stale, .locally-modified-and-stale, 
g.component rect.body, text.bulletin-icon, rect.processor-icon-container, 
circle.restricted-background, circle.is-primary-background, g.connection 
rect.body [...]
 
     // some analog colors for headers and hover states, inputs, stats, etc
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/themes/purple.scss
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/themes/purple.scss
index fe59f65210..40eb88e23a 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/themes/purple.scss
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/themes/purple.scss
@@ -171,7 +171,7 @@ $nifi-canvas-dark-palette: (
     500: #acacac, // g.connection rect.backpressure-object, g.connection 
rect.backpressure-data-size, .cdk-drag-disabled, .resizable-triangle
     600: #545454, // .canvas-background, .navigation-control, 
.operation-control, .lineage
     700: #696060, // .canvas-background, g.component rect.body.unauthorized, 
g.component rect.processor-icon-container.unauthorized, g.connection 
rect.body.unauthorized, #birdseye, .lineage
-    800: rgba(#6b6464, 0.5), // .even, .remote-process-group-sent-stats, 
.processor-stats-in-out, .process-group-queued-stats, 
.process-group-read-write-stats
+    800: rgba(#6b6464, 1), // .even, .remote-process-group-sent-stats, 
.processor-stats-in-out, .process-group-queued-stats, 
.process-group-read-write-stats
     900: rgba(#252424, 0.97), // circle.flowfile-link, 
.processor-read-write-stats, .process-group-stats-in-out, .tooltip, 
.property-editor, .disabled, .enabled, .stopped, .running, .has-errors, 
.invalid, .validating, .transmitting, .not-transmitting, .up-to-date, 
.locally-modified, .sync-failure, .stale, .locally-modified-and-stale, 
g.component rect.body, text.bulletin-icon, rect.processor-icon-container, 
circle.restricted-background, circle.is-primary-background, g.connection 
rect.body [...]
 
     // some analog colors for headers and hover states, inputs, stats, etc
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/styles.scss
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/styles.scss
index 63e2591dec..145863cc71 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/styles.scss
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/styles.scss
@@ -41,6 +41,7 @@
 @use 
'app/pages/flow-designer/ui/canvas/items/remote-process-group/create-remote-process-group/create-remote-process-group.component-theme'
 as create-remote-process-group;
 @use 'app/pages/flow-designer/ui/common/banner/banner.component-theme' as 
banner;
 @use 
'app/pages/flow-designer/ui/controller-service/controller-services.component-theme'
 as controller-service;
+@use 
'app/pages/flow-designer/ui/manage-remote-ports/manage-remote-ports.component-theme'
 as manage-remote-ports;
 @use 'app/pages/login/feature/login.component-theme' as login;
 @use 'app/pages/login/ui/login-form/login-form.component-theme' as login-form;
 @use 'app/pages/provenance/feature/provenance.component-theme' as provenance;
@@ -460,6 +461,10 @@ $appFontPath: '~roboto-fontface/fonts';
         cursor: pointer;
     }
 
+    .disabled {
+        cursor: not-allowed;
+    }
+
     .value,
     .refresh-timestamp {
         font-weight: 500;
@@ -483,6 +488,7 @@ $appFontPath: '~roboto-fontface/fonts';
 @include canvas.nifi-theme($material-theme-light, $nifi-canvas-theme-light);
 @include banner.nifi-theme($material-theme-light);
 @include controller-service.nifi-theme($material-theme-light);
+@include manage-remote-ports.nifi-theme($material-theme-light, 
$nifi-canvas-theme-light);
 @include footer.nifi-theme($material-theme-light, $nifi-canvas-theme-light);
 @include navigation-control.nifi-theme($material-theme-light, 
$nifi-canvas-theme-light);
 @include birdseye-control.nifi-theme($material-theme-light, 
$nifi-canvas-theme-light);
@@ -535,6 +541,7 @@ $appFontPath: '~roboto-fontface/fonts';
     @include canvas.nifi-theme($material-theme-dark, $nifi-canvas-theme-dark);
     @include banner.nifi-theme($material-theme-dark);
     @include controller-service.nifi-theme($material-theme-dark);
+    @include manage-remote-ports.nifi-theme($material-theme-dark, 
$nifi-canvas-theme-dark);
     @include footer.nifi-theme($material-theme-dark, $nifi-canvas-theme-dark);
     @include navigation-control.nifi-theme($material-theme-dark, 
$nifi-canvas-theme-dark);
     @include birdseye-control.nifi-theme($material-theme-dark, 
$nifi-canvas-theme-dark);

Reply via email to