You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by rf...@apache.org on 2025/10/15 21:16:23 UTC

(nifi) branch main updated: NIFI-15089: Fixing conditional logic for rendering errors when loadin… (#10421)

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

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


The following commit(s) were added to refs/heads/main by this push:
     new 3279c5e881 NIFI-15089: Fixing conditional logic for rendering errors when loadin… (#10421)
3279c5e881 is described below

commit 3279c5e8818e77ee09cb4b3e5e449b7f132ed06f
Author: Matt Gilman <ma...@gmail.com>
AuthorDate: Wed Oct 15 17:16:17 2025 -0400

    NIFI-15089: Fixing conditional logic for rendering errors when loadin… (#10421)
    
    * NIFI-15089: Fixing conditional logic for rendering errors when loading data based on whether data is already loaded or not.
    
    * NIFI-15089: Fixing lint issues noted in review.
    
    This closes #10421
---
 .../state/bulletin-board/bulletin-board.actions.ts |   5 +
 .../bulletin-board/bulletin-board.effects.spec.ts  | 193 ++++++++++++++
 .../state/bulletin-board/bulletin-board.effects.ts |  30 ++-
 .../state/bulletin-board/bulletin-board.reducer.ts |   6 +
 .../bulletin-board/bulletin-board.selectors.ts     |   5 +-
 .../cluster-listing/cluster-listing.actions.ts     |   5 +
 .../cluster-listing.effects.spec.ts                | 227 +++++++++++++++++
 .../cluster-listing/cluster-listing.effects.ts     |  22 +-
 .../cluster-listing/cluster-listing.reducer.ts     |   6 +
 .../counter-listing/counter-listing.actions.ts     |   5 +
 .../counter-listing.effects.spec.ts                |  91 ++++++-
 .../counter-listing/counter-listing.effects.ts     |  27 +-
 .../counter-listing/counter-listing.reducer.ts     |   5 +
 .../counter-listing/counter-listing.selectors.ts   |   7 +-
 .../flow-configuration-history-listing.actions.ts  |   5 +
 ...w-configuration-history-listing.effects.spec.ts | 227 +++++++++++++++++
 .../flow-configuration-history-listing.effects.ts  |  27 +-
 .../flow-configuration-history-listing.reducer.ts  |   6 +
 .../controller-services.actions.ts                 |   5 +
 .../controller-services.effects.spec.ts            | 224 +++++++++++++++++
 .../controller-services.effects.ts                 |  29 ++-
 .../controller-services.reducer.ts                 |   5 +
 .../controller-services.selectors.ts               |   4 +-
 .../flow-designer/state/flow/flow.effects.spec.ts  |  65 ++++-
 .../pages/flow-designer/state/flow/flow.effects.ts |   8 +-
 .../flow-designer/state/flow/flow.selectors.ts     |   5 +
 .../manage-remote-ports.actions.ts                 |   5 +
 .../manage-remote-ports.effects.spec.ts            | 280 +++++++++++++++++++++
 .../manage-remote-ports.effects.ts                 |  27 +-
 .../manage-remote-ports.reducer.ts                 |   5 +
 .../manage-remote-ports.selectors.ts               |   5 +-
 .../parameter-context-listing.actions.ts           |   5 +
 .../parameter-context-listing.effects.spec.ts      | 154 +++++++++++-
 .../parameter-context-listing.effects.ts           |  27 +-
 .../parameter-context-listing.reducer.ts           |   5 +
 .../parameter-context-listing.selectors.ts         |   4 +-
 .../flow-analysis-rules.actions.ts                 |   5 +
 .../flow-analysis-rules.effects.spec.ts            | 202 +++++++++++++++
 .../flow-analysis-rules.effects.ts                 |  27 +-
 .../flow-analysis-rules.reducer.ts                 |   5 +
 .../flow-analysis-rules.selectors.ts               |   4 +-
 .../settings/state/general/general.effects.spec.ts | 122 +++++++++
 .../settings/state/general/general.effects.ts      |  10 +-
 .../settings/state/general/general.selectors.ts    |   2 -
 .../management-controller-services.actions.ts      |   5 +
 .../management-controller-services.effects.spec.ts | 206 +++++++++++++++
 .../management-controller-services.effects.ts      |  27 +-
 .../management-controller-services.reducer.ts      |   5 +
 .../management-controller-services.selectors.ts    |   4 +-
 .../parameter-providers.actions.ts                 |   7 +-
 .../parameter-providers.effects.spec.ts            | 204 +++++++++++++++
 .../parameter-providers.effects.ts                 |  34 ++-
 .../parameter-providers.reducer.ts                 |   6 +
 .../parameter-providers.selectors.ts               |   4 +-
 .../registry-clients/registry-clients.actions.ts   |   7 +-
 .../registry-clients.effects.spec.ts               | 227 +++++++++++++++++
 .../registry-clients/registry-clients.effects.ts   |  29 ++-
 .../registry-clients/registry-clients.reducer.ts   |   5 +
 .../registry-clients/registry-clients.selectors.ts |   5 +-
 .../reporting-tasks/reporting-tasks.actions.ts     |   7 +-
 .../reporting-tasks.effects.spec.ts                | 191 ++++++++++++++
 .../reporting-tasks/reporting-tasks.effects.ts     |  29 ++-
 .../reporting-tasks/reporting-tasks.reducer.ts     |   5 +
 .../reporting-tasks/reporting-tasks.selectors.ts   |   5 +-
 .../summary-listing/summary-listing.actions.ts     |   5 +
 .../summary-listing.effects.spec.ts                | 251 ++++++++++++++++++
 .../summary-listing/summary-listing.effects.ts     |  29 ++-
 .../summary-listing/summary-listing.reducer.ts     |   6 +
 .../state/user-listing/user-listing.actions.ts     |   5 +
 .../user-listing/user-listing.effects.spec.ts      | 186 ++++++++++++++
 .../state/user-listing/user-listing.effects.ts     |  22 +-
 .../state/user-listing/user-listing.reducer.ts     |   5 +
 .../state/user-listing/user-listing.selectors.ts   |   5 +-
 .../src/app/service/error-helper.service.spec.ts   | 226 +++++++++++++++++
 .../nifi/src/app/service/error-helper.service.ts   |   4 +-
 75 files changed, 3777 insertions(+), 117 deletions(-)

diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/bulletins/state/bulletin-board/bulletin-board.actions.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/bulletins/state/bulletin-board/bulletin-board.actions.ts
index 0d2fa79f3f..954fad9f6c 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/bulletins/state/bulletin-board/bulletin-board.actions.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/bulletins/state/bulletin-board/bulletin-board.actions.ts
@@ -30,6 +30,11 @@ export const loadBulletinBoardSuccess = createAction(
     props<{ response: LoadBulletinBoardResponse }>()
 );
 
+export const loadBulletinBoardError = createAction(
+    `${BULLETIN_BOARD_PREFIX} Load Bulletin Board Error`,
+    props<{ errorResponse: any; loadedTimestamp: string; status: 'pending' | 'success' }>()
+);
+
 export const resetBulletinBoardState = createAction(`${BULLETIN_BOARD_PREFIX} Reset Bulletin Board State`);
 
 export const clearBulletinBoard = createAction(`${BULLETIN_BOARD_PREFIX} Clear Bulletin Board`);
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/bulletins/state/bulletin-board/bulletin-board.effects.spec.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/bulletins/state/bulletin-board/bulletin-board.effects.spec.ts
new file mode 100644
index 0000000000..2cc682d539
--- /dev/null
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/bulletins/state/bulletin-board/bulletin-board.effects.spec.ts
@@ -0,0 +1,193 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { TestBed } from '@angular/core/testing';
+import { provideMockActions } from '@ngrx/effects/testing';
+import { MockStore, provideMockStore } from '@ngrx/store/testing';
+import { Action } from '@ngrx/store';
+import { ReplaySubject, of, take, throwError } from 'rxjs';
+import { HttpErrorResponse } from '@angular/common/http';
+
+import * as BulletinBoardActions from './bulletin-board.actions';
+import { BulletinBoardEffects } from './bulletin-board.effects';
+import { BulletinBoardService } from '../../service/bulletin-board.service';
+import { ErrorHelper } from '../../../../service/error-helper.service';
+import { initialBulletinBoardState } from './bulletin-board.reducer';
+
+describe('BulletinBoardEffects', () => {
+    interface SetupOptions {
+        bulletinBoardState?: any;
+    }
+
+    const mockResponse = {
+        bulletinBoard: {
+            generated: '2023-01-01 12:00:00 EST',
+            bulletins: []
+        }
+    };
+
+    async function setup({ bulletinBoardState = initialBulletinBoardState }: SetupOptions = {}) {
+        await TestBed.configureTestingModule({
+            providers: [
+                BulletinBoardEffects,
+                provideMockActions(() => action$),
+                provideMockStore({
+                    initialState: {
+                        bulletins: {
+                            'bulletin-board': bulletinBoardState
+                        }
+                    }
+                }),
+                { provide: BulletinBoardService, useValue: { getBulletins: jest.fn() } },
+                { provide: ErrorHelper, useValue: { handleLoadingError: jest.fn() } }
+            ]
+        }).compileComponents();
+
+        const store = TestBed.inject(MockStore);
+        const effects = TestBed.inject(BulletinBoardEffects);
+        const bulletinBoardService = TestBed.inject(BulletinBoardService);
+        const errorHelper = TestBed.inject(ErrorHelper);
+
+        return { effects, store, bulletinBoardService, errorHelper };
+    }
+
+    let action$: ReplaySubject<Action>;
+    beforeEach(() => {
+        action$ = new ReplaySubject<Action>();
+    });
+
+    it('should create', async () => {
+        const { effects } = await setup();
+        expect(effects).toBeTruthy();
+    });
+
+    describe('Load Bulletin Board', () => {
+        it('should load bulletin board successfully', async () => {
+            const { effects, bulletinBoardService } = await setup();
+
+            const request = { after: 1, limit: 10 };
+            action$.next(BulletinBoardActions.loadBulletinBoard({ request }));
+            jest.spyOn(bulletinBoardService, 'getBulletins').mockReturnValueOnce(of(mockResponse) as never);
+
+            const result = await new Promise((resolve) => effects.loadBulletinBoard$.pipe(take(1)).subscribe(resolve));
+
+            expect(result).toEqual(
+                BulletinBoardActions.loadBulletinBoardSuccess({
+                    response: {
+                        bulletinBoard: mockResponse.bulletinBoard,
+                        loadedTimestamp: mockResponse.bulletinBoard.generated
+                    }
+                })
+            );
+        });
+
+        it('should fail to load bulletin board on initial load with hasExistingData=false', async () => {
+            const { effects, bulletinBoardService } = await setup();
+
+            const request = { after: 1, limit: 10 };
+            action$.next(BulletinBoardActions.loadBulletinBoard({ request }));
+            const error = new HttpErrorResponse({ status: 500 });
+            jest.spyOn(bulletinBoardService, 'getBulletins').mockImplementationOnce(() => throwError(() => error));
+
+            const result = await new Promise((resolve) => effects.loadBulletinBoard$.pipe(take(1)).subscribe(resolve));
+
+            expect(result).toEqual(
+                BulletinBoardActions.loadBulletinBoardError({
+                    errorResponse: error,
+                    loadedTimestamp: initialBulletinBoardState.loadedTimestamp,
+                    status: 'pending'
+                })
+            );
+        });
+
+        it('should fail to load bulletin board on refresh with hasExistingData=true', async () => {
+            const stateWithData = { ...initialBulletinBoardState, loadedTimestamp: '2023-01-01 11:00:00 EST' };
+            const { effects, bulletinBoardService } = await setup({ bulletinBoardState: stateWithData });
+
+            const request = { after: 1, limit: 10 };
+            action$.next(BulletinBoardActions.loadBulletinBoard({ request }));
+            const error = new HttpErrorResponse({ status: 500 });
+            jest.spyOn(bulletinBoardService, 'getBulletins').mockImplementationOnce(() => throwError(() => error));
+
+            const result = await new Promise((resolve) => effects.loadBulletinBoard$.pipe(take(1)).subscribe(resolve));
+
+            expect(result).toEqual(
+                BulletinBoardActions.loadBulletinBoardError({
+                    errorResponse: error,
+                    loadedTimestamp: stateWithData.loadedTimestamp,
+                    status: 'success'
+                })
+            );
+        });
+    });
+
+    describe('Bulletin Board Listing Error', () => {
+        it('should handle bulletin board listing error for initial load', async () => {
+            const { effects, errorHelper } = await setup();
+
+            const error = new HttpErrorResponse({ status: 500 });
+            const errorAction = BulletinBoardActions.loadBulletinBoardSuccess({
+                response: {
+                    bulletinBoard: mockResponse.bulletinBoard,
+                    loadedTimestamp: mockResponse.bulletinBoard.generated
+                }
+            });
+            action$.next(
+                BulletinBoardActions.loadBulletinBoardError({
+                    errorResponse: error,
+                    loadedTimestamp: initialBulletinBoardState.loadedTimestamp,
+                    status: 'pending'
+                })
+            );
+            jest.spyOn(errorHelper, 'handleLoadingError').mockReturnValueOnce(errorAction);
+
+            const result = await new Promise((resolve) =>
+                effects.bulletinBoardListingError$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(errorHelper.handleLoadingError).toHaveBeenCalledWith(false, error);
+            expect(result).toEqual(errorAction);
+        });
+
+        it('should handle bulletin board listing error for refresh', async () => {
+            const { effects, errorHelper } = await setup();
+
+            const error = new HttpErrorResponse({ status: 500 });
+            const errorAction = BulletinBoardActions.loadBulletinBoardSuccess({
+                response: {
+                    bulletinBoard: mockResponse.bulletinBoard,
+                    loadedTimestamp: mockResponse.bulletinBoard.generated
+                }
+            });
+            action$.next(
+                BulletinBoardActions.loadBulletinBoardError({
+                    errorResponse: error,
+                    loadedTimestamp: '2023-01-01 11:00:00 EST',
+                    status: 'success'
+                })
+            );
+            jest.spyOn(errorHelper, 'handleLoadingError').mockReturnValueOnce(errorAction);
+
+            const result = await new Promise((resolve) =>
+                effects.bulletinBoardListingError$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(errorHelper.handleLoadingError).toHaveBeenCalledWith(true, error);
+            expect(result).toEqual(errorAction);
+        });
+    });
+});
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/bulletins/state/bulletin-board/bulletin-board.effects.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/bulletins/state/bulletin-board/bulletin-board.effects.ts
index a402f2c837..93525cc873 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/bulletins/state/bulletin-board/bulletin-board.effects.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/bulletins/state/bulletin-board/bulletin-board.effects.ts
@@ -23,10 +23,11 @@ import { NiFiState } from '../../../../state';
 import * as BulletinBoardActions from './bulletin-board.actions';
 import { asyncScheduler, catchError, from, interval, map, of, switchMap, takeUntil } from 'rxjs';
 import { BulletinBoardService } from '../../service/bulletin-board.service';
-import { selectBulletinBoardFilter, selectLastBulletinId, selectStatus } from './bulletin-board.selectors';
+import { selectBulletinBoardFilter, selectLastBulletinId, selectLoadedTimestamp } from './bulletin-board.selectors';
 import { LoadBulletinBoardRequest } from './index';
 import { ErrorHelper } from '../../../../service/error-helper.service';
 import { HttpErrorResponse } from '@angular/common/http';
+import { initialBulletinBoardState } from './bulletin-board.reducer';
 
 @Injectable()
 export class BulletinBoardEffects {
@@ -39,8 +40,8 @@ export class BulletinBoardEffects {
         this.actions$.pipe(
             ofType(BulletinBoardActions.loadBulletinBoard),
             map((action) => action.request),
-            concatLatestFrom(() => this.store.select(selectStatus)),
-            switchMap(([request, status]) =>
+            concatLatestFrom(() => this.store.select(selectLoadedTimestamp)),
+            switchMap(([request, loadedTimestamp]) =>
                 from(
                     this.bulletinBoardService.getBulletins(request).pipe(
                         map((response: any) =>
@@ -52,7 +53,16 @@ export class BulletinBoardEffects {
                             })
                         ),
                         catchError((errorResponse: HttpErrorResponse) =>
-                            of(this.errorHelper.handleLoadingError(status, errorResponse))
+                            of(
+                                BulletinBoardActions.loadBulletinBoardError({
+                                    errorResponse,
+                                    loadedTimestamp,
+                                    status:
+                                        loadedTimestamp !== initialBulletinBoardState.loadedTimestamp
+                                            ? 'success'
+                                            : 'pending'
+                                })
+                            )
                         )
                     )
                 )
@@ -60,6 +70,18 @@ export class BulletinBoardEffects {
         )
     );
 
+    bulletinBoardListingError$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(BulletinBoardActions.loadBulletinBoardError),
+            map((action) =>
+                this.errorHelper.handleLoadingError(
+                    action.loadedTimestamp !== initialBulletinBoardState.loadedTimestamp,
+                    action.errorResponse
+                )
+            )
+        )
+    );
+
     setBulletinBoardAutoRefresh$ = createEffect(() =>
         this.actions$.pipe(
             ofType(BulletinBoardActions.setBulletinBoardAutoRefresh),
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/bulletins/state/bulletin-board/bulletin-board.reducer.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/bulletins/state/bulletin-board/bulletin-board.reducer.ts
index 8a0eca6648..18fae2182d 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/bulletins/state/bulletin-board/bulletin-board.reducer.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/bulletins/state/bulletin-board/bulletin-board.reducer.ts
@@ -18,6 +18,7 @@
 import { BulletinBoardEvent, BulletinBoardItem, BulletinBoardState } from './index';
 import { createReducer, on } from '@ngrx/store';
 import {
+    loadBulletinBoardError,
     clearBulletinBoard,
     loadBulletinBoard,
     loadBulletinBoardSuccess,
@@ -69,6 +70,11 @@ export const bulletinBoardReducer = createReducer(
         };
     }),
 
+    on(loadBulletinBoardError, (state: BulletinBoardState, { status }) => ({
+        ...state,
+        status
+    })),
+
     on(resetBulletinBoardState, () => ({ ...initialBulletinBoardState })),
 
     on(clearBulletinBoard, (state) => ({
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/bulletins/state/bulletin-board/bulletin-board.selectors.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/bulletins/state/bulletin-board/bulletin-board.selectors.ts
index f4af3a5f5e..7e8b9260b3 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/bulletins/state/bulletin-board/bulletin-board.selectors.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/bulletins/state/bulletin-board/bulletin-board.selectors.ts
@@ -34,4 +34,7 @@ export const selectBulletinBoardFilter = createSelector(
     (state: BulletinBoardState) => state.filter
 );
 
-export const selectStatus = createSelector(selectBulletinBoardState, (state: BulletinBoardState) => state.status);
+export const selectLoadedTimestamp = createSelector(
+    selectBulletinBoardState,
+    (state: BulletinBoardState) => state.loadedTimestamp
+);
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/cluster/state/cluster-listing/cluster-listing.actions.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/cluster/state/cluster-listing/cluster-listing.actions.ts
index de2ebab642..47e63c1af3 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/cluster/state/cluster-listing/cluster-listing.actions.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/cluster/state/cluster-listing/cluster-listing.actions.ts
@@ -28,6 +28,11 @@ export const loadClusterListingSuccess = createAction(
     props<{ response: ClusterListingEntity }>()
 );
 
+export const loadClusterListingError = createAction(
+    `${CLUSTER_LISTING_PREFIX} Load Cluster Listing Error`,
+    props<{ errorResponse: any; loadedTimestamp: string; status: 'pending' | 'success' }>()
+);
+
 export const confirmAndDisconnectNode = createAction(
     `${CLUSTER_LISTING_PREFIX} Confirm And Disconnect Node`,
     props<{ request: ClusterNode }>()
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/cluster/state/cluster-listing/cluster-listing.effects.spec.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/cluster/state/cluster-listing/cluster-listing.effects.spec.ts
new file mode 100644
index 0000000000..afe69a9db6
--- /dev/null
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/cluster/state/cluster-listing/cluster-listing.effects.spec.ts
@@ -0,0 +1,227 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { TestBed } from '@angular/core/testing';
+import { ClusterListingEffects } from './cluster-listing.effects';
+import { provideMockActions } from '@ngrx/effects/testing';
+import { MockStore, provideMockStore } from '@ngrx/store/testing';
+import { initialClusterState } from './cluster-listing.reducer';
+import { of, ReplaySubject, take, throwError } from 'rxjs';
+import { Action } from '@ngrx/store';
+import * as ClusterListingActions from './cluster-listing.actions';
+import { ClusterService } from '../../service/cluster.service';
+import { MatDialog } from '@angular/material/dialog';
+import { ErrorHelper } from '../../../../service/error-helper.service';
+import { HttpErrorResponse } from '@angular/common/http';
+import { Router } from '@angular/router';
+import { clusterFeatureKey } from '../index';
+import { clusterListingFeatureKey } from './index';
+
+describe('ClusterListingEffects', () => {
+    interface SetupOptions {
+        clusterListingState?: any;
+    }
+
+    const mockClusterResponse = {
+        cluster: {
+            nodes: [
+                {
+                    nodeId: 'node-1',
+                    address: '192.168.1.100',
+                    apiPort: 8080,
+                    status: 'CONNECTED',
+                    heartbeat: '2023-01-01 12:00:00 EST',
+                    roles: ['Primary Node'],
+                    events: []
+                }
+            ],
+            generated: '2023-01-01 12:00:00 EST'
+        }
+    };
+
+    let action$: ReplaySubject<Action>;
+
+    async function setup({ clusterListingState = initialClusterState }: SetupOptions = {}) {
+        await TestBed.configureTestingModule({
+            providers: [
+                ClusterListingEffects,
+                provideMockActions(() => action$),
+                provideMockStore({
+                    initialState: {
+                        [clusterFeatureKey]: {
+                            [clusterListingFeatureKey]: clusterListingState
+                        }
+                    }
+                }),
+                {
+                    provide: ClusterService,
+                    useValue: {
+                        getClusterListing: jest.fn()
+                    }
+                },
+                {
+                    provide: MatDialog,
+                    useValue: {
+                        open: jest.fn()
+                    }
+                },
+                {
+                    provide: Router,
+                    useValue: {
+                        navigate: jest.fn()
+                    }
+                },
+                {
+                    provide: ErrorHelper,
+                    useValue: {
+                        handleLoadingError: jest.fn(),
+                        getErrorString: jest.fn()
+                    }
+                }
+            ]
+        }).compileComponents();
+
+        const store = TestBed.inject(MockStore);
+        const clusterService = TestBed.inject(ClusterService) as jest.Mocked<ClusterService>;
+        const errorHelper = TestBed.inject(ErrorHelper) as jest.Mocked<ErrorHelper>;
+        const effects = TestBed.inject(ClusterListingEffects);
+
+        return { store, clusterService, errorHelper, effects };
+    }
+
+    beforeEach(() => {
+        action$ = new ReplaySubject<Action>(1);
+    });
+
+    afterEach(() => {
+        action$.complete();
+    });
+
+    it('should be created', async () => {
+        const { effects } = await setup();
+        expect(effects).toBeTruthy();
+    });
+
+    describe('loadClusterListing', () => {
+        it('should load cluster listing successfully', async () => {
+            const { effects, clusterService } = await setup();
+
+            action$.next(ClusterListingActions.loadClusterListing());
+            jest.spyOn(clusterService, 'getClusterListing').mockReturnValueOnce(of(mockClusterResponse) as never);
+
+            const result = await new Promise((resolve) => effects.loadClusterListing$.pipe(take(1)).subscribe(resolve));
+
+            expect(result).toEqual(
+                ClusterListingActions.loadClusterListingSuccess({
+                    response: mockClusterResponse.cluster
+                })
+            );
+        });
+
+        it('should fail to load cluster listing on initial load (no existing data)', async () => {
+            const { effects, clusterService } = await setup();
+
+            action$.next(ClusterListingActions.loadClusterListing());
+            const error = new HttpErrorResponse({ status: 500 });
+            jest.spyOn(clusterService, 'getClusterListing').mockImplementationOnce(() => throwError(() => error));
+
+            const result = await new Promise((resolve) => effects.loadClusterListing$.pipe(take(1)).subscribe(resolve));
+
+            expect(result).toEqual(
+                ClusterListingActions.loadClusterListingError({
+                    errorResponse: error,
+                    loadedTimestamp: initialClusterState.loadedTimestamp,
+                    status: 'pending'
+                })
+            );
+        });
+
+        it('should fail to load cluster listing on refresh (existing data)', async () => {
+            const stateWithData = {
+                ...initialClusterState,
+                loadedTimestamp: '2023-01-01 11:00:00 EST'
+            };
+            const { effects, clusterService } = await setup({ clusterListingState: stateWithData });
+
+            action$.next(ClusterListingActions.loadClusterListing());
+            const error = new HttpErrorResponse({ status: 500 });
+            jest.spyOn(clusterService, 'getClusterListing').mockImplementationOnce(() => throwError(() => error));
+
+            const result = await new Promise((resolve) => effects.loadClusterListing$.pipe(take(1)).subscribe(resolve));
+
+            expect(result).toEqual(
+                ClusterListingActions.loadClusterListingError({
+                    errorResponse: error,
+                    loadedTimestamp: stateWithData.loadedTimestamp,
+                    status: 'success'
+                })
+            );
+        });
+    });
+
+    describe('loadClusterListingError', () => {
+        it('should handle loadClusterListingError with pending status', async () => {
+            const { effects, errorHelper } = await setup();
+
+            const error = new HttpErrorResponse({ status: 500 });
+            const errorAction = ClusterListingActions.loadClusterListingSuccess({
+                response: mockClusterResponse.cluster
+            });
+            jest.spyOn(errorHelper, 'handleLoadingError').mockReturnValueOnce(errorAction);
+
+            action$.next(
+                ClusterListingActions.loadClusterListingError({
+                    errorResponse: error,
+                    loadedTimestamp: '',
+                    status: 'pending'
+                })
+            );
+
+            const result = await new Promise((resolve) =>
+                effects.loadClusterListingError$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(errorHelper.handleLoadingError).toHaveBeenCalledWith(false, error);
+            expect(result).toEqual(errorAction);
+        });
+
+        it('should handle loadClusterListingError with success status', async () => {
+            const { effects, errorHelper } = await setup();
+
+            const error = new HttpErrorResponse({ status: 500 });
+            const errorAction = ClusterListingActions.loadClusterListingSuccess({
+                response: mockClusterResponse.cluster
+            });
+            jest.spyOn(errorHelper, 'handleLoadingError').mockReturnValueOnce(errorAction);
+
+            action$.next(
+                ClusterListingActions.loadClusterListingError({
+                    errorResponse: error,
+                    loadedTimestamp: '2023-01-01 11:00:00 EST',
+                    status: 'success'
+                })
+            );
+
+            const result = await new Promise((resolve) =>
+                effects.loadClusterListingError$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(errorHelper.handleLoadingError).toHaveBeenCalledWith(true, error);
+            expect(result).toEqual(errorAction);
+        });
+    });
+});
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/cluster/state/cluster-listing/cluster-listing.effects.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/cluster/state/cluster-listing/cluster-listing.effects.ts
index f4aac0841b..e6ae26c052 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/cluster/state/cluster-listing/cluster-listing.effects.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/cluster/state/cluster-listing/cluster-listing.effects.ts
@@ -20,11 +20,12 @@ import { Actions, createEffect, ofType } from '@ngrx/effects';
 import { ActionCreator, Creator, Store } from '@ngrx/store';
 import { NiFiState } from '../../../../state';
 import { ErrorHelper } from '../../../../service/error-helper.service';
+import { initialClusterState } from './cluster-listing.reducer';
 import { Router } from '@angular/router';
 import * as ClusterListingActions from './cluster-listing.actions';
 import { catchError, filter, from, map, of, switchMap, take, tap } from 'rxjs';
 import { HttpErrorResponse } from '@angular/common/http';
-import { selectClusterListingStatus } from './cluster-listing.selectors';
+import { selectClusterListingLoadedTimestamp } from './cluster-listing.selectors';
 import { reloadSystemDiagnostics } from '../../../../state/system-diagnostics/system-diagnostics.actions';
 import { ClusterService } from '../../service/cluster.service';
 import { MatDialog } from '@angular/material/dialog';
@@ -47,12 +48,18 @@ export class ClusterListingEffects {
     loadClusterListing$ = createEffect(() =>
         this.actions$.pipe(
             ofType(ClusterListingActions.loadClusterListing),
-            concatLatestFrom(() => [this.store.select(selectClusterListingStatus)]),
-            switchMap(([, listingStatus]) =>
+            concatLatestFrom(() => [this.store.select(selectClusterListingLoadedTimestamp)]),
+            switchMap(([, loadedTimestamp]) =>
                 from(this.clusterService.getClusterListing()).pipe(
                     map((response) => ClusterListingActions.loadClusterListingSuccess({ response: response.cluster })),
                     catchError((errorResponse: HttpErrorResponse) =>
-                        of(this.errorHelper.handleLoadingError(listingStatus, errorResponse))
+                        of(
+                            ClusterListingActions.loadClusterListingError({
+                                errorResponse,
+                                loadedTimestamp,
+                                status: loadedTimestamp !== initialClusterState.loadedTimestamp ? 'success' : 'pending'
+                            })
+                        )
                     )
                 )
             )
@@ -75,6 +82,13 @@ export class ClusterListingEffects {
         )
     );
 
+    loadClusterListingError$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(ClusterListingActions.loadClusterListingError),
+            map((action) => this.errorHelper.handleLoadingError(action.status === 'success', action.errorResponse))
+        )
+    );
+
     confirmAndDisconnectNode$ = createEffect(
         () =>
             this.actions$.pipe(
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/cluster/state/cluster-listing/cluster-listing.reducer.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/cluster/state/cluster-listing/cluster-listing.reducer.ts
index aac1eded30..9df15985e0 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/cluster/state/cluster-listing/cluster-listing.reducer.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/cluster/state/cluster-listing/cluster-listing.reducer.ts
@@ -21,6 +21,7 @@ import {
     connectNode,
     disconnectNode,
     loadClusterListing,
+    loadClusterListingError,
     loadClusterListingSuccess,
     removeNode,
     removeNodeSuccess,
@@ -55,6 +56,11 @@ export const clusterListingReducer = createReducer(
         status: 'success' as const
     })),
 
+    on(loadClusterListingError, (state, { status }) => ({
+        ...state,
+        status
+    })),
+
     on(disconnectNode, connectNode, removeNode, (state) => ({
         ...state,
         saving: true
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/counters/state/counter-listing/counter-listing.actions.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/counters/state/counter-listing/counter-listing.actions.ts
index d30b84b043..908377dd31 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/counters/state/counter-listing/counter-listing.actions.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/counters/state/counter-listing/counter-listing.actions.ts
@@ -28,6 +28,11 @@ export const loadCountersSuccess = createAction(
     props<{ response: LoadCounterListingResponse }>()
 );
 
+export const loadCountersError = createAction(
+    `${COUNTER_PREFIX} Load Counters Error`,
+    props<{ errorResponse: any; loadedTimestamp: string; status: 'pending' | 'success' }>()
+);
+
 export const counterListingApiError = createAction(
     `${COUNTER_PREFIX} Load Counter Listing Error`,
     props<{ errorResponse: HttpErrorResponse }>()
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/counters/state/counter-listing/counter-listing.effects.spec.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/counters/state/counter-listing/counter-listing.effects.spec.ts
index 2a6f494b67..ca2235870d 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/counters/state/counter-listing/counter-listing.effects.spec.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/counters/state/counter-listing/counter-listing.effects.spec.ts
@@ -28,6 +28,7 @@ import { MatDialog } from '@angular/material/dialog';
 import { ErrorHelper } from '../../../../service/error-helper.service';
 import { HttpErrorResponse } from '@angular/common/http';
 import { CounterEntity } from './index';
+import { countersFeatureKey } from '../index';
 
 describe('CounterListingEffects', () => {
     let action$: ReplaySubject<Action>;
@@ -69,8 +70,8 @@ describe('CounterListingEffects', () => {
                 provideMockActions(() => action$),
                 provideMockStore({
                     initialState: {
-                        counters: {
-                            counters: counterListingState
+                        [countersFeatureKey]: {
+                            [countersFeatureKey]: counterListingState
                         }
                     }
                 }),
@@ -142,21 +143,99 @@ describe('CounterListingEffects', () => {
             );
         });
 
-        it('should fail to load counters', async () => {
-            const { action$, effects, countersService, errorHelper } = await setup();
+        it('should fail to load counters on initial load with hasExistingData=false', async () => {
+            const { action$, effects, countersService } = await setup();
 
             action$.next(CounterListingActions.loadCounters());
 
             const error = new HttpErrorResponse({ status: 500 });
-            const errorAction = CounterListingActions.counterListingApiError({ errorResponse: error });
 
             jest.spyOn(countersService, 'getCounters').mockImplementationOnce(() => {
                 return throwError(() => error);
             });
-            jest.spyOn(errorHelper, 'handleLoadingError').mockReturnValueOnce(errorAction);
 
             const result = await new Promise((resolve) => effects.loadCounters$.pipe(take(1)).subscribe(resolve));
 
+            expect(result).toEqual(
+                CounterListingActions.loadCountersError({
+                    errorResponse: error,
+                    loadedTimestamp: initialState.loadedTimestamp,
+                    status: 'pending'
+                })
+            );
+        });
+
+        it('should fail to load counters on refresh with hasExistingData=true', async () => {
+            const stateWithData = {
+                ...initialState,
+                loadedTimestamp: '2023-01-01 11:00:00 EST'
+            };
+            const { action$, effects, countersService } = await setup({
+                counterListingState: stateWithData
+            });
+
+            action$.next(CounterListingActions.loadCounters());
+
+            const error = new HttpErrorResponse({ status: 500 });
+
+            jest.spyOn(countersService, 'getCounters').mockImplementationOnce(() => {
+                return throwError(() => error);
+            });
+
+            const result = await new Promise((resolve) => effects.loadCounters$.pipe(take(1)).subscribe(resolve));
+
+            expect(result).toEqual(
+                CounterListingActions.loadCountersError({
+                    errorResponse: error,
+                    loadedTimestamp: stateWithData.loadedTimestamp,
+                    status: 'success'
+                })
+            );
+        });
+    });
+
+    describe('Counter Listing Error', () => {
+        it('should handle counter listing error for initial load', async () => {
+            const { action$, effects, errorHelper } = await setup();
+
+            const error = new HttpErrorResponse({ status: 500 });
+            const errorAction = CounterListingActions.counterListingApiError({ errorResponse: error });
+            action$.next(
+                CounterListingActions.loadCountersError({
+                    errorResponse: error,
+                    loadedTimestamp: initialState.loadedTimestamp,
+                    status: 'pending'
+                })
+            );
+            jest.spyOn(errorHelper, 'handleLoadingError').mockReturnValueOnce(errorAction);
+
+            const result = await new Promise((resolve) =>
+                effects.counterListingError$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(errorHelper.handleLoadingError).toHaveBeenCalledWith(false, error);
+            expect(result).toEqual(errorAction);
+        });
+
+        it('should handle counter listing error for refresh', async () => {
+            const { action$, effects, errorHelper } = await setup();
+
+            const error = new HttpErrorResponse({ status: 500 });
+            const errorAction = CounterListingActions.counterListingApiError({ errorResponse: error });
+            action$.next(
+                CounterListingActions.loadCountersError({
+                    errorResponse: error,
+                    loadedTimestamp: '2023-01-01 11:00:00 EST',
+                    status: 'success'
+                })
+            );
+            jest.spyOn(errorHelper, 'handleLoadingError').mockReturnValueOnce(errorAction);
+
+            const result = await new Promise((resolve) =>
+                effects.counterListingError$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(errorHelper.handleLoadingError).toHaveBeenCalledWith(true, error);
             expect(result).toEqual(errorAction);
         });
     });
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/counters/state/counter-listing/counter-listing.effects.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/counters/state/counter-listing/counter-listing.effects.ts
index 0bf5f254c9..8587e7e9d0 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/counters/state/counter-listing/counter-listing.effects.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/counters/state/counter-listing/counter-listing.effects.ts
@@ -28,7 +28,8 @@ import { SMALL_DIALOG, YesNoDialog } from '@nifi/shared';
 import * as ErrorActions from '../../../../state/error/error.actions';
 import { ErrorHelper } from '../../../../service/error-helper.service';
 import { HttpErrorResponse } from '@angular/common/http';
-import { selectStatus } from './counter-listing.selectors';
+import { selectLoadedTimestamp } from './counter-listing.selectors';
+import { initialState } from './counter-listing.reducer';
 
 @Injectable()
 export class CounterListingEffects {
@@ -41,8 +42,8 @@ export class CounterListingEffects {
     loadCounters$ = createEffect(() =>
         this.actions$.pipe(
             ofType(CounterListingActions.loadCounters),
-            concatLatestFrom(() => this.store.select(selectStatus)),
-            switchMap(([, status]) =>
+            concatLatestFrom(() => this.store.select(selectLoadedTimestamp)),
+            switchMap(([, loadedTimestamp]) =>
                 from(this.countersService.getCounters()).pipe(
                     map((response) =>
                         CounterListingActions.loadCountersSuccess({
@@ -53,13 +54,31 @@ export class CounterListingEffects {
                         })
                     ),
                     catchError((errorResponse: HttpErrorResponse) =>
-                        of(this.errorHelper.handleLoadingError(status, errorResponse))
+                        of(
+                            CounterListingActions.loadCountersError({
+                                errorResponse,
+                                loadedTimestamp,
+                                status: loadedTimestamp !== initialState.loadedTimestamp ? 'success' : 'pending'
+                            })
+                        )
                     )
                 )
             )
         )
     );
 
+    counterListingError$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(CounterListingActions.loadCountersError),
+            map((action) =>
+                this.errorHelper.handleLoadingError(
+                    action.loadedTimestamp !== initialState.loadedTimestamp,
+                    action.errorResponse
+                )
+            )
+        )
+    );
+
     promptCounterReset$ = createEffect(
         () =>
             this.actions$.pipe(
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/counters/state/counter-listing/counter-listing.reducer.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/counters/state/counter-listing/counter-listing.reducer.ts
index 15c0d85507..b853be1b77 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/counters/state/counter-listing/counter-listing.reducer.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/counters/state/counter-listing/counter-listing.reducer.ts
@@ -19,6 +19,7 @@ import { CounterListingState } from './index';
 import { createReducer, on } from '@ngrx/store';
 import {
     counterListingApiError,
+    loadCountersError,
     loadCounters,
     loadCountersSuccess,
     resetAllCountersSuccess,
@@ -50,6 +51,10 @@ export const counterListingReducer = createReducer(
         ...state,
         saving: false
     })),
+    on(loadCountersError, (state, { status }) => ({
+        ...state,
+        status
+    })),
     on(resetCounterSuccess, (state, { response }) => {
         return produce(state, (draftState) => {
             const index: number = draftState.counters.findIndex((c: any) => c.id === response.counter.id);
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/counters/state/counter-listing/counter-listing.selectors.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/counters/state/counter-listing/counter-listing.selectors.ts
index f644e7910f..3d7aa21065 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/counters/state/counter-listing/counter-listing.selectors.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/counters/state/counter-listing/counter-listing.selectors.ts
@@ -24,6 +24,7 @@ export const selectCounterListingState = createSelector(
     (state: CountersState) => state[countersFeatureKey]
 );
 
-export const selectCounters = createSelector(selectCounterListingState, (state: CounterListingState) => state.counters);
-
-export const selectStatus = createSelector(selectCounterListingState, (state: CounterListingState) => state.status);
+export const selectLoadedTimestamp = createSelector(
+    selectCounterListingState,
+    (state: CounterListingState) => state.loadedTimestamp
+);
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-configuration-history/state/flow-configuration-history-listing/flow-configuration-history-listing.actions.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-configuration-history/state/flow-configuration-history-listing/flow-configuration-history-listing.actions.ts
index 63c80e6f06..6f67620809 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-configuration-history/state/flow-configuration-history-listing/flow-configuration-history-listing.actions.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-configuration-history/state/flow-configuration-history-listing/flow-configuration-history-listing.actions.ts
@@ -34,6 +34,11 @@ export const loadHistorySuccess = createAction(
     props<{ response: HistoryEntity }>()
 );
 
+export const loadHistoryError = createAction(
+    `${HISTORY_PREFIX} Load History Error`,
+    props<{ errorResponse: any; loadedTimestamp: string; status: 'pending' | 'success' }>()
+);
+
 export const resetHistoryState = createAction(`${HISTORY_PREFIX} Reset History State`);
 
 export const clearHistorySelection = createAction(`${HISTORY_PREFIX} Clear Selection`);
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-configuration-history/state/flow-configuration-history-listing/flow-configuration-history-listing.effects.spec.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-configuration-history/state/flow-configuration-history-listing/flow-configuration-history-listing.effects.spec.ts
new file mode 100644
index 0000000000..2e255ec7aa
--- /dev/null
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-configuration-history/state/flow-configuration-history-listing/flow-configuration-history-listing.effects.spec.ts
@@ -0,0 +1,227 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { TestBed } from '@angular/core/testing';
+import { FlowConfigurationHistoryListingEffects } from './flow-configuration-history-listing.effects';
+import { provideMockActions } from '@ngrx/effects/testing';
+import { MockStore, provideMockStore } from '@ngrx/store/testing';
+import { initialHistoryState } from './flow-configuration-history-listing.reducer';
+import { of, ReplaySubject, take, throwError } from 'rxjs';
+import { Action } from '@ngrx/store';
+import * as HistoryActions from './flow-configuration-history-listing.actions';
+import { FlowConfigurationHistoryService } from '../../service/flow-configuration-history.service';
+import { MatDialog } from '@angular/material/dialog';
+import { ErrorHelper } from '../../../../service/error-helper.service';
+import { HttpErrorResponse } from '@angular/common/http';
+import { Router } from '@angular/router';
+import { flowConfigurationHistoryFeatureKey } from '../index';
+import { flowConfigurationHistoryListingFeatureKey } from './index';
+
+describe('FlowConfigurationHistoryListingEffects', () => {
+    interface SetupOptions {
+        historyListingState?: any;
+    }
+
+    const mockHistoryResponse = {
+        history: {
+            actions: [
+                {
+                    id: 1,
+                    timestamp: '2023-01-01 12:00:00 EST',
+                    userIdentity: 'admin',
+                    operation: 'Create',
+                    componentDetails: {
+                        name: 'Test Component',
+                        type: 'Processor'
+                    },
+                    sourceId: 'source-1',
+                    canRead: true
+                }
+            ],
+            total: 1,
+            lastRefreshed: '2023-01-01 12:00:00 EST'
+        }
+    };
+
+    let action$: ReplaySubject<Action>;
+
+    async function setup({ historyListingState = initialHistoryState }: SetupOptions = {}) {
+        await TestBed.configureTestingModule({
+            providers: [
+                FlowConfigurationHistoryListingEffects,
+                provideMockActions(() => action$),
+                provideMockStore({
+                    initialState: {
+                        [flowConfigurationHistoryFeatureKey]: {
+                            [flowConfigurationHistoryListingFeatureKey]: historyListingState || initialHistoryState
+                        }
+                    }
+                }),
+                {
+                    provide: FlowConfigurationHistoryService,
+                    useValue: {
+                        getHistory: jest.fn(),
+                        purgeHistory: jest.fn()
+                    }
+                },
+                {
+                    provide: MatDialog,
+                    useValue: {
+                        open: jest.fn()
+                    }
+                },
+                {
+                    provide: Router,
+                    useValue: {
+                        navigate: jest.fn()
+                    }
+                },
+                {
+                    provide: ErrorHelper,
+                    useValue: {
+                        handleLoadingError: jest.fn(),
+                        getErrorString: jest.fn()
+                    }
+                }
+            ]
+        }).compileComponents();
+
+        const store = TestBed.inject(MockStore);
+        const historyService = TestBed.inject(
+            FlowConfigurationHistoryService
+        ) as jest.Mocked<FlowConfigurationHistoryService>;
+        const errorHelper = TestBed.inject(ErrorHelper) as jest.Mocked<ErrorHelper>;
+        const effects = TestBed.inject(FlowConfigurationHistoryListingEffects);
+
+        return { store, historyService, errorHelper, effects };
+    }
+
+    beforeEach(() => {
+        action$ = new ReplaySubject<Action>(1);
+    });
+
+    afterEach(() => {
+        action$.complete();
+    });
+
+    it('should be created', async () => {
+        const { effects } = await setup();
+        expect(effects).toBeTruthy();
+    });
+
+    describe('loadHistory', () => {
+        it('should load history successfully', async () => {
+            const { effects, historyService } = await setup();
+
+            const request = { count: 50, offset: 0 };
+            action$.next(HistoryActions.loadHistory({ request }));
+            jest.spyOn(historyService, 'getHistory').mockReturnValueOnce(of(mockHistoryResponse) as never);
+
+            const result = await new Promise((resolve) => effects.loadHistory$.pipe(take(1)).subscribe(resolve));
+
+            expect(result).toEqual(
+                HistoryActions.loadHistorySuccess({
+                    response: mockHistoryResponse
+                })
+            );
+        });
+
+        it('should fail to load history on initial load with hasExistingData=false', async () => {
+            const { effects, historyService } = await setup();
+
+            const request = { count: 50, offset: 0 };
+            action$.next(HistoryActions.loadHistory({ request }));
+            const error = new HttpErrorResponse({ status: 500 });
+            jest.spyOn(historyService, 'getHistory').mockImplementationOnce(() => throwError(() => error));
+
+            const result = await new Promise((resolve) => effects.loadHistory$.pipe(take(1)).subscribe(resolve));
+
+            expect(result).toEqual(
+                HistoryActions.loadHistoryError({
+                    errorResponse: error,
+                    loadedTimestamp: initialHistoryState.loadedTimestamp,
+                    status: 'pending'
+                })
+            );
+        });
+
+        it('should fail to load history on refresh with hasExistingData=true', async () => {
+            const stateWithData = {
+                ...initialHistoryState,
+                loadedTimestamp: '2023-01-01 11:00:00 EST'
+            };
+            const { effects, historyService } = await setup({ historyListingState: stateWithData });
+
+            const request = { count: 50, offset: 0 };
+            action$.next(HistoryActions.loadHistory({ request }));
+            const error = new HttpErrorResponse({ status: 500 });
+            jest.spyOn(historyService, 'getHistory').mockImplementationOnce(() => throwError(() => error));
+
+            const result = await new Promise((resolve) => effects.loadHistory$.pipe(take(1)).subscribe(resolve));
+
+            expect(result).toEqual(
+                HistoryActions.loadHistoryError({
+                    errorResponse: error,
+                    loadedTimestamp: stateWithData.loadedTimestamp,
+                    status: 'success'
+                })
+            );
+        });
+    });
+
+    describe('Load History Error', () => {
+        it('should handle history error for initial load', async () => {
+            const { effects, errorHelper } = await setup();
+
+            const error = new HttpErrorResponse({ status: 500 });
+            const errorAction = HistoryActions.flowConfigurationHistorySnackbarError({ errorResponse: error });
+            action$.next(
+                HistoryActions.loadHistoryError({
+                    errorResponse: error,
+                    loadedTimestamp: initialHistoryState.loadedTimestamp,
+                    status: 'pending'
+                })
+            );
+            jest.spyOn(errorHelper, 'handleLoadingError').mockReturnValueOnce(errorAction);
+
+            const result = await new Promise((resolve) => effects.loadHistoryError$.pipe(take(1)).subscribe(resolve));
+
+            expect(errorHelper.handleLoadingError).toHaveBeenCalledWith(false, error);
+            expect(result).toEqual(errorAction);
+        });
+
+        it('should handle history error for refresh', async () => {
+            const { effects, errorHelper } = await setup();
+
+            const error = new HttpErrorResponse({ status: 500 });
+            const errorAction = HistoryActions.flowConfigurationHistorySnackbarError({ errorResponse: error });
+            action$.next(
+                HistoryActions.loadHistoryError({
+                    errorResponse: error,
+                    loadedTimestamp: '2023-01-01 11:00:00 EST',
+                    status: 'success'
+                })
+            );
+            jest.spyOn(errorHelper, 'handleLoadingError').mockReturnValueOnce(errorAction);
+
+            const result = await new Promise((resolve) => effects.loadHistoryError$.pipe(take(1)).subscribe(resolve));
+
+            expect(errorHelper.handleLoadingError).toHaveBeenCalledWith(true, error);
+            expect(result).toEqual(errorAction);
+        });
+    });
+});
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-configuration-history/state/flow-configuration-history-listing/flow-configuration-history-listing.effects.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-configuration-history/state/flow-configuration-history-listing/flow-configuration-history-listing.effects.ts
index 14f8976609..35b0bf1acf 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-configuration-history/state/flow-configuration-history-listing/flow-configuration-history-listing.effects.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-configuration-history/state/flow-configuration-history-listing/flow-configuration-history-listing.effects.ts
@@ -21,10 +21,11 @@ import { concatLatestFrom } from '@ngrx/operators';
 import { Store } from '@ngrx/store';
 import { NiFiState } from '../../../../state';
 import { ErrorHelper } from '../../../../service/error-helper.service';
+import { initialHistoryState } from './flow-configuration-history-listing.reducer';
 import { MatDialog } from '@angular/material/dialog';
 import * as HistoryActions from './flow-configuration-history-listing.actions';
 import { catchError, from, map, of, switchMap, take, tap } from 'rxjs';
-import { selectHistoryQuery, selectHistoryStatus } from './flow-configuration-history-listing.selectors';
+import { selectHistoryLoadedTimestamp, selectHistoryQuery } from './flow-configuration-history-listing.selectors';
 import { FlowConfigurationHistoryService } from '../../service/flow-configuration-history.service';
 import { HistoryEntity } from './index';
 import { HttpErrorResponse } from '@angular/common/http';
@@ -48,8 +49,8 @@ export class FlowConfigurationHistoryListingEffects {
         this.actions$.pipe(
             ofType(HistoryActions.loadHistory),
             map((action) => action.request),
-            concatLatestFrom(() => this.store.select(selectHistoryStatus)),
-            switchMap(([request, status]) =>
+            concatLatestFrom(() => this.store.select(selectHistoryLoadedTimestamp)),
+            switchMap(([request, loadedTimestamp]) =>
                 from(this.historyService.getHistory(request)).pipe(
                     map((response: HistoryEntity) =>
                         HistoryActions.loadHistorySuccess({
@@ -57,13 +58,31 @@ export class FlowConfigurationHistoryListingEffects {
                         })
                     ),
                     catchError((errorResponse: HttpErrorResponse) =>
-                        of(this.errorHelper.handleLoadingError(status, errorResponse))
+                        of(
+                            HistoryActions.loadHistoryError({
+                                errorResponse,
+                                loadedTimestamp,
+                                status: loadedTimestamp !== initialHistoryState.loadedTimestamp ? 'success' : 'pending'
+                            })
+                        )
                     )
                 )
             )
         )
     );
 
+    loadHistoryError$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(HistoryActions.loadHistoryError),
+            map((action) =>
+                this.errorHelper.handleLoadingError(
+                    action.loadedTimestamp !== initialHistoryState.loadedTimestamp,
+                    action.errorResponse
+                )
+            )
+        )
+    );
+
     flowConfigurationHistorySnackbarError = createEffect(() =>
         this.actions$.pipe(
             ofType(HistoryActions.flowConfigurationHistorySnackbarError),
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-configuration-history/state/flow-configuration-history-listing/flow-configuration-history-listing.reducer.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-configuration-history/state/flow-configuration-history-listing/flow-configuration-history-listing.reducer.ts
index 09025ba471..fa11eb7872 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-configuration-history/state/flow-configuration-history-listing/flow-configuration-history-listing.reducer.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-configuration-history/state/flow-configuration-history-listing/flow-configuration-history-listing.reducer.ts
@@ -22,6 +22,7 @@ import {
     flowConfigurationHistorySnackbarError,
     loadHistory,
     loadHistorySuccess,
+    loadHistoryError,
     purgeHistory,
     purgeHistorySuccess,
     resetHistoryState,
@@ -55,6 +56,11 @@ export const flowConfigurationHistoryListingReducer = createReducer(
         total: response.history.total
     })),
 
+    on(loadHistoryError, (state, { status }) => ({
+        ...state,
+        status
+    })),
+
     on(resetHistoryState, () => ({
         ...initialHistoryState
     })),
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.actions.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.actions.ts
index c920c92764..a966e1c571 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.actions.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.actions.ts
@@ -46,6 +46,11 @@ export const loadControllerServicesSuccess = createAction(
     props<{ response: LoadControllerServicesResponse }>()
 );
 
+export const loadControllerServicesError = createAction(
+    '[Controller Services] Load Controller Services Error',
+    props<{ errorResponse: any; loadedTimestamp: string; status: 'pending' | 'success' }>()
+);
+
 export const controllerServicesBannerApiError = createAction(
     '[Controller Services] Controller Services Banner Api Error',
     props<{ error: string }>()
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.effects.spec.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.effects.spec.ts
new file mode 100644
index 0000000000..f251b7c21e
--- /dev/null
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.effects.spec.ts
@@ -0,0 +1,224 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { TestBed } from '@angular/core/testing';
+import { provideMockActions } from '@ngrx/effects/testing';
+import { MockStore, provideMockStore } from '@ngrx/store/testing';
+import { Action } from '@ngrx/store';
+import { ReplaySubject, of, take, throwError } from 'rxjs';
+import { HttpErrorResponse } from '@angular/common/http';
+
+import * as ControllerServicesActions from './controller-services.actions';
+import { ControllerServicesEffects } from './controller-services.effects';
+import { ControllerServiceService } from '../../service/controller-service.service';
+import { ErrorHelper } from '../../../../service/error-helper.service';
+import { MatDialog } from '@angular/material/dialog';
+import { Router } from '@angular/router';
+import { PropertyTableHelperService } from '../../../../service/property-table-helper.service';
+import { ParameterHelperService } from '../../service/parameter-helper.service';
+import { ExtensionTypesService } from '../../../../service/extension-types.service';
+import { Client } from '../../../../service/client.service';
+import { Storage } from '@nifi/shared';
+import { ParameterContextService } from '../../../parameter-contexts/service/parameter-contexts.service';
+import { initialState } from './controller-services.reducer';
+
+describe('ControllerServicesEffects', () => {
+    interface SetupOptions {
+        controllerServicesState?: any;
+    }
+
+    const mockControllerServicesResponse = {
+        controllerServices: [],
+        currentTime: '2023-01-01 12:00:00 EST'
+    };
+    const mockFlowResponse = {
+        processGroupFlow: {
+            id: 'pg-1',
+            breadcrumb: { id: 'pg-1', name: 'PG', permissions: { canRead: true }, versionedFlowState: null },
+            parameterContext: null
+        }
+    } as any;
+
+    async function setup({ controllerServicesState = initialState }: SetupOptions = {}) {
+        await TestBed.configureTestingModule({
+            providers: [
+                ControllerServicesEffects,
+                provideMockActions(() => action$),
+                provideMockStore({
+                    initialState: {
+                        canvas: {
+                            controllerServiceListing: controllerServicesState
+                        }
+                    }
+                }),
+                {
+                    provide: ControllerServiceService,
+                    useValue: { getControllerServices: jest.fn(), getFlow: jest.fn() }
+                },
+                { provide: ErrorHelper, useValue: { handleLoadingError: jest.fn(), getErrorString: jest.fn() } },
+                { provide: MatDialog, useValue: { open: jest.fn(), closeAll: jest.fn() } },
+                { provide: Router, useValue: { navigate: jest.fn() } },
+                { provide: PropertyTableHelperService, useValue: {} },
+                { provide: ParameterHelperService, useValue: {} },
+                { provide: ExtensionTypesService, useValue: {} },
+                { provide: Client, useValue: {} },
+                { provide: Storage, useValue: { setItem: jest.fn(), getItem: jest.fn() } },
+                { provide: ParameterContextService, useValue: { getParameterContext: jest.fn() } }
+            ]
+        }).compileComponents();
+
+        const store = TestBed.inject(MockStore);
+        const effects = TestBed.inject(ControllerServicesEffects);
+        const controllerServiceService = TestBed.inject(ControllerServiceService);
+        const errorHelper = TestBed.inject(ErrorHelper);
+
+        return { effects, store, controllerServiceService, errorHelper };
+    }
+
+    let action$: ReplaySubject<Action>;
+    beforeEach(() => {
+        action$ = new ReplaySubject<Action>();
+    });
+
+    it('should create', async () => {
+        const { effects } = await setup();
+        expect(effects).toBeTruthy();
+    });
+
+    describe('Load Controller Services', () => {
+        it('should load controller services successfully', async () => {
+            const { effects, controllerServiceService } = await setup();
+
+            action$.next(ControllerServicesActions.loadControllerServices({ request: { processGroupId: 'pg-1' } }));
+            jest.spyOn(controllerServiceService, 'getControllerServices').mockReturnValueOnce(
+                of(mockControllerServicesResponse) as never
+            );
+            jest.spyOn(controllerServiceService, 'getFlow').mockReturnValueOnce(of(mockFlowResponse) as never);
+
+            const result = await new Promise((resolve) =>
+                effects.loadControllerServices$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(result).toEqual(
+                ControllerServicesActions.loadControllerServicesSuccess({
+                    response: {
+                        processGroupId: mockFlowResponse.processGroupFlow.id,
+                        controllerServices: mockControllerServicesResponse.controllerServices,
+                        loadedTimestamp: mockControllerServicesResponse.currentTime,
+                        breadcrumb: mockFlowResponse.processGroupFlow.breadcrumb,
+                        parameterContext: mockFlowResponse.processGroupFlow.parameterContext
+                    }
+                })
+            );
+        });
+
+        it('should fail to load controller services on initial load with hasExistingData=false', async () => {
+            const { effects, controllerServiceService } = await setup();
+
+            action$.next(ControllerServicesActions.loadControllerServices({ request: { processGroupId: 'pg-1' } }));
+            const error = new HttpErrorResponse({ status: 500 });
+            jest.spyOn(controllerServiceService, 'getControllerServices').mockImplementationOnce(() =>
+                throwError(() => error)
+            );
+            jest.spyOn(controllerServiceService, 'getFlow').mockReturnValueOnce(of(mockFlowResponse) as never);
+
+            const result = await new Promise((resolve) =>
+                effects.loadControllerServices$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(result).toEqual(
+                ControllerServicesActions.loadControllerServicesError({
+                    errorResponse: error,
+                    loadedTimestamp: initialState.loadedTimestamp,
+                    status: 'pending'
+                })
+            );
+        });
+
+        it('should fail to load controller services on refresh with hasExistingData=true', async () => {
+            const stateWithData = { ...initialState, loadedTimestamp: '2023-01-01 11:00:00 EST' };
+            const { effects, controllerServiceService } = await setup({
+                controllerServicesState: stateWithData
+            });
+
+            action$.next(ControllerServicesActions.loadControllerServices({ request: { processGroupId: 'pg-1' } }));
+            const error = new HttpErrorResponse({ status: 500 });
+            jest.spyOn(controllerServiceService, 'getControllerServices').mockImplementationOnce(() =>
+                throwError(() => error)
+            );
+            jest.spyOn(controllerServiceService, 'getFlow').mockReturnValueOnce(of(mockFlowResponse) as never);
+
+            const result = await new Promise((resolve) =>
+                effects.loadControllerServices$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(result).toEqual(
+                ControllerServicesActions.loadControllerServicesError({
+                    errorResponse: error,
+                    loadedTimestamp: stateWithData.loadedTimestamp,
+                    status: 'success'
+                })
+            );
+        });
+    });
+
+    describe('Controller Services Listing Error', () => {
+        it('should handle controller services listing error for initial load', async () => {
+            const { effects, errorHelper } = await setup();
+
+            const error = new HttpErrorResponse({ status: 500 });
+            const errorAction = ControllerServicesActions.controllerServicesBannerApiError({ error: 'Error loading' });
+            action$.next(
+                ControllerServicesActions.loadControllerServicesError({
+                    errorResponse: error,
+                    loadedTimestamp: initialState.loadedTimestamp,
+                    status: 'pending'
+                })
+            );
+            jest.spyOn(errorHelper, 'handleLoadingError').mockReturnValueOnce(errorAction);
+
+            const result = await new Promise((resolve) =>
+                effects.controllerServicesListingError$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(errorHelper.handleLoadingError).toHaveBeenCalledWith(false, error);
+            expect(result).toEqual(errorAction);
+        });
+
+        it('should handle controller services listing error for refresh', async () => {
+            const { effects, errorHelper } = await setup();
+
+            const error = new HttpErrorResponse({ status: 500 });
+            const errorAction = ControllerServicesActions.controllerServicesBannerApiError({ error: 'Error loading' });
+            action$.next(
+                ControllerServicesActions.loadControllerServicesError({
+                    errorResponse: error,
+                    loadedTimestamp: '2023-01-01 11:00:00 EST',
+                    status: 'success'
+                })
+            );
+            jest.spyOn(errorHelper, 'handleLoadingError').mockReturnValueOnce(errorAction);
+
+            const result = await new Promise((resolve) =>
+                effects.controllerServicesListingError$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(errorHelper.handleLoadingError).toHaveBeenCalledWith(true, error);
+            expect(result).toEqual(errorAction);
+        });
+    });
+});
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.effects.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.effects.ts
index c2a047d1c1..c8ca4aff37 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.effects.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.effects.ts
@@ -40,9 +40,9 @@ import {
 import { Router } from '@angular/router';
 import {
     selectCurrentProcessGroupId,
+    selectLoadedTimestamp,
     selectParameterContext,
-    selectSaving,
-    selectStatus
+    selectSaving
 } from './controller-services.selectors';
 import { ControllerServiceService } from '../../service/controller-service.service';
 import { EnableControllerService } from '../../../../ui/common/controller-service/enable-controller-service/enable-controller-service.component';
@@ -53,6 +53,7 @@ import { ErrorHelper } from '../../../../service/error-helper.service';
 import { HttpErrorResponse } from '@angular/common/http';
 import { ParameterHelperService } from '../../service/parameter-helper.service';
 import { ExtensionTypesService } from '../../../../service/extension-types.service';
+import { initialState } from './controller-services.reducer';
 import { ChangeComponentVersionDialog } from '../../../../ui/common/change-component-version-dialog/change-component-version-dialog';
 import {
     resetPropertyVerificationState,
@@ -87,8 +88,8 @@ export class ControllerServicesEffects {
         this.actions$.pipe(
             ofType(ControllerServicesActions.loadControllerServices),
             map((action) => action.request),
-            concatLatestFrom(() => this.store.select(selectStatus)),
-            switchMap(([request, status]) =>
+            concatLatestFrom(() => this.store.select(selectLoadedTimestamp)),
+            switchMap(([request, loadedTimestamp]) =>
                 combineLatest([
                     this.controllerServiceService.getControllerServices(request.processGroupId),
                     this.controllerServiceService.getFlow(request.processGroupId)
@@ -105,13 +106,31 @@ export class ControllerServicesEffects {
                         })
                     ),
                     catchError((errorResponse: HttpErrorResponse) =>
-                        of(this.errorHelper.handleLoadingError(status, errorResponse))
+                        of(
+                            ControllerServicesActions.loadControllerServicesError({
+                                errorResponse,
+                                loadedTimestamp,
+                                status: loadedTimestamp !== initialState.loadedTimestamp ? 'success' : 'pending'
+                            })
+                        )
                     )
                 )
             )
         )
     );
 
+    controllerServicesListingError$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(ControllerServicesActions.loadControllerServicesError),
+            map((action) =>
+                this.errorHelper.handleLoadingError(
+                    action.loadedTimestamp !== initialState.loadedTimestamp,
+                    action.errorResponse
+                )
+            )
+        )
+    );
+
     openNewControllerServiceDialog$ = createEffect(
         () =>
             this.actions$.pipe(
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.reducer.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.reducer.ts
index 179511805a..a88f8ec9fe 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.reducer.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.reducer.ts
@@ -20,6 +20,7 @@ import {
     configureControllerService,
     configureControllerServiceSuccess,
     controllerServicesBannerApiError,
+    loadControllerServicesError,
     createControllerService,
     createControllerServiceSuccess,
     deleteControllerService,
@@ -71,6 +72,10 @@ export const controllerServicesReducer = createReducer(
         loadedTimestamp: response.loadedTimestamp,
         status: 'success' as const
     })),
+    on(loadControllerServicesError, (state, { status }) => ({
+        ...state,
+        status
+    })),
     on(controllerServicesBannerApiError, (state) => ({
         ...state,
         saving: false
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.selectors.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.selectors.ts
index 0ce3a1cd33..41d8eba257 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.selectors.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.selectors.ts
@@ -31,9 +31,9 @@ export const selectSaving = createSelector(
     (state: ControllerServicesState) => state.saving
 );
 
-export const selectStatus = createSelector(
+export const selectLoadedTimestamp = createSelector(
     selectControllerServicesState,
-    (state: ControllerServicesState) => state.status
+    (state: ControllerServicesState) => state.loadedTimestamp
 );
 
 export const selectCurrentProcessGroupId = createSelector(
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.effects.spec.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.effects.spec.ts
index d48a391558..2bddcbed21 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.effects.spec.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.effects.spec.ts
@@ -16,7 +16,7 @@
  */
 import { FlowService } from '../../service/flow.service';
 import * as FlowActions from './flow.actions';
-import { of, ReplaySubject, take } from 'rxjs';
+import { of, ReplaySubject, take, throwError } from 'rxjs';
 import { MatDialog, MatDialogRef } from '@angular/material/dialog';
 import { ComponentHistoryEntity } from '../../../../state/shared';
 import { EditProcessor } from '../../ui/canvas/items/processor/edit-processor/edit-processor.component';
@@ -59,6 +59,9 @@ import { BirdseyeView } from '../../service/birdseye-view.service';
 import { selectDisconnectionAcknowledged } from '../../../../state/cluster-summary/cluster-summary.selectors';
 import { ComponentType } from '@nifi/shared';
 import { ParameterContextService } from '../../../parameter-contexts/service/parameter-contexts.service';
+import { HttpErrorResponse } from '@angular/common/http';
+import { ErrorHelper } from '../../../../service/error-helper.service';
+import { selectConnectedStateChanged } from '../../../../state/cluster-summary/cluster-summary.selectors';
 
 describe('FlowEffects', () => {
     let action$: ReplaySubject<Action>;
@@ -913,6 +916,66 @@ describe('FlowEffects', () => {
         }
     });
 
+    describe('loadProcessGroup error handling', () => {
+        it('dispatches full-screen error on initial load (hasExistingData=false)', async () => {
+            // Arrange selectors
+            store.overrideSelector(flowSelectors.selectHasFlowData, false);
+            store.overrideSelector(selectConnectedStateChanged, false);
+
+            // Arrange service methods
+            (flowService as any).getFlow = jest.fn(() => throwError(() => new HttpErrorResponse({ status: 500 })));
+            (flowService as any).getFlowStatus = jest.fn(() => of({}));
+            (flowService as any).getControllerBulletins = jest.fn(() => of({}));
+            jest.spyOn(TestBed.inject(RegistryService), 'getRegistryClients').mockReturnValueOnce(
+                of({ registries: [] }) as any
+            );
+
+            // Arrange error helper
+            const errorHelper = TestBed.inject(ErrorHelper);
+            const errorAction = FlowActions.flowBannerError({
+                errorContext: { context: 'FLOW', errors: ['e'] } as any
+            });
+            jest.spyOn(errorHelper, 'handleLoadingError').mockReturnValueOnce(errorAction as any);
+
+            // Act
+            action$.next(FlowActions.loadProcessGroup({ request: { id: 'pg-1', transitionRequired: false } }));
+            const result = await new Promise((resolve) => effects.loadProcessGroup$.pipe(take(1)).subscribe(resolve));
+
+            // Assert
+            expect(errorHelper.handleLoadingError).toHaveBeenCalledWith(false, expect.any(HttpErrorResponse));
+            expect(result).toEqual(errorAction);
+        });
+
+        it('dispatches snackbar/banner error on refresh (hasExistingData=true)', async () => {
+            // Arrange selectors
+            store.overrideSelector(flowSelectors.selectHasFlowData, true);
+            store.overrideSelector(selectConnectedStateChanged, false);
+
+            // Arrange service methods
+            (flowService as any).getFlow = jest.fn(() => throwError(() => new HttpErrorResponse({ status: 500 })));
+            (flowService as any).getFlowStatus = jest.fn(() => of({}));
+            (flowService as any).getControllerBulletins = jest.fn(() => of({}));
+            jest.spyOn(TestBed.inject(RegistryService), 'getRegistryClients').mockReturnValueOnce(
+                of({ registries: [] }) as any
+            );
+
+            // Arrange error helper
+            const errorHelper = TestBed.inject(ErrorHelper);
+            const errorAction = FlowActions.flowBannerError({
+                errorContext: { context: 'FLOW', errors: ['e'] } as any
+            });
+            jest.spyOn(errorHelper, 'handleLoadingError').mockReturnValueOnce(errorAction as any);
+
+            // Act
+            action$.next(FlowActions.loadProcessGroup({ request: { id: 'pg-1', transitionRequired: false } }));
+            const result = await new Promise((resolve) => effects.loadProcessGroup$.pipe(take(1)).subscribe(resolve));
+
+            // Assert
+            expect(errorHelper.handleLoadingError).toHaveBeenCalledWith(true, expect.any(HttpErrorResponse));
+            expect(result).toEqual(errorAction);
+        });
+    });
+
     describe('#moveToFront', () => {
         it('calls the flow service to update the component when max index matches request index', async () => {
             const REQUEST: MoveToFrontRequest = {
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.effects.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.effects.ts
index 74f7d049cb..912e0c4019 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.effects.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.effects.ts
@@ -85,7 +85,7 @@ import {
     selectCurrentParameterContext,
     selectCurrentProcessGroupId,
     selectCurrentProcessGroupRevision,
-    selectFlowLoadingStatus,
+    selectHasFlowData,
     selectInputPort,
     selectMaxZIndex,
     selectOutputPort,
@@ -249,11 +249,11 @@ export class FlowEffects {
             ofType(FlowActions.loadProcessGroup),
             map((action) => action.request),
             concatLatestFrom(() => [
-                this.store.select(selectFlowLoadingStatus),
+                this.store.select(selectHasFlowData),
                 this.store.select(selectConnectedStateChanged)
             ]),
             tap(() => this.store.dispatch(resetConnectedStateChanged())),
-            switchMap(([request, status, connectedStateChanged]) =>
+            switchMap(([request, hasFlowData, connectedStateChanged]) =>
                 combineLatest([
                     this.flowService.getFlow(request.id),
                     this.flowService.getFlowStatus(),
@@ -274,7 +274,7 @@ export class FlowEffects {
                         });
                     }),
                     catchError((errorResponse: HttpErrorResponse) =>
-                        of(this.errorHelper.handleLoadingError(status, errorResponse))
+                        of(this.errorHelper.handleLoadingError(hasFlowData, errorResponse))
                     )
                 )
             )
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.selectors.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.selectors.ts
index e3d30335b1..18193590c9 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.selectors.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.selectors.ts
@@ -24,6 +24,11 @@ export const selectFlowState = createSelector(selectCanvasState, (state: CanvasS
 
 export const selectFlowLoadingStatus = createSelector(selectFlowState, (state: FlowState) => state.status);
 
+export const selectHasFlowData = createSelector(
+    selectFlowState,
+    (state: FlowState) => state.flow.processGroupFlow.id !== ''
+);
+
 export const selectChangeVersionRequest = createSelector(
     selectFlowState,
     (state: FlowState) => state.changeVersionRequest
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/manage-remote-ports/manage-remote-ports.actions.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/manage-remote-ports/manage-remote-ports.actions.ts
index a084dc5b2d..9c267c43ef 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/manage-remote-ports/manage-remote-ports.actions.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/manage-remote-ports/manage-remote-ports.actions.ts
@@ -39,6 +39,11 @@ export const loadRemotePortsSuccess = createAction(
     props<{ response: LoadRemotePortsResponse }>()
 );
 
+export const loadRemotePortsError = createAction(
+    '[Manage Remote Ports] Load Remote Ports Error',
+    props<{ errorResponse: any; loadedTimestamp: string; status: 'pending' | 'success' }>()
+);
+
 export const remotePortsBannerApiError = createAction(
     '[Manage Remote Ports] Remote Ports Banner Api Error',
     props<{ error: string }>()
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/manage-remote-ports/manage-remote-ports.effects.spec.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/manage-remote-ports/manage-remote-ports.effects.spec.ts
new file mode 100644
index 0000000000..b556e3e656
--- /dev/null
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/manage-remote-ports/manage-remote-ports.effects.spec.ts
@@ -0,0 +1,280 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { TestBed } from '@angular/core/testing';
+import { ManageRemotePortsEffects } from './manage-remote-ports.effects';
+import { provideMockActions } from '@ngrx/effects/testing';
+import { MockStore, provideMockStore } from '@ngrx/store/testing';
+import { initialState } from './manage-remote-ports.reducer';
+import { of, ReplaySubject, take, throwError } from 'rxjs';
+import { Action } from '@ngrx/store';
+import * as ManageRemotePortsActions from './manage-remote-ports.actions';
+import { ManageRemotePortService } from '../../service/manage-remote-port.service';
+import { MatDialog } from '@angular/material/dialog';
+import { ErrorHelper } from '../../../../service/error-helper.service';
+import { HttpErrorResponse } from '@angular/common/http';
+import { Router } from '@angular/router';
+import { ClusterConnectionService } from '../../../../service/cluster-connection.service';
+import { remotePortsFeatureKey } from './index';
+
+describe('ManageRemotePortsEffects', () => {
+    interface SetupOptions {
+        remotePortsState?: any;
+    }
+
+    const mockRemotePortsResponse = {
+        component: {
+            contents: {
+                inputPorts: [
+                    {
+                        id: 'port-1',
+                        name: 'Test Input Port',
+                        targetId: 'target-1',
+                        targetRunning: false,
+                        transmitting: false,
+                        exists: true,
+                        connected: true,
+                        batchSettings: {
+                            count: 1,
+                            size: '1 KB',
+                            duration: '0 sec'
+                        },
+                        comments: '',
+                        concurrentlySchedulableTaskCount: 1,
+                        groupId: 'rpg-1',
+                        useCompression: false,
+                        versionedComponentId: 'version-1'
+                    }
+                ],
+                outputPorts: [
+                    {
+                        id: 'port-2',
+                        name: 'Test Output Port',
+                        targetId: 'target-2',
+                        targetRunning: false,
+                        transmitting: false,
+                        exists: true,
+                        connected: true,
+                        batchSettings: {
+                            count: 1,
+                            size: '1 KB',
+                            duration: '0 sec'
+                        },
+                        comments: '',
+                        concurrentlySchedulableTaskCount: 1,
+                        groupId: 'rpg-1',
+                        useCompression: false,
+                        versionedComponentId: 'version-1'
+                    }
+                ]
+            }
+        },
+        id: 'rpg-1',
+        name: 'Test Remote Process Group'
+    };
+
+    const mockTimeOffset = 0;
+    const mockAbout = {
+        timezone: 'EST'
+    };
+
+    let action$: ReplaySubject<Action>;
+
+    async function setup({ remotePortsState = initialState }: SetupOptions = {}) {
+        await TestBed.configureTestingModule({
+            providers: [
+                ManageRemotePortsEffects,
+                provideMockActions(() => action$),
+                provideMockStore({
+                    initialState: {
+                        [remotePortsFeatureKey]: remotePortsState || initialState,
+                        flowConfiguration: {
+                            flowConfiguration: {
+                                timeOffset: mockTimeOffset
+                            }
+                        },
+                        about: {
+                            about: mockAbout
+                        }
+                    }
+                }),
+                {
+                    provide: ManageRemotePortService,
+                    useValue: {
+                        getRemotePorts: jest.fn(),
+                        configureRemotePort: jest.fn()
+                    }
+                },
+                {
+                    provide: MatDialog,
+                    useValue: {
+                        open: jest.fn()
+                    }
+                },
+                {
+                    provide: Router,
+                    useValue: {
+                        navigate: jest.fn()
+                    }
+                },
+                {
+                    provide: ClusterConnectionService,
+                    useValue: {
+                        isDisconnectionAcknowledged: jest.fn().mockReturnValue(false)
+                    }
+                },
+                {
+                    provide: ErrorHelper,
+                    useValue: {
+                        handleLoadingError: jest.fn(),
+                        getErrorString: jest.fn()
+                    }
+                }
+            ]
+        }).compileComponents();
+
+        const store = TestBed.inject(MockStore);
+        const remotePortService = TestBed.inject(ManageRemotePortService) as jest.Mocked<ManageRemotePortService>;
+        const errorHelper = TestBed.inject(ErrorHelper) as jest.Mocked<ErrorHelper>;
+        const effects = TestBed.inject(ManageRemotePortsEffects);
+
+        return { store, remotePortService, errorHelper, effects };
+    }
+
+    beforeEach(() => {
+        action$ = new ReplaySubject<Action>(1);
+    });
+
+    afterEach(() => {
+        action$.complete();
+    });
+
+    it('should be created', async () => {
+        const { effects } = await setup();
+        expect(effects).toBeTruthy();
+    });
+
+    describe('loadRemotePorts', () => {
+        it('should load remote ports successfully', async () => {
+            const { effects, remotePortService } = await setup();
+
+            const request = { rpgId: 'rpg-1' };
+            action$.next(ManageRemotePortsActions.loadRemotePorts({ request }));
+            jest.spyOn(remotePortService, 'getRemotePorts').mockReturnValueOnce(of(mockRemotePortsResponse) as never);
+
+            const result = await new Promise((resolve) => effects.loadRemotePorts$.pipe(take(1)).subscribe(resolve));
+
+            expect(result).toEqual(
+                ManageRemotePortsActions.loadRemotePortsSuccess({
+                    response: expect.objectContaining({
+                        ports: expect.any(Array),
+                        loadedTimestamp: expect.any(String),
+                        rpg: expect.any(Object)
+                    })
+                })
+            );
+        });
+
+        it('should fail to load remote ports on initial load with hasExistingData=false', async () => {
+            const { effects, remotePortService } = await setup();
+
+            const request = { rpgId: 'rpg-1' };
+            action$.next(ManageRemotePortsActions.loadRemotePorts({ request }));
+            const error = new HttpErrorResponse({ status: 500 });
+            jest.spyOn(remotePortService, 'getRemotePorts').mockImplementationOnce(() => throwError(() => error));
+
+            const result = await new Promise((resolve) => effects.loadRemotePorts$.pipe(take(1)).subscribe(resolve));
+
+            expect(result).toEqual(
+                ManageRemotePortsActions.loadRemotePortsError({
+                    errorResponse: error,
+                    loadedTimestamp: initialState.loadedTimestamp,
+                    status: 'pending'
+                })
+            );
+        });
+
+        it('should fail to load remote ports on refresh with hasExistingData=true', async () => {
+            const stateWithData = {
+                ...initialState,
+                loadedTimestamp: '2023-01-01 11:00:00 EST'
+            };
+            const { effects, remotePortService } = await setup({ remotePortsState: stateWithData });
+
+            const request = { rpgId: 'rpg-1' };
+            action$.next(ManageRemotePortsActions.loadRemotePorts({ request }));
+            const error = new HttpErrorResponse({ status: 500 });
+            jest.spyOn(remotePortService, 'getRemotePorts').mockImplementationOnce(() => throwError(() => error));
+
+            const result = await new Promise((resolve) => effects.loadRemotePorts$.pipe(take(1)).subscribe(resolve));
+
+            expect(result).toEqual(
+                ManageRemotePortsActions.loadRemotePortsError({
+                    errorResponse: error,
+                    loadedTimestamp: stateWithData.loadedTimestamp,
+                    status: 'success'
+                })
+            );
+        });
+    });
+
+    describe('Load Remote Ports Error', () => {
+        it('should handle remote ports error for initial load', async () => {
+            const { effects, errorHelper } = await setup();
+
+            const error = new HttpErrorResponse({ status: 500 });
+            const errorAction = ManageRemotePortsActions.remotePortsBannerApiError({ error: 'Error message' });
+            action$.next(
+                ManageRemotePortsActions.loadRemotePortsError({
+                    errorResponse: error,
+                    loadedTimestamp: initialState.loadedTimestamp,
+                    status: 'pending'
+                })
+            );
+            jest.spyOn(errorHelper, 'handleLoadingError').mockReturnValueOnce(errorAction);
+
+            const result = await new Promise((resolve) =>
+                effects.loadRemotePortsError$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(errorHelper.handleLoadingError).toHaveBeenCalledWith(false, error);
+            expect(result).toEqual(errorAction);
+        });
+
+        it('should handle remote ports error for refresh', async () => {
+            const { effects, errorHelper } = await setup();
+
+            const error = new HttpErrorResponse({ status: 500 });
+            const errorAction = ManageRemotePortsActions.remotePortsBannerApiError({ error: 'Error message' });
+            action$.next(
+                ManageRemotePortsActions.loadRemotePortsError({
+                    errorResponse: error,
+                    loadedTimestamp: '2023-01-01 11:00:00 EST',
+                    status: 'success'
+                })
+            );
+            jest.spyOn(errorHelper, 'handleLoadingError').mockReturnValueOnce(errorAction);
+
+            const result = await new Promise((resolve) =>
+                effects.loadRemotePortsError$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(errorHelper.handleLoadingError).toHaveBeenCalledWith(true, error);
+            expect(result).toEqual(errorAction);
+        });
+    });
+});
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/manage-remote-ports/manage-remote-ports.effects.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/manage-remote-ports/manage-remote-ports.effects.ts
index 963ad1d665..38bf553243 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/manage-remote-ports/manage-remote-ports.effects.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/manage-remote-ports/manage-remote-ports.effects.ts
@@ -24,9 +24,10 @@ import { MatDialog } from '@angular/material/dialog';
 import { Store } from '@ngrx/store';
 import { NiFiState } from '../../../../state';
 import { Router } from '@angular/router';
-import { selectRpg, selectRpgIdFromRoute, selectStatus } from './manage-remote-ports.selectors';
+import { selectLoadedTimestamp, selectRpg, selectRpgIdFromRoute } from './manage-remote-ports.selectors';
 import * as ErrorActions from '../../../../state/error/error.actions';
 import { ErrorHelper } from '../../../../service/error-helper.service';
+import { initialState } from './manage-remote-ports.reducer';
 import { HttpErrorResponse } from '@angular/common/http';
 import { ManageRemotePortService } from '../../service/manage-remote-port.service';
 import { PortSummary } from './index';
@@ -53,11 +54,11 @@ export class ManageRemotePortsEffects {
             ofType(ManageRemotePortsActions.loadRemotePorts),
             map((action) => action.request),
             concatLatestFrom(() => [
-                this.store.select(selectStatus),
+                this.store.select(selectLoadedTimestamp),
                 this.store.select(selectTimeOffset).pipe(isDefinedAndNotNull()),
                 this.store.select(selectAbout).pipe(isDefinedAndNotNull())
             ]),
-            switchMap(([request, status, timeOffset, about]) => {
+            switchMap(([request, loadedTimestamp, timeOffset, about]) => {
                 return this.manageRemotePortService.getRemotePorts(request.rpgId).pipe(
                     map((response) => {
                         // get the current user time to properly convert the server time
@@ -100,13 +101,31 @@ export class ManageRemotePortsEffects {
                         });
                     }),
                     catchError((errorResponse: HttpErrorResponse) =>
-                        of(this.errorHelper.handleLoadingError(status, errorResponse))
+                        of(
+                            ManageRemotePortsActions.loadRemotePortsError({
+                                errorResponse,
+                                loadedTimestamp,
+                                status: loadedTimestamp !== initialState.loadedTimestamp ? 'success' : 'pending'
+                            })
+                        )
                     )
                 );
             })
         )
     );
 
+    loadRemotePortsError$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(ManageRemotePortsActions.loadRemotePortsError),
+            map((action) =>
+                this.errorHelper.handleLoadingError(
+                    action.loadedTimestamp !== initialState.loadedTimestamp,
+                    action.errorResponse
+                )
+            )
+        )
+    );
+
     navigateToEditPort$ = createEffect(
         () =>
             this.actions$.pipe(
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/manage-remote-ports/manage-remote-ports.reducer.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/manage-remote-ports/manage-remote-ports.reducer.ts
index 98b5f244d6..aa8f0dcc7a 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/manage-remote-ports/manage-remote-ports.reducer.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/manage-remote-ports/manage-remote-ports.reducer.ts
@@ -21,6 +21,7 @@ import {
     configureRemotePortSuccess,
     loadRemotePorts,
     loadRemotePortsSuccess,
+    loadRemotePortsError,
     remotePortsBannerApiError,
     resetRemotePortsState
 } from './manage-remote-ports.actions';
@@ -51,6 +52,10 @@ export const manageRemotePortsReducer = createReducer(
         rpg: response.rpg,
         status: 'success' as const
     })),
+    on(loadRemotePortsError, (state, { status }) => ({
+        ...state,
+        status
+    })),
     on(remotePortsBannerApiError, (state) => ({
         ...state,
         saving: false
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/manage-remote-ports/manage-remote-ports.selectors.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/manage-remote-ports/manage-remote-ports.selectors.ts
index c58d69054e..cd7e7e6b31 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/manage-remote-ports/manage-remote-ports.selectors.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/manage-remote-ports/manage-remote-ports.selectors.ts
@@ -23,7 +23,10 @@ export const selectRemotePortsState = createFeatureSelector<RemotePortsState>(re
 
 export const selectSaving = createSelector(selectRemotePortsState, (state: RemotePortsState) => state.saving);
 
-export const selectStatus = createSelector(selectRemotePortsState, (state: RemotePortsState) => state.status);
+export const selectLoadedTimestamp = createSelector(
+    selectRemotePortsState,
+    (state: RemotePortsState) => state.loadedTimestamp
+);
 
 export const selectRpgIdFromRoute = createSelector(selectCurrentRoute, (route) => {
     if (route) {
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/parameter-context-listing.actions.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/parameter-context-listing.actions.ts
index ec70ea82b6..11b50535e1 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/parameter-context-listing.actions.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/parameter-context-listing.actions.ts
@@ -33,6 +33,11 @@ export const loadParameterContextsSuccess = createAction(
     props<{ response: LoadParameterContextsResponse }>()
 );
 
+export const loadParameterContextsError = createAction(
+    '[Parameter Context Listing] Load Parameter Contexts Error',
+    props<{ errorResponse: any; loadedTimestamp: string; status: 'pending' | 'success' }>()
+);
+
 export const parameterContextListingSnackbarApiError = createAction(
     '[Parameter Context Listing] Load Parameter Context Listing Snackbar Api Error',
     props<{ error: string }>()
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/parameter-context-listing.effects.spec.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/parameter-context-listing.effects.spec.ts
index e384df3ab4..2a445559bb 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/parameter-context-listing.effects.spec.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/parameter-context-listing.effects.spec.ts
@@ -18,7 +18,8 @@
 import { TestBed } from '@angular/core/testing';
 import { provideMockActions } from '@ngrx/effects/testing';
 import { Action } from '@ngrx/store';
-import { ReplaySubject, of } from 'rxjs';
+import { ReplaySubject, of, throwError } from 'rxjs';
+import { take } from 'rxjs/operators';
 import { provideMockStore } from '@ngrx/store/testing';
 import { MatDialog } from '@angular/material/dialog';
 import { Router } from '@angular/router';
@@ -30,11 +31,13 @@ import { ErrorHelper } from '../../../../service/error-helper.service';
 import { Storage, NiFiCommon } from '@nifi/shared';
 import { initialState } from './parameter-context-listing.reducer';
 import { ParameterContextUpdateRequest, ParameterContextUpdateRequestEntity } from '../../../../state/shared';
+import { HttpErrorResponse } from '@angular/common/http';
 
 describe('ParameterContextListingEffects', () => {
     interface SetupOptions {
         updateRequest?: ParameterContextUpdateRequestEntity | null;
         deleteUpdateRequestInitiated?: boolean;
+        listStateOverride?: any;
     }
 
     let action$: ReplaySubject<Action>;
@@ -54,24 +57,37 @@ describe('ParameterContextListingEffects', () => {
         };
     }
 
-    async function setup({ updateRequest = null, deleteUpdateRequestInitiated = false }: SetupOptions = {}) {
+    async function setup({
+        updateRequest = null,
+        deleteUpdateRequestInitiated = false,
+        listStateOverride
+    }: SetupOptions = {}) {
         await TestBed.configureTestingModule({
             providers: [
                 ParameterContextListingEffects,
                 provideMockActions(() => action$),
                 provideMockStore({
                     initialState: {
-                        parameterContextListing: {
-                            ...initialState,
-                            updateRequestEntity: updateRequest,
-                            deleteUpdateRequestInitiated
+                        parameterContexts: {
+                            parameterContextListing: {
+                                ...initialState,
+                                ...listStateOverride,
+                                updateRequestEntity: updateRequest,
+                                deleteUpdateRequestInitiated
+                            }
                         }
                     }
                 }),
-                { provide: ParameterContextService, useValue: { deleteParameterContextUpdate: jest.fn() } },
+                {
+                    provide: ParameterContextService,
+                    useValue: { deleteParameterContextUpdate: jest.fn(), getParameterContexts: jest.fn() }
+                },
                 { provide: MatDialog, useValue: { open: jest.fn() } },
                 { provide: Router, useValue: { navigate: jest.fn() } },
-                { provide: ErrorHelper, useValue: { getErrorString: jest.fn() } },
+                {
+                    provide: ErrorHelper,
+                    useValue: { getErrorString: jest.fn(), handleLoadingError: jest.fn(), fullScreenError: jest.fn() }
+                },
                 { provide: Storage, useValue: { setItem: jest.fn() } },
                 { provide: NiFiCommon, useValue: { stripProtocol: jest.fn() } }
             ]
@@ -81,7 +97,8 @@ describe('ParameterContextListingEffects', () => {
         const parameterContextService = TestBed.inject(ParameterContextService) as jest.Mocked<ParameterContextService>;
         action$ = new ReplaySubject<Action>();
 
-        return { effects, parameterContextService };
+        const errorHelper = TestBed.inject(ErrorHelper);
+        return { effects, parameterContextService, errorHelper };
     }
 
     beforeEach(() => {
@@ -99,6 +116,125 @@ describe('ParameterContextListingEffects', () => {
         expect(effects).toBeTruthy();
     });
 
+    describe('loadParameterContexts$', () => {
+        it('loads successfully', async () => {
+            const { effects, parameterContextService } = await setup();
+
+            action$.next(ParameterContextListingActions.loadParameterContexts());
+
+            (parameterContextService.getParameterContexts as jest.Mock).mockReturnValueOnce(
+                of({ parameterContexts: [], currentTime: 't' })
+            );
+
+            const result = await new Promise((resolve) =>
+                effects.loadParameterContexts$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(result).toEqual(
+                ParameterContextListingActions.loadParameterContextsSuccess({
+                    response: { parameterContexts: [], loadedTimestamp: 't' }
+                })
+            );
+        });
+
+        it('errors on initial load (hasExistingData=false)', async () => {
+            const { effects, parameterContextService } = await setup();
+
+            action$.next(ParameterContextListingActions.loadParameterContexts());
+
+            const error = new HttpErrorResponse({ status: 500 });
+            (parameterContextService.getParameterContexts as jest.Mock).mockImplementationOnce(() =>
+                throwError(() => error)
+            );
+
+            const result = await new Promise((resolve) =>
+                effects.loadParameterContexts$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(result).toEqual(
+                ParameterContextListingActions.loadParameterContextsError({
+                    errorResponse: error,
+                    loadedTimestamp: initialState.loadedTimestamp,
+                    status: 'pending'
+                })
+            );
+        });
+
+        it('errors on refresh (hasExistingData=true)', async () => {
+            const stateWithData = { loadedTimestamp: 'prev' };
+            const { effects, parameterContextService } = await setup({ listStateOverride: stateWithData });
+
+            action$.next(ParameterContextListingActions.loadParameterContexts());
+
+            const error = new HttpErrorResponse({ status: 500 });
+            (parameterContextService.getParameterContexts as jest.Mock).mockImplementationOnce(() =>
+                throwError(() => error)
+            );
+
+            const result = await new Promise((resolve) =>
+                effects.loadParameterContexts$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(result).toEqual(
+                ParameterContextListingActions.loadParameterContextsError({
+                    errorResponse: error,
+                    loadedTimestamp: stateWithData.loadedTimestamp,
+                    status: 'success'
+                })
+            );
+        });
+    });
+
+    describe('loadParameterContextsError$', () => {
+        it('should handle parameter contexts error for initial load', async () => {
+            const { effects, errorHelper } = await setup();
+
+            const error = new HttpErrorResponse({ status: 500 });
+            const errorAction = ParameterContextListingActions.parameterContextListingBannerApiError({
+                error: 'Error message'
+            });
+            action$.next(
+                ParameterContextListingActions.loadParameterContextsError({
+                    errorResponse: error,
+                    loadedTimestamp: initialState.loadedTimestamp,
+                    status: 'pending'
+                })
+            );
+            jest.spyOn(errorHelper, 'handleLoadingError').mockReturnValueOnce(errorAction);
+
+            const result = await new Promise((resolve) =>
+                effects.loadParameterContextsError$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(errorHelper.handleLoadingError).toHaveBeenCalledWith(false, error);
+            expect(result).toEqual(errorAction);
+        });
+
+        it('should handle parameter contexts error for refresh', async () => {
+            const { effects, errorHelper } = await setup();
+
+            const error = new HttpErrorResponse({ status: 500 });
+            const errorAction = ParameterContextListingActions.parameterContextListingBannerApiError({
+                error: 'Error message'
+            });
+            action$.next(
+                ParameterContextListingActions.loadParameterContextsError({
+                    errorResponse: error,
+                    loadedTimestamp: 'prev',
+                    status: 'success'
+                })
+            );
+            jest.spyOn(errorHelper, 'handleLoadingError').mockReturnValueOnce(errorAction);
+
+            const result = await new Promise((resolve) =>
+                effects.loadParameterContextsError$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(errorHelper.handleLoadingError).toHaveBeenCalledWith(true, error);
+            expect(result).toEqual(errorAction);
+        });
+    });
+
     describe('stopPollingParameterContextUpdateRequest$', () => {
         it('should dispatch deleteParameterContextUpdateRequest when triggered', async () => {
             const { effects } = await setup();
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/parameter-context-listing.effects.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/parameter-context-listing.effects.ts
index 1ca35bf3e5..e2eb5de8e2 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/parameter-context-listing.effects.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/parameter-context-listing.effects.ts
@@ -41,8 +41,8 @@ import { Router } from '@angular/router';
 import { ParameterContextService } from '../../service/parameter-contexts.service';
 import { Parameter, YesNoDialog } from '@nifi/shared';
 import {
+    selectParameterContextLoadedTimestamp,
     selectParameterContexts,
-    selectParameterContextStatus,
     selectSaving,
     selectUpdateRequest,
     selectDeleteUpdateRequestInitiated
@@ -52,6 +52,7 @@ import { EditParameterDialog } from '../../../../ui/common/edit-parameter-dialog
 import { OkDialog } from '../../../../ui/common/ok-dialog/ok-dialog.component';
 import { ErrorHelper } from '../../../../service/error-helper.service';
 import { HttpErrorResponse } from '@angular/common/http';
+import { initialState } from './parameter-context-listing.reducer';
 import { BackNavigation } from '../../../../state/navigation';
 import { isDefinedAndNotNull, MEDIUM_DIALOG, SMALL_DIALOG, XL_DIALOG, NiFiCommon, Storage } from '@nifi/shared';
 import { ErrorContextKey } from '../../../../state/error';
@@ -70,8 +71,8 @@ export class ParameterContextListingEffects {
     loadParameterContexts$ = createEffect(() =>
         this.actions$.pipe(
             ofType(ParameterContextListingActions.loadParameterContexts),
-            concatLatestFrom(() => this.store.select(selectParameterContextStatus)),
-            switchMap(([, status]) =>
+            concatLatestFrom(() => this.store.select(selectParameterContextLoadedTimestamp)),
+            switchMap(([, loadedTimestamp]) =>
                 from(this.parameterContextService.getParameterContexts()).pipe(
                     map((response) =>
                         ParameterContextListingActions.loadParameterContextsSuccess({
@@ -82,13 +83,31 @@ export class ParameterContextListingEffects {
                         })
                     ),
                     catchError((errorResponse: HttpErrorResponse) =>
-                        of(this.errorHelper.handleLoadingError(status, errorResponse))
+                        of(
+                            ParameterContextListingActions.loadParameterContextsError({
+                                errorResponse,
+                                loadedTimestamp,
+                                status: loadedTimestamp !== initialState.loadedTimestamp ? 'success' : 'pending'
+                            })
+                        )
                     )
                 )
             )
         )
     );
 
+    loadParameterContextsError$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(ParameterContextListingActions.loadParameterContextsError),
+            map((action) =>
+                this.errorHelper.handleLoadingError(
+                    action.loadedTimestamp !== initialState.loadedTimestamp,
+                    action.errorResponse
+                )
+            )
+        )
+    );
+
     openNewParameterContextDialog$ = createEffect(
         () =>
             this.actions$.pipe(
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/parameter-context-listing.reducer.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/parameter-context-listing.reducer.ts
index 21d9e67997..1b87cdda99 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/parameter-context-listing.reducer.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/parameter-context-listing.reducer.ts
@@ -25,6 +25,7 @@ import {
     editParameterContextComplete,
     loadParameterContexts,
     loadParameterContextsSuccess,
+    loadParameterContextsError,
     parameterContextListingSnackbarApiError,
     parameterContextListingBannerApiError,
     pollParameterContextUpdateRequestSuccess,
@@ -58,6 +59,10 @@ export const parameterContextListingReducer = createReducer(
         error: null,
         status: 'success' as const
     })),
+    on(loadParameterContextsError, (state, { status }) => ({
+        ...state,
+        status
+    })),
     on(parameterContextListingSnackbarApiError, parameterContextListingBannerApiError, (state) => ({
         ...state,
         saving: false
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/parameter-context-listing.selectors.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/parameter-context-listing.selectors.ts
index 755dafe58e..c7129487c2 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/parameter-context-listing.selectors.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/parameter-contexts/state/parameter-context-listing/parameter-context-listing.selectors.ts
@@ -55,9 +55,9 @@ export const selectParameterContexts = createSelector(
     (state: ParameterContextListingState) => state.parameterContexts
 );
 
-export const selectParameterContextStatus = createSelector(
+export const selectParameterContextLoadedTimestamp = createSelector(
     selectParameterContextListingState,
-    (state: ParameterContextListingState) => state.status
+    (state: ParameterContextListingState) => state.loadedTimestamp
 );
 
 export const selectContext = (id: string) =>
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/flow-analysis-rules/flow-analysis-rules.actions.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/flow-analysis-rules/flow-analysis-rules.actions.ts
index 11c9dc2778..6046cdeb94 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/flow-analysis-rules/flow-analysis-rules.actions.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/flow-analysis-rules/flow-analysis-rules.actions.ts
@@ -42,6 +42,11 @@ export const loadFlowAnalysisRulesSuccess = createAction(
     props<{ response: LoadFlowAnalysisRulesResponse }>()
 );
 
+export const loadFlowAnalysisRulesError = createAction(
+    '[Flow Analysis Rules] Load Flow Analysis Rules Error',
+    props<{ errorResponse: any; loadedTimestamp: string; status: 'pending' | 'success' }>()
+);
+
 export const openConfigureFlowAnalysisRuleDialog = createAction(
     '[Flow Analysis Rules] Open Flow Analysis Rule Dialog',
     props<{ request: EditFlowAnalysisRuleDialogRequest }>()
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/flow-analysis-rules/flow-analysis-rules.effects.spec.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/flow-analysis-rules/flow-analysis-rules.effects.spec.ts
new file mode 100644
index 0000000000..f57c5d79c7
--- /dev/null
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/flow-analysis-rules/flow-analysis-rules.effects.spec.ts
@@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { TestBed } from '@angular/core/testing';
+import { provideMockActions } from '@ngrx/effects/testing';
+import { MockStore, provideMockStore } from '@ngrx/store/testing';
+import { Action } from '@ngrx/store';
+import { ReplaySubject, of, take, throwError } from 'rxjs';
+import { HttpErrorResponse } from '@angular/common/http';
+
+import * as FlowAnalysisRuleActions from './flow-analysis-rules.actions';
+import { FlowAnalysisRulesEffects } from './flow-analysis-rules.effects';
+import { FlowAnalysisRuleService } from '../../service/flow-analysis-rule.service';
+import { ManagementControllerServiceService } from '../../service/management-controller-service.service';
+import { ErrorHelper } from '../../../../service/error-helper.service';
+import { MatDialog } from '@angular/material/dialog';
+import { Router } from '@angular/router';
+import { PropertyTableHelperService } from '../../../../service/property-table-helper.service';
+import { ExtensionTypesService } from '../../../../service/extension-types.service';
+import { initialState } from './flow-analysis-rules.reducer';
+import { settingsFeatureKey } from '../index';
+import { flowAnalysisRulesFeatureKey } from './index';
+
+describe('FlowAnalysisRulesEffects', () => {
+    interface SetupOptions {
+        flowAnalysisRulesState?: any;
+    }
+
+    const mockResponse = {
+        flowAnalysisRules: [],
+        currentTime: '2023-01-01 12:00:00 EST'
+    };
+
+    async function setup({ flowAnalysisRulesState = initialState }: SetupOptions = {}) {
+        await TestBed.configureTestingModule({
+            providers: [
+                FlowAnalysisRulesEffects,
+                provideMockActions(() => action$),
+                provideMockStore({
+                    initialState: {
+                        [settingsFeatureKey]: {
+                            [flowAnalysisRulesFeatureKey]: flowAnalysisRulesState
+                        }
+                    }
+                }),
+                { provide: FlowAnalysisRuleService, useValue: { getFlowAnalysisRule: jest.fn() } },
+                { provide: ManagementControllerServiceService, useValue: {} },
+                { provide: ErrorHelper, useValue: { handleLoadingError: jest.fn() } },
+                { provide: MatDialog, useValue: { open: jest.fn(), closeAll: jest.fn() } },
+                { provide: Router, useValue: { navigate: jest.fn() } },
+                { provide: PropertyTableHelperService, useValue: {} },
+                { provide: ExtensionTypesService, useValue: {} }
+            ]
+        }).compileComponents();
+
+        const store = TestBed.inject(MockStore);
+        const effects = TestBed.inject(FlowAnalysisRulesEffects);
+        const flowAnalysisRuleService = TestBed.inject(FlowAnalysisRuleService);
+        const errorHelper = TestBed.inject(ErrorHelper);
+
+        return { effects, store, flowAnalysisRuleService, errorHelper };
+    }
+
+    let action$: ReplaySubject<Action>;
+    beforeEach(() => {
+        action$ = new ReplaySubject<Action>();
+    });
+
+    it('should create', async () => {
+        const { effects } = await setup();
+        expect(effects).toBeTruthy();
+    });
+
+    describe('Load Flow Analysis Rules', () => {
+        it('should load flow analysis rules successfully', async () => {
+            const { effects, flowAnalysisRuleService } = await setup();
+
+            action$.next(FlowAnalysisRuleActions.loadFlowAnalysisRules());
+            jest.spyOn(flowAnalysisRuleService, 'getFlowAnalysisRule').mockReturnValueOnce(of(mockResponse) as never);
+
+            const result = await new Promise((resolve) =>
+                effects.loadFlowAnalysisRule$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(result).toEqual(
+                FlowAnalysisRuleActions.loadFlowAnalysisRulesSuccess({
+                    response: {
+                        flowAnalysisRules: mockResponse.flowAnalysisRules,
+                        loadedTimestamp: mockResponse.currentTime
+                    }
+                })
+            );
+        });
+
+        it('should fail to load flow analysis rules on initial load with hasExistingData=false', async () => {
+            const { effects, flowAnalysisRuleService } = await setup();
+
+            action$.next(FlowAnalysisRuleActions.loadFlowAnalysisRules());
+            const error = new HttpErrorResponse({ status: 500 });
+            jest.spyOn(flowAnalysisRuleService, 'getFlowAnalysisRule').mockImplementationOnce(() =>
+                throwError(() => error)
+            );
+
+            const result = await new Promise((resolve) =>
+                effects.loadFlowAnalysisRule$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(result).toEqual(
+                FlowAnalysisRuleActions.loadFlowAnalysisRulesError({
+                    errorResponse: error,
+                    loadedTimestamp: initialState.loadedTimestamp,
+                    status: 'pending'
+                })
+            );
+        });
+
+        it('should fail to load flow analysis rules on refresh with hasExistingData=true', async () => {
+            const stateWithData = { ...initialState, loadedTimestamp: '2023-01-01 11:00:00 EST' };
+            const { effects, flowAnalysisRuleService } = await setup({
+                flowAnalysisRulesState: stateWithData
+            });
+
+            action$.next(FlowAnalysisRuleActions.loadFlowAnalysisRules());
+            const error = new HttpErrorResponse({ status: 500 });
+            jest.spyOn(flowAnalysisRuleService, 'getFlowAnalysisRule').mockImplementationOnce(() =>
+                throwError(() => error)
+            );
+
+            const result = await new Promise((resolve) =>
+                effects.loadFlowAnalysisRule$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(result).toEqual(
+                FlowAnalysisRuleActions.loadFlowAnalysisRulesError({
+                    errorResponse: error,
+                    loadedTimestamp: stateWithData.loadedTimestamp,
+                    status: 'success'
+                })
+            );
+        });
+    });
+
+    describe('Load Flow Analysis Rules Error', () => {
+        it('should handle flow analysis rules error for initial load', async () => {
+            const { effects, errorHelper } = await setup();
+
+            const error = new HttpErrorResponse({ status: 500 });
+            const errorAction = FlowAnalysisRuleActions.flowAnalysisRuleBannerApiError({ error: 'Error loading' });
+            action$.next(
+                FlowAnalysisRuleActions.loadFlowAnalysisRulesError({
+                    errorResponse: error,
+                    loadedTimestamp: initialState.loadedTimestamp,
+                    status: 'pending'
+                })
+            );
+            jest.spyOn(errorHelper, 'handleLoadingError').mockReturnValueOnce(errorAction);
+
+            const result = await new Promise((resolve) =>
+                effects.loadFlowAnalysisRulesError$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(errorHelper.handleLoadingError).toHaveBeenCalledWith(false, error);
+            expect(result).toEqual(errorAction);
+        });
+
+        it('should handle flow analysis rules error for refresh', async () => {
+            const { effects, errorHelper } = await setup();
+
+            const error = new HttpErrorResponse({ status: 500 });
+            const errorAction = FlowAnalysisRuleActions.flowAnalysisRuleBannerApiError({ error: 'Error loading' });
+            action$.next(
+                FlowAnalysisRuleActions.loadFlowAnalysisRulesError({
+                    errorResponse: error,
+                    loadedTimestamp: '2023-01-01 11:00:00 EST',
+                    status: 'success'
+                })
+            );
+            jest.spyOn(errorHelper, 'handleLoadingError').mockReturnValueOnce(errorAction);
+
+            const result = await new Promise((resolve) =>
+                effects.loadFlowAnalysisRulesError$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(errorHelper.handleLoadingError).toHaveBeenCalledWith(true, error);
+            expect(result).toEqual(errorAction);
+        });
+    });
+});
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/flow-analysis-rules/flow-analysis-rules.effects.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/flow-analysis-rules/flow-analysis-rules.effects.ts
index e3349fb88e..04e317778a 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/flow-analysis-rules/flow-analysis-rules.effects.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/flow-analysis-rules/flow-analysis-rules.effects.ts
@@ -39,7 +39,8 @@ import {
 import { PropertyTableHelperService } from '../../../../service/property-table-helper.service';
 import * as ErrorActions from '../../../../state/error/error.actions';
 import { ErrorHelper } from '../../../../service/error-helper.service';
-import { selectStatus } from './flow-analysis-rules.selectors';
+import { selectLoadedTimestamp } from './flow-analysis-rules.selectors';
+import { initialState } from './flow-analysis-rules.reducer';
 import { HttpErrorResponse } from '@angular/common/http';
 import { ChangeComponentVersionDialog } from '../../../../ui/common/change-component-version-dialog/change-component-version-dialog';
 import { ExtensionTypesService } from '../../../../service/extension-types.service';
@@ -70,8 +71,8 @@ export class FlowAnalysisRulesEffects {
     loadFlowAnalysisRule$ = createEffect(() =>
         this.actions$.pipe(
             ofType(FlowAnalysisRuleActions.loadFlowAnalysisRules),
-            concatLatestFrom(() => this.store.select(selectStatus)),
-            switchMap(([, status]) =>
+            concatLatestFrom(() => this.store.select(selectLoadedTimestamp)),
+            switchMap(([, loadedTimestamp]) =>
                 from(this.flowAnalysisRuleService.getFlowAnalysisRule()).pipe(
                     map((response) =>
                         FlowAnalysisRuleActions.loadFlowAnalysisRulesSuccess({
@@ -82,13 +83,31 @@ export class FlowAnalysisRulesEffects {
                         })
                     ),
                     catchError((errorResponse: HttpErrorResponse) =>
-                        of(this.errorHelper.handleLoadingError(status, errorResponse))
+                        of(
+                            FlowAnalysisRuleActions.loadFlowAnalysisRulesError({
+                                errorResponse,
+                                loadedTimestamp,
+                                status: loadedTimestamp !== initialState.loadedTimestamp ? 'success' : 'pending'
+                            })
+                        )
                     )
                 )
             )
         )
     );
 
+    loadFlowAnalysisRulesError$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(FlowAnalysisRuleActions.loadFlowAnalysisRulesError),
+            map((action) =>
+                this.errorHelper.handleLoadingError(
+                    action.loadedTimestamp !== initialState.loadedTimestamp,
+                    action.errorResponse
+                )
+            )
+        )
+    );
+
     openNewFlowAnalysisRuleDialog$ = createEffect(
         () =>
             this.actions$.pipe(
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/flow-analysis-rules/flow-analysis-rules.reducer.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/flow-analysis-rules/flow-analysis-rules.reducer.ts
index d558869999..0ef9563918 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/flow-analysis-rules/flow-analysis-rules.reducer.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/flow-analysis-rules/flow-analysis-rules.reducer.ts
@@ -26,6 +26,7 @@ import {
     deleteFlowAnalysisRuleSuccess,
     loadFlowAnalysisRules,
     loadFlowAnalysisRulesSuccess,
+    loadFlowAnalysisRulesError,
     flowAnalysisRuleBannerApiError,
     resetFlowAnalysisRulesState,
     disableFlowAnalysisRule,
@@ -58,6 +59,10 @@ export const flowAnalysisRulesReducer = createReducer(
         loadedTimestamp: response.loadedTimestamp,
         status: 'success' as const
     })),
+    on(loadFlowAnalysisRulesError, (state, { status }) => ({
+        ...state,
+        status
+    })),
     on(flowAnalysisRuleBannerApiError, flowAnalysisRuleSnackbarApiError, (state) => ({
         ...state,
         saving: false
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/flow-analysis-rules/flow-analysis-rules.selectors.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/flow-analysis-rules/flow-analysis-rules.selectors.ts
index c26bf32830..5f5850bd52 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/flow-analysis-rules/flow-analysis-rules.selectors.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/flow-analysis-rules/flow-analysis-rules.selectors.ts
@@ -30,9 +30,9 @@ export const selectSaving = createSelector(
     (state: FlowAnalysisRulesState) => state.saving
 );
 
-export const selectStatus = createSelector(
+export const selectLoadedTimestamp = createSelector(
     selectFlowAnalysisRulesState,
-    (state: FlowAnalysisRulesState) => state.status
+    (state: FlowAnalysisRulesState) => state.loadedTimestamp
 );
 
 export const selectFlowAnalysisRuleIdFromRoute = createSelector(selectCurrentRoute, (route) => {
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/general/general.effects.spec.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/general/general.effects.spec.ts
new file mode 100644
index 0000000000..b2e60c1874
--- /dev/null
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/general/general.effects.spec.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 { TestBed } from '@angular/core/testing';
+import { provideMockActions } from '@ngrx/effects/testing';
+import { provideMockStore } from '@ngrx/store/testing';
+import { ReplaySubject, of, take, throwError } from 'rxjs';
+import { Action } from '@ngrx/store';
+
+import * as GeneralActions from './general.actions';
+import { GeneralEffects } from './general.effects';
+import { ControllerService } from '../../service/controller.service';
+import { ErrorHelper } from '../../../../service/error-helper.service';
+import { HttpErrorResponse } from '@angular/common/http';
+
+describe('GeneralEffects', () => {
+    interface SetupOptions {
+        settingsState?: any;
+    }
+
+    async function setup({ settingsState = { general: {} } }: SetupOptions = {}) {
+        await TestBed.configureTestingModule({
+            providers: [
+                GeneralEffects,
+                provideMockActions(() => action$),
+                provideMockStore({
+                    initialState: {
+                        settings: settingsState
+                    }
+                }),
+                {
+                    provide: ControllerService,
+                    useValue: {
+                        getControllerConfig: jest.fn(),
+                        updateControllerConfig: jest.fn()
+                    }
+                },
+                {
+                    provide: ErrorHelper,
+                    useValue: {
+                        fullScreenError: jest.fn(),
+                        getErrorString: jest.fn()
+                    }
+                }
+            ]
+        }).compileComponents();
+
+        const effects = TestBed.inject(GeneralEffects);
+        const controllerService = TestBed.inject(ControllerService);
+        const errorHelper = TestBed.inject(ErrorHelper);
+
+        return { effects, controllerService, errorHelper };
+    }
+
+    let action$: ReplaySubject<Action>;
+    beforeEach(() => {
+        action$ = new ReplaySubject<Action>();
+    });
+
+    it('should create', async () => {
+        const { effects } = await setup();
+        expect(effects).toBeTruthy();
+    });
+
+    describe('loadControllerConfig', () => {
+        it('loads successfully', async () => {
+            const { effects, controllerService } = await setup();
+
+            action$.next(GeneralActions.loadControllerConfig());
+
+            const controllerEntity = {
+                revision: { version: 1, clientId: 'c1' },
+                component: { maxTimerDrivenThreadCount: 10 }
+            } as any;
+            jest.spyOn(controllerService, 'getControllerConfig').mockReturnValueOnce(of(controllerEntity) as never);
+
+            const result = await new Promise((resolve) =>
+                effects.loadControllerConfig$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(result).toEqual(
+                GeneralActions.loadControllerConfigSuccess({
+                    response: {
+                        controller: controllerEntity
+                    }
+                })
+            );
+        });
+
+        it('handles error with fullScreenError', async () => {
+            const { effects, controllerService, errorHelper } = await setup();
+
+            action$.next(GeneralActions.loadControllerConfig());
+
+            const error = new HttpErrorResponse({ status: 500 });
+            const errorAction = GeneralActions.controllerConfigApiError({ error: 'e' });
+
+            jest.spyOn(controllerService, 'getControllerConfig').mockImplementationOnce(() => throwError(() => error));
+            jest.spyOn(errorHelper, 'fullScreenError').mockReturnValueOnce(errorAction);
+
+            const result = await new Promise((resolve) =>
+                effects.loadControllerConfig$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(errorHelper.fullScreenError).toHaveBeenCalledWith(error);
+            expect(result).toEqual(errorAction);
+        });
+    });
+});
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/general/general.effects.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/general/general.effects.ts
index 09d6503b25..6b6f4066bb 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/general/general.effects.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/general/general.effects.ts
@@ -17,7 +17,6 @@
 
 import { Injectable, inject } from '@angular/core';
 import { Actions, createEffect, ofType } from '@ngrx/effects';
-import { concatLatestFrom } from '@ngrx/operators';
 import * as GeneralActions from './general.actions';
 import * as ErrorActions from '../../../../state/error/error.actions';
 import { catchError, from, map, of, switchMap, tap } from 'rxjs';
@@ -25,16 +24,12 @@ import { ControllerService } from '../../service/controller.service';
 import { MatDialog } from '@angular/material/dialog';
 import { OkDialog } from '../../../../ui/common/ok-dialog/ok-dialog.component';
 import { ErrorHelper } from '../../../../service/error-helper.service';
-import { selectStatus } from './general.selectors';
-import { NiFiState } from '../../../../state';
-import { Store } from '@ngrx/store';
 import { HttpErrorResponse } from '@angular/common/http';
 import { SMALL_DIALOG } from '@nifi/shared';
 
 @Injectable()
 export class GeneralEffects {
     private actions$ = inject(Actions);
-    private store = inject<Store<NiFiState>>(Store);
     private controllerService = inject(ControllerService);
     private errorHelper = inject(ErrorHelper);
     private dialog = inject(MatDialog);
@@ -42,8 +37,7 @@ export class GeneralEffects {
     loadControllerConfig$ = createEffect(() =>
         this.actions$.pipe(
             ofType(GeneralActions.loadControllerConfig),
-            concatLatestFrom(() => this.store.select(selectStatus)),
-            switchMap(([, status]) =>
+            switchMap(() =>
                 from(this.controllerService.getControllerConfig()).pipe(
                     map((response) =>
                         GeneralActions.loadControllerConfigSuccess({
@@ -53,7 +47,7 @@ export class GeneralEffects {
                         })
                     ),
                     catchError((errorResponse: HttpErrorResponse) =>
-                        of(this.errorHelper.handleLoadingError(status, errorResponse))
+                        of(this.errorHelper.fullScreenError(errorResponse))
                     )
                 )
             )
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/general/general.selectors.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/general/general.selectors.ts
index 01b30d26e8..7c25d199bf 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/general/general.selectors.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/general/general.selectors.ts
@@ -22,5 +22,3 @@ import { generalFeatureKey, GeneralState } from './index';
 export const selectGeneral = createSelector(selectSettingsState, (state: SettingsState) => state[generalFeatureKey]);
 
 export const selectSaving = createSelector(selectGeneral, (state: GeneralState) => state.saving);
-
-export const selectStatus = createSelector(selectGeneral, (state: GeneralState) => state.status);
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.actions.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.actions.ts
index 9e470b13d6..f9a5e76528 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.actions.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.actions.ts
@@ -46,6 +46,11 @@ export const loadManagementControllerServicesSuccess = createAction(
     props<{ response: LoadManagementControllerServicesResponse }>()
 );
 
+export const loadManagementControllerServicesError = createAction(
+    '[Management Controller Services] Load Management Controller Services Error',
+    props<{ errorResponse: any; loadedTimestamp: string; status: 'pending' | 'success' }>()
+);
+
 export const managementControllerServicesBannerApiError = createAction(
     '[Management Controller Services] Management Controller Services Banner Api Error',
     props<{ error: string }>()
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.effects.spec.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.effects.spec.ts
new file mode 100644
index 0000000000..e052cdfca7
--- /dev/null
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.effects.spec.ts
@@ -0,0 +1,206 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { TestBed } from '@angular/core/testing';
+import { provideMockActions } from '@ngrx/effects/testing';
+import { MockStore, provideMockStore } from '@ngrx/store/testing';
+import { Action } from '@ngrx/store';
+import { ReplaySubject, of, take, throwError } from 'rxjs';
+import { HttpErrorResponse } from '@angular/common/http';
+
+import * as ManagementControllerServicesActions from './management-controller-services.actions';
+import { ManagementControllerServicesEffects } from './management-controller-services.effects';
+import { ManagementControllerServiceService } from '../../service/management-controller-service.service';
+import { ErrorHelper } from '../../../../service/error-helper.service';
+import { MatDialog } from '@angular/material/dialog';
+import { Router } from '@angular/router';
+import { PropertyTableHelperService } from '../../../../service/property-table-helper.service';
+import { Client } from '../../../../service/client.service';
+import { ExtensionTypesService } from '../../../../service/extension-types.service';
+import { initialState } from './management-controller-services.reducer';
+
+describe('ManagementControllerServicesEffects', () => {
+    interface SetupOptions {
+        managementControllerServicesState?: any;
+    }
+
+    const mockResponse = {
+        controllerServices: [],
+        currentTime: '2023-01-01 12:00:00 EST'
+    };
+
+    async function setup({ managementControllerServicesState = initialState }: SetupOptions = {}) {
+        await TestBed.configureTestingModule({
+            providers: [
+                ManagementControllerServicesEffects,
+                provideMockActions(() => action$),
+                provideMockStore({
+                    initialState: {
+                        settings: {
+                            managementControllerServices: managementControllerServicesState
+                        }
+                    }
+                }),
+                { provide: ManagementControllerServiceService, useValue: { getControllerServices: jest.fn() } },
+                { provide: ErrorHelper, useValue: { handleLoadingError: jest.fn() } },
+                { provide: MatDialog, useValue: { open: jest.fn(), closeAll: jest.fn() } },
+                { provide: Router, useValue: { navigate: jest.fn() } },
+                { provide: PropertyTableHelperService, useValue: {} },
+                { provide: Client, useValue: {} },
+                { provide: ExtensionTypesService, useValue: {} }
+            ]
+        }).compileComponents();
+
+        const store = TestBed.inject(MockStore);
+        const effects = TestBed.inject(ManagementControllerServicesEffects);
+        const managementControllerServiceService = TestBed.inject(ManagementControllerServiceService);
+        const errorHelper = TestBed.inject(ErrorHelper);
+
+        return { effects, store, managementControllerServiceService, errorHelper };
+    }
+
+    let action$: ReplaySubject<Action>;
+    beforeEach(() => {
+        action$ = new ReplaySubject<Action>();
+    });
+
+    it('should create', async () => {
+        const { effects } = await setup();
+        expect(effects).toBeTruthy();
+    });
+
+    describe('Load Management Controller Services', () => {
+        it('should load management controller services successfully', async () => {
+            const { effects, managementControllerServiceService } = await setup();
+
+            action$.next(ManagementControllerServicesActions.loadManagementControllerServices());
+            jest.spyOn(managementControllerServiceService, 'getControllerServices').mockReturnValueOnce(
+                of(mockResponse) as never
+            );
+
+            const result = await new Promise((resolve) =>
+                effects.loadManagementControllerServices$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(result).toEqual(
+                ManagementControllerServicesActions.loadManagementControllerServicesSuccess({
+                    response: {
+                        controllerServices: mockResponse.controllerServices,
+                        loadedTimestamp: mockResponse.currentTime
+                    }
+                })
+            );
+        });
+
+        it('should fail to load management controller services on initial load with hasExistingData=false', async () => {
+            const { effects, managementControllerServiceService } = await setup();
+
+            action$.next(ManagementControllerServicesActions.loadManagementControllerServices());
+            const error = new HttpErrorResponse({ status: 500 });
+            jest.spyOn(managementControllerServiceService, 'getControllerServices').mockImplementationOnce(() =>
+                throwError(() => error)
+            );
+
+            const result = await new Promise((resolve) =>
+                effects.loadManagementControllerServices$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(result).toEqual(
+                ManagementControllerServicesActions.loadManagementControllerServicesError({
+                    errorResponse: error,
+                    loadedTimestamp: initialState.loadedTimestamp,
+                    status: 'pending'
+                })
+            );
+        });
+
+        it('should fail to load management controller services on refresh with hasExistingData=true', async () => {
+            const stateWithData = { ...initialState, loadedTimestamp: '2023-01-01 11:00:00 EST' };
+            const { effects, managementControllerServiceService } = await setup({
+                managementControllerServicesState: stateWithData
+            });
+
+            action$.next(ManagementControllerServicesActions.loadManagementControllerServices());
+            const error = new HttpErrorResponse({ status: 500 });
+            jest.spyOn(managementControllerServiceService, 'getControllerServices').mockImplementationOnce(() =>
+                throwError(() => error)
+            );
+
+            const result = await new Promise((resolve) =>
+                effects.loadManagementControllerServices$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(result).toEqual(
+                ManagementControllerServicesActions.loadManagementControllerServicesError({
+                    errorResponse: error,
+                    loadedTimestamp: stateWithData.loadedTimestamp,
+                    status: 'success'
+                })
+            );
+        });
+    });
+
+    describe('Load Management Controller Services Error', () => {
+        it('should handle management controller services error for initial load', async () => {
+            const { effects, errorHelper } = await setup();
+
+            const error = new HttpErrorResponse({ status: 500 });
+            const errorAction = ManagementControllerServicesActions.managementControllerServicesBannerApiError({
+                error: 'Error message'
+            });
+            action$.next(
+                ManagementControllerServicesActions.loadManagementControllerServicesError({
+                    errorResponse: error,
+                    loadedTimestamp: initialState.loadedTimestamp,
+                    status: 'pending'
+                })
+            );
+            jest.spyOn(errorHelper, 'handleLoadingError').mockReturnValueOnce(errorAction);
+
+            const result = await new Promise((resolve) =>
+                effects.loadManagementControllerServicesError$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(errorHelper.handleLoadingError).toHaveBeenCalledWith(false, error);
+            expect(result).toEqual(errorAction);
+        });
+
+        it('should handle management controller services error for refresh', async () => {
+            const { effects, errorHelper } = await setup();
+
+            const error = new HttpErrorResponse({ status: 500 });
+            const errorAction = ManagementControllerServicesActions.managementControllerServicesBannerApiError({
+                error: 'Error message'
+            });
+            action$.next(
+                ManagementControllerServicesActions.loadManagementControllerServicesError({
+                    errorResponse: error,
+                    loadedTimestamp: '2023-01-01 11:00:00 EST',
+                    status: 'success'
+                })
+            );
+            jest.spyOn(errorHelper, 'handleLoadingError').mockReturnValueOnce(errorAction);
+
+            const result = await new Promise((resolve) =>
+                effects.loadManagementControllerServicesError$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(errorHelper.handleLoadingError).toHaveBeenCalledWith(true, error);
+            expect(result).toEqual(errorAction);
+        });
+    });
+});
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.effects.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.effects.ts
index c53bdd5a96..a141aec27c 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.effects.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.effects.ts
@@ -39,13 +39,14 @@ import {
     UpdateControllerServiceRequest
 } from '../../../../state/shared';
 import { Router } from '@angular/router';
-import { selectSaving, selectStatus } from './management-controller-services.selectors';
+import { selectLoadedTimestamp, selectSaving } from './management-controller-services.selectors';
 import { EnableControllerService } from '../../../../ui/common/controller-service/enable-controller-service/enable-controller-service.component';
 import { DisableControllerService } from '../../../../ui/common/controller-service/disable-controller-service/disable-controller-service.component';
 import { PropertyTableHelperService } from '../../../../service/property-table-helper.service';
 import { HttpErrorResponse } from '@angular/common/http';
 import { ErrorHelper } from '../../../../service/error-helper.service';
 import { ComponentType, LARGE_DIALOG, SMALL_DIALOG, XL_DIALOG, YesNoDialog } from '@nifi/shared';
+import { initialState } from './management-controller-services.reducer';
 import { ChangeComponentVersionDialog } from '../../../../ui/common/change-component-version-dialog/change-component-version-dialog';
 import { ExtensionTypesService } from '../../../../service/extension-types.service';
 import {
@@ -75,8 +76,8 @@ export class ManagementControllerServicesEffects {
     loadManagementControllerServices$ = createEffect(() =>
         this.actions$.pipe(
             ofType(ManagementControllerServicesActions.loadManagementControllerServices),
-            concatLatestFrom(() => this.store.select(selectStatus)),
-            switchMap(([, status]) =>
+            concatLatestFrom(() => this.store.select(selectLoadedTimestamp)),
+            switchMap(([, loadedTimestamp]) =>
                 from(this.managementControllerServiceService.getControllerServices()).pipe(
                     map((response) =>
                         ManagementControllerServicesActions.loadManagementControllerServicesSuccess({
@@ -87,13 +88,31 @@ export class ManagementControllerServicesEffects {
                         })
                     ),
                     catchError((errorResponse: HttpErrorResponse) =>
-                        of(this.errorHelper.handleLoadingError(status, errorResponse))
+                        of(
+                            ManagementControllerServicesActions.loadManagementControllerServicesError({
+                                errorResponse,
+                                loadedTimestamp,
+                                status: loadedTimestamp !== initialState.loadedTimestamp ? 'success' : 'pending'
+                            })
+                        )
                     )
                 )
             )
         )
     );
 
+    loadManagementControllerServicesError$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(ManagementControllerServicesActions.loadManagementControllerServicesError),
+            map((action) =>
+                this.errorHelper.handleLoadingError(
+                    action.loadedTimestamp !== initialState.loadedTimestamp,
+                    action.errorResponse
+                )
+            )
+        )
+    );
+
     openNewControllerServiceDialog$ = createEffect(
         () =>
             this.actions$.pipe(
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.reducer.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.reducer.ts
index 8e533504a5..4915195286 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.reducer.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.reducer.ts
@@ -27,6 +27,7 @@ import {
     inlineCreateControllerServiceSuccess,
     loadManagementControllerServices,
     loadManagementControllerServicesSuccess,
+    loadManagementControllerServicesError,
     managementControllerServicesBannerApiError,
     managementControllerServicesSnackbarApiError,
     resetManagementControllerServicesState
@@ -55,6 +56,10 @@ export const managementControllerServicesReducer = createReducer(
         loadedTimestamp: response.loadedTimestamp,
         status: 'success' as const
     })),
+    on(loadManagementControllerServicesError, (state, { status }) => ({
+        ...state,
+        status
+    })),
     on(managementControllerServicesBannerApiError, managementControllerServicesSnackbarApiError, (state) => ({
         ...state,
         saving: false
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.selectors.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.selectors.ts
index 0468358c58..c7ac2f26a8 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.selectors.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.selectors.ts
@@ -31,9 +31,9 @@ export const selectSaving = createSelector(
     (state: ManagementControllerServicesState) => state.saving
 );
 
-export const selectStatus = createSelector(
+export const selectLoadedTimestamp = createSelector(
     selectManagementControllerServicesState,
-    (state: ManagementControllerServicesState) => state.status
+    (state: ManagementControllerServicesState) => state.loadedTimestamp
 );
 
 export const selectControllerServiceIdFromRoute = createSelector(selectCurrentRoute, (route) => {
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.actions.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.actions.ts
index 80faf24305..5652317803 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.actions.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.actions.ts
@@ -43,8 +43,13 @@ export const loadParameterProvidersSuccess = createAction(
     props<{ response: LoadParameterProvidersResponse }>()
 );
 
-export const parameterProvidersBannerApiError = createAction(
+export const loadParameterProvidersError = createAction(
     `${PARAMETER_PROVIDERS_PREFIX} Load Parameter Providers Error`,
+    props<{ errorResponse: any; loadedTimestamp: string; status: 'pending' | 'success' }>()
+);
+
+export const parameterProvidersBannerApiError = createAction(
+    `${PARAMETER_PROVIDERS_PREFIX} Parameter Providers Banner Api Error`,
     props<{ error: string }>()
 );
 
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.effects.spec.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.effects.spec.ts
new file mode 100644
index 0000000000..ea95c21a85
--- /dev/null
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.effects.spec.ts
@@ -0,0 +1,204 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { TestBed } from '@angular/core/testing';
+import { provideMockActions } from '@ngrx/effects/testing';
+import { MockStore, provideMockStore } from '@ngrx/store/testing';
+import { Action } from '@ngrx/store';
+import { ReplaySubject, of, take, throwError } from 'rxjs';
+import { HttpErrorResponse } from '@angular/common/http';
+
+import * as ParameterProviderActions from './parameter-providers.actions';
+import { ParameterProvidersEffects } from './parameter-providers.effects';
+import { ParameterProviderService } from '../../service/parameter-provider.service';
+import { ManagementControllerServiceService } from '../../service/management-controller-service.service';
+import { ErrorHelper } from '../../../../service/error-helper.service';
+import { MatDialog } from '@angular/material/dialog';
+import { Router } from '@angular/router';
+import { PropertyTableHelperService } from '../../../../service/property-table-helper.service';
+import { Client } from '../../../../service/client.service';
+import { ExtensionTypesService } from '../../../../service/extension-types.service';
+import { initialParameterProvidersState } from './parameter-providers.reducer';
+
+describe('ParameterProvidersEffects', () => {
+    interface SetupOptions {
+        parameterProvidersState?: any;
+    }
+
+    const mockResponse = {
+        parameterProviders: [],
+        currentTime: '2023-01-01 12:00:00 EST'
+    };
+
+    async function setup({ parameterProvidersState = initialParameterProvidersState }: SetupOptions = {}) {
+        await TestBed.configureTestingModule({
+            providers: [
+                ParameterProvidersEffects,
+                provideMockActions(() => action$),
+                provideMockStore({
+                    initialState: {
+                        settings: {
+                            parameterProviders: parameterProvidersState
+                        }
+                    }
+                }),
+                { provide: ParameterProviderService, useValue: { getParameterProviders: jest.fn() } },
+                { provide: ManagementControllerServiceService, useValue: {} },
+                { provide: ErrorHelper, useValue: { handleLoadingError: jest.fn() } },
+                { provide: MatDialog, useValue: { open: jest.fn(), closeAll: jest.fn() } },
+                { provide: Router, useValue: { navigate: jest.fn() } },
+                { provide: PropertyTableHelperService, useValue: {} },
+                { provide: Client, useValue: {} },
+                { provide: ExtensionTypesService, useValue: {} }
+            ]
+        }).compileComponents();
+
+        const store = TestBed.inject(MockStore);
+        const effects = TestBed.inject(ParameterProvidersEffects);
+        const parameterProviderService = TestBed.inject(ParameterProviderService);
+        const errorHelper = TestBed.inject(ErrorHelper);
+
+        return { effects, store, parameterProviderService, errorHelper };
+    }
+
+    let action$: ReplaySubject<Action>;
+    beforeEach(() => {
+        action$ = new ReplaySubject<Action>();
+    });
+
+    it('should create', async () => {
+        const { effects } = await setup();
+        expect(effects).toBeTruthy();
+    });
+
+    describe('Load Parameter Providers', () => {
+        it('should load parameter providers successfully', async () => {
+            const { effects, parameterProviderService } = await setup();
+
+            action$.next(ParameterProviderActions.loadParameterProviders());
+            jest.spyOn(parameterProviderService, 'getParameterProviders').mockReturnValueOnce(
+                of(mockResponse) as never
+            );
+
+            const result = await new Promise((resolve) =>
+                effects.loadParameterProviders$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(result).toEqual(
+                ParameterProviderActions.loadParameterProvidersSuccess({
+                    response: {
+                        parameterProviders: mockResponse.parameterProviders,
+                        loadedTimestamp: mockResponse.currentTime
+                    }
+                })
+            );
+        });
+
+        it('should fail to load parameter providers on initial load with hasExistingData=false', async () => {
+            const { effects, parameterProviderService } = await setup();
+
+            action$.next(ParameterProviderActions.loadParameterProviders());
+            const error = new HttpErrorResponse({ status: 500 });
+            jest.spyOn(parameterProviderService, 'getParameterProviders').mockImplementationOnce(() =>
+                throwError(() => error)
+            );
+
+            const result = await new Promise((resolve) =>
+                effects.loadParameterProviders$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(result).toEqual(
+                ParameterProviderActions.loadParameterProvidersError({
+                    errorResponse: error,
+                    loadedTimestamp: initialParameterProvidersState.loadedTimestamp,
+                    status: 'pending'
+                })
+            );
+        });
+
+        it('should fail to load parameter providers on refresh with hasExistingData=true', async () => {
+            const stateWithData = { ...initialParameterProvidersState, loadedTimestamp: '2023-01-01 11:00:00 EST' };
+            const { effects, parameterProviderService } = await setup({
+                parameterProvidersState: stateWithData
+            });
+
+            action$.next(ParameterProviderActions.loadParameterProviders());
+            const error = new HttpErrorResponse({ status: 500 });
+            jest.spyOn(parameterProviderService, 'getParameterProviders').mockImplementationOnce(() =>
+                throwError(() => error)
+            );
+
+            const result = await new Promise((resolve) =>
+                effects.loadParameterProviders$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(result).toEqual(
+                ParameterProviderActions.loadParameterProvidersError({
+                    errorResponse: error,
+                    loadedTimestamp: stateWithData.loadedTimestamp,
+                    status: 'success'
+                })
+            );
+        });
+    });
+
+    describe('loadParameterProvidersError$', () => {
+        it('should handle parameter providers error for initial load', async () => {
+            const { effects, errorHelper } = await setup();
+
+            const error = new HttpErrorResponse({ status: 500 });
+            const errorAction = ParameterProviderActions.parameterProvidersBannerApiError({ error: 'Error message' });
+            action$.next(
+                ParameterProviderActions.loadParameterProvidersError({
+                    errorResponse: error,
+                    loadedTimestamp: initialParameterProvidersState.loadedTimestamp,
+                    status: 'pending'
+                })
+            );
+            jest.spyOn(errorHelper, 'handleLoadingError').mockReturnValueOnce(errorAction);
+
+            const result = await new Promise((resolve) =>
+                effects.loadParameterProvidersError$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(errorHelper.handleLoadingError).toHaveBeenCalledWith(false, error);
+            expect(result).toEqual(errorAction);
+        });
+
+        it('should handle parameter providers error for refresh', async () => {
+            const { effects, errorHelper } = await setup();
+
+            const error = new HttpErrorResponse({ status: 500 });
+            const errorAction = ParameterProviderActions.parameterProvidersBannerApiError({ error: 'Error message' });
+            action$.next(
+                ParameterProviderActions.loadParameterProvidersError({
+                    errorResponse: error,
+                    loadedTimestamp: '2023-01-01 11:00:00 EST',
+                    status: 'success'
+                })
+            );
+            jest.spyOn(errorHelper, 'handleLoadingError').mockReturnValueOnce(errorAction);
+
+            const result = await new Promise((resolve) =>
+                effects.loadParameterProvidersError$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(errorHelper.handleLoadingError).toHaveBeenCalledWith(true, error);
+            expect(result).toEqual(errorAction);
+        });
+    });
+});
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.effects.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.effects.ts
index 5bd82c3e6a..cfb2a545d1 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.effects.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.effects.ts
@@ -42,8 +42,8 @@ import {
 } from 'rxjs';
 import {
     selectApplyParameterProviderParametersRequest,
-    selectSaving,
-    selectStatus
+    selectLoadedTimestamp,
+    selectSaving
 } from './parameter-providers.selectors';
 import {
     selectExtensionTypesLoadingStatus,
@@ -59,6 +59,7 @@ import * as ErrorActions from '../../../../state/error/error.actions';
 import { HttpErrorResponse } from '@angular/common/http';
 import { ErrorHelper } from '../../../../service/error-helper.service';
 import { LARGE_DIALOG, SMALL_DIALOG, XL_DIALOG, YesNoDialog } from '@nifi/shared';
+import { initialParameterProvidersState } from './parameter-providers.reducer';
 import {
     resetPropertyVerificationState,
     verifyProperties
@@ -86,8 +87,8 @@ export class ParameterProvidersEffects {
     loadParameterProviders$ = createEffect(() =>
         this.actions$.pipe(
             ofType(loadParameterProviders),
-            concatLatestFrom(() => this.store.select(selectStatus)),
-            switchMap(([, status]) =>
+            concatLatestFrom(() => this.store.select(selectLoadedTimestamp)),
+            switchMap(([, loadedTimestamp]) =>
                 from(this.parameterProviderService.getParameterProviders()).pipe(
                     map((response) =>
                         ParameterProviderActions.loadParameterProvidersSuccess({
@@ -97,7 +98,30 @@ export class ParameterProvidersEffects {
                             }
                         })
                     ),
-                    catchError((error: HttpErrorResponse) => of(this.errorHelper.handleLoadingError(status, error)))
+                    catchError((errorResponse: HttpErrorResponse) =>
+                        of(
+                            ParameterProviderActions.loadParameterProvidersError({
+                                errorResponse,
+                                loadedTimestamp,
+                                status:
+                                    loadedTimestamp !== initialParameterProvidersState.loadedTimestamp
+                                        ? 'success'
+                                        : 'pending'
+                            })
+                        )
+                    )
+                )
+            )
+        )
+    );
+
+    loadParameterProvidersError$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(ParameterProviderActions.loadParameterProvidersError),
+            map((action) =>
+                this.errorHelper.handleLoadingError(
+                    action.loadedTimestamp !== initialParameterProvidersState.loadedTimestamp,
+                    action.errorResponse
                 )
             )
         )
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.reducer.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.reducer.ts
index 624063548d..b705a8ff28 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.reducer.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.reducer.ts
@@ -28,6 +28,7 @@ import {
     fetchParameterProviderParametersSuccess,
     loadParameterProviders,
     loadParameterProvidersSuccess,
+    loadParameterProvidersError,
     parameterProvidersBannerApiError,
     pollParameterProviderParametersUpdateRequestSuccess,
     resetFetchedParameterProvider,
@@ -65,6 +66,11 @@ export const parameterProvidersReducer = createReducer(
         status: 'success' as const
     })),
 
+    on(loadParameterProvidersError, (state: ParameterProvidersState, { status }) => ({
+        ...state,
+        status
+    })),
+
     on(parameterProvidersBannerApiError, (state: ParameterProvidersState) => ({
         ...state,
         saving: false
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.selectors.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.selectors.ts
index 468d46eff5..c85c148309 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.selectors.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/parameter-providers/parameter-providers.selectors.ts
@@ -30,9 +30,9 @@ export const selectSaving = createSelector(
     (state: ParameterProvidersState) => state.saving
 );
 
-export const selectStatus = createSelector(
+export const selectLoadedTimestamp = createSelector(
     selectParameterProvidersState,
-    (state: ParameterProvidersState) => state.status
+    (state: ParameterProvidersState) => state.loadedTimestamp
 );
 
 export const selectParameterProviderIdFromRoute = createSelector(selectCurrentRoute, (route) => {
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/registry-clients/registry-clients.actions.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/registry-clients/registry-clients.actions.ts
index 4adeac7e44..c6402ca8b4 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/registry-clients/registry-clients.actions.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/registry-clients/registry-clients.actions.ts
@@ -37,8 +37,13 @@ export const loadRegistryClientsSuccess = createAction(
     props<{ response: LoadRegistryClientsResponse }>()
 );
 
+export const loadRegistryClientsError = createAction(
+    '[Registry Clients] Load Registry Clients Error',
+    props<{ errorResponse: any; loadedTimestamp: string; status: 'pending' | 'success' }>()
+);
+
 export const registryClientsBannerApiError = createAction(
-    '[Registry Clients] Load Registry Clients Banner Api Error',
+    '[Registry Clients] Registry Clients Banner Api Error',
     props<{ error: string }>()
 );
 
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/registry-clients/registry-clients.effects.spec.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/registry-clients/registry-clients.effects.spec.ts
new file mode 100644
index 0000000000..d7064a4149
--- /dev/null
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/registry-clients/registry-clients.effects.spec.ts
@@ -0,0 +1,227 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { TestBed } from '@angular/core/testing';
+import { provideMockActions } from '@ngrx/effects/testing';
+import { MockStore, provideMockStore } from '@ngrx/store/testing';
+import { Action } from '@ngrx/store';
+import { ReplaySubject, of, take, throwError } from 'rxjs';
+import { HttpErrorResponse } from '@angular/common/http';
+
+import * as RegistryClientsActions from './registry-clients.actions';
+import { RegistryClientsEffects } from './registry-clients.effects';
+import { RegistryClientService } from '../../service/registry-client.service';
+import { ManagementControllerServiceService } from '../../service/management-controller-service.service';
+import { ErrorHelper } from '../../../../service/error-helper.service';
+import { MatDialog } from '@angular/material/dialog';
+import { Router } from '@angular/router';
+import { PropertyTableHelperService } from '../../../../service/property-table-helper.service';
+import { Client } from '../../../../service/client.service';
+import { NiFiCommon } from '@nifi/shared';
+import { ClusterConnectionService } from '../../../../service/cluster-connection.service';
+import { ExtensionTypesService } from '../../../../service/extension-types.service';
+import { initialState } from './registry-clients.reducer';
+
+describe('RegistryClientsEffects', () => {
+    interface SetupOptions {
+        registryClientsState?: any;
+    }
+
+    const mockRegistryClientsResponse = {
+        registries: [],
+        currentTime: '2023-01-01 12:00:00 EST'
+    };
+
+    async function setup({ registryClientsState = initialState }: SetupOptions = {}) {
+        await TestBed.configureTestingModule({
+            providers: [
+                RegistryClientsEffects,
+                provideMockActions(() => action$),
+                provideMockStore({
+                    initialState: {
+                        settings: {
+                            registryClients: registryClientsState
+                        }
+                    }
+                }),
+                {
+                    provide: RegistryClientService,
+                    useValue: {
+                        getRegistryClients: jest.fn()
+                    }
+                },
+                {
+                    provide: ManagementControllerServiceService,
+                    useValue: {}
+                },
+                {
+                    provide: ErrorHelper,
+                    useValue: {
+                        handleLoadingError: jest.fn()
+                    }
+                },
+                {
+                    provide: MatDialog,
+                    useValue: { open: jest.fn(), closeAll: jest.fn() }
+                },
+                {
+                    provide: Router,
+                    useValue: { navigate: jest.fn() }
+                },
+                { provide: PropertyTableHelperService, useValue: {} },
+                { provide: Client, useValue: {} },
+                { provide: NiFiCommon, useValue: {} },
+                { provide: ClusterConnectionService, useValue: {} },
+                { provide: ExtensionTypesService, useValue: {} }
+            ]
+        }).compileComponents();
+
+        const store = TestBed.inject(MockStore);
+        const effects = TestBed.inject(RegistryClientsEffects);
+        const registryClientService = TestBed.inject(RegistryClientService);
+        const errorHelper = TestBed.inject(ErrorHelper);
+
+        return { effects, store, registryClientService, errorHelper };
+    }
+
+    let action$: ReplaySubject<Action>;
+    beforeEach(() => {
+        action$ = new ReplaySubject<Action>();
+    });
+
+    it('should create', async () => {
+        const { effects } = await setup();
+        expect(effects).toBeTruthy();
+    });
+
+    describe('Load Registry Clients', () => {
+        it('should load registry clients successfully', async () => {
+            const { effects, registryClientService } = await setup();
+
+            action$.next(RegistryClientsActions.loadRegistryClients());
+            jest.spyOn(registryClientService, 'getRegistryClients').mockReturnValueOnce(
+                of(mockRegistryClientsResponse) as never
+            );
+            const result = await new Promise((resolve) =>
+                effects.loadRegistryClients$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(result).toEqual(
+                RegistryClientsActions.loadRegistryClientsSuccess({
+                    response: {
+                        registryClients: mockRegistryClientsResponse.registries,
+                        loadedTimestamp: mockRegistryClientsResponse.currentTime
+                    }
+                })
+            );
+        });
+
+        it('should fail to load registry clients on initial load with hasExistingData=false', async () => {
+            const { effects, registryClientService } = await setup();
+
+            action$.next(RegistryClientsActions.loadRegistryClients());
+            const error = new HttpErrorResponse({ status: 500 });
+            jest.spyOn(registryClientService, 'getRegistryClients').mockImplementationOnce(() =>
+                throwError(() => error)
+            );
+
+            const result = await new Promise((resolve) =>
+                effects.loadRegistryClients$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(result).toEqual(
+                RegistryClientsActions.loadRegistryClientsError({
+                    errorResponse: error,
+                    loadedTimestamp: initialState.loadedTimestamp,
+                    status: 'pending'
+                })
+            );
+        });
+
+        it('should fail to load registry clients on refresh with hasExistingData=true', async () => {
+            const stateWithData = {
+                ...initialState,
+                loadedTimestamp: '2023-01-01 11:00:00 EST'
+            };
+            const { effects, registryClientService } = await setup({
+                registryClientsState: stateWithData
+            });
+
+            action$.next(RegistryClientsActions.loadRegistryClients());
+            const error = new HttpErrorResponse({ status: 500 });
+            jest.spyOn(registryClientService, 'getRegistryClients').mockImplementationOnce(() =>
+                throwError(() => error)
+            );
+
+            const result = await new Promise((resolve) =>
+                effects.loadRegistryClients$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(result).toEqual(
+                RegistryClientsActions.loadRegistryClientsError({
+                    errorResponse: error,
+                    loadedTimestamp: stateWithData.loadedTimestamp,
+                    status: 'success'
+                })
+            );
+        });
+
+        it('should handle loadRegistryClientsError with pending status (no existing data)', async () => {
+            const { effects, errorHelper } = await setup();
+
+            action$.next(
+                RegistryClientsActions.loadRegistryClientsError({
+                    errorResponse: new HttpErrorResponse({ status: 500 }),
+                    loadedTimestamp: initialState.loadedTimestamp,
+                    status: 'pending'
+                })
+            );
+
+            const errorAction = RegistryClientsActions.registryClientsBannerApiError({ error: 'Error loading' });
+            jest.spyOn(errorHelper, 'handleLoadingError').mockReturnValueOnce(errorAction);
+
+            const result = await new Promise((resolve) =>
+                effects.loadRegistryClientsError$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(errorHelper.handleLoadingError).toHaveBeenCalledWith(false, expect.any(HttpErrorResponse));
+            expect(result).toEqual(errorAction);
+        });
+
+        it('should handle loadRegistryClientsError with success status (existing data)', async () => {
+            const { effects, errorHelper } = await setup();
+
+            action$.next(
+                RegistryClientsActions.loadRegistryClientsError({
+                    errorResponse: new HttpErrorResponse({ status: 503 }),
+                    loadedTimestamp: '2023-01-01 11:00:00 EST',
+                    status: 'success'
+                })
+            );
+
+            const errorAction = RegistryClientsActions.registryClientsBannerApiError({ error: 'Error loading' });
+            jest.spyOn(errorHelper, 'handleLoadingError').mockReturnValueOnce(errorAction);
+
+            const result = await new Promise((resolve) =>
+                effects.loadRegistryClientsError$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(errorHelper.handleLoadingError).toHaveBeenCalledWith(true, expect.any(HttpErrorResponse));
+            expect(result).toEqual(errorAction);
+        });
+    });
+});
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/registry-clients/registry-clients.effects.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/registry-clients/registry-clients.effects.ts
index 9d5caebae9..3eb051afa0 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/registry-clients/registry-clients.effects.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/registry-clients/registry-clients.effects.ts
@@ -31,7 +31,7 @@ import { LARGE_DIALOG, SMALL_DIALOG, YesNoDialog } from '@nifi/shared';
 import { Router } from '@angular/router';
 import { RegistryClientService } from '../../service/registry-client.service';
 import { CreateRegistryClient } from '../../ui/registry-clients/create-registry-client/create-registry-client.component';
-import { selectSaving, selectStatus } from './registry-clients.selectors';
+import { selectLoadedTimestamp, selectSaving } from './registry-clients.selectors';
 import { EditRegistryClient } from '../../ui/registry-clients/edit-registry-client/edit-registry-client.component';
 import { ManagementControllerServiceService } from '../../service/management-controller-service.service';
 import { EditRegistryClientRequest } from './index';
@@ -39,6 +39,7 @@ import { PropertyTableHelperService } from '../../../../service/property-table-h
 import * as ErrorActions from '../../../../state/error/error.actions';
 import { ErrorHelper } from '../../../../service/error-helper.service';
 import { HttpErrorResponse } from '@angular/common/http';
+import { initialState } from './registry-clients.reducer';
 import { BackNavigation } from '../../../../state/navigation';
 import { ErrorContextKey } from '../../../../state/error';
 
@@ -56,8 +57,8 @@ export class RegistryClientsEffects {
     loadRegistryClients$ = createEffect(() =>
         this.actions$.pipe(
             ofType(RegistryClientsActions.loadRegistryClients),
-            concatLatestFrom(() => this.store.select(selectStatus)),
-            switchMap(([, status]) =>
+            concatLatestFrom(() => this.store.select(selectLoadedTimestamp)),
+            switchMap(([, loadedTimestamp]) =>
                 from(this.registryClientService.getRegistryClients()).pipe(
                     map((response) =>
                         RegistryClientsActions.loadRegistryClientsSuccess({
@@ -67,14 +68,30 @@ export class RegistryClientsEffects {
                             }
                         })
                     ),
-                    catchError((errorResponse: HttpErrorResponse) =>
-                        of(this.errorHelper.handleLoadingError(status, errorResponse))
-                    )
+                    catchError((errorResponse: HttpErrorResponse) => {
+                        const status = loadedTimestamp !== initialState.loadedTimestamp ? 'success' : 'pending';
+                        return of(
+                            RegistryClientsActions.loadRegistryClientsError({
+                                errorResponse,
+                                loadedTimestamp,
+                                status
+                            })
+                        );
+                    })
                 )
             )
         )
     );
 
+    loadRegistryClientsError$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(RegistryClientsActions.loadRegistryClientsError),
+            switchMap((action) =>
+                of(this.errorHelper.handleLoadingError(action.status === 'success', action.errorResponse))
+            )
+        )
+    );
+
     openNewRegistryClientDialog$ = createEffect(
         () =>
             this.actions$.pipe(
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/registry-clients/registry-clients.reducer.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/registry-clients/registry-clients.reducer.ts
index ba68c17737..a9f12a0178 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/registry-clients/registry-clients.reducer.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/registry-clients/registry-clients.reducer.ts
@@ -26,6 +26,7 @@ import {
     deleteRegistryClient,
     deleteRegistryClientSuccess,
     loadRegistryClients,
+    loadRegistryClientsError,
     loadRegistryClientsSuccess,
     registryClientsBannerApiError,
     registryClientsSnackbarApiError,
@@ -54,6 +55,10 @@ export const registryClientsReducer = createReducer(
         loadedTimestamp: response.loadedTimestamp,
         status: 'success' as const
     })),
+    on(loadRegistryClientsError, (state, { status }) => ({
+        ...state,
+        status
+    })),
     on(registryClientsBannerApiError, registryClientsSnackbarApiError, (state) => ({
         ...state,
         saving: false
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/registry-clients/registry-clients.selectors.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/registry-clients/registry-clients.selectors.ts
index da098b31e8..e16fd28d81 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/registry-clients/registry-clients.selectors.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/registry-clients/registry-clients.selectors.ts
@@ -28,7 +28,10 @@ export const selectRegistryClientsState = createSelector(
 
 export const selectSaving = createSelector(selectRegistryClientsState, (state: RegistryClientsState) => state.saving);
 
-export const selectStatus = createSelector(selectRegistryClientsState, (state: RegistryClientsState) => state.status);
+export const selectLoadedTimestamp = createSelector(
+    selectRegistryClientsState,
+    (state: RegistryClientsState) => state.loadedTimestamp
+);
 
 export const selectRegistryClientIdFromRoute = createSelector(selectCurrentRoute, (route) => {
     if (route) {
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/reporting-tasks/reporting-tasks.actions.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/reporting-tasks/reporting-tasks.actions.ts
index 746baf6136..9cb54e2776 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/reporting-tasks/reporting-tasks.actions.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/reporting-tasks/reporting-tasks.actions.ts
@@ -42,6 +42,11 @@ export const loadReportingTasksSuccess = createAction(
     props<{ response: LoadReportingTasksResponse }>()
 );
 
+export const loadReportingTasksError = createAction(
+    '[Reporting Tasks] Load Reporting Tasks Error',
+    props<{ errorResponse: any; loadedTimestamp: string; status: 'pending' | 'success' }>()
+);
+
 export const openConfigureReportingTaskDialog = createAction(
     '[Reporting Tasks] Open Reporting Task Dialog',
     props<{ request: EditReportingTaskDialogRequest }>()
@@ -58,7 +63,7 @@ export const configureReportingTaskSuccess = createAction(
 );
 
 export const reportingTasksBannerApiError = createAction(
-    '[Reporting Tasks] Load Reporting Tasks Banner Api Error',
+    '[Reporting Tasks] Reporting Tasks Banner Api Error',
     props<{ error: string }>()
 );
 
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/reporting-tasks/reporting-tasks.effects.spec.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/reporting-tasks/reporting-tasks.effects.spec.ts
new file mode 100644
index 0000000000..391b44cec8
--- /dev/null
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/reporting-tasks/reporting-tasks.effects.spec.ts
@@ -0,0 +1,191 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { TestBed } from '@angular/core/testing';
+import { provideMockActions } from '@ngrx/effects/testing';
+import { MockStore, provideMockStore } from '@ngrx/store/testing';
+import { Action } from '@ngrx/store';
+import { ReplaySubject, of, take, throwError } from 'rxjs';
+import { HttpErrorResponse } from '@angular/common/http';
+
+import * as ReportingTaskActions from './reporting-tasks.actions';
+import { ReportingTasksEffects } from './reporting-tasks.effects';
+import { ReportingTaskService } from '../../service/reporting-task.service';
+import { ManagementControllerServiceService } from '../../service/management-controller-service.service';
+import { ErrorHelper } from '../../../../service/error-helper.service';
+import { MatDialog } from '@angular/material/dialog';
+import { Router } from '@angular/router';
+import { PropertyTableHelperService } from '../../../../service/property-table-helper.service';
+import { ExtensionTypesService } from '../../../../service/extension-types.service';
+import { initialState } from './reporting-tasks.reducer';
+
+describe('ReportingTasksEffects', () => {
+    interface SetupOptions {
+        reportingTasksState?: any;
+    }
+
+    const mockResponse = {
+        reportingTasks: [],
+        currentTime: '2023-01-01 12:00:00 EST'
+    };
+
+    async function setup({ reportingTasksState = initialState }: SetupOptions = {}) {
+        await TestBed.configureTestingModule({
+            providers: [
+                ReportingTasksEffects,
+                provideMockActions(() => action$),
+                provideMockStore({
+                    initialState: {
+                        settings: {
+                            reportingTasks: reportingTasksState
+                        }
+                    }
+                }),
+                { provide: ReportingTaskService, useValue: { getReportingTasks: jest.fn() } },
+                { provide: ManagementControllerServiceService, useValue: {} },
+                { provide: ErrorHelper, useValue: { handleLoadingError: jest.fn() } },
+                { provide: MatDialog, useValue: { open: jest.fn(), closeAll: jest.fn() } },
+                { provide: Router, useValue: { navigate: jest.fn() } },
+                { provide: PropertyTableHelperService, useValue: {} },
+                { provide: ExtensionTypesService, useValue: {} }
+            ]
+        }).compileComponents();
+
+        const store = TestBed.inject(MockStore);
+        const effects = TestBed.inject(ReportingTasksEffects);
+        const reportingTaskService = TestBed.inject(ReportingTaskService);
+        const errorHelper = TestBed.inject(ErrorHelper);
+
+        return { effects, store, reportingTaskService, errorHelper };
+    }
+
+    let action$: ReplaySubject<Action>;
+    beforeEach(() => {
+        action$ = new ReplaySubject<Action>();
+    });
+
+    it('should create', async () => {
+        const { effects } = await setup();
+        expect(effects).toBeTruthy();
+    });
+
+    describe('Load Reporting Tasks', () => {
+        it('should load reporting tasks successfully', async () => {
+            const { effects, reportingTaskService } = await setup();
+
+            action$.next(ReportingTaskActions.loadReportingTasks());
+            jest.spyOn(reportingTaskService, 'getReportingTasks').mockReturnValueOnce(of(mockResponse) as never);
+
+            const result = await new Promise((resolve) => effects.loadReportingTasks$.pipe(take(1)).subscribe(resolve));
+
+            expect(result).toEqual(
+                ReportingTaskActions.loadReportingTasksSuccess({
+                    response: {
+                        reportingTasks: mockResponse.reportingTasks,
+                        loadedTimestamp: mockResponse.currentTime
+                    }
+                })
+            );
+        });
+
+        it('should fail to load reporting tasks on initial load with hasExistingData=false', async () => {
+            const { effects, reportingTaskService } = await setup();
+
+            action$.next(ReportingTaskActions.loadReportingTasks());
+            const error = new HttpErrorResponse({ status: 500 });
+            jest.spyOn(reportingTaskService, 'getReportingTasks').mockImplementationOnce(() => throwError(() => error));
+
+            const result = await new Promise((resolve) => effects.loadReportingTasks$.pipe(take(1)).subscribe(resolve));
+
+            expect(result).toEqual(
+                ReportingTaskActions.loadReportingTasksError({
+                    errorResponse: error,
+                    loadedTimestamp: initialState.loadedTimestamp,
+                    status: 'pending'
+                })
+            );
+        });
+
+        it('should fail to load reporting tasks on refresh with hasExistingData=true', async () => {
+            const stateWithData = {
+                ...initialState,
+                loadedTimestamp: '2023-01-01 11:00:00 EST'
+            };
+            const { effects, reportingTaskService } = await setup({
+                reportingTasksState: stateWithData
+            });
+
+            action$.next(ReportingTaskActions.loadReportingTasks());
+            const error = new HttpErrorResponse({ status: 500 });
+            jest.spyOn(reportingTaskService, 'getReportingTasks').mockImplementationOnce(() => throwError(() => error));
+
+            const result = await new Promise((resolve) => effects.loadReportingTasks$.pipe(take(1)).subscribe(resolve));
+
+            expect(result).toEqual(
+                ReportingTaskActions.loadReportingTasksError({
+                    errorResponse: error,
+                    loadedTimestamp: stateWithData.loadedTimestamp,
+                    status: 'success'
+                })
+            );
+        });
+
+        it('should handle loadReportingTasksError with pending status (no existing data)', async () => {
+            const { effects, errorHelper } = await setup();
+
+            action$.next(
+                ReportingTaskActions.loadReportingTasksError({
+                    errorResponse: new HttpErrorResponse({ status: 500 }),
+                    loadedTimestamp: initialState.loadedTimestamp,
+                    status: 'pending'
+                })
+            );
+
+            const errorAction = ReportingTaskActions.reportingTasksBannerApiError({ error: 'Error loading' });
+            jest.spyOn(errorHelper, 'handleLoadingError').mockReturnValueOnce(errorAction);
+
+            const result = await new Promise((resolve) =>
+                effects.loadReportingTasksError$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(errorHelper.handleLoadingError).toHaveBeenCalledWith(false, expect.any(HttpErrorResponse));
+            expect(result).toEqual(errorAction);
+        });
+
+        it('should handle loadReportingTasksError with success status (existing data)', async () => {
+            const { effects, errorHelper } = await setup();
+
+            action$.next(
+                ReportingTaskActions.loadReportingTasksError({
+                    errorResponse: new HttpErrorResponse({ status: 503 }),
+                    loadedTimestamp: '2023-01-01 11:00:00 EST',
+                    status: 'success'
+                })
+            );
+
+            const errorAction = ReportingTaskActions.reportingTasksBannerApiError({ error: 'Error loading' });
+            jest.spyOn(errorHelper, 'handleLoadingError').mockReturnValueOnce(errorAction);
+
+            const result = await new Promise((resolve) =>
+                effects.loadReportingTasksError$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(errorHelper.handleLoadingError).toHaveBeenCalledWith(true, expect.any(HttpErrorResponse));
+            expect(result).toEqual(errorAction);
+        });
+    });
+});
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/reporting-tasks/reporting-tasks.effects.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/reporting-tasks/reporting-tasks.effects.ts
index d0755f7a86..e196c5cb4f 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/reporting-tasks/reporting-tasks.effects.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/reporting-tasks/reporting-tasks.effects.ts
@@ -35,7 +35,8 @@ import { ManagementControllerServiceService } from '../../service/management-con
 import { PropertyTableHelperService } from '../../../../service/property-table-helper.service';
 import * as ErrorActions from '../../../../state/error/error.actions';
 import { ErrorHelper } from '../../../../service/error-helper.service';
-import { selectStatus } from './reporting-tasks.selectors';
+import { selectLoadedTimestamp } from './reporting-tasks.selectors';
+import { initialState } from './reporting-tasks.reducer';
 import { HttpErrorResponse } from '@angular/common/http';
 import { ChangeComponentVersionDialog } from '../../../../ui/common/change-component-version-dialog/change-component-version-dialog';
 import { ExtensionTypesService } from '../../../../service/extension-types.service';
@@ -66,8 +67,8 @@ export class ReportingTasksEffects {
     loadReportingTasks$ = createEffect(() =>
         this.actions$.pipe(
             ofType(ReportingTaskActions.loadReportingTasks),
-            concatLatestFrom(() => this.store.select(selectStatus)),
-            switchMap(([, status]) =>
+            concatLatestFrom(() => this.store.select(selectLoadedTimestamp)),
+            switchMap(([, loadedTimestamp]) =>
                 from(this.reportingTaskService.getReportingTasks()).pipe(
                     map((response) =>
                         ReportingTaskActions.loadReportingTasksSuccess({
@@ -77,14 +78,30 @@ export class ReportingTasksEffects {
                             }
                         })
                     ),
-                    catchError((errorResponse: HttpErrorResponse) =>
-                        of(this.errorHelper.handleLoadingError(status, errorResponse))
-                    )
+                    catchError((errorResponse: HttpErrorResponse) => {
+                        const status = loadedTimestamp !== initialState.loadedTimestamp ? 'success' : 'pending';
+                        return of(
+                            ReportingTaskActions.loadReportingTasksError({
+                                errorResponse,
+                                loadedTimestamp,
+                                status
+                            })
+                        );
+                    })
                 )
             )
         )
     );
 
+    loadReportingTasksError$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(ReportingTaskActions.loadReportingTasksError),
+            switchMap((action) =>
+                of(this.errorHelper.handleLoadingError(action.status === 'success', action.errorResponse))
+            )
+        )
+    );
+
     openNewReportingTaskDialog$ = createEffect(
         () =>
             this.actions$.pipe(
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/reporting-tasks/reporting-tasks.reducer.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/reporting-tasks/reporting-tasks.reducer.ts
index a59b12d218..2157882615 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/reporting-tasks/reporting-tasks.reducer.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/reporting-tasks/reporting-tasks.reducer.ts
@@ -25,6 +25,7 @@ import {
     deleteReportingTask,
     deleteReportingTaskSuccess,
     loadReportingTasks,
+    loadReportingTasksError,
     loadReportingTasksSuccess,
     reportingTasksBannerApiError,
     reportingTasksSnackbarApiError,
@@ -56,6 +57,10 @@ export const reportingTasksReducer = createReducer(
         loadedTimestamp: response.loadedTimestamp,
         status: 'success' as const
     })),
+    on(loadReportingTasksError, (state, { status }) => ({
+        ...state,
+        status
+    })),
     on(reportingTasksBannerApiError, reportingTasksSnackbarApiError, (state) => ({
         ...state,
         saving: false
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/reporting-tasks/reporting-tasks.selectors.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/reporting-tasks/reporting-tasks.selectors.ts
index 407364ab79..fd145a55d5 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/reporting-tasks/reporting-tasks.selectors.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/state/reporting-tasks/reporting-tasks.selectors.ts
@@ -27,7 +27,10 @@ export const selectReportingTasksState = createSelector(
 
 export const selectSaving = createSelector(selectReportingTasksState, (state: ReportingTasksState) => state.saving);
 
-export const selectStatus = createSelector(selectReportingTasksState, (state: ReportingTasksState) => state.status);
+export const selectLoadedTimestamp = createSelector(
+    selectReportingTasksState,
+    (state: ReportingTasksState) => state.loadedTimestamp
+);
 
 export const selectReportingTaskIdFromRoute = createSelector(selectCurrentRoute, (route) => {
     if (route) {
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/summary/state/summary-listing/summary-listing.actions.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/summary/state/summary-listing/summary-listing.actions.ts
index 65d00a7f60..204e4d8f74 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/summary/state/summary-listing/summary-listing.actions.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/summary/state/summary-listing/summary-listing.actions.ts
@@ -38,6 +38,11 @@ export const loadSummaryListingSuccess = createAction(
     props<{ response: SummaryListingResponse }>()
 );
 
+export const loadSummaryListingError = createAction(
+    `${SUMMARY_LISTING_PREFIX} Load Summary Listing Error`,
+    props<{ errorResponse: any; loadedTimestamp: string; status: 'pending' | 'success' }>()
+);
+
 export const selectProcessorStatus = createAction(
     `${SUMMARY_LISTING_PREFIX} Select Processor Status`,
     props<{ request: SelectProcessorStatusRequest }>()
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/summary/state/summary-listing/summary-listing.effects.spec.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/summary/state/summary-listing/summary-listing.effects.spec.ts
new file mode 100644
index 0000000000..31012ae4f8
--- /dev/null
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/summary/state/summary-listing/summary-listing.effects.spec.ts
@@ -0,0 +1,251 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { TestBed } from '@angular/core/testing';
+import { SummaryListingEffects } from './summary-listing.effects';
+import { provideMockActions } from '@ngrx/effects/testing';
+import { MockStore, provideMockStore } from '@ngrx/store/testing';
+import { initialState } from './summary-listing.reducer';
+import { of, ReplaySubject, take, throwError } from 'rxjs';
+import { Action } from '@ngrx/store';
+import * as SummaryListingActions from './summary-listing.actions';
+import { ProcessGroupStatusService } from '../../service/process-group-status.service';
+import { ErrorHelper } from '../../../../service/error-helper.service';
+import { HttpErrorResponse } from '@angular/common/http';
+import { Router } from '@angular/router';
+import { summaryFeatureKey } from '../index';
+import { summaryListingFeatureKey } from './index';
+
+describe('SummaryListingEffects', () => {
+    interface SetupOptions {
+        summaryListingState?: any;
+    }
+
+    const mockSummaryResponse = {
+        status: {
+            canRead: true,
+            processGroupStatus: {
+                id: 'root',
+                name: 'NiFi Flow',
+                statsLastRefreshed: '2023-01-01 12:00:00 EST',
+                aggregateSnapshot: {
+                    id: 'root',
+                    name: 'NiFi Flow',
+                    flowFilesIn: 0,
+                    bytesIn: 0,
+                    input: '0 / 0 bytes',
+                    flowFilesOut: 0,
+                    bytesOut: 0,
+                    output: '0 / 0 bytes',
+                    flowFilesTransferred: 0,
+                    bytesTransferred: 0,
+                    transferred: '0 / 0 bytes',
+                    bytesReceived: 0,
+                    flowFilesReceived: 0,
+                    received: '0 / 0 bytes',
+                    bytesSent: 0,
+                    flowFilesSent: 0,
+                    sent: '0 / 0 bytes',
+                    bytesRead: 0,
+                    read: '0 bytes',
+                    bytesWritten: 0,
+                    written: '0 bytes',
+                    activeThreadCount: 0,
+                    terminatedThreadCount: 0,
+                    processingNanos: 0,
+                    statelessActiveThreadCount: 0,
+                    processorStatusSnapshots: [],
+                    connectionStatusSnapshots: [],
+                    processGroupStatusSnapshots: [],
+                    inputPortStatusSnapshots: [],
+                    outputPortStatusSnapshots: [],
+                    remoteProcessGroupStatusSnapshots: []
+                }
+            }
+        }
+    };
+
+    let action$: ReplaySubject<Action>;
+
+    async function setup({ summaryListingState = initialState }: SetupOptions = {}) {
+        await TestBed.configureTestingModule({
+            providers: [
+                SummaryListingEffects,
+                provideMockActions(() => action$),
+                provideMockStore({
+                    initialState: {
+                        [summaryFeatureKey]: {
+                            [summaryListingFeatureKey]: summaryListingState || initialState
+                        }
+                    }
+                }),
+                {
+                    provide: ProcessGroupStatusService,
+                    useValue: {
+                        getProcessGroupsStatus: jest.fn()
+                    }
+                },
+                {
+                    provide: Router,
+                    useValue: {
+                        navigate: jest.fn()
+                    }
+                },
+                {
+                    provide: ErrorHelper,
+                    useValue: {
+                        handleLoadingError: jest.fn(),
+                        getErrorString: jest.fn()
+                    }
+                }
+            ]
+        }).compileComponents();
+
+        const store = TestBed.inject(MockStore);
+        const summaryService = TestBed.inject(ProcessGroupStatusService) as jest.Mocked<ProcessGroupStatusService>;
+        const errorHelper = TestBed.inject(ErrorHelper) as jest.Mocked<ErrorHelper>;
+        const effects = TestBed.inject(SummaryListingEffects);
+
+        return { store, summaryService, errorHelper, effects };
+    }
+
+    beforeEach(() => {
+        action$ = new ReplaySubject<Action>(1);
+    });
+
+    afterEach(() => {
+        action$.complete();
+    });
+
+    it('should be created', async () => {
+        const { effects } = await setup();
+        expect(effects).toBeTruthy();
+    });
+
+    describe('loadSummaryListing', () => {
+        it('should load summary listing successfully', async () => {
+            const { effects, summaryService } = await setup();
+
+            action$.next(SummaryListingActions.loadSummaryListing({ recursive: false }));
+            jest.spyOn(summaryService, 'getProcessGroupsStatus').mockReturnValueOnce(
+                of(mockSummaryResponse.status) as never
+            );
+
+            const result = await new Promise((resolve) => effects.loadSummaryListing$.pipe(take(1)).subscribe(resolve));
+
+            expect(result).toEqual(
+                SummaryListingActions.loadSummaryListingSuccess({
+                    response: {
+                        status: mockSummaryResponse.status
+                    }
+                })
+            );
+        });
+
+        it('should fail to load summary listing on initial load with hasExistingData=false', async () => {
+            const { effects, summaryService } = await setup();
+
+            action$.next(SummaryListingActions.loadSummaryListing({ recursive: false }));
+            const error = new HttpErrorResponse({ status: 500 });
+            jest.spyOn(summaryService, 'getProcessGroupsStatus').mockImplementationOnce(() => throwError(() => error));
+
+            const result = await new Promise((resolve) => effects.loadSummaryListing$.pipe(take(1)).subscribe(resolve));
+
+            expect(result).toEqual(
+                SummaryListingActions.loadSummaryListingError({
+                    errorResponse: error,
+                    loadedTimestamp: initialState.loadedTimestamp,
+                    status: 'pending'
+                })
+            );
+        });
+
+        it('should fail to load summary listing on refresh with hasExistingData=true', async () => {
+            const stateWithData = {
+                ...initialState,
+                loadedTimestamp: '2023-01-01 11:00:00 EST'
+            };
+            const { effects, summaryService } = await setup({ summaryListingState: stateWithData });
+
+            action$.next(SummaryListingActions.loadSummaryListing({ recursive: false }));
+            const error = new HttpErrorResponse({ status: 500 });
+            jest.spyOn(summaryService, 'getProcessGroupsStatus').mockImplementationOnce(() => throwError(() => error));
+
+            const result = await new Promise((resolve) => effects.loadSummaryListing$.pipe(take(1)).subscribe(resolve));
+
+            expect(result).toEqual(
+                SummaryListingActions.loadSummaryListingError({
+                    errorResponse: error,
+                    loadedTimestamp: stateWithData.loadedTimestamp,
+                    status: 'success'
+                })
+            );
+        });
+
+        it('should handle loadSummaryListingError with pending status (no existing data)', async () => {
+            const { effects, errorHelper } = await setup();
+
+            action$.next(
+                SummaryListingActions.loadSummaryListingError({
+                    errorResponse: new HttpErrorResponse({ status: 500 }),
+                    loadedTimestamp: initialState.loadedTimestamp,
+                    status: 'pending'
+                })
+            );
+
+            const errorAction = SummaryListingActions.loadSummaryListingSuccess({
+                response: {
+                    status: mockSummaryResponse.status
+                }
+            });
+            jest.spyOn(errorHelper, 'handleLoadingError').mockReturnValueOnce(errorAction);
+
+            const result = await new Promise((resolve) =>
+                effects.loadSummaryListingError$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(errorHelper.handleLoadingError).toHaveBeenCalledWith(false, expect.any(HttpErrorResponse));
+            expect(result).toEqual(errorAction);
+        });
+
+        it('should handle loadSummaryListingError with success status (existing data)', async () => {
+            const { effects, errorHelper } = await setup();
+
+            action$.next(
+                SummaryListingActions.loadSummaryListingError({
+                    errorResponse: new HttpErrorResponse({ status: 503 }),
+                    loadedTimestamp: '2023-01-01 11:00:00 EST',
+                    status: 'success'
+                })
+            );
+
+            const errorAction = SummaryListingActions.loadSummaryListingSuccess({
+                response: {
+                    status: mockSummaryResponse.status
+                }
+            });
+            jest.spyOn(errorHelper, 'handleLoadingError').mockReturnValueOnce(errorAction);
+
+            const result = await new Promise((resolve) =>
+                effects.loadSummaryListingError$.pipe(take(1)).subscribe(resolve)
+            );
+
+            expect(errorHelper.handleLoadingError).toHaveBeenCalledWith(true, expect.any(HttpErrorResponse));
+            expect(result).toEqual(errorAction);
+        });
+    });
+});
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/summary/state/summary-listing/summary-listing.effects.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/summary/state/summary-listing/summary-listing.effects.ts
index 76e9af00a7..ed4c396ea4 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/summary/state/summary-listing/summary-listing.effects.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/summary/state/summary-listing/summary-listing.effects.ts
@@ -27,8 +27,9 @@ import { catchError, filter, from, map, of, switchMap, tap } from 'rxjs';
 import { Router } from '@angular/router';
 import { ComponentType } from '@nifi/shared';
 import { ErrorHelper } from '../../../../service/error-helper.service';
+import { initialState } from './summary-listing.reducer';
 import { HttpErrorResponse } from '@angular/common/http';
-import { selectSelectedClusterNode, selectSummaryListingStatus } from './summary-listing.selectors';
+import { selectSelectedClusterNode, selectSummaryListingLoadedTimestamp } from './summary-listing.selectors';
 import { LoadSummaryRequest } from './index';
 
 @Injectable()
@@ -44,10 +45,10 @@ export class SummaryListingEffects {
             ofType(SummaryListingActions.loadSummaryListing),
             map((action) => action.recursive),
             concatLatestFrom(() => [
-                this.store.select(selectSummaryListingStatus),
+                this.store.select(selectSummaryListingLoadedTimestamp),
                 this.store.select(selectSelectedClusterNode)
             ]),
-            switchMap(([recursive, listingStatus, selectedClusterNode]) => {
+            switchMap(([recursive, loadedTimestamp, selectedClusterNode]) => {
                 const request: LoadSummaryRequest = {
                     recursive
                 };
@@ -62,14 +63,30 @@ export class SummaryListingEffects {
                             }
                         })
                     ),
-                    catchError((errorResponse: HttpErrorResponse) =>
-                        of(this.errorHelper.handleLoadingError(listingStatus, errorResponse))
-                    )
+                    catchError((errorResponse: HttpErrorResponse) => {
+                        const status = loadedTimestamp !== initialState.loadedTimestamp ? 'success' : 'pending';
+                        return of(
+                            SummaryListingActions.loadSummaryListingError({
+                                errorResponse,
+                                loadedTimestamp,
+                                status
+                            })
+                        );
+                    })
                 );
             })
         )
     );
 
+    loadSummaryListingError$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(SummaryListingActions.loadSummaryListingError),
+            switchMap((action) =>
+                of(this.errorHelper.handleLoadingError(action.status === 'success', action.errorResponse))
+            )
+        )
+    );
+
     selectProcessorStatus$ = createEffect(
         () =>
             this.actions$.pipe(
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/summary/state/summary-listing/summary-listing.reducer.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/summary/state/summary-listing/summary-listing.reducer.ts
index 0248543f7e..a27178c239 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/summary/state/summary-listing/summary-listing.reducer.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/summary/state/summary-listing/summary-listing.reducer.ts
@@ -26,6 +26,7 @@ import {
 } from '../index';
 import {
     loadSummaryListing,
+    loadSummaryListingError,
     loadSummaryListingSuccess,
     resetSummaryState,
     selectClusterNode
@@ -96,6 +97,11 @@ export const summaryListingReducer = createReducer(
         };
     }),
 
+    on(loadSummaryListingError, (state, { status }) => ({
+        ...state,
+        status
+    })),
+
     on(resetSummaryState, () => ({
         ...initialState
     })),
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/users/state/user-listing/user-listing.actions.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/users/state/user-listing/user-listing.actions.ts
index bc07f408c5..63a378d548 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/users/state/user-listing/user-listing.actions.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/users/state/user-listing/user-listing.actions.ts
@@ -44,6 +44,11 @@ export const loadTenantsSuccess = createAction(
     props<{ response: LoadTenantsSuccess }>()
 );
 
+export const loadTenantsError = createAction(
+    `${USER_PREFIX} Load Tenants Error`,
+    props<{ errorResponse: any; loadedTimestamp: string; status: 'pending' | 'success' }>()
+);
+
 export const usersApiSnackbarError = createAction(
     `${USER_PREFIX} Users Api Snackbar Error`,
     props<{ error: string }>()
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/users/state/user-listing/user-listing.effects.spec.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/users/state/user-listing/user-listing.effects.spec.ts
new file mode 100644
index 0000000000..108d54abaf
--- /dev/null
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/users/state/user-listing/user-listing.effects.spec.ts
@@ -0,0 +1,186 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { TestBed } from '@angular/core/testing';
+import { provideMockActions } from '@ngrx/effects/testing';
+import { MockStore, provideMockStore } from '@ngrx/store/testing';
+import { Action } from '@ngrx/store';
+import { ReplaySubject, of, take, throwError } from 'rxjs';
+import { HttpErrorResponse } from '@angular/common/http';
+
+import * as UserListingActions from './user-listing.actions';
+import { UserListingEffects } from './user-listing.effects';
+import { UsersService } from '../../service/users.service';
+import { ErrorHelper } from '../../../../service/error-helper.service';
+import { MatDialog } from '@angular/material/dialog';
+import { Router } from '@angular/router';
+import { Client } from '../../../../service/client.service';
+import { NiFiCommon } from '@nifi/shared';
+import { initialState } from './user-listing.reducer';
+
+describe('UserListingEffects', () => {
+    interface SetupOptions {
+        userListingState?: any;
+    }
+
+    const mockUsersResponse = { users: [], generated: '2023-01-01 12:00:00 EST' };
+    const mockUserGroupsResponse = { userGroups: [] };
+
+    async function setup({ userListingState = initialState }: SetupOptions = {}) {
+        await TestBed.configureTestingModule({
+            providers: [
+                UserListingEffects,
+                provideMockActions(() => action$),
+                provideMockStore({
+                    initialState: {
+                        users: {
+                            users: userListingState
+                        }
+                    }
+                }),
+                { provide: UsersService, useValue: { getUsers: jest.fn(), getUserGroups: jest.fn() } },
+                { provide: ErrorHelper, useValue: { handleLoadingError: jest.fn() } },
+                { provide: MatDialog, useValue: { open: jest.fn(), closeAll: jest.fn() } },
+                { provide: Router, useValue: { navigate: jest.fn() } },
+                { provide: Client, useValue: {} },
+                { provide: NiFiCommon, useValue: {} }
+            ]
+        }).compileComponents();
+
+        const store = TestBed.inject(MockStore);
+        const effects = TestBed.inject(UserListingEffects);
+        const usersService = TestBed.inject(UsersService);
+        const errorHelper = TestBed.inject(ErrorHelper);
+
+        return { effects, store, usersService, errorHelper };
+    }
+
+    let action$: ReplaySubject<Action>;
+    beforeEach(() => {
+        action$ = new ReplaySubject<Action>();
+    });
+
+    it('should create', async () => {
+        const { effects } = await setup();
+        expect(effects).toBeTruthy();
+    });
+
+    describe('Load Tenants', () => {
+        it('should load tenants successfully', async () => {
+            const { effects, usersService } = await setup();
+
+            action$.next(UserListingActions.loadTenants());
+            jest.spyOn(usersService, 'getUsers').mockReturnValueOnce(of(mockUsersResponse) as never);
+            jest.spyOn(usersService, 'getUserGroups').mockReturnValueOnce(of(mockUserGroupsResponse) as never);
+
+            const result = await new Promise((resolve) => effects.loadTenants$.pipe(take(1)).subscribe(resolve));
+
+            expect(result).toEqual(
+                UserListingActions.loadTenantsSuccess({
+                    response: {
+                        users: mockUsersResponse.users,
+                        userGroups: mockUserGroupsResponse.userGroups,
+                        loadedTimestamp: mockUsersResponse.generated
+                    }
+                })
+            );
+        });
+
+        it('should fail to load tenants on initial load (no existing data)', async () => {
+            const { effects, usersService } = await setup();
+
+            action$.next(UserListingActions.loadTenants());
+            const error = new HttpErrorResponse({ status: 500 });
+            jest.spyOn(usersService, 'getUsers').mockImplementationOnce(() => throwError(() => error));
+            jest.spyOn(usersService, 'getUserGroups').mockReturnValueOnce(of(mockUserGroupsResponse) as never);
+
+            const result = await new Promise((resolve) => effects.loadTenants$.pipe(take(1)).subscribe(resolve));
+
+            expect(result).toEqual(
+                UserListingActions.loadTenantsError({
+                    errorResponse: error,
+                    loadedTimestamp: initialState.loadedTimestamp,
+                    status: 'pending'
+                })
+            );
+        });
+
+        it('should fail to load tenants on refresh (existing data)', async () => {
+            const stateWithData = { ...initialState, loadedTimestamp: '2023-01-01 11:00:00 EST' };
+            const { effects, usersService } = await setup({ userListingState: stateWithData });
+
+            action$.next(UserListingActions.loadTenants());
+            const error = new HttpErrorResponse({ status: 500 });
+            jest.spyOn(usersService, 'getUsers').mockImplementationOnce(() => throwError(() => error));
+            jest.spyOn(usersService, 'getUserGroups').mockReturnValueOnce(of(mockUserGroupsResponse) as never);
+
+            const result = await new Promise((resolve) => effects.loadTenants$.pipe(take(1)).subscribe(resolve));
+
+            expect(result).toEqual(
+                UserListingActions.loadTenantsError({
+                    errorResponse: error,
+                    loadedTimestamp: stateWithData.loadedTimestamp,
+                    status: 'success'
+                })
+            );
+        });
+    });
+
+    describe('Load Tenants Error', () => {
+        it('should handle loadTenantsError with pending status', async () => {
+            const { effects, errorHelper } = await setup();
+
+            const error = new HttpErrorResponse({ status: 500 });
+            const errorAction = UserListingActions.usersApiBannerError({ error: 'Error loading' });
+            jest.spyOn(errorHelper, 'handleLoadingError').mockReturnValueOnce(errorAction);
+
+            action$.next(
+                UserListingActions.loadTenantsError({
+                    errorResponse: error,
+                    loadedTimestamp: '',
+                    status: 'pending'
+                })
+            );
+
+            const result = await new Promise((resolve) => effects.loadTenantsError$.pipe(take(1)).subscribe(resolve));
+
+            expect(errorHelper.handleLoadingError).toHaveBeenCalledWith(false, error);
+            expect(result).toEqual(errorAction);
+        });
+
+        it('should handle loadTenantsError with success status', async () => {
+            const { effects, errorHelper } = await setup();
+
+            const error = new HttpErrorResponse({ status: 500 });
+            const errorAction = UserListingActions.usersApiBannerError({ error: 'Error loading' });
+            jest.spyOn(errorHelper, 'handleLoadingError').mockReturnValueOnce(errorAction);
+
+            action$.next(
+                UserListingActions.loadTenantsError({
+                    errorResponse: error,
+                    loadedTimestamp: '2023-01-01 11:00:00 EST',
+                    status: 'success'
+                })
+            );
+
+            const result = await new Promise((resolve) => effects.loadTenantsError$.pipe(take(1)).subscribe(resolve));
+
+            expect(errorHelper.handleLoadingError).toHaveBeenCalledWith(true, error);
+            expect(result).toEqual(errorAction);
+        });
+    });
+});
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/users/state/user-listing/user-listing.effects.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/users/state/user-listing/user-listing.effects.ts
index a8598f24df..f39a46e0d5 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/users/state/user-listing/user-listing.effects.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/users/state/user-listing/user-listing.effects.ts
@@ -28,13 +28,14 @@ import { MatDialog } from '@angular/material/dialog';
 import { UsersService } from '../../service/users.service';
 import { YesNoDialog } from '@nifi/shared';
 import { EditTenantDialog } from '../../../../ui/common/edit-tenant/edit-tenant-dialog.component';
-import { selectSaving, selectStatus, selectUserGroups, selectUsers } from './user-listing.selectors';
+import { selectLoadedTimestamp, selectSaving, selectUserGroups, selectUsers } from './user-listing.selectors';
 import { EditTenantRequest, UserGroupEntity } from '../../../../state/shared';
 import { Client } from '../../../../service/client.service';
 import { LARGE_DIALOG, MEDIUM_DIALOG, SMALL_DIALOG, NiFiCommon } from '@nifi/shared';
 import { UserAccessPolicies } from '../../ui/user-listing/user-access-policies/user-access-policies.component';
 import * as ErrorActions from '../../../../state/error/error.actions';
 import { ErrorHelper } from '../../../../service/error-helper.service';
+import { initialState } from './user-listing.reducer';
 import { HttpErrorResponse } from '@angular/common/http';
 import { ErrorContextKey } from '../../../../state/error';
 
@@ -54,8 +55,8 @@ export class UserListingEffects {
     loadTenants$ = createEffect(() =>
         this.actions$.pipe(
             ofType(UserListingActions.loadTenants),
-            concatLatestFrom(() => this.store.select(selectStatus)),
-            switchMap(([, status]) =>
+            concatLatestFrom(() => this.store.select(selectLoadedTimestamp)),
+            switchMap(([, loadedTimestamp]) =>
                 combineLatest([this.usersService.getUsers(), this.usersService.getUserGroups()]).pipe(
                     map(([usersResponse, userGroupsResponse]) =>
                         UserListingActions.loadTenantsSuccess({
@@ -67,7 +68,13 @@ export class UserListingEffects {
                         })
                     ),
                     catchError((errorResponse: HttpErrorResponse) =>
-                        of(this.errorHelper.handleLoadingError(status, errorResponse))
+                        of(
+                            UserListingActions.loadTenantsError({
+                                errorResponse,
+                                loadedTimestamp,
+                                status: loadedTimestamp !== initialState.loadedTimestamp ? 'success' : 'pending'
+                            })
+                        )
                     )
                 )
             )
@@ -86,6 +93,13 @@ export class UserListingEffects {
         { dispatch: false }
     );
 
+    loadTenantsError$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(UserListingActions.loadTenantsError),
+            map((action) => this.errorHelper.handleLoadingError(action.status === 'success', action.errorResponse))
+        )
+    );
+
     openCreateTenantDialog$ = createEffect(
         () =>
             this.actions$.pipe(
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/users/state/user-listing/user-listing.reducer.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/users/state/user-listing/user-listing.reducer.ts
index 2e11869222..bbb790ae04 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/users/state/user-listing/user-listing.reducer.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/users/state/user-listing/user-listing.reducer.ts
@@ -23,6 +23,7 @@ import {
     createUserGroup,
     createUserGroupSuccess,
     loadTenants,
+    loadTenantsError,
     loadTenantsSuccess,
     resetUsersState,
     updateUser,
@@ -57,6 +58,10 @@ export const userListingReducer = createReducer(
         loadedTimestamp: response.loadedTimestamp,
         status: 'success' as const
     })),
+    on(loadTenantsError, (state, { status }) => ({
+        ...state,
+        status
+    })),
     on(createUser, updateUser, createUserGroup, updateUserGroup, (state) => ({
         ...state,
         saving: true
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/users/state/user-listing/user-listing.selectors.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/users/state/user-listing/user-listing.selectors.ts
index 65f7433545..3c25d10de5 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/users/state/user-listing/user-listing.selectors.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/users/state/user-listing/user-listing.selectors.ts
@@ -24,7 +24,10 @@ export const selectUserListingState = createSelector(selectUserState, (state: Us
 
 export const selectSaving = createSelector(selectUserListingState, (state: UserListingState) => state.saving);
 
-export const selectStatus = createSelector(selectUserListingState, (state: UserListingState) => state.status);
+export const selectLoadedTimestamp = createSelector(
+    selectUserListingState,
+    (state: UserListingState) => state.loadedTimestamp
+);
 
 export const selectUsers = createSelector(selectUserListingState, (state: UserListingState) => state.users);
 
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/error-helper.service.spec.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/error-helper.service.spec.ts
new file mode 100644
index 0000000000..74b99fefa5
--- /dev/null
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/error-helper.service.spec.ts
@@ -0,0 +1,226 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { TestBed } from '@angular/core/testing';
+import { ErrorHelper } from './error-helper.service';
+import { HttpErrorResponse } from '@angular/common/http';
+import * as ErrorActions from '../state/error/error.actions';
+
+describe('ErrorHelper', () => {
+    let service: ErrorHelper;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({});
+        service = TestBed.inject(ErrorHelper);
+    });
+
+    it('should be created', () => {
+        expect(service).toBeTruthy();
+    });
+
+    describe('handleLoadingError', () => {
+        describe('when hasExistingData is false (initial load)', () => {
+            it('should return fullScreenError for any error status', () => {
+                const errorResponse = new HttpErrorResponse({
+                    status: 500,
+                    error: 'Server error'
+                });
+
+                const result = service.handleLoadingError(false, errorResponse);
+
+                expect(result.type).toBe(ErrorActions.fullScreenError.type);
+            });
+
+            it('should return fullScreenError for 403 status', () => {
+                const errorResponse = new HttpErrorResponse({
+                    status: 403,
+                    error: 'Forbidden'
+                });
+
+                const result = service.handleLoadingError(false, errorResponse);
+
+                expect(result.type).toBe(ErrorActions.fullScreenError.type);
+            });
+
+            it('should return fullScreenError for 401 status', () => {
+                const errorResponse = new HttpErrorResponse({
+                    status: 401,
+                    error: 'Unauthorized'
+                });
+
+                const result = service.handleLoadingError(false, errorResponse);
+
+                expect(result.type).toBe(ErrorActions.fullScreenError.type);
+            });
+        });
+
+        describe('when hasExistingData is true (refresh/reload)', () => {
+            it('should return snackBarError for 400 status', () => {
+                const errorResponse = new HttpErrorResponse({
+                    status: 400,
+                    error: 'Bad request'
+                });
+
+                const result = service.handleLoadingError(true, errorResponse);
+
+                expect(result.type).toBe(ErrorActions.snackBarError.type);
+                expect((result as any).error).toBe('Bad request');
+            });
+
+            it('should return snackBarError for 403 status', () => {
+                const errorResponse = new HttpErrorResponse({
+                    status: 403,
+                    error: 'Forbidden'
+                });
+
+                const result = service.handleLoadingError(true, errorResponse);
+
+                expect(result.type).toBe(ErrorActions.snackBarError.type);
+            });
+
+            it('should return snackBarError for 404 status', () => {
+                const errorResponse = new HttpErrorResponse({
+                    status: 404,
+                    error: 'Not found'
+                });
+
+                const result = service.handleLoadingError(true, errorResponse);
+
+                expect(result.type).toBe(ErrorActions.snackBarError.type);
+            });
+
+            it('should return snackBarError for 409 status', () => {
+                const errorResponse = new HttpErrorResponse({
+                    status: 409,
+                    error: 'Conflict'
+                });
+
+                const result = service.handleLoadingError(true, errorResponse);
+
+                expect(result.type).toBe(ErrorActions.snackBarError.type);
+            });
+
+            it('should return snackBarError for 413 status', () => {
+                const errorResponse = new HttpErrorResponse({
+                    status: 413,
+                    error: 'Payload too large'
+                });
+
+                const result = service.handleLoadingError(true, errorResponse);
+
+                expect(result.type).toBe(ErrorActions.snackBarError.type);
+            });
+
+            it('should return snackBarError for 503 status', () => {
+                const errorResponse = new HttpErrorResponse({
+                    status: 503,
+                    error: 'Service unavailable'
+                });
+
+                const result = service.handleLoadingError(true, errorResponse);
+
+                expect(result.type).toBe(ErrorActions.snackBarError.type);
+            });
+
+            it('should return fullScreenError for 401 status', () => {
+                const errorResponse = new HttpErrorResponse({
+                    status: 401,
+                    error: 'Unauthorized'
+                });
+
+                const result = service.handleLoadingError(true, errorResponse);
+
+                expect(result.type).toBe(ErrorActions.fullScreenError.type);
+            });
+
+            it('should return fullScreenError for 500 status', () => {
+                const errorResponse = new HttpErrorResponse({
+                    status: 500,
+                    error: 'Internal server error'
+                });
+
+                const result = service.handleLoadingError(true, errorResponse);
+
+                expect(result.type).toBe(ErrorActions.fullScreenError.type);
+            });
+
+            it('should return fullScreenError for 0 status (network error)', () => {
+                const errorResponse = new HttpErrorResponse({
+                    status: 0,
+                    error: null
+                });
+
+                const result = service.handleLoadingError(true, errorResponse);
+
+                expect(result.type).toBe(ErrorActions.fullScreenError.type);
+            });
+        });
+    });
+
+    describe('showErrorInContext', () => {
+        it('should return true for status codes that should show in context', () => {
+            expect(service.showErrorInContext(400)).toBe(true);
+            expect(service.showErrorInContext(403)).toBe(true);
+            expect(service.showErrorInContext(404)).toBe(true);
+            expect(service.showErrorInContext(409)).toBe(true);
+            expect(service.showErrorInContext(413)).toBe(true);
+            expect(service.showErrorInContext(503)).toBe(true);
+        });
+
+        it('should return false for status codes that should not show in context', () => {
+            expect(service.showErrorInContext(401)).toBe(false);
+            expect(service.showErrorInContext(500)).toBe(false);
+            expect(service.showErrorInContext(502)).toBe(false);
+            expect(service.showErrorInContext(0)).toBe(false);
+        });
+    });
+
+    describe('getErrorString', () => {
+        it('should return the error message from the response', () => {
+            const errorResponse = new HttpErrorResponse({
+                status: 500,
+                error: 'Server error message'
+            });
+
+            const result = service.getErrorString(errorResponse);
+
+            expect(result).toBe('Server error message');
+        });
+
+        it('should return error with prefix when provided', () => {
+            const errorResponse = new HttpErrorResponse({
+                status: 500,
+                error: 'Server error message'
+            });
+
+            const result = service.getErrorString(errorResponse, 'Failed to load');
+
+            expect(result).toBe('Failed to load - [Server error message]');
+        });
+
+        it('should handle status 0 (network error)', () => {
+            const errorResponse = new HttpErrorResponse({
+                status: 0,
+                error: null
+            });
+
+            const result = service.getErrorString(errorResponse);
+
+            expect(result).toBe('An unspecified error occurred.');
+        });
+    });
+});
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/error-helper.service.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/error-helper.service.ts
index 07398f3b2f..290d24dfd2 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/error-helper.service.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/error-helper.service.ts
@@ -67,8 +67,8 @@ export class ErrorHelper {
         return [400, 403, 404, 409, 413, 503].includes(status);
     }
 
-    handleLoadingError(status: string, errorResponse: HttpErrorResponse): Action {
-        if (status === 'success') {
+    handleLoadingError(hasExistingData: boolean, errorResponse: HttpErrorResponse): Action {
+        if (hasExistingData) {
             if (this.showErrorInContext(errorResponse.status)) {
                 return ErrorActions.snackBarError({ error: this.getErrorString(errorResponse) });
             } else {