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

scottyaslan 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 a7546a7095 NIFI-12589: Queue Listing (#8235)
a7546a7095 is described below

commit a7546a7095670fc871f0a45750183677fb72538a
Author: Matt Gilman <matt.c.gil...@gmail.com>
AuthorDate: Fri Jan 12 16:09:48 2024 -0500

    NIFI-12589: Queue Listing (#8235)
    
    * NIFI-12589:
    - Introducing queue listing.
    - View flowfile content.
    - Download flowfile content.
    - View flowfile dialog.
    
    * NIFI-12589:
    - Addressing review feedback.
    
    * NIFI-12589:
    - Fixing merge conflict and removing additional @ts-ignore.
    
    This closes #8235
---
 .../src/main/nifi/src/app/app-routing.module.ts    |   5 +
 .../src/main/nifi/src/app/app.module.ts            |   1 -
 .../state/access-policy/access-policy.effects.ts   |  34 +--
 .../component-access-policies.component.ts         |  17 +-
 .../global-access-policies.component.ts            |  17 +-
 .../service/canvas-context-menu.service.ts         |  15 +-
 .../flow-designer/service/canvas-view.service.ts   |   5 +
 .../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    |   4 +
 .../flow-designer/ui/canvas/canvas.component.ts    |  27 +-
 .../new-canvas-item/new-canvas-item.component.ts   |   3 +-
 .../nifi/src/app/pages/provenance/state/index.ts   |   2 +-
 .../pages/queue/feature/queue-routing.module.ts    |  40 +++
 .../app/pages/queue/feature/queue.component.html   |  27 ++
 .../app/pages/queue/feature/queue.component.scss   |  16 +
 .../pages/queue/feature/queue.component.spec.ts    |  48 +++
 .../src/app/pages/queue/feature/queue.component.ts |  40 +++
 .../src/app/pages/queue/feature/queue.module.ts    |  33 +++
 .../src/app/pages/queue/service/queue.service.ts   | 122 ++++++++
 .../app/pages/{provenance => queue}/state/index.ts |  22 +-
 .../app/pages/queue/state/queue-listing/index.ts   | 109 +++++++
 .../state/queue-listing/queue-listing.actions.ts   |  86 ++++++
 .../state/queue-listing/queue-listing.effects.ts   | 322 +++++++++++++++++++++
 .../state/queue-listing/queue-listing.reducer.ts   |  62 ++++
 .../state/queue-listing/queue-listing.selectors.ts |  52 ++++
 .../flowfile-dialog/flowfile-dialog.component.html | 229 +++++++++++++++
 .../flowfile-dialog/flowfile-dialog.component.scss |  41 +++
 .../flowfile-dialog.component.spec.ts              |  60 ++++
 .../flowfile-dialog/flowfile-dialog.component.ts}  |  55 ++--
 .../flowfile-table/flowfile-table.component.html   | 147 ++++++++++
 .../flowfile-table/flowfile-table.component.scss   |  50 ++++
 .../flowfile-table.component.spec.ts               |  40 +++
 .../flowfile-table/flowfile-table.component.ts     | 142 +++++++++
 .../queue-listing/queue-listing-routing.module.ts  |  33 +++
 .../ui/queue-listing/queue-listing.component.html  |  48 +++
 .../ui/queue-listing/queue-listing.component.scss  |  16 +
 .../queue-listing/queue-listing.component.spec.ts  |  41 +++
 .../ui/queue-listing/queue-listing.component.ts    | 108 +++++++
 .../queue/ui/queue-listing/queue-listing.module.ts |  43 +++
 .../controller-service-state.effects.ts            |  26 +-
 .../src/main/nifi/src/app/state/shared/index.ts    |  11 +
 .../provenance-event-dialog.component.ts           |   5 +-
 .../main/nifi/src/assets/styles/listing-table.scss |   9 +-
 44 files changed, 2093 insertions(+), 140 deletions(-)

diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app-routing.module.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app-routing.module.ts
index 37c7aec4e1..3a70a49d5b 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app-routing.module.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app-routing.module.ts
@@ -68,6 +68,11 @@ const routes: Routes = [
         canMatch: [authenticationGuard],
         loadChildren: () => 
import('./pages/bulletins/feature/bulletins.module').then((m) => 
m.BulletinsModule)
     },
+    {
+        path: 'queue',
+        canMatch: [authenticationGuard],
+        loadChildren: () => 
import('./pages/queue/feature/queue.module').then((m) => m.QueueModule)
+    },
     {
         path: '',
         canMatch: [authenticationGuard],
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app.module.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app.module.ts
index f19e2ab92c..4fde32890c 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app.module.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/app.module.ts
@@ -42,7 +42,6 @@ import { ControllerServiceStateEffects } from 
'./state/contoller-service-state/c
 import { SystemDiagnosticsEffects } from 
'./state/system-diagnostics/system-diagnostics.effects';
 import { FlowConfigurationEffects } from 
'./state/flow-configuration/flow-configuration.effects';
 
-// @ts-ignore
 @NgModule({
     declarations: [AppComponent],
     imports: [
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/access-policies/state/access-policy/access-policy.effects.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/access-policies/state/access-policy/access-policy.effects.ts
index 3ccf7ec75c..fa551d40e7 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/access-policies/state/access-policy/access-policy.effects.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/access-policies/state/access-policy/access-policy.effects.ts
@@ -27,7 +27,7 @@ import { AccessPolicyService } from 
'../../service/access-policy.service';
 import { AccessPolicyEntity, ComponentResourceAction, PolicyStatus, 
ResourceAction } from '../shared';
 import { selectAccessPolicy, selectResourceAction, selectSaving } from 
'./access-policy.selectors';
 import { YesNoDialog } from 
'../../../../ui/common/yes-no-dialog/yes-no-dialog.component';
-import { TenantEntity } from '../../../../state/shared';
+import { isDefinedAndNotNull, TenantEntity } from '../../../../state/shared';
 import { AddTenantToPolicyDialog } from 
'../../ui/common/add-tenant-to-policy-dialog/add-tenant-to-policy-dialog.component';
 import { AddTenantsToPolicyRequest } from './index';
 import { ComponentAccessPolicies } from 
'../../ui/component-access-policies/component-access-policies.component';
@@ -62,13 +62,11 @@ export class AccessPolicyEffects {
     reloadAccessPolicy$ = createEffect(() =>
         this.actions$.pipe(
             ofType(AccessPolicyActions.reloadAccessPolicy),
-            withLatestFrom(this.store.select(selectResourceAction)),
-            filter(([action, resourceAction]) => resourceAction != null),
+            
withLatestFrom(this.store.select(selectResourceAction).pipe(isDefinedAndNotNull())),
             switchMap(([action, resourceAction]) => {
                 return of(
                     AccessPolicyActions.loadAccessPolicy({
                         request: {
-                            // @ts-ignore
                             resourceAction
                         }
                     })
@@ -139,10 +137,8 @@ export class AccessPolicyEffects {
     createAccessPolicy$ = createEffect(() =>
         this.actions$.pipe(
             ofType(AccessPolicyActions.createAccessPolicy),
-            withLatestFrom(this.store.select(selectResourceAction)),
-            filter(([action, resourceAction]) => resourceAction != null),
+            
withLatestFrom(this.store.select(selectResourceAction).pipe(isDefinedAndNotNull())),
             switchMap(([action, resourceAction]) =>
-                // @ts-ignore
                 
from(this.accessPoliciesService.createAccessPolicy(resourceAction)).pipe(
                     map((response) => {
                         const accessPolicy: AccessPolicyEntity = response;
@@ -243,12 +239,8 @@ export class AccessPolicyEffects {
         this.actions$.pipe(
             ofType(AccessPolicyActions.addTenantsToPolicy),
             map((action) => action.request),
-            withLatestFrom(this.store.select(selectAccessPolicy)),
-            filter(([request, accessPolicyEntity]) => accessPolicyEntity != 
null),
-            switchMap(([request, accessPolicyEntity]) => {
-                // @ts-ignore
-                const accessPolicy: AccessPolicyEntity = accessPolicyEntity;
-
+            
withLatestFrom(this.store.select(selectAccessPolicy).pipe(isDefinedAndNotNull())),
+            switchMap(([request, accessPolicy]) => {
                 const users: TenantEntity[] = 
[...accessPolicy.component.users, ...request.users];
                 const userGroups: TenantEntity[] = 
[...accessPolicy.component.userGroups, ...request.userGroups];
 
@@ -303,12 +295,8 @@ export class AccessPolicyEffects {
         this.actions$.pipe(
             ofType(AccessPolicyActions.removeTenantFromPolicy),
             map((action) => action.request),
-            withLatestFrom(this.store.select(selectAccessPolicy)),
-            filter(([request, accessPolicyEntity]) => accessPolicyEntity != 
null),
-            switchMap(([request, accessPolicyEntity]) => {
-                // @ts-ignore
-                const accessPolicy: AccessPolicyEntity = accessPolicyEntity;
-
+            
withLatestFrom(this.store.select(selectAccessPolicy).pipe(isDefinedAndNotNull())),
+            switchMap(([request, accessPolicy]) => {
                 const users: TenantEntity[] = 
[...accessPolicy.component.users];
                 const userGroups: TenantEntity[] = 
[...accessPolicy.component.userGroups];
 
@@ -376,17 +364,17 @@ export class AccessPolicyEffects {
     deleteAccessPolicy$ = createEffect(() =>
         this.actions$.pipe(
             ofType(AccessPolicyActions.deleteAccessPolicy),
-            withLatestFrom(this.store.select(selectResourceAction), 
this.store.select(selectAccessPolicy)),
-            filter(([action, resourceAction, accessPolicy]) => resourceAction 
!= null && accessPolicy != null),
+            withLatestFrom(
+                
this.store.select(selectResourceAction).pipe(isDefinedAndNotNull()),
+                
this.store.select(selectAccessPolicy).pipe(isDefinedAndNotNull())
+            ),
             switchMap(([action, resourceAction, accessPolicy]) =>
-                // @ts-ignore
                 
from(this.accessPoliciesService.deleteAccessPolicy(accessPolicy)).pipe(
                     map((response) => {
                         // the policy was removed, we need to reload the 
policy for this resource and action to fetch
                         // the inherited policy or correctly when it's not 
found
                         return AccessPolicyActions.loadAccessPolicy({
                             request: {
-                                // @ts-ignore
                                 resourceAction
                             }
                         });
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/access-policies/ui/component-access-policies/component-access-policies.component.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/access-policies/ui/component-access-policies/component-access-policies.component.ts
index 484d5b928e..b22bdeb566 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/access-policies/ui/component-access-policies/component-access-policies.component.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/access-policies/ui/component-access-policies/component-access-policies.component.ts
@@ -38,7 +38,7 @@ import { distinctUntilChanged, filter } from 'rxjs';
 import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
 import { FormBuilder, FormControl, FormGroup, Validators } from 
'@angular/forms';
 import { NiFiCommon } from '../../../../service/nifi-common.service';
-import { ComponentType, SelectOption, TextTipInput } from 
'../../../../state/shared';
+import { ComponentType, isDefinedAndNotNull, SelectOption, TextTipInput } from 
'../../../../state/shared';
 import { TextTip } from 
'../../../../ui/common/tooltips/text-tip/text-tip.component';
 import { AccessPolicyEntity, Action, ComponentResourceAction, PolicyStatus, 
ResourceAction } from '../../state/shared';
 import { loadFlowConfiguration } from 
'../../../../state/flow-configuration/flow-configuration.actions';
@@ -145,18 +145,13 @@ export class ComponentAccessPolicies implements OnInit, 
OnDestroy {
         this.store
             .select(selectComponentResourceActionFromRoute)
             .pipe(
-                filter((resourceAction) => resourceAction != null),
+                isDefinedAndNotNull(),
                 distinctUntilChanged((a, b) => {
-                    // @ts-ignore
-                    const aResourceAction: ComponentResourceAction = a;
-                    // @ts-ignore
-                    const bResourceAction: ComponentResourceAction = b;
-
                     return (
-                        aResourceAction.action == bResourceAction.action &&
-                        aResourceAction.policy == bResourceAction.policy &&
-                        aResourceAction.resource == bResourceAction.resource &&
-                        aResourceAction.resourceIdentifier == 
bResourceAction.resourceIdentifier
+                        a.action == b.action &&
+                        a.policy == b.policy &&
+                        a.resource == b.resource &&
+                        a.resourceIdentifier == b.resourceIdentifier
                     );
                 }),
                 takeUntilDestroyed()
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/access-policies/ui/global-access-policies/global-access-policies.component.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/access-policies/ui/global-access-policies/global-access-policies.component.ts
index 9dc9a234c5..6f0f7f89bd 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/access-policies/ui/global-access-policies/global-access-policies.component.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/access-policies/ui/global-access-policies/global-access-policies.component.ts
@@ -38,7 +38,13 @@ import { distinctUntilChanged, filter } from 'rxjs';
 import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
 import { FormBuilder, FormControl, FormGroup, Validators } from 
'@angular/forms';
 import { NiFiCommon } from '../../../../service/nifi-common.service';
-import { ComponentType, RequiredPermission, SelectOption, TextTipInput } from 
'../../../../state/shared';
+import {
+    ComponentType,
+    isDefinedAndNotNull,
+    RequiredPermission,
+    SelectOption,
+    TextTipInput
+} from '../../../../state/shared';
 import { TextTip } from 
'../../../../ui/common/tooltips/text-tip/text-tip.component';
 import { AccessPolicyEntity, Action, PolicyStatus, ResourceAction } from 
'../../state/shared';
 import { loadExtensionTypesForPolicies } from 
'../../../../state/extension-types/extension-types.actions';
@@ -126,13 +132,8 @@ export class GlobalAccessPolicies implements OnInit, 
OnDestroy {
         this.store
             .select(selectGlobalResourceActionFromRoute)
             .pipe(
-                filter((resourceAction) => resourceAction != null),
-                distinctUntilChanged((aResourceAction, bResourceAction) => {
-                    // @ts-ignore
-                    const a: ResourceAction = aResourceAction;
-                    // @ts-ignore
-                    const b: ResourceAction = bResourceAction;
-
+                isDefinedAndNotNull(),
+                distinctUntilChanged((a, b) => {
                     return (
                         a.action == b.action && a.resource == b.resource && 
a.resourceIdentifier == b.resourceIdentifier
                     );
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 4a6b03ea15..f9df765743 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 {
     navigateToEditCurrentProcessGroup,
     navigateToManageComponentPolicies,
     navigateToProvenanceForComponent,
+    navigateToQueueListing,
     navigateToViewStatusHistoryForComponent,
     reloadFlow,
     replayLastProvenanceEvent,
@@ -688,13 +689,19 @@ export class CanvasContextMenu implements 
ContextMenuDefinitionProvider {
             },
             {
                 condition: (selection: any) => {
-                    // TODO - canListQueue
-                    return false;
+                    return this.canvasUtils.isConnection(selection);
                 },
                 clazz: 'fa fa-list',
                 text: 'List queue',
-                action: () => {
-                    // TODO - listQueue
+                action: (selection: any) => {
+                    const selectionData = selection.datum();
+                    this.store.dispatch(
+                        navigateToQueueListing({
+                            request: {
+                                connectionId: 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/canvas-view.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-view.service.ts
index 73dbfd04a6..b65dd312b7 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-view.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-view.service.ts
@@ -188,6 +188,11 @@ export class CanvasView {
     public updateCanvasVisibility(): void {
         const self: CanvasView = this;
         const canvasContainer: any = 
document.getElementById('canvas-container');
+
+        if (canvasContainer == null) {
+            return;
+        }
+
         let translate = [this.x, this.y];
         const scale = this.k;
 
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 e5bdc330ee..a6c616670f 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
@@ -67,7 +67,8 @@ import {
     UpdateConnectionRequest,
     UpdateConnectionSuccess,
     UpdatePositionsRequest,
-    UploadProcessGroupRequest
+    UploadProcessGroupRequest,
+    NavigateToQueueListing
 } from './index';
 import { StatusHistoryRequest } from '../../../../state/status-history';
 
@@ -315,6 +316,11 @@ export const navigateToControllerServicesForProcessGroup = 
createAction(
     props<{ request: NavigateToControllerServicesRequest }>()
 );
 
+export const navigateToQueueListing = createAction(
+    `${CANVAS_PREFIX} Navigate To Queue Listing`,
+    props<{ request: NavigateToQueueListing }>()
+);
+
 export const editCurrentProcessGroup = createAction(
     `${CANVAS_PREFIX} Edit Current Process Group`,
     props<{
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 67ba2913cb..16020b5edf 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
@@ -686,6 +686,18 @@ export class FlowEffects {
         { dispatch: false }
     );
 
+    navigateToQueueListing$ = createEffect(
+        () =>
+            this.actions$.pipe(
+                ofType(FlowActions.navigateToQueueListing),
+                map((action) => action.request),
+                tap((request) => {
+                    this.router.navigate(['/queue', request.connectionId]);
+                })
+            ),
+        { dispatch: false }
+    );
+
     navigateToViewStatusHistoryForComponent$ = 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 bbc4aaa4da..8ea5f224ee 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
@@ -230,6 +230,10 @@ export interface NavigateToControllerServicesRequest {
     id: string;
 }
 
+export interface NavigateToQueueListing {
+    connectionId: string;
+}
+
 export interface EditCurrentProcessGroupRequest {
     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/ui/canvas/canvas.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/canvas/canvas.component.ts
index aead437ccd..4c985f9bfc 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/canvas.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/canvas/canvas.component.ts
@@ -58,9 +58,8 @@ import {
 } from '../../state/flow/flow.selectors';
 import { filter, map, switchMap, take, withLatestFrom } from 'rxjs';
 import { restoreViewport, zoomFit } from 
'../../state/transform/transform.actions';
-import { ComponentType } from '../../../../state/shared';
+import { ComponentType, isDefinedAndNotNull } from '../../../../state/shared';
 import { initialState } from '../../state/flow/flow.reducer';
-import { ContextMenuDefinitionProvider } from 
'../../../../ui/common/context-menu/context-menu.component';
 import { CanvasContextMenu } from '../../service/canvas-context-menu.service';
 import { getStatusHistoryAndOpenDialog } from 
'../../../../state/status-history/status-history.actions';
 import { loadFlowConfiguration } from 
'../../../../state/flow-configuration/flow-configuration.actions';
@@ -168,37 +167,33 @@ export class Canvas implements OnInit, OnDestroy {
             .pipe(
                 filter((processGroupId) => processGroupId != initialState.id),
                 switchMap(() => 
this.store.select(selectSingleEditedComponent)),
-                // ensure there is a selected component
-                filter((selectedComponent) => selectedComponent != null),
+                isDefinedAndNotNull(),
                 switchMap((selectedComponent) => {
-                    // @ts-ignore
-                    const component: SelectedComponent = selectedComponent;
-
                     let component$;
-                    switch (component.componentType) {
+                    switch (selectedComponent.componentType) {
                         case ComponentType.Processor:
-                            component$ = 
this.store.select(selectProcessor(component.id));
+                            component$ = 
this.store.select(selectProcessor(selectedComponent.id));
                             break;
                         case ComponentType.InputPort:
-                            component$ = 
this.store.select(selectInputPort(component.id));
+                            component$ = 
this.store.select(selectInputPort(selectedComponent.id));
                             break;
                         case ComponentType.OutputPort:
-                            component$ = 
this.store.select(selectOutputPort(component.id));
+                            component$ = 
this.store.select(selectOutputPort(selectedComponent.id));
                             break;
                         case ComponentType.ProcessGroup:
-                            component$ = 
this.store.select(selectProcessGroup(component.id));
+                            component$ = 
this.store.select(selectProcessGroup(selectedComponent.id));
                             break;
                         case ComponentType.RemoteProcessGroup:
-                            component$ = 
this.store.select(selectRemoteProcessGroup(component.id));
+                            component$ = 
this.store.select(selectRemoteProcessGroup(selectedComponent.id));
                             break;
                         case ComponentType.Connection:
-                            component$ = 
this.store.select(selectConnection(component.id));
+                            component$ = 
this.store.select(selectConnection(selectedComponent.id));
                             break;
                         case ComponentType.Funnel:
-                            component$ = 
this.store.select(selectFunnel(component.id));
+                            component$ = 
this.store.select(selectFunnel(selectedComponent.id));
                             break;
                         case ComponentType.Label:
-                            component$ = 
this.store.select(selectLabel(component.id));
+                            component$ = 
this.store.select(selectLabel(selectedComponent.id));
                             break;
                         default:
                             throw 'Unrecognized Component Type';
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/canvas/header/new-canvas-item/new-canvas-item.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/canvas/header/new-canvas-item/new-canvas-item.component.ts
index 40336aca09..77d841fc29 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/header/new-canvas-item/new-canvas-item.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/canvas/header/new-canvas-item/new-canvas-item.component.ts
@@ -36,8 +36,7 @@ import { ComponentType } from 
'../../../../../../state/shared';
     styleUrls: ['./new-canvas-item.component.scss']
 })
 export class NewCanvasItem implements OnInit {
-    // @ts-ignore
-    @Input() type: ComponentType;
+    @Input() type!: ComponentType;
     @Input() iconClass: string = '';
     @Input() iconHoverClass: string = '';
 
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/provenance/state/index.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/provenance/state/index.ts
index 2fd8eb3b53..b88bb12ee4 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/provenance/state/index.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/provenance/state/index.ts
@@ -16,7 +16,7 @@
  */
 
 /*
-  Parameter Contexts
+  Provenance
  */
 
 import { Action, combineReducers, createFeatureSelector } from '@ngrx/store';
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/feature/queue-routing.module.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/feature/queue-routing.module.ts
new file mode 100644
index 0000000000..9063539da3
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/feature/queue-routing.module.ts
@@ -0,0 +1,40 @@
+/*
+ * 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 { RouterModule, Routes } from '@angular/router';
+import { Queue } from './queue.component';
+import { QueueListingRoutingModule } from 
'../ui/queue-listing/queue-listing-routing.module';
+
+const routes: Routes = [
+    {
+        path: '',
+        component: Queue,
+        children: [
+            {
+                path: '',
+                loadChildren: () => 
import('../ui/queue-listing/queue-listing.module').then((m) => 
m.QueueListingModule)
+            }
+        ]
+    }
+];
+
+@NgModule({
+    imports: [RouterModule.forChild(routes)],
+    exports: [RouterModule]
+})
+export class QueueRoutingModule {}
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/feature/queue.component.html
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/feature/queue.component.html
new file mode 100644
index 0000000000..c349efca0c
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/feature/queue.component.html
@@ -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.
+  -->
+
+<div class="p-4 flex flex-col h-screen justify-between gap-y-5">
+    <div class="flex justify-end">
+        <button class="nifi-button" [routerLink]="['/']">
+            <i class="fa fa-times"></i>
+        </button>
+    </div>
+    <div class="flex-1">
+        <router-outlet></router-outlet>
+    </div>
+</div>
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/feature/queue.component.scss
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/feature/queue.component.scss
new file mode 100644
index 0000000000..2944f98194
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/feature/queue.component.scss
@@ -0,0 +1,16 @@
+/*
+ * 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.
+ */
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/feature/queue.component.spec.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/feature/queue.component.spec.ts
new file mode 100644
index 0000000000..1eb8b766a1
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/feature/queue.component.spec.ts
@@ -0,0 +1,48 @@
+/*
+ * 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 { Queue } from './queue.component';
+import { provideMockStore } from '@ngrx/store/testing';
+import { RouterModule } from '@angular/router';
+import { RouterTestingModule } from '@angular/router/testing';
+import { initialState } from '../state/queue-listing/queue-listing.reducer';
+
+describe('Queue', () => {
+    let component: Queue;
+    let fixture: ComponentFixture<Queue>;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            declarations: [Queue],
+            imports: [RouterModule, RouterTestingModule],
+            providers: [
+                provideMockStore({
+                    initialState
+                })
+            ]
+        });
+        fixture = TestBed.createComponent(Queue);
+        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/queue/feature/queue.component.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/feature/queue.component.ts
new file mode 100644
index 0000000000..1c00fd0415
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/feature/queue.component.ts
@@ -0,0 +1,40 @@
+/*
+ * 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 { NiFiState } from '../../../state';
+import { startCurrentUserPolling, stopCurrentUserPolling } from 
'../../../state/current-user/current-user.actions';
+import { loadAbout } from '../../../state/about/about.actions';
+
+@Component({
+    selector: 'queue',
+    templateUrl: './queue.component.html',
+    styleUrls: ['./queue.component.scss']
+})
+export class Queue implements OnInit, OnDestroy {
+    constructor(private store: Store<NiFiState>) {}
+
+    ngOnInit(): void {
+        this.store.dispatch(startCurrentUserPolling());
+        this.store.dispatch(loadAbout());
+    }
+
+    ngOnDestroy(): void {
+        this.store.dispatch(stopCurrentUserPolling());
+    }
+}
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/feature/queue.module.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/feature/queue.module.ts
new file mode 100644
index 0000000000..222a2ec746
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/feature/queue.module.ts
@@ -0,0 +1,33 @@
+/*
+ * 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 { StoreModule } from '@ngrx/store';
+import { EffectsModule } from '@ngrx/effects';
+import { Queue } from './queue.component';
+import { QueueRoutingModule } from './queue-routing.module';
+import { queueFeatureKey, reducers } from '../state';
+import { MatDialogModule } from '@angular/material/dialog';
+import { QueueListingEffects } from 
'../state/queue-listing/queue-listing.effects';
+
+@NgModule({
+    declarations: [Queue],
+    exports: [Queue],
+    imports: [CommonModule, MatDialogModule, QueueRoutingModule]
+})
+export class QueueModule {}
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/service/queue.service.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/service/queue.service.ts
new file mode 100644
index 0000000000..7d3322addf
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/service/queue.service.ts
@@ -0,0 +1,122 @@
+/*
+ * 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, throwError } from 'rxjs';
+import { HttpClient } from '@angular/common/http';
+import { NiFiCommon } from '../../../service/nifi-common.service';
+import { ParameterContextUpdateRequest, SubmitParameterContextUpdate } from 
'../../../state/shared';
+import {
+    FlowFileSummary,
+    ListingRequest,
+    ListingRequestEntity,
+    SubmitQueueListingRequest
+} from '../state/queue-listing';
+
+@Injectable({ providedIn: 'root' })
+export class QueueService {
+    private static readonly API: string = '../nifi-api';
+
+    constructor(
+        private httpClient: HttpClient,
+        private nifiCommon: NiFiCommon
+    ) {}
+
+    /**
+     * The NiFi model contain the url for each component. That URL is an 
absolute URL. Angular CSRF handling
+     * does not work on absolute URLs, so we need to strip off the proto for 
the request header to be added.
+     *
+     * https://stackoverflow.com/a/59586462
+     *
+     * @param url
+     * @private
+     */
+    private stripProtocol(url: string): string {
+        return this.nifiCommon.substringAfterFirst(url, ':');
+    }
+
+    getConnection(connectionId: string): Observable<any> {
+        return 
this.httpClient.get(`${QueueService.API}/connections/${connectionId}`);
+    }
+
+    getFlowFile(flowfileSummary: FlowFileSummary): Observable<any> {
+        return this.httpClient.get(this.stripProtocol(flowfileSummary.uri));
+    }
+
+    submitQueueListingRequest(queueListingRequest: SubmitQueueListingRequest): 
Observable<any> {
+        return this.httpClient.post(
+            
`${QueueService.API}/flowfile-queues/${queueListingRequest.connectionId}/listing-requests`,
+            {}
+        );
+    }
+
+    pollQueueListingRequest(listingRequest: ListingRequest): Observable<any> {
+        return this.httpClient.get(this.stripProtocol(listingRequest.uri));
+    }
+
+    deleteQueueListingRequest(listingRequest: ListingRequest): Observable<any> 
{
+        return this.httpClient.delete(this.stripProtocol(listingRequest.uri));
+    }
+
+    downloadContent(flowfileSummary: FlowFileSummary): void {
+        let dataUri: string = 
`${this.stripProtocol(flowfileSummary.uri)}/content`;
+
+        const queryParameters: any = {};
+
+        // TODO - flowFileSummary.clusterNodeId in query parameters
+
+        if (Object.keys(queryParameters).length > 0) {
+            const query: string = new 
URLSearchParams(queryParameters).toString();
+            dataUri = `${dataUri}?${query}`;
+        }
+
+        window.open(dataUri);
+    }
+
+    viewContent(flowfileSummary: FlowFileSummary, contentViewerUrl: string): 
void {
+        // build the uri to the data
+        let dataUri: string = 
`${this.stripProtocol(flowfileSummary.uri)}/content`;
+
+        const dataUriParameters: any = {};
+
+        // TODO - flowFileSummary.clusterNodeId in query parameters
+
+        // include parameters if necessary
+        if (Object.keys(dataUriParameters).length > 0) {
+            const dataUriQuery: string = new 
URLSearchParams(dataUriParameters).toString();
+            dataUri = `${dataUri}?${dataUriQuery}`;
+        }
+
+        // if there's already a query string don't add another ?... this 
assumes valid
+        // input meaning that if the url has already included a ? it also 
contains at
+        // least one query parameter
+        let contentViewer: string = contentViewerUrl;
+        if (contentViewer.indexOf('?') === -1) {
+            contentViewer += '?';
+        } else {
+            contentViewer += '&';
+        }
+
+        const contentViewerParameters: any = {
+            ref: dataUri
+        };
+
+        // open the content viewer
+        const contentViewerQuery: string = new 
URLSearchParams(contentViewerParameters).toString();
+        window.open(`${contentViewer}${contentViewerQuery}`);
+    }
+}
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/provenance/state/index.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/index.ts
similarity index 52%
copy from 
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/provenance/state/index.ts
copy to 
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/index.ts
index 2fd8eb3b53..5809b3efe2 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/provenance/state/index.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/index.ts
@@ -16,27 +16,23 @@
  */
 
 /*
-  Parameter Contexts
+  Queue Listing
  */
 
 import { Action, combineReducers, createFeatureSelector } from '@ngrx/store';
-import { provenanceEventListingFeatureKey, ProvenanceEventListingState } from 
'./provenance-event-listing';
-import { provenanceEventListingReducer } from 
'./provenance-event-listing/provenance-event-listing.reducer';
-import { lineageFeatureKey, LineageState } from './lineage';
-import { lineageReducer } from './lineage/lineage.reducer';
+import { queueListingFeatureKey, QueueListingState } from './queue-listing';
+import { queueListingReducer } from './queue-listing/queue-listing.reducer';
 
-export const provenanceFeatureKey = 'provenance';
+export const queueFeatureKey = 'queue';
 
-export interface ProvenanceState {
-    [provenanceEventListingFeatureKey]: ProvenanceEventListingState;
-    [lineageFeatureKey]: LineageState;
+export interface QueueState {
+    [queueListingFeatureKey]: QueueListingState;
 }
 
-export function reducers(state: ProvenanceState | undefined, action: Action) {
+export function reducers(state: QueueState | undefined, action: Action) {
     return combineReducers({
-        [provenanceEventListingFeatureKey]: provenanceEventListingReducer,
-        [lineageFeatureKey]: lineageReducer
+        [queueListingFeatureKey]: queueListingReducer
     })(state, action);
 }
 
-export const selectProvenanceState = 
createFeatureSelector<ProvenanceState>(provenanceFeatureKey);
+export const selectQueueState = 
createFeatureSelector<QueueState>(queueFeatureKey);
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/index.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/index.ts
new file mode 100644
index 0000000000..0ce0492f81
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/index.ts
@@ -0,0 +1,109 @@
+/*
+ * 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.
+ */
+
+export const queueListingFeatureKey = 'queueListing';
+
+export interface FlowFile extends FlowFileSummary {
+    attributes: {
+        [key: string]: string;
+    };
+    contentClaimContainer?: string;
+    contentClaimSection?: string;
+    contentClaimIdentifier?: string;
+    contentClaimOffset?: number;
+    contentClaimFileSize?: string;
+    contentClaimFileSizeBytes?: number;
+}
+
+export interface FlowFileSummary {
+    uri: string;
+    uuid: string;
+    filename: string;
+    position?: number;
+    size: number;
+    queuedDuration: number;
+    lineageDuration: number;
+    penaltyExpiresIn: number;
+    penalized: boolean;
+    clusterNodeId?: string;
+    clusterNodeAddress?: string;
+}
+
+export interface QueueSize {
+    byteCount: number;
+    objectCount: number;
+}
+
+export interface ListingRequest {
+    id: string;
+    uri: string;
+    submissionTime: string;
+    lastUpdated: string;
+    percentCompleted: number;
+    finished: boolean;
+    failureReason: string;
+    maxResults: number;
+    sourceRunning: boolean;
+    destinationRunning: boolean;
+    state: string;
+    queueSize: QueueSize;
+    flowFileSummaries: FlowFileSummary[];
+}
+
+export interface ListingRequestEntity {
+    listingRequest: ListingRequest;
+}
+
+export interface LoadConnectionLabelRequest {
+    connectionId: string;
+}
+
+export interface LoadConnectionLabelResponse {
+    connectionLabel: string;
+}
+
+export interface SubmitQueueListingRequest {
+    connectionId: string;
+}
+
+export interface PollQueueListingSuccess {
+    requestEntity: ListingRequestEntity;
+}
+
+export interface ViewFlowFileRequest {
+    flowfileSummary: FlowFileSummary;
+}
+
+export interface DownloadFlowFileContentRequest {
+    flowfileSummary: FlowFileSummary;
+}
+
+export interface ViewFlowFileContentRequest {
+    flowfileSummary: FlowFileSummary;
+}
+
+export interface FlowFileDialogRequest {
+    flowfile: FlowFile;
+}
+
+export interface QueueListingState {
+    requestEntity: ListingRequestEntity | null;
+    connectionLabel: string;
+    loadedTimestamp: string;
+    error: string | null;
+    status: 'pending' | 'loading' | 'error' | 'success';
+}
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/queue-listing.actions.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/queue-listing.actions.ts
new file mode 100644
index 0000000000..65d587a196
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/queue-listing.actions.ts
@@ -0,0 +1,86 @@
+/*
+ * 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 {
+    DownloadFlowFileContentRequest,
+    FlowFileDialogRequest,
+    LoadConnectionLabelRequest,
+    LoadConnectionLabelResponse,
+    PollQueueListingSuccess,
+    SubmitQueueListingRequest,
+    ViewFlowFileContentRequest,
+    ViewFlowFileRequest
+} from './index';
+
+const QUEUE_PREFIX = '[Queue Listing]';
+
+export const loadConnectionLabel = createAction(
+    `[${QUEUE_PREFIX}] Load Connection Label`,
+    props<{ request: LoadConnectionLabelRequest }>()
+);
+
+export const loadConnectionLabelSuccess = createAction(
+    `[${QUEUE_PREFIX}] Load Connection Label Success`,
+    props<{ response: LoadConnectionLabelResponse }>()
+);
+
+export const queueListingApiError = createAction(`[${QUEUE_PREFIX}] Queue 
Error`, props<{ error: string }>());
+
+export const resetQueueListingState = createAction(`[${QUEUE_PREFIX}] Reset 
Queue Listing State`);
+
+export const submitQueueListingRequest = createAction(
+    `[${QUEUE_PREFIX}] Submit Queue Listing Request`,
+    props<{ request: SubmitQueueListingRequest }>()
+);
+
+export const resubmitQueueListingRequest = createAction(`[${QUEUE_PREFIX}] 
Resubmit Queue Listing Request`);
+
+export const submitQueueListingRequestSuccess = createAction(
+    `[${QUEUE_PREFIX}] Submit Queue Listing Request Success`,
+    props<{ response: PollQueueListingSuccess }>()
+);
+
+export const startPollingQueueListingRequest = createAction(`[${QUEUE_PREFIX}] 
Start Polling Queue Listing Request`);
+
+export const pollQueueListingRequest = createAction(`[${QUEUE_PREFIX}] Poll 
Queue Listing Request`);
+
+export const pollQueueListingRequestSuccess = createAction(
+    `[${QUEUE_PREFIX}] Poll Queue Listing Request Success`,
+    props<{ response: PollQueueListingSuccess }>()
+);
+
+export const stopPollingQueueListingRequest = createAction(`[${QUEUE_PREFIX}] 
Stop Polling Queue Listing Request`);
+
+export const deleteQueueListingRequest = createAction(`[${QUEUE_PREFIX}] 
Delete Queue Listing Request`);
+
+export const viewFlowFile = createAction(`[${QUEUE_PREFIX}] View FlowFile`, 
props<{ request: ViewFlowFileRequest }>());
+
+export const openFlowFileDialog = createAction(
+    `[${QUEUE_PREFIX}] Open FlowFile Dialog`,
+    props<{ request: FlowFileDialogRequest }>()
+);
+
+export const downloadFlowFileContent = createAction(
+    `[${QUEUE_PREFIX}] Download FlowFile Content`,
+    props<{ request: DownloadFlowFileContentRequest }>()
+);
+
+export const viewFlowFileContent = createAction(
+    `[${QUEUE_PREFIX}] View FlowFile Content`,
+    props<{ request: ViewFlowFileContentRequest }>()
+);
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/queue-listing.effects.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/queue-listing.effects.ts
new file mode 100644
index 0000000000..ca2638abcf
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/queue-listing.effects.ts
@@ -0,0 +1,322 @@
+/*
+ * 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, createEffect, ofType } from '@ngrx/effects';
+import * as QueueListingActions from './queue-listing.actions';
+import { Store } from '@ngrx/store';
+import { CanvasState } from '../../../flow-designer/state';
+import {
+    asyncScheduler,
+    catchError,
+    filter,
+    from,
+    interval,
+    map,
+    of,
+    switchMap,
+    take,
+    takeUntil,
+    tap,
+    withLatestFrom
+} from 'rxjs';
+import { selectConnectionIdFromRoute, selectListingRequestEntity } from 
'./queue-listing.selectors';
+import { QueueService } from '../../service/queue.service';
+import { ListingRequest } from './index';
+import { CancelDialog } from 
'../../../../ui/common/cancel-dialog/cancel-dialog.component';
+import { MatDialog } from '@angular/material/dialog';
+import { selectAbout } from '../../../../state/about/about.selectors';
+import { FlowFileDialog } from 
'../../ui/queue-listing/flowfile-dialog/flowfile-dialog.component';
+import { NiFiCommon } from '../../../../service/nifi-common.service';
+import { isDefinedAndNotNull } from '../../../../state/shared';
+
+@Injectable()
+export class QueueListingEffects {
+    constructor(
+        private actions$: Actions,
+        private store: Store<CanvasState>,
+        private queueService: QueueService,
+        private dialog: MatDialog,
+        private nifiCommon: NiFiCommon
+    ) {}
+
+    loadConnectionLabel$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(QueueListingActions.loadConnectionLabel),
+            map((action) => action.request),
+            switchMap((request) =>
+                
from(this.queueService.getConnection(request.connectionId)).pipe(
+                    map((response) => {
+                        const connection: any = response.component;
+
+                        let connectionLabel: string = 'Connection';
+                        if (!this.nifiCommon.isBlank(connection.name)) {
+                            connectionLabel = connection.name;
+                        } else if (connection.selectedRelationships) {
+                            connectionLabel = 
connection.selectedRelationships.join(', ');
+                        }
+
+                        return QueueListingActions.loadConnectionLabelSuccess({
+                            response: {
+                                connectionLabel
+                            }
+                        });
+                    }),
+                    catchError((error) =>
+                        of(
+                            QueueListingActions.loadConnectionLabelSuccess({
+                                response: {
+                                    connectionLabel: 'Connection'
+                                }
+                            })
+                        )
+                    )
+                )
+            )
+        )
+    );
+
+    submitQueueListingRequest$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(QueueListingActions.submitQueueListingRequest),
+            map((action) => action.request),
+            switchMap((request) => {
+                const dialogReference = this.dialog.open(CancelDialog, {
+                    data: {
+                        title: 'Queue Listing',
+                        message: 'Waiting for queue listing to complete...'
+                    },
+                    disableClose: true,
+                    panelClass: 'small-dialog'
+                });
+
+                
dialogReference.componentInstance.cancel.pipe(take(1)).subscribe(() => {
+                    
this.store.dispatch(QueueListingActions.stopPollingQueueListingRequest());
+                });
+
+                return 
from(this.queueService.submitQueueListingRequest(request)).pipe(
+                    map((response) =>
+                        QueueListingActions.submitQueueListingRequestSuccess({
+                            response: {
+                                requestEntity: response
+                            }
+                        })
+                    ),
+                    catchError((error) =>
+                        of(
+                            QueueListingActions.queueListingApiError({
+                                error: error.error
+                            })
+                        )
+                    )
+                );
+            })
+        )
+    );
+
+    resubmitQueueListingRequest$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(QueueListingActions.resubmitQueueListingRequest),
+            withLatestFrom(this.store.select(selectConnectionIdFromRoute)),
+            switchMap(([action, connectionId]) =>
+                of(QueueListingActions.submitQueueListingRequest({ request: { 
connectionId } }))
+            )
+        )
+    );
+
+    submitQueueListingRequestSuccess$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(QueueListingActions.submitQueueListingRequestSuccess),
+            map((action) => action.response),
+            switchMap((response) => {
+                const listingRequest: ListingRequest = 
response.requestEntity.listingRequest;
+                if (listingRequest.finished) {
+                    return of(QueueListingActions.deleteQueueListingRequest());
+                } else {
+                    return 
of(QueueListingActions.startPollingQueueListingRequest());
+                }
+            })
+        )
+    );
+
+    startPollingQueueListingRequest$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(QueueListingActions.startPollingQueueListingRequest),
+            switchMap(() =>
+                interval(2000, asyncScheduler).pipe(
+                    
takeUntil(this.actions$.pipe(ofType(QueueListingActions.stopPollingQueueListingRequest)))
+                )
+            ),
+            switchMap(() => of(QueueListingActions.pollQueueListingRequest()))
+        )
+    );
+
+    pollQueueListingRequest$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(QueueListingActions.pollQueueListingRequest),
+            
withLatestFrom(this.store.select(selectListingRequestEntity).pipe(isDefinedAndNotNull())),
+            switchMap(([action, requestEntity]) => {
+                return 
from(this.queueService.pollQueueListingRequest(requestEntity.listingRequest)).pipe(
+                    map((response) =>
+                        QueueListingActions.pollQueueListingRequestSuccess({
+                            response: {
+                                requestEntity: response
+                            }
+                        })
+                    ),
+                    catchError((error) =>
+                        of(
+                            QueueListingActions.queueListingApiError({
+                                error: error.error
+                            })
+                        )
+                    )
+                );
+            })
+        )
+    );
+
+    pollQueueListingRequestSuccess$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(QueueListingActions.pollQueueListingRequestSuccess),
+            map((action) => action.response),
+            filter((response) => 
response.requestEntity.listingRequest.finished),
+            switchMap((response) => 
of(QueueListingActions.stopPollingQueueListingRequest()))
+        )
+    );
+
+    stopPollingQueueListingRequest$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(QueueListingActions.stopPollingQueueListingRequest),
+            switchMap((response) => 
of(QueueListingActions.deleteQueueListingRequest()))
+        )
+    );
+
+    deleteQueueListingRequest$ = createEffect(
+        () =>
+            this.actions$.pipe(
+                ofType(QueueListingActions.deleteQueueListingRequest),
+                withLatestFrom(this.store.select(selectListingRequestEntity)),
+                tap(([action, requestEntity]) => {
+                    this.dialog.closeAll();
+
+                    if (requestEntity) {
+                        
this.queueService.deleteQueueListingRequest(requestEntity.listingRequest).subscribe();
+                    }
+                })
+            ),
+        { dispatch: false }
+    );
+
+    viewFlowFile$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(QueueListingActions.viewFlowFile),
+            map((action) => action.request),
+            switchMap((request) =>
+                
from(this.queueService.getFlowFile(request.flowfileSummary)).pipe(
+                    map((response) =>
+                        QueueListingActions.openFlowFileDialog({
+                            request: {
+                                flowfile: response.flowFile
+                            }
+                        })
+                    ),
+                    catchError((error) =>
+                        of(
+                            QueueListingActions.queueListingApiError({
+                                error: error.error
+                            })
+                        )
+                    )
+                )
+            )
+        )
+    );
+
+    openFlowFileDialog$ = createEffect(
+        () =>
+            this.actions$.pipe(
+                ofType(QueueListingActions.openFlowFileDialog),
+                map((action) => action.request),
+                withLatestFrom(this.store.select(selectAbout)),
+                filter((about) => about != null),
+                tap(([request, about]) => {
+                    const dialogReference = this.dialog.open(FlowFileDialog, {
+                        data: request,
+                        panelClass: 'large-dialog'
+                    });
+
+                    dialogReference.componentInstance.contentViewerAvailable = 
about?.contentViewerUrl != null ?? false;
+
+                    dialogReference.componentInstance.downloadContent
+                        .pipe(takeUntil(dialogReference.afterClosed()))
+                        .subscribe(() => {
+                            this.store.dispatch(
+                                QueueListingActions.downloadFlowFileContent({
+                                    request: { flowfileSummary: 
request.flowfile }
+                                })
+                            );
+                        });
+
+                    if (about) {
+                        dialogReference.componentInstance.viewContent
+                            .pipe(takeUntil(dialogReference.afterClosed()))
+                            .subscribe(() => {
+                                this.store.dispatch(
+                                    QueueListingActions.viewFlowFileContent({
+                                        request: { flowfileSummary: 
request.flowfile }
+                                    })
+                                );
+                            });
+                    }
+                })
+            ),
+        { dispatch: false }
+    );
+
+    downloadFlowFileContent$ = createEffect(
+        () =>
+            this.actions$.pipe(
+                ofType(QueueListingActions.downloadFlowFileContent),
+                map((action) => action.request),
+                tap((request) => 
this.queueService.downloadContent(request.flowfileSummary))
+            ),
+        { dispatch: false }
+    );
+
+    viewFlowFileContent$ = createEffect(
+        () =>
+            this.actions$.pipe(
+                ofType(QueueListingActions.viewFlowFileContent),
+                map((action) => action.request),
+                
withLatestFrom(this.store.select(selectAbout).pipe(isDefinedAndNotNull())),
+                tap(([request, about]) => {
+                    this.queueService.viewContent(request.flowfileSummary, 
about.contentViewerUrl);
+                })
+            ),
+        { dispatch: false }
+    );
+
+    queueListingApiError$ = createEffect(
+        () =>
+            this.actions$.pipe(
+                ofType(QueueListingActions.queueListingApiError),
+                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/queue/state/queue-listing/queue-listing.reducer.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/queue-listing.reducer.ts
new file mode 100644
index 0000000000..e45e94f6b7
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/queue-listing.reducer.ts
@@ -0,0 +1,62 @@
+/*
+ * 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 { QueueListingState } from './index';
+import {
+    pollQueueListingRequestSuccess,
+    submitQueueListingRequest,
+    submitQueueListingRequestSuccess,
+    resetQueueListingState,
+    queueListingApiError,
+    loadConnectionLabelSuccess
+} from './queue-listing.actions';
+
+export const initialState: QueueListingState = {
+    requestEntity: null,
+    connectionLabel: 'Connection',
+    loadedTimestamp: 'N/A',
+    error: null,
+    status: 'pending'
+};
+
+export const queueListingReducer = createReducer(
+    initialState,
+    on(loadConnectionLabelSuccess, (state, { response }) => ({
+        ...state,
+        connectionLabel: response.connectionLabel
+    })),
+    on(submitQueueListingRequest, (state, { request }) => ({
+        ...state,
+        status: 'loading' as const
+    })),
+    on(submitQueueListingRequestSuccess, pollQueueListingRequestSuccess, 
(state, { response }) => ({
+        ...state,
+        requestEntity: response.requestEntity,
+        loadedTimestamp: response.requestEntity.listingRequest.lastUpdated,
+        error: null,
+        status: 'success' as const
+    })),
+    on(queueListingApiError, (state, { error }) => ({
+        ...state,
+        error,
+        status: 'error' as const
+    })),
+    on(resetQueueListingState, (state) => ({
+        ...initialState
+    }))
+);
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/queue-listing.selectors.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/queue-listing.selectors.ts
new file mode 100644
index 0000000000..01f3e8af39
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/queue-listing.selectors.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 { createSelector } from '@ngrx/store';
+import { queueListingFeatureKey, QueueListingState } from './index';
+import { QueueState, selectQueueState } from '../index';
+import { selectCurrentRoute } from '../../../../state/router/router.selectors';
+
+export const selectQueueListingState = createSelector(
+    selectQueueState,
+    (state: QueueState) => state[queueListingFeatureKey]
+);
+
+export const selectListingRequestEntity = createSelector(
+    selectQueueListingState,
+    (state: QueueListingState) => state.requestEntity
+);
+
+export const selectStatus = createSelector(selectQueueListingState, (state: 
QueueListingState) => state.status);
+
+export const selectError = createSelector(selectQueueListingState, (state: 
QueueListingState) => state.error);
+
+export const selectConnectionLabel = createSelector(
+    selectQueueListingState,
+    (state: QueueListingState) => state.connectionLabel
+);
+
+export const selectLoadedTimestamp = createSelector(
+    selectQueueListingState,
+    (state: QueueListingState) => state.loadedTimestamp
+);
+
+export const selectConnectionIdFromRoute = createSelector(selectCurrentRoute, 
(route) => {
+    if (route?.params.connectionId != null) {
+        return route.params.connectionId;
+    }
+    return null;
+});
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-dialog/flowfile-dialog.component.html
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-dialog/flowfile-dialog.component.html
new file mode 100644
index 0000000000..2e17a72811
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-dialog/flowfile-dialog.component.html
@@ -0,0 +1,229 @@
+<!--
+  ~ 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>FlowFile</h2>
+<div class="flowfile">
+    <mat-dialog-content>
+        <mat-tab-group>
+            <mat-tab label="Details">
+                <div class="tab-content py-4">
+                    <div class="absolute inset-0 flex gap-x-4">
+                        <div class="w-full flex flex-col gap-y-3">
+                            <div class="flex flex-col">
+                                <div class="flowfile-header">FlowFile 
Details</div>
+                            </div>
+                            <div class="flex flex-col">
+                                <div>UUID</div>
+                                <ng-container
+                                    *ngTemplateOutlet="
+                                        formatValue;
+                                        context: { $implicit: 
request.flowfile.uuid }
+                                    "></ng-container>
+                            </div>
+                            <div class="flex flex-col">
+                                <div>Filename</div>
+                                <ng-container
+                                    *ngTemplateOutlet="
+                                        formatValue;
+                                        context: { $implicit: 
request.flowfile.filename }
+                                    "></ng-container>
+                            </div>
+                            <div class="flex flex-col">
+                                <div>File Size</div>
+                                <ng-container
+                                    *ngTemplateOutlet="
+                                        formatValue;
+                                        context: {
+                                            $implicit: request.flowfile.size,
+                                            title: request.flowfile.size + ' 
bytes'
+                                        }
+                                    "></ng-container>
+                            </div>
+                            <div class="flex flex-col">
+                                <div>Queue Position</div>
+                                <ng-container
+                                    *ngTemplateOutlet="
+                                        formatValue;
+                                        context: { $implicit: 
request.flowfile.position }
+                                    "></ng-container>
+                            </div>
+                            <div class="flex flex-col">
+                                <div>Queued Duration</div>
+                                <ng-container
+                                    *ngTemplateOutlet="
+                                        formatDuration;
+                                        context: { $implicit: 
request.flowfile.queuedDuration }
+                                    "></ng-container>
+                            </div>
+                            <div class="flex flex-col">
+                                <div>Lineage Duration</div>
+                                <ng-container
+                                    *ngTemplateOutlet="
+                                        formatDuration;
+                                        context: { $implicit: 
request.flowfile.lineageDuration }
+                                    "></ng-container>
+                            </div>
+                            <div class="flex flex-col">
+                                <div>Penalized</div>
+                                <div class="value">{{ 
request.flowfile.penalized ? 'Yes' : 'No' }}</div>
+                            </div>
+                            <ng-container 
*ngIf="request.flowfile.clusterNodeId">
+                                <div class="flex flex-col">
+                                    <div>Node Address</div>
+                                    <ng-container
+                                        *ngTemplateOutlet="
+                                            formatValue;
+                                            context: { $implicit: 
request.flowfile.clusterNodeAddress }
+                                        "></ng-container>
+                                </div>
+                            </ng-container>
+                            <ng-template #formatDuration let-duration>
+                                <ng-container *ngIf="duration != null; else 
noDuration">
+                                    <div class="value">{{ 
formatDurationValue(duration) }}</div>
+                                </ng-container>
+                                <ng-template #noDuration>
+                                    <div class="unset">No value set</div>
+                                </ng-template>
+                            </ng-template>
+                        </div>
+                        <div class="w-full flex flex-col gap-y-3">
+                            <div class="flex flex-col">
+                                <div class="flowfile-header">Content 
Claim</div>
+                            </div>
+                            <div
+                                class="flex flex-col gap-y-3"
+                                *ngIf="request.flowfile.contentClaimContainer 
!= null; else noContent">
+                                <div>
+                                    <div>Container</div>
+                                    <ng-container
+                                        *ngTemplateOutlet="
+                                            formatContentValue;
+                                            context: { $implicit: 
request.flowfile.contentClaimContainer }
+                                        "></ng-container>
+                                </div>
+                                <div>
+                                    <div>Section</div>
+                                    <ng-container
+                                        *ngTemplateOutlet="
+                                            formatContentValue;
+                                            context: { $implicit: 
request.flowfile.contentClaimSection }
+                                        "></ng-container>
+                                </div>
+                                <div>
+                                    <div>Identifier</div>
+                                    <ng-container
+                                        *ngTemplateOutlet="
+                                            formatContentValue;
+                                            context: { $implicit: 
request.flowfile.contentClaimIdentifier }
+                                        "></ng-container>
+                                </div>
+                                <div>
+                                    <div>Offset</div>
+                                    <ng-container
+                                        *ngTemplateOutlet="
+                                            formatContentValue;
+                                            context: { $implicit: 
request.flowfile.contentClaimOffset }
+                                        "></ng-container>
+                                </div>
+                                <div>
+                                    <div>Size</div>
+                                    <ng-container
+                                        *ngTemplateOutlet="
+                                            formatContentValue;
+                                            context: {
+                                                $implicit: 
request.flowfile.contentClaimFileSize,
+                                                title: 
request.flowfile.contentClaimFileSizeBytes + ' bytes'
+                                            }
+                                        "></ng-container>
+                                </div>
+                                <div class="flex">
+                                    <button color="accent" mat-raised-button 
(click)="downloadContentClicked()">
+                                        <i class="fa fa-download"></i>
+                                        Download
+                                    </button>
+                                    <button
+                                        *ngIf="contentViewerAvailable"
+                                        class="ml-3"
+                                        color="accent"
+                                        mat-raised-button
+                                        (click)="viewContentClicked()">
+                                        <i class="fa fa-eye"></i>
+                                        View
+                                    </button>
+                                </div>
+                            </div>
+                            <ng-template #noContent>
+                                <div class="unset">No Content Available</div>
+                            </ng-template>
+                        </div>
+                    </div>
+                </div>
+            </mat-tab>
+            <mat-tab label="Attributes">
+                <div class="tab-content py-4">
+                    <div class="absolute inset-0 flex flex-col gap-y-4">
+                        <div class="flex">
+                            <div class="flowfile-header">Attribute Values</div>
+                        </div>
+                        <div class="flex flex-col">
+                            <div *ngFor="let attribute of 
request.flowfile.attributes | keyvalue">
+                                <div class="mb-4 flex flex-col">
+                                    <div>{{ attribute.key }}</div>
+                                    <ng-container
+                                        *ngTemplateOutlet="
+                                            formatValue;
+                                            context: { $implicit: 
attribute.value }
+                                        "></ng-container>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </mat-tab>
+        </mat-tab-group>
+        <ng-template #formatValue let-value let-title="title">
+            <ng-container *ngIf="value != null; else nullValue">
+                <ng-container *ngIf="value === ''; else nonEmptyValue">
+                    <div class="unset">Empty string set</div>
+                </ng-container>
+                <ng-template #nonEmptyValue>
+                    <div class="value" *ngIf="title == null; else 
valueWithTitle">{{ value }}</div>
+                    <ng-template #valueWithTitle>
+                        <div class="value" [title]="title">{{ value }}</div>
+                    </ng-template>
+                </ng-template>
+            </ng-container>
+            <ng-template #nullValue>
+                <div class="unset">No value set</div>
+            </ng-template>
+        </ng-template>
+        <ng-template #formatContentValue let-value let-title="title">
+            <ng-container *ngIf="value != null; else nullValue">
+                <div class="value" *ngIf="title == null; else 
valueWithTitle">{{ value }}</div>
+                <ng-template #valueWithTitle>
+                    <div class="value" [title]="title">{{ value }}</div>
+                </ng-template>
+            </ng-container>
+            <ng-template #nullValue>
+                <div class="unset">No value previously set</div>
+            </ng-template>
+        </ng-template>
+    </mat-dialog-content>
+    <mat-dialog-actions align="end">
+        <button color="primary" mat-raised-button mat-dialog-close>Ok</button>
+    </mat-dialog-actions>
+</div>
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-dialog/flowfile-dialog.component.scss
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-dialog/flowfile-dialog.component.scss
new file mode 100644
index 0000000000..665cb9f029
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-dialog/flowfile-dialog.component.scss
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+.flowfile {
+    @include mat.button-density(-1);
+
+    .mdc-dialog__content {
+        padding: 0 16px;
+        font-size: 14px;
+
+        .tab-content {
+            position: relative;
+            height: 475px;
+            overflow-y: auto;
+
+            .flowfile-header {
+                color: #728e9b;
+                font-size: 15px;
+                font-family: 'Roboto Slab';
+                font-style: normal;
+                font-weight: bold;
+            }
+        }
+    }
+}
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-dialog/flowfile-dialog.component.spec.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-dialog/flowfile-dialog.component.spec.ts
new file mode 100644
index 0000000000..9e8a68be3b
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-dialog/flowfile-dialog.component.spec.ts
@@ -0,0 +1,60 @@
+/*
+ * 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 { FlowFileDialog } from './flowfile-dialog.component';
+import { MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { FlowFileDialogRequest } from '../../../state/queue-listing';
+
+describe('FlowFileDialog', () => {
+    let component: FlowFileDialog;
+    let fixture: ComponentFixture<FlowFileDialog>;
+
+    const data: FlowFileDialogRequest = {
+        flowfile: {
+            uri: 
'https://localhost:4200/nifi-api/flowfile-queues/eea858d0-018c-1000-57fe-66ba110b3bcb/flowfiles/fc165889-3493-404c-9895-62d49a06801b',
+            uuid: 'fc165889-3493-404c-9895-62d49a06801b',
+            filename: '93a06a31-6b50-4d04-9b45-6a2a4a5e2dd1',
+            size: 0,
+            queuedDuration: 172947006,
+            lineageDuration: 172947006,
+            penaltyExpiresIn: 0,
+            attributes: {
+                path: './',
+                filename: '93a06a31-6b50-4d04-9b45-6a2a4a5e2dd1',
+                uuid: 'fc165889-3493-404c-9895-62d49a06801b'
+            },
+            penalized: false
+        }
+    };
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            imports: [FlowFileDialog, BrowserAnimationsModule],
+            providers: [{ provide: MAT_DIALOG_DATA, useValue: data }]
+        });
+        fixture = TestBed.createComponent(FlowFileDialog);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it('should create', () => {
+        expect(component).toBeTruthy();
+    });
+});
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/provenance-event-dialog/provenance-event-dialog.component.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-dialog/flowfile-dialog.component.ts
similarity index 53%
copy from 
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/provenance-event-dialog/provenance-event-dialog.component.ts
copy to 
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-dialog/flowfile-dialog.component.ts
index 7d8f2176f9..23fdd7472f 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/provenance-event-dialog/provenance-event-dialog.component.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-dialog/flowfile-dialog.component.ts
@@ -17,20 +17,20 @@
 
 import { Component, EventEmitter, Inject, Input, Output } from '@angular/core';
 import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
-import { FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
 import { MatInputModule } from '@angular/material/input';
 import { MatCheckboxModule } from '@angular/material/checkbox';
 import { MatButtonModule } from '@angular/material/button';
-import { AsyncPipe, NgForOf, NgIf, NgTemplateOutlet } from '@angular/common';
+import { AsyncPipe, KeyValuePipe, NgForOf, NgIf, NgTemplateOutlet } from 
'@angular/common';
 import { MatDatepickerModule } from '@angular/material/datepicker';
-import { NiFiCommon } from '../../../service/nifi-common.service';
 import { MatTabsModule } from '@angular/material/tabs';
-import { Attribute, ProvenanceEvent, ProvenanceEventDialogRequest } from 
'../../../state/shared';
+import { FlowFileDialogRequest } from '../../../state/queue-listing';
+import { NiFiCommon } from '../../../../../service/nifi-common.service';
 
 @Component({
-    selector: 'provenance-event-dialog',
+    selector: 'flowfile-dialog',
     standalone: true,
-    templateUrl: './provenance-event-dialog.component.html',
+    templateUrl: './flowfile-dialog.component.html',
     imports: [
         ReactiveFormsModule,
         MatDialogModule,
@@ -43,21 +43,19 @@ import { Attribute, ProvenanceEvent, 
ProvenanceEventDialogRequest } from '../../
         MatDatepickerModule,
         MatTabsModule,
         NgTemplateOutlet,
-        FormsModule
+        FormsModule,
+        KeyValuePipe
     ],
-    styleUrls: ['./provenance-event-dialog.component.scss']
+    styleUrls: ['./flowfile-dialog.component.scss']
 })
-export class ProvenanceEventDialog {
+export class FlowFileDialog {
     @Input() contentViewerAvailable!: boolean;
 
-    @Output() downloadContent: EventEmitter<string> = new 
EventEmitter<string>();
-    @Output() viewContent: EventEmitter<string> = new EventEmitter<string>();
-    @Output() replay: EventEmitter<void> = new EventEmitter<void>();
-
-    onlyShowModifiedAttributes: boolean = false;
+    @Output() downloadContent: EventEmitter<void> = new EventEmitter<void>();
+    @Output() viewContent: EventEmitter<void> = new EventEmitter<void>();
 
     constructor(
-        @Inject(MAT_DIALOG_DATA) public request: ProvenanceEventDialogRequest,
+        @Inject(MAT_DIALOG_DATA) public request: FlowFileDialogRequest,
         private nifiCommon: NiFiCommon
     ) {}
 
@@ -69,30 +67,11 @@ export class ProvenanceEventDialog {
         return this.nifiCommon.formatDuration(duration);
     }
 
-    attributeValueChanged(attribute: Attribute): boolean {
-        return attribute.value != attribute.previousValue;
-    }
-
-    shouldShowAttribute(attribute: Attribute): boolean {
-        // if the attribute value has changed, show it
-        if (this.attributeValueChanged(attribute)) {
-            return true;
-        }
-
-        // attribute value hasn't changed, only show when
-        // the user does not want to only see modified attributes
-        return !this.onlyShowModifiedAttributes;
-    }
-
-    downloadContentClicked(direction: string): void {
-        this.downloadContent.next(direction);
-    }
-
-    viewContentClicked(direction: string): void {
-        this.viewContent.next(direction);
+    downloadContentClicked(): void {
+        this.downloadContent.next();
     }
 
-    replayClicked(): void {
-        this.replay.next();
+    viewContentClicked(): void {
+        this.viewContent.next();
     }
 }
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-table/flowfile-table.component.html
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-table/flowfile-table.component.html
new file mode 100644
index 0000000000..2b0f76c870
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-table/flowfile-table.component.html
@@ -0,0 +1,147 @@
+<!--
+  ~ 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="flowfile-table h-full flex flex-col gap-y-2">
+    <h3 class="text-xl bold queue-listing-header">{{ connectionLabel }}</h3>
+    <div class="flex justify-between">
+        <div class="value">
+            Display {{ displayObjectCount }} of {{ 
formatCount(queueSizeObjectCount) }} ({{
+                formatBytes(queueSizeByteCount)
+            }}) bytes
+        </div>
+        <div class="listing-message">
+            <ng-container *ngIf="sourceRunning && destinationRunning; else 
bothNotRunning">
+                The source and destination of this queue are currently 
running. This listing may no longer be accurate.
+            </ng-container>
+            <ng-template #bothNotRunning>
+                <ng-container *ngIf="sourceRunning; else sourceNotRunning">
+                    The source of this queue is currently running. This 
listing may no longer be accurate.
+                </ng-container>
+            </ng-template>
+            <ng-template #sourceNotRunning>
+                <ng-container *ngIf="destinationRunning">
+                    The destination of this queue is currently running. This 
listing may no longer be accurate.
+                </ng-container>
+            </ng-template>
+        </div>
+    </div>
+    <div class="flex-1 relative">
+        <div class="listing-table border absolute inset-0 overflow-y-auto">
+            <table mat-table [dataSource]="dataSource">
+                <!-- More Details Column -->
+                <ng-container matColumnDef="moreDetails">
+                    <th mat-header-cell *matHeaderCellDef></th>
+                    <td mat-cell *matCellDef="let item">
+                        <div
+                            class="pointer fa fa-info-circle"
+                            title="View Details"
+                            (click)="viewFlowFileClicked(item)"></div>
+                    </td>
+                </ng-container>
+
+                <!-- Position Column -->
+                <ng-container matColumnDef="position">
+                    <th mat-header-cell *matHeaderCellDef>Position</th>
+                    <td mat-cell *matCellDef="let item">
+                        {{ item.position }}
+                    </td>
+                </ng-container>
+
+                <!-- FlowFile Uuid Column -->
+                <ng-container matColumnDef="flowFileUuid">
+                    <th mat-header-cell *matHeaderCellDef>UUID</th>
+                    <td mat-cell *matCellDef="let item">
+                        {{ item.uuid }}
+                    </td>
+                </ng-container>
+
+                <!-- File Name Column -->
+                <ng-container matColumnDef="fileName">
+                    <th mat-header-cell *matHeaderCellDef>Filename</th>
+                    <td mat-cell *matCellDef="let item">
+                        {{ item.filename }}
+                    </td>
+                </ng-container>
+
+                <!-- File Size Column -->
+                <ng-container matColumnDef="fileSize">
+                    <th mat-header-cell *matHeaderCellDef>File Size</th>
+                    <td mat-cell *matCellDef="let item">
+                        {{ formatBytes(item.size) }}
+                    </td>
+                </ng-container>
+
+                <!-- Queued Duration Column -->
+                <ng-container matColumnDef="queuedDuration">
+                    <th mat-header-cell *matHeaderCellDef>Queued Duration</th>
+                    <td mat-cell *matCellDef="let item">
+                        {{ formatDuration(item.queuedDuration) }}
+                    </td>
+                </ng-container>
+
+                <!-- Lineage Duration Column -->
+                <ng-container matColumnDef="lineageDuration">
+                    <th mat-header-cell *matHeaderCellDef>Lineage Duration</th>
+                    <td mat-cell *matCellDef="let item">
+                        {{ formatDuration(item.lineageDuration) }}
+                    </td>
+                </ng-container>
+
+                <!-- Penalized Column -->
+                <ng-container matColumnDef="penalized">
+                    <th mat-header-cell *matHeaderCellDef>Penalized</th>
+                    <td mat-cell *matCellDef="let item">
+                        {{ item.penalized ? 'Yes' : 'No' }}
+                    </td>
+                </ng-container>
+
+                <!-- Actions Column -->
+                <ng-container matColumnDef="actions">
+                    <th mat-header-cell *matHeaderCellDef></th>
+                    <td mat-cell *matCellDef="let item">
+                        <div class="flex items-center gap-x-3">
+                            <div
+                                *ngIf="item.size > 0"
+                                class="pointer fa fa-download"
+                                title="Download content"
+                                (click)="downloadContentClicked(item)"></div>
+                            <div
+                                *ngIf="contentViewerAvailable && item.size > 0"
+                                class="pointer fa fa-eye"
+                                title="View content"
+                                (click)="viewContentClicked(item)"></div>
+                            <div
+                                
*ngIf="currentUser.provenancePermissions.canRead"
+                                class="pointer icon icon-provenance"
+                                title="Provenance"
+                                [routerLink]="['/provenance']"
+                                [queryParams]="{ flowFileUuid: item.uuid 
}"></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>
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-table/flowfile-table.component.scss
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-table/flowfile-table.component.scss
new file mode 100644
index 0000000000..158206f8a7
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-table/flowfile-table.component.scss
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.flowfile-table {
+    .queue-listing-header {
+        color: #728e9b;
+    }
+
+    .listing-message {
+        color: #ba554a;
+    }
+
+    .listing-table {
+        table {
+            .mat-column-moreDetails {
+                min-width: 50px;
+                width: 50px;
+            }
+
+            .mat-column-position {
+                min-width: 75px;
+                width: 75px;
+            }
+
+            .mat-column-penalized {
+                min-width: 85px;
+                width: 85px;
+            }
+
+            .mat-column-actions {
+                min-width: 100px;
+                width: 100px;
+            }
+        }
+    }
+}
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-table/flowfile-table.component.spec.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-table/flowfile-table.component.spec.ts
new file mode 100644
index 0000000000..fd491f6d65
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-table/flowfile-table.component.spec.ts
@@ -0,0 +1,40 @@
+/*
+ * 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 { FlowFileTable } from './flowfile-table.component';
+import { MatTableModule } from '@angular/material/table';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+
+describe('FlowFileTable', () => {
+    let component: FlowFileTable;
+    let fixture: ComponentFixture<FlowFileTable>;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            imports: [FlowFileTable, MatTableModule, BrowserAnimationsModule]
+        });
+        fixture = TestBed.createComponent(FlowFileTable);
+        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/queue/ui/queue-listing/flowfile-table/flowfile-table.component.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-table/flowfile-table.component.ts
new file mode 100644
index 0000000000..0f0480a3cf
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-table/flowfile-table.component.ts
@@ -0,0 +1,142 @@
+/*
+ * 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 { AfterViewInit, Component, EventEmitter, Input, Output } from 
'@angular/core';
+import { MatTableDataSource, MatTableModule } from '@angular/material/table';
+import { TextTip } from 
'../../../../../ui/common/tooltips/text-tip/text-tip.component';
+import { BulletinsTip } from 
'../../../../../ui/common/tooltips/bulletins-tip/bulletins-tip.component';
+import { ValidationErrorsTip } from 
'../../../../../ui/common/tooltips/validation-errors-tip/validation-errors-tip.component';
+import { NiFiCommon } from '../../../../../service/nifi-common.service';
+import { NgForOf, NgIf } from '@angular/common';
+import { ProvenanceEventSummary } from '../../../../../state/shared';
+import { RouterLink } from '@angular/router';
+import { FlowFileSummary, ListingRequest } from '../../../state/queue-listing';
+import { CurrentUser } from '../../../../../state/current-user';
+import { Flow } from '../../../../flow-designer/state/flow';
+
+@Component({
+    selector: 'flowfile-table',
+    standalone: true,
+    templateUrl: './flowfile-table.component.html',
+    imports: [MatTableModule, NgForOf, NgIf, RouterLink],
+    styleUrls: ['./flowfile-table.component.scss', 
'../../../../../../assets/styles/listing-table.scss']
+})
+export class FlowFileTable implements AfterViewInit {
+    @Input() connectionLabel!: string;
+    @Input() set listingRequest(listingRequest: ListingRequest) {
+        if (listingRequest.flowFileSummaries) {
+            this.dataSource.data = 
this.sortFlowFiles(listingRequest.flowFileSummaries);
+
+            this.displayObjectCount = this.dataSource.data.length;
+            this.queueSizeObjectCount = listingRequest.queueSize.objectCount;
+            this.queueSizeByteCount = listingRequest.queueSize.byteCount;
+
+            this.sourceRunning = listingRequest.sourceRunning;
+            this.destinationRunning = listingRequest.destinationRunning;
+        }
+    }
+    @Input() currentUser!: CurrentUser;
+    @Input() contentViewerAvailable!: boolean;
+
+    @Output() viewFlowFile: EventEmitter<FlowFileSummary> = new 
EventEmitter<FlowFileSummary>();
+    @Output() downloadContent: EventEmitter<FlowFileSummary> = new 
EventEmitter<FlowFileSummary>();
+    @Output() viewContent: EventEmitter<FlowFileSummary> = new 
EventEmitter<FlowFileSummary>();
+
+    protected readonly TextTip = TextTip;
+    protected readonly BulletinsTip = BulletinsTip;
+    protected readonly ValidationErrorsTip = ValidationErrorsTip;
+
+    // TODO - conditionally include the cluster column
+    displayedColumns: string[] = [
+        'moreDetails',
+        'position',
+        'flowFileUuid',
+        'fileName',
+        'fileSize',
+        'queuedDuration',
+        'lineageDuration',
+        'penalized',
+        'actions'
+    ];
+    dataSource: MatTableDataSource<FlowFileSummary> = new 
MatTableDataSource<FlowFileSummary>();
+    selectedUuid: string | null = null;
+
+    sourceRunning: boolean = false;
+    destinationRunning: boolean = false;
+
+    displayObjectCount: number = 0;
+    queueSizeObjectCount: number = 0;
+    queueSizeByteCount: number = 0;
+
+    constructor(private nifiCommon: NiFiCommon) {}
+
+    ngAfterViewInit(): void {}
+
+    sortFlowFiles(summaries: FlowFileSummary[]): FlowFileSummary[] {
+        const data: FlowFileSummary[] = summaries.slice();
+        return data.sort((a: FlowFileSummary, b: FlowFileSummary) => {
+            const aIsUndefined: boolean = typeof a.position === 'undefined';
+            const bIsUndefined: boolean = typeof b.position === 'undefined';
+
+            if (aIsUndefined && bIsUndefined) {
+                return 0;
+            } else if (aIsUndefined) {
+                return 1;
+            } else if (bIsUndefined) {
+                return -1;
+            }
+
+            // @ts-ignore
+            return this.nifiCommon.compareNumber(a.position, b.position);
+        });
+    }
+
+    formatBytes(size: number): string {
+        return this.nifiCommon.formatDataSize(size);
+    }
+
+    formatCount(count: number): string {
+        return this.nifiCommon.formatInteger(count);
+    }
+
+    formatDuration(duration: number): string {
+        return this.nifiCommon.formatDuration(duration);
+    }
+
+    select(summary: FlowFileSummary): void {
+        this.selectedUuid = summary.uuid;
+    }
+
+    isSelected(summary: FlowFileSummary): boolean {
+        if (this.selectedUuid) {
+            return summary.uuid == this.selectedUuid;
+        }
+        return false;
+    }
+
+    viewFlowFileClicked(summary: FlowFileSummary): void {
+        this.viewFlowFile.next(summary);
+    }
+
+    downloadContentClicked(summary: FlowFileSummary): void {
+        this.downloadContent.next(summary);
+    }
+
+    viewContentClicked(summary: FlowFileSummary): void {
+        this.viewContent.next(summary);
+    }
+}
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/queue-listing-routing.module.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/queue-listing-routing.module.ts
new file mode 100644
index 0000000000..a59d048f50
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/queue-listing-routing.module.ts
@@ -0,0 +1,33 @@
+/*
+ * 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 { RouterModule, Routes } from '@angular/router';
+import { QueueListing } from './queue-listing.component';
+
+const routes: Routes = [
+    {
+        path: ':connectionId',
+        component: QueueListing
+    }
+];
+
+@NgModule({
+    imports: [RouterModule.forChild(routes)],
+    exports: [RouterModule]
+})
+export class QueueListingRoutingModule {}
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/queue-listing.component.html
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/queue-listing.component.html
new file mode 100644
index 0000000000..c5824e47dc
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/queue-listing.component.html
@@ -0,0 +1,48 @@
+<!--
+  ~ 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="flex flex-col gap-y-2 h-full" *ngIf="status$ | async; let status">
+    <div class="flex-1">
+        <div class="value" *ngIf="status === 'error'; else noError">
+            {{ error$ | async }}
+        </div>
+        <ng-template #noError>
+            <ng-container *ngIf="listingRequestEntity$ | async as entity; else 
initialLoading">
+                <flowfile-table
+                    [connectionLabel]="(connectionLabel$ | async)!"
+                    [listingRequest]="entity.listingRequest"
+                    [currentUser]="(currentUser$ | async)!"
+                    [contentViewerAvailable]="contentViewerAvailable((about$ | 
async)!)"
+                    (viewFlowFile)="viewFlowFile($event)"
+                    (downloadContent)="downloadContent($event)"
+                    (viewContent)="viewContent($event)"></flowfile-table>
+            </ng-container>
+            <ng-template #initialLoading>
+                <ngx-skeleton-loader count="3"></ngx-skeleton-loader>
+            </ng-template>
+        </ng-template>
+    </div>
+    <div class="flex justify-between">
+        <div class="flex items-center gap-x-2">
+            <button class="nifi-button" (click)="refreshClicked()">
+                <i class="fa fa-refresh" [class.fa-spin]="status === 
'loading'"></i>
+            </button>
+            <div>Last updated:</div>
+            <div class="refresh-timestamp">{{ loadedTimestamp$ | async }}</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/queue/ui/queue-listing/queue-listing.component.scss
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/queue-listing.component.scss
new file mode 100644
index 0000000000..2944f98194
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/queue-listing.component.scss
@@ -0,0 +1,16 @@
+/*
+ * 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.
+ */
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/queue-listing.component.spec.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/queue-listing.component.spec.ts
new file mode 100644
index 0000000000..041ef817fe
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/queue-listing.component.spec.ts
@@ -0,0 +1,41 @@
+/*
+ * 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 { QueueListing } from './queue-listing.component';
+import { provideMockStore } from '@ngrx/store/testing';
+import { initialState } from '../../state/queue-listing/queue-listing.reducer';
+
+describe('QueueListing', () => {
+    let component: QueueListing;
+    let fixture: ComponentFixture<QueueListing>;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            declarations: [QueueListing],
+            providers: [provideMockStore({ initialState })]
+        });
+        fixture = TestBed.createComponent(QueueListing);
+        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/queue/ui/queue-listing/queue-listing.component.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/queue-listing.component.ts
new file mode 100644
index 0000000000..2f48c84425
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/queue-listing.component.ts
@@ -0,0 +1,108 @@
+/*
+ * 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 } from '@angular/core';
+import { Store } from '@ngrx/store';
+import { distinctUntilChanged, filter } from 'rxjs';
+import {
+    selectConnectionIdFromRoute,
+    selectConnectionLabel,
+    selectError,
+    selectListingRequestEntity,
+    selectLoadedTimestamp,
+    selectStatus
+} from '../../state/queue-listing/queue-listing.selectors';
+import { FlowFileSummary } from '../../state/queue-listing';
+import {
+    downloadFlowFileContent,
+    loadConnectionLabel,
+    resetQueueListingState,
+    resubmitQueueListingRequest,
+    submitQueueListingRequest,
+    viewFlowFile,
+    viewFlowFileContent
+} from '../../state/queue-listing/queue-listing.actions';
+import { selectCurrentUser } from 
'../../../../state/current-user/current-user.selectors';
+import { NiFiState } from '../../../../state';
+import { selectAbout } from '../../../../state/about/about.selectors';
+import { About } from '../../../../state/about';
+import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
+
+@Component({
+    selector: 'queue-listing',
+    templateUrl: './queue-listing.component.html',
+    styleUrls: ['./queue-listing.component.scss']
+})
+export class QueueListing implements OnDestroy {
+    status$ = this.store.select(selectStatus);
+    error$ = this.store.select(selectError);
+    connectionLabel$ = this.store.select(selectConnectionLabel);
+    loadedTimestamp$ = this.store.select(selectLoadedTimestamp);
+    listingRequestEntity$ = this.store.select(selectListingRequestEntity);
+    currentUser$ = this.store.select(selectCurrentUser);
+    about$ = this.store.select(selectAbout);
+
+    constructor(private store: Store<NiFiState>) {
+        this.store
+            .select(selectConnectionIdFromRoute)
+            .pipe(
+                filter((connectionId) => connectionId != null),
+                distinctUntilChanged(),
+                takeUntilDestroyed()
+            )
+            .subscribe((connectionId) => {
+                this.store.dispatch(
+                    loadConnectionLabel({
+                        request: {
+                            connectionId
+                        }
+                    })
+                );
+                this.store.dispatch(
+                    submitQueueListingRequest({
+                        request: {
+                            connectionId
+                        }
+                    })
+                );
+            });
+    }
+
+    refreshClicked(): void {
+        this.store.dispatch(resubmitQueueListingRequest());
+    }
+
+    contentViewerAvailable(about: About): boolean {
+        return about.contentViewerUrl != null;
+    }
+
+    viewFlowFile(flowfileSummary: FlowFileSummary): void {
+        this.store.dispatch(viewFlowFile({ request: { flowfileSummary } }));
+    }
+
+    downloadContent(flowfileSummary: FlowFileSummary): void {
+        this.store.dispatch(downloadFlowFileContent({ request: { 
flowfileSummary } }));
+    }
+
+    viewContent(flowfileSummary: FlowFileSummary): void {
+        this.store.dispatch(viewFlowFileContent({ request: { flowfileSummary } 
}));
+    }
+
+    ngOnDestroy(): void {
+        this.store.dispatch(resetQueueListingState());
+    }
+}
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/queue-listing.module.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/queue-listing.module.ts
new file mode 100644
index 0000000000..a35f591f7f
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/queue-listing.module.ts
@@ -0,0 +1,43 @@
+/*
+ * 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 { QueueListing } from './queue-listing.component';
+import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
+import { NifiTooltipDirective } from 
'../../../../ui/common/tooltips/nifi-tooltip.directive';
+import { QueueListingRoutingModule } from './queue-listing-routing.module';
+import { FlowFileTable } from './flowfile-table/flowfile-table.component';
+import { StoreModule } from '@ngrx/store';
+import { EffectsModule } from '@ngrx/effects';
+import { queueFeatureKey, reducers } from '../../state';
+import { QueueListingEffects } from 
'../../state/queue-listing/queue-listing.effects';
+
+@NgModule({
+    declarations: [QueueListing],
+    exports: [QueueListing],
+    imports: [
+        CommonModule,
+        QueueListingRoutingModule,
+        NgxSkeletonLoaderModule,
+        NifiTooltipDirective,
+        FlowFileTable,
+        StoreModule.forFeature(queueFeatureKey, reducers),
+        EffectsModule.forFeature(QueueListingEffects)
+    ]
+})
+export class QueueListingModule {}
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/contoller-service-state/controller-service-state.effects.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/contoller-service-state/controller-service-state.effects.ts
index acc51cc8c5..3bdc332259 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/contoller-service-state/controller-service-state.effects.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/contoller-service-state/controller-service-state.effects.ts
@@ -37,7 +37,7 @@ import { selectControllerService, 
selectControllerServiceSetEnableRequest } from
 import { OkDialog } from '../../ui/common/ok-dialog/ok-dialog.component';
 import { MatDialog } from '@angular/material/dialog';
 import { ControllerServiceStateService } from 
'../../service/controller-service-state.service';
-import { ControllerServiceEntity, ControllerServiceReferencingComponentEntity 
} from '../shared';
+import { ControllerServiceEntity, ControllerServiceReferencingComponentEntity, 
isDefinedAndNotNull } from '../shared';
 import { SetEnableRequest, SetEnableStep } from './index';
 
 @Injectable()
@@ -53,12 +53,10 @@ export class ControllerServiceStateEffects {
         this.actions$.pipe(
             ofType(ControllerServiceActions.submitEnableRequest),
             map((action) => action.request),
-            withLatestFrom(this.store.select(selectControllerService)),
-            filter(([request, controllerService]) => !!controllerService),
+            
withLatestFrom(this.store.select(selectControllerService).pipe(isDefinedAndNotNull())),
             switchMap(([request, controllerService]) => {
                 if (
                     request.scope === 'SERVICE_AND_REFERENCING_COMPONENTS' &&
-                    // @ts-ignore
                     
this.hasUnauthorizedReferences(controllerService.component.referencingComponents)
                 ) {
                     return of(
@@ -79,10 +77,8 @@ export class ControllerServiceStateEffects {
     submitDisableRequest$ = createEffect(() =>
         this.actions$.pipe(
             ofType(ControllerServiceActions.submitDisableRequest),
-            withLatestFrom(this.store.select(selectControllerService)),
-            filter(([request, controllerService]) => !!controllerService),
+            
withLatestFrom(this.store.select(selectControllerService).pipe(isDefinedAndNotNull())),
             switchMap(([request, controllerService]) => {
-                // @ts-ignore
                 if 
(this.hasUnauthorizedReferences(controllerService.component.referencingComponents))
 {
                     return of(
                         ControllerServiceActions.setEnableStepFailure({
@@ -168,20 +164,16 @@ export class ControllerServiceStateEffects {
         this.actions$.pipe(
             ofType(ControllerServiceActions.pollControllerService),
             withLatestFrom(
-                this.store.select(selectControllerService),
+                
this.store.select(selectControllerService).pipe(isDefinedAndNotNull()),
                 this.store.select(selectControllerServiceSetEnableRequest)
             ),
-            filter(([action, controllerService, setEnableRequest]) => 
!!controllerService),
-            switchMap(([action, controllerService, setEnableRequest]) => {
-                // @ts-ignore
-                const cs: ControllerServiceEntity = controllerService;
-
-                return 
from(this.controllerServiceStateService.getControllerService(cs.id)).pipe(
+            switchMap(([action, controllerService, setEnableRequest]) =>
+                
from(this.controllerServiceStateService.getControllerService(controllerService.id)).pipe(
                     map((response) =>
                         ControllerServiceActions.pollControllerServiceSuccess({
                             response: {
                                 controllerService: response,
-                                currentStep: 
this.getNextStep(setEnableRequest, cs)
+                                currentStep: 
this.getNextStep(setEnableRequest, controllerService)
                             },
                             previousStep: setEnableRequest.currentStep
                         })
@@ -196,8 +188,8 @@ export class ControllerServiceStateEffects {
                             })
                         )
                     )
-                );
-            })
+                )
+            )
         )
     );
 
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/shared/index.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/shared/index.ts
index bfc6c16fd9..199e5b03c7 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/shared/index.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/shared/index.ts
@@ -15,6 +15,17 @@
  * limitations under the License.
  */
 
+import { filter, Observable } from 'rxjs';
+
+export function isDefinedAndNotNull<T>() {
+    return (source$: Observable<null | undefined | T>) =>
+        source$.pipe(
+            filter((input: null | undefined | T): input is T => {
+                return input !== null && typeof input !== undefined;
+            })
+        );
+}
+
 export interface OkDialogRequest {
     title: string;
     message: string;
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/provenance-event-dialog/provenance-event-dialog.component.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/provenance-event-dialog/provenance-event-dialog.component.ts
index 7d8f2176f9..f91e4b0b2c 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/provenance-event-dialog/provenance-event-dialog.component.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/provenance-event-dialog/provenance-event-dialog.component.ts
@@ -17,7 +17,7 @@
 
 import { Component, EventEmitter, Inject, Input, Output } from '@angular/core';
 import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
-import { FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { FormsModule } from '@angular/forms';
 import { MatInputModule } from '@angular/material/input';
 import { MatCheckboxModule } from '@angular/material/checkbox';
 import { MatButtonModule } from '@angular/material/button';
@@ -25,14 +25,13 @@ import { AsyncPipe, NgForOf, NgIf, NgTemplateOutlet } from 
'@angular/common';
 import { MatDatepickerModule } from '@angular/material/datepicker';
 import { NiFiCommon } from '../../../service/nifi-common.service';
 import { MatTabsModule } from '@angular/material/tabs';
-import { Attribute, ProvenanceEvent, ProvenanceEventDialogRequest } from 
'../../../state/shared';
+import { Attribute, ProvenanceEventDialogRequest } from 
'../../../state/shared';
 
 @Component({
     selector: 'provenance-event-dialog',
     standalone: true,
     templateUrl: './provenance-event-dialog.component.html',
     imports: [
-        ReactiveFormsModule,
         MatDialogModule,
         MatInputModule,
         MatCheckboxModule,
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/styles/listing-table.scss
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/styles/listing-table.scss
index a11af25a18..1b26a24606 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/styles/listing-table.scss
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/assets/styles/listing-table.scss
@@ -46,14 +46,19 @@
             background-color: #f4f6f7;
         }
 
-        .fa,
-        .icon {
+        .fa {
             color: #004849;
             width: 10px;
             height: 14px;
             text-align: center;
         }
 
+        .icon {
+            color: #004849;
+            width: 10px;
+            text-align: center;
+        }
+
         .mat-column-moreDetails {
             min-width: 30px;
         }


Reply via email to