mirror of
https://github.com/apache/nifi.git
synced 2025-02-07 18:48:51 +00:00
Nifi 12505 system diagnostics (#8190)
* [NIFI-12505] System Diagnostics * Refactor Status History dialog to use the load and open action pattern. * address review feedback This closes #8190
This commit is contained in:
parent
a73d812c23
commit
e15aecce67
@ -39,6 +39,7 @@ import { AboutEffects } from './state/about/about.effects';
|
||||
import { StatusHistoryEffects } from './state/status-history/status-history.effects';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
import { ControllerServiceStateEffects } from './state/contoller-service-state/controller-service-state.effects';
|
||||
import { SystemDiagnosticsEffects } from './state/system-diagnostics/system-diagnostics.effects';
|
||||
|
||||
// @ts-ignore
|
||||
@NgModule({
|
||||
@ -62,7 +63,8 @@ import { ControllerServiceStateEffects } from './state/contoller-service-state/c
|
||||
ExtensionTypesEffects,
|
||||
AboutEffects,
|
||||
StatusHistoryEffects,
|
||||
ControllerServiceStateEffects
|
||||
ControllerServiceStateEffects,
|
||||
SystemDiagnosticsEffects
|
||||
),
|
||||
StoreDevtoolsModule.instrument({
|
||||
maxAge: 25,
|
||||
|
@ -41,6 +41,9 @@
|
||||
<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>
|
||||
</ng-template>
|
||||
|
@ -36,8 +36,12 @@ import {
|
||||
import { selectUser } from '../../../../state/user/user.selectors';
|
||||
import { filter, switchMap, take } from 'rxjs';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { openStatusHistoryDialog } from '../../../../state/status-history/status-history.actions';
|
||||
import {
|
||||
getStatusHistoryAndOpenDialog,
|
||||
openStatusHistoryDialog
|
||||
} 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',
|
||||
@ -67,7 +71,7 @@ export class ConnectionStatusListing {
|
||||
.subscribe((connection) => {
|
||||
if (connection) {
|
||||
this.store.dispatch(
|
||||
openStatusHistoryDialog({
|
||||
getStatusHistoryAndOpenDialog({
|
||||
request: {
|
||||
source: 'summary',
|
||||
componentType: ComponentType.Connection,
|
||||
@ -104,4 +108,14 @@ export class ConnectionStatusListing {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
openSystemDiagnostics() {
|
||||
this.store.dispatch(
|
||||
getSystemDiagnosticsAndOpenDialog({
|
||||
request: {
|
||||
nodewise: false
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -41,6 +41,9 @@
|
||||
<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>
|
||||
</ng-template>
|
||||
|
@ -27,6 +27,7 @@ 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',
|
||||
@ -59,4 +60,14 @@ export class InputPortStatusListing {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
openSystemDiagnostics() {
|
||||
this.store.dispatch(
|
||||
getSystemDiagnosticsAndOpenDialog({
|
||||
request: {
|
||||
nodewise: false
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -41,6 +41,9 @@
|
||||
<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>
|
||||
</ng-template>
|
||||
|
@ -29,6 +29,7 @@ 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',
|
||||
@ -61,4 +62,14 @@ export class OutputPortStatusListing {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
openSystemDiagnostics() {
|
||||
this.store.dispatch(
|
||||
getSystemDiagnosticsAndOpenDialog({
|
||||
request: {
|
||||
nodewise: false
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,9 @@
|
||||
<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>
|
||||
</ng-template>
|
||||
|
@ -37,9 +37,13 @@ import {
|
||||
} from '../../state/summary-listing/summary-listing.selectors';
|
||||
import { filter, switchMap, take } from 'rxjs';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { openStatusHistoryDialog } from '../../../../state/status-history/status-history.actions';
|
||||
import {
|
||||
getStatusHistoryAndOpenDialog,
|
||||
openStatusHistoryDialog
|
||||
} from '../../../../state/status-history/status-history.actions';
|
||||
import { ComponentType } from '../../../../state/shared';
|
||||
import { selectUser } from '../../../../state/user/user.selectors';
|
||||
import { getSystemDiagnosticsAndOpenDialog } from '../../../../state/system-diagnostics/system-diagnostics.actions';
|
||||
|
||||
@Component({
|
||||
selector: 'process-group-status-listing',
|
||||
@ -70,7 +74,7 @@ export class ProcessGroupStatusListing {
|
||||
.subscribe((pg) => {
|
||||
if (pg) {
|
||||
this.store.dispatch(
|
||||
openStatusHistoryDialog({
|
||||
getStatusHistoryAndOpenDialog({
|
||||
request: {
|
||||
source: 'summary',
|
||||
componentType: ComponentType.ProcessGroup,
|
||||
@ -107,4 +111,14 @@ export class ProcessGroupStatusListing {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
openSystemDiagnostics() {
|
||||
this.store.dispatch(
|
||||
getSystemDiagnosticsAndOpenDialog({
|
||||
request: {
|
||||
nodewise: false
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +40,9 @@
|
||||
<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>
|
||||
</ng-template>
|
||||
|
@ -28,11 +28,15 @@ import {
|
||||
import { ProcessorStatusSnapshotEntity, SummaryListingState } from '../../state/summary-listing';
|
||||
import { selectUser } from '../../../../state/user/user.selectors';
|
||||
import { initialState } from '../../state/summary-listing/summary-listing.reducer';
|
||||
import { openStatusHistoryDialog } from '../../../../state/status-history/status-history.actions';
|
||||
import {
|
||||
getStatusHistoryAndOpenDialog,
|
||||
openStatusHistoryDialog
|
||||
} from '../../../../state/status-history/status-history.actions';
|
||||
import { ComponentType } from '../../../../state/shared';
|
||||
import { filter, switchMap, take } from 'rxjs';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import * as SummaryListingActions from '../../state/summary-listing/summary-listing.actions';
|
||||
import { getSystemDiagnosticsAndOpenDialog } from '../../../../state/system-diagnostics/system-diagnostics.actions';
|
||||
|
||||
@Component({
|
||||
selector: 'processor-status-listing',
|
||||
@ -63,7 +67,7 @@ export class ProcessorStatusListing {
|
||||
.subscribe((processor) => {
|
||||
if (processor) {
|
||||
this.store.dispatch(
|
||||
openStatusHistoryDialog({
|
||||
getStatusHistoryAndOpenDialog({
|
||||
request: {
|
||||
source: 'summary',
|
||||
componentType: ComponentType.Processor,
|
||||
@ -100,4 +104,14 @@ export class ProcessorStatusListing {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
openSystemDiagnostics() {
|
||||
this.store.dispatch(
|
||||
getSystemDiagnosticsAndOpenDialog({
|
||||
request: {
|
||||
nodewise: false
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -41,6 +41,9 @@
|
||||
<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>
|
||||
</ng-template>
|
||||
|
@ -29,10 +29,14 @@ 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 { openStatusHistoryDialog } from '../../../../state/status-history/status-history.actions';
|
||||
import {
|
||||
getStatusHistoryAndOpenDialog,
|
||||
openStatusHistoryDialog
|
||||
} 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',
|
||||
@ -62,7 +66,7 @@ export class RemoteProcessGroupStatusListing {
|
||||
.subscribe((rpg) => {
|
||||
if (rpg) {
|
||||
this.store.dispatch(
|
||||
openStatusHistoryDialog({
|
||||
getStatusHistoryAndOpenDialog({
|
||||
request: {
|
||||
source: 'summary',
|
||||
componentType: ComponentType.RemoteProcessGroup,
|
||||
@ -99,4 +103,14 @@ export class RemoteProcessGroupStatusListing {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
openSystemDiagnostics() {
|
||||
this.store.dispatch(
|
||||
getSystemDiagnosticsAndOpenDialog({
|
||||
request: {
|
||||
nodewise: false
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Client } from './client.service';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class SystemDiagnosticsService {
|
||||
private static readonly API: string = '../nifi-api';
|
||||
|
||||
constructor(
|
||||
private httpClient: HttpClient,
|
||||
private client: Client
|
||||
) {}
|
||||
|
||||
getSystemDiagnostics(nodewise?: boolean) {
|
||||
if (nodewise) {
|
||||
const params = {
|
||||
nodewise: true
|
||||
};
|
||||
return this.httpClient.get(`${SystemDiagnosticsService.API}/system-diagnostics`, { params });
|
||||
}
|
||||
return this.httpClient.get(`${SystemDiagnosticsService.API}/system-diagnostics`);
|
||||
}
|
||||
}
|
@ -27,6 +27,8 @@ import { statusHistoryFeatureKey, StatusHistoryState } from './status-history';
|
||||
import { statusHistoryReducer } from './status-history/status-history.reducer';
|
||||
import { controllerServiceStateFeatureKey, ControllerServiceState } from './contoller-service-state';
|
||||
import { controllerServiceStateReducer } from './contoller-service-state/controller-service-state.reducer';
|
||||
import { systemDiagnosticsFeatureKey, SystemDiagnosticsState } from './system-diagnostics';
|
||||
import { systemDiagnosticsReducer } from './system-diagnostics/system-diagnostics.reducer';
|
||||
|
||||
export interface NiFiState {
|
||||
router: RouterReducerState;
|
||||
@ -35,6 +37,7 @@ export interface NiFiState {
|
||||
[aboutFeatureKey]: AboutState;
|
||||
[statusHistoryFeatureKey]: StatusHistoryState;
|
||||
[controllerServiceStateFeatureKey]: ControllerServiceState;
|
||||
[systemDiagnosticsFeatureKey]: SystemDiagnosticsState;
|
||||
}
|
||||
|
||||
export const rootReducers: ActionReducerMap<NiFiState> = {
|
||||
@ -43,5 +46,6 @@ export const rootReducers: ActionReducerMap<NiFiState> = {
|
||||
[extensionTypesFeatureKey]: extensionTypesReducer,
|
||||
[aboutFeatureKey]: aboutReducer,
|
||||
[statusHistoryFeatureKey]: statusHistoryReducer,
|
||||
[controllerServiceStateFeatureKey]: controllerServiceStateReducer
|
||||
[controllerServiceStateFeatureKey]: controllerServiceStateReducer,
|
||||
[systemDiagnosticsFeatureKey]: systemDiagnosticsReducer
|
||||
};
|
||||
|
@ -20,14 +20,24 @@ import { StatusHistoryRequest, StatusHistoryResponse } from './index';
|
||||
|
||||
const STATUS_HISTORY_PREFIX: string = '[Status History]';
|
||||
|
||||
export const loadStatusHistory = createAction(
|
||||
`${STATUS_HISTORY_PREFIX} Load Status History`,
|
||||
export const reloadStatusHistory = createAction(
|
||||
`${STATUS_HISTORY_PREFIX} Reload Status History`,
|
||||
props<{ request: StatusHistoryRequest }>()
|
||||
);
|
||||
|
||||
export const getStatusHistoryAndOpenDialog = createAction(
|
||||
`${STATUS_HISTORY_PREFIX} Get Status History and Open Dialog`,
|
||||
props<{ request: StatusHistoryRequest }>()
|
||||
);
|
||||
|
||||
export const reloadStatusHistorySuccess = createAction(
|
||||
`${STATUS_HISTORY_PREFIX} Reload Status History Success`,
|
||||
props<{ response: StatusHistoryResponse }>()
|
||||
);
|
||||
|
||||
export const loadStatusHistorySuccess = createAction(
|
||||
`${STATUS_HISTORY_PREFIX} Load Status History Success`,
|
||||
props<{ response: StatusHistoryResponse }>()
|
||||
props<{ request: StatusHistoryRequest; response: StatusHistoryResponse }>()
|
||||
);
|
||||
|
||||
export const openStatusHistoryDialog = createAction(
|
||||
|
@ -36,9 +36,9 @@ export class StatusHistoryEffects {
|
||||
private dialog: MatDialog
|
||||
) {}
|
||||
|
||||
loadStatusHistory$ = createEffect(() =>
|
||||
reloadStatusHistory$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(StatusHistoryActions.loadStatusHistory),
|
||||
ofType(StatusHistoryActions.reloadStatusHistory),
|
||||
map((action) => action.request),
|
||||
switchMap((request: StatusHistoryRequest) =>
|
||||
from(
|
||||
@ -46,7 +46,7 @@ export class StatusHistoryEffects {
|
||||
.getProcessorStatusHistory(request.componentType, request.componentId)
|
||||
.pipe(
|
||||
map((response: any) =>
|
||||
StatusHistoryActions.loadStatusHistorySuccess({
|
||||
StatusHistoryActions.reloadStatusHistorySuccess({
|
||||
response: {
|
||||
statusHistory: {
|
||||
canRead: response.canRead,
|
||||
@ -68,6 +68,47 @@ export class StatusHistoryEffects {
|
||||
)
|
||||
);
|
||||
|
||||
getStatusHistoryAndOpenDialog$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(StatusHistoryActions.getStatusHistoryAndOpenDialog),
|
||||
map((action) => action.request),
|
||||
switchMap((request) =>
|
||||
from(
|
||||
this.statusHistoryService
|
||||
.getProcessorStatusHistory(request.componentType, request.componentId)
|
||||
.pipe(
|
||||
map((response: any) =>
|
||||
StatusHistoryActions.loadStatusHistorySuccess({
|
||||
request,
|
||||
response: {
|
||||
statusHistory: {
|
||||
canRead: response.canRead,
|
||||
statusHistory: response.statusHistory
|
||||
}
|
||||
}
|
||||
})
|
||||
),
|
||||
catchError((error) =>
|
||||
of(
|
||||
StatusHistoryActions.statusHistoryApiError({
|
||||
error: error.error
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
loadStatusHistorySuccess$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(StatusHistoryActions.loadStatusHistorySuccess),
|
||||
map((action) => action.request),
|
||||
switchMap((request) => of(StatusHistoryActions.openStatusHistoryDialog({ request })))
|
||||
)
|
||||
);
|
||||
|
||||
openStatusHistoryDialog$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
|
@ -19,10 +19,12 @@ import { StatusHistoryEntity, StatusHistoryState } from './index';
|
||||
import { createReducer, on } from '@ngrx/store';
|
||||
import {
|
||||
clearStatusHistory,
|
||||
loadStatusHistory,
|
||||
reloadStatusHistory,
|
||||
loadStatusHistorySuccess,
|
||||
statusHistoryApiError,
|
||||
viewStatusHistoryComplete
|
||||
viewStatusHistoryComplete,
|
||||
reloadStatusHistorySuccess,
|
||||
getStatusHistoryAndOpenDialog
|
||||
} from './status-history.actions';
|
||||
import { produce } from 'immer';
|
||||
|
||||
@ -36,12 +38,12 @@ export const initialState: StatusHistoryState = {
|
||||
export const statusHistoryReducer = createReducer(
|
||||
initialState,
|
||||
|
||||
on(loadStatusHistory, (state) => ({
|
||||
on(reloadStatusHistory, getStatusHistoryAndOpenDialog, (state) => ({
|
||||
...state,
|
||||
status: 'loading' as const
|
||||
})),
|
||||
|
||||
on(loadStatusHistorySuccess, (state, { response }) => ({
|
||||
on(loadStatusHistorySuccess, reloadStatusHistorySuccess, (state, { response }) => ({
|
||||
...state,
|
||||
error: null,
|
||||
status: 'success' as const,
|
||||
|
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export const systemDiagnosticsFeatureKey = 'systemDiagnostics';
|
||||
|
||||
export interface SystemDiagnostics {
|
||||
aggregateSnapshot: SystemDiagnosticSnapshot;
|
||||
nodeSnapshots?: NodeSnapshot[];
|
||||
}
|
||||
|
||||
export interface RepositoryStorageUsage {
|
||||
freeSpace: string;
|
||||
freeSpaceBytes: number;
|
||||
totalSpace: string;
|
||||
totalSpaceBytes: number;
|
||||
usedSpace: string;
|
||||
usedSpaceBytes: number;
|
||||
utilization: string;
|
||||
identifier?: string;
|
||||
}
|
||||
export interface GarbageCollection {
|
||||
collectionCount: number;
|
||||
collectionMillis: number;
|
||||
collectionTime: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface VersionInfo {
|
||||
buildBranch: string;
|
||||
buildRevision: string;
|
||||
buildTag: string;
|
||||
buildTimestamp: string;
|
||||
javaVendor: string;
|
||||
javaVersion: string;
|
||||
niFiVersion: string;
|
||||
osArchitecture: string;
|
||||
osName: string;
|
||||
osVersion: string;
|
||||
}
|
||||
|
||||
export interface NodeSnapshot {
|
||||
address: string;
|
||||
apiPort: number;
|
||||
nodeId: string;
|
||||
snapshot: SystemDiagnosticSnapshot;
|
||||
}
|
||||
|
||||
export interface SystemDiagnosticSnapshot {
|
||||
availableProcessors: number;
|
||||
contentRepositoryStorageUsage: RepositoryStorageUsage[];
|
||||
daemonThreads: number;
|
||||
flowFileRepositoryStorageUsage: RepositoryStorageUsage;
|
||||
freeHeap: string;
|
||||
freeHeapBytes: number;
|
||||
freeNonHeap: string;
|
||||
freeNonHeapBytes: number;
|
||||
garbageCollection: GarbageCollection[];
|
||||
heapUtilization: string;
|
||||
maxHeap: string;
|
||||
maxHeapBytes: number;
|
||||
maxNonHeap: string;
|
||||
maxNonHeapBytes: number;
|
||||
processorLoadAverage: number;
|
||||
provenanceRepositoryStorageUsage: RepositoryStorageUsage[];
|
||||
statsLastRefreshed: string;
|
||||
totalHeap: string;
|
||||
totalHeapBytes: number;
|
||||
totalNonHeap: string;
|
||||
totalNonHeapBytes: string;
|
||||
totalThreads: number;
|
||||
uptime: string;
|
||||
usedHeap: string;
|
||||
usedHeapBytes: number;
|
||||
usedNonHeap: string;
|
||||
usedNonHeapBytes: number;
|
||||
versionInfo: VersionInfo;
|
||||
}
|
||||
|
||||
export interface SystemDiagnosticsRequest {
|
||||
nodewise: boolean;
|
||||
}
|
||||
|
||||
export interface OpenSystemDiagnosticsDialogRequest {}
|
||||
|
||||
export interface SystemDiagnosticsResponse {
|
||||
systemDiagnostics: SystemDiagnostics;
|
||||
}
|
||||
|
||||
export interface SystemDiagnosticsState {
|
||||
systemDiagnostics: SystemDiagnostics | null;
|
||||
loadedTimestamp: string;
|
||||
error: string | null;
|
||||
status: 'pending' | 'loading' | 'error' | 'success';
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createAction, props } from '@ngrx/store';
|
||||
import { OpenSystemDiagnosticsDialogRequest, SystemDiagnosticsRequest, SystemDiagnosticsResponse } from './index';
|
||||
|
||||
const SYSTEM_DIAGNOSTICS_PREFIX: string = '[System Diagnostics]';
|
||||
|
||||
export const reloadSystemDiagnostics = createAction(
|
||||
`${SYSTEM_DIAGNOSTICS_PREFIX} Load System Diagnostics`,
|
||||
props<{ request: SystemDiagnosticsRequest }>()
|
||||
);
|
||||
|
||||
export const loadSystemDiagnosticsSuccess = createAction(
|
||||
`${SYSTEM_DIAGNOSTICS_PREFIX} Load System Diagnostics Success`,
|
||||
props<{ response: SystemDiagnosticsResponse }>()
|
||||
);
|
||||
|
||||
export const reloadSystemDiagnosticsSuccess = createAction(
|
||||
`${SYSTEM_DIAGNOSTICS_PREFIX} Reload System Diagnostics Success`,
|
||||
props<{ response: SystemDiagnosticsResponse }>()
|
||||
);
|
||||
|
||||
export const getSystemDiagnosticsAndOpenDialog = createAction(
|
||||
`${SYSTEM_DIAGNOSTICS_PREFIX} Get System Diagnostics and Open Dialog`,
|
||||
props<{ request: SystemDiagnosticsRequest }>()
|
||||
);
|
||||
|
||||
export const openSystemDiagnosticsDialog = createAction(`${SYSTEM_DIAGNOSTICS_PREFIX} Open System Diagnostics Dialog`);
|
||||
|
||||
export const systemDiagnosticsApiError = createAction(
|
||||
`${SYSTEM_DIAGNOSTICS_PREFIX} Load System Diagnostics Error`,
|
||||
props<{ error: string }>()
|
||||
);
|
||||
|
||||
export const resetSystemDiagnostics = createAction(`${SYSTEM_DIAGNOSTICS_PREFIX} Clear System Diagnostics`);
|
||||
|
||||
export const viewSystemDiagnosticsComplete = createAction(
|
||||
`${SYSTEM_DIAGNOSTICS_PREFIX} View System Diagnostics Complete`
|
||||
);
|
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { act, Actions, createEffect, ofType } from '@ngrx/effects';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { NiFiState } from '../index';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { SystemDiagnosticsService } from '../../service/system-diagnostics.service';
|
||||
import * as SystemDiagnosticsActions from './system-diagnostics.actions';
|
||||
import { catchError, from, map, of, switchMap, tap } from 'rxjs';
|
||||
import { SystemDiagnosticsRequest } from './index';
|
||||
import { SystemDiagnosticsDialog } from '../../ui/common/system-diagnostics-dialog/system-diagnostics-dialog.component';
|
||||
|
||||
@Injectable()
|
||||
export class SystemDiagnosticsEffects {
|
||||
constructor(
|
||||
private actions$: Actions,
|
||||
private store: Store<NiFiState>,
|
||||
private systemDiagnosticsService: SystemDiagnosticsService,
|
||||
private dialog: MatDialog
|
||||
) {}
|
||||
|
||||
reloadSystemDiagnostics$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(SystemDiagnosticsActions.reloadSystemDiagnostics),
|
||||
map((action) => action.request),
|
||||
switchMap((request: SystemDiagnosticsRequest) =>
|
||||
from(this.systemDiagnosticsService.getSystemDiagnostics(request.nodewise)).pipe(
|
||||
map((response: any) =>
|
||||
SystemDiagnosticsActions.reloadSystemDiagnosticsSuccess({
|
||||
response: {
|
||||
systemDiagnostics: response.systemDiagnostics
|
||||
}
|
||||
})
|
||||
),
|
||||
catchError((error) =>
|
||||
of(
|
||||
SystemDiagnosticsActions.systemDiagnosticsApiError({
|
||||
error: error.error
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
getSystemDiagnosticsAndOpenDialog$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(SystemDiagnosticsActions.getSystemDiagnosticsAndOpenDialog),
|
||||
map((action) => action.request),
|
||||
switchMap((request) =>
|
||||
from(this.systemDiagnosticsService.getSystemDiagnostics(request.nodewise)).pipe(
|
||||
map((response: any) =>
|
||||
SystemDiagnosticsActions.loadSystemDiagnosticsSuccess({
|
||||
response: {
|
||||
systemDiagnostics: response.systemDiagnostics
|
||||
}
|
||||
})
|
||||
),
|
||||
catchError((error) =>
|
||||
of(
|
||||
SystemDiagnosticsActions.systemDiagnosticsApiError({
|
||||
error: error.error
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
loadSystemDiagnosticsSuccess$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(SystemDiagnosticsActions.loadSystemDiagnosticsSuccess),
|
||||
switchMap(() => of(SystemDiagnosticsActions.openSystemDiagnosticsDialog()))
|
||||
)
|
||||
);
|
||||
|
||||
openSystemDiagnosticsDialog$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType(SystemDiagnosticsActions.openSystemDiagnosticsDialog),
|
||||
tap(() => {
|
||||
this.dialog
|
||||
.open(SystemDiagnosticsDialog, { panelClass: 'large-dialog' })
|
||||
.afterClosed()
|
||||
.subscribe(() => {
|
||||
this.store.dispatch(SystemDiagnosticsActions.viewSystemDiagnosticsComplete());
|
||||
});
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { SystemDiagnosticsState } from './index';
|
||||
import { createReducer, on } from '@ngrx/store';
|
||||
import {
|
||||
reloadSystemDiagnostics,
|
||||
loadSystemDiagnosticsSuccess,
|
||||
resetSystemDiagnostics,
|
||||
systemDiagnosticsApiError,
|
||||
viewSystemDiagnosticsComplete,
|
||||
getSystemDiagnosticsAndOpenDialog,
|
||||
reloadSystemDiagnosticsSuccess
|
||||
} from './system-diagnostics.actions';
|
||||
|
||||
export const initialSystemDiagnosticsState: SystemDiagnosticsState = {
|
||||
systemDiagnostics: null,
|
||||
status: 'pending',
|
||||
error: null,
|
||||
loadedTimestamp: ''
|
||||
};
|
||||
|
||||
export const systemDiagnosticsReducer = createReducer(
|
||||
initialSystemDiagnosticsState,
|
||||
|
||||
on(reloadSystemDiagnostics, getSystemDiagnosticsAndOpenDialog, (state) => ({
|
||||
...state,
|
||||
status: 'loading' as const
|
||||
})),
|
||||
|
||||
on(loadSystemDiagnosticsSuccess, reloadSystemDiagnosticsSuccess, (state, { response }) => ({
|
||||
...state,
|
||||
error: null,
|
||||
status: 'success' as const,
|
||||
loadedTimestamp: response.systemDiagnostics.aggregateSnapshot.statsLastRefreshed,
|
||||
systemDiagnostics: response.systemDiagnostics
|
||||
})),
|
||||
|
||||
on(systemDiagnosticsApiError, (state, { error }) => ({
|
||||
...state,
|
||||
error,
|
||||
status: 'error' as const
|
||||
})),
|
||||
|
||||
on(resetSystemDiagnostics, (state) => ({
|
||||
...initialSystemDiagnosticsState
|
||||
})),
|
||||
|
||||
on(viewSystemDiagnosticsComplete, (state) => ({
|
||||
...initialSystemDiagnosticsState
|
||||
}))
|
||||
);
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createFeatureSelector, createSelector } from '@ngrx/store';
|
||||
import { systemDiagnosticsFeatureKey, SystemDiagnosticsState } from './index';
|
||||
|
||||
export const selectSystemDiagnosticsState = createFeatureSelector<SystemDiagnosticsState>(systemDiagnosticsFeatureKey);
|
||||
|
||||
export const selectSystemDiagnostics = createSelector(
|
||||
selectSystemDiagnosticsState,
|
||||
(state: SystemDiagnosticsState) => state.systemDiagnostics
|
||||
);
|
||||
|
||||
export const selectSystemDiagnosticsLoadedTimestamp = createSelector(
|
||||
selectSystemDiagnosticsState,
|
||||
(state: SystemDiagnosticsState) => state.loadedTimestamp
|
||||
);
|
||||
|
||||
export const selectSystemDiagnosticsError = createSelector(
|
||||
selectSystemDiagnosticsState,
|
||||
(state: SystemDiagnosticsState) => state.error
|
||||
);
|
||||
|
||||
export const selectSystemDiagnosticsStatus = createSelector(
|
||||
selectSystemDiagnosticsState,
|
||||
(state: SystemDiagnosticsState) => state.status
|
||||
);
|
@ -621,13 +621,14 @@ export class StatusHistoryChart {
|
||||
const marginTop: any = controlContainer.computedStyleMap().get('margin-top');
|
||||
const statusHistory = document.getElementsByClassName('status-history')![0];
|
||||
const dialogContent = statusHistory.getElementsByClassName('dialog-content')![0];
|
||||
const descriptorContainer = document.getElementsByClassName('selected-descriptor-container')![0];
|
||||
const dialogStyles: any = dialogContent.computedStyleMap();
|
||||
const bodyHeight = document.body.getBoundingClientRect().height;
|
||||
|
||||
return (
|
||||
bodyHeight -
|
||||
controlContainer.clientHeight -
|
||||
50 -
|
||||
descriptorContainer.clientHeight -
|
||||
parseInt(marginTop.value, 10) -
|
||||
parseInt(dialogStyles.get('top')?.value) -
|
||||
parseInt(dialogStyles.get('bottom')?.value)
|
||||
|
@ -35,12 +35,12 @@
|
||||
*ngIf="componentDetails$ | async; let componentDetails"
|
||||
class="flex flex-1 w-full gap-x-4">
|
||||
<div class="component-details flex flex-col gap-y-3">
|
||||
<div
|
||||
*ngFor="let entry of Object.entries(componentDetails)"
|
||||
class="flex flex-col">
|
||||
<div>{{ entry[0] }}</div>
|
||||
<div class="value">{{ entry[1] }}</div>
|
||||
</div>
|
||||
<ng-container *ngFor="let entry of Object.entries(componentDetails)">
|
||||
<div *ngIf="entry[0] && entry[1]" class="flex flex-col">
|
||||
<div>{{ entry[0] }}</div>
|
||||
<div class="value">{{ entry[1] }}</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<div class="flex flex-col">
|
||||
<div>Start</div>
|
||||
<div class="value">{{ minDate }}</div>
|
||||
@ -89,7 +89,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart-panel grow flex flex-col">
|
||||
<div *ngIf="fieldDescriptors$ | async">
|
||||
<div class="selected-descriptor-container" *ngIf="fieldDescriptors$ | async">
|
||||
<mat-form-field>
|
||||
<mat-select formControlName="fieldDescriptor">
|
||||
<ng-container *ngFor="let descriptor of fieldDescriptors">
|
||||
|
@ -65,6 +65,10 @@
|
||||
.mat-mdc-dialog-content {
|
||||
max-height: unset;
|
||||
}
|
||||
|
||||
.selected-descriptor-container {
|
||||
height: 68px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ import {
|
||||
StatusHistoryState
|
||||
} from '../../../state/status-history';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { loadStatusHistory } from '../../../state/status-history/status-history.actions';
|
||||
import { reloadStatusHistory } from '../../../state/status-history/status-history.actions';
|
||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||
import {
|
||||
selectStatusHistory,
|
||||
@ -115,8 +115,6 @@ export class StatusHistory implements OnInit, AfterViewInit {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.refresh();
|
||||
|
||||
this.statusHistory$.pipe(filter((entity) => !!entity)).subscribe((entity: StatusHistoryEntity) => {
|
||||
if (entity) {
|
||||
this.instances = [];
|
||||
@ -171,9 +169,6 @@ export class StatusHistory implements OnInit, AfterViewInit {
|
||||
this.maxDate = this.nifiCommon.formatDateTime(new Date(maxDate));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.fieldDescriptors$
|
||||
.pipe(
|
||||
filter((descriptors) => !!descriptors),
|
||||
@ -184,8 +179,11 @@ export class StatusHistory implements OnInit, AfterViewInit {
|
||||
|
||||
// select the first field description by default
|
||||
this.statusHistoryForm.get('fieldDescriptor')?.setValue(descriptors[0]);
|
||||
this.selectedDescriptor = descriptors[0];
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
// when the selected descriptor changes, update the chart
|
||||
this.statusHistoryForm.get('fieldDescriptor')?.valueChanges.subscribe((descriptor: FieldDescriptor) => {
|
||||
if (this.instances.length > 0) {
|
||||
@ -199,7 +197,7 @@ export class StatusHistory implements OnInit, AfterViewInit {
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.store.dispatch(loadStatusHistory({ request: this.request }));
|
||||
this.store.dispatch(reloadStatusHistory({ request: this.request }));
|
||||
}
|
||||
|
||||
getSelectOptionTipData(descriptor: FieldDescriptor): TextTipInput {
|
||||
|
@ -0,0 +1,264 @@
|
||||
<!--
|
||||
~ Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
~ contributor license agreements. See the NOTICE file distributed with
|
||||
~ this work for additional information regarding copyright ownership.
|
||||
~ The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
~ (the "License"); you may not use this file except in compliance with
|
||||
~ the License. You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<ng-container *ngIf="(systemDiagnostics$ | async)?.aggregateSnapshot; let systemDiagnostics">
|
||||
<h2 mat-dialog-title>System Diagnostics</h2>
|
||||
<div class="system-diagnostics">
|
||||
<mat-dialog-content>
|
||||
<div class="dialog-content">
|
||||
<mat-tab-group>
|
||||
<mat-tab label="JVM">
|
||||
<div class="tab-content py-4 h-full w-full">
|
||||
<div class="inset-0 flex gap-y-4">
|
||||
<div class="flex flex-col flex-1 gap-y-4">
|
||||
<section>
|
||||
<div class="section-header">Heap ({{ systemDiagnostics.heapUtilization }})</div>
|
||||
|
||||
<div class="flex flex-col gap-y-3">
|
||||
<div class="flex flex-col">
|
||||
<div>Max</div>
|
||||
<div class="value">{{ systemDiagnostics.maxHeap }}</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<div>Total</div>
|
||||
<div class="value">{{ systemDiagnostics.totalHeap }}</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<div>Used</div>
|
||||
<div class="value">{{ systemDiagnostics.usedHeap }}</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<div>Free</div>
|
||||
<div class="value">{{ systemDiagnostics.freeHeap }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="section-header">Garbage Collection</div>
|
||||
|
||||
<div class="flex flex-col gap-y-3" *ngIf="sortedGarbageCollections">
|
||||
<div class="flex flex-col" *ngFor="let gc of sortedGarbageCollections">
|
||||
<div>{{ gc.name }}</div>
|
||||
<div class="value">
|
||||
{{ gc.collectionCount }} times ({{ gc.collectionTime }})
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col flex-1 gap-y-4">
|
||||
<section>
|
||||
<div class="section-header">Non Heap</div>
|
||||
|
||||
<div class="flex flex-col gap-y-3">
|
||||
<div class="flex flex-col">
|
||||
<div>Max</div>
|
||||
<div class="value">{{ systemDiagnostics.maxNonHeap }}</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<div>Total</div>
|
||||
<div class="value">{{ systemDiagnostics.totalNonHeap }}</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<div>Used</div>
|
||||
<div class="value">{{ systemDiagnostics.usedNonHeap }}</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<div>Free</div>
|
||||
<div class="value">{{ systemDiagnostics.freeNonHeap }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="section-header">Runtime</div>
|
||||
|
||||
<div class="flex flex-col gap-y-3">
|
||||
<div class="flex flex-col">
|
||||
<div>Uptime</div>
|
||||
<div class="value">{{ systemDiagnostics.uptime }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
<mat-tab label="System">
|
||||
<div class="tab-content py-4 gap-y-6 h-full w-full flex flex-col">
|
||||
<div class="flex">
|
||||
<div class="flex flex-col flex-1 gap-y-4">
|
||||
<div class="flex flex-col gap-y-3">
|
||||
<div class="flex flex-col">
|
||||
<div>Available Cores</div>
|
||||
<div class="value">{{ systemDiagnostics.availableProcessors }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col flex-1 gap-y-4">
|
||||
<div class="flex flex-col gap-y-3">
|
||||
<div class="flex flex-col">
|
||||
<div class="flex gap-x-3 items-center">
|
||||
<div>Core Load Average</div>
|
||||
<div
|
||||
class="fa fa-question-circle"
|
||||
nifiTooltip
|
||||
[tooltipComponentType]="TextTip"
|
||||
[tooltipInputData]="getCoreLoadTooltip()"></div>
|
||||
</div>
|
||||
<div class="value">
|
||||
{{ formatFloat(systemDiagnostics.processorLoadAverage) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="flex flex-col pr-4">
|
||||
<div class="section-header">FlowFile Repository Usage</div>
|
||||
<div>
|
||||
<div class="capitalize">Usage:</div>
|
||||
<mat-progress-bar
|
||||
mode="determinate"
|
||||
[value]="
|
||||
getRepositoryStorageUsagePercent(
|
||||
systemDiagnostics.flowFileRepositoryStorageUsage
|
||||
)
|
||||
">
|
||||
</mat-progress-bar>
|
||||
<div class="value">
|
||||
{{ systemDiagnostics.flowFileRepositoryStorageUsage.utilization }}
|
||||
({{ systemDiagnostics.flowFileRepositoryStorageUsage.usedSpace }}
|
||||
of
|
||||
{{ systemDiagnostics.flowFileRepositoryStorageUsage.totalSpace }})
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="flex flex-col pr-4">
|
||||
<div class="section-header">Content Repository Usage</div>
|
||||
<div class="repository-storage-container flex flex-col gap-y-2">
|
||||
<div *ngFor="let repo of systemDiagnostics.contentRepositoryStorageUsage">
|
||||
<div class="capitalize">Usage for {{ repo.identifier }}:</div>
|
||||
<mat-progress-bar
|
||||
mode="determinate"
|
||||
[value]="getRepositoryStorageUsagePercent(repo)">
|
||||
</mat-progress-bar>
|
||||
<div class="value">
|
||||
{{ repo.utilization }} ({{ repo.usedSpace }} of {{ repo.totalSpace }})
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="flex flex-col pr-4">
|
||||
<div class="section-header">Provenance Repository Usage</div>
|
||||
<div class="repository-storage-container flex flex-col gap-y-2">
|
||||
<div *ngFor="let repo of systemDiagnostics.provenanceRepositoryStorageUsage">
|
||||
<div class="capitalize">Usage for {{ repo.identifier }}:</div>
|
||||
<mat-progress-bar
|
||||
mode="determinate"
|
||||
[value]="getRepositoryStorageUsagePercent(repo)">
|
||||
</mat-progress-bar>
|
||||
<div class="value">
|
||||
{{ repo.utilization }} ({{ repo.usedSpace }} of {{ repo.totalSpace }})
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</mat-tab>
|
||||
<mat-tab label="Version">
|
||||
<div class="tab-content py-4 h-full w-full">
|
||||
<div class="inset-0 flex flex-col gap-y-4">
|
||||
<section>
|
||||
<div class="section-header">NiFi</div>
|
||||
|
||||
<dl class="setting-attributes-list">
|
||||
<dt>NiFi Version</dt>
|
||||
<dd>{{ systemDiagnostics.versionInfo.niFiVersion }}</dd>
|
||||
|
||||
<dt>Tag</dt>
|
||||
<dd>{{ systemDiagnostics.versionInfo.buildTag }}</dd>
|
||||
|
||||
<dt>Build Date/Time</dt>
|
||||
<dd>{{ systemDiagnostics.versionInfo.buildTimestamp }}</dd>
|
||||
|
||||
<dt>Branch</dt>
|
||||
<dd>{{ systemDiagnostics.versionInfo.buildBranch }}</dd>
|
||||
|
||||
<dt>Revision</dt>
|
||||
<dd>{{ systemDiagnostics.versionInfo.buildRevision }}</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="section-header">Java</div>
|
||||
|
||||
<dl class="setting-attributes-list">
|
||||
<dt>Version</dt>
|
||||
<dd>{{ systemDiagnostics.versionInfo.javaVersion }}</dd>
|
||||
|
||||
<dt>Vendor</dt>
|
||||
<dd>{{ systemDiagnostics.versionInfo.javaVendor }}</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="section-header">Operating System</div>
|
||||
|
||||
<dl class="setting-attributes-list">
|
||||
<dt>Name</dt>
|
||||
<dd>{{ systemDiagnostics.versionInfo.osName }}</dd>
|
||||
|
||||
<dt>Version</dt>
|
||||
<dd>{{ systemDiagnostics.versionInfo.osVersion }}</dd>
|
||||
|
||||
<dt>Architecture</dt>
|
||||
<dd>{{ systemDiagnostics.versionInfo.osArchitecture }}</dd>
|
||||
</dl>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
<div class="flex flex-1 justify-between">
|
||||
<div class="refresh-container flex items-center gap-x-2">
|
||||
<button class="nifi-button" (click)="refreshSystemDiagnostics()">
|
||||
<i class="fa fa-refresh" [class.fa-spin]="(status$ | async) === 'loading'"></i>
|
||||
</button>
|
||||
<div>Last updated:</div>
|
||||
<div class="refresh-timestamp">{{ loadedTimestamp$ | async }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<button color="primary" mat-raised-button mat-dialog-close>Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</mat-dialog-actions>
|
||||
</div>
|
||||
</ng-container>
|
@ -0,0 +1,67 @@
|
||||
/*!
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@use '@angular/material' as mat;
|
||||
|
||||
.system-diagnostics {
|
||||
@include mat.button-density(-1);
|
||||
overflow-y: auto;
|
||||
|
||||
.mdc-dialog__content {
|
||||
padding: 0 16px;
|
||||
font-size: 14px;
|
||||
|
||||
.dialog-content {
|
||||
min-height: 500px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
position: relative;
|
||||
height: 480px;
|
||||
|
||||
.section-header {
|
||||
color: #728e9b;
|
||||
font-size: 15px;
|
||||
font-family: 'Roboto Slab';
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.setting-attributes-list {
|
||||
dt {
|
||||
float: left;
|
||||
clear: left;
|
||||
padding: 0 0.5em 0.2em 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
dd {
|
||||
margin-left: 9em;
|
||||
padding-bottom: 0.2em;
|
||||
}
|
||||
}
|
||||
|
||||
.mat-mdc-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
mat-dialog-actions {
|
||||
margin-top: auto;
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SystemDiagnosticsDialog } from './system-diagnostics-dialog.component';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { initialSystemDiagnosticsState } from '../../../state/system-diagnostics/system-diagnostics.reducer';
|
||||
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
|
||||
describe('SystemDiagnosticsDialog', () => {
|
||||
let component: SystemDiagnosticsDialog;
|
||||
let fixture: ComponentFixture<SystemDiagnosticsDialog>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [SystemDiagnosticsDialog],
|
||||
providers: [
|
||||
{ provide: MAT_DIALOG_DATA, useValue: {} },
|
||||
provideMockStore({ initialState: initialSystemDiagnosticsState })
|
||||
]
|
||||
});
|
||||
fixture = TestBed.createComponent(SystemDiagnosticsDialog);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, Inject, OnInit, signal } from '@angular/core';
|
||||
import { CommonModule, NgForOf } from '@angular/common';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||
import {
|
||||
GarbageCollection,
|
||||
OpenSystemDiagnosticsDialogRequest,
|
||||
RepositoryStorageUsage,
|
||||
SystemDiagnosticsState
|
||||
} from '../../../state/system-diagnostics';
|
||||
import { Store } from '@ngrx/store';
|
||||
import {
|
||||
selectSystemDiagnostics,
|
||||
selectSystemDiagnosticsLoadedTimestamp,
|
||||
selectSystemDiagnosticsStatus
|
||||
} from '../../../state/system-diagnostics/system-diagnostics.selectors';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { reloadSystemDiagnostics } from '../../../state/system-diagnostics/system-diagnostics.actions';
|
||||
import { NiFiCommon } from '../../../service/nifi-common.service';
|
||||
import { filter } from 'rxjs';
|
||||
import { TextTip } from '../tooltips/text-tip/text-tip.component';
|
||||
import { NifiTooltipDirective } from '../tooltips/nifi-tooltip.directive';
|
||||
import { TextTipInput } from '../../../state/shared';
|
||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||
|
||||
@Component({
|
||||
selector: 'system-diagnostics-dialog',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatTabsModule,
|
||||
MatDialogModule,
|
||||
MatButtonModule,
|
||||
NgForOf,
|
||||
NifiTooltipDirective,
|
||||
MatProgressBarModule
|
||||
],
|
||||
templateUrl: './system-diagnostics-dialog.component.html',
|
||||
styleUrls: ['./system-diagnostics-dialog.component.scss']
|
||||
})
|
||||
export class SystemDiagnosticsDialog implements OnInit {
|
||||
systemDiagnostics$ = this.store.select(selectSystemDiagnostics);
|
||||
loadedTimestamp$ = this.store.select(selectSystemDiagnosticsLoadedTimestamp);
|
||||
status$ = this.store.select(selectSystemDiagnosticsStatus);
|
||||
sortedGarbageCollections: GarbageCollection[] | null = null;
|
||||
|
||||
constructor(
|
||||
private store: Store<SystemDiagnosticsState>,
|
||||
private nifiCommon: NiFiCommon,
|
||||
@Inject(MAT_DIALOG_DATA) public request: OpenSystemDiagnosticsDialogRequest
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.systemDiagnostics$.pipe(filter((diagnostics) => !!diagnostics)).subscribe((diagnostics) => {
|
||||
const sorted = diagnostics!.aggregateSnapshot.garbageCollection.slice();
|
||||
sorted.sort((a, b) => {
|
||||
return this.nifiCommon.compareString(a.name, b.name);
|
||||
});
|
||||
this.sortedGarbageCollections = sorted;
|
||||
});
|
||||
}
|
||||
|
||||
refreshSystemDiagnostics() {
|
||||
this.store.dispatch(
|
||||
reloadSystemDiagnostics({
|
||||
request: {
|
||||
nodewise: false
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
formatFloat(value: number): string {
|
||||
return this.nifiCommon.formatFloat(value);
|
||||
}
|
||||
|
||||
getCoreLoadTooltip(): TextTipInput {
|
||||
return {
|
||||
text: 'Core load average for the last minute. Not available on all platforms.'
|
||||
};
|
||||
}
|
||||
|
||||
getRepositoryStorageUsagePercent(repoStorage: RepositoryStorageUsage): number {
|
||||
return (repoStorage.usedSpaceBytes / repoStorage.totalSpaceBytes) * 100;
|
||||
}
|
||||
|
||||
protected readonly TextTip = TextTip;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user