[NIFI-12552] - Support client-side pagination on Summary tables (#8192)

* [NIFI-12552] - Support client-side pagination on Summary tables
* updated the filter control to only show the label indicating matches when a filter is active. similar to how it is done for the events table
* aligned cells like Run Status so the icon and text are vertically centered in the table cell

* moving System Diagnostics out from the Summary page to the main application menu

* reset summary listing table selection when pagination or filter changes.

* remove area chart icon from the System Diagnostics menu option as it duplicates the Node Status History option

* refactor selection clearing to its own action

This closes #8192
This commit is contained in:
Rob Fellows 2024-01-03 18:37:23 -05:00 committed by GitHub
parent 689f990978
commit 3877146170
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 1570 additions and 1212 deletions

View File

@ -114,6 +114,14 @@
<i class="fa fa-fw fa-area-chart mr-2"></i>
Node Status History
</button>
<button
mat-menu-item
class="global-menu-item"
[disabled]="!user.systemPermissions.canRead"
(click)="viewSystemDiagnostics()">
<i class="fa fa-fw mr-2"></i>
System Diagnostics
</button>
<mat-divider></mat-divider>
<button
mat-menu-item

View File

@ -38,7 +38,8 @@ import { AsyncPipe, NgIf, NgOptimizedImage } from '@angular/common';
import { MatDividerModule } from '@angular/material/divider';
import { RouterLink } from '@angular/router';
import { FlowStatus } from './flow-status/flow-status.component';
import * as StatusHistoryActions from '../../../../../state/status-history/status-history.actions';
import { getNodeStatusHistoryAndOpenDialog } from '../../../../../state/status-history/status-history.actions';
import { getSystemDiagnosticsAndOpenDialog } from '../../../../../state/system-diagnostics/system-diagnostics.actions';
@Component({
selector: 'fd-header',
@ -88,11 +89,21 @@ export class HeaderComponent {
viewNodeStatusHistory(): void {
this.store.dispatch(
StatusHistoryActions.getNodeStatusHistoryAndOpenDialog({
getNodeStatusHistoryAndOpenDialog({
request: {
source: 'menu'
}
})
);
}
viewSystemDiagnostics() {
this.store.dispatch(
getSystemDiagnosticsAndOpenDialog({
request: {
nodewise: false
}
})
);
}
}

View File

@ -146,7 +146,7 @@ export interface ProcessGroupStatusSnapshot extends BaseSnapshot {
versionedFlowState?: VersionedFlowState;
}
export interface AggregateSnapshot extends ProcessGroupStatusSnapshot {}
export type AggregateSnapshot = ProcessGroupStatusSnapshot;
export interface PortStatusSnapshot extends BaseSnapshot {
runStatus: string;

View File

@ -25,7 +25,7 @@ import {
SummaryListingResponse
} from './index';
const SUMMARY_LISTING_PREFIX: string = '[Summary Listing]';
const SUMMARY_LISTING_PREFIX = '[Summary Listing]';
export const loadSummaryListing = createAction(
`${SUMMARY_LISTING_PREFIX} Load Summary Listing`,
@ -47,31 +47,53 @@ export const selectProcessorStatus = createAction(
props<{ request: SelectProcessorStatusRequest }>()
);
export const clearProcessorStatusSelection = createAction(`${SUMMARY_LISTING_PREFIX} Clear Processor Status Selection`);
export const selectProcessGroupStatus = createAction(
`${SUMMARY_LISTING_PREFIX} Select Process Group Status`,
props<{ request: SelectProcessGroupStatusRequest }>()
);
export const clearProcessGroupStatusSelection = createAction(
`${SUMMARY_LISTING_PREFIX} Clear Process Group Status Selection`
);
export const selectInputPortStatus = createAction(
`${SUMMARY_LISTING_PREFIX} Select Input Port Status`,
props<{ request: SelectPortStatusRequest }>()
);
export const clearInputPortStatusSelection = createAction(
`${SUMMARY_LISTING_PREFIX} Clear Input Port Status Selection`
);
export const selectOutputPortStatus = createAction(
`${SUMMARY_LISTING_PREFIX} Select Output Port Status`,
props<{ request: SelectPortStatusRequest }>()
);
export const clearOutputPortStatusSelection = createAction(
`${SUMMARY_LISTING_PREFIX} Clear Output Port Status Selection`
);
export const selectConnectionStatus = createAction(
`${SUMMARY_LISTING_PREFIX} Select Connection Status`,
props<{ request: SelectConnectionStatusRequest }>()
);
export const clearConnectionStatusSelection = createAction(
`${SUMMARY_LISTING_PREFIX} Clear Connection Status Selection`
);
export const selectRemoteProcessGroupStatus = createAction(
`${SUMMARY_LISTING_PREFIX} Select Remote Process Group Status`,
props<{ request: SelectRemoteProcessGroupStatusRequest }>()
);
export const clearRemoteProcessGroupStatusSelection = createAction(
`${SUMMARY_LISTING_PREFIX} Clear Remote Process Group Status Selection`
);
export const navigateToViewProcessorStatusHistory = createAction(
`${SUMMARY_LISTING_PREFIX} Navigate To Processor Status History`,
props<{ id: string }>()

View File

@ -73,6 +73,17 @@ export class SummaryListingEffects {
{ dispatch: false }
);
clearProcessorStatusSelection$ = createEffect(
() =>
this.actions$.pipe(
ofType(SummaryListingActions.clearProcessorStatusSelection),
tap(() => {
this.router.navigate(['/summary', 'processors']);
})
),
{ dispatch: false }
);
selectProcessGroupStatus$ = createEffect(
() =>
this.actions$.pipe(
@ -85,6 +96,17 @@ export class SummaryListingEffects {
{ dispatch: false }
);
clearProcessGroupStatusSelection$ = createEffect(
() =>
this.actions$.pipe(
ofType(SummaryListingActions.clearProcessGroupStatusSelection),
tap(() => {
this.router.navigate(['/summary', 'process-groups']);
})
),
{ dispatch: false }
);
selectInputPortStatus$ = createEffect(
() =>
this.actions$.pipe(
@ -97,6 +119,17 @@ export class SummaryListingEffects {
{ dispatch: false }
);
clearInputPortStatusSelection$ = createEffect(
() =>
this.actions$.pipe(
ofType(SummaryListingActions.clearInputPortStatusSelection),
tap(() => {
this.router.navigate(['/summary', 'input-ports']);
})
),
{ dispatch: false }
);
selectOutputPortStatus$ = createEffect(
() =>
this.actions$.pipe(
@ -109,6 +142,17 @@ export class SummaryListingEffects {
{ dispatch: false }
);
clearOutputPortStatusSelection$ = createEffect(
() =>
this.actions$.pipe(
ofType(SummaryListingActions.clearOutputPortStatusSelection),
tap(() => {
this.router.navigate(['/summary', 'output-ports']);
})
),
{ dispatch: false }
);
selectConnectionStatus$ = createEffect(
() =>
this.actions$.pipe(
@ -121,6 +165,17 @@ export class SummaryListingEffects {
{ dispatch: false }
);
clearConnectionStatusSelection$ = createEffect(
() =>
this.actions$.pipe(
ofType(SummaryListingActions.clearConnectionStatusSelection),
tap(() => {
this.router.navigate(['/summary', 'connections']);
})
),
{ dispatch: false }
);
selectRpgStatus$ = createEffect(
() =>
this.actions$.pipe(
@ -133,6 +188,17 @@ export class SummaryListingEffects {
{ dispatch: false }
);
clearRpgStatusSelection = createEffect(
() =>
this.actions$.pipe(
ofType(SummaryListingActions.clearRemoteProcessGroupStatusSelection),
tap(() => {
this.router.navigate(['/summary', 'remote-process-groups']);
})
),
{ dispatch: false }
);
navigateToProcessorStatusHistory$ = createEffect(
() =>
this.actions$.pipe(

View File

@ -14,134 +14,168 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<div class="flex flex-col h-full gap-y-2">
<div class="flex-1">
<ng-container>
<div class="port-status-table h-full flex flex-col">
<!-- allow filtering of the table -->
<summary-table-filter
[filteredCount]="filteredCount"
[totalCount]="totalCount"
[filterableColumns]="filterableColumns"
[includeStatusFilter]="true"
[includePrimaryNodeOnlyFilter]="false"
(filterChanged)="applyFilter($event)"></summary-table-filter>
<div class="port-status-table h-full flex flex-col">
<!-- allow filtering of the table -->
<summary-table-filter
[filteredCount]="filteredCount"
[totalCount]="totalCount"
[filterableColumns]="filterableColumns"
[includeStatusFilter]="true"
[includePrimaryNodeOnlyFilter]="false"
(filterChanged)="applyFilter($event)"></summary-table-filter>
<div class="flex-1 relative">
<div class="listing-table overflow-y-auto border absolute inset-0">
<table
mat-table
[dataSource]="dataSource"
matSort
matSortDisableClear
(matSortChange)="sortData($event)"
[matSortActive]="initialSortColumn"
[matSortDirection]="initialSortDirection">
<!-- More Details Column -->
<ng-container matColumnDef="moreDetails">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let item"></td>
</ng-container>
<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
<div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap">Name</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatName(item)">
{{ formatName(item) }}
</td>
</ng-container>
<!-- Run Status column -->
<ng-container matColumnDef="runStatus">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Run Status</th>
<td mat-cell *matCellDef="let item">
<div class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1.5">
<span [ngClass]="getRunStatusIcon(item)"></span>
<span [title]="formatRunStatus(item)">{{ formatRunStatus(item) }}</span>
<ng-container *ngIf="item.processorStatusSnapshot as pg">
<span
*ngIf="pg.terminatedThreadCount > 0; else activeThreads"
title="Threads: (Active / Terminated)"
>({{ pg.activeThreadCount }}/{{ pg.terminatedThreadCount }})</span
>
<ng-template #activeThreads>
<span *ngIf="pg.activeThreadCount > 0" title="Active Threads"
>({{ pg.activeThreadCount }})</span
>
</ng-template>
<div class="flex-1 relative">
<div class="listing-table overflow-y-auto border absolute inset-0">
<table
mat-table
[dataSource]="dataSource"
matSort
matSortDisableClear
(matSortChange)="sortData($event)"
[matSortActive]="initialSortColumn"
[matSortDirection]="initialSortDirection">
<!-- More Details Column -->
<ng-container matColumnDef="moreDetails">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let item"></td>
</ng-container>
</div>
</td>
</ng-container>
<!-- Input column -->
<ng-container matColumnDef="in">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Count / data size in the last 5 minutes">
<div class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span [ngClass]="{ underline: multiSort.active === 'in' && multiSort.sortValueIndex === 0 }"
>In</span
>
<span [ngClass]="{ underline: multiSort.active === 'in' && multiSort.sortValueIndex === 1 }"
>(Size)</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatIn(item)">
{{ formatIn(item) }}
</td>
</ng-container>
<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
<div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap">Name</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatName(item)">
{{ formatName(item) }}
</td>
</ng-container>
<!-- Output column -->
<ng-container matColumnDef="out">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Count / data size in the last 5 minutes">
<div class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span
[ngClass]="{ underline: multiSort.active === 'out' && multiSort.sortValueIndex === 0 }">
Out
</span>
<span
[ngClass]="{ underline: multiSort.active === 'out' && multiSort.sortValueIndex === 1 }">
(Size)
</span>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatOut(item)">
{{ formatOut(item) }}
</td>
</ng-container>
<!-- Run Status column -->
<ng-container matColumnDef="runStatus">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Run Status</th>
<td mat-cell *matCellDef="let item">
<div
class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1.5 align-middle">
<span [ngClass]="getRunStatusIcon(item)"></span>
<span [title]="formatRunStatus(item)">{{ formatRunStatus(item) }}</span>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let item">
<div class="flex items-center gap-x-3">
<div
class="pointer fa fa-long-arrow-right"
[routerLink]="getPortLink(item)"
(click)="$event.stopPropagation()"
title="Go to {{ portType }}} port"></div>
</div>
</td>
</ng-container>
<ng-container *ngIf="item.processorStatusSnapshot as pg">
<span
*ngIf="pg.terminatedThreadCount > 0; else activeThreads"
title="Threads: (Active / Terminated)"
>({{ pg.activeThreadCount }}/{{ pg.terminatedThreadCount }})</span
>
<ng-template #activeThreads>
<span *ngIf="pg.activeThreadCount > 0" title="Active Threads"
>({{ pg.activeThreadCount }})</span
>
</ng-template>
</ng-container>
</div>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
<tr
mat-row
*matRowDef="let row; let even = even; columns: displayedColumns"
[class.even]="even"
(click)="select(row)"
[class.selected]="isSelected(row)"></tr>
</table>
<!-- Input column -->
<ng-container matColumnDef="in">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Count / data size in the last 5 minutes">
<div
class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span
[ngClass]="{
underline: multiSort.active === 'in' && multiSort.sortValueIndex === 0
}"
>In</span
>
<span
[ngClass]="{
underline: multiSort.active === 'in' && multiSort.sortValueIndex === 1
}"
>(Size)</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatIn(item)">
{{ formatIn(item) }}
</td>
</ng-container>
<!-- Output column -->
<ng-container matColumnDef="out">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Count / data size in the last 5 minutes">
<div
class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span
[ngClass]="{
underline: multiSort.active === 'out' && multiSort.sortValueIndex === 0
}">
Out
</span>
<span
[ngClass]="{
underline: multiSort.active === 'out' && multiSort.sortValueIndex === 1
}">
(Size)
</span>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatOut(item)">
{{ formatOut(item) }}
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let item">
<div class="flex items-center gap-x-3">
<div
class="pointer fa fa-long-arrow-right"
[routerLink]="getPortLink(item)"
(click)="$event.stopPropagation()"
title="Go to {{ portType }}} port"></div>
</div>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
<tr
mat-row
*matRowDef="let row; let even = even; columns: displayedColumns"
[class.even]="even"
(click)="select(row)"
[class.selected]="isSelected(row)"></tr>
</table>
</div>
</div>
</div>
</ng-container>
</div>
<div class="flex justify-between align-middle">
<div class="refresh-container flex items-center gap-x-2">
<button class="nifi-button" (click)="refresh.next()">
<i class="fa fa-refresh" [class.fa-spin]="summaryListingStatus === 'loading'"></i>
</button>
<div>Last updated:</div>
<div class="refresh-timestamp">{{ loadedTimestamp }}</div>
</div>
<div>
<mat-paginator
[pageSize]="100"
[hidePageSize]="true"
[showFirstLastButtons]="true"
(page)="paginationChanged()"></mat-paginator>
</div>
</div>
</div>

View File

@ -15,7 +15,7 @@
* limitations under the License.
*/
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { AfterViewInit, Component, EventEmitter, Input, Output, ViewChild } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatSortModule, Sort, SortDirection } from '@angular/material/sort';
import { MultiSort } from '../index';
@ -29,25 +29,26 @@ import {
import { ComponentType } from '../../../../../state/shared';
import { RouterLink } from '@angular/router';
import { NiFiCommon } from '../../../../../service/nifi-common.service';
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
export type SupportedColumns = 'name' | 'runStatus' | 'in' | 'out';
@Component({
selector: 'port-status-table',
standalone: true,
imports: [CommonModule, SummaryTableFilterModule, MatSortModule, MatTableModule, RouterLink],
imports: [CommonModule, SummaryTableFilterModule, MatSortModule, MatTableModule, RouterLink, MatPaginatorModule],
templateUrl: './port-status-table.component.html',
styleUrls: ['./port-status-table.component.scss', '../../../../../../assets/styles/listing-table.scss']
})
export class PortStatusTable {
export class PortStatusTable implements AfterViewInit {
private _initialSortColumn: SupportedColumns = 'name';
private _initialSortDirection: SortDirection = 'asc';
private _portType!: 'input' | 'output';
filterableColumns: SummaryTableFilterColumn[] = [{ key: 'name', label: 'name' }];
totalCount: number = 0;
filteredCount: number = 0;
totalCount = 0;
filteredCount = 0;
multiSort: MultiSort = {
active: this._initialSortColumn,
@ -60,8 +61,14 @@ export class PortStatusTable {
dataSource: MatTableDataSource<PortStatusSnapshotEntity> = new MatTableDataSource<PortStatusSnapshotEntity>();
@ViewChild(MatPaginator) paginator!: MatPaginator;
constructor(private nifiCommon: NiFiCommon) {}
ngAfterViewInit(): void {
this.dataSource.paginator = this.paginator;
}
@Input() set portType(type: 'input' | 'output') {
if (type === 'input') {
this.displayedColumns = ['moreDetails', 'name', 'runStatus', 'in', 'actions'];
@ -120,11 +127,29 @@ export class PortStatusTable {
}
}
@Input() summaryListingStatus: string | null = null;
@Input() loadedTimestamp: string | null = null;
@Output() refresh: EventEmitter<void> = new EventEmitter<void>();
@Output() selectPort: EventEmitter<PortStatusSnapshotEntity> = new EventEmitter<PortStatusSnapshotEntity>();
@Output() clearSelection: EventEmitter<void> = new EventEmitter<void>();
applyFilter(filter: SummaryTableFilterArgs) {
this.dataSource.filter = JSON.stringify(filter);
this.filteredCount = this.dataSource.filteredData.length;
this.resetPaginator();
this.selectNone();
}
resetPaginator(): void {
if (this.dataSource.paginator) {
this.dataSource.paginator.firstPage();
}
}
paginationChanged(): void {
// clear out any selection
this.selectNone();
}
formatName(port: PortStatusSnapshotEntity): string {
@ -172,6 +197,10 @@ export class PortStatusTable {
this.selectPort.next(port);
}
private selectNone() {
this.clearSelection.next();
}
isSelected(port: PortStatusSnapshotEntity): boolean {
if (this.selectedPortId) {
return port.id === this.selectedPortId;
@ -226,7 +255,7 @@ export class PortStatusTable {
}
return data.slice().sort((a, b) => {
const isAsc: boolean = sort.direction === 'asc';
let retVal: number = 0;
let retVal = 0;
switch (sort.active) {
case 'name':
retVal = this.nifiCommon.compareString(a.portStatusSnapshot.name, b.portStatusSnapshot.name);

View File

@ -16,7 +16,9 @@
-->
<div class="summary-table-filter-container">
<div class="value">Displaying {{ filteredCount }} of {{ totalCount }}</div>
<div class="value {{ showFilterMatchedLabel ? 'visible' : 'invisible' }}">
Filter matched {{ filteredCount }} of {{ totalCount }}
</div>
<form [formGroup]="filterForm">
<div class="flex pt-1 gap-1 items-baseline">
<div>

View File

@ -37,13 +37,14 @@ export interface SummaryTableFilterArgs {
})
export class SummaryTableFilter implements AfterViewInit {
filterForm: FormGroup;
private _filteredCount: number = 0;
private _totalCount: number = 0;
private _initialFilterColumn: string = 'name';
private _filteredCount = 0;
private _totalCount = 0;
private _initialFilterColumn = 'name';
showFilterMatchedLabel = false;
@Input() filterableColumns: SummaryTableFilterColumn[] = [];
@Input() includeStatusFilter: boolean = false;
@Input() includePrimaryNodeOnlyFilter: boolean = false;
@Input() includeStatusFilter = false;
@Input() includePrimaryNodeOnlyFilter = false;
@Output() filterChanged: EventEmitter<SummaryTableFilterArgs> = new EventEmitter<SummaryTableFilterArgs>();
@Input() set filterTerm(term: string) {
@ -133,5 +134,6 @@ export class SummaryTableFilter implements AfterViewInit {
filterTerm,
primaryOnly
});
this.showFilterMatchedLabel = filterTerm?.length > 0 || filterStatus !== 'All' || primaryOnly;
}
}

View File

@ -22,7 +22,7 @@ import { MatInputModule } from '@angular/material/input';
import { MatOptionModule } from '@angular/material/core';
import { MatSelectModule } from '@angular/material/select';
import { ReactiveFormsModule } from '@angular/forms';
import { NgForOf, NgIf } from '@angular/common';
import { NgClass, NgForOf, NgIf } from '@angular/common';
import { MatCheckboxModule } from '@angular/material/checkbox';
@NgModule({
@ -35,7 +35,8 @@ import { MatCheckboxModule } from '@angular/material/checkbox';
ReactiveFormsModule,
NgForOf,
NgIf,
MatCheckboxModule
MatCheckboxModule,
NgClass
],
exports: [SummaryTableFilter],
providers: []

View File

@ -21,30 +21,16 @@
</div>
<ng-template #loaded>
<div class="flex flex-col h-full gap-y-2">
<div class="flex-1" *ngIf="currentUser$ | async as user">
<ng-container>
<connection-status-table
[connections]="(connectionStatusSnapshots$ | async)!"
[selectedConnectionId]="selectedConnectionId$ | async"
(selectConnection)="selectConnection($event)"
(viewStatusHistory)="viewStatusHistory($event)"
initialSortColumn="sourceName"
initialSortDirection="asc"></connection-status-table>
</ng-container>
</div>
<div class="flex justify-between">
<div class="refresh-container flex items-center gap-x-2">
<button class="nifi-button" (click)="refreshSummaryListing()">
<i class="fa fa-refresh" [class.fa-spin]="(summaryListingStatus$ | async) === 'loading'"></i>
</button>
<div>Last updated:</div>
<div class="refresh-timestamp">{{ loadedTimestamp$ | async }}</div>
</div>
<div *ngIf="(currentUser$ | async)?.systemPermissions?.canRead">
<a (click)="openSystemDiagnostics()">System Diagnostics</a>
</div>
</div>
</div>
<connection-status-table
[connections]="(connectionStatusSnapshots$ | async)!"
[selectedConnectionId]="selectedConnectionId$ | async"
[loadedTimestamp]="loadedTimestamp$ | async"
[summaryListingStatus]="summaryListingStatus$ | async"
(selectConnection)="selectConnection($event)"
(clearSelection)="clearSelection()"
(viewStatusHistory)="viewStatusHistory($event)"
(refresh)="refreshSummaryListing()"
initialSortColumn="sourceName"
initialSortDirection="asc"></connection-status-table>
</ng-template>
</ng-container>

View File

@ -17,18 +17,13 @@
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import {
ConnectionStatusSnapshotEntity,
PortStatusSnapshotEntity,
SummaryListingState
} from '../../state/summary-listing';
import { ConnectionStatusSnapshotEntity, SummaryListingState } from '../../state/summary-listing';
import { initialState } from '../../state/summary-listing/summary-listing.reducer';
import * as SummaryListingActions from '../../state/summary-listing/summary-listing.actions';
import {
selectConnectionIdFromRoute,
selectConnectionStatus,
selectConnectionStatusSnapshots,
selectProcessorStatus,
selectSummaryListingLoadedTimestamp,
selectSummaryListingStatus,
selectViewStatusHistory
@ -36,12 +31,8 @@ import {
import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors';
import { filter, switchMap, take } from 'rxjs';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import {
getStatusHistoryAndOpenDialog,
openStatusHistoryDialog
} from '../../../../state/status-history/status-history.actions';
import { getStatusHistoryAndOpenDialog } from '../../../../state/status-history/status-history.actions';
import { ComponentType } from '../../../../state/shared';
import { getSystemDiagnosticsAndOpenDialog } from '../../../../state/system-diagnostics/system-diagnostics.actions';
@Component({
selector: 'connection-status-listing',
@ -101,6 +92,10 @@ export class ConnectionStatusListing {
);
}
clearSelection() {
this.store.dispatch(SummaryListingActions.clearConnectionStatusSelection());
}
viewStatusHistory(connection: ConnectionStatusSnapshotEntity): void {
this.store.dispatch(
SummaryListingActions.navigateToViewConnectionStatusHistory({
@ -108,14 +103,4 @@ export class ConnectionStatusListing {
})
);
}
openSystemDiagnostics() {
this.store.dispatch(
getSystemDiagnosticsAndOpenDialog({
request: {
nodewise: false
}
})
);
}
}

View File

@ -21,10 +21,11 @@ import { CommonModule } from '@angular/common';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { PortStatusTable } from '../common/port-status-table/port-status-table.component';
import { ConnectionStatusTable } from './connection-status-table/connection-status-table.component';
import { ProcessorStatusTable } from '../processor-status-listing/processor-status-table/processor-status-table.component';
@NgModule({
declarations: [ConnectionStatusListing],
exports: [ConnectionStatusListing],
imports: [CommonModule, NgxSkeletonLoaderModule, PortStatusTable, ConnectionStatusTable]
imports: [CommonModule, NgxSkeletonLoaderModule, PortStatusTable, ConnectionStatusTable, ProcessorStatusTable]
})
export class ConnectionStatusListingModule {}

View File

@ -14,192 +14,236 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<div class="connection-status-table h-full flex flex-col">
<!-- allow filtering of the table -->
<summary-table-filter
[filteredCount]="filteredCount"
[totalCount]="totalCount"
[filterableColumns]="filterableColumns"
[includeStatusFilter]="false"
[includePrimaryNodeOnlyFilter]="false"
filterColumn="sourceName"
(filterChanged)="applyFilter($event)"></summary-table-filter>
<div class="flex flex-col h-full gap-y-2">
<div class="flex-1">
<ng-container>
<div class="connection-status-table h-full flex flex-col">
<!-- allow filtering of the table -->
<summary-table-filter
[filteredCount]="filteredCount"
[totalCount]="totalCount"
[filterableColumns]="filterableColumns"
[includeStatusFilter]="false"
[includePrimaryNodeOnlyFilter]="false"
filterColumn="sourceName"
(filterChanged)="applyFilter($event)"></summary-table-filter>
<div class="flex-1 relative">
<div class="listing-table overflow-y-auto border absolute inset-0">
<table
mat-table
[dataSource]="dataSource"
matSort
matSortDisableClear
(matSortChange)="sortData($event)"
[matSortActive]="initialSortColumn"
[matSortDirection]="initialSortDirection">
<!-- More Details Column -->
<ng-container matColumnDef="moreDetails">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let item"></td>
</ng-container>
<div class="flex-1 relative">
<div class="listing-table overflow-y-auto border absolute inset-0">
<table
mat-table
[dataSource]="dataSource"
matSort
matSortDisableClear
(matSortChange)="sortData($event)"
[matSortActive]="initialSortColumn"
[matSortDirection]="initialSortDirection">
<!-- More Details Column -->
<ng-container matColumnDef="moreDetails">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let item"></td>
</ng-container>
<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
<div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap">Name</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatName(item)">
{{ formatName(item) }}
</td>
</ng-container>
<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
<div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap">Name</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatName(item)">
{{ formatName(item) }}
</td>
</ng-container>
<!-- Queued column -->
<ng-container matColumnDef="queue">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Count / data size in the last 5 minutes">
<div class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span
[ngClass]="{
underline: multiSort.active === 'queue' && multiSort.sortValueIndex === 0
}"
>Queue</span
>
<span
[ngClass]="{
underline: multiSort.active === 'queue' && multiSort.sortValueIndex === 1
}"
>(Size)</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatQueue(item)">
{{ formatQueue(item) }}
</td>
</ng-container>
<!-- Queued column -->
<ng-container matColumnDef="queue">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Count / data size in the last 5 minutes">
<div
class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span
[ngClass]="{
underline:
multiSort.active === 'queue' && multiSort.sortValueIndex === 0
}"
>Queue</span
>
<span
[ngClass]="{
underline:
multiSort.active === 'queue' && multiSort.sortValueIndex === 1
}"
>(Size)</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatQueue(item)">
{{ formatQueue(item) }}
</td>
</ng-container>
<!-- Threshold column -->
<ng-container matColumnDef="threshold">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Percent of threshold used for count and data size">
<div class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span>Threshold %:</span>
<span
[ngClass]="{
underline: multiSort.active === 'threshold' && multiSort.sortValueIndex === 0
}"
>Queue</span
>
<span>|</span>
<span
[ngClass]="{
underline: multiSort.active === 'threshold' && multiSort.sortValueIndex === 1
}"
>Size</span
>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatThreshold(item)">
{{ formatThreshold(item) }}
</td>
</ng-container>
<!-- Threshold column -->
<ng-container matColumnDef="threshold">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Percent of threshold used for count and data size">
<div
class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span>Threshold %:</span>
<span
[ngClass]="{
underline:
multiSort.active === 'threshold' && multiSort.sortValueIndex === 0
}"
>Queue</span
>
<span>|</span>
<span
[ngClass]="{
underline:
multiSort.active === 'threshold' && multiSort.sortValueIndex === 1
}"
>Size</span
>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatThreshold(item)">
{{ formatThreshold(item) }}
</td>
</ng-container>
<!-- Input column -->
<ng-container matColumnDef="in">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Count / data size in the last 5 minutes">
<div class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span [ngClass]="{ underline: multiSort.active === 'in' && multiSort.sortValueIndex === 0 }"
>In</span
>
<span [ngClass]="{ underline: multiSort.active === 'in' && multiSort.sortValueIndex === 1 }"
>(Size)</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatIn(item)">
{{ formatIn(item) }}
</td>
</ng-container>
<!-- Input column -->
<ng-container matColumnDef="in">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Count / data size in the last 5 minutes">
<div
class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span
[ngClass]="{
underline: multiSort.active === 'in' && multiSort.sortValueIndex === 0
}"
>In</span
>
<span
[ngClass]="{
underline: multiSort.active === 'in' && multiSort.sortValueIndex === 1
}"
>(Size)</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatIn(item)">
{{ formatIn(item) }}
</td>
</ng-container>
<!-- Source Column -->
<ng-container matColumnDef="sourceName">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
<div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap">From Source</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatSource(item)">
{{ formatSource(item) }}
</td>
</ng-container>
<!-- Source Column -->
<ng-container matColumnDef="sourceName">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
<div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap">
From Source
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatSource(item)">
{{ formatSource(item) }}
</td>
</ng-container>
<!-- Output column -->
<ng-container matColumnDef="out">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Count / data size in the last 5 minutes">
<div class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span
[ngClass]="{ underline: multiSort.active === 'out' && multiSort.sortValueIndex === 0 }"
>Out</span
>
<span
[ngClass]="{ underline: multiSort.active === 'out' && multiSort.sortValueIndex === 1 }"
>(Size)</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatOut(item)">
{{ formatOut(item) }}
</td>
</ng-container>
<!-- Output column -->
<ng-container matColumnDef="out">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Count / data size in the last 5 minutes">
<div
class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span
[ngClass]="{
underline: multiSort.active === 'out' && multiSort.sortValueIndex === 0
}"
>Out</span
>
<span
[ngClass]="{
underline: multiSort.active === 'out' && multiSort.sortValueIndex === 1
}"
>(Size)</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatOut(item)">
{{ formatOut(item) }}
</td>
</ng-container>
<!-- Destination Column -->
<ng-container matColumnDef="destinationName">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
<div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap">To Destination</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatDestination(item)">
{{ formatDestination(item) }}
</td>
</ng-container>
<!-- Destination Column -->
<ng-container matColumnDef="destinationName">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
<div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap">
To Destination
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatDestination(item)">
{{ formatDestination(item) }}
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let item">
<div class="flex items-center gap-x-3">
<div
class="pointer fa fa-long-arrow-right"
[routerLink]="getConnectionLink(item)"
(click)="$event.stopPropagation()"
title="Go to connection"></div>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let item">
<div class="flex items-center gap-x-3">
<div
class="pointer fa fa-long-arrow-right"
[routerLink]="getConnectionLink(item)"
(click)="$event.stopPropagation()"
title="Go to connection"></div>
<div
class="pointer fa fa-area-chart"
title="View Status History"
(click)="viewStatusHistoryClicked($event, item)"></div>
</div>
</td>
</ng-container>
<div
class="pointer fa fa-area-chart"
title="View Status History"
(click)="viewStatusHistoryClicked($event, item)"></div>
</div>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
<tr
mat-row
*matRowDef="let row; let even = even; columns: displayedColumns"
[class.even]="even"
(click)="select(row)"
[class.selected]="isSelected(row)"></tr>
</table>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
<tr
mat-row
*matRowDef="let row; let even = even; columns: displayedColumns"
[class.even]="even"
(click)="select(row)"
[class.selected]="isSelected(row)"></tr>
</table>
</div>
</div>
</div>
</ng-container>
</div>
<div class="flex justify-between align-middle">
<div class="refresh-container flex items-center gap-x-2">
<button class="nifi-button" (click)="refresh.next()">
<i class="fa fa-refresh" [class.fa-spin]="summaryListingStatus === 'loading'"></i>
</button>
<div>Last updated:</div>
<div class="refresh-timestamp">{{ loadedTimestamp }}</div>
</div>
<div>
<mat-paginator
[pageSize]="100"
[hidePageSize]="true"
[showFirstLastButtons]="true"
(page)="paginationChanged()"></mat-paginator>
</div>
</div>
</div>

View File

@ -15,7 +15,7 @@
* limitations under the License.
*/
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Component, EventEmitter, Input, Output, ViewChild } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SummaryTableFilterModule } from '../../common/summary-table-filter/summary-table-filter.module';
import { MatSortModule, Sort, SortDirection } from '@angular/material/sort';
@ -29,13 +29,14 @@ import { ConnectionStatusSnapshot, ConnectionStatusSnapshotEntity } from '../../
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { ComponentType } from '../../../../../state/shared';
import { RouterLink } from '@angular/router';
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
export type SupportedColumns = 'name' | 'queue' | 'in' | 'out' | 'threshold' | 'sourceName' | 'destinationName';
@Component({
selector: 'connection-status-table',
standalone: true,
imports: [CommonModule, SummaryTableFilterModule, MatSortModule, RouterLink, MatTableModule],
imports: [CommonModule, SummaryTableFilterModule, MatSortModule, RouterLink, MatTableModule, MatPaginatorModule],
templateUrl: './connection-status-table.component.html',
styleUrls: ['./connection-status-table.component.scss', '../../../../../../assets/styles/listing-table.scss']
})
@ -49,8 +50,8 @@ export class ConnectionStatusTable {
{ key: 'destinationName', label: 'destination' }
];
totalCount: number = 0;
filteredCount: number = 0;
totalCount = 0;
filteredCount = 0;
multiSort: MultiSort = {
active: this._initialSortColumn,
@ -73,8 +74,15 @@ export class ConnectionStatusTable {
dataSource: MatTableDataSource<ConnectionStatusSnapshotEntity> =
new MatTableDataSource<ConnectionStatusSnapshotEntity>();
@ViewChild(MatPaginator) paginator!: MatPaginator;
constructor(private nifiCommon: NiFiCommon) {}
ngAfterViewInit(): void {
this.dataSource.paginator = this.paginator;
}
@Input() set initialSortColumn(initialSortColumn: SupportedColumns) {
this._initialSortColumn = initialSortColumn;
this.multiSort = { ...this.multiSort, active: initialSortColumn };
@ -116,14 +124,36 @@ export class ConnectionStatusTable {
}
}
@Input() summaryListingStatus: string | null = null;
@Input() loadedTimestamp: string | null = null;
@Output() refresh: EventEmitter<void> = new EventEmitter<void>();
@Output() viewStatusHistory: EventEmitter<ConnectionStatusSnapshotEntity> =
new EventEmitter<ConnectionStatusSnapshotEntity>();
@Output() selectConnection: EventEmitter<ConnectionStatusSnapshotEntity> =
new EventEmitter<ConnectionStatusSnapshotEntity>();
@Output() clearSelection: EventEmitter<void> = new EventEmitter<void>();
resetPaginator(): void {
if (this.dataSource.paginator) {
this.dataSource.paginator.firstPage();
}
}
applyFilter(filter: SummaryTableFilterArgs) {
this.dataSource.filter = JSON.stringify(filter);
this.filteredCount = this.dataSource.filteredData.length;
this.resetPaginator();
this.selectNone();
}
paginationChanged(): void {
// clear out any selection
this.selectNone();
}
private selectNone() {
this.clearSelection.next();
}
getConnectionLink(connection: ConnectionStatusSnapshotEntity): string[] {
@ -222,7 +252,7 @@ export class ConnectionStatusTable {
}
return data.slice().sort((a, b) => {
const isAsc: boolean = sort.direction === 'asc';
let retVal: number = 0;
let retVal = 0;
switch (sort.active) {
case 'name':
retVal = this.nifiCommon.compareString(

View File

@ -21,30 +21,16 @@
</div>
<ng-template #loaded>
<div class="flex flex-col h-full gap-y-2">
<div class="flex-1" *ngIf="currentUser$ | async as user">
<ng-container>
<port-status-table
[ports]="(portStatusSnapshots$ | async)!"
[selectedPortId]="selectedPortId$ | async"
portType="input"
(selectPort)="selectPort($event)"
initialSortColumn="name"
initialSortDirection="asc"></port-status-table>
</ng-container>
</div>
<div class="flex justify-between">
<div class="refresh-container flex items-center gap-x-2">
<button class="nifi-button" (click)="refreshSummaryListing()">
<i class="fa fa-refresh" [class.fa-spin]="(summaryListingStatus$ | async) === 'loading'"></i>
</button>
<div>Last updated:</div>
<div class="refresh-timestamp">{{ loadedTimestamp$ | async }}</div>
</div>
<div *ngIf="(currentUser$ | async)?.systemPermissions?.canRead">
<a (click)="openSystemDiagnostics()">System Diagnostics</a>
</div>
</div>
</div>
<port-status-table
[ports]="(portStatusSnapshots$ | async)!"
[selectedPortId]="selectedPortId$ | async"
[loadedTimestamp]="loadedTimestamp$ | async"
[summaryListingStatus]="summaryListingStatus$ | async"
portType="input"
(selectPort)="selectPort($event)"
(clearSelection)="clearSelection()"
(refresh)="refreshSummaryListing()"
initialSortColumn="name"
initialSortDirection="asc"></port-status-table>
</ng-template>
</ng-container>

View File

@ -27,7 +27,6 @@ import { PortStatusSnapshotEntity, SummaryListingState } from '../../state/summa
import { Store } from '@ngrx/store';
import { initialState } from '../../state/summary-listing/summary-listing.reducer';
import * as SummaryListingActions from '../../state/summary-listing/summary-listing.actions';
import { getSystemDiagnosticsAndOpenDialog } from '../../../../state/system-diagnostics/system-diagnostics.actions';
@Component({
selector: 'input-port-status-listing',
@ -61,13 +60,7 @@ export class InputPortStatusListing {
);
}
openSystemDiagnostics() {
this.store.dispatch(
getSystemDiagnosticsAndOpenDialog({
request: {
nodewise: false
}
})
);
clearSelection() {
this.store.dispatch(SummaryListingActions.clearInputPortStatusSelection());
}
}

View File

@ -21,9 +21,10 @@ import { InputPortStatusListing } from './input-port-status-listing.component';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { ProcessGroupStatusTable } from '../process-group-status-listing/process-group-status-table/process-group-status-table.component';
import { PortStatusTable } from '../common/port-status-table/port-status-table.component';
import { ProcessorStatusTable } from '../processor-status-listing/processor-status-table/processor-status-table.component';
@NgModule({
declarations: [InputPortStatusListing],
imports: [CommonModule, NgxSkeletonLoaderModule, ProcessGroupStatusTable, PortStatusTable]
imports: [CommonModule, NgxSkeletonLoaderModule, ProcessGroupStatusTable, PortStatusTable, ProcessorStatusTable]
})
export class InputPortStatusListingModule {}

View File

@ -21,30 +21,16 @@
</div>
<ng-template #loaded>
<div class="flex flex-col h-full gap-y-2">
<div class="flex-1" *ngIf="currentUser$ | async as user">
<ng-container>
<port-status-table
[ports]="(portStatusSnapshots$ | async)!"
[selectedPortId]="selectedPortId$ | async"
portType="output"
(selectPort)="selectPort($event)"
initialSortColumn="name"
initialSortDirection="asc"></port-status-table>
</ng-container>
</div>
<div class="flex justify-between">
<div class="refresh-container flex items-center gap-x-2">
<button class="nifi-button" (click)="refreshSummaryListing()">
<i class="fa fa-refresh" [class.fa-spin]="(summaryListingStatus$ | async) === 'loading'"></i>
</button>
<div>Last updated:</div>
<div class="refresh-timestamp">{{ loadedTimestamp$ | async }}</div>
</div>
<div *ngIf="(currentUser$ | async)?.systemPermissions?.canRead">
<a (click)="openSystemDiagnostics()">System Diagnostics</a>
</div>
</div>
</div>
<port-status-table
[ports]="(portStatusSnapshots$ | async)!"
[selectedPortId]="selectedPortId$ | async"
[loadedTimestamp]="loadedTimestamp$ | async"
[summaryListingStatus]="summaryListingStatus$ | async"
portType="output"
(selectPort)="selectPort($event)"
(clearSelection)="clearSelection()"
(refresh)="refreshSummaryListing()"
initialSortColumn="name"
initialSortDirection="asc"></port-status-table>
</ng-template>
</ng-container>

View File

@ -17,8 +17,6 @@
import { Component } from '@angular/core';
import {
selectInputPortIdFromRoute,
selectInputPortStatusSnapshots,
selectOutputPortIdFromRoute,
selectOutputPortStatusSnapshots,
selectSummaryListingLoadedTimestamp,
@ -29,7 +27,6 @@ import { Store } from '@ngrx/store';
import { PortStatusSnapshotEntity, SummaryListingState } from '../../state/summary-listing';
import { initialState } from '../../state/summary-listing/summary-listing.reducer';
import * as SummaryListingActions from '../../state/summary-listing/summary-listing.actions';
import { getSystemDiagnosticsAndOpenDialog } from '../../../../state/system-diagnostics/system-diagnostics.actions';
@Component({
selector: 'output-port-status-listing',
@ -63,13 +60,7 @@ export class OutputPortStatusListing {
);
}
openSystemDiagnostics() {
this.store.dispatch(
getSystemDiagnosticsAndOpenDialog({
request: {
nodewise: false
}
})
);
clearSelection() {
this.store.dispatch(SummaryListingActions.clearOutputPortStatusSelection());
}
}

View File

@ -20,10 +20,11 @@ import { CommonModule } from '@angular/common';
import { OutputPortStatusListing } from './output-port-status-listing.component';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { PortStatusTable } from '../common/port-status-table/port-status-table.component';
import { ProcessorStatusTable } from '../processor-status-listing/processor-status-table/processor-status-table.component';
@NgModule({
declarations: [OutputPortStatusListing],
exports: [OutputPortStatusListing],
imports: [CommonModule, NgxSkeletonLoaderModule, PortStatusTable]
imports: [CommonModule, NgxSkeletonLoaderModule, PortStatusTable, ProcessorStatusTable]
})
export class OutputPortStatusListingModule {}

View File

@ -21,31 +21,17 @@
</div>
<ng-template #loaded>
<div class="flex flex-col h-full gap-y-2">
<div class="flex-1" *ngIf="currentUser$ | async as user">
<ng-container>
<process-group-status-table
[processGroups]="(processGroupStatusSnapshots$ | async)!"
[selectedProcessGroupId]="selectedProcessGroupId$ | async"
[rootProcessGroup]="(processGroupStatus$ | async)?.processGroupStatus?.aggregateSnapshot!"
(viewStatusHistory)="viewStatusHistory($event)"
(selectProcessGroup)="selectProcessGroup($event)"
initialSortColumn="name"
initialSortDirection="asc"></process-group-status-table>
</ng-container>
</div>
<div class="flex justify-between">
<div class="refresh-container flex items-center gap-x-2">
<button class="nifi-button" (click)="refreshSummaryListing()">
<i class="fa fa-refresh" [class.fa-spin]="(summaryListingStatus$ | async) === 'loading'"></i>
</button>
<div>Last updated:</div>
<div class="refresh-timestamp">{{ loadedTimestamp$ | async }}</div>
</div>
<div *ngIf="(currentUser$ | async)?.systemPermissions?.canRead">
<a (click)="openSystemDiagnostics()">System Diagnostics</a>
</div>
</div>
</div>
<process-group-status-table
[processGroups]="(processGroupStatusSnapshots$ | async)!"
[selectedProcessGroupId]="selectedProcessGroupId$ | async"
[rootProcessGroup]="(processGroupStatus$ | async)?.processGroupStatus?.aggregateSnapshot!"
[loadedTimestamp]="loadedTimestamp$ | async"
[summaryListingStatus]="summaryListingStatus$ | async"
(viewStatusHistory)="viewStatusHistory($event)"
(selectProcessGroup)="selectProcessGroup($event)"
(clearSelection)="clearSelection()"
(refresh)="refreshSummaryListing()"
initialSortColumn="name"
initialSortDirection="asc"></process-group-status-table>
</ng-template>
</ng-container>

View File

@ -18,32 +18,22 @@
import { Component } from '@angular/core';
import { initialState } from '../../state/summary-listing/summary-listing.reducer';
import * as SummaryListingActions from '../../state/summary-listing/summary-listing.actions';
import {
ProcessGroupStatusSnapshotEntity,
ProcessorStatusSnapshotEntity,
SummaryListingState
} from '../../state/summary-listing';
import { ProcessGroupStatusSnapshotEntity, SummaryListingState } from '../../state/summary-listing';
import { Store } from '@ngrx/store';
import {
selectProcessGroupIdFromRoute,
selectProcessGroupStatus,
selectProcessGroupStatusItem,
selectProcessGroupStatusSnapshots,
selectProcessorStatus,
selectProcessorStatusSnapshots,
selectSummaryListingLoadedTimestamp,
selectSummaryListingStatus,
selectViewStatusHistory
} from '../../state/summary-listing/summary-listing.selectors';
import { filter, switchMap, take } from 'rxjs';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import {
getStatusHistoryAndOpenDialog,
openStatusHistoryDialog
} from '../../../../state/status-history/status-history.actions';
import { getStatusHistoryAndOpenDialog } from '../../../../state/status-history/status-history.actions';
import { ComponentType } from '../../../../state/shared';
import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors';
import { getSystemDiagnosticsAndOpenDialog } from '../../../../state/system-diagnostics/system-diagnostics.actions';
@Component({
selector: 'process-group-status-listing',
@ -112,13 +102,7 @@ export class ProcessGroupStatusListing {
);
}
openSystemDiagnostics() {
this.store.dispatch(
getSystemDiagnosticsAndOpenDialog({
request: {
nodewise: false
}
})
);
clearSelection() {
this.store.dispatch(SummaryListingActions.clearProcessGroupStatusSelection());
}
}

View File

@ -14,300 +14,363 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<div class="process-group-status-table h-full flex flex-col">
<!-- allow filtering of the table -->
<summary-table-filter
[filteredCount]="filteredCount"
[totalCount]="totalCount"
[filterableColumns]="filterableColumns"
[includeStatusFilter]="false"
[includePrimaryNodeOnlyFilter]="false"
(filterChanged)="applyFilter($event)"></summary-table-filter>
<div class="flex flex-col h-full gap-y-2">
<div class="flex-1">
<ng-container>
<div class="process-group-status-table h-full flex flex-col">
<!-- allow filtering of the table -->
<summary-table-filter
[filteredCount]="filteredCount"
[totalCount]="totalCount"
[filterableColumns]="filterableColumns"
[includeStatusFilter]="false"
[includePrimaryNodeOnlyFilter]="false"
(filterChanged)="applyFilter($event)"></summary-table-filter>
<div class="flex-1 relative">
<div class="listing-table overflow-y-auto border absolute inset-0">
<table
mat-table
[dataSource]="dataSource"
matSort
matSortDisableClear
(matSortChange)="sortData($event)"
[matSortActive]="initialSortColumn"
[matSortDirection]="initialSortDirection">
<!-- More Details Column -->
<ng-container matColumnDef="moreDetails">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let item">
<ng-container *ngIf="canRead(item)">
<div class="flex items-center gap-x-3">
<!-- TODO - handle read only in configure component? -->
<div
class="pointer fa fa-info-circle"
*ngIf="canRead(item)"
title="View Process Group Details"></div>
</div>
</ng-container>
</td>
</ng-container>
<div class="flex-1 relative">
<div class="listing-table overflow-y-auto border absolute inset-0">
<table
mat-table
[dataSource]="dataSource"
matSort
matSortDisableClear
(matSortChange)="sortData($event)"
[matSortActive]="initialSortColumn"
[matSortDirection]="initialSortDirection">
<!-- More Details Column -->
<ng-container matColumnDef="moreDetails">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let item">
<ng-container *ngIf="canRead(item)">
<div class="flex items-center gap-x-3">
<!-- TODO - handle read only in configure component? -->
<div
class="pointer fa fa-info-circle"
*ngIf="canRead(item)"
title="View Process Group Details"></div>
</div>
</ng-container>
</td>
</ng-container>
<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
<div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap">Name</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatName(item)">
<div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap">
{{ formatName(item) }}
</div>
</td>
</ng-container>
<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
<div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap">Name</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatName(item)">
<div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap">
{{ formatName(item) }}
</div>
</td>
</ng-container>
<!-- Version State column -->
<ng-container matColumnDef="versionedFlowState">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
<div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap">Version State</div>
</th>
<td mat-cell *matCellDef="let item">
<div class="flex items-center gap-x-1.5" [title]="formatVersionedFlowState(item)">
<div [ngClass]="getVersionedFlowStateIcon(item)"></div>
<div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap min-w-0">
{{ formatVersionedFlowState(item) }}
</div>
</div>
</td>
</ng-container>
<!-- Version State column -->
<ng-container matColumnDef="versionedFlowState">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
<div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap">
Version State
</div>
</th>
<td mat-cell *matCellDef="let item">
<div class="flex items-center gap-x-1.5" [title]="formatVersionedFlowState(item)">
<div [ngClass]="getVersionedFlowStateIcon(item)"></div>
<div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap min-w-0">
{{ formatVersionedFlowState(item) }}
</div>
</div>
</td>
</ng-container>
<!-- Transferred column -->
<ng-container matColumnDef="transferred">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
<div
class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1"
title="Count / data size transferred to and from connections in the last 5 min">
<span
[ngClass]="{
underline: multiSort.active === 'transferred' && multiSort.sortValueIndex === 0
}"
>Transferred</span
>
<span
[ngClass]="{
underline: multiSort.active === 'transferred' && multiSort.sortValueIndex === 1
}"
>(Size)</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatTransferred(item)">
{{ formatTransferred(item) }}
</td>
</ng-container>
<!-- Transferred column -->
<ng-container matColumnDef="transferred">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
<div
class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1"
title="Count / data size transferred to and from connections in the last 5 min">
<span
[ngClass]="{
underline:
multiSort.active === 'transferred' && multiSort.sortValueIndex === 0
}"
>Transferred</span
>
<span
[ngClass]="{
underline:
multiSort.active === 'transferred' && multiSort.sortValueIndex === 1
}"
>(Size)</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatTransferred(item)">
{{ formatTransferred(item) }}
</td>
</ng-container>
<!-- Input column -->
<ng-container matColumnDef="in">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Count / data size in the last 5 minutes">
<div class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span [ngClass]="{ underline: multiSort.active === 'in' && multiSort.sortValueIndex === 0 }"
>In</span
>
<span [ngClass]="{ underline: multiSort.active === 'in' && multiSort.sortValueIndex === 1 }"
>(Size)</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatIn(item)">
{{ formatIn(item) }}
</td>
</ng-container>
<!-- Input column -->
<ng-container matColumnDef="in">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Count / data size in the last 5 minutes">
<div
class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span
[ngClass]="{
underline: multiSort.active === 'in' && multiSort.sortValueIndex === 0
}"
>In</span
>
<span
[ngClass]="{
underline: multiSort.active === 'in' && multiSort.sortValueIndex === 1
}"
>(Size)</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatIn(item)">
{{ formatIn(item) }}
</td>
</ng-container>
<!-- Read Write column -->
<ng-container matColumnDef="readWrite">
<th mat-header-cell *matHeaderCellDef mat-sort-header title="Data size in the last 5 minutes">
<div class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span
[ngClass]="{
underline: multiSort.active === 'readWrite' && multiSort.sortValueIndex === 0
}"
>Read</span
>
<span>|</span>
<span
[ngClass]="{
underline: multiSort.active === 'readWrite' && multiSort.sortValueIndex === 1
}"
>Write</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatReadWrite(item)">
{{ formatReadWrite(item) }}
</td>
</ng-container>
<!-- Read Write column -->
<ng-container matColumnDef="readWrite">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Data size in the last 5 minutes">
<div
class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span
[ngClass]="{
underline:
multiSort.active === 'readWrite' && multiSort.sortValueIndex === 0
}"
>Read</span
>
<span>|</span>
<span
[ngClass]="{
underline:
multiSort.active === 'readWrite' && multiSort.sortValueIndex === 1
}"
>Write</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatReadWrite(item)">
{{ formatReadWrite(item) }}
</td>
</ng-container>
<!-- Output column -->
<ng-container matColumnDef="out">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Count / data size in the last 5 minutes">
<div class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span
[ngClass]="{ underline: multiSort.active === 'out' && multiSort.sortValueIndex === 0 }"
>Out</span
>
<span
[ngClass]="{ underline: multiSort.active === 'out' && multiSort.sortValueIndex === 1 }"
>(Size)</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatOut(item)">
{{ formatOut(item) }}
</td>
</ng-container>
<!-- Output column -->
<ng-container matColumnDef="out">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Count / data size in the last 5 minutes">
<div
class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span
[ngClass]="{
underline: multiSort.active === 'out' && multiSort.sortValueIndex === 0
}"
>Out</span
>
<span
[ngClass]="{
underline: multiSort.active === 'out' && multiSort.sortValueIndex === 1
}"
>(Size)</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatOut(item)">
{{ formatOut(item) }}
</td>
</ng-container>
<!-- Sent column -->
<ng-container matColumnDef="sent">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Count / data size in the last 5 minutes">
<div class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span
[ngClass]="{ underline: multiSort.active === 'sent' && multiSort.sortValueIndex === 0 }"
>Sent</span
>
<span
[ngClass]="{ underline: multiSort.active === 'sent' && multiSort.sortValueIndex === 1 }"
>(Size)</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatSent(item)">
{{ formatSent(item) }}
</td>
</ng-container>
<!-- Sent column -->
<ng-container matColumnDef="sent">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Count / data size in the last 5 minutes">
<div
class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span
[ngClass]="{
underline: multiSort.active === 'sent' && multiSort.sortValueIndex === 0
}"
>Sent</span
>
<span
[ngClass]="{
underline: multiSort.active === 'sent' && multiSort.sortValueIndex === 1
}"
>(Size)</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatSent(item)">
{{ formatSent(item) }}
</td>
</ng-container>
<!-- Received column -->
<ng-container matColumnDef="received">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Count / data size in the last 5 minutes">
<div class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span
[ngClass]="{
underline: multiSort.active === 'received' && multiSort.sortValueIndex === 0
}"
>Received</span
>
<span
[ngClass]="{
underline: multiSort.active === 'received' && multiSort.sortValueIndex === 1
}"
>(Size)</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatReceived(item)">
{{ formatReceived(item) }}
</td>
</ng-container>
<!-- Received column -->
<ng-container matColumnDef="received">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Count / data size in the last 5 minutes">
<div
class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span
[ngClass]="{
underline:
multiSort.active === 'received' && multiSort.sortValueIndex === 0
}"
>Received</span
>
<span
[ngClass]="{
underline:
multiSort.active === 'received' && multiSort.sortValueIndex === 1
}"
>(Size)</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatReceived(item)">
{{ formatReceived(item) }}
</td>
</ng-container>
<!-- Received column -->
<ng-container matColumnDef="activeThreads">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Total active thread count within ProcessGroup (% of total active thread count compared to overall active thread count in root ProcessGroup) in the last 5 min">
<div class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span
[ngClass]="{
underline: multiSort.active === 'activeThreads' && multiSort.sortValueIndex === 0
}"
>Active Threads</span
>
<span
[ngClass]="{
underline: multiSort.active === 'activeThreads' && multiSort.sortValueIndex === 1
}"
>(%)</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatActiveThreads(item)">
{{ formatActiveThreads(item) }}
</td>
</ng-container>
<!-- Received column -->
<ng-container matColumnDef="activeThreads">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Total active thread count within ProcessGroup (% of total active thread count compared to overall active thread count in root ProcessGroup) in the last 5 min">
<div
class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span
[ngClass]="{
underline:
multiSort.active === 'activeThreads' &&
multiSort.sortValueIndex === 0
}"
>Active Threads</span
>
<span
[ngClass]="{
underline:
multiSort.active === 'activeThreads' &&
multiSort.sortValueIndex === 1
}"
>(%)</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatActiveThreads(item)">
{{ formatActiveThreads(item) }}
</td>
</ng-container>
<!-- Tasks column -->
<ng-container matColumnDef="tasks">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Total task duration within ProcessGroup (% of total task duration compared to overall task duration in root ProcessGroup) in the last 5 min">
<div class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span
[ngClass]="{
underline: multiSort.active === 'tasks' && multiSort.sortValueIndex === 0
}"
>Total Task Duration</span
>
<span
[ngClass]="{
underline: multiSort.active === 'tasks' && multiSort.sortValueIndex === 1
}"
>(%)</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatTasks(item)">
{{ formatTasks(item) }}
</td>
</ng-container>
<!-- Tasks column -->
<ng-container matColumnDef="tasks">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Total task duration within ProcessGroup (% of total task duration compared to overall task duration in root ProcessGroup) in the last 5 min">
<div
class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span
[ngClass]="{
underline:
multiSort.active === 'tasks' && multiSort.sortValueIndex === 0
}"
>Total Task Duration</span
>
<span
[ngClass]="{
underline:
multiSort.active === 'tasks' && multiSort.sortValueIndex === 1
}"
>(%)</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatTasks(item)">
{{ formatTasks(item) }}
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let item">
<div class="flex items-center gap-x-3">
<div
class="pointer fa fa-long-arrow-right"
[routerLink]="getProcessGroupLink(item)"
(click)="$event.stopPropagation()"
title="Go to Process Group {{ item?.processGroupStatusSnapshot?.name }}"></div>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let item">
<div class="flex items-center gap-x-3">
<div
class="pointer fa fa-long-arrow-right"
[routerLink]="getProcessGroupLink(item)"
(click)="$event.stopPropagation()"
title="Go to Process Group {{
item?.processGroupStatusSnapshot?.name
}}"></div>
<div
class="pointer fa fa-area-chart"
title="View Status History"
(click)="viewStatusHistoryClicked($event, item)"></div>
</div>
</td>
</ng-container>
<div
class="pointer fa fa-area-chart"
title="View Status History"
(click)="viewStatusHistoryClicked($event, item)"></div>
</div>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
<tr
mat-row
*matRowDef="let row; let even = even; columns: displayedColumns"
[class.even]="even"
(click)="select(row)"
[class.selected]="isSelected(row)"></tr>
</table>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
<tr
mat-row
*matRowDef="let row; let even = even; columns: displayedColumns"
[class.even]="even"
(click)="select(row)"
[class.selected]="isSelected(row)"></tr>
</table>
</div>
</div>
</div>
</ng-container>
</div>
<div class="flex justify-between align-middle">
<div class="refresh-container flex items-center gap-x-2">
<button class="nifi-button" (click)="refresh.next()">
<i class="fa fa-refresh" [class.fa-spin]="summaryListingStatus === 'loading'"></i>
</button>
<div>Last updated:</div>
<div class="refresh-timestamp">{{ loadedTimestamp }}</div>
</div>
<div>
<mat-paginator
[pageSize]="100"
[hidePageSize]="true"
[showFirstLastButtons]="true"
(page)="paginationChanged()"></mat-paginator>
</div>
</div>
</div>

View File

@ -15,25 +15,20 @@
* limitations under the License.
*/
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { AfterViewInit, Component, EventEmitter, Input, Output, ViewChild } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatSortModule, Sort, SortDirection } from '@angular/material/sort';
import { MultiSort } from '../../common';
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { SummaryTableFilterModule } from '../../common/summary-table-filter/summary-table-filter.module';
import {
ProcessGroupStatusSnapshot,
ProcessGroupStatusSnapshotEntity,
ProcessorStatusSnapshot,
ProcessorStatusSnapshotEntity,
VersionedFlowState
} from '../../../state/summary-listing';
import { ProcessGroupStatusSnapshot, ProcessGroupStatusSnapshotEntity } from '../../../state/summary-listing';
import {
SummaryTableFilterArgs,
SummaryTableFilterColumn
} from '../../common/summary-table-filter/summary-table-filter.component';
import { NiFiCommon } from '../../../../../service/nifi-common.service';
import { RouterLink } from '@angular/router';
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
export type SupportedColumns =
| 'name'
@ -50,17 +45,17 @@ export type SupportedColumns =
@Component({
selector: 'process-group-status-table',
standalone: true,
imports: [CommonModule, MatSortModule, MatTableModule, SummaryTableFilterModule, RouterLink],
imports: [CommonModule, MatSortModule, MatTableModule, SummaryTableFilterModule, RouterLink, MatPaginatorModule],
templateUrl: './process-group-status-table.component.html',
styleUrls: ['./process-group-status-table.component.scss', '../../../../../../assets/styles/listing-table.scss']
})
export class ProcessGroupStatusTable {
export class ProcessGroupStatusTable implements AfterViewInit {
private _initialSortColumn: SupportedColumns = 'name';
private _initialSortDirection: SortDirection = 'asc';
filterableColumns: SummaryTableFilterColumn[] = [{ key: 'name', label: 'name' }];
totalCount: number = 0;
filteredCount: number = 0;
totalCount = 0;
filteredCount = 0;
multiSort: MultiSort = {
active: this._initialSortColumn,
@ -92,6 +87,8 @@ export class ProcessGroupStatusTable {
applyFilter(filter: SummaryTableFilterArgs) {
this.dataSource.filter = JSON.stringify(filter);
this.filteredCount = this.dataSource.filteredData.length;
this.resetPaginator();
this.selectNone();
}
@Input() selectedProcessGroupId!: string;
@ -138,10 +135,32 @@ export class ProcessGroupStatusTable {
}
}
@Input() summaryListingStatus: string | null = null;
@Input() loadedTimestamp: string | null = null;
@Output() viewStatusHistory: EventEmitter<ProcessGroupStatusSnapshotEntity> =
new EventEmitter<ProcessGroupStatusSnapshotEntity>();
@Output() selectProcessGroup: EventEmitter<ProcessGroupStatusSnapshotEntity> =
new EventEmitter<ProcessGroupStatusSnapshotEntity>();
@Output() clearSelection: EventEmitter<void> = new EventEmitter<void>();
@Output() refresh: EventEmitter<void> = new EventEmitter<void>();
@ViewChild(MatPaginator) paginator!: MatPaginator;
ngAfterViewInit(): void {
this.dataSource.paginator = this.paginator;
}
resetPaginator(): void {
if (this.dataSource.paginator) {
this.dataSource.paginator.firstPage();
}
}
paginationChanged(): void {
// clear out any selection
this.selectNone();
}
formatName(pg: ProcessGroupStatusSnapshotEntity): string {
return pg.processGroupStatusSnapshot.name;
@ -169,6 +188,7 @@ export class ProcessGroupStatusTable {
label: 'Sync failure'
}
};
formatVersionedFlowState(pg: ProcessGroupStatusSnapshotEntity): string {
if (!pg.processGroupStatusSnapshot.versionedFlowState) {
return '';
@ -280,8 +300,8 @@ export class ProcessGroupStatusTable {
return [];
}
let aggregateDuration: number = 0;
let aggregateActiveThreads: number = 0;
let aggregateDuration = 0;
let aggregateActiveThreads = 0;
if (this.rootProcessGroup) {
aggregateDuration = this.rootProcessGroup.processingNanos;
aggregateActiveThreads = this.rootProcessGroup.activeThreadCount;
@ -289,7 +309,7 @@ export class ProcessGroupStatusTable {
return data.slice().sort((a, b) => {
const isAsc = sort.direction === 'asc';
let retVal: number = 0;
let retVal = 0;
switch (sort.active) {
case 'name':
retVal = this.nifiCommon.compareString(this.formatName(a), this.formatName(b));
@ -422,6 +442,7 @@ export class ProcessGroupStatusTable {
select(pg: ProcessGroupStatusSnapshotEntity): void {
this.selectProcessGroup.next(pg);
}
isSelected(pg: ProcessGroupStatusSnapshotEntity): boolean {
if (this.selectedProcessGroupId) {
return pg.id === this.selectedProcessGroupId;
@ -433,4 +454,8 @@ export class ProcessGroupStatusTable {
event.stopPropagation();
this.viewStatusHistory.next(pg);
}
private selectNone() {
this.clearSelection.next();
}
}

View File

@ -20,30 +20,16 @@
</div>
<ng-template #loaded>
<div class="flex flex-col h-full gap-y-2">
<div class="flex-1" *ngIf="currentUser$ | async as user">
<ng-container>
<processor-status-table
[processors]="(processorStatusSnapshots$ | async)!"
[selectedProcessorId]="selectedProcessorId$ | async"
(viewStatusHistory)="viewStatusHistory($event)"
(selectProcessor)="selectProcessor($event)"
initialSortColumn="name"
initialSortDirection="asc"></processor-status-table>
</ng-container>
</div>
<div class="flex justify-between">
<div class="refresh-container flex items-center gap-x-2">
<button class="nifi-button" (click)="refreshSummaryListing()">
<i class="fa fa-refresh" [class.fa-spin]="(summaryListingStatus$ | async) === 'loading'"></i>
</button>
<div>Last updated:</div>
<div class="refresh-timestamp">{{ loadedTimestamp$ | async }}</div>
</div>
<div *ngIf="(currentUser$ | async)?.systemPermissions?.canRead">
<a (click)="openSystemDiagnostics()">System Diagnostics</a>
</div>
</div>
</div>
<processor-status-table
[processors]="(processorStatusSnapshots$ | async)!"
[selectedProcessorId]="selectedProcessorId$ | async"
[loadedTimestamp]="loadedTimestamp$ | async"
[summaryListingStatus]="summaryListingStatus$ | async"
(viewStatusHistory)="viewStatusHistory($event)"
(selectProcessor)="selectProcessor($event)"
(clearSelection)="clearSelection()"
(refresh)="refreshSummaryListing()"
initialSortColumn="name"
initialSortDirection="asc"></processor-status-table>
</ng-template>
</ng-container>

View File

@ -15,7 +15,7 @@
* limitations under the License.
*/
import { Component } from '@angular/core';
import { AfterViewInit, Component, ViewChild } from '@angular/core';
import { Store } from '@ngrx/store';
import {
selectProcessorIdFromRoute,
@ -28,14 +28,13 @@ import {
import { ProcessorStatusSnapshotEntity, SummaryListingState } from '../../state/summary-listing';
import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors';
import { initialState } from '../../state/summary-listing/summary-listing.reducer';
import {
getStatusHistoryAndOpenDialog,
openStatusHistoryDialog
} from '../../../../state/status-history/status-history.actions';
import { getStatusHistoryAndOpenDialog } from '../../../../state/status-history/status-history.actions';
import { ComponentType } from '../../../../state/shared';
import { filter, switchMap, take } from 'rxjs';
import { combineLatest, delay, filter, Subject, switchMap, take } from 'rxjs';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import * as SummaryListingActions from '../../state/summary-listing/summary-listing.actions';
import { MatPaginator } from '@angular/material/paginator';
import { ProcessorStatusTable } from './processor-status-table/processor-status-table.component';
import { getSystemDiagnosticsAndOpenDialog } from '../../../../state/system-diagnostics/system-diagnostics.actions';
@Component({
@ -43,7 +42,7 @@ import { getSystemDiagnosticsAndOpenDialog } from '../../../../state/system-diag
templateUrl: './processor-status-listing.component.html',
styleUrls: ['./processor-status-listing.component.scss']
})
export class ProcessorStatusListing {
export class ProcessorStatusListing implements AfterViewInit {
processorStatusSnapshots$ = this.store.select(selectProcessorStatusSnapshots);
loadedTimestamp$ = this.store.select(selectSummaryListingLoadedTimestamp);
summaryListingStatus$ = this.store.select(selectSummaryListingStatus);
@ -51,6 +50,10 @@ export class ProcessorStatusListing {
currentUser$ = this.store.select(selectCurrentUser);
@ViewChild(MatPaginator) paginator!: MatPaginator;
@ViewChild(ProcessorStatusTable) table!: ProcessorStatusTable;
private subject: Subject<void> = new Subject<void>();
constructor(private store: Store<SummaryListingState>) {
this.store
.select(selectViewStatusHistory)
@ -79,6 +82,23 @@ export class ProcessorStatusListing {
});
}
ngAfterViewInit(): void {
combineLatest([this.processorStatusSnapshots$, this.loadedTimestamp$])
.pipe(
filter(([processors, ts]) => !!processors && !this.isInitialLoading(ts)),
delay(0)
)
.subscribe(() => {
this.subject.next();
});
this.subject.subscribe(() => {
if (this.table) {
this.table.paginator = this.paginator;
}
});
}
isInitialLoading(loadedTimestamp: string): boolean {
return loadedTimestamp == initialState.loadedTimestamp;
}
@ -105,13 +125,7 @@ export class ProcessorStatusListing {
);
}
openSystemDiagnostics() {
this.store.dispatch(
getSystemDiagnosticsAndOpenDialog({
request: {
nodewise: false
}
})
);
clearSelection() {
this.store.dispatch(SummaryListingActions.clearProcessorStatusSelection());
}
}

View File

@ -31,6 +31,9 @@ import { RouterLink } from '@angular/router';
import { NifiTooltipDirective } from '../../../../ui/common/tooltips/nifi-tooltip.directive';
import { SummaryTableFilterModule } from '../common/summary-table-filter/summary-table-filter.module';
import { ProcessorStatusTable } from './processor-status-table/processor-status-table.component';
import { MatPaginatorModule } from '@angular/material/paginator';
import { ProcessGroupStatusTable } from '../process-group-status-listing/process-group-status-table/process-group-status-table.component';
import { RemoteProcessGroupStatusTable } from '../remote-process-group-status-listing/remote-process-group-status-table/remote-process-group-status-table.component';
@NgModule({
declarations: [ProcessorStatusListing],
@ -49,7 +52,10 @@ import { ProcessorStatusTable } from './processor-status-table/processor-status-
RouterLink,
NifiTooltipDirective,
SummaryTableFilterModule,
ProcessorStatusTable
ProcessorStatusTable,
MatPaginatorModule,
ProcessGroupStatusTable,
RemoteProcessGroupStatusTable
]
})
export class ProcessorStatusListingModule {}

View File

@ -14,224 +14,271 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<div class="processor-status-table h-full flex flex-col">
<!-- allow filtering of the table -->
<summary-table-filter
[filteredCount]="filteredCount"
[totalCount]="totalCount"
[filterableColumns]="filterableColumns"
[includeStatusFilter]="true"
[includePrimaryNodeOnlyFilter]="true"
(filterChanged)="applyFilter($event)"></summary-table-filter>
<div class="flex flex-col h-full gap-y-2">
<div class="flex-1">
<ng-container>
<div class="processor-status-table h-full flex flex-col">
<!-- allow filtering of the table -->
<summary-table-filter
[filteredCount]="filteredCount"
[totalCount]="totalCount"
[filterableColumns]="filterableColumns"
[includeStatusFilter]="true"
[includePrimaryNodeOnlyFilter]="true"
(filterChanged)="applyFilter($event)"></summary-table-filter>
<div class="flex-1 relative">
<div class="listing-table overflow-y-auto border absolute inset-0">
<table
mat-table
[dataSource]="dataSource"
matSort
matSortDisableClear
(matSortChange)="sortData($event)"
[matSortActive]="initialSortColumn"
[matSortDirection]="initialSortDirection">
<!-- More Details Column -->
<ng-container matColumnDef="moreDetails">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let item">
<ng-container *ngIf="canRead(item)">
<div class="flex items-center gap-x-3">
<!-- TODO - handle read only in configure component? -->
<div
class="pointer fa fa-info-circle"
*ngIf="canRead(item)"
title="View Processor Details"></div>
</div>
</ng-container>
</td>
</ng-container>
<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
<div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap">Name</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatName(item)">
{{ formatName(item) }}
</td>
</ng-container>
<!-- Type column -->
<ng-container matColumnDef="type">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
<div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap">Type</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatType(item)">
{{ formatType(item) }}
</td>
</ng-container>
<!-- Process Group column -->
<ng-container matColumnDef="processGroup">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
<div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap">Process Group</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatProcessGroup(item)">
{{ formatProcessGroup(item) }}
</td>
</ng-container>
<!-- Run Status column -->
<ng-container matColumnDef="runStatus">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Run Status</th>
<td mat-cell *matCellDef="let item">
<div class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1.5">
<span [ngClass]="getRunStatusIcon(item)"></span>
<span [title]="formatRunStatus(item)">{{ formatRunStatus(item) }}</span>
<ng-container *ngIf="item.processorStatusSnapshot as pg">
<span
*ngIf="pg.terminatedThreadCount > 0; else activeThreads"
title="Threads: (Active / Terminated)"
>({{ pg.activeThreadCount }}/{{ pg.terminatedThreadCount }})</span
>
<ng-template #activeThreads>
<span *ngIf="pg.activeThreadCount > 0" title="Active Threads"
>({{ pg.activeThreadCount }})</span
>
</ng-template>
<div class="flex-1 relative">
<div class="listing-table overflow-y-auto border absolute inset-0">
<table
mat-table
[dataSource]="dataSource"
matSort
matSortDisableClear
(matSortChange)="sortData($event)"
[matSortActive]="initialSortColumn"
[matSortDirection]="initialSortDirection">
<!-- More Details Column -->
<ng-container matColumnDef="moreDetails">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let item">
<ng-container *ngIf="canRead(item)">
<div class="flex items-center gap-x-3">
<!-- TODO - handle read only in configure component? -->
<div
class="pointer fa fa-info-circle"
*ngIf="canRead(item)"
title="View Processor Details"></div>
</div>
</ng-container>
</td>
</ng-container>
</div>
</td>
</ng-container>
<!-- Input column -->
<ng-container matColumnDef="in">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Count / data size in the last 5 minutes">
<div class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span [ngClass]="{ underline: multiSort.active === 'in' && multiSort.sortValueIndex === 0 }"
>In</span
>
<span [ngClass]="{ underline: multiSort.active === 'in' && multiSort.sortValueIndex === 1 }"
>(Size)</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatIn(item)">
{{ formatIn(item) }}
</td>
</ng-container>
<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
<div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap">Name</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatName(item)">
{{ formatName(item) }}
</td>
</ng-container>
<!-- Read Write column -->
<ng-container matColumnDef="readWrite">
<th mat-header-cell *matHeaderCellDef mat-sort-header title="Data size in the last 5 minutes">
<div class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span
[ngClass]="{
underline: multiSort.active === 'readWrite' && multiSort.sortValueIndex === 0
}"
>Read</span
>
<span>|</span>
<span
[ngClass]="{
underline: multiSort.active === 'readWrite' && multiSort.sortValueIndex === 1
}"
>Write</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatReadWrite(item)">
{{ formatReadWrite(item) }}
</td>
</ng-container>
<!-- Type column -->
<ng-container matColumnDef="type">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
<div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap">Type</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatType(item)">
{{ formatType(item) }}
</td>
</ng-container>
<!-- Output column -->
<ng-container matColumnDef="out">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Count / data size in the last 5 minutes">
<div class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span
[ngClass]="{ underline: multiSort.active === 'out' && multiSort.sortValueIndex === 0 }">
Out
</span>
<span
[ngClass]="{ underline: multiSort.active === 'out' && multiSort.sortValueIndex === 1 }">
(Size)
</span>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatOut(item)">
{{ formatOut(item) }}
</td>
</ng-container>
<!-- Process Group column -->
<ng-container matColumnDef="processGroup">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
<div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap">
Process Group
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatProcessGroup(item)">
{{ formatProcessGroup(item) }}
</td>
</ng-container>
<!-- Tasks column -->
<ng-container matColumnDef="tasks">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Count / duration in the last 5 minutes">
<div class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span
[ngClass]="{
underline: multiSort.active === 'tasks' && multiSort.sortValueIndex === 0
}"
>Tasks</span
>
<span>|</span>
<span
[ngClass]="{
underline: multiSort.active === 'tasks' && multiSort.sortValueIndex === 1
}"
>Time</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatTasks(item)">
{{ formatTasks(item) }}
</td>
</ng-container>
<!-- Run Status column -->
<ng-container matColumnDef="runStatus">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Run Status</th>
<td mat-cell *matCellDef="let item">
<div
class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1.5 align-middle">
<span [ngClass]="getRunStatusIcon(item)"></span>
<span [title]="formatRunStatus(item)">{{ formatRunStatus(item) }}</span>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let item">
<div class="flex items-center gap-x-3">
<div
class="pointer fa fa-long-arrow-right"
[routerLink]="getProcessorLink(item)"
(click)="$event.stopPropagation()"
title="Go to Processor in {{
item?.processorStatusSnapshot?.processGroupNamePath
}}"></div>
<ng-container *ngIf="item.processorStatusSnapshot as pg">
<span
*ngIf="pg.terminatedThreadCount > 0; else activeThreads"
title="Threads: (Active / Terminated)"
>({{ pg.activeThreadCount }}/{{ pg.terminatedThreadCount }})</span
>
<ng-template #activeThreads>
<span *ngIf="pg.activeThreadCount > 0" title="Active Threads"
>({{ pg.activeThreadCount }})</span
>
</ng-template>
</ng-container>
</div>
</td>
</ng-container>
<div
class="pointer fa fa-area-chart"
title="View Status History"
(click)="viewStatusHistoryClicked($event, item)"></div>
</div>
</td>
</ng-container>
<!-- Input column -->
<ng-container matColumnDef="in">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Count / data size in the last 5 minutes">
<div
class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span
[ngClass]="{
underline: multiSort.active === 'in' && multiSort.sortValueIndex === 0
}"
>In</span
>
<span
[ngClass]="{
underline: multiSort.active === 'in' && multiSort.sortValueIndex === 1
}"
>(Size)</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatIn(item)">
{{ formatIn(item) }}
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
<tr
mat-row
*matRowDef="let row; let even = even; columns: displayedColumns"
[class.even]="even"
(click)="select(row)"
[class.selected]="isSelected(row)"></tr>
</table>
<!-- Read Write column -->
<ng-container matColumnDef="readWrite">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Data size in the last 5 minutes">
<div
class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span
[ngClass]="{
underline:
multiSort.active === 'readWrite' && multiSort.sortValueIndex === 0
}"
>Read</span
>
<span>|</span>
<span
[ngClass]="{
underline:
multiSort.active === 'readWrite' && multiSort.sortValueIndex === 1
}"
>Write</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatReadWrite(item)">
{{ formatReadWrite(item) }}
</td>
</ng-container>
<!-- Output column -->
<ng-container matColumnDef="out">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Count / data size in the last 5 minutes">
<div
class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span
[ngClass]="{
underline: multiSort.active === 'out' && multiSort.sortValueIndex === 0
}">
Out
</span>
<span
[ngClass]="{
underline: multiSort.active === 'out' && multiSort.sortValueIndex === 1
}">
(Size)
</span>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatOut(item)">
{{ formatOut(item) }}
</td>
</ng-container>
<!-- Tasks column -->
<ng-container matColumnDef="tasks">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Count / duration in the last 5 minutes">
<div
class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span
[ngClass]="{
underline:
multiSort.active === 'tasks' && multiSort.sortValueIndex === 0
}"
>Tasks</span
>
<span>|</span>
<span
[ngClass]="{
underline:
multiSort.active === 'tasks' && multiSort.sortValueIndex === 1
}"
>Time</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatTasks(item)">
{{ formatTasks(item) }}
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let item">
<div class="flex items-center gap-x-3">
<div
class="pointer fa fa-long-arrow-right"
[routerLink]="getProcessorLink(item)"
(click)="$event.stopPropagation()"
title="Go to Processor in {{
item?.processorStatusSnapshot?.processGroupNamePath
}}"></div>
<div
class="pointer fa fa-area-chart"
title="View Status History"
(click)="viewStatusHistoryClicked($event, item)"></div>
</div>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
<tr
mat-row
*matRowDef="let row; let even = even; columns: displayedColumns"
[class.even]="even"
(click)="select(row)"
[class.selected]="isSelected(row)"></tr>
</table>
</div>
</div>
</div>
</ng-container>
</div>
<div class="flex justify-between align-middle">
<div class="refresh-container flex items-center gap-x-2">
<button class="nifi-button" (click)="refresh.next()">
<i class="fa fa-refresh" [class.fa-spin]="summaryListingStatus === 'loading'"></i>
</button>
<div>Last updated:</div>
<div class="refresh-timestamp">{{ loadedTimestamp }}</div>
</div>
<div>
<mat-paginator
[pageSize]="100"
[hidePageSize]="true"
[showFirstLastButtons]="true"
(page)="paginationChanged()"></mat-paginator>
</div>
</div>
</div>

View File

@ -15,7 +15,7 @@
* limitations under the License.
*/
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { AfterViewInit, Component, EventEmitter, Input, Output, ViewChild } from '@angular/core';
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { ProcessorStatusSnapshot, ProcessorStatusSnapshotEntity } from '../../../state/summary-listing';
import { MatSortModule, Sort, SortDirection } from '@angular/material/sort';
@ -29,6 +29,7 @@ import { NgClass, NgIf } from '@angular/common';
import { ComponentType } from '../../../../../state/shared';
import { MultiSort } from '../../common';
import { NiFiCommon } from '../../../../../service/nifi-common.service';
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
export type SupportedColumns = 'name' | 'type' | 'processGroup' | 'runStatus' | 'in' | 'out' | 'readWrite' | 'tasks';
@ -37,9 +38,9 @@ export type SupportedColumns = 'name' | 'type' | 'processGroup' | 'runStatus' |
templateUrl: './processor-status-table.component.html',
styleUrls: ['./processor-status-table.component.scss', '../../../../../../assets/styles/listing-table.scss'],
standalone: true,
imports: [RouterLink, SummaryTableFilterModule, MatTableModule, MatSortModule, NgClass, NgIf]
imports: [RouterLink, SummaryTableFilterModule, MatTableModule, MatSortModule, NgClass, NgIf, MatPaginatorModule]
})
export class ProcessorStatusTable {
export class ProcessorStatusTable implements AfterViewInit {
private _initialSortColumn: SupportedColumns = 'name';
private _initialSortDirection: SortDirection = 'asc';
@ -47,8 +48,8 @@ export class ProcessorStatusTable {
{ key: 'name', label: 'name' },
{ key: 'type', label: 'type' }
];
totalCount: number = 0;
filteredCount: number = 0;
totalCount = 0;
filteredCount = 0;
multiSort: MultiSort = {
active: this._initialSortColumn,
@ -72,11 +73,19 @@ export class ProcessorStatusTable {
dataSource: MatTableDataSource<ProcessorStatusSnapshotEntity> =
new MatTableDataSource<ProcessorStatusSnapshotEntity>();
@ViewChild(MatPaginator) paginator!: MatPaginator;
constructor(private nifiCommon: NiFiCommon) {}
ngAfterViewInit(): void {
this.dataSource.paginator = this.paginator;
}
applyFilter(filter: SummaryTableFilterArgs) {
this.dataSource.filter = JSON.stringify(filter);
this.filteredCount = this.dataSource.filteredData.length;
this.resetPaginator();
this.selectNone();
}
@Input() selectedProcessorId!: string;
@ -131,10 +140,26 @@ export class ProcessorStatusTable {
}
}
@Input() summaryListingStatus: string | null = null;
@Input() loadedTimestamp: string | null = null;
@Output() refresh: EventEmitter<void> = new EventEmitter<void>();
@Output() viewStatusHistory: EventEmitter<ProcessorStatusSnapshotEntity> =
new EventEmitter<ProcessorStatusSnapshotEntity>();
@Output() selectProcessor: EventEmitter<ProcessorStatusSnapshotEntity> =
new EventEmitter<ProcessorStatusSnapshotEntity>();
@Output() clearSelection: EventEmitter<void> = new EventEmitter<void>();
resetPaginator(): void {
if (this.dataSource.paginator) {
this.dataSource.paginator.firstPage();
}
}
paginationChanged(): void {
// clear out any selection
this.selectNone();
}
formatName(processor: ProcessorStatusSnapshotEntity): string {
return processor.processorStatusSnapshot.name;
@ -318,6 +343,7 @@ export class ProcessorStatusTable {
select(processor: ProcessorStatusSnapshotEntity): void {
this.selectProcessor.next(processor);
}
isSelected(processor: ProcessorStatusSnapshotEntity): boolean {
if (this.selectedProcessorId) {
return processor.id === this.selectedProcessorId;
@ -329,4 +355,8 @@ export class ProcessorStatusTable {
event.stopPropagation();
this.viewStatusHistory.next(processor);
}
private selectNone() {
this.clearSelection.next();
}
}

View File

@ -21,30 +21,16 @@
</div>
<ng-template #loaded>
<div class="flex flex-col h-full gap-y-2">
<div class="flex-1" *ngIf="currentUser$ | async as user">
<ng-container>
<remote-process-group-status-table
[remoteProcessGroups]="(rpgStatusSnapshots$ | async)!"
[selectedRemoteProcessGroupId]="selectedRpgId$ | async"
(selectRemoteProcessGroup)="selectRemoteProcessGroup($event)"
(viewStatusHistory)="viewStatusHistory($event)"
initialSortColumn="name"
initialSortDirection="asc"></remote-process-group-status-table>
</ng-container>
</div>
<div class="flex justify-between">
<div class="refresh-container flex items-center gap-x-2">
<button class="nifi-button" (click)="refreshSummaryListing()">
<i class="fa fa-refresh" [class.fa-spin]="(summaryListingStatus$ | async) === 'loading'"></i>
</button>
<div>Last updated:</div>
<div class="refresh-timestamp">{{ loadedTimestamp$ | async }}</div>
</div>
<div *ngIf="(currentUser$ | async)?.systemPermissions?.canRead">
<a (click)="openSystemDiagnostics()">System Diagnostics</a>
</div>
</div>
</div>
<remote-process-group-status-table
[remoteProcessGroups]="(rpgStatusSnapshots$ | async)!"
[selectedRemoteProcessGroupId]="selectedRpgId$ | async"
[loadedTimestamp]="loadedTimestamp$ | async"
[summaryListingStatus]="summaryListingStatus$ | async"
(selectRemoteProcessGroup)="selectRemoteProcessGroup($event)"
(clearSelection)="clearSelection()"
(viewStatusHistory)="viewStatusHistory($event)"
(refresh)="refreshSummaryListing()"
initialSortColumn="name"
initialSortDirection="asc"></remote-process-group-status-table>
</ng-template>
</ng-container>

View File

@ -29,14 +29,10 @@ import { Store } from '@ngrx/store';
import { RemoteProcessGroupStatusSnapshotEntity, SummaryListingState } from '../../state/summary-listing';
import { filter, switchMap, take } from 'rxjs';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import {
getStatusHistoryAndOpenDialog,
openStatusHistoryDialog
} from '../../../../state/status-history/status-history.actions';
import { getStatusHistoryAndOpenDialog } from '../../../../state/status-history/status-history.actions';
import { ComponentType } from '../../../../state/shared';
import { initialState } from '../../state/summary-listing/summary-listing.reducer';
import * as SummaryListingActions from '../../state/summary-listing/summary-listing.actions';
import { getSystemDiagnosticsAndOpenDialog } from '../../../../state/system-diagnostics/system-diagnostics.actions';
@Component({
selector: 'remote-process-group-status-listing',
@ -96,6 +92,10 @@ export class RemoteProcessGroupStatusListing {
);
}
clearSelection() {
this.store.dispatch(SummaryListingActions.clearRemoteProcessGroupStatusSelection());
}
viewStatusHistory(rpg: RemoteProcessGroupStatusSnapshotEntity): void {
this.store.dispatch(
SummaryListingActions.navigateToViewRemoteProcessGroupStatusHistory({
@ -103,14 +103,4 @@ export class RemoteProcessGroupStatusListing {
})
);
}
openSystemDiagnostics() {
this.store.dispatch(
getSystemDiagnosticsAndOpenDialog({
request: {
nodewise: false
}
})
);
}
}

View File

@ -20,10 +20,11 @@ import { RemoteProcessGroupStatusListing } from './remote-process-group-status-l
import { CommonModule } from '@angular/common';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { RemoteProcessGroupStatusTable } from './remote-process-group-status-table/remote-process-group-status-table.component';
import { ProcessorStatusTable } from '../processor-status-listing/processor-status-table/processor-status-table.component';
@NgModule({
declarations: [RemoteProcessGroupStatusListing],
exports: [RemoteProcessGroupStatusListing],
imports: [CommonModule, NgxSkeletonLoaderModule, RemoteProcessGroupStatusTable]
imports: [CommonModule, NgxSkeletonLoaderModule, RemoteProcessGroupStatusTable, ProcessorStatusTable]
})
export class RemoteProcessGroupStatusListingModule {}

View File

@ -15,144 +15,177 @@
~ limitations under the License.
-->
<div class="remote-process-group-status-table h-full flex flex-col">
<!-- allow filtering of the table -->
<summary-table-filter
[filteredCount]="filteredCount"
[totalCount]="totalCount"
[filterableColumns]="filterableColumns"
[includeStatusFilter]="false"
[includePrimaryNodeOnlyFilter]="false"
(filterChanged)="applyFilter($event)"></summary-table-filter>
<div class="flex flex-col h-full gap-y-2">
<div class="flex-1">
<ng-container>
<div class="remote-process-group-status-table h-full flex flex-col">
<!-- allow filtering of the table -->
<summary-table-filter
[filteredCount]="filteredCount"
[totalCount]="totalCount"
[filterableColumns]="filterableColumns"
[includeStatusFilter]="false"
[includePrimaryNodeOnlyFilter]="false"
(filterChanged)="applyFilter($event)"></summary-table-filter>
<div class="flex-1 relative">
<div class="listing-table overflow-y-auto border absolute inset-0">
<table
mat-table
[dataSource]="dataSource"
matSort
matSortDisableClear
(matSortChange)="sortData($event)"
[matSortActive]="initialSortColumn"
[matSortDirection]="initialSortDirection">
<!-- More Details Column -->
<ng-container matColumnDef="moreDetails">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let item"></td>
</ng-container>
<div class="flex-1 relative">
<div class="listing-table overflow-y-auto border absolute inset-0">
<table
mat-table
[dataSource]="dataSource"
matSort
matSortDisableClear
(matSortChange)="sortData($event)"
[matSortActive]="initialSortColumn"
[matSortDirection]="initialSortDirection">
<!-- More Details Column -->
<ng-container matColumnDef="moreDetails">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let item"></td>
</ng-container>
<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
<div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap">Name</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatName(item)">
{{ formatName(item) }}
</td>
</ng-container>
<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
<div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap">Name</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatName(item)">
{{ formatName(item) }}
</td>
</ng-container>
<!-- Target URI Column -->
<ng-container matColumnDef="uri">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
<div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap">Target URI</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatUri(item)">
{{ formatUri(item) }}
</td>
</ng-container>
<!-- Target URI Column -->
<ng-container matColumnDef="uri">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
<div class="flex-1 overflow-ellipsis overflow-hidden whitespace-nowrap">
Target URI
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatUri(item)">
{{ formatUri(item) }}
</td>
</ng-container>
<!-- Transmission Status column -->
<ng-container matColumnDef="transmitting">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Transmitting</th>
<td mat-cell *matCellDef="let item">
<div class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1.5">
<span [ngClass]="getTransmissionStatusIcon(item)"></span>
<span [title]="formatTransmitting(item)">{{ formatTransmitting(item) }}</span>
<span *ngIf="item.activeThreadCount > 0" title="Active Threads"
>({{ item.activeThreadCount }})</span
>
</div>
</td>
</ng-container>
<!-- Transmission Status column -->
<ng-container matColumnDef="transmitting">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Transmitting</th>
<td mat-cell *matCellDef="let item">
<div
class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1.5 align-middle">
<span [ngClass]="getTransmissionStatusIcon(item)"></span>
<span [title]="formatTransmitting(item)">{{ formatTransmitting(item) }}</span>
<span *ngIf="item.activeThreadCount > 0" title="Active Threads"
>({{ item.activeThreadCount }})</span
>
</div>
</td>
</ng-container>
<!-- Sent column -->
<ng-container matColumnDef="sent">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Count / data size in the last 5 minutes">
<div class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span
[ngClass]="{ underline: multiSort.active === 'sent' && multiSort.sortValueIndex === 0 }"
>Sent</span
>
<span
[ngClass]="{ underline: multiSort.active === 'sent' && multiSort.sortValueIndex === 1 }"
>(Size)</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatSent(item)">
{{ formatSent(item) }}
</td>
</ng-container>
<!-- Sent column -->
<ng-container matColumnDef="sent">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Count / data size in the last 5 minutes">
<div
class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span
[ngClass]="{
underline: multiSort.active === 'sent' && multiSort.sortValueIndex === 0
}"
>Sent</span
>
<span
[ngClass]="{
underline: multiSort.active === 'sent' && multiSort.sortValueIndex === 1
}"
>(Size)</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatSent(item)">
{{ formatSent(item) }}
</td>
</ng-container>
<!-- Received column -->
<ng-container matColumnDef="received">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Count / data size in the last 5 minutes">
<div class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span
[ngClass]="{
underline: multiSort.active === 'received' && multiSort.sortValueIndex === 0
}"
>Received</span
>
<span
[ngClass]="{
underline: multiSort.active === 'received' && multiSort.sortValueIndex === 1
}"
>(Size)</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatReceived(item)">
{{ formatReceived(item) }}
</td>
</ng-container>
<!-- Received column -->
<ng-container matColumnDef="received">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
title="Count / data size in the last 5 minutes">
<div
class="inline-block overflow-hidden overflow-ellipsis whitespace-nowrap space-x-1">
<span
[ngClass]="{
underline:
multiSort.active === 'received' && multiSort.sortValueIndex === 0
}"
>Received</span
>
<span
[ngClass]="{
underline:
multiSort.active === 'received' && multiSort.sortValueIndex === 1
}"
>(Size)</span
>
<span class="font-light">5 min</span>
</div>
</th>
<td mat-cell *matCellDef="let item" [title]="formatReceived(item)">
{{ formatReceived(item) }}
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let item">
<div class="flex items-center gap-x-3">
<div
class="pointer fa fa-long-arrow-right"
[routerLink]="getRemoteProcessGroupLink(item)"
(click)="$event.stopPropagation()"
title="Go to remote process group"></div>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let item">
<div class="flex items-center gap-x-3">
<div
class="pointer fa fa-long-arrow-right"
[routerLink]="getRemoteProcessGroupLink(item)"
(click)="$event.stopPropagation()"
title="Go to remote process group"></div>
<div
class="pointer fa fa-area-chart"
title="View Status History"
(click)="viewStatusHistoryClicked($event, item)"></div>
</div>
</td>
</ng-container>
<div
class="pointer fa fa-area-chart"
title="View Status History"
(click)="viewStatusHistoryClicked($event, item)"></div>
</div>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
<tr
mat-row
*matRowDef="let row; let even = even; columns: displayedColumns"
[class.even]="even"
(click)="select(row)"
[class.selected]="isSelected(row)"></tr>
</table>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
<tr
mat-row
*matRowDef="let row; let even = even; columns: displayedColumns"
[class.even]="even"
(click)="select(row)"
[class.selected]="isSelected(row)"></tr>
</table>
</div>
</div>
</div>
</ng-container>
</div>
<div class="flex justify-between align-middle">
<div class="refresh-container flex items-center gap-x-2">
<button class="nifi-button" (click)="refresh.next()">
<i class="fa fa-refresh" [class.fa-spin]="summaryListingStatus === 'loading'"></i>
</button>
<div>Last updated:</div>
<div class="refresh-timestamp">{{ loadedTimestamp }}</div>
</div>
<div>
<mat-paginator
[pageSize]="100"
[hidePageSize]="true"
[showFirstLastButtons]="true"
(page)="paginationChanged()"></mat-paginator>
</div>
</div>
</div>

View File

@ -15,7 +15,7 @@
* limitations under the License.
*/
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { AfterViewInit, Component, EventEmitter, Input, Output, ViewChild } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SummaryTableFilterModule } from '../../common/summary-table-filter/summary-table-filter.module';
import { MatSortModule, Sort, SortDirection } from '@angular/material/sort';
@ -26,27 +26,27 @@ import {
import { MultiSort } from '../../common';
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import {
PortStatusSnapshot,
RemoteProcessGroupStatusSnapshot,
RemoteProcessGroupStatusSnapshotEntity
} from '../../../state/summary-listing';
import { NiFiCommon } from '../../../../../service/nifi-common.service';
import { ComponentType } from '../../../../../state/shared';
import { RouterLink } from '@angular/router';
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
export type SupportedColumns = 'name' | 'uri' | 'transmitting' | 'sent' | 'received';
@Component({
selector: 'remote-process-group-status-table',
standalone: true,
imports: [CommonModule, SummaryTableFilterModule, MatSortModule, MatTableModule, RouterLink],
imports: [CommonModule, SummaryTableFilterModule, MatSortModule, MatTableModule, RouterLink, MatPaginatorModule],
templateUrl: './remote-process-group-status-table.component.html',
styleUrls: [
'./remote-process-group-status-table.component.scss',
'../../../../../../assets/styles/listing-table.scss'
]
})
export class RemoteProcessGroupStatusTable {
export class RemoteProcessGroupStatusTable implements AfterViewInit {
private _initialSortColumn: SupportedColumns = 'name';
private _initialSortDirection: SortDirection = 'asc';
@ -55,8 +55,8 @@ export class RemoteProcessGroupStatusTable {
{ key: 'targetUri', label: 'uri' }
];
totalCount: number = 0;
filteredCount: number = 0;
totalCount = 0;
filteredCount = 0;
multiSort: MultiSort = {
active: this._initialSortColumn,
@ -70,6 +70,12 @@ export class RemoteProcessGroupStatusTable {
dataSource: MatTableDataSource<RemoteProcessGroupStatusSnapshotEntity> =
new MatTableDataSource<RemoteProcessGroupStatusSnapshotEntity>();
@ViewChild(MatPaginator) paginator!: MatPaginator;
ngAfterViewInit(): void {
this.dataSource.paginator = this.paginator;
}
constructor(private nifiCommon: NiFiCommon) {}
@Input() set initialSortColumn(initialSortColumn: SupportedColumns) {
@ -113,14 +119,32 @@ export class RemoteProcessGroupStatusTable {
}
}
@Input() summaryListingStatus: string | null = null;
@Input() loadedTimestamp: string | null = null;
@Output() refresh: EventEmitter<void> = new EventEmitter<void>();
@Output() viewStatusHistory: EventEmitter<RemoteProcessGroupStatusSnapshotEntity> =
new EventEmitter<RemoteProcessGroupStatusSnapshotEntity>();
@Output() selectRemoteProcessGroup: EventEmitter<RemoteProcessGroupStatusSnapshotEntity> =
new EventEmitter<RemoteProcessGroupStatusSnapshotEntity>();
@Output() clearSelection: EventEmitter<void> = new EventEmitter<void>();
applyFilter(filter: SummaryTableFilterArgs) {
this.dataSource.filter = JSON.stringify(filter);
this.filteredCount = this.dataSource.filteredData.length;
this.resetPaginator();
this.selectNone();
}
resetPaginator(): void {
if (this.dataSource.paginator) {
this.dataSource.paginator.firstPage();
}
}
paginationChanged(): void {
// clear out any selection
this.selectNone();
}
getRemoteProcessGroupLink(rpg: RemoteProcessGroupStatusSnapshotEntity): string[] {
@ -136,6 +160,10 @@ export class RemoteProcessGroupStatusTable {
this.selectRemoteProcessGroup.next(rpg);
}
private selectNone() {
this.clearSelection.next();
}
isSelected(rpg: RemoteProcessGroupStatusSnapshotEntity): boolean {
if (this.selectedRemoteProcessGroupId) {
return rpg.id === this.selectedRemoteProcessGroupId;
@ -231,7 +259,7 @@ export class RemoteProcessGroupStatusTable {
return data.slice().sort((a, b) => {
const isAsc: boolean = sort.direction === 'asc';
let retVal: number = 0;
let retVal = 0;
switch (sort.active) {
case 'name':
retVal = this.nifiCommon.compareString(